type Face = 'front' | 'top' | 'back' | 'bottom' | 'left' | 'right'; type Pane = 'hello' | 'id' | 'activities' | 'links' | 'friends' | 'six'; const panes: Pane[] = ['hello', 'id', 'activities', 'links', 'friends', 'six']; /** * - for front, left, right: up is up * - for back: up is down (lol) * - for top: up is away from you * - for bottom: up is towards you */ type Orientation = 'up' | 'left' | 'down' | 'right'; type Place = [Face, Orientation]; function table(m: Record): (x: A) => B { return x => m[x]; } const doCwO = table({up: 'right', right: 'down', down: 'left', left: 'up'}); const doCcwO = table({up: 'left', left: 'down', down: 'right', right: 'up'}); type Direction = 'up' | 'left' | 'down' | 'right'; type Rotation = 'cw' | 'ccw'; type Movement = Direction | Rotation; // if you rotate the cube "up" (along the x axis), face f becomes up(f) // et cetera const faceMoves: Record Face> = { up: table({ front: 'top', top: 'back', back: 'bottom', bottom: 'front', left: 'left', right: 'right' }), down: table({ front: 'bottom', top: 'front', back: 'top', bottom: 'back', left: 'left', right: 'right' }), left: table({ front: 'left', top: 'top', back: 'right', bottom: 'bottom', left: 'back', right: 'front' }), right: table({ front: 'right', top: 'top', back: 'left', bottom: 'bottom', left: 'front', right: 'back' }), cw: table({ front: 'front', back: 'back', left: 'top', right: 'bottom', top: 'right', bottom: 'left' }), ccw: table({ front: 'front', back: 'back', left: 'bottom', right: 'top', top: 'left', bottom: 'right' }) }; const orientationMoves: Record Orientation> = { up(f, o) { switch (f) { case 'left': return doCcwO(o); case 'right': return doCwO(o); default: return o; } }, down(f, o) { switch (f) { case 'left': return doCwO(o); case 'right': return doCcwO(o); default: return o; } }, left(f, o) { switch (f) { case 'top': return doCwO(o); case 'bottom': return doCcwO(o); case 'left': case 'back': return doCwO(doCwO(o)); default: return o; } }, right(f, o) { switch (f) { case 'top': return doCcwO(o); case 'bottom': return doCwO(o); case 'right': case 'back': return doCcwO(doCcwO(o)); default: return o; } }, cw(f, o) { return f == 'back' ? doCcwO(o) : doCwO(o); }, ccw(f, o) { return f == 'back' ? doCwO(o) : doCcwO(o); } }; function applyMoves([f, o]: Place, ms: Movement[]): Place { for (const m of ms) { o = orientationMoves[m](f, o); f = faceMoves[m](f); } return [f, o]; } const toFront = table({ front: [], top: ['down'], back: ['left', 'left'], bottom: ['up'], left: ['right'], right: ['left'] }); const toUpright = table({ up: [], left: ['cw'], down: ['cw', 'cw'], right: ['ccw'] }); function toFrontUpright([f, o]: Place): [Direction[], Rotation[]] { const directions = toFront(f); const rotations = toUpright(applyMoves([f, o], directions)[1]); return [directions, rotations]; } const movementToTransform = table({ up: 'rotateX(.25turn)', down: 'rotateX(-.25turn)', left: 'rotateY(-.25turn)', right: 'rotateY(.25turn)', cw: 'rotateZ(.25turn)', ccw: 'rotateZ(-.25turn)' }); function movementsToTransform(ms: Movement[]) { return ms.map(movementToTransform).join(' '); } const faceToTransform = table({ front: '', top: 'rotateX(.25turn)', back: 'rotateX(.5turn)', bottom: 'rotateX(-.25turn)', left: 'rotateY(-.25turn)', right: 'rotateY(.25turn)' }); const orientationToTransform = table({ up: '', left: 'rotateZ(-.25turn)', down: 'rotateZ(-.5turn)', right: 'rotateZ(.25turn)' }); function toTransform(f: Face, o: Orientation): string { const ft = faceToTransform(f); const ot = orientationToTransform(o); return ft || ot ? `${ft} ${ot}` : 'none'; } type Conf = Record; // the back face is 'down' so it has the same visual orientation as other side faces let current: Conf = { hello: ['front', 'up'], id: ['left', 'up'], activities: ['back', 'down'], links: ['right', 'up'], friends: ['bottom', 'up'], six: ['top', 'up'], }; function applyConfiguration(): void { for (const pane of panes) { const element = document.getElementById(pane)!; const [face, ori] = current[pane]; element.style.setProperty('--base-transform', toTransform(face, ori)); element.style.pointerEvents = face == 'front' ? 'auto' : 'none'; } } function move(c: Conf, ...ms: Movement[]): Conf { let res: Partial = {}; for (const p of panes) { res[p] = applyMoves(c[p], ms) } return res as Conf; } function animateMoveWith(ds: Direction[], rs: Rotation[]): void { const outer = document.getElementById('outer')!; const cube = document.getElementById('cube')!; cube.dataset.moving = 'true'; cube.style.transition = '0.4s cubic-bezier(.4, -0.29, .43, 1.26)'; outer.style.transition = `0.4s 0.25s cubic-bezier(.48, 0, .44, 1.07)`; function transitionListener(elem: HTMLElement): () => void { function handler(e: Event) { if (e.target == elem) { finish(); } } elem.addEventListener('transitionend', handler); return () => elem.removeEventListener('transitionend', handler); } let removeOuter = () => {}; let removeCube = () => {}; if (rs.length > 0) { removeOuter = transitionListener(outer); cube.style.transform = movementsToTransform(ds); outer.style.transform = movementsToTransform(rs); } else if (ds.length > 0) { removeCube = transitionListener(cube); cube.style.transform = movementsToTransform(ds); } else { finish(); } function finish() { removeOuter(); removeCube(); outer.style.transition = cube.style.transition = outer.style.transform = cube.style.transform = 'none'; delete cube.dataset.moving; current = move(current, ...ds, ...rs); applyConfiguration(); } } function animateMoveTo(pane: Pane): void { const [ds, rs] = toFrontUpright(current[pane]); animateMoveWith(ds, rs); history.replaceState(null, '', `#${pane}`); } function setup(): void { for (const pane of panes) { const box = document.getElementById(`b-${pane}`) as HTMLInputElement; box.addEventListener('change', () => { if (box.checked) { animateMoveTo(pane); } }); if (location.hash == `#${pane}`) { current = move(current, ...toFrontUpright(current[pane]).flat()); box.checked = true; } } applyConfiguration(); } document.addEventListener('DOMContentLoaded', setup); /* function LEFT() { animateMoveWith(['left'], []); } function RIGHT() { animateMoveWith(['right'], []); } function UP() { animateMoveWith(['up'], []); } function DOWN() { animateMoveWith(['down'], []); } function CW() { animateMoveWith([], ['cw']); } function CCW() { animateMoveWith([], ['ccw']); } */ export {};