├── CNAME ├── 128.png ├── 16.png ├── 32.png ├── 64.png ├── emojin_favicon.xcf ├── README.md ├── index.html ├── style.css ├── emojin.js └── emojin_es5.js /CNAME: -------------------------------------------------------------------------------- 1 | https://emojin.xyz 2 | -------------------------------------------------------------------------------- /128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebyollin/emojin/HEAD/128.png -------------------------------------------------------------------------------- /16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebyollin/emojin/HEAD/16.png -------------------------------------------------------------------------------- /32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebyollin/emojin/HEAD/32.png -------------------------------------------------------------------------------- /64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebyollin/emojin/HEAD/64.png -------------------------------------------------------------------------------- /emojin_favicon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madebyollin/emojin/HEAD/emojin_favicon.xcf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Emojin 2 | A simple client-side kaomoji generator using a context-free grammar. 3 | 4 | ![Screenshot](http://i.imgur.com/jD48BT1.png "Emojin in action") 5 | 6 | [Try it out](http://emojin.xyz/) 7 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Emojin, the Kaomoji Generator 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | \( • ロ • )/ 17 |
18 | wow so many! 19 |
20 |
21 | 22 | GenerateKaomoji! 23 | 24 |
25 | 27 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Inconsolata", "Monaco", sans-serif; 3 | font-size: 1.2em; 4 | min-width: 640px; 5 | margin: 0; 6 | padding: 0; 7 | position: relative; 8 | } 9 | 10 | * { 11 | box-sizing: border-box; 12 | } 13 | 14 | #wrapper { 15 | margin-top: 15vh; 16 | text-align: center; 17 | height: 1em; 18 | transition: .1s ease all; 19 | } 20 | #soManyMessage { 21 | position: absolute; 22 | color: rgb(32,160,255); 23 | transform: rotate(-25deg); 24 | top: 0; 25 | right: 75%; 26 | display: none; 27 | text-align: center; 28 | } 29 | 30 | a.button { 31 | padding: .5em 1em; 32 | line-height: 1.5em; 33 | color: white; 34 | background: rgb(32,160,255); 35 | text-decoration: none; 36 | border-radius: 128px; 37 | transition: .1s ease all; 38 | box-shadow: 0px 8px 0px -8px white; 39 | } 40 | 41 | a.button:hover { 42 | box-shadow: 0px 8px 16px -8px rgba(32,160,255,0.5); 43 | font-size: 1.525em; 44 | } 45 | 46 | a.button:active { 47 | box-shadow: 0px 8px 16px -8px white; 48 | background: rgb(32,128,240); 49 | font-size: 1.475em; 50 | color: hsl(0,0%,95%); 51 | } 52 | 53 | a.button input { 54 | background: transparent; 55 | color: inherit; 56 | font-family: inherit; 57 | font-size: inherit; 58 | border: none; 59 | padding:none; 60 | } 61 | 62 | .bigButton { 63 | font-size: 1.5em; 64 | cursor: pointer; 65 | } 66 | 67 | 68 | #display { 69 | top: 20vh; 70 | margin: auto; 71 | width: 40%; 72 | min-width: 640px; 73 | border: 1px solid hsl(0,0%,95%); 74 | border-radius: 1em; 75 | text-align: center; 76 | font-size: 1.2em; 77 | padding: 0; 78 | padding-top: 1.5em; 79 | background: white; 80 | margin-bottom: 4em; 81 | box-shadow: 0px 0px 32px 32px white; 82 | } 83 | 84 | #display li { 85 | margin: 0; 86 | list-style-type: none; 87 | height: 3em; 88 | line-height: 3em; 89 | border-top: 1px solid hsl(0,0%,95%); 90 | } 91 | #display li:first-child { 92 | border: none; 93 | } 94 | 95 | #howMany { 96 | width: 2em; 97 | margin: 0; 98 | padding: 0; 99 | text-align: center; 100 | } 101 | 102 | #footer { 103 | display: block; 104 | position: fixed; 105 | bottom: 0px; 106 | text-align: center; 107 | font-size: 0.8em; 108 | padding: 1.5em; 109 | width: 100%; 110 | color: hsl(0,0%,50%); 111 | z-index: -1; 112 | } 113 | 114 | #footer a { 115 | text-decoration: none; 116 | color: rgb(32,160,255); 117 | } 118 | #footer a:hover { 119 | text-decoration: none; 120 | color: rgb(32,128,240); 121 | } 122 | -------------------------------------------------------------------------------- /emojin.js: -------------------------------------------------------------------------------- 1 | // Customizations and utilities 2 | Array.prototype.randomElement = function(){ 3 | return this[Math.floor(Math.random() * this.length)]; 4 | } 5 | 6 | function debug(args, rating = 0) { 7 | const level = 0; 8 | if (rating < level) { 9 | console.log(args); 10 | } 11 | } 12 | 13 | // A context-free grammar with functions to generate random, valid instances 14 | // of the language 15 | class Grammar { 16 | constructor(grammarJSON, nameOfInitialSymbol) { 17 | // Throw exception if nameOfInitialSymbol is not present in grammar 18 | if (!(nameOfInitialSymbol in grammarJSON)) { 19 | throw nameOfInitialSymbol + " must be a valid non-terminal in the grammar for it to be usable"; 20 | } 21 | 22 | this.grammarJSON = grammarJSON; 23 | this.initial = nameOfInitialSymbol; 24 | } 25 | 26 | generateOne(current = this.initial) { 27 | debug(`generateOne(${current})`, 1); 28 | 29 | // if the current token is already a non-terminal, return it 30 | if (!(current in this.grammarJSON)) { 31 | return current; 32 | } 33 | 34 | // otherwise, pick a random expresssion for the current token 35 | const options = this.grammarJSON[current]; 36 | const option = options.randomElement(); 37 | debug(`\tselected ${option} from ${options}`, 1); 38 | 39 | // split it up, recurse on each part, and concatenate the results 40 | const parts = option.split(" "); 41 | let result = this.generateOne(parts[0]); 42 | for (let i = 1; i < parts.length; i++) { 43 | result += " " + this.generateOne(parts[i]); 44 | } 45 | return result; 46 | } 47 | 48 | generate(howMany) { 49 | const strings = []; 50 | for (let i = 0; i < howMany; i++) { 51 | let result = this.generateOne(); 52 | while (strings.includes(result)) { 53 | debug(`skipped duplicate of ${result}`); 54 | result = this.generateOne(); 55 | } 56 | debug(`Received generated string ${result} from grammar`); 57 | strings.push(result); 58 | } 59 | return strings; 60 | } 61 | } 62 | 63 | // Generates list items containing countInput.value instances from the given grammar 64 | // and appends them to displayContainer 65 | class GrammarDisplayer { 66 | constructor(grammar, countInput, displayContainer) { 67 | if (!( grammar && countInput && displayContainer)) { 68 | throw "arguments to constructor cannot be undefined"; 69 | } 70 | this.generator = grammar; 71 | this.howManyInput = countInput; 72 | this.displayContainer = displayContainer; 73 | } 74 | 75 | display() { 76 | var self = this; 77 | this.generator.generate(this.howMany()).map((string) => 78 | const li = document.createElement("li"); 79 | li.textContent = string; 80 | self.displayContainer.appendChild(li); 81 | ); 82 | } 83 | 84 | howMany() { 85 | return this.howManyInput.value; 86 | } 87 | 88 | clear() { 89 | let current = this.displayContainer.firstChild; 90 | while (current) { 91 | this.displayContainer.removeChild(current); 92 | current = this.displayContainer.firstChild; 93 | } 94 | } 95 | } 96 | 97 | // Setup 98 | 99 | function init() { 100 | // Configuration 101 | const displayer = new GrammarDisplayer( 102 | new Grammar({ 103 | "kaomoji" : ["head" , "bracketed_head", "symmetrically_armed_head" , "face"], 104 | "head" : ["( face )", "[ face ]", "༼ face ༽"], 105 | "bracketed_head": ["left_arm head right_arm"], 106 | "symmetrically_armed_head" : ["d( face )b", "(╯ face )╯", "(っ face ς)", "ᕕ( face )ᕗ","(つ face )つ", 107 | "ᕦ( face )ᕤ", "(づ face )づ"], 108 | "face" : ["^ mouth ^", "• mouth •", "o mouth o", "O mouth O", "> mouth <", "x mouth x", 109 | "' mouth '", "; mouth ;", " ̿ mouth ̿", "- mouth -", "* mouth *", "´ mouth ´", 110 | "~ mouth ~", "• mouth <", "> mouth •", "ಠ mouth ಠ", "¬ mouth ¬", " ̄ mouth  ̄", 111 | "ಥ mouth ಥ", "ʘ mouth ʘ", "◎ mouth ◎", "•́ mouth •̀", "T mouth T", "⌒ mouth ⌒", 112 | " ˃̶ mouth ˂̶", "ര mouth ര", "⇀ mouth ⇀", "╥ mouth ╥", "´ mouth ´", 113 | "˘ mouth ˘", "❛ mouth ❛", "৺ mouth ৺", "୨ mouth ୧", "눈 mouth 눈", 114 | "° mouth °", "⌐■ mouth ■", "≖ mouth ≖", "•̀ mouth •́", "◕ mouth ◕", "⊙ mouth ☉", 115 | "ᗒ mouth ᗕ", " ͒ mouth ͒ ", "´・ mouth ・`"], 116 | "mouth" : ["_", ".", "__", "д", "ω", "-", ",", "////", "皿", "益", "人", 117 | "﹏", "ㅿ", "□", "ʖ", "ᴗ", "ㅂ", "▂", "ᴥ", "〜", "∀", "ロ", 118 | "▿▿▿▿", "ヮ", "ڡ", "︿", "‸", "ϖ", "˫", "֊", "෴", "³", "ᗣ"], 119 | "left_arm" : ["\\", "c", "┗", "ヽ", "〜", "┌", "ヾ", "\"], 120 | "right_arm" : ["/", "7", "~", "┛", "ノ", "〜", "┘", "⊃", "ง", "ゞ", "ノ*:・゚✧", 121 | "乂 head ノ", "❤ head", "و"] 122 | }, "kaomoji"), 123 | document.getElementById("howMany"), 124 | document.getElementById("display") 125 | ); 126 | 127 | // Event binding 128 | document.getElementById("generate").addEventListener("click", function() { 129 | displayer.display(); 130 | }); 131 | 132 | // Easter egg display for maximum value of the input field 133 | document.getElementById("howMany").addEventListener("input", function() { 134 | if (this.value == this.max) { 135 | document.getElementById("soManyMessage").style.display = "block"; 136 | } else { 137 | document.getElementById("soManyMessage").style.display = "none"; 138 | } 139 | }); 140 | 141 | // Initial generation 142 | displayer.display(); 143 | } 144 | 145 | window.onload = init; 146 | -------------------------------------------------------------------------------- /emojin_es5.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _createClass = function() { 4 | function defineProperties(target, props) { 5 | for (var i = 0; i < props.length; i++) { 6 | var descriptor = props[i]; 7 | descriptor.enumerable = descriptor.enumerable || false; 8 | descriptor.configurable = true; 9 | if ("value" in descriptor) descriptor.writable = true; 10 | Object.defineProperty(target, descriptor.key, descriptor); 11 | } 12 | } 13 | return function(Constructor, protoProps, staticProps) { 14 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 15 | if (staticProps) defineProperties(Constructor, staticProps); 16 | return Constructor; 17 | }; 18 | }(); 19 | 20 | function _classCallCheck(instance, Constructor) { 21 | if (!(instance instanceof Constructor)) { 22 | throw new TypeError("Cannot call a class as a function"); 23 | } 24 | } 25 | 26 | // Customizations and utilities 27 | Array.prototype.randomElement = function() { 28 | return this[Math.floor(Math.random() * this.length)]; 29 | }; 30 | 31 | function debug(args) { 32 | var rating = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; 33 | 34 | var level = 0; 35 | if (rating < level) { 36 | console.log(args); 37 | } 38 | } 39 | 40 | // A context-free grammar with functions to generate random, valid instances 41 | // of the language 42 | 43 | var Grammar = function() { 44 | function Grammar(grammarJSON, nameOfInitialSymbol) { 45 | _classCallCheck(this, Grammar); 46 | 47 | // Throw exception if nameOfInitialSymbol is not present in grammar 48 | if (!(nameOfInitialSymbol in grammarJSON)) { 49 | throw nameOfInitialSymbol + " must be a valid non-terminal in the grammar for it to be usable"; 50 | } 51 | 52 | this.grammarJSON = grammarJSON; 53 | this.initial = nameOfInitialSymbol; 54 | } 55 | 56 | _createClass(Grammar, [{ 57 | key: "generateOne", 58 | value: function generateOne() { 59 | var current = arguments.length <= 0 || arguments[0] === undefined ? this.initial : arguments[0]; 60 | 61 | debug("generateOne(" + current + ")", 1); 62 | 63 | // if the current token is already a non-terminal, return it 64 | if (!(current in this.grammarJSON)) { 65 | return current; 66 | } 67 | 68 | // otherwise, pick a random expresssion for the current token 69 | var options = this.grammarJSON[current]; 70 | var option = options.randomElement(); 71 | debug("\tselected " + option + " from " + options, 1); 72 | 73 | // split it up, recurse on each part, and concatenate the results 74 | var parts = option.split(" "); 75 | var result = this.generateOne(parts[0]); 76 | for (var i = 1; i < parts.length; i++) { 77 | result += " " + this.generateOne(parts[i]); 78 | } 79 | return result; 80 | } 81 | }, { 82 | key: "generate", 83 | value: function generate(howMany) { 84 | var strings = []; 85 | for (var i = 0; i < howMany; i++) { 86 | var result = this.generateOne(); 87 | while (strings.includes(result)) { 88 | debug("skipped duplicate of " + result); 89 | result = this.generateOne(); 90 | } 91 | debug("Received generated string " + result + " from grammar"); 92 | strings.push(result); 93 | } 94 | return strings; 95 | } 96 | }]); 97 | 98 | return Grammar; 99 | }(); 100 | 101 | // Generates list items containing countInput.value instances from the given grammar 102 | // and appends them to displayContainer 103 | 104 | 105 | var GrammarDisplayer = function() { 106 | function GrammarDisplayer(grammar, countInput, displayContainer) { 107 | _classCallCheck(this, GrammarDisplayer); 108 | 109 | if (!(grammar && countInput && displayContainer)) { 110 | throw "arguments to constructor cannot be undefined"; 111 | } 112 | this.generator = grammar; 113 | this.howManyInput = countInput; 114 | this.displayContainer = displayContainer; 115 | } 116 | 117 | _createClass(GrammarDisplayer, [{ 118 | key: "display", 119 | value: function display() { 120 | this.clear(); 121 | debug("Generating " + this.howMany() + " strings"); 122 | var strings = this.generator.generate(this.howMany()); 123 | debug("Displaying the following strings: " + strings); 124 | for (var i = 0; i < strings.length; i++) { 125 | debug("----------------", 1); 126 | var li = document.createElement("li"); 127 | li.textContent = strings[i]; 128 | this.displayContainer.appendChild(li); 129 | } 130 | } 131 | }, { 132 | key: "howMany", 133 | value: function howMany() { 134 | return this.howManyInput.value; 135 | } 136 | }, { 137 | key: "clear", 138 | value: function clear() { 139 | var current = this.displayContainer.firstChild; 140 | while (current) { 141 | this.displayContainer.removeChild(current); 142 | current = this.displayContainer.firstChild; 143 | } 144 | } 145 | }]); 146 | 147 | return GrammarDisplayer; 148 | }(); 149 | 150 | // Setup 151 | 152 | function init() { 153 | // Configuration 154 | var displayer = new GrammarDisplayer(new Grammar({ 155 | "kaomoji": ["head", "bracketed_head", "symmetrically_armed_head", "face"], 156 | "head": ["( face )", "[ face ]", "༼ face ༽"], 157 | "bracketed_head": ["left_arm head right_arm"], 158 | "symmetrically_armed_head": ["d( face )b", "(╯ face )╯", "(っ face ς)", "ᕕ( face )ᕗ", "(つ face )つ", 159 | "ᕦ( face )ᕤ", "(づ face )づ" 160 | ], 161 | "face": ["^ mouth ^", "• mouth •", "o mouth o", "O mouth O", "> mouth <", "x mouth x", 162 | "' mouth '", "; mouth ;", " ̿ mouth ̿", "- mouth -", "* mouth *", "´ mouth ´", 163 | "~ mouth ~", "• mouth <", "> mouth •", "ಠ mouth ಠ", "¬ mouth ¬", " ̄ mouth  ̄", 164 | "ಥ mouth ಥ", "ʘ mouth ʘ", "◎ mouth ◎", "•́ mouth •̀", "T mouth T", "⌒ mouth ⌒", 165 | " ˃̶ mouth ˂̶", "ര mouth ര", "⇀ mouth ⇀", "╥ mouth ╥", "´ mouth ´", 166 | "˘ mouth ˘", "❛ mouth ❛", "৺ mouth ৺", "୨ mouth ୧", "눈 mouth 눈", 167 | "° mouth °", "⌐■ mouth ■", "≖ mouth ≖", "•̀ mouth •́", "◕ mouth ◕", "⊙ mouth ☉", 168 | "ᗒ mouth ᗕ", " ͒ mouth ͒ ", "´・ mouth ・`" 169 | ], 170 | "mouth": ["_", ".", "__", "д", "ω", "-", ",", "////", "皿", "益", "人", 171 | "﹏", "ㅿ", "□", "ʖ", "ᴗ", "ㅂ", "▂", "ᴥ", "〜", "∀", "ロ", 172 | "▿▿▿▿", "ヮ", "ڡ", "︿", "‸", "ϖ", "˫", "֊", "෴", "³", "ᗣ" 173 | ], 174 | "left_arm": ["\\", "c", "┗", "ヽ", "〜", "┌", "ヾ", "\"], 175 | "right_arm": ["/", "7", "~", "┛", "ノ", "〜", "┘", "⊃", "ง", "ゞ", "ノ*:・゚✧", 176 | "乂 head ノ", "❤ head", "و" 177 | ] 178 | }, "kaomoji"), document.getElementById("howMany"), document.getElementById("display")); 179 | 180 | // Event binding 181 | document.getElementById("generate").addEventListener("click", function() { 182 | displayer.display(); 183 | }); 184 | 185 | // Easter egg display for maximum value of the input field 186 | document.getElementById("howMany").addEventListener("input", function() { 187 | if (this.value == this.max) { 188 | document.getElementById("soManyMessage").style.display = "block"; 189 | } else { 190 | document.getElementById("soManyMessage").style.display = "none"; 191 | } 192 | }); 193 | 194 | // Initial generation 195 | displayer.display(); 196 | } 197 | 198 | window.onload = init; 199 | --------------------------------------------------------------------------------