Init site Compagnie AspiRêves
This commit is contained in:
commit
3862b32343
22 changed files with 6290 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
use flake
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
.direnv/
|
||||
20
README.md
Normal file
20
README.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<div align="center">
|
||||
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
||||
</div>
|
||||
|
||||
# Run and deploy your AI Studio app
|
||||
|
||||
This contains everything you need to run your app locally.
|
||||
|
||||
View your app in AI Studio: https://ai.studio/apps/888a76b0-af1d-4274-9218-1817cdc461fb
|
||||
|
||||
## Run Locally
|
||||
|
||||
**Prerequisites:** Node.js
|
||||
|
||||
|
||||
1. Install dependencies:
|
||||
`npm install`
|
||||
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
||||
3. Run the app:
|
||||
`npm run dev`
|
||||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1771207753,
|
||||
"narHash": "sha256-b9uG8yN50DRQ6A7JdZBfzq718ryYrlmGgqkRm9OOwCE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d1c15b7d5806069da59e819999d70e1cec0760bf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
20
flake.nix
Normal file
20
flake.nix
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
};
|
||||
|
||||
outputs = { nixpkgs, ... }:
|
||||
let
|
||||
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
|
||||
in
|
||||
{
|
||||
devShells = forAllSystems (pkgs: {
|
||||
default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
nodejs_22
|
||||
];
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
13
index.html
Normal file
13
index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>My Google AI Studio App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
5
metadata.json
Normal file
5
metadata.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Compagnie AspiRêves",
|
||||
"description": "La Compagnie AspiRêves vous embarque sur son petit nuage. Un petit nuage tout doux, poétique, drôle et burlesque.",
|
||||
"requestFramePermissions": []
|
||||
}
|
||||
5205
package-lock.json
generated
Normal file
5205
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
35
package.json
Normal file
35
package.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "react-example",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port=3000 --host=0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"clean": "rm -rf dist",
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "^1.29.0",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"lucide-react": "^0.546.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"vite": "^6.2.0",
|
||||
"express": "^4.21.2",
|
||||
"dotenv": "^17.2.3",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"motion": "^12.23.24"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.14.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "~5.8.2",
|
||||
"vite": "^6.2.0",
|
||||
"@types/express": "^4.17.21"
|
||||
}
|
||||
}
|
||||
41
src/App.tsx
Normal file
41
src/App.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { useState } from 'react';
|
||||
import Navbar from './components/Navbar';
|
||||
import Home from './components/Home';
|
||||
import Spectacles from './components/Spectacles';
|
||||
import Agenda from './components/Agenda';
|
||||
import Ateliers from './components/Ateliers';
|
||||
import Contact from './components/Contact';
|
||||
import Footer from './components/Footer';
|
||||
|
||||
export default function App() {
|
||||
const [currentPage, setCurrentPage] = useState('home');
|
||||
|
||||
const renderPage = () => {
|
||||
switch (currentPage) {
|
||||
case 'home':
|
||||
return <Home onNavigate={setCurrentPage} />;
|
||||
case 'spectacles':
|
||||
return <Spectacles />;
|
||||
case 'agenda':
|
||||
return <Agenda />;
|
||||
case 'ateliers':
|
||||
return <Ateliers />;
|
||||
case 'contact':
|
||||
return <Contact />;
|
||||
default:
|
||||
return <Home onNavigate={setCurrentPage} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-cloud min-h-screen flex flex-col">
|
||||
<Navbar currentPage={currentPage} onNavigate={setCurrentPage} />
|
||||
|
||||
<main className="flex-grow">
|
||||
{renderPage()}
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
99
src/components/Agenda.tsx
Normal file
99
src/components/Agenda.tsx
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { motion } from 'motion/react';
|
||||
import { agenda, spectacles } from '../data';
|
||||
import { MapPin, Ticket, Info } from 'lucide-react';
|
||||
|
||||
export default function Agenda() {
|
||||
const sortedAgenda = [...agenda].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
|
||||
|
||||
return (
|
||||
<div className="pt-32 pb-24 min-h-screen">
|
||||
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<h1 className="font-serif text-5xl md:text-7xl text-night mb-6">Agenda</h1>
|
||||
<p className="font-sans text-night/60 max-w-2xl mx-auto text-lg">
|
||||
Retrouvez toutes nos prochaines représentations.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="space-y-8">
|
||||
{sortedAgenda.map((event, index) => {
|
||||
const spectacle = spectacles.find(s => s.id === event.spectacleId);
|
||||
const dateObj = new Date(event.date);
|
||||
const isPast = dateObj < new Date();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={event.id}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className={`group relative flex flex-col md:flex-row gap-8 p-8 rounded-3xl border transition-all ${
|
||||
isPast ? 'opacity-50 grayscale bg-night/5 border-transparent' : 'bg-white border-night/5 shadow-sm hover:shadow-xl hover:shadow-night/5'
|
||||
}`}
|
||||
>
|
||||
{/* Date Block */}
|
||||
<div className="flex-shrink-0 flex flex-col items-center justify-center w-24 h-24 md:w-32 md:h-32 rounded-2xl bg-night text-cloud">
|
||||
<span className="font-sans text-sm uppercase tracking-widest opacity-70">
|
||||
{dateObj.toLocaleString('fr-FR', { month: 'short' })}
|
||||
</span>
|
||||
<span className="font-serif text-4xl md:text-5xl">
|
||||
{dateObj.getDate()}
|
||||
</span>
|
||||
<span className="font-sans text-xs opacity-50 mt-1">
|
||||
{dateObj.getFullYear()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Info Block */}
|
||||
<div className="flex-grow flex flex-col justify-center space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-sans text-xs uppercase tracking-widest text-star font-bold">
|
||||
{spectacle?.title}
|
||||
</span>
|
||||
{isPast && <span className="text-[10px] uppercase tracking-tighter bg-night/10 px-2 py-0.5 rounded text-night/50">Passé</span>}
|
||||
</div>
|
||||
<h3 className="font-serif text-3xl text-night">{spectacle?.title}</h3>
|
||||
<div className="flex items-center gap-2 text-night/60 font-sans">
|
||||
<MapPin size={16} className="text-star" />
|
||||
<span>{event.location}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Block */}
|
||||
<div className="flex-shrink-0 flex items-center">
|
||||
{event.bookingLink ? (
|
||||
<a
|
||||
href={event.bookingLink}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="w-full md:w-auto flex items-center justify-center gap-2 bg-night text-cloud px-6 py-3 rounded-full font-sans text-sm font-medium hover:bg-night/80 transition-colors"
|
||||
>
|
||||
<Ticket size={18} />
|
||||
Réserver
|
||||
</a>
|
||||
) : (
|
||||
<div className="w-full md:w-auto flex items-center justify-center gap-2 border border-night/10 text-night/40 px-6 py-3 rounded-full font-sans text-sm italic">
|
||||
<Info size={18} />
|
||||
Entrée libre / Sur place
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{sortedAgenda.length === 0 && (
|
||||
<div className="text-center py-20 border-2 border-dashed border-night/10 rounded-3xl">
|
||||
<p className="font-serif text-2xl text-night/40 italic">Aucune date prévue pour le moment...</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
89
src/components/Ateliers.tsx
Normal file
89
src/components/Ateliers.tsx
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { motion } from 'motion/react';
|
||||
import { Mail, Clock, MapPin, Users } from 'lucide-react';
|
||||
import { companyInfo } from '../data';
|
||||
|
||||
export default function Ateliers() {
|
||||
return (
|
||||
<div className="pt-32 pb-24 min-h-screen">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
>
|
||||
<h1 className="font-serif text-5xl md:text-7xl text-night mb-8">Ateliers Théâtre</h1>
|
||||
<div className="space-y-6 font-sans text-lg text-night/70 leading-relaxed">
|
||||
<p>
|
||||
La Compagnie AspiRêves propose des ateliers pour petits et grands enfants, pour que vous aussi vous voliez sur votre petit nuage.
|
||||
</p>
|
||||
<p>
|
||||
Nos ateliers sont basés sur le jeu, l'expression corporelle, la découverte du clown et la pratique théâtrale dans la bienveillance et le plaisir.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 space-y-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 rounded-2xl bg-star/10 flex items-center justify-center text-star flex-shrink-0">
|
||||
<Users size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-serif text-xl text-night">Pour tous les âges</h3>
|
||||
<p className="font-sans text-night/60">Enfants, adolescents et adultes.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 rounded-2xl bg-star/10 flex items-center justify-center text-star flex-shrink-0">
|
||||
<Clock size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-serif text-xl text-night">Horaires & Tarifs</h3>
|
||||
<p className="font-sans text-night/60">Contactez-nous pour recevoir la plaquette complète.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 rounded-2xl bg-star/10 flex items-center justify-center text-star flex-shrink-0">
|
||||
<MapPin size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-serif text-xl text-night">Lieux</h3>
|
||||
<p className="font-sans text-night/60">Interventions dans le Tarn et l'Hérault.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-12">
|
||||
<a
|
||||
href={`mailto:${companyInfo.emails.ateliers}`}
|
||||
className="inline-flex items-center gap-3 bg-night text-cloud px-8 py-4 rounded-full font-sans font-medium hover:bg-night/90 transition-all"
|
||||
>
|
||||
<Mail size={18} />
|
||||
S'inscrire ou se renseigner
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
className="relative"
|
||||
>
|
||||
<div className="aspect-square rounded-full overflow-hidden border-8 border-white shadow-2xl">
|
||||
<img
|
||||
src="https://picsum.photos/seed/theater-workshop/800/800"
|
||||
alt="Atelier Théâtre"
|
||||
className="w-full h-full object-cover"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
</div>
|
||||
{/* Decorative element */}
|
||||
<div className="absolute -bottom-10 -right-10 w-40 h-40 bg-star rounded-full mix-blend-multiply filter blur-3xl opacity-30 animate-pulse"></div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
105
src/components/Contact.tsx
Normal file
105
src/components/Contact.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import { motion } from 'motion/react';
|
||||
import { Mail, Instagram, Facebook, Youtube, Send } from 'lucide-react';
|
||||
import { companyInfo } from '../data';
|
||||
|
||||
export default function Contact() {
|
||||
return (
|
||||
<div className="pt-32 pb-24 min-h-screen">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-20"
|
||||
>
|
||||
<h1 className="font-serif text-5xl md:text-7xl text-night mb-6">Nous Joindre</h1>
|
||||
<p className="font-sans text-night/60 max-w-2xl mx-auto text-lg">
|
||||
Une question ? Un projet ? N'hésitez pas à nous contacter, nous vous répondrons avec plaisir.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
|
||||
{/* Email Cards */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="space-y-6"
|
||||
>
|
||||
<div className="bg-white p-8 rounded-3xl border border-night/5 shadow-sm hover:shadow-xl hover:shadow-night/5 transition-all">
|
||||
<div className="w-12 h-12 rounded-2xl bg-night text-cloud flex items-center justify-center mb-6">
|
||||
<Mail size={24} />
|
||||
</div>
|
||||
<h3 className="font-serif text-2xl text-night mb-2">Diffusion & Programmation</h3>
|
||||
<p className="font-sans text-night/60 mb-6">Pour toute demande concernant nos spectacles.</p>
|
||||
<a
|
||||
href={`mailto:${companyInfo.emails.pro}`}
|
||||
className="inline-flex items-center gap-2 text-night font-sans font-semibold hover:text-star transition-colors"
|
||||
>
|
||||
{companyInfo.emails.pro}
|
||||
<Send size={14} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-8 rounded-3xl border border-night/5 shadow-sm hover:shadow-xl hover:shadow-night/5 transition-all">
|
||||
<div className="w-12 h-12 rounded-2xl bg-star text-night flex items-center justify-center mb-6">
|
||||
<Mail size={24} />
|
||||
</div>
|
||||
<h3 className="font-serif text-2xl text-night mb-2">Ateliers & Pédagogie</h3>
|
||||
<p className="font-sans text-night/60 mb-6">Pour les inscriptions et renseignements ateliers.</p>
|
||||
<a
|
||||
href={`mailto:${companyInfo.emails.ateliers}`}
|
||||
className="inline-flex items-center gap-2 text-night font-sans font-semibold hover:text-star transition-colors"
|
||||
>
|
||||
{companyInfo.emails.ateliers}
|
||||
<Send size={14} />
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Social & Info */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="bg-night text-cloud p-12 rounded-3xl flex flex-col justify-between"
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-serif text-4xl mb-8">Suivez nos aventures</h3>
|
||||
<p className="font-sans text-cloud/60 text-lg mb-12 leading-relaxed">
|
||||
Rejoignez-nous sur les réseaux sociaux pour découvrir les coulisses, les photos de tournée et les actualités de la compagnie.
|
||||
</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
<a href={companyInfo.socials.instagram} target="_blank" rel="noreferrer" className="flex items-center gap-4 group">
|
||||
<div className="w-12 h-12 rounded-full border border-cloud/20 flex items-center justify-center group-hover:border-star group-hover:text-star transition-all">
|
||||
<Instagram size={20} />
|
||||
</div>
|
||||
<span className="font-sans text-lg group-hover:text-star transition-colors">Instagram</span>
|
||||
</a>
|
||||
<a href={companyInfo.socials.facebook} target="_blank" rel="noreferrer" className="flex items-center gap-4 group">
|
||||
<div className="w-12 h-12 rounded-full border border-cloud/20 flex items-center justify-center group-hover:border-star group-hover:text-star transition-all">
|
||||
<Facebook size={20} />
|
||||
</div>
|
||||
<span className="font-sans text-lg group-hover:text-star transition-colors">Facebook</span>
|
||||
</a>
|
||||
<a href={companyInfo.socials.youtube} target="_blank" rel="noreferrer" className="flex items-center gap-4 group">
|
||||
<div className="w-12 h-12 rounded-full border border-cloud/20 flex items-center justify-center group-hover:border-star group-hover:text-star transition-all">
|
||||
<Youtube size={20} />
|
||||
</div>
|
||||
<span className="font-sans text-lg group-hover:text-star transition-colors">YouTube</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 pt-12 border-t border-cloud/10">
|
||||
<p className="font-serif text-xl italic text-cloud/40">
|
||||
"Le théâtre est une nourriture aussi indispensable que le pain et le vin."
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
76
src/components/Footer.tsx
Normal file
76
src/components/Footer.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { Instagram, Facebook, Youtube, Mail } from 'lucide-react';
|
||||
import { companyInfo } from '../data';
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="bg-night text-cloud py-16 border-t border-cloud/10">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-12 md:gap-8">
|
||||
|
||||
{/* Brand */}
|
||||
<div className="col-span-1 md:col-span-2">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-10 rounded-full bg-cloud flex items-center justify-center overflow-hidden">
|
||||
<span className="text-night font-serif italic text-xl">A</span>
|
||||
</div>
|
||||
<span className="font-serif text-2xl font-medium tracking-wide text-cloud">
|
||||
AspiRêves
|
||||
</span>
|
||||
</div>
|
||||
<p className="font-sans text-cloud/60 max-w-sm leading-relaxed mb-8">
|
||||
{companyInfo.description}
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<a href={companyInfo.socials.instagram} target="_blank" rel="noreferrer" className="w-10 h-10 rounded-full border border-cloud/20 flex items-center justify-center text-cloud/60 hover:text-cloud hover:border-cloud transition-colors">
|
||||
<span className="sr-only">Instagram</span>
|
||||
<Instagram size={18} />
|
||||
</a>
|
||||
<a href={companyInfo.socials.facebook} target="_blank" rel="noreferrer" className="w-10 h-10 rounded-full border border-cloud/20 flex items-center justify-center text-cloud/60 hover:text-cloud hover:border-cloud transition-colors">
|
||||
<span className="sr-only">Facebook</span>
|
||||
<Facebook size={18} />
|
||||
</a>
|
||||
<a href={companyInfo.socials.youtube} target="_blank" rel="noreferrer" className="w-10 h-10 rounded-full border border-cloud/20 flex items-center justify-center text-cloud/60 hover:text-cloud hover:border-cloud transition-colors">
|
||||
<span className="sr-only">YouTube</span>
|
||||
<Youtube size={18} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact */}
|
||||
<div>
|
||||
<h4 className="font-sans text-sm uppercase tracking-widest text-cloud/40 mb-6">Contact</h4>
|
||||
<ul className="space-y-4 font-sans text-cloud/80">
|
||||
<li>
|
||||
<span className="block text-xs text-cloud/50 uppercase tracking-wider mb-1">Professionnels</span>
|
||||
<a href={`mailto:${companyInfo.emails.pro}`} className="hover:text-star transition-colors flex items-center gap-2">
|
||||
<Mail size={14} />
|
||||
{companyInfo.emails.pro}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<span className="block text-xs text-cloud/50 uppercase tracking-wider mb-1">Ateliers Théâtre</span>
|
||||
<a href={`mailto:${companyInfo.emails.ateliers}`} className="hover:text-star transition-colors flex items-center gap-2">
|
||||
<Mail size={14} />
|
||||
{companyInfo.emails.ateliers}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Legal */}
|
||||
<div>
|
||||
<h4 className="font-sans text-sm uppercase tracking-widest text-cloud/40 mb-6">Informations</h4>
|
||||
<ul className="space-y-3 font-sans text-cloud/80 text-sm">
|
||||
<li><a href="#" className="hover:text-cloud transition-colors">Mentions légales</a></li>
|
||||
<li><a href="#" className="hover:text-cloud transition-colors">Politique de confidentialité</a></li>
|
||||
<li className="pt-4 text-cloud/40">
|
||||
© {new Date().getFullYear()} Compagnie AspiRêves. Tous droits réservés.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
180
src/components/Home.tsx
Normal file
180
src/components/Home.tsx
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import { motion } from 'motion/react';
|
||||
import { Play, Calendar, ArrowRight } from 'lucide-react';
|
||||
import { companyInfo, spectacles, agenda } from '../data';
|
||||
|
||||
interface HomeProps {
|
||||
onNavigate: (page: string) => void;
|
||||
}
|
||||
|
||||
export default function Home({ onNavigate }: HomeProps) {
|
||||
// Get the next 2 upcoming shows
|
||||
const upcomingShows = agenda
|
||||
.filter(event => new Date(event.date) >= new Date())
|
||||
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
|
||||
.slice(0, 2);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-cloud">
|
||||
{/* Hero Section */}
|
||||
<section className="relative h-[90vh] flex items-center justify-center overflow-hidden">
|
||||
{/* Abstract Cloud/Dream Background Elements */}
|
||||
<div className="absolute top-1/4 -left-20 w-96 h-96 bg-star/10 rounded-full mix-blend-multiply filter blur-3xl opacity-70 animate-blob"></div>
|
||||
<div className="absolute top-1/3 -right-20 w-96 h-96 bg-night/5 rounded-full mix-blend-multiply filter blur-3xl opacity-70 animate-blob animation-delay-2000"></div>
|
||||
<div className="absolute -bottom-32 left-1/3 w-96 h-96 bg-star/5 rounded-full mix-blend-multiply filter blur-3xl opacity-70 animate-blob animation-delay-4000"></div>
|
||||
|
||||
<div className="relative z-10 max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
>
|
||||
<h1 className="font-serif text-6xl md:text-8xl lg:text-9xl text-night leading-none mb-6">
|
||||
Embarquez sur <br />
|
||||
<span className="italic font-light text-night/80">notre nuage</span>
|
||||
</h1>
|
||||
<p className="font-sans text-lg md:text-xl text-night/70 max-w-2xl mx-auto leading-relaxed mb-10">
|
||||
{companyInfo.description}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-6">
|
||||
<button
|
||||
onClick={() => onNavigate('spectacles')}
|
||||
className="group flex items-center gap-3 bg-night text-cloud px-8 py-4 rounded-full font-sans font-medium tracking-wide hover:bg-night/90 transition-all hover:scale-105"
|
||||
>
|
||||
Découvrir les spectacles
|
||||
<ArrowRight size={18} className="group-hover:translate-x-1 transition-transform" />
|
||||
</button>
|
||||
|
||||
<a
|
||||
href={`https://www.youtube.com/watch?v=${companyInfo.teaserVideoId}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="group flex items-center gap-3 text-night font-sans font-medium hover:text-star transition-colors"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-full border border-night/20 flex items-center justify-center group-hover:border-star group-hover:bg-star/10 transition-all">
|
||||
<Play size={18} className="ml-1" />
|
||||
</div>
|
||||
Voir le teaser
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Featured Spectacles Section */}
|
||||
<section className="py-24 bg-white">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-end mb-16">
|
||||
<div>
|
||||
<h2 className="font-serif text-4xl md:text-5xl text-night mb-4">À l'affiche</h2>
|
||||
<p className="font-sans text-night/60 uppercase tracking-widest text-sm">Créations Jeune Public</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onNavigate('spectacles')}
|
||||
className="hidden md:flex items-center gap-2 text-night/60 hover:text-night transition-colors font-sans uppercase tracking-wider text-sm"
|
||||
>
|
||||
Tout voir <ArrowRight size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{spectacles.map((spectacle, index) => (
|
||||
<motion.div
|
||||
key={spectacle.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: index * 0.2, duration: 0.6 }}
|
||||
className="group cursor-pointer"
|
||||
onClick={() => onNavigate('spectacles')}
|
||||
>
|
||||
<div className="relative aspect-[4/5] overflow-hidden rounded-2xl mb-6 bg-night/5">
|
||||
<img
|
||||
src={spectacle.image}
|
||||
alt={spectacle.title}
|
||||
className="object-cover w-full h-full transform group-hover:scale-105 transition-transform duration-700 ease-in-out"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-night/80 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end p-6">
|
||||
<span className="text-cloud font-sans text-sm uppercase tracking-wider border border-cloud/30 rounded-full px-4 py-2 backdrop-blur-sm">
|
||||
En savoir plus
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="font-serif text-3xl text-night mb-2">{spectacle.title}</h3>
|
||||
<div className="flex items-center gap-3 font-sans text-sm text-night/60 uppercase tracking-wider">
|
||||
<span>{spectacle.age}</span>
|
||||
<span className="w-1 h-1 rounded-full bg-star"></span>
|
||||
<span>{spectacle.duration}</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Quick Agenda Section */}
|
||||
<section className="py-24 bg-night text-cloud">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
|
||||
<div>
|
||||
<h2 className="font-serif text-5xl md:text-6xl mb-6">Prochaines dates</h2>
|
||||
<p className="font-sans text-cloud/70 text-lg mb-10 max-w-md">
|
||||
Retrouvez-nous près de chez vous. Venez partager un moment suspendu avec la compagnie.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => onNavigate('agenda')}
|
||||
className="inline-flex items-center gap-3 border border-cloud/30 hover:border-cloud px-8 py-4 rounded-full font-sans uppercase tracking-wider text-sm transition-colors"
|
||||
>
|
||||
Voir tout l'agenda
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{upcomingShows.length > 0 ? (
|
||||
upcomingShows.map((event, index) => {
|
||||
const spec = spectacles.find(s => s.id === event.spectacleId);
|
||||
const dateObj = new Date(event.date);
|
||||
return (
|
||||
<motion.div
|
||||
key={event.id}
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: index * 0.2 }}
|
||||
className="group flex flex-col sm:flex-row gap-6 p-6 rounded-2xl border border-cloud/10 hover:bg-cloud/5 transition-colors cursor-pointer"
|
||||
onClick={() => onNavigate('agenda')}
|
||||
>
|
||||
<div className="flex-shrink-0 flex flex-col items-center justify-center w-24 h-24 rounded-xl bg-cloud/10 text-cloud">
|
||||
<span className="font-sans text-sm uppercase tracking-widest opacity-70">
|
||||
{dateObj.toLocaleString('fr-FR', { month: 'short' })}
|
||||
</span>
|
||||
<span className="font-serif text-4xl">
|
||||
{dateObj.getDate()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-grow flex flex-col justify-center">
|
||||
<h4 className="font-serif text-2xl mb-1">{spec?.title}</h4>
|
||||
<div className="flex items-center gap-2 font-sans text-sm text-cloud/60">
|
||||
<Calendar size={14} />
|
||||
<span>{event.location}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end sm:opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<ArrowRight className="text-star" />
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="p-8 border border-cloud/10 rounded-2xl text-center">
|
||||
<p className="font-sans text-cloud/60 italic">De nouvelles dates seront annoncées prochainement.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
121
src/components/Navbar.tsx
Normal file
121
src/components/Navbar.tsx
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { Menu, X, Instagram, Facebook, Youtube } from 'lucide-react';
|
||||
import { companyInfo } from '../data';
|
||||
|
||||
interface NavbarProps {
|
||||
currentPage: string;
|
||||
onNavigate: (page: string) => void;
|
||||
}
|
||||
|
||||
export default function Navbar({ currentPage, onNavigate }: NavbarProps) {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 20);
|
||||
};
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
const navLinks = [
|
||||
{ id: 'home', label: 'Accueil' },
|
||||
{ id: 'spectacles', label: 'Spectacles' },
|
||||
{ id: 'agenda', label: 'Agenda' },
|
||||
{ id: 'ateliers', label: 'Ateliers Théâtre' },
|
||||
{ id: 'contact', label: 'Contact' },
|
||||
];
|
||||
|
||||
const handleNavClick = (id: string) => {
|
||||
onNavigate(id);
|
||||
setIsMobileMenuOpen(false);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
};
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={`fixed top-0 w-full z-50 transition-all duration-300 ${
|
||||
isScrolled ? 'bg-cloud/90 backdrop-blur-md shadow-sm py-3' : 'bg-transparent py-5'
|
||||
}`}
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-center">
|
||||
{/* Logo Area */}
|
||||
<div
|
||||
className="flex items-center gap-3 cursor-pointer group"
|
||||
onClick={() => handleNavClick('home')}
|
||||
>
|
||||
{/* Placeholder for the actual logo image */}
|
||||
<div className="w-10 h-10 rounded-full bg-night flex items-center justify-center overflow-hidden transition-transform group-hover:scale-105">
|
||||
<span className="text-cloud font-serif italic text-xl">A</span>
|
||||
</div>
|
||||
<span className="font-serif text-2xl font-medium tracking-wide text-night">
|
||||
AspiRêves
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden md:flex items-center space-x-8">
|
||||
{navLinks.map((link) => (
|
||||
<button
|
||||
key={link.id}
|
||||
onClick={() => handleNavClick(link.id)}
|
||||
className={`text-sm tracking-wide uppercase transition-colors relative group ${
|
||||
currentPage === link.id ? 'text-night font-medium' : 'text-night/60 hover:text-night'
|
||||
}`}
|
||||
>
|
||||
{link.label}
|
||||
<span
|
||||
className={`absolute -bottom-1 left-0 h-px bg-night transition-all duration-300 ${
|
||||
currentPage === link.id ? 'w-full' : 'w-0 group-hover:w-full'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<div className="md:hidden flex items-center">
|
||||
<button
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
className="text-night p-2 focus:outline-none"
|
||||
>
|
||||
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{isMobileMenuOpen && (
|
||||
<div className="md:hidden absolute top-full left-0 w-full bg-cloud shadow-lg border-t border-night/5">
|
||||
<div className="px-4 pt-2 pb-6 space-y-1">
|
||||
{navLinks.map((link) => (
|
||||
<button
|
||||
key={link.id}
|
||||
onClick={() => handleNavClick(link.id)}
|
||||
className={`block w-full text-left px-3 py-4 text-base uppercase tracking-wider ${
|
||||
currentPage === link.id ? 'text-night font-medium bg-night/5' : 'text-night/70'
|
||||
}`}
|
||||
>
|
||||
{link.label}
|
||||
</button>
|
||||
))}
|
||||
<div className="flex gap-4 px-3 pt-6 pb-2">
|
||||
<a href={companyInfo.socials.instagram} target="_blank" rel="noreferrer" className="text-night/60 hover:text-night">
|
||||
<Instagram size={20} />
|
||||
</a>
|
||||
<a href={companyInfo.socials.facebook} target="_blank" rel="noreferrer" className="text-night/60 hover:text-night">
|
||||
<Facebook size={20} />
|
||||
</a>
|
||||
<a href={companyInfo.socials.youtube} target="_blank" rel="noreferrer" className="text-night/60 hover:text-night">
|
||||
<Youtube size={20} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
78
src/components/Spectacles.tsx
Normal file
78
src/components/Spectacles.tsx
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { motion } from 'motion/react';
|
||||
import { spectacles } from '../data';
|
||||
import { ExternalLink, Clock, Users } from 'lucide-react';
|
||||
|
||||
export default function Spectacles() {
|
||||
return (
|
||||
<div className="pt-32 pb-24 min-h-screen">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-20"
|
||||
>
|
||||
<h1 className="font-serif text-5xl md:text-7xl text-night mb-6">Nos Spectacles</h1>
|
||||
<p className="font-sans text-night/60 max-w-2xl mx-auto text-lg">
|
||||
Des créations originales pour petits et grands, mêlant poésie, humour et burlesque.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="space-y-32">
|
||||
{spectacles.map((spectacle, index) => (
|
||||
<motion.div
|
||||
key={spectacle.id}
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className={`flex flex-col ${index % 2 === 1 ? 'md:flex-row-reverse' : 'md:flex-row'} gap-12 items-center`}
|
||||
>
|
||||
{/* Image Side */}
|
||||
<div className="w-full md:w-1/2">
|
||||
<div className="relative aspect-[4/3] overflow-hidden rounded-3xl shadow-2xl shadow-night/10 group">
|
||||
<img
|
||||
src={spectacle.image}
|
||||
alt={spectacle.title}
|
||||
className="object-cover w-full h-full transition-transform duration-700 group-hover:scale-105"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-night/10 group-hover:bg-transparent transition-colors duration-300" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content Side */}
|
||||
<div className="w-full md:w-1/2 space-y-6">
|
||||
<h2 className="font-serif text-4xl md:text-5xl text-night">{spectacle.title}</h2>
|
||||
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<div className="flex items-center gap-2 bg-night/5 px-4 py-2 rounded-full text-sm font-sans text-night/70">
|
||||
<Users size={16} />
|
||||
<span>{spectacle.age}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-night/5 px-4 py-2 rounded-full text-sm font-sans text-night/70">
|
||||
<Clock size={16} />
|
||||
<span>{spectacle.duration}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="font-sans text-lg text-night/70 leading-relaxed">
|
||||
{spectacle.summary}
|
||||
</p>
|
||||
|
||||
<div className="pt-6 flex flex-wrap gap-4">
|
||||
<button className="bg-night text-cloud px-8 py-3 rounded-full font-sans font-medium hover:bg-night/90 transition-all">
|
||||
Dossier Pédagogique
|
||||
</button>
|
||||
<button className="border border-night/20 text-night px-8 py-3 rounded-full font-sans font-medium hover:bg-night/5 transition-all flex items-center gap-2">
|
||||
Voir les photos <ExternalLink size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
80
src/data.ts
Normal file
80
src/data.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
export const companyInfo = {
|
||||
name: "Compagnie AspiRêves",
|
||||
description: "La Compagnie AspiRêves vous embarque sur son petit nuage. Un petit nuage tout doux, poétique, drôle et burlesque. Mais attention, ça tangue ! C'est du rêve qui secoue, qui trébuche, des songes d'adultes, des jeux d'enfants... c'est du spectacle vivant.",
|
||||
emails: {
|
||||
pro: "aspireves@gmail.com",
|
||||
ateliers: "compagnieaspireves@gmail.com"
|
||||
},
|
||||
socials: {
|
||||
instagram: "https://www.instagram.com/compagnieaspireves",
|
||||
facebook: "https://www.facebook.com/compagnieaspireves",
|
||||
youtube: "https://www.youtube.com/@aspireves"
|
||||
},
|
||||
teaserVideoId: "fImFdHPvXa8" // Extrait de https://www.youtube.com/watch?v=fImFdHPvXa8
|
||||
};
|
||||
|
||||
export const spectacles = [
|
||||
{
|
||||
id: "bassibulle",
|
||||
title: "BASSIBULLE",
|
||||
age: "De 6 mois à 5 ans",
|
||||
duration: "30 min",
|
||||
summary: "Un voyage sensoriel et poétique où les bulles de savon racontent des histoires. Une expérience immersive pour les tout-petits.",
|
||||
image: "https://picsum.photos/seed/bassibulle/800/600?blur=2",
|
||||
gallery: [
|
||||
"https://picsum.photos/seed/bassi1/400/300",
|
||||
"https://picsum.photos/seed/bassi2/400/300"
|
||||
],
|
||||
dossierPro: "#"
|
||||
},
|
||||
{
|
||||
id: "minus",
|
||||
title: "MiNUS",
|
||||
age: "À partir de 3 ans",
|
||||
duration: "45 min",
|
||||
summary: "L'histoire d'un petit être qui découvre le monde. Un spectacle clownesque et tendre sur la différence et l'acceptation.",
|
||||
image: "https://picsum.photos/seed/minus/800/600",
|
||||
gallery: [
|
||||
"https://picsum.photos/seed/minus1/400/300",
|
||||
"https://picsum.photos/seed/minus2/400/300"
|
||||
],
|
||||
dossierPro: "#"
|
||||
},
|
||||
{
|
||||
id: "cadeau-surprise",
|
||||
title: "Le Cadeau Surprise !",
|
||||
age: "À partir de 4 ans",
|
||||
duration: "50 min",
|
||||
summary: "Que se cache-t-il dans cette boîte géante ? Une aventure rocambolesque pleine de rebondissements et de magie.",
|
||||
image: "https://picsum.photos/seed/cadeau/800/600",
|
||||
gallery: [
|
||||
"https://picsum.photos/seed/cadeau1/400/300",
|
||||
"https://picsum.photos/seed/cadeau2/400/300"
|
||||
],
|
||||
dossierPro: "#"
|
||||
}
|
||||
];
|
||||
|
||||
export const agenda = [
|
||||
{
|
||||
id: "1",
|
||||
date: "2024-05-15T10:30:00",
|
||||
location: "Théâtre de la Plume, Montpellier",
|
||||
spectacleId: "bassibulle",
|
||||
bookingLink: "https://example.com/book"
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
date: "2024-06-02T15:00:00",
|
||||
location: "Médiathèque Émile Zola, Montpellier",
|
||||
spectacleId: "minus",
|
||||
bookingLink: null // Entrée libre
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
date: "2024-07-10T16:00:00",
|
||||
location: "Festival d'Avignon Off",
|
||||
spectacleId: "cadeau-surprise",
|
||||
bookingLink: "https://example.com/book-avignon"
|
||||
}
|
||||
];
|
||||
33
src/index.css
Normal file
33
src/index.css
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500&family=Outfit:wght@300;400;500;600&display=swap');
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--font-sans: "Outfit", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-serif: "Cormorant Garamond", ui-serif, Georgia, serif;
|
||||
|
||||
--color-cloud: #FAF9F6;
|
||||
--color-night: #18181B;
|
||||
--color-star: #FBBF24;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-cloud);
|
||||
color: var(--color-night);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Custom scrollbar for a polished feel */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--color-cloud);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d4d4d8;
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #a1a1aa;
|
||||
}
|
||||
10
src/main.tsx
Normal file
10
src/main.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import {StrictMode} from 'react';
|
||||
import {createRoot} from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
import './index.css';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"module": "ESNext",
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
},
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
24
vite.config.ts
Normal file
24
vite.config.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import tailwindcss from '@tailwindcss/vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
import {defineConfig, loadEnv} from 'vite';
|
||||
|
||||
export default defineConfig(({mode}) => {
|
||||
const env = loadEnv(mode, '.', '');
|
||||
return {
|
||||
plugins: [react(), tailwindcss()],
|
||||
define: {
|
||||
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, '.'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
// HMR is disabled in AI Studio via DISABLE_HMR env var.
|
||||
// Do not modifyâfile watching is disabled to prevent flickering during agent edits.
|
||||
hmr: process.env.DISABLE_HMR !== 'true',
|
||||
},
|
||||
};
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue