September 10

Blackjack v.3

Third checkpoint in the Blackjack game development [...]
Quick-add: + add another resource
            
              div[id*="pcard"] .popover { left: -95px !important; }
div[id*="dcard"] .popover { left: -105px !important; }

#wrapper {
	margin: 0 auto;
	width: 1000px;
}

h3 {
	margin: 5px 0;
	text-align: center;
}

#game {
	background: transparent url('https://www.clowerweb.com/cors/blackjack/table.png') 0 0 no-repeat;
	border-radius: 5px;
	color: #333;
	font: 14px/17px Helvetica, Arial, Verdana, sans-serif;
	height: 550px;
	margin: 0 auto;
	position: relative;
	width: 1000px;
}

div#dscore {
	position: absolute;
	bottom: 30px;
}

div#pscore {
	position: absolute;
	bottom: 15px;
}

div#phand, div#dhand {
	font-weight: 700;
	/*left: 50%;*/
	position: absolute;
	top: -122px;
	white-space: nowrap;
	width: 1000px;
}

div#phand { }

div#dhand { }

div[class*="card"] {
	background: #FFF;
	border: 6px solid #FFF;
	border-radius: 5px;
	box-shadow: 0 0 1px #000;
	display: inline-block;
	height: 106px;
	margin: 0 5px;
	position: absolute;
	right: 0;
	width: 79px;
}

.down {
	background: #B20000 url('https://www.clowerweb.com/cors/blackjack/card.png') center center no-repeat !important;
}

span.pos-0 {
	left: 0;
	position: absolute;
	top: 0;
}

span.pos-1 {
	bottom: 0;
	filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
	position: absolute;
	right: 0;
	transform: rotate(180deg);
  -moz-transform: rotate(180deg);
  -webkit-transform: rotate(180deg);
}

span.rank {
	font-size: 18px;
}

span.suit {
	font-size: 22px;
}

.black {
	color: #000;
}

.red {
	color: #F00;
}

#result {
	position: absolute;
	bottom: 0;
}

#actions {
	margin: 15px 0 0 0;
    text-align: center;
}

input#wager { margin-left: 5px; position: relative; top: 4px }

#money, #money:before {
	border-radius: 5px;
	bottom: 0;
	height: 55px;
	padding: 7px;
	position: absolute;
	right: 0;
	width: 250px;
}

#money {
	border: 2px solid #FFC926;
}

#money:before {
	background: #333;
	content: '';
	opacity: 0.8;
}

	#cash, #bank {
		color: #FFF;
		font: 700 18px/20px Helvetica, Arial, Verdana, sans-serif;
		margin: 12px 0;
		position: relative;
		text-shadow: 1px 1px #000;
		z-index: 1;
	}

#alert {
	margin: 0 auto;
	opacity: 0.95;
	position: relative;
	text-align: center;
	top: 34.5%;
	width: 500px;
	z-index: 0;
}
            
          
            
              /*

A JavaScript Blackjack game created June 2013 by Chris Clower 
(clowerweb.com). Deck class loosely based on a tutorial at:
http://www.codecademy.com/courses/blackjack-part-1

All graphics and code were designed/written by me except for the
chip box on the table, which was taken from the image at:
http://www.marketwallpapers.com/wallpapers/9/wallpaper-52946.jpg

Uses Twitter Bootstrap and jQuery, which also were not created by
me :)

Fonts used:
* "Blackjack" logo: Exmouth
* Symbol/floral graphics: Dingleberries
* All other fonts: Adobe Garamond Pro

All graphics designed in Adobe Fireworks CS6

You are free to use or modify this code for any purpose, but I ask
that you leave this comment intact. Please understand that this is
still very much a work in progress, and is not feature complete nor
without bugs.

I will also try to comment the code better for future updates :D

*/

/*global $, confirm, Game, Player, renderCard, Card, setActions, 
resetBoard, showBoard, showAlert, getWinner, jQuery, wager */

