Section 1
Slogan
Section 3
<script src="https://unpkg.com/gsap@3/dist/gsap.min.js"></script>
<script src="https://unpkg.com/gsap@3/dist/ScrollTrigger.min.js"></script>

<div class="section" id="section-1">Section 1</div>

<div class="section" id="section-2">
  <div class="box-image-animated">
    <div class="triangle triangle-1"></div>
    <div class="triangle triangle-2"></div>
    <div class="triangle triangle-3"></div>
    <div class="red-dot"></div>
  </div>
  <div class="logo">Logo</div>
  <div class="slogan">Slogan</div>
</div>

<div class="section" id="section-3">Section 3</div>
.section {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  background: #1e1e2f;
  position: relative;
  overflow: hidden;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}


.triangle {
  background: linear-gradient(to top, #a1c4fd, #c2e9fb);
  clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
  opacity: 0;
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}

.triangle-1 {
  width: 250px;
  height: 250px;
  z-index: 4;
    opacity: 1;
}

.triangle-2 {
  width: 350px;
  height: 350px;
  z-index: 2;
   background: #999
}

.triangle-3 {
  width: 450px;
  height: 450px;
   background: yellow;
  z-index: 1;
}

.red-dot {
  width: 20px;
  height: 20px;
  background: red;
  border-radius: 50%;
  position: absolute;
  bottom: 240px;
  left: 50%;
  transform: translateX(-50%);
  opacity: 0;
  z-index: 3;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); 
}

.logo {
  font-size: 24px;
  color: #fff;
  font-weight: bold; 
  position: absolute;
  top: 50px;
  left: 50px;
  opacity: 0;
  z-index: 5;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); 
}

.slogan {
  font-size: 18px;
  color: #fff;
  font-style: italic;
  position: absolute;
  top: 200px;
  left: 50px;
  opacity: 0;
  z-index: 6;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); 
}

#section-1 {
  background-color: #000;
}

#section-3 {
  background-color: #333;
}
gsap.registerPlugin(ScrollTrigger);

const section = document.querySelector("#section-2");
const elements = [
  '.triangle-2',
  '.triangle-3',
  '.red-dot',
  '.logo',
  '.slogan'
];

let scrollStep = 0;
let isScrolling = false;
let isLocked = false;
let touchStartY = 0;

// Ẩn tất cả phần tử ban đầu
gsap.set(elements, { opacity: 0, y: 50 });

// ScrollTrigger để pin section-2
ScrollTrigger.create({
  trigger: section,
  start: "top top",
  end: () => "+=" + window.innerHeight,
  pin: true,
  pinSpacing: true,
  scrub: false,

  onEnter: () => {
    if (scrollStep < elements.length) lockScroll();
  },

  onLeave: () => {
    if (scrollStep === elements.length) unlockScroll();
  },

  onLeaveBack: () => {
    unlockScroll(); // Rời khỏi section-2 về section-1 thì mở lại scroll
  },

  onEnterBack: () => {
    if (scrollStep > 0) lockScroll();
  }
});

// ✅ Xử lý trạng thái scrollStep khi reload
window.addEventListener("load", () => {
  const sectionRect = section.getBoundingClientRect();
  const isSectionVisible = sectionRect.top <= 0 && sectionRect.bottom >= 0;

  if (isSectionVisible) {
    const sectionHeight = section.offsetHeight;
    const visibleRatio = (window.innerHeight - sectionRect.top) / sectionHeight;

    // Ước tính số phần tử nên hiển thị dựa trên % section được nhìn thấy
    scrollStep = Math.min(elements.length, Math.floor(visibleRatio * elements.length));

    // Hiện đúng số phần tử theo step
    for (let i = 0; i < scrollStep; i++) {
      gsap.set(elements[i], { opacity: 1, y: 0 });
    }

    if (scrollStep < elements.length) {
      lockScroll(); // nếu chưa hết bước, vẫn khóa
    }
  }
});

