<div id="app">
  <div id="overlay">
    <button class="replaybutton">Replay <svg class="replayicon" height="20" width="20" fill="#fff" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
  <path d="m 8.0017778,1037.4886 0,1.7441 c -2.79721,0 -5.23232,1.9407 -5.85352,4.668 -0.6212,2.7274 0.73261,5.5328 3.25391,6.7442 2.5213,1.2113 5.5558192,0.5155 7.2968692,-1.6739 1.74105,-2.1893 1.73629,-5.3045 -0.0117,-7.4883 l -1.17187,0.9375 c 1.31573,1.6438 1.32022,3.9693 0.01,5.6172 -1.31049,1.6479 -3.5768192,2.1677 -5.4746092,1.2559 -1.89779,-0.9118 -2.90703,-3.0057 -2.43945,-5.0586 0.46757,-2.0529 2.28516,-3.502 4.39062,-3.502 l 0,1.7989 2.9999992,-2.5215 z" color="#000" font-weight="400" font-family="sans-serif" overflow="visible" transform="translate(0 -1036.362)"/>
  <style>
  </style>
</svg></button>
  </div>
  
  <div class="explainer">
    <h2 class="explain1">We start with an object</h2>
    <h2 class="explain2 visually-hidden">We add a handler and proxy the object</h2>
    <h2 class="explain3 visually-hidden">In this case it's the same</h2>
    <h2 class="explain4 visually-hidden">But look! We can intercept the object with this proxy</h2>
    <h2 class="explain6 visually-hidden">But here's the cool part: if we change the first object...</h2>
    <h2 class="explain7 visually-hidden">The proxied object updates accordingly, without adjusting the handler!</h2>
    <h2 class="explain8 visually-hidden">With reactivity we can respond to changes instantly! 🎉</h2>
  </div>
  <main>
    <section>
      <p class="code code1">target: {<br>
<span class="string sp">width</span>: <span class="cube1prop1">200</span>,<br>
<span class="string sp">background</span>: 'white'<br>
}</p>
      <div class="scene">
        <div class="show-front cube cube1">
          <div class="face front">front</div>
          <div class="face back">back</div>
          <div class="face right">right</div>
          <div class="face left">left</div>
          <div class="face top">top</div>
          <div class="face bottom">bottom</div>
        </div>
      </div>
    </section>

    <section class="handler">
      <p class="code code2"><span class="keyword">const</span> handler = {<br>
<span class="sp"></span><span class="keyword">get</span>(target, objectKey) {<br>
<span class="visually-hidden ifblock">
<span class="sp"></span><span class="sp"></span><span class="keyword">if</span> (objectKey === <span class="string">'background'</span>) {<br>
<span class="sp"></span><span class="sp"></span><span class="sp"></span><span class="keyword">return</span> <span class="string">'green'</span><br>
<span class="sp"></span><span class="sp"></span>}<br>
</span><!--visuallyhidden ifblock-->
<span class="sp"></span><span class="sp"></span><span class="keyword">return</span> target[objectKey]<br>
<span class="sp"></span>}<br>
}<br>
<br>
<span class="keyword">const</span> proxiedObj = <span class="keyword">new Proxy</span>(target, handler)
</p>
</section>

    <section>
      <p class="code code3">proxiedObj: {<br>
        <span class="string sp">width</span>: <span class="cube2prop1">200</span>,<br>
        <span class="string sp">background</span>: '<span class="cube2prop2">white</span>'<br>
}</p>
      <div class="scene">
        <div class="show-front cube cube2">
          <div class="face front">front</div>
          <div class="face back">back</div>
          <div class="face right">right</div>
          <div class="face left">left</div>
          <div class="face top">top</div>
          <div class="face bottom">bottom</div>
        </div>
      </div>
    </section>
  </main>
</div>
@function strip-unit($value) {
  @return $value / ($value * 0 + 1);
}

