/* Function Reference ================== loadLevel() - loads up the level fireRule() - fires the css rule updateProgressUI() - adds a checkmark to the level menu and header when a correct guess is made, removes it if incorrect hideTooltip() - hides markup tooltip that hovers over the elements showHelp() - Loads help text & examples for each level ..to be continued! */ var level; // Holds current level info var currentLevel = parseInt(localStorage.currentLevel,10) || 0; // Keeps track of the current level Number (0 is level 1) var levelTimeout = 1000; // Delay between levels after completing var finished = false; // Keeps track if the game is showing the Your Rock! screen (so that tooltips can be disabled) var blankProgress = { totalCorrect : 0, percentComplete : 0, lastPercentEvent : 0, guessHistory : {} } // Get progress from localStorage, or start from scratch if we don't have any var progress = JSON.parse(localStorage.getItem("progress")) || blankProgress; $(document).ready(function(){ $(".share-menu").on("click","a",function(){ var type = $(this).attr("type"); if(type == "twitter"){ var url = "https://twitter.com/intent/tweet?text=Learning%20CSS?%20Try%20CSS%20Diner,%20the%20fun%20way%20to%20practice%20selectors%20%E2%86%92&hashtags=css,cssdiner,webdev&url=http%3A%2F%2Fcssdiner.com%2F&via=flukeout"; } else if (type == "facebook") { var url = "https://www.facebook.com/sharer.php?src=sp&u=http%3A%2F%2Fcssdiner.com"; } else if (type == "email") { var url = "mailto:?subject=Check+out+CSS+Diner&body=It's+a+fun+game+to+learn+%26+practice+CSS+selectors.%0D%0A%0D%0AYou+can+try+it+at+http://cssdiner.com"; } PopupCenter(url, "title", 600, 450); sendEvent("share", type, ""); return false; }); $(window).on("keydown",function(e){ if(e.keyCode == 27) { closeMenu(); } }); // Custom scrollbar plugin $(".left-col, .level-menu").mCustomScrollbar({ scrollInertia: 0, autoHideScrollbar: true }); $(".note-toggle").on("click", function(){ $(this).hide(); $(".note").slideToggle(); }); $(".level-menu-toggle-wrapper").on("click",function(){ if($(".menu-open").length == 0) { openMenu(); } else { closeMenu(); } }); $(".level-nav").on("click","a",function(){ var direction; if($(this).hasClass("next")) { direction = "next"; } addAnimation($(this),"link-jiggle"); if(direction == "next") { currentLevel++; if(currentLevel >= levels.length) { currentLevel = levels.length - 1; } } else { currentLevel--; if(currentLevel < 0) { currentLevel = 0; } } loadLevel(); return false; }); // Resets progress and progress indicators $(".reset-progress").on("click",function(){ resetProgress(); return false; }) //Handle inputs from the input box on enter $("input").on("keypress",function(e){ e.stopPropagation(); if(e.keyCode == 13){ enterHit(); return false; } }); $("input").on("keyup",function(e){ e.stopPropagation(); var length = $(this).val().length; if(length > 0) { $("input").removeClass("input-strobe"); } else { $("input").addClass("input-strobe"); } }); $(".editor").on("click",function(){ $("input").focus(); }); //Add tooltips $(".table").on("mouseover","*",function(e){ e.stopPropagation(); showTooltip($(this)); }); //Shows the tooltip on the table $(".markup").on("mouseover","*",function(e){ el = $(this); var markupElements = $(".markup *"); var index = markupElements.index(el); showTooltip($(".table *").eq(index)); e.stopPropagation(); }); // Shows the tooltip on the table $(".markup").on("mouseout","*",function(e){ e.stopPropagation(); hideTooltip(); }); $(".table").on("mouseout","*", function(e){ hideTooltip(); e.stopPropagation(); }); $(".enter-button").on("click",function(){ enterHit(); }) $(".table-wrapper,.table-edge").css("opacity",0); buildLevelmenu(); setTimeout(function(){ loadLevel(); $(".table-wrapper,.table-edge").css("opacity",1); },50); }); function addAnimation(el, className){ el.addClass("link-jiggle"); el.one("animationend",function(e){ $(e.target).removeClass("link-jiggle"); }) } // Reset all progress // * Removes checkmarks from level header and list // * Scrolls level menu to top // * Resets the progress object function resetProgress(){ currentLevel = 0; progress = blankProgress; localStorage.setItem("progress",JSON.stringify(progress)); finished = false; $(".completed").removeClass("completed"); loadLevel(); closeMenu(); $("#mCSB_2_container").css("top",0); // Strange element to reset scroll due to scroll plugin } //Checks if the level is completed function checkCompleted(levelNumber){ if(progress.guessHistory[levelNumber]){ if(progress.guessHistory[levelNumber].correct){ return true; } else { return false; } } else { return false; } } // Builds the slide-out level menu function buildLevelmenu(){ for(var i = 0; i < levels.length; i++){ var level = levels[i]; var item = document.createElement("a"); $(item).html("" + (i+1) + "" + level.syntax); $(".level-menu .levels").append(item); if(checkCompleted(i)){ $(item).addClass("completed"); } $(item).on("click",function(){ finished = false; currentLevel = $(this).index(); loadLevel(); closeMenu(); }); } } function closeMenu(){ $(".right-col").removeClass("menu-open"); } function openMenu(){ $(".right-col").addClass("menu-open"); } // Hides & shows the tooltip that appears when an eleemnt // on the table or the editor is hovered over. function hideTooltip(){ $(".enhance").removeClass("enhance"); $("[data-hovered]").removeAttr("data-hovered"); $(".helper").hide(); } function showTooltip(el){ if(finished){ return; // Only show tooltip if the game isn't finished yet } el.attr("data-hovered",true); var tableElements = $(".table *"); var index = tableElements.index(el); var that = el; $(".markup *").eq(index).addClass("enhance").find("*").addClass("enhance"); var helper = $(".helper"); var pos = el.offset(); helper.css("top",pos.top - 65); helper.css("left",pos.left + (el.width()/2)); var helpertext; var elType = el.get(0).tagName; elType = elType.toLowerCase(); helpertext = '<' + elType; var elClass = el.attr("class"); if(elClass) { if(elClass.indexOf("strobe") > -1){ elClass = elClass.replace("strobe",""); } } if(elClass) { helpertext = helpertext + ' class="' + elClass + '"'; } var elFor = el.attr("for"); if(elFor) { helpertext = helpertext + ' for="' + elFor + '"'; } var id = el.attr("id"); if(id) { helpertext = helpertext + ' id="' + id + '"'; } helpertext = helpertext + '>'; helper.show(); helper.text(helpertext); } //Animate the enter button function enterHit(){ $(".enter-button").removeClass("enterhit"); $(".enter-button").width($(".enter-button").width()); $(".enter-button").addClass("enterhit"); var value = $("input").val(); handleInput(value); } //Parses text from the input field function handleInput(text){ if(parseInt(text,10) > 0 && parseInt(text,10) < levels.length+1) { currentLevel = parseInt(text,10) - 1; loadLevel(); return; } fireRule(text); } // Loads up the help text & examples for each level function showHelp() { var helpTitle = level.helpTitle || ""; var help = level.help || ""; var examples = level.examples ||[]; var selector = level.selector || ""; var syntax = level.syntax || ""; var syntaxExample = level.syntaxExample || ""; var selectorName = level.selectorName || ""; $(".display-help .syntax").html(syntax); $(".display-help .syntax-example").html(syntaxExample); $(".display-help .selector-name").html(selectorName); $(".display-help .title").html(helpTitle); $(".display-help .examples").html(""); $(".display-help .examples-title").hide(); // Hide the "Examples" heading for(var i = 0; i < examples.length; i++){ var example = $("
" + examples[i] + "
"); $(".display-help .examples").append(example); $(".display-help .examples-title").show(); // Show it if there are examples } $(".display-help .hint").html(help); $(".display-help .selector").text(selector); } function resetTable(){ $(".display-help").removeClass("open-help"); $(".clean,.strobe").removeClass("clean,strobe"); $(".clean,.strobe").removeClass("clean,strobe"); $("input").addClass("input-strobe"); $(".table *").each(function(){ $(this).width($(this).width()); // $(this).removeAttr("style"); // TODO - needed?? Probably not, everything gets removed anyway }); var tableWidth = $(".table").outerWidth(); $(".table-wrapper, .table-edge").width(tableWidth); } function fireRule(rule) { // prevent cheating if(rule === ".strobe") { rule = null; } $(".shake").removeClass("shake"); $(".strobe,.clean,.shake").each(function(){ $(this).width($(this).width()); $(this).removeAttr("style"); }); /* * Sean Nessworthy * On 03/17/14 * * Allow [div][.table] to preceed the answer. * Makes sense if div.table is going to be included in the HTML viewer * and users want to try and use it in their selectors. * * However, if it is included as a specific match, filter it out. * This resolves the "Match all the things!" level from beheading the table too. * Relatedly, watching that happen made me nearly spill my drink. */ // var baseTable = $('.table-wrapper > .table, .table-wrapper > .nametags, .table-wrapper > .table-surface'); var baseTable = $('.table'); // Check if jQuery will throw an error trying the mystery rule // If it errors out, change the rule to null so the wrong-guess animation will work try { $(".table").find(rule).not(baseTable); } catch(err) { rule = null; } var ruleSelected = $(".table").find(rule).not(baseTable); // What the correct rule finds var levelSelected = $(".table").find(level.selector).not(baseTable); // What the person finds var win = false; // If nothing is selected if(ruleSelected.length == 0) { $(".editor").addClass("shake"); } if(ruleSelected.length == levelSelected.length && ruleSelected.length > 0){ win = checkResults(ruleSelected,levelSelected,rule); } if(win){ ruleSelected.removeClass("strobe"); ruleSelected.addClass("clean"); $("input").val(""); $(".input-wrapper").css("opacity",.2); updateProgressUI(currentLevel, true); currentLevel++; if(currentLevel >= levels.length) { winGame(); } else { setTimeout(function(){ loadLevel(); },levelTimeout); } } else { ruleSelected.removeClass("strobe"); ruleSelected.addClass("shake"); setTimeout(function(){ $(".shake").removeClass("shake"); $(".strobe").removeClass("strobe"); levelSelected.addClass("strobe"); },500); $(".result").fadeOut(); } // If answer is correct, let's track progress if(win){ trackProgress(currentLevel-1, "correct"); } else { trackProgress(currentLevel, "incorrect"); } } // Marks an individual number as completed or incompleted // Just in the level heading though, not the level list function updateProgressUI(levelNumber, completed){ if(completed) { $(".levels a:nth-child("+ (levelNumber+1) + ")").addClass("completed"); $(".level-header").addClass("completed"); } else { $(".level-header").removeClass("completed"); } } function trackProgress(levelNumber, type){ if(!progress.guessHistory[levelNumber]) { progress.guessHistory[levelNumber] = { correct : false, incorrectCount : 0, gaSent : false }; } var levelStats = progress.guessHistory[levelNumber]; if(type == "incorrect"){ if(levelStats.correct == false) { levelStats.incorrectCount++; // Only update the incorrect count until it is guessed correctly } } else { if(levelStats.correct == false) { levelStats.correct = true; progress.totalCorrect++; progress.percentComplete = progress.totalCorrect / levels.length; levelStats.gaSent = true; sendEvent("guess", "correct", levelNumber + 1); // Send event } } // Increments the completion percentage by 10%, and sends an event every time var increment = .1; if(progress.percentComplete >= progress.lastPercentEvent + increment) { progress.lastPercentEvent = progress.lastPercentEvent + increment; sendEvent("progress","percent", Math.round(progress.lastPercentEvent * 100)); } localStorage.setItem("progress",JSON.stringify(progress)); } // Sends event to Google Analytics // Doesn't send events if we're on localhost, as the ga variable is set to false function sendEvent(category, action, label){ if(!ga){ return; } ga('send', { hitType: "event", eventCategory: category, // guess or progress eventAction: action, // action (correct vs not..) eventLabel: label // level number }); } function winGame(){ $(".table").html('You did it!
You rock at CSS.
'); addNametags(); finished = true; resetTable(); } function checkResults(ruleSelected,levelSelected,rule){ var ruleTable = $(".table").clone(); ruleTable.find(".strobe").removeClass("strobe"); ruleTable.find(rule).addClass("strobe"); return($(".table").html() == ruleTable.html()); } // Returns all formatted markup within an element... function getMarkup(el){ var hasChildren = el.children.length > 0 ? true : false; var elName = el.tagName.toLowerCase(); var wrapperEl = $("
"); var attributeString = ""; $.each(el.attributes, function() { if(this.specified) { attributeString = attributeString + ' ' + this.name + '="' + this.value + '"'; } }); var attributeSpace = ""; if(attributeString.length > 0){ attributeSpace = " "; } if(hasChildren) { wrapperEl.text("<" + elName + attributeSpace + attributeString + ">"); $(el.children).each(function(i,el){ wrapperEl.append(getMarkup(el)); }); wrapperEl.append("</" + elName + ">"); } else { wrapperEl.text("<" + elName + attributeSpace + attributeString + "/>"); } return wrapperEl; } //new board loader... function loadBoard(){ var boardString = level.board; // just a placeholder to iterate over... boardMarkup = ""; // what is this var tableMarkup = ""; // what is this var editorMarkup = ""; // this is a string that represents the HTML showHelp(); var markupHolder = $("
") $(level.boardMarkup).each(function(i,el){ if(el.nodeType == 1){ var result = getMarkup(el); markupHolder.append(result); } }); $(".table").html(level.boardMarkup); addNametags(); $(".table *").addClass("pop"); $(".markup").html(markupHolder.html()); } // Adds nametags to the items on the table function addNametags(){ $(".nametags *").remove(); $(".table-wrapper").css("transform","rotateX(0)"); $(".table-wrapper").width($(".table-wrapper").width()); $(".table *").each(function(){ if($(this).attr("for")){ var pos = $(this).position(); var width = $(this).width(); var nameTag = $("
" + $(this).attr("for") + "
"); $(".nametags").append(nameTag); var tagPos = pos.left + (width/2) - nameTag.width()/2 + 12; nameTag.css("left",tagPos); } }); $(".table-wrapper").css("transform","rotateX(20deg)"); } function loadLevel(){ // Make sure we don't load a level we don't have if(currentLevel < 0 || currentLevel >= levels.length) { currentLevel = 0; } hideTooltip(); level = levels[currentLevel]; // Show the help link only for the first three levels if(currentLevel < 3) { $(".note-toggle").show(); } else { $(".note-toggle").hide(); } $(".level-menu .current").removeClass("current"); $(".level-menu div a").eq(currentLevel).addClass("current"); var percent = (currentLevel+1)/levels.length * 100; $(".progress").css("width",percent + "%"); localStorage.setItem("currentLevel",currentLevel); loadBoard(); resetTable(); $(".level-header .level-text").html("Level " + (currentLevel+1) + " of " + levels.length); updateProgressUI(currentLevel, checkCompleted(currentLevel)); $(".order").text(level.doThis); $("input").val("").focus(); $(".input-wrapper").css("opacity",1); $(".result").text(""); //Strobe what's supposed to be selected setTimeout(function(){ $(".table " + level.selector).addClass("strobe"); $(".pop").removeClass("pop"); },200); } // Popup positioning code from... // http://stackoverflow.com/questions/4068373/center-a-popup-window-on-screen function PopupCenter(url, title, w, h) { // Fixes dual-screen position Most browsers Firefox var dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : screen.left; var dualScreenTop = window.screenTop != undefined ? window.screenTop : screen.top; var width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width; var height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height; var left = ((width / 2) - (w / 2)) + dualScreenLeft; var top = ((height / 2) - (h / 2)) + dualScreenTop; var newWindow = window.open(url, title, 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left); // Puts focus on the newWindow if (window.focus) { newWindow.focus(); } }