diff --git a/app/styles/play/campaign-view.sass b/app/styles/play/campaign-view.sass new file mode 100644 index 000000000..802206b8a --- /dev/null +++ b/app/styles/play/campaign-view.sass @@ -0,0 +1,495 @@ +@import "app/styles/mixins" +@import "app/styles/bootstrap/variables" + +$mapHeight: 1536 +$forestMapWidth: 2500 +$dungeonMapWidth: 2350 +$desertMapWidth: 2350 +$desertMapSeaBackground: rgba(113, 186, 208, 1) +$desertMapSeaBackgroundTransparent: rgba(113, 186, 208, 0) +$forestMapSeaBackground: rgba(113, 186, 208, 1) +$forestMapSeaBackgroundTransparent: rgba(113, 186, 208, 0) +$dungeonMapCaveBackground: rgba(68, 54, 45, 1) +$dungeonMapCaveBackgroundTransparent: rgba(68, 54, 45, 0) +$levelDotWidth: 2% +$levelDotHeight: $levelDotWidth * $forestMapWidth / $mapHeight +$levelDotZ: $levelDotHeight * 0.25 +$levelDotHoverZ: $levelDotZ * 2 +$levelDotShadowWidth: 0.8 * $levelDotWidth +$levelDotShadowHeight: 0.8 * $levelDotHeight +$levelClickRadius: 40px +$gameControlSize: 80px +$gameControlMargin: 30px + ++keyframes(levelStartedPulse) + from + @include box-shadow(0px 0px 4px #333) + margin-bottom: -$levelDotHeight / 3 + $levelDotZ + 50% + @include box-shadow(0px 0px 22px skyblue) + margin-bottom: -$levelDotHeight / 3 + ($levelDotHoverZ + $levelDotZ) / 2 + to + @include box-shadow(0px 0px 4px #333) + margin-bottom: -$levelDotHeight / 3 + $levelDotZ + +#campaign-view + width: 100% + height: 100% + position: absolute + + .gradient + position: absolute + z-index: 0 + + &.horizontal-gradient + left: 0 + right: 0 + height: 3% + + &.vertical-gradient + top: 0 + bottom: 0 + width: 3% + + &.top-gradient + top: 0 + + &.right-gradient + right: 0 + + &.bottom-gradient + bottom: 0 + + &.left-gradient + left: 0 + + &.desert + background-color: $desertMapSeaBackground + + .top-gradient + background: linear-gradient(to bottom, $desertMapSeaBackground 0%, $desertMapSeaBackgroundTransparent 100%) + + .right-gradient + background: linear-gradient(to left, $desertMapSeaBackground 0%, $desertMapSeaBackgroundTransparent 100%) + + .bottom-gradient + background: linear-gradient(to top, $desertMapSeaBackground 0%, $desertMapSeaBackgroundTransparent 100%) + + .left-gradient + background: linear-gradient(to right, $desertMapSeaBackground 0%, $desertMapSeaBackgroundTransparent 100%) + + &.forest + background-color: $forestMapSeaBackground + + .top-gradient + background: linear-gradient(to bottom, $forestMapSeaBackground 0%, $forestMapSeaBackgroundTransparent 100%) + + .right-gradient + background: linear-gradient(to left, $forestMapSeaBackground 0%, $forestMapSeaBackgroundTransparent 100%) + + .bottom-gradient + background: linear-gradient(to top, $forestMapSeaBackground 0%, $forestMapSeaBackgroundTransparent 100%) + + .left-gradient + background: linear-gradient(to right, $forestMapSeaBackground 0%, $forestMapSeaBackgroundTransparent 100%) + + &.dungeon + background-color: $dungeonMapCaveBackground + + .top-gradient + background: linear-gradient(to bottom, $dungeonMapCaveBackground 0%, $dungeonMapCaveBackgroundTransparent 100%) + + .right-gradient + background: linear-gradient(to left, $dungeonMapCaveBackground 0%, $dungeonMapCaveBackgroundTransparent 100%) + + .bottom-gradient + background: linear-gradient(to top, $dungeonMapCaveBackground 0%, $dungeonMapCaveBackgroundTransparent 100%) + + .left-gradient + background: linear-gradient(to right, $dungeonMapCaveBackground 0%, $dungeonMapCaveBackgroundTransparent 100%) + + .map + position: relative + + .map-background + width: 100% + height: 100% + background-size: 100% + @include user-select(none) + + &.map-dungeon + background-image: url('/images/pages/play/map_dungeon_1920.jpg') + @media screen and ( max-width: 1366px ) + background-image: url('/images/pages/play/map_dungeon_1366.jpg') + + &.map-forest + background-image: url('/images/pages/play/map_forest_1920.jpg') + @media screen and ( max-width: 1366px ) + background-image: url('/images/pages/play/map_forest_1366.jpg') + + &.map-desert + background-image: url('/images/pages/play/map_desert_1920.jpg') + @media screen and ( max-width: 1366px ) + background-image: url('/images/pages/play/map_desert_1366.jpg') + + .level, .level-shadow + position: absolute + border-radius: 100% + -webkit-transform: scaleY(0.75) + transform: scaleY(0.75) + + .level + z-index: 2 + width: $levelDotWidth + height: $levelDotHeight + margin-left: -0.5 * $levelDotWidth + margin-bottom: -$levelDotHeight / 3 + $levelDotZ + border: 2px groove white + @include transition(margin-bottom 0.5s ease) + + &.disabled, &.locked + background-image: url(/images/pages/game-menu/lock.png) + background-size: 75% + background-repeat: no-repeat + background-position: 50% 50% + opacity: 0.7 + + a + cursor: default + + &.next + width: 2 * $levelDotWidth + height: 2 * $levelDotHeight + margin-left: -0.5 * 2 * $levelDotWidth + margin-bottom: -2 * $levelDotHeight / 3 + 2 * $levelDotZ + + &.started, &.next + border: 3px solid lightgreen + @include box-shadow(0px 0px 35px skyblue) + + // Would be cool, but kills performance, since we have to re-render all the time. + //&:not(:hover) + // -webkit-animation-name: levelStartedPulse + // -webkit-animation-duration: 3s + // -webkit-animation-iteration-count: infinite + + &.complete + border: 3px solid gold + @include box-shadow(0px 0px 35px skyblue) + + img.banner + position: absolute + bottom: 38% + left: -50% + width: 200% + pointer-events: none + + img.star + width: 100% + bottom: 7% + position: absolute + pointer-events: none + + .glyphicon-star + position: absolute + color: lightblue + font-size: 21px + left: 1.5px + + &.started .glyphicon-star + left: 0.5px + + img.hero-portrait + width: 120% + position: absolute + bottom: 75% + left: 75% + border: 1px solid black + border-radius: 100% + background: white + + + .level-shadow + z-index: 1 + width: $levelDotShadowWidth + height: $levelDotShadowHeight + margin-left: -0.5 * $levelDotShadowWidth + margin-bottom: -$levelDotShadowHeight / 3 + background-color: black + @include box-shadow(0px 0px 10px black) + @include opacity(0.75) + + &.next + width: 2 * $levelDotShadowWidth + height: 2 * $levelDotShadowHeight + margin-left: -0.5 * 2 * $levelDotShadowWidth + margin-bottom: -2 * $levelDotShadowHeight / 3 + + .level:hover + // TODO: This rotate stops Firefox from flickering, but also disables the scaleY(0.75) + // TODO: The dot looks like it's jumping. + // TODO: -moz-transform: scaleY(0.75) didn't do anything + // TODO: Does not break Chrome's oval. + -moz-transform: rotate(0) + margin-bottom: -$levelDotHeight / 3 + $levelDotHoverZ + @include box-shadow(0px 0px 35px skyblue) + + &.next + margin-bottom: -2 * $levelDotHeight / 3 + 2 * $levelDotHoverZ + + .level + a + display: block + padding: $levelClickRadius + margin-left: -0.5 * $levelClickRadius + margin-top: -0.5 * $levelClickRadius + border-radius: $levelClickRadius + + &.next a + padding: 2 * $levelClickRadius + margin-left: 2 * -0.5 * $levelClickRadius + margin-top: 2 * -0.5 * $levelClickRadius + border-radius: 2 * $levelClickRadius + + .tooltip + z-index: 2 + + .level-info-container + display: none + position: absolute + z-index: 3 + padding: 10px + border-width: 16px 12px + // Using modernizr-mixin for compat detection + @include yep(borderimage) + border-style: solid + border-image: url(/images/level/popover_border_background.png) 16 12 fill round + @include nope(borderimage) + background-color: rgb(247, 242, 218) + + .level-info.complete h3:after + content: " - Complete!" + color: green + + .level-info.started h3:after + content: " - Started" + color: desaturate(green, 50%) + + .level-info + h3 + margin-top: 0 + margin-bottom: 0px + + .level-description + color: black + text-shadow: 0 1px 0 white + + .campaign-label + text-shadow: 0 1px 0 white + + .start-level + display: block + margin: 10px auto 0 auto + width: 200px + + .campaign-switch + color: purple + position: absolute + z-index: 1 + font-size: 2vw + text-shadow: 0 0 0.3vw white, 0 0 0.3vw white + + &:hover + text-decoration: none + + &#desert-link + left: 90% + top: 18.5% + transform: scaleY(-1.5) scaleX(1.5) + + &#forest-back-link + left: 2% + top: 70.5% + transform: rotate(216deg) + + &#forest-link + left: 94.5% + top: 7% + transform: rotate(-35deg) + + &#dungeon-link + left: 9% + top: 54.5% + transform: rotate(180deg) + color: fuchsia + + .game-controls + position: absolute + right: 1% + bottom: 1% + z-index: 3 + + .btn + &:not(:first-child) + margin-left: $gameControlMargin + width: $gameControlSize + height: $gameControlSize + + background: url(/images/pages/play/menu_icons.png) no-repeat + + position: relative + img + position: absolute + left: 0 + top: 0 + width: 100% + height: 100% + + background-size: cover + @include transition(0.5s ease) + @include box-shadow(2px 2px 4px black) + border: 0 + border-radius: 12px + // IE9 shows a blank white button with this MS gradient filter in place + filter: none + + &:hover + @include box-shadow(0 0 12px #bbf) + + &:active + @include box-shadow(0 0 20px white) + + &.heroes + background-position: (-1 * $gameControlSize) 0px + &.achievements + background-position: (-2 * $gameControlSize) 0px + &.account + background-position: (-3 * $gameControlSize) 0px + &.settings + background-position: (-4 * $gameControlSize) 0px + &.gems + background-position: (-5 * $gameControlSize) 0px + + .tooltip + font-size: 24px + + .tooltip-arrow + display: none + + .user-status + position: absolute + bottom: 16px + left: 8px + text-align: center + font-size: 24px + color: white + text-shadow: 0px 2px 1px black, 0px -2px 1px black, -2px 0px 1px black, 2px 0px 1px black + height: 32px + line-height: 32px + + .user-status-line + position: relative + + button.btn.btn-illustrated + margin-left: 10px + min-width: 90px + height: 32px + color: white + + .gem, .player-level-icon, .player-hero-icon + position: absolute + top: 1px + + #gems-count + margin-left: 40px + + .player-level + margin-left: 34px + + .player-name + margin-left: 45px + + $spriteSheetSize: 30px + + .player-level-icon, .player-hero-icon + background: transparent url(/images/pages/play/play-spritesheet.png) + background-size: cover + background-position: (-2 * $spriteSheetSize) 0 + display: inline-block + width: 30px + height: 30px + margin: 0px 2px + + .player-hero-icon + margin-left: 10px + background-position: (-4 * $spriteSheetSize) 0 + + &.knight + background-position: (-5 * $spriteSheetSize) 0 + &.librarian + background-position: (-6 * $spriteSheetSize) 0 + &.ninja + background-position: (-7 * $spriteSheetSize) 0 + &.potion-master + background-position: (-8 * $spriteSheetSize) 0 + &.samurai + background-position: (-9 * $spriteSheetSize) 0 + &.trapper + background-position: (-10 * $spriteSheetSize) 0 + &.forest-archer + background-position: (-11 * $spriteSheetSize) 0 + &.sorcerer + background-position: (-12 * $spriteSheetSize) 0 + + + #volume-button + position: absolute + left: 1% + top: 1% + padding: 3px 8px + @include opacity(0.75) + + &:hover + @include opacity(1.0) + + .glyphicon + display: none + font-size: 32px + + &.vol-up .glyphicon.glyphicon-volume-up + display: inline-block + + &.vol-off .glyphicon.glyphicon-volume-off + display: inline-block + @include opacity(0.50) + &:hover + @include opacity(0.75) + + &.vol-down .glyphicon.glyphicon-volume-down + display: inline-block + + #campaign-status + position: absolute + left: 0 + top: 15px + width: 100% + margin: 0 + text-align: center + color: rgb(254,188,68) + font-size: 30px + text-shadow: black 2px 2px 0, black -2px -2px 0, black 2px -2px 0, black -2px 2px 0, black 2px 0px 0, black 0px -2px 0, black -2px 0px 0, black 0px 2px 0 + + +body:not(.ipad) #campaign-view + .level-info-container + pointer-events: none + + + +body.ipad #campaign-view + // iPad only supports up to Kithgard Gates for now. + .campaign-switch + display: none + + .old-levels + display: none diff --git a/app/templates/play/campaign-view.jade b/app/templates/play/campaign-view.jade new file mode 100644 index 000000000..2a88a63d9 --- /dev/null +++ b/app/templates/play/campaign-view.jade @@ -0,0 +1,106 @@ +.map + .gradient.horizontal-gradient.top-gradient + .gradient.vertical-gradient.right-gradient + .gradient.horizontal-gradient.bottom-gradient + .gradient.vertical-gradient.left-gradient + .map-background(class="map-"+mapType alt="", draggable="false") + + - var seenNext = nextLevel; + each level in campaign.levels + if !level.hidden + - var next = level.id == nextLevel || (!seenNext && levelStatusMap[level.id] != "complete" && !level.locked && !level.disabled && !editorMode); + - seenNext = seenNext || next; + div(style="left: #{level.x}%; bottom: #{level.y}%; background-color: #{level.color}", class="level" + (next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + levelStatusMap[level.id] || "", data-level-id=level.id, title=level.name + (level.disabled ? ' (Coming Soon to Adventurers)' : '')) + if level.unlocksHero && !level.unlockedHero + img.hero-portrait(src=level.unlocksHero.img) + a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.id}", disabled=level.disabled, data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name) + if level.requiresSubscription + img.star(src="/images/pages/play/star.png") + if levelStatusMap[level.id] === 'complete' + img.banner(src="/images/pages/play/level-banner-complete.png") + if levelStatusMap[level.id] === 'started' + img.banner(src="/images/pages/play/level-banner-started.png") + div(style="left: #{level.x}%; bottom: #{level.y}%", class="level-shadow" + (next ? " next" : "") + " " + levelStatusMap[level.id] || "") + .level-info-container(data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name) + div(class="level-info " + (levelStatusMap[level.id] || "")) + h3= level.name + (level.disabled ? " (Coming soon!)" : (level.locked ? " (Locked)" : "")) + .level-description= level.description + if level.disabled + p + span.spr(data-i18n="play.awaiting_levels_adventurer_prefix") We release five levels per week. + a.spr(href="/contribute/adventurer") + strong(data-i18n="play.awaiting_levels_adventurer") Sign up as an Adventurer + span.spl(data-i18n="play.awaiting_levels_adventurer_suffix") to be the first to play new levels. + + - var playCount = levelPlayCountMap[level.id] + if playCount && playCount.sessions > 20 + div + span.spr #{playCount.sessions} + span(data-i18n="play.players") players + span.spr , #{Math.round(playCount.playtime / 3600)} + span(data-i18n="play.hours_played") hours played + .campaign-label(style="color: #{campaign.color}")= campaign.name + if isIPadApp && !level.disabled && !level.locked + button.btn.btn-success.btn-lg.start-level(data-i18n="common.play") Play + if mapType === 'dungeon' && forestIsAvailable + a#forest-link.glyphicon.glyphicon-share-alt.campaign-switch(href="/play/forest", data-i18n="[title]play.campaign_forest") + if mapType === 'forest' + a#dungeon-link.glyphicon.glyphicon-share-alt.campaign-switch(href="/play/dungeon", data-i18n="[title]play.campaign_dungeon") + if desertIsAvailable + a#desert-link.glyphicon.glyphicon-share-alt.campaign-switch(href="/play/desert", data-i18n="[title]play.campaign_desert") + if mapType === 'desert' + a#forest-back-link.glyphicon.glyphicon-share-alt.campaign-switch(href="/play/forest", data-i18n="[title]play.campaign_forest") + + +.game-controls.header-font + button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items") + button.btn.heroes(data-toggle='coco-modal', data-target='play/modal/PlayHeroesModal', data-i18n="[title]play.heroes") + button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements") + if me.get('anonymous') === false || me.get('iosIdentifierForVendor') || isIPadApp + button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems") + if me.isAdmin() + button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account") + button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings") + else if me.get('anonymous', true) + button.btn.settings(data-toggle='coco-modal', data-target='core/AuthModal', data-i18n="[title]play.settings") + // Don't show these things, they are bad and take us out of the game. Just wait until the new ones work. + //else + // a.btn.achievements(href="/user/#{me.getSlugOrID()}/stats", data-i18n="[title]play.achievements") + // a.btn.account(href="/user/#{me.getSlugOrID()}", data-i18n="[title]play.account") + // a.btn.settings(href='/account', data-i18n="[title]play.settings") + +.user-status.header-font + .user-status-line + span.gem.gem-30 + span#gems-count.spr= me.gems() + span.player-level-icon + span.player-level.spr= me.level() + span.player-hero-icon + if me.get('anonymous') + span.player-name.spr(data-i18n="play.anonymous") Anonymous Player + button.btn.btn-illustrated.login-button.btn-warning(data-i18n="login.log_in") + button.btn.btn-illustrated.signup-button.btn-danger(data-i18n="signup.sign_up") + else + span.player-name.spr= me.get('name') + button#logout-button.btn.btn-illustrated.btn-warning(data-i18n="login.log_out") Log Out + if me.isPremium() + button.btn.btn-illustrated.btn-primary(data-i18n="nav.contact", data-toggle="coco-modal", data-target="core/ContactModal") Contact + + +button.btn.btn-lg.btn-inverse#volume-button(title="Adjust volume") + .glyphicon.glyphicon-volume-off + .glyphicon.glyphicon-volume-down + .glyphicon.glyphicon-volume-up + +//h1#campaign-status +// if mapType == 'dungeon' +// span.spr(data-i18n="play.campaign_dungeon") +// else if mapType == 'forest' +// span.spr(data-i18n="play.campaign_forest") +// | - +// if requiresSubscription +// span.spl(data-i18n="play.subscription_required") +// else if mapType == 'dungeon' +// span.spl(data-i18n="play.free") +// else +// span.spl(data-i18n="play.subscribed") \ No newline at end of file diff --git a/app/views/play/CampaignView.coffee b/app/views/play/CampaignView.coffee index fc3fdb910..24a633d25 100644 --- a/app/views/play/CampaignView.coffee +++ b/app/views/play/CampaignView.coffee @@ -1,5 +1,5 @@ RootView = require 'views/core/RootView' -template = require 'templates/play/world-map-view' +template = require 'templates/play/campaign-view' LevelSession = require 'models/LevelSession' EarnedAchievement = require 'models/EarnedAchievement' CocoCollection = require 'collections/CocoCollection' @@ -23,7 +23,7 @@ class LevelSessionsCollection extends CocoCollection @url = "/db/user/#{me.id}/level.sessions?project=state.complete,levelID" module.exports = class WorldMapView extends RootView - id: 'world-map-view' + id: 'campaign-view' template: template subscriptions: @@ -103,6 +103,7 @@ module.exports = class WorldMapView extends RootView super() getLevelPlayCounts: -> + return # TODO: Either use the campaign object instead of hardcoded data or get the data some other way return unless me.isAdmin() success = (levelPlayCounts) => return if @destroyed