@mixin fluid-type($min-vw, $max-vw, $min-font-size, $max-font-size) {
  $u1: unit($min-vw);
  $u2: unit($max-vw);
  $u3: unit($min-font-size);
  $u4: unit($max-font-size);

  @if $u1 == $u2 and $u1 == $u3 and $u1 == $u4 {
    & {
      font-size: $min-font-size;
      @media screen and (min-width: $min-vw) {
        font-size: calc(#{$min-font-size} + #{strip-unit($max-font-size - $min-font-size)} * ((100vw - #{$min-vw}) / #{strip-unit($max-vw - $min-vw)}));
      }
      @media screen and (min-width: $max-vw) {
        font-size: $max-font-size;
      }
    }
  }
}

#app {
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
}

main {
  margin: 0 0 0 20px;
  width: 96%;
  height: 400px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 1fr;
  grid-column-gap: 40px;
}

h2 {
  color: #273849;
  text-align: center;
  margin-top: 40px;
  @include fluid-type(300px, 1200px, 14px, 28px);
}

section {
  padding: 0;
}

* { box-sizing: border-box; }

body { 
  font-family: 'Source Sans Pro', sans-serif;
  color: #4f5959;
}

#overlay {
  opacity: 0;
  position: fixed; 
  width: 100%; 
  height: 100%; 
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(255, 255, 255, 0.9); 
  z-index: 1000; 
}

.replaybutton {
  display: table;
  border: none;
  padding: 0.7rem 1.5rem 0.9rem;
  margin: 100px auto;
  border-radius: 4px;
  text-decoration: none;
  background: rgb(66, 185, 131);
  color: #ffffff;
  font-family: 'Source Sans Pro', sans-serif;
  font-size: 1.2rem;
  cursor: pointer;
  text-align: center;
  transition: background 250ms ease-in-out, 
              transform 150ms ease;
  -webkit-appearance: none;
  -moz-appearance: none;
}

.replaybutton:hover,
.replaybutton:focus {
    background: #36996c;
}

.replaybutton:focus {
    outline: 1px solid #fff;
    outline-offset: -4px;
}

button:active {
    transform: scale(0.99);
}

.replayicon {
  margin: 0 0 -4px 2px;
}

$bwidth: 200px;
$bheight: 100px;

.scene {
  width: $bwidth;
  height: $bwidth;
  margin: 15px;
  perspective: 400px;
}

.cube {
  width: $bwidth;
  height: $bwidth;
  position: relative;
  transform-style: preserve-3d;
  transform: translateZ(-$bheight);
  transition: transform 1s ease;
}

.cube.show-front  { transform: translateZ(-$bheight) rotateY(   0deg); }
.cube.show-right  { transform: translateZ(-$bheight) rotateY( -90deg); }
.cube.show-back   { transform: translateZ(-$bheight) rotateY(-180deg); }
.cube.show-left   { transform: translateZ(-$bheight) rotateY(  90deg); }
.cube.show-top    { transform: translateZ(-$bheight) rotateX( -90deg); }
.cube.show-bottom { transform: translateZ(-$bheight) rotateX(  90deg); }

.face {
  font-family: 'Roboto Mono', monospace;
  position: absolute;
  width: $bwidth;
  height: $bwidth;
  border: 2px dotted #4f5959;
  line-height: 200px;
  font-size: 40px;
  font-weight: bold;
  color: rgba(66, 185, 131, 0.68);
  text-align: center;
  background-color: rgba(255, 255, 255, 0.75);
}

.front  { transform: rotateY(  0deg) translateZ($bheight); }
.right  { transform: rotateY( 90deg) translateZ($bheight); }
.back   { transform: rotateY(180deg) translateZ($bheight); }
.left   { transform: rotateY(-90deg) translateZ($bheight); }
.top    { transform: rotateX( 90deg) translateZ($bheight); }
.bottom { transform: rotateX(-90deg) translateZ($bheight); }