// Cuộn chuột (desktop)
window.addEventListener('wheel', (e) => {
  if (!isLocked || isScrolling) return;

  const direction = e.deltaY > 0 ? 1 : -1;
  handleScroll(direction, e);
}, { passive: false });

// Vuốt tay (mobile)
window.addEventListener('touchstart', (e) => {
  touchStartY = e.touches[0].clientY;
}, { passive: true });

window.addEventListener('touchend', (e) => {
  if (!isLocked || isScrolling) return;

  const deltaY = touchStartY - (e.changedTouches?.[0]?.clientY || 0);
  const direction = deltaY > 20 ? 1 : deltaY < -20 ? -1 : 0;

  if (direction !== 0) {
    handleScroll(direction, e);
  }
}, { passive: false });

// Xử lý cuộn từng bước
function handleScroll(direction, e) {
  e.preventDefault();
  if (isScrolling) return;

  const sectionRect = section.getBoundingClientRect();
  const isSectionVisible = sectionRect.top <= 0 && sectionRect.bottom >= 0;
  if (!isSectionVisible) return;

  // Cuộn xuống để hiện từng phần tử
  if (direction === 1 && scrollStep < elements.length) {
    isScrolling = true;
    playStep(scrollStep, () => {
      scrollStep++;
      isScrolling = false;

      if (scrollStep === elements.length) {
        unlockScroll();
        ScrollTrigger.refresh();
      }
    });
    return;
  }

  // Cuộn lên để ẩn từng phần tử
  if (direction === -1 && scrollStep > 0) {
    isScrolling = true;
    scrollStep--;
    reverseStep(scrollStep, () => {
      isScrolling = false;
      if (scrollStep === 0) unlockScroll();
    });
  }
}

// Hiển thị phần tử theo bước
function playStep(index, onComplete) {
  const el = elements[index];
  if (!el) return onComplete?.();

  gsap.to(el, {
    opacity: 1,
    y: 0,
    duration: 0.5,
    ease: "power2.out",
    onComplete
  });
}

// Ẩn phần tử theo bước
function reverseStep(index, onComplete) {
  const el = elements[index];
  if (!el) return onComplete?.();

  gsap.to(el, {
    opacity: 0,
    y: 50,
    duration: 0.5,
    ease: "power2.in",
    onComplete
  });
}

// Khóa cuộn
function lockScroll() {
  isLocked = true;
  document.body.style.overflow = 'hidden';
}

// Mở khóa cuộn
function unlockScroll() {
  isLocked = false;
  document.body.style.overflow = '';
}

