
Il caricamento che diventa marchio
Transizioni, SVG, Testo
NuovaGratis
Una scena disegnata e, davanti, una finestra che ne mostra un dettaglio da vicino. Ogni tanto la finestra sbatte le palpebre: si chiude a mandorla fino a una riga, la scena cambia, e si riapre su un altro dettaglio.
Una cartella che funziona da sola: index.html, style.css, script.js e le librerie da CDN.
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>La finestra che sbatte le palpebre</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/standard.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="scena" data-scena>
<!-- lo sfondo: una scena disegnata per strato, una sola visibile alla volta -->
<svg class="sfondo" data-sfondo viewBox="0 0 1600 1000" preserveAspectRatio="xMidYMid slice" aria-hidden="true" focusable="false"></svg>
<p class="nota" data-nota>Ogni tanto la finestra sbatte le palpebre e si riapre su un’altra scena. Premila per farlo subito.</p>
<!-- la finestra: la stessa scena vista da vicino, dentro un’apertura a mandorla -->
<button class="finestra" type="button" data-finestra data-autoclic aria-label="Cambia scena">
<span class="finestra__occhio" data-occhio>
<svg class="finestra__vista" data-vista viewBox="0 0 160 90" preserveAspectRatio="xMidYMid slice" aria-hidden="true" focusable="false"><use data-uso href="#" /></svg>
</span>
<span class="finestra__fessura" aria-hidden="true"></span>
<span class="finestra__angoli" aria-hidden="true"></span>
<span class="finestra__didascalia">
<span data-dettaglio></span>
<span class="finestra__luogo" data-luogo></span>
</span>
</button>
</main>
<!-- le scene, disegnate una volta sola: le usano sia lo sfondo sia la finestra -->
<svg class="nascosto" aria-hidden="true" focusable="false"><defs data-scene></defs></svg>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="script.js"></script>
</body>
</html>/* La finestra che sbatte le palpebre */
:root {
--notte: #0F1116;
--velo: rgba(12, 14, 18, 0.58);
--bianco: #F6F4EF;
--margine: clamp(18px, 4vw, 56px);
--alto: 100vh;
}
@supports (height: 100svh) {
:root { --alto: 100svh; }
}
* { box-sizing: border-box; }
html,
body {
margin: 0;
height: 100%;
}
body {
background: var(--notte);
color: var(--bianco);
font-family: 'Mona Sans Variable', system-ui, sans-serif;
overflow: hidden;
}
.nascosto {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.scena {
position: relative;
height: var(--alto);
overflow: hidden;
}
.sfondo {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.strato {
transform-box: view-box;
transform-origin: 50% 50%;
}
.nota {
position: absolute;
z-index: 2;
top: var(--margine);
left: var(--margine);
max-width: 34ch;
margin: 0;
padding: 0.7em 0.95em;
border-radius: 12px;
background: var(--velo);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
font-size: clamp(0.92rem, 0.84rem + 0.3vw, 1.08rem);
font-weight: 500;
line-height: 1.38;
}
/* ---- la finestra ---- */
.finestra {
position: absolute;
z-index: 1;
top: 54%;
left: 50%;
width: min(48vw, 660px);
aspect-ratio: 16 / 9;
padding: 0;
border: 0;
background: none;
color: inherit;
font: inherit;
cursor: pointer;
transform: translate(-50%, -50%);
-webkit-tap-highlight-color: transparent;
/* quanto è aperta la palpebra: 1 aperta, 0 chiusa (la muove lo script) */
--palpebra: 1;
}
.finestra:focus-visible {
outline: 2px solid var(--bianco);
outline-offset: 18px;
}
/* l'apertura è un’ellisse larga: chiudendosi diventa una mandorla e poi una riga */
.finestra__occhio {
position: absolute;
inset: 0;
overflow: hidden;
background: var(--notte);
clip-path: ellipse(80% calc(var(--palpebra) * 82%) at 50% 50%);
box-shadow: 0 30px 70px rgba(0, 0, 0, 0.35);
}
.finestra__vista {
display: block;
width: 100%;
height: 100%;
transform: scaleY(calc(0.93 + var(--palpebra) * 0.07));
}
.finestra__fessura {
position: absolute;
top: 50%;
left: 3%;
right: 3%;
height: 1px;
background: var(--bianco);
opacity: calc(1 - var(--palpebra));
box-shadow: 0 0 8px rgba(255, 255, 255, 0.6);
}
/* gli angoli del mirino: quattro squadre di luce fuori dalla finestra */
.finestra__angoli {
--l: 20px;
--s: 1.5px;
--c: rgba(246, 244, 239, 0.95);
position: absolute;
inset: -12px;
pointer-events: none;
background:
linear-gradient(var(--c), var(--c)) left top / var(--l) var(--s) no-repeat,
linear-gradient(var(--c), var(--c)) left top / var(--s) var(--l) no-repeat,
linear-gradient(var(--c), var(--c)) right top / var(--l) var(--s) no-repeat,
linear-gradient(var(--c), var(--c)) right top / var(--s) var(--l) no-repeat,
linear-gradient(var(--c), var(--c)) left bottom / var(--l) var(--s) no-repeat,
linear-gradient(var(--c), var(--c)) left bottom / var(--s) var(--l) no-repeat,
linear-gradient(var(--c), var(--c)) right bottom / var(--l) var(--s) no-repeat,
linear-gradient(var(--c), var(--c)) right bottom / var(--s) var(--l) no-repeat;
filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.45));
transition: inset 320ms cubic-bezier(0.23, 1, 0.32, 1);
}
@media (hover: hover) and (pointer: fine) {
.finestra:hover .finestra__angoli {
inset: -18px;
}
}
.finestra:active .finestra__angoli {
inset: -8px;
transition-duration: 120ms;
}
.finestra__didascalia {
position: absolute;
top: calc(100% + 18px);
left: 50%;
display: flex;
gap: 0.9em;
padding: 0.45em 0.85em;
border-radius: 999px;
background: var(--velo);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
font-size: clamp(0.82rem, 0.78rem + 0.2vw, 0.95rem);
font-weight: 600;
white-space: nowrap;
transform: translateX(-50%);
}
.finestra__luogo {
color: rgba(246, 244, 239, 0.66);
font-weight: 500;
}
@media (max-width: 640px) {
.finestra {
top: 52%;
width: 86vw;
}
}/* La finestra che sbatte le palpebre
Una scena disegnata a tutto schermo e, davanti, una finestra che la mostra da vicino: un dettaglio
ingrandito della stessa scena. Ogni tanto la finestra sbatte le palpebre: l’apertura a mandorla si
stringe fino a una riga, mentre è chiusa la scena dietro cambia, e si riapre su un altro dettaglio.
Le scene sono SVG disegnati una volta sola e usati due volte: grandi dietro, ingranditi nella finestra. */
/* ---- valori da cambiare ---- */
const APERTA = 2.4; /* secondi a occhi aperti */
const CHIUDE = 0.9; /* secondi per chiudere */
const BUIO = 0.25; /* secondi a occhi chiusi, mentre la scena cambia */
const APRE = 1.1; /* secondi per riaprire */
/* un numero a caso che è sempre lo stesso: le scene si disegnano uguali a ogni visita */
function caso(seme) {
return () => {
seme = (seme + 0x6d2b79f5) | 0;
let t = Math.imul(seme ^ (seme >>> 15), 1 | seme);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const n1 = (v) => v.toFixed(1);
/* ---------------------------------------------------------------- le scene (1600 × 1000) */
function mare() {
const r = caso(7);
let s = '<linearGradient id="cielo-mare" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#F9DDB8"/><stop offset="1" stop-color="#F09A73"/></linearGradient>';
s += '<rect width="1600" height="640" fill="url(#cielo-mare)"/><circle cx="1060" cy="600" r="130" fill="#FCE7B0"/>';
for (let i = 0; i < 6; i++) {
const x = 260 + r() * 760; const y = 150 + r() * 210; const w = 14 + r() * 10;
s += `<path d="M${n1(x)} ${n1(y)}q${n1(w / 2)} ${n1(-w / 2.5)} ${n1(w)} 0q${n1(w / 2)} ${n1(-w / 2.5)} ${n1(w)} 0" fill="none" stroke="#6B4A3F" stroke-width="3" stroke-linecap="round"/>`;
}
s += '<rect y="620" width="1600" height="380" fill="#2C5E73"/>';
for (let i = 0; i < 9; i++) {
const w = 220 - i * 20 + r() * 30;
s += `<rect x="${n1(1060 - w / 2)}" y="${632 + i * 16}" width="${n1(w)}" height="5" rx="2.5" fill="#F7C58F" opacity="${(0.75 - i * 0.06).toFixed(2)}"/>`;
}
for (let i = 0; i < 16; i++) {
const x = r() * 1500; const y = 670 + r() * 310; const w = 40 + r() * 60;
s += `<path d="M${n1(x)} ${n1(y)}q${n1(w / 4)} -8 ${n1(w / 2)} 0t${n1(w / 2)} 0" fill="none" stroke="#4B8198" stroke-width="3" stroke-linecap="round"/>`;
}
s += '<path d="M1300 1000V660Q1360 612 1480 600Q1560 594 1600 598V1000Z" fill="#3A2E2A"/>';
s += '<path d="M1428 600L1436 470H1464L1472 600Z" fill="#F4EFE6"/><path d="M1432.6 540H1467.4L1469 560H1431ZM1430.2 580H1469.8L1471 596H1429Z" fill="#C8453B"/><rect x="1438" y="452" width="24" height="18" fill="#FCE7B0"/><path d="M1434 452H1466L1450 434Z" fill="#C8453B"/>';
/* il dettaglio: la barca */
s += '<g transform="translate(84 10)"><path d="M556 598H646L632 616H572Z" fill="#8E3B2F"/><circle cx="584" cy="605" r="2.2" fill="#F4EFE6"/><circle cx="600" cy="605" r="2.2" fill="#F4EFE6"/><circle cx="616" cy="605" r="2.2" fill="#F4EFE6"/><rect x="599" y="530" width="3" height="68" fill="#3A2E2A"/><path d="M603 534V592H640Z" fill="#F4EFE6"/><path d="M597 546V592H566Z" fill="#EADBC4"/><path d="M602 530L620 535L602 540Z" fill="#C8453B"/><circle cx="624" cy="590" r="3.2" fill="#3A2E2A"/><rect x="621.5" y="592.5" width="5" height="6" fill="#3A2E2A"/><path d="M560 624q10 -3 20 0t20 0t20 0t20 0" fill="none" stroke="#8E3B2F" stroke-width="2" opacity=".35"/></g>';
return s;
}
function bosco() {
const r = caso(21);
const pino = (x, y, h, c) => `<path d="M${n1(x)} ${n1(y - h)}L${n1(x + h * 0.32)} ${n1(y)}H${n1(x - h * 0.32)}Z" fill="${c}"/>`;
let s = '<linearGradient id="cielo-bosco" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#EEF1DC"/><stop offset="1" stop-color="#C7DCC7"/></linearGradient>';
s += '<rect width="1600" height="1000" fill="url(#cielo-bosco)"/><circle cx="1180" cy="230" r="70" fill="#F7EBC4"/>';
s += '<path d="M0 560Q300 470 600 520T1200 500T1600 520V1000H0Z" fill="#A9C7AE"/>';
for (let i = 0; i < 40; i++) s += pino(r() * 1600, 560 + r() * 30, 70 + r() * 50, '#7FA88A');
s += '<path d="M0 700Q400 640 800 690T1600 670V1000H0Z" fill="#6E9A74"/>';
for (let i = 0; i < 30; i++) {
const x = r() * 1600; const h = 120 + r() * 80;
if (x > 760 && x < 1060) continue;
s += pino(x, 700 + r() * 20, h, '#4B7A5E');
}
/* il dettaglio: la baita col fumo e la finestra accesa */
s += '<g><path d="M948 598q-10 -30 6 -50q14 -18 4 -44" fill="none" stroke="#F2F5F0" stroke-width="10" stroke-linecap="round" opacity=".75"/><rect x="940" y="600" width="16" height="36" fill="#5A3526"/><path d="M858 652L924 606L990 652Z" fill="#5A3526"/><rect x="866" y="650" width="116" height="56" fill="#8A5A3C"/><path d="M866 662H982M866 676H982M866 690H982" stroke="#74482F" stroke-width="2"/><rect x="880" y="664" width="30" height="24" fill="#FFD27A"/><path d="M895 664V688M880 676H910" stroke="#74482F" stroke-width="2.5"/><rect x="936" y="668" width="22" height="38" fill="#4A2E20"/><circle cx="953" cy="688" r="1.8" fill="#FFD27A"/><path d="M846 706H1002" stroke="#3F6B4E" stroke-width="4"/></g>';
s += '<path d="M0 800Q500 740 1000 790T1600 780V1000H0Z" fill="#3F6B4E"/>';
for (let i = 0; i < 12; i++) s += pino(i < 6 ? r() * 380 : 1220 + r() * 380, 820 + r() * 30, 260 + r() * 140, '#2C4F3B');
return s;
}
function citta() {
const r = caso(3);
let s = '<linearGradient id="cielo-citta" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#141B33"/><stop offset="1" stop-color="#34406B"/></linearGradient>';
s += '<rect width="1600" height="1000" fill="url(#cielo-citta)"/>';
for (let i = 0; i < 70; i++) s += `<circle cx="${Math.round(r() * 1600)}" cy="${Math.round(r() * 420)}" r="${n1(0.8 + r() * 1.6)}" fill="#F3E9C6" opacity="${(0.4 + r() * 0.6).toFixed(2)}"/>`;
s += '<circle cx="1290" cy="190" r="62" fill="#F3E9C6"/><circle cx="1312" cy="176" r="10" fill="#E4D9B3"/><circle cx="1272" cy="206" r="7" fill="#E4D9B3"/>';
let x = -20;
while (x < 1600) {
const w = 60 + r() * 90; const h = 180 + r() * 260;
s += `<rect x="${Math.round(x)}" y="${Math.round(760 - h)}" width="${Math.round(w)}" height="${Math.round(h + 240)}" fill="#1F2A4A"/>`;
x += w + 6;
}
/* i palazzi davanti, con le finestre accese un po’ a caso */
x = -10;
while (x < 1600) {
const w = 110 + r() * 120; const h = 260 + r() * 300; const cima = 1000 - h;
s += `<rect x="${Math.round(x)}" y="${Math.round(cima)}" width="${Math.round(w)}" height="${Math.round(h)}" fill="#121A31"/>`;
for (let wy = cima + 24; wy < 980; wy += 36) {
for (let wx = x + 16; wx < x + w - 24; wx += 30) s += `<rect x="${Math.round(wx)}" y="${Math.round(wy)}" width="14" height="20" fill="${r() < 0.34 ? '#F9D77E' : '#223052'}"/>`;
}
x += w + 14;
}
/* il dettaglio: il gatto sul davanzale, con un vaso di fiori */
s += '<g><rect x="640" y="540" width="190" height="460" fill="#0E152A"/><rect x="700" y="580" width="70" height="86" fill="#F9D77E"/><path d="M704 584H731V612Q717 598 704 612Z" fill="#E9A15A" opacity=".55"/><rect x="700" y="580" width="70" height="86" fill="none" stroke="#3A2E2A" stroke-width="5"/><path d="M735 580V666M700 623H770" stroke="#3A2E2A" stroke-width="3"/><rect x="694" y="664" width="82" height="7" fill="#3A2E2A"/><path d="M744 664C743 652 746 642 752 638L750 628L756 634H762L767 628L766 639C771 644 772 654 770 664Z" fill="#161616"/><path d="M768 664C776 670 778 684 772 694" fill="none" stroke="#161616" stroke-width="4" stroke-linecap="round"/><path d="M708 650H724L721 664H711Z" fill="#B5553C"/><circle cx="712" cy="644" r="5" fill="#E8637A"/><circle cx="720" cy="641" r="5" fill="#F3A5B8"/><rect x="715" y="645" width="2" height="6" fill="#3E7C4F"/></g>';
return s;
}
function campo() {
const nuvola = (x, y, k) => `<g fill="#FFFFFF" opacity=".92"><ellipse cx="${x}" cy="${y}" rx="${70 * k}" ry="${22 * k}"/><ellipse cx="${x - 30 * k}" cy="${y - 14 * k}" rx="${36 * k}" ry="${22 * k}"/><ellipse cx="${x + 24 * k}" cy="${y - 20 * k}" rx="${42 * k}" ry="${28 * k}"/></g>`;
let s = '<linearGradient id="cielo-campo" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#A9D8EE"/><stop offset="1" stop-color="#E3F3F7"/></linearGradient>';
s += '<rect width="1600" height="1000" fill="url(#cielo-campo)"/>';
s += nuvola(1180, 180, 1.4) + nuvola(260, 120, 1) + nuvola(1440, 330, 0.8) + nuvola(520, 300, 0.7);
s += '<path d="M0 640Q260 560 560 600T1160 580T1600 600V1000H0Z" fill="#9DB85A"/><path d="M0 720Q400 640 820 700T1600 680V1000H0Z" fill="#D8C15E"/>';
for (let i = 0; i < 12; i++) {
const y = 740 + i * 22;
s += `<path d="M0 ${y}Q400 ${y - 60 + i * 3} 820 ${y - 10}T1600 ${y - 24}" fill="none" stroke="#C4AA45" stroke-width="3" opacity=".7"/>`;
}
s += '<path d="M0 860Q500 800 1000 850T1600 840V1000H0Z" fill="#7E9A45"/>';
s += '<rect x="1180" y="560" width="90" height="56" fill="#C8453B"/><path d="M1170 562L1225 526L1280 562Z" fill="#7A2E27"/><rect x="1215" y="586" width="18" height="30" fill="#5A2520"/><rect x="1190" y="574" width="16" height="14" fill="#FCE7B0"/>';
/* il dettaglio: l’aquilone, col filo che scende fino a chi lo tiene */
s += '<path d="M740 300C820 420 860 560 904 752" fill="none" stroke="#3A2E2A" stroke-width="1.6"/>';
s += '<g><path d="M740 220L778 262L740 300L702 262Z" fill="#FFD166"/><path d="M740 220L778 262H740Z" fill="#E84A5F"/><path d="M702 262H740V300Z" fill="#4F9DDE"/><path d="M740 262H778L740 300Z" fill="#2EC4B6"/><path d="M740 220V300M702 262H778" stroke="#3A2E2A" stroke-width="2"/><path d="M740 300C732 316 748 330 738 346C730 360 746 372 740 388" fill="none" stroke="#3A2E2A" stroke-width="1.6"/><path d="M734 316L746 312L740 322ZM732 346L744 342L738 352ZM734 374L746 370L740 380Z" fill="#E84A5F"/></g>';
s += '<g fill="#3A2E2A"><circle cx="910" cy="734" r="9"/><path d="M902 744H918L922 782H898Z"/><path d="M900 782h8v24h-8zM912 782h8v24h-8z"/></g>';
return s;
}
/* ogni scena: il luogo, il suo dettaglio e il riquadro del dettaglio (x, y, larghezza, altezza, in 16:9) */
const SCENE = [
{ id: 'mare', luogo: 'Il mare', dettaglio: 'La barca', vista: [579, 528, 220, 123.75], disegna: mare },
{ id: 'bosco', luogo: 'Il bosco', dettaglio: 'La baita', vista: [800, 578, 240, 135], disegna: bosco },
{ id: 'citta', luogo: 'La città', dettaglio: 'Il gatto', vista: [615, 560, 240, 135], disegna: citta },
{ id: 'campo', luogo: 'Il campo', dettaglio: 'L’aquilone', vista: [655, 196, 240, 135], disegna: campo },
];
const RIDOTTO = matchMedia('(prefers-reduced-motion: reduce)').matches;
const scena = document.querySelector('[data-scena]');
const sfondo = document.querySelector('[data-sfondo]');
const finestra = document.querySelector('[data-finestra]');
const occhio = document.querySelector('[data-occhio]');
const vista = document.querySelector('[data-vista]');
const uso = document.querySelector('[data-uso]');
const dettaglio = document.querySelector('[data-dettaglio]');
const luogo = document.querySelector('[data-luogo]');
if (RIDOTTO) document.querySelector('[data-nota]').textContent = 'Premi la finestra per passare alla scena successiva.';
/* le scene si disegnano una volta: dietro ognuna ha il suo strato, la finestra usa quella del momento */
document.querySelector('[data-scene]').innerHTML = SCENE.map((s) => `<g id="quadro-${s.id}">${s.disegna()}</g>`).join('');
sfondo.innerHTML = SCENE.map((s, i) => `<g class="strato" data-strato style="opacity:${i ? 0 : 1}"><use href="#quadro-${s.id}"/></g>`).join('');
const strati = [...sfondo.querySelectorAll('[data-strato]')];
let indice = 0;
const riquadro = (v, dx = 0) => `${n1(v[0] + dx)} ${n1(v[1])} ${n1(v[2])} ${n1(v[3])}`;
function mostra(i, anima) {
const s = SCENE[i];
uso.setAttribute('href', `#quadro-${s.id}`);
dettaglio.textContent = s.dettaglio;
luogo.textContent = s.luogo;
finestra.setAttribute('aria-label', `${s.dettaglio}, ${s.luogo}: cambia scena`);
if (!anima) {
vista.setAttribute('viewBox', riquadro(s.vista));
return;
}
/* mentre è aperta la finestra scivola piano sul dettaglio, e la scena dietro si avvicina */
const giro = APERTA + CHIUDE + BUIO + APRE;
gsap.fromTo(vista, { attr: { viewBox: riquadro(s.vista, -s.vista[2] * 0.03) } }, { attr: { viewBox: riquadro(s.vista, s.vista[2] * 0.03) }, duration: giro, ease: 'none', overwrite: true });
gsap.fromTo(strati[i], { scale: 1, transformOrigin: '50% 50%' }, { scale: 1.05, duration: giro + 1, ease: 'none' });
}
function cambia() {
const prima = indice;
indice = (indice + 1) % SCENE.length;
gsap.to(strati[prima], { opacity: 0, duration: RIDOTTO ? 0.25 : 0.7, ease: 'power1.inOut', overwrite: 'auto' });
gsap.to(strati[indice], { opacity: 1, duration: RIDOTTO ? 0.25 : 0.7, ease: 'power1.inOut', overwrite: 'auto' });
mostra(indice, !RIDOTTO);
}
/* ---------------------------------------------------------------- il battito */
let battito = null;
if (!RIDOTTO) {
battito = gsap.timeline({ repeat: -1, paused: true })
.to({}, { duration: APERTA })
.addLabel('chiude')
.to(finestra, { '--palpebra': 0, duration: CHIUDE, ease: 'power3.inOut' })
.call(cambia)
.to({}, { duration: BUIO })
.to(finestra, { '--palpebra': 1, duration: APRE, ease: 'power3.inOut' });
/* fuori dallo schermo non batte */
new IntersectionObserver(([e]) => { if (e.isIntersecting) battito.play(); else battito.pause(); }).observe(scena);
}
/* ---------------------------------------------------------------- il gesto */
finestra.addEventListener('click', () => {
if (RIDOTTO) {
gsap.fromTo(occhio, { opacity: 0.2 }, { opacity: 1, duration: 0.25, ease: 'none' });
cambia();
return;
}
/* a occhi aperti si chiudono subito; se sta già battendo, si lascia finire */
if (battito.time() < battito.labels.chiude) battito.seek('chiude');
});
mostra(0, !RIDOTTO);Ogni scena è un gruppo SVG disegnato una volta sola dentro un defs. Lo sfondo lo usa con un use a tutto schermo (preserveAspectRatio xMidYMid slice), la finestra con un altro use dentro un SVG col viewBox stretto sul dettaglio: essendo vettoriale, ingrandito quattro volte resta nitido. I gradienti stanno dentro il gruppo e li seguono tutti e due.
L’apertura è un clip-path: ellipse(80% calc(var(--palpebra) × 82%) at 50% 50%). Con la variabile a 1 l’ellisse copre anche gli angoli; scendendo a 0 si stringe solo in altezza, e il rettangolo diventa prima una mandorla e poi una riga. GSAP anima la variabile CSS, e la stessa variabile accende la riga di luce e schiaccia appena il contenuto.
Una linea del tempo che si ripete: 2,4 secondi aperta, 0,9 per chiudere con power3.inOut, un richiamo che cambia scena, 0,25 al buio, 1,1 per riaprire. La scena dietro sfuma in 0,7 secondi proprio mentre la finestra è chiusa, così il cambio non si vede mai a occhi aperti.
Mentre la finestra è aperta, il viewBox scorre del 6% della sua larghezza, come una ripresa che si sposta, e la scena dietro passa da scala 1 a 1,05. Premendo la finestra la linea del tempo salta all’etichetta del battito, ma solo se la palpebra è aperta: se sta già battendo, finisce il suo.
| Nome | File | Di partenza | Cosa cambia |
|---|---|---|---|
SCENE | script.js | 4 scene | Per ogni scena: il luogo, il nome del dettaglio, il riquadro del dettaglio in 16:9 e la funzione che la disegna (SVG in 1600 × 1000). |
APERTA, CHIUDE, BUIO, APRE | script.js | 2.4, 0.9, 0.25, 1.1 | I secondi del battito: a occhi aperti, per chiudere, al buio, per riaprire. |
ellipse(80% … 82%) | style.css | 80%, 82% | La forma della palpebra: più stretta in larghezza, più tonda la mandorla. |
Per raccontare un luogo o un prodotto per dettagli: la vista d’insieme dietro, la cosa da guardare davanti, e un gesto che fa pensare a uno sguardo.
La finestra prende quasi tutta la larghezza; lo sfondo, tagliato al centro, mostra comunque i quattro dettagli, che stanno tutti nella parte centrale della scena.
Niente battito, niente scorrimento e niente avvicinamento: la finestra resta aperta e, premendola, scena e dettaglio passano ai successivi con una dissolvenza di un quarto di secondo.
GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.
Peso: 19 KB dei nostri file.
Provata il 27 settembre 2026
Puoi usarla nei tuoi progetti e in quelli dei tuoi clienti. Non puoi rivenderla né ridistribuirla così com’è. GSAP resta sotto la sua licenza Standard. Licenza completa.
Con l’email e la password del tuo account.
Hai fatto l’accesso come .
Il tuo piano è gratis. Quando arriverà, l’accesso a vita si attiverà su questo stesso account.