label { margin-right: 10px; }

.code, pre, code {
  font-family: 'Roboto Mono', monospace;
  @include fluid-type(500px, 1200px, 9px, 13px);
}

.handler {
  margin-top: 70px;
  padding: 0;
}

.string {
  color: #42b983;
}

.keyword {
  color: #d63200;
}

.sp {
  margin-left: 20px;
}

.explainer {
  height: 50px;
}

.explainer, .code1, .code2, .code3, .cube1, .cube2 {
  visibility: hidden;
}

.visually-hidden { 
  position: absolute !important;
  height: 1px; 
  width: 1px;
  overflow: hidden;
  clip: rect(1px, 1px, 1px, 1px);
  white-space: nowrap;
}
View Compiled
gsap.set(".explainer, .code1, .code2, .code3, .cube1, .cube2", {
  visibility: "visible"
});

//------------------//
//  main animation  //
//------------------//

const code1 = new SplitText(".code1", { type: "chars" });
const code3 = new SplitText(".code3", { type: "chars" });
const explainText = new SplitText(".explainer", { type: "chars" });

gsap.set(".explainer h2 div", {
  opacity: 0,
  scaleX: 0
});

gsap.set(".code1 div, .code2, .code3 div", {
  opacity: 0
});

gsap.set(".face", {
  opacity: 0,
  scale: 0.5,
  transformOrigin: "50% 50%"
});

const mainElements = () => {
  gsap
    .timeline({
      delay: 0.5
    })
    .to(".explain1 div", {
      opacity: 1,
      scaleX: 1,
      duration: 0.4,
      delay: 0.5,
      stagger: 0.03,
      ease: "sine"
    })
    .to(code1.chars, {
      opacity: 1,
      ease: "power4",
      duration: 0.2,
      stagger: 0.03
    })
    .to(".cube1 .face", {
      opacity: 1,
      ease: "sine",
      duration: 0.5,
      scale: 1,
      stagger: -0.05
    })
    .to(".explain1 div", {
      opacity: 0,
      scaleX: 0,
      duration: 0.1,
      stagger: 0.02,
      ease: "sine.in"
    })
    .call(hideShow, [".explain1", ".explain2"])
    .to(".explain2 div", {
      opacity: 1,
      scaleX: 1,
      duration: 0.4,
      delay: 0.4,
      stagger: 0.02,
      ease: "sine"
    })
    .to(".code2", {
      opacity: 1,
      ease: "sine",
      duration: 0.6
    })
    .to(".explain2 div", {
      opacity: 0,
      scaleX: 0,
      delay: 1,
      duration: 0.1,
      stagger: 0.02,
      ease: "sine.in"
    })
    .call(hideShow, [".explain2", ".explain3"])
    .to(".explain3 div", {
      opacity: 1,
      scaleX: 1,
      duration: 0.4,
      delay: 1.5,
      stagger: 0.02,
      ease: "sine"
    })
    .to(code3.chars, {
      opacity: 1,
      ease: "power4",
      duration: 0.2,
      stagger: 0.03
    })
    .to(".cube2 .face", {
      opacity: 1,
      ease: "sine",
      duration: 0.5,
      scale: 1,
      stagger: -0.05
    })
    .to(".explain3 div", {
      opacity: 0,
      scaleX: 0,
      delay: 0.3,
      duration: 0.1,
      stagger: 0.02,
      ease: "sine.in"
    })
    .call(hideShow, [".explain3", ".explain4"])
    .to(".explain4 div", {
      opacity: 1,
      scaleX: 1,
      delay: 1,
      duration: 0.4,
      stagger: 0.02,
      ease: "sine"
    })
    .call(showElement, [".ifblock"])
    // explainer
    .to(".ifblock", {
      background: "#eee",
      ease: "sine",
      duration: 0.4
    })
    .call(updateText, [".cube2prop2", "green"])
    .add("updateP1")
    .to(
      ".ifblock",
      {
        background: "none",
        ease: "sine.easeIn",
        duration: 0.3
      },
      "updateP1"
    )
    .to(".explain4 div", {
      opacity: 0,
      scaleX: 0,
      delay: 1,
      duration: 0.1,
      stagger: 0.02,
      ease: "sine.in"
    })
    .call(hideShow, [".explain4", ".explain6"])
    .to(
      ".cube2 .face",
      {
        backgroundColor: "rgba(66, 185, 131, 0.68)",
        border: "2px solid #4f5959",
        ease: "sine",
        duration: 1.5
      },
      "updateP1"
    )
    .to(
      ".cube2 .face",
      {
        color: "rgba(255, 255, 255, 1)",
        ease: "sine",
        duration: 1
      },
      "updateP1+=0.25"
    );
};

