Unverified Commit fd0f4c1c by Paul-Louis NECH Committed by GitHub

Merge pull request #5 from PLNech/feat/cosmicfest-updates

feat(cosmicfest): Integrate audio players and enhance UI/UX
parents 932f76c6 cee4f024
import Head from 'next/head'; import Head from 'next/head';
import Image from 'next/image'; // Using Next.js Image component for optimization import Image from 'next/image'; // Using Next.js Image component for optimization
import { useState, useEffect } from 'react'; // Added useState and useEffect
import styles from '../../styles/cosmicfest.module.css'; import styles from '../../styles/cosmicfest.module.css';
import Countdown from '../../components/cosmicfest/Countdown'; import Countdown from '../../components/cosmicfest/Countdown';
// import ParVaguesFooter from '../../components/ParVaguesFooter'; // Optional: if you want to reuse the main site footer // import ParVaguesFooter from '../../components/ParVaguesFooter'; // Optional: if you want to reuse the main site footer
...@@ -18,6 +19,41 @@ const getNextJune21st = () => { ...@@ -18,6 +19,41 @@ const getNextJune21st = () => {
export default function CosmicFestHome() { export default function CosmicFestHome() {
const nextFestivalDate = getNextJune21st(); const nextFestivalDate = getNextJune21st();
const [isModalOpen, setIsModalOpen] = useState(false);
const [modalImageSrc, setModalImageSrc] = useState('');
const [modalImageAlt, setModalImageAlt] = useState('');
const openModal = (src, alt) => {
setModalImageSrc(src);
setModalImageAlt(alt);
setIsModalOpen(true);
document.body.style.overflow = 'hidden'; // Prevent background scrolling
};
const closeModal = () => {
setIsModalOpen(false);
setModalImageSrc('');
setModalImageAlt('');
document.body.style.overflow = 'auto'; // Restore background scrolling
};
useEffect(() => {
const handleEsc = (event) => {
if (event.key === 'Escape' && isModalOpen) {
closeModal();
}
};
window.addEventListener('keydown', handleEsc);
// Cleanup function to remove listener
return () => {
window.removeEventListener('keydown', handleEsc);
// Ensure body overflow is reset if component unmounts while modal is open
if (isModalOpen) {
document.body.style.overflow = 'auto';
}
};
}, [isModalOpen]); // Dependency array includes isModalOpen
const lineup2024 = [ const lineup2024 = [
{ artist: "Hugo", description: "live guitare, compos et reprises", emojis:"🎸🎙️" }, { artist: "Hugo", description: "live guitare, compos et reprises", emojis:"🎸🎙️" },
...@@ -72,9 +108,9 @@ export default function CosmicFestHome() { ...@@ -72,9 +108,9 @@ export default function CosmicFestHome() {
<div className={styles.grid}> <div className={styles.grid}>
{lineup2024.map((item, index) => ( {lineup2024.map((item, index) => (
<div key={index} className={styles.card}> <div key={index} className={styles.card}>
<h3>{item.artist}</h3> <h3>{item.artist} <span className={styles.cardEmojis}>~ {item.emojis} ~</span></h3>
<p>{item.description}</p><br/> <p>{item.description}</p>
<p>~ {item.emojis} ~</p> {/* The emoji line is now part of the h3, the extra <p> for emojis and <br/> are removed */}
</div> </div>
))} ))}
</div> </div>
...@@ -86,10 +122,17 @@ export default function CosmicFestHome() { ...@@ -86,10 +122,17 @@ export default function CosmicFestHome() {
<h3>photos v0</h3> <h3>photos v0</h3>
<div className={styles.gallery}> <div className={styles.gallery}>
{v0_content.photos.map((photo, index) => ( {v0_content.photos.map((photo, index) => (
<div key={index} style={{backgroundImage: `url(${photo.src})`, backgroundSize: 'cover', backgroundPosition: 'center', height: '150px'}}> <div
{/* <Image src={photo.src} alt={photo.alt} width={200} height={150} objectFit="cover" /> */} key={photo.src} // Using src as key for stability if array order changes but src is unique
{/* Using div with background for now, as next/image needs actual image dimensions for non-fill */} className={`${styles.galleryItem} ${styles.galleryPhotoItem}`} // Ensure both classes are applied
<span style={{color: '#fff', backgroundColor: 'rgba(0,0,0,0.5)', padding: '2px'}}>{photo.alt}</span> style={{backgroundImage: `url(${photo.src})`}}
onClick={() => openModal(photo.src, photo.alt)}
role="button"
tabIndex={0}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openModal(photo.src, photo.alt); } }}
aria-label={`View image: ${photo.alt}`}
>
<span>{photo.alt}</span>
</div> </div>
))} ))}
</div> </div>
...@@ -106,11 +149,17 @@ export default function CosmicFestHome() { ...@@ -106,11 +149,17 @@ export default function CosmicFestHome() {
)} )}
{/* Other videos */} {/* Other videos */}
{v0_content.videos.map((video, index) => ( {v0_content.videos.map((video, index) => (
<div key={index} style={{backgroundImage: `url(${video.src})`, backgroundSize: 'cover', backgroundPosition: 'center', height: '150px'}}> <a
<a href={`https://www.youtube.com/watch?v=${video.id}`} target="_blank" rel="noopener noreferrer"> key={index}
{video.title} href={`https://www.youtube.com/watch?v=${video.id}`}
target="_blank"
rel="noopener noreferrer"
className={`${styles.galleryItem} ${styles.galleryVideoLink}`}
style={video.src ? {backgroundImage: `url(${video.src})`} : {}} // Apply BG only if src exists
aria-label={`Watch video: ${video.title}`}
>
<span>{video.title}</span> {/* Span for styling the text overlay */}
</a> </a>
</div>
))} ))}
</div> </div>
...@@ -120,6 +169,19 @@ export default function CosmicFestHome() { ...@@ -120,6 +169,19 @@ export default function CosmicFestHome() {
<li key={index}>{artist}</li> <li key={index}>{artist}</li>
))} ))}
</ul> </ul>
<h3 style={{marginTop: '2rem'}}>audio ~ ParVagues</h3>
<p>
ecoute chaque piste ~ ecoute le set entier ~ telecharge le tout
</p>
<div className={styles.audioPlayersContainer}>
<div className={`${styles.audioEmbed} ${styles.bandcampPlayer}`}>
<iframe title="Bandcamp player for Parvagues live at Cosmicfest" style={{border: 0, width: "100%", maxWidth: "350px", height: "470px"}} src="https://bandcamp.com/EmbeddedPlayer/album=644862623/size=large/bgcol=333333/linkcol=0f91ff/tracklist=true/artwork=small/transparent=true/" seamless><a href="https://parvagues.bandcamp.com/album/live-cosmicfest">live@cosmicfest by ParVagues</a></iframe>
</div>
<div className={`${styles.audioEmbed} ${styles.soundcloudPlayer}`}>
<iframe title="Soundcloud player for Parvagues live at Cosmicfest" width="450" height="450" scrolling="no" frameBorder="no" allow="autoplay" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/2121089043&color=%233e00f7&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true&visual=true"></iframe><div style={{fontSize: "10px", color: "#cccccc",lineBreak: "anywhere",wordBreak: "normal",overflow: "hidden",whiteSpace: "nowrap",textOverflow: "ellipsis", fontFamily: "Interstate,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Garuda,Verdana,Tahoma,sans-serif",fontWeight: "100"}}><a href="https://soundcloud.com/parvagues" title="ParVagues" target="_blank" rel="noopener noreferrer" style={{color: "#cccccc", textDecoration: "none"}}>ParVagues</a> · <a href="https://soundcloud.com/parvagues/cosmicfest" title="Live@cosmicfest 🌊🌅" target="_blank" rel="noopener noreferrer" style={{color: "#cccccc", textDecoration: "none"}}>Live@cosmicfest 🌊🌅</a></div>
</div>
</div>
</section> </section>
<section className={styles.section} id="next-edition"> <section className={styles.section} id="next-edition">
...@@ -128,9 +190,12 @@ export default function CosmicFestHome() { ...@@ -128,9 +190,12 @@ export default function CosmicFestHome() {
<Countdown targetDate={nextFestivalDate.toISOString()} /> <Countdown targetDate={nextFestivalDate.toISOString()} />
</div> </div>
<div style={{textAlign: 'center', marginTop: '2rem'}}> <div style={{textAlign: 'center', marginTop: '2rem'}}>
<button className={styles.ctaButton} onClick={() => alert('waitlist bientôt disponible!')}> <a
href="mailto:cosmic@nech.pl?subject=je%20suis%20cosmic%20car...&body=voila%20mon%20cot%C3%A9%20cosmique%20%3A%20..."
className={styles.ctaButton}
>
prends la prochaine vague prends la prochaine vague
</button> </a>
<p className={styles.waitlistMessage}> <p className={styles.waitlistMessage}>
pas de billetterie - cosmicfest c'est sur invitation. <br/> pas de billetterie - cosmicfest c'est sur invitation. <br/>
riders, come to the storm. riders, come to the storm.
...@@ -148,6 +213,23 @@ export default function CosmicFestHome() { ...@@ -148,6 +213,23 @@ export default function CosmicFestHome() {
</main> </main>
{isModalOpen && (
<div className={styles.modalOverlay} onClick={closeModal} role="dialog" aria-modal="true" aria-label={modalImageAlt || 'Image viewer'}>
<div className={styles.modalContent} onClick={(e) => e.stopPropagation()}>
<button className={styles.modalCloseButton} onClick={closeModal} aria-label="Close image viewer">
&times;
</button>
{/* For Next/Image, provide intrinsic image width/height if known, or use layout fill with sized parent.
Using layout="responsive" with some default aspect ratio here.
Actual display size will be controlled by CSS for modalImage container. */}
<div className={styles.modalImageContainer}>
<Image src={modalImageSrc} alt={modalImageAlt} layout="intrinsic" width={1200} height={900} objectFit="contain" />
</div>
{modalImageAlt && <p className={styles.modalCaption}>{modalImageAlt}</p>}
</div>
</div>
)}
<footer className={styles.footer}> <footer className={styles.footer}>
<a <a
href="/parvagues/" href="/parvagues/"
......
...@@ -102,7 +102,7 @@ ...@@ -102,7 +102,7 @@
.card { .card {
background-color: #ffffff; background-color: #ffffff;
padding: 1.5rem; padding: 1rem; /* Reduced padding */
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 15px rgba(44, 62, 80, 0.05); box-shadow: 0 4px 15px rgba(44, 62, 80, 0.05);
transition: transform 0.3s ease-out, box-shadow 0.3s ease-out; transition: transform 0.3s ease-out, box-shadow 0.3s ease-out;
...@@ -116,17 +116,24 @@ ...@@ -116,17 +116,24 @@
.card h3 { .card h3 {
font-family: 'Montserrat', sans-serif; font-family: 'Montserrat', sans-serif;
font-size: 1.4rem; /* Slightly larger */ font-size: 1.25rem; /* Reduced font size */
margin-bottom: 0.75rem; margin-bottom: 0.5rem; /* Reduced margin */
color: #16a085; /* Teal/Turquoise for a fresh vibe */ color: #16a085; /* Teal/Turquoise for a fresh vibe */
font-weight: 700; /* Bolder artist names */ font-weight: 700; /* Bolder artist names */
} }
.card p { .card p {
font-size: 0.95rem; font-size: 0.9rem; /* Reduced font size */
line-height: 1.4; /* Added for better readability with smaller font */
color: #34495e; /* Slightly lighter text for description */ color: #34495e; /* Slightly lighter text for description */
} }
/* Specific styling for the emoji line in the card */
.card p:last-child {
margin-top: 0.5rem; /* Add some space above the emoji line */
margin-bottom: 0; /* Remove bottom margin if it's the last element */
}
.countdownContainer { /* Renamed to avoid conflict if .countdown is too generic */ .countdownContainer { /* Renamed to avoid conflict if .countdown is too generic */
text-align: center; text-align: center;
margin: 2.5rem 0; margin: 2.5rem 0;
...@@ -189,6 +196,79 @@ ...@@ -189,6 +196,79 @@
font-size: 0.9rem; font-size: 0.9rem;
} }
.audioEmbed { /* This class is now on the individual player wrappers */
display: flex;
justify-content: center;
/* margin: 1rem 0; Retained for stacking, but might be overridden by flex container gap or item margins */
}
.audioPlayersContainer {
display: flex;
flex-direction: row;
flex-wrap: wrap; /* Allow players to wrap to next line if not enough space */
justify-content: space-around; /* Distribute space around players */
align-items: flex-start; /* Align items to the top if heights differ slightly */
gap: 2rem; /* Spacing between players when side-by-side */
margin-top: 1.5rem; /* Space above the container */
}
.bandcampPlayer {
flex: 1 1 350px;
min-width: 300px;
margin: 0.5rem;
}
.soundcloudPlayer {
flex: 1 1 450px; /* Adjusted flex-basis for SoundCloud player */
min-width: 300px; /* Still allow shrinking if necessary */
max-width: 450px; /* Ensure it doesn't grow beyond 450px unless flex grow allows */
margin: 0.5rem;
}
.bandcampPlayer iframe {
max-width: 100%; /* Responsive within its container */
/* width and height are set inline on the iframe by Bandcamp's embed usually, or by our style attribute */
}
.soundcloudPlayer iframe {
width: 450px;
height: 450px;
max-width: 100%; /* Allows it to shrink if container is smaller than 450px */
}
/* Media query for smaller screens to stack players */
@media (max-width: 768px) { /* Breakpoint can be adjusted based on when it looks best */
.audioPlayersContainer {
flex-direction: column;
align-items: center; /* Center players when stacked */
}
.bandcampPlayer, .soundcloudPlayer {
flex-basis: auto; /* Reset flex-basis for stacked layout */
width: 100%; /* Make players take full width when stacked */
max-width: 400px; /* Optional: constrain max width when stacked, e.g. for SoundCloud visual player */
margin: 1rem 0; /* Adjust margin for vertical stacking */
}
.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 */
}
.card h3 {
font-size: 1.1rem; /* Further reduce font size on small screens for title/emojis */
}
.countdownContainer span div:first-child { /* The number */
font-size: clamp(1.8rem, 5vw, 3rem);
}
}
.footer a { .footer a {
text-decoration: none; text-decoration: none;
color: #3498db; /* Link color for parvagues */ color: #3498db; /* Link color for parvagues */
...@@ -206,49 +286,160 @@ ...@@ -206,49 +286,160 @@
/* Image gallery styles */ /* Image gallery styles */
.gallery { .gallery {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); /* Slightly smaller min for more items */ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); /* Increased min item width */
gap: 1rem; gap: 1rem;
} }
.gallery div, .gallery a { /* Target divs for background images, and links for youtube placeholders */ /* Common styles for all gallery items (photos and video links) */
.galleryItem {
width: 100%; width: 100%;
height: 180px; /* Taller gallery items */ height: 200px; /* Increased height */
border-radius: 8px; /* Consistent rounding */ border-radius: 8px; /* Consistent rounding */
background-color: #ecf0f1; /* Light placeholder color */ background-color: #ecf0f1; /* Light placeholder color for items without BG image */
display: flex; display: flex; /* Enables flexbox for content alignment */
justify-content: center;
align-items: center;
text-align: center;
font-size: 0.9rem;
color: #7f8c8d;
overflow: hidden; /* Ensure content fits */ overflow: hidden; /* Ensure content fits */
position: relative; position: relative; /* For absolute positioning of children or pseudo-elements */
transition: transform 0.3s ease, box-shadow 0.3s ease; transition: transform 0.3s ease, box-shadow 0.3s ease;
text-decoration: none; /* Remove underline from links if .galleryItem is an <a> */
color: #7f8c8d; /* Default text color, useful for video links */
} }
.gallery div:hover, .gallery a:hover { .galleryItem:hover {
transform: scale(1.03); transform: scale(1.03);
box-shadow: 0 5px 15px rgba(44, 62, 80, 0.1); box-shadow: 0 5px 15px rgba(44, 62, 80, 0.1);
} }
.gallery div span, .gallery a { /* For text overlay on images/video placeholders */ /* Specific styles for photo items (divs with background images) */
.galleryPhotoItem {
background-size: cover;
background-position: center;
justify-content: center; /* Center content horizontally (span) */
align-items: flex-end; /* Align content (span) to the bottom */
min-height: 150px; /* Ensure a minimum height as per user feedback, though .galleryItem height should cover this */
}
/* Styling for the text overlay (alt text) on photo items */
.galleryPhotoItem span {
color: #fff; color: #fff;
background-color: rgba(0,0,0,0.6); background-color: rgba(0,0,0,0.7); /* Slightly darker for better contrast */
padding: 0.3em 0.6em; padding: 0.4em 0.8em; /* Increased padding */
border-radius: 4px; border-radius: 4px 4px 0 0; /* Rounded top corners if at bottom edge */
width: 100%; /* Make span take full width of the item */
text-align: center;
font-size: 0.85rem; /* Slightly smaller font for alt text */
z-index: 1; z-index: 1;
text-decoration: none; /* For links */ box-sizing: border-box; /* Ensure padding doesn't increase width beyond 100% */
} }
.gallery a { /* Specific for youtube links if not using background images */ /* Specific styles for video link items (<a> tags) */
.galleryVideoLink { /* This will be used for the <a> tags for videos */
justify-content: center; /* Center text horizontally */
align-items: center; /* Center text vertically */
text-align: center;
font-size: 0.9rem;
background-color: #333; /* Darker for video links */ background-color: #333; /* Darker for video links */
height: 30px; color: #fff; /* Text color for video links */
color: #fff; /* height: 180px; Inherits from galleryItem. If different height is needed, uncomment and set. */
} }
.gallery a:hover { .galleryVideoLink:hover {
background-color: #444; background-color: #444;
/* Potentially darken the background image or text background on hover too */
}
/* Styles for Photo Modal */
.modalOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.85); /* Darker overlay */
display: flex;
justify-content: center;
align-items: center;
z-index: 1000; /* Ensure it's on top */
cursor: pointer; /* Indicate overlay can be clicked to close */
}
.modalContent {
background-color: #2c3e50; /* Dark theme for modal content box */
padding: 20px;
border-radius: 8px;
box-shadow: 0 5px 25px rgba(0,0,0,0.5);
position: relative; /* For close button positioning */
cursor: default; /* Reset cursor for content area */
max-width: 90vw; /* Max width relative to viewport width */
max-height: 90vh; /* Max height relative to viewport height */
display: flex;
flex-direction: column;
align-items: center;
}
.modalImageContainer {
width: 100%; /* Container takes width of modalContent or image's intrinsic */
max-height: calc(85vh - 80px); /* Adjust based on padding and caption height */
display: flex; /* Helps with centering the image if it's smaller than container */
justify-content: center;
align-items: center;
overflow: hidden; /* In case image is larger than this container */
}
/* next/image specific styling might need to target its internal structure if needed,
but objectFit="contain" and layout="intrinsic" should handle most cases.
The parent .modalImageContainer will constrain its max size. */
.modalImageContainer img { /* Targets the actual img tag from Next/Image */
max-width: 100%;
max-height: 100%; /* Ensure image scales down to fit container */
object-fit: contain; /* Redundant with next/image prop but good fallback */
}
.modalCloseButton {
position: absolute;
top: -10px; /* Position outside slightly or adjust padding */
right: -10px;
background: #e74c3c;
color: white;
border: none;
border-radius: 50%;
width: 35px;
height: 35px;
font-size: 1.8rem;
line-height: 30px; /* Center the '×' */
text-align: center;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
transition: background-color 0.2s ease;
z-index: 1001; /* Above modal content */
} }
.modalCloseButton:hover {
background-color: #c0392b;
}
.modalCaption {
color: #ecf0f1; /* Light text for caption */
text-align: center;
margin-top: 10px;
font-size: 0.9rem;
max-width: 100%;
}
/* Styling for the text overlay on video links (if they have a background image) */
.galleryVideoLink span {
color: #fff;
background-color: rgba(0,0,0,0.7); /* Consistent with photo item caption background */
padding: 0.4em 0.8em;
border-radius: 4px;
z-index: 1;
box-sizing: border-box;
max-width: 90%; /* Prevent text from touching edges if too long */
text-align: center; /* Ensure text within span is centered */
}
/* Remove the old generic .gallery a and .gallery div span if they are fully replaced */
/* The old .gallery a styling for height:30px is now incorporated or decided by .galleryVideoLink with .galleryItem height */
/* Section specific styling for V0 */ /* Section specific styling for V0 */
#v0 h3 { #v0 h3 {
font-family: 'Montserrat', sans-serif; font-family: 'Montserrat', sans-serif;
......
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