.gitattributes README.md
<style>
@import url('https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@900&family=Rokkitt:wght@900&display=swap');
.text1 {
  position: absolute;
  top: 3vh;
  left: calc(50% - 50vh);
}
.text2 {
  position: absolute;
  bottom: 4vh;
  left: 50%;
}
.retro {
  font-family: "Roboto Slab";
  font-size: 13vh;
  display: block;
  color: #000;
  text-shadow: -0.5vh 0 #8800aa, 0 0.5vh #8800aa, 0.5vh 0 #aa0088, 0 -0.5vh #aa0088;
}
</style>

<div class="text1">
  <span class="retro">RETRO</span>
</div>
<div class="text2">
  <span class="retro">WAVE</span>
</div>

<script type="module">
import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.123.0/build/three.module.js";
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/three@0.123.0/examples/jsm/controls/OrbitControls.js";
import { TWEEN } from "https://cdn.jsdelivr.net/npm/three@0.123.0/examples/jsm/libs/tween.module.min.js";

let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(-5, 10, 20);
let renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(innerWidth, innerHeight);
document.querySelector("div.prose").appendChild(renderer.domElement);

const textureCube = generateCubeMap();

let controls = new OrbitControls(camera, renderer.domElement);
controls.enableZoom = false;
controls.enablePan = false;
controls.enableKeys = false;

let square = new THREE.GridHelper(20, 1, 0xaaaaff, 0xaaaff);
square.position.y = 0.01;
scene.add(square);

let grid = new THREE.GridHelper(20, 10, "magenta", "magenta");
console.log(grid.geometry.attributes.position.count);
let moveable = [];
for(let i = 0; i < grid.geometry.attributes.position.count / 4; i++){
  moveable.push(1, 1, 0, 0);
}
console.log(moveable.length)
grid.geometry.setAttribute("moveable", new THREE.Float32BufferAttribute(moveable, 1));
let uniforms = {
  time: {value: 0},
  speed: {value: 1},
  size: {value: 20}
}
grid.material.onBeforeCompile = shader => {
  shader.uniforms.time = uniforms.time;
  shader.uniforms.speed = uniforms.speed;
  shader.uniforms.size = uniforms.size;
  shader.vertexShader = `
    uniform float time;
    uniform float speed;
    uniform float size;
    attribute float moveable;
    ${shader.vertexShader}
  `.replace(
    `#include <begin_vertex>`,
    `#include <begin_vertex>
        
        if (floor(moveable + 0.1) > 0.5){
          float start = size * -0.5;
          float zPos = mod( (position.z - start) + (time * speed), size) + start;
          transformed.z = zPos;
        }
    `
  );
  console.log(shader.vertexShader)
}
scene.add(grid);

// palm
let base = new THREE.Object3D();

let baseSpline = new THREE.CatmullRomCurve3([
  new THREE.Vector2(),
  new THREE.Vector2(3, 0),
  new THREE.Vector2(2.5, -7),
  new THREE.Vector2(-4, -6),
  new THREE.Vector2(-4.8, 0)
], true, "catmullrom", 0.1);
let baseG = new THREE.ExtrudeBufferGeometry(new THREE.Shape(baseSpline.getPoints(50)), {depth: 0.2, bevelEnabled: true, bevelThickness: 0.8, bevelSize: 0.2});
let baseObject = new THREE.Mesh(baseG, new THREE.MeshBasicMaterial({color: "magenta", wireframe: false, envMap: textureCube}));
base.add(baseObject);
scene.add(base);
let phalanxes = [];
let f1 = createFinger(new THREE.Object3D(), 0.8, false);   // pinky
let f2 = createFinger(new THREE.Object3D(), 0.95, false);  // ring
let f3 = createFinger(new THREE.Object3D(), 1, false);     // middle
let f4 = createFinger(new THREE.Object3D(), 0.95, false);  // index
let f5Base = new THREE.Object3D();
let f5 = createFinger(new THREE.Object3D(), 0.75, true);  // thumb
f5Base.add(f5);
base.add(f1, f2, f3, f4, f5Base);

f1.position.set( -4, 0.2, 0);
f2.position.set( -2, 0.2, 0);
f3.position.set(  0, 0.2, 0);
f4.position.set(  2, 0.2, 0);
f5Base.position.set(  3, -3, 0);
f5Base.rotation.set( 0, 0, THREE.MathUtils.degToRad(-60));
f5Base.updateMatrixWorld();

let g = createPhalanxGeom(1, 3);
let m = new THREE.MeshBasicMaterial({color: "aqua", wireframe: false, envMap: textureCube});
let o = new THREE.InstancedMesh(g, m, phalanxes.length);
phalanxes.forEach( (ph, i) => {
  ph.updateMatrixWorld();
  o.setMatrixAt(i, ph.matrixWorld);
})
scene.add(o);

window.addEventListener( 'resize', onWindowResize, false );

let t = new TWEEN.Tween({value: Math.PI * 0.075})
  .to({value: Math.PI * 0.45}, 4000)
  .easing(TWEEN.Easing.Quadratic.InOut)
  .repeat(Infinity)
  .yoyo(true)
  .onUpdate(val => {
    phalanxes.forEach((ph, i) => {
      ph.rotation.x = val.value;
      ph.updateMatrixWorld();
      o.setMatrixAt(i, ph.matrixWorld)
    });
    o.instanceMatrix.needsUpdate = true;
  });
t.start();

let clock = new THREE.Clock();

renderer.setAnimationLoop(() => {
  let t = clock.getElapsedTime();
  TWEEN.update();
  uniforms.time.value = t;
  base.rotation.x = (Math.sin(t * 0.125) * 0.5 + 0.5) * -Math.PI * 0.5;
  base.rotation.y = -t * 0.125;
  renderer.render(scene, camera);
});

function onWindowResize() {

  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize( innerWidth, innerHeight );

}

function createFinger(phalanx, scale, isThumb){
  phalanxes.push(phalanx);
  let current = phalanx;
  for(let i = 0; i < (isThumb ? 1 : 2); i++){
    let p = new THREE.Object3D();
    p.position.y = 3;
    p.scale.setScalar(0.85);
    current.add(p);
    phalanxes.push(p);
    current = p;
  }
  phalanx.scale.setScalar(scale);
  return phalanx;
}

function createPhalanxGeom(R, L){
  
  let r = R * 0.85;
  let R1 = R - r;
  let a = Math.asin(R1 / L);

  let path = new THREE.Path();
  path.absarc(0, 0, R, Math.PI * 1.5, a);
  path.absarc(0, L, r, a, Math.PI * 0.5);
  
  let pts = path.getPoints(5);
  
  let g = new THREE.LatheBufferGeometry(pts);
  
  return g;
}

function generateCubeMap(){
	
  
  let images = [];
  
  let c = document.createElement("canvas");
  c.width = 4;
  c.height = c.width;
  let ctx = c.getContext("2d");
  for(let i= 0; i < 6;i++){
  	ctx.fillStyle = "#fff";
    ctx.fillRect(0, 0, c.width, c.height);

    for(let j = 0; j < (c.width * c.height) / 2; j++){
      ctx.fillStyle = Math.random() < 0.5 ? "#f0f" : "#40f";
      ctx.fillRect(
        Math.floor(Math.random() * c.width),
        Math.floor(Math.random() * c.height),
        2,
        1
      );
    }
    
    images.push(c.toDataURL());
    
  }
  
  let cm = new THREE.CubeTextureLoader().load(images);
  
  console.log(cm);
  
  return cm;
}
</script>