Unverified Commit ed78570b by Paul-Louis NECH Committed by GitHub

Merge pull request #3 from PLNech/feat/cosmicfest-landing

feat: Initial landing page for cosmicfest subsite
parents 998a6768 47d35251
import { useState, useEffect } from 'react';
import styles from '../../styles/cosmicfest.module.css'; // Assuming you might want specific styles
const Countdown = ({ targetDate }) => {
const calculateTimeLeft = () => {
const difference = +new Date(targetDate) - +new Date();
let timeLeft = {};
if (difference > 0) {
timeLeft = {
jours: Math.floor(difference / (1000 * 60 * 60 * 24)),
heures: Math.floor((difference / (1000 * 60 * 60)) % 24),
minutes: Math.floor((difference / 1000 / 60) % 60),
secondes: Math.floor((difference / 1000) % 60),
};
} else {
timeLeft = { jours: 0, heures: 0, minutes: 0, secondes: 0 };
}
return timeLeft;
};
const [timeLeft, setTimeLeft] = useState(calculateTimeLeft());
useEffect(() => {
const timer = setTimeout(() => {
setTimeLeft(calculateTimeLeft());
}, 1000);
return () => clearTimeout(timer);
});
const timerComponents = [];
Object.keys(timeLeft).forEach((interval) => {
if (!timeLeft[interval] && timeLeft[interval] !== 0) { // handles case where initial timeLeft is empty
return;
}
timerComponents.push(
<span key={interval} style={{ margin: '0 0.5rem', textAlign: 'center' }}>
<div style={{ fontSize: '2.5rem', fontWeight: 'bold' }}>{timeLeft[interval]}</div>
<div style={{ fontSize: '0.8rem', textTransform: 'uppercase' }}>{interval}</div>
</span>
);
});
return (
<div className={styles.countdown} style={{ display: 'flex', justifyContent: 'center', alignItems: 'baseline' }}>
{timerComponents.length ? timerComponents : <span>c'est l'heure!</span>}
</div>
);
};
export default Countdown;
import Head from 'next/head';
import Image from 'next/image'; // Using Next.js Image component for optimization
import styles from '../../styles/cosmicfest.module.css';
import Countdown from '../../components/cosmicfest/Countdown';
// import ParVaguesFooter from '../../components/ParVaguesFooter'; // Optional: if you want to reuse the main site footer
// Helper function to calculate next June 21st
const getNextJune21st = () => {
const today = new Date();
const currentYear = today.getFullYear();
let nextJune21 = new Date(currentYear, 5, 21); // Month is 0-indexed (5 is June)
if (today > nextJune21) {
nextJune21 = new Date(currentYear + 1, 5, 21);
}
return nextJune21;
};
export default function CosmicFestHome() {
const nextFestivalDate = getNextJune21st();
const lineup2024 = [
{ artist: "Hugo", description: "live guitare, compos et reprises", emojis:"🎸🎙️" },
{ artist: "Flo", description: "live machines downtempo", emojis:"⚙️🎛" },
{ artist: "ParVagues", description: "livecoding breakbeat to techno nujazz", emojis:"💻🎛️" },
{ artist: "DJ @marion.lhn", description: "set drum-n-bass", emojis: "💻📀" },
{ artist: "Kevin & Pipou", description: "lights et visuels interactifs", emojis:"💻📽️" },
{ artist: "PLN", description: "visuels Hydra.js", emojis:"💻📽" },
];
// Placeholder data for v0
const v0_content = {
photos: [
{ src: '/images/cosmicfest/v0_hugo.jpg', alt: 'v0 ~ hugo à la guitare' },
{ src: '/images/cosmicfest/v0_parvagues_lineup.jpg', alt: 'v0 ~ parvagues ~ line-up' },
{ src: '/images/cosmicfest/hero.gif', alt: 'v0 ~ le off au bunker' },
],
videos: [
{ id: '6uoO282xJXM', src: '/images/cosmicfest/v0_parvagues_lineup_crop.jpg', title: 'scene v0 - parvagues livecoding' },
// { id: 'YOUTUBE_VIDEO_ID_V0_PERFORMANCE_2', title: 'live performance v0 - artiste y' },
],
aftermovie: { id: 'YOUTUBE_VIDEO_ID_V0_AFTERMOVIE', title: 'aftermovie cosmicfest v0', released: false },
lineup: [
"20h ~ hugo ~ guitare",
"21h ~ flo ~ downtempo",
"22h ~ parvagues ~ livecoding",
"23h ~ marion.lhn ~ set dnb",
"00+ ~ jam"
]
};
return (
<div className={styles.container}>
<Head>
<title>cosmicfest</title>
<meta name="description" content="cosmicfest - festival indé de musique et création numérique à Labenne Océan. 21 juin." />
<link rel="icon" href="/favicon.ico" /> {/* Consider a specific cosmicfest favicon */}
</Head>
<main className={styles.main}>
<header className={styles.hero}>
{/* Hero image is set via CSS background for now */}
<h1>🌠 cosmicfest 🌌</h1>
<p>ondes de rythmes, océan et code.<br />21 juin, labenne océan.</p>
<h3>🪐</h3>
</header>
<section className={styles.section} id="lineup">
<h2>lineup {new Date().getFullYear()}</h2>
<div className={styles.grid}>
{lineup2024.map((item, index) => (
<div key={index} className={styles.card}>
<h3>{item.artist}</h3>
<p>{item.description}</p><br/>
<p>~ {item.emojis} ~</p>
</div>
))}
</div>
</section>
<section className={styles.section} id="v0">
<h2>souvenirs de la v0 ({nextFestivalDate.getFullYear() -1 })</h2>
<h3>photos v0</h3>
<div className={styles.gallery}>
{v0_content.photos.map((photo, index) => (
<div key={index} style={{backgroundImage: `url(${photo.src})`, backgroundSize: 'cover', backgroundPosition: 'center', height: '150px'}}>
{/* <Image src={photo.src} alt={photo.alt} width={200} height={150} objectFit="cover" /> */}
{/* Using div with background for now, as next/image needs actual image dimensions for non-fill */}
<span style={{color: '#fff', backgroundColor: 'rgba(0,0,0,0.5)', padding: '2px'}}>{photo.alt}</span>
</div>
))}
</div>
<h3 style={{marginTop: '2rem'}}>vidéos v0</h3>
<div className={styles.gallery}>
{/* Aftermovie */}
{v0_content.aftermovie.released && (
<div>
<a href={`https://www.youtube.com/watch?v=${v0_content.aftermovie.id}`} target="_blank" rel="noopener noreferrer">
{v0_content.aftermovie.title} (placeholder: {v0_content.aftermovie.id})
</a>
</div>
)}
{/* Other videos */}
{v0_content.videos.map((video, index) => (
<div key={index} style={{backgroundImage: `url(${video.src})`, backgroundSize: 'cover', backgroundPosition: 'center', height: '150px'}}>
<a href={`https://www.youtube.com/watch?v=${video.id}`} target="_blank" rel="noopener noreferrer">
{video.title}
</a>
</div>
))}
</div>
<h3 style={{marginTop: '2rem'}}>lineup v0</h3>
<ul>
{v0_content.lineup.map((artist, index) => (
<li key={index}>{artist}</li>
))}
</ul>
</section>
<section className={styles.section} id="next-edition">
<h2>prochaine édition:<br/>{nextFestivalDate.toLocaleDateString('fr-FR', { year: 'numeric', month: 'long', day: 'numeric' })}</h2>
<div className={styles.countdownContainer}> {/* Added class for styling countdown section */}
<Countdown targetDate={nextFestivalDate.toISOString()} />
</div>
<div style={{textAlign: 'center', marginTop: '2rem'}}>
<button className={styles.ctaButton} onClick={() => alert('waitlist bientôt disponible!')}>
prends la prochaine vague
</button>
<p className={styles.waitlistMessage}>
pas de billetterie - cosmicfest c'est sur invitation. <br/>
riders, come to the storm.
</p>
</div>
</section>
<section className={styles.section} id="off-festival">
<h2>🏄🏻‍♀️ et le off? 🏄🏽</h2>
<p style={{textAlign: 'center', fontStyle: 'italic'}}>
🌊 RDV sur la plage ~ le dimanche soir après le démontage 🌊<br />
🔊 pour le <b>dernier</b> live ~ au <b>bunker</b> 🔊
</p>
</section>
</main>
<footer className={styles.footer}>
<a
href="/parvagues/"
>
vibe ~
<span style={{fontWeight: 'bold'}}>parvagues</span>
{/* <Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} /> You can use ParVagues logo here */}
</a>
</footer>
{/* <ParVaguesFooter /> */} {/* Or use the existing footer */}
</div>
);
}
// This is a placeholder image. Replace with actual hero image.
This image diff could not be displayed because it is too large. You can view the blob instead.
/* Styles for cosmicfest */
/* vibe: Surfers cross Techno; années 60 cross cyberpunk, Biarritz cross Paris cross Berlin cross Bayonne, Waves of rhythms, ocean and code, VibeCoding meets LiveCoding meets SkateBoarding meets Rock Climbing on former german bunkers meets long nights of chats and fun and drinks and dance and love and freedom */
/* aesthetic: lowercase, light theme, chill et oceanique, mais pas cliche baba cool, juste posé. */
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;700&family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap');
/*
font-family: 'Montserrat', sans-serif; // For general text, headings
font-family: 'Space Mono', monospace; // For techy bits, or accent text
*/
.container {
padding: 0;
margin: 0;
background-color: #fafafa; /* Light, slightly off-white for a softer feel */
color: #2c3e50; /* Dark blue-grey, less harsh than black */
font-family: 'Montserrat', sans-serif;
font-weight: 300;
line-height: 1.6;
text-transform: lowercase; /* Global lowercase aesthetic */
min-height: 100vh;
display: flex;
flex-direction: column;
}
.main {
flex: 1; /* Ensures footer stays at bottom */
/* padding: 4rem 0; remove default padding to allow full-width hero */
}
.hero {
width: 100%;
height: 80vh; /* Taller hero */
min-height: 400px;
background-color: #3498db; /* Placeholder - vibrant blue, like deep ocean */
background-image: url('/images/cosmicfest/hero.jpg');
background-size: cover;
background-position: center 60%; /* Focus slightly lower than true center */
display: flex;
flex-direction: column;
justify-content: center; /* Center content vertically */
align-items: center; /* Center content horizontally */
text-align: center;
color: #ffffff;
padding: 2rem;
position: relative; /* For potential overlay or pseudo-elements */
}
.hero::before { /* Subtle vignette or color overlay */
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.2); /* Dark overlay to help text pop */
/* background: linear-gradient(to bottom, rgba(52, 152, 219, 0.1), rgba(26, 82, 118, 0.5)); /* Ocean gradient overlay */
}
.hero h1 {
font-family: 'Space Mono', monospace;
font-size: clamp(2.5rem, 8vw, 5.5rem); /* Responsive font size */
margin-bottom: 0.5rem;
text-shadow: 0px 2px 8px rgba(0,0,0,0.7);
font-weight: 700;
letter-spacing: 0.05em;
z-index: 1; /* Above overlay */
}
.hero p {
font-size: clamp(1rem, 3vw, 1.5rem);
text-shadow: 0px 1px 4px rgba(0,0,0,0.6);
max-width: 600px; /* Keep tagline readable */
font-weight: 400;
z-index: 1; /* Above overlay */
}
.section {
margin: 3rem auto; /* More vertical space between sections */
padding: 2rem clamp(1rem, 5vw, 3rem); /* Responsive horizontal padding */
max-width: 1000px; /* Max content width */
background-color: rgba(255, 255, 255, 0.9); /* Slightly transparent white for depth */
border-radius: 12px; /* Softer corners */
box-shadow: 0 8px 25px rgba(44, 62, 80, 0.08); /* Softer, more diffused shadow */
}
.section h2 {
font-family: 'Space Mono', monospace;
font-size: clamp(1.8rem, 5vw, 2.8rem);
margin-bottom: 2rem; /* More space after heading */
color: #2980b9; /* Ocean blue, slightly darker than hero for contrast */
text-align: center;
font-weight: 700;
letter-spacing: 0.03em;
}
.grid {
display: grid; /* Using grid for more control */
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); /* Responsive columns */
gap: 1.5rem; /* Space between cards */
}
.card {
background-color: #ffffff;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(44, 62, 80, 0.05);
transition: transform 0.3s ease-out, box-shadow 0.3s ease-out;
text-align: left;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(44, 62, 80, 0.1);
}
.card h3 {
font-family: 'Montserrat', sans-serif;
font-size: 1.4rem; /* Slightly larger */
margin-bottom: 0.75rem;
color: #16a085; /* Teal/Turquoise for a fresh vibe */
font-weight: 700; /* Bolder artist names */
}
.card p {
font-size: 0.95rem;
color: #34495e; /* Slightly lighter text for description */
}
.countdownContainer { /* Renamed to avoid conflict if .countdown is too generic */
text-align: center;
margin: 2.5rem 0;
padding: 2rem;
background-color: rgba(236, 240, 241, 0.7); /* Light grey, slightly transparent */
border-radius: 8px;
}
/* Style for individual countdown items is in Countdown.js for now, can be moved here */
.countdownContainer span div:first-child { /* The number */
font-family: 'Space Mono', monospace;
color: #e67e22; /* Warm orange, like sunset */
font-size: clamp(2rem, 6vw, 3.5rem);
}
.countdownContainer span div:last-child { /* The label (jours, heures) */
color: #7f8c8d; /* Muted grey */
font-size: clamp(0.7rem, 2vw, 0.9rem);
}
.ctaButton {
display: inline-block;
background-color: #007ef9; /* Coral/Red for strong CTA */
color: white;
padding: 0.8rem 2.5rem; /* Adjusted padding */
border-radius: 50px; /* Pill shape */
text-decoration: none;
font-size: 1.2rem;
font-weight: 700; /* Bolder CTA text */
transition: background-color 0.2s ease, transform 0.2s ease;
border: none;
cursor: pointer;
box-shadow: 0 4px 10px rgba(231, 76, 60, 0.3);
font-family: 'Montserrat', sans-serif; /* Consistent font */
}
.ctaButton:hover {
background-color: #c0392b; /* Darker red on hover */
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(231, 76, 60, 0.4);
}
.waitlistMessage {
text-align: center;
margin-top: 1.5rem; /* More space */
font-style: normal; /* Less italic, more direct */
color: #7f8c8d; /* Muted grey */
font-size: 0.9rem;
line-height: 1.4;
}
.footer {
width: 100%;
padding: 2.5rem 1rem; /* More padding */
border-top: 1px solid #ecf0f1; /* Lighter border */
display: flex;
justify-content: center;
align-items: center;
background-color: #ffffff; /* Clean white footer */
color: #95a5a6; /* Muted text color for footer */
font-size: 0.9rem;
}
.footer a {
text-decoration: none;
color: #3498db; /* Link color for parvagues */
transition: color 0.2s ease;
}
.footer a:hover {
color: #2980b9; /* Darker blue on hover */
}
.footer span {
margin-left: 0.3rem;
font-weight: 400; /* Normal weight for 'parvagues' in footer */
}
/* Image gallery styles */
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); /* Slightly smaller min for more items */
gap: 1rem;
}
.gallery div, .gallery a { /* Target divs for background images, and links for youtube placeholders */
width: 100%;
height: 180px; /* Taller gallery items */
border-radius: 8px; /* Consistent rounding */
background-color: #ecf0f1; /* Light placeholder color */
display: flex;
justify-content: center;
align-items: center;
text-align: center;
font-size: 0.9rem;
color: #7f8c8d;
overflow: hidden; /* Ensure content fits */
position: relative;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.gallery div:hover, .gallery a:hover {
transform: scale(1.03);
box-shadow: 0 5px 15px rgba(44, 62, 80, 0.1);
}
.gallery div span, .gallery a { /* For text overlay on images/video placeholders */
color: #fff;
background-color: rgba(0,0,0,0.6);
padding: 0.3em 0.6em;
border-radius: 4px;
z-index: 1;
text-decoration: none; /* For links */
}
.gallery a { /* Specific for youtube links if not using background images */
background-color: #333; /* Darker for video links */
color: #fff;
}
.gallery a:hover {
background-color: #444;
}
/* Section specific styling for V0 */
#v0 h3 {
font-family: 'Montserrat', sans-serif;
font-size: 1.2rem;
color: #2c3e50;
margin-top: 2.5rem;
margin-bottom: 1rem;
text-align: left;
font-weight: 700;
border-bottom: 1px solid #ecf0f1;
padding-bottom: 0.5rem;
}
#v0 ul {
list-style: none;
padding-left: 0;
text-align: left;
}
#v0 li {
padding: 0.3rem 0;
color: #34495e;
}
/* Festival Off */
#off-festival p {
font-family: 'Space Mono', monospace;
color: #9b59b6; /* Mysterious purple */
font-size: 1.1rem;
line-height: 1.7;
}
/* Accessibility: Focus indicator */
/*:global(a:focus), :global(button:focus), :global(input:focus) {*/
/* outline: 2px solid #3498db;*/
/* outline-offset: 2px;*/
/*}*/
/* Responsive adjustments for smaller screens */
@media (max-width: 768px) {
.hero {
height: 70vh; /* Adjust hero height */
}
.section {
margin: 2rem auto;
padding: 1.5rem clamp(0.5rem, 3vw, 1.5rem);
}
.grid {
grid-template-columns: 1fr; /* Stack cards on smaller screens */
}
.card {
width: auto; /* Full width */
}
.countdownContainer span div:first-child { /* The number */
font-size: clamp(1.8rem, 5vw, 3rem);
}
}
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