--- /dev/null
+/**
+ * Module dependencies.
+ */
+
+var config = require('./config')
+ , express = require('express')
+ , parseCookie = require('connect').utils.parseCookie
+ , redisstore = require('connect-redis')(express)
+ , redisurl = require('redis-url')
+ , site = require('./routes/site')
+ , user = require('./routes/user');
+
+/**
+ * Setting up redis.
+ */
+
+var songsdb = redisurl.createClient(config.songsdburl);
+var usersdb = redisurl.createClient(config.usersdburl);
+
+songsdb.on('error', function(err) {
+ console.log(err.toString());
+});
+
+usersdb.on('error', function(err) {
+ console.log(err.toString());
+});
+
+/**
+ * Setting up Express.
+ */
+var sessionstore = new redisstore({client:usersdb})
+ , app = express.createServer();
+
+// Configuration
+app.use(express.static(__dirname + '/public'), {maxAge: 2592000000});
+app.use(express.favicon(__dirname + '/public/static/images/favicon.ico', {maxAge: 2592000000}));
+app.use(express.bodyParser());
+app.use(express.cookieParser());
+app.use(express.session({secret:config.sessionsecret,store:sessionstore}));
+
+app.set('view engine', 'jade');
+app.set('view options', {layout:false});
+
+app.dynamicHelpers({
+ errors: function(req, res) {
+ var errors = req.session.errors;
+ delete req.session.errors;
+ return errors;
+ },
+ oldvalues: function(req, res) {
+ var oldvalues = req.session.oldvalues;
+ delete req.session.oldvalues;
+ return oldvalues;
+ }
+});
+
+// Routes
+site.use({db:songsdb,rooms:config.rooms});
+user.use({db:usersdb});
+
+app.get('/', site.index);
+app.get('/artworks', site.artworks);
+app.get('/signup', site.signup);
+app.post('/signup', user.validateSignUp, user.userExists, user.emailExists, user.createAccount);
+app.get('/login', site.login);
+app.post('/login', user.validateLogin, user.checkUser, user.authenticate);
+app.get('/logout', user.logout);
+app.get('/:room', site.room);
+app.get('/user/*', user.profile);
+
+// App listen
+app.listen(config.port);
+
+/**
+ * Setting up Socket.IO.
+ */
+
+var io = require('socket.io').listen(app)
+ , sockets = Object.create(null); // Sockets of all rooms
+
+// Configuration
+io.enable('browser client minification');
+io.enable('browser client etag');
+io.enable('browser client gzip');
+io.set('log level', 1);
+io.set('transports', [
+ 'websocket'
+ , 'htmlfile'
+ , 'xhr-polling'
+ , 'jsonp-polling'
+]);
+
+// Authorization
+io.set('authorization', function(data, accept) {
+ if(!data.headers.cookie) {
+ return accept('no cookie transmitted', false);
+ }
+ var cookie = parseCookie(data.headers.cookie);
+ sessionstore.get(cookie['connect.sid'], function(err, session) {
+ if (err) {
+ return accept(err.toString(), false);
+ }
+ else if (!session) {
+ return accept('session not found', false);
+ }
+ data.session = session;
+ accept(null, true);
+ });
+});
+
+io.sockets.on('connection', function(socket) {
+ var session = socket.handshake.session;
+ socket.on('getoverview', function() {
+ var data = Object.create(null);
+ for (var prop in rooms) {
+ data[prop] = rooms[prop].getPopulation();
+ }
+ socket.join('home');
+ socket.emit('overview', data);
+ });
+ socket.on('loggedin', function(fn) {
+ return (session.user) ? fn(session.user) : fn(false);
+ });
+ socket.on('joinroom', function(room) {
+ if (session.user && typeof room === 'string' && config.rooms.indexOf(room) !== -1) {
+ if (sockets[session.user]) { // User already in a room
+ socket.emit('alreadyinaroom');
+ return;
+ }
+ socket.nickname = session.user;
+ rooms[room].joinRoom(socket);
+ }
+ });
+ socket.on('joinanonymously', function(nickname, roomname) {
+ if (!socket.nickname && typeof nickname === 'string' && nickname !== '' &&
+ typeof roomname === 'string' && config.rooms.indexOf(roomname) !== -1) {
+ rooms[roomname].setNickName(socket, nickname);
+ }
+ });
+ socket.on('getstatus', function() {
+ if (socket.roomname) {
+ rooms[socket.roomname].sendStatus(socket);
+ }
+ });
+ socket.on('sendchatmsg', function(msg, to) {
+ if (socket.roomname && typeof msg === 'string') {
+ rooms[socket.roomname].sendChatMessage(msg, socket, to);
+ }
+ });
+ socket.on('guess', function(guess) {
+ if (socket.roomname && typeof guess === 'string') {
+ rooms[socket.roomname].guess(socket, guess);
+ }
+ });
+ socket.on('disconnect', function() {
+ if (socket.roomname) {
+ rooms[socket.roomname].removeUser(socket.nickname);
+ }
+ });
+});
+
+/**
+ * Setting up the rooms.
+ */
+
+var roomoptions = {
+ songsdb: songsdb,
+ usersdb: usersdb,
+ io: io,
+ sockets: sockets,
+ songsinarun: config.songsinarun,
+ fifolength: config.songsinarun * config.gameswithnorepeats,
+ threshold: config.allowederrors
+};
+
+var Room = require('./lib/room')(roomoptions)
+ , rooms = Object.create(null); // The Object that contains all the room instances
+
+for (var i=0; i<config.rooms.length; i++) {
+ rooms[config.rooms[i]] = new Room(config.rooms[i]);
+ rooms[config.rooms[i]].start();
+}
+
+console.log(' binb started and listening on port ' + config.port);
+++ /dev/null
-/* The Base Configuration file */
-
-exports.configure = function() {
- this.port = 80;
- this.songsdburl = '';
- this.usersdburl = '';
- this.sessionsecret = '';
- this.songsinarun = 15;
- this.fifolength = this.songsinarun * 3; // 3 is the number of games with no repeats of songs
- this.threshold = 2; // Edit distance threshold
- this.rooms = ["pop", "rock", "rap", "80s", "mixed"];
- return this;
-};
--- /dev/null
+{
+ "port": 80,
+ "songsdburl": "",
+ "usersdburl": "",
+ "sessionsecret": "",
+ "songsinarun": 15,
+ "gameswithnorepeats": 3,
+ "allowederrors": 2,
+ "rooms": ["pop", "rock", "rap", "80s", "mixed"]
+}
-var canvas = require("canvas");
+/**
+ * Module dependencies.
+ */
-module.exports = function(characters) {
+var canvas = require('canvas')
+ , characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
- function Captcha() {
- var code = "";
- while (code.length < 4) {
- code += characters[Math.floor(Math.random() * characters.length)];
- }
- var _canvas = new canvas(64, 26);
- var ctx = _canvas.getContext('2d');
- ctx.fillStyle = "#DDDDDD";
- ctx.fillRect(0, 0, 64, 26);
- ctx.font = "bold 20px Helvetica";
- ctx.lineWidth = 1;
- ctx.textAlign = "center";
- ctx.strokeStyle = "#080";
- ctx.strokeText(code, 31, 20);
- ctx.save();
- this.getCode = function() {
- return code;
- };
- this.toDataURL = function() {
- return _canvas.toDataURL();
- };
- }
+/**
+ * Export the class.
+ */
- return Captcha;
+module.exports = function() {
+ var code = ''
+ , _canvas = new canvas(64, 26)
+ , ctx = _canvas.getContext('2d');
+
+ while (code.length < 4) {
+ code += characters[Math.floor(Math.random() * characters.length)];
+ }
+
+ ctx.fillStyle = '#DDDDDD';
+ ctx.fillRect(0, 0, 64, 26);
+ ctx.font = 'bold 20px Helvetica';
+ ctx.lineWidth = 1;
+ ctx.textAlign = "center";
+ ctx.strokeStyle = '#080';
+ ctx.strokeText(code, 31, 20);
+ ctx.save();
+
+ this.getCode = function() {
+ return code;
+ };
+
+ this.toDataURL = function() {
+ return _canvas.toDataURL();
+ };
};
-module.exports = function(threshold) {
+/**
+ * Check if the edit distance between two strings is smaller than a threshold k.
+ * We dont need to trace back the optimal alignment, so we can run the Levenshtein distance
+ * algorithm in better than O(n*m).
+ * We use only a diagonal stripe of width 2k+1 in the matrix.
+ * See Algorithms on strings, trees, and sequences: computer science and computational biology.
+ * Cambridge, UK: Cambridge University Press. pp 263-264. ISBN 0-521-58519-8.
+ */
+var checkDistance = function(s1, s2, k) {
+ if (k === 0) {
+ return s1 === s2;
+ }
+ if (Math.abs(s1.length - s2.length) > k) {
+ return false;
+ }
+ var d = [];
+ for (var i=0; i <= s1.length; i++) {
+ d[i] = []; // Now d is a matrix with s1.length + 1 rows
+ d[i][0] = i;
+ }
+ for (var j=1; j <= s2.length; j++) {
+ d[0][j] = j;
+ }
+ for (i=1; i <= s1.length; i++) {
+ var l = ((i-k) < 1) ? 1 : i-k;
+ var m = ((i+k) > s2.length) ? s2.length : i+k;
+ for (j=l; j<=m; j++) {
+ if (s1.charAt(i-1) === s2.charAt(j-1)) {
+ d[i][j] = d[i-1][j-1];
+ }
+ else {
+ if ((j === l) && (d[i][j-1] === undefined)) {
+ d[i][j] = Math.min(d[i-1][j-1]+1, d[i-1][j]+1);
+ }
+ else if ((j === m) && (d[i-1][j] === undefined)) {
+ d[i][j] = Math.min(d[i][j-1]+1, d[i-1][j-1]+1);
+ }
+ else {
+ d[i][j] = Math.min(d[i][j-1]+1, d[i-1][j-1]+1, d[i-1][j]+1);
+ }
+ }
+ }
+ }
+ return d[s1.length][s2.length] <= k;
+};
+
+/**
+ * Return `amatch` function.
+ */
+
+module.exports = function(allowederrors) {
+
+ /**
+ * Edit distance threshold.
+ */
+
+ var threshold = allowederrors;
+
+ var amatch = function(subject, guess, enableartistrules) {
+ if (checkDistance(subject, guess, threshold)) {
+ return true;
+ }
+
+ var splitted, trimmed;
+
+ // Ignore dots
+ if (subject.match(/\./) &&
+ checkDistance(subject.replace(/\./g, ""), guess, threshold)) {
+ return true;
+ }
+ // Ignore commas
+ if (subject.match(/,/) &&
+ checkDistance(subject.replace(/,/g, ""), guess, threshold)) {
+ return true;
+ }
+ // Ignore dashes
+ if (subject.match(/\-/) &&
+ checkDistance(subject.replace(/\-/g, ""), guess, threshold)) {
+ return true;
+ }
+ // Allow to write "and" in place of "+"
+ if (subject.match(/\+/) &&
+ checkDistance(subject.replace(/\+/, "and"), guess, threshold)) {
+ return true;
+ }
- /*
- Check if the edit distance between two strings is smaller than a threshold k.
- We dont need to trace back the optimal alignment, so we can run the Levenshtein distance algorithm
- in better than O(n*m).
- We use only a diagonal stripe of width 2k+1 in the matrix.
- See Algorithms on strings, trees, and sequences: computer science and computational biology.
- Cambridge, UK: Cambridge University Press. pp 263-264. ISBN 0-521-58519-8.
- */
- var checkDistance = function(s1, s2, k) {
- if (k === 0) {
- return s1 === s2;
- }
- if (Math.abs(s1.length - s2.length) > k) {
- return false;
- }
- var d = [];
- for (var i=0; i <= s1.length; i++) {
- d[i] = []; // Now d is a matrix with s1.length + 1 rows
- d[i][0] = i;
- }
- for (var j=0; j <= s2.length; j++) {
- d[0][j] = j;
- }
- for (i=1; i <= s1.length; i++) {
- var l = ((i-k) < 1) ? 1 : i-k;
- var m = ((i+k) > s2.length) ? s2.length : i+k;
- for (j=l; j<=m; j++) {
- if (s1.charAt(i-1) === s2.charAt(j-1)) {
- d[i][j] = d[i-1][j-1];
- }
- else {
- if ((j === l) && (d[i][j-1] === undefined)) {
- d[i][j] = Math.min(d[i-1][j-1]+1, d[i-1][j]+1);
- }
- else if ((j === m) && (d[i-1][j] === undefined)) {
- d[i][j] = Math.min(d[i][j-1]+1, d[i-1][j-1]+1);
- }
- else {
- d[i][j] = Math.min(d[i][j-1]+1, d[i-1][j-1]+1, d[i-1][j]+1);
- }
- }
- }
- }
- return d[s1.length][s2.length] <= k;
- };
+ if (enableartistrules) {
+ // Ignore "the" at the beginning of artist name
+ if (subject.match(/^the /)) {
+ var nothe = subject.replace(/^the /, "");
+ if (checkDistance(nothe, guess, threshold)) {
+ return true;
+ }
+ if (nothe.match(/jimi hendrix experience/) &&
+ checkDistance(nothe.replace(/ experience/, ""), guess, threshold)) {
+ return true;
+ }
+ }
+ // Split artist name on "&" (artist name can be composed by more names)
+ splitted = subject.split("&");
+ if (splitted.length !== 1) {
+ for (var i=0; i<splitted.length; i++) {
+ trimmed = splitted[i].replace(/^ +/, "").replace(/ +$/, "");
+ if (checkDistance(trimmed, guess, threshold)) {
+ return true;
+ }
+ if (trimmed.match(/^the /) &&
+ checkDistance(trimmed.replace(/^the /, ""), guess, threshold)) {
+ return true;
+ }
+ }
+ }
+ }
+ else {
+ // Allow to write "and" in place of "&"
+ if (subject.match(/ & /) && !subject.match(/\(/) &&
+ checkDistance(subject.replace(/ & /, " and "), guess, threshold)) {
+ return true;
+ }
+ // Ignore additional info e.g. "(Love Theme from Titanic)"
+ if (subject.match(/\(.+\)\??(?: \[.+\])?/)) {
+ var normalized = subject.replace(/\(.+\)\??(?: \[.+\])?/, "")
+ .replace(/^ +/, "").replace(/ +$/, "");
+ if (checkDistance(normalized, guess, threshold)) {
+ return true;
+ }
+ if (normalized.match(/ & /) &&
+ checkDistance(normalized.replace(/ & /, " and "), guess, threshold)) {
+ return true;
+ }
+ }
+ if (subject.match(/, [pP]t\. [0-9]$/) &&
+ checkDistance(subject.replace(/, [pP]t\. [0-9]$/, ""), guess, threshold)) {
+ return true;
+ }
+ }
- var amatch = function(subject, guess, enableartistrules) {
- if (checkDistance(subject, guess, threshold)) {
- return true;
- }
- var splitted, trimmed;
- if (subject.match(/\./) &&
- checkDistance(subject.replace(/\./g, ""), guess, threshold)) {
- return true;
- }
- if (subject.match(/\-/) &&
- checkDistance(subject.replace(/\-/g, ""), guess, threshold)) {
- return true;
- }
- if (subject.match(/\+/) &&
- checkDistance(subject.replace(/\+/, "and"), guess, threshold)) {
- return true;
- }
- if (enableartistrules) {
- if (subject.match(/^the /)) {
- var nothe = subject.replace(/^the /, "");
- if (checkDistance(nothe, guess, threshold)) {
- return true;
- }
- if (nothe.match(/jimi hendrix experience/) &&
- checkDistance(nothe.replace(/ experience/, ""), guess, threshold)) {
- return true;
- }
- }
- splitted = subject.split("&");
- if (splitted.length !== 1) {
- for (var i=0; i<splitted.length; i++) {
- trimmed = splitted[i].replace(/^ +/, "").replace(/ +$/, "");
- if (checkDistance(trimmed, guess, threshold)) {
- return true;
- }
- if (trimmed.match(/^the /) &&
- checkDistance(trimmed.replace(/^the /, ""), guess, threshold)) {
- return true;
- }
- }
- }
- }
- else {
- if (subject.match(/,/) &&
- checkDistance(subject.replace(/,/g, ""), guess, threshold)) {
- return true;
- }
- if (subject.match(/ & /) && !subject.match(/\(/) &&
- checkDistance(subject.replace(/ & /, " and "), guess, threshold)) {
- return true;
- }
- if (subject.match(/\(.+\)\??(?: \[.+\])?/)) {
- var normalized = subject.replace(/\(.+\)\??(?: \[.+\])?/, "")
- .replace(/^ +/, "").replace(/ +$/, "");
- if (checkDistance(normalized, guess, threshold)) {
- return true;
- }
- if (normalized.match(/ & /) &&
- checkDistance(normalized.replace(/ & /, " and "), guess, threshold)) {
- return true;
- }
- }
- if (subject.match(/, [pP]t\. [0-9]$/) &&
- checkDistance(subject.replace(/, [pP]t\. [0-9]$/, ""), guess, threshold)) {
- return true;
- }
- }
- return false;
- };
+ return false;
+ };
- return amatch;
+ return amatch;
};
+/**
+ * Return the `Room` class
+ */
+
module.exports = function(params) {
- var songsdb = params.songsdb;
- var usersdb = params.usersdb;
- var io = params.io;
- var sockets = params.sockets;
- var songsinarun = params.songsinarun;
- var fifolength = params.fifolength;
- var threshold = params.threshold;
-
- var amatch = require('./match.js')(threshold);
- var collectStats = require('./stats.js')(usersdb);
-
- function Room(roomname) {
-
- var roomname = roomname;
- var totusers = 0;
-
- var usersData = Object.create(null);
- var playedtracks = []; // The list of already played songs
-
- var artistName = null;
- var artistlcase = null;
- var trackName = null;
- var tracklcase = null;
- var collectionName = null;
- var previewUrl = null;
- var artworkUrl = null;
- var trackViewUrl = null;
- var finishline = 1;
- var allowedguess = false;
- var status = null;
- var songtimeleft = null; // Milliseconds
- var songcounter = 0;
- var trackscount = 0;
-
- this.getPopulation = function() {
- return totusers;
- };
-
- var addUser = function(socket, loggedin) {
- sockets[socket.nickname] = socket;
- usersData[socket.nickname] = {
- nickname: socket.nickname,
- registered: loggedin,
- points: 0,
- roundpoints: 0,
- matched: null,
- guessed: 0,
- guesstime: null,
- totguesstime: 0,
- golds: 0,
- silvers: 0,
- bronzes: 0
- };
- totusers = totusers + 1;
- io.sockets.in('home').emit('update', {room:roomname,players:totusers});
- // Broadcast new user event
- socket.emit('ready', {users:usersData,trackscount:trackscount,loggedin:loggedin});
- socket.broadcast.to(roomname).emit('newuser', {nickname:socket.nickname,users:usersData});
- };
-
- var removeUser = function(nickname) {
- // Delete the references
- delete sockets[nickname];
- delete usersData[nickname];
- totusers = totusers - 1;
- io.sockets.in('home').emit('update', {room:roomname,players:totusers});
- };
-
- var userExists = function(nickname) {
- var user = usersData[nickname];
- if (user) {
- return true;
- }
- return false;
- };
-
- this.joinRoom = function(socket) {
- socket.roomname = roomname;
- socket.join(roomname);
- addUser(socket, true);
- };
-
- // A user requested an invalid name
- var invalidNickName = function(socket, feedback) {
- socket.emit('invalidnickname', feedback);
- };
-
- // A user is submitting a name
- this.setNickName = function(socket, data) {
- var feedback = null;
- if (data.nickname.length > 15) {
- feedback = '<span class="label label-important">That name is too long.</span>';
- }
- else if (data.nickname === "binb") {
- feedback = '<span class="label label-important">That name is reserved.</span>';
- }
- else if (sockets[data.nickname]) {
- feedback = '<span class="label label-important">Name already taken.</span>';
- }
- if (feedback) {
- return invalidNickName(socket, feedback);
- }
-
- var key = "user:"+data.nickname;
- usersdb.exists(key, function(err, resp) {
- if (resp === 1) { // User already exists
- feedback = '<span class="label label-important">That name belongs ';
- feedback += 'to a registered user.</span>';
- return invalidNickName(socket, feedback);
- }
- else {
- socket.nickname = data.nickname;
- socket.roomname = roomname;
- socket.join(roomname);
- // Add user to the list of active users
- addUser(socket, false);
- }
- });
- };
-
- // A user has left (DCed, etc.)
- this.userLeft = function(nickname) {
- removeUser(nickname);
- io.sockets.in(roomname).emit('userleft', {nickname:nickname,users:usersData});
- };
-
- this.sendChatMessage = function (socket, data) {
- if (typeof data === "string") {
- var datalcase = data.toLowerCase();
- if (allowedguess && (amatch(artistlcase, datalcase, true) ||
- amatch(tracklcase, datalcase))) {
- var msg = "You are probably right, but you have to use the box above.";
- socket.emit('chatmsg', {from:"binb",to:socket.nickname,chatmsg:msg});
- return;
- }
- io.sockets.in(roomname).emit('chatmsg', {from:socket.nickname,chatmsg:data});
- }
- else if (typeof data === "object" && typeof data.to === "string" &&
- userExists(data.to) && typeof data.chatmsg === "string") {
- // Private message
- socket.emit('chatmsg', {from:socket.nickname,to:data.to,chatmsg:data.chatmsg});
- var recipient = sockets[data.to];
- recipient.emit('chatmsg', {from:socket.nickname,to:data.to,chatmsg:data.chatmsg});
- }
- };
-
- var addPoints = function(nickname, allinone) {
- usersData[nickname].guesstime = 30000 - songtimeleft;
- var stats = {};
- switch (finishline) {
- case 1:
- finishline++;
- usersData[nickname].roundpoints = 6;
- if (allinone) {
- usersData[nickname].points += 6;
- stats.points = 6;
- }
- else {
- usersData[nickname].points += 5;
- stats.points = 5;
- }
- usersData[nickname].golds++;
- stats.gold = true;
- break;
- case 2:
- finishline++;
- usersData[nickname].roundpoints = 5;
- if (allinone) {
- usersData[nickname].points += 5;
- stats.points = 5;
- }
- else {
- usersData[nickname].points += 4;
- stats.points = 4;
- }
- usersData[nickname].silvers++;
- stats.silver = true;
- break;
- case 3:
- finishline++;
- usersData[nickname].roundpoints = 4;
- if (allinone) {
- usersData[nickname].points += 4;
- stats.points = 4;
- }
- else {
- usersData[nickname].points += 3;
- stats.points = 3;
- }
- usersData[nickname].bronzes++;
- stats.bronze = true;
- break;
- default:
- usersData[nickname].roundpoints = 3;
- if (allinone) {
- usersData[nickname].points += 3;
- stats.points = 3;
- }
- else {
- usersData[nickname].points += 2;
- stats.points = 2;
- }
- }
- usersData[nickname].matched = 'both';
- usersData[nickname].guessed++;
- usersData[nickname].totguesstime += usersData[nickname].guesstime;
-
- if (usersData[nickname].registered) {
- stats.userscore = usersData[nickname].points;
- stats.guesstime = usersData[nickname].guesstime;
- collectStats(nickname, stats);
- }
- };
-
- this.guess = function(socket, guess) {
- if (allowedguess) {
- if (!usersData[socket.nickname].matched) { // No track no artist
- if ((artistlcase === tracklcase) && amatch(tracklcase, guess, true)) {
- addPoints(socket.nickname, true);
- socket.emit('bothmatched');
- io.sockets.in(roomname).emit('updateusers', {users:usersData});
- }
- else if (amatch(artistlcase, guess, true)) {
- usersData[socket.nickname].roundpoints++;
- usersData[socket.nickname].points++;
- usersData[socket.nickname].matched = 'artist';
- socket.emit('artistmatched');
- io.sockets.in(roomname).emit('updateusers', {users:usersData});
- if (usersData[socket.nickname].registered) {
- var stats = {points:1,userscore:usersData[socket.nickname].points};
- collectStats(socket.nickname, stats);
- }
- }
- else if (amatch(tracklcase, guess)) {
- usersData[socket.nickname].roundpoints++;
- usersData[socket.nickname].points++;
- usersData[socket.nickname].matched = 'title';
- socket.emit('titlematched');
- io.sockets.in(roomname).emit('updateusers', {users:usersData});
- if (usersData[socket.nickname].registered) {
- var stats = {points:1,userscore:usersData[socket.nickname].points};
- collectStats(socket.nickname, stats);
- }
- }
- else {
- socket.emit('nomatch');
- }
- }
- else if (usersData[socket.nickname].matched !== 'both') { // Track or artist
- if (usersData[socket.nickname].matched === 'artist') {
- if (amatch(tracklcase, guess)) {
- addPoints(socket.nickname, false);
- socket.emit('bothmatched');
- io.sockets.in(roomname).emit('updateusers', {users:usersData});
- }
- else {
- socket.emit('nomatch');
- }
- }
- else {
- if (amatch(artistlcase, guess, true)) {
- addPoints(socket.nickname, false);
- socket.emit('bothmatched');
- io.sockets.in(roomname).emit('updateusers', {users:usersData});
- }
- else {
- socket.emit('nomatch');
- }
- }
- }
- else { // The user has guessed both track and artist
- socket.emit('stoptrying');
- }
- }
- else {
- socket.emit('noguesstime');
- }
- };
-
- var resetPoints = function(roundonly) {
- for (var key in usersData) {
- if (!roundonly) {
- usersData[key].points = 0;
- usersData[key].guessed = 0;
- usersData[key].totguesstime = 0;
- usersData[key].golds = 0;
- usersData[key].silvers = 0;
- usersData[key].bronzes = 0;
- }
- usersData[key].roundpoints = 0;
- usersData[key].matched = null;
- usersData[key].guesstime = null;
- }
- };
-
- var sendLoadTrack = function() {
- songsdb.srandmember(roomname, function(err, res) {
- songsdb.hmget(res, "artistName", "trackName", "collectionName", "previewUrl",
- "artworkUrl60", "trackViewUrl", function(e, replies) {
- if (playedtracks.indexOf(res) !== -1) {
- return sendLoadTrack();
- }
- playedtracks.push(res);
- artistName = replies[0];
- artistlcase = artistName.toLowerCase();
- trackName = replies[1];
- tracklcase = trackName.toLowerCase();
- collectionName = replies[2];
- previewUrl = replies[3];
- artworkUrl = replies[4];
- trackViewUrl = replies[5];
- io.sockets.in(roomname).emit('loadtrack', {previewUrl:previewUrl});
- setTimeout(sendPlayTrack, 5000);
- });
- });
- status = 1; // Loading next song
- };
-
- var sendPlayTrack = function() {
- songcounter = songcounter + 1;
- status = 0; // Playing track
- io.sockets.in(roomname).emit('playtrack', {counter:songcounter,tot:songsinarun,
- users:usersData});
- songTimeLeft(Date.now()+30000, 50);
- allowedguess = true;
- setTimeout(sendTrackInfo, 30000);
- };
-
- var songTimeLeft = function(end, delay) {
- songtimeleft = end - Date.now();
- if (songtimeleft < delay) {
- return;
- }
- setTimeout(songTimeLeft, delay, end, delay);
- };
-
- var sendTrackInfo = function() {
- io.sockets.in(roomname).emit('trackinfo', {artworkUrl:artworkUrl,artistName:artistName,
- trackName:trackName,collectionName:collectionName,
- trackViewUrl:trackViewUrl});
- finishline = 1;
- allowedguess = false;
- if (songcounter < songsinarun) {
- resetPoints(true);
- sendLoadTrack();
- }
- else {
- status = 2; // Sending last track info
- setTimeout(gameOver, 5000);
- }
- };
-
- var gameOver = function() {
- status = 3; // Game over
- var users = [];
- for (var key in usersData) {
- users.push(usersData[key]);
- }
- users.sort(function(a, b) {return b.points - a.points;});
- var podium = users.slice(0,3);
- io.sockets.in(roomname).emit('gameover', {users:podium});
- if (podium[0] && podium[0].registered) {
- collectStats(podium[0].nickname, {firstplace:true});
- }
- if (podium[1] && podium[1].registered) {
- collectStats(podium[1].nickname, {secondplace:true});
- }
- if (podium[2] && podium[2].registered) {
- collectStats(podium[2].nickname, {thirdplace:true});
- }
- resetPoints(false);
- setTimeout(reset, 5000);
- };
-
- this.sendStatus = function(socket) {
- socket.emit('status', {status:status,timeleft:songtimeleft,previewUrl:previewUrl});
- };
-
- var reset = function() {
- songcounter = 0;
- if (playedtracks.length === fifolength) {
- playedtracks.splice(0, songsinarun);
- }
- sendLoadTrack();
- };
-
- // Start the room
- this.start = function() {
- songsdb.scard(roomname, function(err, res) {
- trackscount = res;
- });
- sendLoadTrack();
- };
- }
-
- return Room;
+ /**
+ * Room dependencies.
+ */
+
+ var amatch = require('./match')(params.threshold)
+ , collectStats = require('./stats')(params.usersdb)
+ , fifolength = params.fifolength
+ , io = params.io
+ , sockets = params.sockets
+ , songsdb = params.songsdb
+ , songsinarun = params.songsinarun
+ , usersdb = params.usersdb;
+
+ /**
+ * Room class.
+ */
+
+ function Room(roomname) {
+
+ var allowedguess = false
+ , artistlcase
+ , artistName
+ , artworkUrl
+ , collectionName
+ , finishline = 1
+ , playedtracks = [] // The list of already played songs
+ , previewUrl
+ , songcounter = 0
+ , songtimeleft // Milliseconds
+ , status
+ , tracklcase
+ , trackName
+ , trackscount = 0
+ , trackViewUrl
+ , totusers = 0
+ , usersData = Object.create(null);
+
+ // Add a new user in the room.
+ var addUser = function(socket, loggedin) {
+ sockets[socket.nickname] = socket;
+ usersData[socket.nickname] = {
+ nickname: socket.nickname,
+ registered: loggedin,
+ points: 0,
+ roundpoints: 0,
+ matched: null,
+ guessed: 0,
+ guesstime: null,
+ totguesstime: 0,
+ golds: 0,
+ silvers: 0,
+ bronzes: 0
+ };
+ totusers++;
+ // Broadcast new user event
+ io.sockets.in('home').emit('update', roomname, totusers);
+ socket.emit('ready', usersData, trackscount, loggedin);
+ socket.broadcast.to(roomname).emit('newuser', socket.nickname, usersData);
+ };
+
+ // A user has left (DCed, etc.)
+ this.removeUser = function(nickname) {
+ // Delete the references
+ delete sockets[nickname];
+ delete usersData[nickname];
+ totusers--;
+ // Broadcast the event
+ io.sockets.in('home').emit('update', roomname, totusers);
+ io.sockets.in(roomname).emit('userleft', nickname, usersData);
+ };
+
+ // Return the number of users in the room.
+ this.getPopulation = function() {
+ return totusers;
+ };
+
+ this.joinRoom = function(socket) {
+ socket.roomname = roomname;
+ socket.join(roomname);
+ addUser(socket, true);
+ };
+
+ // A user requested an invalid name
+ var invalidNickName = function(socket, feedback) {
+ socket.emit('invalidnickname', feedback);
+ };
+
+ // A user is submitting a name
+ this.setNickName = function(socket, nickname) {
+ var feedback = null;
+
+ if (nickname.length > 15) {
+ feedback = '<span class="label label-important">That name is too long.</span>';
+ }
+ else if (nickname === 'binb') {
+ feedback = '<span class="label label-important">That name is reserved.</span>';
+ }
+ else if (sockets[nickname]) {
+ feedback = '<span class="label label-important">Name already taken.</span>';
+ }
+
+ if (feedback) {
+ return invalidNickName(socket, feedback);
+ }
+
+ // Check if requested nickname belong to a registered user
+ var key = 'user:'+nickname;
+ usersdb.exists(key, function(err, resp) {
+ if (resp === 1) {
+ feedback = '<span class="label label-important">That name belongs ';
+ feedback += 'to a registered user.</span>';
+ return invalidNickName(socket, feedback);
+ }
+ socket.nickname = nickname;
+ socket.roomname = roomname;
+ socket.join(roomname);
+ addUser(socket, false);
+ });
+ };
+
+ // A user is sending a chat message
+ this.sendChatMessage = function(msg, socket, to) {
+ if (typeof to === 'string') {
+ // Check if the recipient is in the room
+ if (usersData[to]) {
+ socket.emit('chatmsg', msg, socket.nickname, to);
+ var recipient = sockets[to];
+ recipient.emit('chatmsg', msg, socket.nickname, to);
+ }
+ return;
+ }
+ // Censor answers from chat
+ var msglcase = msg.toLowerCase();
+ if (allowedguess && (amatch(artistlcase, msglcase, true) ||
+ amatch(tracklcase, msglcase))) {
+ var notice = 'You are probably right, but you have to use the box above.';
+ socket.emit('chatmsg', notice, 'binb', socket.nickname);
+ return;
+ }
+ io.sockets.in(roomname).emit('chatmsg', msg, socket.nickname);
+ };
+
+ // User points and statistics
+ var addPointsAndStats = function(nickname, allinone) {
+ usersData[nickname].guesstime = 30000 - songtimeleft;
+ var stats = {};
+ switch (finishline) {
+ case 1:
+ finishline++;
+ usersData[nickname].roundpoints = 6;
+ if (allinone) {
+ usersData[nickname].points += 6;
+ stats.points = 6;
+ }
+ else {
+ usersData[nickname].points += 5;
+ stats.points = 5;
+ }
+ usersData[nickname].golds++;
+ stats.gold = true;
+ break;
+ case 2:
+ finishline++;
+ usersData[nickname].roundpoints = 5;
+ if (allinone) {
+ usersData[nickname].points += 5;
+ stats.points = 5;
+ }
+ else {
+ usersData[nickname].points += 4;
+ stats.points = 4;
+ }
+ usersData[nickname].silvers++;
+ stats.silver = true;
+ break;
+ case 3:
+ finishline++;
+ usersData[nickname].roundpoints = 4;
+ if (allinone) {
+ usersData[nickname].points += 4;
+ stats.points = 4;
+ }
+ else {
+ usersData[nickname].points += 3;
+ stats.points = 3;
+ }
+ usersData[nickname].bronzes++;
+ stats.bronze = true;
+ break;
+ default:
+ usersData[nickname].roundpoints = 3;
+ if (allinone) {
+ usersData[nickname].points += 3;
+ stats.points = 3;
+ }
+ else {
+ usersData[nickname].points += 2;
+ stats.points = 2;
+ }
+ }
+ usersData[nickname].matched = 'both';
+ usersData[nickname].guessed++;
+ usersData[nickname].totguesstime += usersData[nickname].guesstime;
+
+ if (usersData[nickname].registered) {
+ stats.userscore = usersData[nickname].points;
+ stats.guesstime = usersData[nickname].guesstime;
+ collectStats(nickname, stats);
+ }
+ };
+
+ // A user is sending a guess
+ this.guess = function(socket, guess) {
+ if (allowedguess) {
+ if (!usersData[socket.nickname].matched) { // No track no artist
+ if ((artistlcase === tracklcase) && amatch(tracklcase, guess, true)) {
+ addPointsAndStats(socket.nickname, true);
+ socket.emit('bothmatched');
+ io.sockets.in(roomname).emit('updateusers', usersData);
+ }
+ else if (amatch(artistlcase, guess, true)) {
+ usersData[socket.nickname].roundpoints++;
+ usersData[socket.nickname].points++;
+ usersData[socket.nickname].matched = 'artist';
+ socket.emit('artistmatched');
+ io.sockets.in(roomname).emit('updateusers', usersData);
+ if (usersData[socket.nickname].registered) {
+ var stats = {points:1,userscore:usersData[socket.nickname].points};
+ collectStats(socket.nickname, stats);
+ }
+ }
+ else if (amatch(tracklcase, guess)) {
+ usersData[socket.nickname].roundpoints++;
+ usersData[socket.nickname].points++;
+ usersData[socket.nickname].matched = 'title';
+ socket.emit('titlematched');
+ io.sockets.in(roomname).emit('updateusers', usersData);
+ if (usersData[socket.nickname].registered) {
+ var stats = {points:1,userscore:usersData[socket.nickname].points};
+ collectStats(socket.nickname, stats);
+ }
+ }
+ else {
+ socket.emit('nomatch');
+ }
+ }
+ else if (usersData[socket.nickname].matched !== 'both') { // Track or artist
+ if (usersData[socket.nickname].matched === 'artist') {
+ if (amatch(tracklcase, guess)) {
+ addPointsAndStats(socket.nickname, false);
+ socket.emit('bothmatched');
+ io.sockets.in(roomname).emit('updateusers', usersData);
+ }
+ else {
+ socket.emit('nomatch');
+ }
+ }
+ else {
+ if (amatch(artistlcase, guess, true)) {
+ addPointsAndStats(socket.nickname, false);
+ socket.emit('bothmatched');
+ io.sockets.in(roomname).emit('updateusers', usersData);
+ }
+ else {
+ socket.emit('nomatch');
+ }
+ }
+ }
+ else { // The user has guessed both track and artist
+ socket.emit('stoptrying');
+ }
+ }
+ else {
+ socket.emit('noguesstime');
+ }
+ };
+
+ var resetPoints = function(roundonly) {
+ for (var key in usersData) {
+ if (!roundonly) {
+ usersData[key].points = 0;
+ usersData[key].guessed = 0;
+ usersData[key].totguesstime = 0;
+ usersData[key].golds = 0;
+ usersData[key].silvers = 0;
+ usersData[key].bronzes = 0;
+ }
+ usersData[key].roundpoints = 0;
+ usersData[key].matched = null;
+ usersData[key].guesstime = null;
+ }
+ };
+
+ // Extract a random track from the database and send the load event
+ var sendLoadTrack = function() {
+ songsdb.srandmember(roomname, function(err, res) {
+ songsdb.hmget(res, 'artistName', 'trackName', 'collectionName', 'previewUrl',
+ 'artworkUrl60', 'trackViewUrl', function(e, replies) {
+ // Check if extracted track is in the list of already played tracks
+ if (playedtracks.indexOf(res) !== -1) {
+ return sendLoadTrack();
+ }
+ playedtracks.push(res);
+ artistName = replies[0];
+ artistlcase = artistName.toLowerCase();
+ trackName = replies[1];
+ tracklcase = trackName.toLowerCase();
+ collectionName = replies[2];
+ previewUrl = replies[3];
+ artworkUrl = replies[4];
+ trackViewUrl = replies[5];
+ io.sockets.in(roomname).emit('loadtrack', previewUrl);
+ setTimeout(sendPlayTrack, 5000);
+ });
+ });
+ status = 1; // Loading next song
+ };
+
+ // Timer for the playing song
+ var songTimeLeft = function(end, delay) {
+ songtimeleft = end - Date.now();
+ if (songtimeleft < delay) {
+ return;
+ }
+ setTimeout(songTimeLeft, delay, end, delay);
+ };
+
+ var sendPlayTrack = function() {
+ songcounter++;
+ status = 0; // Playing track
+ var data = {
+ counter:songcounter,
+ tot:songsinarun,
+ users:usersData
+ };
+ io.sockets.in(roomname).emit('playtrack', data);
+ songTimeLeft(Date.now() + 30000, 50);
+ allowedguess = true;
+ setTimeout(sendTrackInfo, 30000);
+ };
+
+ var sendTrackInfo = function() {
+ var trackinfo = {
+ artworkUrl:artworkUrl,
+ artistName:artistName,
+ trackName:trackName,
+ trackViewUrl:trackViewUrl,
+ collectionName:collectionName
+ };
+ io.sockets.in(roomname).emit('trackinfo', trackinfo);
+ finishline = 1;
+ allowedguess = false;
+
+ if (songcounter < songsinarun) {
+ resetPoints(true);
+ sendLoadTrack();
+ return;
+ }
+
+ status = 2; // Sending last track info
+ setTimeout(gameOver, 5000);
+ };
+
+ var gameOver = function() {
+ status = 3; // Game over
+
+ // Build podium
+ var users = [];
+ for (var key in usersData) {
+ users.push(usersData[key]);
+ }
+ users.sort(function(a, b) {return b.points - a.points;});
+ var podium = users.slice(0,3);
+ io.sockets.in(roomname).emit('gameover', podium);
+
+ // Collect podium stats
+ if (podium[0] && podium[0].registered) {
+ collectStats(podium[0].nickname, {firstplace:true});
+ }
+ if (podium[1] && podium[1].registered) {
+ collectStats(podium[1].nickname, {secondplace:true});
+ }
+ if (podium[2] && podium[2].registered) {
+ collectStats(podium[2].nickname, {thirdplace:true});
+ }
+
+ resetPoints(false);
+ songcounter = 0;
+ // Check if FIFO is full
+ if (playedtracks.length === fifolength) {
+ playedtracks.splice(0, songsinarun);
+ }
+
+ // Start a new game
+ setTimeout(sendLoadTrack, 5000);
+ };
+
+ // Send the room status
+ this.sendStatus = function(socket) {
+ var data = {
+ status:status,
+ timeleft:songtimeleft,
+ previewUrl:previewUrl
+ };
+ socket.emit('status', data);
+ };
+
+ // Start the room
+ this.start = function() {
+ songsdb.scard(roomname, function(err, res) {
+ trackscount = res;
+ });
+ sendLoadTrack();
+ };
+ }
+
+ return Room;
};
+/**
+ * Return `collectStats` function.
+ */
+
module.exports = function(db) {
- var collectStats = function(username, stats) {
- var key = "user:"+username;
- if (stats.points) {
- db.hincrby(key, "totpoints", stats.points);
- }
- if (stats.userscore) {
- // Set personal best
- db.hget(key, "bestscore", function(err, res) {
- if (res < stats.userscore) {
- db.hset(key, "bestscore", stats.userscore);
- }
- });
- }
- if (stats.gold) {
- db.hincrby(key, "golds", 1);
- }
- if (stats.silver) {
- db.hincrby(key, "silvers", 1);
- }
- if (stats.bronze) {
- db.hincrby(key, "bronzes", 1);
- }
- if (stats.guesstime) {
- db.hincrby(key, "guessed", 1);
- db.hincrby(key, "totguesstime", stats.guesstime);
- db.hget(key, "bestguesstime", function(err, res) {
- if (stats.guesstime < res) {
- db.hset(key, "bestguesstime", stats.guesstime);
- }
- });
- db.hget(key, "worstguesstime", function(err, res) {
- if (stats.guesstime > res) {
- db.hset(key, "worstguesstime", stats.guesstime);
- }
- });
- }
- if (stats.firstplace) {
- db.hincrby(key, "victories", 1);
- }
- if (stats.secondplace) {
- db.hincrby(key, "secondplaces", 1);
- }
- if (stats.thirdplace) {
- db.hincrby(key, "thirdplaces", 1);
- }
- };
+ var collectStats = function(username, stats) {
+ var key = 'user:'+username;
+ if (stats.points) {
+ // Update total points
+ db.hincrby(key, 'totpoints', stats.points);
+ }
+ if (stats.userscore) {
+ // Set personal best
+ db.hget(key, 'bestscore', function(err, res) {
+ if (res < stats.userscore) {
+ db.hset(key, 'bestscore', stats.userscore);
+ }
+ });
+ }
+ if (stats.gold) {
+ // Update the number of golds
+ db.hincrby(key, 'golds', 1);
+ }
+ if (stats.silver) {
+ db.hincrby(key, 'silvers', 1);
+ }
+ if (stats.bronze) {
+ db.hincrby(key, 'bronzes', 1);
+ }
+ if (stats.guesstime) {
+ // Update the number of guessed tracks
+ db.hincrby(key, 'guessed', 1);
+ // Update total guess time
+ db.hincrby(key, 'totguesstime', stats.guesstime);
+ // Set best answer time
+ db.hget(key, 'bestguesstime', function(err, res) {
+ if (stats.guesstime < res) {
+ db.hset(key, 'bestguesstime', stats.guesstime);
+ }
+ });
+ // Set worst answer time
+ db.hget(key, 'worstguesstime', function(err, res) {
+ if (stats.guesstime > res) {
+ db.hset(key, 'worstguesstime', stats.guesstime);
+ }
+ });
+ }
+ if (stats.firstplace) {
+ // Update the number of first places
+ db.hincrby(key, 'victories', 1);
+ }
+ if (stats.secondplace) {
+ db.hincrby(key, 'secondplaces', 1);
+ }
+ if (stats.thirdplace) {
+ db.hincrby(key, 'thirdplaces', 1);
+ }
+ };
- return collectStats;
+ return collectStats;
};
--- /dev/null
+/**
+ * Export the class.
+ */
+
+module.exports = function(username, email, salt, hash, joindate) {
+ this.username = username;
+ this.email = email;
+ this.salt = salt;
+ this.password = hash;
+ this.joindate = joindate;
+ this.totpoints = 0;
+ this.bestscore = 0;
+ this.golds = 0;
+ this.silvers = 0;
+ this.bronzes = 0;
+ this.bestguesstime = 30000;
+ this.worstguesstime = 0;
+ this.totguesstime = 0;
+ this.guessed = 0;
+ this.victories = 0;
+ this.secondplaces = 0;
+ this.thirdplaces = 0;
+};
"connect": "1.8.x",
"connect-redis": "1.3.x",
"express": "2.5.x",
- "express-form": "0.6.x",
"jade": "0.26.x",
"redis-url": "0.1.x",
"socket.io": "0.9.x"
},
"subdomain": "binb",
"scripts": {
- "start": "server.js"
+ "start": "app.js"
},
"engines": {
"node": "0.6.x"
},
- "version": "0.3.0-13"
-}
\ No newline at end of file
+ "version": "0.3.1"
+}
font-style: normal;
}
body {
- background: url('/static/images/bg.jpg') repeat-x scroll 0 0 #F5F6F7;
- padding-top:45px;
+ background: url('/static/images/bg.jpg') repeat-x scroll 0 0 #F5F6F7;
+ padding-top:45px;
}
section {
- margin-top:30px;
+ margin-top:30px;
}
.navbar .brand {
- padding: 2px 20px 4px;
+ padding: 2px 20px 4px;
}
.logo {
- font-family: 'ChristopherhandRegular';
+ font-family: 'ChristopherhandRegular';
}
.navbar .brand .logo {
- height: 34px;
- padding-left: 68px;
- line-height: 40px;
- background: url('/static/images/logo.png') no-repeat 0px -54px;
- font-size: 25px;
+ height: 34px;
+ padding-left: 68px;
+ line-height: 40px;
+ background: url('/static/images/logo.png') no-repeat 0px -54px;
+ font-size: 25px;
}
.page-header .logo {
- display: inline-block;
- height: 54px;
- padding-left: 109px;
- line-height: 68px;
- background: url('/static/images/logo.png') no-repeat 0 0;
- font-size: 34px;
- color: #0088CC;
+ display: inline-block;
+ height: 54px;
+ padding-left: 109px;
+ line-height: 68px;
+ background: url('/static/images/logo.png') no-repeat 0 0;
+ font-size: 34px;
+ color: #0088CC;
}
.navbar .navbar-text {
- line-height:19px;
- padding: 10px 10px 11px;
+ line-height:19px;
+ padding: 10px 10px 11px;
}
.form-horizontal .control-group {
- margin-bottom: 10px;
+ margin-bottom: 10px;
}
.form-horizontal .control-label {
- width: 100px;
+ width: 100px;
}
.form-horizontal .controls {
- margin-left: 120px;
+ margin-left: 120px;
}
form .clearfix {
- margin-bottom: 10px;
+ margin-bottom: 10px;
}
.well {
- background-color: #DDDDDD;
- margin-bottom: 18px;
+ background-color: #DDDDDD;
+ margin-bottom: 18px;
}
.alert {
- margin-bottom: 9px;
+ margin-bottom: 9px;
}
#signup-button {
- margin-left: 120px;
- margin-top: 9px;
+ margin-left: 120px;
+ margin-top: 9px;
}
#captcha-input {
- width: 126px;
+ width: 126px;
}
#captcha {
- margin-right:20px;
+ margin-right:20px;
}
.page-header {
- padding: 0;
- margin: 0 0 17px 0;
- border-bottom:1px solid #ddd;
- -webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
- -moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
- box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
+ padding: 0;
+ margin: 0 0 17px 0;
+ border-bottom:1px solid #ddd;
+ -webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
+ -moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
+ box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
}
input {
- margin-bottom: 0;
+ margin-bottom: 0;
}
.modal-footer {
- text-align: left;
+ text-align: left;
}
.modal-footer .btn {
- float: right;
+ float: right;
}
.thumbnails {
- margin-bottom: 0px;
+ margin-bottom: 0px;
}
.thumbnails > li {
- margin: 0 0 18px 80px;
+ margin: 0 0 18px 80px;
}
.thumbnail {
- border: 1px solid #ccc;
- height: 140px;
- opacity: 0.7;
+ border: 1px solid #ccc;
+ height: 140px;
+ opacity: 0.7;
}
.thumbnail:hover {
- opacity: 1;
+ opacity: 1;
}
.thumbnail img {
- float: left;
- width: 70px;
- height: 70px;
+ float: left;
+ width: 70px;
+ height: 70px;
}
.profile {
- font-size: 24px;
- font-weight: bold;
- line-height: 32px;
+ font-size: 24px;
+ font-weight: bold;
+ line-height: 32px;
}
.profile .img {
- width: 32px;
- height: 32px;
- float: left;
- margin-right: 5px;
- background: url('/static/images/sprites.png') no-repeat 0px -56px;
+ width: 32px;
+ height: 32px;
+ float: left;
+ margin-right: 5px;
+ background: url('/static/images/sprites.png') no-repeat 0px -56px;
}
.stats {
- border: 1px solid #ccc;
- border-left: 0;
- margin-top:8px;
+ border: 1px solid #ccc;
+ border-left: 0;
+ margin-top:8px;
}
.stats td {
- border-left: 1px solid #ccc;
- border-top: 1px solid #ccc;
- vertical-align: middle;
+ border-left: 1px solid #ccc;
+ border-top: 1px solid #ccc;
+ vertical-align: middle;
}
.stats tbody tr:nth-child(odd) td {
- background-color: #ddd;
+ background-color: #ddd;
}
.stats tbody tr:hover td {
- background-color: #dadada;
+ background-color: #dadada;
}
.room {
- height: 25px;
- line-height: 25px;
- position: absolute;
- top: 61px;
- background-color: rgba(51,51,51, 0.7);
- color: white;
- font-weight: bold;
- font-size: 14px;
- width: 210px;
- text-align: center;
- text-transform: capitalize;
+ height: 25px;
+ line-height: 25px;
+ position: absolute;
+ top: 61px;
+ background-color: rgba(51,51,51, 0.7);
+ color: white;
+ font-weight: bold;
+ font-size: 14px;
+ width: 210px;
+ text-align: center;
+ text-transform: capitalize;
}
.dropdown-menu {
- min-width: 140px;
+ min-width: 140px;
}
.dropdown-menu a {
- padding: 2px 10px;
+ padding: 2px 10px;
}
.dropdown-menu .divider {
- margin: 4px 1px;
+ margin: 4px 1px;
}
.matched {
- color: #f3a22f;
+ color: #f3a22f;
}
.cups, .medals {
- margin-left: auto;
- margin-right: auto;
+ margin-left: auto;
+ margin-right: auto;
}
.cups {
- width: 16px;
- height: 16px;
+ width: 16px;
+ height: 16px;
}
.medals {
- width: 32px;
- height: 32px;
+ width: 32px;
+ height: 32px;
}
.rank1 {
- background: url('/static/images/sprites.png') no-repeat -32px -16px;
+ background: url('/static/images/sprites.png') no-repeat -32px -16px;
}
.rank2 {
- background: url('/static/images/sprites.png') no-repeat -32px -48px;
+ background: url('/static/images/sprites.png') no-repeat -32px -48px;
}
.rank3 {
- background: url('/static/images/sprites.png') no-repeat -32px -80px;
+ background: url('/static/images/sprites.png') no-repeat -32px -80px;
}
.scoreboard th, .scoreboard td {
- vertical-align: middle;
- text-align: center;
+ vertical-align: middle;
+ text-align: center;
}
.scoreboard .name {
- font-weight:bold;
+ font-weight:bold;
}
.relative {
- position: relative;
+ position: relative;
}
#app-name {
- display: inline-block;
+ display: inline-block;
}
#total-tracks {
- float: right;
- color: #BFBFBF;
- margin-top: 34px;
+ float: right;
+ color: #BFBFBF;
+ margin-top: 34px;
}
#cassette {
- margin-top:22px;
- height: 137px;
- background: url('/static/images/cassette.png') no-repeat 0 0;
+ margin-top:22px;
+ height: 137px;
+ background: url('/static/images/cassette.png') no-repeat 0 0;
}
#countdown {
- position: absolute;
- width: 26px;
- text-align:center;
- top:80px;
- left:175px;
+ position: absolute;
+ width: 26px;
+ text-align:center;
+ top:80px;
+ left:175px;
}
#wheel-left, #wheel-right {
- position:absolute;
- width: 24px;
- height:24px;
- top: 49px;
- background: url('/static/images/sprites.png') no-repeat 0px -32px;
+ position:absolute;
+ width: 24px;
+ height:24px;
+ top: 49px;
+ background: url('/static/images/sprites.png') no-repeat 0px -32px;
}
#wheel-left {
- left:51px;
+ left:51px;
}
#wheel-right {
- left:145px;
+ left:145px;
}
#tape-left, #tape-right {
- height:70px;
- width:70px;
- position:absolute;
- top:25px;
- background-color: black;
- -webkit-border-radius: 70px;
- -moz-border-radius: 70px;
- border-radius: 70px;
- z-index:-1;
+ height:70px;
+ width:70px;
+ position:absolute;
+ top:25px;
+ background-color: black;
+ -webkit-border-radius: 70px;
+ -moz-border-radius: 70px;
+ border-radius: 70px;
+ z-index:-1;
}
#tape-left {
- left: 20px;
+ left: 20px;
}
#tape-right {
- left: 106px;
+ left: 106px;
}
#progress-bar, #progress {
- height:7px;
- -webkit-border-radius:4px;
- -moz-border-radius:4px;
- border-radius:4px;
- -webkit-box-shadow: inset 0px 1px 2px 0px #333;
- -moz-box-shadow: inset 0px 1px 2px 0px #333;
- box-shadow: inset 0px 1px 2px 0px #333;
+ height:7px;
+ -webkit-border-radius:4px;
+ -moz-border-radius:4px;
+ border-radius:4px;
+ -webkit-box-shadow: inset 0px 1px 2px 0px #333;
+ -moz-box-shadow: inset 0px 1px 2px 0px #333;
+ box-shadow: inset 0px 1px 2px 0px #333;
}
#progress-bar {
- position:absolute;
- width: 148px;
- top:85px;
- left:20px;
- border:1px solid #404040;
- border:1px solid rgba(0, 0, 0, 0.5);
+ position:absolute;
+ width: 148px;
+ top:85px;
+ left:20px;
+ border:1px solid #404040;
+ border:1px solid rgba(0, 0, 0, 0.5);
}
#progress {
- background-color: #6184b7;
- width:0px;
+ background-color: #6184b7;
+ width:0px;
}
#touch-backdrop {
- width:220px;
- height:137px;
- -webkit-border-radius:5px;
- -moz-border-radius:5px;
- border-radius:5px;
- background:rgba(50,50,50,0.8);
+ width:220px;
+ height:137px;
+ -webkit-border-radius:5px;
+ -moz-border-radius:5px;
+ border-radius:5px;
+ background:rgba(50,50,50,0.8);
}
#touch-play {
- margin-top: 54px;
- margin-left: 77px;
+ margin-top: 54px;
+ margin-left: 77px;
}
#volume {
- height:159px;
+ height:159px;
}
#volume-button, #volume-slider, #volume-total, #volume-current, #volume-handle {
- position: absolute;
+ position: absolute;
}
#volume-button {
- bottom: 0;
- height: 20px;
- width: 20px;
+ bottom: 0;
+ height: 20px;
+ width: 20px;
}
#volume-button .button {
- display:block;
- height: 18px;
- width: 18px;
- border:1px solid;
- border-color: #CCCCCC #CCCCCC #AAAAAA;
- -webkit-border-radius:3px;
- -moz-border-radius:3px;
- border-radius:3px;
- cursor:pointer;
- filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffffff,EndColorStr=#ffe0e0e0);
- background-image:-moz-linear-gradient(top,#fff 0,#e0e0e0 100%);
- background-image:-ms-linear-gradient(top,#fff 0,#e0e0e0 100%);
- background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);
- background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e0e0e0));
- background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);
- background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);
+ display:block;
+ height: 18px;
+ width: 18px;
+ border:1px solid;
+ border-color: #CCCCCC #CCCCCC #AAAAAA;
+ -webkit-border-radius:3px;
+ -moz-border-radius:3px;
+ border-radius:3px;
+ cursor:pointer;
+ filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffffff,EndColorStr=#ffe0e0e0);
+ background-image:-moz-linear-gradient(top,#fff 0,#e0e0e0 100%);
+ background-image:-ms-linear-gradient(top,#fff 0,#e0e0e0 100%);
+ background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);
+ background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(100%,#e0e0e0));
+ background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);
+ background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);
}
#volume-button .button:hover {
- border-color:#999999;
- -webkit-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
- -moz-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
- box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
+ border-color:#999999;
+ -webkit-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
+ -moz-box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
+ box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
}
#volume-button .button:active {
- border-color:#999999 #AAAAAA #CCCCCC;
- -webkit-box-shadow: 0px 1px 2px 0px #aaa inset;
- -moz-box-shadow: 0px 1px 2px 0px #aaa inset;
- box-shadow: 0px 1px 2px 0px #aaa inset;
- filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#e6e6e6,EndColorStr=#dcdcdc);
- background-image:-moz-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
- background-image:-ms-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
- background-image:-o-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
- background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#e6e6e6),color-stop(100%,#dcdcdc));
- background-image:-webkit-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
- background-image:linear-gradient(to bottom,#e6e6e6 0,#dcdcdc 100%);
+ border-color:#999999 #AAAAAA #CCCCCC;
+ -webkit-box-shadow: 0px 1px 2px 0px #aaa inset;
+ -moz-box-shadow: 0px 1px 2px 0px #aaa inset;
+ box-shadow: 0px 1px 2px 0px #aaa inset;
+ filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#e6e6e6,EndColorStr=#dcdcdc);
+ background-image:-moz-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
+ background-image:-ms-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
+ background-image:-o-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
+ background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#e6e6e6),color-stop(100%,#dcdcdc));
+ background-image:-webkit-linear-gradient(top,#e6e6e6 0,#dcdcdc 100%);
+ background-image:linear-gradient(to bottom,#e6e6e6 0,#dcdcdc 100%);
}
#volume-button .button #icon {
- margin: 1px;
- width: 16px;
- height: 16px;
+ margin: 1px;
+ width: 16px;
+ height: 16px;
}
#volume-button .button .volume-none {
- background: url('/static/images/sprites.png') no-repeat 0 -112px;
+ background: url('/static/images/sprites.png') no-repeat 0 -112px;
}
#volume-button .button .volume-low {
- background: url('/static/images/sprites.png') no-repeat -16px -112px;
+ background: url('/static/images/sprites.png') no-repeat -16px -112px;
}
#volume-button .button .volume-medium {
- background: url('/static/images/sprites.png') no-repeat -32px -112px;
+ background: url('/static/images/sprites.png') no-repeat -32px -112px;
}
#volume-button .button .volume-high {
- background: url('/static/images/sprites.png') no-repeat -48px -112px;
+ background: url('/static/images/sprites.png') no-repeat -48px -112px;
}
#volume-slider {
- display: none;
- height: 116px;
- width: 20px;
- top: -116px;
- -webkit-border-radius:3px;
- -moz-border-radius:3px;
- border-radius:3px;
- background: rgba(50, 50, 50, 0.1);
- z-index: 1;
+ display: none;
+ height: 116px;
+ width: 20px;
+ top: -116px;
+ -webkit-border-radius:3px;
+ -moz-border-radius:3px;
+ border-radius:3px;
+ background: rgba(50, 50, 50, 0.1);
+ z-index: 1;
}
#volume-total, #volume-current {
- left: 8px;
- top: 8px;
- width: 4px;
- height: 100px;
+ left: 8px;
+ top: 8px;
+ width: 4px;
+ height: 100px;
}
#volume-total, #volume-current, #volume-handle {
- -webkit-border-radius: 2px;
- -moz-border-radius: 2px;
- border-radius: 2px;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
}
#volume-total {
- -webkit-box-shadow: inset 0px 0px 3px 0px #333;
- -moz-box-shadow: inset 0px 0px 3px 0px #333;
- box-shadow: inset 0px 0px 3px 0px #333;
+ -webkit-box-shadow: inset 0px 0px 3px 0px #333;
+ -moz-box-shadow: inset 0px 0px 3px 0px #333;
+ box-shadow: inset 0px 0px 3px 0px #333;
}
#volume-current {
- -webkit-box-shadow: inset 0px 0px 1px 0px #000;
- -moz-box-shadow: inset 0px 0px 1px 0px #000;
- box-shadow: inset 0px 0px 1px 0px #000;
- background-color: #6184b7;
+ -webkit-box-shadow: inset 0px 0px 1px 0px #000;
+ -moz-box-shadow: inset 0px 0px 1px 0px #000;
+ box-shadow: inset 0px 0px 1px 0px #000;
+ background-color: #6184b7;
}
#volume-handle {
- left: 1px;
- width: 16px;
- height: 4px;
- background: #ddd;
- background: rgba(255, 255, 255, 0.9);
- cursor: N-resize;
- border:1px solid #999;
+ left: 1px;
+ width: 16px;
+ height: 4px;
+ background: #ddd;
+ background: rgba(255, 255, 255, 0.9);
+ cursor: N-resize;
+ border:1px solid #999;
}
#summary {
- text-align: center;
- margin-bottom:18px;
+ text-align: center;
+ margin-bottom:18px;
}
#users-wrapper {
- margin-left: 120px;
- width: 300px;
+ margin-left: 120px;
+ width: 300px;
}
#users {
- padding-left:20px;
- margin-bottom: 90px;
- max-height: 380px;
- overflow: auto;
+ padding-left:20px;
+ margin-bottom: 90px;
+ max-height: 380px;
+ overflow: auto;
}
#users .name, #tracks .artist {
- font-weight: bold;
+ font-weight: bold;
}
#message-wrapper {
- text-align: right;
+ text-align: right;
}
#users li, #tracks li, #chat li {
- color: #404040;
+ color: #404040;
}
#users li {
- height: 18px;
- position: relative;
+ height: 18px;
+ position: relative;
}
#users .private {
- display: none;
- font-size: 9.75px;
- padding: 2px 4px;
- position: absolute;
- left: -19px;
+ display: none;
+ font-size: 9.75px;
+ padding: 2px 4px;
+ position: absolute;
+ left: -19px;
}
.registered, .round-rank {
- height: 16px;
- width: 16px;
- margin: 1px 2px 0px 0px;
+ height: 16px;
+ width: 16px;
+ margin: 1px 2px 0px 0px;
}
.registered {
- background: url('/static/images/sprites.png') no-repeat 0px -16px;
+ background: url('/static/images/sprites.png') no-repeat 0px -16px;
}
.registered:hover {
- background: url('/static/images/sprites.png') no-repeat -16px -16px;
+ background: url('/static/images/sprites.png') no-repeat -16px -16px;
}
#users .name {
- margin-right: 4px;
+ margin-right: 4px;
}
#users .name, .registered {
- cursor: pointer;
+ cursor: pointer;
}
#users .you {
- cursor: auto;
+ cursor: auto;
}
#users .points, #users .round-points {
- margin-right: 10px;
+ margin-right: 10px;
}
.stand1 {
- background: url('/static/images/sprites.png') no-repeat 0 0;
+ background: url('/static/images/sprites.png') no-repeat 0 0;
}
.stand2 {
- background: url('/static/images/sprites.png') no-repeat -16px 0;
+ background: url('/static/images/sprites.png') no-repeat -16px 0;
}
.stand3 {
- background: url('/static/images/sprites.png') no-repeat -32px 0;
+ background: url('/static/images/sprites.png') no-repeat -32px 0;
}
#users .guess-time {
- font-size: 11px;
- line-height: 18px;
+ font-size: 11px;
+ line-height: 18px;
}
#toggle-chat {
- position: absolute;
- top: -17px;
- left: 805px;
- color: #BFBFBF;
- height: 16px;
- line-height: 16px;
- font-size: 11px;
- border: 1px solid #DDDDDD;
- -moz-border-radius: 4px 4px 0 0;
- -webkit-border-radius: 4px 4px 0 0;
- border-radius: 4px 4px 0 0;
- cursor: pointer;
- padding: 0 10px;
- text-decoration: none;
+ position: absolute;
+ top: -17px;
+ left: 805px;
+ color: #BFBFBF;
+ height: 16px;
+ line-height: 16px;
+ font-size: 11px;
+ border: 1px solid #DDDDDD;
+ -moz-border-radius: 4px 4px 0 0;
+ -webkit-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+ cursor: pointer;
+ padding: 0 10px;
+ text-decoration: none;
}
#toggle-chat:hover {
- border-color: #CCCCCC;
- background-color: #f5f5f5;
- color: #333333;
- background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
- background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
- background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
- background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
- background-image: linear-gradient(top, #ffffff, #e6e6e6);
- filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);
- -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
+ border-color: #CCCCCC;
+ background-color: #f5f5f5;
+ color: #333333;
+ background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+ background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: linear-gradient(top, #ffffff, #e6e6e6);
+ filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
}
#toggle-chat:active {
- background-image: none;
- -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
- box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
- background-color: #e6e6e6;
- background-color: #d9d9d9 \9;
- outline: 0;
+ background-image: none;
+ -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
+ background-color: #e6e6e6;
+ background-color: #d9d9d9 \9;
+ outline: 0;
}
#chat-wrapper {
- margin-bottom: 4px;
- height: 160px;
- background-color: white;
+ margin-bottom: 4px;
+ height: 160px;
+ background-color: white;
}
#chat {
- height: 152px;
- width: 446px;
- margin: 4px 6px;
- overflow: auto;
+ height: 152px;
+ width: 446px;
+ margin: 4px 6px;
+ overflow: auto;
}
.bordered {
- border: 1px solid #ccc;
- -webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
- -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.075);
- -moz-box-shadow: 0 1px 2px rgba(0,0,0,.075);
- box-shadow: 0 1px 2px rgba(0,0,0,.075);
+ border: 1px solid #ccc;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.075);
+ -moz-box-shadow: 0 1px 2px rgba(0,0,0,.075);
+ box-shadow: 0 1px 2px rgba(0,0,0,.075);
}
#chat .join, #chat .left {
- font-style: italic;
+ font-style: italic;
}
#chat .private {
- color: #a65fc3;
+ color: #a65fc3;
}
#feedback {
- text-align: center;
+ text-align: center;
}
#feedback .correct, #users .correct{
- color: #46A546;
+ color: #46A546;
}
#feedback .wrong, #chat .error {
- color: #C43C35;
+ color: #C43C35;
}
#guess.correct {
- border-color: #46A546;
+ border-color: #46A546;
}
#guess.correct:focus {
- -webkit-box-shadow: 0 0 6px #7aba7b;
- -moz-box-shadow: 0 0 6px #7aba7b;
- box-shadow: 0 0 6px #7aba7b;
+ -webkit-box-shadow: 0 0 6px #7aba7b;
+ -moz-box-shadow: 0 0 6px #7aba7b;
+ box-shadow: 0 0 6px #7aba7b;
}
#guess.wrong {
- border-color: #C43C35;
+ border-color: #C43C35;
}
#guess.wrong:focus {
- -webkit-box-shadow: 0 0 6px #d59392;
- -moz-box-shadow: 0 0 6px #d59392;
- box-shadow: 0 0 6px #d59392;
+ -webkit-box-shadow: 0 0 6px #d59392;
+ -moz-box-shadow: 0 0 6px #d59392;
+ box-shadow: 0 0 6px #d59392;
}
#tracks {
- margin: 18px 0;
- max-height: 240px;
- overflow: auto;
+ margin: 18px 0;
+ max-height: 240px;
+ overflow: auto;
}
#tracks li {
- margin: 0 2px 2px 0;
- padding: 8px;
- min-height: 40px;
- background: -moz-linear-gradient(center top , #FBFBFB, #F5F5F5);
- background: -webkit-linear-gradient(center top , #FBFBFB, #F5F5F5);
- background: -o-linear-gradient(center top , #FBFBFB, #F5F5F5);
- background: -ms-linear-gradient(center top , #FBFBFB, #F5F5F5);
- background: linear-gradient(center top , #FBFBFB, #F5F5F5);
+ margin: 0 2px 2px 0;
+ padding: 8px;
+ min-height: 40px;
+ background: -moz-linear-gradient(center top , #FBFBFB, #F5F5F5);
+ background: -webkit-linear-gradient(center top , #FBFBFB, #F5F5F5);
+ background: -o-linear-gradient(center top , #FBFBFB, #F5F5F5);
+ background: -ms-linear-gradient(center top , #FBFBFB, #F5F5F5);
+ background: linear-gradient(center top , #FBFBFB, #F5F5F5);
}
.registered, #users .name, #users .points, .round-rank, .round-points, #users .guess-time, #tracks img.artwork, #tracks .info, #copy, #facebook-button, #twitter-button, #github-button {
- float:left;
+ float:left;
}
#tracks img.artwork {
- width: 40px;
- height: 40px;
- margin-right:10px;
+ width: 40px;
+ height: 40px;
+ margin-right:10px;
}
#tracks .info {
- margin-right:15px;
+ margin-right:15px;
}
#tracks .artist {
- margin-top: 1px;
- font-size: 14px;
+ margin-top: 1px;
+ font-size: 14px;
}
#tracks .round-rank {
- margin-top:12px;
+ margin-top:12px;
}
#tracks .round-points {
- margin-top: 11px;
+ margin-top: 11px;
}
#tracks a {
- float:right;
- display:block;
- width:24px;
- height:24px;
- margin-top:8px;
+ float:right;
+ display:block;
+ width:24px;
+ height:24px;
+ margin-top:8px;
}
#disclaimer {
- position: absolute;
- left: 60px;
- bottom: 18px;
- color: #BFBFBF;
- width:340px;
+ position: absolute;
+ left: 60px;
+ bottom: 18px;
+ color: #BFBFBF;
+ width:340px;
}
footer {
- padding: 0;
- margin-top: 17px;
- border-top:1px solid #DDDDDD;
- color: #BFBFBF;
- text-shadow: 0 1px 0 #FFFFFF;
+ padding: 0;
+ margin-top: 17px;
+ border-top:1px solid #DDDDDD;
+ color: #BFBFBF;
+ text-shadow: 0 1px 0 #FFFFFF;
}
#footer-inner, #twitter-button, #github-button {
- height:20px;
+ height:20px;
}
#footer-inner {
- border-top:1px solid white;
- padding-top:5px;
- margin-bottom:30px;
+ border-top:1px solid white;
+ padding-top:5px;
+ margin-bottom:30px;
}
#copy {
- margin: 0 20px 0 20px;
+ margin: 0 20px 0 20px;
}
#facebook-button {
- width:90px;
- height:21px
+ width:90px;
+ height:21px
}
#twitter-button, #github-button {
- width:100px;
+ width:100px;
}
#copy, #footer-right {
- line-height:20px;
+ line-height:20px;
}
#footer-right {
- float:right;
- margin-right:20px;
+ float:right;
+ margin-right:20px;
}
$(function() {
- if ($.browser.mozilla) {
- // Block ESC button in firefox (it breaks all socket connection).
- $(document).keypress(function(event) {
- if(event.keyCode === 27) {
- return false;
- }
- });
- }
- $.get("/artworks", function(data) {
- $(".thumbnail").each(function(index) {
- var i = index * 6;
- var j = i + 6;
- for(i; i < j; i++) {
- $('<img src="'+data.results[i]+'" />').appendTo($(this));
- }
- });
- });
- var socket = io.connect("http://binb.nodejitsu.com/", {'reconnect':false});
- socket.on("connect", function() {
- socket.emit("getoverview");
- socket.on("overview", function(data) {
- for (var prop in data) {
- $("#"+prop).text(data[prop]);
- }
- });
- socket.on("update", function(data) {
- $("#"+data.room).text(data.players);
- });
- });
+ if ($.browser.mozilla) {
+ // Block ESC button in firefox (it breaks all socket connection).
+ $(document).keypress(function(event) {
+ if(event.keyCode === 27) {
+ return false;
+ }
+ });
+ }
+ $.get("/artworks", function(data) {
+ $(".thumbnail").each(function(index) {
+ var i = index * 6;
+ var j = i + 6;
+ for(i; i < j; i++) {
+ $('<img src="'+data.results[i]+'" />').appendTo($(this));
+ }
+ });
+ });
+ var socket = io.connect("http://binb.nodejitsu.com/", {'reconnect':false});
+ socket.on("connect", function() {
+ socket.emit("getoverview");
+ socket.on("overview", function(data) {
+ for (var prop in data) {
+ $("#"+prop).text(data[prop]);
+ }
+ });
+ socket.on("update", function(room, players) {
+ $("#"+room).text(players);
+ });
+ });
});
(function() {
-
- var touchplay = null;
- var elapsedtime = 0;
- var jplayer = null;
- var nickname = null;
- var socket = null;
- var pvtmsgto = null;
- var roundpoints = 0;
- var stopanimation = false;
- var states = ['A song is already playing, please wait for the next one...',
- 'Game is about to start...', 'Game is over', 'New game will start soon...'];
- var tmstrings = ['Yes, you guessed the title. Who is the artist?', 'Now tell me the artist!',
- 'Correct, do you also know the artist?'];
- var amstrings = ['Yes, that\'s the artist. What about the title?', 'Exactly, now tell me the title!',
- 'Do you also know the title?'];
- var bmstrings = ['Yeah true! do you like this track?', 'Good job!', 'Great!',
- 'Very well done!', 'Exactly!', 'Excellent!', 'Woohoo!'];
- var nmstrings = ['Nope, sorry!', 'No way!', 'Fail', 'Nope', 'No', 'That\'s wrong', 'What?!',
- 'Wrong', 'Haha, what?!', 'You kidding?', 'Don\'t make me laugh', 'You mad?',
- 'Try again'];
- var historyvalues = [];
- var historycursor = 0;
- var DOM = {};
-
- String.prototype.encodeEntities = function() {
- return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
- };
-
- // Exact match version of jQuery :contains selector
- $.expr[":"].econtains = function(obj, index, meta, stack) {
- return $(obj).html() === meta[0].replace(/^[\s\S]+:econtains\(([\s\S]+)\)$/, "$1");
- };
-
- // Prompt for name and send it.
- var joinAnonymously = function(msg) {
- if (/nickname\s*\=/.test(document.cookie) && !msg) {
- nickname = unescape(document.cookie.replace(/.*nickname\s*\=\s*([^;]*);?.*/, "$1"));
- socket.emit('joinanonymously', {nickname:nickname,roomname:roomname});
- }
- else {
- if (!$('body').hasClass('modal-open')) {
- var html = '<div class="modal-header"><h3>You are joining the '+roomname+' room</h3></div>';
- html += '<div class="modal-body"><p>'+(msg || "What's your name?")+'</p></div>';
- html += '<div class="modal-footer">';
- html += '<input id="login" class="" type="text" name="nickname" />';
- html += '<button id="join" class="btn btn-primary">';
- html += '<i class="icon-user icon-white"></i> Join the game</button></div>';
-
- $(html).appendTo(DOM.modal);
- var login = $('#login');
- var button = $('#join');
- button.click(function() {
- var val = $.trim(login.val());
- if (val !== "") {
- nickname = val;
- socket.emit('joinanonymously', {nickname:nickname,roomname:roomname});
- }
- else {
- var txt = "Nickname can't be empty.";
- invalidNickName('<span class="label label-important">'+txt+'</span>');
- }
- login.val("");
- });
- login.keyup(function(event) {
- if (event.keyCode === 13) {
- button.click();
- }
- });
- DOM.modal.modal('show');
- DOM.modal.on('shown', function() {
- login.focus();
- });
- }
- else {
- $('.modal-body p').html(msg);
- $('#login').focus();
- }
- }
- };
-
- // Your submitted name was invalid
- var invalidNickName = function(feedback) {
- joinAnonymously(feedback+"<br/>Try with another one:");
- };
-
- /* Triggered when a logged user tries to join a room from another tab or another browser
- and he is already in a room */
- var alreadyInARoom = function() {
- var html = '<div class="modal-header"><h3>Already in a room</h3></div>';
- html += '<div class="modal-body"><div class="alert alert-error alert-block">';
- html += '<h4 class="alert-heading">Warning!</h4>You are already in a room.<br/>';
- html += 'Leave the other room and refresh this page or close this one.</div></div>';
- $(html).appendTo(DOM.modal);
- DOM.modal.modal('show');
- };
-
- // You joined the room
- var ready = function(data) {
- if (!data.loggedin && !/nickname\s*\=/.test(document.cookie)) {
- document.cookie = "nickname="+escape(nickname)+";path=/;";
- }
- DOM.modal.modal('hide').empty();
- $('#total-tracks span').text(data.trackscount);
- var msg = nickname+" joined the game";
- var joinspan = $("<span class='join'></span>");
- joinspan.text(msg);
- addChatEntry(joinspan);
- updateUsers(data);
-
- DOM.messagebox.keydown(function(event) {
- if (event.keyCode === 13) {
- var val = $.trim(DOM.messagebox.val());
- if (val !== "") {
- var data = (pvtmsgto) ? {to:pvtmsgto,chatmsg:val} : val;
- socket.emit('sendchatmsg', data);
- }
- DOM.messagebox.val("");
- }
- });
-
- DOM.guessbox.keydown(function(event) {
- switch (event.keyCode) {
- case 13: // return
- var guess = $.trim(DOM.guessbox.val());
- if (guess !== "") {
- socket.emit('guess', guess.toLowerCase());
- historyvalues.push(guess);
- if (historyvalues.length > 20) {
- historyvalues.splice(0, 1);
- }
- historycursor = historyvalues.length;
- }
- DOM.guessbox.val("");
- break;
- case 38: // up-arrow
- if (historycursor > 0) {
- DOM.guessbox.val(historyvalues[--historycursor]);
- }
- break;
- case 40: // down-arrow
- if (historycursor < historyvalues.length - 1) {
- DOM.guessbox.val(historyvalues[++historycursor]);
- }
- else {
- historycursor = historyvalues.length;
- DOM.guessbox.val("");
- }
- }
- });
-
- DOM.guessbox.focus();
-
- socket.on('newuser', userJoin);
- socket.on('userleft', userLeft);
- socket.on('updateusers', updateUsers);
- socket.on('chatmsg', getChatMessage);
- socket.on('loadtrack', loadTrack);
- socket.on('playtrack', playTrack);
- socket.on('trackinfo', addTrackInfo);
- socket.on('artistmatched', function() {
- var feedback = amstrings[Math.floor(Math.random()*amstrings.length)];
- addFeedback(feedback, "correct");
- });
- socket.on('titlematched', function() {
- var feedback = tmstrings[Math.floor(Math.random()*tmstrings.length)];
- addFeedback(feedback, "correct");
- });
- socket.on('bothmatched', function() {
- var feedback = bmstrings[Math.floor(Math.random()*bmstrings.length)];
- addFeedback(feedback, "correct");
- });
- socket.on('nomatch', function() {
- var feedback = nmstrings[Math.floor(Math.random()*nmstrings.length)];
- addFeedback(feedback, "wrong");
- });
- socket.on('stoptrying', function() {
- addFeedback('You guessed both artist and title. Please wait...');
- });
- socket.on('noguesstime', function() {
- addFeedback('You have to wait the next song...');
- });
- socket.on('gameover', gameOver);
- socket.on('status', setStatus);
- socket.emit('getstatus');
- };
-
- var setStatus = function(data) {
- if (data.status === 0) {
- cassetteAnimation(Date.now()+data.timeleft, true);
- }
- if (data.status === 1) {
- loadTrack(data);
- }
- addFeedback(states[data.status]);
- };
-
- // A new player joined the game
- var userJoin = function(data) {
- var msg = data.nickname+" joined the game";
- var joinspan = $("<span class='join'></span>");
- joinspan.text(msg);
- addChatEntry(joinspan);
- updateUsers(data);
- };
-
- // A user left the game
- var userLeft = function(data) {
- var leftmsg = data.nickname+" left the game";
- var leftspan = $("<span class='left'></span>");
- leftspan.text(leftmsg);
- addChatEntry(leftspan);
- updateUsers(data);
- };
-
- // Update the list of users
- var updateUsers = function(data) {
- DOM.users.empty();
- var users = [];
- for (var key in data.users) {
- users.push(data.users[key]);
- }
- users.sort(function(a, b) {return b.points - a.points;});
- // Flag to test if our private recipient is in the list of active users
- var found = false;
- for (var i=0; i<users.length; i++) {
- var user = users[i];
- var li = $('<li></li>');
- var pvt = $('<span class="private label label-info">P</span>');
- var username = $('<span class="name"></span>').text(user.nickname);
- var points = $('<span class="points">('+user.points+')</span>');
- var roundrank = $('<span></span>');
- var roundpointsel = $('<span class="round-points"></span>');
- var guesstime = $('<span class="guess-time"></span>');
- li.append(pvt, username, points, roundrank, roundpointsel, guesstime);
- if (user.registered) {
- var href = 'href="/user/'+encodeURI(username.html())+'"';
- pvt.after('<a class="registered" target="_blank" '+href+'></a>');
- }
- DOM.users.append(li);
- if (pvtmsgto === user.nickname) {
- pvt.show();
- username.click(clearPrivate);
- found = true;
- }
- else {
- username.click(function() {
- addPrivate($(this).text());
- });
- }
- if (nickname === user.nickname) {
- username.addClass("you");
- roundpoints = user.roundpoints;
- DOM.rank.text(i+1);
- DOM.points.text(user.points);
- }
- if (user.roundpoints > 0) {
- roundpointsel.text('+'+user.roundpoints);
- if (user.roundpoints === 1) {
- username.addClass("matched");
- }
- else {
- if (user.roundpoints > 3) {
- var stand = 7 - user.roundpoints;
- roundrank.addClass("round-rank stand"+stand);
- var gtime = (user.guesstime / 1000).toFixed(1);
- guesstime.text(gtime+" s");
- }
- username.addClass("correct");
- }
- }
- }
- if (!found && pvtmsgto) {
- var width = DOM.recipient.outerWidth(true) + 1;
- DOM.recipient.css('margin-right','0');
- DOM.recipient.text("");
- DOM.messagebox.animate({'width':'+='+width+'px'}, "fast");
- pvtmsgto = null;
- DOM.messagebox.focus();
- }
- };
-
- var addPrivate = function(usrname) {
- if (pvtmsgto) {
- clearPrivate();
- }
- if (nickname === usrname) {
- return;
- }
- DOM.recipient.css('margin-right','4px');
- DOM.recipient.text("To "+usrname+":");
- var width = DOM.recipient.outerWidth(true) + 1;
- DOM.recipient.hide();
- DOM.messagebox.animate({'width':'-='+width+'px'}, "fast", function() {DOM.recipient.show();});
- var el = $("span.name:econtains("+usrname.encodeEntities()+")");
- el.prevAll(".private").show();
- el.unbind('click');
- el.click(clearPrivate);
- pvtmsgto = usrname;
- DOM.messagebox.focus();
- };
-
- var clearPrivate = function() {
- var width = DOM.recipient.outerWidth(true) + 1;
- DOM.recipient.css('margin-right','0');
- DOM.recipient.text("");
- DOM.messagebox.animate({'width':'+='+width+'px'}, "fast");
- var el = $("span.name:econtains("+pvtmsgto.encodeEntities()+")");
- el.prevAll(".private").hide();
- el.unbind("click");
- el.click(function() {
- addPrivate($(this).text());
- });
- pvtmsgto = null;
- DOM.messagebox.focus();
- };
-
- // Receive a chat message
- var getChatMessage = function(data) {
- var prefix = data.from;
- var msgspan = $("<span class='message'></span>");
- if (data.to) {
- // Private Message
- prefix = (nickname === data.from) ? '(To '+data.to+')' : '(From '+prefix+')';
- msgspan.addClass("private");
- }
- var msg = prefix+": "+data.chatmsg;
- msgspan.text(msg);
- addChatEntry(msgspan);
- };
-
- var loadTrack = function(data) {
- jplayer.jPlayer("mute");
- jplayer.jPlayer("setMedia", {m4a: data.previewUrl});
- };
-
- // Play a track
- var playTrack = function(data) {
- if (touchplay) {
- touchplay.removeClass("btn-danger disabled").addClass("btn-success");
- touchplay.html('<i class="icon-play icon-white"></i> Play');
- }
- jplayer.jPlayer("unmute");
- jplayer.jPlayer("play");
- updateUsers(data);
- cassetteAnimation(Date.now()+30000, true);
- if (data.counter === 1) {
- DOM.modal.modal('hide').empty();
- DOM.tracks.empty();
- }
- DOM.track.text(data.counter+'/'+data.tot);
- addFeedback('What is this song?');
- };
-
- // Start cassette animation
- var cassetteAnimation = function(endtime, forward) {
- var millisleft = endtime - Date.now();
- var secleft = millisleft / 1000;
- var width, deg, offsetleft, offsetright, css;
- if (forward) {
- width = 148 - (148*secleft/30);
- deg = 360 - (360*secleft/30);
- offsetleft = 44 - 24*secleft/30;
- offsetright = 130 - 24*secleft/30;
- DOM.progress.width(width);
- css = {
- '-moz-transform' : 'rotate('+deg+'deg)',
- '-webkit-transform' : 'rotate('+deg+'deg)',
- '-o-transform' : 'rotate('+deg+'deg)',
- '-ms-transform' : 'rotate('+deg+'deg)',
- 'transform' : 'rotate('+deg+'deg)'
- };
- DOM.cassettewheels.css(css);
- DOM.tapeleft.css('left', offsetleft+'px');
- DOM.taperight.css('left', offsetright+'px');
- }
- else {
- width = 148*secleft/5;
- deg = 360*secleft/5;
- offsetleft = 20 + 24*secleft/5;
- offsetright = 106 + 24*secleft/5;
- DOM.progress.width(width);
- css = {
- '-moz-transform' : 'rotate('+deg+'deg)',
- '-webkit-transform' : 'rotate('+deg+'deg)',
- '-o-transform' : 'rotate('+deg+'deg)',
- '-ms-transform' : 'rotate('+deg+'deg)',
- 'transform' : 'rotate('+deg+'deg)'
- };
- DOM.cassettewheels.css(css);
- DOM.tapeleft.css('left', offsetleft+'px');
- DOM.taperight.css('left', offsetright+'px');
- }
- if (forward) {
- DOM.countdown.text(secleft.toFixed(1));
- if (touchplay) {elapsedtime = 30 - Math.round(secleft);}
- }
- else {
- DOM.countdown.text(Math.round(secleft));
- }
- if (stopanimation || millisleft < 50) {
- return;
- }
- setTimeout(function() {cassetteAnimation(endtime, forward);}, 50);
- };
-
- // Add track info
- var addTrackInfo = function(data) {
- if (touchplay) {
- touchplay.removeClass("btn-success").addClass("btn-danger disabled");
- touchplay.html('<i class="icon-play icon-white"></i> Wait');
- }
- cassetteAnimation(Date.now()+5000, false);
- var html = '<li class="bordered"><img class="artwork" src="'+data.artworkUrl+'"/>';
- html += '<div class="info"><div class="artist">'+data.artistName+'</div>';
- var titleattr = '';
- var trackname = data.trackName;
- if (data.trackName.length > 45) {
- titleattr = data.trackName.replace(/"/g, """);
- trackname = data.trackName.substring(0,42) + '...';
- }
- html += '<div class="title" title="'+titleattr+'">'+trackname+'</div></div>';
- var attrs = '';
- var rp = '';
- if (roundpoints > 0) {
- rp = '+'+roundpoints;
- if (roundpoints > 3) {
- var stand = 7 - roundpoints;
- attrs += 'class="round-rank stand'+stand+'"';
- }
- }
- html += '<div '+attrs+'></div><div class="round-points">'+rp+'</div>';
- html += '<a target="_blank" href="'+data.trackViewUrl+'">';
- html += '<img src="/static/images/itunes.png"/></a></li>';
- DOM.tracks.prepend($(html));
- };
-
- // Game over countdown
- var countDown = function(endtime) {
- var millisleft = endtime - Date.now();
- var secleft = millisleft / 1000;
- $('.modal-footer span').text(Math.round(secleft));
- if (millisleft < 200) {
- return;
- }
- setTimeout(function() {countDown(endtime);}, 200);
- };
-
- var gameOver = function(data) {
- var html = '<div class="modal-header"><h3>Game Over</h3></div>';
- html += '<div class="modal-body"><table class="table table-striped scoreboard">';
- html += '<thead><tr><th>#</th><th>Name</th><th>Points</th>';
- html += '<th><div class="cups stand1"></div></th><th><div class="cups stand2"></div></th>';
- html += '<th><div class="cups stand3"></div></th><th>Guessed</th><th>Mean time</th>';
- html += '</thead><tbody>';
- for(var i=0;i<3;i++) {
- if (data.users[i]) {
- var playername = data.users[i].nickname.encodeEntities();
- html += '<tr><td><div class="medals rank'+(i+1)+'"></div></td>';
- html += '<td class="name">'+playername+'</td>';
- html += '<td>'+data.users[i].points+'</td>';
- html += '<td>'+data.users[i].golds+'</td><td>'+data.users[i].silvers+'</td>';
- html += '<td>'+data.users[i].bronzes+'</td><td>'+data.users[i].guessed+'</td>';
- var meantime = "N/A";
- if (data.users[i].guessed !== 0) {
- meantime = data.users[i].totguesstime / data.users[i].guessed;
- meantime = (meantime / 1000).toFixed(1)+" s";
- }
- html += '<td>'+meantime+'</td></tr>';
- }
- }
- html +='</tbody></table></div>';
- html += '<div class="modal-footer">A new game will start in <span></span> second/s</div>';
- DOM.modal.append($(html));
- DOM.modal.modal('show');
- countDown(Date.now()+10000);
- };
-
- // Let the user know when he / she has disconnected
- var disconnect = function() {
- stopanimation = true;
- jplayer.jPlayer("stop");
- var errormsg = "ERROR: You have disconnected.";
- var errorspan = $("<span class='error'></span>");
- errorspan.text(errormsg);
- addChatEntry(errorspan);
- addFeedback('Something wrong happened');
- DOM.users.empty();
- };
-
- // Add a chat entry, whether message, notification, etc.
- var addChatEntry = function(childNode) {
- var li = $("<li class='entry'></li>");
- li.append(childNode);
- DOM.chat.append(li);
- DOM.chat[0].scrollTop = DOM.chat[0].scrollHeight;
- };
-
- var hideChat = function() {
- DOM.togglechat.text("Show chat").unbind('click');
- DOM.chatwrapper.toggle(300);
- DOM.tracks.animate({maxHeight:"434px"}, 300);
- DOM.togglechat.click(showChat);
- };
-
- var showChat = function() {
- DOM.togglechat.text("Hide chat").unbind('click');
- DOM.chatwrapper.toggle(300);
- DOM.tracks.animate({maxHeight:"240px"}, 300, function() {
- DOM.chat[0].scrollTop = DOM.chat[0].scrollHeight;
- });
- DOM.togglechat.click(hideChat);
- };
-
- var addFeedback = function(txt, style) {
- if (typeof style === 'string') {
- var fbspan = $('<span class="'+style+'"></span>');
- fbspan.text(txt);
- DOM.feedback.html(fbspan);
- DOM.guessbox.addClass(style);
- setTimeout(function() {DOM.guessbox.removeClass(style);}, 350);
- return;
- }
- DOM.feedback.text(txt);
- };
-
- var addVolumeControl = function() {
- var volumebutton = $('<div id="volume-button">'+
- '<a class="button"><div id="icon" class="volume-high"></div></a>'+
- '<div id="volume-slider">'+ // Outer background
- '<div id="volume-total"></div>'+ // Rail
- '<div id="volume-current"></div>'+ // Current volume
- '<div id="volume-handle"></div>'+ // Handle
- '</div>'+
- '</div>').appendTo("#volume");
- var icon = volumebutton.find('#icon');
- var volumeslider = volumebutton.find('#volume-slider');
- var volumetotal = volumebutton.find('#volume-total');
- var volumecurrent = volumebutton.find('#volume-current');
- var volumehandle = volumebutton.find('#volume-handle');
- var mouseisdown = false;
- var mouseisover = false;
- var oldvalue = 1;
- var clicked = false;
-
- var positionVolumeHandle = function(volume) {
- if (!volumeslider.is(':visible')) {
- volumeslider.show();
- positionVolumeHandle(volume);
- volumeslider.hide();
- return;
- }
- var totalheight = volumetotal.height();
- var totalposition = volumetotal.position();
- var newtop = totalheight - (totalheight * volume);
- volumehandle.css('top', totalposition.top + newtop - (volumehandle.height() / 2));
- volumecurrent.height(totalheight - newtop );
- volumecurrent.css('top', totalposition.top + newtop);
- };
-
- var handleIcon = function (volume) {
- if (volume === 0) {
- icon.removeClass().addClass('volume-none');
- }
- else if (volume <= 0.33) {
- icon.removeClass().addClass('volume-low');
- }
- else if (volume <= 0.66) {
- icon.removeClass().addClass('volume-medium');
- }
- else {
- icon.removeClass().addClass('volume-high');
- }
- };
-
- var setVolume = function(volume) {
- handleIcon(volume);
- oldvalue = volume;
- jplayer.jPlayer("volume", volume);
- };
-
- var handleVolumeMove = function(e) {
- var totaloffset = volumetotal.offset();
- var newy = e.pageY - totaloffset.top;
- var railheight = volumetotal.height();
- var totalTop = parseInt(volumetotal.css('top').replace(/px/,''),10);
- var volume = (railheight - newy) / railheight;
-
- if (newy < 0) {
- newy = 0;
- }
- else if (newy > railheight) {
- newy = railheight;
- }
-
- volumehandle.css('top', totalTop + newy - (volumehandle.height() / 2));
- volumecurrent.height(railheight - newy);
- volumecurrent.css('top',newy+totalTop);
-
- volume = Math.max(0,volume);
- volume = Math.min(volume,1);
-
- setVolume(volume);
-
- var d = new Date();
- d.setTime(d.getTime() + 31536000000); // One year in milliseconds
- document.cookie = "volume="+volume+";path=/;expires="+d.toGMTString()+";";
- };
-
- var loadFromCookie = function() {
- if (/volume\s*\=/.test(document.cookie)) {
- var value = document.cookie.replace(/.*volume\s*\=\s*([^;]*);?.*/, "$1");
- value = parseFloat(value);
- positionVolumeHandle(value);
- setVolume(value);
- }
- else {
- positionVolumeHandle(1);
- }
- };
-
- volumebutton.hover(function() {
- volumeslider.show();
- mouseisover = true;
- }, function() {
- mouseisover = false;
- if (!mouseisdown) {
- volumeslider.hide();
- }
- });
-
- volumeslider.on('mouseover', function() {
- mouseisover = true;
- }).on('mousedown', function (e) {
- handleVolumeMove(e);
- mouseisdown = true;
- return false;
- });
-
- $(document).on('mouseup', function (e) {
- mouseisdown = false;
- if (!mouseisover) {
- volumeslider.hide();
- }
- }).on('mousemove', function (e) {
- if (mouseisdown) {
- handleVolumeMove(e);
- }
- });
-
- volumebutton.find('.button').click(function() {
- if (!clicked) {
- clicked = true;
- if (oldvalue !== 0) {
- jplayer.jPlayer("volume", 0);
- positionVolumeHandle(0);
- handleIcon(0);
- }
- }
- else {
- clicked = false;
- if (oldvalue !== 0) {
- jplayer.jPlayer("volume", oldvalue);
- positionVolumeHandle(oldvalue);
- handleIcon(oldvalue);
- }
- }
- });
- loadFromCookie();
- };
-
- var setVariables = function() {
- DOM.modal = $('#modal');
- DOM.guessbox = $('#guess');
- DOM.users = $('#users');
- DOM.rank = $('#summary .rank');
- DOM.points = $('#summary .points');
- DOM.messagebox = $('#message');
- DOM.recipient = $('#recipient');
- DOM.tracks = $('#tracks');
- DOM.track = $('#summary .track');
- DOM.progress = $('#progress');
- DOM.cassettewheels = $('#cassette .wheel');
- DOM.tapeleft = $('#tape-left');
- DOM.taperight = $('#tape-right');
- DOM.countdown = $('#countdown');
- DOM.chat = $('#chat');
- DOM.feedback = $('#feedback');
- DOM.togglechat = $('#toggle-chat');
- DOM.chatwrapper = $('#chat-outer-wrapper');
- };
-
- // Set up the room.
- $(function() {
- setVariables();
- DOM.modal.modal({keyboard:false,show:false,backdrop:"static"});
- DOM.togglechat.click(hideChat);
- if ($.browser.mozilla) {
- // Block ESC button in firefox (it breaks all socket connection).
- $(document).keypress(function(event) {
- if(event.keyCode === 27) {
- return false;
- }
- });
- }
- socket = io.connect("http://binb.nodejitsu.com/", {'reconnect':false});
- socket.on("connect", function() {
- jplayer = $("#player").jPlayer({
- ready: function() {
- socket.emit('loggedin', function(data) {
- if (data) {
- nickname = data;
- socket.emit('joinroom', roomname);
- }
- else {
- joinAnonymously();
- }
- });
- if (!$.jPlayer.platform.mobile && !$.jPlayer.platform.tablet) {
- addVolumeControl();
- }
- else {
- var touchbackdrop = $('<div id="touch-backdrop">'+
- '<button id="touch-play" class="btn btn-danger disabled">'+
- '<i class="icon-play icon-white"></i> Wait'+
- '</button></div>').appendTo("#cassette");
- touchplay = $('#touch-play');
- touchplay.click(function() {
- if (!$(this).hasClass("btn-danger")) {
- touchplay = null;
- jplayer.jPlayer('play', elapsedtime);
- touchbackdrop.remove();
- }
- });
- }
- },
- swfPath: "/static/swf/",
- //solution: "flash, html",
- supplied: "m4a",
- preload: "auto",
- volume: 1
- });
- });
- socket.on('alreadyinaroom', alreadyInARoom);
- socket.on('invalidnickname', invalidNickName);
- socket.on('ready', ready);
- socket.on("disconnect", disconnect);
- });
+
+ var touchplay = null;
+ var elapsedtime = 0;
+ var jplayer = null;
+ var nickname = null;
+ var socket = null;
+ var pvtmsgto = null;
+ var roundpoints = 0;
+ var stopanimation = false;
+ var states = ['A song is already playing, please wait for the next one...',
+ 'Game is about to start...', 'Game is over', 'New game will start soon...'];
+ var tmstrings = ['Yes, you guessed the title. Who is the artist?', 'Now tell me the artist!',
+ 'Correct, do you also know the artist?'];
+ var amstrings = ['Yes, that\'s the artist. What about the title?', 'Exactly, now tell me the title!',
+ 'Do you also know the title?'];
+ var bmstrings = ['Yeah true! do you like this track?', 'Good job!', 'Great!',
+ 'Very well done!', 'Exactly!', 'Excellent!', 'Woohoo!'];
+ var nmstrings = ['Nope, sorry!', 'No way!', 'Fail', 'Nope', 'No', 'That\'s wrong', 'What?!',
+ 'Wrong', 'Haha, what?!', 'You kidding?', 'Don\'t make me laugh', 'You mad?',
+ 'Try again'];
+ var historyvalues = [];
+ var historycursor = 0;
+ var DOM = {};
+
+ String.prototype.encodeEntities = function() {
+ return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
+ };
+
+ // Exact match version of jQuery :contains selector
+ $.expr[":"].econtains = function(obj, index, meta, stack) {
+ return $(obj).html() === meta[0].replace(/^[\s\S]+:econtains\(([\s\S]+)\)$/, "$1");
+ };
+
+ // Prompt for name and send it.
+ var joinAnonymously = function(msg) {
+ if (/nickname\s*\=/.test(document.cookie) && !msg) {
+ nickname = unescape(document.cookie.replace(/.*nickname\s*\=\s*([^;]*);?.*/, "$1"));
+ socket.emit('joinanonymously', nickname, roomname);
+ }
+ else {
+ if (!$('body').hasClass('modal-open')) {
+ var html = '<div class="modal-header"><h3>You are joining the '+roomname+' room</h3></div>';
+ html += '<div class="modal-body"><p>'+(msg || "What's your name?")+'</p></div>';
+ html += '<div class="modal-footer">';
+ html += '<input id="login" class="" type="text" name="nickname" />';
+ html += '<button id="join" class="btn btn-primary">';
+ html += '<i class="icon-user icon-white"></i> Join the game</button></div>';
+
+ $(html).appendTo(DOM.modal);
+ var login = $('#login');
+ var button = $('#join');
+ button.click(function() {
+ var val = $.trim(login.val());
+ if (val !== "") {
+ nickname = val;
+ socket.emit('joinanonymously', nickname, roomname);
+ }
+ else {
+ var txt = "Nickname can't be empty.";
+ invalidNickName('<span class="label label-important">'+txt+'</span>');
+ }
+ login.val("");
+ });
+ login.keyup(function(event) {
+ if (event.keyCode === 13) {
+ button.click();
+ }
+ });
+ DOM.modal.modal('show');
+ DOM.modal.on('shown', function() {
+ login.focus();
+ });
+ }
+ else {
+ $('.modal-body p').html(msg);
+ $('#login').focus();
+ }
+ }
+ };
+
+ // Your submitted name was invalid
+ var invalidNickName = function(feedback) {
+ joinAnonymously(feedback+"<br/>Try with another one:");
+ };
+
+ /* Triggered when a logged user tries to join a room from another tab or another browser
+ and he is already in a room */
+ var alreadyInARoom = function() {
+ var html = '<div class="modal-header"><h3>Already in a room</h3></div>';
+ html += '<div class="modal-body"><div class="alert alert-error alert-block">';
+ html += '<h4 class="alert-heading">Warning!</h4>You are already in a room.<br/>';
+ html += 'Leave the other room and refresh this page or close this one.</div></div>';
+ $(html).appendTo(DOM.modal);
+ DOM.modal.modal('show');
+ };
+
+ // You joined the room
+ var ready = function(usersData, trackscount, loggedin) {
+ if (!loggedin && !/nickname\s*\=/.test(document.cookie)) {
+ document.cookie = "nickname="+escape(nickname)+";path=/;";
+ }
+ DOM.modal.modal('hide').empty();
+ $('#total-tracks span').text(trackscount);
+ var msg = nickname+" joined the game";
+ var joinspan = $("<span class='join'></span>");
+ joinspan.text(msg);
+ addChatEntry(joinspan);
+ updateUsers(usersData);
+
+ DOM.messagebox.keydown(function(event) {
+ if (event.keyCode === 13) {
+ var val = $.trim(DOM.messagebox.val());
+ if (val !== "") {
+ if (pvtmsgto) {
+ socket.emit('sendchatmsg', val, pvtmsgto);
+ }
+ else {
+ socket.emit('sendchatmsg', val);
+ }
+ }
+ DOM.messagebox.val("");
+ }
+ });
+
+ DOM.guessbox.keydown(function(event) {
+ switch (event.keyCode) {
+ case 13: // return
+ var guess = $.trim(DOM.guessbox.val());
+ if (guess !== "") {
+ socket.emit('guess', guess.toLowerCase());
+ historyvalues.push(guess);
+ if (historyvalues.length > 20) {
+ historyvalues.splice(0, 1);
+ }
+ historycursor = historyvalues.length;
+ }
+ DOM.guessbox.val("");
+ break;
+ case 38: // up-arrow
+ if (historycursor > 0) {
+ DOM.guessbox.val(historyvalues[--historycursor]);
+ }
+ break;
+ case 40: // down-arrow
+ if (historycursor < historyvalues.length - 1) {
+ DOM.guessbox.val(historyvalues[++historycursor]);
+ }
+ else {
+ historycursor = historyvalues.length;
+ DOM.guessbox.val("");
+ }
+ }
+ });
+
+ DOM.guessbox.focus();
+
+ socket.on('newuser', userJoin);
+ socket.on('userleft', userLeft);
+ socket.on('updateusers', updateUsers);
+ socket.on('chatmsg', getChatMessage);
+ socket.on('loadtrack', loadTrack);
+ socket.on('playtrack', playTrack);
+ socket.on('trackinfo', addTrackInfo);
+ socket.on('artistmatched', function() {
+ var feedback = amstrings[Math.floor(Math.random()*amstrings.length)];
+ addFeedback(feedback, "correct");
+ });
+ socket.on('titlematched', function() {
+ var feedback = tmstrings[Math.floor(Math.random()*tmstrings.length)];
+ addFeedback(feedback, "correct");
+ });
+ socket.on('bothmatched', function() {
+ var feedback = bmstrings[Math.floor(Math.random()*bmstrings.length)];
+ addFeedback(feedback, "correct");
+ });
+ socket.on('nomatch', function() {
+ var feedback = nmstrings[Math.floor(Math.random()*nmstrings.length)];
+ addFeedback(feedback, "wrong");
+ });
+ socket.on('stoptrying', function() {
+ addFeedback('You guessed both artist and title. Please wait...');
+ });
+ socket.on('noguesstime', function() {
+ addFeedback('You have to wait the next song...');
+ });
+ socket.on('gameover', gameOver);
+ socket.on('status', setStatus);
+ socket.emit('getstatus');
+ };
+
+ var setStatus = function(data) {
+ if (data.status === 0) {
+ cassetteAnimation(Date.now()+data.timeleft, true);
+ }
+ if (data.status === 1) {
+ loadTrack(data.previewUrl);
+ }
+ addFeedback(states[data.status]);
+ };
+
+ // A new player joined the game
+ var userJoin = function(username, usersData) {
+ var msg = username+" joined the game";
+ var joinspan = $("<span class='join'></span>");
+ joinspan.text(msg);
+ addChatEntry(joinspan);
+ updateUsers(usersData);
+ };
+
+ // A user left the game
+ var userLeft = function(username, usersData) {
+ var leftmsg = username+" left the game";
+ var leftspan = $("<span class='left'></span>");
+ leftspan.text(leftmsg);
+ addChatEntry(leftspan);
+ updateUsers(usersData);
+ };
+
+ // Update the list of users
+ var updateUsers = function(usersData) {
+ DOM.users.empty();
+ var users = [];
+ for (var key in usersData) {
+ users.push(usersData[key]);
+ }
+ users.sort(function(a, b) {return b.points - a.points;});
+ // Flag to test if our private recipient is in the list of active users
+ var found = false;
+ for (var i=0; i<users.length; i++) {
+ var user = users[i];
+ var li = $('<li></li>');
+ var pvt = $('<span class="private label label-info">P</span>');
+ var username = $('<span class="name"></span>').text(user.nickname);
+ var points = $('<span class="points">('+user.points+')</span>');
+ var roundrank = $('<span></span>');
+ var roundpointsel = $('<span class="round-points"></span>');
+ var guesstime = $('<span class="guess-time"></span>');
+ li.append(pvt, username, points, roundrank, roundpointsel, guesstime);
+ if (user.registered) {
+ var href = 'href="/user/'+encodeURI(username.html())+'"';
+ pvt.after('<a class="registered" target="_blank" '+href+'></a>');
+ }
+ DOM.users.append(li);
+ if (pvtmsgto === user.nickname) {
+ pvt.show();
+ username.click(clearPrivate);
+ found = true;
+ }
+ else {
+ username.click(function() {
+ addPrivate($(this).text());
+ });
+ }
+ if (nickname === user.nickname) {
+ username.addClass("you");
+ roundpoints = user.roundpoints;
+ DOM.rank.text(i+1);
+ DOM.points.text(user.points);
+ }
+ if (user.roundpoints > 0) {
+ roundpointsel.text('+'+user.roundpoints);
+ if (user.roundpoints === 1) {
+ username.addClass("matched");
+ }
+ else {
+ if (user.roundpoints > 3) {
+ var stand = 7 - user.roundpoints;
+ roundrank.addClass("round-rank stand"+stand);
+ var gtime = (user.guesstime / 1000).toFixed(1);
+ guesstime.text(gtime+" s");
+ }
+ username.addClass("correct");
+ }
+ }
+ }
+ if (!found && pvtmsgto) {
+ var width = DOM.recipient.outerWidth(true) + 1;
+ DOM.recipient.css('margin-right','0');
+ DOM.recipient.text("");
+ DOM.messagebox.animate({'width':'+='+width+'px'}, "fast");
+ pvtmsgto = null;
+ DOM.messagebox.focus();
+ }
+ };
+
+ var addPrivate = function(usrname) {
+ if (pvtmsgto) {
+ clearPrivate();
+ }
+ if (nickname === usrname) {
+ return;
+ }
+ DOM.recipient.css('margin-right','4px');
+ DOM.recipient.text("To "+usrname+":");
+ var width = DOM.recipient.outerWidth(true) + 1;
+ DOM.recipient.hide();
+ DOM.messagebox.animate({'width':'-='+width+'px'}, "fast", function() {DOM.recipient.show();});
+ var el = $("span.name:econtains("+usrname.encodeEntities()+")");
+ el.prevAll(".private").show();
+ el.unbind('click');
+ el.click(clearPrivate);
+ pvtmsgto = usrname;
+ DOM.messagebox.focus();
+ };
+
+ var clearPrivate = function() {
+ var width = DOM.recipient.outerWidth(true) + 1;
+ DOM.recipient.css('margin-right','0');
+ DOM.recipient.text("");
+ DOM.messagebox.animate({'width':'+='+width+'px'}, "fast");
+ var el = $("span.name:econtains("+pvtmsgto.encodeEntities()+")");
+ el.prevAll(".private").hide();
+ el.unbind("click");
+ el.click(function() {
+ addPrivate($(this).text());
+ });
+ pvtmsgto = null;
+ DOM.messagebox.focus();
+ };
+
+ // Receive a chat message
+ var getChatMessage = function(chatmsg, from, to) {
+ var prefix = from;
+ var msgspan = $("<span class='message'></span>");
+ if (to) {
+ // Private Message
+ prefix = (nickname === from) ? '(To '+to+')' : '(From '+prefix+')';
+ msgspan.addClass("private");
+ }
+ var msg = prefix+": "+chatmsg;
+ msgspan.text(msg);
+ addChatEntry(msgspan);
+ };
+
+ var loadTrack = function(previewUrl) {
+ jplayer.jPlayer("mute");
+ jplayer.jPlayer("setMedia", {m4a: previewUrl});
+ };
+
+ // Play a track
+ var playTrack = function(data) {
+ if (touchplay) {
+ touchplay.removeClass("btn-danger disabled").addClass("btn-success");
+ touchplay.html('<i class="icon-play icon-white"></i> Play');
+ }
+ jplayer.jPlayer("unmute");
+ jplayer.jPlayer("play");
+ updateUsers(data.users);
+ cassetteAnimation(Date.now()+30000, true);
+ if (data.counter === 1) {
+ DOM.modal.modal('hide').empty();
+ DOM.tracks.empty();
+ }
+ DOM.track.text(data.counter+'/'+data.tot);
+ addFeedback('What is this song?');
+ };
+
+ // Start cassette animation
+ var cassetteAnimation = function(endtime, forward) {
+ var millisleft = endtime - Date.now();
+ var secleft = millisleft / 1000;
+ var width, deg, offsetleft, offsetright, css;
+ if (forward) {
+ width = 148 - (148*secleft/30);
+ deg = 360 - (360*secleft/30);
+ offsetleft = 44 - 24*secleft/30;
+ offsetright = 130 - 24*secleft/30;
+ DOM.progress.width(width);
+ css = {
+ '-moz-transform' : 'rotate('+deg+'deg)',
+ '-webkit-transform' : 'rotate('+deg+'deg)',
+ '-o-transform' : 'rotate('+deg+'deg)',
+ '-ms-transform' : 'rotate('+deg+'deg)',
+ 'transform' : 'rotate('+deg+'deg)'
+ };
+ DOM.cassettewheels.css(css);
+ DOM.tapeleft.css('left', offsetleft+'px');
+ DOM.taperight.css('left', offsetright+'px');
+ }
+ else {
+ width = 148*secleft/5;
+ deg = 360*secleft/5;
+ offsetleft = 20 + 24*secleft/5;
+ offsetright = 106 + 24*secleft/5;
+ DOM.progress.width(width);
+ css = {
+ '-moz-transform' : 'rotate('+deg+'deg)',
+ '-webkit-transform' : 'rotate('+deg+'deg)',
+ '-o-transform' : 'rotate('+deg+'deg)',
+ '-ms-transform' : 'rotate('+deg+'deg)',
+ 'transform' : 'rotate('+deg+'deg)'
+ };
+ DOM.cassettewheels.css(css);
+ DOM.tapeleft.css('left', offsetleft+'px');
+ DOM.taperight.css('left', offsetright+'px');
+ }
+ if (forward) {
+ DOM.countdown.text(secleft.toFixed(1));
+ if (touchplay) {elapsedtime = 30 - Math.round(secleft);}
+ }
+ else {
+ DOM.countdown.text(Math.round(secleft));
+ }
+ if (stopanimation || millisleft < 50) {
+ return;
+ }
+ setTimeout(function() {cassetteAnimation(endtime, forward);}, 50);
+ };
+
+ // Add track info
+ var addTrackInfo = function(data) {
+ if (touchplay) {
+ touchplay.removeClass("btn-success").addClass("btn-danger disabled");
+ touchplay.html('<i class="icon-play icon-white"></i> Wait');
+ }
+ cassetteAnimation(Date.now()+5000, false);
+ var html = '<li class="bordered"><img class="artwork" src="'+data.artworkUrl+'"/>';
+ html += '<div class="info"><div class="artist">'+data.artistName+'</div>';
+ var titleattr = '';
+ var trackname = data.trackName;
+ if (data.trackName.length > 45) {
+ titleattr = data.trackName.replace(/"/g, """);
+ trackname = data.trackName.substring(0,42) + '...';
+ }
+ html += '<div class="title" title="'+titleattr+'">'+trackname+'</div></div>';
+ var attrs = '';
+ var rp = '';
+ if (roundpoints > 0) {
+ rp = '+'+roundpoints;
+ if (roundpoints > 3) {
+ var stand = 7 - roundpoints;
+ attrs += 'class="round-rank stand'+stand+'"';
+ }
+ }
+ html += '<div '+attrs+'></div><div class="round-points">'+rp+'</div>';
+ html += '<a target="_blank" href="'+data.trackViewUrl+'">';
+ html += '<img src="/static/images/itunes.png"/></a></li>';
+ DOM.tracks.prepend($(html));
+ };
+
+ // Game over countdown
+ var countDown = function(endtime) {
+ var millisleft = endtime - Date.now();
+ var secleft = millisleft / 1000;
+ $('.modal-footer span').text(Math.round(secleft));
+ if (millisleft < 200) {
+ return;
+ }
+ setTimeout(function() {countDown(endtime);}, 200);
+ };
+
+ var gameOver = function(podium) {
+ var html = '<div class="modal-header"><h3>Game Over</h3></div>';
+ html += '<div class="modal-body"><table class="table table-striped scoreboard">';
+ html += '<thead><tr><th>#</th><th>Name</th><th>Points</th>';
+ html += '<th><div class="cups stand1"></div></th><th><div class="cups stand2"></div></th>';
+ html += '<th><div class="cups stand3"></div></th><th>Guessed</th><th>Mean time</th>';
+ html += '</thead><tbody>';
+ for(var i=0;i<3;i++) {
+ if (podium[i]) {
+ var playername = podium[i].nickname.encodeEntities();
+ html += '<tr><td><div class="medals rank'+(i+1)+'"></div></td>';
+ html += '<td class="name">'+playername+'</td>';
+ html += '<td>'+podium[i].points+'</td>';
+ html += '<td>'+podium[i].golds+'</td><td>'+podium[i].silvers+'</td>';
+ html += '<td>'+podium[i].bronzes+'</td><td>'+podium[i].guessed+'</td>';
+ var meantime = "N/A";
+ if (podium[i].guessed !== 0) {
+ meantime = podium[i].totguesstime / podium[i].guessed;
+ meantime = (meantime / 1000).toFixed(1)+" s";
+ }
+ html += '<td>'+meantime+'</td></tr>';
+ }
+ }
+ html +='</tbody></table></div>';
+ html += '<div class="modal-footer">A new game will start in <span></span> second/s</div>';
+ DOM.modal.append($(html));
+ DOM.modal.modal('show');
+ countDown(Date.now()+10000);
+ };
+
+ // Let the user know when he / she has disconnected
+ var disconnect = function() {
+ stopanimation = true;
+ jplayer.jPlayer("stop");
+ var errormsg = "ERROR: You have disconnected.";
+ var errorspan = $("<span class='error'></span>");
+ errorspan.text(errormsg);
+ addChatEntry(errorspan);
+ addFeedback('Something wrong happened');
+ DOM.users.empty();
+ };
+
+ // Add a chat entry, whether message, notification, etc.
+ var addChatEntry = function(childNode) {
+ var li = $("<li class='entry'></li>");
+ li.append(childNode);
+ DOM.chat.append(li);
+ DOM.chat[0].scrollTop = DOM.chat[0].scrollHeight;
+ };
+
+ var hideChat = function() {
+ DOM.togglechat.text("Show chat").unbind('click');
+ DOM.chatwrapper.toggle(300);
+ DOM.tracks.animate({maxHeight:"434px"}, 300);
+ DOM.togglechat.click(showChat);
+ };
+
+ var showChat = function() {
+ DOM.togglechat.text("Hide chat").unbind('click');
+ DOM.chatwrapper.toggle(300);
+ DOM.tracks.animate({maxHeight:"240px"}, 300, function() {
+ DOM.chat[0].scrollTop = DOM.chat[0].scrollHeight;
+ });
+ DOM.togglechat.click(hideChat);
+ };
+
+ var addFeedback = function(txt, style) {
+ if (typeof style === 'string') {
+ var fbspan = $('<span class="'+style+'"></span>');
+ fbspan.text(txt);
+ DOM.feedback.html(fbspan);
+ DOM.guessbox.addClass(style);
+ setTimeout(function() {DOM.guessbox.removeClass(style);}, 350);
+ return;
+ }
+ DOM.feedback.text(txt);
+ };
+
+ var addVolumeControl = function() {
+ var volumebutton = $('<div id="volume-button">'+
+ '<a class="button"><div id="icon" class="volume-high"></div></a>'+
+ '<div id="volume-slider">'+ // Outer background
+ '<div id="volume-total"></div>'+ // Rail
+ '<div id="volume-current"></div>'+ // Current volume
+ '<div id="volume-handle"></div>'+ // Handle
+ '</div>'+
+ '</div>').appendTo("#volume");
+ var icon = volumebutton.find('#icon');
+ var volumeslider = volumebutton.find('#volume-slider');
+ var volumetotal = volumebutton.find('#volume-total');
+ var volumecurrent = volumebutton.find('#volume-current');
+ var volumehandle = volumebutton.find('#volume-handle');
+ var mouseisdown = false;
+ var mouseisover = false;
+ var oldvalue = 1;
+ var clicked = false;
+
+ var positionVolumeHandle = function(volume) {
+ if (!volumeslider.is(':visible')) {
+ volumeslider.show();
+ positionVolumeHandle(volume);
+ volumeslider.hide();
+ return;
+ }
+ var totalheight = volumetotal.height();
+ var totalposition = volumetotal.position();
+ var newtop = totalheight - (totalheight * volume);
+ volumehandle.css('top', totalposition.top + newtop - (volumehandle.height() / 2));
+ volumecurrent.height(totalheight - newtop );
+ volumecurrent.css('top', totalposition.top + newtop);
+ };
+
+ var handleIcon = function (volume) {
+ if (volume === 0) {
+ icon.removeClass().addClass('volume-none');
+ }
+ else if (volume <= 0.33) {
+ icon.removeClass().addClass('volume-low');
+ }
+ else if (volume <= 0.66) {
+ icon.removeClass().addClass('volume-medium');
+ }
+ else {
+ icon.removeClass().addClass('volume-high');
+ }
+ };
+
+ var setVolume = function(volume) {
+ handleIcon(volume);
+ oldvalue = volume;
+ jplayer.jPlayer("volume", volume);
+ };
+
+ var handleVolumeMove = function(e) {
+ var totaloffset = volumetotal.offset();
+ var newy = e.pageY - totaloffset.top;
+ var railheight = volumetotal.height();
+ var totalTop = parseInt(volumetotal.css('top').replace(/px/,''),10);
+ var volume = (railheight - newy) / railheight;
+
+ if (newy < 0) {
+ newy = 0;
+ }
+ else if (newy > railheight) {
+ newy = railheight;
+ }
+
+ volumehandle.css('top', totalTop + newy - (volumehandle.height() / 2));
+ volumecurrent.height(railheight - newy);
+ volumecurrent.css('top',newy+totalTop);
+
+ volume = Math.max(0,volume);
+ volume = Math.min(volume,1);
+
+ setVolume(volume);
+
+ var d = new Date();
+ d.setTime(d.getTime() + 31536000000); // One year in milliseconds
+ document.cookie = "volume="+volume+";path=/;expires="+d.toGMTString()+";";
+ };
+
+ var loadFromCookie = function() {
+ if (/volume\s*\=/.test(document.cookie)) {
+ var value = document.cookie.replace(/.*volume\s*\=\s*([^;]*);?.*/, "$1");
+ value = parseFloat(value);
+ positionVolumeHandle(value);
+ setVolume(value);
+ }
+ else {
+ positionVolumeHandle(1);
+ }
+ };
+
+ volumebutton.hover(function() {
+ volumeslider.show();
+ mouseisover = true;
+ }, function() {
+ mouseisover = false;
+ if (!mouseisdown) {
+ volumeslider.hide();
+ }
+ });
+
+ volumeslider.on('mouseover', function() {
+ mouseisover = true;
+ }).on('mousedown', function (e) {
+ handleVolumeMove(e);
+ mouseisdown = true;
+ return false;
+ });
+
+ $(document).on('mouseup', function (e) {
+ mouseisdown = false;
+ if (!mouseisover) {
+ volumeslider.hide();
+ }
+ }).on('mousemove', function (e) {
+ if (mouseisdown) {
+ handleVolumeMove(e);
+ }
+ });
+
+ volumebutton.find('.button').click(function() {
+ if (!clicked) {
+ clicked = true;
+ if (oldvalue !== 0) {
+ jplayer.jPlayer("volume", 0);
+ positionVolumeHandle(0);
+ handleIcon(0);
+ }
+ }
+ else {
+ clicked = false;
+ if (oldvalue !== 0) {
+ jplayer.jPlayer("volume", oldvalue);
+ positionVolumeHandle(oldvalue);
+ handleIcon(oldvalue);
+ }
+ }
+ });
+ loadFromCookie();
+ };
+
+ var setVariables = function() {
+ DOM.modal = $('#modal');
+ DOM.guessbox = $('#guess');
+ DOM.users = $('#users');
+ DOM.rank = $('#summary .rank');
+ DOM.points = $('#summary .points');
+ DOM.messagebox = $('#message');
+ DOM.recipient = $('#recipient');
+ DOM.tracks = $('#tracks');
+ DOM.track = $('#summary .track');
+ DOM.progress = $('#progress');
+ DOM.cassettewheels = $('#cassette .wheel');
+ DOM.tapeleft = $('#tape-left');
+ DOM.taperight = $('#tape-right');
+ DOM.countdown = $('#countdown');
+ DOM.chat = $('#chat');
+ DOM.feedback = $('#feedback');
+ DOM.togglechat = $('#toggle-chat');
+ DOM.chatwrapper = $('#chat-outer-wrapper');
+ };
+
+ // Set up the room.
+ $(function() {
+ setVariables();
+ DOM.modal.modal({keyboard:false,show:false,backdrop:"static"});
+ DOM.togglechat.click(hideChat);
+ if ($.browser.mozilla) {
+ // Block ESC button in firefox (it breaks all socket connection).
+ $(document).keypress(function(event) {
+ if(event.keyCode === 27) {
+ return false;
+ }
+ });
+ }
+ socket = io.connect("http://binb.nodejitsu.com/", {'reconnect':false});
+ socket.on("connect", function() {
+ jplayer = $("#player").jPlayer({
+ ready: function() {
+ socket.emit('loggedin', function(data) {
+ if (data) {
+ nickname = data;
+ socket.emit('joinroom', roomname);
+ }
+ else {
+ joinAnonymously();
+ }
+ });
+ if (!$.jPlayer.platform.mobile && !$.jPlayer.platform.tablet) {
+ addVolumeControl();
+ }
+ else {
+ var touchbackdrop = $('<div id="touch-backdrop">'+
+ '<button id="touch-play" class="btn btn-danger disabled">'+
+ '<i class="icon-play icon-white"></i> Wait'+
+ '</button></div>').appendTo("#cassette");
+ touchplay = $('#touch-play');
+ touchplay.click(function() {
+ if (!$(this).hasClass("btn-danger")) {
+ touchplay = null;
+ jplayer.jPlayer('play', elapsedtime);
+ touchbackdrop.remove();
+ }
+ });
+ }
+ },
+ swfPath: "/static/swf/",
+ //solution: "flash, html",
+ supplied: "m4a",
+ preload: "auto",
+ volume: 1
+ });
+ });
+ socket.on('alreadyinaroom', alreadyInARoom);
+ socket.on('invalidnickname', invalidNickName);
+ socket.on('ready', ready);
+ socket.on("disconnect", disconnect);
+ });
})();
--- /dev/null
+/**
+ * Module dependencies.
+ */
+
+var async = require('async')
+ , Captcha = require('../lib/captcha')
+ , db
+ , rooms;
+
+/**
+ * Generate a task for async.
+ */
+
+var task = function(genre) {
+ return function(callback) {
+ db.srandmember(genre, function(err, res) {
+ db.hget(res, "artworkUrl100", callback);
+ });
+ };
+};
+
+/**
+ * Initialize dependencies.
+ */
+
+exports.use = function(options) {
+ db = options.db;
+ rooms = options.rooms;
+};
+
+exports.index = function(req, res) {
+ if (req.session.user) {
+ res.local('loggedin', req.session.user.replace(/&/g, "&"));
+ }
+ res.render("index", {rooms:rooms});
+};
+
+exports.signup = function(req, res) {
+ var captcha = new Captcha();
+ req.session.captchacode = captcha.getCode();
+ res.render("signup", {captchaurl:captcha.toDataURL()});
+};
+
+exports.login = function(req, res) {
+ res.render("login");
+};
+
+/**
+ * Extract at random in each room, some album covers and return the result as a JSON.
+ */
+
+exports.artworks = function(req, res) {
+ var tasks = [];
+ for (var i=0; i<rooms.length; i++) {
+ for (var j=0; j<6; j++) {
+ tasks.push(task(rooms[i]));
+ }
+ }
+ async.parallel(tasks, function(err, results) {
+ var obj = {
+ resultCount: results.length,
+ results: results
+ };
+ res.json(obj);
+ });
+};
+
+exports.room = function(req, res) {
+ if (rooms.indexOf(req.params.room) !== -1) {
+ if (req.session.user) {
+ res.local('loggedin', req.session.user.replace(/&/g, "&"));
+ }
+ res.render("room", {roomname:req.params.room,rooms:rooms});
+ }
+ else {
+ res.send(404);
+ }
+};
--- /dev/null
+/**
+ * Module dependencies.
+ */
+
+var crypto = require('crypto')
+ , db
+ , User = require('../lib/user');
+
+/**
+ * Extend String with custom methods for input validation.
+ */
+
+String.prototype.trim = function() {
+ return this.replace(/^[\r\n\t\s]+|[\r\n\t\s]+$/g, '');
+};
+
+String.prototype.isEmail = function() {
+ return this.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/);
+};
+
+/**
+ * Initialize dependencies.
+ */
+
+exports.use = function(options) {
+ db = options.db;
+};
+
+/**
+ * Sign up middlewares.
+ */
+
+exports.validateSignUp = function(req, res, next) {
+ var errors = {};
+
+ req.body.username = req.body.username.trim(); // Username sanitization
+ if (req.body.username === 'binb') {
+ errors.username = 'is reserved';
+ }
+ else if (!req.body.username.match(/^[^\x00-\x1F\x7F]{1,15}$/)) {
+ errors.username = '1 to 15 characters required';
+ }
+ if (!req.body.email.isEmail()) {
+ errors.email = 'is not an email address';
+ }
+ if (!req.body.password.match(/^[A-Za-z0-9]{6,15}$/)) {
+ errors.password = '6 to 15 alphanumeric characters required';
+ }
+ if (req.body.captcha !== req.session.captchacode) {
+ errors.captcha = 'no match';
+ }
+
+ // Save old values to repopulate the fields in case of future errors
+ req.session.oldvalues = {
+ username: req.body.username,
+ email: req.body.email
+ };
+
+ if (errors.username || errors.email || errors.password || errors.captcha) {
+ req.session.errors = errors;
+ return res.redirect('/signup');
+ }
+
+ next();
+};
+
+exports.userExists = function(req, res, next) {
+ var key = 'user:'+req.body.username;
+ db.exists(key, function(err, data) {
+ if (data === 1) {
+ // User already exists
+ req.session.errors = {alert: 'A user with that name already exists.'};
+ return res.redirect('/signup');
+ }
+ next();
+ });
+};
+
+exports.emailExists = function(req, res, next) {
+ var key = 'email:'+req.body.email;
+ db.exists(key, function(err, data) {
+ if (data === 1) {
+ // Email already exists
+ req.session.errors = {alert: 'A user with that email already exists.'};
+ return res.redirect('/signup');
+ }
+ next();
+ });
+};
+
+exports.createAccount = function(req, res) {
+ var userkey = 'user:'+req.body.username
+ , mailkey = 'email:'+req.body.email
+ , salt = crypto.randomBytes(6).toString('base64')
+ , hash = crypto.createHash('sha256').update(salt+req.body.password).digest('hex')
+ , date = new Date()
+ , day = date.getDate()
+ , month = date.getMonth() + 1
+ , year = date.getFullYear();
+
+ if (day < 10) {
+ day = '0' + day;
+ }
+ if (month < 10) {
+ month = '0' + month;
+ }
+ var joindate = day+'/'+month+'/'+year;
+ var user = new User(req.body.username, req.body.email, salt, hash, joindate);
+ // Add new user in the db
+ db.hmset(userkey, user);
+ db.set(mailkey, userkey);
+ db.sadd('users', userkey);
+ db.sadd('emails', mailkey);
+ // Delete old fields values (we don't want these to be available in login view)
+ delete req.session.oldvalues;
+ var msg = 'You successfully created your account. You are now ready to login.';
+ res.render('login', {success:msg});
+};
+
+/**
+ * Login middlewares.
+ */
+
+exports.validateLogin = function(req, res, next) {
+ var errors = {};
+
+ req.body.username = req.body.username.trim(); // Username sanitization
+ req.body.password = req.body.password.trim(); // Password sanitization
+ if (req.body.username === '') {
+ errors.username = "can't be empty";
+ }
+ if (req.body.password === '') {
+ errors.password = "can't be empty";
+ }
+
+ req.session.oldvalues = {username: req.body.username};
+ if (errors.username || errors.password) {
+ req.session.errors = errors;
+ return res.redirect('/login');
+ }
+
+ next();
+};
+
+exports.checkUser = function(req, res, next) {
+ var key = 'user:'+req.body.username;
+ db.exists(key, function(err, data) {
+ if (data === 1) {
+ // User exists, proceed with authentication
+ return next();
+ }
+ req.session.errors = {alert: 'The username you specified does not exists.'};
+ res.redirect('/login');
+ });
+};
+
+exports.authenticate = function(req, res) {
+ var key = 'user:'+req.body.username;
+ db.hmget(key, 'salt', 'password', function(err, data) {
+ var hash = crypto.createHash('sha256').update(data[0]+req.body.password).digest('hex');
+ if (hash === data[1]) {
+ // Authentication succeeded, regenerate the session
+ req.session.regenerate(function() {
+ req.session.cookie.maxAge = 604800000; // One week
+ req.session.user = req.body.username;
+ res.redirect('/');
+ });
+ return;
+ }
+ req.session.errors = {alert: 'The password you specified is not correct.'};
+ res.redirect('/login');
+ });
+};
+
+/**
+ * Logout the user.
+ */
+
+exports.logout = function(req, res) {
+ // Destroy the session
+ req.session.destroy(function() {
+ res.redirect('/');
+ });
+};
+
+/**
+ * Show user profile.
+ */
+
+exports.profile = function(req, res) {
+ var key = 'user:'+req.params[0];
+ db.exists(key, function(err, data) {
+ if (data === 1) {
+ db.hgetall(key, function(e, obj) {
+ obj.username = obj.username.replace(/&/g, '&');
+ obj.bestguesstime = (obj.bestguesstime/1000).toFixed(1);
+ obj.worstguesstime = (obj.worstguesstime/1000).toFixed(1);
+ if (obj.guessed !== '0') {
+ obj.meanguesstime = ((obj.totguesstime/obj.guessed)/1000).toFixed(1);
+ }
+ delete obj.email;
+ delete obj.password;
+ delete obj.salt;
+ delete obj.totguesstime;
+ res.render('user', obj);
+ });
+ return;
+ }
+ res.send(404);
+ });
+};
+++ /dev/null
-var async = require('async');
-var characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
-var Captcha = require('./lib/captcha.js')(characters);
-var config = require('./config.js').configure();
-var crypto = require('crypto');
-var express = require('express');
-var form = require('express-form');
-var parseCookie = require('connect').utils.parseCookie;
-var redisstore = require('connect-redis')(express);
-var rooms = Object.create(null); // The Object that contains all the room instances
-var sockets = Object.create(null); // Sockets of all rooms
-
-// Setting up Redis
-var songsdb = require("redis-url").createClient(config.songsdburl);
-var usersdb = require("redis-url").createClient(config.usersdburl);
-
-songsdb.on('error', function(err) {
- console.log("Error: "+err);
-});
-
-usersdb.on('error', function(err) {
- console.log("Error: "+err);
-});
-
-// Setting up Express
-var sessionstore = new redisstore({client:usersdb});
-var app = express.createServer();
-
-// Configuration
-app.use(express.static(__dirname + '/public'), {maxAge: 2592000000});
-app.use(express.favicon(__dirname + '/public/static/images/favicon.ico', {maxAge: 2592000000}));
-app.use(express.bodyParser());
-app.use(express.cookieParser());
-app.use(express.session({secret:config.sessionsecret,store:sessionstore}));
-app.set("view options", {layout:false});
-app.set('view engine', 'jade');
-
-// Routes
-app.get("/", function(req, res) {
- if (req.session.user) {
- res.local('loggedin', req.session.user.replace(/&/g, "&"));
- }
- res.render("index", {rooms:config.rooms});
-});
-
-app.get("/signup", function(req, res) {
- var captcha = new Captcha();
- req.session.captchacode = captcha.getCode();
- res.render("signup", {captchaurl:captcha.toDataURL()});
-});
-
-// Sign up route middlewares
-var checkCaptcha = function(req, res, next) {
- if (req.form.isValid) {
- if (req.session.captchacode !== req.form.captcha) {
- var errors = {captcha:['no match']};
- var captcha = new Captcha();
- req.session.captchacode = captcha.getCode();
- return res.render("signup", {errors:errors,captchaurl:captcha.toDataURL()});
- }
- next();
- }
- else {
- var captcha = new Captcha();
- req.session.captchacode = captcha.getCode();
- res.render("signup", {errors:req.form.getErrors(),captchaurl:captcha.toDataURL()});
- }
-};
-
-var checkUserExists = function(req, res, next) {
- var userkey = "user:"+req.form.username;
- usersdb.exists(userkey, function(err, data) {
- if (data === 1) { // User already exists
- var errors = {alert: "A user with name "+req.form.username+" already exists."};
- var captcha = new Captcha();
- req.session.captchacode = captcha.getCode();
- return res.render("signup", {errors:errors,captchaurl:captcha.toDataURL()});
- }
- next();
- });
-};
-
-var checkEmailExists = function(req, res, next) {
- var mailkey = "email:"+req.form.email;
- usersdb.exists(mailkey, function(err, data) {
- if (data === 1) { // Email already exists
- var errors = {alert: "A user with that email already exists."};
- var captcha = new Captcha();
- req.session.captchacode = captcha.getCode();
- return res.render("signup", {errors:errors,captchaurl:captcha.toDataURL()});
- }
- next();
- });
-};
-
-app.post("/signup",
- form(
- form.filter("username").trim().required().not(/binb/, "is reserved")
- .is(/^[^\x00-\x1F\x7F]{1,15}$/, "1 to 15 characters required"),
- form.filter("email").required().isEmail("is not an email address"),
- form.filter("password").required()
- .is(/^[A-Za-z0-9]{6,15}$/, "6 to 15 alphanumeric characters required"),
- form.filter("captcha").required()
- ),
- checkCaptcha,
- checkUserExists,
- checkEmailExists,
- function (req, res) { // Set up the account
- var userkey = "user:"+req.form.username;
- var mailkey = "email:"+req.form.email;
- var salt = "";
- while (salt.length < 8) {
- salt += characters[Math.floor(Math.random() * characters.length)];
- }
- var hash = crypto.createHash('sha256').update(salt+req.form.password).digest('hex');
- var date = new Date();
- var joindate = date.getDate()+"/"+(date.getMonth()+1)+"/"+date.getFullYear();
- usersdb.hmset(
- userkey,
- "username", req.form.username,
- "email", req.form.email,
- "password", hash,
- "salt", salt,
- "joindate", joindate,
- "totpoints", 0,
- "bestscore", 0,
- "golds", 0,
- "silvers", 0,
- "bronzes", 0,
- "bestguesstime", 30000,
- "worstguesstime", 0,
- "totguesstime", 0,
- "guessed", 0,
- "victories", 0,
- "secondplaces", 0,
- "thirdplaces", 0
- );
- usersdb.set(mailkey, userkey);
- usersdb.sadd("users", userkey);
- usersdb.sadd("emails", mailkey);
- var msg = "You successfully created your account. You are now ready to login.";
- res.render("login", {success:msg});
- }
-);
-
-app.get("/login", function(req, res) {
- res.render("login");
-});
-
-app.post("/login",
- form(
- form.filter("username").trim().required(),
- form.filter("password").trim().required()
- ),
- function(req, res, next) {
- if (req.form.isValid) {
- usersdb.exists("user:"+req.form.username, function(err, data) {
- if (data === 1) { // User exists
- next();
- }
- else {
- var errors = {alert: "The username you specified does not exists."};
- res.render("login", {errors:errors});
- }
- });
- }
- else {
- res.render("login", {errors:req.form.getErrors()});
- }
- },
- function(req, res) { // Authenticate User
- usersdb.hmget("user:"+req.form.username, "salt", "password", function(err, data) {
- var hash = crypto.createHash('sha256').update(data[0]+req.body.password).digest('hex');
- if (hash === data[1]) {
- req.session.regenerate(function() {
- req.session.cookie.maxAge = 604800000; // One week
- req.session.user = req.form.username;
- res.redirect('/');
- });
- }
- else {
- var errors = {alert: "The password you specified is not correct."};
- res.render("login", {errors:errors});
- }
- });
- }
-);
-
-app.get("/logout", function(req, res) {
- req.session.destroy(function() {
- res.redirect("/");
- });
-});
-
-var makeCallBack = function(genre) {
- return function(callback) {
- songsdb.srandmember(genre, function(err, res) {
- songsdb.hget(res, "artworkUrl100", callback);
- });
- };
-};
-
-app.get("/artworks", function(req, res) {
- var callitems = [];
- for (var i=0; i<config.rooms.length; i++) {
- for (var j=0; j<6; j++) {
- callitems.push(makeCallBack(config.rooms[i]));
- }
- }
- async.parallel(callitems, function(err, results) {
- var obj = {
- resultCount: results.length,
- results: results
- };
- res.writeHead(200, {"Content-Type": "application/json"});
- res.end(JSON.stringify(obj));
- });
-});
-
-app.get("/:room", function(req, res) {
- if (config.rooms.indexOf(req.params.room) !== -1) {
- if (req.session.user) {
- res.local('loggedin', req.session.user.replace(/&/g, "&"));
- }
- res.render("room", {roomname:req.params.room,rooms:config.rooms});
- }
- else {
- res.send(404);
- }
-});
-
-app.get("/user/*", function(req, res) {
- var key = "user:"+req.params[0];
- usersdb.exists(key, function(err, data) {
- if (data === 1) {
- usersdb.hgetall(key, function(e, obj) {
- obj.username = obj.username.replace(/&/g, "&");
- obj.bestguesstime = (obj.bestguesstime/1000).toFixed(1);
- obj.worstguesstime = (obj.worstguesstime/1000).toFixed(1);
- if (obj.guessed !== "0") {
- obj.meanguesstime = ((obj.totguesstime/obj.guessed)/1000).toFixed(1);
- }
- delete obj.email;
- delete obj.password;
- delete obj.salt;
- delete obj.totguesstime;
- res.render("user", obj);
- });
- }
- else {
- res.send(404);
- }
- });
-});
-
-// Starting HTTP server
-app.listen(config.port);
-
-// Setting up Socket.IO
-var io = require("socket.io").listen(app);
-
-io.enable('browser client minification'); // send minified client
-io.enable('browser client etag'); // apply etag caching logic based on version number
-io.enable('browser client gzip'); // gzip the file
-io.set('log level', 1); // reduce logging
-// enable transports
-io.set('transports', ['websocket', 'htmlfile', 'xhr-polling', 'jsonp-polling']);
-
-io.set('authorization', function(data, accept) {
- if(data.headers.cookie) {
- var cookie = parseCookie(data.headers.cookie);
- sessionstore.get(cookie['connect.sid'], function(err, session) {
- if (err || !session) {
- accept('Error', false);
- }
- else {
- data.session = session;
- accept(null, true);
- }
- });
- }
- else {
- return accept('No cookie transmitted.', false);
- }
-});
-
-io.sockets.on('connection', function(socket) {
- var session = socket.handshake.session;
- socket.on('getoverview', function() {
- var data = Object.create(null);
- for (var prop in rooms) {
- data[prop] = rooms[prop].getPopulation();
- }
- socket.join('home');
- socket.emit('overview', data);
- });
- socket.on('loggedin', function(fn) {
- return (session.user) ? fn(session.user) : fn(false);
- });
- socket.on('joinroom', function(data) {
- if (session.user && typeof data === "string" && config.rooms.indexOf(data) !== -1) {
- if (sockets[session.user]) { // User already in a room
- socket.emit('alreadyinaroom');
- return;
- }
- socket.nickname = session.user;
- rooms[data].joinRoom(socket);
- }
- });
- socket.on('joinanonymously', function(data) {
- if (!socket.nickname && typeof data === "object" && typeof data.nickname === "string" &&
- data.nickname !== "" && typeof data.roomname === "string" &&
- config.rooms.indexOf(data.roomname) !== -1) {
- rooms[data.roomname].setNickName(socket, data);
- }
- });
- socket.on('getstatus', function() {
- if (socket.roomname) {
- rooms[socket.roomname].sendStatus(socket);
- }
- });
- socket.on('sendchatmsg', function(data) {
- if (socket.roomname) {
- rooms[socket.roomname].sendChatMessage(socket, data);
- }
- });
- socket.on('guess', function(data) {
- if (socket.roomname && typeof data === "string") {
- rooms[socket.roomname].guess(socket, data);
- }
- });
- socket.on("disconnect", function() {
- if (socket.roomname) {
- rooms[socket.roomname].userLeft(socket.nickname);
- }
- });
-});
-
-// Setting up the rooms
-var roomoptions = {
- songsdb: songsdb,
- usersdb: usersdb,
- io: io,
- sockets: sockets,
- songsinarun: config.songsinarun,
- fifolength: config.fifolength,
- threshold: config.threshold
-};
-
-var Room = require('./lib/room.js')(roomoptions);
-
-for (var i=0; i<config.rooms.length; i++) {
- rooms[config.rooms[i]] = new Room(config.rooms[i]);
- rooms[config.rooms[i]].start();
-}
-
-console.log("binb started and listening on port "+config.port);
footer
- #footer-inner
- #copy © 2012 Luigi Pinca
- iframe#facebook-button(allowTransparency="true", frameborder="0",
- scrolling="no", src="//www.facebook.com/plugins/like.php?href=http%3A%2F%2Fbinb.nodejitsu.com&send=false&layout=button_count&show_faces=false&action=like&colorscheme=light&locale=en_US")
- iframe#twitter-button(allowtransparency="true", frameborder="0",
- scrolling="no", src="//platform.twitter.com/widgets/tweet_button.html?url=http%3A%2F%2Fbinb.nodejitsu.com")
- iframe#github-button(allowtransparency="true", frameborder="0", scrolling="0",
- src="http://markdotto.github.com/github-buttons/github-btn.html?user=lpinca&repo=binb&type=watch&count=true")
- #footer-right Made with
- a(target="_blank", href="http://nodejs.org/") node.js
- | ,
- a(target="_blank", href="http://socket.io/") socket.io
- | ,
- a(target="_blank", href="http://redis.io/") redis.io
- | ,
- a(target="_blank", href="http://nodejitsu.com/") nodejitsu
- | . Optimized for Google Chrome.
+ #footer-inner
+ #copy © 2012 Luigi Pinca
+ iframe#facebook-button(allowTransparency="true", frameborder="0",
+ scrolling="no", src="//www.facebook.com/plugins/like.php?href=http%3A%2F%2Fbinb.nodejitsu.com&send=false&layout=button_count&show_faces=false&action=like&colorscheme=light&locale=en_US")
+ iframe#twitter-button(allowtransparency="true", frameborder="0",
+ scrolling="no", src="//platform.twitter.com/widgets/tweet_button.html?url=http%3A%2F%2Fbinb.nodejitsu.com")
+ iframe#github-button(allowtransparency="true", frameborder="0", scrolling="0",
+ src="http://markdotto.github.com/github-buttons/github-btn.html?user=lpinca&repo=binb&type=watch&count=true")
+ #footer-right Made with
+ a(target="_blank", href="http://nodejs.org/") node.js
+ | ,
+ a(target="_blank", href="http://socket.io/") socket.io
+ | ,
+ a(target="_blank", href="http://redis.io/") redis.io
+ | ,
+ a(target="_blank", href="http://nodejitsu.com/") nodejitsu
+ | . Optimized for Google Chrome.
mottos = ['guess the song.', 'name that tune.', 'i know this track.']
motto = mottos[Math.floor(Math.random()*mottos.length)]
head
- link(href="/static/css/bootstrap.min.css", rel="stylesheet")
- link(href="/static/css/style.css", rel="stylesheet")
- meta(charset="UTF-8")
- meta(name="keywords", content="iTunes, music, quiz, binb, beatquest, realtime, multiplayer, listening, game")
- meta(name="description", content="Simple, realtime, multiplayer, competitive music listening game. Guess the song and prove your music knowledge!")
- meta(name="author", content="Luigi Pinca")
- script(src="/static/js/jquery.min.js")
- script
- var _gaq = _gaq || [];
- _gaq.push(['_setAccount', 'UA-29865853-1']);
- _gaq.push(['_trackPageview']);
- (function() {
- var ga = document.createElement('script');
- ga.type = 'text/javascript'; ga.async = true;
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') +
- '.google-analytics.com/ga.js';
- var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
- })();
+ link(href="/static/css/bootstrap.min.css", rel="stylesheet")
+ link(href="/static/css/style.css", rel="stylesheet")
+ meta(charset="UTF-8")
+ meta(name="keywords", content="iTunes, music, quiz, binb, beatquest, realtime, multiplayer, listening, game")
+ meta(name="description", content="Simple, realtime, multiplayer, competitive music listening game. Guess the song and prove your music knowledge!")
+ meta(name="author", content="Luigi Pinca")
+ script(src="/static/js/jquery.min.js")
+ script
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-29865853-1']);
+ _gaq.push(['_trackPageview']);
+ (function() {
+ var ga = document.createElement('script');
+ ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') +
+ '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
doctype html
html
- include header
- title binb
- script(src="/socket.io/socket.io.js")
- script(src="/static/js/bootstrap.min.js")
- script(src="/static/js/home.js")
- body
- include uv.jade
- .navbar.navbar-fixed-top
- .navbar-inner
- .container
- a.brand(href="/")
- .logo #{motto}
- ul.nav.pull-right
- li.active
- a(href="/") Home
- if (typeof(loggedin) !== "undefined")
- li
- p.navbar-text Logged in as
- a#loggedin(href="/user/#{loggedin}",target="_blank") #{loggedin}
- li
- a(href="/logout") Logout
- else
- li
- a(href="/signup") Sign up
- li
- a(href="/login") Login
- .container
- section
- .row
- .span7
- h3 What's this?
- p binb is a realtime, multiplayer, competitive music listening game.
- .span9
- h3 How to play?
- p All you have to do is to guess the song that is playing. A fixed number
- | of songs will run and for each one correctly guessed you will earn an
- | amount of points. That amount depends on the number of correct guesses
- | (artist and/or title of the song) and on how fast you will be on
- | answering compared to other players. At the end a scoreboard will
- | report the best three players of the match. If you have read this
- | far, what are you waiting? Click on a room below and prove your
- | music knowledge!
- section
- .row
- .span16
- ul.thumbnails
- each item in rooms
- li.span4
- a.thumbnail.relative(href="/#{item}")
- .room #{item} - <span id="#{item}"></span> Players
- include footer
+ include header
+ title binb
+ script(src="/socket.io/socket.io.js")
+ script(src="/static/js/bootstrap.min.js")
+ script(src="/static/js/home.js")
+ body
+ include uv.jade
+ .navbar.navbar-fixed-top
+ .navbar-inner
+ .container
+ a.brand(href="/")
+ .logo #{motto}
+ ul.nav.pull-right
+ li.active
+ a(href="/") Home
+ if (typeof(loggedin) !== "undefined")
+ li
+ p.navbar-text Logged in as
+ a#loggedin(href="/user/#{loggedin}",target="_blank") #{loggedin}
+ li
+ a(href="/logout") Logout
+ else
+ li
+ a(href="/signup") Sign up
+ li
+ a(href="/login") Login
+ .container
+ section
+ .row
+ .span7
+ h3 What's this?
+ p binb is a realtime, multiplayer, competitive music listening game.
+ .span9
+ h3 How to play?
+ p All you have to do is to guess the song that is playing. A fixed number
+ | of songs will run and for each one correctly guessed you will earn an
+ | amount of points. That amount depends on the number of correct guesses
+ | (artist and/or title of the song) and on how fast you will be on
+ | answering compared to other players. At the end a scoreboard will
+ | report the best three players of the match. If you have read this
+ | far, what are you waiting? Click on a room below and prove your
+ | music knowledge!
+ section
+ .row
+ .span16
+ ul.thumbnails
+ each item in rooms
+ li.span4
+ a.thumbnail.relative(href="/#{item}")
+ .room #{item} - <span id="#{item}"></span> Players
+ include footer
doctype html
-html
- include header
- title binb :: login
- script(src="/static/js/bootstrap.min.js")
- body
- include uv.jade
- .navbar.navbar-fixed-top
- .navbar-inner
- .container
- a.brand(href="/")
- .logo #{motto}
- ul.nav.pull-right
- li
- a(href="/") Home
- li
- a(href="/signup") Sign up
- li.active
- a(href="/login") Login
- .container
- section
- .row
- .span3
- h3 New user?
- a(href="/signup") Click here to create an account.
- .span13
- if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
- .alert.alert-error
- a.close(data-dismiss="alert") ×
- strong Oh snap!
- | #{errors.alert}
- else if (typeof(success) !== "undefined")
- .alert.alert-success
- a.close(data-dismiss="alert") ×
- strong Well done!
- | #{success}
- form.form-horizontal.well(method="post",action="/login")
- fieldset
- if (typeof(errors) !== "undefined")
- if (typeof(errors.username) !== "undefined")
- .control-group.error
- label.control-label(for="username") Name
- .controls
- input#username(type="text",name="username",
- value="#{username}")
- span.help-inline #{errors.username[0]}
- else
- .control-group
- label.control-label(for="username") Name
- .controls
- input#username(type="text",name="username",
- value="#{username}")
- if (typeof(errors.password) !== 'undefined')
- .control-group.error
- label.control-label(for="password") Password
- .controls
- input#password(type="password",name="password")
- span.help-inline #{errors.password[0]}
- else
- .control-group
- label.control-label(for="password") Password
- .controls
- input#password(type="password",name="password")
- else
- .control-group
- label.control-label(for="username") Name
- .controls
- input#username(type="text",name="username",
- placeholder="enter your nickname...")
- .control-group
- label.control-label(for="password") Password
- .controls
- input#password(type="password",name="password",
- placeholder="enter your password...")
- button#signup-button.btn.btn-primary(type="submit")
- i.icon-user.icon-white
- | Login
- include footer
+html
+ include header
+ title binb :: login
+ script(src="/static/js/bootstrap.min.js")
+ body
+ include uv.jade
+ .navbar.navbar-fixed-top
+ .navbar-inner
+ .container
+ a.brand(href="/")
+ .logo #{motto}
+ ul.nav.pull-right
+ li
+ a(href="/") Home
+ li
+ a(href="/signup") Sign up
+ li.active
+ a(href="/login") Login
+ .container
+ section
+ .row
+ .span3
+ h3 New user?
+ a(href="/signup") Click here to create an account.
+ .span13
+ if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
+ .alert.alert-error
+ a.close(data-dismiss="alert") ×
+ strong Oh snap!
+ | #{errors.alert}
+ else if (typeof(success) !== "undefined")
+ .alert.alert-success
+ a.close(data-dismiss="alert") ×
+ strong Well done!
+ | #{success}
+ form.form-horizontal.well(method="post",action="/login")
+ fieldset
+ if (typeof(errors) !== "undefined")
+ if (typeof(errors.username) !== "undefined")
+ .control-group.error
+ label.control-label(for="username") Name
+ .controls
+ input#username(type="text",name="username",
+ value="#{oldvalues.username}")
+ span.help-inline #{errors.username}
+ else
+ .control-group
+ label.control-label(for="username") Name
+ .controls
+ input#username(type="text",name="username",
+ value="#{oldvalues.username}")
+ if (typeof(errors.password) !== 'undefined')
+ .control-group.error
+ label.control-label(for="password") Password
+ .controls
+ input#password(type="password",name="password")
+ span.help-inline #{errors.password}
+ else
+ .control-group
+ label.control-label(for="password") Password
+ .controls
+ input#password(type="password",name="password",
+ placeholder="enter your password...")
+ else
+ .control-group
+ label.control-label(for="username") Name
+ .controls
+ input#username(type="text",name="username",
+ placeholder="enter your nickname...")
+ .control-group
+ label.control-label(for="password") Password
+ .controls
+ input#password(type="password",name="password",
+ placeholder="enter your password...")
+ button#signup-button.btn.btn-primary(type="submit")
+ i.icon-user.icon-white
+ | Login
+ include footer
doctype html
html
- include header
- title binb :: #{roomname}
- script(src="/socket.io/socket.io.js")
- script(src="/static/js/bootstrap.min.js")
- script(src="/static/js/jquery.jplayer.min.js")
- script(type='text/javascript')
- var roomname = "#{roomname}";
- script(src="/static/js/room.js")
- body
- include uv.jade
- .navbar.navbar-fixed-top
- .navbar-inner
- .container
- ul.nav.pull-right
- li
- a(href="/") Home
- li.active.dropdown
- a.dropdown-toggle(data-toggle="dropdown",href="#") #{roomname}
- b.caret
- ul.dropdown-menu
- each item in rooms
- if item != roomname
- li
- a(href="/#{item}") #{item}
- if (typeof(loggedin) !== "undefined")
- li
- p.navbar-text Logged in as
- a#loggedin(href="/user/#{loggedin}",target="_blank") #{loggedin}
- li
- a(href="/logout") Logout
- else
- li
- a(href="/signup") Sign up
- li
- a(href="/login") Login
- #player
- #modal.modal.fade
- .container
- section
- .row
- .span4.offset1
- #cassette.relative
- #wheel-left.wheel
- #tape-left
- #tape-right
- #wheel-right.wheel
- #progress-bar
- #progress
- #countdown
- .span2
- #volume.relative
- .span8
- .page-header
- .logo #{motto}
- #total-tracks <span></span> tracks.
- #summary.row
- .span2
- .title Rank
- .rank
- .span4
- .title Points
- .points
- .span2
- .title Track
- .track
- p#feedback Waiting for connection...
- input#guess.span8(type="text",tabindex="1",
- placeholder="guess the artist and/or title here")
- section.relative
- .row
- #users-wrapper.span5.offset2
- ul#users.unstyled
- .span8
- a#toggle-chat Hide chat
- #chat-outer-wrapper
- #chat-wrapper.bordered
- ul#chat.unstyled
- #message-wrapper
- span#recipient
- input#message.span8(type="text",tabindex="2")
- ul#tracks.unstyled
- #disclaimer
- div I do not own any right on the songs that are played here.
- div Tracks are played using iTunes api preview.
- div Original idea from
- a(target="_blank", href="http://beatquest.fm/") beatquest.fm
- |.
- include footer
+ include header
+ title binb :: #{roomname}
+ script(src="/socket.io/socket.io.js")
+ script(src="/static/js/bootstrap.min.js")
+ script(src="/static/js/jquery.jplayer.min.js")
+ script(type='text/javascript')
+ var roomname = "#{roomname}";
+ script(src="/static/js/room.js")
+ body
+ include uv.jade
+ .navbar.navbar-fixed-top
+ .navbar-inner
+ .container
+ ul.nav.pull-right
+ li
+ a(href="/") Home
+ li.active.dropdown
+ a.dropdown-toggle(data-toggle="dropdown",href="#") #{roomname}
+ b.caret
+ ul.dropdown-menu
+ each item in rooms
+ if item != roomname
+ li
+ a(href="/#{item}") #{item}
+ if (typeof(loggedin) !== "undefined")
+ li
+ p.navbar-text Logged in as
+ a#loggedin(href="/user/#{loggedin}",target="_blank") #{loggedin}
+ li
+ a(href="/logout") Logout
+ else
+ li
+ a(href="/signup") Sign up
+ li
+ a(href="/login") Login
+ #player
+ #modal.modal.fade
+ .container
+ section
+ .row
+ .span4.offset1
+ #cassette.relative
+ #wheel-left.wheel
+ #tape-left
+ #tape-right
+ #wheel-right.wheel
+ #progress-bar
+ #progress
+ #countdown
+ .span2
+ #volume.relative
+ .span8
+ .page-header
+ .logo #{motto}
+ #total-tracks <span></span> tracks.
+ #summary.row
+ .span2
+ .title Rank
+ .rank
+ .span4
+ .title Points
+ .points
+ .span2
+ .title Track
+ .track
+ p#feedback Waiting for connection...
+ input#guess.span8(type="text",tabindex="1",
+ placeholder="guess the artist and/or title here")
+ section.relative
+ .row
+ #users-wrapper.span5.offset2
+ ul#users.unstyled
+ .span8
+ a#toggle-chat Hide chat
+ #chat-outer-wrapper
+ #chat-wrapper.bordered
+ ul#chat.unstyled
+ #message-wrapper
+ span#recipient
+ input#message.span8(type="text",tabindex="2")
+ ul#tracks.unstyled
+ #disclaimer
+ div I do not own any right on the songs that are played here.
+ div Tracks are played using iTunes api preview.
+ div Original idea from
+ a(target="_blank", href="http://beatquest.fm/") beatquest.fm
+ |.
+ include footer
doctype html
-html
- include header
- title binb :: sign up
- script(src="/static/js/bootstrap.min.js")
- body
- include uv.jade
- .navbar.navbar-fixed-top
- .navbar-inner
- .container
- a.brand(href="/")
- .logo #{motto}
- ul.nav.pull-right
- li
- a(href="/") Home
- li.active
- a(href="/signup") Sign up
- li
- a(href="/login") Login
- .container
- section
- .row
- .span3
- h3 Not a new user?
- a(href="/login") Click here to log in.
- .span13
- h3 Why sign up?
- p Registration is optional, but if you are a regular user consider creating
- | an account. This will provide the following advantages:
- ul
- li You will be the one and only owner of your nickname (no one can use
- | your nickname in your place).
- li Some simple stats related to your account will be collected.
- if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
- .alert.alert-error
- a.close(data-dismiss="alert") ×
- strong Oh snap!
- | #{errors.alert}
- form.form-horizontal.well(method="post",action="/signup")
- fieldset
- if (typeof(errors) !== "undefined")
- if (typeof(errors.username) !== "undefined")
- .control-group.error
- label.control-label(for="username") Name
- .controls
- input#username(type="text",name="username",
- value="#{username}")
- span.help-inline #{errors.username[0]}
- else
- .control-group
- label.control-label(for="username") Name
- .controls
- input#username(type="text",name="username",
- value="#{username}")
- if (typeof(errors.email) !== 'undefined')
- .control-group.error
- label.control-label(for="email") Email
- .controls
- input#email(type="text",name="email",
- value="#{email}")
- span.help-inline #{errors.email[0]}
- else
- .control-group
- label.control-label(for="email") Email
- .controls
- input#email(type="text",name="email",
- value="#{email}")
- if (typeof(errors.password) !== 'undefined')
- .control-group.error
- label.control-label(for="password") Password
- .controls
- input#password(type="password",name="password")
- span.help-inline #{errors.password[0]}
- else
- .control-group
- label.control-label(for="password") Password
- .controls
- input#password(type="password",name="password")
- if (typeof(errors.captcha) !== 'undefined')
- .control-group.error
- label.control-label(for="captcha-input") Are you human?
- .controls
- img#captcha(src="#{captchaurl}")
- input#captcha-input(type="text",name="captcha")
- span.help-inline #{errors.captcha[0]}
- else
- .control-group
- label.control-label(for="captcha-input") Are you human?
- .controls
- img#captcha(src="#{captchaurl}")
- input#captcha-input(type="text",name="captcha")
- else
- .control-group
- label.control-label(for="username") Name
- .controls
- input#username(type="text",name="username",
- placeholder="enter a nickname...")
- .control-group
- label.control-label(for="email") Email
- .controls
- input#email(type="text",name="email",
- placeholder="enter a valid email...")
- .control-group
- label.control-label(for="password") Password
- .controls
- input#password(type="password",name="password",
- placeholder="enter a password...")
- .control-group
- label.control-label(for="captcha-input") Are you human?
- .controls
- img#captcha(src="#{captchaurl}")
- input#captcha-input(type="text",name="captcha",
- placeholder="type what you see...")
- button#signup-button.btn.btn-success(type="submit")
- i.icon-user.icon-white
- | Sign up!
- include footer
+html
+ include header
+ title binb :: sign up
+ script(src="/static/js/bootstrap.min.js")
+ body
+ include uv.jade
+ .navbar.navbar-fixed-top
+ .navbar-inner
+ .container
+ a.brand(href="/")
+ .logo #{motto}
+ ul.nav.pull-right
+ li
+ a(href="/") Home
+ li.active
+ a(href="/signup") Sign up
+ li
+ a(href="/login") Login
+ .container
+ section
+ .row
+ .span3
+ h3 Not a new user?
+ a(href="/login") Click here to log in.
+ .span13
+ h3 Why sign up?
+ p Registration is optional, but if you are a regular user consider creating
+ | an account. This will provide the following advantages:
+ ul
+ li You will be the one and only owner of your nickname (no one can use
+ | your nickname in your place).
+ li Some simple stats related to your account will be collected.
+ if ((typeof(errors) !== "undefined") && (typeof(errors.alert) !== "undefined"))
+ .alert.alert-error
+ a.close(data-dismiss="alert") ×
+ strong Oh snap!
+ | #{errors.alert}
+ form.form-horizontal.well(method="post",action="/signup")
+ fieldset
+ if (typeof(errors) !== "undefined")
+ if (typeof(errors.username) !== "undefined")
+ .control-group.error
+ label.control-label(for="username") Name
+ .controls
+ input#username(type="text",name="username",
+ value="#{oldvalues.username}")
+ span.help-inline #{errors.username}
+ else
+ .control-group
+ label.control-label(for="username") Name
+ .controls
+ input#username(type="text",name="username",
+ value="#{oldvalues.username}")
+ if (typeof(errors.email) !== 'undefined')
+ .control-group.error
+ label.control-label(for="email") Email
+ .controls
+ input#email(type="text",name="email",
+ value="#{oldvalues.email}")
+ span.help-inline #{errors.email}
+ else
+ .control-group
+ label.control-label(for="email") Email
+ .controls
+ input#email(type="text",name="email",
+ value="#{oldvalues.email}")
+ if (typeof(errors.password) !== 'undefined')
+ .control-group.error
+ label.control-label(for="password") Password
+ .controls
+ input#password(type="password",name="password")
+ span.help-inline #{errors.password}
+ else
+ .control-group
+ label.control-label(for="password") Password
+ .controls
+ input#password(type="password",name="password",
+ placeholder="enter a password...")
+ if (typeof(errors.captcha) !== 'undefined')
+ .control-group.error
+ label.control-label(for="captcha-input") Are you human?
+ .controls
+ img#captcha(src="#{captchaurl}")
+ input#captcha-input(type="text",name="captcha")
+ span.help-inline #{errors.captcha}
+ else
+ .control-group
+ label.control-label(for="captcha-input") Are you human?
+ .controls
+ img#captcha(src="#{captchaurl}")
+ input#captcha-input(type="text",name="captcha",
+ placeholder="type what you see...")
+ else
+ .control-group
+ label.control-label(for="username") Name
+ .controls
+ input#username(type="text",name="username",
+ placeholder="enter a nickname...")
+ .control-group
+ label.control-label(for="email") Email
+ .controls
+ input#email(type="text",name="email",
+ placeholder="enter a valid email...")
+ .control-group
+ label.control-label(for="password") Password
+ .controls
+ input#password(type="password",name="password",
+ placeholder="enter a password...")
+ .control-group
+ label.control-label(for="captcha-input") Are you human?
+ .controls
+ img#captcha(src="#{captchaurl}")
+ input#captcha-input(type="text",name="captcha",
+ placeholder="type what you see...")
+ button#signup-button.btn.btn-success(type="submit")
+ i.icon-user.icon-white
+ | Sign up!
+ include footer
doctype html
html
- include header
- title binb :: #{username} info
- body
- include uv.jade
- .navbar.navbar-fixed-top
- .navbar-inner
- .container
- a.brand(href="#")
- .logo #{motto}
- .container
- section
- .row
- .span7.offset1
- .profile #{username}
- .img
- div member since #{joindate}
- section
- .row
- .span7.offset1
- h4 Points
- table.table.table-striped.table-bordered.stats
- tbody
- tr
- td Total
- td #{totpoints}
- tr
- td Best score
- td #{bestscore}
- tr
- td Guessed songs
- td #{guessed}
- h4 Times
- table.table.table-striped.table-bordered.stats
- tbody
- tr
- td Best guess time
- if (bestguesstime !== "30.0")
- td #{bestguesstime} sec
- else
- td N/A
- tr
- td Worst guess time
- if (worstguesstime !== "0.0")
- td #{worstguesstime} sec
- else
- td N/A
- tr
- td Mean guess time
- if (typeof meanguesstime !== "undefined")
- td #{meanguesstime} sec
- else
- td N/A
- .span7
- h4 Awards
- table.table.table-striped.table-bordered.stats
- tbody
- tr
- td Gold cups
- td
- .cups.stand1
- td #{golds}
- tr
- td Silver cups
- td
- .cups.stand2
- td #{silvers}
- tr
- td Bronze cups
- td
- .cups.stand3
- td #{bronzes}
- tr
- td Victories
- td
- .medals.rank1
- td #{victories}
- tr
- td Second places
- td
- .medals.rank2
- td #{secondplaces}
- tr
- td Third places
- td
- .medals.rank3
- td #{thirdplaces}
- include footer
+ include header
+ title binb :: #{username} info
+ body
+ include uv.jade
+ .navbar.navbar-fixed-top
+ .navbar-inner
+ .container
+ a.brand(href="#")
+ .logo #{motto}
+ .container
+ section
+ .row
+ .span7.offset1
+ .profile #{username}
+ .img
+ div member since #{joindate}
+ section
+ .row
+ .span7.offset1
+ h4 Points
+ table.table.table-striped.table-bordered.stats
+ tbody
+ tr
+ td Total
+ td #{totpoints}
+ tr
+ td Best score
+ td #{bestscore}
+ tr
+ td Guessed songs
+ td #{guessed}
+ h4 Times
+ table.table.table-striped.table-bordered.stats
+ tbody
+ tr
+ td Best guess time
+ if (bestguesstime !== "30.0")
+ td #{bestguesstime} sec
+ else
+ td N/A
+ tr
+ td Worst guess time
+ if (worstguesstime !== "0.0")
+ td #{worstguesstime} sec
+ else
+ td N/A
+ tr
+ td Mean guess time
+ if (typeof meanguesstime !== "undefined")
+ td #{meanguesstime} sec
+ else
+ td N/A
+ .span7
+ h4 Awards
+ table.table.table-striped.table-bordered.stats
+ tbody
+ tr
+ td Gold cups
+ td
+ .cups.stand1
+ td #{golds}
+ tr
+ td Silver cups
+ td
+ .cups.stand2
+ td #{silvers}
+ tr
+ td Bronze cups
+ td
+ .cups.stand3
+ td #{bronzes}
+ tr
+ td Victories
+ td
+ .medals.rank1
+ td #{victories}
+ tr
+ td Second places
+ td
+ .medals.rank2
+ td #{secondplaces}
+ tr
+ td Third places
+ td
+ .medals.rank3
+ td #{thirdplaces}
+ include footer
script
- var uvOptions = {};
- (function() {
- var uv = document.createElement('script');
- uv.type = 'text/javascript';
- uv.async = true;
- uv.src = ('https:' == document.location.protocol ? 'https://' : 'http://') +
- 'widget.uservoice.com/LSMjFAQRifhD6BjOG2KWw.js';
- var s = document.getElementsByTagName('script')[0];
- s.parentNode.insertBefore(uv, s);
- })();
+ var uvOptions = {};
+ (function() {
+ var uv = document.createElement('script');
+ uv.type = 'text/javascript';
+ uv.async = true;
+ uv.src = ('https:' == document.location.protocol ? 'https://' : 'http://') +
+ 'widget.uservoice.com/LSMjFAQRifhD6BjOG2KWw.js';
+ var s = document.getElementsByTagName('script')[0];
+ s.parentNode.insertBefore(uv, s);
+ })();