diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee index 0c0d8dd64..51b2e2789 100644 --- a/app/lib/surface/Surface.coffee +++ b/app/lib/surface/Surface.coffee @@ -530,11 +530,20 @@ module.exports = Surface = class Surface extends CocoClass newWidth = 0.55 * pageWidth newHeight = newWidth / aspectRatio return unless newWidth > 0 and newHeight > 0 - return if newWidth is oldWidth and newHeight is oldHeight and not @options.spectateGame - return if newWidth < 200 or newHeight < 200 + #scaleFactor = if application.isIPadApp then 2 else 1 # Retina scaleFactor = 1 - @normalCanvas.add(@webGLCanvas).attr width: newWidth * scaleFactor, height: newHeight * scaleFactor + if @options.stayVisible + availableHeight = window.innerHeight + availableHeight -= $('.ad-container').outerHeight() + availableHeight -= $('#game-area').outerHeight() - $('#canvas-wrapper').outerHeight() + scaleFactor = availableHeight / newHeight if availableHeight < newHeight + newWidth *= scaleFactor + newHeight *= scaleFactor + + return if newWidth is oldWidth and newHeight is oldHeight and not @options.spectateGame + return if newWidth < 200 or newHeight < 200 + @normalCanvas.add(@webGLCanvas).attr width: newWidth, height: newHeight # Cannot do this to the webGLStage because it does not use scaleX/Y. # Instead the LayerAdapter scales webGL-enabled layers. diff --git a/app/locale/en.coffee b/app/locale/en.coffee index e1b26dd8b..6f12fe859 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -502,6 +502,7 @@ feature5: "Video tutorials" feature6: "Premium email support" feature7: "Private <strong>Clans</strong>" + feature8: "<strong>No ads!</strong>" free: "Free" month: "month" must_be_logged: "You must be logged in first. Please create an account or log in from the menu above." diff --git a/app/models/User.coffee b/app/models/User.coffee index 356fa46fe..8ea24ea5c 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -77,7 +77,7 @@ module.exports = class User extends CocoModel # y = a * ln(1/b * (x + c)) + 1 @levelFromExp: (xp) -> - if xp > 0 then Math.floor(a * Math.log((1/b) * (xp + c))) + 1 else 1 + if xp > 0 then Math.floor(a * Math.log((1 / b) * (xp + c))) + 1 else 1 # x = b * e^((y-1)/a) - c @expForLevel: (level) -> @@ -137,6 +137,16 @@ module.exports = class User extends CocoModel application.tracker.identify announcesActionAudioGroup: @announcesActionAudioGroup unless me.isAdmin() @announcesActionAudioGroup + getCampaignAdsGroup: -> + return @campaignAdsGroup if @campaignAdsGroup + group = me.get('testGroupNumber') % 2 + @campaignAdsGroup = switch group + when 0 then 'no-ads' + when 1 then 'leaderboard-ads' + @campaignAdsGroup = 'no-ads' if me.isAdmin() + application.tracker.identify campaignAdsGroup: @campaignAdsGroup unless me.isAdmin() + @campaignAdsGroup + getHomepageGroup: -> # Only testing on en-US so localization issues are not a factor return 'new-home-student' unless _.string.startsWith(me.get('preferredLanguage', true) or 'en-US', 'en') diff --git a/app/styles/modal/subscribe-modal.sass b/app/styles/modal/subscribe-modal.sass index 1d588e8f2..4a9efb066 100644 --- a/app/styles/modal/subscribe-modal.sass +++ b/app/styles/modal/subscribe-modal.sass @@ -111,7 +111,7 @@ text-align: center tr td - padding: 3px + padding: 2px border-width: 0px border-top-width: 1px border-color: rgba(85, 85, 85, 0.1) diff --git a/app/styles/play/campaign-view.sass b/app/styles/play/campaign-view.sass index 8fbd22a7f..fd2bddcd0 100644 --- a/app/styles/play/campaign-view.sass +++ b/app/styles/play/campaign-view.sass @@ -628,6 +628,14 @@ $gameControlMargin: 30px img height: 30px + .ad-container + width: 100% + height: 90px + text-align: center + + .gameplay-container + position: absolute + body.ipad #campaign-view // iPad only supports up to Kithgard Gates for now. .campaign-switch diff --git a/app/styles/play/level.sass b/app/styles/play/level.sass index ad2a515c5..fb3f236bc 100644 --- a/app/styles/play/level.sass +++ b/app/styles/play/level.sass @@ -66,6 +66,7 @@ $level-resize-transition-time: 0.5s .level-content position: relative + background-color: black #canvas-wrapper top: 50px @@ -83,14 +84,14 @@ $level-resize-transition-time: 0.5s canvas#webgl-surface background-color: #333 z-index: 1 - + canvas#normal-surface z-index: 1 position: absolute top: 0 left: 0 pointer-events: none - + canvas#webgl-surface, canvas#normal-surface display: block z-index: 2 @@ -259,6 +260,10 @@ $level-resize-transition-time: 0.5s right: 15px font-size: 30px + .ad-container + width: 100% + height: 90px + text-align: center html.fullscreen-editor #level-view diff --git a/app/templates/core/subscribe-modal.jade b/app/templates/core/subscribe-modal.jade index fec7039f7..b0eca0241 100644 --- a/app/templates/core/subscribe-modal.jade +++ b/app/templates/core/subscribe-modal.jade @@ -68,18 +68,26 @@ span.glyphicon.glyphicon-ok tr td.feature-description - span(data-i18n="subscribe.feature6") + span(data-i18n="[html]subscribe.feature7") if !me.isOnPremiumServer() td.free-cell td.center-ok span.glyphicon.glyphicon-ok tr td.feature-description - span(data-i18n="[html]subscribe.feature7") + span(data-i18n="subscribe.feature6") if !me.isOnPremiumServer() td.free-cell td.center-ok span.glyphicon.glyphicon-ok + if me.getCampaignAdsGroup() === 'leaderboard-ads' + tr + td.feature-description + span(data-i18n="[html]subscribe.feature8") + if !me.isOnPremiumServer() + td.free-cell + td.center-ok + span.glyphicon.glyphicon-ok #parents-info(data-i18n="subscribe.parents") #payment-methods-info(data-i18n="subscribe.payment_methods") diff --git a/app/templates/play/campaign-view.jade b/app/templates/play/campaign-view.jade index b2b0aa87b..96b3bfe32 100644 --- a/app/templates/play/campaign-view.jade +++ b/app/templates/play/campaign-view.jade @@ -1,165 +1,182 @@ -a(href="/").picoctf-hide - img.small-nav-logo(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat") - -.picoctf-show - a(href="http://staging.picoctf.com").picoctf-logo - img.small-nav-logo(src="http://picoctf.com/img/2014_logo_blue2.svg", title="picoCTF home", alt="picoCTF home") - a(href="http://codecombat.com").picoctf-powered-by - em.spr powered by - img(src="/images/pages/base/logo.png", title="Powered by CodeCombat - Learn how to code by playing a game ", alt="Powered by CodeCombat") - -if campaign - .map - .gradient.horizontal-gradient.top-gradient - .gradient.vertical-gradient.right-gradient - .gradient.horizontal-gradient.bottom-gradient - .gradient.vertical-gradient.left-gradient - .map-background(alt="", draggable="false") - - each level in levels - if !level.hidden - div(style="left: #{level.position.x}%; bottom: #{level.position.y}%; background-color: #{level.color}", class="level" + (level.next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + (levelStatusMap[level.slug] || ""), data-level-slug=level.slug, data-level-original=level.original, title=i18n(level, 'name') + (level.disabled ? ' (Coming Soon to Adventurers)' : '')) - if level.unlocksHero && (!level.purchasedHero || editorMode) - img.hero-portrait(src="/file/db/thang.type/#{level.unlocksHero}/portrait.png") - a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.slug}", disabled=level.disabled, data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name) - if level.slug == 'lost-viking' - img.star(src="/file/db/thang.type/5441c3144e9aeb727cc97111/portrait.png") - else if level.requiresSubscription - img.star(src="/images/pages/play/star.png") - if levelStatusMap[level.slug] === 'complete' - img.banner(src="/images/pages/play/level-banner-complete.png") - if levelStatusMap[level.slug] === 'started' - img.banner(src="/images/pages/play/level-banner-started.png") - if levelDifficultyMap[level.slug] - .level-difficulty-banner-text= levelDifficultyMap[level.slug] - div(style="left: #{level.position.x}%; bottom: #{level.position.y}%", class="level-shadow" + (level.next ? " next" : "") + " " + (levelStatusMap[level.slug] || "")) - .level-info-container(data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name) - - var playCount = levelPlayCountMap[level.slug] - .progress.progress-striped.active.hide - .progress-bar(style="width: 100%") - - var showsLeaderboard = levelStatusMap[level.slug] === 'complete' && ((level.scoreTypes && level.scoreTypes.length) || ['hero-ladder', 'course-ladder'].indexOf(level.type) !== -1); - - div(class="level-info " + (levelStatusMap[level.slug] || "") + (level.requiresSubscription ? " premium" : "") + (showsLeaderboard ? " shows-leaderboard" : "")) - .level-status - h3= i18n(level, 'name') + (level.disabled ? " (Coming soon!)" : (level.locked ? " (Locked)" : "")) - - var description = i18n(level, 'description') || level.description || "" - .level-description!= marked(description, {sanitize: !picoCTF}) - if level.disabled - p - span.spr(data-i18n="play.awaiting_levels_adventurer_prefix") We release five levels per week. - a.spr(href="/contribute/adventurer") - strong(data-i18n="play.awaiting_levels_adventurer") Sign up as an Adventurer - span.spl(data-i18n="play.awaiting_levels_adventurer_suffix") to be the first to play new levels. - if level.displayConcepts && level.displayConcepts.length - p - for concept in level.displayConcepts - kbd(data-i18n="concepts." + concept) - - if !level.disabled && !level.locked - if playCount && playCount.sessions - .play-counts.hidden - span.spl.spr= playCount.sessions - span(data-i18n="play.players") players - span.spr , #{Math.round(playCount.playtime / 3600)} - span(data-i18n="play.hours_played") hours played - if showsLeaderboard - button.btn.btn-warning.btn.btn-lg.btn-illustrated.view-solutions(data-level-slug=level.slug) - span(data-i18n="leaderboard.scores") - button.btn.btn-success.btn.btn-lg.btn-illustrated.start-level(data-i18n="common.play") Play - if me.get('courseInstances') && me.get('courseInstances').length - .course-version.hidden(data-level-original=level.original) - em(data-i18n="general.or") - | ... - br - button.btn.btn-primary.btn.btn-lg.btn-illustrated - span(data-i18n="play.play_classroom_version") Play Classroom Version - else if level.unlocksHero && !level.purchasedHero - img.hero-portrait(src="/file/db/thang.type/#{level.unlocksHero}/portrait.png", style="left: #{level.position.x}%; bottom: #{level.position.y}%;") - - for adjacentCampaign in adjacentCampaigns - a(href=(editorMode ? "/editor/campaign/" : "/play/") + adjacentCampaign.slug) - span.glyphicon.glyphicon-share-alt.campaign-switch(style=adjacentCampaign.style, title=adjacentCampaign.name, data-campaign-id=adjacentCampaign.id) - -else - .portal - .portals - for campaignSlug in ['dungeon', 'forest', 'desert', 'mountain', 'glacier', 'volcano'] - - var campaign = campaigns[campaignSlug]; - - var godmode = me.get('permissions', true).indexOf('godmode') != -1; - div(class="campaign #{campaignSlug}" + (campaign ? "" : " silhouette") + (campaign && campaign.locked && !godmode ? " locked" : ""), data-campaign-slug=campaignSlug) - .campaign-label - h2.campaign-name - if campaign - span= i18n(campaign.attributes, 'fullName') - else - span ??? - if campaign && campaign.levelsTotal - h3.levels-completed - span= campaign.levelsCompleted - | / - span= campaign.levelsTotal - if campaign && campaign.locked && !godmode - h3.campaign-locked(data-i18n="play.locked") Locked - else if campaign - btn(data-i18n="common.play").btn.btn-illustrated.btn-lg.btn-success.play-button - if campaign && campaign.get('description') - p.campaign-description - span= i18n(campaign.attributes, 'description') - -.game-controls.header-font.picoctf-hide - button.btn.poll.hidden(data-i18n="[title]play.poll") - a.btn.clans(href="/clans", data-i18n="[title]clans.clans") - button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items") - button.btn.heroes(data-toggle='coco-modal', data-target='play/modal/PlayHeroesModal', data-i18n="[title]play.heroes") - button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements") - if me.get('anonymous') === false || me.get('iosIdentifierForVendor') || isIPadApp - button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems") - if !me.get('anonymous', true) - button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account") - //if me.isAdmin() - // button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings") - if me.get('anonymous', true) - button.btn.settings(data-toggle='coco-modal', data-target='core/CreateAccountModal', data-i18n="[title]play.settings") - -.user-status.header-font.picoctf-hide - .user-status-line - span.gem.gem-30 - span#gems-count.spr= me.gems() - span.level-indicator(data-i18n="general.player_level") - span.player-level.spr= me.level() - span.player-hero-icon - if me.get('anonymous') - span.player-name.spr(data-i18n="play.anonymous") Anonymous Player - button.btn.btn-illustrated.login-button.btn-warning(data-i18n="login.log_in") - button.btn.btn-illustrated.signup-button.btn-danger(data-i18n="signup.sign_up") +if view.showAds() + // TODO: loading this multiple times yields script error: + // Uncaught TagError: adsbygoogle.push() error: All ins elements in the DOM with class=adsbygoogle already have ads in them. + .ad-container + if campaign + script(async, src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js") + ins.adsbygoogle(style="display:inline-block;width:728px;height:90px", data-ad-client="ca-pub-6640930638193614", data-ad-slot="4924994487") + script. + (adsbygoogle = window.adsbygoogle || []).push({}); else - a(data-toggle="coco-modal", data-target="play/modal/PlayAccountModal").player-name.spr= me.get('name') - button#logout-button.btn.btn-illustrated.btn-warning(data-i18n="login.log_out") Log Out - if me.isPremium() - button.btn.btn-illustrated.btn-primary(data-i18n="nav.contact", data-toggle="coco-modal", data-target="core/ContactModal") Contact + script(async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js") + ins.adsbygoogle(style="display:inline-block;width:728px;height:90px", data-ad-client="ca-pub-6640930638193614", data-ad-slot="4469166082") + script. + (adsbygoogle = window.adsbygoogle || []).push({}); -button.btn.btn-lg.btn-inverse.campaign-control-button.picoctf-hide#volume-button(data-i18n="[title]play.adjust_volume", title="Adjust volume") - .glyphicon.glyphicon-volume-off - .glyphicon.glyphicon-volume-down - .glyphicon.glyphicon-volume-up +// TODO: .gameplay-container causes world map buttons to briefly appear in top left of screen +.gameplay-container + a(href="/").picoctf-hide + img.small-nav-logo(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat") -if campaign && !editorMode - button.btn.btn-lg.btn-inverse.campaign-control-button.picoctf-hide#back-button(data-i18n="[title]resources.campaigns", title="Campaigns") - .glyphicon.glyphicon-globe + .picoctf-show + a(href="http://staging.picoctf.com").picoctf-logo + img.small-nav-logo(src="http://picoctf.com/img/2014_logo_blue2.svg", title="picoCTF home", alt="picoCTF home") + a(href="http://codecombat.com").picoctf-powered-by + em.spr powered by + img(src="/images/pages/base/logo.png", title="Powered by CodeCombat - Learn how to code by playing a game ", alt="Powered by CodeCombat") -if editorMode - button.btn.btn-lg.btn-inverse.campaign-control-button#clear-storage-button(data-i18n="[title]editor.clear_storage", title="Clear your local changes") - .glyphicon.glyphicon-refresh + if campaign + .map + .gradient.horizontal-gradient.top-gradient + .gradient.vertical-gradient.right-gradient + .gradient.horizontal-gradient.bottom-gradient + .gradient.vertical-gradient.left-gradient + .map-background(alt="", draggable="false") -if campaign && campaign.loaded - h1#campaign-status.picoctf-hide - .campaign-status-background - .campaign-name - - var fullName = i18n(campaign.attributes, 'fullName') - if (me.get('preferredLanguage', true) || 'en-US').split('-')[0] == 'en' || fullName != campaign.get('fullName') - // We have a translation. - span= fullName - .levels-completed - span= levelsCompleted - | / - span= levelsTotal + each level in levels + if !level.hidden + div(style="left: #{level.position.x}%; bottom: #{level.position.y}%; background-color: #{level.color}", class="level" + (level.next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + (levelStatusMap[level.slug] || ""), data-level-slug=level.slug, data-level-original=level.original, title=i18n(level, 'name') + (level.disabled ? ' (Coming Soon to Adventurers)' : '')) + if level.unlocksHero && (!level.purchasedHero || editorMode) + img.hero-portrait(src="/file/db/thang.type/#{level.unlocksHero}/portrait.png") + a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.slug}", disabled=level.disabled, data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name) + if level.slug == 'lost-viking' + img.star(src="/file/db/thang.type/5441c3144e9aeb727cc97111/portrait.png") + else if level.requiresSubscription + img.star(src="/images/pages/play/star.png") + if levelStatusMap[level.slug] === 'complete' + img.banner(src="/images/pages/play/level-banner-complete.png") + if levelStatusMap[level.slug] === 'started' + img.banner(src="/images/pages/play/level-banner-started.png") + if levelDifficultyMap[level.slug] + .level-difficulty-banner-text= levelDifficultyMap[level.slug] + div(style="left: #{level.position.x}%; bottom: #{level.position.y}%", class="level-shadow" + (level.next ? " next" : "") + " " + (levelStatusMap[level.slug] || "")) + .level-info-container(data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name) + - var playCount = levelPlayCountMap[level.slug] + .progress.progress-striped.active.hide + .progress-bar(style="width: 100%") + - var showsLeaderboard = levelStatusMap[level.slug] === 'complete' && ((level.scoreTypes && level.scoreTypes.length) || ['hero-ladder', 'course-ladder'].indexOf(level.type) !== -1); + + div(class="level-info " + (levelStatusMap[level.slug] || "") + (level.requiresSubscription ? " premium" : "") + (showsLeaderboard ? " shows-leaderboard" : "")) + .level-status + h3= i18n(level, 'name') + (level.disabled ? " (Coming soon!)" : (level.locked ? " (Locked)" : "")) + - var description = i18n(level, 'description') || level.description || "" + .level-description!= marked(description, {sanitize: !picoCTF}) + if level.disabled + p + span.spr(data-i18n="play.awaiting_levels_adventurer_prefix") We release five levels per week. + a.spr(href="/contribute/adventurer") + strong(data-i18n="play.awaiting_levels_adventurer") Sign up as an Adventurer + span.spl(data-i18n="play.awaiting_levels_adventurer_suffix") to be the first to play new levels. + if level.displayConcepts && level.displayConcepts.length + p + for concept in level.displayConcepts + kbd(data-i18n="concepts." + concept) + + if !level.disabled && !level.locked + if playCount && playCount.sessions + .play-counts.hidden + span.spl.spr= playCount.sessions + span(data-i18n="play.players") players + span.spr , #{Math.round(playCount.playtime / 3600)} + span(data-i18n="play.hours_played") hours played + if showsLeaderboard + button.btn.btn-warning.btn.btn-lg.btn-illustrated.view-solutions(data-level-slug=level.slug) + span(data-i18n="leaderboard.scores") + button.btn.btn-success.btn.btn-lg.btn-illustrated.start-level(data-i18n="common.play") Play + if me.get('courseInstances') && me.get('courseInstances').length + .course-version.hidden(data-level-original=level.original) + em(data-i18n="general.or") + | ... + br + button.btn.btn-primary.btn.btn-lg.btn-illustrated + span(data-i18n="play.play_classroom_version") Play Classroom Version + else if level.unlocksHero && !level.purchasedHero + img.hero-portrait(src="/file/db/thang.type/#{level.unlocksHero}/portrait.png", style="left: #{level.position.x}%; bottom: #{level.position.y}%;") + + for adjacentCampaign in adjacentCampaigns + a(href=(editorMode ? "/editor/campaign/" : "/play/") + adjacentCampaign.slug) + span.glyphicon.glyphicon-share-alt.campaign-switch(style=adjacentCampaign.style, title=adjacentCampaign.name, data-campaign-id=adjacentCampaign.id) + + else + .portal + .portals + for campaignSlug in ['dungeon', 'forest', 'desert', 'mountain', 'glacier', 'volcano'] + - var campaign = campaigns[campaignSlug]; + - var godmode = me.get('permissions', true).indexOf('godmode') != -1; + div(class="campaign #{campaignSlug}" + (campaign ? "" : " silhouette") + (campaign && campaign.locked && !godmode ? " locked" : ""), data-campaign-slug=campaignSlug) + .campaign-label + h2.campaign-name + if campaign + span= i18n(campaign.attributes, 'fullName') + else + span ??? + if campaign && campaign.levelsTotal + h3.levels-completed + span= campaign.levelsCompleted + | / + span= campaign.levelsTotal + if campaign && campaign.locked && !godmode + h3.campaign-locked(data-i18n="play.locked") Locked + else if campaign + btn(data-i18n="common.play").btn.btn-illustrated.btn-lg.btn-success.play-button + if campaign && campaign.get('description') + p.campaign-description + span= i18n(campaign.attributes, 'description') + + .game-controls.header-font.picoctf-hide + button.btn.poll.hidden(data-i18n="[title]play.poll") + a.btn.clans(href="/clans", data-i18n="[title]clans.clans") + button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items") + button.btn.heroes(data-toggle='coco-modal', data-target='play/modal/PlayHeroesModal', data-i18n="[title]play.heroes") + button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements") + if me.get('anonymous') === false || me.get('iosIdentifierForVendor') || isIPadApp + button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems") + if !me.get('anonymous', true) + button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account") + //if me.isAdmin() + // button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings") + if me.get('anonymous', true) + button.btn.settings(data-toggle='coco-modal', data-target='core/CreateAccountModal', data-i18n="[title]play.settings") + + .user-status.header-font.picoctf-hide + .user-status-line + span.gem.gem-30 + span#gems-count.spr= me.gems() + span.level-indicator(data-i18n="general.player_level") + span.player-level.spr= me.level() + span.player-hero-icon + if me.get('anonymous') + span.player-name.spr(data-i18n="play.anonymous") Anonymous Player + button.btn.btn-illustrated.login-button.btn-warning(data-i18n="login.log_in") + button.btn.btn-illustrated.signup-button.btn-danger(data-i18n="signup.sign_up") + else + a(data-toggle="coco-modal", data-target="play/modal/PlayAccountModal").player-name.spr= me.get('name') + button#logout-button.btn.btn-illustrated.btn-warning(data-i18n="login.log_out") Log Out + if me.isPremium() + button.btn.btn-illustrated.btn-primary(data-i18n="nav.contact", data-toggle="coco-modal", data-target="core/ContactModal") Contact + + button.btn.btn-lg.btn-inverse.campaign-control-button.picoctf-hide#volume-button(data-i18n="[title]play.adjust_volume", title="Adjust volume") + .glyphicon.glyphicon-volume-off + .glyphicon.glyphicon-volume-down + .glyphicon.glyphicon-volume-up + + if campaign && !editorMode + button.btn.btn-lg.btn-inverse.campaign-control-button.picoctf-hide#back-button(data-i18n="[title]resources.campaigns", title="Campaigns") + .glyphicon.glyphicon-globe + + if editorMode + button.btn.btn-lg.btn-inverse.campaign-control-button#clear-storage-button(data-i18n="[title]editor.clear_storage", title="Clear your local changes") + .glyphicon.glyphicon-refresh + + if campaign && campaign.loaded + h1#campaign-status.picoctf-hide + .campaign-status-background + .campaign-name + - var fullName = i18n(campaign.attributes, 'fullName') + if (me.get('preferredLanguage', true) || 'en-US').split('-')[0] == 'en' || fullName != campaign.get('fullName') + // We have a translation. + span= fullName + .levels-completed + span= levelsCompleted + | / + span= levelsTotal diff --git a/app/templates/play/level.jade b/app/templates/play/level.jade index 02bc0c9b7..5bf5a8efe 100644 --- a/app/templates/play/level.jade +++ b/app/templates/play/level.jade @@ -1,49 +1,59 @@ -#level-loading-view +if view.showAds() + // TODO: loading this multiple times yields script error: + // Uncaught TagError: adsbygoogle.push() error: All ins elements in the DOM with class=adsbygoogle already have ads in them. + .ad-container + script(async, src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js") + ins.adsbygoogle(style="display:inline-block;width:728px;height:90px", data-ad-client="ca-pub-6640930638193614", data-ad-slot="5527096883") + script. + (adsbygoogle = window.adsbygoogle || []).push({}); -.level-content - #control-bar-view +.game-container + #level-loading-view - #fullscreen-editor-background-screen(title="Click to minimize the code editor") + .level-content + #control-bar-view - #code-area - #code-area-gradient.gradient - #tome-view + #fullscreen-editor-background-screen(title="Click to minimize the code editor") - #game-area + #code-area + #code-area-gradient.gradient + #tome-view - #canvas-wrapper - canvas(width=924, height=589)#webgl-surface - canvas(width=924, height=589)#normal-surface - #ascii-surface - #canvas-left-gradient.gradient - #canvas-top-gradient.gradient - #goals-view + #game-area - #level-flags-view + #canvas-wrapper + canvas(width=924, height=589)#webgl-surface + canvas(width=924, height=589)#normal-surface + #ascii-surface + #canvas-left-gradient.gradient + #canvas-top-gradient.gradient + #goals-view - #gold-view + #level-flags-view - #problem-alert-view + #gold-view - #level-chat-view + #problem-alert-view - #multiplayer-status-view + #level-chat-view - #duel-stats-view + #multiplayer-status-view - #playback-view + #duel-stats-view - #thang-hud + #playback-view - #level-dialogue-view + #thang-hud - button.btn.btn-lg.btn-warning.banner.header-font#stop-real-time-playback-button(title="Stop real-time playback", data-i18n="play_level.skip") Skip + #level-dialogue-view -#level-footer-shadow -#level-footer-background + button.btn.btn-lg.btn-warning.banner.header-font#stop-real-time-playback-button(title="Stop real-time playback", data-i18n="play_level.skip") Skip -if !me.get('anonymous') - #play-footer(class=me.isPremium() ? "premium" : "") - p(class='footer-link-text').picoctf-hide - a.contact-link(title='Send CodeCombat a message', tabindex=-1, data-i18n="nav.contact") Contact + #level-footer-shadow + #level-footer-background + + if !me.get('anonymous') + #play-footer(class=me.isPremium() ? "premium" : "") + p(class='footer-link-text').picoctf-hide + a.contact-link(title='Send CodeCombat a message', tabindex=-1, data-i18n="nav.contact") Contact diff --git a/app/templates/play/modal/buy-gems-modal.jade b/app/templates/play/modal/buy-gems-modal.jade index c67c31ca0..261fa9e41 100644 --- a/app/templates/play/modal/buy-gems-modal.jade +++ b/app/templates/play/modal/buy-gems-modal.jade @@ -22,7 +22,7 @@ .product h4.subscription-gem-amount x{{gems}} / mo h3(data-i18n="account.subscription") - if me.isPremium() + if me.hasSubscription() button.disabled.start-subscription-button.btn.btn-lg.btn-illustrated.btn-success | ✓ span(data-i18n="account.subscribed") diff --git a/app/views/play/CampaignView.coffee b/app/views/play/CampaignView.coffee index f2fbfee90..ffb0da0b6 100644 --- a/app/views/play/CampaignView.coffee +++ b/app/views/play/CampaignView.coffee @@ -266,6 +266,11 @@ module.exports = class CampaignView extends RootView authModal.mode = 'signup' @openModalView authModal + showAds: -> + if application.isProduction() && !me.isPremium() && !me.isTeacher() && !window.serverConfig.picoCTF + return me.getCampaignAdsGroup() is 'leaderboard-ads' + false + annotateLevel: (level) -> level.position ?= { x: 10, y: 10 } level.locked = not me.ownsLevel level.original @@ -554,6 +559,7 @@ module.exports = class CampaignView extends RootView aspectRatio = mapWidth / mapHeight pageWidth = @$el.width() pageHeight = @$el.height() + pageHeight -= adContainerHeight if adContainerHeight = $('.ad-container').outerHeight() widthRatio = pageWidth / mapWidth heightRatio = pageHeight / mapHeight # Make sure we can see the whole map, fading to background in one dimension. diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee index b9a4590e1..71c134ca8 100644 --- a/app/views/play/level/PlayLevelView.coffee +++ b/app/views/play/level/PlayLevelView.coffee @@ -148,6 +148,13 @@ module.exports = class PlayLevelView extends RootView application.tracker?.trackEvent 'Finished Level Load', category: 'Play Level', label: @levelID, level: @levelID, loadDuration: @loadDuration application.tracker?.trackTiming @loadDuration, 'Level Load Time', @levelID, @levelID + isCourseMode: -> @courseID and @courseInstanceID + + showAds: -> + if application.isProduction() && !me.isPremium() && !me.isTeacher() && !window.serverConfig.picoCTF && !@isCourseMode() + return me.getCampaignAdsGroup() is 'leaderboard-ads' + false + # CocoView overridden methods ############################################### getRenderData: -> @@ -326,7 +333,13 @@ module.exports = class PlayLevelView extends RootView initSurface: -> webGLSurface = $('canvas#webgl-surface', @$el) normalSurface = $('canvas#normal-surface', @$el) - @surface = new Surface(@world, normalSurface, webGLSurface, thangTypes: @supermodel.getModels(ThangType), observing: @observing, playerNames: @findPlayerNames(), levelType: @level.get('type', true)) + surfaceOptions = + thangTypes: @supermodel.getModels(ThangType) + observing: @observing + playerNames: @findPlayerNames() + levelType: @level.get('type', true) + stayVisible: @showAds() + @surface = new Surface(@world, normalSurface, webGLSurface, surfaceOptions) worldBounds = @world.getBounds() bounds = [{x: worldBounds.left, y: worldBounds.top}, {x: worldBounds.right, y: worldBounds.bottom}] @surface.camera.setBounds(bounds) @@ -499,7 +512,8 @@ module.exports = class PlayLevelView extends RootView break Backbone.Mediator.publish 'tome:cast-spell', {} - onWindowResize: (e) => @endHighlight() + onWindowResize: (e) => + @endHighlight() onDisableControls: (e) -> return if e.controls and not ('level' in e.controls) @@ -535,7 +549,7 @@ module.exports = class PlayLevelView extends RootView @endHighlight() options = {level: @level, supermodel: @supermodel, session: @session, hasReceivedMemoryWarning: @hasReceivedMemoryWarning, courseID: @courseID, courseInstanceID: @courseInstanceID, world: @world} ModalClass = if @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder'] then HeroVictoryModal else VictoryModal - ModalClass = CourseVictoryModal if @courseID and @courseInstanceID + ModalClass = CourseVictoryModal if @isCourseMode() ModalClass = PicoCTFVictoryModal if window.serverConfig.picoCTF victoryModal = new ModalClass(options) @openModalView(victoryModal)