11 CSS Page Transitions04 / 11

CSS + JSMIT licensed

Clip-Path Wipe

A dramatic clip-path reveal that wipes the new page into view. High-impact and memorable — best reserved for creative portfolios and brand sites.

Published

Live Demo
Try it

This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.

The code

<!-- Pages -->
<div class="page" id="page-0">
  <div class="bg-word">STUDIO</div>
  <div class="hero-grid">
    <div>
      <div class="hero-eyebrow">Creative Studio · Est. 2019</div>
      <h1 class="hero-title">WE BUILD<br><span>BOLD</span><br>FUTURES</h1>
      <p class="hero-body">A multi-disciplinary studio pushing the boundaries of brand, motion, and digital experience design.</p>
      <button class="btn-hero" onclick="goTo(1)">View Work →</button>
    </div>
    <div class="hero-side">
      <div class="hero-stat">
        <div class="hero-stat-num">142</div>
        <div class="hero-stat-label">Projects</div>
      </div>
      <div class="hero-stat">
        <div class="hero-stat-num">8yr</div>
        <div class="hero-stat-label">Experience</div>
      </div>
      <div class="hero-stat">
        <div class="hero-stat-num">4.9</div>
        <div class="hero-stat-label">Avg Rating</div>
      </div>
    </div>
  </div>
</div>

<div class="page page-incoming" id="page-1">
  <div class="page-header">
    <h1>SELECTED<br>WORK</h1>
  </div>
  <div class="proj-grid">
    <div class="proj-card"><div class="proj-num">01</div><div class="proj-cat">Brand Identity</div><div class="proj-title">Luminary<br>Financial</div><div class="proj-arrow">→</div></div>
    <div class="proj-card"><div class="proj-num">02</div><div class="proj-cat">Digital Experience</div><div class="proj-title">Atlas<br>Platform</div><div class="proj-arrow">→</div></div>
    <div class="proj-card"><div class="proj-num">03</div><div class="proj-cat">Motion & Film</div><div class="proj-title">Epoch<br>Campaign</div><div class="proj-arrow">→</div></div>
    <div class="proj-card"><div class="proj-num">04</div><div class="proj-cat">Product Design</div><div class="proj-title">Nova<br>Wearables</div><div class="proj-arrow">→</div></div>
  </div>
</div>

<div class="page page-incoming" id="page-2">
  <div class="about-grid">
    <div>
      <h1>WE ARE<br><em>FORM</em><br>STUDIO</h1>
    </div>
    <div>
      <p class="about-body">A tight team of designers, developers, and strategists who believe that great work comes from obsessing over every pixel, every word, and every interaction.</p>
      <p class="about-body">We've partnered with everyone from pre-seed startups to Fortune 500s — always bringing the same level of care.</p>
      <div class="skill-row">
        <div class="skill-item">
          <div class="skill-name"><span>Brand Design</span><span>97%</span></div>
          <div class="skill-bar"><div class="skill-fill" style="width:97%"></div></div>
        </div>
        <div class="skill-item">
          <div class="skill-name"><span>Web Development</span><span>92%</span></div>
          <div class="skill-bar"><div class="skill-fill" style="width:92%"></div></div>
        </div>
        <div class="skill-item">
          <div class="skill-name"><span>Motion Design</span><span>88%</span></div>
          <div class="skill-bar"><div class="skill-fill" style="width:88%"></div></div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- Nav -->
<nav class="site-nav">
  <div class="nav-logo">FORM</div>
  <div class="nav-links">
    <button class="nav-link active" data-idx="0" onclick="goTo(0)">Home</button>
    <button class="nav-link" data-idx="1" onclick="goTo(1)">Work</button>
    <button class="nav-link" data-idx="2" onclick="goTo(2)">About</button>
  </div>
</nav>

<!-- Page dots -->
<div class="page-indicator">
  <button class="ind-dot active" data-idx="0" onclick="goTo(0)"></button>
  <button class="ind-dot" data-idx="1" onclick="goTo(1)"></button>
  <button class="ind-dot" data-idx="2" onclick="goTo(2)"></button>
</div>
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

:root {
  --dur: 800ms;
  --ease: cubic-bezier(0.76, 0, 0.24, 1);
}

html,
body {
  height: 100%;
  overflow: hidden;
  font-family: 'Space Grotesk', sans-serif;
}
/* Pages */

.page {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
}
/* Base state: clipped to nothing (right side) */

