Unverified Commit 1e64cacb by Paul-Louis NECH Committed by GitHub

Merge branch 'main' into parvagues-and-dunbar

parents a3812f69 fd0f4c1c
import React from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { FaEnvelope, FaInstagram, FaTwitter } from 'react-icons/fa';
import { FaEnvelope, FaInstagram, FaTwitter, FaYoutube } from 'react-icons/fa';
import { SiMastodon, SiBluesky } from 'react-icons/si';
import styles from '@/styles/parvagues.module.css';
......@@ -11,7 +11,8 @@ export default function ParVaguesFooter() {
{ 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' }
{ icon: <FaInstagram />, label: 'instagram', url: 'https://instagram.com/parvagues.mp3' },
{ icon: <FaYoutube />, label: 'youtube', url: 'https://www.youtube.com/@parvagues' }
];
const year = new Date().getFullYear();
......
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 { useState, useEffect } from 'react'; // Added useState and useEffect
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 [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 = [
{ 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>
<h3 style={{ paddingTop: '1em' }}>
ondes de rythmes, plages sonores, et code source.
</h3>
<h2>21 juin, labenne océan.<br />🪐</h2>
</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} <span className={styles.cardEmojis}>~ {item.emojis} ~</span></h3>
<p>{item.description}</p>
{/* The emoji line is now part of the h3, the extra <p> for emojis and <br/> are removed */}
</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={photo.src} // Using src as key for stability if array order changes but src is unique
className={`${styles.galleryItem} ${styles.galleryPhotoItem}`} // Ensure both classes are applied
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>
<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) => (
<a
key={index}
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>
))}
</div>
<h3 style={{marginTop: '2rem'}}>lineup v0</h3>
<ul>
{v0_content.lineup.map((artist, index) => (
<li key={index}>{artist}</li>
))}
</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 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'}}>
<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
</a>
<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>
{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}>
<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.
This image diff could not be displayed because it is too large. You can view the blob instead.
This image diff could not be displayed because it is too large. You can view the blob instead.
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: 1rem; /* Reduced padding */
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.25rem; /* Reduced font size */
margin-bottom: 0.5rem; /* Reduced margin */
color: #16a085; /* Teal/Turquoise for a fresh vibe */
font-weight: 700; /* Bolder artist names */
}
.card p {
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 */
}
/* 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 */
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;
}
.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 {
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(220px, 1fr)); /* Increased min item width */
gap: 1rem;
}
/* Common styles for all gallery items (photos and video links) */
.galleryItem {
width: 100%;
height: 200px; /* Increased height */
border-radius: 8px; /* Consistent rounding */
background-color: #ecf0f1; /* Light placeholder color for items without BG image */
display: flex; /* Enables flexbox for content alignment */
overflow: hidden; /* Ensure content fits */
position: relative; /* For absolute positioning of children or pseudo-elements */
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 */
}
.galleryItem:hover {
transform: scale(1.03);
box-shadow: 0 5px 15px rgba(44, 62, 80, 0.1);
}
/* 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;
background-color: rgba(0,0,0,0.7); /* Slightly darker for better contrast */
padding: 0.4em 0.8em; /* Increased padding */
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;
box-sizing: border-box; /* Ensure padding doesn't increase width beyond 100% */
}
/* 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 */
color: #fff; /* Text color for video links */
/* height: 180px; Inherits from galleryItem. If different height is needed, uncomment and set. */
}
.galleryVideoLink:hover {
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 */
#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