feat: layout base con header, footer e 404
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
interface Props { variant?: 'full' | 'footer' }
|
||||||
|
const { variant = 'full' } = Astro.props;
|
||||||
|
---
|
||||||
|
<form class:list={['cform', `cform--${variant}`]} method="post" action="/api/contact" novalidate>
|
||||||
|
<input class="field" name="firstName" placeholder="Nome" required maxlength="100" />
|
||||||
|
{variant === 'full' && <input class="field" name="lastName" placeholder="Cognome" maxlength="100" />}
|
||||||
|
{variant === 'full' && <input class="field" name="phone" placeholder="Telefono" maxlength="40" />}
|
||||||
|
<input class="field" type="email" name="email" placeholder="E-mail" required maxlength="200" />
|
||||||
|
<textarea class="field" name="message" placeholder="Messaggio" required maxlength="5000"></textarea>
|
||||||
|
<input class="hp" type="text" name="website" tabindex="-1" autocomplete="off" />
|
||||||
|
<button class="btn" type="submit">Invia messaggio</button>
|
||||||
|
<p class="form-msg" hidden></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.querySelectorAll<HTMLFormElement>('.cform').forEach((form) => {
|
||||||
|
form.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const msg = form.querySelector<HTMLParagraphElement>('.form-msg')!;
|
||||||
|
const btn = form.querySelector<HTMLButtonElement>('button')!;
|
||||||
|
btn.disabled = true;
|
||||||
|
msg.hidden = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/contact', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(Object.fromEntries(new FormData(form))),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
msg.textContent = res.ok ? 'Messaggio inviato, ti ricontatteremo al più presto.' : (data.error ?? 'Errore di invio, riprova più tardi.');
|
||||||
|
msg.className = `form-msg ${res.ok ? 'form-msg--ok' : 'form-msg--err'}`;
|
||||||
|
if (res.ok) form.reset();
|
||||||
|
} catch {
|
||||||
|
msg.textContent = 'Errore di rete, riprova più tardi.';
|
||||||
|
msg.className = 'form-msg form-msg--err';
|
||||||
|
}
|
||||||
|
msg.hidden = false;
|
||||||
|
btn.disabled = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
import { site } from '../data/site';
|
||||||
|
import ContactForm from './ContactForm.astro';
|
||||||
|
const year = new Date().getFullYear();
|
||||||
|
---
|
||||||
|
<footer class="ftr">
|
||||||
|
<div class="container ftr__grid">
|
||||||
|
<div>
|
||||||
|
<img src="/img/logo-light.png" alt="InsanityLab" width="140" loading="lazy" />
|
||||||
|
<p class="ftr__tag">PERFORMANCE. BALANCE. LONGEVITY.</p>
|
||||||
|
<p>Promuoviamo il movimento come cura di sé. Abitudini sostenibili per benessere fisico e mentale.</p>
|
||||||
|
<p>{site.phone}<br />{site.address}, {site.cityLine}</p>
|
||||||
|
{site.hours.map((h) => <p class="ftr__hours">{h}</p>)}
|
||||||
|
</div>
|
||||||
|
<nav aria-label="training">
|
||||||
|
<h4>Training</h4>
|
||||||
|
{site.footerTraining.map((l) => <a href={l.href}>{l.label}</a>)}
|
||||||
|
</nav>
|
||||||
|
<nav aria-label="programmi">
|
||||||
|
<h4>Programmi</h4>
|
||||||
|
{site.footerPrograms.map((l) => <a href={l.href}>{l.label}</a>)}
|
||||||
|
</nav>
|
||||||
|
<div>
|
||||||
|
<h4>Contact</h4>
|
||||||
|
<ContactForm variant="footer" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ftr__bar">
|
||||||
|
<div class="container ftr__bar-in">
|
||||||
|
<p>© {year} <strong>Insanitylab</strong>, All Rights Reserved</p>
|
||||||
|
<p>Seguici {site.socials.map((s) => <a href={s.url} rel="noopener" target="_blank">{s.label}</a>)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.ftr { background: var(--c-dark); color: #b9aea4; font-size: .88rem; }
|
||||||
|
.ftr a { color: #b9aea4; text-decoration: none; display: block; padding: 4px 0; }
|
||||||
|
.ftr a:hover { color: #fff; }
|
||||||
|
.ftr h4 { color: #fff; text-transform: uppercase; letter-spacing: .18em; font-size: .78rem; margin-bottom: 18px; }
|
||||||
|
.ftr__grid { display: grid; grid-template-columns: 1.3fr 1fr 1fr 1.3fr; gap: 40px; padding-block: 70px; }
|
||||||
|
.ftr__tag { font-family: var(--font-heading); font-size: .7rem; letter-spacing: .2em; color: #fff; }
|
||||||
|
.ftr__hours { margin: 0; }
|
||||||
|
.ftr__bar { border-top: 1px solid rgba(255,255,255,.08); padding-block: 18px; }
|
||||||
|
.ftr__bar-in { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 10px; }
|
||||||
|
.ftr__bar-in a { display: inline; margin-left: 12px; }
|
||||||
|
@media (max-width: 991px) { .ftr__grid { grid-template-columns: 1fr 1fr; } }
|
||||||
|
@media (max-width: 600px) { .ftr__grid { grid-template-columns: 1fr; } }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
import { site } from '../data/site';
|
||||||
|
const path = Astro.url.pathname;
|
||||||
|
---
|
||||||
|
<header class="hdr" id="site-header">
|
||||||
|
<div class="container hdr__in">
|
||||||
|
<a href="/" class="hdr__logo"><img src="/img/logo-dark.png" alt="InsanityLab" width="150" /></a>
|
||||||
|
<nav class="hdr__nav" id="site-nav" aria-label="principale">
|
||||||
|
{site.navigation.map((item) => (
|
||||||
|
<a href={item.href} class:list={['hdr__link', { 'is-active': path === item.href || (item.href !== '/' && path.startsWith(item.href)) }]}>{item.label}</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
<button class="hdr__burger" id="nav-toggle" aria-label="Apri menu" aria-expanded="false">
|
||||||
|
<span></span><span></span><span></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.hdr { position: sticky; top: 0; z-index: 100; background: #fff; transition: box-shadow .2s; }
|
||||||
|
.hdr.is-scrolled { box-shadow: 0 2px 14px rgba(0,0,0,.08); }
|
||||||
|
.hdr__in { display: flex; align-items: center; justify-content: space-between; height: var(--header-h); }
|
||||||
|
.hdr__nav { display: flex; gap: 34px; }
|
||||||
|
.hdr__link { font-family: var(--font-heading); font-size: .72rem; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; text-decoration: none; color: var(--c-heading); }
|
||||||
|
.hdr__link:hover, .hdr__link.is-active { color: var(--c-accent-dark); }
|
||||||
|
.hdr__burger { display: none; background: none; border: 0; cursor: pointer; padding: 8px; }
|
||||||
|
.hdr__burger span { display: block; width: 22px; height: 2px; background: var(--c-heading); margin: 5px 0; }
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
.hdr__nav { display: none; position: absolute; top: var(--header-h); left: 0; right: 0; background: #fff; flex-direction: column; gap: 0; padding: 10px 20px 20px; box-shadow: 0 10px 20px rgba(0,0,0,.08); }
|
||||||
|
.hdr__nav.is-open { display: flex; }
|
||||||
|
.hdr__link { padding: 12px 0; }
|
||||||
|
.hdr__burger { display: block; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const header = document.getElementById('site-header')!;
|
||||||
|
const toggle = document.getElementById('nav-toggle')!;
|
||||||
|
const nav = document.getElementById('site-nav')!;
|
||||||
|
addEventListener('scroll', () => header.classList.toggle('is-scrolled', scrollY > 10), { passive: true });
|
||||||
|
toggle.addEventListener('click', () => {
|
||||||
|
const open = nav.classList.toggle('is-open');
|
||||||
|
toggle.setAttribute('aria-expanded', String(open));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
import '@fontsource/montserrat/400.css';
|
||||||
|
import '@fontsource/montserrat/500.css';
|
||||||
|
import '@fontsource/montserrat/600.css';
|
||||||
|
import '@fontsource/montserrat/700.css';
|
||||||
|
import '@fontsource/open-sans/400.css';
|
||||||
|
import '@fontsource/open-sans/600.css';
|
||||||
|
import '../styles/global.css';
|
||||||
|
import Header from '../components/Header.astro';
|
||||||
|
import Footer from '../components/Footer.astro';
|
||||||
|
|
||||||
|
interface Props { title: string; description?: string }
|
||||||
|
const { title, description = 'InsanityLab — Performance. Balance. Longevity. Allenamento personalizzato, piccoli gruppi e benessere a Bologna.' } = Astro.props;
|
||||||
|
---
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>{title} — InsanityLab</title>
|
||||||
|
<meta name="description" content={description} />
|
||||||
|
<meta property="og:title" content={`${title} — InsanityLab`} />
|
||||||
|
<meta property="og:description" content={description} />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||||
|
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<Header />
|
||||||
|
<main><slot /></main>
|
||||||
|
<Footer />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
import Base from '../layouts/Base.astro';
|
||||||
|
export const prerender = true;
|
||||||
|
---
|
||||||
|
<Base title="Pagina non trovata">
|
||||||
|
<section class="section" style="text-align:center; padding-block:140px;">
|
||||||
|
<div class="container">
|
||||||
|
<p class="eyebrow">Errore 404</p>
|
||||||
|
<h1>Pagina non trovata</h1>
|
||||||
|
<p>La pagina che cerchi non esiste o è stata spostata.</p>
|
||||||
|
<a class="btn" href="/">Torna alla home</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Base>
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
---
|
---
|
||||||
|
import Base from '../layouts/Base.astro';
|
||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
---
|
---
|
||||||
<html lang="it"><head><title>InsanityLab</title></head>
|
<Base title="Performance. Balance. Longevity.">
|
||||||
<body><h1>InsanityLab — in costruzione</h1></body></html>
|
<section class="section"><div class="container"><h1>Homepage in costruzione</h1></div></section>
|
||||||
|
</Base>
|
||||||
|
|||||||
Reference in New Issue
Block a user