<canvas id="Canvas"></canvas>

  <script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/gsap-latest-beta.min.js"></script>
  <script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/EasePack3.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
* {
  margin: 0;
  padding: 0;
}

html {
  background-color: #245ded;
  font-family: 'Muli', sans-serif;
  color: #fff;
}

 
#Canvas {
  display: block;
  position: absolute;
  top: calc(50% - 270px);
  left: calc(50% - 480px);
  width: 960px;
  height: 540px;
  z-index: -1;
/*   border: 1px solid #f00; */
}

 
// load webfont from Google Fonts
WebFont.load({
  google: {
    families: ['Muli:600'] },

  classes: false,
  active: () => {
    createCanvas();
    updateCanvas();
  } });


// vertex shader
const vertexShader = `
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform float time;
uniform float index;
uniform float divisions;
uniform float tOffsetX;
uniform vec4 tween;
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUv;

mat2 scale2D(vec2 scale) {
return mat2(scale.x, 0.0, 0.0, scale.y);
}

void main() {
vUv = uv;
vec3 pos = position;

float scaleX = tween.x;
pos.x += tOffsetX;
pos.xy *= scale2D(vec2(scaleX, 1.0));
pos.x -= tOffsetX;

gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`;

// fragment shader
const fragmentShader = `
precision highp float;

uniform sampler2D texture;
uniform float time;
uniform float index;
uniform float divisions;
uniform vec4 tween;
varying vec2 vUv;

void main() {
vec2 uv = vUv;
float startV = index * (1.0 / divisions);
vec2 tuv = vec2(uv.x, startV + uv.y * (1.0 / divisions));

vec4 color = texture2D(texture, tuv);

gl_FragColor = color;
//gl_FragColor = vec4(vec3(tuv.y), 1.0);
}
`;

// general settings
const width = 960;
const height = 540;
const aspectRatio = width / height;
const dpr = Math.min(window.devicePixelRatio, 2);
const sceneWidth = 2;
const sceneHeight = sceneWidth / aspectRatio;
let scene, camera, renderer;

// title settings
const titleWidth = 2.0;
const titleHeight = 0.5;
const numDivisions = 40;
const titleMeshes = [];
let numMeshes = 0;

// animation settings
const tweens1 = []; // array of tweens for title1
const tweens2 = []; // array of tweens for title2
const tweens3 = []; // array of tweens for title3

function createCanvas() {
  scene = new THREE.Scene();

  camera = new THREE.OrthographicCamera(
  -sceneWidth / 2,
  sceneWidth / 2,
  sceneHeight / 2,
  -sceneHeight / 2,
  0.01,
  1000);

  camera.position.set(0, 0, 1);

  renderer = new THREE.WebGLRenderer({
    antialias: true,
    alpha: true,
    canvas: document.getElementById('Canvas') });

  renderer.setSize(width, height);
  renderer.setPixelRatio(dpr);

  const texture1 = createFontTexture({
    width: 1024,
    height: 256,
    fontSize: 230,
    text: 'EFFECT' });


  const texture2 = createFontTexture({
    width: 1024,
    height: 256,
    fontSize: 230,
    text: 'TEXT' });


  const texture3 = createFontTexture({
    width: 1224,
    height: 256,
    fontSize: 230,
    text: 'PROFIDEV' });


  createTitle({
    texture: texture1,
    tweens: tweens1 });


  createTitle({
    texture: texture2,
    tweens: tweens2 });


  createTitle({
    texture: texture3,
    tweens: tweens3 });


  numMeshes = titleMeshes.length;

  animateTitle();
}

function createFontTexture(options) {
  const canvas = document.createElement('canvas');
  const width = options.width * dpr;
  const height = options.height * dpr;
  canvas.width = width;
  canvas.height = height;

  const ctx = canvas.getContext('2d');

  // ctx.fillStyle = 'rgba(255, 0, 0, 0.1)'
  // ctx.fillRect(0, 0, width, height)

  ctx.font = `600 ${options.fontSize * dpr}px 'Muli'`;
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillStyle = '#fff';
  ctx.fillText(options.text, width / 2, height / 2 + 25);

  const texture = new THREE.CanvasTexture(canvas);
  texture.minFilter = THREE.LinearFilter;
  texture.magFilter = THREE.LinearFilter;
  texture.format = THREE.RGBAFormat;
  texture.needsUpdate = false;

  return texture;
}

function createTitle(params) {
  for (let i = 0; i < numDivisions; i++) {
    const dividedHeight = titleHeight / numDivisions;
    const geometry = new THREE.PlaneBufferGeometry(titleWidth, dividedHeight);
    geometry.removeAttribute('normal');

    const material = new THREE.RawShaderMaterial({
      uniforms: {
        texture: { value: params.texture },
        time: { value: 0 },
        index: { value: i },
        divisions: { value: numDivisions },
        tOffsetX: { value: -1.0 },
        tween: { value: new THREE.Vector4(0, 0, 1, 1) } },

      vertexShader,
      fragmentShader,
      transparent: true });


    const y = -titleHeight * 0.5 + i * dividedHeight + dividedHeight * 0.5;

    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.y = y;

    scene.add(mesh);
    titleMeshes.push(mesh);
    params.tweens.push(material.uniforms.tween.value);
  }
}

function animateTitle() {
  const tl = gsap.timeline({
    repeat: -1,
    defaults: {
      ease: "power3.inOut",
      duration: 0.8,
      stagger: 0.01 } });



  const setTransformOffset = (titleIndex, value) => {
    const startIndex = titleIndex * numDivisions;
    const endIndex = startIndex + numDivisions;
    for (let i = startIndex; i < endIndex; i++) {
      titleMeshes[i].material.uniforms.tOffsetX.value = value;
    }
  };

  tl.to(tweens1, {
    x: 1.0,
    onStart: () => {
      setTransformOffset(0, -1.0);
    } },
  'start');
  tl.to(tweens1, {
    x: 0.0,
    onStart: () => {
      setTransformOffset(0, 1.0);
    } },
  'next1');

  tl.to(tweens2, {
    x: 1.0,
    onStart: () => {
      setTransformOffset(1, -1.0);
    } },
  'next1');
  tl.to(tweens2, {
    x: 0.0,
    onStart: () => {
      setTransformOffset(1, 1.0);
    } },
  'next2');

  tl.to(tweens3, {
    x: 1.0,
    onStart: () => {
      setTransformOffset(2, -1.0);
    } },
  'next2');
  tl.to(tweens3, {
    x: 0.0,
    onStart: () => {
      setTransformOffset(2, 1.0);
    } });

}

function updateCanvas() {
  requestAnimationFrame(updateCanvas);

  const time = performance.now() * 0.001;
  for (let i = 0; i < numMeshes; i++) {
    const mesh = titleMeshes[i];
    mesh.material.uniforms.time.value = time;
  }

  renderer.render(scene, camera);
}