.page-incoming {
  clip-path: polygon(100% 0%, 100% 0%, 100% 100%, 100% 100%);
  transition: clip-path var(--dur) var(--ease);
  pointer-events: none;
  z-index: 2;
}
/* Fully revealed */

.page-incoming.reveal {
  clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}
/* Exiting page stays visible beneath */

.page-outgoing {
  z-index: 1;
}
/* Page 0 — Hero */

#page-0 {
  background: #0e0e14;
  justify-content: flex-end;
  padding: 80px;
  color: white;
}

#page-0 .bg-word {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: 'Bebas Neue', sans-serif;
  font-size: min(25vw, 240px);
  line-height: 1;
  color: rgba(255,255,255,0.04);
  white-space: nowrap;
  pointer-events: none;
  letter-spacing: -0.02em;
}

#page-0 .hero-grid {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: end;
  gap: 40px;
  width: 100%;
  max-width: 1100px;
}

#page-0 .hero-eyebrow {
  font-size: 11px;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: #8888aa;
  margin-bottom: 24px;
}

#page-0 .hero-title {
  font-family: 'Bebas Neue', sans-serif;
  font-size: clamp(52px, 9vw, 120px);
  line-height: 0.92;
  letter-spacing: -0.01em;
  color: white;
  margin-bottom: 32px;
}

#page-0 .hero-title span {
  color: #c8ff00;
}

#page-0 .hero-body {
  font-size: 15px;
  line-height: 1.75;
  color: #8888aa;
  max-width: 420px;
  margin-bottom: 48px;
}

.btn-hero {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  background: #c8ff00;
  color: #0e0e14;
  border: none;
  padding: 18px 36px;
  font-family: 'Space Grotesk', sans-serif;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  border-radius: 2px;
  transition: transform 0.2s, background 0.2s;
}

.btn-hero:hover {
  background: #d4ff33;
  transform: translateX(3px);
}

.hero-side {
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: flex-end;
}

.hero-stat {
  text-align: right;
  border-right: 2px solid #c8ff00;
  padding-right: 20px;
}

.hero-stat-num {
  font-family: 'Bebas Neue', sans-serif;
  font-size: 40px;
  color: white;
  line-height: 1;
}

.hero-stat-label {
  font-size: 10px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: #555570;
}
/* Page 1 — Projects */

#page-1 {
  background: #c8ff00;
  overflow-y: auto;
  padding: 80px;
  color: #0e0e14;
}

#page-1 .page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 64px;
}

#page-1 h1 {
  font-family: 'Bebas Neue', sans-serif;
  font-size: clamp(52px, 9vw, 112px);
  line-height: 0.9;
  letter-spacing: -0.01em;
  color: #0e0e14;
}

.proj-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 3px;
}

.proj-card {
  background: #0e0e14;
  padding: 36px;
  color: white;
  display: flex;
  flex-direction: column;
  min-height: 220px;
  position: relative;
  overflow: hidden;
  cursor: pointer;
  transition: background 0.3s;
}

.proj-card:nth-child(even) {
  background: #1a1a24;
}

.proj-card:hover {
  background: #2a2a3a;
}

.proj-num {
  font-family: 'Bebas Neue', sans-serif;
  font-size: 64px;
  color: rgba(200,255,0,0.15);
  line-height: 1;
  position: absolute;
  top: 16px;
  right: 24px;
  letter-spacing: -0.04em;
}

.proj-cat {
  font-size: 9px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #555570;
  margin-bottom: 12px;
}

.proj-title {
  font-family: 'Bebas Neue', sans-serif;
  font-size: 36px;
  letter-spacing: 0.01em;
  line-height: 1.1;
  color: white;
  margin-bottom: auto;
}

.proj-arrow {
  margin-top: 32px;
  font-size: 20px;
  color: #c8ff00;
}
/* Page 2 — About */

#page-2 {
  background: #1a0a2e;
  color: white;
  padding: 80px;
  display: flex;
  align-items: center;
}

#page-2 .about-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 80px;
  align-items: center;
  max-width: 1000px;
  margin: 0 auto;
  width: 100%;
}

#page-2 h1 {
  font-family: 'Bebas Neue', sans-serif;
  font-size: clamp(52px, 8vw, 100px);
  line-height: 0.9;
  letter-spacing: -0.01em;
}

#page-2 h1 em {
  color: #c8ff00;
  font-style: italic;
  font-family: inherit;
}

