 So Alex, we're gonna be talking about building mobile apps with Ember, apologies if I yawn on stage, I think everybody's kind of having trouble staying awake at the moment, so it's not you. So who am I, a programmer, I've been on Ember for about three years now, before that, like everybody I started with Java and C, spent a couple of years writing Go as well, which I still really enjoy doing. And my second ever Ember app was a Cordova based application, which is what kind of started this fascination. I promise this will be the only plug, I work for a company called Isle of Code, we basically specialize in building Ember Cordova hybrid application, so that's what we do, we're mostly based in Toronto, we have an office in Chicago now as well, we also do some of the more generic Ember work, always hiring, always looking for new people to work with. So the reason I wanted to do this talk today is I feel like a lot of people have gone and built Ember Cordova applications before, or you've built something in Cordova, and it just didn't work right, it just felt that little bit out of reach, you couldn't do what it is that you needed to do. Got to put gifts in. So what we're going to talk about today, there's a little bit of a preamble just in terms of what we're doing, and we're going to talk about how to actually build the Ember and Cordova application together, what tools we used, what add-ons are out there, a little bit about the basics as well, you know how do we handle splash screens and icons within Ember and the basic changes we need to make to our Ember app, and we're just going to close off with performance and how do we make these things at least feel as close to native as possible. So why do we want to do this? Obviously because Ember, Ember is fun, it's nice to write, but one of the reasons I always talk about with clients, that it's like to float as well, is it's much easier to go and find a single Ember developer than it is to go and find developers for every platform. And if you're technical, maybe that's not so much of a problem, but at least for us, a lot of people we work with aren't technical and recruiting is a very big problem for them as a result. So we want to make that as simple as we can. So just a quick show of hands, just most people here know the difference between Cordova and PhoneGap. Okay, not many. So for those who don't, it would be fair to say that PhoneGap implements Cordova. Cordova is that core engine that wraps a web view and creates a native application. It also has the CLN, the basic plugin ecosystem. PhoneGap is Adobe's basically proprietary service on top that gives you build services, some extra add-ons, some extra plugins and stuff like that. Historically it was always called PhoneGap, but basically when the Apache Foundation stuff started, you know some trademark things happen, they needed to change, I'm not going to touch that discussion. Every time I do it, the trolls come out on Twitter. So last point in the preamble is that, building a good mobile app is basically just writing good code to start with. There's no real magic bullet because we're dealing in mobile, especially when we're dealing in Cordova, which is basically just a web browser anyway. I think a lot of people just get used to being lazy because the desktop does let you away with just some worse practices. I used to always make the point as well that a desktop was far more powerful than a mobile phone, but I think we've all seen the benchmarks now where mobile Safari is actually outperforming desktop Chrome in a lot of JavaScript benchmarks. That's not necessarily the case anymore either. So getting into the real content, how do we build these apps? So in the old days, this is how I started. We'd build just a cludgy make file that would go and run Ember build, copy things over and run Cordova build for us. Then later came this library called Ember CLI Cordova, if you've heard of it before. What it did was it automated what that make file did, let you run your Ember CDV build and it would go and build both applications for you. It also included library load, which you could do in Cordova, but it made it easy to do that while you're running an Ember server. So a couple of weeks ago, we decided to fork that repo. We'd been helping out maintaining it and writing new features in it for the last six months or so. We decided to create a fork and the reason for that was twofold. One is we've been doing some broader platforms work and we needed just to destroy backwards compatibility and you can't really do that with an established repo. Then as we started hacking more and more, we just started adding a lot of new features and a lot of the things that we'd wanted to see in CLI Cordova for a long time. So that's why we did the fork and what have we changed? So we refactored all of the internals and tasks so it now follows your canonical Ember add-on structure. We actually inherit from tasks instead of anonymous functions. By default, it doesn't include any assumptions as to how you're writing Ember. So there's no in-build assumption that using mobile touch or hammer time or anything like that, that's on you. The only assumptions we make are how you're going to wrap Cordova. We've also added hook support, which we'll talk about a little bit later. Splash screens and icons are first class citizens now. We've added some Cordova device and platform services, which I'll tell you about. And we're also starting to work on a plugin ecosystem specifically for Cordova. So we're taking those core Cordova plugins like Camera, writing an Ember service that will bind them so that you don't need to worry about writing your custom stuff. And we're also working on something so you can Ember CDV install your Ember add-on and it will install both the Ember add-on and the Cordova plugin for you so you can pair those two things together. If you have an existing Cordova application, you want to try this out. All you need to do is move your Cordova directory into Ember Cordova slash Cordova. Everything else is going to work as you expect. So what are we working on next? Just more plugin bindings. We're fixing the tests. Unfortunately, they were pretty broken before we did the fork, but we're working on cleaning those up as quickly as we can. That said, more and more plugin bindings. So to get started is pretty simple. We run Ember new mobile project, just do an Ember repo, then Ember install Ember Cordova. By virtue of running that install, it is going to install Cordova before you as well and it is going to init your Cordova directory as well. Right now, because it's a much younger repo, we have a very tight assumption as to where you're going to place that Cordova package. So installing this just adds a couple of commands to your Ember CLA. All of the Ember Cordova commands are just Ember Cordova or Ember CDV if you prefer to do shorthand. So obviously we have a build command. What build will do is it will run your Ember build for you. It will then sim link your disk directory to your Cordova's WWE directory and then it's going to run Cordova build. So we'll actually pull out an Ember Cordova application for you. And you can pass whatever flags you normally would there, right, platform, environment, whatever you're looking for. Some of the other ones worth noticing is Cordova prepare. Every time I talk to Cordova developers, it's interesting how people handle Cordova plugins. You know, I've seen people do bash scripts that try and install every plugin that they want. I've gone to projects that have a readme that says if you clone this repo, install these 10 plugins please. If you weren't aware, whenever you add a platform or a plugin to Cordova, you can actually append a save flag, not save dev, but save. And what that will do is back it up in config.xml for you. Then you can run Cordova prepare and it will actually go and reinstall every plugin and every platform for you. It is the canonical way to handle that and that's what that command will do for you. If you're wondering why that's a config.xml, it's honestly just because Cordova predates NPM and there is a plan to migrate to package JSON relatively soon. Last two commands worth noting, Ember Cordova serve will actually boot library load on the phone for you. There are some conflict flags. If you want to know more about it, just go to the readme and Ember Cordova as well. So if you want to run a raw Cordova command, you can just proxy straight through to the repo without needing to type CD Ember Cordova. So if you're truly lazy, you can do that as well. So in terms of the hooks we've made available, we have before and after build, before and after prepare. So at the moment, we're mostly using these for things like warning a client that they're maybe doing a production build and they need to change something or selective index HTML modifications, but those hooks are there for you to use. Lastly, I'm going to talk about in tooling. Did we just lose slides? Here we go. Last thing we're going to talk about in tooling is remote inspection. So if you weren't aware, there's this great little package called Ember CLI Remote Inspector. What it will let you do is as you're running your Cordova application on the phone, you can still run Ember Inspector. So you can still get inside and see what's going on. It's completely worth having a look at. So now we're getting the fun stuff, handling the basics. How do we actually build this application? What does it look like? So the first thing you need to change is your location type must be a hash. That's an assumption building to Ember Cordova. It will actually warn you if that's not the case, so you'll be pretty safe. As because Cordova uses file-based URLs, you need the hash in there. Another thing you'll need to change, especially if you're developing for iOS, if you're developing for mobile web and Safari, you already know this, is you need to make sure that your data Ember actions have a cursor pointer. The reason for this is by default, mobile Safari specifically will only raise touch events instead of click events. So if you have a click-to-touch polyfill, it's actually not going to fire. So if you change this, what happens is Safari will recognize that the cursor pointer is something that it should register on, and all of a sudden, your actions are going to work again. Where this applies is anything that's not an A-link or a button. It's generally just safe to add that to your CSS and keep moving. Last little CSS hack you're going to want to do is this thing up there. I'm not going to read it out. But by default, a browser will highlight a link that you've clicked. By default, a mobile application does not. That's what this piece of CSS handles. It will make sure a link that's been clicked will just retain its original styling. So in terms of handling Splash as well, as I said, Splash is a first-class citizen-hour repo, so it will go and install the Cordova Splash plugin for you. Then there are two ways that you can actually dismiss your Splash screen. I'll get into how you package them in a second as well. By default, we propose that you just import the Splash screen mix-in, and what that will do is, on your application routes after a model hook, it will go and hide the Splash screen. So that's something that you will probably save you a lot of time. And icons are coming next week. This is something that we're hoping to have out by the time I was talking at Ambracount, but we kind of really only decided to have this ready about seven days ago, and we've pulled all nighters basically every day since to try and get it out. So that's coming soon. Basically what we're doing is making sure the icons will be handled by default, and then instead of using your icon at two X names, we're actually going to have iPhone 6 or iPad 2 or whatever it is you're packaging for, just so if you're not used to working in this environment, it's a little bit simpler. That's what this is going to look like. So the Splash screen you can already do today, but just in your standard config file, go and add code over, path to icon, path to Splash screen, and what devices you want to support. The way the Splash screen will work as well is that if you're going up and you're scaling up to a bigger device, it's going to figure out what the colors were just on the left and right most pixel and scale out that way. It's not actually going to scale your image up, so you'll retain your original dimensions. So one of the other things you're going to need to do pretty frequently is understand what platform it is you're dealing with. So this is something that we just completely forked over from Ionic, it's something they did really, really well in our opinion. So we've created a platform service, and what that would do is let you know whether you're running in iOS, whether you're running on an iPad, whether it's Android, crosswalk, running in a web view of browser or something like that. So like anything, you just inject that platform service, then you'll see some function, if this is iOS, we're going to go and do something different. That might not seem very helpful, but you're going to find a lot of cases where you want to slightly change something because it's iOS or Android, and having that recognition makes it very, very easy. By the way, all of these services work with your standard amber, gatters and setters and promises, so it's built to work with amber versus just returning something straight up. So we've also gone and added a Cordova service, and this is for if you need to bind the things that are happening inside of Cordova, right? That somebody pressed the volume up button, that the battery is low, the battery is clerical, anything like that. So what you do, again, is you can inject the Cordova service, there's also a mix-in if you prefer to consume it that way, and then you'll see we just subscribe. So we've subscribed to Cordova volume up, and then we can go and do whatever we want to do. I know that this example shows a route, but again, you can inject this wherever you want. If you prefer to handle that in a component or a controller, knock yourself out. If you're wondering what events are available, that's basically what you'll have. So one of the other things we need to do is figure out how to handle touch events. So generally we'll do something that implements hammer. I'm going to show you a hammer example in a second. If you're native to amber, you can obviously go and use your hammer times or your mobile touches, or depending on what amber version you're dealing with as well. I'm going to show you a raw hammer example, though, just so you can kind of get a feel for what you can change. As you can see, I actually took this screenshot with a transparent background, and I'm throwing some horrible error in the background. But you'll see we just start a new hammer manager, and we're registering a tap, a press, and a hold, or a pan, sorry. And we're registering a tap as a single one, so we can actually get a double tap, a triple tap, whatever it is we're looking for. If we want to press, we're defining that as holding for 800 milliseconds, and a pan. We drop the listeners, and we're going to fire events just as if somebody clicked. So one of the other things you need to be aware of as well is how Device Ready works in this platform. If you're not familiar with what Device Ready is, it's something that fires in Cordova to say, hey, Cordova's finished booting. We're ready, it's now safe to go and run whatever it is we want to run. And a lot of times JavaScript developers are afraid of using Device Ready because they're worried they'll drop the listener after Device Ready's already fired. Device Ready's actually a special case in the sense that even if you drop a listener once the device is ready, it's going to fire immediately for this reason. So where this really applies to you is if you're building an initializer around a plugin. A great example would be push notifications where you need to register your API keys. You can't do that until the device is ready, but your Ember app may be booting at the same time. So make sure you go to every initializer and just wrap it around some type of Device Ready hook. Works just like any other listener. So one of the other things that you're going to want to do all the time is interact with the keyboard. You might want to close the keyboard in certain cases, open it, or just have an awareness of what's going on. Again, we've gone and forked the Ionic Keyboard plugin. It's pretty, pretty good. And you can see we go here, Keyboard on did show, on did hide, and you can also call keyboard.show or keyboard.hide. And while we're talking about keyboards, this is one of the more important points when you're dealing in hybrid. A lot of people assume that image to the left is correct, that when the keyboard is open, our viewport size doesn't change. That's actually not the case. Once the keyboard is popped open, you're going to find that your viewport size has changed relative to how much space the keyboard's using. And this just thrashes a lot of developers because all of a sudden when you reflow, everything on the screen gets 30% smaller or 40% smaller and you're wondering what's going on. So how do we handle this? For Android, it's relatively simple. You just want to add these two settings to your config.xml, and that's going to make sure that instead of the keyboard resizing the viewport, the keyboard's going to overlay the viewport, which is exactly what we want in most cases. For iOS, that's a little bit more complicated, so you probably just want to go and use the keyboard plugin we've written. It's all documented in the README. This was actually worked on by Chris, mostly, or Runspired, and what he did was he went and made sure that when the keyboard is open, the body container will maintain state for you. So you're going to be completely protected from that. So two tools left before we get to performance. Just use LiquidFire. I feel like it's been talked about all day, so I'm not going to go into the example. But what I will tell you is that when you're dealing in a Cordova environment, LiquidFire runs completely fine. There's no choppiness. It's as smooth as you'd want it to be. And again, Yofuta kind of spoke about this this morning, so I'm not going to go too deep into it, but infinite scroll, use smoke and mirrors, it works completely fine on the phone again. In fact, you really do want to be using something like that for a phone. So getting into the fun bit now, which is performance, right? How do we make sure that these apps actually are efficient? How do we make sure that they're maybe comparing to the performance of a native application? So the first thing you probably want to look at is this library called Crosswalk. If you're not familiar with what Crosswalk is, it's actually built by Intel. Initially, they were trying to do this as kind of a Cordova competitor, but now it's something people package much more with Cordova instead. It's explicitly for Android. The downside is it will add about 20 megabytes to your size or your application size. I don't really think that matters, to be honest, but if you're dealing with crappy 3G connections when people are downloading, that might be a bit of a problem. But it gives you two gifts. The first gift it gives you is it actually standardizes the in-app browser on Android. If you want to wear different versions of Android, come package with completely different versions of the in-app browser. But unlike, or not the in-app browser, the web view, but unlike iOS, we're actually allowed to change that. It's not against Android's terms of service to put something better into our application. And especially on Android 4.4, you're going to find that it's actually a terrible web view. It's really slow for JavaScript. You're going to find about a five to 10x performance speed or performance boost just by implementing Crosswalk on those devices. The other thing it gives you is you're going to find because of the difference in web views, your CSS rule is going to pass slightly differently based on what Android platform you're dealing with. Once you install Crosswalk, you're dealing with a consistent platform. Again, you don't need to start changing your CSS based on the platform. Another one I always bring up is just know which web view you're dealing in. Especially in iOS, you have two, your UI web view and your WK web view. If you're not familiar, the WK web view is the newer one and it actually implements the newer Nitro engine which is far more performance. So I found that just by implementing a WK web view if I'm allowed to, has led to about a 20% performance boost in just any application that I'm shipping with Amber. So make sure you upgrade that. I always forget which version of iOS it is, but basically if you're dealing with anything older than a five, you can go and implement a WK web view. I've actually had a lot of cases where clients of us just come in and do a performance audit for them and we fix every problem just by going and upgrading the web view. Another thing that really will hit you on mobile more than other platforms is that using too much memory has really negative implications. So if you're not aware, you generally want to make sure that you're, especially your heap size isn't getting bigger than about 25 to 50 megs. You'll hear a lot of cut over devs say that, you know, when you get to 25 megs, you're starting to hit your danger zone and once you pass 50 megs, you're just going to fall off a cliff and things are going to break. Again, very much especially on Android. Android is the worst platform for this and it's the one you need to accommodate the most. If you're not familiar, the reason for this is that garbage collection generally requires about six times the amount of memory you're using to actually be effective. So it's not that we're using 50 megs, we're going somewhere between 300 and 350 and that's not something we really have available on the phone. If you're interested in reading more on that or you want to see a source, that is exactly where you'd go. So just to talk a little bit more about memory leaks as well, again, you're probably leaking on desktop if you're finding that it's leaking on the phone, it's just going to hurt you so much more when you're leaking on the phone. So in V8, which is your Android's web view, you're going to find that there are two types of garbage collection going on, your young generation and your old generation. Most things stay in the young generation, right? They get collected, then they go away. It's something I always forget, but roughly 90% of things will never make it to the old generation. The old generation collection is very expensive. So what you're going to find is some collections are going to take less than 10 milliseconds, then all of a sudden your app's going to freeze and it's going to take a really long time, you don't know what's going on. Probably an old generation collection and what you need to understand is that every time you're declaring a variable you're basically starting to fill up that queue so you should be conscious about not creating variables you don't need. So this is an arbitrary leak example and then I'm going to show you one that actually happens in Ember. This won't happen in Ember because obviously your component's going to get destroyed when it goes out, but if you're writing a raw JavaScript, if you do a var node and we presumably have a div called node on the page, then we create a second reference to it on line two, and then we create our body and we remove that node from the HTML. So we assume right now that we've just completely cleared up our memory, right? But we haven't because we're maintaining references to that node, so even the DOM fragments aren't going to be cleaned up until we remove all of the references to that node. So on line five we do node equals null, we're still not going to be garbage collected because we still maintain one reference to that node. Once we do node again equals null we've all of a sudden cleared up. So to show you this example, excuse the crappy code in the follow-up, I basically copied a leak example from Google that completely bloated up a div with memory, and the reason for this was so I could get a heap size of about 50, 60 megs just to kind of really see the difference. But what you're going to see is that up the top, you know we have VARP equals this P, this is not how I'd normally code by the way, this was just a really fast example on a plane, then we're going and creating an element called detached, and then we're setting those on the component, right? This dot set P, this dot set detached. What you're going to find is even though you've removed that node from the DOM, so again you'd hope it's being cleaned up, once you transition away from that screen, that is still going to be maintained and your heap size is going to be as big as it was before, you know that's not going to be collected and you're just going to start leaking and leaking and leaking. So make sure especially be paranoid about on your destroy component hooks or whatever that you're going through and nulling references to things, especially where this hurts people is if you go and put a reference to say a submit button or some fragment that you're going to keep interacting with in the component, make sure you're nulling that on the way out or you might have some troubles. So how do we figure this out? If you're not familiar, I tend to still use the Chrome profiling tools, mostly because again Chrome is the least efficient platform for this, we kind of want to profile the one that needs the most help. So just pop open your normal inspector and press profiles and take a heap snapshot. What that's going to do is go and kind of collect what's been going on and give you an output. I'm going to show you what that looks like on the next page or sometimes what I do is if I'm actively developing and testing the page, I'll go to the record heap allocation down the bottom. That's just going to give you a real time snapshot of what's been going on and generally as I'm developing in two screens, I just keep my arm on the left screen to see if something's spiking in a way I don't expect. And that's more preventative, right? I'm not really looking for anything that's wrong, but I'm trying to figure it out before it happens. So once we've got our heap profile, I generally switch to the containment view. It doesn't really matter which one you're using. It just kind of removes a lot of stuff you maybe don't want to be looking at. Then you'll see I went into window, clicked down to detach and we have all of these nodes that have just not been garbage collected. At this point, they're completely often nodes. They're not going to be collected. It's just going to sit in our apps memory the whole time. So go and run the heap profiler as much as you can. I didn't even need to necessarily know how this works yet. You really should read into it. But if you're seeing that red and you're seeing a lot of detached nodes, it means you probably have a serious problem. Another example of this is probably biting you all the time if you weren't aware. Again, this comes from our blog, is if you'd go and register custom jQuery listeners. We have jQuery on the element swipe and then we're binding some type of swipe handler. If you don't actually unbind that listener on your will destroy element or on some type of cleanup, again, that listener is just gonna be hanging around forever. It's obviously not going to fire, but that piece of memory you took isn't going to be cleaned up until somebody completely force closes and reopens the app. So you've got to be really careful about stuff like this. One easy way to find out is to go and name your closures for a better profiling. So what I mean by that, again, I put sources on the bottom of the slide. So if you're interested in reading more, you can just click through. You'll see online for, you know, varlc equals function, that's a completely anonymous function. On line 11, we've called it function lc. What that means is in our Chrome profiles, it's actually going to show up as lc. So we can start to see the impact. And I wouldn't really encourage that you go and start naming everything if that's not your normal dev flow. Much as if you're worried that something's maybe not performant or maybe not as efficient, this is a really easy way to go and identify that. Another really fast point here, you know, app size does matter a lot in terms of how big is this, you know, compiled Ember application that you're shipping. You know, for Android, anything, you know, from 500 to 750 kilobytes is generally safe. Once you're getting over that, you're just going to find degrading performance in V8, especially on the phone. For iOS, that's less of a problem. You know, two megabytes is being completely fine for me prior. And again, if you kind of want to read more into how that works, just go down the bottom there. Another little tool we're going to use all the time. And again, you're probably realizing that a lot of this is just your standard web-based stuff. It's almost like we're using a tool that just wraps a website in a native application for us. So window performance is something that you should be using if you don't know. What it does is it lets you register timestamps in your app to kind of see how long things are taking. I've seen people do a pattern before they're just getting timestamps or date stamps and then minusing them from each other. That's actually not an accurate way to measure time because if your thread's busy or something else is going on, you're not always going to get an accurate to the second representation. Window performance is built to give you that. You can also just run window performance mark. If you want to mark certain events and see what was happening at this time, right? Use a press submit, use a press bulk upload, whatever it is we're trying to do. Another performance thing that's so important on mobile that maybe doesn't hit you on desktop, you've got to make sure you're coalescing your requests. Obviously, if you're using Ember data, please make sure your coalesce final request is turned on. That's something that will just thrash the phone otherwise. And you're probably going to find pretty quickly that you're going to want to move to WebSockets because it is a little bit cleaner, especially on a phone anyway. You're going to start needing to push content down. I've had very little apps that we built where a client hasn't eventually come back with a spec of I want you to push something back down to the phone all the time. So start thinking about that ahead of time and maybe just use WebSockets earlier. So I'm starting to speed up so we can get to some of the last points. Workers and threads, again, there was a worker presentation earlier this morning, so I'm not going to kind of cover too much content here, but within a Cordova environment where workers will really help you, they're obviously completely isolated, right? They can't access our Cordova plugins, they can't access our window objects, but I use them a lot from bulk passing operations. A good example I had recently was we had a spec we needed to download an entire user's contact book, kind of mangle it and then send it back to a server. Once we'd downloaded from the Cordova contact book, all of that mangling and kind of resubmitting the Ajax requests happened in a web work and that was completely fine. We took the performance from about 45 seconds down to about eight seconds just by spawning that off onto a new thread. So it's completely worth looking at. One of the most important things is reflow. Just again, a quick show of hands. Who here knows what reflow is? Okay, about half. So if you didn't know what reflow is, it's one of the most expensive operations that your phone is going to be throwing, but again, this happens in your desktop Chrome and desktop Safari as well. It's the browser needing to go through and geometrically recompute what's going on, where is it, what do I need to repaint, et cetera. It's something that you really want to be conscious of and avoid as much as possible. And what causes reflow? Anytime you resize the browser window, you're reflowing. Anytime you use our computed styles in JavaScript, anytime you start manually touching the DOM, you change a class, you add new context, you add new data, and anytime we change an element's classes. So the TLDR here is it's not necessarily that you're going to start reflowing, that's basically impossible, but maybe you're going to start looking at cases where you were toggling classes and realize that that's not what you were wanting to do. Maybe you're going to realize that you were toggling a class, but the component gets destroyed three seconds later anyway. So let's just not cause that double whammy. How do we stop reflow as well? Use your CSS transforms. I've sometimes seen developers, they want to drag an element across the screen, so they're doing a position absolute top zero, left zero, and then top 10, left 10, we're reflowing every single time. If you use a translate x, translate y functions, you're going to find that that doesn't reflow at all. So that's why sometimes developers get so excited about that. Another really good case, sometimes once you've learned about reflow, you'll find developers who go ahead and put display non-elements because they don't want to show them, they don't want to remove them because they think it would be expensive, but you're still causing a reflow with display none because you've changed a class. Visibility hidden is another special case where once you trigger it, you're actually not causing a reflow. So if you're in that scenario where you want to take something off the page, but you know the component's going away in the next 30 seconds, couple of minutes anyway, generally speaking, visibility hidden it, you're going to get what you're looking for. You're not going to cause a reflow, but it's going to go off the page. So last point, if you need to do animations, again I've seen some people do jQuery animate, I've seen people handle up before. We generally recommend that people use Velocity.js if you've heard of it. One of the great things I have on this front page is a benchmarking tool where you can run all of the different animations libraries and run the same animation and see how they perform. So on their front page here, what I'm doing is we have 250 circles and we're just going to animate them with jQuery left to right, and isn't that smooth. So what Velocity does is it just imagine because we don't have a lot of time, the internals are much more efficient, so when we run the same animation, way better. So hopefully you've just kind of got a basic overview on how we build these Kudo for Ambly applications and just some basic performance tips. That's it, thank you very much.