- "Low Poly Human Hands (All Quads)" (https://skfb.ly/WuXz) by Jeremy E.
- Grayson is licensed under Creative Commons Attribution
- (http://creativecommons.org/licenses/by/4.0/).
+
diff --git a/package-lock.json b/package-lock.json
index 26bf026..c59f5f2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,10 +9,13 @@
"version": "1.0.0",
"license": "MIT",
"devDependencies": {
+ "@types/three": "^0.138.0",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"esbuild": "^0.14.25",
"eslint": "^8.10.0",
+ "esm-seedrandom": "^3.0.5",
+ "parse-srt": "^1.0.0-alpha",
"three": "^0.138.3"
}
},
@@ -106,6 +109,12 @@
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true
},
+ "node_modules/@types/three": {
+ "version": "0.138.0",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.138.0.tgz",
+ "integrity": "sha512-D8AoV7h2kbCfrv/DcebHOFh1WDwyus3HdooBkAwcBikXArdqnsQ38PQ85JCunnvun160oA9jz53GszF3zch3tg==",
+ "dev": true
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.14.0.tgz",
@@ -994,6 +1003,12 @@
"node": ">=4.0"
}
},
+ "node_modules/esm-seedrandom": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/esm-seedrandom/-/esm-seedrandom-3.0.5.tgz",
+ "integrity": "sha512-pMAq0mFIr5JQ3Ihbng7EBLMJ+llMbaDKkiG44pqbSXS0NIZWtEANpOpxb5s6Q8Q2R562P26qMHPv8YtP/NHh9g==",
+ "dev": true
+ },
"node_modules/espree": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz",
@@ -1479,6 +1494,12 @@
"node": ">=6"
}
},
+ "node_modules/parse-srt": {
+ "version": "1.0.0-alpha",
+ "resolved": "https://registry.npmjs.org/parse-srt/-/parse-srt-1.0.0-alpha.tgz",
+ "integrity": "sha1-hePGDTHk3Qkk+vVB6oWhiPw0T2E=",
+ "dev": true
+ },
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -1916,6 +1937,12 @@
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true
},
+ "@types/three": {
+ "version": "0.138.0",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.138.0.tgz",
+ "integrity": "sha512-D8AoV7h2kbCfrv/DcebHOFh1WDwyus3HdooBkAwcBikXArdqnsQ38PQ85JCunnvun160oA9jz53GszF3zch3tg==",
+ "dev": true
+ },
"@typescript-eslint/eslint-plugin": {
"version": "5.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.14.0.tgz",
@@ -2437,6 +2464,12 @@
"integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
"dev": true
},
+ "esm-seedrandom": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/esm-seedrandom/-/esm-seedrandom-3.0.5.tgz",
+ "integrity": "sha512-pMAq0mFIr5JQ3Ihbng7EBLMJ+llMbaDKkiG44pqbSXS0NIZWtEANpOpxb5s6Q8Q2R562P26qMHPv8YtP/NHh9g==",
+ "dev": true
+ },
"espree": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz",
@@ -2823,6 +2856,12 @@
"callsites": "^3.0.0"
}
},
+ "parse-srt": {
+ "version": "1.0.0-alpha",
+ "resolved": "https://registry.npmjs.org/parse-srt/-/parse-srt-1.0.0-alpha.tgz",
+ "integrity": "sha1-hePGDTHk3Qkk+vVB6oWhiPw0T2E=",
+ "dev": true
+ },
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
diff --git a/package.json b/package.json
index cb789f5..361013d 100644
--- a/package.json
+++ b/package.json
@@ -12,10 +12,13 @@
"author": "Silas",
"license": "MIT",
"devDependencies": {
+ "@types/three": "^0.138.0",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"esbuild": "^0.14.25",
"eslint": "^8.10.0",
+ "esm-seedrandom": "^3.0.5",
+ "parse-srt": "^1.0.0-alpha",
"three": "^0.138.3"
}
}
diff --git a/src/audio.ts b/src/audio.ts
index 404863f..0862e7a 100644
--- a/src/audio.ts
+++ b/src/audio.ts
@@ -1,19 +1,25 @@
-import { Audio, AudioLoader } from "three";
+import { Audio, AudioAnalyser, AudioListener, AudioLoader } from "three";
+import { SoundAverages } from "./types";
+import { avg } from "./utils";
const loader = new AudioLoader();
-export function LoadAudio(listener): Promise
{
+export function LoadAudio(
+ listener: AudioListener,
+ url: string,
+ volume: number
+): Promise {
const loadingDiv = document.getElementById("loader");
loadingDiv.innerHTML = "Loading audio: 0%";
return new Promise((resolve, reject) => {
loader.load(
- "/static/audio.mp3",
+ url,
(audio) => {
const sound = new Audio(listener);
sound.setBuffer(audio);
sound.setLoop(false);
- sound.setVolume(0.1);
+ sound.setVolume(volume);
loadingDiv.innerHTML = "";
return resolve(sound);
},
@@ -28,3 +34,53 @@ export function LoadAudio(listener): Promise {
);
});
}
+
+export function calculateSoundAverages(
+ audioAnalyser: AudioAnalyser
+): SoundAverages {
+ const soundArray = audioAnalyser.getFrequencyData();
+ const soundAvg = avg(soundArray) / soundArray.length;
+
+ const lowArray = soundArray.slice(0, soundArray.length / 5 - 1);
+
+ const lowMidArray = soundArray.slice(
+ soundArray.length / 5 - 1,
+ 2 * (soundArray.length / 5) - 1
+ );
+
+ const midArray = soundArray.slice(
+ (2 * soundArray.length) / 5 - 1,
+ 3 * (soundArray.length / 5) - 1
+ );
+
+ const highMidArray = soundArray.slice(
+ (3 * soundArray.length) / 5 - 1,
+ 4 * (soundArray.length / 5) - 1
+ );
+
+ const highArray = soundArray.slice(
+ 4 * (soundArray.length / 5) - 1,
+ soundArray.length - 1
+ );
+
+ const lowAvg = avg(lowArray) / lowArray.length;
+ const lowMidAvg = avg(lowMidArray) / lowMidArray.length;
+ const midAvg = avg(midArray) / midArray.length;
+ const highMidAvg = avg(highMidArray) / highMidArray.length;
+ const highAvg = avg(highArray) / highArray.length;
+
+ document.getElementById("debug").innerHTML = `lows: ${lowAvg.toFixed(
+ 2
+ )}, low mids: ${lowMidAvg.toFixed(2)}, mids: ${midAvg.toFixed(
+ 2
+ )}, high mids: ${highMidAvg.toFixed(2)}, highs: ${highAvg.toFixed(2)}`;
+
+ return {
+ soundAvg,
+ lowAvg,
+ lowMidAvg,
+ midAvg,
+ highMidAvg,
+ highAvg,
+ };
+}
diff --git a/src/index.ts b/src/index.ts
index 1034997..e07e9a1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -7,65 +7,114 @@ import {
AudioListener,
AudioAnalyser,
Clock,
+ Mesh,
+ MeshBasicMaterial,
+ MathUtils,
+ BufferGeometry,
+ Points,
+ SphereGeometry,
} from "three";
-import { LoadAudio } from "./audio";
+import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
+import { calculateSoundAverages, LoadAudio } from "./audio";
import { LoadModel } from "./model";
+import { SETTINGS } from "./settings";
+import { CORE, DISTORTION, DistortionMesh, SoundAverages, SRT } from "./types";
+import { prng_alea } from "esm-seedrandom";
+import parseSRT from "parse-srt";
-const GLOBAL = {
+const CORE: CORE = {
renderer: null,
scene: null,
camera: null,
+ clock: null,
+ rng: prng_alea(
+ "Polka-Jovial-Makeover-Wieldable-Spirited-Footprint-Recall-Handpick-Sacrifice-Jester"
+ ),
+ avgs: null,
+ subtitleDiv: document.getElementById("subtitles"),
+ parser: parseSRT,
+ srt: null,
+ elapsedTime: 0,
+ playing: false,
+};
+
+const DISTORTION: DISTORTION = {
light: null,
hand: null,
positions: null,
- distortionLevel: null,
audioListener: null,
audioAnalyser: null,
- clock: null,
};
-const avg = (list) => list.reduce((prev, curr) => prev + curr) / list.length;
+
+const ROTATION = {
+ cam: {
+ angle: 0,
+ angularSpeed: MathUtils.degToRad(20),
+ radius: 5,
+ },
+ light: {
+ angle: 30,
+ angularSpeed: MathUtils.degToRad(40),
+ radius: 2,
+ },
+};
init().then(() => animate());
async function init() {
const container = document.getElementById("container");
- GLOBAL.scene = new Scene();
- GLOBAL.camera = new PerspectiveCamera(
+ CORE.scene = new Scene();
+ CORE.camera = new PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
1,
10000
);
- GLOBAL.camera.position.z = 3;
+ CORE.scene.add(CORE.camera);
- GLOBAL.light = new PointLight(0x119911, 1);
- GLOBAL.light.counter = 0;
- GLOBAL.light.position.set(0, 0.15, 0.15);
- GLOBAL.scene.add(GLOBAL.light);
+ DISTORTION.light = new PointLight(0x119911, 1);
+ DISTORTION.light.counter = 0;
- GLOBAL.renderer = new WebGLRenderer();
- GLOBAL.renderer.setPixelRatio(window.devicePixelRatio);
- GLOBAL.renderer.setSize(window.innerWidth, window.innerHeight);
- container.appendChild(GLOBAL.renderer.domElement);
+ const geometry = new SphereGeometry(0.2);
+ const material = new MeshBasicMaterial({ color: 0xcccc00 });
+ const sphere = new Mesh(geometry, material);
+ DISTORTION.light.add(sphere);
+ CORE.scene.add(DISTORTION.light);
+
+ CORE.renderer = new WebGLRenderer();
+ CORE.renderer.setPixelRatio(window.devicePixelRatio);
+ CORE.renderer.setSize(window.innerWidth, window.innerHeight);
+ container.appendChild(CORE.renderer.domElement);
window.addEventListener("resize", onWindowResize);
- GLOBAL.audioListener = new AudioListener();
- GLOBAL.scene.add(GLOBAL.audioListener);
+ DISTORTION.audioListener = new AudioListener();
+ CORE.scene.add(DISTORTION.audioListener);
- GLOBAL.clock = new Clock();
+ CORE.clock = new Clock();
try {
const model = await LoadModel();
initializeModel(model);
+ updateRotations(0);
+ initializeStars();
- const audio = await LoadAudio(GLOBAL.audioListener);
- initializeAudio(audio);
+ const audio = await LoadAudio(
+ DISTORTION.audioListener,
+ "/static/who_am_i_with_music.mp3",
+ 1
+ );
+ audio.onEnded = () => {
+ document.getElementById("credits").style.display = "block";
+ };
+ CORE.srt = await loadSRT();
+ DISTORTION.audioAnalyser = new AudioAnalyser(audio, 512);
const startButton = document.getElementById("startButton");
startButton.style.display = "block";
startButton.addEventListener("click", () => {
audio.play();
+ CORE.playing = true;
startButton.remove();
});
} catch (err) {
@@ -73,63 +122,125 @@ async function init() {
}
}
-function initializeAudio(audio) {
- GLOBAL.audioAnalyser = new AudioAnalyser(audio, 512);
+async function loadSRT(): Promise {
+ const response = await fetch("static/who_am_i_with_music.srt");
+ const responseText = await response.text();
+ return CORE.parser(responseText);
}
-function initializeModel(model) {
+function initializeModel(model: GLTF) {
// remove second hand with text above it
const objToRemove = model.scene.getObjectByName("Object_3");
objToRemove.parent.remove(objToRemove);
// turn remaining hand into wireframe
- GLOBAL.hand = model.scene.getObjectByName("Object_4");
- GLOBAL.hand.material.wireframe = true;
+ DISTORTION.hand = model.scene.getObjectByName("Object_4");
+ DISTORTION.hand.material.wireframe = true;
// set up distortion for each vertex
- GLOBAL.hand.originalPositions = GLOBAL.hand.geometry.getAttribute("position");
- GLOBAL.hand.distortions = GLOBAL.hand.originalPositions.array
+ DISTORTION.hand.originalPositions =
+ DISTORTION.hand.geometry.getAttribute("position");
+ DISTORTION.hand.distortions = (
+ DISTORTION.hand.originalPositions.array as Array
+ )
.slice(0)
- .map(() => Math.random() * 2 - 1);
- GLOBAL.positions = GLOBAL.hand.geometry.getAttribute("position");
+ .map(() => CORE.rng() * 2 - 1);
+ DISTORTION.positions = DISTORTION.hand.geometry.getAttribute("position");
+ DISTORTION.hand.parent.position.x = DISTORTION.hand.parent.position.x + 2.8;
+ DISTORTION.hand.parent.position.y = DISTORTION.hand.parent.position.y + 0;
+ DISTORTION.hand.parent.position.z = DISTORTION.hand.parent.position.z + 2.2;
+ DISTORTION.hand.parent.rotateZ(Math.PI / 2);
+ DISTORTION.hand.geometry.center();
- // center hand in scene
- GLOBAL.hand.position.x = GLOBAL.hand.position.x + 1.5;
+ CORE.scene.add(model.scene);
+}
- GLOBAL.scene.add(model.scene);
+function initializeStars() {
+ const geometry = new BufferGeometry();
+ const vertices = [];
+ for (let i = 0; i < 10000; i++) {
+ const x = CORE.rng() * 2000 - 1000;
+ const y = CORE.rng() * 2000 - 1000;
+ const z = CORE.rng() * 2000 - 1000;
+
+ vertices.push(x, y, z);
+ }
+
+ geometry.setAttribute("position", new Float32BufferAttribute(vertices, 3));
+ const points = new Points(geometry);
+ CORE.scene.add(points);
}
function animate() {
requestAnimationFrame(animate);
- const delta = GLOBAL.clock.getDelta();
+ const delta = CORE.clock.getDelta();
+ CORE.avgs = calculateSoundAverages(DISTORTION.audioAnalyser);
- const soundArray = GLOBAL.audioAnalyser.getFrequencyData();
- const soundAvg = avg(soundArray) / soundArray.length;
-
- render(delta, Math.pow(soundAvg * 5, 5));
+ render(delta, CORE.avgs);
}
-function render(delta, soundAvg) {
- // modulate light intensity between 0.5 and 1.5
- GLOBAL.light.counter += delta + 0.02;
- GLOBAL.light.intensity = Math.sin(GLOBAL.light.counter) / 2 + 1;
+function render(delta: number, soundAvg: SoundAverages) {
+ // modulate light intensity
+ DISTORTION.light.counter += delta + SETTINGS.lightSpeed;
+ DISTORTION.light.intensity =
+ Math.sin(DISTORTION.light.counter) / 2 + SETTINGS.lightMin;
+ const sound = Math.pow(
+ soundAvg.soundAvg * SETTINGS.distortionMax,
+ SETTINGS.distortionPower
+ );
+
+ if (CORE.playing) {
+ updateRotations(delta);
+ updateSubtitles(delta);
+ }
const newPositions = new Float32BufferAttribute(
- GLOBAL.positions.array.map((_position, index) => {
- const distortion = GLOBAL.hand.distortions[index] * soundAvg;
- return distortion / 10 + GLOBAL.hand.originalPositions.array[index];
+ (DISTORTION.positions.array as Array).map((_position, index) => {
+ const distortion = DISTORTION.hand.distortions[index] * sound;
+ return distortion / 10 + DISTORTION.hand.originalPositions.array[index];
}),
3
);
- GLOBAL.hand.geometry.setAttribute("position", newPositions);
- GLOBAL.renderer.render(GLOBAL.scene, GLOBAL.camera);
+ DISTORTION.hand.geometry.setAttribute("position", newPositions);
+ CORE.camera.lookAt(DISTORTION.hand.position);
+ CORE.renderer.render(CORE.scene, CORE.camera);
+}
+
+function updateRotations(delta: number) {
+ DISTORTION.hand.parent.rotateZ(3 / 1000);
+
+ CORE.camera.position.x = Math.sin(ROTATION.cam.angle) * ROTATION.cam.radius;
+ CORE.camera.position.z = Math.cos(ROTATION.cam.angle) * ROTATION.cam.radius;
+ CORE.camera.position.y = Math.cos(ROTATION.cam.angle) * 1.5;
+ ROTATION.cam.angle += ROTATION.cam.angularSpeed * delta;
+
+ DISTORTION.light.position.x =
+ Math.cos(ROTATION.light.angle) * ROTATION.light.radius;
+ DISTORTION.light.position.z =
+ Math.sin(ROTATION.light.angle) * ROTATION.light.radius;
+ DISTORTION.light.position.y = Math.cos(ROTATION.light.angle) * 5;
+
+ ROTATION.light.angle += ROTATION.light.angularSpeed * delta;
+}
+
+function updateSubtitles(delta: number) {
+ CORE.elapsedTime += delta;
+ let found = false;
+ CORE.srt.forEach((srt) => {
+ if (CORE.elapsedTime > srt.start && CORE.elapsedTime < srt.end) {
+ found = true;
+ CORE.subtitleDiv.innerHTML = srt.text;
+ }
+ });
+
+ if (!found) CORE.subtitleDiv.innerHTML = "";
}
function onWindowResize() {
- GLOBAL.camera.aspect = window.innerWidth / window.innerHeight;
- GLOBAL.camera.updateProjectionMatrix();
+ CORE.camera.aspect = window.innerWidth / window.innerHeight;
+ CORE.camera.updateProjectionMatrix();
- GLOBAL.renderer.setSize(window.innerWidth, window.innerHeight);
+ CORE.renderer.setSize(window.innerWidth, window.innerHeight);
}
diff --git a/src/model.ts b/src/model.ts
index adb865c..f457fd4 100644
--- a/src/model.ts
+++ b/src/model.ts
@@ -1,22 +1,13 @@
-import { AnimationClip, Scene, Camera } from "three";
-import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
+import { GLTFLoader, GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
const loader = new GLTFLoader();
-export interface GLTF {
- animations: AnimationClip[];
- scene: Scene;
- scenes: Scene[];
- cameras: Camera[];
- asset: object;
-}
-
export function LoadModel(): Promise {
const loadingDiv = document.getElementById("loader");
return new Promise((resolve, reject) => {
loader.load(
"/static/scene.gltf",
- (object: GLTF) => resolve(object),
+ (gltf: GLTF) => resolve(gltf),
(progress) =>
(loadingDiv.innerHTML = `Loading model: ${
(progress.loaded / progress.total) * 100
diff --git a/src/settings.ts b/src/settings.ts
new file mode 100644
index 0000000..abd176d
--- /dev/null
+++ b/src/settings.ts
@@ -0,0 +1,6 @@
+export const SETTINGS = {
+ lightSpeed: 0.02,
+ lightMin: 1.25,
+ distortionMax: 4,
+ distortionPower: 3,
+};
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..4434b1e
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,69 @@
+import {
+ AudioAnalyser,
+ AudioListener,
+ BufferAttribute,
+ Clock,
+ InterleavedBufferAttribute,
+ Mesh,
+ MeshBasicMaterial,
+ PerspectiveCamera,
+ PointLight,
+ Scene,
+ WebGLRenderer,
+} from "three";
+
+export interface CORE {
+ renderer: WebGLRenderer;
+ scene: Scene;
+ camera: PerspectiveCamera;
+ clock: Clock;
+ rng: RandomNumberGenerator;
+ avgs: SoundAverages;
+ subtitleDiv: HTMLElement;
+ parser: SRTParser;
+ srt: SRT[];
+ elapsedTime: number;
+ playing: boolean;
+}
+
+export interface DISTORTION {
+ light: CountingLight;
+ hand: DistortionMesh;
+ positions: BufferAttribute | InterleavedBufferAttribute;
+ audioListener: AudioListener;
+ audioAnalyser: AudioAnalyser;
+}
+
+export interface CountingLight extends PointLight {
+ counter?: number;
+}
+
+export interface DistortionMesh extends Mesh {
+ material: MeshBasicMaterial;
+ originalPositions: BufferAttribute | InterleavedBufferAttribute;
+ distortions: Array;
+}
+
+export type SoundAverages = {
+ soundAvg: number;
+ lowAvg: number;
+ lowMidAvg: number;
+ midAvg: number;
+ highMidAvg: number;
+ highAvg: number;
+};
+
+type RandomNumberGenerator = {
+ (): number;
+};
+
+type SRTParser = {
+ (srt: string): SRT[];
+};
+
+export type SRT = {
+ id: number;
+ start: number;
+ end: number;
+ text: string;
+};
diff --git a/src/utils.ts b/src/utils.ts
new file mode 100644
index 0000000..ea7b605
--- /dev/null
+++ b/src/utils.ts
@@ -0,0 +1,3 @@
+export function avg(list: Uint8Array) {
+ return list.reduce((prev, curr) => prev + curr) / list.length;
+}
diff --git a/static/audio.mp3 b/static/audio.mp3
deleted file mode 100644
index 32c31d7..0000000
Binary files a/static/audio.mp3 and /dev/null differ
diff --git a/static/font/VictorMono-Medium.otf b/static/font/VictorMono-Medium.otf
new file mode 100644
index 0000000..edcb21d
Binary files /dev/null and b/static/font/VictorMono-Medium.otf differ
diff --git a/static/font/VictorMono-Medium.ttf b/static/font/VictorMono-Medium.ttf
new file mode 100644
index 0000000..fed0693
Binary files /dev/null and b/static/font/VictorMono-Medium.ttf differ
diff --git a/static/font/VictorMono-Medium.woff b/static/font/VictorMono-Medium.woff
new file mode 100644
index 0000000..2e58481
Binary files /dev/null and b/static/font/VictorMono-Medium.woff differ
diff --git a/static/font/VictorMono-Medium.woff2 b/static/font/VictorMono-Medium.woff2
new file mode 100644
index 0000000..89df0cf
Binary files /dev/null and b/static/font/VictorMono-Medium.woff2 differ
diff --git a/static/styles.css b/static/styles.css
new file mode 100644
index 0000000..bf6e605
--- /dev/null
+++ b/static/styles.css
@@ -0,0 +1,65 @@
+@font-face {
+ font-family: "Victor Mono";
+ src: url("/static/font/VictorMono-Medium.woff2") format("woff2"),
+ /* Super Modern Browsers */ url("/static/font/VictorMono-Medium.woff")
+ format("woff"),
+ /* Pretty Modern Browsers */ url("/static/font/VictorMono-Medium.ttf")
+ format("truetype"); /* Safari, Android, iOS */
+}
+
+body {
+ font-family: "Victor Mono", monospace;
+}
+
+#startButton {
+ color: #efefef;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 80px;
+ height: 25px;
+ border: 2px solid #efefef;
+ text-align: center;
+ padding: 20px 40px;
+ cursor: pointer;
+}
+
+#subtitles {
+ position: absolute;
+ bottom: 200px;
+ left: 50%;
+ color: #efefef;
+ font-size: 20px;
+ transform: translateX(-50%);
+ width: 100%;
+ max-width: 800px;
+ text-align: center;
+}
+
+#credits {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ color: #efefef;
+ font-size: 12px;
+ transform: translate(-50%, -50%);
+ max-width: 600px;
+ width: 100%;
+}
+
+#loader {
+ color: #efefef;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+a:link {
+ color: #33aa33;
+}
+
+a:visited {
+ color: #33aa33;
+}
diff --git a/static/who_am_i_with_music.mp3 b/static/who_am_i_with_music.mp3
new file mode 100644
index 0000000..7c384fe
Binary files /dev/null and b/static/who_am_i_with_music.mp3 differ
diff --git a/static/who_am_i_with_music.srt b/static/who_am_i_with_music.srt
new file mode 100644
index 0000000..356dc54
--- /dev/null
+++ b/static/who_am_i_with_music.srt
@@ -0,0 +1,44 @@
+1
+00:00:17,134 --> 00:00:19,470
+Who am I?
+
+2
+00:00:20,570 --> 00:00:23,870
+Am I the clothes on my back and the hair on my head?
+
+3
+00:00:25,110 --> 00:00:28,210
+Am I the people I’m with and the words I’ve said?
+
+4
+00:00:29,510 --> 00:00:32,810
+Am I the field I study and the school I attend?
+
+5
+00:00:33,510 --> 00:00:37,010
+Am I the programs I write and the poems I’ve penned?
+
+6
+00:00:38,110 --> 00:00:41,610
+Am I the music I enjoy and the guitar I play?
+
+7
+00:00:42,200 --> 00:00:45,710
+Am I the grade on my test and my ten page essay?
+
+8
+00:00:46,500 --> 00:00:50,490
+Am I the habits I’ve formed and the phrases I repeat?
+
+9
+00:00:51,500 --> 00:00:55,200
+Am I the fake smile given to every person I meet?
+
+10
+00:00:56,000 --> 00:01:00,500
+Am I the religion I follow and politician I elect?
+
+11
+00:01:01,000 --> 00:01:06,500
+Am I every action I carry out and every thought I reflect?
+
diff --git a/static/who_am_i_with_music.wf b/static/who_am_i_with_music.wf
new file mode 100644
index 0000000..40b0c06
Binary files /dev/null and b/static/who_am_i_with_music.wf differ