(function () {

/*****************************************************************/
/*************************** Globals *****************************/
/*****************************************************************/

	var game      = new Game(),
			player    = new Player(),
			dealer    = new Player(),
			running   = false,
			blackjack = false,
			insured   = 0,
			deal;

/*****************************************************************/
/*************************** Classes *****************************/
/*****************************************************************/

	function Player() {
		var hand  = [],
				wager = 0,
				cash  = 1000,
				bank  = 0,
				ele   = '',
				score = '';

		this.getElements = function() {
			if(this === player) {
				ele   = '#phand';
				score = '#pcard-0 .popover-content';
			} else {
				ele   = '#dhand';
				score = '#dcard-0 .popover-content';
			}

			return {'ele': ele, 'score': score};
		};

		this.getHand = function() {
			return hand;
		};

		this.setHand = function(card) {
			hand.push(card);
		};

		this.resetHand = function() {
			hand = [];
		};

		this.getWager = function() {
			return wager;
		};

		this.setWager = function(money) {
			wager += parseInt(money, 0);
		};

		this.resetWager = function() {
			wager = 0;
		};

		this.checkWager = function() {
			return wager <= cash ? true : false;
		};

		this.getCash = function() {
			return cash.formatMoney(2, '.', ',');
		};

		this.setCash = function(money) {
			cash += money;
			this.updateBoard();
		};

		this.getBank = function() {
			$('#bank').html('Winnings: $' + bank.formatMoney(2, '.', ','));

			if(bank < 0) {
				$('#bank').html('Winnings: <span style="color: #D90000">-$' + 
				bank.formatMoney(2, '.', ',').toString().replace('-', '') + '</span>');
			}
		};

		this.setBank = function(money) {
			bank += money;
			this.updateBoard();
		};

		this.flipCards = function() {
			$('.down').each(function() {
				$(this).removeClass('down').addClass('up');
				renderCard(false, false, false, $(this));
			});

			$('#dcard-0 .popover-content').html(dealer.getScore());
		};
	}

	function Deck() {
		var ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'],
				suits = ['&#9824;', '&#9827;', '&#9829;', '&#9670;'],
				deck  = [],
				i, x, card;

		this.getDeck = function() {
			return this.setDeck();
		};

		this.setDeck = function() {
			for(i = 0; i < ranks.length; i++) {
				for(x = 0; x < suits.length; x++) {
					card = new Card({'rank': ranks[i]});

					deck.push({
						'rank' : ranks[i],
						'suit' : suits[x],
						'value': card.getValue()
					});
				}
			}

			return deck;
		};
	}

	function Shuffle(deck) {
		var set      = deck.getDeck(),
				shuffled = [],
				card;

		this.setShuffle = function() {
			while(set.length > 0) {
				card = Math.floor(Math.random() * set.length);

				shuffled.push(set[card]);
				set.splice(card, 1);
			}

			return shuffled;
		};

		this.getShuffle = function() {	
			return this.setShuffle();
		};
	}

	function Card(card) {
		this.getRank = function() {
			return card.rank;
		};

		this.getSuit = function() {
			return card.suit;
		};

		this.getValue = function() {
			var rank  = this.getRank(),
				  value = 0;

			if(rank === 'A') {
				value = 11;
			} else if(rank === 'K') {
				value = 10;
			} else if(rank === 'Q') {
				value = 10;
			} else if(rank === 'J') {
				value = 10;
			} else {
				value = parseInt(rank, 0);
			}

			return value;
		};
	}

	function Deal() {
		var deck     = new Deck(),
				shuffle  = new Shuffle(deck),
				shuffled = shuffle.getShuffle(),
				card;

		this.getCard = function(sender) {
			this.setCard(sender);
			return card;
		};

		this.setCard = function(sender) {
			card = shuffled[0];
			shuffled.splice(card, 1);
			sender.setHand(card);
		};

		this.dealCard = function(num, i, obj) {
			if(i >= num) { return false; }

			var sender   = obj[i],
					elements = obj[i].getElements(),
					score    = elements.score,
					ele      = elements.ele,
					dhand    = dealer.getHand();

			deal.getCard(sender);

			if(i < 3) {
				renderCard(ele, sender, 'up');
				$(score).html(sender.getScore());
			} else {
				renderCard(ele, sender, 'down');
			}

			if(player.getHand().length < 3) {
				if(dhand.length > 0 && dhand[0].rank === 'A') {
					setActions('insurance');
				}

				if(player.getScore() === 21) {
					if(!blackjack) {
						blackjack = true;
						getWinner();
					} else {
						dealer.flipCards();
						$('#dscore span').html(dealer.getScore());
					}
				} else {
					if(dhand.length > 1) {
						setActions('run');
					}
				}
			}

			function showCards() {
				setTimeout(function() {
					deal.dealCard(num, i + 1, obj);
				}, 500);
			}

			clearTimeout(showCards());
		};
	}

	function Game() {
		this.newGame = function() {
			var wager = $.trim($('#wager').val());

			player.resetWager();
			player.setWager(wager);

			if(player.checkWager()) {
				$('#deal').prop('disabled', true);
				resetBoard();
				player.setCash(-wager);

				deal      = new Deal();
				running   = true;
				blackjack = false;
				insured   = false;

				player.resetHand();
				dealer.resetHand();
				showBoard();
			} else {
				player.setWager(-wager);
				$('#alert').removeClass('alert-info alert-success').addClass('alert-error');
				showAlert('Wager cannot exceed available cash!');
			}
		};
	}

/*****************************************************************/
/************************* Extensions ****************************/
/*****************************************************************/

	Player.prototype.hit = function(dbl) {
		var pscore;

		deal.dealCard(1, 0, [this]);
		pscore = player.getScore();

		if(dbl || pscore > 21) {
			running = false;

			setTimeout(function() {
				player.stand();
			}, 500);
		} else {
			this.getHand();
		}

		setActions();

		player.updateBoard();
	};

	Player.prototype.stand = function() {
		var timeout = 0;

    running = false;
		dealer.flipCards();

		function checkDScore() {
			if(dealer.getScore() < 17 && player.getScore() <= 21) {
				timeout += 200;

				setTimeout(function() {
					dealer.hit();
					checkDScore();
				}, 500);
			} else {
				setTimeout(function() {
					getWinner();
				}, timeout);
			}
		}

		checkDScore();
	};

	Player.prototype.dbl = function() {
		var wager = this.getWager();

		if(this.checkWager(wager * 2)) {
			$('#double').prop('disabled', true);
			this.setWager(wager);
			this.setCash(-wager);
			
			this.hit(true);
		} else {
			$('#alert').removeClass('alert-info alert-success').addClass('alert-error');
			showAlert('You don\'t have enough cash to double down!');
		}
	};

	Player.prototype.split = function() {
		$('#alert').removeClass('alert-info alert-success').addClass('alert-error');
		showAlert('Split function is not yet working.');
	};

	Player.prototype.insure = function() {
		var wager    = this.getWager() / 2,
		  	newWager = 0;

		$('#insurance').prop('disabled', true);
		this.setWager(wager);

		if(this.checkWager()) {
			newWager -= wager;
			this.setCash(newWager);
			insured = wager;
		} else {
			this.setWager(-wager);
			$('#alert').removeClass('alert-info alert-success').addClass('alert-error');
			showAlert('You don\'t have enough for insurance!');
		}
	};

	Player.prototype.getScore = function() {
		var hand  = this.getHand(),
				score = 0,
				aces  = 0,
				i;

		for(i = 0; i < hand.length; i++) {
			score += hand[i].value;

			if(hand[i].value === 11) { aces += 1; }

			if(score > 21 && aces > 0) {
				score -= 10;
				aces--;
			}
		}

		return score;
	};

	Player.prototype.updateBoard = function() {
		var score = '#dcard-0 .popover-content';

		if(this === player) {
			score = '#pcard-0 .popover-content';
		}

		$(score).html(this.getScore());
		$('#cash span').html(player.getCash());
		player.getBank();
	};

	Number.prototype.formatMoney = function(c, d, t) {
		var n = this, 
		    s = n < 0 ? '-' : '',
		    i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + '',
		    j = i.length;
		    j = j > 3 ? j % 3 : 0;
	   return s + (j ? i.substr(0, j) + t : '') + i.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : '');
	 };

/*****************************************************************/
/************************** Functions ****************************/
/*****************************************************************/

	(function($) {
    $.fn.disableSelection = function() {
      return this.attr('unselectable', 'on')
                 .css('user-select', 'none')
                 .on('selectstart', false);
    };
	}(jQuery));

	(function($) {
		$.fn.numOnly = function() {
			this.on('keydown', function(e) {
				if(e.keyCode === 46 || e.keyCode === 8 || e.keyCode === 9 || e.keyCode === 27 || e.keyCode === 13 || (e.keyCode === 65 && e.ctrlKey === true) || (e.keyCode >= 35 && e.keyCode <= 39)) {
					return true;
				} else {
					if(e.shifKey || ((e.keyCode < 48 || e.keyCode > 57) && (e.keyCode < 96 || e.keyCode > 105))) {
						e.preventDefault();
					}
				}
			});
		};
	}(jQuery));

	function showAlert(msg) {
		$('#alert span').html('<strong>' + msg + '</strong>');
		$('#alert').fadeIn();
	}

	function setActions(opts) {
		var hand = player.getHand();

		if(!running) {
			$('#deal')  .prop('disabled', false);
			$('#hit')   .prop('disabled', true);
			$('#stand') .prop('disabled', true);
			$('#double').prop('disabled', true);
			$('#split') .prop('disabled', true);
			$('#insurance').prop('disabled', true);
		}

		if(opts === 'run') {
			$('#deal')  .prop('disabled', true);
			$('#hit')   .prop('disabled', false);
			$('#stand') .prop('disabled', false);

			if(player.checkWager(wager * 2)) {
				$('#double').prop('disabled', false);
			}
		} else if(opts === 'split') {
			$('#split').prop('disabled', false);
		} else if(opts === 'insurance') {
			$('#insurance').prop('disabled', false);
		} else if(hand.length > 2) {
			$('#double')   .prop('disabled', true);
			$('#split')    .prop('disabled', true);
			$('#insurance').prop('disabled', true);
		}
	}

	function showBoard() {
		deal.dealCard(4, 0, [player, dealer, player, dealer]);
	}

	function renderCard(ele, sender, type, item) {
		var hand, i, card;

		if(!item) {
			hand = sender.getHand();
		 	i    = hand.length - 1;
		 	card = new Card(hand[i]);
		} else {
		 	hand = dealer.getHand();
		 	card = new Card(hand[1]);
		}

		var	rank  = card.getRank(),
				suit  = card.getSuit(),
				color = 'red',
				posx  = 402,
				posy  = 182,
				speed = 200,
				cards = ele + ' .card-' + i;

		if(i > 0) {
			posx -= 50 * i;
		}

		if(!item) {
			$(ele).append(
				'<div class="card-' + i + ' ' + type + '">' + 
					'<span class="pos-0">' +
						'<span class="rank">&nbsp;</span>' +
						'<span class="suit">&nbsp;</span>' +
					'</span>' +
					'<span class="pos-1">' +
						'<span class="rank">&nbsp;</span>' +
						'<span class="suit">&nbsp;</span>' +
					'</span>' +
				'</div>'
			);

			if(ele === '#phand') {
				posy  = 360;
				speed = 500;
				$(ele + ' div.card-' + i).attr('id', 'pcard-' + i);

				if(hand.length < 2) {
					$('#pcard-0').popover({
						animation: false,
						container: '#pcard-0',
						content: player.getScore(),
						placement: 'left',
						title: 'You Have',
						trigger: 'manual'
					});

					setTimeout(function() {
						$('#pcard-0').popover('show');
						$('#pcard-0 .popover').css('display', 'none').fadeIn();
					}, 500);
				}
			} else {
				$(ele + ' div.card-' + i).attr('id', 'dcard-' + i);

				if(hand.length < 2) {
					$('#dcard-0').popover({
						container: '#dcard-0',
						content: dealer.getScore(),
						placement: 'left',
						title: 'Dealer Has',
						trigger: 'manual'
					});

					setTimeout(function() {
						$('#dcard-0').popover('show');
						$('#dcard-0 .popover').fadeIn();
					}, 100);
				}
			}

			$(ele + ' .card-' + i).css('z-index', i);

			$(ele + ' .card-' + i).animate({
				'top': posy,
				'right': posx
			}, speed);

			$(ele).queue(function() {
				$(this).animate({ 'left': '-=25.5px' }, 100);
				$(this).dequeue();
			});
		} else {
			cards = item;
		}

		if(type === 'up' || item) {
			if(suit !== '&#9829;' && suit !== '&#9670;') {
				color = 'black';
			}

			$(cards).find('span[class*="pos"]').addClass(color);
			$(cards).find('span.rank').html(rank);
			$(cards).find('span.suit').html(suit);
		}
	}

	function resetBoard() {
		$('#dhand').html('');
		$('#phand').html('');
		$('#result').html('');
		$('#phand, #dhand').css('left', 0);
	}

	function getWinner() {
		var phand    = player.getHand(),
				dhand    = dealer.getHand(),
				pscore   = player.getScore(),
				dscore   = dealer.getScore(),
				wager    = player.getWager(),
				winnings = 0,
				result;

		running = false;
		setActions();

		if(pscore > dscore) {
			if(pscore === 21 && phand.length < 3) {
				winnings = (wager * 2) + (wager / 2);
				player.setCash(winnings);
				player.setBank(winnings - wager);
				$('#alert').removeClass('alert-info alert-error').addClass('alert-success');
				result = 'Blackjack!';
			} else if(pscore <= 21) {
				winnings = wager * 2;
				player.setCash(winnings);
				player.setBank(winnings - wager);
				$('#alert').removeClass('alert-info alert-error').addClass('alert-success');
				result = 'You win!';
			} else if(pscore > 21) {
				winnings -= wager;
				player.setBank(winnings);
				$('#alert').removeClass('alert-info alert-success').addClass('alert-error');
				result = 'Bust';
			}
		} else if(pscore < dscore) {
			if(pscore <= 21 && dscore > 21) {
				winnings = wager * 2;
				player.setCash(winnings);
				player.setBank(winnings - wager);
				$('#alert').removeClass('alert-info alert-error').addClass('alert-success');
				result = 'You win - dealer bust!';
			} else if(dscore <= 21) {
				winnings -= wager;
				player.setBank(winnings);
				$('#alert').removeClass('alert-info alert-success').addClass('alert-error');
				result = 'You lose!';
			}
		} else if(pscore === dscore) {
			if(pscore <= 21) {
				if(dscore === 21 && dhand.length < 3 && phand.length > 2) {
					winnings -= wager;
					player.setBank(winnings);
					$('#alert').removeClass('alert-info alert-success').addClass('alert-error');
					result = 'You lose - dealer Blackjack!';
				} else {
					winnings = wager;
					$('#alert').removeClass('alert-error alert-success').addClass('alert-info');
					player.setCash(winnings);
					result = 'Push';
				}
			} else {
				winnings -= wager;
				player.setBank(winnings);
				$('#alert').removeClass('alert-info alert-success').addClass('alert-error');
				result = 'Bust';
			}
		}

		showAlert(result);

		dealer.flipCards();
		dealer.updateBoard();

		if(parseInt(player.getCash()) < 1) {
			$('#myModal').modal();
			$('#newGame').on('click', function() {
				player.setCash(1000);
				$(this).unbind('click');
				$('#myModal').modal('hide');
			});
		}
	}

/*****************************************************************/
/*************************** Actions *****************************/
/*****************************************************************/

	$('#deal').on('click', function() {
		var cash = parseInt(player.getCash());

		$('#alert').fadeOut();

		if(cash > 0 && !running) {
			if($.trim($('#wager').val()) > 0) {
				game.newGame();
			} else {
				$('#alert').removeClass('alert-info alert-success').addClass('alert-error');
				showAlert('The minimum bet is $1.');
			}
		} else {
			$('#myModal').modal();
		}
	});

	$('#hit').on('click', function() {
		player.hit();
	});

	$('#stand').on('click', function() {
		player.stand();
	});

	$('#double').on('click', function() {
		player.dbl();
	});

	$('#split').on('click', function() {
		player.split();
	});

	$('#insurance').on('click', function() {
		player.insure();
	});

/*****************************************************************/
/*************************** Loading *****************************/
/*****************************************************************/

	$('#wager').numOnly();
	$('#actions:not(#wager), #game, #myModal').disableSelection();
	$('#newGame, #cancel').on('click', function(e) { e.preventDefault(); });
	$('#cancel').on('click', function() { $('#myModal').modal('hide'); });
	$('#wager').val(100);
	$('#cash span').html(player.getCash());
	player.getBank();

}());
            
          
September 03

Angular Connect 4 & Simple AI

This project started with me wondering how difficu [...]
Quick-add: + add another resource
            
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
<div class="container" ng-app="connectFour" ng-controller="mainController as main">
	<div ng-show="main.modal" class="modal" ng-click="main.modal = false">
		<div class="modal-inner">
			<span class="modalx" ng-click="main.modal = false">X</span>
			<p>This project started with me wondering how difficult it would be to make connect 4 with HTML/CSS/JS. I built a quick prototype with jQuery and was pleased that it worked. I ended up switching to Angular mostly because of the huge stack of html that I had to insert to get all of the cells, and thought Angular's repeater would work nicely for that.</p>

			<p>Before talking about the AI, I'd like to mention that since building this I've read about mathematically perfect connect 4 AI's that look at every possible game path and <u>always</u> choose the best option and can't be beat (but can be tied). This AI is not that. I wanted to build the computer to play how a human plays the game. First the computer will say 'can I win right now?' and take action if it can. Failing that, it will say 'can the opponent win as soon as they go?' at which point it will prevent that. After that it will re-examine both of those scenarios, but 2 turns out rather than one. If all of that fails, it will just play randomly. It does this by looking for patterns in the horizontal rows, vertical columns, and diagonals. For example, it knows that if this exists in a horizontal row: [0,r,r,r] (r being red, 0 being empty) it needs to first check if there's anything under that 0 for it's piece to land on, and if so, play there to block the opponents win.</p>

			<p>If you're a developer, feel free to fork this off and make something cool, just attribute me please =]</p>
		</div>
	</div>
	<div class="wrap winner-{{main.winner}}">
		<h2>Angular Connect 4</h2>
		<div class="buttons">
			<h4>Game Type:</h4>
			<label><input type="radio" ng-model="main.playType" name="gameType" value="2" /> 2 player (Local)</label>
			<label><input type="radio" ng-model="main.playType" name="gameType" value="1" /> AI (In Dev)</label>
		</div>
		<div class="board active-{{main.active}}">
			<span class="row" ng-repeat="row in main.boardState track by $index">
				<div ng-repeat="cell in row track by $index" class="cell active-{{cell}}" ng-click="main.drop($parent.$index,$index)"></div>
				<p style="clear:both;"></p>
			</span>
			<div class="leftFoot"></div>
			<div class="rightFoot"></div>
		</div>
		<div style="clear:both"></div>
		<button class="reset" ng-click="main.init()">Reset</button>
		<button class="about" ng-click="main.modal = true">About</button>
		<div ng-show="main.playType == 1" class="aicomments" scroll-glue>
			<h3>AI Comments</h3>
			<div class="comment" ng-repeat="comment in main.aiComments track by $index">{{comment}}</div>
		</div>
		<h2 ng-show="main.winner">Winner is {{main.winner}}</h2>
	</div>
</div>

            
          
            
              .cell{
	width:50px;
	height:50px;
	position:relative;
	overflow:hidden;
	float:left;
	border:10px solid rgb(69, 170, 184)
}
.cell:before{
	content: '';
	position: absolute;
	bottom: 0%;
	width: 100%;
	height: 100%;
	border-radius: 100%;
	box-shadow: 0px 300px 0px 345px rgb(69, 170, 184);
}

p {
	clear:both;
	margin:0;
}

.board {
	width:490px;;
	margin:0 auto;
	position:relative;
	border:20px solid rgb(69, 170, 184);
	background:white;
}

.leftFoot, .rightFoot {
	background-color:rgb(69, 170, 184);
	width:30px;
	height:100px;
	float:left;
}

.leftFoot {
	margin-left:-20px;
}
.rightFoot {
	float:right;
	margin-right:-20px;
}

.leftFoot:after, .rightFoot:after {
	content:' ';
	width: 0; 
	height: 0; 
	border-left: 35px solid transparent;
	border-right: 35px solid transparent;	
	border-bottom: 80px solid rgb(69, 170, 184);
	position: absolute;
	bottom: -100px;
}

.leftFoot:after {
	left:-40px;
}
.rightFoot:after {
	right:-40px;
}

.active-r .cell:after {
	background-color: rgba(240, 107, 80,0.2);
	content: ' ';
	height: 120px;
	width: 120px;
	display: block;
	display:none;
}

.active-y .cell:after {
	background-color: rgba(225, 215, 114,0.2);
	content: ' ';
	height: 120px;
	width: 120px;
	display: block;
	display:none;
}

.cell:hover:after {
	display:block;
}

.cell.active-r:after {
	display:block;
	background-color: rgba(240, 107, 80,0.9);
}
.cell.active-y:after {
	display:block;
	background-color: rgba(225, 215, 114,0.9);
}

.reset {
	background-color:rgb(57, 66, 64);
	color:white;
	border:none;
	padding:10px;
	font-size:20px;
	text-align:center;
	width:10%;
	margin:0 auto;
	display:block;
	margin-bottom:10px;
}

.about {
	background-color:rgb(127, 131, 130);
	color:white;
	border:none;
	padding:10px;
	font-size:20px;
	text-align:center;
	width:10%;
	margin:0 auto;
	display:block;
	margin-bottom:30px;
}


h2 {
	text-align:center;
	font-family:sans-serif;
	text-transform:uppercase;
}
.wrap  {
	display:block;
	height:100%;
	padding-top:40px;
}

.wrap.winner-red {
	background:rgb(240, 107, 80);
}

.wrap.winner-yellow {
	background:rgb(225, 215, 114);
}

.wrap.winner-yellow .cell:hover:after, .wrap.winner-red .cell:hover:after {
	display:none;
}

.wrap {
	color:rgb(57, 66, 64);
}

.buttons {
	text-align:center;
	margin-bottom:10px;
}

.modal {
    width: 100%;
    z-index: 999;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0,0,0,0.5);
}

.modal-inner {
    background: white;
    position: absolute;
    left: 20%;
    right: 20%;
    top: 20%;
    bottom: 20%;
    padding: 15px;
    overflow-x: hidden;
    overflow-y: scroll;
}

.modalx {
      text-align: right;
    font-family: sans-serif;
    float: right;
    padding: 8px 10px;
    background-color: rgba(0,0,0,0.2);
}

.modal p {
  margin-bottom:20px;
}

.aicomments {
	width: 50%;
    margin: 0 auto;
    text-align: center;
    border: 1px solid #B6B6B6;
    margin-bottom:20px;
    max-height:100px;
    overflow-y:scroll;
    overflow-x:hidden;
}

.aicomments h3 {
	background: rgb(57, 66, 64);
	margin: 0;
	padding: 10px;
	color: white;
	font-family:sans-serif;
}

.comment {
	text-align:left;
	background:white;
	padding:5px;
}
.comment:nth-child(even) {
	background:#ccc;
}

@media screen and (max-width:600px) {
	.board {
		width:252px;
	}
	.cell {
		width:32px;
		height:32px;
		border: 2px solid rgb(69, 170, 184);
	}
	.reset,.about {
		width:30%;
	}
}
            
          
            
              angular.module('connectFour', ['luegg.directives'])

