Commit 3af699a6 by PLN (Algolia)

feat(Parvagues): refact with claude

parent a87618c6
import { useState } from 'react';
import Image from 'next/image';
import Masonry from 'react-masonry-css';
export default function ImageGallery({ images, slug }) {
const [selectedImage, setSelectedImage] = useState(null);
const breakpointColumns = {
default: 3,
768: 2,
480: 1
};
return (
<>
<div className="mt-8">
<h3 className="text-xl font-semibold mb-4 text-purple-400">Galerie</h3>
<Masonry
breakpointCols={breakpointColumns}
className="masonry-grid"
columnClassName="masonry-grid_column"
>
{images.map((image, i) => (
<div
key={i}
className="mb-4 cursor-pointer hover:opacity-75 transition-opacity"
onClick={() => setSelectedImage(image)}
>
<div className="relative rounded-lg overflow-hidden">
<Image
src={image}
alt={`${slug} image ${i + 1}`}
width={300}
height={300}
className="w-full h-auto object-cover"
/>
</div>
</div>
))}
</Masonry>
</div>
{/* Lightbox */}
{selectedImage && (
<div
className="fixed inset-0 z-50 bg-black/90 flex items-center justify-center cursor-zoom-out"
onClick={() => setSelectedImage(null)}
>
<div className="relative max-w-[90vw] max-h-[90vh]">
<Image
src={selectedImage}
alt="Selected image"
width={1200}
height={800}
className="max-w-full max-h-full object-contain"
/>
<button
className="absolute top-4 right-4 text-white text-2xl hover:text-purple-400 transition-colors"
onClick={(e) => {
e.stopPropagation();
setSelectedImage(null);
}}
>
×
</button>
</div>
</div>
)}
</>
);
}
---
title: "AlgoRave Lyon 2025"
date: "2025-XX-XX"
time: "XX:XX"
location: "Venue TBD"
address: "Lyon, France"
description: "Live algorithmic performance"
ctaURL: "https://nech.pl/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: "LIVE CODING @ENSAD"
date: "2025-05-22"
time: "18:30-20:00"
location: "ENSAD Paris"
address: "20 cours Saint Vincent, Issy"
description: "breakbeat.nujazz.hybrid()"
ctaURL: "https://nech.pl/ensad"
ctaText: "En savoir plus"
teasing1: |
# Session en préparation...
```tidal
d1 $ s "bazart:22.05" # orbit 8
d2 $ cookies # "collab" # gain 1.2
```
LIVE CODING @ensad.paris
22.05 > 18:30-20h
BAZART // cookie collective
w/ @bubobubo @azertype @cyber_flemme
@z0rg_ @neon_delice @incontinentlab
teasing2: |
# SNEAK PEEK // BAZART '25
```tidal
d8 $ s "break:unreleased" # cut 1
d4 $ s "nujazz:rhodes" # lpf 1800
```
20h30 session incoming
20 cours Saint Vincent
Jeudi 22 mai
sound on 🔊
teasing3: |
# Session tonight!
```tidal
d9 $ s "bazart:tonight"
d1 $ stack [sound "cp", sound "ho:3"]
```
18:30 live @ ENSAD
nech.pl/ensad for details
video: ""
audio: ""
archive: ""
tags: ["livecoding", "bazart", "ensad", "nujazz", "breakbeat"]
---
# BAZART @ ENSAD
Session live coding breakbeat/nujazz au sein de l'exposition BAZART à l'ENSAD Paris.
Une exploration sonore à la croisée du breakbeat et du nujazz, avec un setup live coding TidalCycles.
## Programme
- 18:30 : Accueil
- 19:00 : Live coding session
- 20:00 : Échanges
## Collectif Cookie
Performance réalisée dans le cadre du collectif Cookie, avec la participation de plusieurs artistes de la scène algorave parisienne.
# ParVagues Live Events System
## Quick Start
1. **Install dependencies**:
```bash
npm install marked prismjs react-masonry-css
```
2. **Create a new event**:
```bash
# Create markdown file in appropriate year directory
# Follow the _template.md format
cp content/lives/_template.md content/lives/2025/my-event-YYYY-MM-DD.md
```
3. **Add images**:
```bash
# Create directory matching event slug
mkdir -p public/images/parvagues/lives/2025/my-event-YYYY-MM-DD/
# Add jpg/png/gif/webp files to this directory
```
4. **Test locally**:
```bash
npm run dev
# Visit: http://localhost:3000/parvagues
# Check: http://localhost:3000/parvagues/live/my-event-YYYY-MM-DD
```
## How it works
### Event Lifecycle
1. **Pre-event**: Shows countdown with teasings based on days remaining
- J-14+: teasing1
- J-7 to J-3: teasing2
- J-3 to event: teasing3
2. **Post-event**: Shows full content with:
- Video/audio links (if provided)
- Image gallery (automatic)
- Collapsible teasings (for nostalgia)
### Event Metadata Structure
```yaml
---
title: "Event Title"
date: "2025-05-22"
time: "18:30-20:00"
location: "Venue Name"
address: "Full Address"
description: "Brief description"
ctaURL: "https://..."
ctaText: "RSVP"
# ... teasings, video, audio etc.
---
```
### File Structure
```
content/lives/
├── 2025/
│ ├── ensad-2025-05-22.md
│ └── algorave-lyon-2025-XX-XX.md
└── 2022/
└── (historical events)
public/images/parvagues/lives/
├── 2025/
│ ├── ensad-2025-05-22/
│ │ ├── photo1.jpg
│ │ └── photo2.jpg
│ └── algorave-lyon-2025-XX-XX/
└── 2022/
└── (historical images)
```
## URL Structure
- Landing: `/parvagues`
- Event page: `/parvagues/live/[slug]`
- Slug format: `event-name-YYYY-MM-DD`
## URL Shortener Integration
Create short URLs for events:
- `nech.pl/ensad``/parvagues/live/ensad-2025-05-22`
- `nech.pl/lyon``/parvagues/live/algorave-lyon-2025-XX-XX`
## Development Notes
- Page regenerates every minute for countdown accuracy
- Images are loaded dynamically from filesystem
- Support markdown in all text fields
- Code blocks use Haskell syntax highlighting
---
title: "Event Title"
date: "2025-XX-XX"
time: "XX:XX"
location: "Location Name"
address: "Full Address"
description: "Brief event description"
ctaURL: "https://nech.pl/shortlink"
ctaText: "RSVP / TICKETS"
teasing1: |
# Teasing 1 - J-14
Markdown content with code blocks, images, etc.
```tidal
d1 $ sound "cp"
```
teasing2: |
# Teasing 2 - J-7
Another teasing markdown content
teasing3: |
# Teasing 3 - J-3
Final teasing content
video: ""
audio: ""
archive: ""
tags: ["livecoding", "algorave", "nujazz"]
---
# Event Description
Main event content goes here...
# ParVagues Site Upgrade - Implementation Checklist
## Phase 1: File Structure Setup
- [x] Create `/content/lives/` directory
- [x] Create `/content/lives/2025/` subdirectory
- [x] Create `/content/lives/2022/` subdirectory
- [x] Create `/content/lives/_template.md`
- [x] Create event metadata files:
- [x] `/content/lives/2025/ensad-2025-05-22.md`
- [x] `/content/lives/2025/algorave-lyon-2025-XX-XX.md`
- [x] Create image directories:
- [x] `/public/images/parvagues/live-placeholder.jpg`
- [x] `/public/images/parvagues/code-sample-1.png`
- [x] `/public/images/parvagues/setup-placeholder.jpg`
- [x] `/public/images/parvagues/lives/2025/ensad-2025-05-22/`
- [x] `/public/images/parvagues/lives/2025/algorave-lyon-2025-XX-XX/`
## Phase 2: Dynamic Live Page Implementation
- [x] Create `/pages/parvagues/live/[id].js`
- [x] Implement getStaticPaths to read lives directory
- [x] Implement getStaticProps to load markdown metadata
- [x] Create countdown component
- [x] Create teasing display logic
- [x] Create post-event archive layout
- [x] Add collapsible teasing section
- [x] Implement gallery component (masonry layout)
- [ ] Add responsive design
## Phase 3: Landing Page Redesign
- [x] Backup existing `/pages/parvagues.js`
- [x] Create new hero section with cyberpunk theme
- [x] Add code sample section with highlight.js
- [x] Implement live events sidebar/list
- [x] Add dark theme with purple/pink gradients
- [x] Add subtle rain/glitch effects
- [ ] Create responsive layout
- [ ] Add scroll animations
## Phase 4: Content Creation
- [x] Create ENSAD event metadata
- [x] Create AlgoRave Lyon metadata (placeholder date)
- [x] Write teasing content for both events
- [x] Add placeholder images with descriptive names
- [x] Create sample TidalCycles code snippets
## Phase 5: Testing & Verification
- [ ] Test countdown functionality
- [ ] Test gallery display
- [ ] Test responsive design
- [ ] Verify markdown rendering
- [ ] Test live page routing
- [ ] Verify image loading
- [ ] Test date logic for pre/post event states
## Phase 6: Polish & Deployment
- [ ] Add accessibility attributes
- [ ] Optimize image loading
- [ ] Add loading states
- [ ] Test SEO metadata
- [ ] Verify all links work
- [ ] Final visual polish
## Dependencies to Install
- [x] marked (for markdown rendering)
- [x] prismjs (for syntax highlighting)
- [x] react-masonry-css (for gallery layout)
- [ ] NOTE: Run `npm install marked prismjs react-masonry-css`
---
*Last updated: May 11, 2025*
# Dependencies to Install
These packages need to be installed for the ParVagues site upgrade:
```bash
npm install marked prismjs react-masonry-css
```
## New dependencies:
- **marked**: For markdown rendering
- **prismjs**: For syntax highlighting
- **react-masonry-css**: For gallery layout
## Already installed:
- **gray-matter**: For frontmatter parsing ✓
- **next**: For the Next.js framework ✓
- **react**: For React components ✓
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
const livesDirectory = path.join(process.cwd(), 'content/lives');
export function getAllLives() {
const lives = [];
// Read all years
const years = fs.readdirSync(livesDirectory).filter(item =>
fs.statSync(path.join(livesDirectory, item)).isDirectory()
);
years.forEach(year => {
const yearPath = path.join(livesDirectory, year);
const yearFiles = fs.readdirSync(yearPath);
yearFiles.forEach(fileName => {
if (fileName.endsWith('.md')) {
const slug = fileName.replace(/\.md$/, '');
const fullPath = path.join(yearPath, fileName);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data } = matter(fileContents);
lives.push({
slug,
year,
...data,
});
}
});
});
// Sort by date, most recent first
return lives.sort((a, b) => new Date(b.date) - new Date(a.date));
}
export async function getLiveData(slug) {
// Find the file across all year directories
const years = fs.readdirSync(livesDirectory).filter(item =>
fs.statSync(path.join(livesDirectory, item)).isDirectory()
);
for (const year of years) {
const filePath = path.join(livesDirectory, year, `${slug}.md`);
if (fs.existsSync(filePath)) {
const fileContents = fs.readFileSync(filePath, 'utf8');
const { data, content } = matter(fileContents);
return {
slug,
year,
frontmatter: data,
content,
};
}
}
throw new Error(`Live with slug "${slug}" not found`);
}
export function getLivesImages(slug) {
const years = fs.readdirSync(livesDirectory).filter(item =>
fs.statSync(path.join(livesDirectory, item)).isDirectory()
);
for (const year of years) {
const imagesPath = path.join(process.cwd(), 'public/images/parvagues/lives', year, slug);
if (fs.existsSync(imagesPath)) {
const files = fs.readdirSync(imagesPath);
return files
.filter(file => /\.(jpg|jpeg|png|gif|webp)$/i.test(file))
.map(file => `/images/parvagues/lives/${year}/${slug}/${file}`);
}
}
return [];
}
...@@ -13,11 +13,14 @@ ...@@ -13,11 +13,14 @@
"date-fns": "^3.3.1", "date-fns": "^3.3.1",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"hydra-synth": "^1.3.29", "hydra-synth": "^1.3.29",
"marked": "^15.0.11",
"next": "^15.3.0", "next": "^15.3.0",
"prismjs": "^1.30.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-instantsearch": "^7.15.7", "react-instantsearch": "^7.15.7",
"react-instantsearch-dom": "^6.40.4", "react-instantsearch-dom": "^6.40.4",
"react-masonry-css": "^1.0.16",
"react-player": "^2.14.1", "react-player": "^2.14.1",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"remark": "^14.0.0", "remark": "^14.0.0",
...@@ -1904,6 +1907,18 @@ ...@@ -1904,6 +1907,18 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/marked": {
"version": "15.0.11",
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.11.tgz",
"integrity": "sha512-1BEXAU2euRCG3xwgLVT1y0xbJEld1XOrmRJpUwRCcy7rxhSCwMrmEu9LXoPhHSCJG41V7YcQ2mjKRr5BA3ITIA==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/mdast-util-definitions": { "node_modules/mdast-util-definitions": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz",
...@@ -2677,9 +2692,9 @@ ...@@ -2677,9 +2692,9 @@
} }
}, },
"node_modules/prismjs": { "node_modules/prismjs": {
"version": "1.29.0", "version": "1.30.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
...@@ -2858,6 +2873,15 @@ ...@@ -2858,6 +2873,15 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-masonry-css": {
"version": "1.0.16",
"resolved": "https://registry.npmjs.org/react-masonry-css/-/react-masonry-css-1.0.16.tgz",
"integrity": "sha512-KSW0hR2VQmltt/qAa3eXOctQDyOu7+ZBevtKgpNDSzT7k5LA/0XntNa9z9HKCdz3QlxmJHglTZ18e4sX4V8zZQ==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.0.0"
}
},
"node_modules/react-player": { "node_modules/react-player": {
"version": "2.16.0", "version": "2.16.0",
"resolved": "https://registry.npmjs.org/react-player/-/react-player-2.16.0.tgz", "resolved": "https://registry.npmjs.org/react-player/-/react-player-2.16.0.tgz",
......
...@@ -16,11 +16,14 @@ ...@@ -16,11 +16,14 @@
"date-fns": "^3.3.1", "date-fns": "^3.3.1",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"hydra-synth": "^1.3.29", "hydra-synth": "^1.3.29",
"marked": "^15.0.11",
"next": "^15.3.0", "next": "^15.3.0",
"prismjs": "^1.30.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-instantsearch": "^7.15.7", "react-instantsearch": "^7.15.7",
"react-instantsearch-dom": "^6.40.4", "react-instantsearch-dom": "^6.40.4",
"react-masonry-css": "^1.0.16",
"react-player": "^2.14.1", "react-player": "^2.14.1",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"remark": "^14.0.0", "remark": "^14.0.0",
......
import Image from "next/image"; import Head from 'next/head';
import Link from "next/link"; import Link from 'next/link';
import Head from "next/head"; import Image from 'next/image';
import Layout from "../components/layout"; import { getAllLives } from '../lib/livesData';
import utilStyles from "../styles/utils.module.css"; import { useEffect, useRef } from 'react';
import Prism from 'prismjs';
import 'prismjs/components/prism-haskell';
import 'prismjs/themes/prism-tomorrow.css';
import SyntaxHighlighter from "react-syntax-highlighter"; export default function ParVagues({ lives }) {
const rainRef = useRef(null);
import React from "react";
import ReactPlayer from "react-player"; useEffect(() => {
// Highlight code blocks
export async function getStaticProps(context) { Prism.highlightAll();
const tidalSampleUrl =
"https://git.plnech.fr/pln/Tidal/raw/f5bfbc74e68dcaac0f6afa93f2b47d35321274c8/live/dnb/automne_electrique.tidal"; // Subtle rain effect
const response = await fetch(tidalSampleUrl); const canvas = rainRef.current;
const source = await response.text(); if (!canvas) return;
// Remove working title
const sourceClean = source.split("\n").slice(1).join("\n"); const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
return { canvas.height = window.innerHeight;
props: {
urlSC: "https://soundcloud.com/parvagues/", const drops = [];
urlTwitch: "https://twitch.tv/parvagues/", for (let i = 0; i < 50; i++) {
urlTwitchExample: "https://www.twitch.tv/videos/965233250", drops.push({
urlAutomne: "https://soundcloud.com/parvagues/automne-electrique", x: Math.random() * canvas.width,
tidalSample: sourceClean, y: Math.random() * canvas.height,
}, length: Math.random() * 20 + 10,
}; opacity: Math.random() * 0.5,
} speed: Math.random() * 3 + 2,
});
export default function ParVagues({ }
urlSC,
urlTwitch, function animate() {
urlTwitchExample, ctx.clearRect(0, 0, canvas.width, canvas.height);
tidalSample,
}) { drops.forEach(drop => {
ctx.strokeStyle = `rgba(147, 51, 234, ${drop.opacity})`;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(drop.x, drop.y);
ctx.lineTo(drop.x, drop.y + drop.length);
ctx.stroke();
drop.y += drop.speed;
if (drop.y > canvas.height) {
drop.y = -drop.length;
drop.x = Math.random() * canvas.width;
}
});
requestAnimationFrame(animate);
}
animate();
const handleResize = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
const upcomingLives = lives.filter(live => new Date(live.date) > new Date());
const pastLives = lives.filter(live => new Date(live.date) <= new Date());
return ( return (
<Layout> <>
<Head> <Head>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <title>ParVagues - Live Algorithmic Music</title>
<title>ParVagues</title> <meta name="description" content="Live coding performances, algorithmic music, and cyberpunk aesthetics" />
</Head> </Head>
<div>
<section className={utilStyles.headingMd}> <div className="min-h-screen bg-gradient-to-br from-gray-900 via-purple-900 to-gray-900 text-white overflow-hidden relative">
<h1>I create music with patterns</h1> <canvas
<h4> ref={rainRef}
<i> className="fixed inset-0 pointer-events-none opacity-40"
ParVagues, c'est des ondes sonores qui naissent dans un océan style={{ mixBlendMode: 'screen' }}
binaire pour parfois s'échouer sur vos plages sonores. />
</i>
</h4> <div className="relative z-10">
{/*<Image {/* Hero Section */}
alt="ParVagues performing" <section className="min-h-screen flex flex-col justify-center px-4 relative">
src="/images/ParVagues.jpg" <div className="max-w-6xl mx-auto w-full">
layout="fill" <div className="flex flex-col md:flex-row items-center justify-between">
width={700} <div className="md:w-2/3 mb-8 md:mb-0">
height={475} <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">
</section> ParVagues
<section className={utilStyles.headingMd}> </span>
<h5> </h1>
A source sample: the code behind <a href="">Automne Électrique</a>: <p className="text-xl text-gray-300 mb-8 font-mono">
</h5> live.coding() breakbeat.nujazz.hybrid()
<SyntaxHighlighter </p>
className="source-code" <div className="grid gap-4">
width="64em" <pre className="bg-gray-900 p-4 rounded-lg text-sm overflow-x-auto border border-purple-500/20">
language="haskell" <code className="language-haskell">{`d1 $ every 4 (0.25 <~)
wrapLines={true} $ fast 2 $ sound "cp ho:3 mt lt"
> d8 $ chop 16 $ loopAt 2
{tidalSample} $ sound "jungle_breaks:45"`}</code>
</SyntaxHighlighter> </pre>
</section> </div>
<section className={utilStyles.headingMd}> </div>
<h4>
I sometimes post recordings on <a href={urlSC}>SoundCloud</a> {/* Live Events Sidebar */}
</h4> <div className="md:w-1/3 md:pl-8">
<div className="player-wrapper"> <div className="bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 border border-purple-500/20">
<ReactPlayer <h2 className="text-xl font-semibold mb-4 text-purple-400">// upcoming</h2>
className="react-player" {upcomingLives.length > 0 ? (
url={urlSC} <div className="space-y-4 max-h-60 overflow-y-auto pr-2">
width="100%" {upcomingLives.map(live => (
height="32em" <Link key={live.slug} href={`/parvagues/live/${live.slug}`}>
controls={true} <div className="cursor-pointer bg-gray-900/50 p-4 rounded hover:bg-gray-900/70 transition-colors">
config={{ <h3 className="font-semibold text-purple-300">{live.title}</h3>
soundcloud: { <p className="text-sm text-gray-400">{live.date}</p>
options: { <p className="text-xs text-gray-500">{live.location}</p>
auto_play: false, </div>
}, </Link>
}, ))}
}} </div>
/> ) : (
</div> <p className="text-gray-400 text-sm">No upcoming events</p>
</section> )}
<section className={utilStyles.headingMd}> </div>
<h4> </div>
I sometimes do live performances on <a href={urlTwitch}>Twitch</a> </div>
</h4> </div>
<div className="player-wrapper"> </section>
<ReactPlayer
className="react-player" {/* Code Samples Section */}
url={urlTwitchExample} <section className="py-20 px-4">
width="100%" <div className="max-w-6xl mx-auto">
height="32em" <h2 className="text-3xl font-bold mb-8 text-center">
controls={true} <span className="bg-gradient-to-r from-purple-400 to-pink-600 bg-clip-text text-transparent">
/> Live Coding Samples
</div> </span>
</section> </h2>
<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[
0, 0, 0, [0,7],
0, [0,3], 0, [7,0],
0, 0, 7, [7,3],
[7,0], 0, [3,0], 7
])
# s "jungle_breaks"
# cut 8
# amp (range 0.7 1.2 rand)`}</code>
</pre>
</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>")
$ off 0.125 (|+ n 7)
$ chord "<am9 dm9 bm7b5 e7b9>"
# s "nujazz_keys"
# lpf (sine * 1000 + 1200)
# amp 0.8`}</code>
</pre>
</div>
</div>
</div>
</section>
{/* Visual Showcase */}
<section className="py-20 px-4">
<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">
Visual Universe
</span>
</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>
<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>
<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>
</div>
</div>
</section>
{/* Past Performances */}
<section className="py-20 px-4">
<div className="max-w-6xl mx-auto">
<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">
Archives
</span>
</h2>
<div className="grid md:grid-cols-3 gap-6">
{pastLives.slice(0, 6).map(live => (
<Link key={live.slug} href={`/parvagues/live/${live.slug}`}>
<div className="cursor-pointer bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 border border-purple-500/20 hover:border-purple-400 transition-all transform hover:scale-105">
<h3 className="font-semibold mb-2">{live.title}</h3>
<p className="text-sm text-gray-400 mb-1">{live.date}</p>
<p className="text-xs text-gray-500">{live.location}</p>
</div>
</Link>
))}
</div>
{pastLives.length > 6 && (
<div className="text-center mt-8">
<button className="text-purple-400 hover:text-purple-300 transition-colors">
// show more archives
</button>
</div>
)}
</div>
</section>
{/* Footer */}
<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
</p>
<div className="flex justify-center gap-4 mt-4">
<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">
Bandcamp
</a>
<a href="https://instagram.com/parvagues" className="text-gray-400 hover:text-purple-400 transition-colors">
Instagram
</a>
</div>
</div>
</footer>
</div>
</div> </div>
</Layout> </>
); );
} }
export async function getStaticProps() {
const lives = getAllLives();
return {
props: {
lives,
},
revalidate: 60, // Revalidate every minute
};
}
import Image from "next/image";
import Link from "next/link";
import Head from "next/head";
import Layout from "../components/layout";
import utilStyles from "../styles/utils.module.css";
import SyntaxHighlighter from "react-syntax-highlighter";
import React from "react";
import ReactPlayer from "react-player";
export async function getStaticProps(context) {
const tidalSampleUrl =
"https://git.plnech.fr/pln/Tidal/raw/f5bfbc74e68dcaac0f6afa93f2b47d35321274c8/live/dnb/automne_electrique.tidal";
const response = await fetch(tidalSampleUrl);
const source = await response.text();
// Remove working title
const sourceClean = source.split("\n").slice(1).join("\n");
return {
props: {
urlSC: "https://soundcloud.com/parvagues/",
urlTwitch: "https://twitch.tv/parvagues/",
urlTwitchExample: "https://www.twitch.tv/videos/965233250",
urlAutomne: "https://soundcloud.com/parvagues/automne-electrique",
tidalSample: sourceClean,
},
};
}
export default function ParVagues({
urlSC,
urlTwitch,
urlTwitchExample,
tidalSample,
}) {
return (
<Layout>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ParVagues</title>
</Head>
<div>
<section className={utilStyles.headingMd}>
<h1>I create music with patterns</h1>
<h4>
<i>
ParVagues, c'est des ondes sonores qui naissent dans un océan
binaire pour parfois s'échouer sur vos plages sonores.
</i>
</h4>
{/*<Image
alt="ParVagues performing"
src="/images/ParVagues.jpg"
layout="fill"
width={700}
height={475}
/>*/}
</section>
<section className={utilStyles.headingMd}>
<h5>
A source sample: the code behind <a href="">Automne Électrique</a>:
</h5>
<SyntaxHighlighter
className="source-code"
width="64em"
language="haskell"
wrapLines={true}
>
{tidalSample}
</SyntaxHighlighter>
</section>
<section className={utilStyles.headingMd}>
<h4>
I sometimes post recordings on <a href={urlSC}>SoundCloud</a>
</h4>
<div className="player-wrapper">
<ReactPlayer
className="react-player"
url={urlSC}
width="100%"
height="32em"
controls={true}
config={{
soundcloud: {
options: {
auto_play: false,
},
},
}}
/>
</div>
</section>
<section className={utilStyles.headingMd}>
<h4>
I sometimes do live performances on <a href={urlTwitch}>Twitch</a>
</h4>
<div className="player-wrapper">
<ReactPlayer
className="react-player"
url={urlTwitchExample}
width="100%"
height="32em"
controls={true}
/>
</div>
</section>
</div>
</Layout>
);
}
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';
import 'prismjs/themes/prism-tomorrow.css';
export default function Live({ data, slug, images }) {
const [timeToEvent, setTimeToEvent] = useState(null);
const [currentTeasing, setCurrentTeasing] = useState(null);
useEffect(() => {
if (typeof window !== 'undefined') {
Prism.highlightAll();
}
}, [currentTeasing]);
useEffect(() => {
const updateCountdown = () => {
const now = new Date();
const eventDate = new Date(data.frontmatter.date);
const timeDiff = eventDate - now;
if (timeDiff > 0) {
const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
setTimeToEvent(days);
// 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);
}
} else {
setTimeToEvent(-1); // Event has passed
}
};
updateCountdown();
const interval = setInterval(updateCountdown, 1000 * 60 * 60); // Update every hour
return () => clearInterval(interval);
}, [data.frontmatter.date]);
const renderMarkdown = (content) => {
if (!content) return '';
const html = marked(content);
return { __html: html };
};
return (
<>
<Head>
<title>{data.frontmatter.title} - ParVagues</title>
<meta name="description" content={data.frontmatter.description} />
</Head>
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-purple-900 to-gray-900 text-white">
<nav className="sticky top-0 bg-black/50 backdrop-blur-sm 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">
Retour ParVagues
</Link>
</div>
</nav>
<main className="max-w-6xl mx-auto px-4 py-8">
<div className="mb-8">
<h1 className="text-4xl font-bold mb-2 bg-gradient-to-r from-purple-400 to-pink-600 bg-clip-text text-transparent">
{data.frontmatter.title}
</h1>
<p className="text-gray-400">{data.frontmatter.location} - {data.frontmatter.date}</p>
</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">
<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-gray-800/50 backdrop-blur-sm rounded-lg p-6 border border-purple-500/20">
<div
className="prose prose-purple prose-invert prose-pre:bg-gray-900 prose-pre:border prose-pre:border-purple-500/20 max-w-none"
dangerouslySetInnerHTML={renderMarkdown(currentTeasing)}
/>
</div>
)}
{data.frontmatter.ctaURL && (
<div className="text-center">
<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"
>
{data.frontmatter.ctaText || data.frontmatter.title}
</a>
</div>
)}
</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>
{(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-gray-800/50 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-gray-800/50 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>
)}
</div>
)}
{images && images.length > 0 && (
<ImageGallery images={images} slug={slug} />
)}
<details className="group">
<summary className="cursor-pointer p-4 bg-gray-800/30 rounded-lg hover:bg-gray-800/50 transition-colors">
<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-gray-800/20 rounded p-4">
<div
className="prose prose-purple prose-invert prose-pre:bg-gray-900 prose-pre:border prose-pre:border-purple-500/20 max-w-none text-sm"
dangerouslySetInnerHTML={renderMarkdown(teasing)}
/>
</div>
)
)}
</div>
</details>
</div>
)}
</main>
</div>
</>
);
}
export async function getStaticPaths() {
const lives = getAllLives();
const paths = lives.map((live) => ({
params: { id: live.slug },
}));
return {
paths,
fallback: 'blocking',
};
}
export async function getStaticProps({ params }) {
const data = await getLiveData(params.id);
const images = getLivesImages(params.id);
return {
props: {
data,
slug: params.id,
images,
},
revalidate: 60, // Revalidate every minute
};
}
# 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
# Placeholder for live setup photo
\ No newline at end of file
.masonry-grid {
display: flex;
margin-left: -1rem; /* gutter size offset */
width: auto;
}
.masonry-grid_column {
padding-left: 1rem; /* gutter size */
background-clip: padding-box;
}
/* Style different sized items */
.masonry-grid_column > div {
margin-bottom: 1rem;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.title {
font-size: 2.5rem;
margin-bottom: 1rem;
color: #333;
}
.subtitle {
font-size: 1.5rem;
margin-bottom: 2rem;
color: #666;
}
.searchContainer {
background-color: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin-bottom: 30px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.searchBar {
display: flex;
margin-bottom: 20px;
}
.searchBar input {
flex: 1;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
font-size: 16px;
}
.searchBar button {
background-color: #2196F3;
color: white;
border: none;
padding: 12px 20px;
border-radius: 0 4px 4px 0;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
.searchBar button:hover {
background-color: #0b7dda;
}
.searchBar button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.movieGrid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 20px;
margin-top: 20px;
}
.movieCard {
position: relative;
border-radius: 8px;
overflow: hidden;
transition: transform 0.3s ease;
background-color: #1a1a1a;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.movieCard:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
}
.posterContainer {
position: relative;
width: 100%;
height: 270px;
overflow: hidden;
}
.poster {
width: 100%;
height: 100%;
object-fit: cover;
}
.noPoster {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #2a2a2a;
color: #999;
text-align: center;
padding: 10px;
}
.movieOverlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.9) 100%);
color: white;
padding: 15px;
display: flex;
flex-direction: column;
opacity: 0;
transition: opacity 0.3s ease;
}
.movieCard:hover .movieOverlay {
opacity: 1;
}
.movieTitle {
font-size: 16px;
font-weight: 600;
margin: 0 0 5px 0;
}
.movieYear {
font-size: 14px;
color: #ccc;
margin-bottom: 5px;
}
.movieRating {
font-size: 14px;
color: #ffcc00;
margin-bottom: 5px;
}
.movieGenres {
font-size: 12px;
color: #aaa;
margin-bottom: 10px;
}
.moviePlot {
font-size: 12px;
line-height: 1.4;
color: #ddd;
flex-grow: 1;
overflow: hidden;
}
.movieActions {
margin-top: auto;
}
.voteButton {
background-color: #e50914;
color: white;
border: none;
border-radius: 4px;
padding: 8px 12px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s ease;
width: 100%;
}
.voteButton:hover {
background-color: #f40612;
}
.voteButton:disabled {
background-color: #666;
cursor: not-allowed;
}
.movieInfo {
padding: 10px;
}
.movieInfo h4 {
margin: 0;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.movieMeta {
display: flex;
justify-content: space-between;
font-size: 12px;
color: #999;
margin-top: 5px;
}
.votes {
color: #e50914;
}
.searchSection {
margin-bottom: 30px;
}
.searchForm {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.searchInput {
flex-grow: 1;
padding: 12px;
border-radius: 4px;
border: 1px solid #333;
background-color: #222;
color: white;
}
.searchButton {
background-color: #e50914;
color: white;
border: none;
border-radius: 4px;
padding: 0 20px;
cursor: pointer;
}
.filterSection {
display: flex;
gap: 15px;
margin-top: 10px;
}
.filterButton {
background-color: transparent;
border: 1px solid #333;
border-radius: 4px;
padding: 8px 16px;
cursor: pointer;
}
.winner {
position: relative;
}
.winnerBadge {
position: absolute;
top: -10px;
right: -10px;
background-color: gold;
color: black;
padding: 5px 10px;
border-radius: 20px;
font-weight: bold;
z-index: 1;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.manualAdd {
margin-top: 20px;
}
.manualAdd form {
display: flex;
}
.manualAdd input {
flex: 1;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
font-size: 16px;
}
.manualAdd button {
background-color: #2196F3;
color: white;
border: none;
padding: 12px 20px;
border-radius: 0 4px 4px 0;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
.manualAdd button:hover {
background-color: #0b7dda;
}
.manualAdd button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.searchResults {
margin: 20px 0;
}
.noResults {
text-align: center;
padding: 30px;
background-color: #f9f9f9;
border-radius: 4px;
color: #666;
}
/* Responsive styles */
@media (max-width: 768px) {
.movieGrid {
grid-template-columns: 1fr;
}
.searchBar {
flex-direction: column;
}
.searchBar input {
border-radius: 4px;
margin-bottom: 10px;
}
.searchBar button {
border-radius: 4px;
}
.manualAdd form {
flex-direction: column;
}
.manualAdd input {
border-radius: 4px;
margin-bottom: 10px;
}
.manualAdd button {
border-radius: 4px;
}
}
\ No newline at end of file
...@@ -473,14 +473,18 @@ ...@@ -473,14 +473,18 @@
.hoverPopup { .hoverPopup {
position: absolute; position: absolute;
left: 50%; left: 50%;
bottom: calc(100% + 15px);
transform: translateX(-50%); transform: translateX(-50%);
z-index: 100; z-index: 100;
min-width: 380px; min-width: 250px;
max-width: 500px; max-width: 30vw;
width: max-content; width: max-content;
max-width: calc(100vw - 40px); /* Ensure popup doesn't exceed viewport width */ max-height: 80vh;
overflow: hidden;
top: 10vh;
bottom: auto;
margin: 0 10vw;
animation: fadeIn 0.2s ease-in-out; animation: fadeIn 0.2s ease-in-out;
} }
......
...@@ -896,6 +896,11 @@ lowlight@^1.17.0: ...@@ -896,6 +896,11 @@ lowlight@^1.17.0:
fault "^1.0.0" fault "^1.0.0"
highlight.js "~10.7.0" highlight.js "~10.7.0"
marked@^15.0.11:
version "15.0.11"
resolved "https://registry.npmjs.org/marked/-/marked-15.0.11.tgz"
integrity sha512-1BEXAU2euRCG3xwgLVT1y0xbJEld1XOrmRJpUwRCcy7rxhSCwMrmEu9LXoPhHSCJG41V7YcQ2mjKRr5BA3ITIA==
mdast-util-definitions@^5.0.0: mdast-util-definitions@^5.0.0:
version "5.1.2" version "5.1.2"
resolved "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz" resolved "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz"
...@@ -1292,10 +1297,10 @@ preact@^10.10.0: ...@@ -1292,10 +1297,10 @@ preact@^10.10.0:
resolved "https://registry.npmjs.org/preact/-/preact-10.26.6.tgz" resolved "https://registry.npmjs.org/preact/-/preact-10.26.6.tgz"
integrity sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g== integrity sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==
prismjs@^1.27.0: prismjs@^1.27.0, prismjs@^1.30.0:
version "1.29.0" version "1.30.0"
resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz" resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz"
integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==
prismjs@~1.27.0: prismjs@~1.27.0:
version "1.27.0" version "1.27.0"
...@@ -1405,6 +1410,11 @@ react-is@^16.13.1: ...@@ -1405,6 +1410,11 @@ react-is@^16.13.1:
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-masonry-css@^1.0.16:
version "1.0.16"
resolved "https://registry.npmjs.org/react-masonry-css/-/react-masonry-css-1.0.16.tgz"
integrity sha512-KSW0hR2VQmltt/qAa3eXOctQDyOu7+ZBevtKgpNDSzT7k5LA/0XntNa9z9HKCdz3QlxmJHglTZ18e4sX4V8zZQ==
react-player@^2.14.1: react-player@^2.14.1:
version "2.16.0" version "2.16.0"
resolved "https://registry.npmjs.org/react-player/-/react-player-2.16.0.tgz" resolved "https://registry.npmjs.org/react-player/-/react-player-2.16.0.tgz"
...@@ -1428,7 +1438,7 @@ react-syntax-highlighter@^15.5.0: ...@@ -1428,7 +1438,7 @@ react-syntax-highlighter@^15.5.0:
prismjs "^1.27.0" prismjs "^1.27.0"
refractor "^3.6.0" refractor "^3.6.0"
"react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", react@^18.2.0, "react@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react@^18.3.1, "react@>= 0.14.0", "react@>= 16.3.0 < 19", "react@>= 16.8.0 < 20", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0", react@>=16.6.0: "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", react@^18.2.0, "react@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react@^18.3.1, "react@>= 0.14.0", "react@>= 16.3.0 < 19", "react@>= 16.8.0 < 20", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0", react@>=16.0.0, react@>=16.6.0:
version "18.3.1" version "18.3.1"
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
......
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