├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── README.md ├── bundle.js ├── index.html ├── package.json ├── src ├── eval.js ├── i18n.js ├── index.js └── style.scss └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | ; http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | indent_size = 4 15 | 16 | [node_modules/**.js] 17 | codepaint = false 18 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "env": { 4 | "browser": true 5 | }, 6 | "rules": { 7 | "semi": [2, "never"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #[JavaScript24](https://handsomeone.github.io/JavaScript24/) 2 | 3 | A minimal arithmetical game with JavaScript syntax. 4 | 5 | [![demo](http://i.imgur.com/ih9nPau.gif)](https://handsomeone.github.io/JavaScript24/) 6 | 7 | If embedded in another page, use the following code to sync `iframe`'s height. 8 | 9 | ```javascript 10 | onmessage = function (e) { 11 | if (e.origin.split('//')[1] === 'handsomeone.github.io') { 12 | document.querySelector('iframe').height = e.data.height 13 | } 14 | } 15 | ``` 16 | -------------------------------------------------------------------------------- /bundle.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(a){if(o[a])return o[a].exports;var i=o[a]={exports:{},id:a,loaded:!1};return e[a].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var o={};return t.m=e,t.c=o,t.p="",t(0)}([function(e,t,o){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){return document.querySelector(e)}function n(e){return document.querySelectorAll(e)}function r(){i("#won").innerHTML=localStorage.won,i("#total").innerHTML=localStorage.total,i("#average-length").innerHTML=(+localStorage.averageLength).toFixed(3)}function s(){for(var e=0;e<4;e+=1)for(;y[e].children.length>1;)y[e].removeChild(y[e].children[0])}function l(){for(var e=0;e<4;e+=1){var t=document.createElement("div");t.className="style-"+Math.floor(4*Math.random()),t.innerHTML=localStorage.digits[e],y[e].className="animation-"+Math.floor(6*Math.random()),y[e].appendChild(t)}setTimeout(s,1250),r()}function d(){localStorage.digits=(""+Math.random()).substr(2,4),l(),b.value="",b.removeAttribute("disabled"),b.focus()}function c(){parent.postMessage({height:document.body.clientHeight},"*")}function f(){localStorage.averageLength=(localStorage.averageLength*localStorage.won+b.value.length)/(+localStorage.won+1),localStorage.won=+localStorage.won+1,localStorage.total=+localStorage.total+1,w.className="animate",v.className="animate",v.innerHTML=" = "+b.value,b.setAttribute("disabled",""),setTimeout(function(){r(),k.className="hover"},250),setTimeout(function(){k.className=""},1750),setTimeout(function(){w.className="",w.innerHTML="",d()},2250),setTimeout(function(){v.className="",v.innerHTML=""},2500)}function u(e){for(var t=b.value,o=t.replace(/(\d|\))-/g,/$1/).replace(/&&|\|\|/g,"#").replace(/>>|<<|[+*\/%~^&|()]/g,"").replace(/0x|0o/gi,"0"),a=0,i=0;i<4;i+=1)o.match(localStorage.digits[i])&&(o=o.replace(localStorage.digits[i],""),a+=1);var n=!0;24!==e&&(x[1].className="bad",n=!1),(4!==a||o.match(/\d/))&&(x[2].className="bad",o=o.replace(/\d/g,""),n=!1),o.match(/-/)&&(x[4].className="bad",o=o.replace(/-/g,""),n=!1),o&&(x[3].className="bad",n=!1),n&&f()}o(6);var m=o(2),p=a(m),g=o(3),h=a(g);(0,h.default)();var b=i("#expr"),w=i("#result"),v=i("#solution"),k=i("#stats"),y=n("#digits li"),x=n("#rules li");localStorage.won=localStorage.won||0,localStorage.total=localStorage.total||0,localStorage.averageLength=localStorage.averageLength||0,localStorage.digits=localStorage.digits||(""+Math.random()).substr(2,4),parent!==window&&(window.addEventListener("resize",c),c()),setInterval(function(){b.placeholder=document.activeElement===b||b.placeholder?"":"█"},500),l(),b.addEventListener("keydown",function(e){32===e.keyCode&&e.preventDefault()}),b.addEventListener("focus",function(){b.placeholder=""}),b.addEventListener("input",function(){var e=b.value,t=b.value.replace(/\s/g,"");e!==t&&(b.value=t);for(var o=0,a=x.length;otype skip",rule0:"Find a valid JavaScript expression",rule1:"whose value is (Number)24.",rule2:"Each of the four given numerical digits must appear once, and no other digits are allowed.",rule3:"Besides digits, only these operators are allowed: + - * / % ~ & | ^ << >> ( ) and x o(after a given 0).
* Can you do it like a boss? Try not to use parentheses ( ).",boss:"",rule4:"In additional, to avoid -~ from appearing continuously, - is not allowed to be used as a unary operator."},zh:{won:"已获胜",total:"总局数",averageLength:"平均长度",tooHard:"太难了?
输入 skip",rule0:"求一个合法的 JavaScript 表达式,",rule1:"其值为整数 24。",rule2:"四个给定的数字恰好在表达式中出现一次,未给定数字不允许出现。",rule3:"除数字外,表达式中仅允许包含下列运算符:+ - * / % ~ & | ^ << >> ( ) 以及 x o(在给定的 0 后面)。
* 挑战自己!试着不使用小括号 ( )",rule4:"另外,为了防止 -~ 连续出现,不允许- 用作一元运算符。"}},t="en",o=0;odiv{width:100%;height:100%;position:absolute}#digits li.animation-0>div:last-child{top:0;left:100%;-webkit-animation:animation-0 .5s ease-out forwards;animation:animation-0 .5s ease-out forwards}#digits li.animation-1>div:last-child{top:0;left:-100%;-webkit-animation:animation-1 .5s ease-out forwards;animation:animation-1 .5s ease-out forwards}#digits li.animation-2>div:last-child{left:0;top:100%;-webkit-animation:animation-2 .5s ease-out forwards;animation:animation-2 .5s ease-out forwards}#digits li.animation-3>div:last-child{left:0;top:-100%;-webkit-animation:animation-3 .5s ease-out forwards;animation:animation-3 .5s ease-out forwards}#digits li.animation-4>div{-webkit-transform:scaleX(1);transform:scaleX(1);-webkit-animation:animation-4-old .5s ease-in forwards;animation:animation-4-old .5s ease-in forwards}#digits li.animation-4>div:last-child{-webkit-transform:scaleX(0);transform:scaleX(0);-webkit-animation:animation-4 .5s ease-out forwards;animation:animation-4 .5s ease-out forwards}#digits li.animation-5>div{-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-animation:animation-5-old .5s ease-in forwards;animation:animation-5-old .5s ease-in forwards}#digits li.animation-5>div:last-child{-webkit-transform:scaleY(0);transform:scaleY(0);-webkit-animation:animation-5 .5s ease-out forwards;animation:animation-5 .5s ease-out forwards}#digits li:nth-child(2)>div{-webkit-animation-delay:.25s!important;animation-delay:.25s!important}#digits li:nth-child(3)>div{-webkit-animation-delay:.5s!important;animation-delay:.5s!important}#digits li:nth-child(4)>div{-webkit-animation-delay:.75s!important;animation-delay:.75s!important}@-webkit-keyframes animation-0{to{left:0}}@keyframes animation-0{to{left:0}}@-webkit-keyframes animation-1{to{left:0}}@keyframes animation-1{to{left:0}}@-webkit-keyframes animation-2{to{top:0}}@keyframes animation-2{to{top:0}}@-webkit-keyframes animation-3{to{top:0}}@keyframes animation-3{to{top:0}}@-webkit-keyframes animation-4-old{50%{-webkit-transform:scaleX(0);transform:scaleX(0)}to{-webkit-transform:scaleX(0);transform:scaleX(0)}}@keyframes animation-4-old{50%{-webkit-transform:scaleX(0);transform:scaleX(0)}to{-webkit-transform:scaleX(0);transform:scaleX(0)}}@-webkit-keyframes animation-4{50%{-webkit-transform:scaleX(0);transform:scaleX(0)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes animation-4{50%{-webkit-transform:scaleX(0);transform:scaleX(0)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@-webkit-keyframes animation-5-old{50%{-webkit-transform:scaleY(0);transform:scaleY(0)}to{-webkit-transform:scaleY(0);transform:scaleY(0)}}@keyframes animation-5-old{50%{-webkit-transform:scaleY(0);transform:scaleY(0)}to{-webkit-transform:scaleY(0);transform:scaleY(0)}}@-webkit-keyframes animation-5{50%{-webkit-transform:scaleY(0);transform:scaleY(0)}to{-webkit-transform:scaleY(1);transform:scaleY(1)}}@keyframes animation-5{50%{-webkit-transform:scaleY(0);transform:scaleY(0)}to{-webkit-transform:scaleY(1);transform:scaleY(1)}}#hints{position:absolute;top:0;right:0;width:1.5625em;height:1.5625em;text-align:center;background:#373a33;color:#ffcd00;display:table;opacity:0;-webkit-transition:opacity .5s ease;transition:opacity .5s ease}#hints:hover{opacity:1}#hints>div{font-size:.25em;display:table-cell;vertical-align:middle}#hints code{font-weight:700;background-color:#ffcd00;color:#373a33;padding:0 .2em}#expr-wrapper{position:relative;font-family:Fira Code,monospace}#expr-caret{position:absolute;top:50%;left:16px;color:#fff;line-height:0;opacity:.5}#expr{font-family:Fira Code,monospace;background-color:#373a33;color:#fff;border-radius:0;margin:0;width:100%;border-width:0;box-sizing:border-box;font-size:20px;line-height:20px;padding:10px;padding-left:36px;text-align:right}#expr:focus{outline:0 none}#rules{margin:16px 0;padding:0}#rules li{list-style-type:none;line-height:22px;padding:4px 16px;-webkit-transition:background-color .5s;transition:background-color .5s}#rules li b{text-transform:uppercase;text-shadow:0 -4px 0 #fff}#rules li.bad{background-color:#ffcd00}#rules code{font-weight:700;background-color:#373a33;color:#ffcd00;padding:0 .2em}footer{text-align:center;height:32px;line-height:32px;position:relative;border:4px solid #373a33;-webkit-transition:background-color .5s;transition:background-color .5s}footer:hover{background-color:#ffcd00}footer a{text-decoration:none;font-weight:700;color:#373a33;display:block}',""])},function(e,t,o){function a(e,t){for(var o=0;o=0&&v.splice(t,1)}function s(e){var t=document.createElement("style");return t.type="text/css",n(e,t),t}function l(e){var t=document.createElement("link");return t.rel="stylesheet",n(e,t),t}function d(e,t){var o,a,i;if(t.singleton){var n=w++;o=b||(b=s(t)),a=c.bind(null,o,n,!1),i=c.bind(null,o,n,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(o=l(t),a=u.bind(null,o),i=function(){r(o),o.href&&URL.revokeObjectURL(o.href)}):(o=s(t),a=f.bind(null,o),i=function(){r(o)});return a(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;a(e=t)}else i()}}function c(e,t,o,a){var i=o?"":a.css;if(e.styleSheet)e.styleSheet.cssText=k(t,i);else{var n=document.createTextNode(i),r=e.childNodes;r[t]&&e.removeChild(r[t]),r.length?e.insertBefore(n,r[t]):e.appendChild(n)}}function f(e,t){var o=t.css,a=t.media;if(a&&e.setAttribute("media",a),e.styleSheet)e.styleSheet.cssText=o;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(o))}}function u(e,t){var o=t.css,a=t.sourceMap;a&&(o+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(a))))+" */");var i=new Blob([o],{type:"text/css"}),n=e.href;e.href=URL.createObjectURL(i),n&&URL.revokeObjectURL(n)}var m={},p=function(e){var t;return function(){return"undefined"==typeof t&&(t=e.apply(this,arguments)),t}},g=p(function(){return/msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase())}),h=p(function(){return document.head||document.getElementsByTagName("head")[0]}),b=null,w=0,v=[];e.exports=function(e,t){t=t||{},"undefined"==typeof t.singleton&&(t.singleton=g()),"undefined"==typeof t.insertAt&&(t.insertAt="bottom");var o=i(e);return a(o,t),function(e){for(var n=[],r=0;r 2 | 3 | 4 | 5 | 6 | 24 7 | 8 | 9 | 10 | 11 |
12 |

13 |
14 |
JS
15 |
16 | 24 17 | 18 |
19 |
20 |

21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
{{won}}
{{total}}
{{averageLength}}
36 |
37 |
    38 |
  • 39 |
  • 40 |
  • 41 |
  • 42 |
43 |
44 |
{{tooHard}}
45 |
46 |
47 |
48 |
>
49 | 50 |
51 | 52 |
    53 |
  1. {{rule0}}
  2. 54 |
  3. {{rule1}}
  4. 55 |
  5. {{rule2}}
  6. 56 |
  7. {{rule3}}
  8. 57 |
  9. {{rule4}}
  10. 58 |
59 | 60 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript-24", 3 | "version": "1.0.0", 4 | "description": "A minimal arithmetical game with JavaScript syntax.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server -d --inline --hot", 8 | "build": "webpack -p" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/HandsomeOne/JavaScript24.git" 13 | }, 14 | "keywords": [ 15 | "javascript", 16 | "arithmetic", 17 | "game" 18 | ], 19 | "author": "Zhou Qi ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/HandsomeOne/JavaScript24/issues" 23 | }, 24 | "homepage": "https://github.com/HandsomeOne/JavaScript24#readme", 25 | "devDependencies": { 26 | "autoprefixer": "^6.5.1", 27 | "babel-core": "^6.17.0", 28 | "babel-loader": "^6.2.5", 29 | "babel-preset-latest": "^6.16.0", 30 | "css-loader": "^0.25.0", 31 | "eslint": "^3.8.0", 32 | "eslint-config-airbnb-base": "^9.0.0", 33 | "eslint-plugin-import": "^2.0.1", 34 | "node-sass": "^3.10.1", 35 | "postcss-loader": "^1.0.0", 36 | "sass-loader": "^4.0.2", 37 | "style-loader": "^0.13.1", 38 | "webpack": "^1.13.2", 39 | "webpack-dev-server": "^1.16.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/eval.js: -------------------------------------------------------------------------------- 1 | export default function (e) { 2 | /* eslint no-eval: off */ 3 | return eval(e) 4 | } 5 | -------------------------------------------------------------------------------- /src/i18n.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | const i18n = { 3 | en: { 4 | won: 'won', 5 | total: 'total', 6 | averageLength: 'average length', 7 | tooHard: 'too difficult?
type skip', 8 | rule0: 'Find a valid JavaScript expression', 9 | rule1: 'whose value is (Number)24.', 10 | rule2: 'Each of the four given numerical digits must appear once, and no other digits are allowed.', 11 | rule3: 'Besides digits, only these operators are allowed: + - * / % ~ & | ^ << >> ( ) and x o(after a given 0).
* Can you do it like a boss? Try not to use parentheses ( ).', 12 | boss: '', 13 | rule4: 'In additional, to avoid -~ from appearing continuously, - is not allowed to be used as a unary operator.', 14 | }, 15 | zh: { 16 | won: '已获胜', 17 | total: '总局数', 18 | averageLength: '平均长度', 19 | tooHard: '太难了?
输入 skip', 20 | rule0: '求一个合法的 JavaScript 表达式,', 21 | rule1: '其值为整数 24。', 22 | rule2: '四个给定的数字恰好在表达式中出现一次,未给定数字不允许出现。', 23 | rule3: '除数字外,表达式中仅允许包含下列运算符:+ - * / % ~ & | ^ << >> ( ) 以及 x o(在给定的 0 后面)。
* 挑战自己!试着不使用小括号 ( )', 24 | rule4: '另外,为了防止 -~ 连续出现,不允许- 用作一元运算符。', 25 | }, 26 | } 27 | 28 | let language = 'en' 29 | for (let i = 0; i < navigator.languages.length; i += 1) { 30 | const lang = navigator.languages[i].toLowerCase() 31 | if (lang in i18n) { 32 | language = lang 33 | break 34 | } 35 | } 36 | document.body.innerHTML = 37 | document.body.innerHTML.replace(/\{\{.*?\}\}/g, match => i18n[language][match.slice(2, -2)]) 38 | } 39 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './style.scss' 2 | import evaluate from './eval' 3 | import i18n from './i18n' 4 | 5 | i18n() 6 | 7 | function $(selector) { 8 | return document.querySelector(selector) 9 | } 10 | function $$(selector) { 11 | return document.querySelectorAll(selector) 12 | } 13 | const $expr = $('#expr') 14 | const $result = $('#result') 15 | const $solution = $('#solution') 16 | const $stats = $('#stats') 17 | const $digits = $$('#digits li') 18 | const $rules = $$('#rules li') 19 | 20 | function showStats() { 21 | $('#won').innerHTML = localStorage.won 22 | $('#total').innerHTML = localStorage.total 23 | $('#average-length').innerHTML = (+localStorage.averageLength).toFixed(3) 24 | } 25 | 26 | function removeOldDigits() { 27 | for (let i = 0; i < 4; i += 1) { 28 | while ($digits[i].children.length > 1) { 29 | $digits[i].removeChild($digits[i].children[0]) 30 | } 31 | } 32 | } 33 | function show() { 34 | for (let i = 0; i < 4; i += 1) { 35 | const digit = document.createElement('div') 36 | digit.className = `style-${Math.floor(Math.random() * 4)}` 37 | digit.innerHTML = localStorage.digits[i] 38 | $digits[i].className = `animation-${Math.floor(Math.random() * 6)}` 39 | $digits[i].appendChild(digit) 40 | } 41 | setTimeout(removeOldDigits, 1250) 42 | 43 | showStats() 44 | } 45 | function refresh() { 46 | localStorage.digits = (`${Math.random()}`).substr(2, 4) 47 | show() 48 | 49 | $expr.value = '' 50 | $expr.removeAttribute('disabled') 51 | $expr.focus() 52 | } 53 | 54 | function resize() { 55 | parent.postMessage({ 56 | height: document.body.clientHeight, 57 | }, '*') 58 | } 59 | function success() { 60 | localStorage.averageLength = 61 | ((localStorage.averageLength * localStorage.won) + $expr.value.length) / (+localStorage.won + 1) 62 | localStorage.won = +localStorage.won + 1 63 | localStorage.total = +localStorage.total + 1 64 | 65 | $result.className = 'animate' 66 | $solution.className = 'animate' 67 | $solution.innerHTML = ` = ${$expr.value}` 68 | $expr.setAttribute('disabled', '') 69 | setTimeout(() => { 70 | showStats() 71 | $stats.className = 'hover' 72 | }, 250) 73 | setTimeout(() => { 74 | $stats.className = '' 75 | }, 1750) 76 | setTimeout(() => { 77 | $result.className = '' 78 | $result.innerHTML = '' 79 | refresh() 80 | }, 2250) 81 | setTimeout(() => { 82 | $solution.className = '' 83 | $solution.innerHTML = '' 84 | }, 2500) 85 | } 86 | function check(result) { 87 | const expr = $expr.value 88 | let e = expr 89 | .replace(/(\d|\))-/g, /$1/) 90 | .replace(/&&|\|\|/g, '#') 91 | .replace(/>>|<<|[+*/%~^&|()]/g, '') 92 | .replace(/0x|0o/ig, '0') 93 | let digitsAppeared = 0 94 | for (let i = 0; i < 4; i += 1) { 95 | if (e.match(localStorage.digits[i])) { 96 | e = e.replace(localStorage.digits[i], '') 97 | digitsAppeared += 1 98 | } 99 | } 100 | let valid = true 101 | if (result !== 24) { 102 | $rules[1].className = 'bad' 103 | valid = false 104 | } 105 | if (digitsAppeared !== 4 || e.match(/\d/)) { 106 | $rules[2].className = 'bad' 107 | e = e.replace(/\d/g, '') 108 | valid = false 109 | } 110 | if (e.match(/-/)) { 111 | $rules[4].className = 'bad' 112 | e = e.replace(/-/g, '') 113 | valid = false 114 | } 115 | if (e) { 116 | $rules[3].className = 'bad' 117 | valid = false 118 | } 119 | if (valid) { 120 | success() 121 | } 122 | } 123 | 124 | localStorage.won = localStorage.won || 0 125 | localStorage.total = localStorage.total || 0 126 | localStorage.averageLength = localStorage.averageLength || 0 127 | localStorage.digits = localStorage.digits || (`${Math.random()}`).substr(2, 4) 128 | if (parent !== window) { 129 | window.addEventListener('resize', resize) 130 | resize() 131 | } 132 | setInterval(() => { 133 | $expr.placeholder = document.activeElement === $expr || $expr.placeholder ? 134 | '' : '█' 135 | }, 500) 136 | show() 137 | 138 | $expr.addEventListener('keydown', (e) => { 139 | if (e.keyCode === 32) { 140 | e.preventDefault() 141 | } 142 | }) 143 | 144 | $expr.addEventListener('focus', () => { 145 | $expr.placeholder = '' 146 | }) 147 | 148 | $expr.addEventListener('input', () => { 149 | const oldValue = $expr.value 150 | const expr = $expr.value.replace(/\s/g, '') 151 | if (oldValue !== expr) { 152 | $expr.value = expr 153 | } 154 | 155 | for (let i = 0, l = $rules.length; i < l; i += 1) { 156 | $rules[i].className = '' 157 | } 158 | switch (expr.toLowerCase()) { 159 | case '': 160 | $result.innerHTML = '' 161 | return 162 | case 's': 163 | $result.innerHTML = '~ is useful' 164 | return 165 | case 'sk': 166 | $result.innerHTML = 'x<<y is x*2y' 167 | return 168 | case 'ski': 169 | $result.innerHTML = 'try & | ^' 170 | return 171 | case 'skip': 172 | localStorage.total = +localStorage.total + 1 173 | refresh() 174 | $stats.className = 'hover' 175 | setTimeout(() => { 176 | $stats.className = '' 177 | }, 1500) 178 | $result.innerHTML = '' 179 | return 180 | default: 181 | try { 182 | const result = evaluate($expr.value) 183 | if (result === null) { 184 | $result.innerHTML = 'null' 185 | } else { 186 | $result.innerHTML = result 187 | } 188 | check(result) 189 | } catch (e) { 190 | $result.innerHTML = expr 191 | $rules[0].className = 'bad' 192 | } 193 | } 194 | }) 195 | 196 | document.addEventListener('keydown', () => { 197 | $expr.focus() 198 | }) 199 | -------------------------------------------------------------------------------- /src/style.scss: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700); 2 | 3 | $light: white; 4 | $primary: #ffcd00; 5 | $dark: #373a33; 6 | 7 | ::selection { 8 | background-color: $dark; 9 | color: $primary; 10 | } 11 | body { 12 | font-family: "Source Sans Pro", sans-serif; 13 | margin: 0 auto; 14 | max-width: 960px; 15 | color: $dark; 16 | position: relative; 17 | text-rendering: optimizeLegibility; 18 | } 19 | code { 20 | font-family: 'Fira Code', monospace; 21 | } 22 | header { 23 | font-size: 4rem; 24 | overflow: hidden; 25 | @media (max-width: 520px) { 26 | & { 27 | font-size: 3rem; 28 | } 29 | } 30 | } 31 | 32 | h1 { 33 | font-size: 1em; 34 | font-weight: bold; 35 | margin: 0; 36 | height: 1.5625em; 37 | position: relative; 38 | letter-spacing: -0.05em; 39 | } 40 | #js-background { 41 | position: absolute; 42 | width: 0; 43 | height: 1.5625em; 44 | background-color: $primary; 45 | animation: js-background 0.5s ease-in forwards; 46 | } 47 | @keyframes js-background { 48 | to { width: 1.5625em; } 49 | } 50 | 51 | #js { 52 | position: absolute; 53 | bottom: 0; 54 | left: 0.125em; 55 | color: $dark; 56 | opacity: 0; 57 | animation: js 0.75s 0.25s ease-in-out forwards; 58 | } 59 | @keyframes js { 60 | to { left: 0.375em; opacity: 1; } 61 | } 62 | 63 | #twenty-four { 64 | position: absolute; 65 | left: 2em; 66 | bottom: 0; 67 | color: $primary; 68 | white-space: nowrap; 69 | opacity: 0; 70 | animation: twenty-four 0.75s 0.25s ease-in-out forwards; 71 | } 72 | @keyframes twenty-four { 73 | to { left: 1.75em; opacity: 1; } 74 | } 75 | 76 | #result { 77 | position: absolute; 78 | left: 1.75em; 79 | bottom: 0; 80 | max-width: calc(100% - 3.5em); 81 | white-space: nowrap; 82 | overflow: hidden; 83 | text-overflow: ellipsis; 84 | padding-right: 0.05em; 85 | box-sizing: border-box; 86 | &.animate { 87 | animation: fade-out 0.5s 1.75s forwards; 88 | } 89 | } 90 | @keyframes fade-out { 91 | from { opacity: 1; } 92 | to { opacity: 0; } 93 | } 94 | 95 | #solution { 96 | opacity: 0; 97 | position: relative; 98 | &::before { 99 | content: ""; 100 | display: block; 101 | height: 100%; 102 | background-color: $light; 103 | position: absolute; 104 | left: 0; 105 | top: 0; 106 | } 107 | &::after { 108 | content: ""; 109 | display: block; 110 | height: 100%; 111 | background-color: $light; 112 | position: absolute; 113 | width: 100%; 114 | top: 0; 115 | right: 0; 116 | } 117 | &.animate { 118 | opacity: 1; 119 | animation: fade-out 0.5s 2s forwards; 120 | } 121 | &.animate::before { 122 | animation: slide-in 0.5s 2s forwards; 123 | } 124 | &.animate::after { 125 | animation: slide-out 0.5s 0.25s forwards; 126 | } 127 | } 128 | @keyframes slide-in { 129 | from { width: 0; } 130 | to { width: 100%; } 131 | } 132 | @keyframes slide-out { 133 | from { width: 100%; } 134 | to { width: 0; } 135 | } 136 | 137 | #stats { 138 | background-color: $primary; 139 | width: 1.5625em; 140 | height: 1.5625em; 141 | position: absolute; 142 | top: 0; 143 | left: 0; 144 | opacity: 0; 145 | transition: opacity 0.5s; 146 | &.hover, 147 | &:hover { 148 | opacity: 1; 149 | } 150 | td { 151 | text-transform: uppercase; 152 | vertical-align: top; 153 | font-size: 0.1875em; 154 | padding: 0 0.2em; 155 | &[id] { 156 | text-align: right; 157 | font-weight: bold; 158 | } 159 | } 160 | } 161 | 162 | #digits { 163 | margin: 0; 164 | padding: 0; 165 | width: 1.5625em; 166 | height: 1.5625em; 167 | position: absolute; 168 | top: 0; 169 | right: 0; 170 | li { 171 | margin: 0; 172 | padding: 0; 173 | list-style: none; 174 | width: 50%; 175 | height: 50%; 176 | text-align: center; 177 | font-size: 50%; 178 | line-height: 1.5625em; 179 | font-weight: bold; 180 | position: absolute; 181 | overflow: hidden; 182 | &:nth-child(1) { 183 | top: 0; 184 | left: 0; 185 | } 186 | &:nth-child(2) { 187 | top: 0; 188 | left: 50%; 189 | } 190 | &:nth-child(3) { 191 | top: 50%; 192 | left: 0; 193 | } 194 | &:nth-child(4) { 195 | top: 50%; 196 | left: 50%; 197 | } 198 | } 199 | .style-0 { 200 | background: $light; 201 | color: $dark; 202 | } 203 | .style-1 { 204 | background: $primary; 205 | color: $dark; 206 | } 207 | .style-2 { 208 | background: $dark; 209 | color: $primary; 210 | } 211 | .style-3 { 212 | background: $dark; 213 | color: $light; 214 | } 215 | li > div { 216 | width: 100%; 217 | height: 100%; 218 | position: absolute; 219 | } 220 | li.animation-0 > div:last-child { 221 | top: 0; 222 | left: 100%; 223 | animation: animation-0 0.5s ease-out forwards; 224 | } 225 | li.animation-1 > div:last-child { 226 | top: 0; 227 | left: -100%; 228 | animation: animation-1 0.5s ease-out forwards; 229 | } 230 | li.animation-2 > div:last-child { 231 | left: 0; 232 | top: 100%; 233 | animation: animation-2 0.5s ease-out forwards; 234 | } 235 | li.animation-3 > div:last-child { 236 | left: 0; 237 | top: -100%; 238 | animation: animation-3 0.5s ease-out forwards; 239 | } 240 | li.animation-4 > div { 241 | transform: scaleX(1); 242 | animation: animation-4-old 0.5s ease-in forwards; 243 | } 244 | li.animation-4 > div:last-child { 245 | transform: scaleX(0); 246 | animation: animation-4 0.5s ease-out forwards; 247 | } 248 | li.animation-5 > div { 249 | transform: scaleY(1); 250 | animation: animation-5-old 0.5s ease-in forwards; 251 | } 252 | li.animation-5 > div:last-child { 253 | transform: scaleY(0); 254 | animation: animation-5 0.5s ease-out forwards; 255 | } 256 | li:nth-child(2) > div { 257 | animation-delay: 0.25s !important; 258 | } 259 | li:nth-child(3) > div { 260 | animation-delay: 0.5s !important; 261 | } 262 | li:nth-child(4) > div { 263 | animation-delay: 0.75s !important; 264 | } 265 | } 266 | @keyframes animation-0 { 267 | to { left: 0; } 268 | } 269 | @keyframes animation-1 { 270 | to { left: 0; } 271 | } 272 | @keyframes animation-2 { 273 | to { top: 0; } 274 | } 275 | @keyframes animation-3 { 276 | to { top: 0; } 277 | } 278 | @keyframes animation-4-old { 279 | 50% { transform: scaleX(0); } 280 | to { transform: scaleX(0); } 281 | } 282 | @keyframes animation-4 { 283 | 50% { transform: scaleX(0); } 284 | to { transform: scaleX(1); } 285 | } 286 | @keyframes animation-5-old { 287 | 50% { transform: scaleY(0); } 288 | to { transform: scaleY(0); } 289 | } 290 | @keyframes animation-5 { 291 | 50% { transform: scaleY(0); } 292 | to { transform: scaleY(1); } 293 | } 294 | 295 | #hints { 296 | position: absolute; 297 | top: 0; 298 | right: 0; 299 | width: 1.5625em; 300 | height: 1.5625em; 301 | text-align: center; 302 | background: $dark; 303 | color: $primary; 304 | display: table; 305 | opacity: 0; 306 | transition: opacity 0.5s ease; 307 | &:hover { 308 | opacity: 1; 309 | } 310 | & > div { 311 | font-size: 0.25em; 312 | display: table-cell; 313 | vertical-align: middle; 314 | } 315 | code { 316 | font-weight: bold; 317 | background-color: $primary; 318 | color: $dark; 319 | padding: 0 0.2em; 320 | } 321 | } 322 | 323 | #expr-wrapper { 324 | position: relative; 325 | font-family: 'Fira Code', monospace; 326 | } 327 | #expr-caret { 328 | position: absolute; 329 | top: 50%; 330 | left: 16px; 331 | color: $light; 332 | line-height: 0; 333 | opacity: 0.5; 334 | } 335 | #expr { 336 | font-family: 'Fira Code', monospace; 337 | background-color: $dark; 338 | color: $light; 339 | border-radius: 0; 340 | margin: 0; 341 | width: 100%; 342 | border-width: 0; 343 | box-sizing: border-box; 344 | font-size: 20px; 345 | line-height: 20px; 346 | padding: 10px; 347 | padding-left: 36px; 348 | text-align: right; 349 | &:focus { 350 | outline: 0 none; 351 | } 352 | } 353 | 354 | #rules { 355 | margin: 16px 0; 356 | padding: 0; 357 | li { 358 | list-style-type: none; 359 | line-height: 22px; 360 | padding: 4px 16px; 361 | transition: background-color 0.5s; 362 | b { 363 | text-transform: uppercase; 364 | text-shadow: 0 -4px 0 $light; 365 | } 366 | &.bad { 367 | background-color: $primary; 368 | } 369 | } 370 | code { 371 | font-weight: bold; 372 | background-color: $dark; 373 | color: $primary; 374 | padding: 0 0.2em; 375 | } 376 | } 377 | 378 | footer { 379 | text-align: center; 380 | height: 32px; 381 | line-height: 32px; 382 | position: relative; 383 | border: 4px solid $dark; 384 | transition: background-color 0.5s; 385 | &:hover { 386 | background-color: $primary; 387 | } 388 | a { 389 | text-decoration: none; 390 | font-weight: bold; 391 | color: $dark; 392 | display: block; 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: 0 */ 2 | const autoprefixer = require('autoprefixer') 3 | const path = require('path') 4 | 5 | module.exports = { 6 | context: path.join(__dirname, './src'), 7 | entry: './index.js', 8 | output: { 9 | path: './', 10 | filename: 'bundle.js', 11 | }, 12 | module: { 13 | loaders: [ 14 | { 15 | test: /\.scss$/, 16 | loaders: ['style', 'css', 'postcss', 'sass'], 17 | }, 18 | { 19 | test: /\.js$/, 20 | loader: 'babel', 21 | query: { 22 | presets: ['latest'], 23 | }, 24 | }, 25 | ], 26 | }, 27 | postcss: [autoprefixer()], 28 | } 29 | --------------------------------------------------------------------------------