.controller('mainController', ['$scope', '$http', '$timeout', function($scope, $http, $timeout) {

    var vm = this;

    vm.init = function() {
        vm.boardState = [
            [0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0]
        ];

        vm.active = 'r';

        vm.dropAllowed = true;

        vm.winner = false;

        vm.aiComments = ['Once you start playing, the AI will tell you why it\'s using specific moves here.'];
    };
    vm.init();

    vm.playType = 1;

    vm.modal = false;


    vm.winDetect = function() {
        var tempWinner = false;
        //horiz
        for (var i = 0; i < vm.boardState.length; i++) {
            var rowMatch = vm.boardState[i].join('').match(/r{4}|y{4}/);
            if (rowMatch) {
                rowMatch[0].indexOf("r") > -1 ? tempWinner = "red" : tempWinner = "yellow";
            }
        }
        //vertical
        var columns = vm.getColumns();
        for (var j = 0; j < columns.length; j++) {
            var colMatch = columns[j].join('').match(/r{4}|y{4}/);
            if (colMatch) {
                colMatch[0].indexOf("r") > -1 ? tempWinner = "red" : tempWinner = "yellow";
            }
        }
        //diag
        var diags = vm.getDiags();
        for (var l = 0; l < diags.length; l++) {
            var diagMatch = diags[l].join('').match(/r{4}|y{4}/);
            if (diagMatch) {
                diagMatch[0].indexOf("r") > -1 ? tempWinner = "red" : tempWinner = "yellow";
            }
        }
        return tempWinner;
    };

    vm.getColumns = function(){
        var columns = [];
        for (var j = 0; j < vm.boardState[0].length; j++) {
            var column = [];
            for (var k = 0; k < vm.boardState.length; k++) {
                column.push(vm.boardState[k][j]);
            }
            columns.push(column);
        }
        return columns;
    };

    vm.getDiags = function(arr) {
        if (typeof arr === 'undefined') arr = vm.boardState;
        var diags = [];
        for (var i = -5; i < 7; i++) {
            var group = [];
            for (var j = 0; j < 6; j++) {
                if ((i + j) >= 0 && (i + j) < 7) {
                    group.push(arr[j][i + j]);
                }
            }
            diags.push(group);
        }
        for (i = 0; i < 12; i++) {
            var group = [];
            for (var j = 5; j >= 0; j--) {
                if ((i - j) >= 0 && (i - j) < 7) {
                    group.push(arr[j][i - j]);
                }
            }
            diags.push(group);
        }
        return diags.filter(function(a) {
            return a.length > 3;
        });
    };

    vm.ai = function(){
        var decision = null;
        function threatDetect(lt, type) {
            //vertical threat assessment & response
            var columns = vm.getColumns();
            for (var i = 0; i < columns.length; i++) {
                var vertMatch;
                type == 'major' ? vertMatch = "0"+lt+lt+lt : vertMatch = "00"+lt+lt;
                var colMatch = columns[i].join('').match(vertMatch);
                if (colMatch) {
                    decision = i;
                    console.log('ai: responding to a '+type+' vertical '+responseType);
                    vm.aiComments.push('ai: responding to a '+type+' vertical '+responseType);
                }
            }

            if (!decision) {
                //horiz threat assessment & response
                var horizThreatPatterns;
                if (type == 'major') {
                    horizThreatPatterns = ['0'+lt+lt+lt, lt+'0'+lt+lt, lt+lt+'0'+lt, lt+lt+lt+'0'];
                }
                else {
                    horizThreatPatterns = ['00'+lt+lt, '0'+lt+lt+'0', '0'+lt+'0'+lt, lt+'0'+lt+'0', '0'+lt+lt+'0', lt+lt+'00'];
                }

                for (i = 0; i < vm.boardState.length; i++) {
                    var found = [];
                    var joined = vm.boardState[i].join('');
                    for (var j = 0; j < horizThreatPatterns.length; j++) {
                        var match = joined.match(horizThreatPatterns[j]);
                        if (match) found.push(match[0]);
                    }
                    if (found.length) {
                        var testCase = 0;
                        if (i == vm.boardState.length - 1) {
                            if (found[0] == '00yy' || found[0] == '00rr') testCase = 1;
                            decision = joined.indexOf(found[0])+found[0].indexOf('0')+testCase;
                            console.log('ai: responding to a '+type+' horizontal '+responseType);
                            vm.aiComments.push('ai: responding to a '+type+' horizontal '+responseType);
                        }
                        else {
                            matchPosition = joined.indexOf(found[0])+found[0].indexOf('0');
                            if (found[0] == '00yy' || found[0] == '00rr') matchPosition++;
                            if (vm.boardState[i+1][matchPosition]!==0) {
                                decision = matchPosition;
                                console.log('ai: responding to a '+type+' horizontal '+responseType);
                                vm.aiComments.push('ai: responding to a '+type+' horizontal '+responseType);
                            }
                        }
                    }
                }
            }

            if (!decision) {
                //diag threat assessment & response
                var diags = vm.getDiags();
                var diagThreatPatterns = ['0'+lt+lt+lt, lt+'0'+lt+lt, lt+lt+'0'+lt, lt+lt+lt+'0'];
                for (i = 0; i < diags.length; i++) {
                    var found = [];
                    var joined = diags[i].join('');
                    for (var j = 0; j < diagThreatPatterns.length; j++) {
                        var match = joined.match(diagThreatPatterns[j]);
                        if (match) found.push(match[0]);
                    }
                    if (found.length) {
                        for (var l = 0; l < found.length; l++) {
                            diagMap = vm.getDiags([[0,1,2,3,4,5,6],[7,8,9,10,11,12,13],[14,15,16,17,18,19,20],[21,22,23,24,25,26,27],[28,29,30,31,32,33,34],[35,36,37,38,39,40,41]]);
                            var vulnSlot = diagMap[i][found[l].indexOf('0')];
                            if ( typeof vm.boardState[Math.floor(vulnSlot/7)+1] === 'undefined' || vm.boardState[Math.floor(vulnSlot/7)+1][(vulnSlot%7)] !== 0) {
                                decision = vulnSlot%7;
                                console.log('ai: responding to a '+type+' diagonal '+responseType);
                                vm.aiComments.push('ai: responding to a '+type+' diagonal '+responseType);
                            }
                        }
                    }
                }
            }
        }

        function opportunityDetect(type) {
            //detecting our opportunities is just like detecting threats (mostly, 3 extra patterns)
            //we want to be defensive over offensive, so we only look for opportunities
            //if there are no immediate threats
            responseType = 'opportunity';
            threatDetect(vm.active,type);
        }

        //look for winning opportunities
        opportunityDetect('major');

        //if none, look for major threats
        if (decision === null) {
            var responseType = 'threat';
            threatDetect((vm.active == 'r' ? 'y' : 'r'), 'major');
        }

        //if none look for minor opportunities
        if (decision === null) {
            opportunityDetect('minor');
        }

        //if none look for minor threats
        if (decision === null) {
            var responseType = 'threat';
            threatDetect((vm.active == 'r' ? 'y' : 'r'), 'minor');
        }

        if (decision !== null && vm.boardState[0][decision] === 0) {
            vm.drop(0,decision);
        }
        else {
            console.log('ai: no threats or opportunities found, goin random');
            var random = Math.floor(Math.random() * 7);
            var failSafe = 0;
            var boardValue = vm.boardState[0][random];
            while (boardValue !== 0 && failSafe < 100) {
                random = Math.floor(Math.random() * 7);
                boardValue = vm.boardState[0][random];
                failSafe++;
            }
            vm.drop(0,random);
        }
    };

    vm.drop = function(index, index2) {
        if (vm.dropAllowed && vm.boardState[index][index2] === 0) {
            vm.dropAllowed = false;
            vm.boardState[0][index2] = vm.active;
            //recursive timeout loop
            (function dropLoop(i) {
                $timeout(function() {
                    if (typeof vm.boardState[i] !== 'undefined' && vm.boardState[i][index2] === 0 && i <= 5) {
                        vm.boardState[i - 1][index2] = 0;
                        vm.boardState[i][index2] = vm.active;
                        dropLoop(i + 1);
                    } else {
                        vm.active = (vm.active == 'r' ? 'y' : 'r');
                        vm.dropAllowed = true;
                        vm.winner = vm.winDetect();
                        if (vm.winner) {
                            vm.dropAllowed = false;
                        }
                        if (vm.playType == 1 && vm.active == 'y') {
                            //ai
                            vm.ai();                            
                        }
                    }
                }, 50);
            })(1);
        }
    };

}]);

/*
    To do list:
    hypothetical thinking ahead. Before the ai makes a move, see if that would put the other player in a winning position
*/
            
          

Asset uploading is a PRO feature.

As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.

Go PRO

September 03

Bubble Game (pure CSS)

A bubble-popping game made without using JavaScrip [...]
Quick-add: + add another resource
            
              - var maxBubbles = 40;

form(action="")
  - for (var b = 0; b < maxBubbles; ++b) {
      input(type="checkbox",id="bubble" + (b + 1))
      label(for="bubble" + (b + 1)).bubble
        span.bubble-inner
            span
  - }
  div.timer= "Time"
    div.time-left
      span
  div.score
  div.intro= "Pop as many bubbles as you can!"
  div.menu
    h1= "Game Over"
    ul
      li
        button(type="submit")
          svg(class="menu-icon",viewBox="0 0 24 24"): path(vector-effect="non-scaling-stroke",fill="#000000",d="M2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2A10,10 0 0,0 2,12M4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12M10,17L15,12L10,7V17Z")/
          = "Play Again"
            
          
            
              // game itself
