JSFiddle - React, Tailwind, and code Playground

by soulwire

HTML

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
<div class="container">
  <div class="text"/>
</div>

CSS

:root {
  --color-1: #3b4c55;
  --color-2: #b5d5bc;
  --color-3: #f3f3f3;
  --color-4: #ef6486;
}

html, body {
  font-family: "IBM Plex Mono", monospace;
  font-weight: 400;
  font-style: normal;
  background: #111;
  color: #fff;
}

.container {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}

.text {
  max-width: 400px;
  text-align: left;
}

JavaScript

const container = document.querySelector('.container');
const text = document.querySelector('.text');

const gridSize = 24;

"Keep your face always toward sunshine, and shadows will fall behind you.".split('').map(char => {
  const el = document.createElement('span');
  el.classList.add('char')
  el.innerText = char;
  text.appendChild(el);
})

const chars = [];
document.querySelectorAll('.char').forEach(el => {
  const bounds = el.getBoundingClientRect();
  chars.push({
    el: el,
    value: el.innerText,
    target: bounds,
    path: createWanderPath(),
    index: 0,
    done: false,
  });
});

function createWanderPath_() {
  const path = [{ x: 0, y: 0 }];
  let x = 0;
  let y = 0;
  let a = Math.random() * Math.PI;
  let steps = 100 + Math.random() * 400;
  for (let i = 0; i < steps; i++) {
    const d = Math.random() * 3;
    a += (Math.random() - 0.5) * Math.PI * 0.2;
    x += Math.cos(a) * d;
    y += Math.sin(a) * d;
    path.push({
      x: clamp(x, gridSize),
      y: clamp(y, gridSize),
      char: getRandomChar(),
      // x,
      // y,
    })
  }
  return path.reverse();
}

function createWanderPath() {
  const path = [{ x: 0, y: 0 }];
  let x = 0;
  let y = 0;
  let char;
  let steps = 20 + Math.random() * 300;
  for (let i = 0; i < steps; i++) {
    if (Math.random() < Math.random() * 2) {
      path.push({ x, y, char })
      continue;
    }
    const mx = Math.floor(Math.random() * 3) - 1;
    const my = Math.floor(Math.random() * 3) - 1;
    char = getRandomChar(),
    path.push({
      x: x += mx * gridSize * Math.random(),
      y: y += my * gridSize * Math.random(),
      char,
      // x,
      // y,
    })
  }
  return path.reverse();
}

function clamp(n, g) {
  return Math.floor(n / g) * g;
}

chars.forEach(({ el, target }) => {
  el.style.position = 'absolute';
  el.style.left = target.left + 'px';
  el.style.top = target.top + 'px';
})

function update()...