Skip to content

Instantly share code, notes, and snippets.

@marekhrabe
Last active August 20, 2018 17:56
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save marekhrabe/5189295 to your computer and use it in GitHub Desktop.
Save marekhrabe/5189295 to your computer and use it in GitHub Desktop.
Typing Enigma from http://getenigma64.com/
<div id="enigma"></div>
/**
* Makes Enigma awesome.
*
* @class Enigma
* @param {Element} element Enigma main element
* @constructor
*/
var Enigma = function (element) {
// basic environment
this.element = element;
this.playing = false;
this.writing = true;
this.time = +new Date();
this.letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
// wait mode
this.wait = false;
// button properties
this.buttonPressLength = 300;
// creates canvases and buttons
this.prepareElements();
// attaches event listeners
this.prepareEventListeners();
// activate awesome mode
this.element.className = 'awesome';
// text content
this.lines = ['DATA:IMAGE/PNG;BASE64,'];
this.queue = [];
this.nextLetterAppend = 3000;
// blinking text cursor
this.cursorVisible = true;
this.nextCursorChange = 200;
this.cursorInterval = 300;
// base rendering settings and font properties
this.ctx.font = 'bold 11px sans-serif';
this.ctx.fillStyle = '#b37b4e';
this.baseY = 33;
this.baseX = 25;
this.scrollY = 0;
this.lineHeight = 14;
this.scrollSpeed = 30;
this.maxWidth = 170;
this.maxLines = 2;
// words
this.nextWord = 15000;
this.wordInterval = 15000;
this.words = ['piffle', 'css/hat', 'enigma', 'by/marek', 'this/is/not/random', 'lol', 'omg', 'base64', ' lol /lag ', 'wtf', 'dafuq?', 'jan/palounek', 'jan/palounek', 'jan/palounek', 'jan/palounek', 'petr/brzek', 'si/velky/panko'];
this.wordSeparator = '/';
};
/**
* Creates elements for Enigma. Canvases for display and reflection and buttons.
*
* @method prepareElements
*/
Enigma.prototype.prepareElements = function () {
// buttons
this.buttonsContainer = document.createElement('div');
this.buttons = {};
for (var i = 0, ii = this.letters.length; i < ii; ++i) {
this.buttons[this.letters[i]] = document.createElement('span');
this.buttons[this.letters[i]].className = this.letters[i].toLowerCase();
this.buttons[this.letters[i]].innerHTML = '<b></b>';
this.buttonsContainer.appendChild(this.buttons[this.letters[i]]);
}
this.element.appendChild(this.buttonsContainer);
// main display canvas
this.canvas = document.createElement('canvas');
this.canvas.width = 218;
this.canvas.height = 58;
this.canvas.className = 'display';
this.ctx = this.canvas.getContext('2d');
this.element.appendChild(this.canvas);
// reflection canvas
this.reflection = document.createElement('canvas');
this.reflection.width = 218;
this.reflection.height = 58;
this.reflection.className = 'reflection';
this.reflectionCtx = this.reflection.getContext('2d');
this.element.appendChild(this.reflection);
// blinking LED
var led = document.createElement('span');
led.className = 'led';
this.element.appendChild(led);
};
/**
* Attaches event listeners.
*
* @method prepareEventListeners
*/
Enigma.prototype.prepareEventListeners = function () {
var that = this;
window.addEventListener('scroll', function () {
if (window.scrollY > 250 && that.playing) {
that.stop();
}
if (window.scrollY < 250 && !that.playing) {
that.start();
}
}, false);
window.addEventListener('resize', function () {
if (window.innerWidth < 800 && that.playing) {
that.stop();
}
if (window.innerWidth > 799 && !that.playing) {
that.start();
}
}, false);
document.addEventListener('keydown', function (e) {
var letter = String.fromCharCode(e.which);
if (that.buttons[letter]) {
that.buttonDown(letter);
that.appendLetter(letter);
} else if (e.which === 32) {
that.appendLetter('/');
} else if (e.which === 13) {
if (that.lines[that.lines.length - 1].replace('_', '') !== '') {
that.lines.push('');
}
}
this.wait = 5000;
}, false);
document.addEventListener('keyup', function (e) {
var letter = String.fromCharCode(e.which);
if (that.buttons[letter]) {
that.buttonUp(letter);
}
}, false);
this.element.addEventListener('mouseover', function () {
that.writing = false;
}, false);
this.element.addEventListener('mouseout', function () {
that.writing = true;
}, false);
this.element.addEventListener('mousedown', function (e) {
var btn = e.target;
if (btn.tagName === 'B') {
btn = btn.parentElement;
}
if (btn.tagName === 'SPAN') {
that.appendLetter(btn.className.substr(0, 1).toUpperCase());
}
}, false);
window.addEventListener('blur', function () {
that.stop();
}, false);
window.addEventListener('focus', function () {
that.start();
}, false);
};
/**
* Starts function and rendering loop.
*
* @method start
*/
Enigma.prototype.start = function () {
this.playing = true;
this.loop();
};
/**
* Stops function and rendering loop.
*
* @method stop
*/
Enigma.prototype.stop = function () {
this.playing = false;
};
/**
* Function and rendering loop.
*
* @method loop
* @param {Integer} time Current time
*/
Enigma.prototype.loop = function (time) {
if (this.playing) {
var that = this;
requestAnimationFrame(function (time) {
that.loop(time);
});
var delta = time ? time - this.time : 0;
this.time = time;
this.update(delta);
this.render();
}
};
/**
* Computes if given line will fit on our display line.
*
* @method willFit
* @param {String} line Line of text
* @return {Bool} Returns true when line fits
*/
Enigma.prototype.willFit = function (line) {
var dimensions = this.ctx.measureText(line);
return dimensions.width < this.maxWidth;
};
/**
* Appends a letter on display.
*
* @method appendLetter
* @param {String} letter A letter
*/
Enigma.prototype.appendLetter = function (letter) {
var lastLine = this.lines[this.lines.length - 1];
if (this.willFit(lastLine + letter)) {
this.lines[this.lines.length - 1] += letter;
} else {
this.lines.push(letter);
}
};
/**
* Pushes a letter and automaticaly lifts it up
*
* @method pushButton
* @param {String} letter A letter of button
*/
Enigma.prototype.pushButton = function (letter) {
this.buttonDown(letter);
var that = this;
setTimeout(function () {
that.buttonUp(letter);
}, this.buttonPressLength);
};
/**
* Pushes the button down
*
* @method buttonDown
* @param {String} letter A letter of button
*/
Enigma.prototype.buttonDown = function (letter) {
var btn = this.buttons[letter];
if (btn) {
btn.className += ' down';
if (!this.writing) {
this.element.className += ' pressed';
}
}
};
/**
* Lifts the button up
*
* @method buttonDown
* @param {String} letter A letter of button
*/
Enigma.prototype.buttonUp = function (letter) {
var btn = this.buttons[letter];
if (btn) {
btn.className = btn.className.replace(/ down/g, '');
this.element.className = this.element.className.replace(/ pressed/g, '');
}
};
/**
* Logical part of animation loop. Computes lines of text, may append letter.
*
* @method update
* @param {Integer} delta Delta between this time and last time loop was called in miliseconds
*/
Enigma.prototype.update = function (delta) {
// first run
delta = delta || 0;
// text cursor cleanup
for (var i = 0, ii = this.lines.length; i < ii; ++i) {
this.lines[i] = this.lines[i].replace('_', '');
}
// letter appending
if (this.writing && !this.wait) {
this.nextLetterAppend -= delta;
if (this.nextLetterAppend < 0) {
this.nextLetterAppend = 100 + Math.random() * 300;
var nextLetter = this.queue.length ? this.queue.shift() : this.letters[Math.floor(this.letters.length * Math.random())];
this.appendLetter(nextLetter);
this.pushButton(nextLetter);
}
}
// waiting
if (this.wait) {
this.wait -= delta;
if (this.wait <= 0) {
this.wait = false;
}
}
// blinking text cursor
this.nextCursorChange -= delta;
if (this.nextCursorChange < 0) {
this.nextCursorChange = this.cursorInterval;
this.cursorVisible = !this.cursorVisible;
}
if (this.cursorVisible) {
this.appendLetter('_');
}
// scrolling
if (this.lines.length > this.maxLines) {
this.scrollY += Math.ceil((delta / 1000) * this.scrollSpeed);
if (this.scrollY >= this.lineHeight) {
this.lines.shift();
this.scrollY = 0;
}
}
// random words
if (this.writing) {
this.nextWord -= delta;
}
if (this.nextWord < 0 && this.writing) {
this.nextWord = this.wordInterval + 5 * Math.random();
if (!this.queue.length) {
this.queue = this.words[Math.floor(this.words.length * Math.random())].toUpperCase().split('');
this.queue.unshift(this.wordSeparator);
this.queue.push(this.wordSeparator);
}
}
// reflection
this.reflectionCtx.putImageData(this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height), 0, 0);
};
/**
* Rendering part of animation loop.
*
* @method render
*/
Enigma.prototype.render = function () {
var c = this.ctx;
c.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (var i = 0, ii = this.lines.length; i < ii; ++i) {
// animate first line opacity when scrolling
if (i === 0 && this.scrollY !== 0) {
c.globalAlpha = 1 - this.scrollY / this.lineHeight;
} else {
c.globalAlpha = 1;
}
// render line
c.fillText(this.lines[i], this.baseX, this.baseY - this.scrollY + i * this.lineHeight);
}
};
// code uses LESSHat mixin library
// http://lesshat.com/
#enigma {
display: block;
.size (467px, 401px);
background-repeat: no-repeat;
background-image: url(http://getenigma64.com/static/img/enigma.png);
position: absolute;
top: 30px;
left: -100px;
.user-select (none);
canvas {
display: none;
}
&.awesome {
background-image: url(http://getenigma64.com/static/img/enigma-blank.png);
.perspective (1000px);
canvas {
display: block;
position: absolute;
}
.display {
left: 100px;
top: 48px;
.transform (~'rotateX(45deg) rotateZ(-16deg)');
}
.reflection {
left: 92px;
top: 15px;
opacity: .8;
.transform (~'rotateX(290deg) rotateY(189deg) rotateZ(181deg)');
-webkit-filter: blur(1.66px);
-moz-filter: blur(1.66px);
-ms-filter: blur(1.66px);
-o-filter: blur(1.66px);
filter: blur(1.66px);
}
div {
display: block;
position: relative;
z-index: 10;
span {
display: block;
position: absolute;
background-image: url(http://getenigma64.com/static/img/buttons.png);
background-repeat: no-repeat;
.transition (~'.1s ease');
.transform (~'translate3d(0, 0, 0)');
b {
content: '';
display: block;
width: 28px;
height: 28px;
position: absolute;
top: -3px;
left: -3px;
cursor: pointer;
}
&:hover {
.transform (~'translate3d(0, 2px, 0)');
}
&.down, &:active {
.transform (~'translate3d(0, 6px, 0)');
}
}
.letter (@x, @y, @w, @h) {
.size (@w, @h);
top: @y;
left: @x;
background-position: -@x -@y;
}
.a {
.letter (144px, 148px, 22px, 15px);
}
.b {
.letter (289px, 148px, 23px, 14px);
}
.c {
.letter (234px, 160px, 22px, 15px);
}
.d {
.letter (202px, 136px, 22px, 14px);
}
.e {
.letter (175px, 113px, 21px, 14px);
}
.f {
.letter (230px, 131px, 22px, 14px);
}
.g {
.letter (257px, 125px, 21px, 14px);
}
.h {
.letter (283px, 120px, 21px, 13px);
}
.i {
.letter (304px, 90px, 20px, 11px);
}
.j {
.letter (309px, 115px, 21px, 13px);
}
.k {
.letter (334px, 110px, 20px, 13px);
}
.l {
.letter (367px, 132px, 22px, 13px);
}
.m {
.letter (342px, 137px, 22px, 14px);
}
.n {
.letter (316px, 142px, 22px, 14px);
}
.o {
.letter (328px, 85px, 20px, 12px);
}
.p {
.letter (143px, 179px, 24px, 17px);
}
.q {
.letter (118px, 124px, 22px, 13px);
}
.r {
.letter (203px, 108px, 20px, 13px);
}
.s {
.letter (173px, 142px, 23px, 15px);
}
.t {
.letter (229px, 104px, 21px, 12px);
}
.u {
.letter (280px, 94px, 20px, 12px);
}
.v {
.letter (262px, 154px, 22px, 15px);
}
.w {
.letter (147px, 119px, 22px, 13px);
}
.x {
.letter (205px, 166px, 22px, 16px);
}
.y {
.letter (175px, 172px, 23px, 17px);
}
.z {
.letter (255px, 99px, 20px, 12px);
}
}
.led {
display: block;
.size (125px, 140px);
background-image: url(http://getenigma64.com/static/img/led.png);
position: absolute;
top: 111px;
left: 63px;
.animation (~'led 2s ease infinite');
.transition (~'.3s ease');
.transform (~'translate3d(0, 0, 0)');
}
&:hover {
.led {
.opacity (1);
.animation (~'blink 3s ease infinite');
}
}
&.pressed {
.led {
.opacity (0);
.animation (none);
.transition (~'.1s ease');
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment