5c0ed95e9e
- add PostalAddress and areaServed (Malaga city + province) to the sitewide JSON-LD structured data - show the same address as visible NAP text in the footer and on the contact page, matching the schema exactly - add address and area served to llms.txt for AEO/AI-crawler readability
223 lines
8.1 KiB
Plaintext
223 lines
8.1 KiB
Plaintext
---
|
|
import SiteHeader from '../components/SiteHeader.astro';
|
|
import SiteFooter from '../components/SiteFooter.astro';
|
|
import WhatsAppButton from '../components/WhatsAppButton.astro';
|
|
import CookieBanner from '../components/CookieBanner.astro';
|
|
import '../styles/tokens.css';
|
|
import '../styles/base.css';
|
|
import '../styles/layout.css';
|
|
import '../styles/components.css';
|
|
import '../styles/utilities.css';
|
|
import '../styles/animations.css';
|
|
|
|
interface Props {
|
|
title: string;
|
|
description?: string;
|
|
activeNav?: string;
|
|
canonical?: string;
|
|
ogImage?: string;
|
|
robots?: string;
|
|
structuredData?: Record<string, unknown> | Array<Record<string, unknown>>;
|
|
}
|
|
|
|
const {
|
|
title,
|
|
description = 'MNQ Catering y Evento · Experiencias gastronómicas premium para bodas, eventos corporativos y celebraciones familiares.',
|
|
activeNav,
|
|
canonical,
|
|
ogImage = '/images/foto-bodas.jpg',
|
|
robots = 'index,follow',
|
|
structuredData,
|
|
} = Astro.props;
|
|
|
|
const site = Astro.site ?? new URL('https://www.mnqcatering.com');
|
|
const canonicalUrl = new URL(canonical ?? Astro.url.pathname, site).toString();
|
|
const ogImageUrl = new URL(ogImage, site).toString();
|
|
const baseStructuredData = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'CateringService',
|
|
name: 'MNQ Catering y Evento',
|
|
url: site.origin,
|
|
description:
|
|
'Servicio de catering para bodas, eventos corporativos y reuniones familiares con enfoque premium.',
|
|
telephone: '+34 678 17 15 13',
|
|
serviceType: ['Bodas', 'Eventos corporativos', 'Reuniones familiares'],
|
|
// Verified business address (owner-confirmed). Geo coordinates not confirmed — left out.
|
|
address: {
|
|
'@type': 'PostalAddress',
|
|
streetAddress: 'Calle Camino Vivero, 6',
|
|
postalCode: '29014',
|
|
addressLocality: 'Málaga',
|
|
addressRegion: 'Málaga',
|
|
addressCountry: 'ES',
|
|
},
|
|
// Kept conservative to the confirmed service area: city + province of Málaga.
|
|
areaServed: [
|
|
{
|
|
'@type': 'City',
|
|
name: 'Málaga',
|
|
},
|
|
{
|
|
'@type': 'AdministrativeArea',
|
|
name: 'Provincia de Málaga',
|
|
},
|
|
],
|
|
contactPoint: {
|
|
'@type': 'ContactPoint',
|
|
contactType: 'customer service',
|
|
telephone: '+34 678 17 15 13',
|
|
availableLanguage: ['es'],
|
|
},
|
|
};
|
|
const structuredDataScripts = [
|
|
baseStructuredData,
|
|
...((Array.isArray(structuredData)
|
|
? structuredData
|
|
: structuredData
|
|
? [structuredData]
|
|
: []) as Array<Record<string, unknown>>),
|
|
];
|
|
const umamiHost = Astro.site ? Astro.site.origin : 'https://www.mnqcatering.com';
|
|
const rawUmamiUrl = Astro.url ? import.meta.env.PUBLIC_UMAMI_URL : undefined;
|
|
const umamiWebsiteId = import.meta.env.PUBLIC_UMAMI_WEBSITE_ID;
|
|
const normalizedUmamiUrl = rawUmamiUrl
|
|
? rawUmamiUrl.startsWith('http://') || rawUmamiUrl.startsWith('https://')
|
|
? rawUmamiUrl
|
|
: `https://${rawUmamiUrl}`
|
|
: undefined;
|
|
const shouldLoadUmami = Boolean(normalizedUmamiUrl && umamiWebsiteId);
|
|
---
|
|
|
|
<!doctype html>
|
|
<html lang="es-ES">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>{title}</title>
|
|
<meta name="description" content={description} />
|
|
<meta name="robots" content={robots} />
|
|
<link rel="canonical" href={canonicalUrl} />
|
|
<meta property="og:title" content={title} />
|
|
<meta property="og:description" content={description} />
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:url" content={canonicalUrl} />
|
|
<meta property="og:image" content={ogImageUrl} />
|
|
<meta property="og:site_name" content="MNQ Catering y Evento" />
|
|
<meta property="og:locale" content="es_ES" />
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
<meta name="twitter:title" content={title} />
|
|
<meta name="twitter:description" content={description} />
|
|
<meta name="twitter:image" content={ogImageUrl} />
|
|
<link rel="icon" type="image/png" href="/images/logo-ojo-transparent.webp" />
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
<link
|
|
rel="preload"
|
|
as="style"
|
|
href="https://fonts.googleapis.com/css2?family=Noto+Serif:ital@0;1&family=Inter:wght@400;500;600&display=swap"
|
|
onload="this.onload=null;this.rel='stylesheet'"
|
|
/>
|
|
<noscript>
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://fonts.googleapis.com/css2?family=Noto+Serif:ital@0;1&family=Inter:wght@400;500;600&display=swap"
|
|
/>
|
|
</noscript>
|
|
{shouldLoadUmami && (
|
|
<script
|
|
async
|
|
defer
|
|
src={`${normalizedUmamiUrl}/script.js`}
|
|
data-website-id={umamiWebsiteId}
|
|
data-domains={umamiHost.replace(/^https?:\/\//, '')}
|
|
></script>
|
|
)}
|
|
{
|
|
structuredDataScripts.map((schema) => (
|
|
<script is:inline type="application/ld+json" set:html={JSON.stringify(schema)} />
|
|
))
|
|
}
|
|
</head>
|
|
<body class="page-ready">
|
|
|
|
<!-- Scroll progress bar -->
|
|
<div class="scroll-progress" id="scrollProgress" role="progressbar" aria-label="Progreso de lectura" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
|
|
|
<SiteHeader activeNav={activeNav} />
|
|
<main>
|
|
<slot />
|
|
</main>
|
|
<SiteFooter />
|
|
<WhatsAppButton />
|
|
<CookieBanner />
|
|
|
|
<script>
|
|
// ─── INTERSECTION OBSERVER ───────────────────
|
|
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
|
|
if (!prefersReduced) {
|
|
const io = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
entry.target.classList.add('visible');
|
|
io.unobserve(entry.target);
|
|
}
|
|
});
|
|
},
|
|
{ threshold: 0.1, rootMargin: '0px 0px -48px 0px' }
|
|
);
|
|
|
|
document.querySelectorAll('.reveal, .reveal-left, .reveal-scale, .gold-line, .section-eyebrow, .value-card-animate').forEach((el) => {
|
|
io.observe(el);
|
|
});
|
|
|
|
// ─── SCROLL PROGRESS ────────────────────────
|
|
const progressBar = document.getElementById('scrollProgress') as HTMLElement | null;
|
|
if (progressBar) {
|
|
const updateProgress = () => {
|
|
const scrollTop = window.scrollY;
|
|
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
|
const progress = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0;
|
|
progressBar.style.transform = `scaleX(${progress / 100})`;
|
|
progressBar.setAttribute('aria-valuenow', String(Math.round(progress)));
|
|
};
|
|
window.addEventListener('scroll', updateProgress, { passive: true });
|
|
updateProgress();
|
|
}
|
|
|
|
// ─── PAGE TRANSITIONS ───────────────────────
|
|
// Fade IN: page loads → body gets .page-ready → CSS handles fade-in
|
|
document.body.classList.add('page-ready');
|
|
|
|
// Fade OUT: intercept internal navigation links
|
|
const handleNavClick = (e: MouseEvent) => {
|
|
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
|
|
const target = e.target as HTMLElement;
|
|
const anchor = target.closest('a');
|
|
if (!anchor) return;
|
|
|
|
const href = anchor.getAttribute('href');
|
|
if (!href || href.startsWith('#') || href.startsWith('http') || href.startsWith('mailto') || href.startsWith('tel') || anchor.target === '_blank') return;
|
|
|
|
e.preventDefault();
|
|
document.body.classList.remove('page-ready');
|
|
setTimeout(() => {
|
|
window.location.href = href;
|
|
}, 300);
|
|
};
|
|
|
|
document.addEventListener('click', handleNavClick);
|
|
|
|
} else {
|
|
// Reduced motion: show immediately, no transitions
|
|
document.querySelectorAll('.reveal, .reveal-left, .reveal-scale, .gold-line, .section-eyebrow, .value-card-animate').forEach((el) => {
|
|
el.classList.add('visible');
|
|
});
|
|
document.body.classList.add('page-ready');
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|