#page-2 .about-body {
  font-size: 15px;
  line-height: 1.8;
  color: #8888aa;
  margin-bottom: 32px;
}

.skill-row {
  margin-top: 32px;
}

.skill-item {
  margin-bottom: 16px;
}

.skill-name {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 6px;
  color: #aaaacc;
}

.skill-bar {
  height: 2px;
  background: rgba(255,255,255,0.1);
  border-radius: 1px;
  overflow: hidden;
}

.skill-fill {
  height: 100%;
  background: #c8ff00;
  border-radius: 1px;
}
/* Nav */

.site-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  padding: 28px 48px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  mix-blend-mode: difference;
}

.nav-logo {
  font-family: 'Bebas Neue', sans-serif;
  font-size: 24px;
  letter-spacing: 0.05em;
  color: white;
  cursor: pointer;
}

.nav-links {
  display: flex;
  gap: 32px;
}

.nav-link {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: white;
  cursor: pointer;
  border: none;
  background: none;
  position: relative;
  padding-bottom: 4px;
  transition: opacity 0.2s;
}

.nav-link::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  width: 0%;
  height: 1px;
  background: white;
  transition: width 0.3s;
}

.nav-link:hover::after,
.nav-link.active::after {
  width: 100%;
}
/* Page indicator */

.page-indicator {
  position: fixed;
  right: 32px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: 8px;
  mix-blend-mode: difference;
}

.ind-dot {
  width: 4px;
  height: 4px;
  background: rgba(255,255,255,0.3);
  border-radius: 2px;
  transition: height 0.3s, background 0.3s;
  cursor: pointer;
  border: none;
}

.ind-dot.active {
  height: 24px;
  background: white;
}

@media (max-width: 640px) {
  #page-0,
    #page-1,
    #page-2 {
    padding: 80px 24px 40px;
  }

  #page-0 .hero-grid {
    grid-template-columns: 1fr;
  }

  .hero-side {
    display: none;
  }

  .proj-grid {
    grid-template-columns: 1fr;
  }

  #page-2 .about-grid {
    grid-template-columns: 1fr;
    gap: 32px;
  }

  .site-nav {
    padding: 20px 24px;
  }

  .nav-links {
    gap: 16px;
  }
}
const pages = document.querySelectorAll('.page');
  const navLinks = document.querySelectorAll('.nav-link');
  const indDots = document.querySelectorAll('.ind-dot');
  let current = 0;
  let transitioning = false;

  function goTo(idx) {
    if (idx === current || transitioning) return;
    transitioning = true;

    const prev = current;
    current = idx;

    // Update nav & dots
    navLinks.forEach(l => l.classList.toggle('active', +l.dataset.idx === current));
    indDots.forEach(d => d.classList.toggle('active', +d.dataset.idx === current));

    // Set the incoming page visible but clipped
    const incoming = pages[current];
    incoming.classList.add('page-incoming');
    incoming.classList.remove('reveal');
    incoming.style.zIndex = 2;

    // Trigger reflow then animate
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        incoming.classList.add('reveal');
      });
    });

    // After animation, clean up old page
    setTimeout(() => {
      pages[prev].style.zIndex = 1;
      incoming.style.zIndex = 1;

      // Reset all other pages to hidden state
      pages.forEach((p, i) => {
        if (i !== current) {
          p.classList.add('page-incoming');
          p.classList.remove('reveal');
          p.style.zIndex = '';
        } else {
          p.classList.remove('page-incoming');
          p.classList.remove('reveal');
          p.style.zIndex = '';
        }
      });

      transitioning = false;
    }, 850);
  }

  // Initialize: show first page, hide rest
  pages.forEach((p, i) => {
    if (i === 0) {
      p.classList.remove('page-incoming');
    } else {
      p.classList.add('page-incoming');
    }
  });

  // Keyboard
  document.addEventListener('keydown', e => {
    if (e.key === 'ArrowRight' || e.key === 'ArrowDown') goTo(Math.min(current + 1, pages.length - 1));
    if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') goTo(Math.max(current - 1, 0));
  });

  // Wheel
  let lastWheel = 0;
  document.addEventListener('wheel', e => {
    const now = Date.now();
    if (now - lastWheel < 900) return;
    lastWheel = now;
    if (e.deltaY > 0) goTo(Math.min(current + 1, pages.length - 1));
    if (e.deltaY < 0) goTo(Math.max(current - 1, 0));
  });
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Page Transition from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Clip-Path Wipe
Source: https://codefronts.com/motion/css-page-transitions/clip-path-wipe/