//-------------------//
//  final animation  //
//-------------------//

const finalAnimation = () => {
  gsap
    .timeline({ delay: 18 })
    .to(".explain6 div", {
      opacity: 1,
      scaleX: 1,
      duration: 0.2,
      delay: 1,
      stagger: 0.01,
      ease: "sine"
    })
    .call(updateText, [".cube1prop1", "250"])
    .call(updateText, [".cube2prop1", "250"])
    .add("finalstart")
    .to(".cube1prop1, .cube2prop1", {
      background: "#eee",
      ease: "sine",
      duration: 0.4
    })
    .to(".cube1prop1, .cube2prop1", {
      background: "none",
      ease: "sine.easeIn",
      duration: 0.3
    })
    .to(
      ".front, .back, .top, .bottom",
      {
        width: 250,
        ease: "sine",
        duration: 0.3
      },
      "finalstart+=0.1"
    )
    .to(
      ".right",
      {
        x: 150,
        ease: "sine",
        duration: 0.3
      },
      "finalstart+=0.1"
    )
    .to(".explain6 div", {
      opacity: 0,
      scaleX: 0,
      delay: 1,
      duration: 0.1,
      stagger: 0.01,
      ease: "sine.in"
    })
    .call(hideShow, [".explain6", ".explain7"])
    .to(".explain7 div", {
      opacity: 1,
      scaleX: 1,
      duration: 0.2,
      stagger: 0.01,
      ease: "sine"
    })
    .to(".explain7 div", {
      opacity: 0,
      scaleX: 0,
      delay: 3,
      duration: 0.1,
      stagger: 0.01,
      ease: "sine.in"
    })
    .call(hideShow, [".explain7", ".explain8"])
    .add("finalspin", "+=0.5")
    .to(
      ".explain8 div",
      {
        opacity: 1,
        scaleX: 1,
        duration: 0.2,
        stagger: 0.01,
        ease: "sine"
      },
      "finalspin"
    )
    .to(
      ".cube",
      {
        rotationY: 360,
        ease: "power4.in",
        duration: 1.25
      },
      "finalspin"
    )
    .to("#overlay", {
      delay: 3,
      opacity: 1,
      duration: 0.25
    })
    .to(".replayicon", {
      rotation: 720,
      duration: 1.5,
      ease: "back"
    });
};

window.onload = () => {
  mainElements();
  finalAnimation();
};

//-------------//
//  helpers    //
//-------------//

const hideShow = (el1, el2) => {
  let elref1 = document.querySelector(el1);
  elref1.classList.add("visually-hidden");

  let elref2 = document.querySelector(el2);
  elref2.classList.remove("visually-hidden");
};

const showElement = (el) => {
  let elref = document.querySelector(el);
  elref.classList.remove("visually-hidden");
};

const updateText = (el, text) => {
  let elref = document.querySelector(el);
  elref.innerHTML = text;
};

const button = document.querySelector(".replaybutton");
button.addEventListener("click", () => {
  location.reload();
});
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/gsap-latest-beta.min.js
  2. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/SplitText3.min.js