// Đảm bảo không bị kẹt scroll khi reload ngoài section-2
document.addEventListener('DOMContentLoaded', () => {
  setTimeout(() => {
    const rect = section.getBoundingClientRect();
    const isVisible = rect.top < window.innerHeight && rect.bottom > 0;

    if (!isVisible) unlockScroll();
  }, 100);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/smooth-scrollbar/8.8.4/smooth-scrollbar.min.js"></script>

		<div class="scroller">
      
			<section class="orange">
				<div class="text">This is some text inside of a div block.</div>
			</section>
      
			<section class="black">
        
				<div class="text-wrap">          
          <div class="panel-text blue-text">Blue</div> 
          <div class="panel-text red-text">Red</div>   
          <div class="panel-text orange-text">Orange</div> 
          <div class="panel-text purple-text">Purple</div> 
        </div>
        
				<div class="p-wrap">
					<div class="panel blue"></div> 
					<div class="panel red"></div>
					<div class="panel orange"></div> 
					<div class="panel purple"></div> 
				</div>
        
			</section>
      
			<section class="blue"></section>
      
		</div>
.scroller {
  height: 100vh;
}

.orange {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  height: 100vh;
  -webkit-box-pack: center;
  -webkit-justify-content: center;
  -ms-flex-pack: center;
  justify-content: center;
  -webkit-box-align: center;
  -webkit-align-items: center;
  -ms-flex-align: center;
  align-items: center;
  background-color: #753500;
}

.text {
  color: #fff;
}

.black {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  height: 100vh;
  -webkit-justify-content: space-around;
  -ms-flex-pack: distribute;
  justify-content: space-around;
  -webkit-box-align: center;
  -webkit-align-items: center;
  -ms-flex-align: center;
  align-items: center;
  background-color: #070707;
}

.blue {
  height: 100vh;
  background-color: #00026d;
}


.text-wrap {
  position: relative;
  overflow: hidden;
  width: 450px;
  height: 80vh;
}

.panel-text {
  position: absolute;
  left: 0%;
  top: 0%;
  right: 0%;
  bottom: 0%;
  z-index: 1;
  width: 100%;
  height: 100%;
  font-size: 40px;
  text-transform: uppercase;
  font-weight: 900;
  text-align:center;
  background-color: #070707;
  
  transform:translateY(100%);
  opacity: 0;
}

.panel-text.blue-text {
  color: blue;
}

.panel-text.red-text {
  color: red;
}

.panel-text.purple-text {
  color: purple;
}

.panel-text.orange-text {
  color: orange;
}






.p-wrap {
  position: relative;
  overflow: hidden;
  width: 450px;
  height: 80vh;
}

.panel {
  position: absolute;
  left: 0%;
  top: 0%;
  right: 0%;
  bottom: 0%;
  z-index: 1;
  width: 100%;
  height: 100%;
  background-image: url("../images/5ed12171d9d512cb2feead83_5.jpg");
  background-position: 50% 50%;
  background-size: cover;
  background-repeat: no-repeat;
}

.panel._2 {
  z-index: 1;
  background-image: url("../images/5f5a5b3515c4dd0c2c455925_110642301_938622823267359_7859124022958180678_n201.jpg");
}

.panel.blue {
  z-index: auto;
}

.panel.red {
  z-index: auto;
  background-color: red;
  background-image: none;
}

.panel.orange {
  z-index: auto;
  background-color: #cf5d00;
  background-image: none;
}

.panel.purple {
  z-index: auto;
  background-color: #808;
  background-image: none;
}
gsap.registerPlugin(ScrollTrigger);
let bodyScrollBar = Scrollbar.init(document.body, {
  damping: 0.1,
  delegateTo: document,
});
ScrollTrigger.scrollerProxy(".scroller", {
  scrollTop(value) {
    if (arguments.length) {
      bodyScrollBar.scrollTop = value;
    }
    return bodyScrollBar.scrollTop;
  },
});
bodyScrollBar.addListener(ScrollTrigger.update);







gsap.set(".panel", { zIndex: (i, target, targets) => targets.length - i });

var images = gsap.utils.toArray('.panel:not(.purple)');

images.forEach((image, i) => {
  
  var tl = gsap.timeline({
    
    scrollTrigger: {
      trigger: "section.black",
      scroller: ".scroller",
      start: () => "top -" + (window.innerHeight*(i+0.5)),
      end: () => "+=" + window.innerHeight,
      scrub: true,
      toggleActions: "play none reverse none",
      invalidateOnRefresh: true,     
    }
    
  })
  
  tl
  .to(image, { height: 0 })
  ;
  
});







gsap.set(".panel-text", { zIndex: (i, target, targets) => targets.length - i });

var texts = gsap.utils.toArray('.panel-text');

texts.forEach((text, i) => {
  
  var tl = gsap.timeline({
    
    scrollTrigger: {
      trigger: "section.black",
      scroller: ".scroller",
      start: () => "top -" + (window.innerHeight*i),
      end: () => "+=" + window.innerHeight,
      scrub: true,
      toggleActions: "play none reverse none",
      invalidateOnRefresh: true,     
    }
    
  })
  
  tl
  .to(text, { duration: 0.33, opacity: 1, y:"50%" })  
  .to(text, { duration: 0.33, opacity: 0, y:"0%" }, 0.66)
  ;
  
});





ScrollTrigger.create({

    trigger: "section.black",
    scroller: ".scroller",
    scrub: true,
    markers: true,
    pin: true,
    start: () => "top top",
    end: () => "+=" + ((images.length + 1) * window.innerHeight),
    invalidateOnRefresh: true,

});