A dramatic clip-path reveal that wipes the new page into view. High-impact and memorable — best reserved for creative portfolios and brand sites.
## HTML
```html
<!-- Pages -->
<div class="page" id="page-0">
  <div class="bg-word">STUDIO</div>
  <div class="hero-grid">
    <div>
      <div class="hero-eyebrow">Creative Studio · Est. 2019</div>
      <h1 class="hero-title">WE BUILD<br><span>BOLD</span><br>FUTURES</h1>
      <p class="hero-body">A multi-disciplinary studio pushing the boundaries of brand, motion, and digital experience design.</p>
      <button class="btn-hero" onclick="goTo(1)">View Work →</button>
    </div>
    <div class="hero-side">
      <div class="hero-stat">
        <div class="hero-stat-num">142</div>
        <div class="hero-stat-label">Projects</div>
      </div>
      <div class="hero-stat">
        <div class="hero-stat-num">8yr</div>
        <div class="hero-stat-label">Experience</div>
      </div>
      <div class="hero-stat">
        <div class="hero-stat-num">4.9</div>
        <div class="hero-stat-label">Avg Rating</div>
      </div>
    </div>
  </div>
</div>

<div class="page page-incoming" id="page-1">
  <div class="page-header">
    <h1>SELECTED<br>WORK</h1>
  </div>
  <div class="proj-grid">
    <div class="proj-card"><div class="proj-num">01</div><div class="proj-cat">Brand Identity</div><div class="proj-title">Luminary<br>Financial</div><div class="proj-arrow">→</div></div>
    <div class="proj-card"><div class="proj-num">02</div><div class="proj-cat">Digital Experience</div><div class="proj-title">Atlas<br>Platform</div><div class="proj-arrow">→</div></div>
    <div class="proj-card"><div class="proj-num">03</div><div class="proj-cat">Motion & Film</div><div class="proj-title">Epoch<br>Campaign</div><div class="proj-arrow">→</div></div>
    <div class="proj-card"><div class="proj-num">04</div><div class="proj-cat">Product Design</div><div class="proj-title">Nova<br>Wearables</div><div class="proj-arrow">→</div></div>
  </div>
</div>

<div class="page page-incoming" id="page-2">
  <div class="about-grid">
    <div>
      <h1>WE ARE<br><em>FORM</em><br>STUDIO</h1>
    </div>
    <div>
      <p class="about-body">A tight team of designers, developers, and strategists who believe that great work comes from obsessing over every pixel, every word, and every interaction.</p>
      <p class="about-body">We've partnered with everyone from pre-seed startups to Fortune 500s — always bringing the same level of care.</p>
      <div class="skill-row">
        <div class="skill-item">
          <div class="skill-name"><span>Brand Design</span><span>97%</span></div>
          <div class="skill-bar"><div class="skill-fill" style="width:97%"></div></div>
        </div>
        <div class="skill-item">
          <div class="skill-name"><span>Web Development</span><span>92%</span></div>
          <div class="skill-bar"><div class="skill-fill" style="width:92%"></div></div>
        </div>
        <div class="skill-item">
          <div class="skill-name"><span>Motion Design</span><span>88%</span></div>
          <div class="skill-bar"><div class="skill-fill" style="width:88%"></div></div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- Nav -->
<nav class="site-nav">
  <div class="nav-logo">FORM</div>
  <div class="nav-links">
    <button class="nav-link active" data-idx="0" onclick="goTo(0)">Home</button>
    <button class="nav-link" data-idx="1" onclick="goTo(1)">Work</button>
    <button class="nav-link" data-idx="2" onclick="goTo(2)">About</button>
  </div>
</nav>

<!-- Page dots -->
<div class="page-indicator">
  <button class="ind-dot active" data-idx="0" onclick="goTo(0)"></button>
  <button class="ind-dot" data-idx="1" onclick="goTo(1)"></button>
  <button class="ind-dot" data-idx="2" onclick="goTo(2)"></button>
</div>
```
## CSS
```css
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

:root {
  --dur: 800ms;
  --ease: cubic-bezier(0.76, 0, 0.24, 1);
}

html,
body {
  height: 100%;
  overflow: hidden;
  font-family: 'Space Grotesk', sans-serif;
}
/* Pages */

.page {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
}
/* Base state: clipped to nothing (right side) */

.page-incoming {
  clip-path: polygon(100% 0%, 100% 0%, 100% 100%, 100% 100%);
  transition: clip-path var(--dur) var(--ease);
  pointer-events: none;
  z-index: 2;
}
/* Fully revealed */

.page-incoming.reveal {
  clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}
/* Exiting page stays visible beneath */

.page-outgoing {
  z-index: 1;
}
/* Page 0 — Hero */

#page-0 {
  background: #0e0e14;
  justify-content: flex-end;
  padding: 80px;
  color: white;
}

#page-0 .bg-word {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: 'Bebas Neue', sans-serif;
  font-size: min(25vw, 240px);
  line-height: 1;
  color: rgba(255,255,255,0.04);
  white-space: nowrap;
  pointer-events: none;
  letter-spacing: -0.02em;
}

#page-0 .hero-grid {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: end;
  gap: 40px;
  width: 100%;
  max-width: 1100px;
}

#page-0 .hero-eyebrow {
  font-size: 11px;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: #8888aa;
  margin-bottom: 24px;
}

#page-0 .hero-title {
  font-family: 'Bebas Neue', sans-serif;
  font-size: clamp(52px, 9vw, 120px);
  line-height: 0.92;
  letter-spacing: -0.01em;
  color: white;
  margin-bottom: 32px;
}

#page-0 .hero-title span {
  color: #c8ff00;
}

#page-0 .hero-body {
  font-size: 15px;
  line-height: 1.75;
  color: #8888aa;
  max-width: 420px;
  margin-bottom: 48px;
}

.btn-hero {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  background: #c8ff00;
  color: #0e0e14;
  border: none;
  padding: 18px 36px;
  font-family: 'Space Grotesk', sans-serif;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  border-radius: 2px;
  transition: transform 0.2s, background 0.2s;
}

.btn-hero:hover {
  background: #d4ff33;
  transform: translateX(3px);
}

.hero-side {
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: flex-end;
}

.hero-stat {
  text-align: right;
  border-right: 2px solid #c8ff00;
  padding-right: 20px;
}

.hero-stat-num {
  font-family: 'Bebas Neue', sans-serif;
  font-size: 40px;
  color: white;
  line-height: 1;
}

.hero-stat-label {
  font-size: 10px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: #555570;
}
/* Page 1 — Projects */

#page-1 {
  background: #c8ff00;
  overflow-y: auto;
  padding: 80px;
  color: #0e0e14;
}

#page-1 .page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 64px;
}

#page-1 h1 {
  font-family: 'Bebas Neue', sans-serif;
  font-size: clamp(52px, 9vw, 112px);
  line-height: 0.9;
  letter-spacing: -0.01em;
  color: #0e0e14;
}

.proj-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 3px;
}

.proj-card {
  background: #0e0e14;
  padding: 36px;
  color: white;
  display: flex;
  flex-direction: column;
  min-height: 220px;
  position: relative;
  overflow: hidden;
  cursor: pointer;
  transition: background 0.3s;
}

.proj-card:nth-child(even) {
  background: #1a1a24;
}

.proj-card:hover {
  background: #2a2a3a;
}

.proj-num {
  font-family: 'Bebas Neue', sans-serif;
  font-size: 64px;
  color: rgba(200,255,0,0.15);
  line-height: 1;
  position: absolute;
  top: 16px;
  right: 24px;
  letter-spacing: -0.04em;
}

.proj-cat {
  font-size: 9px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #555570;
  margin-bottom: 12px;
}

.proj-title {
  font-family: 'Bebas Neue', sans-serif;
  font-size: 36px;
  letter-spacing: 0.01em;
  line-height: 1.1;
  color: white;
  margin-bottom: auto;
}

.proj-arrow {
  margin-top: 32px;
  font-size: 20px;
  color: #c8ff00;
}
/* Page 2 — About */

#page-2 {
  background: #1a0a2e;
  color: white;
  padding: 80px;
  display: flex;
  align-items: center;
}

#page-2 .about-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 80px;
  align-items: center;
  max-width: 1000px;
  margin: 0 auto;
  width: 100%;
}

#page-2 h1 {
  font-family: 'Bebas Neue', sans-serif;
  font-size: clamp(52px, 8vw, 100px);
  line-height: 0.9;
  letter-spacing: -0.01em;
}

#page-2 h1 em {
  color: #c8ff00;
  font-style: italic;
  font-family: inherit;
}

#page-2 .about-body {
  font-size: 15px;
  line-height: 1.8;
  color: #8888aa;
  margin-bottom: 32px;
}

.skill-row {
  margin-top: 32px;
}

.skill-item {
  margin-bottom: 16px;
}

.skill-name {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 6px;
  color: #aaaacc;
}

.skill-bar {
  height: 2px;
  background: rgba(255,255,255,0.1);
  border-radius: 1px;
  overflow: hidden;
}

.skill-fill {
  height: 100%;
  background: #c8ff00;
  border-radius: 1px;
}
/* Nav */

.site-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  padding: 28px 48px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  mix-blend-mode: difference;
}

.nav-logo {
  font-family: 'Bebas Neue', sans-serif;
  font-size: 24px;
  letter-spacing: 0.05em;
  color: white;
  cursor: pointer;
}

.nav-links {
  display: flex;
  gap: 32px;
}

.nav-link {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: white;
  cursor: pointer;
  border: none;
  background: none;
  position: relative;
  padding-bottom: 4px;
  transition: opacity 0.2s;
}

.nav-link::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  width: 0%;
  height: 1px;
  background: white;
  transition: width 0.3s;
}

.nav-link:hover::after,
.nav-link.active::after {
  width: 100%;
}
/* Page indicator */

.page-indicator {
  position: fixed;
  right: 32px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: 8px;
  mix-blend-mode: difference;
}

.ind-dot {
  width: 4px;
  height: 4px;
  background: rgba(255,255,255,0.3);
  border-radius: 2px;
  transition: height 0.3s, background 0.3s;
  cursor: pointer;
  border: none;
}

.ind-dot.active {
  height: 24px;
  background: white;
}

@media (max-width: 640px) {
  #page-0,
    #page-1,
    #page-2 {
    padding: 80px 24px 40px;
  }

  #page-0 .hero-grid {
    grid-template-columns: 1fr;
  }

  .hero-side {
    display: none;
  }

  .proj-grid {
    grid-template-columns: 1fr;
  }

  #page-2 .about-grid {
    grid-template-columns: 1fr;
    gap: 32px;
  }

  .site-nav {
    padding: 20px 24px;
  }

  .nav-links {
    gap: 16px;
  }
}
```