$bg: #08c;
$minGameW: 300px;
$timer: 45s;
// bubbles
$bubbleSize: 15vw;
$bubbleColor: #f44 #f84 #ff8 #8f8 #8ff #88f;
$bubbleSizeCut: 0.5 0.6 0.7 0.8 0.9 1;
$maxBubbles: 40;
$minDur: 2s;
// font
$minFontSize: 20pt;
$addedFontSize: 1.1vw;
// score
$scoreW: 70px;
// bubble texture reference - http://img.brothersoft.com/screenshots/softimage/f/flowbubbles-69406-1.jpeg
@mixin createBubble($color, $size) {
  box-shadow: 0 (-$size * 0.019) ($size * 0.032) #fff inset, 0 (-$size * 0.051) ($size * 0.128) $color inset, 0 ($size * 0.01) ($size * 0.01) $color inset, ($size * 0.01) 0 ($size * 0.032) #fff inset, -($size * 0.01) 0 ($size * 0.032) #fff inset, 0 ($size * 0.026) ($size * 0.128) ($color + #aaa) inset;
  width: $size;
  height: $size;
  max-width: $size;
  max-height: $size;
  &:before {
    top: $size * 0.115;
    left: $size * 0.179;
    width: $size * 0.16;
    height: $size * 0.064;
  }
  &:after {
    opacity: 0.1;
    top: $size * 0.16;
    left: $size * 0.16;
    width: $size;
    height: $size;
  }
  span {
    background: radial-gradient(at center bottom, transparent, transparent 70%, ($color + #aaa));
    top: ($size * 0.01);
    left: $size * 0.096;
    width: $size * 0.808;
    height: $size * 0.622;
  }
}

// used later for random bubble placement and animation delay
@function randomNumber($min, $max) {
  @return ceil((random() * $max) - $min) + $min;
}

body {
  background: $bg linear-gradient($bg * 1.5, $bg, $bg / 2);
  counter-reset: popped;
  margin: 0;
  overflow: hidden;
}

body,
button {
  font-family: Lato, sans-serif;
  font-size: calc(#{$minFontSize} + #{$addedFontSize});
  font-weight: 300;
  line-height: calc(#{$minFontSize} + #{$addedFontSize});
}

h1 {
  font-size: calc(#{$minFontSize * 1.5} + #{$addedFontSize * 1.5});
  margin-top: 40vh;
}

button,
path {
  transition: all 0.2s;
}

button {
  background: transparent;
  border: 0;
  padding: 0;
  -webkit-appearance: none;
  &:hover {
    color: #fff;
    path {
      fill: #fff;
    }
  }
}

form {
  margin: auto;
  position: relative;
  height: 100vh;
  min-width: $minGameW;
  width: 75%;
}

input {
  position: absolute;
  top: -20px;
  &:checked {
    counter-increment: popped;
    + .bubble {
      display: none;
    }
  }
}

.timer,
.score,
.intro,
.menu,
.bubble {
  position: absolute;
}

.timer,
.score,
.intro {
  z-index: 0;
}

.score,
.intro,
.menu {
  text-align: center;
}

.timer {
  display: flex;
  display: -webkit-flex;
  display: -ms-flex;
  justify-content: space-between;
  -webkit-justify-content: space-between;
  -ms-justify-content: space-between;
  align-items: center;
  -webkit-align-items: center;
  -ms-align-items: center;
  font-size: calc(#{$minFontSize/2} + #{$addedFontSize});
  line-height: calc(#{$minFontSize/2} + #{$addedFontSize});
  top: calc(15px + #{$addedFontSize});
  left: 5%;
  height: calc(15px + #{$addedFontSize});
  width: 90%;
}

.time-left {
  background: #fff;
  height: calc(15px + #{$addedFontSize});
  margin-left: 15px;
  opacity: 0.8;
  width: 100%;
  span {
    display: block;
    background: #c00;
    height: 100%;
    width: 100%;
    animation: timer $timer linear forwards;
  }
}

.score {
  font-size: calc(#{$minFontSize * 1.5} + #{$addedFontSize});
  margin-left: -$scoreW/2;
  top: calc(50px + #{$addedFontSize * 2});
  left: 50%;
  width: $scoreW;
  &::after {
    content: counter(popped);
  }
}

.intro {
  top: 40%;
  width: 100%;
  animation: fade 1s 2s linear reverse forwards;
}

.menu {
  width: 100%;
  height: 100%;
  visibility: hidden;
  z-index: 2;
  animation: fade 1s $timer linear forwards;
}

ul {
  top: 0;
  left: 0;
  list-style: none;
  margin: 0;
  padding: 0;
}

.menu-icon {
  margin-right: calc(10px + #{$addedFontSize});
  vertical-align: top;
  width: calc(32px + #{$addedFontSize});
  height: calc(32px + #{$addedFontSize});
}

// base bubble styles
.bubble {
  animation-name: ascend;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
  top: 0;
  transform: translateY(100vh);
  will-change: transform;
  z-index: 1;
  .bubble-inner {
    border-radius: 50%;
    display: block;
    &:before,
    &:after,
    span,
    span:after {
      border-radius: 50%;
      content: "";
      display: block;
      position: absolute;
    }
    &:before {
      background: #fff;
      transform: rotate(-30deg);
    }
    &:after {
      background: radial-gradient(transparent, #000 60%, transparent 70%, transparent);
      transform: scale(1.2, 1.2);
    }
    &:hover {
      animation: shake 0.2s linear;
    }
    &:active {
      animation: pop 0.08s cubic-bezier(0.16, 0.87, 0.48, 0.99) forwards;
    }
  }
}

// style six kinds of bubbles with different animation delay, duration (speed), and size
@for $b from 1 through 6 {
  .bubble:nth-of-type(6n + #{$b}) {
    animation-duration: $minDur + (($b - 1) / 2);
    .bubble-inner {
      @include createBubble(nth($bubbleColor, $b), $bubbleSize * nth($bubbleSizeCut, $b));
    }
  }
}

// randomly assign positions and delays
@for $b from 1 through $maxBubbles {
  .bubble:nth-of-type(#{$b}) {
    left: 0% + randomNumber(0, 80);
    animation-delay: 0s + randomNumber(0, $maxBubbles);
  }
}

// animations
@keyframes ascend {
  from {
    transform: translateY(100vh);
    -webkit-transform: translateY(100vh);
  }
  to {
    transform: translateY(-$bubbleSize * 1.2);
    -webkit-transform: translateY(-$bubbleSize * 1.2);
  }
}

@keyframes shake {
  from {
    transform: scale(1, 1);
  }
  33% {
    transform: scale(1, 1.2);
  }
  66% {
    transform: scale(1.2, 1);
  }
  to {
    transform: scale(1, 1);
  }
}

@keyframes pop {
  from {
    opacity: 1;
    transform: translateZ(0) scale(1, 1);
  }
  to {
    opacity: 0;
    transform: translateZ(0) scale(1.75, 1.75);
  }
}

@keyframes fade {
  from {
    opacity: 0;
    visibility: hidden;
  }
  1% {
    opacity: 0;
    visibility: visible;
  }
  to {
    opacity: 1;
    visibility: visible;
  }
}

@keyframes timer {
  from {
    width: 100%;
  }
  to {
    width: 0%;
  }
}
            
          

Asset uploading is a PRO feature.

As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.

Go PRO

Editor Commands

Pen Actions

Also see: Tab Triggers

HTML Specific

Misc

September 03

Draggable and Rotatable Photo Viewer - Polaroid

Draggable and Rotatable Photo Viewer - Polaroid
September 03

google maps api with less pain and more fun

useful for simple map/marker maker with text in mo [...]

google maps api with less pain and more fun

September 03

pagePiling.js plugin | Create a stack of scrolling pages

pagePiling.js plugin by Alvaro Trigo. Create a sta [...]
Tweet

pagePiling.js

Create an original scrolling site



Donations will be appreciated!

jQuery plugin

Pile your sections one over another and access them scrolling or by URL!

Configurable

Plenty of options, methods and callbacks to use.

Compatible

Designed to work on tablet and mobile devices.

Oh! And its compatible with old browsers such as IE 8 or Opera 12!

September 03

jQuery plugin that allows to create responsive scrollers

This is a jQuery plugin that allows to create resp [...]

Join GitHub today

GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.

Sign up

This is a jQuery plugin that allows to create responsive scrollers (carousels) with grid and simple horizontal layouts. RadiantScroller can be cuztomized with the variety of options. API methods and callbacks are available. Elements may also have animated captions.

README.md

jQuery RadiantScroller plugin

This is a jQuery plugin that allows to create responsive scrollers (carousels) with grid and simple horizontal layouts. RadiantScroller can be cuztomized with the variety of options. API methods and callbacks are available. Elements may also have animated captions.

Demos

Images were taken from FreeImages.

Requirements

  1. jQuery 1.7.0+
  2. MouseWheel plugin (only if you want to provide mousewheel support)

Installation and usage

  • Hook up required files

  • Hook up jquery.radiant_scroller.min.js and jquery.radiant_scroller.css

  • Set up basic layout for the scroller:

<div id="your_scroller_id">
    <div class="scroller-el"><img src="image1.jpg" alt="Image1" />div>
    <div class="scroller-el"><img src="image2.jpg" alt="Image2" />div>
    <div class="scroller-el"><img src="image3.jpg" alt="Image3" />div>
div>

The scroller-el class is required.

  • Set up basic styling. In fact, only specifying margins and dimensions for the elements in the scroller is required. This can be done like:
.radiant_scroller .scroller-el {
    width: 200px;
    height: 200px;
    margin-right: 10px;
    margin-bottom: 10px; /* This is optional but recommended - otherwise your images will have no horizontal spacing */
}
  • Initialize the scroller. The values for elementWidth and elementMargin should be the same as your specified in the previous step otherwise the scroller's grid will be built incorrectly. You will probably want to specify scroller's grid dimensions with the cols and rows attributes (the default is 2x2 grid). All available options are listed in the "Options" section.
$(document).ready(function() {
    $('#myScroller').radiantScroller({
        elementWidth: 200,
        elementMargin: 10,
        cols: 3,
        rows: 3
    });
});

Options

Integer Animation duration for the scrolling. The value is specified in milliseconds.
Boolean Whether the scroller should have pagination (often displayed as small navigational dots) enabled.
captionsAnimateDuration Integer Animation duration for the captions.
captionsAnimateEasingType Integer Easing type for the captions' animation. You can specify any other type of easing but bear in mind that jQuery has only swing and linear easings included. You will have to include jQueryUI's effects and easings plugin to get more.
Integer How many (maximum) columns should the scroller have - basically this means how many elements will be visible at once horizontally (but if the scroller's width changes this value will also change).
Integer How long the scroller will wait after the resizing to initiate recalculation (this is done to ensure that the recalculation takes place only after the user has finished resizing the screen).
String Easing type for the scrolling. You can specify any other type of easing but bear in mind that jQuery has only swing and linear easings included. You will have to include jQueryUI's effects and easings plugin to get more.
Integer Horizontal (left/right) margin for the scroller's elements. This value should correspond to the value that is provided in the styles.
Integer Width for the scroller's elements. This value should correspond to the value that is provided in the styles.
String Text to be shown on the "next" button.
String Text to be shown on the "previous" button.
Integer How many rows should the scroller have.
Boolean Whether captions should be enabled. Check Captions section for more information.
Boolean Whether support for the mousewheel scrolling should be enabled. Please note that when this value is set to true, you should hook up MouseWheel plugin.

Styling

jquery.radiant_scroller.css provides only required styles so you have to write some more styles to make the scroller look nice. When the scroller is initialized the following layout is being built (pagination will not be shown until you set addPagination option to true):

<div class="radiant_scroller">
    <div class="radiant_scroller_wrapper">
        <div id="myScroller">
            <div class="radiant_scroller_row">
                <div class="scroller-el"><img src="image1.jpg" alt="Image1" />div>
                <div class="scroller-el"><img src="image2.jpg" alt="Image2" />div>
            div>
            <div class="radiant_scroller_row">
                <div class="scroller-el"><img src="image3.jpg" alt="Image3" />div>
                <div class="scroller-el"><img src="image4.jpg" alt="Image4" />div>
            div>
        div>
    div>
    <div class="radiant-pagination">
        <div class="radiant-page current-page" data-page="1">div>
        <div class="radiant-page" data-page="2">div>
    div>
    <div class="radiant-navigation">
        <div class="radiant-prev">div>
        <div class="radiant-next">div>
    div>
div>

All elements except for #myScroller and .scroller-el are added dynamically.

  • .radiant-pagination contains navigational pages; the active page has the current-page class, so you can style it differently.
  • .radiant-navigation contains next and previous buttons. .radiant-prev and .radiant-next are positioned absolutely (and main wrapper .radiant_scroller is positioned relatively) so you can adjust its position as necessary.

See demos to get the basic idea how the scroller can be styled.

Captions

Starting from version 0.1.0 scroller elements may have animated captions. To use them few things have to be done:

  • Set useCaptions option to true
  • Adjust captionsAnimateDuration and captionsAnimateEasingType if needed
  • Add title attribute to your elements like this:
<div id="your_scroller_id">
    <div class="scroller-el"><img src="image1.jpg" alt="Image1" title="This is a caption" />div>
    <div class="scroller-el"><img src="image2.jpg" alt="Image2" title="Another caption" />div>
div>

By default captions appear at the bottom of the element with white centered text and black semi-opaque background. If you wish to style it differently, assign your styles to .radiant-caption class.

API

To get access to the RadiantScroller's API you should initialize your scroller like this:

var my_scroller = $('#myScroller').radiantScroller({});

And then you can manage your scroller by calling radiantScroller on the my_scroller variable and passing it an API action to invoke. Currently there are a few API methods available:

  • radiantScroller('next') - scroll one page forward
  • radiantScroller('prev') - scroll one page backward
  • radiantScroller() - scroll to a page with the specified number. Page numeration starts from 1. If a non-existent page is provided nothing happens.
  • radiantScroller('by', ) - scroll by a specified number of pages. For example if you are at the 1st page and call my_scroller.radiantScroller('by', 2) you scroll by 2 pages and end up at the 3rd page.

To see them all in action open this demo. More methods coming soon.

Callbacks

Starting from version 0.1.0 the following callbacks are available:

Fires when the scroller has finished loading and is ready. Scroller is being passed as an argument.
Fires before each scroller's moving animation. Scroller is being passed as an argument.
Fires before after scroller's moving animation. Scroller is being passed as an argument.
Fires when the scroller has finished loading and is ready. Scroller is being passed as an argument.
Fires after the caption was hidden. Caption is being passed as an argument.
Fires after the caption was showed. Caption is being passed as an argument.

For Rails developers

jquery-radiantscroller-rails gem provides an easy way to integrate Radiant Scroller into your Rails project.

License

This plugin is licensed under the MIT License.

Copyright (c) 2016 Ilya Bodrov

You can't perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
September 03

Wheel of Fortune

Making one of these is harder than I thought it wo [...]
Quick-add: + add another resource
            
              const TWO_PI = Math.PI * 2;
const HALF_PI = Math.PI * 0.5;
// canvas settings
var viewWidth = 768,
    viewHeight = 768,
    viewCenterX = viewWidth * 0.5,
    viewCenterY = viewHeight * 0.5,
    drawingCanvas = document.getElementById("drawing_canvas"),
    ctx,
    timeStep = (1/60),
    time = 0;

var ppm = 24, // pixels per meter
    physicsWidth = viewWidth / ppm,
    physicsHeight = viewHeight / ppm,
    physicsCenterX = physicsWidth * 0.5,
    physicsCenterY = physicsHeight * 0.5;

var world;

var wheel,
    arrow,
    mouseBody,
    mouseConstraint;

var arrowMaterial,
    pinMaterial,
    contactMaterial;

var wheelSpinning = false,
    wheelStopped = true;

var particles = [];

var statusLabel = document.getElementById('status_label');

window.onload = function() {
    initDrawingCanvas();
    initPhysics();

    requestAnimationFrame(loop);

    statusLabel.innerHTML = 'Give it a good spin!';
};

function initDrawingCanvas() {
    drawingCanvas.width = viewWidth;
    drawingCanvas.height = viewHeight;
    ctx = drawingCanvas.getContext('2d');

    drawingCanvas.addEventListener('mousemove', updateMouseBodyPosition);
    drawingCanvas.addEventListener('mousedown', checkStartDrag);
    drawingCanvas.addEventListener('mouseup', checkEndDrag);
    drawingCanvas.addEventListener('mouseout', checkEndDrag);
}

function updateMouseBodyPosition(e) {
    var p = getPhysicsCoord(e);
    mouseBody.position[0] = p.x;
    mouseBody.position[1] = p.y;
}

function checkStartDrag(e) {
    if (world.hitTest(mouseBody.position, [wheel.body])[0]) {

        mouseConstraint = new p2.RevoluteConstraint(mouseBody, wheel.body, {
            worldPivot:mouseBody.position,
            collideConnected:false
        });

        world.addConstraint(mouseConstraint);
    }

    if (wheelSpinning === true) {
        wheelSpinning = false;
        wheelStopped = true;
        statusLabel.innerHTML = "Impatience will not be rewarded.";
    }
}

function checkEndDrag(e) {
    if (mouseConstraint) {
        world.removeConstraint(mouseConstraint);
        mouseConstraint = null;

        if (wheelSpinning === false && wheelStopped === true) {
            if ( Math.abs(wheel.body.angularVelocity) > 7.5) {
                wheelSpinning = true;
                wheelStopped = false;
                console.log('good spin');
                statusLabel.innerHTML = '...clack clack clack clack clack clack...'
            }
            else {
                console.log('sissy');
                statusLabel.innerHTML = 'Come on, you can spin harder than that.'
            }
        }
    }
}

function getPhysicsCoord(e) {
    var rect = drawingCanvas.getBoundingClientRect(),
        x = (e.clientX - rect.left) / ppm,
        y = physicsHeight - (e.clientY - rect.top) / ppm;

    return {x:x, y:y};
}

function initPhysics() {
    world = new p2.World();
    world.solver.iterations = 100;
    world.solver.tolerance = 0;

    arrowMaterial = new p2.Material();
    pinMaterial = new p2.Material();
    contactMaterial = new p2.ContactMaterial(arrowMaterial, pinMaterial, {
        friction:0.0,
        restitution:0.1
    });
    world.addContactMaterial(contactMaterial);

    var wheelRadius = 8,
        wheelX = physicsCenterX,
        wheelY = wheelRadius + 4,
        arrowX = wheelX,
        arrowY = wheelY + wheelRadius + 0.625;

    wheel = new Wheel(wheelX, wheelY, wheelRadius, 32, 0.25, 7.5);
    wheel.body.angle = (Math.PI / 32.5);
    wheel.body.angularVelocity = 5;
    arrow = new Arrow(arrowX, arrowY, 0.5, 1.5);
    mouseBody = new p2.Body();

    world.addBody(mouseBody);
}

function spawnPartices() {
    for (var i = 0; i < 200; i++) {
        var p0 = new Point(viewCenterX, viewCenterY - 64);
        var p1 = new Point(viewCenterX, 0);
        var p2 = new Point(Math.random() * viewWidth, Math.random() * viewCenterY);
        var p3 = new Point(Math.random() * viewWidth, viewHeight + 64);

        particles.push(new Particle(p0, p1, p2, p3));
    }
}

function update() {
    particles.forEach(function(p) {
        p.update();
        if (p.complete) {
            particles.splice(particles.indexOf(p), 1);
        }
    });

    // p2 does not support continuous collision detection :(
    // but stepping twice seems to help
    // considering there are only a few bodies, this is ok for now.
    world.step(timeStep * 0.5);
    world.step(timeStep * 0.5);

    if (wheelSpinning === true && wheelStopped === false &&
        wheel.body.angularVelocity < 1 && arrow.hasStopped()) {

        var win = wheel.gotLucky();

        wheelStopped = true;
        wheelSpinning = false;

        wheel.body.angularVelocity = 0;

        if (win) {
            spawnPartices();
            statusLabel.innerHTML = 'Woop woop!'
        }
        else {
            statusLabel.innerHTML = 'Too bad! Invite a Facebook friend to try again!';
        }
    }
}

function draw() {
    // ctx.fillStyle = '#fff';
    ctx.clearRect(0, 0, viewWidth, viewHeight);

    wheel.draw();
    arrow.draw();

    particles.forEach(function(p) {
        p.draw();
    });
}

function loop() {
    update();
    draw();

    requestAnimationFrame(loop);
}

/////////////////////////////
// wheel of fortune
/////////////////////////////
function Wheel(x, y, radius, segments, pinRadius, pinDistance) {
    this.x = x;
    this.y = y;
    this.radius = radius;
    this.segments = segments;
    this.pinRadius = pinRadius;
    this.pinDistance = pinDistance;

    this.pX = this.x * ppm;
    this.pY = (physicsHeight - this.y) * ppm;
    this.pRadius = this.radius * ppm;
    this.pPinRadius = this.pinRadius * ppm;
    this.pPinPositions = [];

    this.deltaPI = TWO_PI / this.segments;

    this.createBody();
    this.createPins();
}
Wheel.prototype = {
    createBody:function() {
        this.body = new p2.Body({mass:1, position:[this.x, this.y]});
        this.body.angularDamping = 0.0;
        this.body.addShape(new p2.Circle(this.radius));
        this.body.shapes[0].sensor = true; //TODO use collision bits instead

        var axis = new p2.Body({position:[this.x, this.y]});
        var constraint = new p2.LockConstraint(this.body, axis);
        constraint.collideConnected = false;

        world.addBody(this.body);
        world.addBody(axis);
        world.addConstraint(constraint);
    },
    createPins:function() {
        var l = this.segments,
            pin = new p2.Circle(this.pinRadius);

        pin.material = pinMaterial;

        for (var i = 0; i < l; i++) {
            var x = Math.cos(i / l * TWO_PI) * this.pinDistance,
                y = Math.sin(i / l * TWO_PI) * this.pinDistance;

            this.body.addShape(pin, [x, y]);
            this.pPinPositions[i] = [x * ppm, -y * ppm];
        }
    },
    gotLucky:function() {
        var currentRotation = wheel.body.angle % TWO_PI,
            currentSegment = Math.floor(currentRotation / this.deltaPI);

        return (currentSegment % 2 === 0);
    },
    draw:function() {
        // TODO this should be cached in a canvas, and drawn as an image
        // also, more doodads
        ctx.save();
        ctx.translate(this.pX, this.pY);

        ctx.beginPath();
        ctx.fillStyle = '#DB9E36';
        ctx.arc(0, 0, this.pRadius + 24, 0, TWO_PI);
        ctx.fill();
        ctx.fillRect(-12, 0, 24, 400);

        ctx.rotate(-this.body.angle);

        for (var i = 0; i < this.segments; i++) {
            ctx.fillStyle = (i % 2 === 0) ? '#BD4932' : '#FFFAD5';
            ctx.beginPath();
            ctx.arc(0, 0, this.pRadius, i * this.deltaPI, (i + 1) * this.deltaPI);
            ctx.lineTo(0, 0);
            ctx.closePath();
            ctx.fill();
        }

        ctx.fillStyle = '#401911';

        this.pPinPositions.forEach(function(p) {
            ctx.beginPath();
            ctx.arc(p[0], p[1], this.pPinRadius, 0, TWO_PI);
            ctx.fill();
        }, this);

        ctx.restore();
    }
};
/////////////////////////////
// arrow on top of the wheel of fortune
/////////////////////////////
function Arrow(x, y, w, h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.verts = [];

    this.pX = this.x * ppm;
    this.pY = (physicsHeight - this.y) * ppm;
    this.pVerts = [];

    this.createBody();
}
Arrow.prototype = {
    createBody:function() {
        this.body = new p2.Body({mass:1, position:[this.x, this.y]});
        this.body.addShape(this.createArrowShape());

        var axis = new p2.Body({position:[this.x, this.y]});
        var constraint = new p2.RevoluteConstraint(this.body, axis, {
            worldPivot:[this.x, this.y]
        });
        constraint.collideConnected = false;

        var left = new p2.Body({position:[this.x - 2, this.y]});
        var right = new p2.Body({position:[this.x + 2, this.y]});
        var leftConstraint = new p2.DistanceConstraint(this.body, left, {
            localAnchorA:[-this.w * 2, this.h * 0.25],
            collideConnected:false
        });
        var rightConstraint = new p2.DistanceConstraint(this.body, right, {
            localAnchorA:[this.w * 2, this.h * 0.25],
            collideConnected:false
        });
        var s = 32,
            r = 4;

        leftConstraint.setStiffness(s);
        leftConstraint.setRelaxation(r);
        rightConstraint.setStiffness(s);
        rightConstraint.setRelaxation(r);

        world.addBody(this.body);
        world.addBody(axis);
        world.addConstraint(constraint);
        world.addConstraint(leftConstraint);
        world.addConstraint(rightConstraint);
    },

    createArrowShape:function() {
        this.verts[0] = [0, this.h * 0.25];
        this.verts[1] = [-this.w * 0.5, 0];
        this.verts[2] = [0, -this.h * 0.75];
        this.verts[3] = [this.w * 0.5, 0];

        this.pVerts[0] = [this.verts[0][0] * ppm, -this.verts[0][1] * ppm];
        this.pVerts[1] = [this.verts[1][0] * ppm, -this.verts[1][1] * ppm];
        this.pVerts[2] = [this.verts[2][0] * ppm, -this.verts[2][1] * ppm];
        this.pVerts[3] = [this.verts[3][0] * ppm, -this.verts[3][1] * ppm];

        var shape = new p2.Convex(this.verts);
        shape.material = arrowMaterial;

        return shape;
    },
    hasStopped:function() {
        var angle = Math.abs(this.body.angle % TWO_PI);

        return (angle < 1e-3 || (TWO_PI - angle) < 1e-3);
    },
    update:function() {

    },
    draw:function() {
        ctx.save();
        ctx.translate(this.pX, this.pY);
        ctx.rotate(-this.body.angle);

        ctx.fillStyle = '#401911';

        ctx.beginPath();
        ctx.moveTo(this.pVerts[0][0], this.pVerts[0][1]);
        ctx.lineTo(this.pVerts[1][0], this.pVerts[1][1]);
        ctx.lineTo(this.pVerts[2][0], this.pVerts[2][1]);
        ctx.lineTo(this.pVerts[3][0], this.pVerts[3][1]);
        ctx.closePath();
        ctx.fill();

        ctx.restore();
    }
};
/////////////////////////////
// your reward
/////////////////////////////
Particle = function(p0, p1, p2, p3) {
    this.p0 = p0;
    this.p1 = p1;
    this.p2 = p2;
    this.p3 = p3;

    this.time = 0;
    this.duration = 3 + Math.random() * 2;
    this.color =  'hsl(' + Math.floor(Math.random() * 360) + ',100%,50%)';

    this.w = 10;
    this.h = 7;

    this.complete = false;
};
Particle.prototype = {
    update:function() {
        this.time = Math.min(this.duration, this.time + timeStep);

        var f = Ease.outCubic(this.time, 0, 1, this.duration);
        var p = cubeBezier(this.p0, this.p1, this.p2, this.p3, f);

        var dx = p.x - this.x;
        var dy = p.y - this.y;

        this.r =  Math.atan2(dy, dx) + HALF_PI;
        this.sy = Math.sin(Math.PI * f * 10);
        this.x = p.x;
        this.y = p.y;

        this.complete = this.time === this.duration;
    },
    draw:function() {
        ctx.save();
        ctx.translate(this.x, this.y);
        ctx.rotate(this.r);
        ctx.scale(1, this.sy);

        ctx.fillStyle = this.color;
        ctx.fillRect(-this.w * 0.5, -this.h * 0.5, this.w, this.h);

        ctx.restore();
    }
};
Point = function(x, y) {
    this.x = x || 0;
    this.y = y || 0;
};
/////////////////////////////
// math
/////////////////////////////
/**
 * easing equations from http://gizma.com/easing/
 * t = current time
 * b = start value
 * c = delta value
 * d = duration
 */
var Ease = {
    inCubic:function (t, b, c, d) {
        t /= d;
        return c*t*t*t + b;
    },
    outCubic:function(t, b, c, d) {
        t /= d;
        t--;
        return c*(t*t*t + 1) + b;
    },
    inOutCubic:function(t, b, c, d) {
        t /= d/2;
        if (t < 1) return c/2*t*t*t + b;
        t -= 2;
        return c/2*(t*t*t + 2) + b;
    },
    inBack: function (t, b, c, d, s) {
        s = s || 1.70158;
        return c*(t/=d)*t*((s+1)*t - s) + b;
    }
};

function cubeBezier(p0, c0, c1, p1, t) {
    var p = new Point();
    var nt = (1 - t);

    p.x = nt * nt * nt * p0.x + 3 * nt * nt * t * c0.x + 3 * nt * t * t * c1.x + t * t * t * p1.x;
    p.y = nt * nt * nt * p0.y + 3 * nt * nt * t * c0.y + 3 * nt * t * t * c1.y + t * t * t * p1.y;

    return p;
}
            
          

Asset uploading is a PRO feature.

As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.

Go PRO

September 03

ScratchCard is a library javascript to generate scratch card

ScratchCard is a library javascript to generate sc [...]

ScratchCard

ScratchCard is a library javascript to generate scratch card (HTML5, canvas) in modern browsers with touch events support.

In this exemple if you scratch 50% of the canvas the callback is executed.




Javascript init

                
    var scratch = new Scratch({
        canvasId: 'js-scratch-canvas',
        canvasWidth: 250,
        canvasHeight: 250,
        imageBackground: './images/loose.jpg',
        pictureOver: './images/foreground.jpg',
        cursor: {
            png: './images/piece.png',
            cur: './images/piece.cur',
            x: '17',
            y: '20'
        },
        radius: 20,
        nPoints: 100,
        percent: 50,
        callback: function () {
            alert('I am Callback.');
        },
        pointSize: { x: 3, y: 3}
    });
                
            


Html layout



CSS for this exemple

                
    .scratch_container {
        position: relative;
        margin: 0 auto;
        max-width: 1024px;
    }

    .scratch_viewport {
        position: relative;
        max-width: 250px;
        max-height: 250px;
        margin: 0 auto;
        z-index: 0;
    }

    .scratch_picture-under {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        display: block;
        z-index: -1;
    }

    .scratch_container canvas {
        position: relative;
        width: 100%;
        height: auto;
        z-index: 1;
    }
                
September 03

Slot Machine

The Slot Machine, where one can win one of three b [...]
Quick-add: + add another resource
            
              /* CSS reset { */
body,div,h1,button,p{margin:0;padding:0}
button{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit}
a:active,a:focus {outline: 0}
/* } */

body, input, select, textarea {font:18px/18px "Helvetica Neue",Helvetica,Arial,sans-serif;}
body {
  background:#ddd url(https://subtlepatterns.com/patterns/texturetastic_gray.png);
}

h1 {
  text-align:center;
  margin-top: 20px;
  font-size: 1.75em;
  font-weight: normal;
  color: #bbb;
}

h1, a {
  text-shadow: 0 1px 0 rgba(255,255,255,.8);
}

#sm {
background: rgb(238,238,238);
background: -moz-linear-gradient(top, rgba(238,238,238,1) 0%, rgba(204,204,204,1) 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(238,238,238,1)), color-stop(100%,rgba(204,204,204,1)));
background: -webkit-linear-gradient(top, rgba(238,238,238,1) 0%,rgba(204,204,204,1) 100%);
background: -o-linear-gradient(top, rgba(238,238,238,1) 0%,rgba(204,204,204,1) 100%);
background: -ms-linear-gradient(top, rgba(238,238,238,1) 0%,rgba(204,204,204,1) 100%);
background: linear-gradient(top, rgba(238,238,238,1) 0%,rgba(204,204,204,1) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#cccccc',GradientType=0 );

  width: 540px;
  margin: 10px auto 20px;
  padding: 20px;
  border-radius: 50px;
}

#sm, .lever button {box-shadow: 0 3px 9px rgba(0,0,0,.25)}
  .group, .reel, .lever {display: inline-block;}
  .group, .lever {
background: rgb(252,234,187);
background: -moz-linear-gradient(top, rgba(252,234,187,1) 0%, rgba(252,205,77,1) 50%, rgba(248,181,0,1) 51%, rgba(251,223,147,1) 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(252,234,187,1)), color-stop(50%,rgba(252,205,77,1)), color-stop(51%,rgba(248,181,0,1)), color-stop(100%,rgba(251,223,147,1)));
background: -webkit-linear-gradient(top, rgba(252,234,187,1) 0%,rgba(252,205,77,1) 50%,rgba(248,181,0,1) 51%,rgba(251,223,147,1) 100%);
background: -o-linear-gradient(top, rgba(252,234,187,1) 0%,rgba(252,205,77,1) 50%,rgba(248,181,0,1) 51%,rgba(251,223,147,1) 100%);
background: -ms-linear-gradient(top, rgba(252,234,187,1) 0%,rgba(252,205,77,1) 50%,rgba(248,181,0,1) 51%,rgba(251,223,147,1) 100%);
background: linear-gradient(top, rgba(252,234,187,1) 0%,rgba(252,205,77,1) 50%,rgba(248,181,0,1) 51%,rgba(251,223,147,1) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fceabb', endColorstr='#fbdf93',GradientType=0 );
  }

.group {
  border-radius: 30px;
  padding: 20px 0 20px 20px;
}

  .reel {
background: #fff;
background: -moz-linear-gradient(top, rgba(255,255,255,1) 0%, rgba(241,241,241,1) 50%, rgba(225,225,225,1) 51%, rgba(246,246,246,1) 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,1)), color-stop(50%,rgba(241,241,241,1)), color-stop(51%,rgba(225,225,225,1)), color-stop(100%,rgba(246,246,246,1)));
background: -webkit-linear-gradient(top, rgba(255,255,255,1) 0%,rgba(241,241,241,1) 50%,rgba(225,225,225,1) 51%,rgba(246,246,246,1) 100%);
background: -o-linear-gradient(top, rgba(255,255,255,1) 0%,rgba(241,241,241,1) 50%,rgba(225,225,225,1) 51%,rgba(246,246,246,1) 100%);
background: -ms-linear-gradient(top, rgba(255,255,255,1) 0%,rgba(241,241,241,1) 50%,rgba(225,225,225,1) 51%,rgba(246,246,246,1) 100%);
background: linear-gradient(top, rgba(255,255,255,1) 0%,rgba(241,241,241,1) 50%,rgba(225,225,225,1) 51%,rgba(246,246,246,1) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#f6f6f6',GradientType=0 );

    text-align:center;
    float: left;
    padding:0 10px;
    width: 80px;
    height: 100px;
    overflow: hidden;
    margin-right: 20px;
    border-radius: 10px;
    box-shadow: 0 2px 7px rgba(0,0,0,.3) inset, 0 0px 1px rgba(0,0,0,.2) inset;
  }
    .reel div {
      position: relative;
      top: -48px;
    }
    .reel p {
      font-weight: bold;
      height: 60px;
      margin-top: 10px;
    }
    .reel p:nth-child(1) {color:#c60}
    .reel p:nth-child(2) {color:#690}
    .reel p:nth-child(3) {color:#630}

.lever {
  float: right;
  padding-right: 20px;
}
  .lever button {
    text-align:center;
    border-radius: 10px;
    line-height: 100px;
    width: 100px;
    border: none;
    font-size:1.8em;
    -webkit-transition: all .2s ease;
    -moz-transition: all .2s ease;
    -o-transition: all .2s ease;
background: rgb(252,255,244);
background: -moz-linear-gradient(top, rgba(252,255,244,1) 0%, rgba(233,233,206,1) 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(252,255,244,1)), color-stop(100%,rgba(233,233,206,1)));
background: -webkit-linear-gradient(top, rgba(252,255,244,1) 0%,rgba(233,233,206,1) 100%);
background: -o-linear-gradient(top, rgba(252,255,244,1) 0%,rgba(233,233,206,1) 100%);
background: -ms-linear-gradient(top, rgba(252,255,244,1) 0%,rgba(233,233,206,1) 100%);
background: linear-gradient(top, rgba(252,255,244,1) 0%,rgba(233,233,206,1) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fcfff4', endColorstr='#e9e9ce',GradientType=0 );
  }
  .lever button:active {
    color: #900;
    box-shadow: 0 1px 4px rgba(0,0,0,.3);
    margin: 2px 0 -2px;
  }

.msg {
  text-align: center;
  color: #bbb;
  font-size:1.8em;
  padding: 25px 0 5px;
  text-shadow: 0 -1px 0 rgba(0,0,0,.3), 0 1px 0 rgba(255,255,255,.5);
}

a {
  color: #222cff;
  margin-top: 15px;
  text-align: center;
  display: block;
  text-decoration:none;
}

a:hover {
  color:#ff4c00
}
            
          
            
              /*
	requestAnimationFrame polyfill
*/
(function(w){
	var lastTime = 0,
		vendors = ['webkit', /*'moz',*/ 'o', 'ms'];
	for (var i = 0; i < vendors.length && !w.requestAnimationFrame; ++i){
		w.requestAnimationFrame = w[vendors[i] + 'RequestAnimationFrame'];
		w.cancelAnimationFrame = w[vendors[i] + 'CancelAnimationFrame']
			|| w[vendors[i] + 'CancelRequestAnimationFrame'];
	}

	if (!w.requestAnimationFrame)
		w.requestAnimationFrame = function(callback, element){
			var currTime = +new Date(),
				timeToCall = Math.max(0, 16 - (currTime - lastTime)),
				id = w.setTimeout(function(){ callback(currTime + timeToCall) }, timeToCall);
			lastTime = currTime + timeToCall;
			return id;
		};

	if (!w.cancelAnimationFrame)
		w.cancelAnimationFrame = function(id){
		clearTimeout(id);
	};
})(this);

/*
	Slot Machine
*/
var sm = (function(undefined){

	var tMax = 3000, // animation time, ms
		height = 210,
		speeds = [],
		r = [],
		reels = [
			['coffee maker',   'teapot',       'espresso machine'],
			['coffee filter',  'tea strainer', 'espresso tamper'],
			['coffee grounds', 'loose tea',    'ground espresso beans']
		],
		$reels, $msg,
		start;

	function init(){
		$reels = $('.reel').each(function(i, el){
			el.innerHTML = '<div><p>' + reels[i].join('</p><p>') + '</p></div><div><p>' + reels[i].join('</p><p>') + '</p></div>'
		});

		$msg = $('.msg');

		$('button').click(action);
	}

	function action(){
		if (start !== undefined) return;

		for (var i = 0; i < 3; ++i) {
			speeds[i] = Math.random() + .5;	
			r[i] = (Math.random() * 3 | 0) * height / 3;
		}

		$msg.html('Spinning...');
		animate();
	}

	function animate(now){
		if (!start) start = now;
		var t = now - start || 0;

		for (var i = 0; i < 3; ++i)
			$reels[i].scrollTop = (speeds[i] / tMax / 2 * (tMax - t) * (tMax - t) + r[i]) % height | 0;

		if (t < tMax)
			requestAnimationFrame(animate);
		else {
			start = undefined;
			check();
		}
	}

	function check(){
		$msg.html(
			r[0] === r[1] && r[1] === r[2] ?
				'You won! Enjoy your ' + reels[1][ (r[0] / 70 + 1) % 3 | 0 ].split(' ')[0]
			:
				'Try again'
		);
	}

	return {init: init}

})();

$(sm.init);
            
          

Asset uploading is a PRO feature.

As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.

Go PRO

Editor Commands

Pen Actions

Also see: Tab Triggers

HTML Specific

Misc

September 02

Overview and Requirements

To make a simpler Omnitapps tool

A web tool that allows users to log in and create a touchscreen experience for trade shows etc.

This could be just one game (eg matching pairs example), with the logo's of the client for the 'cards' and various product images for the pairs.

Or it could be a collection of 2 games and 1 quiz - and so on...

ONE config area

Each mini app should share the same inputs to the config area - the company logo, up to 4 main colours and a font selector (Google fonts) will be used on EVERY app. If config is entered once, and then an app is selected, it will automatically have those details in the app - colours will be correct etc.

Other options, such as images for matching game, or images for showcase/gallery, will need to be added in config area too, should they be needed.

Menu Builder/Selector

The 'home page' of any touch screen experience will need to be customisable for clients to want to use the software. This means there should be 5-10 templates, but also a 'customiser' app, that allows a user to drag and drop buttons/images, and select a background, which will become the menu for the app.


September 02

Composer for menus

Omnitaps call it a composer, but basically, its a [...]

We need a few different types of menus for the 'home page' of everytime someone uses it. 

for example, they may want users to see balloon game, whackamole and matching games - so the menu (made with the composer) will have those 3 options on.

so, those 3 options could be in various menu styles - eg as round buttons, as css divs/boxes that fill the screen, with a background image or colour etc. the more styles, the better the solution for end clients.


discuss between james/zahed.

September 02

need to be able to upload an image and then add points on to it with info boxes

need to be able to upload an image and then add po [...]

need to be able to upload an image and then add points on to it with info boxes

September 02

Simple and easy to use lightbox script written in pure JavaScript

Simple and easy to use lightbox script written in [...]

Simple and easy to use lightbox script written in pure JavaScript

September 02

Brochure Selector/Sender

App we already have

App we already have