Compare commits
4 Commits
cd9e914713
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8a0336ae5 | ||
|
|
fce84ff4a6 | ||
|
|
823ca68119 | ||
|
|
7a65e7a1f4 |
|
After Width: | Height: | Size: 406 KiB |
|
After Width: | Height: | Size: 319 KiB |
BIN
src/assets/gallery/gal1.jpg
Normal file
|
After Width: | Height: | Size: 396 KiB |
BIN
src/assets/gallery/gal2.jpg
Normal file
|
After Width: | Height: | Size: 330 KiB |
BIN
src/assets/gallery/gal3.jpg
Normal file
|
After Width: | Height: | Size: 378 KiB |
BIN
src/assets/gallery/gal4.jpg
Normal file
|
After Width: | Height: | Size: 333 KiB |
BIN
src/assets/gallery/gal5.jpg
Normal file
|
After Width: | Height: | Size: 462 KiB |
BIN
src/assets/gallery/gal6.jpg
Normal file
|
After Width: | Height: | Size: 424 KiB |
@@ -3,6 +3,7 @@ import { motion, AnimatePresence } from "framer-motion";
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { ChevronUp } from "lucide-react";
|
import { ChevronUp } from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { SECTIONS } from "@/data/event-data";
|
||||||
|
|
||||||
/** Botón flotante de reserva + scroll to top */
|
/** Botón flotante de reserva + scroll to top */
|
||||||
const FloatingButton = () => {
|
const FloatingButton = () => {
|
||||||
@@ -30,7 +31,7 @@ const FloatingButton = () => {
|
|||||||
className="animate-pulse-glow rounded-full px-6 shadow-elevated"
|
className="animate-pulse-glow rounded-full px-6 shadow-elevated"
|
||||||
asChild
|
asChild
|
||||||
>
|
>
|
||||||
<a href="#booking" className="inline-flex items-center gap-2">
|
<a href={SECTIONS.booking ? "#booking" : "#mixed-booking"} className="inline-flex items-center gap-2">
|
||||||
{/* Inline SVG: dancing couple icon */}
|
{/* Inline SVG: dancing couple icon */}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
|
|||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { EVENT_INFO } from "@/data/event-data";
|
import { EVENT_INFO, SECTIONS } from "@/data/event-data";
|
||||||
import heroBg from "@/assets/hero-bg.jpg";
|
import heroBg from "@/assets/hero-bg.jpg";
|
||||||
|
|
||||||
/** Calcula diferencia entre ahora y la fecha del evento */
|
/** Calcula diferencia entre ahora y la fecha del evento */
|
||||||
@@ -116,7 +116,7 @@ const HeroSection = () => {
|
|||||||
transition={{ delay: 1 }}
|
transition={{ delay: 1 }}
|
||||||
>
|
>
|
||||||
<Button variant="hero" size="lg" className="text-lg px-10 py-6" asChild>
|
<Button variant="hero" size="lg" className="text-lg px-10 py-6" asChild>
|
||||||
<a href="#booking">{t('hero.bookYourPass')}</a>
|
<a href={SECTIONS.booking ? "#booking" : "#mixed-booking"}>{t('hero.bookYourPass')}</a>
|
||||||
</Button>
|
</Button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
213
src/components/MixedBookingSection.tsx
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { MIXED_BOOKING_PACKAGES, ROOM_TYPES, getFullPassPrice } from "@/data/event-data";
|
||||||
|
import type { RoomType } from "@/data/event-data";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Check, Star, Circle, CheckCircle2 } from "lucide-react";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
||||||
|
const FEATURED_PASS = "full";
|
||||||
|
const NON_HOTEL_FEE = 50;
|
||||||
|
const PARTY_PRICE = 25;
|
||||||
|
|
||||||
|
const MixedBookingSection = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fullPassPricing = getFullPassPrice();
|
||||||
|
const [selectedRooms, setSelectedRooms] = useState<Record<string, RoomType>>({
|
||||||
|
full: "individual",
|
||||||
|
party: "individual",
|
||||||
|
});
|
||||||
|
const [wantsHotel, setWantsHotel] = useState<Record<string, boolean | null>>({
|
||||||
|
full: null,
|
||||||
|
party: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
id="mixed-booking"
|
||||||
|
className="section-padding bg-background scroll-mt-24 relative z-10 -mt-[40px] pt-[120px]"
|
||||||
|
style={{ borderRadius: "0 100% 0 0 / 0 120px 0 0" }}
|
||||||
|
>
|
||||||
|
<div className="container mx-auto">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: false, amount: 0.15 }}
|
||||||
|
className="text-center mb-12"
|
||||||
|
>
|
||||||
|
<h2 className="font-display text-4xl md:text-5xl lg:text-7xl break-words font-bold pt-4 pb-10 leading-[1.8] text-gradient">
|
||||||
|
{t("mixedBooking.title")}
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground max-w-2xl mx-auto">{t("mixedBooking.subtitle")}</p>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<div className="grid sm:grid-cols-2 gap-6 lg:gap-8 max-w-4xl mx-auto items-stretch">
|
||||||
|
{MIXED_BOOKING_PACKAGES.map((pkg, i) => {
|
||||||
|
const selectedRoom = selectedRooms[pkg.id] || "individual";
|
||||||
|
const hasHotel = wantsHotel[pkg.id] === true;
|
||||||
|
// Use dynamic pricing for Full Pass, fixed price for Party Pass
|
||||||
|
// Only Full Pass has the +50€ non-hotel fee
|
||||||
|
let basePrice = pkg.id === "full" ? fullPassPricing.price : PARTY_PRICE;
|
||||||
|
let roomPrice = 0;
|
||||||
|
if (hasHotel) {
|
||||||
|
roomPrice = pkg.roomPrices[selectedRoom as keyof typeof pkg.roomPrices];
|
||||||
|
}
|
||||||
|
|
||||||
|
const passPrice = pkg.id === "full"
|
||||||
|
? (hasHotel ? basePrice : basePrice + NON_HOTEL_FEE)
|
||||||
|
: basePrice;
|
||||||
|
const price = passPrice + roomPrice;
|
||||||
|
|
||||||
|
const isFeatured = pkg.id === FEATURED_PASS;
|
||||||
|
const isLastPrice = pkg.id === "full" && fullPassPricing.isLastPrice;
|
||||||
|
const showNonHotelNote = pkg.id === "full" && !hasHotel && basePrice > 0;
|
||||||
|
const features = t(`mixedBooking.${pkg.id}Features`).split("|").map((f: string) => f.trim());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key={pkg.id}
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: false, amount: 0.15 }}
|
||||||
|
transition={{ delay: i * 0.1 }}
|
||||||
|
className={`relative flex h-full min-h-[520px] flex-col rounded-2xl overflow-hidden transition-shadow duration-300 ${
|
||||||
|
isFeatured
|
||||||
|
? "shadow-elevated border-2 border-primary/30"
|
||||||
|
: "shadow-card hover:shadow-elevated border border-border"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="absolute top-4 right-4 z-10 flex flex-col gap-2 items-end">
|
||||||
|
{isFeatured && (
|
||||||
|
<span className="inline-flex items-center gap-1 bg-gradient-tropical text-primary-foreground text-xs font-bold px-3 py-1 rounded-full shadow-md">
|
||||||
|
<Star className="w-3 h-3 fill-current" />
|
||||||
|
{t("mixedBooking.popular")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{isLastPrice && (
|
||||||
|
<span className="inline-flex items-center gap-1 bg-red-600 text-white text-xs font-bold px-3 py-1 rounded-full shadow-md">
|
||||||
|
{t("mixedBooking.lastPrice")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`px-6 pt-8 pb-4 text-center min-h-[160px] flex flex-col justify-between ${
|
||||||
|
isFeatured ? "bg-gradient-tropical" : "bg-secondary/10"
|
||||||
|
}`}>
|
||||||
|
<div>
|
||||||
|
<h3 className={`font-display text-2xl md:text-3xl font-bold mb-1 ${
|
||||||
|
isFeatured ? "text-primary-foreground" : "text-foreground"
|
||||||
|
}`}>
|
||||||
|
{t(`mixedBooking.passTypes.${pkg.id}`)}
|
||||||
|
</h3>
|
||||||
|
<p className={`text-sm ${
|
||||||
|
isFeatured ? "text-primary-foreground/80" : "text-muted-foreground"
|
||||||
|
}`}>
|
||||||
|
{t(`mixedBooking.${pkg.id}Description`)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-6 pt-6 pb-4 text-center bg-card">
|
||||||
|
<p className="text-4xl md:text-5xl font-bold text-primary">
|
||||||
|
{price > 0 ? `${price}€` : t("mixedBooking.priceTBD")}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm font-medium text-foreground mt-2">
|
||||||
|
{t(`mixedBooking.passTypes.${pkg.id}`)}: {passPrice}€ {pkg.id === "full" ? t("mixedBooking.perPerson") : t("mixedBooking.perParty")}
|
||||||
|
</p>
|
||||||
|
{showNonHotelNote && (
|
||||||
|
<p className="text-xs text-red-600 font-medium mt-2">
|
||||||
|
{t("mixedBooking.nonHotelNote")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{hasHotel && (
|
||||||
|
<p className="text-sm font-medium text-foreground mt-2">
|
||||||
|
{t(`mixedBooking.roomTypes.${selectedRoom}`)}: {roomPrice}€
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-6 pt-4 pb-2 bg-card">
|
||||||
|
<Label className="block text-sm font-medium text-foreground mb-3">
|
||||||
|
{t("mixedBooking.hotelQuestion")}
|
||||||
|
</Label>
|
||||||
|
<div className="flex gap-3 mb-4">
|
||||||
|
<button
|
||||||
|
onClick={() => setWantsHotel((prev) => ({ ...prev, [pkg.id]: true }))}
|
||||||
|
className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl border-2 text-sm font-medium transition-all duration-200 ${
|
||||||
|
hasHotel
|
||||||
|
? "border-primary bg-primary/5 text-primary"
|
||||||
|
: "border-input bg-background text-muted-foreground hover:border-primary/30"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<CheckCircle2 className={`w-5 h-5 ${hasHotel ? "text-primary" : "text-muted-foreground"}`} />
|
||||||
|
{t("mixedBooking.hotelYes")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setWantsHotel((prev) => ({ ...prev, [pkg.id]: false }))}
|
||||||
|
className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl border-2 text-sm font-medium transition-all duration-200 ${
|
||||||
|
!hasHotel
|
||||||
|
? "border-primary bg-primary/5 text-primary"
|
||||||
|
: "border-input bg-background text-muted-foreground hover:border-primary/30"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Circle className={`w-5 h-5 ${!hasHotel ? "text-primary" : "text-muted-foreground"}`} />
|
||||||
|
{t("mixedBooking.hotelNo")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasHotel && (
|
||||||
|
<>
|
||||||
|
<Label className="block text-sm font-medium text-foreground mb-2">
|
||||||
|
{t("mixedBooking.selectRoom")}
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={selectedRoom}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setSelectedRooms((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[pkg.id]: value as RoomType,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className={`w-full rounded-xl border px-4 py-3 text-sm text-foreground shadow-sm transition-colors duration-200 ${
|
||||||
|
isFeatured
|
||||||
|
? "border-primary/30 bg-primary/5 hover:border-primary/50"
|
||||||
|
: "border-input bg-background hover:border-primary/30"
|
||||||
|
}`}>
|
||||||
|
<SelectValue placeholder={t("mixedBooking.selectRoom")} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{ROOM_TYPES.map((room) => (
|
||||||
|
<SelectItem key={room.id} value={room.id}>
|
||||||
|
{t(`mixedBooking.roomTypes.${room.id}`)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-6 pt-4 pb-6 flex-1 flex flex-col justify-between bg-card">
|
||||||
|
<ul className="space-y-2.5 mb-6">
|
||||||
|
{features.map((feature: string, idx: number) => (
|
||||||
|
<li key={idx} className="flex items-start gap-2.5 text-sm text-foreground/80">
|
||||||
|
<Check className={`w-4 h-4 mt-0.5 flex-shrink-0 ${
|
||||||
|
isFeatured ? "text-primary" : "text-secondary"
|
||||||
|
}`} />
|
||||||
|
<span>{feature}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MixedBookingSection;
|
||||||
@@ -88,6 +88,16 @@ const PracticalSection = () => {
|
|||||||
<div key={h.method} className="bg-card rounded-lg p-3">
|
<div key={h.method} className="bg-card rounded-lg p-3">
|
||||||
<p className="font-medium text-foreground text-sm">{methodLabel}</p>
|
<p className="font-medium text-foreground text-sm">{methodLabel}</p>
|
||||||
<p className="text-xs text-muted-foreground">{h.details}</p>
|
<p className="text-xs text-muted-foreground">{h.details}</p>
|
||||||
|
{h.link && (
|
||||||
|
<a
|
||||||
|
href={h.link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-block mt-2 px-3 py-1 bg-primary text-primary-foreground text-xs rounded hover:bg-primary/90 transition-colors"
|
||||||
|
>
|
||||||
|
{t("info.viewOnMap")}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -22,18 +22,27 @@ import djklebynho from "@/assets/staff/djklebynho.jpg";
|
|||||||
import letialex from "@/assets/staff/letialex.jpg";
|
import letialex from "@/assets/staff/letialex.jpg";
|
||||||
import djcathie from "@/assets/staff/djcathie.jpg";
|
import djcathie from "@/assets/staff/djcathie.jpg";
|
||||||
|
|
||||||
|
import gal1 from "@/assets/gallery/gal1.jpg";
|
||||||
|
import gal2 from "@/assets/gallery/gal2.jpg";
|
||||||
|
import gal3 from "@/assets/gallery/gal3.jpg";
|
||||||
|
import gal4 from "@/assets/gallery/gal4.jpg";
|
||||||
|
import gal5 from "@/assets/gallery/gal5.jpg";
|
||||||
|
import gal6 from "@/assets/gallery/gal6.jpg";
|
||||||
|
|
||||||
|
|
||||||
// ---- INFORMACIÓN GENERAL DEL EVENTO ----
|
// ---- INFORMACIÓN GENERAL DEL EVENTO ----
|
||||||
export const EVENT_INFO = {
|
export const EVENT_INFO = {
|
||||||
/** Fecha del evento — formato ISO para el countdown */
|
/** Fecha del evento — formato ISO para el countdown */
|
||||||
date: "2026-09-04T12:00:00",
|
date: "2026-09-04T12:00:00",
|
||||||
venue: "[Nombre del Venue]",
|
venue: "Hotel Don Angel",
|
||||||
venueAddress: "[Dirección del venue, Barcelona]",
|
venueAddress: "Carrer de la Riera, 123, 08001 Barcelona, Spain",
|
||||||
/** Google Maps embed URL — reemplazar con la URL real */
|
/** Google Maps embed URL — reemplazar con la URL real */
|
||||||
mapEmbedUrl: "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2993.5!2d2.1734!3d41.3851!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x0%3A0x0!2zNDHCsDIzJzA2LjQiTiAywrAxMCcyNC4yIkU!5e0!3m2!1ses!2ses!4v1234567890",
|
mapEmbedUrl: "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2982.0132199519758!2d2.718142676474322!3d41.63384568067116!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x12bb3e942c5ac469%3A0x9153afdd8d15d0c1!2sHotel%20Don%20Angel!5e0!3m2!1ses!2sus!4v1777971058774!5m2!1ses!2sus",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ---- WEBHOOK N8N ----
|
// ---- WEBHOOK N8N ----
|
||||||
/**
|
/**
|
||||||
* CONFIGURACIÓN DEL WEBHOOK:
|
* CONFIGURACIÓN DEL WEBHOOK:
|
||||||
@@ -249,13 +258,11 @@ export const HOTEL_ROOMS = [
|
|||||||
// ---- INFORMACIÓN PRÁCTICA ----
|
// ---- INFORMACIÓN PRÁCTICA ----
|
||||||
export const PRACTICAL_INFO = {
|
export const PRACTICAL_INFO = {
|
||||||
airports: [
|
airports: [
|
||||||
{ name: "Aeropuerto de Barcelona-El Prat (BCN)", distance: "~15 km del venue" },
|
{ name: "Aeropuerto de Barcelona-El Prat (BCN)", distance: "~85 km del Hotel" },
|
||||||
{ name: "Aeropuerto de Girona (GRO)", distance: "~100 km del venue" },
|
{ name: "Aeropuerto de Girona (GRO)", distance: "~48 km del Hotel" },
|
||||||
{ name: "Aeropuerto de Reus (REU)", distance: "~110 km del venue" },
|
|
||||||
],
|
],
|
||||||
howToGet: [
|
howToGet: [
|
||||||
{ method: "Metro", details: "[Línea y parada más cercana]" },
|
{ method: "Metro", details: "Línea y parada más cercana", link: "https://maps.app.goo.gl/YVyASwX2odX1mW5J6" },
|
||||||
{ method: "Bus", details: "[Líneas de bus cercanas]" },
|
|
||||||
{ method: "Taxi/Uber", details: "Disponible desde cualquier punto de Barcelona" },
|
{ method: "Taxi/Uber", details: "Disponible desde cualquier punto de Barcelona" },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -266,21 +273,87 @@ export const PRACTICAL_INFO = {
|
|||||||
* Para importar: import img from "@/assets/gallery/photo1.jpg"
|
* Para importar: import img from "@/assets/gallery/photo1.jpg"
|
||||||
*/
|
*/
|
||||||
export const GALLERY_IMAGES = [
|
export const GALLERY_IMAGES = [
|
||||||
{ src: "", alt: "[Descripción foto 1]" },
|
{ src: gal1, alt: "[Descripción foto 1]" },
|
||||||
{ src: "", alt: "[Descripción foto 2]" },
|
{ src: gal2, alt: "[Descripción foto 2]" },
|
||||||
{ src: "", alt: "[Descripción foto 3]" },
|
{ src: gal3, alt: "[Descripción foto 3]" },
|
||||||
{ src: "", alt: "[Descripción foto 4]" },
|
{ src: gal4, alt: "[Descripción foto 4]" },
|
||||||
{ src: "", alt: "[Descripción foto 5]" },
|
{ src: gal5, alt: "[Descripción foto 5]" },
|
||||||
{ src: "", alt: "[Descripción foto 6]" },
|
{ src: gal6, alt: "[Descripción foto 6]" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// ---- NAVEGACIÓN ----
|
// ---- PAQUETES MIXTOS (Room + Pass) ----
|
||||||
export const NAV_LINKS = [
|
export type RoomType = "individual" | "double" | "suite";
|
||||||
{ href: "#about" },
|
export type PassType = "full" | "party";
|
||||||
{ href: "#staff" },
|
|
||||||
{ href: "#schedule" },
|
export const ROOM_TYPES: { id: RoomType }[] = [
|
||||||
{ href: "#booking" },
|
{ id: "individual" },
|
||||||
{ href: "#hotel" },
|
{ id: "double" },
|
||||||
{ href: "#info" },
|
{ id: "suite" },
|
||||||
{ href: "#gallery" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// ---- PRECIOS DINÁMICOS FULL PASS ----
|
||||||
|
/**
|
||||||
|
* Calcula el precio del Full Pass según la fecha actual.
|
||||||
|
* - Hasta 1 de julio: 170€
|
||||||
|
* - 1 de julio - 1 de septiembre: 190€
|
||||||
|
* - Después del inicio del evento: 200€ (último precio)
|
||||||
|
*/
|
||||||
|
export function getFullPassPrice(): { price: number; isLastPrice: boolean } {
|
||||||
|
const now = new Date();
|
||||||
|
const julyFirst = new Date("2026-07-01T00:00:00");
|
||||||
|
const septemberFirst = new Date("2026-09-01T00:00:00");
|
||||||
|
const eventStart = new Date(EVENT_INFO.date);
|
||||||
|
|
||||||
|
if (now < julyFirst) {
|
||||||
|
return { price: 170, isLastPrice: false };
|
||||||
|
} else if (now < septemberFirst) {
|
||||||
|
return { price: 190, isLastPrice: false };
|
||||||
|
} else {
|
||||||
|
// After event starts
|
||||||
|
return { price: 200, isLastPrice: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MIXED_BOOKING_PACKAGES = [
|
||||||
|
{ id: "full" as PassType, label: "full", roomPrices: { individual: 100, double: 150, suite: 213 } },
|
||||||
|
{ id: "party" as PassType, label: "party", roomPrices: { individual: 100, double: 150, suite: 213 } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ---- SECCIONES VISIBLES ----
|
||||||
|
export const SECTIONS = {
|
||||||
|
about: true,
|
||||||
|
org: true,
|
||||||
|
staff: true,
|
||||||
|
profesores: true,
|
||||||
|
schedule: true,
|
||||||
|
booking: false,
|
||||||
|
mixed_booking: true,
|
||||||
|
hotel: false,
|
||||||
|
practical: true,
|
||||||
|
gallery: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- NAVEGACIÓN ----
|
||||||
|
const HREF_TO_SECTION: Record<string, keyof typeof SECTIONS> = {
|
||||||
|
"#about": "about",
|
||||||
|
"#staff": "staff",
|
||||||
|
"#schedule": "schedule",
|
||||||
|
"#booking": "booking",
|
||||||
|
"#mixed-booking": "mixed_booking",
|
||||||
|
"#hotel": "hotel",
|
||||||
|
"#info": "practical",
|
||||||
|
"#gallery": "gallery",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NAV_LINKS = (
|
||||||
|
[
|
||||||
|
{ href: "#about" },
|
||||||
|
{ href: "#staff" },
|
||||||
|
{ href: "#schedule" },
|
||||||
|
{ href: "#booking" },
|
||||||
|
{ href: "#mixed-booking" },
|
||||||
|
{ href: "#hotel" },
|
||||||
|
{ href: "#info" },
|
||||||
|
{ href: "#gallery" },
|
||||||
|
] as const
|
||||||
|
).filter((link) => SECTIONS[HREF_TO_SECTION[link.href]]);
|
||||||
|
|||||||
@@ -4,14 +4,15 @@
|
|||||||
"staff": "Staff",
|
"staff": "Staff",
|
||||||
"schedule": "Schedule",
|
"schedule": "Schedule",
|
||||||
"booking": "Book your pass",
|
"booking": "Book your pass",
|
||||||
|
"mixed-booking": "Packs",
|
||||||
"hotel": "Hotel",
|
"hotel": "Hotel",
|
||||||
"info": "Info",
|
"info": "Info",
|
||||||
"gallery": "Gallery"
|
"gallery": "Gallery"
|
||||||
},
|
},
|
||||||
"hero": {
|
"hero": {
|
||||||
"title": "Zouk Lambada Barcelona",
|
"title": "Zouk Lambada Barcelona",
|
||||||
"subtitle": "International Dance Festival",
|
"subtitle": "Beach Festival",
|
||||||
"dates": "June 20-22, 2025",
|
"dates": "04 - 07 September 2026",
|
||||||
"location": "Barcelona, Spain",
|
"location": "Barcelona, Spain",
|
||||||
"registerButton": "Register Now",
|
"registerButton": "Register Now",
|
||||||
"whatsappButton": "Join WhatsApp Group",
|
"whatsappButton": "Join WhatsApp Group",
|
||||||
@@ -56,9 +57,9 @@
|
|||||||
"schedule": {
|
"schedule": {
|
||||||
"title": "Schedule",
|
"title": "Schedule",
|
||||||
"subtitle": "Three days of workshops, shows and social dance.",
|
"subtitle": "Three days of workshops, shows and social dance.",
|
||||||
"friday": "Friday, June 20",
|
"friday": "Friday, September 04",
|
||||||
"saturday": "Saturday, June 21",
|
"saturday": "Saturday, September 05",
|
||||||
"sunday": "Sunday, June 22",
|
"sunday": "Sunday, September 06",
|
||||||
"workshop": "Workshop",
|
"workshop": "Workshop",
|
||||||
"break": "Break",
|
"break": "Break",
|
||||||
"social": "Social Dance",
|
"social": "Social Dance",
|
||||||
@@ -123,6 +124,33 @@
|
|||||||
"description": "[Brief room description]",
|
"description": "[Brief room description]",
|
||||||
"bookButton": "Book at hotel"
|
"bookButton": "Book at hotel"
|
||||||
},
|
},
|
||||||
|
"mixedBooking": {
|
||||||
|
"title": "Room + Pass Bundles",
|
||||||
|
"subtitle": "Room + Pass bundles for your stay.",
|
||||||
|
"priceTBD": "Price TBA",
|
||||||
|
"selectRoom": "Choose room type",
|
||||||
|
"popular": "Popular",
|
||||||
|
"lastPrice": "Last Price",
|
||||||
|
"perPerson": "/ person",
|
||||||
|
"perParty": "/ party",
|
||||||
|
"hotelQuestion": "Do you need a hotel room?",
|
||||||
|
"hotelYes": "Yes",
|
||||||
|
"hotelNo": "No",
|
||||||
|
"nonHotelNote": "+50€ fee for not staying in hotel",
|
||||||
|
"roomTypes": {
|
||||||
|
"individual": "Single Room",
|
||||||
|
"double": "Double Room",
|
||||||
|
"suite": "Triple Room"
|
||||||
|
},
|
||||||
|
"passTypes": {
|
||||||
|
"full": "Full Pass",
|
||||||
|
"party": "Party Pass"
|
||||||
|
},
|
||||||
|
"fullDescription": "Full access to all workshops, socials, and festival activities",
|
||||||
|
"fullFeatures": "All workshops|All social dances|Live shows|DJ sets",
|
||||||
|
"partyDescription": "Price for each party individually, even pool parties.",
|
||||||
|
"partyFeatures": "Friday party|Saturday party|Sunday farewell party|DJ sets"
|
||||||
|
},
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Practical Information",
|
"title": "Practical Information",
|
||||||
"airports": "Nearby Airports",
|
"airports": "Nearby Airports",
|
||||||
@@ -134,7 +162,8 @@
|
|||||||
"busLines": "[Nearby bus lines]",
|
"busLines": "[Nearby bus lines]",
|
||||||
"taxiDetails": "Available from anywhere in Barcelona",
|
"taxiDetails": "Available from anywhere in Barcelona",
|
||||||
"venue": "Event Venue",
|
"venue": "Event Venue",
|
||||||
"mapTitle": "Event Location"
|
"mapTitle": "Event Location",
|
||||||
|
"viewOnMap": "View on map"
|
||||||
},
|
},
|
||||||
"gallery": {
|
"gallery": {
|
||||||
"title": "Gallery"
|
"title": "Gallery"
|
||||||
|
|||||||
@@ -4,14 +4,15 @@
|
|||||||
"staff": "Staff",
|
"staff": "Staff",
|
||||||
"schedule": "Programa",
|
"schedule": "Programa",
|
||||||
"booking": "Reservar",
|
"booking": "Reservar",
|
||||||
|
"mixed-booking": "Packs",
|
||||||
"hotel": "Hotel",
|
"hotel": "Hotel",
|
||||||
"info": "Info",
|
"info": "Info",
|
||||||
"gallery": "Galería"
|
"gallery": "Galería"
|
||||||
},
|
},
|
||||||
"hero": {
|
"hero": {
|
||||||
"title": "Zouk Lambada Barcelona",
|
"title": "Zouk Lambada Barcelona",
|
||||||
"subtitle": "Festival Internacional de Baile",
|
"subtitle": "Beach Festival",
|
||||||
"dates": "20-22 Junio 2025",
|
"dates": "04 - 07 Setiembre 2026",
|
||||||
"location": "Barcelona, España",
|
"location": "Barcelona, España",
|
||||||
"registerButton": "Registrarse Ahora",
|
"registerButton": "Registrarse Ahora",
|
||||||
"whatsappButton": "Unirse al Grupo de WhatsApp",
|
"whatsappButton": "Unirse al Grupo de WhatsApp",
|
||||||
@@ -56,9 +57,9 @@
|
|||||||
"schedule": {
|
"schedule": {
|
||||||
"title": "Programa",
|
"title": "Programa",
|
||||||
"subtitle": "Tres días de workshops, shows y social dance.",
|
"subtitle": "Tres días de workshops, shows y social dance.",
|
||||||
"friday": "Viernes 20 Junio",
|
"friday": "Viernes 04 Setiembre",
|
||||||
"saturday": "Sábado 21 Junio",
|
"saturday": "Sábado 05 Setiembre",
|
||||||
"sunday": "Domingo 22 Junio",
|
"sunday": "Domingo 06 Setiembre",
|
||||||
"workshop": "Workshop",
|
"workshop": "Workshop",
|
||||||
"break": "Pausa",
|
"break": "Pausa",
|
||||||
"social": "Social Dance",
|
"social": "Social Dance",
|
||||||
@@ -123,6 +124,33 @@
|
|||||||
"description": "[Descripción breve de la habitación]",
|
"description": "[Descripción breve de la habitación]",
|
||||||
"bookButton": "Reservar en el hotel"
|
"bookButton": "Reservar en el hotel"
|
||||||
},
|
},
|
||||||
|
"mixedBooking": {
|
||||||
|
"title": "Packs de Habitación + Pase",
|
||||||
|
"subtitle": "Combina habitación + pase para tu estancia.",
|
||||||
|
"priceTBD": "Precio por confirmar",
|
||||||
|
"selectRoom": "Elige tipo de habitación",
|
||||||
|
"popular": "Popular",
|
||||||
|
"lastPrice": "Último Precio",
|
||||||
|
"perPerson": "/ persona",
|
||||||
|
"perParty": "/ fiesta",
|
||||||
|
"hotelQuestion": "¿Necesitas habitación de hotel?",
|
||||||
|
"hotelYes": "Sí",
|
||||||
|
"hotelNo": "No",
|
||||||
|
"nonHotelNote": "+50€ por no alojarse en hotel",
|
||||||
|
"roomTypes": {
|
||||||
|
"individual": "Habitación Individual",
|
||||||
|
"double": "Habitación Doble",
|
||||||
|
"suite": "Habitación Triple"
|
||||||
|
},
|
||||||
|
"passTypes": {
|
||||||
|
"full": "Full Pass",
|
||||||
|
"party": "Party Pass"
|
||||||
|
},
|
||||||
|
"fullDescription": "Acceso completo a todos los workshops, sociales y actividades del festival",
|
||||||
|
"fullFeatures": "Todos los workshops|Todas las social dances|Shows en vivo|DJ sets",
|
||||||
|
"partyDescription": "Precio individual por cada fiesta, incluso las pool parties.",
|
||||||
|
"partyFeatures": "Fiesta del viernes|Fiesta del sábado|Fiesta despedida del domingo|DJ sets"
|
||||||
|
},
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Información Práctica",
|
"title": "Información Práctica",
|
||||||
"airports": "Aeropuertos Cercanos",
|
"airports": "Aeropuertos Cercanos",
|
||||||
@@ -134,7 +162,8 @@
|
|||||||
"busLines": "[Líneas de bus cercanas]",
|
"busLines": "[Líneas de bus cercanas]",
|
||||||
"taxiDetails": "Disponible desde cualquier punto de Barcelona",
|
"taxiDetails": "Disponible desde cualquier punto de Barcelona",
|
||||||
"venue": "Lugar del Evento",
|
"venue": "Lugar del Evento",
|
||||||
"mapTitle": "Ubicación del evento"
|
"mapTitle": "Ubicación del evento",
|
||||||
|
"viewOnMap": "Ver en mapa"
|
||||||
},
|
},
|
||||||
"gallery": {
|
"gallery": {
|
||||||
"title": "Galería"
|
"title": "Galería"
|
||||||
@@ -146,7 +175,7 @@
|
|||||||
"title": "ZoukLambadaBCN",
|
"title": "ZoukLambadaBCN",
|
||||||
"contact": "Contacto",
|
"contact": "Contacto",
|
||||||
"followUs": "Síguenos",
|
"followUs": "Síguenos",
|
||||||
"email": "[email@zouklambadabcn.com]",
|
"email": "[info@zouklambadabcn.com]",
|
||||||
"copyright": "© {{year}} ZoukLambadaBCN. Todos los derechos reservados."
|
"copyright": "© {{year}} ZoukLambadaBCN. Todos los derechos reservados."
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import StaffSection from "@/components/StaffSection";
|
|||||||
import ProfesoresSection from "@/components/ProfesoresSection";
|
import ProfesoresSection from "@/components/ProfesoresSection";
|
||||||
import ScheduleSection from "@/components/ScheduleSection";
|
import ScheduleSection from "@/components/ScheduleSection";
|
||||||
import BookingSection from "@/components/BookingSection";
|
import BookingSection from "@/components/BookingSection";
|
||||||
|
import MixedBookingSection from "@/components/MixedBookingSection";
|
||||||
import HotelSection from "@/components/HotelSection";
|
import HotelSection from "@/components/HotelSection";
|
||||||
import PracticalSection from "@/components/PracticalSection";
|
import PracticalSection from "@/components/PracticalSection";
|
||||||
import GallerySection from "@/components/GallerySection";
|
import GallerySection from "@/components/GallerySection";
|
||||||
import FooterSection from "@/components/FooterSection";
|
import FooterSection from "@/components/FooterSection";
|
||||||
import FloatingButton from "@/components/FloatingButton";
|
import FloatingButton from "@/components/FloatingButton";
|
||||||
|
import { SECTIONS } from "@/data/event-data";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Landing page — Lambada Festival Barcelona
|
* Landing page — Lambada Festival Barcelona
|
||||||
@@ -23,15 +25,16 @@ const Index = () => {
|
|||||||
<div className="min-h-screen">
|
<div className="min-h-screen">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<HeroSection />
|
<HeroSection />
|
||||||
<AboutSection />
|
{SECTIONS.about && <AboutSection />}
|
||||||
<OrgSection />
|
{SECTIONS.org && <OrgSection />}
|
||||||
<StaffSection />
|
{SECTIONS.staff && <StaffSection />}
|
||||||
<ProfesoresSection />
|
{SECTIONS.profesores && <ProfesoresSection />}
|
||||||
<ScheduleSection />
|
{SECTIONS.schedule && <ScheduleSection />}
|
||||||
<BookingSection />
|
{SECTIONS.booking && <BookingSection />}
|
||||||
<HotelSection />
|
{SECTIONS.mixed_booking && <MixedBookingSection />}
|
||||||
<PracticalSection />
|
{SECTIONS.hotel && <HotelSection />}
|
||||||
<GallerySection />
|
{SECTIONS.practical && <PracticalSection />}
|
||||||
|
{SECTIONS.gallery && <GallerySection />}
|
||||||
<FooterSection />
|
<FooterSection />
|
||||||
<FloatingButton />
|
<FloatingButton />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||