Compare commits
3 Commits
modifiche0
...
backend_in
| Author | SHA1 | Date | |
|---|---|---|---|
| d9b617e706 | |||
| 580a659b5a | |||
| bd5b845e43 |
BIN
assets/services_cardiologia.jpg
Normal file
BIN
assets/services_cardiologia.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
@@ -6,6 +6,7 @@ class BookingRequestCreate(BaseModel):
|
|||||||
phone: str = Field(min_length=5, max_length=40)
|
phone: str = Field(min_length=5, max_length=40)
|
||||||
pet_name: str = Field(default="", max_length=120)
|
pet_name: str = Field(default="", max_length=120)
|
||||||
pet_type: str = Field(default="cane", max_length=40)
|
pet_type: str = Field(default="cane", max_length=40)
|
||||||
|
doctor: str = Field(min_length=2, max_length=120)
|
||||||
service: str = Field(min_length=2, max_length=120)
|
service: str = Field(min_length=2, max_length=120)
|
||||||
date: str = Field(min_length=8, max_length=20)
|
date: str = Field(min_length=8, max_length=20)
|
||||||
time: str = Field(default="", max_length=20)
|
time: str = Field(default="", max_length=20)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ def _build_booking_html(payload: BookingRequestCreate) -> str:
|
|||||||
<tr><td><strong>Telefono</strong></td><td>{payload.phone}</td></tr>
|
<tr><td><strong>Telefono</strong></td><td>{payload.phone}</td></tr>
|
||||||
<tr><td><strong>Nome animale</strong></td><td>{payload.pet_name or "-"}</td></tr>
|
<tr><td><strong>Nome animale</strong></td><td>{payload.pet_name or "-"}</td></tr>
|
||||||
<tr><td><strong>Tipo animale</strong></td><td>{payload.pet_type}</td></tr>
|
<tr><td><strong>Tipo animale</strong></td><td>{payload.pet_type}</td></tr>
|
||||||
|
<tr><td><strong>Medico richiesto</strong></td><td>{payload.doctor}</td></tr>
|
||||||
<tr><td><strong>Tipo di visita</strong></td><td>{payload.service}</td></tr>
|
<tr><td><strong>Tipo di visita</strong></td><td>{payload.service}</td></tr>
|
||||||
<tr><td><strong>Data preferita</strong></td><td>{payload.date}</td></tr>
|
<tr><td><strong>Data preferita</strong></td><td>{payload.date}</td></tr>
|
||||||
<tr><td><strong>Orario preferito</strong></td><td>{payload.time or "Qualsiasi orario"}</td></tr>
|
<tr><td><strong>Orario preferito</strong></td><td>{payload.time or "Qualsiasi orario"}</td></tr>
|
||||||
@@ -51,6 +52,7 @@ def _build_booking_text(payload: BookingRequestCreate) -> str:
|
|||||||
f"Telefono: {payload.phone}\n"
|
f"Telefono: {payload.phone}\n"
|
||||||
f"Nome animale: {payload.pet_name or '-'}\n"
|
f"Nome animale: {payload.pet_name or '-'}\n"
|
||||||
f"Tipo animale: {payload.pet_type}\n"
|
f"Tipo animale: {payload.pet_type}\n"
|
||||||
|
f"Medico richiesto: {payload.doctor}\n"
|
||||||
f"Tipo di visita: {payload.service}\n"
|
f"Tipo di visita: {payload.service}\n"
|
||||||
f"Data preferita: {payload.date}\n"
|
f"Data preferita: {payload.date}\n"
|
||||||
f"Orario preferito: {payload.time or 'Qualsiasi orario'}\n"
|
f"Orario preferito: {payload.time or 'Qualsiasi orario'}\n"
|
||||||
|
|||||||
BIN
clinica-app/client/public/images/services_cardiologia.jpg
Normal file
BIN
clinica-app/client/public/images/services_cardiologia.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
@@ -105,19 +105,6 @@ export default function AboutSection() {
|
|||||||
<div className="absolute inset-0 bg-gradient-to-t from-[#1B4F72]/30 to-transparent" />
|
<div className="absolute inset-0 bg-gradient-to-t from-[#1B4F72]/30 to-transparent" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Badge flottante */}
|
|
||||||
<div className="absolute -bottom-6 -left-6 bg-white rounded-2xl shadow-xl p-5 max-w-[200px]">
|
|
||||||
<div
|
|
||||||
className="text-[#1B4F72] font-bold mb-1"
|
|
||||||
style={{ fontFamily: "'Cormorant Garamond', serif", fontSize: "2.5rem" }}
|
|
||||||
>
|
|
||||||
15+
|
|
||||||
</div>
|
|
||||||
<div className="text-gray-600 text-xs leading-tight">
|
|
||||||
Anni di esperienza nella cura degli animali
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Decorazione */}
|
{/* Decorazione */}
|
||||||
<div className="absolute -top-4 -right-4 w-24 h-24 rounded-full bg-[#4ECDC4]/10 -z-10" />
|
<div className="absolute -top-4 -right-4 w-24 h-24 rounded-full bg-[#4ECDC4]/10 -z-10" />
|
||||||
<div className="absolute -bottom-8 right-8 w-16 h-16 rounded-full bg-[#1B4F72]/10 -z-10" />
|
<div className="absolute -bottom-8 right-8 w-16 h-16 rounded-full bg-[#1B4F72]/10 -z-10" />
|
||||||
|
|||||||
@@ -2,13 +2,21 @@
|
|||||||
* DESIGN: "Clinical Warmth"
|
* DESIGN: "Clinical Warmth"
|
||||||
* Sezione prenotazione: form to mail con conferma non vincolante.
|
* Sezione prenotazione: form to mail con conferma non vincolante.
|
||||||
*/
|
*/
|
||||||
import { useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { motion, useInView } from "framer-motion";
|
import { motion, useInView } from "framer-motion";
|
||||||
import { useRef } from "react";
|
import {
|
||||||
import { Button } from "@/components/ui/button";
|
Calendar,
|
||||||
import { Calendar, Clock, User, Phone, PawPrint, CheckCircle2, ShieldCheck } from "lucide-react";
|
CheckCircle2,
|
||||||
|
Clock,
|
||||||
|
PawPrint,
|
||||||
|
Phone,
|
||||||
|
ShieldCheck,
|
||||||
|
User,
|
||||||
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
const services = [
|
const services = [
|
||||||
"Visita clinica generale",
|
"Visita clinica generale",
|
||||||
"Ecografia",
|
"Ecografia",
|
||||||
@@ -24,6 +32,25 @@ const services = [
|
|||||||
"Laparoscopia",
|
"Laparoscopia",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const doctors = [
|
||||||
|
"Dott. Paolo Parmeggiani",
|
||||||
|
"Dott.ssa Irene Paganelli",
|
||||||
|
"Dott. Simone Tinti",
|
||||||
|
"Dott.ssa Michela Sghedoni",
|
||||||
|
"Dott. Luca Pietri",
|
||||||
|
"Dott.ssa Sara Casali",
|
||||||
|
"Dott. Riccardo Suffritti",
|
||||||
|
"Dott.ssa Elena Venturelli",
|
||||||
|
"Dott.ssa Cinzia Pellegrini",
|
||||||
|
];
|
||||||
|
|
||||||
|
const openingHours = [
|
||||||
|
{ days: "Visite: Lunedi - Venerdi", hours: "09:00 - 19:30" },
|
||||||
|
{ days: "Visite: Sabato", hours: "09:00 - 17:00" },
|
||||||
|
{ days: "Urgenze: Lunedi - Venerdi", hours: "08:00 - 22:30" },
|
||||||
|
{ days: "Urgenze: Sabato e Festivi", hours: "09:00 - 20:00" },
|
||||||
|
];
|
||||||
|
|
||||||
const timeSlots = [
|
const timeSlots = [
|
||||||
"09:00",
|
"09:00",
|
||||||
"09:30",
|
"09:30",
|
||||||
@@ -44,6 +71,7 @@ type BookingFormState = {
|
|||||||
phone: string;
|
phone: string;
|
||||||
petName: string;
|
petName: string;
|
||||||
petType: string;
|
petType: string;
|
||||||
|
doctor: string;
|
||||||
service: string;
|
service: string;
|
||||||
date: string;
|
date: string;
|
||||||
time: string;
|
time: string;
|
||||||
@@ -55,6 +83,7 @@ const initialForm: BookingFormState = {
|
|||||||
phone: "",
|
phone: "",
|
||||||
petName: "",
|
petName: "",
|
||||||
petType: "cane",
|
petType: "cane",
|
||||||
|
doctor: "",
|
||||||
service: "",
|
service: "",
|
||||||
date: "",
|
date: "",
|
||||||
time: "",
|
time: "",
|
||||||
@@ -72,7 +101,7 @@ export default function BookingSection() {
|
|||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!form.name || !form.phone || !form.service || !form.date) {
|
if (!form.name || !form.phone || !form.doctor || !form.service || !form.date) {
|
||||||
toast.error("Compila tutti i campi obbligatori");
|
toast.error("Compila tutti i campi obbligatori");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -90,6 +119,7 @@ export default function BookingSection() {
|
|||||||
phone: form.phone,
|
phone: form.phone,
|
||||||
pet_name: form.petName,
|
pet_name: form.petName,
|
||||||
pet_type: form.petType,
|
pet_type: form.petType,
|
||||||
|
doctor: form.doctor,
|
||||||
service: form.service,
|
service: form.service,
|
||||||
date: form.date,
|
date: form.date,
|
||||||
time: form.time,
|
time: form.time,
|
||||||
@@ -111,7 +141,11 @@ export default function BookingSection() {
|
|||||||
description: payload.message,
|
description: payload.message,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : "Si è verificato un problema durante l'invio.";
|
const message =
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Si e verificato un problema durante l'invio.";
|
||||||
|
|
||||||
toast.error("Invio non riuscito", {
|
toast.error("Invio non riuscito", {
|
||||||
description: message,
|
description: message,
|
||||||
});
|
});
|
||||||
@@ -150,14 +184,13 @@ export default function BookingSection() {
|
|||||||
lineHeight: 1.2,
|
lineHeight: 1.2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Richiedi una visita{" "}
|
Richiedi una visita <span className="italic text-[#4ECDC4]">online</span>
|
||||||
<span className="italic text-[#4ECDC4]">online</span>
|
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p className="mb-8 text-base leading-relaxed text-white/80">
|
<p className="mb-8 text-base leading-relaxed text-white/80">
|
||||||
Compila il modulo per inviare una richiesta di appuntamento. La richiesta
|
Compila il modulo per inviare una richiesta di appuntamento. La richiesta
|
||||||
non è vincolante e dovrà essere confermata dallo staff della clinica, che ti
|
non e vincolante e dovra essere confermata dallo staff della clinica, che ti
|
||||||
ricontatterà il prima possibile.
|
ricontattera il prima possibile.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mb-8 rounded-xl border border-white/20 bg-white/10 p-5 backdrop-blur-sm">
|
<div className="mb-8 rounded-xl border border-white/20 bg-white/10 p-5 backdrop-blur-sm">
|
||||||
@@ -183,28 +216,46 @@ export default function BookingSection() {
|
|||||||
<p className="text-sm font-semibold text-white">Richiesta non vincolante</p>
|
<p className="text-sm font-semibold text-white">Richiesta non vincolante</p>
|
||||||
<p className="mt-1 text-sm leading-relaxed text-white/75">
|
<p className="mt-1 text-sm leading-relaxed text-white/75">
|
||||||
L'invio del modulo non costituisce conferma automatica dell'appuntamento.
|
L'invio del modulo non costituisce conferma automatica dell'appuntamento.
|
||||||
Il team verificherà disponibilità, tipologia di visita e urgenza del caso prima
|
Il team verifichera disponibilita, tipologia di visita e urgenza del caso prima
|
||||||
di confermare data e orario.
|
di confermare data e orario.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="max-w-[39rem]">
|
||||||
<p className="mb-3 text-sm font-semibold uppercase tracking-wide text-white/70">
|
<div className="rounded-[26px] border border-white/20 bg-white/10 px-6 py-6 shadow-[0_18px_45px_rgba(0,0,0,0.18)] backdrop-blur-md sm:px-7 sm:py-7">
|
||||||
|
<h4
|
||||||
|
className="mb-5 text-white/95 uppercase tracking-[0.28em]"
|
||||||
|
style={{
|
||||||
|
fontFamily: "'Nunito Sans', sans-serif",
|
||||||
|
fontSize: "0.8rem",
|
||||||
|
fontWeight: 700,
|
||||||
|
}}
|
||||||
|
>
|
||||||
Orari di apertura
|
Orari di apertura
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{openingHours.map((slot) => (
|
||||||
|
<div key={slot.days} className="flex items-start gap-3.5">
|
||||||
|
<div className="mt-0.5 rounded-full bg-[#4ECDC4]/20 p-2">
|
||||||
|
<Clock size={14} className="text-[#7ce3dc]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-white/92 sm:text-[0.98rem]">
|
||||||
|
{slot.days}
|
||||||
</p>
|
</p>
|
||||||
{[
|
<p className="mt-0.5 text-sm text-white/72 sm:text-[0.96rem]">
|
||||||
{ days: "Lunedi - Venerdi", hours: "09:00 - 12:30 · 14:30 - 19:00" },
|
{slot.hours}
|
||||||
{ days: "Sabato", hours: "09:00 - 12:30" },
|
</p>
|
||||||
{ days: "Domenica", hours: "Solo urgenze" },
|
</div>
|
||||||
].map((slot) => (
|
|
||||||
<div key={slot.days} className="flex items-center justify-between text-sm">
|
|
||||||
<span className="text-white/70">{slot.days}</span>
|
|
||||||
<span className="font-medium text-white">{slot.hours}</span>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -224,15 +275,15 @@ export default function BookingSection() {
|
|||||||
Grazie {submittedName || "per la tua richiesta"}
|
Grazie {submittedName || "per la tua richiesta"}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mb-3 text-sm leading-relaxed text-gray-600">
|
<p className="mb-3 text-sm leading-relaxed text-gray-600">
|
||||||
La tua richiesta di prenotazione è stata inviata correttamente.
|
La tua richiesta di prenotazione e stata inviata correttamente.
|
||||||
</p>
|
</p>
|
||||||
<p className="mb-6 text-sm leading-relaxed text-gray-600">
|
<p className="mb-6 text-sm leading-relaxed text-gray-600">
|
||||||
Il team della Clinica Veterinaria Formiginese la prenderà in carico il prima
|
Il team della Clinica Veterinaria Formiginese la prendera in carico il prima
|
||||||
possibile e ti ricontatterà per confermare disponibilità, data e orario.
|
possibile e ti ricontattera per confermare disponibilita, data e orario.
|
||||||
</p>
|
</p>
|
||||||
<div className="mb-6 rounded-xl border border-[#E4D7C6] bg-[#FFF9F1] px-4 py-3 text-left text-sm text-gray-600">
|
<div className="mb-6 rounded-xl border border-[#E4D7C6] bg-[#FFF9F1] px-4 py-3 text-left text-sm text-gray-600">
|
||||||
<strong className="text-[#1B4F72]">Nota importante:</strong> la richiesta inviata
|
<strong className="text-[#1B4F72]">Nota importante:</strong> la richiesta inviata
|
||||||
non equivale a una prenotazione già confermata.
|
non equivale a una prenotazione gia confermata.
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
className="bg-[#1B4F72] text-white hover:bg-[#163d5a]"
|
className="bg-[#1B4F72] text-white hover:bg-[#163d5a]"
|
||||||
@@ -249,12 +300,16 @@ export default function BookingSection() {
|
|||||||
<div>
|
<div>
|
||||||
<h3
|
<h3
|
||||||
className="mb-2 text-[#1B4F72]"
|
className="mb-2 text-[#1B4F72]"
|
||||||
style={{ fontFamily: "'Cormorant Garamond', serif", fontSize: "1.5rem", fontWeight: 600 }}
|
style={{
|
||||||
|
fontFamily: "'Cormorant Garamond', serif",
|
||||||
|
fontSize: "1.5rem",
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Richiedi un appuntamento
|
Richiedi un appuntamento
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm leading-relaxed text-gray-500">
|
<p className="text-sm leading-relaxed text-gray-500">
|
||||||
Compila i campi richiesti e inviaci una proposta di data: sarà lo staff a
|
Compila i campi richiesti e inviaci una proposta di data: sara lo staff a
|
||||||
confermare la visita.
|
confermare la visita.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -276,6 +331,7 @@ export default function BookingSection() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
|
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
|
||||||
Telefono *
|
Telefono *
|
||||||
@@ -310,6 +366,7 @@ export default function BookingSection() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
|
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
|
||||||
Tipo di animale
|
Tipo di animale
|
||||||
@@ -326,6 +383,25 @@ export default function BookingSection() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
|
||||||
|
Medico richiesto *
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
required
|
||||||
|
value={form.doctor}
|
||||||
|
onChange={(e) => setForm({ ...form, doctor: e.target.value })}
|
||||||
|
className="w-full rounded-lg border border-gray-200 bg-white px-3 py-2.5 text-sm transition-all focus:border-transparent focus:outline-none focus:ring-2 focus:ring-[#4ECDC4]"
|
||||||
|
>
|
||||||
|
<option value="">Seleziona un medico</option>
|
||||||
|
{doctors.map((doctor) => (
|
||||||
|
<option key={doctor} value={doctor}>
|
||||||
|
{doctor}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
|
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
|
||||||
Tipo di visita *
|
Tipo di visita *
|
||||||
@@ -337,9 +413,9 @@ export default function BookingSection() {
|
|||||||
className="w-full rounded-lg border border-gray-200 bg-white px-3 py-2.5 text-sm transition-all focus:border-transparent focus:outline-none focus:ring-2 focus:ring-[#4ECDC4]"
|
className="w-full rounded-lg border border-gray-200 bg-white px-3 py-2.5 text-sm transition-all focus:border-transparent focus:outline-none focus:ring-2 focus:ring-[#4ECDC4]"
|
||||||
>
|
>
|
||||||
<option value="">Seleziona un servizio</option>
|
<option value="">Seleziona un servizio</option>
|
||||||
{services.map((s) => (
|
{services.map((service) => (
|
||||||
<option key={s} value={s}>
|
<option key={service} value={service}>
|
||||||
{s}
|
{service}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -362,6 +438,7 @@ export default function BookingSection() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
|
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
|
||||||
Orario preferito
|
Orario preferito
|
||||||
@@ -374,9 +451,9 @@ export default function BookingSection() {
|
|||||||
className="w-full rounded-lg border border-gray-200 bg-white py-2.5 pl-9 pr-3 text-sm transition-all focus:border-transparent focus:outline-none focus:ring-2 focus:ring-[#4ECDC4]"
|
className="w-full rounded-lg border border-gray-200 bg-white py-2.5 pl-9 pr-3 text-sm transition-all focus:border-transparent focus:outline-none focus:ring-2 focus:ring-[#4ECDC4]"
|
||||||
>
|
>
|
||||||
<option value="">Qualsiasi orario</option>
|
<option value="">Qualsiasi orario</option>
|
||||||
{timeSlots.map((t) => (
|
{timeSlots.map((time) => (
|
||||||
<option key={t} value={t}>
|
<option key={time} value={time}>
|
||||||
{t}
|
{time}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -406,7 +483,7 @@ export default function BookingSection() {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<p className="text-center text-xs text-gray-500">
|
<p className="text-center text-xs text-gray-500">
|
||||||
* Campi obbligatori. La richiesta sarà valutata e confermata dallo staff.
|
* Campi obbligatori. La richiesta sara valutata e confermata dallo staff.
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ const quickLinks = [
|
|||||||
{ label: "Chirurgia", href: "/#servizi" },
|
{ label: "Chirurgia", href: "/#servizi" },
|
||||||
{ label: "Laboratorio", href: "/#servizi" },
|
{ label: "Laboratorio", href: "/#servizi" },
|
||||||
{ label: "Il Team", href: "/#team" },
|
{ label: "Il Team", href: "/#team" },
|
||||||
{ label: "News & Blog", href: "/#news" },
|
|
||||||
{ label: "Prenota Visita", href: "/#prenotazione" },
|
{ label: "Prenota Visita", href: "/#prenotazione" },
|
||||||
{ label: "Area Personale", href: "/#registrazione" },
|
{ label: "Area Personale", href: "/#registrazione" },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* DESIGN: "Clinical Warmth"
|
* DESIGN: "Clinical Warmth"
|
||||||
* Hero a schermo intero con immagine animale + overlay gradiente + testo sovrapposto
|
* Hero a schermo intero con immagine animale + overlay gradiente + testo sovrapposto
|
||||||
* Immagine: hero_dog_cat.jpg (golden retriever + gatto in prato soleggiato)
|
* Testo elegante su overlay scuro, card orari raffinata, CTA verdi acqua
|
||||||
* Testo bianco su overlay scuro, CTA verde acqua
|
|
||||||
*/
|
*/
|
||||||
import { useState, useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { ChevronDown, Calendar, Phone, Clock } from "lucide-react";
|
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import { Calendar, ChevronDown, Clock, Phone } from "lucide-react";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
const heroImages = [
|
const heroImages = [
|
||||||
{
|
{
|
||||||
@@ -48,20 +48,26 @@ const heroImages = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const openingHours = [
|
||||||
|
{ days: "Visite: Lunedi - Venerdi", hours: "09:00 - 19:30" },
|
||||||
|
{ days: "Visite: Sabato", hours: "09:00 - 17:00" },
|
||||||
|
{ days: "Urgenze: Lunedi - Venerdi", hours: "08:00 - 22:30" },
|
||||||
|
{ days: "Urgenze: Sabato e Festivi", hours: "09:00 - 20:00" },
|
||||||
|
];
|
||||||
|
|
||||||
export default function HeroSection() {
|
export default function HeroSection() {
|
||||||
const [currentImage, setCurrentImage] = useState(0);
|
const [currentImage, setCurrentImage] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 3 secondi di visualizzazione + 2 secondi di dissolvenza = 5 secondi totali
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
setCurrentImage((prev) => (prev + 1) % heroImages.length);
|
setCurrentImage((prev) => (prev + 1) % heroImages.length);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative h-[92vh] min-h-[600px] max-h-[900px] overflow-hidden">
|
<section className="relative h-[92vh] min-h-[600px] max-h-[900px] overflow-hidden">
|
||||||
{/* Background images con crossfade */}
|
|
||||||
{heroImages.map((img, index) => (
|
{heroImages.map((img, index) => (
|
||||||
<div
|
<div
|
||||||
key={img.url}
|
key={img.url}
|
||||||
@@ -71,104 +77,95 @@ export default function HeroSection() {
|
|||||||
<img
|
<img
|
||||||
src={img.url}
|
src={img.url}
|
||||||
alt={img.alt}
|
alt={img.alt}
|
||||||
className="w-full h-full object-cover"
|
className="h-full w-full object-cover"
|
||||||
style={{ transform: "scale(1.05)" }}
|
style={{ transform: "scale(1.05)" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Overlay gradiente scuro */}
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-[#0d2b3e]/85 via-[#1B4F72]/60 to-transparent" />
|
<div className="absolute inset-0 bg-gradient-to-r from-[#0d2b3e]/85 via-[#1B4F72]/60 to-transparent" />
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-[#0d2b3e]/50 via-transparent to-transparent" />
|
<div className="absolute inset-0 bg-gradient-to-t from-[#0d2b3e]/50 via-transparent to-transparent" />
|
||||||
|
|
||||||
{/* Contenuto hero */}
|
<div className="relative z-10 flex h-full items-center">
|
||||||
<div className="relative z-10 h-full flex items-center">
|
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="max-w-2xl">
|
<div className="max-w-3xl">
|
||||||
{/* Badge */}
|
|
||||||
|
|
||||||
|
|
||||||
{/* Titolo principale */}
|
|
||||||
<motion.h1
|
<motion.h1
|
||||||
initial={{ opacity: 0, y: 30 }}
|
initial={{ opacity: 0, y: 30 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.8, delay: 0.3 }}
|
transition={{ duration: 0.8, delay: 0.3 }}
|
||||||
className="text-white leading-tight mb-4"
|
className="mb-10 max-w-2xl text-white leading-[0.92]"
|
||||||
style={{
|
style={{
|
||||||
fontFamily: "'Cormorant Garamond', serif",
|
fontFamily: "'Cormorant Garamond', serif",
|
||||||
fontSize: "clamp(2.5rem, 6vw, 4.5rem)",
|
fontSize: "clamp(3.15rem, 6.4vw, 5.5rem)",
|
||||||
fontWeight: 600,
|
fontWeight: 500,
|
||||||
|
letterSpacing: "0.03em",
|
||||||
|
textShadow: "0 14px 34px rgba(0,0,0,0.24)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
CLINICA VETERINARIA FORMIGINESE{" "}
|
<span className="block text-white/92">Clinica Veterinaria</span>
|
||||||
|
<span className="block italic text-white">Formiginese</span>
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
|
|
||||||
{/* Sottotitolo */}
|
|
||||||
<motion.p
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.7, delay: 0.5 }}
|
|
||||||
className="text-white/85 text-lg mb-8 leading-relaxed max-w-xl"
|
|
||||||
style={{ fontFamily: "'Nunito Sans', sans-serif" }}
|
|
||||||
>
|
|
||||||
|
|
||||||
</motion.p>
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 22 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.7, delay: 0.7 }}
|
transition={{ duration: 0.7, delay: 0.55 }}
|
||||||
className="flex flex-col sm:flex-row gap-4"
|
className="mb-14 max-w-[39rem]"
|
||||||
>
|
>
|
||||||
<div>
|
<div className="rounded-[26px] border border-white/20 bg-white/10 px-6 py-6 shadow-[0_18px_45px_rgba(0,0,0,0.18)] backdrop-blur-md sm:px-7 sm:py-7">
|
||||||
<h4
|
<h4
|
||||||
className="text-white font-semibold mb-4 text-sm uppercase tracking-widest"
|
className="mb-5 text-white/95 uppercase tracking-[0.28em]"
|
||||||
style={{ fontFamily: "'Nunito Sans', sans-serif" }}
|
style={{
|
||||||
|
fontFamily: "'Nunito Sans', sans-serif",
|
||||||
|
fontSize: "0.8rem",
|
||||||
|
fontWeight: 700,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Orari
|
Orari di apertura
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-2">
|
|
||||||
{[
|
|
||||||
{ days: "Visite: Lunedì — Venerdì", hours: "09:00 — 19:30" },
|
|
||||||
{ days: "Visite: Sabato", hours: "09:00 — 17:00" },
|
|
||||||
{ days: "Urgenze: Lunedì — Venerdì", hours: "08:00 — 22:30" },
|
|
||||||
{ days: "Urgenze: Sabato e Festivi", hours: "09:00 — 20:00" },
|
|
||||||
|
|
||||||
].map((slot) => (
|
<div className="space-y-4">
|
||||||
<div key={slot.days} className="flex items-start gap-2">
|
{openingHours.map((slot) => (
|
||||||
<Clock size={13} className="text-[#4ECDC4] flex-shrink-0 mt-0.5" />
|
<div key={slot.days} className="flex items-start gap-3.5">
|
||||||
|
<div className="mt-0.5 rounded-full bg-[#4ECDC4]/20 p-2">
|
||||||
|
<Clock size={14} className="text-[#7ce3dc]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-white/70 text-xs">{slot.days}</p>
|
<p className="text-sm font-semibold text-white/92 sm:text-[0.98rem]">
|
||||||
<p className="text-white/50 text-xs whitespace-pre-line">{slot.hours}</p>
|
{slot.days}
|
||||||
|
</p>
|
||||||
|
<p className="mt-0.5 text-sm text-white/72 sm:text-[0.96rem]">
|
||||||
|
{slot.hours}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Badge reperibilità */}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* CTA buttons */}
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.7, delay: 0.7 }}
|
transition={{ duration: 0.7, delay: 0.75 }}
|
||||||
className="flex flex-col sm:flex-row gap-4"
|
className="flex flex-col gap-4 pt-1 sm:flex-row"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
className="bg-[#4ECDC4] hover:bg-[#3ab5ad] text-white font-bold text-base px-8 py-6 shadow-lg shadow-[#4ECDC4]/30 transition-all duration-300 hover:shadow-xl hover:shadow-[#4ECDC4]/40 hover:-translate-y-0.5"
|
className="bg-[#4ECDC4] px-8 py-6 text-base font-bold text-white shadow-lg shadow-[#4ECDC4]/30 transition-all duration-300 hover:-translate-y-0.5 hover:bg-[#3ab5ad] hover:shadow-xl hover:shadow-[#4ECDC4]/40"
|
||||||
onClick={() => document.getElementById("prenotazione")?.scrollIntoView({ behavior: "smooth" })}
|
onClick={() =>
|
||||||
|
document.getElementById("prenotazione")?.scrollIntoView({ behavior: "smooth" })
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Calendar size={18} className="mr-2" />
|
<Calendar size={18} className="mr-2" />
|
||||||
Prenota una Visita
|
Prenota una Visita
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="border-2 border-white text-white hover:bg-white hover:text-[#1B4F72] font-semibold text-base px-8 py-6 transition-all duration-300 bg-transparent backdrop-blur-sm"
|
className="border-2 border-white bg-transparent px-8 py-6 text-base font-semibold text-white backdrop-blur-sm transition-all duration-300 hover:bg-white hover:text-[#1B4F72]"
|
||||||
asChild
|
asChild
|
||||||
>
|
>
|
||||||
<a href="tel:0598396263">
|
<a href="tel:0598396263">
|
||||||
@@ -177,32 +174,24 @@ export default function HeroSection() {
|
|||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Stats */}
|
|
||||||
{/* Orari */}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Indicatori slideshow */}
|
<div className="absolute bottom-8 left-1/2 z-10 flex -translate-x-1/2 gap-2">
|
||||||
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex gap-2 z-10">
|
|
||||||
{heroImages.map((_, index) => (
|
{heroImages.map((_, index) => (
|
||||||
<button
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => setCurrentImage(index)}
|
onClick={() => setCurrentImage(index)}
|
||||||
className={`transition-all duration-300 rounded-full ${
|
className={`rounded-full transition-all duration-300 ${
|
||||||
index === currentImage
|
index === currentImage ? "h-2 w-8 bg-[#4ECDC4]" : "h-2 w-2 bg-white/50 hover:bg-white/80"
|
||||||
? "w-8 h-2 bg-[#4ECDC4]"
|
|
||||||
: "w-2 h-2 bg-white/50 hover:bg-white/80"
|
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Scroll indicator */}
|
<div className="absolute bottom-8 right-8 z-10 hidden flex-col items-center gap-2 text-white/60 md:flex">
|
||||||
<div className="absolute bottom-8 right-8 z-10 hidden md:flex flex-col items-center gap-2 text-white/60">
|
<span className="mb-2 rotate-90 text-xs uppercase tracking-widest">Scroll</span>
|
||||||
<span className="text-xs uppercase tracking-widest rotate-90 mb-2">Scroll</span>
|
|
||||||
<ChevronDown size={16} className="animate-bounce" />
|
<ChevronDown size={16} className="animate-bounce" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ const navLinks: NavLink[] = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ label: "Il Team", href: "#team" },
|
{ label: "Il Team", href: "#team" },
|
||||||
{ label: "News", href: "#news" },
|
|
||||||
{ label: "Contatti", href: "#contatti" },
|
{ label: "Contatti", href: "#contatti" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -9,16 +9,15 @@ import { useRef } from "react";
|
|||||||
import {
|
import {
|
||||||
Activity,
|
Activity,
|
||||||
Apple,
|
Apple,
|
||||||
ArrowRight,
|
|
||||||
Bone,
|
Bone,
|
||||||
Eye,
|
Eye,
|
||||||
FlaskConical,
|
FlaskConical,
|
||||||
|
HeartPulse,
|
||||||
Scan,
|
Scan,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Stethoscope,
|
Stethoscope,
|
||||||
Video,
|
Video,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
type ServiceItem = {
|
type ServiceItem = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -103,6 +102,19 @@ const clinicalServices: ServiceItem[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const cardiologyService: ServiceItem = {
|
||||||
|
id: "cardiologia",
|
||||||
|
title: "Cardiologia",
|
||||||
|
subtitle: "Diagnostica cardiaca",
|
||||||
|
description:
|
||||||
|
"Approfondimenti dedicati alla funzionalita cardiaca con esami mirati e monitoraggi utili nei percorsi diagnostici piu delicati.",
|
||||||
|
image: "/images/services_cardiologia.jpg",
|
||||||
|
icon: HeartPulse,
|
||||||
|
color: "#8C5A7A",
|
||||||
|
features: ["Ecocardio", "ECG", "Holter", "Test malattie ereditarie"],
|
||||||
|
tone: "specialist",
|
||||||
|
};
|
||||||
|
|
||||||
const specialistVisits: ServiceItem[] = [
|
const specialistVisits: ServiceItem[] = [
|
||||||
{
|
{
|
||||||
id: "oncologia",
|
id: "oncologia",
|
||||||
@@ -208,11 +220,6 @@ function ServiceCard({
|
|||||||
>
|
>
|
||||||
<service.icon size={22} className="text-white" />
|
<service.icon size={22} className="text-white" />
|
||||||
</div>
|
</div>
|
||||||
{specialist && (
|
|
||||||
<div className="absolute left-4 top-4 rounded-full bg-white/90 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-[#7d5233] shadow-sm">
|
|
||||||
Alta specializzazione
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
@@ -236,16 +243,6 @@ function ServiceCard({
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<button
|
|
||||||
className="group/btn flex items-center gap-1.5 text-sm font-semibold transition-all duration-200"
|
|
||||||
style={{ color: service.color }}
|
|
||||||
onClick={() => {
|
|
||||||
toast("Sezione in arrivo", { description: `La pagina dedicata a ${service.title} sara disponibile a breve.` });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Approfondisci
|
|
||||||
<ArrowRight size={15} className="transition-transform duration-200 group-hover/btn:translate-x-1" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
@@ -267,12 +264,6 @@ export default function ServicesSection() {
|
|||||||
transition={{ duration: 0.7 }}
|
transition={{ duration: 0.7 }}
|
||||||
className="mb-14"
|
className="mb-14"
|
||||||
>
|
>
|
||||||
<div className="mb-4 flex items-center gap-3">
|
|
||||||
<div className="h-0.5 w-12 bg-[#4ECDC4]" />
|
|
||||||
<span className="text-sm font-semibold uppercase tracking-widest text-[#4ECDC4]">
|
|
||||||
Specializzazioni
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<h2
|
<h2
|
||||||
className="mb-4 text-[#1B4F72]"
|
className="mb-4 text-[#1B4F72]"
|
||||||
style={{ fontFamily: "'Cormorant Garamond', serif", fontSize: "clamp(2rem, 4vw, 3rem)", fontWeight: 600 }}
|
style={{ fontFamily: "'Cormorant Garamond', serif", fontSize: "clamp(2rem, 4vw, 3rem)", fontWeight: 600 }}
|
||||||
@@ -290,6 +281,20 @@ export default function ServicesSection() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 xl:hidden flex justify-center">
|
||||||
|
<ServiceCard
|
||||||
|
service={cardiologyService}
|
||||||
|
index={clinicalServices.length}
|
||||||
|
className="w-full md:w-[calc(50%-1rem)]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 hidden xl:grid xl:grid-cols-3 xl:gap-8">
|
||||||
|
<div />
|
||||||
|
<ServiceCard service={cardiologyService} index={clinicalServices.length} className="w-full" />
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
ref={specialistRef}
|
ref={specialistRef}
|
||||||
initial={{ opacity: 0, y: 30 }}
|
initial={{ opacity: 0, y: 30 }}
|
||||||
@@ -297,25 +302,15 @@ export default function ServicesSection() {
|
|||||||
transition={{ duration: 0.7, delay: 0.2 }}
|
transition={{ duration: 0.7, delay: 0.2 }}
|
||||||
className="mt-20"
|
className="mt-20"
|
||||||
>
|
>
|
||||||
<div className="mb-4 flex items-center gap-3">
|
|
||||||
<div className="h-0.5 w-12 bg-[#A95F3A]" />
|
|
||||||
<span className="text-sm font-semibold uppercase tracking-widest text-[#A95F3A]">
|
|
||||||
Alta specializzazione
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<h3
|
<h3
|
||||||
className="mb-4 text-[#1B4F72]"
|
className="mb-4 text-[#1B4F72]"
|
||||||
style={{ fontFamily: "'Cormorant Garamond', serif", fontSize: "clamp(1.9rem, 3.7vw, 2.8rem)", fontWeight: 600 }}
|
style={{ fontFamily: "'Cormorant Garamond', serif", fontSize: "clamp(1.9rem, 3.7vw, 2.8rem)", fontWeight: 600 }}
|
||||||
>
|
>
|
||||||
Visite Specialistiche
|
Visite Specialistiche
|
||||||
</h3>
|
</h3>
|
||||||
<p className="max-w-2xl text-base leading-relaxed text-gray-600">
|
|
||||||
Un secondo livello di consulenza clinica per casi che richiedono competenze dedicate,
|
|
||||||
approfondimenti mirati e percorsi di valutazione ad alto valore aggiunto.
|
|
||||||
</p>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="mt-10 grid grid-cols-1 gap-8 md:grid-cols-2 xl:grid-cols-6">
|
<div className="mt-8 grid grid-cols-1 gap-8 md:grid-cols-2 xl:grid-cols-6">
|
||||||
{specialistVisits.map((service, index) => (
|
{specialistVisits.map((service, index) => (
|
||||||
<ServiceCard
|
<ServiceCard
|
||||||
key={service.id}
|
key={service.id}
|
||||||
@@ -326,22 +321,6 @@ export default function ServicesSection() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={specialistInView ? { opacity: 1 } : {}}
|
|
||||||
transition={{ duration: 0.7, delay: 0.6 }}
|
|
||||||
className="mt-12 text-center"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="inline-flex items-center gap-2 rounded-full border-2 border-[#1B4F72] px-8 py-3 font-semibold text-[#1B4F72] transition-all duration-300 hover:bg-[#1B4F72] hover:text-white"
|
|
||||||
onClick={() => {
|
|
||||||
toast("Tutti i servizi", { description: "La pagina completa dei servizi sara disponibile a breve." });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Tutti i servizi
|
|
||||||
<ArrowRight size={16} />
|
|
||||||
</button>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const medicalTeam: TeamMember[] = [
|
|||||||
color: "#4ECDC4",
|
color: "#4ECDC4",
|
||||||
bio: [
|
bio: [
|
||||||
"Laureata a pieni voti con lode presso l'Universita di Bologna, ha svolto un tirocinio in medicina interna e chirurgia presso l'Ospedale Veterinario dell'Universita di Copenhagen.",
|
"Laureata a pieni voti con lode presso l'Universita di Bologna, ha svolto un tirocinio in medicina interna e chirurgia presso l'Ospedale Veterinario dell'Universita di Copenhagen.",
|
||||||
"Iscritta all'Ordine dei Medici Veterinari di Modena (n. 749), e socia SCIVAC e SIVAE. Ha seguito diversi corsi di perfezionamento in ecografia addominale ed ecocardiografia del cane e del gatto.",
|
"Iscritta all'Ordine dei Medici Veterinari di Modena (n. 749). Ha seguito diversi corsi di perfezionamento in ecografia addominale ed ecocardiografia del cane e del gatto.",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -60,6 +60,24 @@ const medicalTeam: TeamMember[] = [
|
|||||||
specialization: "Profilo in aggiornamento",
|
specialization: "Profilo in aggiornamento",
|
||||||
color: "#C58C63",
|
color: "#C58C63",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Dott. Riccardo Suffritti",
|
||||||
|
role: "Medico veterinario",
|
||||||
|
specialization: "Profilo in aggiornamento",
|
||||||
|
color: "#7A8FA8",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dott.ssa Elena Venturelli",
|
||||||
|
role: "Medico veterinario",
|
||||||
|
specialization: "Profilo in aggiornamento",
|
||||||
|
color: "#4C7A9F",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dott.ssa Cinzia Pellegrini",
|
||||||
|
role: "Medico veterinario",
|
||||||
|
specialization: "Profilo in aggiornamento",
|
||||||
|
color: "#8E6C88",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const collaborators: TeamMember[] = [
|
const collaborators: TeamMember[] = [
|
||||||
@@ -80,16 +98,16 @@ const collaborators: TeamMember[] = [
|
|||||||
color: "#7EA55A",
|
color: "#7EA55A",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Dott.ssa Elena Venturelli",
|
name: "Dott. Boris Del Pozzo",
|
||||||
role: "Collaborazione specialistica",
|
role: "Collaborazione specialistica",
|
||||||
specialization: "Profilo in aggiornamento",
|
specialization: "Profilo in aggiornamento",
|
||||||
color: "#4C7A9F",
|
color: "#6E8FA7",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Dott.ssa Cinzia Pellegrini",
|
name: "Dott.ssa Silvia Palladini",
|
||||||
role: "Collaborazione specialistica",
|
role: "Collaborazione specialistica",
|
||||||
specialization: "Profilo in aggiornamento",
|
specialization: "Profilo in aggiornamento",
|
||||||
color: "#8E6C88",
|
color: "#B6876F",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -173,13 +191,9 @@ function TeamCard({
|
|||||||
|
|
||||||
function TeamGroup({
|
function TeamGroup({
|
||||||
eyebrow,
|
eyebrow,
|
||||||
title,
|
|
||||||
description,
|
|
||||||
members,
|
members,
|
||||||
}: {
|
}: {
|
||||||
eyebrow: string;
|
eyebrow: string;
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
members: TeamMember[];
|
members: TeamMember[];
|
||||||
}) {
|
}) {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
@@ -200,15 +214,6 @@ function TeamGroup({
|
|||||||
{eyebrow}
|
{eyebrow}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-3 md:flex-row md:items-end md:justify-between">
|
|
||||||
<h3
|
|
||||||
className="text-[#1B4F72]"
|
|
||||||
style={{ fontFamily: "'Cormorant Garamond', serif", fontSize: "clamp(1.8rem, 3.2vw, 2.5rem)", fontWeight: 600 }}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</h3>
|
|
||||||
<p className="max-w-xl text-sm leading-relaxed text-gray-600">{description}</p>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 xl:grid-cols-3">
|
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 xl:grid-cols-3">
|
||||||
@@ -226,15 +231,11 @@ export default function TeamSection() {
|
|||||||
<div className="container">
|
<div className="container">
|
||||||
<TeamGroup
|
<TeamGroup
|
||||||
eyebrow="Team medico"
|
eyebrow="Team medico"
|
||||||
title="Team medico"
|
|
||||||
description="I professionisti che seguono l'attivita clinica quotidiana della struttura, con competenze diverse e percorsi di aggiornamento continui."
|
|
||||||
members={medicalTeam}
|
members={medicalTeam}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TeamGroup
|
<TeamGroup
|
||||||
eyebrow="Collaborazioni"
|
eyebrow="Collaborazioni"
|
||||||
title="Collaboratori"
|
|
||||||
description="Una rete di competenze specialistiche che amplia le possibilita diagnostiche e terapeutiche disponibili per i pazienti della clinica."
|
|
||||||
members={collaborators}
|
members={collaborators}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import HeroSection from "@/components/HeroSection";
|
|||||||
import ServicesSection from "@/components/ServicesSection";
|
import ServicesSection from "@/components/ServicesSection";
|
||||||
import AboutSection from "@/components/AboutSection";
|
import AboutSection from "@/components/AboutSection";
|
||||||
import TeamSection from "@/components/TeamSection";
|
import TeamSection from "@/components/TeamSection";
|
||||||
import NewsSection from "@/components/NewsSection";
|
|
||||||
import BookingSection from "@/components/BookingSection";
|
import BookingSection from "@/components/BookingSection";
|
||||||
import AuthSection from "@/components/AuthSection";
|
import AuthSection from "@/components/AuthSection";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
@@ -23,7 +22,6 @@ export default function Home() {
|
|||||||
<ServicesSection />
|
<ServicesSection />
|
||||||
<AboutSection />
|
<AboutSection />
|
||||||
<TeamSection />
|
<TeamSection />
|
||||||
<NewsSection />
|
|
||||||
<BookingSection />
|
<BookingSection />
|
||||||
<AuthSection />
|
<AuthSection />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ export default defineConfig({
|
|||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3001,
|
||||||
strictPort: false, // Will find next available port if 3000 is busy
|
strictPort: false, // Will find next available port if 3000 is busy
|
||||||
host: true,
|
host: true,
|
||||||
allowedHosts: [
|
allowedHosts: [
|
||||||
|
|||||||
Reference in New Issue
Block a user