├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .watchmanconfig ├── README.md ├── app ├── app.js ├── components │ ├── .gitkeep │ ├── chat-app.js │ ├── chat-message.js │ ├── chat-messages.js │ └── chat-username.js ├── controllers │ └── .gitkeep ├── helpers │ └── .gitkeep ├── index.html ├── models │ └── .gitkeep ├── router.js ├── routes │ ├── .gitkeep │ └── index.js ├── services │ ├── current-user.js │ └── pusher.js ├── styles │ ├── app.css │ └── devices.min.css └── templates │ ├── application.hbs │ ├── components │ ├── .gitkeep │ ├── chat-app.hbs │ ├── chat-message.hbs │ ├── chat-messages.hbs │ └── chat-username.hbs │ └── index.hbs ├── bower.json ├── config └── environment.js ├── ember-cli-build.js ├── package.json ├── public ├── assets │ └── images │ │ ├── ember.png │ │ ├── iPad_white_thumb.png │ │ ├── pusher-logo-p.png │ │ └── pusher-logo.png ├── crossdomain.xml └── robots.txt ├── testem.json ├── tests ├── .jshintrc ├── helpers │ ├── resolver.js │ └── start-app.js ├── index.html ├── integration │ └── components │ │ ├── chat-app-test.js │ │ ├── chat-message-test.js │ │ ├── chat-messages-test.js │ │ └── chat-username-test.js ├── test-helper.js └── unit │ ├── .gitkeep │ ├── routes │ └── index-test.js │ └── services │ ├── current-user-test.js │ └── pusher-test.js └── vendor └── .gitkeep /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | insert_final_newline = false 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.css] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.html] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.{diff,md}] 34 | trim_trailing_whitespace = false 35 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "boss": true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esnext": true, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "0.12" 5 | 6 | sudo: false 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | before_install: 13 | - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH 14 | - "npm config set spin false" 15 | - "npm install -g npm@^2" 16 | 17 | install: 18 | - npm install -g bower 19 | - npm install 20 | - bower install 21 | 22 | script: 23 | - npm test 24 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pusher & EmberJS Chat 2 | 3 | This is an example EmberJS chat application built with Pusher. 4 | 5 | [You can see this app running on Heroku](http://emberjs-realtime-chat.herokuapp.com/). 6 | 7 | ## Prerequisites 8 | 9 | You will need the following things properly installed on your computer. 10 | 11 | * [Git](http://git-scm.com/) 12 | * [Node.js](http://nodejs.org/) (with NPM) 13 | * [Bower](http://bower.io/) 14 | * [Ember CLI](http://www.ember-cli.com/) 15 | 16 | ## Installation 17 | 18 | * `git clone git@github.com:pusher-community/emberjs-realtime-chat.git` this repository 19 | * `cd emberjs-realtime-chat` 20 | * `npm install` 21 | * `bower install` 22 | 23 | ## Running / Development 24 | 25 | * `ember server` 26 | * Visit your app at [http://localhost:4200](http://localhost:4200). 27 | * You'll need the [Sinatra Server](https://github.com/pusher-community/sinatra-realtime-server) running on port `4567` too. 28 | 29 | ## Further Reading / Useful Links 30 | 31 | * [ember.js](http://emberjs.com/) 32 | * [ember-cli](http://www.ember-cli.com/) 33 | * Development Browser Extensions 34 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 35 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 36 | 37 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from 'ember/resolver'; 3 | import loadInitializers from 'ember/load-initializers'; 4 | import config from './config/environment'; 5 | 6 | var App; 7 | 8 | Ember.MODEL_FACTORY_INJECTIONS = true; 9 | 10 | App = Ember.Application.extend({ 11 | modulePrefix: config.modulePrefix, 12 | podModulePrefix: config.podModulePrefix, 13 | Resolver: Resolver 14 | }); 15 | 16 | loadInitializers(App, config.modulePrefix); 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher-community/emberjs-realtime-chat/ff53188a98ae9a977f38a15c277b5830e380b060/app/components/.gitkeep -------------------------------------------------------------------------------- /app/components/chat-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | currentUserService: Ember.inject.service('current-user'), 5 | nameIsSet: Ember.computed('currentUserService.user', function() { 6 | return this.get('currentUserService').hasUser(); 7 | }) 8 | }); 9 | -------------------------------------------------------------------------------- /app/components/chat-message.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | timestamp: Ember.computed('message.time', function() { 5 | return strftime('%H:%M:%S %P', new Date(this.get('message').time)); 6 | }), 7 | text: Ember.computed('message.text', function() { 8 | return he.decode(this.get('message').text); 9 | }), 10 | }); 11 | -------------------------------------------------------------------------------- /app/components/chat-messages.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import $ from 'jquery'; 3 | import ENV from 'pusher-chat/config/environment'; 4 | 5 | export default Ember.Component.extend({ 6 | currentUserService: Ember.inject.service('current-user'), 7 | pusher: Ember.inject.service('pusher'), 8 | messages: ['Hi there!', 'Welcome to your chat app!'].map((message) => { 9 | return { 10 | username: 'pusher', 11 | time: new Date(), 12 | text: message, 13 | }; 14 | }), 15 | messageObserver: Ember.observer('messages.length', function() { 16 | Ember.run.scheduleOnce('afterRender', function() { 17 | $("#message-list").scrollTop($("#message-list").height()); 18 | }); 19 | }), 20 | init() { 21 | this._super(...arguments); 22 | 23 | this.get('pusher').onMessage((data) => { 24 | this.get('messages').pushObject(data); 25 | }); 26 | }, 27 | actions: { 28 | newMessage() { 29 | const text = this.get('newMessage'); 30 | 31 | if (!text) return; 32 | 33 | const username = this.get('currentUserService').get('user'); 34 | const time = new Date(); 35 | 36 | const urlBase = ENV.APP.SERVER_URL; 37 | 38 | $.post(`${urlBase}/messages`, { text, username, time }); 39 | 40 | this.set('newMessage', ''); 41 | } 42 | } 43 | }); 44 | -------------------------------------------------------------------------------- /app/components/chat-username.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | currentUserService: Ember.inject.service('current-user'), 5 | actions: { 6 | userSubmittedName() { 7 | this.get('currentUserService').setUser(this.get('userName')); 8 | }, 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher-community/emberjs-realtime-chat/ff53188a98ae9a977f38a15c277b5830e380b060/app/controllers/.gitkeep -------------------------------------------------------------------------------- /app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher-community/emberjs-realtime-chat/ff53188a98ae9a977f38a15c277b5830e380b060/app/helpers/.gitkeep -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PusherChat 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {{content-for 'head-footer'}} 19 | 20 | 21 | {{content-for 'body'}} 22 | 23 | 24 | 25 | 26 | {{content-for 'body-footer'}} 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher-community/emberjs-realtime-chat/ff53188a98ae9a977f38a15c277b5830e380b060/app/models/.gitkeep -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | var Router = Ember.Router.extend({ 5 | location: config.locationType 6 | }); 7 | 8 | Router.map(function() {}); 9 | 10 | export default Router; 11 | -------------------------------------------------------------------------------- /app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher-community/emberjs-realtime-chat/ff53188a98ae9a977f38a15c277b5830e380b060/app/routes/.gitkeep -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /app/services/current-user.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Service.extend({ 4 | user: null, 5 | 6 | setUser(username) { 7 | this.set('user', username); 8 | }, 9 | 10 | hasUser() { 11 | return this.get('user') != undefined; 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /app/services/pusher.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import ENV from 'pusher-chat/config/environment'; 3 | 4 | export default Ember.Service.extend({ 5 | init() { 6 | this._super(...arguments); 7 | 8 | this.set('pusher', new Pusher(ENV.APP.PUSHER.key, { 9 | encrypted: true, 10 | })); 11 | }, 12 | 13 | onMessage(fn) { 14 | const channel = this.get('pusher').subscribe('messages'); 15 | channel.bind('new_message', fn); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /app/styles/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | height: 150vh; 3 | } 4 | 5 | .marvel-device.iphone6.silver { 6 | margin-top: -30px; 7 | } 8 | 9 | .marvel-device .screen { 10 | text-align: left; 11 | z-index: 3; 12 | color: inherit; 13 | -webkit-box-shadow: inherit; 14 | box-shadow: inherit; 15 | } 16 | 17 | .action-bar { 18 | width: 330px; 19 | } 20 | 21 | .option { 22 | width: 42px; 23 | padding-top: 8px; 24 | } 25 | 26 | span.emoji { 27 | font-family: initial; 28 | } 29 | 30 | #msg-input { 31 | width: 245px; 32 | } 33 | 34 | .chat-app { 35 | position: relative; 36 | height: 100%; 37 | padding-top: 0 !important; 38 | } 39 | 40 | .chat-app .action-bar { 41 | position: absolute; 42 | width: 100%; 43 | bottom: 0; 44 | } 45 | 46 | #try-it-out { 47 | width: 160px; 48 | font-weight: 200; 49 | padding: 10px 16px; 50 | vertical-align: bottom; 51 | } 52 | 53 | #message-list { 54 | height: 420px; 55 | overflow: scroll; 56 | } 57 | 58 | #input-name { 59 | width: 350px; 60 | } 61 | 62 | .chat-app .action-bar button.option.send-message { 63 | border-radius: 0; 64 | padding-top: 0; 65 | } 66 | 67 | input.input-message { 68 | display: inline-block; 69 | width: 87%; 70 | } 71 | 72 | input.input-message:focus, 73 | input.input-message:hover { 74 | outline: none; 75 | box-shadow: none; 76 | } 77 | 78 | .red-gradient-background { 79 | background: #fe845a; 80 | background-image: -webkit-linear-gradient(top, #fe845a, #d14e37); 81 | background-image: linear-gradient(to bottom, #fe845a, #d14e37); 82 | } 83 | -------------------------------------------------------------------------------- /app/styles/devices.min.css: -------------------------------------------------------------------------------- 1 | .marvel-device{display:inline-block;position:relative}.marvel-device .screen{width:100%;position:relative;height:100%;color:white;z-index:2;text-align:center;display:block;-webkit-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 0 0 3px #111;box-shadow:0 0 0 3px #111}.marvel-device .top-bar,.marvel-device .bottom-bar{height:3px;background:black;width:100%;display:block}.marvel-device .middle-bar{width:3px;height:4px;top:0px;left:90px;background:black;position:absolute}.marvel-device.iphone6{width:375px;height:667px;padding:105px 24px;background:#d9dbdc;-webkit-border-radius:56px;border-radius:56px;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2)}.marvel-device.iphone6:before{width:calc(100% - 12px);height:calc(100% - 12px);position:absolute;top:6px;content:'';left:6px;-webkit-border-radius:50px;border-radius:50px;background:#f8f8f8;z-index:1}.marvel-device.iphone6:after{width:calc(100% - 16px);height:calc(100% - 16px);position:absolute;top:8px;content:'';left:8px;-webkit-border-radius:48px;border-radius:48px;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #fff;box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #fff;z-index:2}.marvel-device.iphone6 .home{-webkit-border-radius:100%;border-radius:100%;width:68px;height:68px;position:absolute;left:50%;margin-left:-34px;bottom:22px;z-index:3;background:#303233;background:-moz-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #303233), color-stop(50%, #b5b7b9), color-stop(69%, #f0f2f2), color-stop(100%, #303233));background:-webkit-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:-o-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:-ms-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:linear-gradient(135deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#303233', endColorstr='#303233',GradientType=1 )}.marvel-device.iphone6 .home:before{background:#f8f8f8;position:absolute;content:'';-webkit-border-radius:100%;border-radius:100%;width:calc(100% - 8px);height:calc(100% - 8px);top:4px;left:4px}.marvel-device.iphone6 .top-bar{height:14px;background:#bfbfc0;position:absolute;top:68px;left:0}.marvel-device.iphone6 .bottom-bar{height:14px;background:#bfbfc0;position:absolute;bottom:68px;left:0}.marvel-device.iphone6 .sleep{position:absolute;top:190px;right:-4px;width:4px;height:66px;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px;background:#d9dbdc}.marvel-device.iphone6 .volume{position:absolute;left:-4px;top:188px;z-index:0;height:66px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:#d9dbdc}.marvel-device.iphone6 .volume:before{position:absolute;left:2px;top:-78px;height:40px;width:2px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone6 .volume:after{position:absolute;left:0px;top:82px;height:66px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone6 .camera{background:#3c3d3d;width:12px;height:12px;position:absolute;top:24px;left:50%;margin-left:-6px;-webkit-border-radius:100%;border-radius:100%;z-index:3}.marvel-device.iphone6 .sensor{background:#3c3d3d;width:16px;height:16px;position:absolute;top:49px;left:134px;z-index:3;-webkit-border-radius:100%;border-radius:100%}.marvel-device.iphone6 .speaker{background:#292728;width:70px;height:6px;position:absolute;top:54px;left:50%;margin-left:-35px;-webkit-border-radius:6px;border-radius:6px;z-index:3}.marvel-device.iphone6.gold{background:#f9e7d3}.marvel-device.iphone6.gold .top-bar,.marvel-device.iphone6.gold .bottom-bar{background:white}.marvel-device.iphone6.gold .sleep,.marvel-device.iphone6.gold .volume{background:#f9e7d3}.marvel-device.iphone6.gold .home{background:#cebba9;background:-moz-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #cebba9), color-stop(50%, #f9e7d3), color-stop(100%, #cebba9));background:-webkit-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:-o-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:-ms-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:linear-gradient(135deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#cebba9', endColorstr='#cebba9',GradientType=1 )}.marvel-device.iphone6.black{background:#464646;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.7);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.7)}.marvel-device.iphone6.black:before{background:#080808}.marvel-device.iphone6.black:after{-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #212121;box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #212121}.marvel-device.iphone6.black .top-bar,.marvel-device.iphone6.black .bottom-bar{background:#212121}.marvel-device.iphone6.black .volume,.marvel-device.iphone6.black .sleep{background:#464646}.marvel-device.iphone6.black .camera{background:#080808}.marvel-device.iphone6.black .home{background:#080808;background:-moz-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #080808), color-stop(50%, #464646), color-stop(100%, #080808));background:-webkit-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:-o-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:-ms-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:linear-gradient(135deg, #080808 0%, #464646 50%, #080808 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#080808', endColorstr='#080808',GradientType=1 )}.marvel-device.iphone6.black .home:before{background:#080808}.marvel-device.iphone6.landscape{padding:24px 105px;height:375px;width:667px}.marvel-device.iphone6.landscape .sleep{top:100%;-webkit-border-radius:0px 0px 2px 2px;border-radius:0px 0px 2px 2px;right:190px;height:4px;width:66px}.marvel-device.iphone6.landscape .volume{width:66px;height:4px;top:-4px;left:calc(100% - 188px - 66px);-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone6.landscape .volume:before{width:40px;height:2px;top:2px;right:-78px;left:auto;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone6.landscape .volume:after{left:-82px;width:66px;height:4px;top:0;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone6.landscape .top-bar{width:14px;height:100%;left:calc(100% - 68px - 14px);top:0}.marvel-device.iphone6.landscape .bottom-bar{width:14px;height:100%;left:68px;top:0}.marvel-device.iphone6.landscape .home{top:50%;margin-top:-34px;margin-left:0;left:22px}.marvel-device.iphone6.landscape .sensor{top:134px;left:calc(100% - 49px - 16px)}.marvel-device.iphone6.landscape .speaker{height:70px;width:6px;left:calc(100% - 54px - 6px);top:50%;margin-left:0px;margin-top:-35px}.marvel-device.iphone6.landscape .camera{left:calc(100% - 32px);top:50%;margin-left:0px;margin-top:-5px}.marvel-device.iphone6plus{width:414px;height:736px;padding:112px 26px;background:#d9dbdc;-webkit-border-radius:56px;border-radius:56px;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2)}.marvel-device.iphone6plus:before{width:calc(100% - 12px);height:calc(100% - 12px);position:absolute;top:6px;content:'';left:6px;-webkit-border-radius:50px;border-radius:50px;background:#f8f8f8;z-index:1}.marvel-device.iphone6plus:after{width:calc(100% - 16px);height:calc(100% - 16px);position:absolute;top:8px;content:'';left:8px;-webkit-border-radius:48px;border-radius:48px;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #fff;box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #fff;z-index:2}.marvel-device.iphone6plus .home{-webkit-border-radius:100%;border-radius:100%;width:68px;height:68px;position:absolute;left:50%;margin-left:-34px;bottom:24px;z-index:3;background:#303233;background:-moz-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #303233), color-stop(50%, #b5b7b9), color-stop(69%, #f0f2f2), color-stop(100%, #303233));background:-webkit-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:-o-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:-ms-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:linear-gradient(135deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#303233', endColorstr='#303233',GradientType=1 )}.marvel-device.iphone6plus .home:before{background:#f8f8f8;position:absolute;content:'';-webkit-border-radius:100%;border-radius:100%;width:calc(100% - 8px);height:calc(100% - 8px);top:4px;left:4px}.marvel-device.iphone6plus .top-bar{height:14px;background:#bfbfc0;position:absolute;top:68px;left:0}.marvel-device.iphone6plus .bottom-bar{height:14px;background:#bfbfc0;position:absolute;bottom:68px;left:0}.marvel-device.iphone6plus .sleep{position:absolute;top:190px;right:-4px;width:4px;height:66px;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px;background:#d9dbdc}.marvel-device.iphone6plus .volume{position:absolute;left:-4px;top:188px;z-index:0;height:66px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:#d9dbdc}.marvel-device.iphone6plus .volume:before{position:absolute;left:2px;top:-78px;height:40px;width:2px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone6plus .volume:after{position:absolute;left:0px;top:82px;height:66px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone6plus .camera{background:#3c3d3d;width:12px;height:12px;position:absolute;top:29px;left:50%;margin-left:-6px;-webkit-border-radius:100%;border-radius:100%;z-index:3}.marvel-device.iphone6plus .sensor{background:#3c3d3d;width:16px;height:16px;position:absolute;top:54px;left:154px;z-index:3;-webkit-border-radius:100%;border-radius:100%}.marvel-device.iphone6plus .speaker{background:#292728;width:70px;height:6px;position:absolute;top:59px;left:50%;margin-left:-35px;-webkit-border-radius:6px;border-radius:6px;z-index:3}.marvel-device.iphone6plus.gold{background:#f9e7d3}.marvel-device.iphone6plus.gold .top-bar,.marvel-device.iphone6plus.gold .bottom-bar{background:white}.marvel-device.iphone6plus.gold .sleep,.marvel-device.iphone6plus.gold .volume{background:#f9e7d3}.marvel-device.iphone6plus.gold .home{background:#cebba9;background:-moz-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #cebba9), color-stop(50%, #f9e7d3), color-stop(100%, #cebba9));background:-webkit-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:-o-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:-ms-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:linear-gradient(135deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#cebba9', endColorstr='#cebba9',GradientType=1 )}.marvel-device.iphone6plus.black{background:#464646;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.7);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.7)}.marvel-device.iphone6plus.black:before{background:#080808}.marvel-device.iphone6plus.black:after{-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #212121;box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #212121}.marvel-device.iphone6plus.black .top-bar,.marvel-device.iphone6plus.black .bottom-bar{background:#212121}.marvel-device.iphone6plus.black .volume,.marvel-device.iphone6plus.black .sleep{background:#464646}.marvel-device.iphone6plus.black .camera{background:#080808}.marvel-device.iphone6plus.black .home{background:#080808;background:-moz-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #080808), color-stop(50%, #464646), color-stop(100%, #080808));background:-webkit-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:-o-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:-ms-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:linear-gradient(135deg, #080808 0%, #464646 50%, #080808 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#080808', endColorstr='#080808',GradientType=1 )}.marvel-device.iphone6plus.black .home:before{background:#080808}.marvel-device.iphone6plus.landscape{padding:26px 112px;height:414px;width:736px}.marvel-device.iphone6plus.landscape .sleep{top:100%;-webkit-border-radius:0px 0px 2px 2px;border-radius:0px 0px 2px 2px;right:190px;height:4px;width:66px}.marvel-device.iphone6plus.landscape .volume{width:66px;height:4px;top:-4px;left:calc(100% - 188px - 66px);-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone6plus.landscape .volume:before{width:40px;height:2px;top:2px;right:-78px;left:auto;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone6plus.landscape .volume:after{left:-82px;width:66px;height:4px;top:0;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone6plus.landscape .top-bar{width:14px;height:100%;left:calc(100% - 68px - 14px);top:0}.marvel-device.iphone6plus.landscape .bottom-bar{width:14px;height:100%;left:68px;top:0}.marvel-device.iphone6plus.landscape .home{top:50%;margin-top:-34px;margin-left:0;left:24px}.marvel-device.iphone6plus.landscape .sensor{top:154px;left:calc(100% - 54px - 16px)}.marvel-device.iphone6plus.landscape .speaker{height:70px;width:6px;left:calc(100% - 59px - 6px);top:50%;margin-left:0px;margin-top:-35px}.marvel-device.iphone6plus.landscape .camera{left:calc(100% - 29px);top:50%;margin-left:0px;margin-top:-5px}.marvel-device.iphone5s,.marvel-device.iphone5c{padding:105px 22px;background:#2c2b2c;width:320px;height:568px;-webkit-border-radius:50px;border-radius:50px}.marvel-device.iphone5s:before,.marvel-device.iphone5c:before{width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;top:4px;content:'';left:4px;-webkit-border-radius:46px;border-radius:46px;background:#1e1e1e;z-index:1}.marvel-device.iphone5s .sleep,.marvel-device.iphone5c .sleep{position:absolute;top:-4px;right:60px;width:60px;height:4px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px;background:#282727}.marvel-device.iphone5s .volume,.marvel-device.iphone5c .volume{position:absolute;left:-4px;top:180px;z-index:0;height:27px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:#282727}.marvel-device.iphone5s .volume:before,.marvel-device.iphone5c .volume:before{position:absolute;left:0px;top:-75px;height:35px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone5s .volume:after,.marvel-device.iphone5c .volume:after{position:absolute;left:0px;bottom:-64px;height:27px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone5s .camera,.marvel-device.iphone5c .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:32px;left:50%;margin-left:-5px;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;z-index:3}.marvel-device.iphone5s .sensor,.marvel-device.iphone5c .sensor{background:#3c3d3d;width:10px;height:10px;position:absolute;top:60px;left:160px;z-index:3;margin-left:-32px;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px}.marvel-device.iphone5s .speaker,.marvel-device.iphone5c .speaker{background:#292728;width:64px;height:10px;position:absolute;top:60px;left:50%;margin-left:-32px;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;z-index:3}.marvel-device.iphone5s.landscape,.marvel-device.iphone5c.landscape{padding:22px 105px;height:320px;width:568px}.marvel-device.iphone5s.landscape .sleep,.marvel-device.iphone5c.landscape .sleep{right:-4px;top:calc(100% - 120px);height:60px;width:4px;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px}.marvel-device.iphone5s.landscape .volume,.marvel-device.iphone5c.landscape .volume{width:27px;height:4px;top:-4px;left:calc(100% - 180px);-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone5s.landscape .volume:before,.marvel-device.iphone5c.landscape .volume:before{width:35px;height:4px;top:0px;right:-75px;left:auto;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone5s.landscape .volume:after,.marvel-device.iphone5c.landscape .volume:after{bottom:0px;left:-64px;z-index:999;height:4px;width:27px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone5s.landscape .sensor,.marvel-device.iphone5c.landscape .sensor{top:160px;left:calc(100% - 60px);margin-left:0px;margin-top:-32px}.marvel-device.iphone5s.landscape .speaker,.marvel-device.iphone5c.landscape .speaker{height:64px;width:10px;left:calc(100% - 60px);top:50%;margin-left:0px;margin-top:-32px}.marvel-device.iphone5s.landscape .camera,.marvel-device.iphone5c.landscape .camera{left:calc(100% - 32px);top:50%;margin-left:0px;margin-top:-5px}.marvel-device.iphone5s .home{-moz-border-radius:36px;-webkit-border-radius:36px;border-radius:36px;width:68px;-webkit-box-shadow:inset 0 0 0 4px #2c2b2c;box-shadow:inset 0 0 0 4px #2c2b2c;height:68px;position:absolute;left:50%;margin-left:-34px;bottom:19px;z-index:3}.marvel-device.iphone5s .top-bar{top:70px;position:absolute;left:0}.marvel-device.iphone5s .bottom-bar{bottom:70px;position:absolute;left:0}.marvel-device.iphone5s.landscape .home{left:19px;bottom:50%;margin-bottom:-34px;margin-left:0px}.marvel-device.iphone5s.landscape .top-bar{left:70px;top:0px;width:3px;height:100%}.marvel-device.iphone5s.landscape .bottom-bar{right:70px;left:auto;bottom:0px;width:3px;height:100%}.marvel-device.iphone5s.silver{background:#bcbcbc}.marvel-device.iphone5s.silver:before{background:#fcfcfc}.marvel-device.iphone5s.silver .volume,.marvel-device.iphone5s.silver .sleep{background:#d6d6d6}.marvel-device.iphone5s.silver .top-bar,.marvel-device.iphone5s.silver .bottom-bar{background:#eaebec}.marvel-device.iphone5s.silver .home{-webkit-box-shadow:inset 0 0 0 4px #bcbcbc;box-shadow:inset 0 0 0 4px #bcbcbc}.marvel-device.iphone5s.gold{background:#f9e7d3}.marvel-device.iphone5s.gold:before{background:#fcfcfc}.marvel-device.iphone5s.gold .volume,.marvel-device.iphone5s.gold .sleep{background:#f9e7d3}.marvel-device.iphone5s.gold .top-bar,.marvel-device.iphone5s.gold .bottom-bar{background:white}.marvel-device.iphone5s.gold .home{-webkit-box-shadow:inset 0 0 0 4px #f9e7d3;box-shadow:inset 0 0 0 4px #f9e7d3}.marvel-device.iphone5c{background:white;-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,0.2);box-shadow:0 1px 2px 0 rgba(0,0,0,0.2)}.marvel-device.iphone5c .top-bar,.marvel-device.iphone5c .bottom-bar{display:none}.marvel-device.iphone5c .home{background:#242324;-moz-border-radius:36px;-webkit-border-radius:36px;border-radius:36px;width:68px;height:68px;z-index:3;position:absolute;left:50%;margin-left:-34px;bottom:19px}.marvel-device.iphone5c .home:after{width:20px;height:20px;border:1px solid rgba(255,255,255,0.1);-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;position:absolute;display:block;content:'';top:50%;left:50%;margin-top:-11px;margin-left:-11px}.marvel-device.iphone5c.landscape .home{left:19px;bottom:50%;margin-bottom:-34px;margin-left:0px}.marvel-device.iphone5c .volume,.marvel-device.iphone5c .sleep{background:#dddddd}.marvel-device.iphone5c.red{background:#f96b6c}.marvel-device.iphone5c.red .volume,.marvel-device.iphone5c.red .sleep{background:#ed5758}.marvel-device.iphone5c.yellow{background:#f2dc60}.marvel-device.iphone5c.yellow .volume,.marvel-device.iphone5c.yellow .sleep{background:#e5ce4c}.marvel-device.iphone5c.green{background:#97e563}.marvel-device.iphone5c.green .volume,.marvel-device.iphone5c.green .sleep{background:#85d94d}.marvel-device.iphone5c.blue{background:#33a2db}.marvel-device.iphone5c.blue .volume,.marvel-device.iphone5c.blue .sleep{background:#2694cd}.marvel-device.iphone4s{padding:129px 27px;width:320px;height:480px;background:#686868;-webkit-border-radius:54px;border-radius:54px}.marvel-device.iphone4s:before{content:'';width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;top:4px;left:4px;z-index:1;-webkit-border-radius:50px;border-radius:50px;background:#1e1e1e}.marvel-device.iphone4s .top-bar{top:60px;position:absolute;left:0}.marvel-device.iphone4s .bottom-bar{bottom:90px;position:absolute;left:0}.marvel-device.iphone4s .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:72px;left:134px;z-index:3;margin-left:-5px;-webkit-border-radius:100%;border-radius:100%}.marvel-device.iphone4s .speaker{background:#292728;width:64px;height:10px;position:absolute;top:72px;left:50%;z-index:3;margin-left:-32px;-webkit-border-radius:5px;border-radius:5px}.marvel-device.iphone4s .sensor{background:#292728;width:40px;height:10px;position:absolute;top:36px;left:50%;z-index:3;margin-left:-20px;-webkit-border-radius:5px;border-radius:5px}.marvel-device.iphone4s .home{background:#242324;-webkit-border-radius:100%;border-radius:100%;width:72px;height:72px;z-index:3;position:absolute;left:50%;margin-left:-36px;bottom:30px}.marvel-device.iphone4s .home:after{width:20px;height:20px;border:1px solid rgba(255,255,255,0.1);-webkit-border-radius:4px;border-radius:4px;position:absolute;display:block;content:'';top:50%;left:50%;margin-top:-11px;margin-left:-11px}.marvel-device.iphone4s .sleep{position:absolute;top:-4px;right:60px;width:60px;height:4px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px;background:#4D4D4D}.marvel-device.iphone4s .volume{position:absolute;left:-4px;top:160px;height:27px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:#4D4D4D}.marvel-device.iphone4s .volume:before{position:absolute;left:0px;top:-70px;height:35px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone4s .volume:after{position:absolute;left:0px;bottom:-64px;height:27px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone4s.landscape{padding:27px 129px;height:320px;width:480px}.marvel-device.iphone4s.landscape .bottom-bar{left:90px;bottom:0px;height:100%;width:3px}.marvel-device.iphone4s.landscape .top-bar{left:calc(100% - 60px);top:0px;height:100%;width:3px}.marvel-device.iphone4s.landscape .camera{top:134px;left:calc(100% - 72px);margin-left:0}.marvel-device.iphone4s.landscape .speaker{top:50%;margin-left:0;margin-top:-32px;left:calc(100% - 72px);width:10px;height:64px}.marvel-device.iphone4s.landscape .sensor{height:40px;width:10px;left:calc(100% - 36px);top:50%;margin-left:0;margin-top:-20px}.marvel-device.iphone4s.landscape .home{left:30px;bottom:50%;margin-left:0;margin-bottom:-36px}.marvel-device.iphone4s.landscape .sleep{height:60px;width:4px;right:-4px;top:calc(100% - 120px);-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px}.marvel-device.iphone4s.landscape .volume{top:-4px;left:calc(100% - 187px);height:4px;width:27px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone4s.landscape .volume:before{right:-70px;left:auto;top:0px;width:35px;height:4px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone4s.landscape .volume:after{width:27px;height:4px;bottom:0px;left:-64px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone4s.silver{background:#bcbcbc}.marvel-device.iphone4s.silver:before{background:#fcfcfc}.marvel-device.iphone4s.silver .home{background:#fcfcfc;-webkit-box-shadow:inset 0 0 0 1px #bcbcbc;box-shadow:inset 0 0 0 1px #bcbcbc}.marvel-device.iphone4s.silver .home:after{border:1px solid rgba(0,0,0,0.2)}.marvel-device.iphone4s.silver .volume,.marvel-device.iphone4s.silver .sleep{background:#d6d6d6}.marvel-device.nexus5{padding:50px 15px 50px 15px;width:320px;height:568px;background:#1e1e1e;-webkit-border-radius:20px;border-radius:20px}.marvel-device.nexus5:before{-webkit-border-radius:600px / 50px;border-radius:600px / 50px;background:inherit;content:'';top:0;position:absolute;height:103.1%;width:calc(100% - 26px);top:50%;left:50%;-moz-transform:translateX(-50%) translateY(-50%);-webkit-transform:translateX(-50%) translateY(-50%);-o-transform:translateX(-50%) translateY(-50%);-ms-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.marvel-device.nexus5 .top-bar{width:calc(100% - 8px);height:calc(100% - 6px);position:absolute;top:3px;left:4px;-webkit-border-radius:20px;border-radius:20px;background:#181818}.marvel-device.nexus5 .top-bar:before{-webkit-border-radius:600px / 50px;border-radius:600px / 50px;background:inherit;content:'';top:0;position:absolute;height:103.0%;width:calc(100% - 26px);top:50%;left:50%;-moz-transform:translateX(-50%) translateY(-50%);-webkit-transform:translateX(-50%) translateY(-50%);-o-transform:translateX(-50%) translateY(-50%);-ms-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.marvel-device.nexus5 .bottom-bar{display:none}.marvel-device.nexus5 .sleep{width:3px;position:absolute;left:-3px;top:110px;height:100px;background:inherit;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px}.marvel-device.nexus5 .volume{width:3px;position:absolute;right:-3px;top:70px;height:45px;background:inherit;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px}.marvel-device.nexus5 .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:18px;left:50%;z-index:3;margin-left:-5px;-webkit-border-radius:100%;border-radius:100%}.marvel-device.nexus5 .camera:before{background:#3c3d3d;width:6px;height:6px;content:'';display:block;position:absolute;top:2px;left:-100px;z-index:3;-webkit-border-radius:100%;border-radius:100%}.marvel-device.nexus5.landscape{padding:15px 50px 15px 50px;height:320px;width:568px}.marvel-device.nexus5.landscape:before{width:103.1%;height:calc(100% - 26px);-webkit-border-radius:50px / 600px;border-radius:50px / 600px}.marvel-device.nexus5.landscape .top-bar{left:3px;top:4px;height:calc(100% - 8px);width:calc(100% - 6px)}.marvel-device.nexus5.landscape .top-bar:before{width:103%;height:calc(100% - 26px);-webkit-border-radius:50px / 600px;border-radius:50px / 600px}.marvel-device.nexus5.landscape .sleep{height:3px;width:100px;left:calc(100% - 210px);top:-3px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.nexus5.landscape .volume{height:3px;width:45px;right:70px;top:100%;-webkit-border-radius:0px 0px 2px 2px;border-radius:0px 0px 2px 2px}.marvel-device.nexus5.landscape .camera{top:50%;left:calc(100% - 18px);margin-left:0;margin-top:-5px}.marvel-device.nexus5.landscape .camera:before{top:-100px;left:2px}.marvel-device.s5{padding:60px 18px;-webkit-border-radius:42px;border-radius:42px;width:320px;height:568px;background:#bcbcbc}.marvel-device.s5:before,.marvel-device.s5:after{width:calc(100% - 52px);content:'';display:block;height:26px;background:inherit;position:absolute;-webkit-border-radius:500px / 40px;border-radius:500px / 40px;left:50%;-moz-transform:translateX(-50%);-webkit-transform:translateX(-50%);-o-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.marvel-device.s5:before{top:-7px}.marvel-device.s5:after{bottom:-7px}.marvel-device.s5 .bottom-bar{display:none}.marvel-device.s5 .top-bar{-webkit-border-radius:37px;border-radius:37px;width:calc(100% - 10px);height:calc(100% - 10px);top:5px;left:5px;background:radial-gradient(rgba(0,0,0,0.02) 20%, transparent 60%) 0 0,radial-gradient(rgba(0,0,0,0.02) 20%, transparent 60%) 3px 3px;background-color:white;background-size:4px 4px;background-position:center;z-index:2;position:absolute}.marvel-device.s5 .top-bar:before,.marvel-device.s5 .top-bar:after{width:calc(100% - 48px);content:'';display:block;height:26px;background:inherit;position:absolute;-webkit-border-radius:500px / 40px;border-radius:500px / 40px;left:50%;-moz-transform:translateX(-50%);-webkit-transform:translateX(-50%);-o-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.marvel-device.s5 .top-bar:before{top:-7px}.marvel-device.s5 .top-bar:after{bottom:-7px}.marvel-device.s5 .sleep{width:3px;position:absolute;left:-3px;top:100px;height:100px;background:#cecece;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px}.marvel-device.s5 .speaker{width:68px;height:8px;position:absolute;top:20px;display:block;z-index:3;left:50%;margin-left:-34px;background-color:#bcbcbc;background-position:top left;-webkit-border-radius:4px;border-radius:4px}.marvel-device.s5 .sensor{display:block;position:absolute;top:20px;right:110px;background:#3c3d3d;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;width:8px;height:8px;z-index:3}.marvel-device.s5 .sensor:after{display:block;content:'';position:absolute;top:0px;right:12px;background:#3c3d3d;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;width:8px;height:8px;z-index:3}.marvel-device.s5 .camera{display:block;position:absolute;top:24px;right:42px;background:black;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;width:10px;height:10px;z-index:3}.marvel-device.s5 .camera:before{width:4px;height:4px;background:#3c3d3d;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;position:absolute;content:'';top:50%;left:50%;margin-top:-2px;margin-left:-2px}.marvel-device.s5 .home{position:absolute;z-index:3;bottom:17px;left:50%;width:70px;height:20px;background:white;-webkit-border-radius:18px;border-radius:18px;display:block;margin-left:-35px;border:2px solid black}.marvel-device.s5.landscape{padding:18px 60px;height:320px;width:568px}.marvel-device.s5.landscape:before,.marvel-device.s5.landscape:after{height:calc(100% - 52px);width:26px;-webkit-border-radius:40px / 500px;border-radius:40px / 500px;-moz-transform:translateY(-50%);-webkit-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.marvel-device.s5.landscape:before{top:50%;left:-7px}.marvel-device.s5.landscape:after{top:50%;left:auto;right:-7px}.marvel-device.s5.landscape .top-bar:before,.marvel-device.s5.landscape .top-bar:after{width:26px;height:calc(100% - 48px);-webkit-border-radius:40px / 500px;border-radius:40px / 500px;-moz-transform:translateY(-50%);-webkit-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.marvel-device.s5.landscape .top-bar:before{right:-7px;top:50%;left:auto}.marvel-device.s5.landscape .top-bar:after{left:-7px;top:50%;right:auto}.marvel-device.s5.landscape .sleep{height:3px;width:100px;left:calc(100% - 200px);top:-3px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.s5.landscape .speaker{height:68px;width:8px;left:calc(100% - 20px);top:50%;margin-left:0;margin-top:-34px}.marvel-device.s5.landscape .sensor{right:20px;top:calc(100% - 110px)}.marvel-device.s5.landscape .sensor:after{left:-12px;right:0px}.marvel-device.s5.landscape .camera{top:calc(100% - 42px);right:24px}.marvel-device.s5.landscape .home{width:20px;height:70px;bottom:50%;margin-bottom:-35px;margin-left:0;left:17px}.marvel-device.s5.black{background:#1e1e1e}.marvel-device.s5.black .speaker{background:black}.marvel-device.s5.black .sleep{background:#1e1e1e}.marvel-device.s5.black .top-bar{background:radial-gradient(rgba(0,0,0,0.05) 20%, transparent 60%) 0 0,radial-gradient(rgba(0,0,0,0.05) 20%, transparent 60%) 3px 3px;background-color:#2c2b2c;background-size:4px 4px}.marvel-device.s5.black .home{background:#2c2b2c}.marvel-device.lumia920{padding:80px 35px 125px 35px;background:#ffdd00;width:320px;height:533px;-moz-border-radius:40px / 3px;-webkit-border-radius:40px / 3px;border-radius:40px / 3px}.marvel-device.lumia920 .bottom-bar{display:none}.marvel-device.lumia920 .top-bar{width:calc(100% - 24px);height:calc(100% - 32px);position:absolute;top:16px;left:12px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px;background:black;z-index:1}.marvel-device.lumia920 .top-bar:before{background:#1e1e1e;display:block;content:'';width:calc(100% - 4px);height:calc(100% - 4px);top:2px;left:2px;position:absolute;-moz-border-radius:22px;-webkit-border-radius:22px;border-radius:22px}.marvel-device.lumia920 .volume{width:3px;position:absolute;top:130px;height:100px;background:#1e1e1e;right:-3px;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px}.marvel-device.lumia920 .volume:before{width:3px;position:absolute;top:190px;content:'';display:block;height:50px;background:inherit;right:0px;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px}.marvel-device.lumia920 .volume:after{width:3px;position:absolute;top:460px;content:'';display:block;height:50px;background:inherit;right:0px;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px}.marvel-device.lumia920 .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:34px;right:130px;z-index:5;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px}.marvel-device.lumia920 .speaker{background:#292728;width:64px;height:10px;position:absolute;top:38px;left:50%;margin-left:-32px;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;z-index:3}.marvel-device.lumia920.landscape{padding:35px 80px 35px 125px;height:320px;width:568px;-moz-border-radius:2px / 100px;-webkit-border-radius:2px / 100px;border-radius:2px / 100px}.marvel-device.lumia920.landscape .top-bar{height:calc(100% - 24px);width:calc(100% - 32px);left:16px;top:12px}.marvel-device.lumia920.landscape .volume{height:3px;right:130px;width:100px;top:100%;-webkit-border-radius:0px 0px 2px 2px;border-radius:0px 0px 2px 2px}.marvel-device.lumia920.landscape .volume:before{height:3px;right:190px;top:0px;width:50px;-webkit-border-radius:0px 0px 2px 2px;border-radius:0px 0px 2px 2px}.marvel-device.lumia920.landscape .volume:after{height:3px;right:430px;top:0px;width:50px;-webkit-border-radius:0px 0px 2px 2px;border-radius:0px 0px 2px 2px}.marvel-device.lumia920.landscape .camera{right:30px;top:calc(100% - 140px)}.marvel-device.lumia920.landscape .speaker{width:10px;height:64px;top:50%;margin-left:0;margin-top:-32px;left:calc(100% - 48px)}.marvel-device.lumia920.black{background:black}.marvel-device.lumia920.white{background:white;-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,0.2);box-shadow:0 1px 2px 0 rgba(0,0,0,0.2)}.marvel-device.lumia920.blue{background:#00acdd}.marvel-device.lumia920.red{background:#CC3E32}.marvel-device.htc-one{padding:72px 25px 100px 25px;width:320px;height:568px;background:#bebebe;-webkit-border-radius:34px;border-radius:34px}.marvel-device.htc-one:before{content:'';display:block;width:calc(100% - 4px);height:calc(100% - 4px);position:absolute;top:2px;left:2px;background:#adadad;-webkit-border-radius:32px;border-radius:32px}.marvel-device.htc-one:after{content:'';display:block;width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;top:4px;left:4px;background:#eeeeee;-webkit-border-radius:30px;border-radius:30px}.marvel-device.htc-one .top-bar{width:calc(100% - 4px);height:635px;position:absolute;background:#424242;top:50px;z-index:1;left:2px}.marvel-device.htc-one .top-bar:before{content:'';position:absolute;width:calc(100% - 4px);height:100%;position:absolute;background:black;top:0px;z-index:1;left:2px}.marvel-device.htc-one .bottom-bar{display:none}.marvel-device.htc-one .speaker{height:16px;width:216px;display:block;position:absolute;top:22px;z-index:2;left:50%;margin-left:-108px;background:radial-gradient(#343434 25%, transparent 50%) 0 0,radial-gradient(#343434 25%, transparent 50%) 4px 4px;background-size:4px 4px;background-position:top left}.marvel-device.htc-one .speaker:after{content:'';height:16px;width:216px;display:block;position:absolute;top:676px;z-index:2;left:50%;margin-left:-108px;background:inherit}.marvel-device.htc-one .camera{display:block;position:absolute;top:18px;right:38px;background:#3c3d3d;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;width:24px;height:24px;z-index:3}.marvel-device.htc-one .camera:before{width:8px;height:8px;background:black;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;position:absolute;content:'';top:50%;left:50%;margin-top:-4px;margin-left:-4px}.marvel-device.htc-one .sensor{display:block;position:absolute;top:29px;left:60px;background:#3c3d3d;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;width:8px;height:8px;z-index:3}.marvel-device.htc-one .sensor:after{display:block;content:'';position:absolute;top:0px;right:12px;background:#3c3d3d;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;width:8px;height:8px;z-index:3}.marvel-device.htc-one.landscape{padding:25px 72px 25px 100px;height:320px;width:568px}.marvel-device.htc-one.landscape .top-bar{height:calc(100% - 4px);width:635px;left:calc(100% - 685px);top:2px}.marvel-device.htc-one.landscape .speaker{width:16px;height:216px;left:calc(100% - 38px);top:50%;margin-left:0px;margin-top:-108px}.marvel-device.htc-one.landscape .speaker:after{width:16px;height:216px;left:calc(100% - 692px);top:50%;margin-left:0;margin-top:-108px}.marvel-device.htc-one.landscape .camera{right:18px;top:calc(100% - 38px)}.marvel-device.htc-one.landscape .sensor{left:calc(100% - 29px);top:60px}.marvel-device.htc-one.landscape .sensor :after{right:0;top:-12px}.marvel-device.ipad{width:576px;height:768px;padding:90px 25px;background:#242324;-webkit-border-radius:44px;border-radius:44px}.marvel-device.ipad:before{width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;content:'';display:block;top:4px;left:4px;-webkit-border-radius:40px;border-radius:40px;background:#1e1e1e}.marvel-device.ipad .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:44px;left:50%;margin-left:-5px;-webkit-border-radius:100%;border-radius:100%}.marvel-device.ipad .top-bar,.marvel-device.ipad .bottom-bar{display:none}.marvel-device.ipad .home{background:#242324;-webkit-border-radius:36px;border-radius:36px;width:50px;height:50px;position:absolute;left:50%;margin-left:-25px;bottom:22px}.marvel-device.ipad .home:after{width:15px;height:15px;margin-top:-8px;margin-left:-8px;border:1px solid rgba(255,255,255,0.1);-webkit-border-radius:4px;border-radius:4px;position:absolute;display:block;content:'';top:50%;left:50%}.marvel-device.ipad.landscape{height:576px;width:768px;padding:25px 90px}.marvel-device.ipad.landscape .camera{left:calc(100% - 44px);top:50%;margin-left:0;margin-top:-5px}.marvel-device.ipad.landscape .home{top:50%;left:22px;margin-left:0;margin-top:-25px}.marvel-device.ipad.silver{background:#bcbcbc}.marvel-device.ipad.silver:before{background:#fcfcfc}.marvel-device.ipad.silver .home{background:#fcfcfc;-webkit-box-shadow:inset 0 0 0 1px #bcbcbc;box-shadow:inset 0 0 0 1px #bcbcbc}.marvel-device.ipad.silver .home:after{border:1px solid rgba(0,0,0,0.2)} 2 | 3 | -------------------------------------------------------------------------------- /app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | 22 | 23 | {{outlet}} 24 | -------------------------------------------------------------------------------- /app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher-community/emberjs-realtime-chat/ff53188a98ae9a977f38a15c277b5830e380b060/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /app/templates/components/chat-app.hbs: -------------------------------------------------------------------------------- 1 | {{#if nameIsSet}} 2 |

