Codrops 원본
Creating Custom Page Transitions in Astro with Barba.js and GSAP
섹션 · MIT
갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.
SOURCE FILES
원본 코드 읽기
수집한 원본 소스와 실행 안내를 함께 제공합니다.
astro.config.mjs
// @ts-check
import { defineConfig, fontProviders } from 'astro/config';
import glsl from 'vite-plugin-glsl';
// https://astro.build/config
export default defineConfig({
vite: {
plugins: [glsl()],
},
devToolbar: {
enabled: false,
},
});
함께 쓰는 파일 25개 보기
src/scripts/app.js
import gsap from 'gsap';
import barba from '@barba/core';
import WebGLPageTransition from './components/webgl-page-transition';
import MorphSVGPlugin from 'gsap/MorphSVGPlugin';
import MotionText from './components/motion-text';
import { preventLinksMenu, select } from './utils';
import { SplitText } from 'gsap/SplitText';
import { CustomEase } from 'gsap/CustomEase';
import DrawSVGPlugin from 'gsap/DrawSVGPlugin';
class App {
constructor() {
this.motionTexts = new MotionText();
this.motionTexts.init();
this.motionTexts.animationIn();
this.transitionOverlay = select('.transition__overlay');
this.titleDestination = select('.transition__overlay .title__destination');
this.splitTitleDestination = null;
this.getPercentageVerticalClipExample3();
this.barbaWrapper = select("[data-barba='wrapper']");
this.webglPageTransition = new WebGLPageTransition();
barba.init({
transitions: [
{
/* Reference: https://cielrose.tv/about
*/
name: 'default-transition',
before: (data) => {
this.barbaWrapper.classList.add('is__transitioning');
gsap.set(data.next.container, {
position: 'fixed',
inset: 0,
scale: 0.6,
clipPath: 'inset(100% 0 0 0)',
zIndex: 3,
willChange: 'auto',
});
gsap.set(data.current.container, {
zIndex: 2,
willChange: 'auto',
});
},
enter: (data) => {
const contentCurrent = data.current.container.querySelector('.content__wrapper');
const tl = gsap.timeline({
defaults: {
duration: 0.8,
ease: 'power3.inOut',
},
onComplete: () => tl.kill(),
});
tl.to(data.current.container, {
scale: 0.6,
})
.to(data.current.container, {
opacity: 0.45,
ease: 'power3',
})
.to(
contentCurrent,
{
yPercent: -10,
ease: 'power3',
},
'<',
)
.to(
data.next.container,
{
clipPath: 'inset(0% 0 0 0)',
ease: 'power3',
onStart: () => {
this.motionTexts.init(data.next.container); // initialization motion text for next container
this.motionTexts.animationIn();
},
onComplete: () => {
this.motionTexts.destroy(); // destroy motion text on current container
},
},
'<',
)
.to(data.next.container, {
scale: 1,
});
return new Promise((resolve) => {
tl.call(() => {
resolve();
});
});
},
after: (data) => {
this.barbaWrapper.classList.remove('is__transitioning');
gsap.set(data.next.container, {
clearProps: 'all',
});
},
sync: true,
},
{
/* Reference: https://www.faint-film.com/
*/
name: 'example-2-transition',
to: {
namespace: ['about'],
},
before: () => {
this.barbaWrapper.classList.add('is__transitioning');
},
leave: () => {
const tl = gsap.timeline({
defaults: {
duration: 1,
ease: 'power1.in',
},
onComplete: () => tl.kill(),
});
gsap.set('#webgl', {
pointerEvents: 'auto',
autoAlpha: 1,
visibility: 'visible',
});
tl.to(this.webglPageTransition.material.uniforms.uProgress, {
value: -0.75,
});
return new Promise((resolve) => {
tl.call(() => {
this.motionTexts.destroy();
resolve();
});
});
},
after: () => {
const tl = gsap.timeline({
defaults: {
duration: 1,
ease: 'power1.in',
},
onComplete: () => {
gsap.set('#webgl', {
pointerEvents: 'none',
autoAlpha: 0,
visibility: 'hidden',
});
tl.kill();
},
});
tl.to(this.webglPageTransition.material.uniforms.uProgress, {
value: 1.5,
});
return new Promise((resolve) => {
tl.call(() => {
this.barbaWrapper.classList.remove('is__transitioning');
resolve();
});
});
},
},
{
/* Reference: https://codepen.io/GreenSock/full/EaKpEpJ
*/
name: 'example-3-transition',
to: {
namespace: ['works'],
},
before: () => {
this.barbaWrapper.classList.add('is__transitioning');
},
leave: (data) => {
const tl = gsap.timeline({
defaults: {
duration: 0.5,
ease: 'sine.in',
},
onComplete: () => tl.kill(),
});
const path = select('.transition__morph__svg svg path');
gsap.set('.transition__morph__svg', {
pointerEvents: 'auto',
autoAlpha: 1,
visibility: 'visible',
});
let enterCurve = 'M 0 100 V 50 Q 50 0 100 50 V 100 z',
filledPath = 'M 0 100 V 0 Q 50 0 100 0 V 100 z';
if (typeof data.trigger === 'string') {
enterCurve = 'M 0 0 V 50 Q 50 100 100 50 V 0 z';
filledPath = 'M 0 0 V 100 Q 50 100 100 100 V 0 z';
gsap.set(path, {
attr: { d: 'M 0 0 V 0 Q 50 0 100 0 V 0 z' },
});
}
tl.to(path, {
morphSVG: enterCurve,
}).to(
path,
{
morphSVG: filledPath,
ease: 'sine',
},
'<+=.5',
);
return new Promise((resolve) => {
tl.call(() => {
this.motionTexts.destroy();
resolve();
});
});
},
after: (data) => {
const path = select('.transition__morph__svg svg path');
const originalPath = path.dataset.originalPath;
const tl = gsap.timeline({
defaults: {
duration: 0.5,
ease: 'sine.in',
},
onComplete: () => {
gsap.set('.transition__morph__svg', {
pointerEvents: 'none',
autoAlpha: 0,
visibility: 'hidden',
});
gsap.set(path, {
attr: { d: originalPath },
});
tl.kill();
},
});
let leaveCurve = 'M 0 0 V 50 Q 50 0 100 50 V 0 z',
unfilledPath = 'M 0 0 V 0 Q 50 0 100 0 V 0 z';
if (typeof data.trigger === 'string') {
leaveCurve = 'M 0 100 V 50 Q 50 100 100 50 V 100 z';
unfilledPath = 'M 0 100 V 100 Q 50 100 100 100 V 100 z';
}
tl.to(path, {
morphSVG: leaveCurve,
}).to(
path,
{
morphSVG: unfilledPath,
ease: 'sine',
onStart: () => {
this.motionTexts.init();
this.motionTexts.animationIn();
},
},
'<+=.5',
);
return new Promise((resolve) => {
tl.call(() => {
this.barbaWrapper.classList.remove('is__transitioning');
resolve();
});
});
},
},
{
/* Reference: https://bloomparis.tv/
*/
name: 'example-4-transition',
to: {
namespace: ['team'],
},
before: (data) => {
this.barbaWrapper.classList.add('is__transitioning');
this.transitionOverlay.classList.add('team__transition');
const nextDestination = data.next.url.path.split('/').filter(Boolean).pop();
//this.titleDestination.innerHTML = `we're going to ${nextDestination}`;
if (this.splitTitleDestination) this.splitTitleDestination.revert();
this.splitTitleDestination = new SplitText(this.titleDestination, {
type: 'words',
mask: 'words',
wordsClass: 'words',
});
gsap.set(this.transitionOverlay, {
'--clip': `polygon(0% ${50 - this.percentageVerticalClip}%, 0% ${
50 - this.percentageVerticalClip
}%, 0% ${50 + this.percentageVerticalClip}%, 0% ${50 + this.percentageVerticalClip}%)`,
});
},
leave: () => {
const tl = gsap.timeline({
defaults: {
duration: 1,
ease: 'expo.inOut',
},
onComplete: () => tl.kill(),
});
gsap.set(this.transitionOverlay, {
pointerEvents: 'auto',
autoAlpha: 1,
visibility: 'visible',
});
tl.to(this.transitionOverlay, {
'--clip': `polygon(0 ${50 - this.percentageVerticalClip}%, 100% ${
50 - this.percentageVerticalClip
}%, 100% ${50 + this.percentageVerticalClip}%, 0 ${50 + this.percentageVerticalClip}%)`,
});
tl.to(this.transitionOverlay, {
'--clip': 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)',
});
return new Promise((resolve) => {
tl.call(() => {
this.motionTexts.destroy();
resolve();
});
});
},
after: () => {
const tl = gsap.timeline({
defaults: {
duration: 1,
ease: 'hop',
},
onComplete: () => {
if (this.splitTitleDestination) {
this.splitTitleDestination.revert();
this.splitTitleDestination = null;
}
gsap.set(this.transitionOverlay, {
pointerEvents: 'none',
autoAlpha: 0,
visibility: 'hidden',
});
tl.kill();
},
});
tl.to(this.splitTitleDestination.words, {
yPercent: -120,
duration: 0.5,
stagger: {
amount: 0.25,
},
ease: 'elastic.in(1, 1)',
});
tl.to(
this.transitionOverlay,
{
'--clip': 'polygon(0% 0%, 100% 0%, 100% 0%, 0% 0%)',
onStart: () => {
this.motionTexts.init();
this.motionTexts.animationIn();
},
},
'<+0.25',
);
return new Promise((resolve) => {
tl.call(() => {
this.barbaWrapper.classList.remove('is__transitioning');
this.transitionOverlay.classList.remove('team__transition');
resolve();
});
});
},
},
{
/* Reference: https://truus.co/
*/
name: 'example-5-transition',
to: {
namespace: ['archive'],
},
before: () => {
this.barbaWrapper.classList.add('is__transitioning');
},
leave: () => {
const tl = gsap.timeline({
defaults: {
duration: 1.4,
ease: 'sine.inOut',
},
onComplete: () => tl.kill(),
});
gsap.set('.transition__svg__wrapper', {
pointerEvents: 'auto',
autoAlpha: 1,
visibility: 'visible',
});
gsap.set('.svg__transition svg path', {
drawSVG: '0% 0%',
attr: { 'stroke-width': 100 },
opacity: 0,
});
tl.to('.svg__transition svg path', {
opacity: 1,
duration: 0.5,
});
tl.to(
'.svg__transition svg path',
{
drawSVG: '0% 100%',
},
'<',
);
tl.to(
'.svg__transition svg path',
{
attr: { 'stroke-width': 400 },
ease: 'sine.inOut',
},
'<+=0.18',
);
return new Promise((resolve) => {
tl.call(() => {
this.motionTexts.destroy();
resolve();
});
});
},
after: () => {
const tl = gsap.timeline({
defaults: {
duration: 1,
ease: 'sine.inOut',
},
onComplete: () => {
gsap.set('.transition__svg__wrapper', {
pointerEvents: 'none',
autoAlpha: 0,
visibility: 'hidden',
});
gsap.set('.svg__transition svg path', {
drawSVG: '0% 0%',
attr: { 'stroke-width': 100 },
});
tl.kill();
},
});
tl.to('.svg__transition svg path', {
attr: { 'stroke-width': 100 },
});
tl.to(
'.svg__transition svg path',
{
drawSVG: '100% 100%',
},
'<+=0.45',
);
return new Promise((resolve) => {
tl.call(() => {
this.barbaWrapper.classList.remove('is__transitioning');
resolve();
});
});
},
},
{
/* Reference: https://www.leandra-isler.ch/
*/
name: 'example-6-transition',
to: {
namespace: ['contact'],
},
before: (data) => {
this.barbaWrapper.classList.add('is__transitioning');
data.next.container.classList.add('contact__transition');
gsap.set(data.next.container, {
position: 'fixed',
inset: 0,
clipPath: 'polygon(15% 75%, 85% 75%, 85% 75%, 15% 75%)',
zIndex: 3,
height: '100vh',
overflow: 'hidden',
'--clip': 'inset(0 0 0% 0)',
});
},
enter: (data) => {
const tl = gsap.timeline({
defaults: {
duration: 1.25,
ease: 'hop',
},
onComplete: () => tl.kill(),
});
tl.to(data.next.container, {
clipPath: 'polygon(0% 100%, 100% 100%, 100% 0%, 0% 0%)',
});
tl.to(
data.next.container,
{
'--clip': 'inset(0 0 100% 0)',
},
'<+=0.285',
);
tl.call(
() => {
this.motionTexts.destroy(); // destroy motion text on current container
this.motionTexts.init(data.next.container); // initialization motion text for next container
this.motionTexts.animationIn();
},
null,
'<+=0.385',
);
return new Promise((resolve) => {
tl.call(() => {
resolve();
});
});
},
after: (data) => {
this.barbaWrapper.classList.remove('is__transitioning');
data.next.container.classList.remove('contact__transition');
gsap.set(data.next.container, {
clearProps: 'all',
});
},
sync: true,
},
],
});
this.render();
this.addEventListeners();
}
getPercentageVerticalClipExample3() {
const titleDestinationBound = this.titleDestination.getBoundingClientRect();
const halfHeightTitleDestination = titleDestinationBound.height / 2;
const halfHeightViewport = window.innerHeight / 2;
this.percentageVerticalClip = (halfHeightTitleDestination / halfHeightViewport) * 50;
}
onResize() {
this.getPercentageVerticalClipExample3();
this.webglPageTransition.onResize();
}
addEventListeners() {
window.addEventListener('resize', this.onResize.bind(this));
}
render() {
this.webglPageTransition.render();
requestAnimationFrame(this.render.bind(this));
}
}
document.addEventListener('DOMContentLoaded', () => {
preventLinksMenu();
gsap.registerPlugin(SplitText, CustomEase, MorphSVGPlugin, DrawSVGPlugin);
CustomEase.create('hop', '0.56, 0, 0.35, 0.98');
new App();
});
src/scripts/components/motion-text.js
import gsap from "gsap";
import { SplitText } from "gsap/SplitText";
import { setClassSplitText } from "../utils";
class MotionText {
elements = [];
splitText = [];
splitTextTween = [];
constructor() {}
init(container) {
this.elements = container
? container.querySelectorAll("[data-motion-text]")
: document.querySelectorAll("[data-motion-text]");
this.elements.forEach((element) => {
const duration =
parseFloat(element.getAttribute("data-motion-text-duration")) || 0.6;
if (element.hasAttribute("data-motion-text-split")) {
const splitType =
element.getAttribute("data-motion-text-split") || "lines";
const staggers =
parseFloat(element.getAttribute("data-motion-text-stagger")) || 0.05;
const split = new SplitText(element, {
type: splitType,
mask: splitType,
...setClassSplitText(splitType),
});
gsap.set(split[splitType], {
yPercent: 120,
});
gsap.set(element, {
visibility: "visible",
});
this.splitText.push({
el: element,
split,
duration,
staggers,
splitType,
});
}
});
}
animationIn() {
this.splitText.forEach(({ split, duration, staggers, splitType }) => {
const tween = gsap.to(split[splitType], {
yPercent: 0,
duration,
stagger: staggers,
ease: "power2.inOut",
});
this.splitTextTween.push(tween);
});
}
destroy() {
if (
this.splitText.length === 0 &&
this.splitTextTween.length === 0 &&
this.elements.length === 0
)
return;
this.elements.forEach((el) => {
el.dataset.motionText = false;
});
this.splitText.forEach(({ split }) => {
split.revert();
});
this.splitTextTween.forEach((tween) => {
tween.kill();
});
// this.ease
this.splitTextTween = [];
this.elements = [];
this.splitText = [];
}
}
export default MotionText;
src/scripts/components/webgl-page-transition.js
import * as THREE from "three";
import vertexShader from "../shader/vertex.glsl";
import fragmentShader from "../shader/fragment.glsl";
import { hexToRgb } from "../utils";
class WebGLPageTransition {
constructor() {
const rootStyle = getComputedStyle(document.documentElement);
this.color = hexToRgb(rootStyle.getPropertyValue("--about-background"));
this.dimension = {
width: window.innerWidth,
height: window.innerHeight,
pixelRatio: Math.min(window.devicePixelRatio, 1),
};
this.cameraZ = 100;
this.createScene();
this.createCamera();
this.createRenderer();
this.createGeometry();
this.createMesh();
this.onResize();
this.updateMeshSize();
}
createScene() {
this.scene = new THREE.Scene();
}
createCamera() {
const fov =
2 * Math.atan(this.dimension.height / 2 / this.cameraZ) * (180 / Math.PI);
this.camera = new THREE.PerspectiveCamera(
fov,
window.innerWidth / window.innerHeight,
0.1,
1000,
);
this.scene.add(this.camera);
this.camera.position.z = this.cameraZ;
}
createRenderer() {
this.renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true,
});
document.body.appendChild(this.renderer.domElement);
this.renderer.domElement.id = "webgl";
this.renderer.setSize(this.dimension.width, this.dimension.height);
this.renderer.render(this.scene, this.camera);
this.renderer.setPixelRatio(this.dimension.pixelRatio);
}
createGeometry() {
this.geometry = new THREE.PlaneGeometry(1, 1);
}
createMesh() {
this.material = new THREE.ShaderMaterial({
uniforms: {
uColor: {
value: new THREE.Vector3(
this.color.r / 255,
this.color.g / 255,
this.color.b / 255,
),
},
uProgress: {
value: 1.5,
},
},
vertexShader,
fragmentShader,
transparent: true,
});
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.scene.add(this.mesh);
}
updateMeshSize() {
this.mesh.scale.set(this.dimension.width, this.dimension.height, 1);
}
onResize() {
this.dimension.width = window.innerWidth;
this.dimension.height = window.innerHeight;
this.dimension.pixelRatio = Math.min(window.devicePixelRatio, 1);
// Resize camera
this.camera.aspect = this.dimension.width / this.dimension.height;
this.camera.fov =
2 * Math.atan(this.dimension.height / 2 / this.cameraZ) * (180 / Math.PI);
this.camera.updateProjectionMatrix();
// Resize renderer
this.renderer.setSize(this.dimension.width, this.dimension.height);
this.renderer.setPixelRatio(this.dimension.pixelRatio);
this.updateMeshSize();
}
render() {
this.renderer.render(this.scene, this.camera);
}
}
export default WebGLPageTransition;
src/scripts/shader/fragment.glsl
varying vec2 vUv;
uniform float uProgress;
uniform vec3 uColor;
// Resource noise function: https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83
float rand(vec2 n) {
return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
}
float noise(vec2 p){
vec2 ip = floor(p);
vec2 u = fract(p);
u = u*u*(3.0-2.0*u);
float res = mix(
mix(rand(ip),rand(ip+vec2(1.0,0.0)),u.x),
mix(rand(ip+vec2(0.0,1.0)),rand(ip+vec2(1.0,1.0)),u.x),u.y);
return res*res;
}
void main(){
float noise = noise(vUv * 5.);
float edge = 0.185;
float disolve = smoothstep(1. - uProgress - edge, 1. - uProgress + edge, noise);
float alpha = 1. - disolve;
gl_FragColor = vec4(uColor, alpha);
}src/scripts/shader/vertex.glsl
varying vec2 vUv;
void main() {
vec3 pos = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
vUv = uv;
}src/scripts/utils.js
const select = (selector) => document.querySelector(selector);
const selectAll = (selector) => document.querySelectorAll(selector);
const hexToRgb = (hex) => {
hex = hex.replace(/^#/, "");
if (hex.length === 3) {
hex = hex
.split("")
.map((char) => char + char)
.join("");
}
const bigint = parseInt(hex, 16);
const r = (bigint >> 16) & 255;
const g = (bigint >> 8) & 255;
const b = bigint & 255;
return { r, g, b };
};
const setClassSplitText = (type) => {
return type === "lines"
? {
linesClass: "lines",
}
: type === "words"
? {
wordsClass: "words",
}
: {
charsClass: "chars",
};
};
const preventLinksMenu = () => {
const links = selectAll("a.nav__link");
links.forEach((link) => {
link.addEventListener("click", (e) => {
const currentPathname = window.location.pathname;
const linkPathname = new URL(e.currentTarget.href).pathname;
if (currentPathname === linkPathname) e.preventDefault();
});
});
};
export { select, selectAll, hexToRgb, setClassSplitText, preventLinksMenu };
src/styles/app.css
:root {
--vw: 1440;
--multiplier: 100vw;
--color-text: #72706c;
--color-link: #72706c;
--color-link-hover: #000;
--nav-background: #72706c;
--nav-text: #ffffffd6;
--nav-text-hover: #ffffff;
--base-background: #ece9e4;
--base-text: #020403;
--about-background: #beb9b8;
--about-text: #020403;
--contact-background: #b5ab9c;
--contact-text: #020403;
--contact-overlay: #020403;
--team-background: #d3c59c;
--team-text: #020403;
--team-overlay: #020403;
--team-overlay-text: #ece9e4;
--works-background: #a2b1a0;
--works-text: #020403;
--works-overlay: #020403;
--archive-background: #f1f0aa;
--archive-text: #020403;
--archive-overlay: #020403;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
height: 100svh;
font-size: 12px;
overflow-x: clip;
background-color: var(--base-text);
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
'Helvetica Neue',
sans-serif;
scrollbar-width: none;
-ms-overflow-style: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-ms-overflow-style: none;
color: var(--color-text);
}
main {
display: block;
}
img,
svg {
width: 100%;
height: 100%;
object-fit: cover;
}
a {
text-decoration: none;
color: var(--color-link);
outline: none;
cursor: pointer;
&:hover {
color: var(--color-link-hover);
}
&:focus {
outline: none;
background: lightgrey;
&:not(:focus-visible) {
background: transparent;
}
&:focus-visible {
outline: 2px solid red;
background: transparent;
}
}
}
body.is__transitioning a {
pointer-events: none;
}
ul,
ol {
list-style: none;
}
body,
html,
.app__wrapper {
height: 100svh;
scrollbar-width: none;
-ms-overflow-style: none;
}
.frame {
padding: 1rem;
display: grid;
z-index: 1000;
position: fixed;
top: 0;
left: 0;
width: 100%;
grid-row-gap: 1rem;
grid-column-gap: 1rem;
pointer-events: none;
justify-items: center;
justify-content: center;
grid-template-columns: 1fr auto auto auto 1fr;
grid-template-areas:
'title title title title title'
'... back archive github ...'
'tags tags tags tags tags';
a,
button {
pointer-events: auto;
}
.frame__title {
grid-area: title;
font-size: inherit;
font-size: 600;
margin: 0;
}
.frame__back {
grid-area: back;
}
.frame__archive {
grid-area: archive;
}
.frame__github {
grid-area: github;
}
.frame__tags {
grid-area: tags;
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
@media screen and (min-width: 53em) {
grid-template-columns: auto auto auto auto 1fr;
align-content: space-between;
grid-template-areas: 'title back github archive tags';
.frame__title {
margin-right: 2rem;
}
.frame__tags {
justify-self: end;
}
}
}
.app__wrapper {
background-color: var(--base-background);
color: var(--base-text);
overflow-x: hidden;
}
.app__wrapper.contact__transition::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 100;
will-change: clip-path;
background-color: var(--contact-overlay);
clip-path: var(--clip, inset(0 0 100% 0));
}
.transition__overlay {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100dvh;
z-index: 100;
will-change: clip-path;
pointer-events: none;
visibility: hidden;
}
.transition__overlay.team__transition {
background-color: var(--team-overlay);
clip-path: var(--clip, polygon(0% 40%, 0% 40%, 0% 60%, 0% 60%));
}
.transition__overlay .title__destination {
position: absolute;
width: 100%;
top: 50%;
left: 0;
transform: translateY(-50%);
font-size: clamp(1rem, 4vw, 2rem);
color: var(--team-overlay-text);
text-align: center;
font-weight: 500;
line-height: 1;
padding: 0.25em 0;
}
.content__wrapper {
width: 100%;
min-height: 100svh;
position: relative;
}
.content {
min-height: 100svh;
width: 100%;
display: flex;
flex-direction: column;
gap: 7vh;
padding-top: 8vh;
align-items: center;
justify-content: center;
}
.content .title {
font-size: clamp(2rem, 15vw, 10rem);
text-transform: uppercase;
font-weight: 500;
line-height: 1;
}
.content.about {
background-color: var(--about-background);
color: var(--about-text);
}
.content.contact {
background-color: var(--contact-background);
color: var(--contact-text);
}
.content.team {
background-color: var(--team-background);
color: var(--team-text);
}
.content.archive {
background-color: var(--archive-background);
color: var(--archive-text);
}
.content.works {
background-color: var(--works-background);
color: var(--works-text);
}
#webgl {
width: 100%;
pointer-events: none;
z-index: 100;
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
visibility: hidden;
display: block;
}
.transition__morph__svg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100dvh;
pointer-events: none;
z-index: 100;
visibility: hidden;
}
.transition__morph__svg svg path {
fill: var(--works-overlay);
stroke: var(--works-overlay);
}
.transition__svg__wrapper {
position: fixed;
inset: 0;
overflow: hidden;
z-index: 100;
visibility: hidden;
pointer-events: none;
width: 100%;
height: 100vh;
will-change: visibility, pointer-events;
}
.svg__transition {
width: 100%;
height: 100%;
display: grid;
place-items: center;
}
.svg__transition svg {
display: block;
width: 125%;
height: 100%;
aspect-ratio: 1;
}
.svg__transition svg path {
stroke: var(--archive-overlay);
}
.nav {
position: fixed;
bottom: 1rem;
left: 50%;
transform: translateX(-50%);
z-index: 101;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1.5rem;
background-color: var(--nav-background);
padding: 0.75rem 1.5rem;
border-radius: 2em;
}
.nav__link {
border-radius: 3px;
color: var(--nav-text);
}
.nav__link:hover,
.nav__link:focus {
color: var(--nav-text-hover);
}
.lines,
.words,
.chars {
will-change: transform;
}
[data-motion-text='true'] {
visibility: hidden;
}
@media screen and (max-width: 768px) {
.transition__overlay .title__destination {
font-size: clamp(1rem, 5vw, 2rem);
}
}
Original author attribution실행 안내·자료
Creating Custom Page Transitions in Astro with Barba.js and GSAP
Original author: Iqbal Muthahhary
Published by Codrops. Copyright (c) 2026 Codrops.
License: MIT, pursuant to the publisher's downloadable-demo grant.
Keep CODROPS-MIT-2026.txt and the original project notices with copies.
Third-party media exceptions and credits are recorded separately in the source inventory.
.preview/third-party-notices/@barba--core/LICENSE실행 안내·자료
MIT License
Copyright (c) 2024 Luigi De Rosa, Thierry Michel, Xavier Foucrier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
.preview/third-party-notices/astro/LICENSE실행 안내·자료
MIT License
Copyright (c) 2021 Fred K. Schott
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
This license applies to parts of the `packages/create-astro` and `packages/astro` subdirectories originating from the https://github.com/sveltejs/kit repository:
Copyright (c) 2020 [these people](https://github.com/sveltejs/kit/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
"""
This license applies to parts of the `packages/create-astro` and `packages/astro` subdirectories originating from the https://github.com/vitejs/vite repository:
MIT License
Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
.preview/third-party-notices/is-promise/LICENSE실행 안내·자료
Copyright (c) 2014 Forbes Lindesay
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE..preview/third-party-notices/path-to-regexp/LICENSE실행 안내·자료
The MIT License (MIT)
Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
.preview/third-party-notices/three/LICENSE실행 안내·자료
The MIT License
Copyright © 2010-2026 three.js authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Bundled dependency licenses실행 안내·자료
Isolated framework dependency inventory
{
"id": "codrops-a8b5299a0e00",
"packages": [
{
"package": "astro",
"version": "6.1.4",
"declaredLicense": "MIT",
"noticeFiles": [
".preview/third-party-notices/astro/LICENSE"
],
"noticeScope": "Original installed package notices; not a replacement license grant."
},
{
"package": "@barba/core",
"version": "2.10.3",
"declaredLicense": "MIT",
"noticeFiles": [
".preview/third-party-notices/@barba--core/LICENSE"
],
"noticeScope": "Original installed package notices; not a replacement license grant."
},
{
"package": "gsap",
"version": "3.14.2",
"declaredLicense": "Standard 'no charge' license: https://gsap.com/standard-license.",
"noticeFiles": [
".preview/third-party-notices/gsap/README.md",
".preview/third-party-notices/gsap/package.json",
".preview/third-party-notices/gsap/source-copyright-header.txt"
],
"noticeScope": "Original installed package notices; not a replacement license grant."
},
{
"package": "three",
"version": "0.183.2",
"declaredLicense": "MIT",
"noticeFiles": [
".preview/third-party-notices/three/LICENSE"
],
"noticeScope": "Original installed package notices; not a replacement license grant."
},
{
"package": "is-promise",
"version": "4.0.0",
"declaredLicense": "MIT",
"noticeFiles": [
".preview/third-party-notices/is-promise/LICENSE"
],
"noticeScope": "Original installed package notices; not a replacement license grant."
},
{
"package": "path-to-regexp",
"version": "6.3.0",
"declaredLicense": "MIT",
"noticeFiles": [
".preview/third-party-notices/path-to-regexp/LICENSE"
],
"noticeScope": "Original installed package notices; not a replacement license grant."
}
],
"publisherNotice": ".preview/CODROPS-MIT.txt",
"authorAttribution": ".preview/ATTRIBUTION.txt"
}
.preview/ATTRIBUTION.txt
Creating Custom Page Transitions in Astro with Barba.js and GSAP
Original author: Iqbal Muthahhary
Published by Codrops. Copyright (c) 2026 Codrops.
License: MIT, pursuant to the publisher's downloadable-demo grant.
Keep CODROPS-MIT-2026.txt and the original project notices with copies.
Third-party media exceptions and credits are recorded separately in the source inventory.
.preview/third-party-notices/@barba--core/LICENSE
MIT License
Copyright (c) 2024 Luigi De Rosa, Thierry Michel, Xavier Foucrier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
.preview/third-party-notices/astro/LICENSE
MIT License
Copyright (c) 2021 Fred K. Schott
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
This license applies to parts of the `packages/create-astro` and `packages/astro` subdirectories originating from the https://github.com/sveltejs/kit repository:
Copyright (c) 2020 [these people](https://github.com/sveltejs/kit/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
"""
This license applies to parts of the `packages/create-astro` and `packages/astro` subdirectories originating from the https://github.com/vitejs/vite repository:
MIT License
Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
.preview/third-party-notices/gsap/README.md
# GSAP (GreenSock Animation Platform)
[](https://gsap.com)
GSAP is a **framework-agnostic** JavaScript animation library that turns developers into animation superheroes. Build high-performance animations that work in **every** major browser. Animate CSS, SVG, canvas, React, Vue, WebGL, colors, strings, motion paths, generic objects... anything JavaScript can touch! GSAP's <a href="https://gsap.com/docs/v3/Plugins/ScrollTrigger/">ScrollTrigger</a> plugin delivers jaw-dropping scroll-based animations with minimal code. <a href="https://gsap.com/docs/v3/GSAP/gsap.matchMedia()">gsap.matchMedia()</a> makes building responsive, accessibility-friendly animations a breeze.
No other library delivers such advanced sequencing, reliability, and tight control while solving real-world problems on over 12 million sites. GSAP works around countless browser inconsistencies; your animations ***just work***. At its core, GSAP is a high-speed property manipulator, updating values over time with extreme accuracy. It's up to 20x faster than jQuery!
GSAP is completely flexible; sprinkle it wherever you want. **Zero dependencies.**
There are many optional <a href="https://gsap.com/docs/v3/Plugins">plugins</a> and <a href="https://gsap.com/docs/v3/Eases">easing</a> functions for achieving advanced effects easily like <a href="https://gsap.com/docs/v3/Plugins/ScrollTrigger/">scrolling</a>, <a href="https://gsap.com/docs/v3/Plugins/MorphSVGPlugin">morphing</a>, [text splitting](https://gsap.com/docs/v3/Plugins/SplitText), animating along a <a href="https://gsap.com/docs/v3/Plugins/MotionPathPlugin">motion path</a> or <a href="https://gsap.com/docs/v3/Plugins/Flip/">FLIP</a> animations. There's even a handy <a href="https://gsap.com/docs/v3/Plugins/Observer/">Observer</a> for normalizing event detection across browsers/devices.
### Get Started
[](https://gsap.com/get-started)
## Docs & Installation
View the <a href="https://gsap.com/docs">full documentation here</a>, including an <a href="https://gsap.com/install">installation guide</a>.
### CDN
```html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14/dist/gsap.min.js"></script>
```
See <a href="https://www.jsdelivr.com/gsap">JSDelivr's dedicated GSAP page</a> for quick CDN links to the core files/plugins. There are more <a href="https://gsap.com/install">installation instructions</a> at gsap.com.
**Every major ad network excludes GSAP from file size calculations** and most have it on their own CDNs, so contact them for the appropriate URL(s).
### NPM
See the <a href="https://gsap.com/install">guide to using GSAP via NPM here</a>.
```javascript
npm install gsap
```
GSAP's core can animate almost anything including CSS and attributes, plus it includes all of the <a href="https://gsap.com/docs/v3/GSAP/UtilityMethods">utility methods</a> like <a href="https://gsap.com/docs/v3/GSAP/UtilityMethods/interpolate()">interpolate()</a>, <a href="https://gsap.com/docs/v3/GSAP/UtilityMethods/mapRange()">mapRange()</a>, most of the <a href="https://gsap.com/docs/v3/Eases">eases</a>, and it can do snapping and modifiers.
```javascript
// typical import
import gsap from "gsap";
// get other plugins:
import ScrollTrigger from "gsap/ScrollTrigger";
import Flip from "gsap/Flip";
import Draggable from "gsap/Draggable";
// or all tools are exported from the "all" file (excluding members-only plugins):
import { gsap, ScrollTrigger, Draggable, MotionPathPlugin } from "gsap/all";
// don't forget to register plugins
gsap.registerPlugin(ScrollTrigger, Draggable, Flip, MotionPathPlugin);
```
The NPM files are ES modules, but there's also a /dist/ directory with <a href="https://www.davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/">UMD</a> files for extra compatibility.
## GSAP is FREE!
Thanks to [Webflow](https://webflow.com), GSAP is now **100% FREE** including ALL of the bonus plugins like [SplitText](https://gsap.com/docs/v3/Plugins/SplitText), [MorphSVG](https://gsap.com/docs/v3/Plugins/MorphSVGPlugin), and all the others that were exclusively available to Club GSAP members. That's right - the entire GSAP toolset is FREE, even for commercial use! 🤯 Read more [here](https://webflow.com/blog/gsap-becomes-free)
### ScrollTrigger & ScrollSmoother
If you're looking for scroll-driven animations, GSAP's <a href="https://gsap.com/docs/v3/Plugins/ScrollTrigger/">ScrollTrigger</a> plugin is the standard. There's a companion <a href="https://gsap.com/docs/v3/Plugins/ScrollSmoother/">ScrollSmoother</a> as well.
[](https://gsap.com/docs/v3/Plugins/ScrollTrigger)
### Using React?
There's a <a href="https://www.npmjs.com/package/@gsap/react">@gsap/react</a> package that exposes a `useGSAP()` hook which is a drop-in replacement for `useEffect()`/`useLayoutEffect()`, automating cleanup tasks. Please read the <a href="https://gsap.com/react">React guide</a> for details.
### Resources
* <a href="https://gsap.com/">gsap.com</a>
* <a href="https://gsap.com/get-started/">Getting started guide</a>
* <a href="https://gsap.com/docs/">Docs</a>
* <a href="https://gsap.com/demos">Demos & starter templates</a>
* <a href="https://gsap.com/community/">Community forums</a>
* <a href="https://gsap.com/docs/v3/Eases">Ease Visualizer</a>
* <a href="https://gsap.com/showcase">Showcase</a>
* <a href="https://www.youtube.com/@GreenSockLearning">YouTube Channel</a>
* <a href="https://gsap.com/cheatsheet">Cheat sheet</a>
* <a href="https://webflow.com">Webflow</a>
### Need help?
Ask in the friendly <a href="https://gsap.com/community/">GSAP forums</a>. Or share your knowledge and help someone else - it's a great way to sharpen your skills! Report any bugs there too (or <a href="https://github.com/greensock/GSAP/issues">file an issue here</a> if you prefer).
### License
GreenSock's standard "no charge" license can be viewed at <a href="https://gsap.com/standard-license">https://gsap.com/standard-license</a>.
Copyright (c) 2008-2025, GreenSock. All rights reserved.
.preview/third-party-notices/gsap/source-copyright-header.txt
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
/*!
* GSAP 3.14.2
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
.preview/third-party-notices/is-promise/LICENSE
Copyright (c) 2014 Forbes Lindesay
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
.preview/third-party-notices/path-to-regexp/LICENSE
The MIT License (MIT)
Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
.preview/third-party-notices/three/LICENSE
The MIT License
Copyright © 2010-2026 three.js authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
BEHIND THE EXAMPLE
이 장면을 만드는 원리
장면 덮개가 입력을 받는 구간의 시작과 끝
반복 입력에서 현재 의도와 전환의 종료·취소 책임을 명시합니다.
- 이 예제에서는
- about 전환의 leave는 #webgl을 보이고 pointerEvents=auto로 바꾼 후 uProgress를 -0.75까지 보냅니다. after는 1.5로 되돌리고 완료 때 입력을 통과시키며 덮개를 숨깁니다.
