2015-10-04 03:38:34 +08:00
- - -
- - -
2015-10-04 15:03:41 +08:00
animationContainerSize = [ 0 , 0 ]
2015-10-05 04:06:53 +08:00
moveCursor = (start, end) ->
try
selection = document . getSelection ? ( )
return unless selection
child = selection . anchorNode ? . childNodes ? [ 0 ]
if child
node = child
else
node = selection . anchorNode
return unless node
if selection . setBaseAndExtent
selection . setBaseAndExtent ( node , start , node , end )
else if Range
range = new Range
start = Math . min ( node . length ? 1000 , start )
end = Math . min ( node . lengty ? 1000 , end )
range . setStart ( node , start )
range . setEnd ( node , end )
selection . removeAllRanges ? ( )
selection . addRange ? ( range )
else return
catch e
console . error e
return
2015-10-04 15:45:59 +08:00
typeMe = (str, parent, {top, left}) -> new Promise (resolve, reject) ->
el = $ ( " <div contenteditable=true id= ' editable ' /> " )
parent . append ( el )
el . css { top , left }
el . focus ( )
sequence = Promise . resolve ( )
accumulator = " "
setTimeout ->
_ . each str . split ( ' ' ) , (char, i) ->
delay = Math . random ( ) * 120 + 10
sequence = sequence . then -> new Promise (resolve, reject) ->
accumulator += char
el . html ( accumulator )
selection = document . getSelection ( )
2015-10-05 04:06:53 +08:00
moveCursor ( accumulator . length , accumulator . length )
2015-10-04 15:45:59 +08:00
setTimeout ( resolve , delay )
sequence . then ->
resolve ( )
, 1500
2015-10-05 03:30:42 +08:00
addFramesToAnimationContainer = (frames, {wrapId}) ->
2015-10-04 15:03:41 +08:00
i = 0
2015-10-05 03:30:42 +08:00
frameImgs = _ . map frames , ({delay, callback}, frame) ->
2015-10-04 15:03:41 +08:00
i ++
" <img id= ' #{ frame } ' src= ' images/ #{ frame } .png ' style= ' z-index: #{ i } ' /> "
2015-10-05 03:30:42 +08:00
frameImgs = frameImgs . join ( ' ' )
2015-10-05 16:41:22 +08:00
$ ( " # animation-container " ) . append ( " <div id= ' #{ wrapId } ' > #{ frameImgs } </div> " )
2015-10-05 03:30:42 +08:00
return
2015-10-04 15:03:41 +08:00
2015-10-05 03:30:42 +08:00
runFrames = (frames) ->
2015-10-04 15:03:41 +08:00
sequence = Promise . resolve ( )
2015-10-05 03:30:42 +08:00
_ . each frames , ({delay, callback}, frame) ->
2015-10-04 15:03:41 +08:00
sequence = sequence . then -> new Promise (resolve, reject) ->
$ ( " # #{ frame } " ) . show ( )
2015-10-05 03:30:42 +08:00
if callback then callback ( delay , resolve )
else setTimeout ( resolve , delay )
return sequence
2015-10-05 05:57:20 +08:00
window . screencastSequence = ->
2015-10-04 15:03:41 +08:00
2015-10-05 03:30:42 +08:00
# Need to know the dimensions of the images used in step 1
2015-10-05 16:41:22 +08:00
screenHeight = 823
bufferHeight = 100
animationContainerSize = [ 1136 , screenHeight + bufferHeight ]
2015-10-05 03:30:42 +08:00
positionAnimationContainer ( )
typeInReply = (delay, resolve) ->
coords =
top: 449
left: 608
2015-10-05 04:06:53 +08:00
typeMe ( " Wow! Iceland looks awesome. " , $ ( " # step1 " ) , coords )
2015-10-05 03:30:42 +08:00
. then ->
setTimeout ->
2015-10-05 04:06:53 +08:00
moveCursor ( 19 , 26 )
2015-10-05 03:30:42 +08:00
$ ( " # 1-4-hovering-toolbar " ) . addClass ( " pop-in " )
resolve ( )
, delay
markBold = (delay, resolve) ->
setTimeout ->
2015-10-05 04:06:53 +08:00
$ ( " # editable " ) . html ( " Wow! Iceland looks <strong>awesome</strong>. " )
len = $ ( " # editable " ) . html ( ) . length
moveCursor ( len , len )
2015-10-05 03:30:42 +08:00
$ ( " # 1-4-hovering-toolbar " ) . removeClass ( " pop-in " ) . addClass ( " pop-out " )
setTimeout ( resolve , 2 * delay )
, delay
adjustTypedText = (delay, resolve) ->
2015-10-05 08:11:39 +08:00
try
if Audio
a = new Audio
a.src = " images/send.ogg "
a.autoplay = true
$ ( " # step1 " ) . append ( a )
a . play ? ( )
catch
console . log " Audio not supported "
2015-10-05 03:30:42 +08:00
$ ( " # editable " ) . removeAttr ( " contenteditable " )
2015-10-05 08:11:39 +08:00
$ ( " # editable " ) . css top: 568 , left: 607
2015-10-05 03:30:42 +08:00
setTimeout ( resolve , delay )
showMultiSelectToolbar = (delay, resolve) ->
$toolbarWrap = $ ( " <div id= ' toolbar-wrap ' ><img id= ' toolbar ' class= ' slide-in-from-top ' src= ' images/2-topbar.png ' style= ' display:block; position: relative ' /></div> " )
$ ( " # step2 " ) . append ( $toolbarWrap )
$toolbarWrap . css
" display " : " block "
" position " : " absolute "
" overflow " : " hidden "
" z-index " : " 7 "
" left " : " 266px "
" top " : " 32px "
setTimeout ( resolve , delay )
postArchiveUpdate = (delay, resolve) ->
$ ( " # toolbar " ) . removeClass ( " slide-in-from-top " ) . addClass ( " slide-out-to-top " )
$ ( " # 2-8-hover-archive " ) . hide ( )
$ ( " # 2-9-depress-archive " ) . hide ( )
$ ( " # 2-7-select-row-4 " ) . hide ( )
$ ( " # 2-4-select-row-2 " ) . hide ( )
setTimeout ( resolve , delay )
frames =
step1:
" 1-1-initial-outlook-base " : { delay: 3000 }
" 1-2-depress-reply " : { delay: 250 }
" 1-3-show-reply " : { delay: 500 , callback: typeInReply }
" 1-4-hovering-toolbar " : { delay: 1000 , callback: markBold }
" 1-5-depress-send " : { delay: 300 }
" 1-6-sent-message " : { delay: 2000 , callback: adjustTypedText }
step2:
" 2-1-initial-gmail-base " : { delay: 2000 }
" 2-2-select-row-1 " : { delay: 400 , callback: showMultiSelectToolbar }
" 2-3-cursor-to-row-2 " : { delay: 400 }
" 2-4-select-row-2 " : { delay: 400 }
" 2-5-cursor-to-row-3 " : { delay: 250 }
" 2-6-cursor-to-row-4 " : { delay: 400 }
" 2-7-select-row-4 " : { delay: 800 }
" 2-8-hover-archive " : { delay: 1000 }
" 2-9-depress-archive " : { delay: 250 }
" 2-10-updated-threadlist " : { delay: 2000 , callback: postArchiveUpdate }
addFramesToAnimationContainer ( frames . step1 , wrapId: " step1 " )
addFramesToAnimationContainer ( frames . step2 , wrapId: " step2 " )
$ ( " # #{ _ . keys ( frames . step1 ) [ 0 ] } " ) . show ( )
2015-10-05 16:26:41 +08:00
$ ( " # step1 " ) . append ( " <h4>N1 is a brand new email client built by Nylas.</h4> " )
2015-10-05 05:57:20 +08:00
return runFrames ( frames . step1 ) . then -> new Promise (resolve, reject) ->
2015-10-05 16:26:41 +08:00
$ ( " # step2 " ) . append ( " <h4>It feels familiar, even for Gmail users.</h4> " )
2015-10-05 03:30:42 +08:00
$ ( " # step1 " ) . addClass ( " slide-out " )
$ ( " # step2 " ) . addClass ( " slide-in " )
$ ( " # #{ _ . keys ( frames . step2 ) [ 0 ] } " ) . show ( )
2015-10-05 05:57:20 +08:00
$ ( " # step1 " ) . on " animationend " , ->
$ ( " # step1 " ) . off " animationend "
2015-10-05 03:30:42 +08:00
$ ( " # step1 " ) . remove ( )
runFrames ( frames . step2 ) . then ->
2015-10-05 05:57:20 +08:00
$ ( " # step2 " ) . removeClass ( " slide-in " ) . addClass ( " slide-out " )
$ ( " # step2 " ) . on " animationend " , ->
$ ( " # step2 " ) . remove ( )
return resolve ( )
window . providerSequence = ->
2015-10-05 13:07:49 +08:00
new Promise (resolve, reject) ->
providers = [
" outlook "
" exchange "
" gmail "
" icloud "
" yahoo "
]
imgs = providers . map (provider, i) ->
" <img id= ' #{ provider } ' class= ' provider-img p- #{ i } ' src= ' images/providers/ #{ provider } @2x.png ' /> "
. join ( ' ' )
os = " <img id= ' os-image ' src= ' images/platforms.png ' > "
2015-10-05 16:26:41 +08:00
header = " <h4>N1 is universal and cross-platform.</h4> "
2015-10-05 13:07:49 +08:00
$ ( " # animation-container " ) . html ( " <div id= ' provider-wrap ' > #{ header } #{ imgs } <br/> #{ os } </div> " )
setTimeout ->
$ ( " # provider-wrap " ) . addClass ( " slide-out " )
$ ( " # provider-wrap " ) . on " animationend " , ->
$ ( " # provider-wrap " ) . remove ( )
resolve ( )
, 4000
window . pluginsSequence = ->
new Promise (resolve, reject) ->
2015-10-05 16:26:41 +08:00
$ ( " # animation-container " ) . html ( ' <div id= " window-container " class= " window " ><div class= " screenshot " ></div></div><h4 id= " plugins-title " >N1 also supports rich plugins to add new features.</h4> ' )
2015-10-05 15:38:19 +08:00
runPluginsSequence ( resolve )
2015-10-05 03:30:42 +08:00
positionAnimationContainer = ->
2015-10-04 15:03:41 +08:00
winW = $ ( window ) . width ( )
winH = $ ( window ) . height ( ) - $ ( " # nav " ) . height ( )
[ w , h ] = animationContainerSize
2015-10-05 09:12:53 +08:00
leftoverH = Math . max ( winH - h - 80 , 0 )
2015-10-04 15:03:41 +08:00
scaleW = 1 - ( Math . min ( winW - w , 0 ) / - w )
scaleH = 1 - ( Math . min ( winH - h , 0 ) / - h )
scale = Math . min ( scaleW , scaleH )
$ ( " # animation-container " ) . css
" width " : " #{ w } px "
" height " : " #{ h } px "
" margin-left " : " - #{ w / 2 } px "
2015-10-05 09:12:53 +08:00
" margin-top " : " #{ leftoverH / 2 } px "
2015-10-04 15:03:41 +08:00
" -webkit-transform " : " scale( #{ scale } ) "
" -moz-transform " : " scale( #{ scale } ) "
" -ms-transform " : " scale( #{ scale } ) "
" -o-transform " : " scale( #{ scale } ) "
" transform " : " scale( #{ scale } ) "
2015-10-04 05:48:16 +08:00
2015-10-04 08:11:29 +08:00
# To allow for a fixed amount of bleed below the fold regardless of window
# size.
fixHeroHeight = ->
2015-10-04 05:48:16 +08:00
Math . max ( Math . min ( $ ( " # hero " ) ? . height ( $ ( window ) . height ( ) + 200 ) , 640 ) , 1200 )
2015-10-04 08:11:29 +08:00
# To ensure that our overflowing, dynamically sized screenshot pushes the
# remaining content down the correct ammount.
fixHeroMargin = ->
2015-10-04 05:48:16 +08:00
marginBottom = Math . max ( ( $ ( " # main-screenshot " ) . height ( ) + ( $ ( " # main-screenshot " ) . offset ( ) . top - $ ( " # hero " ) . offset ( ) . top ) ) - $ ( " # hero " ) . height ( ) , 0 )
$ ( " # hero " ) . css ( marginBottom: marginBottom )
2015-10-04 08:11:29 +08:00
# To ensure there's enough white-space between the watercolor images to
# let the hero text show through.
2015-10-04 05:48:16 +08:00
fixWatercolors = ->
2015-10-05 12:40:15 +08:00
leftSolid = 708 / 800
leftTrans = 196 / 800
rightSolid = 295 / 800
rightTrans = 433 / 800
hh = $ ( " # hero " ) . height ( )
hw = $ ( " # hero " ) . width ( )
leftSolidWidth = hh * leftSolid
leftTransWidth = hh * leftTrans
rightSolidWidth = hh * rightSolid
rightTransWidth = hh * rightTrans
$ ( " # left-solid " ) . height ( hh ) . width ( leftSolidWidth )
$ ( " # left-trans " ) . height ( hh ) . width ( leftTransWidth )
$ ( " # right-solid " ) . height ( hh ) . width ( rightSolidWidth )
$ ( " # right-trans " ) . height ( hh ) . width ( rightTransWidth )
2015-10-04 05:48:16 +08:00
2015-10-05 12:40:15 +08:00
heroLeft = $ ( " # hero-text " ) . offset ( ) . left
2015-10-04 05:48:16 +08:00
heroRight = $ ( " # hero-text " ) . offset ( ) . left + $ ( " # hero-text " ) . width ( )
2015-10-05 12:40:15 +08:00
lw = ( leftSolidWidth + leftTransWidth )
rw = ( rightSolidWidth + rightTransWidth )
overlapLeft = 50
overlapRight = 160
shiftLeft = Math . min ( heroLeft - lw + overlapLeft , 0 )
shiftRight = Math . min ( ( hw - heroRight ) - rw + overlapRight , 0 )
$ ( " # watercolor-left " ) . css ( left: shiftLeft )
$ ( " # watercolor-right " ) . css ( right: shiftRight )
2015-10-04 05:48:16 +08:00
2015-10-05 10:41:18 +08:00
fixStaticClientImages = ->
overhang = 70
padding = 40
nominalScreenshot = 1280
nominalComposer = 615
innerWidth = $ ( " # static-client-images " ) . innerWidth ( ) - padding - overhang
scale = Math . min ( 1 - ( nominalScreenshot - innerWidth ) / nominalScreenshot , 1 )
$ ( " # static-screenshot, # static-screenshot-wrap " ) . width ( nominalScreenshot * scale )
$ ( " # static-composer " ) . width ( nominalComposer * scale )
2015-10-04 05:48:16 +08:00
onResize = ->
2015-10-04 08:11:29 +08:00
fixHeroHeight ( )
2015-10-04 15:03:41 +08:00
# fixHeroMargin()
2015-10-04 05:48:16 +08:00
fixWatercolors ( )
2015-10-05 03:30:42 +08:00
positionAnimationContainer ( )
2015-10-05 10:41:18 +08:00
fixStaticClientImages ( )
2015-10-04 05:48:16 +08:00
window . onresize = onResize
2015-10-05 14:01:47 +08:00
$ ->
2015-10-04 05:48:16 +08:00
onResize ( )
2015-10-04 15:03:41 +08:00
$ ( " body " ) . addClass ( " initial " )
2015-10-05 15:38:19 +08:00
2015-10-04 15:03:41 +08:00
$ ( " # play-intro " ) . on " click " , ->
2015-10-05 15:38:19 +08:00
$ ( " body " ) . removeClass ( " finished " )
$ ( " # window-container " ) . remove ( )
$ ( " # plugins-title " ) . remove ( )
$ ( " # window-container-after-spacer " ) . removeClass ( " free-falling " )
2015-10-05 10:41:18 +08:00
$ ( " # static-client-images " ) . hide ( )
2015-10-05 05:57:20 +08:00
$ ( " body " ) . addClass ( " start-animation " ) . removeClass ( " initial " )
screencastSequence ( )
. then ( providerSequence )
2015-10-05 13:07:49 +08:00
. then ( pluginsSequence )
2015-10-05 15:38:19 +08:00
. then =>
$ ( " body " ) . addClass ( " finished " )
a = $ ( ' # window-container ' )
a . children ( ' .part ' ) . remove ( )
a . css ( {
width: a [ 0 ] . getBoundingClientRect ( ) . width ,
height: a [ 0 ] . getBoundingClientRect ( ) . height
} )
a . addClass ( " free-falling " )
a . append ( $ ( ' <img id= " static-composer " src= " images/composer-no-shadow.png " class= " composer " > ' ) ) ;
setTimeout =>
a . addClass ( " finished " ) ;
a . css ( {
width: " " ,
height: " "
} )
, 1
$ ( " # hero " ) . parent ( ) . append ( a )
$ ( " # window-container-after-spacer " ) . addClass ( " free-falling " )
$ ( ' # play-intro ' ) . html ( ' <div class= " triangle " ></div>Replay Intro</div> ' )
2015-10-05 05:57:20 +08:00
2015-10-05 13:46:26 +08:00
$ ( " # hamburger " ) . on " click " , ->
$ ( " # nav " ) . toggleClass ( " open " )
2015-10-05 16:41:22 +08:00
console . log ( " %cWe love your curiosity! Let us know what other easter eggs you find. 😊 We ' re always looking for extraordinary people. Check out the jobs page or give me a ping at evan@nylas.com if you ' re interested in learning more about some of the big challenges we ' re tackling " , " font-size: 16px;font-family:FaktPro, sans-serif;line-height:1.7 " )