feat: animazioni stile template prowess (reveal, parallax, marquee, hero, hover)
This commit is contained in:
@@ -3,13 +3,13 @@ import { agenda, disciplines, days, slots } from '../data/agenda';
|
|||||||
---
|
---
|
||||||
<section class="section section--dark agenda">
|
<section class="section section--dark agenda">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-heading"><h2>Agenda</h2></div>
|
<div class="section-heading" data-reveal><h2>Agenda</h2></div>
|
||||||
<div class="agenda__tabs" role="tablist">
|
<div class="agenda__tabs" role="tablist" data-reveal style="--reveal-delay:.1s">
|
||||||
{disciplines.map((d, i) => (
|
{disciplines.map((d, i) => (
|
||||||
<button class:list={['agenda__tab', { 'is-active': i === 0 }]} data-filter={d} role="tab" aria-selected={i === 0 ? 'true' : 'false'}>{d}</button>
|
<button class:list={['agenda__tab', { 'is-active': i === 0 }]} data-filter={d} role="tab" aria-selected={i === 0 ? 'true' : 'false'}>{d}</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div class="agenda__grid">
|
<div class="agenda__grid" data-reveal style="--reveal-delay:.2s">
|
||||||
<div class="agenda__corner"></div>
|
<div class="agenda__corner"></div>
|
||||||
{days.map((d) => <div class="agenda__day">{d}</div>)}
|
{days.map((d) => <div class="agenda__day">{d}</div>)}
|
||||||
{slots.map((slot) => (
|
{slots.map((slot) => (
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const path = Astro.url.pathname;
|
|||||||
.hdr.is-scrolled { box-shadow: 0 2px 14px rgba(0,0,0,.08); }
|
.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__in { display: flex; align-items: center; justify-content: space-between; height: var(--header-h); }
|
||||||
.hdr__nav { display: flex; gap: 34px; }
|
.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 { font-family: var(--font-heading); font-size: .72rem; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; text-decoration: none; color: var(--c-heading); transition: color .3s ease; }
|
||||||
.hdr__link:hover, .hdr__link.is-active { color: var(--c-accent-dark); }
|
.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 { 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; }
|
.hdr__burger span { display: block; width: 22px; height: 2px; background: var(--c-heading); margin: 5px 0; }
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ interface Props { title: string; eyebrow?: string }
|
|||||||
const { title, eyebrow } = Astro.props;
|
const { title, eyebrow } = Astro.props;
|
||||||
---
|
---
|
||||||
<section class="phero">
|
<section class="phero">
|
||||||
<div class="container">
|
<div class="container" data-reveal>
|
||||||
{eyebrow && <p class="eyebrow">{eyebrow}</p>}
|
{eyebrow && <p class="eyebrow">{eyebrow}</p>}
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ const blocks = [
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
{blocks.map((b) => (
|
{blocks.map((b) => (
|
||||||
<div class:list={['feat', { 'feat--reverse': b.reverse }]}>
|
<div class:list={['feat', { 'feat--reverse': b.reverse }]}>
|
||||||
<Image src={b.image} alt={b.title} class="feat__img" widths={[500, 900]} sizes="(max-width: 767px) 100vw, 45vw" />
|
<Image src={b.image} alt={b.title} class="feat__img" widths={[500, 900]} sizes="(max-width: 767px) 100vw, 45vw" data-reveal={b.reverse ? 'right' : 'left'} />
|
||||||
<div class="feat__text">
|
<div class="feat__text" data-reveal={b.reverse ? 'left' : 'right'}>
|
||||||
<h2>{b.title}</h2>
|
<h2>{b.title}</h2>
|
||||||
<p>{b.text}</p>
|
<p>{b.text}</p>
|
||||||
<a class="btn" href="/programs">Scopri</a>
|
<a class="btn" href="/programs">Scopri</a>
|
||||||
|
|||||||
@@ -37,6 +37,19 @@ const slides = [
|
|||||||
.hero__arrow { position: absolute; top: 50%; transform: translateY(-50%); background: none; border: 0; font-size: 3rem; color: var(--c-heading); cursor: pointer; }
|
.hero__arrow { position: absolute; top: 50%; transform: translateY(-50%); background: none; border: 0; font-size: 3rem; color: var(--c-heading); cursor: pointer; }
|
||||||
.hero__arrow--prev { left: 12px; } .hero__arrow--next { right: 12px; }
|
.hero__arrow--prev { left: 12px; } .hero__arrow--next { right: 12px; }
|
||||||
@media (max-width: 991px) { .hero__in { grid-template-columns: 1fr; padding-block: 40px; min-height: 0; } }
|
@media (max-width: 991px) { .hero__in { grid-template-columns: 1fr; padding-block: 40px; min-height: 0; } }
|
||||||
|
|
||||||
|
/* Ingresso al load stile Slider Revolution del template */
|
||||||
|
@keyframes hero-up { from { opacity: 0; transform: translateY(60px); } to { opacity: 1; transform: none; } }
|
||||||
|
@keyframes hero-side { from { opacity: 0; transform: translateX(60px); } to { opacity: 1; transform: none; } }
|
||||||
|
:global(html.js) .hero__line { opacity: 0; animation: hero-up .8s ease forwards; }
|
||||||
|
:global(html.js) .hero__line:nth-child(1) { animation-delay: .15s; }
|
||||||
|
:global(html.js) .hero__line:nth-child(2) { animation-delay: .3s; }
|
||||||
|
:global(html.js) .hero__line:nth-child(3) { animation-delay: .45s; }
|
||||||
|
:global(html.js) .hero__text .btn { opacity: 0; animation: hero-up .8s ease .7s forwards; }
|
||||||
|
:global(html.js) .hero__img { opacity: 0; animation: hero-side 1s ease .2s forwards; }
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
:global(html.js) .hero__line, :global(html.js) .hero__text .btn, :global(html.js) .hero__img { opacity: 1; animation: none; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ const cols = [
|
|||||||
];
|
];
|
||||||
---
|
---
|
||||||
<section class="info" aria-label="pilastri">
|
<section class="info" aria-label="pilastri">
|
||||||
{cols.map((c) => (
|
{cols.map((c, i) => (
|
||||||
<a class="info__col" href="/programs" style={`background-image:url(${c.image.src})`}>
|
<a class="info__col" href="/programs" style={`background-image:url(${c.image.src});--reveal-delay:${i * 0.15}s`} data-reveal="fade">
|
||||||
<span class="info__title">{c.title}</span>
|
<span class="info__title">{c.title}</span>
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ const imgOf = (name: string) => Object.entries(images).find(([p]) => p.includes(
|
|||||||
---
|
---
|
||||||
<section class="section section--alt">
|
<section class="section section--alt">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-heading">
|
<div class="section-heading" data-reveal>
|
||||||
<h2>Il metodo<br />InsanityLab</h2>
|
<h2>Il metodo<br />InsanityLab</h2>
|
||||||
<p set:html={methodIntro.replace('Metodo Insanitylab', '<strong>Metodo Insanitylab</strong>')} />
|
<p set:html={methodIntro.replace('Metodo Insanitylab', '<strong>Metodo Insanitylab</strong>')} />
|
||||||
</div>
|
</div>
|
||||||
<p class="method__note">{methodExtraNote}</p>
|
<p class="method__note">{methodExtraNote}</p>
|
||||||
<div class="method__row">
|
<div class="method__row">
|
||||||
{methodSteps.map((s) => (
|
{methodSteps.map((s, i) => (
|
||||||
<div class:list={['method__step', { 'method__step--extra': s.extra }]}>
|
<div class:list={['method__step', { 'method__step--extra': s.extra }]} data-reveal style={`--reveal-delay:${(i * 0.15).toFixed(2)}s`}>
|
||||||
<div class="method__circle">
|
<div class="method__circle">
|
||||||
{imgOf(s.image) && <Image src={imgOf(s.image)!} alt="" widths={[220]} sizes="220px" />}
|
{imgOf(s.image) && <Image src={imgOf(s.image)!} alt="" widths={[220]} sizes="220px" />}
|
||||||
<span>{s.n}</span>
|
<span>{s.n}</span>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const images = import.meta.glob<{ default: ImageMetadata }>('../../assets/img/pa
|
|||||||
const imgOf = (name: string) => Object.entries(images).find(([p]) => p.includes(name))?.[1].default;
|
const imgOf = (name: string) => Object.entries(images).find(([p]) => p.includes(name))?.[1].default;
|
||||||
---
|
---
|
||||||
<section class="pstrip" aria-label="partner">
|
<section class="pstrip" aria-label="partner">
|
||||||
<div class="container pstrip__in">
|
<div class="container pstrip__in" data-reveal="fade">
|
||||||
{partners.map((p) => imgOf(p.image) && <Image src={imgOf(p.image)!} alt={p.name} height={44} />)}
|
{partners.map((p) => imgOf(p.image) && <Image src={imgOf(p.image)!} alt={p.name} height={44} />)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ const icons: Record<string, string> = {
|
|||||||
---
|
---
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-heading">
|
<div class="section-heading" data-reveal>
|
||||||
<h2>Programmi</h2>
|
<h2>Programmi</h2>
|
||||||
<p>Questi sono i programmi delle classi di Insanity Lab selezionati per voi. Abbiamo scelto i migliori percorsi per offrirvi un’esperienza di allenamento completa e mirata.</p>
|
<p>Questi sono i programmi delle classi di Insanity Lab selezionati per voi. Abbiamo scelto i migliori percorsi per offrirvi un’esperienza di allenamento completa e mirata.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="pgrid">
|
<div class="pgrid">
|
||||||
{programs.map((p) => (
|
{programs.map((p, i) => (
|
||||||
<a class="pgrid__item" href={`/programs/${p.slug}`}>
|
<a class="pgrid__item" href={`/programs/${p.slug}`} data-reveal style={`--reveal-delay:${(i % 3) * 0.15}s`}>
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="var(--c-accent-dark)" stroke-width="1.4" width="52" height="52" aria-hidden="true"><path d={icons[p.slug]} /></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="var(--c-accent-dark)" stroke-width="1.4" width="52" height="52" aria-hidden="true"><path d={icons[p.slug]} /></svg>
|
||||||
<h3>{p.title}</h3>
|
<h3>{p.title}</h3>
|
||||||
<p>{p.subtitle}</p>
|
<p>{p.subtitle}</p>
|
||||||
@@ -29,7 +29,10 @@ const icons: Record<string, string> = {
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.pgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 50px 40px; text-align: center; }
|
.pgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 50px 40px; text-align: center; }
|
||||||
.pgrid__item { text-decoration: none; }
|
.pgrid__item { text-decoration: none; transition: transform .3s ease; }
|
||||||
|
.pgrid__item:hover { transform: translateY(-4px); }
|
||||||
|
.pgrid__item:hover h3 { color: var(--c-accent-dark); }
|
||||||
|
.pgrid__item h3 { transition: color .3s ease; }
|
||||||
.pgrid__item svg { margin-inline: auto; }
|
.pgrid__item svg { margin-inline: auto; }
|
||||||
.pgrid__item h3 { text-transform: uppercase; letter-spacing: .12em; font-size: .95rem; margin: 18px 0 6px; }
|
.pgrid__item h3 { text-transform: uppercase; letter-spacing: .12em; font-size: .95rem; margin: 18px 0 6px; }
|
||||||
.pgrid__item p { font-size: .85rem; color: var(--c-text-light); margin: 0; }
|
.pgrid__item p { font-size: .85rem; color: var(--c-text-light); margin: 0; }
|
||||||
|
|||||||
@@ -1,22 +1,43 @@
|
|||||||
---
|
---
|
||||||
import quoteBg from '../../assets/img/quote-bg.jpg';
|
import quoteBg from '../../assets/img/quote-bg.jpg';
|
||||||
|
const quote = 'PERFORMANCE È SPINGERE OLTRE I PROPRI LIMITI, BALANCE È SAPERLI ASCOLTARE, LONGEVITY È COSTRUIRE NEL TEMPO CIÒ CHE DAVVERO CONTA.';
|
||||||
|
const quoteHtml = quote.replace(/(PERFORMANCE|BALANCE|LONGEVITY)/g, '<strong>$1</strong>');
|
||||||
---
|
---
|
||||||
<section class="quote" style={`background-image:url(${quoteBg.src})`}>
|
<section class="quote" data-parallax>
|
||||||
<div class="container quote__in">
|
<div class="quote__bg" style={`background-image:url(${quoteBg.src})`}></div>
|
||||||
|
<div class="quote__in">
|
||||||
<p class="quote__mark">“</p>
|
<p class="quote__mark">“</p>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p><strong>PERFORMANCE</strong> È SPINGERE OLTRE I PROPRI LIMITI, <strong>BALANCE</strong> È SAPERLI ASCOLTARE, <strong>LONGEVITY</strong> È COSTRUIRE NEL TEMPO CIÒ CHE DAVVERO CONTA.</p>
|
<div class="quote__marquee">
|
||||||
|
<p class="quote__track">
|
||||||
|
<span class="quote__seg" set:html={quoteHtml} />
|
||||||
|
<span class="quote__seg" aria-hidden="true" set:html={quoteHtml} />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<cite>Insanitylab</cite>
|
<cite>Insanitylab</cite>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.quote { position: relative; background-size: cover; background-position: center; text-align: center; }
|
.quote { position: relative; overflow: hidden; text-align: center; }
|
||||||
.quote::before { content: ''; position: absolute; inset: 0; background: rgba(46,32,24,.82); }
|
/* Sfondo su div dedicato, esteso oltre i bordi per il margine di corsa del parallax */
|
||||||
.quote__in { position: relative; padding-block: 110px; max-width: 820px; }
|
.quote__bg { position: absolute; inset: -120px 0; background-size: cover; background-position: center; transform: translateY(var(--parallax-y, 0)); will-change: transform; }
|
||||||
|
.quote__bg::after { content: ''; position: absolute; inset: 0; background: rgba(46,32,24,.82); }
|
||||||
|
.quote__in { position: relative; padding-block: 110px; }
|
||||||
.quote__mark { color: #fff; font-size: 4rem; font-family: var(--font-heading); margin: 0; line-height: 1; }
|
.quote__mark { color: #fff; font-size: 4rem; font-family: var(--font-heading); margin: 0; line-height: 1; }
|
||||||
.quote blockquote { margin: 0; }
|
.quote blockquote { margin: 0; }
|
||||||
.quote blockquote p { color: #fff; font-family: var(--font-heading); font-size: clamp(1.1rem, 2.4vw, 1.6rem); letter-spacing: .06em; line-height: 1.6; }
|
/* Marquee: testo a scorrimento infinito come il ticker del template */
|
||||||
|
.quote__marquee { overflow: hidden; white-space: nowrap; }
|
||||||
|
.quote__track { display: inline-flex; margin: 0; animation: quote-scroll 30s linear infinite; will-change: transform; }
|
||||||
|
.quote__marquee:hover .quote__track { animation-play-state: paused; }
|
||||||
|
.quote__seg { padding-right: 4em; color: #fff; font-family: var(--font-heading); font-size: clamp(1.1rem, 2.4vw, 1.6rem); letter-spacing: .06em; line-height: 1.6; }
|
||||||
|
@keyframes quote-scroll { to { transform: translateX(-50%); } }
|
||||||
.quote cite { display: block; margin-top: 24px; color: var(--c-accent); font-style: normal; font-family: var(--font-heading); font-size: .75rem; letter-spacing: .3em; text-transform: uppercase; }
|
.quote cite { display: block; margin-top: 24px; color: var(--c-accent); font-style: normal; font-family: var(--font-heading); font-size: .75rem; letter-spacing: .3em; text-transform: uppercase; }
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.quote__track { animation: none; white-space: normal; display: block; max-width: 820px; margin-inline: auto; padding-inline: 20px; }
|
||||||
|
.quote__seg { padding-right: 0; }
|
||||||
|
.quote__seg[aria-hidden] { display: none; }
|
||||||
|
.quote__bg { transform: none; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ const imgOf = (name: string) => Object.entries(images).find(([p]) => p.includes(
|
|||||||
---
|
---
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="tteam__head">
|
<div class="tteam__head" data-reveal>
|
||||||
<h2>Team<br />Trainer</h2>
|
<h2>Team<br />Trainer</h2>
|
||||||
<a class="btn" href="/about">Scopri team</a>
|
<a class="btn" href="/about">Scopri team</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="tteam__grid">
|
<div class="tteam__grid">
|
||||||
{members.map((m) => (
|
{members.map((m, i) => (
|
||||||
<figure class="tteam__card">
|
<figure class="tteam__card" data-reveal style={`--reveal-delay:${i * 0.12}s`}>
|
||||||
<Image src={imgOf(m.image!)!} alt={m.name} widths={[300, 500]} sizes="(max-width: 767px) 50vw, 25vw" />
|
<Image src={imgOf(m.image!)!} alt={m.name} widths={[300, 500]} sizes="(max-width: 767px) 50vw, 25vw" />
|
||||||
<figcaption>{m.shortName}</figcaption>
|
<figcaption>{m.shortName}</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -27,8 +27,9 @@ const imgOf = (name: string) => Object.entries(images).find(([p]) => p.includes(
|
|||||||
.tteam__head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 40px; }
|
.tteam__head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 40px; }
|
||||||
.tteam__head h2 { text-transform: uppercase; letter-spacing: .1em; margin: 0; }
|
.tteam__head h2 { text-transform: uppercase; letter-spacing: .1em; margin: 0; }
|
||||||
.tteam__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
|
.tteam__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
|
||||||
.tteam__card { position: relative; margin: 0; }
|
.tteam__card { position: relative; margin: 0; overflow: hidden; }
|
||||||
.tteam__card img { aspect-ratio: 3/4; object-fit: cover; width: 100%; }
|
.tteam__card img { aspect-ratio: 3/4; object-fit: cover; width: 100%; transition: transform .5s ease; }
|
||||||
|
.tteam__card:hover img { transform: scale(1.06); }
|
||||||
.tteam__card figcaption { position: absolute; right: 10px; bottom: 14px; background: #fff; padding: 10px 6px; writing-mode: vertical-rl; font-family: var(--font-heading); font-size: .62rem; letter-spacing: .3em; text-transform: uppercase; color: var(--c-accent-dark); }
|
.tteam__card figcaption { position: absolute; right: 10px; bottom: 14px; background: #fff; padding: 10px 6px; writing-mode: vertical-rl; font-family: var(--font-heading); font-size: .62rem; letter-spacing: .3em; text-transform: uppercase; color: var(--c-accent-dark); }
|
||||||
@media (max-width: 767px) { .tteam__grid { grid-template-columns: 1fr 1fr; } }
|
@media (max-width: 767px) { .tteam__grid { grid-template-columns: 1fr 1fr; } }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -12,13 +12,17 @@ const isSwapRow = (i: number) => Math.floor(i / 2) % 2 === 1;
|
|||||||
{trainings.map((t, i) => {
|
{trainings.map((t, i) => {
|
||||||
const img = imgOf(t.image);
|
const img = imgOf(t.image);
|
||||||
const card = (
|
const card = (
|
||||||
<div class:list={['tcards__cell', `tcards__cell--${tones[i]}`]}>
|
<div class:list={['tcards__cell', `tcards__cell--${tones[i]}`]} data-reveal="fade" style={`--reveal-delay:${(i % 2) * 0.15}s`}>
|
||||||
<h3>{t.title}</h3>
|
<h3>{t.title}</h3>
|
||||||
<p>{t.caption}</p>
|
<p>{t.caption}</p>
|
||||||
<a class="tcards__link" href={t.cta.href}>Scopri →</a>
|
<a class="tcards__link" href={t.cta.href}>Scopri →</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
const photo = img ? <Image src={img} alt={t.title} class="tcards__img" widths={[400, 700]} sizes="(max-width: 767px) 50vw, 25vw" /> : null;
|
const photo = img ? (
|
||||||
|
<div class="tcards__photo" data-reveal="fade" style={`--reveal-delay:${((i % 2) * 0.15 + 0.1).toFixed(2)}s`}>
|
||||||
|
<Image src={img} alt={t.title} class="tcards__img" widths={[400, 700]} sizes="(max-width: 767px) 50vw, 25vw" />
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
return isSwapRow(i) ? <>{photo}{card}</> : <>{card}{photo}</>;
|
return isSwapRow(i) ? <>{photo}{card}</> : <>{card}{photo}</>;
|
||||||
})}
|
})}
|
||||||
</section>
|
</section>
|
||||||
@@ -37,6 +41,8 @@ const isSwapRow = (i: number) => Math.floor(i / 2) % 2 === 1;
|
|||||||
.tcards__cell h3 { text-transform: uppercase; letter-spacing: .12em; }
|
.tcards__cell h3 { text-transform: uppercase; letter-spacing: .12em; }
|
||||||
.tcards__cell p { font-size: .88rem; }
|
.tcards__cell p { font-size: .88rem; }
|
||||||
.tcards__link { color: #fff; font-family: var(--font-heading); font-size: .7rem; letter-spacing: .2em; text-transform: uppercase; text-decoration: none; margin-top: 18px; }
|
.tcards__link { color: #fff; font-family: var(--font-heading); font-size: .7rem; letter-spacing: .2em; text-transform: uppercase; text-decoration: none; margin-top: 18px; }
|
||||||
.tcards__img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 1; }
|
.tcards__photo { overflow: hidden; }
|
||||||
|
.tcards__img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 1; transition: transform .5s ease; }
|
||||||
|
.tcards__photo:hover .tcards__img { transform: scale(1.06); }
|
||||||
@media (max-width: 767px) { .tcards { grid-template-columns: repeat(2, 1fr); } }
|
@media (max-width: 767px) { .tcards { grid-template-columns: repeat(2, 1fr); } }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -25,10 +25,45 @@ const { title, description = 'InsanityLab — Performance. Balance. Longevity. A
|
|||||||
<meta property="og:image" content={new URL('/img/og.jpg', Astro.site)} />
|
<meta property="og:image" content={new URL('/img/og.jpg', Astro.site)} />
|
||||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||||
|
<script is:inline>document.documentElement.classList.add('js');</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<Header />
|
<Header />
|
||||||
<main><slot /></main>
|
<main><slot /></main>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
<script>
|
||||||
|
// Reveal allo scroll: trigger a -100px dal viewport come il template Prowess.
|
||||||
|
const io = new IntersectionObserver((entries) => {
|
||||||
|
for (const e of entries) {
|
||||||
|
if (e.isIntersecting) {
|
||||||
|
e.target.classList.add('is-inview');
|
||||||
|
io.unobserve(e.target);
|
||||||
|
// A reveal concluso l'attributo va tolto, così la transition del reveal
|
||||||
|
// non rallenta gli hover su transform del componente.
|
||||||
|
e.target.addEventListener('transitionend', () => {
|
||||||
|
e.target.removeAttribute('data-reveal');
|
||||||
|
}, { once: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { rootMargin: '0px 0px -100px 0px' });
|
||||||
|
document.querySelectorAll('[data-reveal], .section-heading').forEach((el) => io.observe(el));
|
||||||
|
|
||||||
|
const parallaxEls = document.querySelectorAll<HTMLElement>('[data-parallax]');
|
||||||
|
if (parallaxEls.length && matchMedia('(min-width: 992px)').matches
|
||||||
|
&& !matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||||
|
let raf = 0;
|
||||||
|
const update = () => {
|
||||||
|
raf = 0;
|
||||||
|
for (const el of parallaxEls) {
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
// Clamp entro il margine di sfondo (inset -120px) per non scoprire i bordi
|
||||||
|
const y = Math.max(-110, Math.min(110, (r.top + r.height / 2 - innerHeight / 2) * -0.3));
|
||||||
|
el.style.setProperty('--parallax-y', `${y.toFixed(1)}px`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addEventListener('scroll', () => { if (!raf) raf = requestAnimationFrame(update); }, { passive: true });
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ export const prerender = true;
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
{trainings.map((t, i) => (
|
{trainings.map((t, i) => (
|
||||||
<div class:list={['tblock', { 'tblock--reverse': i % 2 === 1 }]} id={t.id}>
|
<div class:list={['tblock', { 'tblock--reverse': i % 2 === 1 }]} id={t.id}>
|
||||||
{imgOf(t.image) && <Image src={imgOf(t.image)!} alt={t.title} widths={[500, 900]} sizes="(max-width:767px) 100vw, 45vw" />}
|
{imgOf(t.image) && <Image src={imgOf(t.image)!} alt={t.title} widths={[500, 900]} sizes="(max-width:767px) 100vw, 45vw" data-reveal={i % 2 === 1 ? 'right' : 'left'} />}
|
||||||
<div>
|
<div data-reveal={i % 2 === 1 ? 'left' : 'right'}>
|
||||||
<h2>{t.title.toLowerCase()}</h2>
|
<h2>{t.title.toLowerCase()}</h2>
|
||||||
<p>{t.description}</p>
|
<p>{t.description}</p>
|
||||||
<a class="btn" href={t.cta.href}>{t.cta.label}</a>
|
<a class="btn" href={t.cta.href}>{t.cta.label}</a>
|
||||||
|
|||||||
@@ -52,3 +52,19 @@ textarea.field { min-height: 120px; resize: vertical; }
|
|||||||
|
|
||||||
/* honeypot */
|
/* honeypot */
|
||||||
.hp { position: absolute; left: -9999px; opacity: 0; }
|
.hp { position: absolute; left: -9999px; opacity: 0; }
|
||||||
|
|
||||||
|
/* === Animazioni (stile template Prowess) === */
|
||||||
|
/* Lo stato nascosto vale solo con JS attivo (html.js): senza JS tutto resta visibile. */
|
||||||
|
html.js [data-reveal], html.js .section-heading { opacity: 0; transform: translateY(40px); transition: opacity .9s ease, transform .9s ease; transition-delay: var(--reveal-delay, 0s); }
|
||||||
|
html.js [data-reveal="left"] { transform: translateX(-60px); }
|
||||||
|
html.js [data-reveal="right"] { transform: translateX(60px); }
|
||||||
|
html.js [data-reveal="fade"] { transform: none; }
|
||||||
|
html.js [data-reveal].is-inview, html.js .section-heading.is-inview { opacity: 1; transform: none; }
|
||||||
|
|
||||||
|
.btn::after { transition: transform .3s ease; }
|
||||||
|
.btn:hover::after { transform: translateX(5px); }
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
html.js [data-reveal], html.js .section-heading { opacity: 1; transform: none; transition: none; }
|
||||||
|
*, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user