Welcome {{ userName }}

3 | {{#chat-messages}}{{/chat-messages}} 4 | {{else}} 5 |
6 | {{#chat-username}}{{/chat-username}} 7 |
8 | {{/if}} 9 | -------------------------------------------------------------------------------- /app/templates/components/chat-message.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 | {{ message.username }} 7 | {{ timestamp }} 8 | 9 |
10 | 11 |

{{ text }}

12 |
13 | -------------------------------------------------------------------------------- /app/templates/components/chat-messages.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Today 14 |
15 | 16 | {{#each messages as |message|}} 17 |
18 | {{#chat-message message=message}}{{/chat-message}} 19 |
20 | {{/each}} 21 |
22 |
23 |
24 | {{ input value=newMessage class="input-message col-xs-10" id="message-input" }} 25 | 28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /app/templates/components/chat-username.hbs: -------------------------------------------------------------------------------- 1 |

Enter your Twitter name and start chatting!

2 |
3 |
4 | {{input placeholder="Enter your Twitter name!" value=userName id="input-name" class="swish-input" }} 5 | 6 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /app/templates/index.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Realtime Chat with Pusher and Ember 6 |

7 | {{#chat-app}}{{/chat-app}} 8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pusher-chat", 3 | "dependencies": { 4 | "ember": "2.1.0", 5 | "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", 6 | "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", 7 | "ember-data": "2.1.0", 8 | "ember-load-initializers": "ember-cli/ember-load-initializers#0.1.5", 9 | "ember-qunit": "0.4.9", 10 | "ember-qunit-notifications": "0.0.7", 11 | "ember-resolver": "~0.1.18", 12 | "jquery": "^1.11.3", 13 | "loader.js": "ember-cli/loader.js#3.2.1", 14 | "qunit": "~1.18.0", 15 | "pusher": "~3.0.0", 16 | "strftime": "~0.9.2", 17 | "he": "~0.5.0" 18 | }, 19 | "resolutions": { 20 | "ember": "2.1.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function(environment) { 4 | var ENV = { 5 | modulePrefix: 'pusher-chat', 6 | environment: environment, 7 | baseURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | } 14 | }, 15 | 16 | APP: { 17 | // Here you can pass flags/options to your application instance 18 | // when it is created 19 | PUSHER: { 20 | key: '6652ff76df277967f23e', 21 | }, 22 | } 23 | }; 24 | 25 | if (environment === 'development') { 26 | ENV.APP.SERVER_URL = 'http://localhost:4567'; 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.baseURL = '/'; 37 | ENV.locationType = 'none'; 38 | 39 | // keep test console output quieter 40 | ENV.APP.LOG_ACTIVE_GENERATION = false; 41 | ENV.APP.LOG_VIEW_LOOKUPS = false; 42 | 43 | ENV.APP.rootElement = '#ember-testing'; 44 | } 45 | 46 | if (environment === 'production') { 47 | ENV.APP.SERVER_URL = 'https://pusher-chat-sinatra.herokuapp.com'; 48 | 49 | } 50 | 51 | ENV['contentSecurityPolicy'] = { 52 | 'default-src': "'none'", 53 | 'script-src': "'self' https://stats.pusher.com/", 54 | 'connect-src': "'self' wss://ws.pusherapp.com/ http://localhost:4567/", 55 | 'img-src': "'self'", 56 | 'style-src': "'self' fonts.googleapis.com http://d3dhju7igb20wy.cloudfront.net/ 'unsafe-inline'", 57 | 'font-src': "'self' fonts.gstatic.com http://d3dhju7igb20wy.cloudfront.net/", 58 | 'media-src': "'self'", 59 | } 60 | 61 | return ENV; 62 | }; 63 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /* global require, module */ 2 | var EmberApp = require('ember-cli/lib/broccoli/ember-app'); 3 | 4 | module.exports = function(defaults) { 5 | var app = new EmberApp(defaults, { 6 | // Add options here 7 | }); 8 | 9 | app.import('bower_components/pusher/dist/pusher.min.js'); 10 | app.import('bower_components/strftime/strftime-min.js'); 11 | app.import('bower_components/he/he.js'); 12 | app.import('app/styles/devices.min.css'); 13 | // Use `app.import` to add additional libraries to the generated 14 | // output files. 15 | // 16 | // If you need to use different assets in different 17 | // environments, specify an object as the first parameter. That 18 | // object's keys should be the environment name and the values 19 | // should be the asset to use in that environment. 20 | // 21 | // If the library that you are including contains AMD or ES6 22 | // modules that you would like to import into your application 23 | // please specify an object with the list of modules as keys 24 | // along with the exports of each module as its value. 25 | 26 | return app.toTree(); 27 | }; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pusher-chat", 3 | "version": "0.0.0", 4 | "description": "Small description for pusher-chat goes here", 5 | "private": true, 6 | "directories": { 7 | "doc": "doc", 8 | "test": "tests" 9 | }, 10 | "scripts": { 11 | "build": "ember build", 12 | "start": "ember server", 13 | "test": "ember test" 14 | }, 15 | "repository": "", 16 | "engines": { 17 | "node": ">= 0.10.0" 18 | }, 19 | "author": "", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "broccoli-asset-rev": "^2.1.2", 23 | "ember-cli": "1.13.8", 24 | "ember-cli-app-version": "0.5.0", 25 | "ember-cli-babel": "^5.1.3", 26 | "ember-cli-content-security-policy": "0.4.0", 27 | "ember-cli-dependency-checker": "^1.0.1", 28 | "ember-cli-htmlbars": "0.7.9", 29 | "ember-cli-htmlbars-inline-precompile": "^0.2.0", 30 | "ember-cli-ic-ajax": "0.2.1", 31 | "ember-cli-inject-live-reload": "^1.3.1", 32 | "ember-cli-qunit": "^1.0.0", 33 | "ember-cli-release": "0.2.3", 34 | "ember-cli-sri": "^1.0.3", 35 | "ember-cli-uglify": "^1.2.0", 36 | "ember-data": "1.13.8", 37 | "ember-disable-proxy-controllers": "^1.0.0", 38 | "ember-export-application-global": "^1.0.3" 39 | }, 40 | "dependencies": { 41 | "strftime": "^0.9.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/assets/images/ember.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher-community/emberjs-realtime-chat/ff53188a98ae9a977f38a15c277b5830e380b060/public/assets/images/ember.png -------------------------------------------------------------------------------- /public/assets/images/iPad_white_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher-community/emberjs-realtime-chat/ff53188a98ae9a977f38a15c277b5830e380b060/public/assets/images/iPad_white_thumb.png -------------------------------------------------------------------------------- /public/assets/images/pusher-logo-p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher-community/emberjs-realtime-chat/ff53188a98ae9a977f38a15c277b5830e380b060/public/assets/images/pusher-logo-p.png -------------------------------------------------------------------------------- /public/assets/images/pusher-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher-community/emberjs-realtime-chat/ff53188a98ae9a977f38a15c277b5830e380b060/public/assets/images/pusher-logo.png -------------------------------------------------------------------------------- /public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tests/index.html?hidepassed", 4 | "disable_watching": true, 5 | "launch_in_ci": [ 6 | "PhantomJS" 7 | ], 8 | "launch_in_dev": [ 9 | "PhantomJS", 10 | "Chrome" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "define", 10 | "console", 11 | "visit", 12 | "exists", 13 | "fillIn", 14 | "click", 15 | "keyEvent", 16 | "triggerEvent", 17 | "find", 18 | "findWithAssert", 19 | "wait", 20 | "DS", 21 | "andThen", 22 | "currentURL", 23 | "currentPath", 24 | "currentRouteName" 25 | ], 26 | "node": false, 27 | "browser": false, 28 | "boss": true, 29 | "curly": true, 30 | "debug": false, 31 | "devel": false, 32 | "eqeqeq": true, 33 | "evil": true, 34 | "forin": false, 35 | "immed": false, 36 | "laxbreak": false, 37 | "newcap": true, 38 | "noarg": true, 39 | "noempty": false, 40 | "nonew": false, 41 | "nomen": false, 42 | "onevar": false, 43 | "plusplus": false, 44 | "regexp": false, 45 | "undef": true, 46 | "sub": true, 47 | "strict": false, 48 | "white": false, 49 | "eqnull": true, 50 | "esnext": true, 51 | "unused": true 52 | } 53 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember/resolver'; 2 | import config from '../../config/environment'; 3 | 4 | var resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import config from '../../config/environment'; 4 | 5 | export default function startApp(attrs) { 6 | var application; 7 | 8 | var attributes = Ember.merge({}, config.APP); 9 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; 10 | 11 | Ember.run(function() { 12 | application = Application.create(attributes); 13 | application.setupForTesting(); 14 | application.injectTestHelpers(); 15 | }); 16 | 17 | return application; 18 | } 19 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PusherChat Tests 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | {{content-for 'test-head'}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for 'head-footer'}} 18 | {{content-for 'test-head-footer'}} 19 | 20 | 21 | 22 | {{content-for 'body'}} 23 | {{content-for 'test-body'}} 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for 'body-footer'}} 31 | {{content-for 'test-body-footer'}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/integration/components/chat-app-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('chat-app', 'Integration | Component | chat app', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | assert.expect(2); 10 | 11 | // Set any properties with this.set('myProperty', 'value'); 12 | // Handle any actions with this.on('myAction', function(val) { ... }); 13 | 14 | this.render(hbs`{{chat-app}}`); 15 | 16 | assert.equal(this.$().text().trim(), ''); 17 | 18 | // Template block usage: 19 | this.render(hbs` 20 | {{#chat-app}} 21 | template block text 22 | {{/chat-app}} 23 | `); 24 | 25 | assert.equal(this.$().text().trim(), 'template block text'); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/integration/components/chat-message-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('chat-message', 'Integration | Component | chat message', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | assert.expect(2); 10 | 11 | // Set any properties with this.set('myProperty', 'value'); 12 | // Handle any actions with this.on('myAction', function(val) { ... }); 13 | 14 | this.render(hbs`{{chat-message}}`); 15 | 16 | assert.equal(this.$().text().trim(), ''); 17 | 18 | // Template block usage: 19 | this.render(hbs` 20 | {{#chat-message}} 21 | template block text 22 | {{/chat-message}} 23 | `); 24 | 25 | assert.equal(this.$().text().trim(), 'template block text'); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/integration/components/chat-messages-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('chat-messages', 'Integration | Component | chat messages', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | assert.expect(2); 10 | 11 | // Set any properties with this.set('myProperty', 'value'); 12 | // Handle any actions with this.on('myAction', function(val) { ... }); 13 | 14 | this.render(hbs`{{chat-messages}}`); 15 | 16 | assert.equal(this.$().text().trim(), ''); 17 | 18 | // Template block usage: 19 | this.render(hbs` 20 | {{#chat-messages}} 21 | template block text 22 | {{/chat-messages}} 23 | `); 24 | 25 | assert.equal(this.$().text().trim(), 'template block text'); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/integration/components/chat-username-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('chat-username', 'Integration | Component | chat username', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | assert.expect(2); 10 | 11 | // Set any properties with this.set('myProperty', 'value'); 12 | // Handle any actions with this.on('myAction', function(val) { ... }); 13 | 14 | this.render(hbs`{{chat-username}}`); 15 | 16 | assert.equal(this.$().text().trim(), ''); 17 | 18 | // Template block usage: 19 | this.render(hbs` 20 | {{#chat-username}} 21 | template block text 22 | {{/chat-username}} 23 | `); 24 | 25 | assert.equal(this.$().text().trim(), 'template block text'); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher-community/emberjs-realtime-chat/ff53188a98ae9a977f38a15c277b5830e380b060/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/routes/index-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('route:index', 'Unit | Route | index', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | var route = this.subject(); 10 | assert.ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/services/current-user-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('service:current-user', 'Unit | Service | current user', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['service:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function(assert) { 10 | var service = this.subject(); 11 | assert.ok(service); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/services/pusher-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('service:pusher', 'Unit | Service | pusher', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['service:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function(assert) { 10 | var service = this.subject(); 11 | assert.ok(service); 12 | }); 13 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher-community/emberjs-realtime-chat/ff53188a98ae9a977f38a15c277b5830e380b060/vendor/.gitkeep --------------------------------------------------------------------------------