Merge remote-tracking branch 'refs/remotes/codecombat/master' into i18nTags-new-homepage
BIN
app/assets/images/pages/about/bracket_left.png
Executable file
After ![]() (image error) Size: 2.7 KiB |
BIN
app/assets/images/pages/about/bracket_right.png
Executable file
After ![]() (image error) Size: 2.7 KiB |
BIN
app/assets/images/pages/about/character_silouhette.png
Executable file
After ![]() (image error) Size: 7.8 KiB |
Before ![]() (image error) Size: 77 KiB |
BIN
app/assets/images/pages/about/codebackground_zoom_compressed.png
Normal file
After ![]() (image error) Size: 1.2 MiB |
BIN
app/assets/images/pages/about/desert.png
Executable file
After ![]() (image error) Size: 344 KiB |
BIN
app/assets/images/pages/about/dungeon.png
Executable file
After ![]() (image error) Size: 455 KiB |
BIN
app/assets/images/pages/about/forest.png
Executable file
After ![]() (image error) Size: 765 KiB |
BIN
app/assets/images/pages/about/github.png
Executable file
After ![]() (image error) Size: 6.5 KiB |
BIN
app/assets/images/pages/about/github_avatars.png
Normal file
After ![]() (image error) Size: 294 KiB |
BIN
app/assets/images/pages/about/glacier.png
Executable file
After ![]() (image error) Size: 1,018 KiB |
BIN
app/assets/images/pages/about/globe_green.png
Executable file
After ![]() (image error) Size: 11 KiB |
BIN
app/assets/images/pages/about/globe_white.png
Executable file
After ![]() (image error) Size: 7.9 KiB |
BIN
app/assets/images/pages/about/languages.png
Executable file
After ![]() (image error) Size: 24 KiB |
BIN
app/assets/images/pages/about/languages_group1.png
Executable file
After ![]() (image error) Size: 12 KiB |
BIN
app/assets/images/pages/about/languages_group2.png
Executable file
After ![]() (image error) Size: 12 KiB |
BIN
app/assets/images/pages/about/screenshot_desert.png
Executable file
After ![]() (image error) Size: 90 KiB |
BIN
app/assets/images/pages/about/screenshot_dungeon.png
Executable file
After ![]() (image error) Size: 89 KiB |
BIN
app/assets/images/pages/about/screenshot_forest.png
Executable file
After ![]() (image error) Size: 144 KiB |
BIN
app/assets/images/pages/about/screenshot_glacier.png
Executable file
After ![]() (image error) Size: 147 KiB |
BIN
app/assets/images/pages/about/sketch.png
Executable file
After ![]() (image error) Size: 192 KiB |
|
@ -83,8 +83,8 @@ module.exports.setErrorToProperty = setErrorToProperty = (el, property, message,
|
|||
setErrorToField input, message, warning
|
||||
|
||||
module.exports.scrollToFirstError = ($el=$('body')) ->
|
||||
$first = $el.find('.has-error, .alert-danger, .error-help-block, .has-warning, .alert-warning, .warning-help-block').first()
|
||||
$('body').animate({ scrollTop: $first.offset().top - 20 }, 300)
|
||||
$first = $el.find('.has-error, .alert-danger, .error-help-block, .has-warning, .alert-warning, .warning-help-block').filter(':visible').first()
|
||||
$('html, body').animate({ scrollTop: $first.offset().top - 20 }, 300)
|
||||
|
||||
module.exports.clearFormAlerts = (el) ->
|
||||
$('.has-error', el).removeClass('has-error')
|
||||
|
|
|
@ -25,7 +25,7 @@ module.exports = class LevelSetupManager extends CocoClass
|
|||
loadLevel: ->
|
||||
levelURL = "/db/level/#{@options.levelID}"
|
||||
@level = new Level().setURL levelURL
|
||||
@level = @supermodel.loadModel(@level, 'level').model
|
||||
@level = @supermodel.loadModel(@level).model
|
||||
if @level.loaded then @onLevelSync() else @listenToOnce @level, 'sync', @onLevelSync
|
||||
|
||||
loadSession: ->
|
||||
|
@ -33,7 +33,7 @@ module.exports = class LevelSetupManager extends CocoClass
|
|||
#sessionURL += "?team=#{@team}" if @options.team # TODO: figure out how to get the teams for multiplayer PVP hero style
|
||||
sessionURL += "?course=#{@options.courseID}" if @options.courseID
|
||||
@session = new LevelSession().setURL sessionURL
|
||||
@session = @supermodel.loadModel(@session, 'level_session').model
|
||||
@session = @supermodel.loadModel(@session).model
|
||||
if @session.loaded then @onSessionSync() else @listenToOnce @session, 'sync', @onSessionSync
|
||||
|
||||
onLevelSync: ->
|
||||
|
|
|
@ -31,7 +31,6 @@ module.exports = class SpriteBuilder
|
|||
for func in tweenData
|
||||
args = _.cloneDeep(func.a)
|
||||
@dereferenceArgs(args, locals)
|
||||
console.log 'found', args, 'for', func.n
|
||||
tween = tween[func.n](args...)
|
||||
anim.timeline.addTween(tween)
|
||||
|
||||
|
|
|
@ -145,10 +145,10 @@ module.exports.thangNames = thangNames =
|
|||
]
|
||||
'Raven': [
|
||||
# Animal
|
||||
'Nevermore'
|
||||
'Baltimore'
|
||||
'Columbia'
|
||||
'Dawnstar'
|
||||
'Nevermore'
|
||||
]
|
||||
'Cougar': [
|
||||
# Animal
|
||||
|
@ -160,33 +160,35 @@ module.exports.thangNames = thangNames =
|
|||
'Frog': [
|
||||
# Animal
|
||||
'Bighead'
|
||||
'Hypnotoad'
|
||||
'Freddy'
|
||||
'Frogger'
|
||||
'Froggy'
|
||||
'Slippy'
|
||||
'Wart'
|
||||
'Bufo'
|
||||
'Bunda'
|
||||
'Dan\'l Webster'
|
||||
'Freddy'
|
||||
'Frogger'
|
||||
'Froggy'
|
||||
'Hypnotoad'
|
||||
'Mr. Toad'
|
||||
'Trevor'
|
||||
'Wei Qi'
|
||||
'Slippy'
|
||||
'Toada'
|
||||
'Trevor'
|
||||
'Wart'
|
||||
'Wei Qi'
|
||||
]
|
||||
'Horse': [
|
||||
# Animal
|
||||
'Abby'
|
||||
'Wildsilver'
|
||||
'Fleetfire'
|
||||
'Ed'
|
||||
'Silver'
|
||||
'Hurricane'
|
||||
'Beauty'
|
||||
'Lovelace'
|
||||
'Mirial'
|
||||
'Miracle'
|
||||
'Cinnamon'
|
||||
'Codasus'
|
||||
'Ed'
|
||||
'Fleetfire'
|
||||
'Hurricane'
|
||||
'Lovelace'
|
||||
'Miracle'
|
||||
'Mirial'
|
||||
'Powder'
|
||||
'Silver'
|
||||
'Wildsilver'
|
||||
]
|
||||
'Ogre M': [
|
||||
# Male
|
||||
|
@ -245,12 +247,16 @@ module.exports.thangNames = thangNames =
|
|||
'Haggar'
|
||||
'Heizenburg'
|
||||
'Ironjaw'
|
||||
'Mokuhr'
|
||||
'Muul'
|
||||
'Ork\'han'
|
||||
'Roast Beefy'
|
||||
'Toharg'
|
||||
'Trod'
|
||||
'Tuguro'
|
||||
'Turrok'
|
||||
'York'
|
||||
'Zabarek'
|
||||
'Zagurk'
|
||||
'Zeredd'
|
||||
]
|
||||
|
@ -280,6 +286,7 @@ module.exports.thangNames = thangNames =
|
|||
'Gom'
|
||||
'Grek'
|
||||
'Gror'
|
||||
'Grue'
|
||||
'Il\'Du\'duka'
|
||||
'Makas'
|
||||
'Mogadishu'
|
||||
|
@ -306,6 +313,7 @@ module.exports.thangNames = thangNames =
|
|||
'Bonejangles'
|
||||
'Bonesworth'
|
||||
'Bonette'
|
||||
'Boneus'
|
||||
'Doornail'
|
||||
'Drybones'
|
||||
'Grim'
|
||||
|
@ -320,8 +328,8 @@ module.exports.thangNames = thangNames =
|
|||
'Scraps'
|
||||
'Shelly'
|
||||
'Shishka-Bob'
|
||||
'Shishka-Larry'
|
||||
'Shishka-Joe'
|
||||
'Shishka-Larry'
|
||||
'Skeletor'
|
||||
'Skellington'
|
||||
'Skulldugger'
|
||||
|
@ -333,7 +341,6 @@ module.exports.thangNames = thangNames =
|
|||
'Tatava'
|
||||
'Ulna'
|
||||
'Yorick'
|
||||
'Boneus'
|
||||
]
|
||||
'Ogre Headhunter': [
|
||||
# Male
|
||||
|
|
|
@ -545,19 +545,11 @@
|
|||
editor_config_behaviors_description: "Autocompletes brackets, braces, and quotes."
|
||||
|
||||
about:
|
||||
why_codecombat: "Why CodeCombat?"
|
||||
why_paragraph_1: "If you want to learn to program, you don't need lessons. You need to write a lot of code and have a great time doing it."
|
||||
why_paragraph_2_prefix: "That's what programming is about. It's gotta be fun. Not fun like"
|
||||
why_paragraph_2_italic: "yay a badge"
|
||||
why_paragraph_2_center: "but fun like"
|
||||
why_paragraph_2_italic_caps: "NO MOM I HAVE TO FINISH THE LEVEL!"
|
||||
why_paragraph_2_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing."
|
||||
why_paragraph_3: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."
|
||||
press_title: "Bloggers/Press"
|
||||
press_paragraph_1_prefix: "Want to write about us? Feel free to download and use all of the resources included in our"
|
||||
press_paragraph_1_link: "press packet"
|
||||
press_paragraph_1_suffix: ". All logos and images may be used without contacting us directly."
|
||||
team: "Team"
|
||||
mission_title: "Our mission: make programming accessible to every student on Earth."
|
||||
mission_description_1: "Programming is magic. It's the ability to create things from pure imagination. We started CodeCombat to give learners the feeling of wizardly power at their fingertips by using typed code."
|
||||
mission_description_2: "As it turns out, that enables them to learn faster too. WAY faster. It's like having a conversation instead of reading a manual. We want to bring that conversation to every school and to every student, because everyone should have the chance to learn the magic of programming."
|
||||
team_title: "Meet the CodeCombat team"
|
||||
team_values: "We value open and respectful dialog, where the best idea wins. Our decisions are grounded in customer research and our process is focused on delivering tangible results for them. Everyone is hands-on, from our CEO to our Github contributors, because we value growth and learning in our team."
|
||||
nick_title: "Cofounder, CEO"
|
||||
nick_blurb: "Motivation Guru"
|
||||
matt_title: "Cofounder, CTO"
|
||||
|
@ -582,6 +574,49 @@
|
|||
jose_blurb: "Taking Off"
|
||||
carlos_title: "Region Manager, Brazil"
|
||||
carlos_blurb: "Celery Man"
|
||||
community_title: "...and our open-source community"
|
||||
community_subtitle: "Over 450 contributors have helped build CodeCombat, with more joining every week!"
|
||||
community_description_1: "CodeCombat is a community project, with hundreds of players volunteering to create levels, contribute to our code to add features, fix bugs, playtest, and even translate the game into 50 languages so far. Employees, contributors and the site gain by sharing ideas and pooling effort, as does the open source community in general. The site is built on numerous open source projects, and we are open sourced to give back to the community and provide code-curious players a familiar project to explore and experiment with. Anyone can join the CodeCombat community! Check out our "
|
||||
community_description_link: "contribute page "
|
||||
community_description_2: "for more info."
|
||||
story_title: "Our story so far"
|
||||
story_subtitle: "Since 2013, CodeCombat has grown from a mere set of sketches to a living, thriving game."
|
||||
story_statistic_1a: "5,000,000+"
|
||||
story_statistic_1b: "total players"
|
||||
story_statistic_2a: "We’ve been translated into over 50 languages — our players hail from"
|
||||
story_statistic_2b: "200+ countries"
|
||||
story_statistic_3a: "Together, they have written"
|
||||
story_statistic_3b: "1 billion lines of code and counting"
|
||||
story_statistic_3c: "across six different programming languages"
|
||||
story_long_way_1: "Though we've come a long way..."
|
||||
story_sketch_caption: "Nick's very first sketch depicting a programming game in action."
|
||||
story_long_way_2: "we still have much to do before we complete our quest, so..."
|
||||
jobs_title: "Come work with us and help write CodeCombat history!"
|
||||
jobs_subtitle: """Don't see a good fit but interested in keeping in touch? See our "Create Your Own" listing."""
|
||||
jobs_benefits: "Employee Benefits"
|
||||
jobs_benefit_1: "Competitive salary and options"
|
||||
jobs_benefit_2: "15 day minimum vacation policy, excluding company holidays"
|
||||
jobs_benefit_3: "Work from home flexibility"
|
||||
jobs_benefit_4: "Unlimited sick/personal days"
|
||||
jobs_benefit_5: "Professional development and continuing education support"
|
||||
jobs_benefit_6: "Medical/dental/vision insurance"
|
||||
jobs_custom_title: "Create Your Own"
|
||||
jobs_custom_description: "Are you passionate about CodeCombat but don't see a job listed that matches your qualifications? Write us and show how you think you can contribute to our team. We'd love to hear from you!"
|
||||
jobs_custom_contact_1: "Send us a note at "
|
||||
jobs_custom_contact_2: "introducing yourself and we might get in touch in the future!"
|
||||
contact_title: "Press & Contact"
|
||||
contact_subtitle: "Need more information? Get in touch with us at "
|
||||
screenshots_title: "Game Screenshots"
|
||||
screenshots_hint: "(click to view full size)"
|
||||
downloads_title: "Download Assets & Information"
|
||||
about_codecombat: "About CodeCombat"
|
||||
logo: "Logo"
|
||||
screenshots: "Screenshots"
|
||||
character_art: "Character Art"
|
||||
download_all: "Download All"
|
||||
location_title: "We're located in downtown SF:"
|
||||
|
||||
|
||||
|
||||
teachers:
|
||||
who_for_title: "Who is CodeCombat for?"
|
||||
|
|
|
@ -81,10 +81,10 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
|
|||
awaiting_levels_adventurer: "Prisijunk kaip Nuotykių Ieškotojas"
|
||||
awaiting_levels_adventurer_suffix: "kad pamatytum juos pirmas."
|
||||
adjust_volume: "Reguliuoti garsą"
|
||||
# campaign_multiplayer: "Multiplayer Arenas"
|
||||
# campaign_multiplayer_description: "... in which you code head-to-head against other players."
|
||||
# campaign_old_multiplayer: "(Deprecated) Old Multiplayer Arenas"
|
||||
# campaign_old_multiplayer_description: "Relics of a more civilized age. No simulations are run for these older, hero-less multiplayer arenas."
|
||||
campaign_multiplayer: "Daugelio žaidėjų arenos"
|
||||
campaign_multiplayer_description: "... kuriose tu prgramuoji kad įveiktum kitus žaidėjus."
|
||||
campaign_old_multiplayer: "(Pasene) Senos daugelio žaidėjų Arenos"
|
||||
campaign_old_multiplayer_description: "Civilizuoto amžiaus reliktai. Šios senos, be herojų arenos nesimuliuojamos."
|
||||
|
||||
share_progress_modal:
|
||||
blurb: "Tau puikiai sekasi! Parodyk tėvams kiek jau išmokai su CodeCombat."
|
||||
|
@ -187,7 +187,7 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
|
|||
password: "Slaptažodis"
|
||||
message: "Žinutė"
|
||||
code: "Kodas"
|
||||
# ladder: "Ladder"
|
||||
ladder: "Rezultatų lentelė"
|
||||
when: "Kai"
|
||||
opponent: "Priešininkas"
|
||||
rank: "Rangas"
|
||||
|
@ -252,7 +252,7 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
|
|||
victory_sign_up: "Užsiregistruokite, kad išsaugotumėte pažangą"
|
||||
victory_sign_up_poke: "Norite išsaugoti savo kodą? Sukurkite paskyrą nemokamai!"
|
||||
victory_rate_the_level: "Ar patiko šis lygis?"
|
||||
# victory_return_to_ladder: "Return to Ladder"
|
||||
victory_return_to_ladder: "Grįžti į razultatų lentelę"
|
||||
victory_saving_progress: "Išsaugoma pažanga"
|
||||
victory_go_home: "Į Pradžią"
|
||||
victory_review: "Papasakok daugiau!"
|
||||
|
@ -284,11 +284,11 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
|
|||
problem_alert_title: "Taisyti kodą"
|
||||
time_current: "Dabar:"
|
||||
time_total: "Max:"
|
||||
time_goto: "Eiti į:"
|
||||
time_goto: "Eik į:"
|
||||
non_user_code_problem_title: "Nepavyko pakrauti lygio"
|
||||
infinite_loop_title: "Pastebėtas begalinis ciklas!"
|
||||
# infinite_loop_description: "The initial code to build the world never finished running. It's probably either really slow or has an infinite loop. Or there might be a bug. You can either try running this code again or reset the code to the default state. If that doesn't fix it, please let us know."
|
||||
# check_dev_console: "You can also open the developer console to see what might be going wrong."
|
||||
check_dev_console: "Tu taipogi gali atsidaryti kūrėjo konsolę, kad sužinotum kas negerai."
|
||||
check_dev_console_link: "(instrukcijos)"
|
||||
infinite_loop_try_again: "Bandyk dar kartą"
|
||||
infinite_loop_reset_level: "Pradėti lygi išnaujo"
|
||||
|
@ -306,12 +306,12 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
|
|||
tip_forums: "Aplankykite forumą ir parašykite mums Jūsų nuomonę!"
|
||||
tip_baby_coders: "Ateityje ir kūdikiai bus Arkimagais."
|
||||
# tip_morale_improves: "Loading will continue until morale improves."
|
||||
# tip_all_species: "We believe in equal opportunities to learn programming for all species."
|
||||
tip_all_species: "Mes tikime lygiomis galimybėmis visoms rūšims į programavimą."
|
||||
# tip_reticulating: "Reticulating spines."
|
||||
tip_harry: "Tu esi Burtininkas, "
|
||||
# tip_great_responsibility: "With great coding skill comes great debug responsibility."
|
||||
tip_great_responsibility: "Su puikiu programavimo įgudžiu ateina ir didelė atsakomybė."
|
||||
# tip_munchkin: "If you don't eat your vegetables, a munchkin will come after you while you're asleep."
|
||||
# tip_binary: "There are only 10 types of people in the world: those who understand binary, and those who don't."
|
||||
tip_binary: "Pasaulyje yra tik 10 tipų žmonės: tie kurie supranta dvejetainius skaičius ir tie kurie ne."
|
||||
# tip_commitment_yoda: "A programmer must have the deepest commitment, the most serious mind. ~ Yoda"
|
||||
tip_no_try: "Daryk. Arba ne. Jokių 'pabandysiu'. - Yoda"
|
||||
tip_patience: "Kantrybės turėti turi, jaunasis Padavane. - Yoda"
|
||||
|
@ -399,22 +399,22 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
|
|||
recovered: "Atstatyta deimantų pirkimo operacija. Prašome pakraukite puslapį iš naujo."
|
||||
price: "x{{gems}} / mėn"
|
||||
|
||||
# subscribe:
|
||||
# comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
|
||||
# feature1: "110+ basic levels across 4 worlds"
|
||||
# feature2: "10 powerful <strong>new heroes</strong> with unique skills!"
|
||||
# feature3: "80+ bonus levels"
|
||||
# feature4: "<strong>{{gems}} bonus gems</strong> every month!"
|
||||
# feature5: "Video tutorials"
|
||||
# feature6: "Premium email support"
|
||||
# feature7: "Private <strong>Clans</strong>"
|
||||
# free: "Free"
|
||||
# month: "month"
|
||||
subscribe:
|
||||
comparison_blurb: "Pagerink savo įgudžius su CodeCombat prenumerata!"
|
||||
feature1: "110+ įprastų lygių per 4 pasaulius"
|
||||
feature2: "10 galingų <strong>naujų herojų</strong> su unikaliais įgudžiais!"
|
||||
feature3: "80+ papildomų lygių"
|
||||
feature4: "<strong>{{gems}} papildomi krystalai</strong> kiekvieną mėnesį!"
|
||||
feature5: "Video pamokos"
|
||||
feature6: "Papildomas el.pašto palaikymas"
|
||||
feature7: "Privatūs <strong>Klanai</strong>"
|
||||
free: "Nemokama"
|
||||
month: "Mėnesis"
|
||||
# must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
|
||||
# subscribe_title: "Subscribe"
|
||||
# unsubscribe: "Unsubscribe"
|
||||
# confirm_unsubscribe: "Confirm Unsubscribe"
|
||||
# never_mind: "Never Mind, I Still Love You"
|
||||
subscribe_title: "Prenumerata"
|
||||
unsubscribe: "Nebepremenuoruoti"
|
||||
confirm_unsubscribe: "patvirtinti nebeprenumeravimą"
|
||||
never_mind: "Nekreipk dėmesio, aš vis dar tave myliu."
|
||||
# thank_you_months_prefix: "Thank you for supporting us these last"
|
||||
# thank_you_months_suffix: "months."
|
||||
# thank_you: "Thank you for supporting CodeCombat."
|
||||
|
@ -475,7 +475,7 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
|
|||
weapons: "Ginklai"
|
||||
weapons_warrior: "Kardai - artimas atstumas, be Kerų"
|
||||
weapons_ranger: "Arbaletai, Šautuvai - tolimas atstumas, be Kerų"
|
||||
weapons_wizard: "stebuklų lazdelės ir skipetrai - tolimas atstumas, magija"
|
||||
weapons_wizard: "stebuklų lazdelės ir skeptrai - tolimas atstumas, magija"
|
||||
attack: "Žala" # Can also translate as "Attack"
|
||||
health: "Sveikata"
|
||||
speed: "Greitis"
|
||||
|
|
|
@ -85,11 +85,11 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
trackModel: (model, value) ->
|
||||
res = @addModelResource(collection, '', {}, value)
|
||||
res.listen()
|
||||
|
||||
|
||||
trackCollection: (collection, value) ->
|
||||
res = @addModelResource(collection, '', {}, value)
|
||||
res.listen()
|
||||
|
||||
|
||||
# replace or overwrite
|
||||
shouldSaveBackups: (model) -> false
|
||||
|
||||
|
@ -285,7 +285,7 @@ class ModelResource extends Resource
|
|||
fetchModel: ->
|
||||
@jqxhr = @model.fetch(@fetchOptions) unless @model.loading
|
||||
@listen()
|
||||
|
||||
|
||||
listen: ->
|
||||
@listenToOnce @model, 'sync', -> @markLoaded()
|
||||
@listenToOnce @model, 'error', -> @markFailed()
|
||||
|
|
|
@ -1,36 +1,331 @@
|
|||
@import "app/styles/bootstrap/variables"
|
||||
@import "app/styles/mixins"
|
||||
@import "app/styles/style-flat"
|
||||
|
||||
#about-view
|
||||
|
||||
@import "bootstrap/variables"
|
||||
overflow: hidden
|
||||
|
||||
.team-column
|
||||
padding-left: 0
|
||||
#nav-container
|
||||
min-height: 55px
|
||||
nav
|
||||
background: $gold
|
||||
&.affix
|
||||
z-index: 1
|
||||
position: fixed
|
||||
top: 0
|
||||
width: 100%
|
||||
ul
|
||||
margin-top: 5px
|
||||
padding-left: 0
|
||||
list-style: none
|
||||
li
|
||||
height: 22pt
|
||||
display: inline-block
|
||||
margin: 10px 0 0
|
||||
@media (min-width: $screen-xs-min)
|
||||
margin: 10px 5px 0
|
||||
@media (min-width: $screen-sm-min)
|
||||
margin: 10px 18px 0
|
||||
a
|
||||
color: white
|
||||
text-transform: uppercase
|
||||
text-decoration: none
|
||||
li.active
|
||||
.label
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
padding-bottom: 0
|
||||
margin-left: 0.6em
|
||||
margin-right: 0.6em
|
||||
border-bottom: 4px solid white
|
||||
border-radius: 0
|
||||
|
||||
#jumbotron
|
||||
background-color: $navy
|
||||
background-image: url("/images/pages/about/codebackground_zoom_compressed.png")
|
||||
background-size: cover
|
||||
background-repeat: no-repeat
|
||||
|
||||
h1
|
||||
color: white
|
||||
margin-top: 100px
|
||||
|
||||
h2
|
||||
color: white
|
||||
margin-bottom: 100px
|
||||
|
||||
.responsive-side-margins, h3, h4, p
|
||||
max-width: 460px
|
||||
margin-left: auto
|
||||
margin-right: auto
|
||||
@media (min-width: $screen-sm-min)
|
||||
max-width: inherit
|
||||
|
||||
h3
|
||||
margin-top: 60px
|
||||
@media (min-width: $screen-sm-min)
|
||||
margin-top: 150px
|
||||
|
||||
#about-container
|
||||
@media (min-width: $screen-sm-min)
|
||||
margin-top: 85px
|
||||
background-color: white
|
||||
|
||||
#mission-text
|
||||
margin-top: 30px
|
||||
@media (min-width: $screen-sm-min)
|
||||
margin-top: 45px
|
||||
|
||||
#mission-graphic, #community-graphic
|
||||
padding: 30px 40px
|
||||
position: relative
|
||||
min-height: 320px
|
||||
@media (min-width: $screen-sm-min)
|
||||
min-height: 250px
|
||||
|
||||
ul.thumbnails
|
||||
margin-left: 40px
|
||||
h2
|
||||
color: white
|
||||
width: 50%
|
||||
|
||||
#mission-graphic
|
||||
margin-top: 30px
|
||||
@media (min-width: $screen-sm-min)
|
||||
margin-top: 45px
|
||||
background: $navy
|
||||
|
||||
h2
|
||||
float: right
|
||||
|
||||
img
|
||||
position: absolute
|
||||
bottom: 0
|
||||
left: 0
|
||||
|
||||
#mission-graphic-filler
|
||||
background: $navy
|
||||
height: 100%
|
||||
width: 2000px
|
||||
position: absolute
|
||||
right: 100%
|
||||
top: 0
|
||||
|
||||
#team
|
||||
ul
|
||||
text-align: center
|
||||
margin-top: 40px
|
||||
padding: 0
|
||||
|
||||
li
|
||||
list-style-type: none
|
||||
width: 200px
|
||||
height: 230px
|
||||
list-style: none
|
||||
display: inline-block
|
||||
text-align: center
|
||||
color: black
|
||||
small
|
||||
display: block
|
||||
white-space: nowrap
|
||||
.img-thumbnail
|
||||
border-radius: 50%
|
||||
padding: 0
|
||||
background-color: $burgandy
|
||||
border: 5px solid $gold
|
||||
|
||||
// #community
|
||||
// margin-top: 100px
|
||||
|
||||
#community-row-1
|
||||
#community-avatars
|
||||
width: 90%
|
||||
margin: 20px 5%
|
||||
|
||||
#community-row-2
|
||||
margin-top: 35px
|
||||
@media (min-width: $screen-sm-min)
|
||||
margin-top: 70px
|
||||
|
||||
#community-graphic
|
||||
background: $burgandy
|
||||
|
||||
img.img-thumbnail
|
||||
background-color: transparent
|
||||
&:hover
|
||||
background-color: rgba(200, 244, 255, 0.2)
|
||||
float: left
|
||||
width: 80px
|
||||
height: 80px
|
||||
margin: 0px 10px 22px 0px
|
||||
img
|
||||
position: absolute
|
||||
right: 0
|
||||
bottom: 0
|
||||
|
||||
.team_name
|
||||
font-size: 20px
|
||||
margin-top: 0
|
||||
#community-graphic-filler
|
||||
background: $burgandy
|
||||
height: 100%
|
||||
width: 2000px
|
||||
position: absolute
|
||||
left: 100%
|
||||
top: 0
|
||||
|
||||
.team_bio
|
||||
width: 150px
|
||||
float: left
|
||||
#story
|
||||
// margin-top: 150px
|
||||
font-family: $headline-font
|
||||
font-variant: normal
|
||||
#story-graphic-1
|
||||
max-width: 580px
|
||||
margin-top: 50px
|
||||
@media (min-width: $screen-sm-min)
|
||||
margin-top: 80px
|
||||
margin-left: auto
|
||||
margin-right: auto
|
||||
.media-heading
|
||||
color: $burgandy
|
||||
#story-graphic-2
|
||||
//TODO: Fix left-margin of this
|
||||
margin-top: 50px
|
||||
@media (min-width: $screen-sm-min)
|
||||
margin-top: 80px
|
||||
margin-left: auto
|
||||
margin-right: auto
|
||||
max-width: 390px
|
||||
.media-heading
|
||||
color: $forest
|
||||
#story-graphic-3
|
||||
margin-top: 50px
|
||||
@media (min-width: $screen-sm-min)
|
||||
margin-top: 80px
|
||||
p
|
||||
margin-top: 30px
|
||||
img
|
||||
margin-top: 20px
|
||||
#story-bracketed-text
|
||||
width: 100%
|
||||
max-width: 640px
|
||||
margin: 0 auto
|
||||
.text-h1
|
||||
display: inline-block
|
||||
margin: auto 0
|
||||
color: $navy
|
||||
width: 80%
|
||||
vertical-align: bottom
|
||||
img
|
||||
margin: auto 0
|
||||
#left-bracket
|
||||
width: 10%
|
||||
display: inline-block
|
||||
#right-bracket
|
||||
width: 10%
|
||||
display: inline-block
|
||||
#story-languages
|
||||
margin-top: 50px
|
||||
#language-icons
|
||||
display: inline-block
|
||||
padding-left: auto
|
||||
padding-right: auto
|
||||
|
||||
#story-graphic-4
|
||||
margin-left: auto
|
||||
margin-right: auto
|
||||
margin-top: 50px
|
||||
@media (min-width: $screen-sm-min)
|
||||
margin-top: 150px
|
||||
p
|
||||
margin-top: 20px
|
||||
figure
|
||||
img
|
||||
display: block
|
||||
margin: 0 auto
|
||||
#jobs
|
||||
// margin-top: 50px
|
||||
// @media (min-width: $screen-sm-min)
|
||||
// margin-top: 100px
|
||||
#jobs-row
|
||||
#benefits, .job-listing
|
||||
margin-top: 65px
|
||||
border: thin solid $navy
|
||||
border-radius: 8px
|
||||
padding: 50px
|
||||
width: 370px
|
||||
height: 420px
|
||||
margin-left: auto
|
||||
margin-right: auto
|
||||
h5
|
||||
text-align: center
|
||||
color: inherit
|
||||
li
|
||||
padding-bottom: 7px
|
||||
#benefits
|
||||
color: white
|
||||
background: $navy
|
||||
ul
|
||||
margin-top: 20px
|
||||
.job-listing
|
||||
color: $navy
|
||||
background: white
|
||||
// centering hack
|
||||
position: relative
|
||||
.label
|
||||
text-transform: uppercase
|
||||
color: grey
|
||||
a.job-link
|
||||
width: 170px
|
||||
bottom: 45px
|
||||
// centering hack
|
||||
position: absolute
|
||||
left: 50%
|
||||
margin-left: -85px
|
||||
p
|
||||
margin-top: 15px
|
||||
|
||||
#files
|
||||
padding: 30px 0
|
||||
margin-top: 50px
|
||||
border: thin solid gray
|
||||
border-radius: 8px
|
||||
.label
|
||||
color: black
|
||||
display: block
|
||||
#screenshots
|
||||
text-align: center
|
||||
#screenshot-grid
|
||||
img
|
||||
display: inline-block
|
||||
margin: 6.5px
|
||||
|
||||
#downloads-container
|
||||
position: relative
|
||||
height: 250px
|
||||
#downloads
|
||||
//TODO: How do I center this in small view?
|
||||
margin: 20px auto 0
|
||||
width: 260px
|
||||
ul
|
||||
width: 260px
|
||||
margin-top: 10px
|
||||
margin-bottom: 20px
|
||||
margin-left: -25px
|
||||
a
|
||||
color: black
|
||||
#download-button
|
||||
margin: 0 auto
|
||||
color: $navy
|
||||
.glyphicon-download-alt
|
||||
margin-right: 15px
|
||||
vertical-align: middle
|
||||
font-size: 1.5em
|
||||
|
||||
#screenshot-lightbox
|
||||
.modal-dialog
|
||||
width: auto
|
||||
max-width: 1024px
|
||||
|
||||
div
|
||||
font-size: 12px
|
||||
line-height: 14px
|
||||
padding-bottom: 5px
|
||||
#location
|
||||
margin-top: 75px
|
||||
margin-bottom: 100px
|
||||
text-align: center
|
||||
p b
|
||||
margin-top: 40px
|
||||
a
|
||||
color: inherit
|
||||
text-decoration: underline
|
||||
iframe
|
||||
border: 2px solid lightgray
|
||||
|
||||
.anchor::before
|
||||
content: ""
|
||||
display: block
|
||||
height: 55px
|
||||
margin: -55px 0 0 0
|
|
@ -20,7 +20,7 @@ iframe
|
|||
.selectable
|
||||
cursor: pointer
|
||||
|
||||
.modal-dialog
|
||||
.modal-dialog.game
|
||||
padding: 5px
|
||||
margin-top: 30px
|
||||
margin-bottom: 0px
|
||||
|
|
|
@ -1,205 +1,6 @@
|
|||
@import "app/styles/bootstrap/variables"
|
||||
@import "app/styles/mixins"
|
||||
|
||||
// TODO: Move flat style into probably several files and Bootstrap variables
|
||||
|
||||
// Variables
|
||||
|
||||
$headline-font: 'Arvo', serif
|
||||
$body-font: 'Open Sans', sans-serif
|
||||
|
||||
$burgandy: #7D0101
|
||||
$gold: #F2BE19
|
||||
$navy: #0E4C60
|
||||
$forest: #20572B
|
||||
|
||||
.style-flat
|
||||
background: white
|
||||
|
||||
// Fonts
|
||||
h1, h2, h3, h4, h5, h6
|
||||
// Unsetting game styles
|
||||
font-variant: normal
|
||||
color: black
|
||||
margin: 0
|
||||
|
||||
h1
|
||||
font-family: $headline-font
|
||||
font-weight: normal
|
||||
font-size: 46px
|
||||
line-height: 62px
|
||||
|
||||
h2
|
||||
font-family: $body-font
|
||||
font-weight: lighter
|
||||
font-size: 30px
|
||||
line-height: 42px
|
||||
|
||||
h3
|
||||
font-family: $headline-font
|
||||
font-weight: normal
|
||||
font-size: 33px
|
||||
line-height: 45px
|
||||
|
||||
h4
|
||||
font-family: $body-font
|
||||
font-weight: lighter
|
||||
font-size: 22px
|
||||
line-height: 32px
|
||||
|
||||
h5
|
||||
font-family: $headline-font
|
||||
font-weight: bold
|
||||
font-size: 20px
|
||||
line-height: 31px
|
||||
|
||||
h6
|
||||
font-family: $body-font
|
||||
font-weight: bold
|
||||
font-size: 14px
|
||||
line-height: 20px
|
||||
|
||||
p
|
||||
margin: 0 0 14px
|
||||
|
||||
.small
|
||||
font-weight: normal
|
||||
font-size: 14px
|
||||
line-height: 20px
|
||||
|
||||
font-family: $body-font
|
||||
font-size: 18px
|
||||
line-height: 29px
|
||||
|
||||
blockquote
|
||||
border: none
|
||||
|
||||
&:before
|
||||
font-family: "Monaco"
|
||||
content: "\201C"
|
||||
position: absolute
|
||||
left: 0px
|
||||
top: 20px
|
||||
font-size: 40px
|
||||
opacity: 0.5
|
||||
|
||||
// Navbar
|
||||
|
||||
.navbar
|
||||
background: white
|
||||
margin-bottom: 0
|
||||
white-space: nowrap // prevent home icon from going under brand
|
||||
|
||||
a.navbar-brand
|
||||
#logo-img
|
||||
width: 230px
|
||||
height: 65px
|
||||
margin-right: 10px
|
||||
|
||||
color: $burgandy
|
||||
&:hover
|
||||
color: white
|
||||
background: $burgandy
|
||||
|
||||
.glyphicon-home
|
||||
position: relative
|
||||
top: 3px
|
||||
|
||||
.navbar-toggle
|
||||
color: black
|
||||
margin: 30px 25px 0
|
||||
|
||||
.nav > li > a
|
||||
// TODO: Move this to bootstrap variables for navbars
|
||||
font-weight: bold
|
||||
font-family: $body-font
|
||||
font-size: 16px
|
||||
padding: 38px 15px 37px
|
||||
color: $burgandy
|
||||
text-shadow: 0 0 0
|
||||
|
||||
&:hover
|
||||
background: $burgandy
|
||||
color: white
|
||||
|
||||
#language-dropdown-wrapper
|
||||
display: inline-block
|
||||
padding: 30px 10px
|
||||
width: 100%
|
||||
|
||||
@media (max-width: $screen-sm-min)
|
||||
.nav > li > a
|
||||
padding: 10px 20px
|
||||
#language-dropdown-wrapper
|
||||
display: inline-block
|
||||
padding: 10px 10px
|
||||
.language-dropdown
|
||||
width: 150px
|
||||
|
||||
.img-circle
|
||||
border: $gold 8px solid
|
||||
width: 98px
|
||||
height: 98px // Includes the border
|
||||
|
||||
.user-level
|
||||
position: absolute
|
||||
top: 76px
|
||||
right: 42px
|
||||
color: $gold
|
||||
text-shadow: 1px 1px black, -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black
|
||||
|
||||
// Buttons
|
||||
|
||||
.btn
|
||||
border: none
|
||||
border-radius: 5px
|
||||
font-family: $body-font
|
||||
font-weight: normal
|
||||
background-image: none // overrides legacy buttons
|
||||
|
||||
.btn-primary, .btn-navy
|
||||
background-color: $navy
|
||||
color: white
|
||||
|
||||
.btn-primary-alt, .btn-navy-alt
|
||||
background-color: white
|
||||
border: 1px solid $navy
|
||||
color: $navy
|
||||
|
||||
.btn-forest
|
||||
background-color: $forest
|
||||
color: white
|
||||
|
||||
.btn-forest-alt
|
||||
background-color: white
|
||||
border: 1px solid $forest
|
||||
color: $forest
|
||||
|
||||
.btn-gold
|
||||
background-color: $gold
|
||||
color: white
|
||||
|
||||
.btn-gold-alt
|
||||
background-color: white
|
||||
border: 1px solid $gold
|
||||
color: $gold
|
||||
|
||||
.btn-lg
|
||||
font-size: 18px
|
||||
|
||||
// Classes
|
||||
|
||||
.text-navy
|
||||
color: $navy
|
||||
|
||||
.bg-navy
|
||||
background-color: $navy
|
||||
color: white
|
||||
h1, h2, h3, h4, h5, h6, a
|
||||
color: white
|
||||
a.btn-primary-alt
|
||||
color: $navy
|
||||
|
||||
@import "app/styles/style-flat"
|
||||
|
||||
#new-home-view
|
||||
|
||||
|
@ -464,40 +265,3 @@ $forest: #20572B
|
|||
img
|
||||
margin-bottom: 20px
|
||||
|
||||
#footer
|
||||
background-image: url("/images/pages/home/footer_background.png")
|
||||
height: 229px
|
||||
margin: -22px auto 0
|
||||
color: white
|
||||
|
||||
@media (max-width: $screen-sm-min)
|
||||
background-color: #201a15
|
||||
background-image: none
|
||||
height: auto
|
||||
|
||||
ul
|
||||
margin: 30px
|
||||
li:first-child
|
||||
border-bottom: 1px solid white
|
||||
margin-bottom: 10px
|
||||
a
|
||||
color: white
|
||||
|
||||
#final-footer
|
||||
position: absolute
|
||||
left: 0
|
||||
right: 0
|
||||
height: 60px
|
||||
color: white
|
||||
background-color: #463a2c
|
||||
@media (max-width: $screen-sm-min)
|
||||
position: inherit
|
||||
padding: 20px
|
||||
height: auto
|
||||
|
||||
a
|
||||
color: white
|
||||
|
||||
img
|
||||
width: 150px
|
||||
margin: 0 10px
|
|
@ -1,27 +1,32 @@
|
|||
#request-quote-view
|
||||
|
||||
label
|
||||
margin-bottom: 2px
|
||||
|
||||
.row
|
||||
margin: 10px 0
|
||||
#site-content-area
|
||||
//TODO: Maybe this should go in style-flat
|
||||
margin: 50px 10px 100px
|
||||
|
||||
.section
|
||||
margin-top: 80px
|
||||
margin-bottom: 50px
|
||||
|
||||
.form-group
|
||||
label
|
||||
margin-bottom: 0
|
||||
|
||||
label.checkbox
|
||||
font-weight: normal
|
||||
|
||||
.help-block
|
||||
margin: 0
|
||||
margin: -4px 0 2px
|
||||
|
||||
p
|
||||
margin: 0 0 20px
|
||||
|
||||
hr
|
||||
margin: 30px 0
|
||||
|
||||
.checkbox, .checkbox-inline
|
||||
margin: 0
|
||||
|
||||
#anything-else-row
|
||||
margin: 50px 0 20px
|
||||
input
|
||||
margin-top: 8px
|
||||
|
||||
#other-education-level-input
|
||||
label
|
||||
display: inline-block
|
||||
display: inline-block
|
||||
width: 200px
|
||||
margin-left: 5px
|
||||
|
|
239
app/styles/style-flat.sass
Normal file
|
@ -0,0 +1,239 @@
|
|||
@import "app/styles/bootstrap/variables"
|
||||
@import "app/styles/mixins"
|
||||
|
||||
// TODO: Move flat style into probably several files and Bootstrap variables
|
||||
|
||||
// Variables
|
||||
|
||||
$headline-font: 'Arvo', serif
|
||||
$body-font: 'Open Sans', sans-serif
|
||||
|
||||
$burgandy: #7D0101
|
||||
$gold: #F2BE19
|
||||
$navy: #0E4C60
|
||||
$forest: #20572B
|
||||
|
||||
.style-flat
|
||||
background: white
|
||||
|
||||
// Fonts
|
||||
h1, h2, h3, h4, h5, h6
|
||||
// Unsetting game styles
|
||||
font-variant: normal
|
||||
color: black
|
||||
margin: 0
|
||||
|
||||
h1, .text-h1
|
||||
font-family: $headline-font
|
||||
font-weight: normal
|
||||
font-size: 46px
|
||||
line-height: 62px
|
||||
|
||||
h2, .text-h2
|
||||
font-family: $body-font
|
||||
font-weight: lighter
|
||||
font-size: 30px
|
||||
line-height: 42px
|
||||
|
||||
h3, .text-h3
|
||||
font-family: $headline-font
|
||||
font-weight: normal
|
||||
font-size: 33px
|
||||
line-height: 45px
|
||||
|
||||
h4, .text-h4
|
||||
font-family: $body-font
|
||||
font-weight: lighter
|
||||
font-size: 22px
|
||||
line-height: 32px
|
||||
|
||||
h5, .text-h5
|
||||
font-family: $headline-font
|
||||
font-weight: bold
|
||||
font-size: 20px
|
||||
line-height: 31px
|
||||
|
||||
h6, .text-h6
|
||||
font-family: $body-font
|
||||
font-weight: bold
|
||||
font-size: 14px
|
||||
line-height: 20px
|
||||
|
||||
p
|
||||
margin: 0 0 14px
|
||||
|
||||
.small
|
||||
font-weight: normal
|
||||
font-size: 14px
|
||||
line-height: 20px
|
||||
|
||||
font-family: $body-font
|
||||
font-size: 18px
|
||||
line-height: 29px
|
||||
|
||||
blockquote
|
||||
border: none
|
||||
|
||||
&:before
|
||||
font-family: "Monaco"
|
||||
content: "\201C"
|
||||
position: absolute
|
||||
left: 0px
|
||||
top: 20px
|
||||
font-size: 40px
|
||||
opacity: 0.5
|
||||
|
||||
// Navbar
|
||||
|
||||
.navbar
|
||||
background: white
|
||||
margin-bottom: 0
|
||||
white-space: nowrap // prevent home icon from going under brand
|
||||
|
||||
a.navbar-brand
|
||||
#logo-img
|
||||
width: 230px
|
||||
height: 65px
|
||||
margin-right: 10px
|
||||
|
||||
color: $burgandy
|
||||
&:hover
|
||||
color: white
|
||||
background: $burgandy
|
||||
|
||||
.glyphicon-home
|
||||
position: relative
|
||||
top: 3px
|
||||
|
||||
.navbar-toggle
|
||||
color: black
|
||||
margin: 30px 25px 0
|
||||
|
||||
.nav > li > a
|
||||
// TODO: Move this to bootstrap variables for navbars
|
||||
font-weight: bold
|
||||
font-family: $body-font
|
||||
font-size: 16px
|
||||
padding: 38px 15px 37px
|
||||
color: $burgandy
|
||||
text-shadow: 0 0 0
|
||||
|
||||
&:hover
|
||||
background: $burgandy
|
||||
color: white
|
||||
|
||||
#language-dropdown-wrapper
|
||||
display: inline-block
|
||||
padding: 30px 10px
|
||||
width: 100%
|
||||
|
||||
@media (max-width: $screen-sm-min)
|
||||
.nav > li > a
|
||||
padding: 10px 20px
|
||||
#language-dropdown-wrapper
|
||||
display: inline-block
|
||||
padding: 10px 10px
|
||||
.language-dropdown
|
||||
width: 150px
|
||||
|
||||
.img-circle
|
||||
border: $gold 8px solid
|
||||
width: 98px
|
||||
height: 98px // Includes the border
|
||||
|
||||
.user-level
|
||||
position: absolute
|
||||
top: 76px
|
||||
right: 42px
|
||||
color: $gold
|
||||
text-shadow: 1px 1px black, -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black
|
||||
|
||||
// Buttons
|
||||
|
||||
.btn
|
||||
border: none
|
||||
border-radius: 5px
|
||||
font-family: $body-font
|
||||
font-weight: normal
|
||||
background-image: none // overrides legacy buttons
|
||||
|
||||
.btn-primary, .btn-navy
|
||||
background-color: $navy
|
||||
color: white
|
||||
|
||||
.btn-primary-alt, .btn-navy-alt
|
||||
background-color: white
|
||||
border: 1px solid $navy
|
||||
color: $navy
|
||||
|
||||
.btn-forest
|
||||
background-color: $forest
|
||||
color: white
|
||||
|
||||
.btn-forest-alt
|
||||
background-color: white
|
||||
border: 1px solid $forest
|
||||
color: $forest
|
||||
|
||||
.btn-gold
|
||||
background-color: $gold
|
||||
color: white
|
||||
|
||||
.btn-gold-alt
|
||||
background-color: white
|
||||
border: 1px solid $gold
|
||||
color: $gold
|
||||
|
||||
.btn-lg
|
||||
font-size: 18px
|
||||
|
||||
// Classes
|
||||
|
||||
.text-navy
|
||||
color: $navy
|
||||
|
||||
.bg-navy
|
||||
background-color: $navy
|
||||
color: white
|
||||
h1, h2, h3, h4, h5, h6, a
|
||||
color: white
|
||||
a.btn-primary-alt
|
||||
color: $navy
|
||||
|
||||
#footer
|
||||
background-image: url("/images/pages/home/footer_background.png")
|
||||
height: 229px
|
||||
margin: -22px auto 0
|
||||
color: white
|
||||
|
||||
@media (max-width: $screen-sm-min)
|
||||
background-color: #201a15
|
||||
background-image: none
|
||||
height: auto
|
||||
|
||||
ul
|
||||
margin: 30px
|
||||
li:first-child
|
||||
border-bottom: 1px solid white
|
||||
margin-bottom: 10px
|
||||
a
|
||||
color: white
|
||||
|
||||
#final-footer
|
||||
position: absolute
|
||||
left: 0
|
||||
right: 0
|
||||
height: 60px
|
||||
color: white
|
||||
background-color: #463a2c
|
||||
@media (max-width: $screen-sm-min)
|
||||
position: inherit
|
||||
padding: 20px
|
||||
height: auto
|
||||
|
||||
a
|
||||
color: white
|
||||
|
||||
img
|
||||
width: 150px
|
||||
margin: 0 10px
|
|
@ -1,188 +1,441 @@
|
|||
extends /templates/base
|
||||
extends /templates/base-flat
|
||||
|
||||
block content
|
||||
.style-flat(data-spy="scroll", data-target="#nav-container")
|
||||
.container-fluid#jumbotron
|
||||
.container
|
||||
.row
|
||||
//TODO: Size these correctly
|
||||
.col-xs-10
|
||||
h1 If you want to learn to program, you need to write (a lot of) code.
|
||||
.row
|
||||
.col-xs-8
|
||||
h2 At CodeCombat, our job is to make sure you're doing that with a smile on your face.
|
||||
|
||||
#nav-container
|
||||
nav#fixed-nav.nav.navbar
|
||||
.container-fluid.text-center
|
||||
ul.center-block
|
||||
li.active
|
||||
a(href="#mission")#mission-link
|
||||
small.label(data-i18n="about.mission_link")
|
||||
| Mission
|
||||
li
|
||||
a(href="#team")#team-link
|
||||
small.label(data-i18n="about.team_link")
|
||||
| Team
|
||||
li
|
||||
a(href="#community")#community-link
|
||||
small.label(data-i18n="about.community_link")
|
||||
| Community
|
||||
li
|
||||
a(href="#story")#story-link
|
||||
small.label(data-i18n="about.story_link")
|
||||
| Story
|
||||
li
|
||||
a(href="#jobs")#jobs-link
|
||||
small.label(data-i18n="about.jobs_link")
|
||||
| Careers
|
||||
li
|
||||
a(href="#contact")#contact-link
|
||||
small.label(data-i18n="about.contact_link")
|
||||
| Press
|
||||
|
||||
#about-container.container
|
||||
#mission.anchor.row
|
||||
.col-sm-6#mission-graphic.responsive-side-margins
|
||||
img(src="/images/pages/about/globe_white.png")
|
||||
h2(data-i18n="about.mission_title")
|
||||
| Our mission: make programming accessible to every student on Earth.
|
||||
#mission-graphic-filler
|
||||
.col-sm-5.col-sm-offset-1
|
||||
#mission-text.responsive-side-margins
|
||||
p(data-i18n="about.mission_description_1")
|
||||
| Programming is magic. It's the ability to create things from pure imagination. We started CodeCombat to give learners the feeling of wizardly power at their fingertips by using typed code.
|
||||
p(data-i18n="about.mission_description_2")
|
||||
| As it turns out, that enables them to learn faster too. WAY faster. It's like having a conversation instead of reading a manual. We want to bring that conversation to every school and to every student, because everyone should have the chance to learn the magic of programming.
|
||||
|
||||
img(src="/images/pages/about/coco_comic.jpg")
|
||||
|
||||
.row
|
||||
|
||||
.col-sm-6
|
||||
|
||||
h2(data-i18n="about.why_codecombat")
|
||||
| Why CodeCombat?
|
||||
|
||||
p(data-i18n="about.why_paragraph_1")
|
||||
| If you want to learn to program, you don't need lessons.
|
||||
| You need to write a lot of code and have a great time doing it.
|
||||
|
||||
p
|
||||
span(data-i18n="about.why_paragraph_2_prefix")
|
||||
| That's what programming is about. It's gotta be fun.
|
||||
| Not fun like
|
||||
span
|
||||
i(data-i18n="about.why_paragraph_2_italic")
|
||||
| yay a badge
|
||||
span
|
||||
span(data-i18n="about.why_paragraph_2_center")
|
||||
| but fun like
|
||||
span
|
||||
i(data-i18n="about.why_paragraph_2_italic_caps")
|
||||
| NO MOM I HAVE TO FINISH THE LEVEL!
|
||||
span
|
||||
span(data-i18n="about.why_paragraph_2_suffix")
|
||||
| That's why CodeCombat is a multiplayer game,
|
||||
| not a gamified lesson course. We won't stop
|
||||
| until you can't stop--but this time, that's a good thing.
|
||||
|
||||
p(data-i18n="about.why_paragraph_3")
|
||||
| If you're going to get addicted to some game,
|
||||
| get addicted to this one and become one of the wizards of the tech age.
|
||||
|
||||
h2(data-i18n="about.press_title")
|
||||
| Bloggers/Press
|
||||
|
||||
p
|
||||
span.spr(data-i18n="about.press_paragraph_1_prefix")
|
||||
| Want to write about us? Feel free to download and use all of the resources included in our
|
||||
a(href="https://s3.amazonaws.com/CodeCombatMisc/press_packet.zip", data-i18n="about.press_paragraph_1_link") press packet
|
||||
span(data-i18n="about.press_paragraph_1_suffix")
|
||||
| . All logos and images may be used without contacting us directly.
|
||||
|
||||
h2(data-i18n="nav.contact")
|
||||
| Contact
|
||||
p
|
||||
span CodeCombat Inc.
|
||||
br
|
||||
span 360 3rd St Suite 700 (Livefyre)
|
||||
br
|
||||
span San Francisco, CA 94107
|
||||
br
|
||||
a(href='mailto:team@codecombat.com') team@codecombat.com
|
||||
if me.get('preferredLanguage') == 'pt-BR'
|
||||
span CodeCombat, Brazil
|
||||
br
|
||||
span Rua 1814 Militão Chaves
|
||||
br
|
||||
span Natal, Brazil, RN 59064-500
|
||||
|
||||
ul.col-sm-6.team-column
|
||||
|
||||
ul.thumbnails
|
||||
|
||||
li.row
|
||||
|
||||
h2(data-i18n="about.team") Team
|
||||
|
||||
#team.anchor
|
||||
.text-center
|
||||
h3(data-i18n="about.team_title")
|
||||
| Meet the CodeCombat team
|
||||
h4.responsive-side-margins(data-i18n="about.team_values")
|
||||
| We value open and respectful dialog, where the best idea wins. Our decisions are grounded in customer research and our process is focused on delivering tangible results for them. Everyone is hands-on, from our CEO to our Github contributors, because we value growth and learning in our team.
|
||||
ul
|
||||
// Full time
|
||||
li
|
||||
a(href="http://www.nickwinter.net" rel="external")
|
||||
img(src="/images/pages/about/nick_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name
|
||||
a(href="http://www.nickwinter.net" rel="external") Nick Winter
|
||||
small(data-i18n="about.nick_title")
|
||||
small(data-i18n="about.nick_blurb")
|
||||
|
||||
a(href="http://www.nickwinter.net")
|
||||
img(src="/images/pages/about/nick_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name
|
||||
a(href="http://www.nickwinter.net") Nick Winter
|
||||
div(data-i18n="about.nick_title")
|
||||
div(data-i18n="about.nick_blurb")
|
||||
li
|
||||
a(href="http://www.mattlott.com/" rel="external")
|
||||
img(src="/images/pages/about/matt_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name
|
||||
a(href="http://www.mattlott.com/" rel="external") Matt Lott
|
||||
small(data-i18n="about.matt_title")
|
||||
small(data-i18n="about.matt_blurb")
|
||||
|
||||
a(href="http://www.mattlott.com/")
|
||||
img(src="/images/pages/about/matt_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name
|
||||
a(href="http://www.mattlott.com/") Matt Lott
|
||||
div(data-i18n="about.matt_title")
|
||||
div(data-i18n="about.matt_blurb")
|
||||
li
|
||||
a(href="http://cat.zdh.com/" rel="external")
|
||||
img(src="/images/pages/about/cat_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name
|
||||
a(href="http://cat.zdh.com/" rel="external") Catherine Weresow
|
||||
small(data-i18n="about.cat_title")
|
||||
small(data-i18n="about.cat_blurb")
|
||||
|
||||
li.row
|
||||
li
|
||||
img(src="/images/pages/about/scott_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name Scott Erickson
|
||||
small(data-i18n="about.scott_title")
|
||||
small(data-i18n="about.scott_blurb")
|
||||
|
||||
a(href="http://cat.zdh.com/")
|
||||
img(src="/images/pages/about/cat_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name
|
||||
a(href="http://cat.zdh.com/") Catherine Weresow
|
||||
div(data-i18n="about.cat_title")
|
||||
div(data-i18n="about.cat_blurb")
|
||||
li
|
||||
img(src="/images/pages/about/maka_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name Michael 'Maka' Gradin
|
||||
small(data-i18n="about.maka_title")
|
||||
small(data-i18n="about.maka_blurb")
|
||||
|
||||
img(src="/images/pages/about/scott_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name Scott Erickson
|
||||
div(data-i18n="about.scott_title")
|
||||
div(data-i18n="about.scott_blurb")
|
||||
a(href="http://basicer.com/" rel="external")
|
||||
|
||||
li
|
||||
a(href="http://basicer.com/" rel="external")
|
||||
img(src="/images/pages/about/rob_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name
|
||||
a(href="http://basicer.com/" rel="external") Rob Blanckaert
|
||||
small(data-i18n="about.rob_title")
|
||||
small(data-i18n="about.rob_blurb")
|
||||
|
||||
li.row
|
||||
li
|
||||
img(src="/images/pages/about/josh_c_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name Josh Callebaut
|
||||
small(data-i18n="about.josh_c_title")
|
||||
small(data-i18n="about.josh_c_blurb")
|
||||
|
||||
img(src="/images/pages/about/maka_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name Michael 'Maka' Gradin
|
||||
div(data-i18n="about.maka_title")
|
||||
div(data-i18n="about.maka_blurb")
|
||||
|
||||
a(href="http://basicer.com/")
|
||||
img(src="/images/pages/about/rob_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name
|
||||
a(href="http://basicer.com/") Rob Blanckaert
|
||||
div(data-i18n="about.rob_title")
|
||||
div(data-i18n="about.rob_blurb")
|
||||
|
||||
li.row
|
||||
|
||||
img(src="/images/pages/about/josh_c_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name Josh Callebaut
|
||||
div(data-i18n="about.josh_c_title")
|
||||
div(data-i18n="about.josh_c_blurb")
|
||||
|
||||
a(href="http://robinyang.com/")
|
||||
img(src="/images/pages/about/robin_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name
|
||||
a(href="http://robinyang.com/") Robin Yang
|
||||
div(data-i18n="about.robin_title")
|
||||
div(data-i18n="about.robin_blurb")
|
||||
a(href="http://robinyang.com/" rel="external")
|
||||
|
||||
li
|
||||
a(href="http://robinyang.com/" rel="external")
|
||||
img(src="/images/pages/about/robin_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name
|
||||
a(href="http://robinyang.com/" rel="external") Robin Yang
|
||||
small(data-i18n="about.robin_title")
|
||||
small(data-i18n="about.robin_blurb")
|
||||
|
||||
|
||||
// Part time / contract
|
||||
// Part time / contract
|
||||
|
||||
li.row
|
||||
li
|
||||
a(href="http://floor.is/lava/" rel="external")
|
||||
img(src="/images/pages/about/josh_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name
|
||||
a(href="http://floor.is/lava/" rel="external") Josh Lee
|
||||
small(data-i18n="about.josh_title")
|
||||
small(data-i18n="about.josh_blurb")
|
||||
|
||||
a(href="http://floor.is/lava/")
|
||||
img(src="/images/pages/about/josh_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name
|
||||
a(href="http://floor.is/lava/") Josh Lee
|
||||
div(data-i18n="about.josh_title")
|
||||
div(data-i18n="about.josh_blurb")
|
||||
li
|
||||
a(href="https://soundcloud.com/taking-off" rel="external")
|
||||
img(src="/images/pages/about/jose_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name
|
||||
a(href="https://soundcloud.com/taking-off" rel="external") Jose Antonini
|
||||
small(data-i18n="about.jose_title")
|
||||
small(data-i18n="about.jose_blurb")
|
||||
|
||||
a(href="https://soundcloud.com/taking-off")
|
||||
img(src="/images/pages/about/jose_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name
|
||||
a(href="https://soundcloud.com/taking-off") Jose Antonini
|
||||
div(data-i18n="about.jose_title")
|
||||
div(data-i18n="about.jose_blurb")
|
||||
li
|
||||
a(href="http://retrostylegames.com/" rel="external")
|
||||
img(src="/images/pages/about/pavel_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name
|
||||
a(href="http://retrostylegames.com/" rel="external") Pavel Konstantinov
|
||||
small(data-i18n="about.retrostyle_title")
|
||||
small(data-i18n="about.retrostyle_blurb")
|
||||
|
||||
li.row
|
||||
|
||||
a(href="http://retrostylegames.com/")
|
||||
img(src="/images/pages/about/pavel_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name
|
||||
a(href="http://retrostylegames.com/") Pavel Konstantinov
|
||||
div(data-i18n="about.retrostyle_title")
|
||||
div(data-i18n="about.retrostyle_blurb")
|
||||
|
||||
a(href="http://retrostylegames.com/")
|
||||
img(src="/images/pages/about/oleg_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name
|
||||
a(href="http://retrostylegames.com/") Oleg Ulyanickiy
|
||||
div(data-i18n="about.retrostyle_title")
|
||||
div(data-i18n="about.retrostyle_blurb")
|
||||
|
||||
li.row
|
||||
|
||||
img(src="/images/pages/about/carlos_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name Carlos Maia
|
||||
div(data-i18n="about.carlos_title")
|
||||
div(data-i18n="about.carlos_blurb")
|
||||
li
|
||||
a(href="http://retrostylegames.com/" rel="external")
|
||||
img(src="/images/pages/about/oleg_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name
|
||||
a(href="http://retrostylegames.com/" rel="external") Oleg Ulyanickiy
|
||||
small(data-i18n="about.retrostyle_title")
|
||||
small(data-i18n="about.retrostyle_blurb")
|
||||
|
||||
li
|
||||
img(src="/images/pages/about/carlos_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name Carlos Maia
|
||||
small(data-i18n="about.carlos_title")
|
||||
small(data-i18n="about.carlos_blurb")
|
||||
|
||||
#community.anchor
|
||||
#community-row-1.row.text-center
|
||||
.col-sm-12
|
||||
.text-center
|
||||
h3(data-i18n="about.community_title")
|
||||
| ...and our open-source community
|
||||
h4(data-i18n="about.community_subtitle")
|
||||
| Over 450 contributors have helped build CodeCombat, with more joining every week!
|
||||
img(src="/images/pages/about/github_avatars.png")#community-avatars
|
||||
|
||||
#community-row-2.row
|
||||
.col-sm-5
|
||||
p.responsive-side-margins
|
||||
span(data-i18n="about.community_description_1")
|
||||
| CodeCombat is a community project, with hundreds of players volunteering to create levels, contribute to our code to add features, fix bugs, playtest, and even translate the game into 50 languages so far. Employees, contributors and the site gain by sharing ideas and pooling effort, as does the open source community in general. The site is built on numerous open source projects, and we are open sourced to give back to the community and provide code-curious players a familiar project to explore and experiment with. Anyone can join the CodeCombat community! Check out our
|
||||
a(href="/contribute")
|
||||
span(data-i18n="about.community_description_link")
|
||||
| contribute page
|
||||
span(data-i18n="about.community_description_2")
|
||||
| for more info.
|
||||
.col-sm-6.col-sm-offset-1#community-graphic.responsive-side-margins
|
||||
h2 Over 450 contributors have lent their support and time to this project.
|
||||
img(src="/images/pages/about/github.png")
|
||||
#community-graphic-filler
|
||||
|
||||
#story.anchor
|
||||
.text-center
|
||||
h3(data-i18n="about.story_title")
|
||||
| Our story so far
|
||||
h4.responsive-side-margins(data-i18n="about.story_subtitle")
|
||||
| Since 2013, CodeCombat has grown from a mere set of sketches to a living, thriving game.
|
||||
.row
|
||||
#story-graphic-1.col-lg-6
|
||||
.media
|
||||
.pull-left
|
||||
img.media-object#story-image-1(src="/images/pages/about/Character_Silouhette.png")
|
||||
.media-body
|
||||
.media-heading.text-h1
|
||||
span(data-i18n="about.story_statistic_1a")
|
||||
| 5,000,000+
|
||||
br
|
||||
span(data-i18n="about.story_statistic_1b")
|
||||
| total players
|
||||
p.text-h5 have started their programming journey through CodeCombat
|
||||
|
||||
.col-lg-5.col-lg-offset-1
|
||||
#story-graphic-2
|
||||
.media
|
||||
p.text-h5(data-i18n="about.story_statistic_2a")
|
||||
| We’ve been translated into over 50 languages — our players hail from
|
||||
.pull-right
|
||||
img(src="/images/pages/about/globe_green.png")
|
||||
.media-body
|
||||
.media-heading.text-h1(data-i18n="about.story_statistic_2b")
|
||||
| 200+ countries
|
||||
|
||||
#story-graphic-3.text-center
|
||||
p
|
||||
div.text-h5(data-i18n="about.story_statistic_3a")
|
||||
| Together, they have written
|
||||
#story-bracketed-text
|
||||
div#left-bracket
|
||||
img(src="/images/pages/about/bracket_left.png")
|
||||
div.text-h1(data-i18n="about.story_statistic_3b")
|
||||
| 1 billion lines of code and counting
|
||||
div#right-bracket
|
||||
img(src="/images/pages/about/bracket_right.png")
|
||||
#story-languages
|
||||
.text-center
|
||||
.text-h5(data-i18n="about.story_statistic_3c")
|
||||
| across six different programming languages
|
||||
#language-icons.text-center
|
||||
img.hidden-xs(src="/images/pages/about/Languages.png")
|
||||
img.hidden-sm.hidden-md.hidden-lg(src="/images/pages/about/Languages_group1.png")
|
||||
img.hidden-sm.hidden-md.hidden-lg(src="/images/pages/about/Languages_group2.png")
|
||||
|
||||
#story-graphic-4.text-center
|
||||
p
|
||||
div.text-h5(data-i18n="about.story_long_way_1")
|
||||
| Though we've come a long way...
|
||||
figure
|
||||
img(src="/images/pages/about/sketch.png")
|
||||
figcaption
|
||||
small(data-i18n="about.story_sketch_caption")
|
||||
| Nick's very first sketch depicting a programming game in action.
|
||||
p
|
||||
.text-h5(data-i18n="about.story_long_way_2")
|
||||
| we still have much to do before we complete our quest, so...
|
||||
|
||||
#jobs.anchor
|
||||
.text-center
|
||||
h3(data-i18n="about.jobs_title")
|
||||
| Come work with us and help write CodeCombat history!
|
||||
h4(data-i18n="about.jobs_subtitle")
|
||||
| Don't see a good fit but interested in keeping in touch? See our "Create Your Own" listing.
|
||||
#jobs-row.row
|
||||
.col-sm-6.col-md-5.col-md-offset-1.col-lg-4.col-lg-offset-0
|
||||
#benefits
|
||||
h5(data-i18n="about.jobs_benefits")
|
||||
| Employee Benefits
|
||||
ul
|
||||
li.small(data-i18n="about.jobs_benefit_1")
|
||||
| Competitive salary and options
|
||||
li.small(data-i18n="about.jobs_benefit_2")
|
||||
| 15 day minimum vacation policy, excluding company holidays
|
||||
li.small(data-i18n="about.jobs_benefit_3")
|
||||
| Work from home flexibility
|
||||
li.small(data-i18n="about.jobs_benefit_4")
|
||||
| Unlimited sick/personal days
|
||||
li.small(data-i18n="about.jobs_benefit_5")
|
||||
| Professional development and continuing education support
|
||||
li.small(data-i18n="about.jobs_benefit_6")
|
||||
| Medical/dental/vision insurance
|
||||
.col-sm-6.col-md-5.col-lg-4
|
||||
.job-listing
|
||||
h5 Software Engineer, iOS
|
||||
.text-center
|
||||
small.label
|
||||
| San Francisco • Fulltime
|
||||
p.small Want to write the first iPad app for CodeCombat? We’re looking for a product-focused engineer to translate our core gameplay and educational tools into an experience that feels like it was born on the iPad.
|
||||
a.job-link.btn.btn-lg.btn-navy(href="https://jobs.lever.co/codecombat/eb190007-7195-49ee-a322-893b0d1cdcb4" rel="external")
|
||||
| Learn More
|
||||
.col-sm-6.col-md-5.col-md-offset-1.col-lg-4.col-lg-offset-0
|
||||
.job-listing
|
||||
h5 Head of Sales
|
||||
.text-center
|
||||
small.label
|
||||
| San Francisco • Fulltime
|
||||
p.small School districts are scrambling to offer computer science classes to all their students as a core subject. They have had no solution, because they can't afford to hire enough programming teachers – until now.
|
||||
a.job-link.btn.btn-lg.btn-navy(href="https://jobs.lever.co/codecombat/2d0194ed-7c69-4aab-b1f9-363ca4b9f04b" rel="external")
|
||||
| Learn More
|
||||
.col-sm-6.col-md-5.col-lg-4
|
||||
.job-listing
|
||||
h5 Sales Representative
|
||||
.text-center
|
||||
small.label
|
||||
| San Francisco • Fulltime
|
||||
p.small School districts are scrambling to offer computer science classes to all their students as a core subject. They have had no solution, because they can't afford to hire enough programming teachers – until now.
|
||||
a.job-link.btn.btn-lg.btn-navy(href="https://jobs.lever.co/codecombat/3f6ff123-16ce-4ecb-aba3-dcf4e8927c47" rel="external")
|
||||
| Learn More
|
||||
.col-sm-6.col-md-5.col-md-offset-1.col-lg-4.col-lg-offset-0
|
||||
.job-listing
|
||||
h5(data-i18n="about.jobs_custom_title")
|
||||
| Create Your Own
|
||||
p.small(data-i18n="about.jobs_custom_description")
|
||||
| Are you passionate about CodeCombat but don't see a job listed that matches your qualifications? Write us and show how you think you can contribute to our team. We'd love to hear from you!
|
||||
p.small
|
||||
span(data-i18n="about.jobs_custom_contact_1")
|
||||
| Send us a note at
|
||||
a(href="mailto:team@codecombat.com")
|
||||
| team@codecombat.com
|
||||
span(data-i18n="about.jobs_custom_contact_2")
|
||||
| introducing yourself and we might get in touch in the future!
|
||||
|
||||
#contact.anchor
|
||||
.text-center
|
||||
h3(data-i18n="about.contact_title")
|
||||
| Press & Contact
|
||||
h4(data-i18n="about.contact_subtitle")
|
||||
| Need more information? Get in touch with us at
|
||||
a(href="mailto:team@codecombat.com") team@codecombat.com
|
||||
|
||||
#files
|
||||
.row
|
||||
.col-md-6.col-lg-8
|
||||
#screenshots
|
||||
span.label(data-i18n="about.screenshots_title")
|
||||
| Game Screenshots
|
||||
.hidden-sm.hidden-md.hidden-lg
|
||||
small(data-i18n="about.screenshots_hint")
|
||||
| (click to view full size)
|
||||
#screenshot-grid
|
||||
a.screen-thumbnail(data-toggle="modal", data-target="#screenshot-lightbox", data-index='0')
|
||||
img(src="/images/pages/about/screenshot_desert.png")
|
||||
a.screen-thumbnail(data-toggle="modal", data-target="#screenshot-lightbox", data-index='1')
|
||||
img(src="/images/pages/about/screenshot_forest.png")
|
||||
a.screen-thumbnail(data-toggle="modal", data-target="#screenshot-lightbox", data-index='2')
|
||||
img(src="/images/pages/about/screenshot_dungeon.png")
|
||||
a.screen-thumbnail(data-toggle="modal", data-target="#screenshot-lightbox", data-index='3')
|
||||
img(src="/images/pages/about/screenshot_glacier.png")
|
||||
.clearfix.hidden-xs
|
||||
small(data-i18n="about.screenshots_hint")
|
||||
| (click to view full size)
|
||||
.col-md-6.col-lg-4
|
||||
#downloads-container
|
||||
#downloads
|
||||
.label(data-i18n="about.downloads_title")
|
||||
| Download Assets & Information
|
||||
ul
|
||||
li
|
||||
a.small(href="http://files.codecombat.com/presspack/AboutCodeCombat.pdf")
|
||||
span(data-i18n="about.about_codecombat")
|
||||
| About CodeCombat
|
||||
| (.pdf)
|
||||
li
|
||||
a.small(href="http://files.codecombat.com/presspack/CodeCombat-Logo.ai")
|
||||
span(data-i18n="about.logo")
|
||||
| Logo
|
||||
| (.ai)
|
||||
li
|
||||
a.small(href="http://files.codecombat.com/presspack/CodeCombat-Logo.png")
|
||||
span(data-i18n="about.logo")
|
||||
| Logo
|
||||
| (.png)
|
||||
li
|
||||
a.small(href="http://files.codecombat.com/presspack/GameImages.zip")
|
||||
span(data-i18n="about.screenshots")
|
||||
| Screenshots
|
||||
| (.zip)
|
||||
li
|
||||
a.small(href="http://files.codecombat.com/presspack/gameimages/CodeCombat_Splash.png")
|
||||
span(data-i18n="about.character_art")
|
||||
| Character Art
|
||||
| (.png)
|
||||
.text-center
|
||||
a.btn.btn-lg.btn-primary-alt#download-button(href="http://files.codecombat.com/presspack/CodeCombat_PressPack.zip")
|
||||
span.glyphicon.glyphicon-download-alt
|
||||
span(data-i18n="about.download_all")
|
||||
| Download All
|
||||
|
||||
#screenshot-lightbox.modal.fade(data-show="false")
|
||||
.modal-dialog
|
||||
.modal-content
|
||||
#screenshot-carousel.carousel
|
||||
ol.carousel-indicators
|
||||
li(data-target=".screenshot-display", data-slide-to="0").active
|
||||
li(data-target=".screenshot-display", data-slide-to="1")
|
||||
li(data-target=".screenshot-display", data-slide-to="2")
|
||||
li(data-target=".screenshot-display", data-slide-to="3")
|
||||
|
||||
.carousel-inner
|
||||
.item.active
|
||||
img#screenshot-desert(src="/images/pages/about/Desert.png")
|
||||
.item
|
||||
img#screenshot-forest(src="/images/pages/about/Forest.png")
|
||||
.item
|
||||
img#screenshot-dungeon(src="/images/pages/about/Dungeon.png")
|
||||
.item
|
||||
img#screenshot-glacier(src="/images/pages/about/Glacier.png")
|
||||
a#carousel-left.left.carousel-control(href="#screenshot-carousel", role="button")
|
||||
span.glyphicon.glyphicons-chevron-left.glyphicon-chevron-left(aria-hidden="true")
|
||||
span.sr-only Previous
|
||||
a#carousel-right.right.carousel-control(href="#screenshot-carousel", role="button")
|
||||
span.glyphicon.glyphicons-chevron-right.glyphicon-chevron-right(aria-hidden="true")
|
||||
span.sr-only Next
|
||||
|
||||
#location
|
||||
.row
|
||||
.col-sm-4.col-sm-offset-2
|
||||
p
|
||||
b CodeCombat Inc.
|
||||
p 360 3rd St.
|
||||
p Suite 700 (Livefyre)
|
||||
p San Francisco, CA 94107
|
||||
a(href="mailto:team@codecombat.com") team@codecombat.com
|
||||
.col-sm-4
|
||||
p
|
||||
b(data-i18n="about.location_title")
|
||||
| We're located in downtown SF:
|
||||
iframe(width="370", height="280", frameBorder="2", src="https://www.google.com/maps/embed/v1/place?key=AIzaSyAIGQz3OMbv5YWivScUM86-zESjEgJR2Xo&q=360+3rd+St+Suite+700,+San+Francisco,+CA+94107")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
extends /templates/core/modal-base
|
||||
extends /templates/core/modal-base-flat
|
||||
|
||||
block modal-header-content
|
||||
h3 Administer User
|
||||
|
|
|
@ -8,28 +8,28 @@ block content
|
|||
.container-fluid
|
||||
.row
|
||||
.col-md-5.big-stat.active-classes
|
||||
div.description Monthly Active Classes
|
||||
if activeClasses.length > 0
|
||||
div.description Monthly Active Classes
|
||||
div.count= activeClasses[0].groups[activeClasses[0].groups.length - 1]
|
||||
.col-md-5.big-stat.recurring-revenue
|
||||
div.description Monthly Recurring Revenue
|
||||
if revenue.length > 0
|
||||
div.description Monthly Recurring Revenue
|
||||
div.count $#{Math.round((revenue[0].groups[revenue[0].groups.length - 1]) / 100)}
|
||||
.col-md-5.big-stat.classroom-active-users
|
||||
div.description Classroom Monthly Active Users
|
||||
if activeUsers.length > 0
|
||||
- var classroomBigMAU = 0;
|
||||
each count, event in activeUsers[0].events
|
||||
if event.indexOf('MAU classroom') >= 0
|
||||
- classroomBigMAU += count;
|
||||
div.description Classroom Monthly Active Users
|
||||
div.count= classroomBigMAU
|
||||
.col-md-5.big-stat.campaign-active-users
|
||||
div.description Campaign Monthly Active Users
|
||||
if activeUsers.length > 0
|
||||
- var campaignBigMAU = 0;
|
||||
each count, event in activeUsers[0].events
|
||||
if event.indexOf('MAU campaign') >= 0
|
||||
- campaignBigMAU += count;
|
||||
div.description Campaign Monthly Active Users
|
||||
div.count= campaignBigMAU
|
||||
|
||||
ul.nav.nav-tabs
|
||||
|
@ -62,7 +62,10 @@ block content
|
|||
.small Paid class: at least one paid student in the classroom
|
||||
.small Trial class: not paid, at least one trial student in classroom
|
||||
.small Free class: not paid, not trial
|
||||
.active-classes-chart.line-chart-container
|
||||
.active-classes-chart-90.line-chart-container
|
||||
|
||||
h3 Active Classes 365 days
|
||||
.active-classes-chart-365.line-chart-container
|
||||
|
||||
h1#active-classes-table Active Classes
|
||||
table.table.table-striped.table-condensed
|
||||
|
@ -93,7 +96,7 @@ block content
|
|||
h1#recurring-revenue-table Recurring Revenue
|
||||
table.table.table-striped.table-condensed
|
||||
tr
|
||||
th Day
|
||||
th(style='min-width:85px;') Day
|
||||
for group in revenueGroups
|
||||
th= group.replace('DRR ', 'Daily ').replace('MRR ', 'Monthly ')
|
||||
each entry in revenue
|
||||
|
@ -107,34 +110,114 @@ block content
|
|||
.small Paid student: user.coursePrepaidID set and prepaid.properties.trialRequestID NOT set
|
||||
.small Trial student: user.coursePrepaidID set and prepaid.properties.trialRequestID set
|
||||
.small Free student: not paid, not trial
|
||||
.classroom-daily-active-users-chart.line-chart-container
|
||||
.classroom-daily-active-users-chart-90.line-chart-container
|
||||
|
||||
h3#classroom-maus-graph Classroom Monthly Active Users 90 days
|
||||
.classroom-monthly-active-users-chart.line-chart-container
|
||||
.classroom-monthly-active-users-chart-90.line-chart-container
|
||||
|
||||
h3#classroom-daus-graph Classroom Daily Active Users 365 days
|
||||
.classroom-daily-active-users-chart-365.line-chart-container
|
||||
|
||||
h3#classroom-maus-graph Classroom Monthly Active Users 365 days
|
||||
.classroom-monthly-active-users-chart-365.line-chart-container
|
||||
|
||||
h3#enrollments-graph Enrollments Issued and Redeemed 90 days
|
||||
.paid-courses-chart.line-chart-container
|
||||
|
||||
h3#furthest-courses-table Furthest Course
|
||||
.small Teacher: owner of a course instance
|
||||
.small Student: member of a course instance (assigned to course)
|
||||
.small For course instances created in last #{view.furthestCourseDayRange} days, not Single Player, hourOfCode != true
|
||||
.small Counts are not summed. I.e. a student or teacher only contributes to the count of one course.
|
||||
if view.teacherCourseDistribution
|
||||
table.table.table-striped.table-condensed
|
||||
tr
|
||||
th Course
|
||||
th Teachers
|
||||
th Students
|
||||
th Avg students per teacher
|
||||
each count, courseIndex in view.teacherCourseDistribution
|
||||
#furthest-course
|
||||
h3 Furthest Course in last #{view.furthestCourseDayRangeRecent} days
|
||||
.small Restricted to courses instances from last #{view.furthestCourseDayRangeRecent} days
|
||||
.small Teacher: owner of a course instance
|
||||
.small Student: member of a course instance (assigned to course)
|
||||
.small For course instances != Single Player, hourOfCode != true
|
||||
.small Counts are not summed. I.e. a student or teacher only contributes to the count of one course
|
||||
.small Paid student: user.coursePrepaidID set and prepaid.properties.trialRequestID NOT set
|
||||
.small Trial student: user.coursePrepaidID set and prepaid.properties.trialRequestID set
|
||||
.small Free student: not paid, not trial
|
||||
.small Paid teacher: at least one paid student in course instance
|
||||
.small Trial teacher: at least one trial student in course instance, and no paid students
|
||||
.small Free teacher: no paid students, no trial students
|
||||
.small Paid status takes precedent over furthest course, so teacher furthest course is furthest course of highest paid status student
|
||||
if view.courseDistributionsRecent
|
||||
table.table.table-striped.table-condensed
|
||||
tr
|
||||
td= view.courses.models[courseIndex].get('name')
|
||||
td= count
|
||||
td= view.studentCourseDistribution[courseIndex] || 0
|
||||
td= Math.round((view.studentCourseDistribution[courseIndex] || 0) / count)
|
||||
else
|
||||
div Loading ...
|
||||
th Course
|
||||
th Paid Teachers
|
||||
th Trial Teachers
|
||||
th Free Teachers
|
||||
th Total Teachers
|
||||
th Paid Students
|
||||
th Trial Students
|
||||
th Free Students
|
||||
th Total Students
|
||||
each row in view.courseDistributionsRecent
|
||||
tr
|
||||
td= row.courseName
|
||||
td= row.totals['Paid Teachers'] || 0
|
||||
td= row.totals['Trial Teachers'] || 0
|
||||
td= row.totals['Free Teachers'] || 0
|
||||
td= row.totals['Total Teachers'] || 0
|
||||
td= row.totals['Paid Students'] || 0
|
||||
td= row.totals['Trial Students'] || 0
|
||||
td= row.totals['Free Students'] || 0
|
||||
td= row.totals['Total Students'] || 0
|
||||
else
|
||||
div Loading ...
|
||||
|
||||
h3 Furthest Course in last #{view.furthestCourseDayRange} days
|
||||
if view.courseDistributions
|
||||
table.table.table-striped.table-condensed
|
||||
tr
|
||||
th Course
|
||||
th Paid Teachers
|
||||
th Trial Teachers
|
||||
th Free Teachers
|
||||
th Total Teachers
|
||||
th Paid Students
|
||||
th Trial Students
|
||||
th Free Students
|
||||
th Total Students
|
||||
each row in view.courseDistributions
|
||||
tr
|
||||
td= row.courseName
|
||||
td= row.totals['Paid Teachers'] || 0
|
||||
td= row.totals['Trial Teachers'] || 0
|
||||
td= row.totals['Free Teachers'] || 0
|
||||
td= row.totals['Total Teachers'] || 0
|
||||
td= row.totals['Paid Students'] || 0
|
||||
td= row.totals['Trial Students'] || 0
|
||||
td= row.totals['Free Students'] || 0
|
||||
td= row.totals['Total Students'] || 0
|
||||
else
|
||||
div Loading ...
|
||||
|
||||
#school-sales
|
||||
h3 School Sales
|
||||
if view.schoolSales
|
||||
table.table.table-striped.table-condensed
|
||||
tr
|
||||
th Amount
|
||||
th(style='min-width:85px;') Created
|
||||
th PaymentID
|
||||
th PrepaidID
|
||||
th Description
|
||||
th Email
|
||||
th School
|
||||
each val, i in view.schoolSales
|
||||
tr
|
||||
td $#{Math.round(val.amount / 100, 2)}
|
||||
td= new Date(val.created).toISOString().substring(0, 10)
|
||||
td= val._id
|
||||
td= val.prepaidID
|
||||
td= val.description
|
||||
if val.user
|
||||
td= val.user.emailLower
|
||||
td= val.user.schoolName
|
||||
else
|
||||
td
|
||||
td
|
||||
else
|
||||
div Loading ...
|
||||
|
||||
#school-counts
|
||||
h3 School Counts
|
||||
|
@ -199,13 +282,19 @@ block content
|
|||
td 0
|
||||
|
||||
.tab-pane#tab_campaign
|
||||
h3#campaign-daus-graph Campaign Daily Active Users 90 days
|
||||
h3 Campaign Daily Active Users 90 days
|
||||
.small Paid user: had monthly or yearly sub on given day
|
||||
.small Free user: not paid
|
||||
.campaign-daily-active-users-chart.line-chart-container
|
||||
.campaign-daily-active-users-chart-90.line-chart-container
|
||||
|
||||
h3#campaign-maus-graph Campaign Monthly Active Users 90 days
|
||||
.campaign-monthly-active-users-chart.line-chart-container
|
||||
h3 Campaign Monthly Active Users 90 days
|
||||
.campaign-monthly-active-users-chart-90.line-chart-container
|
||||
|
||||
h3 Campaign Daily Active Users 365 days
|
||||
.campaign-daily-active-users-chart-365.line-chart-container
|
||||
|
||||
h3 Campaign Monthly Active Users 365 days
|
||||
.campaign-monthly-active-users-chart-365.line-chart-container
|
||||
|
||||
h1#active-users-table Active Users
|
||||
if activeUsers.length > 0
|
||||
|
@ -236,7 +325,6 @@ block content
|
|||
.campaign-vs-classroom-monthly-active-users-recent-chart.line-chart-container
|
||||
|
||||
h3#campaign-vs-classroom-paid-maus-graph Campaign vs Classroom Paid Monthly Active Users 365 days
|
||||
.small TODO: aggregate active user data from last year
|
||||
.campaign-vs-classroom-monthly-active-users-chart.line-chart-container
|
||||
|
||||
h1#active-users-table Active Users
|
||||
|
|
|
@ -20,11 +20,12 @@ block content
|
|||
th Location
|
||||
th Age / Level
|
||||
th Students
|
||||
th How Found / Notes
|
||||
th Role
|
||||
th Phone
|
||||
th Status
|
||||
tbody
|
||||
- var numReviewed = 0
|
||||
- var maxReviewedShown = 100
|
||||
- var maxReviewedShown = 1000
|
||||
each trialRequest in trialRequests
|
||||
if trialRequest.get('status') !== 'submitted'
|
||||
- numReviewed++
|
||||
|
@ -42,7 +43,8 @@ block content
|
|||
td= props.location || trialRequest.locationString()
|
||||
td= props.age || trialRequest.educationLevelString()
|
||||
td= props.numStudents
|
||||
td= props.heardAbout || props.notes
|
||||
td= props.role
|
||||
td= props.phoneNumber
|
||||
td.status-cell
|
||||
if trialRequest.get('status') === 'submitted'
|
||||
button.btn.btn-xs.btn-success.btn-approve(data-trial-request-id=trialRequest.id) Approve
|
||||
|
@ -51,3 +53,10 @@ block content
|
|||
span= trialRequest.get('prepaidCode')
|
||||
else
|
||||
span= trialRequest.get('status')
|
||||
if props.heardAbout || props.notes
|
||||
tr
|
||||
td(colspan=2)
|
||||
td(colspan=7)
|
||||
strong #{trialRequest.nameString()} notes:
|
||||
div= props.heardAbout || props.notes
|
||||
td
|
||||
|
|
144
app/templates/base-flat.jade
Normal file
|
@ -0,0 +1,144 @@
|
|||
.style-flat
|
||||
block header
|
||||
.container-fluid.text-center
|
||||
.alert.alert-danger.lt-ie9
|
||||
strong(data-i18n="home.no_ie") The CodeCombat game does not run in Internet Explorer 8 or older. Sorry!
|
||||
|
||||
if view.isIPadBrowser() || view.isMobile()
|
||||
.alert.alert-danger.mobile
|
||||
strong(data-i18n="home.no_mobile") CodeCombat gameplay was not designed for mobile devices and may not work!
|
||||
else if view.isOldBrowser()
|
||||
.alert.alert-danger.old-browser
|
||||
strong(data-i18n="home.old_browser") Uh oh, your browser is too old to play CodeCombat. Sorry!
|
||||
br
|
||||
span(data-i18n="home.old_browser_suffix") You can try anyway, but it probably won't work.
|
||||
|
||||
nav.navbar.navbar-default
|
||||
.container
|
||||
.row
|
||||
.col-md-3.col-sm-4
|
||||
.navbar-header
|
||||
button.navbar-toggle.collapsed(data-toggle='collapse', data-target='#navbar-collapse' aria-expanded='false')
|
||||
span.sr-only Toggle navigation
|
||||
span.icon-bar
|
||||
span.icon-bar
|
||||
span.icon-bar
|
||||
a.navbar-brand(href="/")
|
||||
img#logo-img(src="/images/pages/base/logo.png")
|
||||
span.glyphicon.glyphicon-home
|
||||
|
||||
.col-md-9.col-sm-8
|
||||
#navbar-collapse.collapse.navbar-collapse
|
||||
ul.nav.navbar-nav.pull-left
|
||||
li
|
||||
a(href="/about") About
|
||||
li
|
||||
a(href="/courses") Courses
|
||||
li
|
||||
a(href="/courses/teachers") Teachers
|
||||
li
|
||||
a(href="https://discourse.codecombat.com/") Forum
|
||||
if me.isAnonymous()
|
||||
li
|
||||
a#create-account-link.signup-button Create Account
|
||||
li
|
||||
a#login-link.login-button Login
|
||||
else
|
||||
li.dropdown
|
||||
a.dropdown-toggle(href="#", data-toggle="dropdown" role="button" aroa-haspopup="true" aria-expanded="false")
|
||||
span.spr My Account
|
||||
ul.dropdown-menu
|
||||
li.user-dropdown-header.text-center
|
||||
span.user-level= me.level()
|
||||
a(href="/user/#{me.getSlugOrID()}")
|
||||
img.img-circle(src=me.getPhotoURL())
|
||||
h5=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(href="/account/prepaid", data-i18n="account.prepaid_codes") Prepaid Codes
|
||||
li
|
||||
a#logout-button(data-i18n="login.log_out")
|
||||
|
||||
li
|
||||
#language-dropdown-wrapper
|
||||
select.language-dropdown.form-control
|
||||
|
||||
|
||||
block outer_content
|
||||
#site-content-area
|
||||
block content
|
||||
p If this is showing, you dun goofed
|
||||
|
||||
block footer
|
||||
#character-lineup.text-center
|
||||
img(src="/images/pages/home/character_lineup.png")
|
||||
.container-fluid
|
||||
#footer.small
|
||||
.container
|
||||
.row
|
||||
.col-sm-3
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong CodeCombat
|
||||
li
|
||||
a(href="/about") About
|
||||
li
|
||||
a(href="/Careers") Jobs
|
||||
li
|
||||
a(href="http://blog.codecombat.com/", data-i18n="nav.blog")
|
||||
li
|
||||
a(href="/legal") Legal
|
||||
|
||||
.col-sm-3
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong Schools
|
||||
li
|
||||
a(href="/courses/teachers") Teachers
|
||||
li
|
||||
a(href="https://sites.google.com/a/codecombat.com/teacher-guides/") Educator Wiki
|
||||
li
|
||||
a(href="/teachers/quote") Request a Quote
|
||||
|
||||
.col-sm-3
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong Get Involved
|
||||
li
|
||||
a(href='/community', data-i18n="nav.community")
|
||||
li
|
||||
a(href="/contribute") Contribute
|
||||
li
|
||||
a(href=view.forumLink(), data-i18n="nav.forum")
|
||||
li
|
||||
a(href="/play/ladder") Multiplayer
|
||||
li
|
||||
a(href="https://github.com/codecombat/codecombat") Open source (GitHub)
|
||||
.col-sm-3
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong Support
|
||||
li
|
||||
a(href="https://discourse.codecombat.com/t/faq-check-before-posting/1027") FAQs
|
||||
li
|
||||
a(tabindex=-1, data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact")
|
||||
li
|
||||
a(href="https://www.facebook.com/codecombat", data-i18n="nav.facebook")
|
||||
li
|
||||
a(href="https://twitter.com/codecombat", data-i18n="nav.twitter")
|
||||
|
||||
#final-footer.small.text-center
|
||||
| Copyright ©2016 CodeCombat. All Rights Reserved.
|
||||
br.hidden-lg.hidden-md
|
||||
img(src="/images/pages/base/logo.png" alt="CodeCombat")
|
||||
br.hidden-lg.hidden-md
|
||||
span.spr Need help? Email
|
||||
a(href="mailto:team@codecombat.com") team@codecombat.com
|
||||
span.spl and we'll get in touch!
|
32
app/templates/core/modal-base-flat.jade
Normal file
|
@ -0,0 +1,32 @@
|
|||
.modal-dialog
|
||||
.modal-content.style-flat
|
||||
block modal-header
|
||||
.modal-header
|
||||
if view.closeButton
|
||||
.button.close(type="button", data-dismiss="modal", aria-hidden="true") ×
|
||||
block modal-header-content
|
||||
if view.options.headerContent
|
||||
h3!= view.options.headerContent
|
||||
else
|
||||
h3 man bites God
|
||||
|
||||
block modal-body
|
||||
.modal-body
|
||||
block modal-body-content
|
||||
if view.options.bodyContent
|
||||
div!= view.options.bodyContent
|
||||
else
|
||||
p Man Bites God are the bad boys of the Melbourne live music and comedy scene. It is like being drowned in a bathtub of harmony.
|
||||
img(src="http://www.manbitesgod.com/images/picturecoupleb.jpg")
|
||||
img(src="http://www.manbitesgod.com/images/manrantb.jpg")
|
||||
|
||||
.modal-body.wait.secret
|
||||
block modal-body-wait-content
|
||||
h3 Reticulating Splines...
|
||||
.progress.progress-striped.active
|
||||
.progress-bar
|
||||
|
||||
block modal-footer
|
||||
.modal-footer
|
||||
block modal-footer-content
|
||||
button.btn.btn-primary(type="button", data-dismiss="modal", aria-hidden="true", data-i18n="modal.okay") Okay
|
|
@ -1,4 +1,4 @@
|
|||
.modal-dialog
|
||||
.modal-dialog.game
|
||||
.background-wrapper
|
||||
.modal-content
|
||||
block modal-header
|
||||
|
|
|
@ -1,388 +1,253 @@
|
|||
.container-fluid.text-center
|
||||
.alert.alert-danger.lt-ie9
|
||||
strong(data-i18n="home.no_ie") The CodeCombat game does not run in Internet Explorer 8 or older. Sorry!
|
||||
extends /templates/base-flat
|
||||
|
||||
if view.isIPadBrowser() || view.isMobile()
|
||||
.alert.alert-danger.mobile
|
||||
strong(data-i18n="home.no_mobile") CodeCombat gameplay was not designed for mobile devices and may not work!
|
||||
else if view.isOldBrowser()
|
||||
.alert.alert-danger.old-browser
|
||||
strong(data-i18n="home.old_browser") Uh oh, your browser is too old to play CodeCombat. Sorry!
|
||||
br
|
||||
span(data-i18n="home.old_browser_suffix") You can try anyway, but it probably won't work.
|
||||
|
||||
nav.navbar.navbar-default
|
||||
.container
|
||||
.row
|
||||
.col-md-3.col-sm-4
|
||||
.navbar-header
|
||||
button.navbar-toggle.collapsed(data-toggle='collapse', data-target='#navbar-collapse' aria-expanded='false')
|
||||
span.sr-only Toggle navigation
|
||||
span.icon-bar
|
||||
span.icon-bar
|
||||
span.icon-bar
|
||||
a.navbar-brand(href="/")
|
||||
img#logo-img(src="/images/pages/base/logo.png")
|
||||
span.glyphicon.glyphicon-home
|
||||
|
||||
.col-md-9.col-sm-8
|
||||
#navbar-collapse.collapse.navbar-collapse
|
||||
ul.nav.navbar-nav.pull-left
|
||||
li
|
||||
a(href="/about") About
|
||||
li
|
||||
a(href="/courses") Courses
|
||||
li
|
||||
a(href="/courses/teachers") Teachers
|
||||
li
|
||||
a(href="https://discourse.codecombat.com/") Forum
|
||||
if me.isAnonymous()
|
||||
li
|
||||
a#create-account-link.signup-button Create Account
|
||||
li
|
||||
a#login-link.login-button Login
|
||||
else
|
||||
li.dropdown
|
||||
a.dropdown-toggle(href="#", data-toggle="dropdown" role="button" aroa-haspopup="true" aria-expanded="false")
|
||||
span.spr My Account
|
||||
ul.dropdown-menu
|
||||
li.user-dropdown-header.text-center
|
||||
span.user-level= me.level()
|
||||
a(href="/user/#{me.getSlugOrID()}")
|
||||
img.img-circle(src=me.getPhotoURL())
|
||||
h5=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(href="/account/prepaid", data-i18n="account.prepaid_codes") Prepaid Codes
|
||||
li
|
||||
a#logout-button(data-i18n="login.log_out")
|
||||
|
||||
li
|
||||
#language-dropdown-wrapper
|
||||
select.language-dropdown.form-control
|
||||
|
||||
.container-fluid#jumbotron-container-fluid(class=view.jumbotron === 'student' ? 'alt-image' : '')
|
||||
.container
|
||||
.row
|
||||
.col-lg-7.col-md-8
|
||||
h1 The most engaging game for learning programming.
|
||||
|
||||
.col-lg-3.col-lg-offset-2.col-md-4
|
||||
.well.text-center.hidden-md.hidden-lg
|
||||
.row
|
||||
.col-xs-8
|
||||
h6 Classroom Edition:
|
||||
.col-xs-4
|
||||
h6 Learn to code:
|
||||
.row
|
||||
.col-xs-4
|
||||
button#teacher-btn.btn.btn-primary.btn-lg.btn-block Teacher
|
||||
.col-xs-4
|
||||
a.btn.btn-forest.btn-lg.btn-block(href="/courses") Student
|
||||
.col-xs-4
|
||||
a.btn.btn-gold.btn-lg.btn-block(href=view.playURL) Play Now
|
||||
|
||||
.well.text-center.hidden-xs.hidden-sm
|
||||
h6#classroom-edition-header Classroom Edition:
|
||||
div
|
||||
button#teacher-btn.btn.btn-primary.btn-lg.btn-block I'm a Teacher
|
||||
div
|
||||
a.btn.btn-forest.btn-lg.btn-block(href="/courses") I'm a Student
|
||||
|
||||
h6#learn-to-code-header Learn to code:
|
||||
a.btn.btn-gold.btn-lg.btn-block(href=view.playURL) Play Now
|
||||
.row#learn-more-row
|
||||
.col-xs-12.text-center
|
||||
a#learn-more-link
|
||||
h6 Learn more
|
||||
h2
|
||||
span.glyphicon.glyphicon-chevron-down
|
||||
|
||||
|
||||
.container#classroom-in-box-container
|
||||
#classroom-in-box-row.row
|
||||
.col-sm-6
|
||||
h2.text-navy A classroom in-a-box for teaching computer science.
|
||||
.col-sm-6
|
||||
p CodeCombat is a platform for students to learn computer science while playing through a real game.
|
||||
p Our courses have been specifically playtested to excel in a classroom setting, even by teachers with little to no prior programming experience.
|
||||
|
||||
#feature-spread-row.row.text-center
|
||||
h3 Designed with teachers in mind
|
||||
.col-sm-4
|
||||
img.img-circle(src="/images/pages/home/F1_typedcode.png")
|
||||
h4
|
||||
| Real, typed code
|
||||
br
|
||||
| from the first level
|
||||
p.small Getting students to typed code as quickly as possible is critical to learning programming syntax and proper structure.
|
||||
|
||||
.col-sm-4
|
||||
img.img-circle(src="/images/pages/home/F2_teacherguides.png")
|
||||
h4
|
||||
| Educator resources
|
||||
br
|
||||
| and course guides
|
||||
p.small Teaching computer science does not require a costly degree, because we provide tools to support educators of all backgrounds.
|
||||
|
||||
.col-sm-4
|
||||
img.img-circle(src="/images/pages/home/F3_accessible.png")
|
||||
h4
|
||||
| Accessible to
|
||||
br
|
||||
| everyone
|
||||
p.small Democratizing the process of learning coding is at the core of our philosophy. Everyone should be able to learn to code.
|
||||
|
||||
.testimonials-rows
|
||||
.testimonials-filler-left
|
||||
.testimonials-filler-right
|
||||
.row
|
||||
.col-lg-offset-2.col-lg-7.col-sm-8
|
||||
blockquote
|
||||
h3 I think they actually forgot that they were actually learning something.
|
||||
|
||||
.col-lg-2.col-sm-3.text-center
|
||||
img.img-circle(src="/images/pages/home/timmaki.png")
|
||||
h6 Tim Maki
|
||||
.small Director of Technology, Tilton School
|
||||
|
||||
.row
|
||||
.col-lg-7.col-sm-8.col-sm-push-4.col-lg-push-3
|
||||
blockquote
|
||||
h3 Coding is something I've always wanted to do, and I never thought I would be able to learn it in school.
|
||||
|
||||
.col-lg-2.col-sm-3.col-lg-offset-1.text-center.col-sm-pull-8.col-lg-pull-7
|
||||
img.img-circle(src="/images/pages/home/dylan.png")
|
||||
h6 Dylan
|
||||
.small 3rd Grader
|
||||
|
||||
|
||||
h3.text-center Why is learning through games important?
|
||||
|
||||
#benefit-row-1.row
|
||||
#benefit-graphic-1.col-sm-6.col-sm-offset-1.col-sm-push-6
|
||||
h2 Games reward the productive struggle.
|
||||
img(src="/images/pages/home/G1_reward.png")
|
||||
#benefit-graphic-1-filler
|
||||
|
||||
.col-sm-5.col-sm-pull-6
|
||||
p
|
||||
| Gaming is a medium that encourages interaction, discovery, and trial-and-error.
|
||||
| A good game challenges the player to master skills over time,
|
||||
| which is the same critical process students go through as they learn.
|
||||
p
|
||||
| Games excel at rewarding “
|
||||
a(href="http://blog.mindresearch.org/blog/game-based-learning-infographic-strong-math-practices" target="_blank") productive struggle
|
||||
span.spr ” - the kind of struggle that results in learning that’s engaging and
|
||||
a(href="http://www.gamesandlearning.org/2014/06/09/teachers-on-using-games-in-class/" target="_blank") motivating
|
||||
| , not tedious.
|
||||
|
||||
|
||||
|
||||
#benefit-row-2.row
|
||||
#benefit-graphic-2.col-sm-6
|
||||
h2 Studies suggest gaming is good for children’s brains. (it’s true!)
|
||||
img(src="/images/pages/home/G2_brains.png")
|
||||
#benefit-graphic-2-filler
|
||||
|
||||
.col-sm-5.col-sm-offset-1
|
||||
p
|
||||
span.spr When game-based learning systems are
|
||||
a(href="http://schoolsweek.co.uk/gaming-is-good-for-childrens-brains-study-suggests/" target="_blank") compared
|
||||
span.spl.spr against conventional assessment methods, the difference is clear: games are better at helping students retain knowledge, concentrate and
|
||||
a(href="http://dev.k-12techdecisions.com/article/game_based_learning_is_where_vygotsky_meets_dweck/P3" target="_blank") perform at a higher level of achievement
|
||||
| .
|
||||
p
|
||||
| Games also provide real-time feedback that allows students to adjust their solution path and understand concepts more holistically, instead of being limited to just “correct” or “incorrect” answers.
|
||||
|
||||
#benefit-row-3.row
|
||||
#benefit-graphic-3.col-sm-6.col-sm-offset-1.col-sm-push-6
|
||||
h2 A real game, played with real coding.
|
||||
img(src="/images/pages/home/G3_game.png")
|
||||
#benefit-graphic-3-filler
|
||||
|
||||
.col-sm-5.col-sm-pull-6
|
||||
p
|
||||
| A great game is more than just badges and achievements - it’s about a player’s journey, well-designed puzzles, and the ability to tackle challenges with agency and confidence.
|
||||
p
|
||||
| CodeCombat is a game that gives players that agency and confidence with our robust typed code engine, which helps beginner and advanced students alike write proper, valid code.
|
||||
|
||||
|
||||
.request-demo-row.text-center
|
||||
h3 Curious? Request a demo and we'll show you the ropes
|
||||
h4 Or create a class and see it for yourself!
|
||||
div
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/freetrial") Request a Demo
|
||||
a.btn.btn-primary-alt.btn-lg(href="/courses/teachers") Create a Class
|
||||
div
|
||||
if me.isAnonymous()
|
||||
span.spr Already have an account?
|
||||
a.login-button Login
|
||||
else
|
||||
span.spr You are currently logged in as #{me.broadName()}.
|
||||
a(href="/courses/teachers") View my classes
|
||||
span.spr.spl or
|
||||
a#logout-button logout
|
||||
|
||||
h3.text-center Computer science courses for all ages
|
||||
h4.text-center
|
||||
span#school-level-label Show me lesson time estimates for:
|
||||
select#school-level-dropdown.form-control.text-navy
|
||||
option(value='elementary') Elementary School
|
||||
option(value='middle', selected=true) Middle School
|
||||
option(value='high') High School
|
||||
h5.text-center#total-hours-header
|
||||
span.spr Total curriculum hours:
|
||||
span#semester-duration
|
||||
#courses-row.row
|
||||
- var conceptsSeen = {};
|
||||
- var lastScreenshot = "";
|
||||
for course, courseIndex in view.courses.models
|
||||
.col-md-3.col-sm-4
|
||||
.media.course-details(data-course-slug=course.get('slug'))
|
||||
if courseIndex === 0
|
||||
.free-course
|
||||
h6 Free for all students
|
||||
.media-body(title=course.get('description'))
|
||||
h6.course-name= course.get('name') + ':'
|
||||
p.small
|
||||
- var total = 0;
|
||||
each concept in course.get('concepts')
|
||||
- if (conceptsSeen[concept]) continue;
|
||||
- conceptsSeen[concept] = true;
|
||||
if total === 3
|
||||
- total += 1;
|
||||
span ...
|
||||
- continue;
|
||||
else if total > 3
|
||||
- continue;
|
||||
else if total > 0
|
||||
span.spr ,
|
||||
- total += 1;
|
||||
span(data-i18n="concepts." + concept)
|
||||
img.media-object(src="/images/pages/home/course"+(courseIndex+1)+".png")
|
||||
- lastScreenshot = course.get('screenshot');
|
||||
h6.course-duration
|
||||
span.spr Lesson time:
|
||||
span.course-hours= course.get('duration') || 0
|
||||
span.spl.unit(data-i18n="units.hours")
|
||||
for upcomingCourse in ['Computer Science 6', 'Computer Science 7', 'Computer Science 8']
|
||||
.col-md-3.col-sm-4
|
||||
.media.disabled
|
||||
.media-body
|
||||
h6.course-name= upcomingCourse + ':'
|
||||
p.small Coming soon!
|
||||
img.media-object(src="/images/pages/home/inprogress.png")
|
||||
|
||||
.clearfix
|
||||
.text-center
|
||||
h4
|
||||
img(src="/images/pages/home/course_languages.png")
|
||||
div Courses are available in JavaScript, Python, and Java (coming soon!)
|
||||
|
||||
.testimonials-rows
|
||||
.testimonials-filler-left
|
||||
.testimonials-filler-right
|
||||
.row
|
||||
.col-lg-offset-2.col-lg-7.col-sm-8
|
||||
blockquote
|
||||
h3 Boasts riddles that are complex enough to fascinate gamers and coders alike.
|
||||
|
||||
.col-lg-2.col-sm-3.text-center
|
||||
img.img-circle(src="/images/pages/home/opensource.png")
|
||||
h6 Open Source
|
||||
.small opensource.com
|
||||
|
||||
.row
|
||||
.col-lg-7.col-sm-8.col-sm-push-4.col-lg-push-3
|
||||
blockquote
|
||||
h3 A winning combination of RPG gameplay and programming homework that pulls off making kid-friendly education legitimately enjoyable.
|
||||
|
||||
.col-lg-2.col-sm-3.col-lg-offset-1.text-center.col-sm-pull-8.col-lg-pull-7
|
||||
img.img-circle(src="/images/pages/home/pcmag.png")
|
||||
h6 PC Mag
|
||||
.small pcmag.com
|
||||
|
||||
|
||||
.request-demo-row.text-center
|
||||
h3 Everything you need to run a computer science class in your school today, no CS background required.
|
||||
p
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/freetrial") Request a Demo
|
||||
a.btn.btn-primary-alt.btn-lg(href="/courses/teachers") Create a Class
|
||||
|
||||
|
||||
.text-center
|
||||
img(src="/images/pages/home/character_lineup.png")
|
||||
|
||||
|
||||
.container-fluid
|
||||
#footer.small
|
||||
block content
|
||||
.container-fluid#jumbotron-container-fluid(class=view.jumbotron === 'student' ? 'alt-image' : '')
|
||||
.container
|
||||
.row
|
||||
.col-sm-3
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong CodeCombat
|
||||
li
|
||||
a(href="/about") About
|
||||
li
|
||||
a(href="/Careers") Jobs
|
||||
li
|
||||
a(href="http://blog.codecombat.com/", data-i18n="nav.blog")
|
||||
li
|
||||
a(href="/legal") Legal
|
||||
.col-lg-7.col-md-8
|
||||
h1 The most engaging game for learning programming.
|
||||
|
||||
.col-lg-3.col-lg-offset-2.col-md-4
|
||||
.well.text-center.hidden-md.hidden-lg
|
||||
.row
|
||||
.col-xs-8
|
||||
h6 Classroom Edition:
|
||||
.col-xs-4
|
||||
h6 Learn to code:
|
||||
.row
|
||||
.col-xs-4
|
||||
button#teacher-btn.btn.btn-primary.btn-lg.btn-block Teacher
|
||||
.col-xs-4
|
||||
a.btn.btn-forest.btn-lg.btn-block(href="/courses") Student
|
||||
.col-xs-4
|
||||
a.btn.btn-gold.btn-lg.btn-block(href=view.playURL) Play Now
|
||||
|
||||
.well.text-center.hidden-xs.hidden-sm
|
||||
h6#classroom-edition-header Classroom Edition:
|
||||
div
|
||||
button#teacher-btn.btn.btn-primary.btn-lg.btn-block I'm a Teacher
|
||||
div
|
||||
a.btn.btn-forest.btn-lg.btn-block(href="/courses") I'm a Student
|
||||
|
||||
.col-sm-3
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong Schools
|
||||
li
|
||||
a(href="/courses/teachers") Teachers
|
||||
li
|
||||
a(href="https://sites.google.com/a/codecombat.com/teacher-guides/") Educator Wiki
|
||||
li
|
||||
a(href="/teachers/quote") Request a Quote
|
||||
h6#learn-to-code-header Learn to code:
|
||||
a.btn.btn-gold.btn-lg.btn-block(href=view.playURL) Play Now
|
||||
.row#learn-more-row
|
||||
.col-xs-12.text-center
|
||||
a#learn-more-link
|
||||
h6 Learn more
|
||||
h2
|
||||
span.glyphicon.glyphicon-chevron-down
|
||||
|
||||
|
||||
.container#classroom-in-box-container
|
||||
#classroom-in-box-row.row
|
||||
.col-sm-6
|
||||
h2.text-navy A classroom in-a-box for teaching computer science.
|
||||
.col-sm-6
|
||||
p CodeCombat is a platform for students to learn computer science while playing through a real game.
|
||||
p Our courses have been specifically playtested to excel in a classroom setting, even by teachers with little to no prior programming experience.
|
||||
|
||||
#feature-spread-row.row.text-center
|
||||
h3 Designed with teachers in mind
|
||||
.col-sm-4
|
||||
img.img-circle(src="/images/pages/home/F1_typedcode.png")
|
||||
h4
|
||||
| Real, typed code
|
||||
br
|
||||
| from the first level
|
||||
p.small Getting students to typed code as quickly as possible is critical to learning programming syntax and proper structure.
|
||||
|
||||
.col-sm-4
|
||||
img.img-circle(src="/images/pages/home/F2_teacherguides.png")
|
||||
h4
|
||||
| Educator resources
|
||||
br
|
||||
| and course guides
|
||||
p.small Teaching computer science does not require a costly degree, because we provide tools to support educators of all backgrounds.
|
||||
|
||||
.col-sm-4
|
||||
img.img-circle(src="/images/pages/home/F3_accessible.png")
|
||||
h4
|
||||
| Accessible to
|
||||
br
|
||||
| everyone
|
||||
p.small Democratizing the process of learning coding is at the core of our philosophy. Everyone should be able to learn to code.
|
||||
|
||||
.testimonials-rows
|
||||
.testimonials-filler-left
|
||||
.testimonials-filler-right
|
||||
.row
|
||||
.col-lg-offset-2.col-lg-7.col-sm-8
|
||||
blockquote
|
||||
h3 I think they actually forgot that they were actually learning something.
|
||||
|
||||
.col-lg-2.col-sm-3.text-center
|
||||
img.img-circle(src="/images/pages/home/timmaki.png")
|
||||
h6 Tim Maki
|
||||
.small Director of Technology, Tilton School
|
||||
|
||||
.row
|
||||
.col-lg-7.col-sm-8.col-sm-push-4.col-lg-push-3
|
||||
blockquote
|
||||
h3 Coding is something I've always wanted to do, and I never thought I would be able to learn it in school.
|
||||
|
||||
.col-sm-3
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong Get Involved
|
||||
li
|
||||
a(href='/community', data-i18n="nav.community")
|
||||
li
|
||||
a(href="/contribute") Contribute
|
||||
li
|
||||
a(href=view.forumLink(), data-i18n="nav.forum")
|
||||
li
|
||||
a(href="/play/ladder") Multiplayer
|
||||
li
|
||||
a(href="https://github.com/codecombat/codecombat") Open source (GitHub)
|
||||
.col-sm-3
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong Support
|
||||
li
|
||||
a(href="https://discourse.codecombat.com/t/faq-check-before-posting/1027") FAQs
|
||||
li
|
||||
a(tabindex=-1, data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact")
|
||||
li
|
||||
a(href="https://www.facebook.com/codecombat", data-i18n="nav.facebook")
|
||||
li
|
||||
a(href="https://twitter.com/codecombat", data-i18n="nav.twitter")
|
||||
|
||||
#final-footer.small.text-center
|
||||
| Copyright ©2016 CodeCombat. All Rights Reserved.
|
||||
br.hidden-lg.hidden-md
|
||||
img(src="/images/pages/base/logo.png" alt="CodeCombat")
|
||||
br.hidden-lg.hidden-md
|
||||
span.spr Need help? Email
|
||||
a(href="mailto:team@codecombat.com") team@codecombat.com
|
||||
span.spl and we'll get in touch!
|
||||
|
||||
.col-lg-2.col-sm-3.col-lg-offset-1.text-center.col-sm-pull-8.col-lg-pull-7
|
||||
img.img-circle(src="/images/pages/home/dylan.png")
|
||||
h6 Dylan
|
||||
.small 3rd Grader
|
||||
|
||||
|
||||
h3.text-center Why is learning through games important?
|
||||
|
||||
#benefit-row-1.row
|
||||
#benefit-graphic-1.col-sm-6.col-sm-offset-1.col-sm-push-6
|
||||
h2 Games reward the productive struggle.
|
||||
img(src="/images/pages/home/G1_reward.png")
|
||||
#benefit-graphic-1-filler
|
||||
|
||||
.col-sm-5.col-sm-pull-6
|
||||
p
|
||||
| Gaming is a medium that encourages interaction, discovery, and trial-and-error.
|
||||
| A good game challenges the player to master skills over time,
|
||||
| which is the same critical process students go through as they learn.
|
||||
p
|
||||
| Games excel at rewarding “
|
||||
a(href="http://blog.mindresearch.org/blog/game-based-learning-infographic-strong-math-practices" target="_blank") productive struggle
|
||||
span.spr ” - the kind of struggle that results in learning that’s engaging and
|
||||
a(href="http://www.gamesandlearning.org/2014/06/09/teachers-on-using-games-in-class/" target="_blank") motivating
|
||||
| , not tedious.
|
||||
|
||||
|
||||
|
||||
#benefit-row-2.row
|
||||
#benefit-graphic-2.col-sm-6
|
||||
h2 Studies suggest gaming is good for children’s brains. (it’s true!)
|
||||
img(src="/images/pages/home/G2_brains.png")
|
||||
#benefit-graphic-2-filler
|
||||
|
||||
.col-sm-5.col-sm-offset-1
|
||||
p
|
||||
span.spr When game-based learning systems are
|
||||
a(href="http://schoolsweek.co.uk/gaming-is-good-for-childrens-brains-study-suggests/" target="_blank") compared
|
||||
span.spl.spr against conventional assessment methods, the difference is clear: games are better at helping students retain knowledge, concentrate and
|
||||
a(href="http://dev.k-12techdecisions.com/article/game_based_learning_is_where_vygotsky_meets_dweck/P3" target="_blank") perform at a higher level of achievement
|
||||
| .
|
||||
p
|
||||
| Games also provide real-time feedback that allows students to adjust their solution path and understand concepts more holistically, instead of being limited to just “correct” or “incorrect” answers.
|
||||
|
||||
#benefit-row-3.row
|
||||
#benefit-graphic-3.col-sm-6.col-sm-offset-1.col-sm-push-6
|
||||
h2 A real game, played with real coding.
|
||||
img(src="/images/pages/home/G3_game.png")
|
||||
#benefit-graphic-3-filler
|
||||
|
||||
.col-sm-5.col-sm-pull-6
|
||||
p
|
||||
| A great game is more than just badges and achievements - it’s about a player’s journey, well-designed puzzles, and the ability to tackle challenges with agency and confidence.
|
||||
p
|
||||
| CodeCombat is a game that gives players that agency and confidence with our robust typed code engine, which helps beginner and advanced students alike write proper, valid code.
|
||||
|
||||
|
||||
.request-demo-row.text-center
|
||||
h3 Curious? Request a demo and we'll show you the ropes
|
||||
h4 Or create a class and see it for yourself!
|
||||
div
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/freetrial") Request a Demo
|
||||
a.btn.btn-primary-alt.btn-lg(href="/courses/teachers") Create a Class
|
||||
div
|
||||
if me.isAnonymous()
|
||||
span.spr Already have an account?
|
||||
a.login-button Login
|
||||
else
|
||||
span.spr You are currently logged in as #{me.broadName()}.
|
||||
a(href="/courses/teachers") View my classes
|
||||
span.spr.spl or
|
||||
a#logout-button logout
|
||||
|
||||
h3.text-center Computer science courses for all ages
|
||||
h4.text-center
|
||||
span#school-level-label Show me lesson time estimates for:
|
||||
select#school-level-dropdown.form-control.text-navy
|
||||
option(value='elementary') Elementary School
|
||||
option(value='middle', selected=true) Middle School
|
||||
option(value='high') High School
|
||||
h5.text-center#total-hours-header
|
||||
span.spr Total curriculum hours:
|
||||
span#semester-duration
|
||||
#courses-row.row
|
||||
- var conceptsSeen = {};
|
||||
- var lastScreenshot = "";
|
||||
for course, courseIndex in view.courses.models
|
||||
.col-md-3.col-sm-4
|
||||
.media.course-details(data-course-slug=course.get('slug'))
|
||||
if courseIndex === 0
|
||||
.free-course
|
||||
h6 Free for all students
|
||||
.media-body(title=course.get('description'))
|
||||
h6.course-name= course.get('name') + ':'
|
||||
p.small
|
||||
- var total = 0;
|
||||
each concept in course.get('concepts')
|
||||
- if (conceptsSeen[concept]) continue;
|
||||
- conceptsSeen[concept] = true;
|
||||
if total === 3
|
||||
- total += 1;
|
||||
span ...
|
||||
- continue;
|
||||
else if total > 3
|
||||
- continue;
|
||||
else if total > 0
|
||||
span.spr ,
|
||||
- total += 1;
|
||||
span(data-i18n="concepts." + concept)
|
||||
img.media-object(src="/images/pages/home/course"+(courseIndex+1)+".png")
|
||||
- lastScreenshot = course.get('screenshot');
|
||||
h6.course-duration
|
||||
span.spr Lesson time:
|
||||
span.course-hours= course.get('duration') || 0
|
||||
span.spl.unit(data-i18n="units.hours")
|
||||
for upcomingCourse in ['Computer Science 6', 'Computer Science 7', 'Computer Science 8']
|
||||
.col-md-3.col-sm-4
|
||||
.media.disabled
|
||||
.media-body
|
||||
h6.course-name= upcomingCourse + ':'
|
||||
p.small Coming soon!
|
||||
img.media-object(src="/images/pages/home/inprogress.png")
|
||||
|
||||
.clearfix
|
||||
.text-center
|
||||
h4
|
||||
img(src="/images/pages/home/course_languages.png")
|
||||
div Courses are available in JavaScript, Python, and Java (coming soon!)
|
||||
|
||||
.testimonials-rows
|
||||
.testimonials-filler-left
|
||||
.testimonials-filler-right
|
||||
.row
|
||||
.col-lg-offset-2.col-lg-7.col-sm-8
|
||||
blockquote
|
||||
h3 Boasts riddles that are complex enough to fascinate gamers and coders alike.
|
||||
|
||||
.col-lg-2.col-sm-3.text-center
|
||||
img.img-circle(src="/images/pages/home/opensource.png")
|
||||
h6 Open Source
|
||||
.small opensource.com
|
||||
|
||||
.row
|
||||
.col-lg-7.col-sm-8.col-sm-push-4.col-lg-push-3
|
||||
blockquote
|
||||
h3 A winning combination of RPG gameplay and programming homework that pulls off making kid-friendly education legitimately enjoyable.
|
||||
|
||||
.col-lg-2.col-sm-3.col-lg-offset-1.text-center.col-sm-pull-8.col-lg-pull-7
|
||||
img.img-circle(src="/images/pages/home/pcmag.png")
|
||||
h6 PC Mag
|
||||
.small pcmag.com
|
||||
|
||||
|
||||
.request-demo-row.text-center
|
||||
h3 Everything you need to run a computer science class in your school today, no CS background required.
|
||||
p
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/freetrial") Request a Demo
|
||||
a.btn.btn-primary-alt.btn-lg(href="/courses/teachers") Create a Class
|
||||
|
||||
|
||||
|
|
|
@ -1,155 +1,152 @@
|
|||
extends /templates/base
|
||||
extends /templates/base-flat
|
||||
|
||||
block content
|
||||
form.form(class=view.trialRequest.isNew() ? '' : 'hide')
|
||||
h1.text-center(data-i18n="teachers_quote.title")
|
||||
p.text-center(data-i18n="teachers_quote.subtitle")
|
||||
.container
|
||||
form.form(class=view.trialRequest.isNew() ? '' : 'hide')
|
||||
h1.text-center(data-i18n="teachers_quote.title")
|
||||
h2.text-center(data-i18n="teachers_quote.subtitle")
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.name")
|
||||
- var name = me.get('name') || '';
|
||||
input.form-control(name="name" value=name, disabled=!!name)
|
||||
|
||||
.col-sm-4
|
||||
#email-form-group.form-group
|
||||
label.control-label(data-i18n="general.email")
|
||||
- var email = me.get('email') || '';
|
||||
input.form-control(name="email" type="email", value=email, disabled=!!email)
|
||||
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
.form-group
|
||||
label.control-label
|
||||
span(data-i18n="teachers_quote.phone_number")
|
||||
span.spl.text-muted(data-i18n="signup.optional")
|
||||
.help-block
|
||||
em.text-info(data-i18n="teachers_quote.phone_number_help")
|
||||
input.form-control(name="phoneNumber")
|
||||
|
||||
.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.role_label")
|
||||
.help-block
|
||||
em.text-info(data-i18n="teachers_quote.role_help")
|
||||
select.form-control(name="role")
|
||||
option
|
||||
option(data-i18n="courses.teacher", value="Teacher")
|
||||
option(data-i18n="teachers_quote.tech_coordinator", value="Technology coordinator")
|
||||
option(data-i18n="teachers_quote.advisor", value="Advisor")
|
||||
option(data-i18n="teachers_quote.principal", value="Principal")
|
||||
option(data-i18n="teachers_quote.superintendent", value="Superintendent")
|
||||
option(data-i18n="teachers_quote.parent", value="Parent")
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-8
|
||||
hr
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.organization_label")
|
||||
input.form-control(name="organization")
|
||||
|
||||
.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.city")
|
||||
input.form-control(name="city")
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.state")
|
||||
input.form-control(name="state")
|
||||
|
||||
.col-sm-4
|
||||
.form-group
|
||||
label.control-labellabel.control-label(data-i18n="teachers_quote.country")
|
||||
input.form-control(name="country")
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-8
|
||||
hr
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-5
|
||||
.form-group
|
||||
label.control-label(data-i18n="courses.number_students")
|
||||
.help-block
|
||||
em.text-info(data-i18n="teachers_quote.num_students_help")
|
||||
select.form-control(name="numStudents")
|
||||
option
|
||||
option 1-10
|
||||
option 11-50
|
||||
option 51-100
|
||||
option 101-200
|
||||
option 201-500
|
||||
option 501-1000
|
||||
option 1000+
|
||||
|
||||
.form-group
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
label.control-label(data-i18n="teachers_quote.education_level_label")
|
||||
.help-block
|
||||
em.text-info(data-i18n="teachers_quote.education_level_help")
|
||||
#form-teacher-info.section
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.name")
|
||||
- var name = me.get('name') || '';
|
||||
input.form-control(name="name" value=name, disabled=!!name)
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-2
|
||||
label.control-label.checkbox
|
||||
input(type="checkbox" name="educationLevel" value="Elementary")
|
||||
span(data-i18n="teachers_quote.elementary_school")
|
||||
.col-sm-2
|
||||
label.control-label.checkbox
|
||||
input(type="checkbox" name="educationLevel" value="High")
|
||||
span(data-i18n="teachers_quote.high_school")
|
||||
.col-sm-6
|
||||
// Other field uses custom logic, so no name field is included in either input.
|
||||
// That way the forms library ignores it.
|
||||
.checkbox-inline
|
||||
label.control-label
|
||||
input#other-education-level-checkbox(type="checkbox")
|
||||
span(data-i18n="nav.other")
|
||||
br
|
||||
span(data-i18n="teachers_quote.please_explain")
|
||||
input#other-education-level-input.form-control
|
||||
.col-sm-4
|
||||
#email-form-group.form-group
|
||||
label.control-label(data-i18n="general.email")
|
||||
- var email = me.get('email') || '';
|
||||
input.form-control(name="email" type="email", value=email, disabled=!!email)
|
||||
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-2
|
||||
label.control-label.checkbox
|
||||
input(type="checkbox" name="educationLevel" value="Middle")
|
||||
span(data-i18n="teachers_quote.middle_school")
|
||||
.col-sm-2
|
||||
label.control-label.checkbox
|
||||
input(type="checkbox" name="educationLevel" value="College+")
|
||||
span(data-i18n="teachers_quote.college_plus")
|
||||
|
||||
#anything-else-row.row
|
||||
.col-sm-offset-2.col-sm-8
|
||||
label.control-label
|
||||
span(data-i18n="teachers_quote.anything_else")
|
||||
span.spl.text-muted(data-i18n="signup.optional")
|
||||
|
||||
textarea.form-control(rows=8, name="notes")
|
||||
|
||||
#buttons-row.row.text-center
|
||||
input#submit-request-btn.btn.btn-primary(type="submit" data-i18n="[value]common.send")
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
.form-group
|
||||
label.control-label
|
||||
span(data-i18n="teachers_quote.phone_number")
|
||||
span.spl.text-muted(data-i18n="signup.optional")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.phone_number_help")
|
||||
input.form-control(name="phoneNumber")
|
||||
|
||||
.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.role_label")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.role_help")
|
||||
select.form-control(name="role")
|
||||
option
|
||||
option(data-i18n="courses.teacher", value="Teacher")
|
||||
option(data-i18n="teachers_quote.tech_coordinator", value="Technology coordinator")
|
||||
option(data-i18n="teachers_quote.advisor", value="Advisor")
|
||||
option(data-i18n="teachers_quote.principal", value="Principal")
|
||||
option(data-i18n="teachers_quote.superintendent", value="Superintendent")
|
||||
option(data-i18n="teachers_quote.parent", value="Parent")
|
||||
|
||||
|
||||
#form-submit-success.text-center(class=view.trialRequest.isNew() ? 'hide' : '')
|
||||
h1.text-center(data-i18n="teachers_quote.thanks_header")
|
||||
p.text-center
|
||||
span.spr(data-i18n="teachers_quote.thanks_p")
|
||||
a.spl(href="mailto:team@codecombat.com") team@codecombat.com
|
||||
#form-school-info.section
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.organization_label")
|
||||
input.form-control(name="organization")
|
||||
|
||||
.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.city")
|
||||
input.form-control(name="city")
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="teachers_quote.state")
|
||||
input.form-control(name="state")
|
||||
|
||||
.col-sm-4
|
||||
.form-group
|
||||
label.control-labellabel.control-label(data-i18n="teachers_quote.country")
|
||||
input.form-control(name="country")
|
||||
|
||||
#form-students-info.section
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-5
|
||||
.form-group
|
||||
label.control-label(data-i18n="courses.number_students")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.num_students_help")
|
||||
select.form-control(name="numStudents")
|
||||
option
|
||||
option 1-10
|
||||
option 11-50
|
||||
option 51-100
|
||||
option 101-200
|
||||
option 201-500
|
||||
option 501-1000
|
||||
option 1000+
|
||||
|
||||
.form-group
|
||||
|
||||
if me.isAnonymous()
|
||||
p.text-center(data-i18n="teachers_quote.thanks_anon")
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-4
|
||||
label.control-label(data-i18n="teachers_quote.education_level_label")
|
||||
.help-block.small
|
||||
em.text-info(data-i18n="teachers_quote.education_level_help")
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-2
|
||||
label.control-label.checkbox
|
||||
input(type="checkbox" name="educationLevel" value="Elementary")
|
||||
span(data-i18n="teachers_quote.elementary_school")
|
||||
.col-sm-2
|
||||
label.control-label.checkbox
|
||||
input(type="checkbox" name="educationLevel" value="High")
|
||||
span(data-i18n="teachers_quote.high_school")
|
||||
.col-sm-2
|
||||
label.control-label.checkbox
|
||||
input(type="checkbox" name="educationLevel" value="Middle")
|
||||
span(data-i18n="teachers_quote.middle_school")
|
||||
.col-sm-2
|
||||
label.control-label.checkbox
|
||||
input(type="checkbox" name="educationLevel" value="College+")
|
||||
span(data-i18n="teachers_quote.college_plus")
|
||||
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-6
|
||||
// Other field uses custom logic, so no name field is included in either input.
|
||||
// That way the forms library ignores it.
|
||||
.form-group
|
||||
label.control-label.checkbox
|
||||
input#other-education-level-checkbox(type="checkbox")
|
||||
span(data-i18n="nav.other")
|
||||
|
|
||||
span(data-i18n="teachers_quote.please_explain")
|
||||
input#other-education-level-input.form-control
|
||||
|
||||
#anything-else-row.section
|
||||
.row
|
||||
.col-sm-offset-2.col-sm-8
|
||||
label.control-label
|
||||
span(data-i18n="teachers_quote.anything_else")
|
||||
span.spl.text-muted(data-i18n="signup.optional")
|
||||
|
||||
textarea.form-control(rows=8, name="notes")
|
||||
|
||||
#buttons-row.row.text-center
|
||||
input#submit-request-btn.btn.btn-lg.btn-primary(type="submit" data-i18n="[value]common.send")
|
||||
|
||||
|
||||
#form-submit-success.text-center(class=view.trialRequest.isNew() ? 'hide' : '')
|
||||
h1.text-center(data-i18n="teachers_quote.thanks_header")
|
||||
p.text-center
|
||||
button#login-btn.btn.btn-info(data-i18n="login.log_in")
|
||||
button#signup-btn.btn.btn-info(data-i18n="login.sign_up")
|
||||
else
|
||||
p.text-center(data-i18n="teachers_quote.thanks_logged_in")
|
||||
span.spr(data-i18n="teachers_quote.thanks_p")
|
||||
a.spl(href="mailto:team@codecombat.com") team@codecombat.com
|
||||
|
||||
if me.isAnonymous()
|
||||
p.text-center(data-i18n="teachers_quote.thanks_anon")
|
||||
|
||||
p.text-center
|
||||
button#login-btn.btn.btn-info(data-i18n="login.log_in")
|
||||
button#signup-btn.btn.btn-info(data-i18n="login.sign_up")
|
||||
else
|
||||
p.text-center(data-i18n="teachers_quote.thanks_logged_in")
|
|
@ -5,4 +5,89 @@ module.exports = class AboutView extends RootView
|
|||
id: 'about-view'
|
||||
template: template
|
||||
|
||||
logoutRedirectURL: false
|
||||
logoutRedirectURL: false
|
||||
|
||||
events:
|
||||
'click #mission-link': 'onClickMissionLink'
|
||||
'click #team-link': 'onClickTeamLink'
|
||||
'click #community-link': 'onClickCommunityLink'
|
||||
'click #story-link': 'onClickStoryLink'
|
||||
'click #jobs-link': 'onClickJobsLink'
|
||||
'click #contact-link': 'onClickContactLink'
|
||||
'click .screen-thumbnail': 'onClickScreenThumbnail'
|
||||
'click #carousel-left': 'onLeftPressed'
|
||||
'click #carousel-right': 'onRightPressed'
|
||||
|
||||
shortcuts:
|
||||
'right': 'onRightPressed'
|
||||
'left': 'onLeftPressed'
|
||||
'esc': 'onEscapePressed'
|
||||
|
||||
afterRender: ->
|
||||
super(arguments...)
|
||||
@$('#fixed-nav').affix({
|
||||
offset:
|
||||
top: ->
|
||||
$('#nav-container').offset().top
|
||||
})
|
||||
#TODO: Maybe cache top value between page resizes to save CPU
|
||||
$('body').scrollspy(
|
||||
target: '#nav-container'
|
||||
offset: 150
|
||||
)
|
||||
@$('#screenshot-lightbox').modal()
|
||||
|
||||
@$('#screenshot-carousel').carousel({
|
||||
interval: 0
|
||||
keyboard: false
|
||||
})
|
||||
|
||||
onClickMissionLink: (event) ->
|
||||
event.preventDefault()
|
||||
@scrollToLink('#mission')
|
||||
|
||||
onClickTeamLink: (event) ->
|
||||
event.preventDefault()
|
||||
@scrollToLink('#team')
|
||||
|
||||
onClickCommunityLink: (event) ->
|
||||
event.preventDefault()
|
||||
@scrollToLink('#community')
|
||||
|
||||
onClickStoryLink: (event) ->
|
||||
event.preventDefault()
|
||||
@scrollToLink('#story')
|
||||
|
||||
onClickJobsLink: (event) ->
|
||||
event.preventDefault()
|
||||
@scrollToLink('#jobs')
|
||||
|
||||
onClickContactLink: (event) ->
|
||||
event.preventDefault()
|
||||
@scrollToLink('#contact')
|
||||
|
||||
onRightPressed: (event) ->
|
||||
# Special handling, otherwise after you click the control, keyboard presses move the slide twice
|
||||
return if event.type is 'keydown' and $(document.activeElement).is('.carousel-control')
|
||||
if $('#screenshot-lightbox').data('bs.modal')?.isShown
|
||||
event.preventDefault()
|
||||
$('#screenshot-carousel').carousel('next')
|
||||
|
||||
onLeftPressed: (event) ->
|
||||
return if event.type is 'keydown' and $(document.activeElement).is('.carousel-control')
|
||||
if $('#screenshot-lightbox').data('bs.modal')?.isShown
|
||||
event.preventDefault()
|
||||
$('#screenshot-carousel').carousel('prev')
|
||||
|
||||
onEscapePressed: (event) ->
|
||||
if $('#screenshot-lightbox').data('bs.modal')?.isShown
|
||||
event.preventDefault()
|
||||
$('#screenshot-lightbox').modal('hide')
|
||||
|
||||
onClickScreenThumbnail: (event) ->
|
||||
unless $('#screenshot-lightbox').data('bs.modal')?.isShown
|
||||
event.preventDefault()
|
||||
# Modal opening happens automatically from bootstrap
|
||||
$('#screenshot-carousel').carousel($(event.currentTarget).data("index"))
|
||||
|
||||
|
||||
|
|
|
@ -41,16 +41,6 @@ module.exports = class HomeView extends RootView
|
|||
afterInsert: ->
|
||||
super(arguments...)
|
||||
|
||||
isOldBrowser: ->
|
||||
if $.browser
|
||||
majorVersion = $.browser.versionNumber
|
||||
return true if $.browser.mozilla && majorVersion < 25
|
||||
return true if $.browser.chrome && majorVersion < 31 # Noticed Gems in the Deep not loading with 30
|
||||
return true if $.browser.safari && majorVersion < 6 # 6 might have problems with Aether, or maybe just old minors of 6: https://errorception.com/projects/51a79585ee207206390002a2/errors/547a202e1ead63ba4e4ac9fd
|
||||
else
|
||||
console.warn 'no more jquery browser version...'
|
||||
return false
|
||||
|
||||
justPlaysCourses: ->
|
||||
# This heuristic could be better, but currently we don't add to me.get('courseInstances') for single-player anonymous intro courses, so they have to beat a level without choosing a hero.
|
||||
return me.get('stats')?.gamesCompleted and not me.get('heroConfig')
|
||||
|
|
|
@ -75,14 +75,5 @@ module.exports = class NewHomeView extends RootView
|
|||
onClickTeacherButton: ->
|
||||
@scrollToLink('.request-demo-row', 600)
|
||||
|
||||
isOldBrowser: ->
|
||||
if $.browser
|
||||
majorVersion = $.browser.versionNumber
|
||||
return true if $.browser.mozilla && majorVersion < 25
|
||||
return true if $.browser.chrome && majorVersion < 31 # Noticed Gems in the Deep not loading with 30
|
||||
return true if $.browser.safari && majorVersion < 6 # 6 might have problems with Aether, or maybe just old minors of 6: https://errorception.com/projects/51a79585ee207206390002a2/errors/547a202e1ead63ba4e4ac9fd
|
||||
else
|
||||
console.warn 'no more jquery browser version...'
|
||||
return false
|
||||
|
||||
|
||||
|
|
|
@ -105,6 +105,7 @@ module.exports = class RequestQuoteView extends RootView
|
|||
.addClass('has-error')
|
||||
.append($("<div class='help-block error-help-block'>#{userExists} <a id='email-exists-login-link'>#{logIn}</a>"))
|
||||
@$('#submit-request-btn').text('Submit').attr('disabled', false)
|
||||
forms.scrollToFirstError()
|
||||
|
||||
onClickEmailExistsLoginLink: ->
|
||||
modal = new AuthModal({
|
||||
|
|
|
@ -66,8 +66,8 @@ module.exports = class AccountSettingsView extends CocoView
|
|||
renderData =
|
||||
confirmTitle: 'Are you really sure?'
|
||||
confirmBody: 'This will completely delete your account. This action CANNOT be undone. Are you entirely sure?'
|
||||
confirmDecline: 'Not really'
|
||||
confirmConfirm: 'Definitely'
|
||||
confirmDecline: 'Cancel'
|
||||
confirmConfirm: 'DELETE Your Account'
|
||||
confirmModal = new ConfirmModal renderData
|
||||
confirmModal.on 'confirm', @deleteAccount
|
||||
@openModalView confirmModal
|
||||
|
@ -76,9 +76,9 @@ module.exports = class AccountSettingsView extends CocoView
|
|||
@validateCredentialsForDestruction @$el.find('#reset-progress-form'), =>
|
||||
renderData =
|
||||
confirmTitle: 'Are you really sure?'
|
||||
confirmBody: 'This will completely erase your progress: code, levels, achievements, earned gems, etc. This action CANNOT be undone. Are you entirely sure?'
|
||||
confirmDecline: 'Not really'
|
||||
confirmConfirm: 'Definitely'
|
||||
confirmBody: 'This will completely erase your progress: code, levels, achievements, earned gems, and course work. This action CANNOT be undone. Are you entirely sure?'
|
||||
confirmDecline: 'Cancel'
|
||||
confirmConfirm: 'Erase ALL Progress'
|
||||
confirmModal = new ConfirmModal renderData
|
||||
confirmModal.on 'confirm', @resetProgress
|
||||
@openModalView confirmModal
|
||||
|
|
|
@ -6,7 +6,6 @@ Prepaid = require 'models/Prepaid'
|
|||
module.exports = class AdministerUserModal extends ModalView
|
||||
id: "administer-user-modal"
|
||||
template: template
|
||||
plain: true
|
||||
|
||||
events:
|
||||
'click #save-changes': 'onSaveChanges'
|
||||
|
@ -14,7 +13,7 @@ module.exports = class AdministerUserModal extends ModalView
|
|||
|
||||
constructor: (options, @userHandle) ->
|
||||
super(options)
|
||||
@user = @supermodel.loadModel(new User({_id:@userHandle}), 'user', {cache: false}).model
|
||||
@user = @supermodel.loadModel(new User({_id:@userHandle}), {cache: false}).model
|
||||
options = {cache: false, url: '/stripe/coupons'}
|
||||
options.success = (@coupons) =>
|
||||
@couponsResource = @supermodel.addRequestResource('coupon', options)
|
||||
|
|
|
@ -3,6 +3,7 @@ Course = require 'models/Course'
|
|||
CourseInstance = require 'models/CourseInstance'
|
||||
require 'vendor/d3'
|
||||
d3Utils = require 'core/d3_utils'
|
||||
Payment = require 'models/Payment'
|
||||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/admin/analytics'
|
||||
utils = require 'core/utils'
|
||||
|
@ -10,7 +11,8 @@ utils = require 'core/utils'
|
|||
module.exports = class AnalyticsView extends RootView
|
||||
id: 'admin-analytics-view'
|
||||
template: template
|
||||
furthestCourseDayRange: 30
|
||||
furthestCourseDayRangeRecent: 60
|
||||
furthestCourseDayRange: 365
|
||||
lineColors: ['red', 'blue', 'green', 'purple', 'goldenrod', 'brown', 'darkcyan']
|
||||
minSchoolCount: 20
|
||||
|
||||
|
@ -149,6 +151,16 @@ module.exports = class AnalyticsView extends RootView
|
|||
@renderSelectors?('#school-counts')
|
||||
}, 0).load()
|
||||
|
||||
@supermodel.addRequestResource({
|
||||
url: '/db/payment/-/school_sales'
|
||||
success: (@schoolSales) =>
|
||||
@schoolSales?.sort (a, b) ->
|
||||
return -1 if a.created > b.created
|
||||
return 0 if a.created is b.created
|
||||
1
|
||||
@renderSelectors?('#school-sales')
|
||||
}, 0).load()
|
||||
|
||||
@supermodel.addRequestResource({
|
||||
url: '/db/prepaid/-/courses'
|
||||
method: 'POST'
|
||||
|
@ -227,35 +239,106 @@ module.exports = class AnalyticsView extends RootView
|
|||
options.error = (models, response, options) =>
|
||||
return if @destroyed
|
||||
console.error 'Failed to get recent course instances', response
|
||||
options.success = (models) =>
|
||||
@courseInstances = models ? []
|
||||
@onCourseInstancesSync()
|
||||
@render?()
|
||||
options.success = (data) =>
|
||||
@onCourseInstancesSync(data)
|
||||
@renderSelectors?('#furthest-course')
|
||||
@supermodel.addRequestResource(options, 0).load()
|
||||
|
||||
onCourseInstancesSync: ->
|
||||
return unless @courseInstances
|
||||
onCourseInstancesSync: (data) ->
|
||||
@courseDistributionsRecent = []
|
||||
@courseDistributions = []
|
||||
return unless data.courseInstances and data.students and data.prepaids
|
||||
|
||||
# Find highest course for teachers and students
|
||||
@teacherFurthestCourseMap = {}
|
||||
@studentFurthestCourseMap = {}
|
||||
for courseInstance in @courseInstances
|
||||
courseID = courseInstance.courseID
|
||||
teacherID = courseInstance.ownerID
|
||||
if not @teacherFurthestCourseMap[teacherID] or @teacherFurthestCourseMap[teacherID] < @courseOrderMap[courseID]
|
||||
@teacherFurthestCourseMap[teacherID] = @courseOrderMap[courseID]
|
||||
for studentID in courseInstance.members
|
||||
if not @studentFurthestCourseMap[studentID] or @studentFurthestCourseMap[studentID] < @courseOrderMap[courseID]
|
||||
@studentFurthestCourseMap[studentID] = @courseOrderMap[courseID]
|
||||
createCourseDistributions = (numDays) =>
|
||||
# Find student furthest course
|
||||
startDate = new Date()
|
||||
startDate.setUTCDate(startDate.getUTCDate() - numDays)
|
||||
teacherStudentsMap = {}
|
||||
studentFurthestCourseMap = {}
|
||||
studentPaidStatusMap = {}
|
||||
for courseInstance in data.courseInstances
|
||||
continue if utils.objectIdToDate(courseInstance._id) < startDate
|
||||
courseID = courseInstance.courseID
|
||||
teacherID = courseInstance.ownerID
|
||||
for studentID in courseInstance.members
|
||||
studentPaidStatusMap[studentID] = 'free'
|
||||
if not studentFurthestCourseMap[studentID] or studentFurthestCourseMap[studentID] < @courseOrderMap[courseID]
|
||||
studentFurthestCourseMap[studentID] = @courseOrderMap[courseID]
|
||||
teacherStudentsMap[teacherID] ?= []
|
||||
teacherStudentsMap[teacherID].push(studentID)
|
||||
|
||||
@teacherCourseDistribution = {}
|
||||
for teacherID, courseIndex of @teacherFurthestCourseMap
|
||||
@teacherCourseDistribution[courseIndex] ?= 0
|
||||
@teacherCourseDistribution[courseIndex]++
|
||||
@studentCourseDistribution = {}
|
||||
for studentID, courseIndex of @studentFurthestCourseMap
|
||||
@studentCourseDistribution[courseIndex] ?= 0
|
||||
@studentCourseDistribution[courseIndex]++
|
||||
# Find paid students
|
||||
prepaidUserMap = {}
|
||||
for user in data.students
|
||||
continue unless studentPaidStatusMap[user._id]
|
||||
if prepaidID = user.coursePrepaidID
|
||||
studentPaidStatusMap[user._id] = 'paid'
|
||||
prepaidUserMap[prepaidID] ?= []
|
||||
prepaidUserMap[prepaidID].push(user._id)
|
||||
|
||||
# Find trial students
|
||||
for prepaid in data.prepaids
|
||||
continue unless prepaidUserMap[prepaid._id]
|
||||
if prepaid.properties?.trialRequestID
|
||||
for userID in prepaidUserMap[prepaid._id]
|
||||
studentPaidStatusMap[userID] = 'trial'
|
||||
|
||||
# Find teacher furthest course and paid status based on their students
|
||||
# Paid teacher: at least one paid student
|
||||
# Trial teacher: at least one trial student in course instance, and no paid students
|
||||
# Free teacher: no paid students, no trial students
|
||||
# Teacher furthest course is furthest course of highest paid status student
|
||||
teacherFurthestCourseMap = {}
|
||||
teacherPaidStatusMap = {}
|
||||
for teacher, students of teacherStudentsMap
|
||||
for student in students
|
||||
if not teacherPaidStatusMap[teacher]
|
||||
teacherPaidStatusMap[teacher] = studentPaidStatusMap[student]
|
||||
teacherFurthestCourseMap[teacher] = studentFurthestCourseMap[student]
|
||||
else if teacherPaidStatusMap[teacher] is 'trial' and studentPaidStatusMap[student] is 'paid'
|
||||
teacherPaidStatusMap[teacher] = studentPaidStatusMap[student]
|
||||
teacherFurthestCourseMap[teacher] = studentFurthestCourseMap[student]
|
||||
else if teacherPaidStatusMap[teacher] is 'free' and studentPaidStatusMap[student] in ['paid', 'trial']
|
||||
teacherPaidStatusMap[teacher] = studentPaidStatusMap[student]
|
||||
teacherFurthestCourseMap[teacher] = studentFurthestCourseMap[student]
|
||||
else if teacherFurthestCourseMap[teacher] < studentFurthestCourseMap[student]
|
||||
teacherFurthestCourseMap[teacher] = studentFurthestCourseMap[student]
|
||||
|
||||
# Build table of student/teacher paid/trial/free totals
|
||||
updateCourseTotalsMap = (courseTotalsMap, furthestCourseMap, paidStatusMap, columnSuffix) =>
|
||||
for user, courseIndex of furthestCourseMap
|
||||
courseName = @courses.models[courseIndex].get('name')
|
||||
courseTotalsMap[courseName] ?= {}
|
||||
columnName = switch paidStatusMap[user]
|
||||
when 'paid' then 'Paid ' + columnSuffix
|
||||
when 'trial' then 'Trial ' + columnSuffix
|
||||
when 'free' then 'Free ' + columnSuffix
|
||||
courseTotalsMap[courseName][columnName] ?= 0
|
||||
courseTotalsMap[courseName][columnName]++
|
||||
courseTotalsMap[courseName]['Total ' + columnSuffix] ?= 0
|
||||
courseTotalsMap[courseName]['Total ' + columnSuffix]++
|
||||
courseTotalsMap['All Courses']['Total ' + columnSuffix] ?= 0
|
||||
courseTotalsMap['All Courses']['Total ' + columnSuffix]++
|
||||
courseTotalsMap['All Courses'][columnName] ?= 0
|
||||
courseTotalsMap['All Courses'][columnName]++
|
||||
courseTotalsMap = {'All Courses': {}}
|
||||
updateCourseTotalsMap(courseTotalsMap, teacherFurthestCourseMap, teacherPaidStatusMap, 'Teachers')
|
||||
updateCourseTotalsMap(courseTotalsMap, studentFurthestCourseMap, studentPaidStatusMap, 'Students')
|
||||
|
||||
courseDistributions = []
|
||||
for courseName, totals of courseTotalsMap
|
||||
courseDistributions.push({courseName: courseName, totals: totals})
|
||||
courseDistributions.sort (a, b) ->
|
||||
if a.courseName.indexOf('Introduction') >= 0 and b.courseName.indexOf('Introduction') < 0 then return -1
|
||||
else if b.courseName.indexOf('Introduction') >= 0 and a.courseName.indexOf('Introduction') < 0 then return 1
|
||||
else if a.courseName.indexOf('All Courses') >= 0 and b.courseName.indexOf('All Courses') < 0 then return 1
|
||||
else if b.courseName.indexOf('All Courses') >= 0 and a.courseName.indexOf('All Courses') < 0 then return -1
|
||||
a.courseName.localeCompare(b.courseName)
|
||||
|
||||
courseDistributions
|
||||
|
||||
@courseDistributionsRecent = createCourseDistributions(@furthestCourseDayRangeRecent)
|
||||
@courseDistributions = createCourseDistributions(@furthestCourseDayRange)
|
||||
|
||||
createLineChartPoints: (days, data) ->
|
||||
points = []
|
||||
|
@ -287,11 +370,16 @@ module.exports = class AnalyticsView extends RootView
|
|||
visibleWidth = $('.kpi-recent-chart').width()
|
||||
d3Utils.createLineChart('.kpi-recent-chart', @kpiRecentChartLines, visibleWidth)
|
||||
d3Utils.createLineChart('.kpi-chart', @kpiChartLines, visibleWidth)
|
||||
d3Utils.createLineChart('.active-classes-chart', @activeClassesChartLines, visibleWidth)
|
||||
d3Utils.createLineChart('.classroom-daily-active-users-chart', @classroomDailyActiveUsersChartLines, visibleWidth)
|
||||
d3Utils.createLineChart('.classroom-monthly-active-users-chart', @classroomMonthlyActiveUsersChartLines, visibleWidth)
|
||||
d3Utils.createLineChart('.campaign-daily-active-users-chart', @campaignDailyActiveUsersChartLines, visibleWidth)
|
||||
d3Utils.createLineChart('.campaign-monthly-active-users-chart', @campaignMonthlyActiveUsersChartLines, visibleWidth)
|
||||
d3Utils.createLineChart('.active-classes-chart-90', @activeClassesChartLines90, visibleWidth)
|
||||
d3Utils.createLineChart('.active-classes-chart-365', @activeClassesChartLines365, visibleWidth)
|
||||
d3Utils.createLineChart('.classroom-daily-active-users-chart-90', @classroomDailyActiveUsersChartLines90, visibleWidth)
|
||||
d3Utils.createLineChart('.classroom-monthly-active-users-chart-90', @classroomMonthlyActiveUsersChartLines90, visibleWidth)
|
||||
d3Utils.createLineChart('.classroom-daily-active-users-chart-365', @classroomDailyActiveUsersChartLines365, visibleWidth)
|
||||
d3Utils.createLineChart('.classroom-monthly-active-users-chart-365', @classroomMonthlyActiveUsersChartLines365, visibleWidth)
|
||||
d3Utils.createLineChart('.campaign-daily-active-users-chart-90', @campaignDailyActiveUsersChartLines90, visibleWidth)
|
||||
d3Utils.createLineChart('.campaign-monthly-active-users-chart-90', @campaignMonthlyActiveUsersChartLines90, visibleWidth)
|
||||
d3Utils.createLineChart('.campaign-daily-active-users-chart-365', @campaignDailyActiveUsersChartLines365, visibleWidth)
|
||||
d3Utils.createLineChart('.campaign-monthly-active-users-chart-365', @campaignMonthlyActiveUsersChartLines365, visibleWidth)
|
||||
d3Utils.createLineChart('.campaign-vs-classroom-monthly-active-users-recent-chart.line-chart-container', @campaignVsClassroomMonthlyActiveUsersRecentChartLines, visibleWidth)
|
||||
d3Utils.createLineChart('.campaign-vs-classroom-monthly-active-users-chart.line-chart-container', @campaignVsClassroomMonthlyActiveUsersChartLines, visibleWidth)
|
||||
d3Utils.createLineChart('.paid-courses-chart', @enrollmentsChartLines, visibleWidth)
|
||||
|
@ -393,9 +481,9 @@ module.exports = class AnalyticsView extends RootView
|
|||
showYScale: true
|
||||
|
||||
updateActiveClassesChartData: ->
|
||||
@activeClassesChartLines = []
|
||||
@activeClassesChartLines90 = []
|
||||
@activeClassesChartLines365 = []
|
||||
return unless @activeClasses?.length
|
||||
days = d3Utils.createContiguousDays(90)
|
||||
|
||||
groupDayMap = {}
|
||||
for entry in @activeClasses
|
||||
|
@ -404,35 +492,42 @@ module.exports = class AnalyticsView extends RootView
|
|||
groupDayMap[@activeClassGroups[i]][entry.day] ?= 0
|
||||
groupDayMap[@activeClassGroups[i]][entry.day] += count
|
||||
|
||||
lines = []
|
||||
colorIndex = 0
|
||||
totalMax = 0
|
||||
for group, entries of groupDayMap
|
||||
data = []
|
||||
for day, count of entries
|
||||
data.push
|
||||
day: day
|
||||
value: count
|
||||
data.reverse()
|
||||
points = @createLineChartPoints(days, data)
|
||||
@activeClassesChartLines.push
|
||||
points: points
|
||||
description: group.replace('Active classes ', '')
|
||||
lineColor: @lineColors[colorIndex++ % @lineColors.length]
|
||||
strokeWidth: 1
|
||||
min: 0
|
||||
showYScale: group is 'Total'
|
||||
totalMax = _.max(points, 'y').y if group is 'Total'
|
||||
line.max = totalMax for line in @activeClassesChartLines
|
||||
createActiveClassesChartLines = (lines, numDays) =>
|
||||
days = d3Utils.createContiguousDays(numDays)
|
||||
colorIndex = 0
|
||||
totalMax = 0
|
||||
for group, entries of groupDayMap
|
||||
data = []
|
||||
for day, count of entries
|
||||
data.push
|
||||
day: day
|
||||
value: count
|
||||
data.reverse()
|
||||
points = @createLineChartPoints(days, data)
|
||||
lines.push
|
||||
points: points
|
||||
description: group.replace('Active classes ', '')
|
||||
lineColor: @lineColors[colorIndex++ % @lineColors.length]
|
||||
strokeWidth: 1
|
||||
min: 0
|
||||
showYScale: group is 'Total'
|
||||
totalMax = _.max(points, 'y').y if group is 'Total'
|
||||
line.max = totalMax for line in lines
|
||||
|
||||
createActiveClassesChartLines(@activeClassesChartLines90, 90)
|
||||
createActiveClassesChartLines(@activeClassesChartLines365, 365)
|
||||
|
||||
updateActiveUsersChartData: ->
|
||||
# Create chart lines for the active user events returned by active_users in analytics_perday_handler
|
||||
@campaignDailyActiveUsersChartLines = []
|
||||
@campaignMonthlyActiveUsersChartLines = []
|
||||
@classroomDailyActiveUsersChartLines = []
|
||||
@classroomMonthlyActiveUsersChartLines = []
|
||||
@campaignDailyActiveUsersChartLines90 = []
|
||||
@campaignMonthlyActiveUsersChartLines90 = []
|
||||
@campaignDailyActiveUsersChartLines365 = []
|
||||
@campaignMonthlyActiveUsersChartLines365 = []
|
||||
@classroomDailyActiveUsersChartLines90 = []
|
||||
@classroomMonthlyActiveUsersChartLines90 = []
|
||||
@classroomDailyActiveUsersChartLines365 = []
|
||||
@classroomMonthlyActiveUsersChartLines365 = []
|
||||
return unless @activeUsers?.length
|
||||
days = d3Utils.createContiguousDays(90)
|
||||
|
||||
# Separate day/value arrays by event
|
||||
eventDataMap = {}
|
||||
|
@ -444,65 +539,33 @@ module.exports = class AnalyticsView extends RootView
|
|||
day: entry.day
|
||||
value: count
|
||||
|
||||
# Build chart lines for each event
|
||||
eventLineMap =
|
||||
'DAU campaign': {max: 0, colorIndex: 0}
|
||||
'MAU campaign': {max: 0, colorIndex: 0}
|
||||
'DAU classroom': {max: 0, colorIndex: 0}
|
||||
'MAU classroom': {max: 0, colorIndex: 0}
|
||||
for event, data of eventDataMap
|
||||
data.reverse()
|
||||
points = @createLineChartPoints(days, data)
|
||||
max = _.max(points, 'y').y
|
||||
if event.indexOf('DAU campaign') >= 0
|
||||
chartLines = @campaignDailyActiveUsersChartLines
|
||||
eventLineMap['DAU campaign'].max = Math.max(eventLineMap['DAU campaign'].max, max)
|
||||
lineColor = @lineColors[eventLineMap['DAU campaign'].colorIndex++ % @lineColors.length]
|
||||
else if event.indexOf('MAU campaign') >= 0
|
||||
chartLines = @campaignMonthlyActiveUsersChartLines
|
||||
eventLineMap['MAU campaign'].max = Math.max(eventLineMap['MAU campaign'].max, max)
|
||||
lineColor = @lineColors[eventLineMap['MAU campaign'].colorIndex++ % @lineColors.length]
|
||||
else if event.indexOf('DAU classroom') >= 0
|
||||
chartLines = @classroomDailyActiveUsersChartLines
|
||||
eventLineMap['DAU classroom'].max = Math.max(eventLineMap['DAU classroom'].max, max)
|
||||
lineColor = @lineColors[eventLineMap['DAU classroom'].colorIndex++ % @lineColors.length]
|
||||
else if event.indexOf('MAU classroom') >= 0
|
||||
chartLines = @classroomMonthlyActiveUsersChartLines
|
||||
eventLineMap['MAU classroom'].max = Math.max(eventLineMap['MAU classroom'].max, max)
|
||||
lineColor = @lineColors[eventLineMap['MAU classroom'].colorIndex++ % @lineColors.length]
|
||||
chartLines.push
|
||||
points: points
|
||||
description: event
|
||||
lineColor: lineColor
|
||||
strokeWidth: 1
|
||||
min: 0
|
||||
showYScale: false
|
||||
createActiveUsersChartLines = (lines, numDays, eventPrefix) =>
|
||||
days = d3Utils.createContiguousDays(numDays)
|
||||
colorIndex = 0
|
||||
lineMax = 0
|
||||
showYScale = true
|
||||
for event, data of eventDataMap
|
||||
continue unless event.indexOf(eventPrefix) >= 0
|
||||
points = @createLineChartPoints(days, _.cloneDeep(data).reverse())
|
||||
lineMax = Math.max(_.max(points, 'y').y, lineMax)
|
||||
lines.push
|
||||
points: points
|
||||
description: event
|
||||
lineColor: @lineColors[colorIndex++ % @lineColors.length]
|
||||
strokeWidth: 1
|
||||
min: 0
|
||||
showYScale: showYScale
|
||||
showYScale = false
|
||||
line.max = lineMax for line in lines
|
||||
|
||||
# Update line Y scales and maxes
|
||||
showYScaleSet = false
|
||||
for line in @campaignDailyActiveUsersChartLines
|
||||
line.max = eventLineMap['DAU campaign'].max
|
||||
unless showYScaleSet
|
||||
line.showYScale = true
|
||||
showYScaleSet = true
|
||||
showYScaleSet = false
|
||||
for line in @campaignMonthlyActiveUsersChartLines
|
||||
line.max = eventLineMap['MAU campaign'].max
|
||||
unless showYScaleSet
|
||||
line.showYScale = true
|
||||
showYScaleSet = true
|
||||
showYScaleSet = false
|
||||
for line in @classroomDailyActiveUsersChartLines
|
||||
line.max = eventLineMap['DAU classroom'].max
|
||||
unless showYScaleSet
|
||||
line.showYScale = true
|
||||
showYScaleSet = true
|
||||
showYScaleSet = false
|
||||
for line in @classroomMonthlyActiveUsersChartLines
|
||||
line.max = eventLineMap['MAU classroom'].max
|
||||
unless showYScaleSet
|
||||
line.showYScale = true
|
||||
showYScaleSet = true
|
||||
createActiveUsersChartLines(@campaignDailyActiveUsersChartLines90, 90, 'DAU campaign')
|
||||
createActiveUsersChartLines(@campaignMonthlyActiveUsersChartLines90, 90, 'MAU campaign')
|
||||
createActiveUsersChartLines(@classroomDailyActiveUsersChartLines90, 90, 'DAU classroom')
|
||||
createActiveUsersChartLines(@classroomMonthlyActiveUsersChartLines90, 90, 'MAU classroom')
|
||||
createActiveUsersChartLines(@campaignDailyActiveUsersChartLines365, 365, 'DAU campaign')
|
||||
createActiveUsersChartLines(@campaignMonthlyActiveUsersChartLines365, 365, 'MAU campaign')
|
||||
createActiveUsersChartLines(@classroomDailyActiveUsersChartLines365, 365, 'DAU classroom')
|
||||
createActiveUsersChartLines(@classroomMonthlyActiveUsersChartLines365, 365, 'MAU classroom')
|
||||
|
||||
updateCampaignVsClassroomActiveUsersChartData: ->
|
||||
@campaignVsClassroomMonthlyActiveUsersRecentChartLines = []
|
||||
|
|
|
@ -30,7 +30,7 @@ module.exports = class TrialRequestsView extends RootView
|
|||
-1
|
||||
else
|
||||
1
|
||||
@trialRequests = new CocoCollection([], { url: '/db/trial.request?conditions[sort]=-created&conditions[limit]=500', model: TrialRequest, comparator: sortRequests })
|
||||
@trialRequests = new CocoCollection([], { url: '/db/trial.request?conditions[sort]=-created&conditions[limit]=1000', model: TrialRequest, comparator: sortRequests })
|
||||
@supermodel.loadCollection(@trialRequests, 'trial-requests', {cache: false})
|
||||
|
||||
getRenderData: ->
|
||||
|
|
|
@ -60,8 +60,8 @@ module.exports = class ClanDetailsView extends RootView
|
|||
@listenTo @memberAchievements, 'sync', @onMemberAchievementsSync
|
||||
@listenTo @memberSessions, 'sync', @onMemberSessionsSync
|
||||
|
||||
@supermodel.loadModel @campaigns, 'campaigns', cache: false
|
||||
@supermodel.loadModel @clan, 'clan', cache: false
|
||||
@supermodel.loadModel @campaigns, cache: false
|
||||
@supermodel.loadModel @clan, cache: false
|
||||
@supermodel.loadCollection(@members, 'members', {cache: false})
|
||||
@supermodel.loadCollection(@memberAchievements, 'member_achievements', {cache: false})
|
||||
|
||||
|
@ -204,7 +204,7 @@ module.exports = class ClanDetailsView extends RootView
|
|||
unless @owner?
|
||||
@owner = new User _id: @clan.get('ownerID')
|
||||
@listenTo @owner, 'sync', => @render?()
|
||||
@supermodel.loadModel @owner, 'owner', cache: false
|
||||
@supermodel.loadModel @owner, cache: false
|
||||
if @clan.get("dashboardType") is "premium"
|
||||
@supermodel.loadCollection(@memberSessions, 'member_sessions', {cache: false})
|
||||
@render?()
|
||||
|
|
|
@ -13,7 +13,7 @@ module.exports = class LevelSessionCodeView extends CocoView
|
|||
@session = options.session
|
||||
@level = LevelSession.getReferencedModel(@session.get('level'), LevelSession.schema.properties.level)
|
||||
@level.setProjection ['employerDescription', 'name', 'icon', 'banner', 'slug']
|
||||
@supermodel.loadModel @level, 'level'
|
||||
@supermodel.loadModel @level
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
|
|
|
@ -17,7 +17,7 @@ module.exports = class UserView extends RootView
|
|||
@user = me
|
||||
@onLoaded()
|
||||
@user = new User _id: @userID
|
||||
@supermodel.loadModel @user, 'user', cache: false
|
||||
@supermodel.loadModel @user, cache: false
|
||||
|
||||
getRenderData: ->
|
||||
context = super()
|
||||
|
|
|
@ -171,5 +171,15 @@ module.exports = class RootView extends CocoView
|
|||
console.warn 'Error saving language:', errors
|
||||
res.success (model, response, options) ->
|
||||
#console.log 'Saved language:', newLang
|
||||
|
||||
isOldBrowser: ->
|
||||
if $.browser
|
||||
majorVersion = $.browser.versionNumber
|
||||
return true if $.browser.mozilla && majorVersion < 25
|
||||
return true if $.browser.chrome && majorVersion < 31 # Noticed Gems in the Deep not loading with 30
|
||||
return true if $.browser.safari && majorVersion < 6 # 6 might have problems with Aether, or maybe just old minors of 6: https://errorception.com/projects/51a79585ee207206390002a2/errors/547a202e1ead63ba4e4ac9fd
|
||||
else
|
||||
console.warn 'no more jquery browser version...'
|
||||
return false
|
||||
|
||||
logoutRedirectURL: '/'
|
||||
|
|
|
@ -31,7 +31,7 @@ module.exports = class CourseDetailsView extends RootView
|
|||
if @course.loaded
|
||||
@onCourseSync()
|
||||
else
|
||||
@supermodel.loadModel @course, 'course'
|
||||
@supermodel.loadModel @course
|
||||
|
||||
getRenderData: ->
|
||||
context = super()
|
||||
|
@ -67,7 +67,7 @@ module.exports = class CourseDetailsView extends RootView
|
|||
if @campaign.loaded
|
||||
@onCampaignSync()
|
||||
else
|
||||
@supermodel.loadModel @campaign, 'campaign'
|
||||
@supermodel.loadModel @campaign
|
||||
@render()
|
||||
|
||||
onCampaignSync: ->
|
||||
|
@ -106,7 +106,7 @@ module.exports = class CourseDetailsView extends RootView
|
|||
if @courseInstance.loaded
|
||||
@onCourseInstanceSync()
|
||||
else
|
||||
@courseInstance = @supermodel.loadModel(@courseInstance, 'course_instance').model
|
||||
@courseInstance = @supermodel.loadModel(@courseInstance).model
|
||||
|
||||
onCourseInstancesSync: ->
|
||||
return if @destroyed
|
||||
|
@ -128,14 +128,14 @@ module.exports = class CourseDetailsView extends RootView
|
|||
# console.log 'onCourseInstanceSync'
|
||||
if @courseInstance.get('classroomID')
|
||||
@classroom = new Classroom({_id: @courseInstance.get('classroomID')})
|
||||
@supermodel.loadModel @classroom, 'classroom'
|
||||
@supermodel.loadModel @classroom
|
||||
@singlePlayerMode = @courseInstance.get('name') is 'Single Player'
|
||||
@teacherMode = @courseInstance.get('ownerID') is me.id and not @singlePlayerMode
|
||||
@levelSessions = new CocoCollection([], { url: "/db/course_instance/#{@courseInstance.id}/level_sessions", model: LevelSession, comparator: '_id' })
|
||||
@listenToOnce @levelSessions, 'sync', @onLevelSessionsSync
|
||||
@supermodel.loadCollection @levelSessions, 'level_sessions', cache: false
|
||||
@owner = new User({_id: @courseInstance.get('ownerID')})
|
||||
@supermodel.loadModel @owner, 'user'
|
||||
@supermodel.loadModel @owner
|
||||
@render()
|
||||
|
||||
onLevelSessionsSync: ->
|
||||
|
@ -192,7 +192,7 @@ module.exports = class CourseDetailsView extends RootView
|
|||
if @nextCourseInstance
|
||||
nextCourseID = @nextCourseInstance.get('courseID')
|
||||
@nextCourse = @supermodel.getModel(Course, nextCourseID) or new Course _id: nextCourseID
|
||||
@nextCourse = @supermodel.loadModel(@nextCourse, 'course').model
|
||||
@nextCourse = @supermodel.loadModel(@nextCourse).model
|
||||
else if @allCourses?.loaded
|
||||
@nextCourse = _.find @allCourses.models, (course) => course.id > @course.id
|
||||
else
|
||||
|
|
|
@ -46,7 +46,7 @@ module.exports = class HourOfCodeView extends RootView
|
|||
if @lastSession
|
||||
@lastLevel = new Level()
|
||||
levelData = @lastSession.get('level')
|
||||
@supermodel.loadModel(@lastLevel, 'level', {
|
||||
@supermodel.loadModel(@lastLevel, {
|
||||
url: "/db/level/#{levelData.original}/version/#{levelData.majorVersion}"
|
||||
data: {
|
||||
project: 'name,slug'
|
||||
|
|
|
@ -36,7 +36,7 @@ module.exports = class DeltaView extends CocoView
|
|||
@[modelName] = options[modelName]
|
||||
continue unless @[modelName] and options.loadModels
|
||||
if not @[modelName].isLoaded
|
||||
@[modelName] = @supermodel.loadModel(@[modelName], 'document').model
|
||||
@[modelName] = @supermodel.loadModel(@[modelName]).model
|
||||
|
||||
@buildDeltas() if @supermodel.finished()
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ module.exports = class PatchModal extends ModalView
|
|||
@originalSource = @targetModel.clone(false)
|
||||
else
|
||||
@originalSource = new @targetModel.constructor({_id:targetID})
|
||||
@supermodel.loadModel @originalSource, 'source_document'
|
||||
@supermodel.loadModel @originalSource
|
||||
|
||||
applyDelta: ->
|
||||
@headModel = null
|
||||
|
|
|
@ -22,7 +22,7 @@ module.exports = class AchievementEditView extends RootView
|
|||
super options
|
||||
@achievement = new Achievement(_id: @achievementID)
|
||||
@achievement.saveBackups = true
|
||||
@supermodel.loadModel @achievement, 'achievement'
|
||||
@supermodel.loadModel @achievement
|
||||
@pushChangesToPreview = _.throttle(@pushChangesToPreview, 500)
|
||||
|
||||
onLoaded: ->
|
||||
|
|
|
@ -20,7 +20,7 @@ module.exports = class ArticleEditView extends RootView
|
|||
super options
|
||||
@article = new Article({_id: @articleID})
|
||||
@article.saveBackups = true
|
||||
@supermodel.loadModel @article, 'article'
|
||||
@supermodel.loadModel @article
|
||||
@pushChangesToPreview = _.throttle(@pushChangesToPreview, 500)
|
||||
|
||||
onLoaded: ->
|
||||
|
|
|
@ -32,7 +32,7 @@ module.exports = class CampaignEditorView extends RootView
|
|||
constructor: (options, @campaignHandle) ->
|
||||
super(options)
|
||||
@campaign = new Campaign({_id:@campaignHandle})
|
||||
@supermodel.loadModel(@campaign, 'campaign')
|
||||
@supermodel.loadModel(@campaign)
|
||||
@listenToOnce @campaign, 'sync', (model, response, jqXHR) ->
|
||||
@campaign.set '_id', response._id
|
||||
@campaign.url = -> '/db/campaign/' + @id
|
||||
|
@ -72,7 +72,7 @@ module.exports = class CampaignEditorView extends RootView
|
|||
thangType = new ThangType()
|
||||
thangType.setProjection(thangTypeProject)
|
||||
thangType.setURL("/db/thang.type/#{original}/version")
|
||||
@supermodel.loadModel(thangType, 'thang')
|
||||
@supermodel.loadModel(thangType)
|
||||
|
||||
onFundamentalLoaded: ->
|
||||
# Load any levels which haven't been denormalized into our campaign.
|
||||
|
@ -82,7 +82,7 @@ module.exports = class CampaignEditorView extends RootView
|
|||
model = new Level({})
|
||||
model.setProjection Campaign.denormalizedLevelProperties
|
||||
model.setURL("/db/level/#{level.original}/version")
|
||||
@levels.add @supermodel.loadModel(model, 'level').model
|
||||
@levels.add @supermodel.loadModel(model).model
|
||||
achievements = new RelatedAchievementsCollection level.original
|
||||
achievements.setProjection achievementProject
|
||||
@supermodel.loadCollection achievements, 'achievements'
|
||||
|
@ -109,20 +109,20 @@ module.exports = class CampaignEditorView extends RootView
|
|||
rewardObject.hero = reward
|
||||
thangType = new ThangType({}, {project: thangTypeProject})
|
||||
thangType.setURL("/db/thang.type/#{reward}/version")
|
||||
@supermodel.loadModel(thangType, 'thang')
|
||||
@supermodel.loadModel(thangType)
|
||||
|
||||
if rewardType is 'levels'
|
||||
rewardObject.level = reward
|
||||
if not @levels.findWhere({original: reward})
|
||||
level = new Level({}, {project: Campaign.denormalizedLevelProperties})
|
||||
level.setURL("/db/level/#{reward}/version")
|
||||
@supermodel.loadModel(level, 'level')
|
||||
@supermodel.loadModel(level)
|
||||
|
||||
if rewardType is 'items'
|
||||
rewardObject.item = reward
|
||||
thangType = new ThangType({}, {project: thangTypeProject})
|
||||
thangType.setURL("/db/thang.type/#{reward}/version")
|
||||
@supermodel.loadModel(thangType, 'thang')
|
||||
@supermodel.loadModel(thangType)
|
||||
|
||||
rewards.push rewardObject
|
||||
campaignLevel.rewards = rewards
|
||||
|
|
|
@ -71,7 +71,7 @@ module.exports = class ThangComponentsEditView extends CocoView
|
|||
levelComponent = new LevelComponent(componentRef)
|
||||
url = "/db/level.component/#{componentRef.original}/version/#{componentRef.majorVersion}"
|
||||
levelComponent.setURL(url)
|
||||
resource = @supermodel.loadModel levelComponent, 'component'
|
||||
resource = @supermodel.loadModel levelComponent
|
||||
continue unless resource.isLoading
|
||||
@listenToOnce resource, 'loaded', ->
|
||||
return if @handlingChange
|
||||
|
|
|
@ -30,7 +30,7 @@ module.exports = class SystemsTabView extends CocoView
|
|||
for system in @buildDefaultSystems()
|
||||
url = "/db/level.system/#{system.original}/version/#{system.majorVersion}"
|
||||
ls = new LevelSystem().setURL(url)
|
||||
@supermodel.loadModel(ls, 'system')
|
||||
@supermodel.loadModel(ls)
|
||||
|
||||
afterRender: ->
|
||||
@buildSystemsTreema()
|
||||
|
|
|
@ -30,7 +30,7 @@ module.exports = class VersionsModal extends ModalView
|
|||
constructor: (options, @ID, @model) ->
|
||||
super options
|
||||
@original = new @model(_id: @ID)
|
||||
@original = @supermodel.loadModel(@original, 'document').model
|
||||
@original = @supermodel.loadModel(@original).model
|
||||
@listenToOnce(@original, 'sync', @onViewSync)
|
||||
|
||||
onViewSync: ->
|
||||
|
|
|
@ -25,7 +25,7 @@ module.exports = class PollEditView extends RootView
|
|||
loadPoll: ->
|
||||
@poll = new Poll _id: @pollID
|
||||
@poll.saveBackups = true
|
||||
@supermodel.loadModel @poll, 'poll'
|
||||
@supermodel.loadModel @poll
|
||||
|
||||
loadUserPollsRecord: ->
|
||||
url = "/db/user.polls.record/-/user/#{me.id}"
|
||||
|
@ -34,7 +34,7 @@ module.exports = class PollEditView extends RootView
|
|||
return if @destroyed
|
||||
@userPollsRecord.url = -> '/db/user.polls.record/' + @id
|
||||
@listenToOnce @userPollsRecord, 'sync', onRecordSync
|
||||
@userPollsRecord = @supermodel.loadModel(@userPollsRecord, 'user_polls_record').model
|
||||
@userPollsRecord = @supermodel.loadModel(@userPollsRecord).model
|
||||
onRecordSync.call @ if @userPollsRecord.loaded
|
||||
|
||||
onLoaded: ->
|
||||
|
|
|
@ -13,7 +13,7 @@ module.exports = class ThangTypeColorsTabView extends CocoView
|
|||
|
||||
constructor: (@thangType, options) ->
|
||||
super options
|
||||
@supermodel.loadModel @thangType, 'thang'
|
||||
@supermodel.loadModel @thangType
|
||||
@colorConfig = {hue: 0, saturation: 0.5, lightness: 0.5}
|
||||
@spriteBuilder = new SpriteBuilder(@thangType) if @thangType.get('raw')
|
||||
f = =>
|
||||
|
|
|
@ -174,7 +174,7 @@ module.exports = class ThangTypeEditView extends RootView
|
|||
super options
|
||||
@mockThang = $.extend(true, {}, @mockThang)
|
||||
@thangType = new ThangType(_id: @thangTypeID)
|
||||
@thangType = @supermodel.loadModel(@thangType, 'thang').model
|
||||
@thangType = @supermodel.loadModel(@thangType).model
|
||||
@thangType.saveBackups = true
|
||||
@listenToOnce @thangType, 'sync', ->
|
||||
@files = @supermodel.loadCollection(new DocumentFiles(@thangType), 'files').model
|
||||
|
|
|
@ -19,7 +19,7 @@ module.exports = class I18NEditModelView extends RootView
|
|||
constructor: (options, @modelHandle) ->
|
||||
super(options)
|
||||
@model = new @modelClass(_id: @modelHandle)
|
||||
@model = @supermodel.loadModel(@model, 'model').model
|
||||
@model = @supermodel.loadModel(@model).model
|
||||
@model.saveBackups = true
|
||||
@selectedLanguage = me.get('preferredLanguage', true)
|
||||
|
||||
|
@ -172,7 +172,7 @@ module.exports = class I18NEditModelView extends RootView
|
|||
# Override PUT so we can trigger postNewVersion logic
|
||||
# or you're POSTing a Patch
|
||||
type = 'POST'
|
||||
res = modelToSave.save(null, {type: type})
|
||||
res = modelToSave.save(null, {type: type})
|
||||
return button.text('Failed to Submit Changes') unless res
|
||||
button.text('Submitting...')
|
||||
res.error => button.text('Error Submitting Changes')
|
||||
|
|
|
@ -41,7 +41,7 @@ module.exports = class LadderView extends RootView
|
|||
|
||||
constructor: (options, @levelID, @leagueType, @leagueID) ->
|
||||
super(options)
|
||||
@level = @supermodel.loadModel(new Level(_id: @levelID), 'level').model
|
||||
@level = @supermodel.loadModel(new Level(_id: @levelID)).model
|
||||
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(@levelID), 'your_sessions', {cache: false}).model
|
||||
@teams = []
|
||||
@loadLeague()
|
||||
|
@ -51,8 +51,7 @@ module.exports = class LadderView extends RootView
|
|||
@leagueID = @leagueType = null unless @leagueType in ['clan', 'course']
|
||||
return unless @leagueID
|
||||
modelClass = if @leagueType is 'clan' then Clan else CourseInstance
|
||||
resourceString = if @leagueType is 'clan' then 'clans.clan' else 'courses.course'
|
||||
@league = @supermodel.loadModel(new modelClass(_id: @leagueID), resourceString).model
|
||||
@league = @supermodel.loadModel(new modelClass(_id: @leagueID)).model
|
||||
if @leagueType is 'course'
|
||||
if @league.loaded
|
||||
@onCourseInstanceLoaded @league
|
||||
|
@ -62,7 +61,7 @@ module.exports = class LadderView extends RootView
|
|||
onCourseInstanceLoaded: (courseInstance) ->
|
||||
return if @destroyed
|
||||
course = new Course({_id: courseInstance.get('courseID')})
|
||||
@course = @supermodel.loadModel(course, 'courses.course').model
|
||||
@course = @supermodel.loadModel(course).model
|
||||
@listenToOnce @course, 'sync', @render
|
||||
|
||||
onLoaded: ->
|
||||
|
|
|
@ -13,7 +13,7 @@ module.exports = class ModelModal extends ModalView
|
|||
super options
|
||||
@models = options.models
|
||||
for model in @models when not model.loaded
|
||||
@supermodel.loadModel model, 'source_document'
|
||||
@supermodel.loadModel model
|
||||
model.fetch cache: false
|
||||
|
||||
afterRender: ->
|
||||
|
|
|
@ -82,7 +82,7 @@ module.exports = class CampaignView extends RootView
|
|||
|
||||
@campaign = new Campaign({_id:@terrain})
|
||||
@campaign.saveBackups = @editorMode
|
||||
@campaign = @supermodel.loadModel(@campaign, 'campaign').model
|
||||
@campaign = @supermodel.loadModel(@campaign).model
|
||||
|
||||
# Temporary attempt to make sure all earned rewards are accounted for. Figure out a better solution...
|
||||
@earnedAchievements = new CocoCollection([], {url: '/db/earned_achievement', model:EarnedAchievement, project: ['earnedRewards']})
|
||||
|
@ -442,11 +442,11 @@ module.exports = class CampaignView extends RootView
|
|||
preloadLevel: (levelSlug) ->
|
||||
levelURL = "/db/level/#{levelSlug}"
|
||||
level = new Level().setURL levelURL
|
||||
level = @supermodel.loadModel(level, 'level', null, 0).model
|
||||
level = @supermodel.loadModel(level, null, 0).model
|
||||
sessionURL = "/db/level/#{levelSlug}/session"
|
||||
@preloadedSession = new LevelSession().setURL sessionURL
|
||||
@listenToOnce @preloadedSession, 'sync', @onSessionPreloaded
|
||||
@preloadedSession = @supermodel.loadModel(@preloadedSession, 'level_session', {cache: false}).model
|
||||
@preloadedSession = @supermodel.loadModel(@preloadedSession, {cache: false}).model
|
||||
@preloadedSession.levelSlug = levelSlug
|
||||
|
||||
onSessionPreloaded: (session) ->
|
||||
|
@ -604,7 +604,7 @@ module.exports = class CampaignView extends RootView
|
|||
continue if @supermodel.getModel url
|
||||
fullHero = new ThangType()
|
||||
fullHero.setURL url
|
||||
@supermodel.loadModel fullHero, 'thang'
|
||||
@supermodel.loadModel fullHero
|
||||
|
||||
updateVolume: (volume) ->
|
||||
volume ?= me.get('volume') ? 1.0
|
||||
|
@ -675,7 +675,7 @@ module.exports = class CampaignView extends RootView
|
|||
else
|
||||
console.log 'Poll will be ready in', (22 * 60 * 60 * 1000 - interval) / (60 * 60 * 1000), 'hours.'
|
||||
@listenToOnce @userPollsRecord, 'sync', onRecordSync
|
||||
@userPollsRecord = @supermodel.loadModel(@userPollsRecord, 'user_polls_record', null, 0).model
|
||||
@userPollsRecord = @supermodel.loadModel(@userPollsRecord, null, 0).model
|
||||
onRecordSync.call @ if @userPollsRecord.loaded
|
||||
|
||||
loadPoll: ->
|
||||
|
@ -693,7 +693,7 @@ module.exports = class CampaignView extends RootView
|
|||
delete @poll
|
||||
@listenToOnce @poll, 'sync', onPollSync
|
||||
@listenToOnce @poll, 'error', onPollError
|
||||
@poll = @supermodel.loadModel(@poll, 'poll', null, 0).model
|
||||
@poll = @supermodel.loadModel(@poll, null, 0).model
|
||||
onPollSync.call @ if @poll.loaded
|
||||
|
||||
activatePoll: ->
|
||||
|
|
|
@ -17,7 +17,7 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
template: template
|
||||
closesOnClickOutside: false
|
||||
|
||||
|
||||
|
||||
initialize: (options) ->
|
||||
@courseID = options.courseID
|
||||
@courseInstanceID = options.courseInstanceID
|
||||
|
@ -27,7 +27,7 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
@level = options.level
|
||||
@newItems = new ThangTypes()
|
||||
@newHeroes = new ThangTypes()
|
||||
|
||||
|
||||
@achievements = options.achievements
|
||||
if not @achievements
|
||||
@achievements = new Achievements()
|
||||
|
@ -36,18 +36,18 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
@listenToOnce @achievements, 'sync', @onAchievementsLoaded
|
||||
else
|
||||
@onAchievementsLoaded()
|
||||
|
||||
|
||||
@playSound 'victory'
|
||||
@nextLevel = options.nextLevel
|
||||
if (nextLevel = @level.get('nextLevel')) and not @nextLevel
|
||||
@nextLevel = new Level().setURL "/db/level/#{nextLevel.original}/version/#{nextLevel.majorVersion}"
|
||||
@nextLevel = @supermodel.loadModel(@nextLevel, 'level').model
|
||||
|
||||
@nextLevel = @supermodel.loadModel(@nextLevel).model
|
||||
|
||||
@campaign = new Campaign()
|
||||
@course = options.course
|
||||
if @courseID and not @course
|
||||
@course = new Course().setURL "/db/course/#{@courseID}"
|
||||
@course = @supermodel.loadModel(@course, 'course').model
|
||||
@course = @supermodel.loadModel(@course).model
|
||||
if @course.loading
|
||||
@listenToOnce @course, 'sync', @onCourseLoaded
|
||||
else
|
||||
|
@ -58,16 +58,16 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
if @courseInstanceID
|
||||
@levelSessions = new LevelSessions()
|
||||
@levelSessions.fetchForCourseInstance(@courseInstanceID)
|
||||
@levelSessions = @supermodel.loadCollection(@levelSessions, 'sessions', {
|
||||
@levelSessions = @supermodel.loadCollection(@levelSessions, 'sessions', {
|
||||
data: { project: 'state.complete level.original playtime changed' }
|
||||
}).model
|
||||
|
||||
|
||||
onCourseLoaded: ->
|
||||
@campaign.set('_id', @course.get('campaignID'))
|
||||
@campaign = @supermodel.loadModel(@campaign, 'campaign').model
|
||||
@campaign = @supermodel.loadModel(@campaign).model
|
||||
|
||||
|
||||
|
||||
onAchievementsLoaded: ->
|
||||
@achievements.models = _.filter @achievements.models, (m) -> not m.get('query')?.ladderAchievementDifficulty # Don't show higher AI difficulty achievements
|
||||
itemOriginals = []
|
||||
|
@ -89,9 +89,9 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
thang= new ThangType()
|
||||
thang.url = "/db/thang.type/#{original}/version"
|
||||
thang.project = project
|
||||
@supermodel.loadModel(thang, 'thang')
|
||||
@supermodel.loadModel(thang)
|
||||
newThangTypeCollection.add(thang)
|
||||
|
||||
|
||||
@newEarnedAchievements = []
|
||||
for achievement in @achievements.models
|
||||
continue unless achievement.completed
|
||||
|
@ -108,25 +108,25 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
model.sr.markLoaded()
|
||||
if _.all((ea.id for ea in @newEarnedAchievements))
|
||||
unless me.loading
|
||||
@supermodel.loadModel(me, 'user', {cache: false})
|
||||
@supermodel.loadModel(me, {cache: false})
|
||||
@newEarnedAchievementsResource.markLoaded()
|
||||
|
||||
|
||||
|
||||
# have to use a something resource because addModelResource doesn't handle models being upserted/fetched via POST like we're doing here
|
||||
@newEarnedAchievementsResource = @supermodel.addSomethingResource('earned achievements') if @newEarnedAchievements.length
|
||||
|
||||
|
||||
|
||||
onLoaded: ->
|
||||
super()
|
||||
@views = []
|
||||
|
||||
|
||||
# TODO: Add main victory view
|
||||
# TODO: Add level up view
|
||||
# TODO: Add new hero view?
|
||||
|
||||
|
||||
for newItem in @newItems.models
|
||||
@views.push(new NewItemView({item: newItem}))
|
||||
|
||||
|
||||
progressView = new ProgressView({
|
||||
level: @level
|
||||
nextLevel: @nextLevel
|
||||
|
@ -134,15 +134,15 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
campaign: @campaign
|
||||
levelSessions: @levelSessions
|
||||
})
|
||||
|
||||
|
||||
progressView.once 'done', @onDone, @
|
||||
progressView.once 'next-level', @onNextLevel, @
|
||||
for view in @views
|
||||
view.on 'continue', @onViewContinue, @
|
||||
@views.push(progressView)
|
||||
|
||||
|
||||
@showView(_.first(@views))
|
||||
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@showView(@currentView)
|
||||
|
@ -166,5 +166,3 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
onDone: ->
|
||||
link = "/courses/#{@courseID}/#{@courseInstanceID}"
|
||||
application.router.navigate(link, {trigger: true})
|
||||
|
||||
|
|
@ -66,10 +66,10 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
if @level.get('type', true) is 'course'
|
||||
if nextLevel = @level.get('nextLevel')
|
||||
@nextLevel = new Level().setURL "/db/level/#{nextLevel.original}/version/#{nextLevel.majorVersion}"
|
||||
@nextLevel = @supermodel.loadModel(@nextLevel, 'level').model
|
||||
@nextLevel = @supermodel.loadModel(@nextLevel).model
|
||||
if @courseID
|
||||
@course = new Course().setURL "/db/course/#{@courseID}"
|
||||
@course = @supermodel.loadModel(@course, 'course').model
|
||||
@course = @supermodel.loadModel(@course).model
|
||||
if @level.get('type', true) in ['course', 'course-ladder']
|
||||
@saveReviewEventually = _.debounce(@saveReviewEventually, 2000)
|
||||
@loadExistingFeedback()
|
||||
|
@ -123,7 +123,7 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
thangType.url = "/db/thang.type/#{thangTypeOriginal}/version"
|
||||
#thangType.project = ['original', 'rasterIcon', 'name', 'soundTriggers', 'i18n'] # This is what we need, but the PlayHeroesModal needs more, and so we load more to fill up the supermodel.
|
||||
thangType.project = ['original', 'rasterIcon', 'name', 'slug', 'soundTriggers', 'featureImages', 'gems', 'heroClass', 'description', 'components', 'extendedName', 'unlockLevelName', 'i18n']
|
||||
@thangTypes[thangTypeOriginal] = @supermodel.loadModel(thangType, 'thang').model
|
||||
@thangTypes[thangTypeOriginal] = @supermodel.loadModel(thangType).model
|
||||
|
||||
@newEarnedAchievements = []
|
||||
hadOneCompleted = false
|
||||
|
|
|
@ -170,7 +170,7 @@ module.exports = class CastButtonView extends CocoView
|
|||
url = "/db/level/#{@options.level.get('slug') or @options.level.id}/session"
|
||||
url += "?team=#{if me.team is 'humans' then 'ogres' else 'humans'}"
|
||||
mirrorSession = new LevelSession().setURL url
|
||||
@mirrorSession = @supermodel.loadModel(mirrorSession, 'level_session', {cache: false}).model
|
||||
@mirrorSession = @supermodel.loadModel(mirrorSession, {cache: false}).model
|
||||
|
||||
updateLadderSubmissionViews: ->
|
||||
@removeSubView subview for key, subview of @subviews when subview instanceof LadderSubmissionView
|
||||
|
|
|
@ -21,7 +21,7 @@ module.exports = class LeaderboardModal extends ModalView
|
|||
@levelSlug = @options.levelSlug
|
||||
level = new Level({_id: @levelSlug})
|
||||
level.project = ['name', 'i18n', 'scoreType', 'original']
|
||||
@level = @supermodel.loadModel(level, 'level').model
|
||||
@level = @supermodel.loadModel(level).model
|
||||
|
||||
getRenderData: (c) ->
|
||||
c = super c
|
||||
|
|
|
@ -139,7 +139,7 @@ module.exports = class PlayHeroesModal extends ModalView
|
|||
return fullHero
|
||||
fullHero = new ThangType()
|
||||
fullHero.setURL url
|
||||
fullHero = (@supermodel.loadModel fullHero, 'thang').model
|
||||
fullHero = (@supermodel.loadModel fullHero).model
|
||||
fullHero
|
||||
|
||||
preloadHero: (heroIndex) ->
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* global ISODate */
|
||||
/* global Mongo */
|
||||
/* global ObjectId */
|
||||
/* global db */
|
||||
/* global printjson */
|
||||
|
@ -18,6 +20,11 @@ try {
|
|||
var today = startDay.toISOString().substr(0, 10);
|
||||
startDay.setUTCDate(startDay.getUTCDate() - numDays);
|
||||
startDay = startDay.toISOString().substr(0, 10);
|
||||
var endDay = new Date();
|
||||
endDay = endDay.toISOString().substr(0, 10);
|
||||
|
||||
// startDay = '2015-03-01';
|
||||
// endDay = '2015-06-01';
|
||||
|
||||
var activeUserEvents = ['Finished Signup', 'Started Level'];
|
||||
|
||||
|
@ -26,9 +33,10 @@ try {
|
|||
|
||||
log("Today is " + today);
|
||||
log("Start day is " + startDay);
|
||||
log("End day is " + endDay);
|
||||
|
||||
log("Getting active user counts..");
|
||||
var activeUserCounts = getActiveUserCounts(startDay, activeUserEvents);
|
||||
var activeUserCounts = getActiveUserCounts(startDay, endDay, activeUserEvents);
|
||||
// printjson(activeUserCounts);
|
||||
log("Inserting active user counts..");
|
||||
for (var day in activeUserCounts) {
|
||||
|
@ -47,7 +55,7 @@ finally {
|
|||
log("Script runtime: " + (new Date() - scriptStartTime));
|
||||
}
|
||||
|
||||
function getActiveUserCounts(startDay, activeUserEvents) {
|
||||
function getActiveUserCounts(startDay, endDay, activeUserEvents) {
|
||||
// Counts active users per day
|
||||
if (!startDay) return {};
|
||||
|
||||
|
@ -55,8 +63,10 @@ function getActiveUserCounts(startDay, activeUserEvents) {
|
|||
|
||||
log("Finding active user log events..");
|
||||
var startObj = objectIdWithTimestamp(ISODate(startDay + "T00:00:00.000Z"));
|
||||
var endObj = objectIdWithTimestamp(ISODate(endDay + "T00:00:00.000Z"));
|
||||
var queryParams = {$and: [
|
||||
{_id: {$gte: startObj}},
|
||||
{_id: {$lt: endObj}},
|
||||
{'event': {$in: activeUserEvents}}
|
||||
]};
|
||||
cursor = logDB['log'].find(queryParams);
|
||||
|
@ -73,8 +83,11 @@ function getActiveUserCounts(startDay, activeUserEvents) {
|
|||
if (!dayUserMap[day]) dayUserMap[day] = {};
|
||||
dayUserMap[day][user] = true;
|
||||
userIDs.push(ObjectId(user));
|
||||
// if (userIDs.length % 100000 === 0) {
|
||||
// log('Users so far: ' + userIDs.length);
|
||||
// }
|
||||
}
|
||||
print('User count: ', userIDs.length);
|
||||
log('User count: ' + userIDs.length);
|
||||
|
||||
log("Finding classroom members..");
|
||||
var classroomUserObjectIds = [];
|
||||
|
@ -163,15 +176,7 @@ function getActiveUserCounts(startDay, activeUserEvents) {
|
|||
var dailyEventNames = {};
|
||||
for (day in dayUserMap) {
|
||||
for (var user in dayUserMap[day]) {
|
||||
var event = userEventMap[user];
|
||||
if (!event) {
|
||||
if (dayUserPaidMap[day] && dayUserPaidMap[day][user]) {
|
||||
event = 'DAU campaign paid';
|
||||
}
|
||||
else {
|
||||
event = 'DAU campaign free';
|
||||
}
|
||||
}
|
||||
var event = userEventMap[user] || (dayUserPaidMap[day] && dayUserPaidMap[day][user] ? 'DAU campaign paid' : 'DAU campaign free');
|
||||
dailyEventNames[event] = true;
|
||||
if (!activeUsersCounts[day]) activeUsersCounts[day] = {};
|
||||
if (!activeUsersCounts[day][event]) activeUsersCounts[day][event] = 0;
|
||||
|
|
|
@ -60,6 +60,7 @@ function getRecurringRevenueCounts(startDay) {
|
|||
}
|
||||
|
||||
if (doc.service === 'ios' || doc.service === 'bitcoin') continue;
|
||||
if (!doc.amount || doc.amount <= 0) continue;
|
||||
|
||||
if (doc.prepaidID) {
|
||||
if (prepaidDayAmountMap[doc.prepaidID.valueOf()]) {
|
||||
|
|
|
@ -207,9 +207,27 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler
|
|||
query = {$and: [{name: {$ne: 'Single Player'}}, {hourOfCode: {$ne: true}}]}
|
||||
query["$and"].push(_id: {$gte: objectIdFromTimestamp(req.body.startDay + "T00:00:00.000Z")}) if req.body.startDay?
|
||||
query["$and"].push(_id: {$lt: objectIdFromTimestamp(req.body.endDay + "T00:00:00.000Z")}) if req.body.endDay?
|
||||
CourseInstance.find query, (err, courseInstances) =>
|
||||
CourseInstance.find query, {courseID: 1, members: 1, ownerID: 1}, (err, courseInstances) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
@sendSuccess(res, courseInstances)
|
||||
userIDs = []
|
||||
for courseInstance in courseInstances
|
||||
if members = courseInstance.get('members')
|
||||
userIDs.push(userID) for userID in members
|
||||
|
||||
User.find {_id: {$in: userIDs}}, {coursePrepaidID: 1}, (err, users) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
prepaidIDs = []
|
||||
for user in users
|
||||
if prepaidID = user.get('coursePrepaidID')
|
||||
prepaidIDs.push(prepaidID)
|
||||
|
||||
Prepaid.find {_id: {$in: prepaidIDs}}, {properties: 1}, (err, prepaids) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
data =
|
||||
courseInstances: (@formatEntity(req, courseInstance) for courseInstance in courseInstances)
|
||||
students: (@formatEntity(req, user) for user in users)
|
||||
prepaids: (@formatEntity(req, prepaid) for prepaid in prepaids)
|
||||
@sendSuccess(res, data)
|
||||
|
||||
inviteStudents: (req, res, courseInstanceID) ->
|
||||
return @sendUnauthorizedError(res) if not req.user?
|
||||
|
|
|
@ -3,6 +3,7 @@ errors = require '../commons/errors'
|
|||
wrap = require 'co-express'
|
||||
Promise = require 'bluebird'
|
||||
database = require '../commons/database'
|
||||
mongoose = require 'mongoose'
|
||||
|
||||
module.exports =
|
||||
names: (Model, options={}) -> wrap (req, res) ->
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
Payment = require './Payment'
|
||||
Prepaid = require '../prepaids/Prepaid'
|
||||
Product = require '../models/Product'
|
||||
User = require '../users/User'
|
||||
Handler = require '../commons/Handler'
|
||||
|
@ -27,6 +28,11 @@ PaymentHandler = class PaymentHandler extends Handler
|
|||
res.send(payments)
|
||||
)
|
||||
|
||||
getByRelationship: (req, res, args...) ->
|
||||
relationship = args[1]
|
||||
return @getSchoolSalesAPI(req, res) if relationship is 'school_sales'
|
||||
super arguments...
|
||||
|
||||
logPaymentError: (req, msg) ->
|
||||
console.warn "Payment Error: #{req.user.get('slug')} (#{req.user._id}): '#{msg}'"
|
||||
|
||||
|
@ -37,6 +43,43 @@ PaymentHandler = class PaymentHandler extends Handler
|
|||
payment.set 'created', new Date().toISOString()
|
||||
payment
|
||||
|
||||
getSchoolSalesAPI: (req, res, code) ->
|
||||
return @sendUnauthorizedError(res) unless req.user?.isAdmin()
|
||||
userIDs = [];
|
||||
Payment.find({}, {amount: 1, created: 1, description: 1, prepaidID: 1, productID: 1, purchaser: 1, service: 1}).exec (err, payments) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
schoolSales = []
|
||||
prepaidIDs = []
|
||||
prepaidPaymentMap = {}
|
||||
for payment in payments
|
||||
continue unless payment.get('amount')? and payment.get('amount') > 0
|
||||
unless created = payment.get('created')
|
||||
created = payment.get('_id').getTimestamp()
|
||||
description = payment.get('description') ? ''
|
||||
if prepaidID = payment.get('prepaidID')
|
||||
unless prepaidPaymentMap[prepaidID.valueOf()]
|
||||
prepaidPaymentMap[prepaidID.valueOf()] = {_id: payment.get('_id').valueOf(), amount: payment.get('amount'), created: created, description: description, userID: payment.get('purchaser').valueOf(), prepaidID: prepaidID.valueOf()}
|
||||
prepaidIDs.push(prepaidID)
|
||||
userIDs.push(payment.get('purchaser'))
|
||||
else if payment.get('productID') is 'custom' or payment.get('service') is 'external' or payment.get('service') is 'invoice'
|
||||
schoolSales.push({_id: payment.get('_id').valueOf(), amount: payment.get('amount'), created: created, description: description, userID: payment.get('purchaser').valueOf()})
|
||||
userIDs.push(payment.get('purchaser'))
|
||||
|
||||
Prepaid.find({$and: [{_id: {$in: prepaidIDs}}, {type: 'course'}]}, {_id: 1}).exec (err, prepaids) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
for prepaid in prepaids
|
||||
schoolSales.push(prepaidPaymentMap[prepaid.get('_id').valueOf()])
|
||||
|
||||
User.find({_id: {$in: userIDs}}).exec (err, users) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
userMap = {}
|
||||
for user in users
|
||||
userMap[user.get('_id').valueOf()] = user
|
||||
for schoolSale in schoolSales
|
||||
schoolSale.user = userMap[schoolSale.userID]?.toObject()
|
||||
|
||||
@sendSuccess(res, schoolSales)
|
||||
|
||||
post: (req, res, pathName) ->
|
||||
if pathName is 'check-stripe-charges'
|
||||
return @checkStripeCharges(req, res)
|
||||
|
|
|
@ -110,9 +110,6 @@ describe 'CocoModel', ->
|
|||
expect(request).toBeUndefined()
|
||||
|
||||
xdescribe 'Achievement polling', ->
|
||||
NewAchievementCollection = require 'collections/NewAchievementCollection'
|
||||
EarnedAchievement = require 'models/EarnedAchievement'
|
||||
|
||||
# TODO: Figure out how to do debounce in tests so that this test doesn't need to use keepDoingUntil
|
||||
|
||||
it 'achievements are polled upon saving a model', (done) ->
|
||||
|
@ -173,4 +170,4 @@ describe 'CocoModel', ->
|
|||
})
|
||||
|
||||
m.updateI18NCoverage()
|
||||
expect(JSON.stringify(m.get('i18nCoverage'))).toBe('["es"]')
|
||||
expect(JSON.stringify(m.get('i18nCoverage'))).toBe('["es"]')
|
||||
|
|
|
@ -27,14 +27,14 @@ describe 'SuperModel', ->
|
|||
it 'starts loading the model if it isn\'t already loading', ->
|
||||
s = new SuperModel()
|
||||
m = new User({_id: '12345'})
|
||||
s.loadModel(m, 'user')
|
||||
s.loadModel(m)
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request).toBeDefined()
|
||||
|
||||
it 'also loads collections', ->
|
||||
s = new SuperModel()
|
||||
c = new ComponentsCollection()
|
||||
s.loadModel(c, 'collection')
|
||||
s.loadModel(c)
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request).toBeDefined()
|
||||
|
||||
|
@ -44,7 +44,7 @@ describe 'SuperModel', ->
|
|||
m = new User({_id: '12345'})
|
||||
triggered = false
|
||||
s.once 'loaded-all', -> triggered = true
|
||||
s.loadModel(m, 'user')
|
||||
s.loadModel(m)
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({status: 200, responseText: '{}'})
|
||||
_.defer ->
|
||||
|
|