diff --git a/app/assets/images/pages/home/boy_coding.png b/app/assets/images/pages/home/boy_coding.png new file mode 100644 index 000000000..474d8c958 Binary files /dev/null and b/app/assets/images/pages/home/boy_coding.png differ diff --git a/app/assets/images/pages/home/girl_coding.png b/app/assets/images/pages/home/girl_coding.png new file mode 100644 index 000000000..4d587c31a Binary files /dev/null and b/app/assets/images/pages/home/girl_coding.png differ diff --git a/app/assets/images/pages/play/menu_icons.png b/app/assets/images/pages/play/menu_icons.png index 53028d44b..37cd5fc03 100644 Binary files a/app/assets/images/pages/play/menu_icons.png and b/app/assets/images/pages/play/menu_icons.png differ diff --git a/app/assets/images/pages/play/modal/equip-buttons.png b/app/assets/images/pages/play/modal/equip-buttons.png new file mode 100644 index 000000000..0c3eaf15b Binary files /dev/null and b/app/assets/images/pages/play/modal/equip-buttons.png differ diff --git a/app/lib/surface/FlagLank.coffee b/app/lib/surface/FlagLank.coffee index 8dcd1c68d..d95ed91d2 100644 --- a/app/lib/surface/FlagLank.coffee +++ b/app/lib/surface/FlagLank.coffee @@ -31,4 +31,6 @@ module.exports = class FlagLank extends IndieLank toggleCursor: (to) -> @options.isCursor = to @thang.alpha = if to then 0.33 else 0.67 # 1.0 is for flags that have been placed + #@thang.action = if to then 'idle' else 'appear' # TODO: why doesn't this work? Does it not render the action or something? + @thang.action = 'appear' @updateAlpha() diff --git a/app/lib/surface/SingularSprite.coffee b/app/lib/surface/SingularSprite.coffee index 2350fcda5..0fed6c824 100644 --- a/app/lib/surface/SingularSprite.coffee +++ b/app/lib/surface/SingularSprite.coffee @@ -1,6 +1,8 @@ SpriteBuilder = require 'lib/sprites/SpriteBuilder' -floors = ['Dungeon Floor', 'Indoor Floor', 'Grass', 'Grass01', 'Grass02', 'Grass03', 'Grass04', 'Grass05', 'Goal Trigger', 'Obstacle', 'Sand 01', 'Sand 02', 'Sand 03', 'Sand 04', 'Sand 05', 'Sand 06', 'Talus 1', 'Talus 2', 'Talus 3', 'Talus 4', 'Talus 5', 'Talus 6', 'Firn 1', 'Firn 2', 'Firn 3', 'Firn 4', 'Firn 5', 'Firn 6', 'Ice Rink 1', 'Ice Rink 2', 'Ice Rink 3'] +floors = ['Dungeon Floor', 'Indoor Floor', 'Grass', 'Grass01', 'Grass02', 'Grass03', 'Grass04', 'Grass05', 'Goal Trigger', 'Obstacle', 'Sand 01', 'Sand 02', 'Sand 03', 'Sand 04', 'Sand 05', 'Sand 06', 'Talus 1', 'Talus 2', 'Talus 3', 'Talus 4', 'Talus 5', 'Talus 6', 'Firn 1', 'Firn 2', 'Firn 3', 'Firn 4', 'Firn 5', 'Firn 6', 'Ice Rink 1', 'Ice Rink 2', 'Ice Rink 3', 'Firn Cliff'] + +cliffs = ['Dungeon Pit', 'Grass Cliffs'] module.exports = class SingularSprite extends createjs.Sprite childMovieClips: null @@ -71,6 +73,13 @@ module.exports = class SingularSprite extends createjs.Sprite @baseScaleY = @scaleY if @camera and @thangType.get('name') in floors @baseScaleY *= @camera.y2x + else if @camera and @thangType.get('name') in cliffs + if actionName is 'idle_side' + @baseScaleX *= @camera.x2y# / 0.85 + @baseScaleY *= @camera.y2x * 0.85 + else + @baseScaleY *= @camera.y2x / 0.85 + console.log 'it is a cliff!', actionName, @baseScaleX, @baseScaleY @currentAnimation = actionName return diff --git a/app/lib/world/names.coffee b/app/lib/world/names.coffee index bc9cf9d7f..08f2863e6 100644 --- a/app/lib/world/names.coffee +++ b/app/lib/world/names.coffee @@ -27,7 +27,7 @@ module.exports.thangNames = thangNames = 'Godel' 'Goreball' 'Gordok' - 'Toremon' + 'Gorylo' 'Gort' 'Kog' 'Kogpole' @@ -37,11 +37,13 @@ module.exports.thangNames = thangNames = 'Oogre' 'Raack' 'Ragtime' + 'Raort' 'Rexxar' 'Skoggen' 'Smerk' 'Snortt' 'Thabt' + 'Toremon' 'Treg' 'Ursa' 'Vorobun' @@ -184,6 +186,7 @@ module.exports.thangNames = thangNames = 'Belch' 'Booz' 'Brusentsov' + 'Demonik' 'Dronck' 'Gorlog' 'Grumus' @@ -204,7 +207,7 @@ module.exports.thangNames = thangNames = 'Trogdor' 'Trung' 'Vargutt' - 'Demonik' + 'Vyle' ] 'Ogre F': [ # Female diff --git a/app/locale/de-DE.coffee b/app/locale/de-DE.coffee index 965e34457..caf2d7d90 100644 --- a/app/locale/de-DE.coffee +++ b/app/locale/de-DE.coffee @@ -933,7 +933,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription: contribute: page_title: "Mitwirken" - intro_blurb: "CodeCombat ist zu 100% Open Source! Hunderte hingebungsvolle Spieler haben uns geholfen das Spiel zu dem zu machen was es heute ist. Tritt uns bei und schreibe das nächste Kapitel in CodeCombat' Aufgabe, der Welt das Programmieren zu lehren!" + intro_blurb: "CodeCombat ist zu 100% Open Source! Hunderte hingebungsvolle Spieler haben uns geholfen das Spiel zu dem zu machen, was es heute ist. Tritt uns bei und schreibe das nächste Kapitel in CodeCombats Mission, der Welt das Programmieren zu lehren!" alert_account_message_intro: "Hey du!" alert_account_message: "Um Klassen-Emails abonnieren zu können, musst du dich zuerst anmelden." archmage_introduction: "Einer der größten Vorteile daran ein Spiel aufzubauen, ist es, dass so viele verschiedene Aspekte eine Rolle spielen. Grafiken, Sound, Echtzeit Networking, Social Networking und natürlich viele der gewöhnlichen Aspekte des Programmierens, von low-level Datenbankmanagement und Server Administration bis hin zum Aufbau von Design und Interface. Es gibt viel zu tun und wenn du ein erfahrener Programmierer bist, mit einer Veranlagung dazu, wirklich knallhart bei CodeCombat einzutauchen, dann könnte diese Klasse etwas für dich sein. Wir würden uns wahnsinnig über deine Hilfe dabei freuen, das beste Programmierspiel der Welt aufzubauen." diff --git a/app/locale/pt-PT.coffee b/app/locale/pt-PT.coffee index fb90c71e3..9e50673b6 100644 --- a/app/locale/pt-PT.coffee +++ b/app/locale/pt-PT.coffee @@ -293,7 +293,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: tip_scrub_shortcut: "Usa Ctrl+[ para rebobinar e Ctrl+] para avançar." tip_guide_exists: "Clica no guia, dentro do menu do jogo (no topo da página), para informações úteis." tip_open_source: "O CodeCombat é 100% open source!" -# tip_tell_friends: "Enjoying CodeCombat? Tell your friends about us!" + tip_tell_friends: "Estás a gostar do CodeCombat? Fala de nós aos teus amigos!" tip_beta_launch: "O CodeCombat lançou o seu beta em outubro de 2013." tip_think_solution: "Pensa na solução, não no problema." tip_theory_practice: "Teoricamente, não há diferença entre a teoria e a prática. Mas na prática, há. - Yogi Berra" @@ -337,7 +337,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: tip_recurse: "Iterar é humano, recursar é divino. - L. Peter Deutsch" tip_free_your_mind: "Tens de libertar tudo, Neo. Medo, dúvida e descrença. Liberta a tua mente. - Morpheus" tip_strong_opponents: "Até o mais forte dos adversários tem uma fraqueza. - Itachi Uchiha" -# tip_paper_and_pen: "Before you start coding, you can always plan with a sheet of paper and a pen." + tip_paper_and_pen: "Antes de começares a programar, podes sempre planear com uma folha de papel e uma caneta." game_menu: inventory_tab: "Inventário" @@ -851,8 +851,8 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: indoor: "Interior" desert: "Deserto" grassy: "Relvado" -# mountain: "Mountain" -# glacier: "Glacier" + mountain: "Montanha" + glacier: "Glaciar" small: "Pequeno" large: "Grande" fork_title: "Bifurcar Nova Versão" @@ -1211,7 +1211,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: delta: added: "Adicionado" modified: "Modificado" -# not_modified: "Not Modified" + not_modified: "Não Modificado" deleted: "Eliminado" moved_index: "Índice Movido" text_diff: "Diferença de Texto" diff --git a/app/styles/courses/mock1/course-details.sass b/app/styles/courses/mock1/course-details.sass index 01f73f109..7d023e1a4 100644 --- a/app/styles/courses/mock1/course-details.sass +++ b/app/styles/courses/mock1/course-details.sass @@ -1,29 +1,42 @@ #course-details-view .concept-completion-container - position: relative - .concept-completion-background - position: absolute - height: 100% - left: 0px - top: 0px - background-color: blue - opacity: 0.25 - .concept-completed-foreground - font-size: 12pt + font-size: 10pt - #editDescriptionModal .modal-dialog + .summary-container + font-size: 14pt + + .statistics-container + font-size: 12pt + td + padding-right: 8px + + .table-concepts-summary + width: 100% + + .concept-summary + width: 100% + background-color: white + cursor: default + display: inline-block + white-space: nowrap + font-size: 9pt + font-weight: normal + border: 1px solid gray + border-radius: 5px + margin: 0px + padding: 2px background-color: white - #editNameModal .modal-dialog + #editSettingsModal .modal-dialog background-color: white - max-width: 400px + font-size: 14pt .edit-description-input width: 100% .edit-name-input - width: 100% + width: 50% .member-header cursor: pointer diff --git a/app/styles/courses/mock1/courses.sass b/app/styles/courses/mock1/courses.sass index 41d6f2f17..a652bff20 100644 --- a/app/styles/courses/mock1/courses.sass +++ b/app/styles/courses/mock1/courses.sass @@ -1,5 +1,8 @@ #courses-view + .btn-continue + margin-top: 40px + .center text-align: center diff --git a/app/styles/home.sass b/app/styles/home.sass index f12074e9f..bd1678ae1 100644 --- a/app/styles/home.sass +++ b/app/styles/home.sass @@ -2,7 +2,29 @@ @import "app/styles/bootstrap/variables" #home-view - + + #kids-coding-container + $coding-image-size: 272px + position: relative + width: 1000px + $coding-image-size + margin: 0px auto + + @media screen and ( max-width: 1400px ) + display: none + + .kid-coding + position: absolute + z-index: 1 + top: -57px + @media screen and ( max-height: 800px ) + top: 50px + + &#boy-coding + left: -$coding-image-size / 2 + + &#girl-coding + right: -$coding-image-size / 2 + #spacer height: 626px @media screen and ( max-height: 800px ) diff --git a/app/styles/play/campaign-view.sass b/app/styles/play/campaign-view.sass index 9611133cd..10fb60465 100644 --- a/app/styles/play/campaign-view.sass +++ b/app/styles/play/campaign-view.sass @@ -365,19 +365,21 @@ $gameControlMargin: 30px &:active, &.highlighted @include box-shadow(0 0 20px white) - &.heroes + &.items background-position: (-1 * $gameControlSize) 0px - &.achievements + &.heroes background-position: (-2 * $gameControlSize) 0px - &.account - //background-position: (-3 * $gameControlSize) 0px - background-position: (-4 * $gameControlSize) 0px - &.settings - background-position: (-4 * $gameControlSize) 0px - &.gems - background-position: (-5 * $gameControlSize) 0px - &.poll + &.achievements background-position: (-3 * $gameControlSize) 0px + &.account + //background-position: (-4 * $gameControlSize) 0px + background-position: (-5 * $gameControlSize) 0px + &.settings + background-position: (-5 * $gameControlSize) 0px + &.gems + background-position: (-6 * $gameControlSize) 0px + &.poll + background-position: (-4 * $gameControlSize) 0px .tooltip font-size: 24px diff --git a/app/styles/play/menu/inventory-modal.sass b/app/styles/play/menu/inventory-modal.sass index c7bf281de..b16e29de6 100644 --- a/app/styles/play/menu/inventory-modal.sass +++ b/app/styles/play/menu/inventory-modal.sass @@ -350,9 +350,15 @@ $itemSlotGridHeight: 51px img margin: 1px button - margin-top: -2px - height: 19px + //margin-top: -2px + //height: 19px font-size: 12px + margin: -1px 1px 1px 1px + + &:active, &.active + background-position: -57px * 2 0 + color: white + text-shadow: 0 1px 0 black, 1px 0 0 black, -1px 0 0 black, 0 -1px 0 black img width: 56px @@ -360,22 +366,32 @@ $itemSlotGridHeight: 51px display: block button - width: 100% - height: 17px - border: 1px solid rgb(46,46,46) - background: white + background: transparent url(/images/pages/play/modal/equip-buttons.png) + width: 56px + height: 21px + border: 0 + //border: 1px solid rgb(46,46,46) + //background: white font-size: 11px border-radius: 1px - padding: 0 - @include transition(0.1s ease) + padding: 0 0 2px 0 + @include transition(font-size 0.1s ease) + //text-shadow: 0 1px 0 white, 1px 0 0 white, -1px 0 0 white, 0 -1px 0 white + + text-transform: uppercase + font-weight: bold + color: black + text-spacing: 0px &.active background-color: rgb(81,153,236) button - background-color: lighten(rgb(89,136,47), 10%) + //background-color: lighten(rgb(89,136,47), 10%) box-shadow: 1px 1px 4px #333 color: white + text-shadow: 0 1px 0 black, 1px 0 0 black, -1px 0 0 black, 0 -1px 0 black + background-position: -57px 0 //.status-message .should-equip-message // display: inline @@ -594,3 +610,12 @@ $itemSlotGridHeight: 51px &.male left: -16px +body[lang='ja'], body[lang^='zh'], body[lang='ca'], body[lang^='es'], body[lang^='pt'], body[lang='ro'], body[lang='fi'], body[lang='sv'], body[lang='uk'], body[lang='vi'], body[lang='cz'] + #inventory-modal #unequipped .item button + font-weight: normal + letter-spacing: -1px + padding-bottom: 1px + +body[lang='ro'], body[lang='fi'], body[lang='uk'] + #inventory-modal #unequipped .item button + text-transform: none diff --git a/app/templates/base.jade b/app/templates/base.jade index e3eba21f4..bf21fc9a9 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -1,49 +1,50 @@ block header #site-nav - a(href="/") - img#nav-logo(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat") - div#site-nav-links + block site_nav a(href="/") - img#small-nav-logo(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat") - a(href="/") - span.glyphicon.glyphicon-home - a(href="/about", data-i18n="nav.about") - a(href='/teachers', data-i18n="nav.teachers") Teachers - a(href='/clans', data-i18n="clans.clans") Clans - a(href='http://discourse.codecombat.com/', data-i18n="nav.forum") - a(href='/community', data-i18n="nav.community") - //a(href='/play/ladder', data-i18n="home.multiplayer").multiplayer-nav-link + img#nav-logo(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat") + div#site-nav-links + a(href="/") + img#small-nav-logo(src="/images/pages/base/logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat") + a(href="/") + span.glyphicon.glyphicon-home + a(href="/about", data-i18n="nav.about") + a(href='/teachers', data-i18n="nav.teachers") Teachers + a(href='/clans', data-i18n="clans.clans") Clans + a(href='http://discourse.codecombat.com/', data-i18n="nav.forum") + a(href='/community', data-i18n="nav.community") + //a(href='/play/ladder', data-i18n="home.multiplayer").multiplayer-nav-link - if me.get('anonymous') === false - span.dropdown - button.btn.btn-sm.header-font.dropdown-toggle(href="#", data-toggle="dropdown") - if me.get('photoURL') - img.account-settings-image(src=me.getPhotoURL(18), alt="") - else - i.glyphicon.glyphicon-user - span.spl.spr(data-i18n="nav.account" href="/account") - span.caret - ul.dropdown-menu(role="menu") - li.user-dropdown-header - span.user-level= me.level() - a(href="/user/#{me.getSlugOrID()}") - div.img-circle(style="background-image: url(#{me.getPhotoURL()})") - h3=me.displayName() - li - a(href="/user/#{me.getSlugOrID()}" data-i18n="nav.profile") - li - a(href="/account/settings", data-i18n="play.settings") - li - a(href="/account/payments", data-i18n="account.payments") - li - a(href="/account/subscription", data-i18n="account.subscription") - li - a#logout-button(data-i18n="login.log_out") - - else - button.btn.btn-sm.btn-primary.header-font.signup-button(data-i18n="login.sign_up") - button.btn.btn-sm.btn-default.header-font.login-button(data-i18n="login.log_in") - select.language-dropdown.form-control + if me.get('anonymous') === false + span.dropdown + button.btn.btn-sm.header-font.dropdown-toggle(href="#", data-toggle="dropdown") + if me.get('photoURL') + img.account-settings-image(src=me.getPhotoURL(18), alt="") + else + i.glyphicon.glyphicon-user + span.spl.spr(data-i18n="nav.account" href="/account") + span.caret + ul.dropdown-menu(role="menu") + li.user-dropdown-header + span.user-level= me.level() + a(href="/user/#{me.getSlugOrID()}") + div.img-circle(style="background-image: url(#{me.getPhotoURL()})") + h3=me.displayName() + li + a(href="/user/#{me.getSlugOrID()}" data-i18n="nav.profile") + li + a(href="/account/settings", data-i18n="play.settings") + li + a(href="/account/payments", data-i18n="account.payments") + li + a(href="/account/subscription", data-i18n="account.subscription") + li + a#logout-button(data-i18n="login.log_out") + + else + button.btn.btn-sm.btn-primary.header-font.signup-button(data-i18n="login.sign_up") + button.btn.btn-sm.btn-default.header-font.login-button(data-i18n="login.log_in") + select.language-dropdown.form-control block outer_content diff --git a/app/templates/courses/mock1/course-details.jade b/app/templates/courses/mock1/course-details.jade index 44e4c8a79..5c2a01c65 100644 --- a/app/templates/courses/mock1/course-details.jade +++ b/app/templates/courses/mock1/course-details.jade @@ -5,7 +5,6 @@ block content //- DO NOT localize / i18n div TODO: fix ugly tabs - div TODO: not enrolled yet div span *UNDER CONSTRUCTION, send feedback to a.spl(href='mailto:team@codecombat.com') team@codecombat.com @@ -14,169 +13,183 @@ block content span.spl Student view div(style='border-bottom: 1px solid black;') - .modal#editNameModal + .modal#editSettingsModal .modal-dialog .modal-header button.close(data-dismiss='modal') span × - h3.modal-title Edit Class Name + h3.modal-title Edit Class Settings .modal-body - p This title will be displayed to everyone enrolled in this class. - input.edit-name-input(type='text', value="#{instance.name}") + p This title will be displayed to everyone in the class. + p + input.edit-name-input(type='text', value="#{instance.name}") + p This description will be displayed to everyone in the class. + p + textarea.edit-description-input(rows=2)= instance.description + p Select programming languages available to the class: + p + select.form-control.select-language + option(value="Python") Python + option(value="JavaScript") JavaScript + option(value="All Languages") All Languages + p + input(type='checkbox', checked) + span.spl Show student progress to everyone in the class .modal-footer - button.btn.edit-name-save-btn(data-i18n="common.save_changes") + button.btn.btn-save-settings(data-i18n="common.save_changes") - .modal#editDescriptionModal - .modal-dialog - .modal-header - button.close(data-dismiss='modal') - span × - h3.modal-title Edit Class Description - .modal-body - p This description will be displayed to everyone enrolled in this class. - textarea.edit-description-input(rows=2)= instance.description - .modal-footer - button.btn.edit-description-save-btn(data-i18n="common.save_changes") - - h1= course.title - p= course.description - - h3= instance.name - if !studentMode - span.spl - button.btn.btn-xs.edit-class-name-btn(data-toggle='modal', data-target='#editNameModal') edit class name + h1= instance.name + small.spl (#{course.title}) p if instance.description each line in instance.description.split('\n') div= line - if !studentMode - span.spl - button.btn.btn-xs.edit-description-btn(data-toggle='modal', data-target='#editDescriptionModal') edit class description - else if !studentMode - div - button.btn.btn-xs.edit-description-btn add class description if !studentMode - .form-group - span.spr Select language - select.form-control.select-language - option(value="Python") Python - option(value="JavaScript") JavaScript - option(value="All Languages") All Languages + p + button.btn.btn-xs.btn-edit-settings(data-toggle='modal', data-target='#editSettingsModal') edit class settings div(role='tabpanel') ul.nav.nav-tabs(role='tablist') - if !studentMode + if studentMode + li.active(role='presentation') + a(href='#levels', aria-controls='levels', role='tab', data-toggle='tab') Levels + li(role='presentation') + a(href='#progress', aria-controls='progress', role='tab', data-toggle='tab') Class + else li.active(role='presentation') a(href='#progress', aria-controls='progress', role='tab', data-toggle='tab') Class li(role='presentation') a(href='#invite', aria-controls='invite', role='tab', data-toggle='tab') Add Students - li(role='presentation') - a(href='#levels', aria-controls='levels', role='tab', data-toggle='tab') Levels + li(role='presentation') + a(href='#levels', aria-controls='levels', role='tab', data-toggle='tab') Levels .tab-content - if !studentMode + if studentMode + .tab-pane.active#levels(role='tabpanel') + +levels-tab + .tab-pane#progress(role='tabpanel') + +progress-tab + else .tab-pane.active#progress(role='tabpanel') - if instance.students - h3 Summary - - var averagePlaytime = Math.round(Math.random() * 30) + 30 - p - strong(style='font-size:12pt;') Average Play Time - p #{averagePlaytime} minutes - p - strong(style='font-size:12pt;') Concepts Covered - table.table.table-condensed + +progress-tab + .tab-pane#invite(role='tabpanel') + br + p Invite students to join this class. + if course.title !== 'Introduction to Computer Science' + p Student unlock code: #{instance.code} + p Class capacity: 34/50 + textarea.textarea-emails(rows=3, placeholder="Enter student emails to invite, one per line") + div(style='margin-top:10px;') + button.btn.btn-success.btn-invite Send Invites + .tab-pane#levels(role='tabpanel') + +levels-tab + +mixin progress-tab + if instance.students + .container-fluid.summary-container + .row + .col-md-6 + h3 Statistics + table.statistics-container + tr + td Total students: + td #{instance.students.length} + tr + td Average level play time: + td #{stats.averageLevelPlaytime} seconds + tr + td Total play time: + td #{stats.totalPlayTime} seconds + tr + td Average levels completed: + td #{stats.averageLevelsCompleted} + tr + td Total levels completed: + td #{stats.totalLevelsCompleted} + tr + td Last level completed: + td #{stats.lastLevelCompleted} + .col-md-6 + h3 Concepts Covered + table.table-concepts-summary each concept in courseConcepts - var conceptCompletion = Math.round(parseFloat(conceptsCompleted[concept]) / instance.students.length * 100) if isNaN(conceptCompletion) - conceptCompletion = 0 tr td.concept-completion-container + span.concept-summary(style="width:#{conceptCompletion}%;") span.concept-completed-foreground(data-i18n="concepts." + concept) span.spl - #{conceptCompletion}% - span.concept-completion-background(style="width:#{conceptCompletion}%;") - h3 Students - table.table.table-condensed - thead - tr - th - span.member-header.spr Name - if memberSort === 'nameAsc' - span.member-header.glyphicon.glyphicon-chevron-up - else if memberSort === 'nameDesc' - span.member-header.glyphicon.glyphicon-chevron-down - th - span.progress-header.spr Progress - if memberSort === 'progressAsc' - span.progress-header.glyphicon.glyphicon-chevron-up - else if memberSort === 'progressDesc' - span.progress-header.glyphicon.glyphicon-chevron-down - else - span(style='padding-left:16px;') - span.progress-key.progress-key-complete complete - span.progress-key.progress-key-started started - span.progress-key not started - if maxLastStartedIndex > 30 - input.expand-progress-checkbox(type='checkbox') - span.spl.expand-progress-label(data-i18n="clans.exp_levels") Expand levels - tbody - each student in instance.students - tr - td - a= student - td.progress-cell - .level-progression-concepts Concepts - each concept in courseConcepts - if userConceptsMap[student] && userConceptsMap[student][concept] === 'complete' - span.spr.progress-concept-cell.progress-concept-cell-complete(data-i18n="concepts." + concept) - else if userConceptsMap[student] && userConceptsMap[student][concept] === 'started' - span.spr.progress-concept-cell.progress-concept-cell-started(data-i18n="concepts." + concept) - else - span.spr.progress-concept-cell.progress-concept-cell-not-started(data-i18n="concepts." + concept) - .level-progression-levels Levels - - var i = 0 - each level in course.levels - if userLevelStateMap[student][level] === 'complete' - span.progress-level-cell.progress-level-cell-complete #{i + 1} - if showExpandedProgress || i === 0 || i === course.levels.length - 1 - span.spl #{level} - .level-popup-container - h3 #{i + 1}. #{level} - p - div - - var playTime = Math.round(Math.random() * 600) - span Time to solve - span : #{playTime} seconds - div - - var completionDate = new Date() - - completionDate.setUTCDate(completionDate.getUTCDate() - Math.round(Math.random() * 60)) - span Completed on - span : #{moment(completionDate).format('MMMM Do YYYY, h:mm:ss a')} - strong(data-i18n="clans.view_solution") Click to view solution. - else if userLevelStateMap[student][level] === 'started' - span.progress-level-cell.progress-level-cell-started #{i + 1} #{level} - else - span.progress-level-cell.level-progression-level-not-started #{i + 1} - if showExpandedProgress || i === 0 - span.spl #{level} - - i++ + h3 Students + table.table.table-condensed + thead + tr + th + span.member-header.spr Name + if memberSort === 'nameAsc' + span.member-header.glyphicon.glyphicon-chevron-up + else if memberSort === 'nameDesc' + span.member-header.glyphicon.glyphicon-chevron-down + th + span.progress-header.spr Progress + if memberSort === 'progressAsc' + span.progress-header.glyphicon.glyphicon-chevron-up + else if memberSort === 'progressDesc' + span.progress-header.glyphicon.glyphicon-chevron-down + else + span(style='padding-left:16px;') + span.progress-key.progress-key-complete complete + span.progress-key.progress-key-started started + span.progress-key not started + if maxLastStartedIndex > 30 + input.expand-progress-checkbox(type='checkbox') + span.spl.expand-progress-label(data-i18n="clans.exp_levels") Expand levels + tbody + each student in instance.students + tr + td + a= student + td.progress-cell + .level-progression-concepts Concepts + each concept in courseConcepts + if userConceptsMap[student] && userConceptsMap[student][concept] === 'complete' + span.spr.progress-concept-cell.progress-concept-cell-complete(data-i18n="concepts." + concept) + else if userConceptsMap[student] && userConceptsMap[student][concept] === 'started' + span.spr.progress-concept-cell.progress-concept-cell-started(data-i18n="concepts." + concept) + else + span.spr.progress-concept-cell.progress-concept-cell-not-started(data-i18n="concepts." + concept) - .tab-pane#invite(role='tabpanel') - p Invite students to join this class. - if course.title !== 'Introduction to Computer Science' - p Student unlock code: #{instance.code} - p Class capacity: 34/50 - textarea.textarea-emails(rows=3, placeholder="Enter student emails to invite, one per line") - div - button.btn.btn-success.btn-invite Send Invites - - .tab-pane#levels(role='tabpanel') - +levels-tab - else - .tab-pane.active#levels(role='tabpanel') - +levels-tab + .level-progression-levels Levels + - var i = 0 + each level in course.levels + if userLevelStateMap[student][level] === 'complete' + span.progress-level-cell.progress-level-cell-complete #{i + 1} + if showExpandedProgress || i === 0 || i === course.levels.length - 1 + span.spl #{level} + .level-popup-container + h3 #{i + 1}. #{level} + p + div + - var playTime = Math.round(Math.random() * 600) + span Time to solve + span : #{playTime} seconds + div + - var completionDate = new Date() + - completionDate.setUTCDate(completionDate.getUTCDate() - Math.round(Math.random() * 60)) + span Completed on + span : #{moment(completionDate).format('MMMM Do YYYY, h:mm:ss a')} + strong(data-i18n="clans.view_solution") Click to view solution. + else if userLevelStateMap[student][level] === 'started' + span.progress-level-cell.progress-level-cell-started #{i + 1} #{level} + else + span.progress-level-cell.level-progression-level-not-started #{i + 1} + if showExpandedProgress || i === 0 + span.spl #{level} + - i++ mixin levels-tab table.table.table-striped.table-condensed diff --git a/app/templates/courses/mock1/courses.jade b/app/templates/courses/mock1/courses.jade index d80fe04e1..2fd547b8d 100644 --- a/app/templates/courses/mock1/courses.jade +++ b/app/templates/courses/mock1/courses.jade @@ -29,7 +29,7 @@ block content each inst in instances option= inst.name .col-md-4 - button.btn.btn-success.btn-enter(data-course-id="#{courseID}") Enter + button.btn.btn-success.btn-enter Enter .row.button-row.center.row-pick-class .col-md-12 div.or Or @@ -43,39 +43,34 @@ block content .col-md-8 input.code-input(type='text', placeholder="Enter unlock code") .col-md-4 - button.btn.btn-success.btn-enroll(data-course-id="#{courseID}") Enroll + button.btn.btn-success.btn-enroll Enroll .row.button-row.center .col-md-12 div.or Or .row.button-row.center .col-md-12 - button.btn.btn-success.btn-lg.btn-buy(data-course-id="#{courseID}") Buy this course + button.btn.btn-success.btn-lg.btn-buy Buy this course - h1.center Courses + h1.center Courses on CodeCombat .info-container - //- p.center.gameplay-img-container - //- img(src="/images/pages/courses/101_info.png" width='800') - p A course is a selection of CodeCombat levels designed to introduce computer science concepts in a fun and engaging environment. + p Courses are designed to introduce computer science concepts using CodeCombat's fun and engaging environment. CodeCombat levels are organized around key topics to encourage progressive learning, over the course of 5 hours. + .container-fluid - .row - .col-md-12 - p.center - button.btn.btn-success.btn-lg.btn-buy Enroll Now! .row .col-md-6 - div With CodeCombat courses, you and your students will: ul li Learn more in less time - li With no experience necesssary - li Monitor student progress - br + li No coding experience necesssary + li Easily monitor student progress + + div Purchase a course for your entire class. It's easy to sign up your students! .col-md-6 - p + .well.well-sm div.praise-quote "#{praise.quote}" div.caption-text - #{praise.source} - //- h2.center Available Courses + h2.center Choose Your Course: .container-fluid - var i = 0 @@ -95,13 +90,13 @@ mixin course-block(course, courseID) span.spr #{course.title} strong #{course.unlocked ? '[ enrolled ]' : ''} .panel-body - strong Topics - ul - each topic in course.topics - li= topic - strong Hours of content: #{course.duration} .container-fluid - .row.button-row.center - .col-md-12 - br - button.btn.btn-success.btn-continue(data-toggle='modal', data-target="#continueModal", data-course-title="#{course.title}", data-course-id="#{courseID}") #{course.unlocked ? 'Continue' : 'Enter'} + .row.button-row + .col-md-6 + strong Topics + ul + each topic in course.topics + li= topic + strong Hours of content: #{course.duration} + .col-md-6.center + button.btn.btn-lg.btn-success.btn-continue(data-toggle='modal', data-target="#continueModal", data-course-title="#{course.title}", data-course-id="#{courseID}") #{course.unlocked ? 'Continue' : 'Enter'} diff --git a/app/templates/home-view.jade b/app/templates/home-view.jade index 8ad164d34..28a136c55 100644 --- a/app/templates/home-view.jade +++ b/app/templates/home-view.jade @@ -1,5 +1,10 @@ extends /templates/base +block append site_nav + #kids-coding-container + img#boy-coding.kid-coding(src="/images/pages/home/boy_coding.png", alt="", draggable="false") + img#girl-coding.kid-coding(src="/images/pages/home/girl_coding.png", alt="", draggable="false") + block outer_content #spacer diff --git a/app/templates/play/campaign-view.jade b/app/templates/play/campaign-view.jade index 5eec34f1e..53bb2bb36 100644 --- a/app/templates/play/campaign-view.jade +++ b/app/templates/play/campaign-view.jade @@ -90,6 +90,7 @@ else .game-controls.header-font 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") @@ -118,9 +119,6 @@ else 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 - span.spl - a.btn.btn-illustrated(href='/clans', data-i18n="clans.clans") Clans - button.btn.btn-lg.btn-inverse.campaign-control-button#volume-button(data-i18n="[title]play.adjust_volume", title="Adjust volume") .glyphicon.glyphicon-volume-off diff --git a/app/views/courses/mock1/CourseDetailsView.coffee b/app/views/courses/mock1/CourseDetailsView.coffee index fddd6360b..dd9ec574b 100644 --- a/app/views/courses/mock1/CourseDetailsView.coffee +++ b/app/views/courses/mock1/CourseDetailsView.coffee @@ -10,11 +10,9 @@ module.exports = class CourseDetailsView extends RootView events: 'change .expand-progress-checkbox': 'onExpandedProgressCheckbox' - 'change .select-session': 'onChangeSession' 'change .student-mode-checkbox': 'onChangeStudent' 'click .btn-play-level': 'onClickPlayLevel' - 'click .edit-description-save-btn': 'onEditDescriptionSave' - 'click .edit-name-save-btn': 'onEditNameSave' + 'click .btn-save-settings': 'onClickSaveSettings' 'click .member-header': 'onClickMemberHeader' 'click .progress-header': 'onClickProgressHeader' 'mouseenter .progress-level-cell': 'onMouseEnterPoint' @@ -49,6 +47,14 @@ module.exports = class CourseDetailsView extends RootView conceptsCompleted[concept]++ context.conceptsCompleted = conceptsCompleted + stats = + averageLevelPlaytime: _.random(30, 240) + averageLevelsCompleted: _.random(1, @course.levels.length) + stats.totalPlayTime = context.instance.students?.length * stats.averageLevelPlaytime ? 0 + stats.totalLevelsCompleted = context.instance.students?.length * stats.averageLevelsCompleted ? 0 + stats.lastLevelCompleted = @course.levels[@maxLastStartedIndex] ? @course.levels[@course.levels.length - 1] + context.stats = stats + context initData: -> @@ -144,26 +150,12 @@ module.exports = class CourseDetailsView extends RootView @render?() $('.student-mode-checkbox').attr('checked', @options.studentMode) - onChangeSession: (e) -> - @showExpandedProgress = false - newSessionValue = $(e.target).val() - for val, index in @instances when val.name is newSessionValue - @currentInstanceIndex = index - @updateLevelMaps() - @onCampaignSync() - onExpandedProgressCheckbox: (e) -> @showExpandedProgress = $('.expand-progress-checkbox').prop('checked') # TODO: why does render reset the checkbox to be unchecked? @render?() $('.expand-progress-checkbox').attr('checked', @showExpandedProgress) - onClickEditClassName: (e) -> - alert 'TODO: Popup for editing name for this course session' - - onClickEditClassDescription: (e) -> - alert 'TODO: Popup for editing description for this course session' - onClickMemberHeader: (e) -> @memberSort = if @memberSort is 'nameAsc' then 'nameDesc' else 'nameAsc' @sortMembers() @@ -183,16 +175,12 @@ module.exports = class CourseDetailsView extends RootView viewArgs: [{}, levelSlug] } - onEditDescriptionSave: (e) -> - description = $('.edit-description-input').val() - @instances[@currentInstanceIndex].description = description - $('#editDescriptionModal').modal('hide') - @render?() - - onEditNameSave: (e) -> + onClickSaveSettings: (e) -> if name = $('.edit-name-input').val() @instances[@currentInstanceIndex].name = name - $('#editNameModal').modal('hide') + description = $('.edit-description-input').val() + @instances[@currentInstanceIndex].description = description + $('#editSettingsModal').modal('hide') @render?() onMouseEnterPoint: (e) -> diff --git a/app/views/courses/mock1/CoursesMockData.coffee b/app/views/courses/mock1/CoursesMockData.coffee index 6ddedf593..26d58fd47 100644 --- a/app/views/courses/mock1/CoursesMockData.coffee +++ b/app/views/courses/mock1/CoursesMockData.coffee @@ -40,7 +40,7 @@ data.courses = [ image: '/images/pages/courses/102_info.png' }, { - title: 'CS Course 3' + title: 'Computer Science 3' description: 'Learn how to handle input.' topics: ['If Statements', 'Arithmetic', 'Input Handling'] duration: 5 @@ -48,7 +48,7 @@ data.courses = [ image: '/images/pages/courses/103_info.png' }, { - title: 'CS 4' + title: 'Computer Science 4' description: 'Time to tackle arrays and some pvp stuff.' topics: ['Loops', 'Break Statements', 'Arrays'] duration: 5 @@ -56,7 +56,7 @@ data.courses = [ image: '/images/pages/courses/104_info.png' }, { - title: 'Course 5' + title: 'Computer Science 5' description: 'Time to tackle arrays and some PVP.' topics: ['Break Statements', 'Arrays', 'Object Literals'] duration: 5 @@ -64,7 +64,7 @@ data.courses = [ image: '/images/pages/courses/105_info.png' }, { - title: 'Course 6' + title: 'Computer Science 6' description: 'For loops!' topics: ['Break Statements', 'Object Literals', 'For loops'] duration: 5 @@ -72,7 +72,7 @@ data.courses = [ image: '/images/pages/courses/106_info.png' }, { - title: 'Course 7' + title: 'Computer Science 7' description: 'Functions!' topics: ['Object Literals', 'For loops', 'Functions'] duration: 5 @@ -80,7 +80,7 @@ data.courses = [ image: '/images/pages/courses/107_info.png' }, { - title: 'CS 108' + title: 'Computer Science 8' description: 'Maths.' topics: ['For loops', 'Functions', 'Math Operations'] duration: 5 @@ -88,7 +88,7 @@ data.courses = [ image: '/images/pages/courses/107_info.png' }, { - title: 'Computer Science 109' + title: 'Computer Science 9' description: 'Vectors and strings.' topics: ['Vectors', 'Advanced Strings'] duration: 5 diff --git a/app/views/courses/mock1/CoursesView.coffee b/app/views/courses/mock1/CoursesView.coffee index b6f49c607..45df18359 100644 --- a/app/views/courses/mock1/CoursesView.coffee +++ b/app/views/courses/mock1/CoursesView.coffee @@ -51,6 +51,10 @@ module.exports = class CoursesView extends RootView $('#continueModal').find('.btn-enroll').data('course-id', courseID) $('#continueModal').find('.btn-enter').data('course-id', courseID) $('#continueModal .row-pick-class').show() if @courses[courseID]?.unlocked + if courseTitle is 'Introduction to Computer Science' + $('#continueModal .btn-buy').prop('innerText', 'Get this FREE course!') + else + $('#continueModal .btn-buy').prop('innerText', 'Buy this course') onClickEnroll: (e) -> $('#continueModal').modal('hide') @@ -65,7 +69,8 @@ module.exports = class CoursesView extends RootView $('#continueModal').modal('hide') courseID = $(e.target).data('course-id') instanceName = $('.select-session').val() - instanceID = index for val, index in @instances when val.name is instanceName + for val, index in @instances when val.name is instanceName + instanceID = index viewClass = require 'views/courses/mock1/CourseDetailsView' viewArgs = [{}, courseID, instanceID] navigationEvent = route: "/courses/mock1/#{courseID}", viewClass: viewClass, viewArgs: viewArgs diff --git a/app/views/play/CampaignView.coffee b/app/views/play/CampaignView.coffee index f5d50e172..f0eb003b6 100644 --- a/app/views/play/CampaignView.coffee +++ b/app/views/play/CampaignView.coffee @@ -30,7 +30,7 @@ class LevelSessionsCollection extends CocoCollection constructor: (model) -> super() - @url = "/db/user/#{me.id}/level.sessions?project=state.complete,levelID,state.difficulty" + @url = "/db/user/#{me.id}/level.sessions?project=state.complete,levelID,state.difficulty,playtime" class CampaignsCollection extends CocoCollection url: '/db/campaign' @@ -261,6 +261,7 @@ module.exports = class CampaignView extends RootView annotateLevel: (level) -> level.position ?= { x: 10, y: 10 } level.locked = not me.ownsLevel level.original + level.locked = true if level.slug is 'kithgard-mastery' and @calculateExperienceScore() is 0 level.locked = false if @levelStatusMap[level.slug] in ['started', 'complete'] level.locked = false if @editorMode level.locked = false if @campaign?.get('name') is 'Auditions' @@ -302,12 +303,22 @@ module.exports = class CampaignView extends RootView determineNextLevel: (levels) -> foundNext = false + dontPointTo = ['lost-viking', 'kithgard-mastery'] # Challenge levels we don't want most players bashing heads against for level in levels + # Iterate through all levels in order and look to find the first unlocked one that meets all our criteria for being pointed out as the next level. level.nextLevels = (reward.level for reward in level.rewards ? [] when reward.level) unless foundNext for nextLevelOriginal in level.nextLevels nextLevel = _.find levels, original: nextLevelOriginal - dontPointTo = ['lost-viking','kithgard-mastery'] + + # If it's a challenge level, we efficiently determine whether we actually do want to point it out. + if nextLevel and nextLevel.slug is 'kithgard-mastery' and not nextLevel.locked and not @levelStatusMap[nextLevel.slug] and @calculateExperienceScore() >= 3 + unless (timesPointedOut = storage.load("pointed-out-#{nextLevel.slug}") or 0) > 3 + # We may determineNextLevel more than once per render, so we can't just do this once. But we do give up after a couple highlights. + dontPointTo = _.without dontPointTo, nextLevel.slug + storage.save "pointed-out-#{nextLevel.slug}", timesPointedOut + 1 + + # Should we point this level out? if nextLevel and not nextLevel.locked and not nextLevel.disabled and @levelStatusMap[nextLevel.slug] isnt 'complete' and nextLevel.slug not in dontPointTo and not nextLevel.replayable and ( me.isPremium() or not nextLevel.requiresSubscription or @@ -320,6 +331,15 @@ module.exports = class CampaignView extends RootView if not foundNext and levels[0] and not levels[0].locked and @levelStatusMap[levels[0].slug] isnt 'complete' levels[0].next = true + calculateExperienceScore: -> + adultPoint = me.get('ageRange') in ['18-24', '25-34', '35-44', '45-100'] # They have to have answered the poll for this, likely after Shadow Guard. + speedPoints = 0 + for [levelSlug, speedThreshold] in [['dungeons-of-kithgard', 50], ['gems-in-the-deep', 55], ['shadow-guard', 55], ['forgetful-gemsmith', 40], ['true-names', 40]] + if _.find(@sessions.models, (session) -> session.get('levelID') is levelSlug)?.attributes.playtime <= speedThreshold + ++speedPoints + experienceScore = adultPoint + speedPoints # 0-6 score of how likely we think they are to be experienced and ready for Kithgard Mastery + return experienceScore + createLine: (o1, o2) -> p1 = x: o1.x, y: 0.66 * o1.y + 0.5 p2 = x: o2.x, y: 0.66 * o2.y + 0.5 diff --git a/app/views/play/level/modal/HeroVictoryModal.coffee b/app/views/play/level/modal/HeroVictoryModal.coffee index 94a0fa1bc..85bff49ca 100644 --- a/app/views/play/level/modal/HeroVictoryModal.coffee +++ b/app/views/play/level/modal/HeroVictoryModal.coffee @@ -141,7 +141,8 @@ module.exports = class HeroVictoryModal extends ModalView getRenderData: -> c = super() c.levelName = utils.i18n @level.attributes, 'name' - c.victoryText = utils.i18n @level.get('victory') ? {}, 'body' + if @level.get('type', true) isnt 'hero' + c.victoryText = utils.i18n @level.get('victory') ? {}, 'body' earnedAchievementMap = _.indexBy(@newEarnedAchievements or [], (ea) -> ea.get('achievement')) for achievement in (@achievements?.models or []) earnedAchievement = earnedAchievementMap[achievement.id] diff --git a/app/views/play/modal/PollModal.coffee b/app/views/play/modal/PollModal.coffee index a4fe1d589..3682e2d0e 100644 --- a/app/views/play/modal/PollModal.coffee +++ b/app/views/play/modal/PollModal.coffee @@ -73,7 +73,7 @@ module.exports = class PollModal extends ModalView onClickAnswer: (e) -> $selectedAnswer = $(e.target).closest('.answer') pollVotes = @userPollsRecord.get('polls') ? {} - pollVotes[@poll.id] = $selectedAnswer.data('answer') + pollVotes[@poll.id] = $selectedAnswer.data('answer').toString() @userPollsRecord.set 'polls', pollVotes @updateAnswers true @userPollsRecord.save {polls: pollVotes}, {success: => @awardRandomGems?()} diff --git a/scripts/analytics/mongodb/queries/averageLevelPlaytimes.js b/scripts/analytics/mongodb/queries/averageLevelPlaytimes.js index 0a7f18330..d3a889202 100644 --- a/scripts/analytics/mongodb/queries/averageLevelPlaytimes.js +++ b/scripts/analytics/mongodb/queries/averageLevelPlaytimes.js @@ -11,8 +11,8 @@ var excludedLevels = ['deadly-dungeon-rescue', 'kithgard-brawl', 'cavern-survival', 'kithgard-mastery', 'destroying-angel', 'kithgard-apprentice', 'wild-horses', 'lost-viking', 'forest-flower-grove', 'boulder-woods', 'the-trials']; var scriptStartTime = new Date(); -var startDay = '2015-05-10'; -var endDay = '2015-06-11'; +var startDay = '2015-07-01'; +var endDay = '2015-08-06'; log("Dates: " + startDay + " to " + endDay); diff --git a/scripts/analytics/mongodb/queries/teacherSurveyCounts.js b/scripts/analytics/mongodb/queries/teacherSurveyCounts.js new file mode 100644 index 000000000..2bae4a745 --- /dev/null +++ b/scripts/analytics/mongodb/queries/teacherSurveyCounts.js @@ -0,0 +1,25 @@ + // Print out teacher survey counts by day + +// Usage: +// mongo
:/