← Mike Higgins Home
Custom Code Experiment •

Domination

Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  <title>Perspective · Creative Loop</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }

    body {
      display: grid;
      place-items: center;
      min-height: 100vh;
      background: black;
      font-family: system-ui, 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif;
      overflow-x: hidden;
    }

    .container {
      display: grid;
      grid-template-columns: 1fr;
      grid-template-rows: 1fr;
      width: 100%;
      max-width: 1200px;
      padding: 1rem;
      perspective: 800px;
    }

    h1 {
      grid-row: 1;
      grid-column: 1;
      font-size: clamp(2.8rem, 12vw, 6rem);
      font-weight: 800;
      letter-spacing: -0.02em;
      color: hsl(0 0% 98% / 0.2);
      text-shadow: 0 0 1px rgba(255,255,255,0.3);
      position: relative;
      text-align: center;
      margin: 0;
      line-height: 1.2;
      backface-visibility: hidden;
      will-change: transform, opacity;
    }

    h1:after {
      content: attr(data-word);
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 100%;
      text-align: center;
      background: linear-gradient(135deg, #fff 0%, #e0e0e0 100%);
      -webkit-background-clip: text;
      background-clip: text;
      color: transparent;
      font-weight: 800;
      letter-spacing: -0.02em;
      clip-path: inset(var(--t, 0%) 0 0 var(--l, 100%));
      pointer-events: none;
    }

    @supports not (background-clip: text) {
      h1:after { background: none; color: hsl(0 0% 98%); }
    }

    #domination .glitch-clone {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 100%;
      text-align: center;
      font-size: inherit;
      font-weight: 800;
      letter-spacing: -0.02em;
      opacity: 0;
      pointer-events: none;
      white-space: nowrap;
    }
    #domination .glitch-clone.red  { color: #ff003c; mix-blend-mode: screen; }
    #domination .glitch-clone.blue { color: #00e5ff; mix-blend-mode: screen; }
  </style>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
</head>
<body>
<div class="container">
  <h1 data-word="Madness">Madness</h1>
  <h1 data-word="Power">Power</h1>
  <h1 data-word="Domination" id="domination">Domination</h1>
</div>

<script>
(function () {
  const words = Array.from(document.querySelectorAll('.container h1'));
  const total = words.length;
  let current = 0;

  const domEl = document.getElementById('domination');
  domEl.insertAdjacentHTML('beforeend',
    `<span class="glitch-clone red"  aria-hidden="true">Domination</span>` +
    `<span class="glitch-clone blue" aria-hidden="true">Domination</span>`
  );
  const redClone  = domEl.querySelector('.glitch-clone.red');
  const blueClone = domEl.querySelector('.glitch-clone.blue');

  gsap.set(words, {
    xPercent: 100,
    transformOrigin: '120% 100%',
    opacity: 0,
    rotate: 0,
    '--t': '0%',
    '--l': '100%'
  });
  gsap.set([redClone, blueClone], { opacity: 0 });

  function glitchEffect() {
    const tl = gsap.timeline();
    const hits = 10;

    for (let i = 0; i < hits; i++) {
      const t = (i / hits) * 1.6;
      const rx = (Math.random() - 0.5) * 18;
      const bx = (Math.random() - 0.5) * 18;
      const ry = (Math.random() - 0.5) * 6;
      const by = (Math.random() - 0.5) * 6;
      const dur = 0.04 + Math.random() * 0.06;

      tl.to(redClone, { x: rx, y: ry, opacity: 0.85, duration: dur, ease: 'none' }, t)
        .to(redClone, { x: 0,  y: 0,  opacity: 0,    duration: dur, ease: 'none' }, t + dur);

      tl.to(blueClone, { x: -rx, y: -by, opacity: 0.85, duration: dur, ease: 'none' }, t + 0.01)
        .to(blueClone, { x: 0,   y: 0,   opacity: 0,    duration: dur, ease: 'none' }, t + dur + 0.01);

      tl.to(domEl, { skewX: (Math.random() - 0.5) * 8, duration: dur, ease: 'none' }, t)
        .to(domEl, { skewX: 0, duration: dur * 0.5, ease: 'none' }, t + dur);
    }

    tl.set([redClone, blueClone], { x: 0, y: 0, opacity: 0 });
    tl.set(domEl, { skewX: 0 });
    tl.to({}, { duration: 0.4 });
    return tl;
  }

  function animateNext() {
    const incoming = words[current % total];
    const outgoing = words[(current - 1 + total) % total];
    const isDomination = incoming === domEl;

    const tl = gsap.timeline({ onComplete: animateNext });

    tl.set(incoming, {
      xPercent: 100,
      rotate: 0,
      opacity: 0,
      '--t': '100%',
      '--l': '0%'
    });

    if (isDomination) {
      tl.set([redClone, blueClone], { x: 0, y: 0, opacity: 0 });
      tl.set(domEl, { skewX: 0 });
    }

    tl.to(incoming, {
      xPercent: 0,
      opacity: 1,
      duration: 1,
      ease: 'back.out(0.6)'
    }, '+=0.1');

    tl.to(outgoing, {
      xPercent: -100,
      rotate: 75,
      duration: 1,
      ease: 'power2.in'
    }, '<');

    tl.to(incoming, {
      '--t': '0%',
      duration: 1,
      ease: 'sine.inOut'
    }, '<+0.1');

    tl.to(outgoing, {
      '--l': '100%',
      duration: 1,
      ease: 'sine.inOut'
    }, '<');

    tl.to(outgoing, {
      opacity: 0,
      duration: 0.9,
      ease: 'power2.in'
    }, '<+0.1');

    tl.set(outgoing, {
      xPercent: 100,
      rotate: 0,
      opacity: 0,
      '--t': '0%',
      '--l': '100%'
    });

    if (isDomination) {
      tl.add(glitchEffect());
    } else {
      tl.to({}, { duration: 1.0 });
    }

    current++;
  }

  animateNext();

  window.addEventListener('resize', () => {
    gsap.set(words, { transformOrigin: '120% 100%' });
  });
})();
</script>
</body>
</html>