## JavaScript
```js
const pages = document.querySelectorAll('.page');
  const navLinks = document.querySelectorAll('.nav-link');
  const indDots = document.querySelectorAll('.ind-dot');
  let current = 0;
  let transitioning = false;

  function goTo(idx) {
    if (idx === current || transitioning) return;
    transitioning = true;

    const prev = current;
    current = idx;

    // Update nav & dots
    navLinks.forEach(l => l.classList.toggle('active', +l.dataset.idx === current));
    indDots.forEach(d => d.classList.toggle('active', +d.dataset.idx === current));

    // Set the incoming page visible but clipped
    const incoming = pages[current];
    incoming.classList.add('page-incoming');
    incoming.classList.remove('reveal');
    incoming.style.zIndex = 2;

    // Trigger reflow then animate
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        incoming.classList.add('reveal');
      });
    });

    // After animation, clean up old page
    setTimeout(() => {
      pages[prev].style.zIndex = 1;
      incoming.style.zIndex = 1;

      // Reset all other pages to hidden state
      pages.forEach((p, i) => {
        if (i !== current) {
          p.classList.add('page-incoming');
          p.classList.remove('reveal');
          p.style.zIndex = '';
        } else {
          p.classList.remove('page-incoming');
          p.classList.remove('reveal');
          p.style.zIndex = '';
        }
      });

      transitioning = false;
    }, 850);
  }

  // Initialize: show first page, hide rest
  pages.forEach((p, i) => {
    if (i === 0) {
      p.classList.remove('page-incoming');
    } else {
      p.classList.add('page-incoming');
    }
  });

  // Keyboard
  document.addEventListener('keydown', e => {
    if (e.key === 'ArrowRight' || e.key === 'ArrowDown') goTo(Math.min(current + 1, pages.length - 1));
    if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') goTo(Math.max(current - 1, 0));
  });

  // Wheel
  let lastWheel = 0;
  document.addEventListener('wheel', e => {
    const now = Date.now();
    if (now - lastWheel < 900) return;
    lastWheel = now;
    if (e.deltaY > 0) goTo(Math.min(current + 1, pages.length - 1));
    if (e.deltaY < 0) goTo(Math.max(current - 1, 0));
  });
```

Techniques used in this demo

Search CodeFronts

Loading…