From cb9ac2640f720c963f31c4821cad78ea8bbb1286 Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Wed, 2 Feb 2022 00:52:01 +0000
Subject: [PATCH] Setup basic word mutating system

Next up: prettification!
---
 .gitignore    | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++
 css/theme.css |  32 ++++++++++
 index.html    |  24 ++++++++
 js/index.mjs  |  68 +++++++++++++++++++++
 js/mutate.mjs |  33 +++++++++++
 package.json  |  15 +++++
 6 files changed, 332 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 css/theme.css
 create mode 100644 index.html
 create mode 100644 js/index.mjs
 create mode 100644 js/mutate.mjs
 create mode 100644 package.json

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1828c11
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,160 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/git,node
+# Edit at https://www.toptal.com/developers/gitignore?templates=git,node
+
+### Git ###
+# Created by git for backups. To disable backups in Git:
+# $ git config --global mergetool.keepBackup false
+*.orig
+
+# Created by git when using merge tools for conflicts
+*.BACKUP.*
+*.BASE.*
+*.LOCAL.*
+*.REMOTE.*
+*_BACKUP_*.txt
+*_BASE_*.txt
+*_LOCAL_*.txt
+*_REMOTE_*.txt
+
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+### Node Patch ###
+# Serverless Webpack directories
+.webpack/
+
+# Optional stylelint cache
+
+# SvelteKit build / generate output
+.svelte-kit
+
+# End of https://www.toptal.com/developers/gitignore/api/git,node
diff --git a/css/theme.css b/css/theme.css
new file mode 100644
index 0000000..455dd5a
--- /dev/null
+++ b/css/theme.css
@@ -0,0 +1,32 @@
+:root {
+	--bg: #bff4fb;
+	--accent: #333333;
+	--accent-dark: #777777;
+	/* --bg: #038de8;
+	--accent: #e87e04;
+	--accent-dark: #d55400; */
+}
+
+html, body { font-size: 100%; }
+body, input[type=text] {
+    font-family: "Ubuntu", sans-serif;
+	text-align: center;
+	font-size: 150%;
+	
+	background: var(--bg);
+	color: var(--accent);
+}
+
+input[type=text] {
+	border: 0;
+	border-bottom: 0.2em solid var(--accent-dark);
+}
+
+.mutation-step {
+	display: flex;
+	justify-content: space-evenly;
+}
+
+.mutation-option {
+	cursor: pointer;
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..59884ff
--- /dev/null
+++ b/index.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset='utf-8' />
+		<title>mutate-a-word!</title>
+	</head>
+	<body>
+		<h1>mutate-a-word!</h1>
+		<section>
+			<h2>Enter a starting word</h2>
+			<p>
+				<input type="text" id="starting-word" value="" placeholder="e.g. rockets" />
+			</p>
+		</section>
+		
+		<section id="mutations">
+			<em>Mutations will appear here</em>
+		</section>
+		
+		<!---------------->
+		<link rel="stylesheet" href="./css/theme.css" />
+		<script src="./js/index.mjs" charset="utf-8" type="module"></script>
+	</body>
+</html>
diff --git a/js/index.mjs b/js/index.mjs
new file mode 100644
index 0000000..8316306
--- /dev/null
+++ b/js/index.mjs
@@ -0,0 +1,68 @@
+"use strict";
+
+import mutate from './mutate.mjs';
+
+let display_options = 3;
+
+
+window.addEventListener("load", (_event) => {
+	const el_starting_word = document.querySelector("#starting-word");
+	
+	if(el_starting_word.value.length > 0)
+		handle_starting_keyup();
+	
+	el_starting_word.addEventListener("keyup", handle_starting_keyup);
+});
+
+function handle_starting_keyup(_event) {
+	const el_starting_word = document.querySelector("#starting-word");
+	const el_mutations = document.querySelector("#mutations");
+	
+	el_mutations.replaceChildren(); // Empty node of all children
+	
+	mutation_step(el_starting_word.value);
+}
+
+function handle_mutation_word_keyup(event) {
+	// 1: Find DOM elements
+	const el_step = event.target.closest(".mutation-step");
+	const el_word = event.target.closest(".mutation-option");
+	
+	// 2: Update UI - Add classes, Remove all subsequent mutation steps
+	el_word.classList.add("selected");
+	while(el_step.nextElementSibling !== null)
+		el_step.parentNode.removeChild(el_step.nextElementSibling);
+	
+	// 3: Do a new mutation step
+	mutation_step(el_word.dataset.word);
+}
+
+function mutation_step(word) {
+	// 0: Validate input
+	if(typeof word !== "string")
+		throw new Error(`Error: Expected argument 1 (word) to be of type string, but got value of type ${typeof word}.`);
+	
+	// 1: Find DOM elements
+	const el_mutations = document.querySelector("#mutations");
+	
+	// 2: Generate mutations
+	// TODO: Eliminate duplicates
+	const words_new = Array(display_options).fill(null)
+		.map(() => mutate(word));
+	
+	// 3: Update DOM
+	let el_step = document.createElement("div");
+	el_step.classList.add("mutation-step");
+	for(let word_item of words_new) {
+		let el_word = document.createElement("span");
+		el_word.classList.add("mutation-option");
+		el_word.appendChild(document.createTextNode(word_item));
+		el_word.dataset.word = word_item;
+		el_step.appendChild(el_word);
+		el_word.addEventListener("click", handle_mutation_word_keyup);
+		el_word.addEventListener("touchend", handle_mutation_word_keyup);
+		el_word.addEventListener("mouseup", handle_mutation_word_keyup);
+	}
+	
+	el_mutations.appendChild(el_step);
+}
diff --git a/js/mutate.mjs b/js/mutate.mjs
new file mode 100644
index 0000000..af5388c
--- /dev/null
+++ b/js/mutate.mjs
@@ -0,0 +1,33 @@
+"use strict";
+
+const vowels = "aeiou".split("");
+const consonants = "bcdfghjklmnpqrstvwxyz".split("");
+
+function random_item(arr) {
+	return arr[Math.floor(Math.random() * arr.length)];
+}
+
+export default function(word) {
+	console.log(`***** MUTATE *****`)
+	const chars = word.toLowerCase().split("");
+	const targetpos = Math.floor(Math.random() * word.length);
+	const targetchar = chars[targetpos];
+	console.log("TARGET", targetchar, "POS", targetpos);
+	let pool = consonants.concat(vowels);
+	if(vowels.includes(targetchar))
+		pool = vowels;
+	if(consonants.includes(targetchar))
+		pool = consonants;
+	
+	pool = [...pool]; // Shallow copy to avoid splice mutating the original array
+	console.log("POOL BEFORE", pool);
+	if(pool.includes(targetchar)) {
+		console.log("REMOVING TARGET");
+		pool.splice(pool.indexOf(targetchar), 1);
+	}
+	console.log("POOL AFTER", pool);
+	
+	let newchar = random_item(pool);
+	chars.splice(targetpos, 1, newchar);
+	return chars.join("");
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..21e5402
--- /dev/null
+++ b/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "mutate-a-word",
+  "version": "1.0.0",
+  "description": "Mutate a word to find a cool name!",
+  "main": "js/index.mjs",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://git.starbeamrainbowlabs.com/sbrl/mutate-a-word.git"
+  },
+  "author": "Starbeamrainbowlabs",
+  "license": "MPL-2.0"
+}
