├── 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 | 
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 |
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 |
--------------------------------------------------------------------------------