Home: navigazione, footer, CTA, servizi (Breaking Glass), scroll-spy
- Allinea scroll a #contatti, misura header e scroll-padding, spy con documentTopY e piccolo SPY_FUDGE; hashchange/ResizeObserver; footer visibile azzera Contattaci - Footer: id site-footer, griglia senza blocco brand sopra, tagline a destra del copyright; global.css fallback scroll-padding - CTA: eyebrow Contatti, bordo/sezione allineate alle FAQ, #contatti sulla section - Servizi: callout Breaking Glass con look glossy (gradient, highlight, luce) Made-with: Cursor
This commit is contained in:
@ -19,20 +19,9 @@ const linkClass =
|
||||
'text-sm text-sky-400/90 transition-colors hover:text-sky-300';
|
||||
---
|
||||
|
||||
<footer class="relative z-[1] border-t border-nx-border px-4 py-12 sm:px-6">
|
||||
<footer id="site-footer" class="relative z-[1] border-t border-nx-border px-4 py-12 sm:px-6">
|
||||
<div class="mx-auto max-w-6xl">
|
||||
<div class="border-b border-nx-border pb-10">
|
||||
<a
|
||||
href="/"
|
||||
class="inline-block text-base font-semibold tracking-tight text-nx-fg hover:text-white sm:text-lg"
|
||||
>{footer.brandName}</a
|
||||
>
|
||||
<p class="mt-2 max-w-xl text-sm leading-relaxed text-nx-muted">
|
||||
{footer.tagline}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Quattro colonne omogenee (solo link / elenchi); preferenze cookie sotto, staccate -->
|
||||
<!-- Griglia link; brand + slogan spostati in fondo a fianco al copyright -->
|
||||
<div class="border-b border-nx-border py-10">
|
||||
<div
|
||||
class="grid grid-cols-1 gap-10 sm:grid-cols-2 lg:grid-cols-4 lg:items-start lg:gap-x-10 xl:gap-x-12"
|
||||
@ -210,8 +199,17 @@ const linkClass =
|
||||
}
|
||||
</div>
|
||||
|
||||
<p class="pt-8 text-xs leading-relaxed text-nx-muted sm:text-sm">
|
||||
<div
|
||||
class="mt-0 flex flex-col gap-3 border-t border-nx-border pt-8 sm:flex-row sm:items-end sm:justify-between sm:gap-6"
|
||||
>
|
||||
<p class="min-w-0 text-xs leading-relaxed text-nx-muted sm:text-sm">
|
||||
© {new Date().getFullYear()} NexStudio. {footer.legalLine}
|
||||
</p>
|
||||
<p
|
||||
class="max-w-md shrink-0 text-xs leading-relaxed text-nx-muted sm:max-w-lg sm:text-right sm:text-sm"
|
||||
>
|
||||
{footer.tagline}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
/** Call-to-action (blocco contatti / chiusura pagina) */
|
||||
export const cta = {
|
||||
/** Riga sezione (stesso ruolo di FAQ, Stack, …) */
|
||||
eyebrow: 'Contatti',
|
||||
|
||||
/** Titolo CTA su due righe (dopo il punto) */
|
||||
titleLine1: 'Automatizza il complesso.',
|
||||
titleLine2: 'Velocizza il processo.',
|
||||
|
||||
@ -339,14 +339,30 @@ const techSliderItems = stackSection.techSlider.items;
|
||||
{card.body}
|
||||
</p>
|
||||
{'callout' in card && card.callout && (
|
||||
<div class="mt-4 rounded-xl border border-sky-500/25 bg-sky-950/20 p-4">
|
||||
<p class="text-sm font-semibold text-sky-200/95">
|
||||
<div
|
||||
class="group relative mt-4 overflow-hidden rounded-xl border border-sky-400/35 bg-gradient-to-br from-sky-500/20 via-slate-900/70 to-violet-950/50 p-4 shadow-[inset_0_1px_0_0_rgba(255,255,255,0.16),0_8px_32px_-6px_rgba(14,165,233,0.18)]"
|
||||
>
|
||||
<div
|
||||
class="pointer-events-none absolute inset-0 bg-gradient-to-b from-white/[0.14] via-white/[0.02] to-transparent"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div
|
||||
class="pointer-events-none absolute -right-6 -top-12 h-28 w-36 rotate-12 rounded-full bg-gradient-to-br from-cyan-200/30 via-sky-400/15 to-transparent blur-2xl"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div
|
||||
class="pointer-events-none absolute -bottom-8 -left-4 h-20 w-32 rotate-[-8deg] rounded-full bg-gradient-to-tr from-violet-500/10 to-transparent blur-xl"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div class="relative z-10">
|
||||
<p class="text-sm font-semibold text-sky-100/95">
|
||||
{card.callout.title}
|
||||
</p>
|
||||
<p class="mt-2 text-sm leading-relaxed text-nx-muted">
|
||||
{card.callout.body}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{'bullets' in card && card.bullets && (
|
||||
<ul class="mt-4 space-y-2 text-sm text-sky-300/90">
|
||||
@ -581,22 +597,38 @@ const techSliderItems = stackSection.techSlider.items;
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="contatti" class="relative z-10 px-4 pb-16 pt-4 sm:px-6 sm:pb-20">
|
||||
<section
|
||||
id="contatti"
|
||||
class="relative z-10 border-t border-nx-border bg-nx-surface/20 px-4 pt-14 pb-16 sm:px-6 sm:pt-20 sm:pb-20"
|
||||
>
|
||||
<div class="mx-auto max-w-6xl">
|
||||
<h2
|
||||
class="text-sm font-semibold uppercase tracking-wider text-sky-400/90"
|
||||
>
|
||||
{cta.eyebrow}
|
||||
</h2>
|
||||
<p
|
||||
class="mt-2 text-3xl font-semibold leading-snug tracking-tight text-nx-fg sm:text-4xl"
|
||||
>
|
||||
<span class="block">{cta.titleLine1}</span>
|
||||
<span class="mt-1 block text-sky-200 sm:text-sky-100/95"
|
||||
>{cta.titleLine2}</span
|
||||
>
|
||||
</p>
|
||||
|
||||
<div
|
||||
id="nx-cta-card"
|
||||
class="relative mx-auto max-w-6xl overflow-hidden rounded-3xl border border-nx-border bg-gradient-to-br from-sky-500/15 via-nx-elevated to-violet-600/15 px-5 py-5 sm:px-6 sm:py-6"
|
||||
class="mt-8 sm:mt-10"
|
||||
>
|
||||
<div
|
||||
class="nx-cta-card relative mx-auto max-w-6xl overflow-hidden rounded-3xl border border-nx-border bg-gradient-to-br from-sky-500/15 via-nx-elevated to-violet-600/15 px-5 py-5 sm:px-6 sm:py-6"
|
||||
>
|
||||
<div
|
||||
class="relative z-10 flex flex-col gap-6 lg:flex-row lg:items-start lg:gap-10"
|
||||
>
|
||||
<div class="min-w-0 flex-1 lg:max-w-[min(100%,28rem)] xl:max-w-[min(100%,32rem)]">
|
||||
<h2
|
||||
class="text-2xl font-semibold leading-snug tracking-tight sm:text-3xl"
|
||||
<div
|
||||
class="min-w-0 flex-1 lg:max-w-[min(100%,28rem)] xl:max-w-[min(100%,32rem)]"
|
||||
>
|
||||
<span class="block text-nx-fg">{cta.titleLine1}</span>
|
||||
<span class="mt-0.5 block text-sky-200 sm:text-[#bae6fd]">{cta.titleLine2}</span>
|
||||
</h2>
|
||||
<p class="mt-3 max-w-2xl text-nx-muted">
|
||||
<p class="max-w-2xl text-base leading-relaxed text-nx-muted sm:text-lg">
|
||||
{cta.body}
|
||||
</p>
|
||||
</div>
|
||||
@ -607,6 +639,8 @@ const techSliderItems = stackSection.techSlider.items;
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
|
||||
@ -1,16 +1,38 @@
|
||||
/**
|
||||
* Home: evidenzia in bianco il link del menu che corrisponde alla sezione in vista.
|
||||
* `scrollPaddingTop` e l’altezza header hanno stesso valore. Lo spy usa +4px
|
||||
* (SPY_VIEWPORT_FUDGE_PX) così il collegamento attivo segue l’inizio sezione
|
||||
* a schermo, senza dover forzare altro scroll a mano.
|
||||
*/
|
||||
const SECTION_IDS = ['prodotti', 'servizi', 'stack', 'faq', 'contatti'] as const;
|
||||
const HEADER_OFFSET = 88;
|
||||
let headerOffset = 80;
|
||||
/** Soglia leggermente sotto l’inizio reale sezione, così il link si accende in linea con lo sguardo. */
|
||||
const SPY_VIEWPORT_FUDGE_PX = 4;
|
||||
|
||||
function documentTopY(el: Element): number {
|
||||
const { top } = el.getBoundingClientRect();
|
||||
return top + window.scrollY;
|
||||
}
|
||||
|
||||
function getActiveSectionId(): string | null {
|
||||
const y = window.scrollY + HEADER_OFFSET;
|
||||
// y = scrollY + (header reale) + leggera tolleranza: l’inizio sezione attraverso
|
||||
// l’orizzonte “link attivo” un poco prima del minimo in px.
|
||||
const y = window.scrollY + headerOffset + SPY_VIEWPORT_FUDGE_PX;
|
||||
let active: string | null = null;
|
||||
for (const id of SECTION_IDS) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) continue;
|
||||
if (el.offsetTop <= y) active = id;
|
||||
if (documentTopY(el) <= y) active = id;
|
||||
}
|
||||
// Fuori focus dalla CTA: quando entra in vista il footer, non evidenziare Contattaci.
|
||||
if (active === 'contatti') {
|
||||
const siteFooter = document.getElementById('site-footer');
|
||||
if (siteFooter) {
|
||||
const { top: footerTop } = siteFooter.getBoundingClientRect();
|
||||
if (footerTop < window.innerHeight) {
|
||||
active = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return active;
|
||||
}
|
||||
@ -36,15 +58,45 @@ function onScroll(): void {
|
||||
});
|
||||
}
|
||||
|
||||
function measureStickyBarHeight(): number {
|
||||
const header = document.querySelector<HTMLElement>('header');
|
||||
if (!header) return headerOffset;
|
||||
// Riga fissa o, se il menu mobile è aperto, pannello incluso: deve coincidere con
|
||||
// l’ingombro reale sotto al quale il main può iniziare.
|
||||
return Math.round(header.getBoundingClientRect().height);
|
||||
}
|
||||
|
||||
function measureHeaderAndSync(): void {
|
||||
headerOffset = measureStickyBarHeight();
|
||||
if (headerOffset > 0) {
|
||||
document.documentElement.style.scrollPaddingTop = `${headerOffset}px`;
|
||||
}
|
||||
applyActive();
|
||||
}
|
||||
|
||||
function bind(): void {
|
||||
const path = window.location.pathname;
|
||||
if (path !== '/' && path !== '/index.html') return;
|
||||
|
||||
applyActive();
|
||||
const header = document.querySelector('header');
|
||||
measureHeaderAndSync();
|
||||
const reflow = () => {
|
||||
requestAnimationFrame(() => {
|
||||
measureHeaderAndSync();
|
||||
});
|
||||
};
|
||||
reflow();
|
||||
if (header && 'ResizeObserver' in window) {
|
||||
new ResizeObserver(() => {
|
||||
reflow();
|
||||
}).observe(header);
|
||||
}
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
window.addEventListener('resize', onScroll, { passive: true });
|
||||
window.addEventListener('hashchange', applyActive);
|
||||
window.addEventListener('load', applyActive);
|
||||
window.addEventListener('resize', reflow, { passive: true });
|
||||
window.addEventListener('hashchange', () => {
|
||||
reflow();
|
||||
});
|
||||
window.addEventListener('load', reflow, { passive: true });
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
@layer base {
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
/* Fallback: la homepage alza il valore misurando l’header in nx-nav-spy.ts */
|
||||
scroll-padding-top: 5.5rem;
|
||||
}
|
||||
body {
|
||||
@apply bg-nx-bg text-nx-fg antialiased;
|
||||
|
||||
Reference in New Issue
Block a user