├── .gitignore ├── .project ├── LICENSE ├── README.md ├── bin └── server.dart ├── lib ├── cachingusermediaretriever.dart ├── filehandler.dart ├── webrtcstreammanager.dart └── websockethandler.dart ├── pubspec.yaml ├── screenshot.png └── web ├── animate.min.css ├── animate.title.css ├── basic-title.ttf ├── big-noodle.ttf ├── index.css ├── index.dart └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.js* 2 | *.log 3 | .DS_Store 4 | .project 5 | packages 6 | pubspec.lock 7 | build 8 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | dart 4 | 5 | 6 | 7 | 8 | 9 | com.google.dart.tools.core.dartBuilder 10 | 11 | 12 | 13 | 14 | 15 | com.google.dart.tools.core.dartNature 16 | 17 | 18 | 19 | 1382193670209 20 | 21 | 30 22 | 23 | com.google.dart.tools.core.packagesFolderMatcher 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nick Evers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dart-webrtc 2 | =========== 3 | A clean and simple proof of concept for webrtc in dart. 4 | 5 | How to get started: 6 | 7 | 0. Install dart from https://www.dartlang.org/ and make sure the dart binaries are available in $PATH. 8 | 1. Checkout the project and go to the newly created folder. 9 | 2. Compile the client dart code using pub: 10 | > ``` 11 | > $ pub build 12 | > Your pubspec has changed, so we need to update your lockfile: 13 | > Resolving dependencies... 14 | > + browser 0.10.0+2 15 | > + logging 0.9.2 16 | > Changed 2 dependencies! 17 | > Loading source assets... 18 | > Building dart_webrtc... 19 | > [Info from Dart2JS]: 20 | > Compiling dart_webrtc|web/index.dart... 21 | > [Info from Dart2JS]: 22 | > Took 0:00:08.410037 to compile dart_webrtc|web/index.dart. 23 | > Built 9 files to "build". 24 | > ``` 25 | 26 | 3. Start the server: 27 | > ``` 28 | > $ cd bin 29 | > bin$ dart server.dart 30 | > Starting server 31 | > Listening for connections at 0.0.0.0:1337 32 | > ``` 33 | 34 | 4. Browse to http://localhost:1337 and you should see the following page: 35 | > ![Screenshot](screenshot.png) 36 | -------------------------------------------------------------------------------- /bin/server.dart: -------------------------------------------------------------------------------- 1 | import "dart:io"; 2 | import "dart:async"; 3 | import "package:dart_webrtc/filehandler.dart"; 4 | import "package:dart_webrtc/websockethandler.dart"; 5 | 6 | var bindAddress = "0.0.0.0"; 7 | var port = 1337; 8 | var webSocketContextPath = "/ws"; 9 | FileHandler fileHandler = new FileHandler(); 10 | WebSocketHandler webSocketHandler = new WebSocketHandler(); 11 | 12 | void main() { 13 | print("Starting server"); 14 | HttpServer.bind(bindAddress, port).then((HttpServer server) { 15 | print("Listening for connections at $bindAddress:$port"); 16 | server.listen((request) { 17 | handleRequest(request); 18 | }); 19 | }); 20 | } 21 | 22 | void handleRequest(HttpRequest request) { 23 | var path = request.uri.path; 24 | if(path.contains(webSocketContextPath)) 25 | webSocketHandler.handleRequest(request); 26 | else 27 | fileHandler.handleRequest(request); 28 | } 29 | -------------------------------------------------------------------------------- /lib/cachingusermediaretriever.dart: -------------------------------------------------------------------------------- 1 | library cachingusermediaretriever; 2 | 3 | import "dart:html"; 4 | import "dart:async"; 5 | 6 | class CachingUserMediaRetriever { 7 | MediaStream mediaStream; 8 | 9 | Future get() { 10 | Completer completer = new Completer(); 11 | if(mediaStream != null) 12 | completer.complete(mediaStream); 13 | else 14 | window.navigator.getUserMedia(audio: false, video: true).then((MediaStream stream) { 15 | mediaStream = stream; 16 | completer.complete(mediaStream); 17 | }); 18 | return completer.future; 19 | } 20 | } -------------------------------------------------------------------------------- /lib/filehandler.dart: -------------------------------------------------------------------------------- 1 | library filehandler; 2 | 3 | import "dart:io"; 4 | 5 | //TODO Add headers depending on the file extension 6 | class FileHandler { 7 | static final String DOCUMENT_ROOT = "../build/web"; 8 | 9 | void handleRequest(HttpRequest request) { 10 | String path = (request.uri.path.endsWith('/')) ? "${request.uri.path}index.html" : request.uri.path; 11 | path = DOCUMENT_ROOT + path; 12 | HttpResponse response = request.response; 13 | response.headers.contentType = getContentType(path); 14 | 15 | File file = new File(path); 16 | file.exists().then((bool exists) { 17 | if (exists) { 18 | var stream = file.openRead(); 19 | stream.pipe(response).catchError((e) => print(e)); 20 | } else { 21 | response.statusCode = HttpStatus.NOT_FOUND; 22 | response.close(); 23 | } 24 | }); 25 | } 26 | 27 | ContentType getContentType(String path) { 28 | if(hasValidExtension(path)) 29 | return getContentTypeFromExtension(getExtension(path)); 30 | else 31 | return ContentType.parse("text/plain"); 32 | } 33 | 34 | ContentType getContentTypeFromExtension(String extension) { 35 | switch(extension) { 36 | case ".html": 37 | return ContentType.parse("text/html; charset=UTF-8"); 38 | case ".css": 39 | return ContentType.parse("text/css; charset=UTF-8"); 40 | case ".dart": 41 | return ContentType.parse("application/dart; charset=UTF-8"); 42 | case ".js": 43 | return ContentType.parse("application/javascript; charset=UTF-8"); 44 | case ".ttf": 45 | return ContentType.parse("application/x-font-ttf"); 46 | case ".ico": 47 | return ContentType.parse("image/ico"); 48 | default: 49 | return ContentType.parse("text/plain"); 50 | } 51 | } 52 | 53 | bool hasValidExtension(String path) { 54 | return path.indexOf('.') != -1 && path.length > 1; 55 | } 56 | 57 | String getExtension(String path) { 58 | String trimmedPath = path.trim().toLowerCase(); 59 | int start = trimmedPath.lastIndexOf('.'); 60 | int stop = trimmedPath.length; 61 | return trimmedPath.substring(start, stop); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/webrtcstreammanager.dart: -------------------------------------------------------------------------------- 1 | library webrtcstreammanager; 2 | 3 | import "dart:html"; 4 | import "dart:convert"; 5 | import "cachingusermediaretriever.dart"; 6 | 7 | class WebRtcStreamManager { 8 | WebSocket webSocket; 9 | var sendingRtcPeerConnections; 10 | var receivingRtcPeerConnections; 11 | var cachingUserMediaRetriever; 12 | var streamAddHandler; 13 | var streamRemoveHandler; 14 | 15 | WebRtcStreamManager() { 16 | sendingRtcPeerConnections = new Map(); 17 | receivingRtcPeerConnections = new Map(); 18 | cachingUserMediaRetriever = new CachingUserMediaRetriever(); 19 | 20 | var uri = "ws://" + window.location.host + "/ws"; 21 | log("Creating websocket to: '$uri'"); 22 | webSocket = new WebSocket(uri); 23 | log("Waiting for websocket to open..."); 24 | webSocket.onOpen.listen(handleOpen); 25 | webSocket.onClose.listen(handleClose); 26 | webSocket.onMessage.listen(handleMessage); 27 | webSocket.onError.listen(handleError); 28 | } 29 | 30 | RtcPeerConnection createRtcPeerConnection() { 31 | var rtcIceServers = { 32 | "iceServers": [{"url": "stun:stun.l.google.com:19302"}] 33 | }; 34 | 35 | var mediaConstraints = { 36 | "optional": [{"RtpDataChannels": true}, {"DtlsSrtpKeyAgreement": true}] 37 | }; 38 | 39 | return new RtcPeerConnection(rtcIceServers, mediaConstraints); 40 | } 41 | 42 | void sendMessage(message) { 43 | log("Sending message: targetClientId='${message["targetClientId"]}' type='${message["type"]}'"); 44 | webSocket.send(JSON.encode(message)); 45 | } 46 | 47 | void handleOpen(message) { 48 | log("Websocket opened"); 49 | } 50 | 51 | void handleClose(closeEvent) { 52 | log("Websocket closed"); 53 | } 54 | 55 | void handleMessage(message) { 56 | var parsedData = JSON.decode(message.data); 57 | var messageType = parsedData["type"]; 58 | var originClientId = parsedData["originClientId"]; 59 | var messageContent = parsedData["content"]; 60 | 61 | log("Received message: originClientId='${originClientId}' messageType='${messageType}'"); 62 | 63 | switch(messageType) { 64 | case "clientIds": 65 | handleCliendIds(messageContent); 66 | break; 67 | case "clientAdd": 68 | createSendingRtcPeerConnection(messageContent); 69 | break; 70 | case "clientRemove": 71 | handleClientRemove(messageContent); 72 | break; 73 | case "offer": 74 | handleOffer(originClientId, messageContent); 75 | break; 76 | case "answer": 77 | handleAnswer(originClientId, messageContent); 78 | break; 79 | case "receiverCandidate": 80 | handleReceiverCandidate(originClientId, messageContent); 81 | break; 82 | case "senderCandidate": 83 | handleSenderCandidate(originClientId, messageContent); 84 | break; 85 | } 86 | } 87 | 88 | void handleCliendIds(List clientIds) { 89 | log("handleClientIds: clientIds='${clientIds}'"); 90 | clientIds.forEach((id) { 91 | createSendingRtcPeerConnection(id); 92 | }); 93 | } 94 | 95 | void createSendingRtcPeerConnection(id) { 96 | RtcPeerConnection sendingRtcPeerConnection = createRtcPeerConnection();; 97 | sendingRtcPeerConnections[id] = sendingRtcPeerConnection; 98 | cachingUserMediaRetriever.get().then((MediaStream stream) { 99 | sendingRtcPeerConnection.addStream(stream); 100 | sendOffer(id, sendingRtcPeerConnection); 101 | }); 102 | } 103 | 104 | void handleClientRemove(id) { 105 | RtcPeerConnection receivingRtcPeerConnection = receivingRtcPeerConnections[id]; 106 | receivingRtcPeerConnections.remove(id); 107 | notifyRemoveStream(id); 108 | 109 | RtcPeerConnection sendingRtcPeerConnection = sendingRtcPeerConnections[id]; 110 | sendingRtcPeerConnections.remove(id); 111 | } 112 | 113 | void sendOffer(originClientId, sendingRtcPeerConnection) { 114 | sendingRtcPeerConnection.createOffer({}).then((RtcSessionDescription description) { 115 | sendingRtcPeerConnection.setLocalDescription(description); 116 | sendMessage({"type": "offer", "targetClientId": originClientId, "content": {"sdp": description.sdp, "type": description.type}}); 117 | sendingRtcPeerConnection.onIceCandidate.listen((RtcIceCandidateEvent event) { 118 | if(event.candidate != null) 119 | sendMessage({"type": "senderCandidate", "targetClientId": originClientId, "content": {"sdpMLineIndex": event.candidate.sdpMLineIndex, "candidate": event.candidate.candidate}}); 120 | }); 121 | }); 122 | } 123 | 124 | void handleOffer(originClientId, offer) { 125 | var receivingRtcPeerConnection = createRtcPeerConnection(); 126 | receivingRtcPeerConnections[originClientId] = receivingRtcPeerConnection; 127 | receivingRtcPeerConnection.setRemoteDescription(new RtcSessionDescription(offer)); 128 | receivingRtcPeerConnection.createAnswer({}).then((RtcSessionDescription description) { 129 | receivingRtcPeerConnection.setLocalDescription(description); 130 | sendMessage({"type": "answer", "targetClientId": originClientId, "content": {"sdp": description.sdp, "type": description.type}}); 131 | }); 132 | receivingRtcPeerConnection.onIceCandidate.listen((RtcIceCandidateEvent event) { 133 | if(event.candidate != null) 134 | sendMessage({"type": "receiverCandidate", "targetClientId": originClientId, "content": {"sdpMLineIndex": event.candidate.sdpMLineIndex, "candidate": event.candidate.candidate}}); 135 | }); 136 | receivingRtcPeerConnection.onAddStream.listen((MediaStreamEvent event) { 137 | notifyAddStream(originClientId, event.stream); 138 | }); 139 | receivingRtcPeerConnection.onIceConnectionStateChange.listen((Event event){ 140 | if(receivingRtcPeerConnection.iceConnectionState == "disconnected" && receivingRtcPeerConnections.containsKey(originClientId)) 141 | handleClientRemove(originClientId); 142 | }); 143 | } 144 | 145 | void notifyAddStream(originClientId, MediaStream stream) { 146 | if(streamAddHandler != null) 147 | streamAddHandler(originClientId, stream); 148 | } 149 | 150 | void notifyRemoveStream(originClientId) { 151 | if(streamRemoveHandler != null) 152 | streamRemoveHandler(originClientId); 153 | } 154 | 155 | void handleAnswer(originClientId, answer) { 156 | log("Got answer from ${originClientId}"); 157 | var sendingRtcPeerConnection = sendingRtcPeerConnections[originClientId]; 158 | sendingRtcPeerConnection.setRemoteDescription(new RtcSessionDescription(answer)); 159 | } 160 | 161 | void handleReceiverCandidate(originClientId, candidate) { 162 | var rtcIceCandidate = new RtcIceCandidate({"sdpMLineIndex": candidate["sdpMLineIndex"], "candidate": candidate["candidate"]}); 163 | log("handleReceiverCandidate: originClientId=${originClientId}"); 164 | var sendingRtcPeerConnection = sendingRtcPeerConnections[originClientId]; 165 | sendingRtcPeerConnection.addIceCandidate(rtcIceCandidate, handleSuccess, handleError); 166 | } 167 | 168 | void handleSenderCandidate(originClientId, candidate) { 169 | var rtcIceCandidate = new RtcIceCandidate({"sdpMLineIndex": candidate["sdpMLineIndex"], "candidate": candidate["candidate"]}); 170 | log("handleSenderCandidate: originClientId=${originClientId}"); 171 | var receivingRtcPeerConnection = receivingRtcPeerConnections[originClientId]; 172 | receivingRtcPeerConnection.addIceCandidate(rtcIceCandidate, handleSuccess, handleError); 173 | } 174 | 175 | void setStreamAddHandler(var handler) { 176 | streamAddHandler = handler; 177 | } 178 | 179 | void setStreamRemoveHandler(var handler) { 180 | streamRemoveHandler = handler; 181 | } 182 | 183 | void handleSuccess() { 184 | log("Success"); 185 | } 186 | 187 | void handleError(errorMessage) { 188 | log("Error: $errorMessage"); 189 | } 190 | 191 | void log(message) { 192 | print(message); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /lib/websockethandler.dart: -------------------------------------------------------------------------------- 1 | library websockethandler; 2 | 3 | import "dart:io"; 4 | import "dart:async"; 5 | import "dart:convert"; 6 | 7 | class WebSocketHandler { 8 | StreamController streamController; 9 | Set sockets; 10 | 11 | WebSocketHandler() { 12 | sockets = new Set(); 13 | streamController = new StreamController(); 14 | streamController.stream.transform(new WebSocketTransformer()).listen(handleWebSocketConnection); 15 | } 16 | 17 | void handleRequest(HttpRequest request) { 18 | streamController.add(request); 19 | } 20 | 21 | void handleWebSocketConnection(WebSocket socket) { 22 | socket.listen((data) { 23 | handleData(socket, data); 24 | }, onDone: () { 25 | sendClientRemove(socket); 26 | }, onError: (error) { 27 | sendClientRemove(socket); 28 | }); 29 | sockets.add(socket); 30 | sendClientIds(socket); 31 | sendClientAdd(socket); 32 | } 33 | 34 | void sendClientIds(WebSocket socket) { 35 | var clientIds = getClientIdsExcl(socket); 36 | var data = JSON.encode({"type": "clientIds", "content": clientIds}); 37 | sendMessage(socket, data); 38 | } 39 | 40 | void sendClientAdd(WebSocket socket) { 41 | var data = JSON.encode({"type": "clientAdd", "content": getClientId(socket)}); 42 | broadcastMessageExcl(socket, data); 43 | } 44 | 45 | void sendClientRemove(WebSocket socket) { 46 | var data = JSON.encode({"type": "clientRemove", "content": getClientId(socket)}); 47 | broadcastMessage(data); 48 | sockets.remove(socket); 49 | } 50 | 51 | void handleData(WebSocket socket, String data) { 52 | var parsedData = JSON.decode(data); 53 | var targetClientId = parsedData["targetClientId"]; 54 | if(!isClientAvailable(targetClientId)) return; 55 | parsedData["originClientId"] = getClientId(socket); 56 | var targetSocket = getSocketFromClientId(targetClientId); 57 | sendMessage(targetSocket, JSON.encode(parsedData)); 58 | } 59 | 60 | bool isClientAvailable(clientId) { 61 | try { 62 | sockets.firstWhere((socket) => getClientId(socket) == clientId); 63 | return true; 64 | } catch (e) { 65 | return false; 66 | } 67 | } 68 | 69 | WebSocket getSocketFromClientId(clientId) { 70 | return sockets.firstWhere((socket) => getClientId(socket) == clientId); 71 | } 72 | 73 | int getClientId(socket) { 74 | return socket.hashCode; 75 | } 76 | 77 | List getClientIdsExcl(excludedSocket) { 78 | return sockets.where((socket) => socket != excludedSocket).map((socket) => getClientId(socket)).toList(); 79 | } 80 | 81 | void broadcastMessage(message) { 82 | broadcastMessageExcl(null, message); 83 | } 84 | 85 | void broadcastMessageExcl(WebSocket excludedSocket, message) { 86 | sockets.forEach((socket) { 87 | if(excludedSocket == socket) 88 | return; 89 | sendMessage(socket, message); 90 | }); 91 | } 92 | 93 | void sendMessage(socket, message) { 94 | // Send a message on the event loop at the next opportunity 95 | new Future.delayed(Duration.ZERO, () => socket.add(message)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart_webrtc 2 | 3 | dependencies: 4 | browser: any 5 | logging: any 6 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nevers/dart-webrtc/6ddb699b5ffe2e72dbdfb9a186e8241f72a6d71f/screenshot.png -------------------------------------------------------------------------------- /web/animate.min.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";body{-webkit-backface-visibility:hidden;}.animated{-webkit-animation-duration:1s;-moz-animation-duration:1s;-o-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;-moz-animation-fill-mode:both;-o-animation-fill-mode:both;animation-fill-mode:both;}.animated.hinge{-webkit-animation-duration:2s;-moz-animation-duration:2s;-o-animation-duration:2s;animation-duration:2s;}@-webkit-keyframes flash{0%,50%,100%{opacity:1;}25%,75%{opacity:0;}}@-moz-keyframes flash{0%,50%,100%{opacity:1;}25%,75%{opacity:0;}}@-o-keyframes flash{0%,50%,100%{opacity:1;}25%,75%{opacity:0;}}@keyframes flash{0%,50%,100%{opacity:1;}25%,75%{opacity:0;}}.animated.flash{-webkit-animation-name:flash;-moz-animation-name:flash;-o-animation-name:flash;animation-name:flash;}@-webkit-keyframes shake{0%,100%{-webkit-transform:translateX(0);}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);}20%,40%,60%,80%{-webkit-transform:translateX(10px);}}@-moz-keyframes shake{0%,100%{-moz-transform:translateX(0);}10%,30%,50%,70%,90%{-moz-transform:translateX(-10px);}20%,40%,60%,80%{-moz-transform:translateX(10px);}}@-o-keyframes shake{0%,100%{-o-transform:translateX(0);}10%,30%,50%,70%,90%{-o-transform:translateX(-10px);}20%,40%,60%,80%{-o-transform:translateX(10px);}}@keyframes shake{0%,100%{transform:translateX(0);}10%,30%,50%,70%,90%{transform:translateX(-10px);}20%,40%,60%,80%{transform:translateX(10px);}}.animated.shake{-webkit-animation-name:shake;-moz-animation-name:shake;-o-animation-name:shake;animation-name:shake;}@-webkit-keyframes bounce{0%,20%,50%,80%,100%{-webkit-transform:translateY(0);}40%{-webkit-transform:translateY(-30px);}60%{-webkit-transform:translateY(-15px);}}@-moz-keyframes bounce{0%,20%,50%,80%,100%{-moz-transform:translateY(0);}40%{-moz-transform:translateY(-30px);}60%{-moz-transform:translateY(-15px);}}@-o-keyframes bounce{0%,20%,50%,80%,100%{-o-transform:translateY(0);}40%{-o-transform:translateY(-30px);}60%{-o-transform:translateY(-15px);}}@keyframes bounce{0%,20%,50%,80%,100%{transform:translateY(0);}40%{transform:translateY(-30px);}60%{transform:translateY(-15px);}}.animated.bounce{-webkit-animation-name:bounce;-moz-animation-name:bounce;-o-animation-name:bounce;animation-name:bounce;}@-webkit-keyframes tada{0%{-webkit-transform:scale(1);}10%,20%{-webkit-transform:scale(0.9) rotate(-3deg);}30%,50%,70%,90%{-webkit-transform:scale(1.1) rotate(3deg);}40%,60%,80%{-webkit-transform:scale(1.1) rotate(-3deg);}100%{-webkit-transform:scale(1) rotate(0);}}@-moz-keyframes tada{0%{-moz-transform:scale(1);}10%,20%{-moz-transform:scale(0.9) rotate(-3deg);}30%,50%,70%,90%{-moz-transform:scale(1.1) rotate(3deg);}40%,60%,80%{-moz-transform:scale(1.1) rotate(-3deg);}100%{-moz-transform:scale(1) rotate(0);}}@-o-keyframes tada{0%{-o-transform:scale(1);}10%,20%{-o-transform:scale(0.9) rotate(-3deg);}30%,50%,70%,90%{-o-transform:scale(1.1) rotate(3deg);}40%,60%,80%{-o-transform:scale(1.1) rotate(-3deg);}100%{-o-transform:scale(1) rotate(0);}}@keyframes tada{0%{transform:scale(1);}10%,20%{transform:scale(0.9) rotate(-3deg);}30%,50%,70%,90%{transform:scale(1.1) rotate(3deg);}40%,60%,80%{transform:scale(1.1) rotate(-3deg);}100%{transform:scale(1) rotate(0);}}.animated.tada{-webkit-animation-name:tada;-moz-animation-name:tada;-o-animation-name:tada;animation-name:tada;}@-webkit-keyframes swing{20%,40%,60%,80%,100%{-webkit-transform-origin:top center;}20%{-webkit-transform:rotate(15deg);}40%{-webkit-transform:rotate(-10deg);}60%{-webkit-transform:rotate(5deg);}80%{-webkit-transform:rotate(-5deg);}100%{-webkit-transform:rotate(0deg);}}@-moz-keyframes swing{20%{-moz-transform:rotate(15deg);}40%{-moz-transform:rotate(-10deg);}60%{-moz-transform:rotate(5deg);}80%{-moz-transform:rotate(-5deg);}100%{-moz-transform:rotate(0deg);}}@-o-keyframes swing{20%{-o-transform:rotate(15deg);}40%{-o-transform:rotate(-10deg);}60%{-o-transform:rotate(5deg);}80%{-o-transform:rotate(-5deg);}100%{-o-transform:rotate(0deg);}}@keyframes swing{20%{transform:rotate(15deg);}40%{transform:rotate(-10deg);}60%{transform:rotate(5deg);}80%{transform:rotate(-5deg);}100%{transform:rotate(0deg);}}.animated.swing{-webkit-transform-origin:top center;-moz-transform-origin:top center;-o-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;-moz-animation-name:swing;-o-animation-name:swing;animation-name:swing;}@-webkit-keyframes wobble{0%{-webkit-transform:translateX(0%);}15%{-webkit-transform:translateX(-25%) rotate(-5deg);}30%{-webkit-transform:translateX(20%) rotate(3deg);}45%{-webkit-transform:translateX(-15%) rotate(-3deg);}60%{-webkit-transform:translateX(10%) rotate(2deg);}75%{-webkit-transform:translateX(-5%) rotate(-1deg);}100%{-webkit-transform:translateX(0%);}}@-moz-keyframes wobble{0%{-moz-transform:translateX(0%);}15%{-moz-transform:translateX(-25%) rotate(-5deg);}30%{-moz-transform:translateX(20%) rotate(3deg);}45%{-moz-transform:translateX(-15%) rotate(-3deg);}60%{-moz-transform:translateX(10%) rotate(2deg);}75%{-moz-transform:translateX(-5%) rotate(-1deg);}100%{-moz-transform:translateX(0%);}}@-o-keyframes wobble{0%{-o-transform:translateX(0%);}15%{-o-transform:translateX(-25%) rotate(-5deg);}30%{-o-transform:translateX(20%) rotate(3deg);}45%{-o-transform:translateX(-15%) rotate(-3deg);}60%{-o-transform:translateX(10%) rotate(2deg);}75%{-o-transform:translateX(-5%) rotate(-1deg);}100%{-o-transform:translateX(0%);}}@keyframes wobble{0%{transform:translateX(0%);}15%{transform:translateX(-25%) rotate(-5deg);}30%{transform:translateX(20%) rotate(3deg);}45%{transform:translateX(-15%) rotate(-3deg);}60%{transform:translateX(10%) rotate(2deg);}75%{transform:translateX(-5%) rotate(-1deg);}100%{transform:translateX(0%);}}.animated.wobble{-webkit-animation-name:wobble;-moz-animation-name:wobble;-o-animation-name:wobble;animation-name:wobble;}@-webkit-keyframes pulse{0%{-webkit-transform:scale(1);}50%{-webkit-transform:scale(1.1);}100%{-webkit-transform:scale(1);}}@-moz-keyframes pulse{0%{-moz-transform:scale(1);}50%{-moz-transform:scale(1.1);}100%{-moz-transform:scale(1);}}@-o-keyframes pulse{0%{-o-transform:scale(1);}50%{-o-transform:scale(1.1);}100%{-o-transform:scale(1);}}@keyframes pulse{0%{transform:scale(1);}50%{transform:scale(1.1);}100%{transform:scale(1);}}.animated.pulse{-webkit-animation-name:pulse;-moz-animation-name:pulse;-o-animation-name:pulse;animation-name:pulse;}@-webkit-keyframes flip{0%{-webkit-transform:perspective(400px) translateZ(0) rotateY(0) scale(1);-webkit-animation-timing-function:ease-out;}40%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(170deg) scale(1);-webkit-animation-timing-function:ease-out;}50%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(190deg) scale(1);-webkit-animation-timing-function:ease-in;}80%{-webkit-transform:perspective(400px) translateZ(0) rotateY(360deg) scale(.95);-webkit-animation-timing-function:ease-in;}100%{-webkit-transform:perspective(400px) translateZ(0) rotateY(360deg) scale(1);-webkit-animation-timing-function:ease-in;}}@-moz-keyframes flip{0%{-moz-transform:perspective(400px) translateZ(0) rotateY(0) scale(1);-moz-animation-timing-function:ease-out;}40%{-moz-transform:perspective(400px) translateZ(150px) rotateY(170deg) scale(1);-moz-animation-timing-function:ease-out;}50%{-moz-transform:perspective(400px) translateZ(150px) rotateY(190deg) scale(1);-moz-animation-timing-function:ease-in;}80%{-moz-transform:perspective(400px) translateZ(0) rotateY(360deg) scale(.95);-moz-animation-timing-function:ease-in;}100%{-moz-transform:perspective(400px) translateZ(0) rotateY(360deg) scale(1);-moz-animation-timing-function:ease-in;}}@-o-keyframes flip{0%{-o-transform:perspective(400px) translateZ(0) rotateY(0) scale(1);-o-animation-timing-function:ease-out;}40%{-o-transform:perspective(400px) translateZ(150px) rotateY(170deg) scale(1);-o-animation-timing-function:ease-out;}50%{-o-transform:perspective(400px) translateZ(150px) rotateY(190deg) scale(1);-o-animation-timing-function:ease-in;}80%{-o-transform:perspective(400px) translateZ(0) rotateY(360deg) scale(.95);-o-animation-timing-function:ease-in;}100%{-o-transform:perspective(400px) translateZ(0) rotateY(360deg) scale(1);-o-animation-timing-function:ease-in;}}@keyframes flip{0%{transform:perspective(400px) translateZ(0) rotateY(0) scale(1);animation-timing-function:ease-out;}40%{transform:perspective(400px) translateZ(150px) rotateY(170deg) scale(1);animation-timing-function:ease-out;}50%{transform:perspective(400px) translateZ(150px) rotateY(190deg) scale(1);animation-timing-function:ease-in;}80%{transform:perspective(400px) translateZ(0) rotateY(360deg) scale(.95);animation-timing-function:ease-in;}100%{transform:perspective(400px) translateZ(0) rotateY(360deg) scale(1);animation-timing-function:ease-in;}}.animated.flip{-webkit-backface-visibility:visible !important;-webkit-animation-name:flip;-moz-backface-visibility:visible !important;-moz-animation-name:flip;-o-backface-visibility:visible !important;-o-animation-name:flip;backface-visibility:visible !important;animation-name:flip;}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);opacity:0;}40%{-webkit-transform:perspective(400px) rotateX(-10deg);}70%{-webkit-transform:perspective(400px) rotateX(10deg);}100%{-webkit-transform:perspective(400px) rotateX(0deg);opacity:1;}}@-moz-keyframes flipInX{0%{-moz-transform:perspective(400px) rotateX(90deg);opacity:0;}40%{-moz-transform:perspective(400px) rotateX(-10deg);}70%{-moz-transform:perspective(400px) rotateX(10deg);}100%{-moz-transform:perspective(400px) rotateX(0deg);opacity:1;}}@-o-keyframes flipInX{0%{-o-transform:perspective(400px) rotateX(90deg);opacity:0;}40%{-o-transform:perspective(400px) rotateX(-10deg);}70%{-o-transform:perspective(400px) rotateX(10deg);}100%{-o-transform:perspective(400px) rotateX(0deg);opacity:1;}}@keyframes flipInX{0%{transform:perspective(400px) rotateX(90deg);opacity:0;}40%{transform:perspective(400px) rotateX(-10deg);}70%{transform:perspective(400px) rotateX(10deg);}100%{transform:perspective(400px) rotateX(0deg);opacity:1;}}.animated.flipInX{-webkit-backface-visibility:visible !important;-webkit-animation-name:flipInX;-moz-backface-visibility:visible !important;-moz-animation-name:flipInX;-o-backface-visibility:visible !important;-o-animation-name:flipInX;backface-visibility:visible !important;animation-name:flipInX;}@-webkit-keyframes flipOutX{0%{-webkit-transform:perspective(400px) rotateX(0deg);opacity:1;}100%{-webkit-transform:perspective(400px) rotateX(90deg);opacity:0;}}@-moz-keyframes flipOutX{0%{-moz-transform:perspective(400px) rotateX(0deg);opacity:1;}100%{-moz-transform:perspective(400px) rotateX(90deg);opacity:0;}}@-o-keyframes flipOutX{0%{-o-transform:perspective(400px) rotateX(0deg);opacity:1;}100%{-o-transform:perspective(400px) rotateX(90deg);opacity:0;}}@keyframes flipOutX{0%{transform:perspective(400px) rotateX(0deg);opacity:1;}100%{transform:perspective(400px) rotateX(90deg);opacity:0;}}.animated.flipOutX{-webkit-animation-name:flipOutX;-webkit-backface-visibility:visible !important;-moz-animation-name:flipOutX;-moz-backface-visibility:visible !important;-o-animation-name:flipOutX;-o-backface-visibility:visible !important;animation-name:flipOutX;backface-visibility:visible !important;}@-webkit-keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);opacity:0;}40%{-webkit-transform:perspective(400px) rotateY(-10deg);}70%{-webkit-transform:perspective(400px) rotateY(10deg);}100%{-webkit-transform:perspective(400px) rotateY(0deg);opacity:1;}}@-moz-keyframes flipInY{0%{-moz-transform:perspective(400px) rotateY(90deg);opacity:0;}40%{-moz-transform:perspective(400px) rotateY(-10deg);}70%{-moz-transform:perspective(400px) rotateY(10deg);}100%{-moz-transform:perspective(400px) rotateY(0deg);opacity:1;}}@-o-keyframes flipInY{0%{-o-transform:perspective(400px) rotateY(90deg);opacity:0;}40%{-o-transform:perspective(400px) rotateY(-10deg);}70%{-o-transform:perspective(400px) rotateY(10deg);}100%{-o-transform:perspective(400px) rotateY(0deg);opacity:1;}}@keyframes flipInY{0%{transform:perspective(400px) rotateY(90deg);opacity:0;}40%{transform:perspective(400px) rotateY(-10deg);}70%{transform:perspective(400px) rotateY(10deg);}100%{transform:perspective(400px) rotateY(0deg);opacity:1;}}.animated.flipInY{-webkit-backface-visibility:visible !important;-webkit-animation-name:flipInY;-moz-backface-visibility:visible !important;-moz-animation-name:flipInY;-o-backface-visibility:visible !important;-o-animation-name:flipInY;backface-visibility:visible !important;animation-name:flipInY;}@-webkit-keyframes flipOutY{0%{-webkit-transform:perspective(400px) rotateY(0deg);opacity:1;}100%{-webkit-transform:perspective(400px) rotateY(90deg);opacity:0;}}@-moz-keyframes flipOutY{0%{-moz-transform:perspective(400px) rotateY(0deg);opacity:1;}100%{-moz-transform:perspective(400px) rotateY(90deg);opacity:0;}}@-o-keyframes flipOutY{0%{-o-transform:perspective(400px) rotateY(0deg);opacity:1;}100%{-o-transform:perspective(400px) rotateY(90deg);opacity:0;}}@keyframes flipOutY{0%{transform:perspective(400px) rotateY(0deg);opacity:1;}100%{transform:perspective(400px) rotateY(90deg);opacity:0;}}.animated.flipOutY{-webkit-backface-visibility:visible !important;-webkit-animation-name:flipOutY;-moz-backface-visibility:visible !important;-moz-animation-name:flipOutY;-o-backface-visibility:visible !important;-o-animation-name:flipOutY;backface-visibility:visible !important;animation-name:flipOutY;}@-webkit-keyframes fadeIn{0%{opacity:0;}100%{opacity:1;}}@-moz-keyframes fadeIn{0%{opacity:0;}100%{opacity:1;}}@-o-keyframes fadeIn{0%{opacity:0;}100%{opacity:1;}}@keyframes fadeIn{0%{opacity:0;}100%{opacity:1;}}.animated.fadeIn{-webkit-animation-name:fadeIn;-moz-animation-name:fadeIn;-o-animation-name:fadeIn;animation-name:fadeIn;}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translateY(20px);}100%{opacity:1;-webkit-transform:translateY(0);}}@-moz-keyframes fadeInUp{0%{opacity:0;-moz-transform:translateY(20px);}100%{opacity:1;-moz-transform:translateY(0);}}@-o-keyframes fadeInUp{0%{opacity:0;-o-transform:translateY(20px);}100%{opacity:1;-o-transform:translateY(0);}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px);}100%{opacity:1;transform:translateY(0);}}.animated.fadeInUp{-webkit-animation-name:fadeInUp;-moz-animation-name:fadeInUp;-o-animation-name:fadeInUp;animation-name:fadeInUp;}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translateY(-20px);}100%{opacity:1;-webkit-transform:translateY(0);}}@-moz-keyframes fadeInDown{0%{opacity:0;-moz-transform:translateY(-20px);}100%{opacity:1;-moz-transform:translateY(0);}}@-o-keyframes fadeInDown{0%{opacity:0;-o-transform:translateY(-20px);}100%{opacity:1;-o-transform:translateY(0);}}@keyframes fadeInDown{0%{opacity:0;transform:translateY(-20px);}100%{opacity:1;transform:translateY(0);}}.animated.fadeInDown{-webkit-animation-name:fadeInDown;-moz-animation-name:fadeInDown;-o-animation-name:fadeInDown;animation-name:fadeInDown;}@-webkit-keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translateX(-20px);}100%{opacity:1;-webkit-transform:translateX(0);}}@-moz-keyframes fadeInLeft{0%{opacity:0;-moz-transform:translateX(-20px);}100%{opacity:1;-moz-transform:translateX(0);}}@-o-keyframes fadeInLeft{0%{opacity:0;-o-transform:translateX(-20px);}100%{opacity:1;-o-transform:translateX(0);}}@keyframes fadeInLeft{0%{opacity:0;transform:translateX(-20px);}100%{opacity:1;transform:translateX(0);}}.animated.fadeInLeft{-webkit-animation-name:fadeInLeft;-moz-animation-name:fadeInLeft;-o-animation-name:fadeInLeft;animation-name:fadeInLeft;}@-webkit-keyframes fadeInRight{0%{opacity:0;-webkit-transform:translateX(20px);}100%{opacity:1;-webkit-transform:translateX(0);}}@-moz-keyframes fadeInRight{0%{opacity:0;-moz-transform:translateX(20px);}100%{opacity:1;-moz-transform:translateX(0);}}@-o-keyframes fadeInRight{0%{opacity:0;-o-transform:translateX(20px);}100%{opacity:1;-o-transform:translateX(0);}}@keyframes fadeInRight{0%{opacity:0;transform:translateX(20px);}100%{opacity:1;transform:translateX(0);}}.animated.fadeInRight{-webkit-animation-name:fadeInRight;-moz-animation-name:fadeInRight;-o-animation-name:fadeInRight;animation-name:fadeInRight;}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);}100%{opacity:1;-webkit-transform:translateY(0);}}@-moz-keyframes fadeInUpBig{0%{opacity:0;-moz-transform:translateY(2000px);}100%{opacity:1;-moz-transform:translateY(0);}}@-o-keyframes fadeInUpBig{0%{opacity:0;-o-transform:translateY(2000px);}100%{opacity:1;-o-transform:translateY(0);}}@keyframes fadeInUpBig{0%{opacity:0;transform:translateY(2000px);}100%{opacity:1;transform:translateY(0);}}.animated.fadeInUpBig{-webkit-animation-name:fadeInUpBig;-moz-animation-name:fadeInUpBig;-o-animation-name:fadeInUpBig;animation-name:fadeInUpBig;}@-webkit-keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translateY(-2000px);}100%{opacity:1;-webkit-transform:translateY(0);}}@-moz-keyframes fadeInDownBig{0%{opacity:0;-moz-transform:translateY(-2000px);}100%{opacity:1;-moz-transform:translateY(0);}}@-o-keyframes fadeInDownBig{0%{opacity:0;-o-transform:translateY(-2000px);}100%{opacity:1;-o-transform:translateY(0);}}@keyframes fadeInDownBig{0%{opacity:0;transform:translateY(-2000px);}100%{opacity:1;transform:translateY(0);}}.animated.fadeInDownBig{-webkit-animation-name:fadeInDownBig;-moz-animation-name:fadeInDownBig;-o-animation-name:fadeInDownBig;animation-name:fadeInDownBig;}@-webkit-keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translateX(-2000px);}100%{opacity:1;-webkit-transform:translateX(0);}}@-moz-keyframes fadeInLeftBig{0%{opacity:0;-moz-transform:translateX(-2000px);}100%{opacity:1;-moz-transform:translateX(0);}}@-o-keyframes fadeInLeftBig{0%{opacity:0;-o-transform:translateX(-2000px);}100%{opacity:1;-o-transform:translateX(0);}}@keyframes fadeInLeftBig{0%{opacity:0;transform:translateX(-2000px);}100%{opacity:1;transform:translateX(0);}}.animated.fadeInLeftBig{-webkit-animation-name:fadeInLeftBig;-moz-animation-name:fadeInLeftBig;-o-animation-name:fadeInLeftBig;animation-name:fadeInLeftBig;}@-webkit-keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translateX(2000px);}100%{opacity:1;-webkit-transform:translateX(0);}}@-moz-keyframes fadeInRightBig{0%{opacity:0;-moz-transform:translateX(2000px);}100%{opacity:1;-moz-transform:translateX(0);}}@-o-keyframes fadeInRightBig{0%{opacity:0;-o-transform:translateX(2000px);}100%{opacity:1;-o-transform:translateX(0);}}@keyframes fadeInRightBig{0%{opacity:0;transform:translateX(2000px);}100%{opacity:1;transform:translateX(0);}}.animated.fadeInRightBig{-webkit-animation-name:fadeInRightBig;-moz-animation-name:fadeInRightBig;-o-animation-name:fadeInRightBig;animation-name:fadeInRightBig;}@-webkit-keyframes fadeOut{0%{opacity:1;}100%{opacity:0;}}@-moz-keyframes fadeOut{0%{opacity:1;}100%{opacity:0;}}@-o-keyframes fadeOut{0%{opacity:1;}100%{opacity:0;}}@keyframes fadeOut{0%{opacity:1;}100%{opacity:0;}}.animated.fadeOut{-webkit-animation-name:fadeOut;-moz-animation-name:fadeOut;-o-animation-name:fadeOut;animation-name:fadeOut;}@-webkit-keyframes fadeOutUp{0%{opacity:1;-webkit-transform:translateY(0);}100%{opacity:0;-webkit-transform:translateY(-20px);}}@-moz-keyframes fadeOutUp{0%{opacity:1;-moz-transform:translateY(0);}100%{opacity:0;-moz-transform:translateY(-20px);}}@-o-keyframes fadeOutUp{0%{opacity:1;-o-transform:translateY(0);}100%{opacity:0;-o-transform:translateY(-20px);}}@keyframes fadeOutUp{0%{opacity:1;transform:translateY(0);}100%{opacity:0;transform:translateY(-20px);}}.animated.fadeOutUp{-webkit-animation-name:fadeOutUp;-moz-animation-name:fadeOutUp;-o-animation-name:fadeOutUp;animation-name:fadeOutUp;}@-webkit-keyframes fadeOutDown{0%{opacity:1;-webkit-transform:translateY(0);}100%{opacity:0;-webkit-transform:translateY(20px);}}@-moz-keyframes fadeOutDown{0%{opacity:1;-moz-transform:translateY(0);}100%{opacity:0;-moz-transform:translateY(20px);}}@-o-keyframes fadeOutDown{0%{opacity:1;-o-transform:translateY(0);}100%{opacity:0;-o-transform:translateY(20px);}}@keyframes fadeOutDown{0%{opacity:1;transform:translateY(0);}100%{opacity:0;transform:translateY(20px);}}.animated.fadeOutDown{-webkit-animation-name:fadeOutDown;-moz-animation-name:fadeOutDown;-o-animation-name:fadeOutDown;animation-name:fadeOutDown;}@-webkit-keyframes fadeOutLeft{0%{opacity:1;-webkit-transform:translateX(0);}100%{opacity:0;-webkit-transform:translateX(-20px);}}@-moz-keyframes fadeOutLeft{0%{opacity:1;-moz-transform:translateX(0);}100%{opacity:0;-moz-transform:translateX(-20px);}}@-o-keyframes fadeOutLeft{0%{opacity:1;-o-transform:translateX(0);}100%{opacity:0;-o-transform:translateX(-20px);}}@keyframes fadeOutLeft{0%{opacity:1;transform:translateX(0);}100%{opacity:0;transform:translateX(-20px);}}.animated.fadeOutLeft{-webkit-animation-name:fadeOutLeft;-moz-animation-name:fadeOutLeft;-o-animation-name:fadeOutLeft;animation-name:fadeOutLeft;}@-webkit-keyframes fadeOutRight{0%{opacity:1;-webkit-transform:translateX(0);}100%{opacity:0;-webkit-transform:translateX(20px);}}@-moz-keyframes fadeOutRight{0%{opacity:1;-moz-transform:translateX(0);}100%{opacity:0;-moz-transform:translateX(20px);}}@-o-keyframes fadeOutRight{0%{opacity:1;-o-transform:translateX(0);}100%{opacity:0;-o-transform:translateX(20px);}}@keyframes fadeOutRight{0%{opacity:1;transform:translateX(0);}100%{opacity:0;transform:translateX(20px);}}.animated.fadeOutRight{-webkit-animation-name:fadeOutRight;-moz-animation-name:fadeOutRight;-o-animation-name:fadeOutRight;animation-name:fadeOutRight;}@-webkit-keyframes fadeOutUpBig{0%{opacity:1;-webkit-transform:translateY(0);}100%{opacity:0;-webkit-transform:translateY(-2000px);}}@-moz-keyframes fadeOutUpBig{0%{opacity:1;-moz-transform:translateY(0);}100%{opacity:0;-moz-transform:translateY(-2000px);}}@-o-keyframes fadeOutUpBig{0%{opacity:1;-o-transform:translateY(0);}100%{opacity:0;-o-transform:translateY(-2000px);}}@keyframes fadeOutUpBig{0%{opacity:1;transform:translateY(0);}100%{opacity:0;transform:translateY(-2000px);}}.animated.fadeOutUpBig{-webkit-animation-name:fadeOutUpBig;-moz-animation-name:fadeOutUpBig;-o-animation-name:fadeOutUpBig;animation-name:fadeOutUpBig;}@-webkit-keyframes fadeOutDownBig{0%{opacity:1;-webkit-transform:translateY(0);}100%{opacity:0;-webkit-transform:translateY(2000px);}}@-moz-keyframes fadeOutDownBig{0%{opacity:1;-moz-transform:translateY(0);}100%{opacity:0;-moz-transform:translateY(2000px);}}@-o-keyframes fadeOutDownBig{0%{opacity:1;-o-transform:translateY(0);}100%{opacity:0;-o-transform:translateY(2000px);}}@keyframes fadeOutDownBig{0%{opacity:1;transform:translateY(0);}100%{opacity:0;transform:translateY(2000px);}}.animated.fadeOutDownBig{-webkit-animation-name:fadeOutDownBig;-moz-animation-name:fadeOutDownBig;-o-animation-name:fadeOutDownBig;animation-name:fadeOutDownBig;}@-webkit-keyframes fadeOutLeftBig{0%{opacity:1;-webkit-transform:translateX(0);}100%{opacity:0;-webkit-transform:translateX(-2000px);}}@-moz-keyframes fadeOutLeftBig{0%{opacity:1;-moz-transform:translateX(0);}100%{opacity:0;-moz-transform:translateX(-2000px);}}@-o-keyframes fadeOutLeftBig{0%{opacity:1;-o-transform:translateX(0);}100%{opacity:0;-o-transform:translateX(-2000px);}}@keyframes fadeOutLeftBig{0%{opacity:1;transform:translateX(0);}100%{opacity:0;transform:translateX(-2000px);}}.animated.fadeOutLeftBig{-webkit-animation-name:fadeOutLeftBig;-moz-animation-name:fadeOutLeftBig;-o-animation-name:fadeOutLeftBig;animation-name:fadeOutLeftBig;}@-webkit-keyframes fadeOutRightBig{0%{opacity:1;-webkit-transform:translateX(0);}100%{opacity:0;-webkit-transform:translateX(2000px);}}@-moz-keyframes fadeOutRightBig{0%{opacity:1;-moz-transform:translateX(0);}100%{opacity:0;-moz-transform:translateX(2000px);}}@-o-keyframes fadeOutRightBig{0%{opacity:1;-o-transform:translateX(0);}100%{opacity:0;-o-transform:translateX(2000px);}}@keyframes fadeOutRightBig{0%{opacity:1;transform:translateX(0);}100%{opacity:0;transform:translateX(2000px);}}.animated.fadeOutRightBig{-webkit-animation-name:fadeOutRightBig;-moz-animation-name:fadeOutRightBig;-o-animation-name:fadeOutRightBig;animation-name:fadeOutRightBig;}@-webkit-keyframes bounceIn{0%{opacity:0;-webkit-transform:scale(.3);}50%{opacity:1;-webkit-transform:scale(1.05);}70%{-webkit-transform:scale(.9);}100%{-webkit-transform:scale(1);}}@-moz-keyframes bounceIn{0%{opacity:0;-moz-transform:scale(.3);}50%{opacity:1;-moz-transform:scale(1.05);}70%{-moz-transform:scale(.9);}100%{-moz-transform:scale(1);}}@-o-keyframes bounceIn{0%{opacity:0;-o-transform:scale(.3);}50%{opacity:1;-o-transform:scale(1.05);}70%{-o-transform:scale(.9);}100%{-o-transform:scale(1);}}@keyframes bounceIn{0%{opacity:0;transform:scale(.3);}50%{opacity:1;transform:scale(1.05);}70%{transform:scale(.9);}100%{transform:scale(1);}}@-webkit-keyframes slideInDown{0%{opacity:0;-webkit-transform:translateY(-2000px);}100%{-webkit-transform:translateY(0);}}@-moz-keyframes slideInDown{0%{opacity:0;-moz-transform:translateY(-2000px);}100%{-moz-transform:translateY(0);}}@-o-keyframes slideInDown{0%{opacity:0;-o-transform:translateY(-2000px);}100%{-o-transform:translateY(0);}}@keyframes slideInDown{0%{opacity:0;transform:translateY(-2000px);}100%{transform:translateY(0);}}.slideInDown{-webkit-animation-name:slideInDown;-moz-animation-name:slideInDown;-o-animation-name:slideInDown;animation-name:slideInDown;}@-webkit-keyframes slideInLeft{0%{opacity:0;-webkit-transform:translateX(-2000px);}100%{-webkit-transform:translateX(0);}}@-moz-keyframes slideInLeft{0%{opacity:0;-moz-transform:translateX(-2000px);}100%{-moz-transform:translateX(0);}}@-o-keyframes slideInLeft{0%{opacity:0;-o-transform:translateX(-2000px);}100%{-o-transform:translateX(0);}}@keyframes slideInLeft{0%{opacity:0;transform:translateX(-2000px);}100%{transform:translateX(0);}}.slideInLeft{-webkit-animation-name:slideInLeft;-moz-animation-name:slideInLeft;-o-animation-name:slideInLeft;animation-name:slideInLeft;}@-webkit-keyframes slideInRight{0%{opacity:0;-webkit-transform:translateX(2000px);}100%{-webkit-transform:translateX(0);}}@-moz-keyframes slideInRight{0%{opacity:0;-moz-transform:translateX(2000px);}100%{-moz-transform:translateX(0);}}@-o-keyframes slideInRight{0%{opacity:0;-o-transform:translateX(2000px);}100%{-o-transform:translateX(0);}}@keyframes slideInRight{0%{opacity:0;transform:translateX(2000px);}100%{transform:translateX(0);}}.slideInRight{-webkit-animation-name:slideInRight;-moz-animation-name:slideInRight;-o-animation-name:slideInRight;animation-name:slideInRight;}@-webkit-keyframes slideOutLeft{0%{-webkit-transform:translateX(0);}100%{opacity:0;-webkit-transform:translateX(-2000px);}}@-moz-keyframes slideOutLeft{0%{-moz-transform:translateX(0);}100%{opacity:0;-moz-transform:translateX(-2000px);}}@-o-keyframes slideOutLeft{0%{-o-transform:translateX(0);}100%{opacity:0;-o-transform:translateX(-2000px);}}@keyframes slideOutLeft{0%{transform:translateX(0);}100%{opacity:0;transform:translateX(-2000px);}}.slideOutLeft{-webkit-animation-name:slideOutLeft;-moz-animation-name:slideOutLeft;-o-animation-name:slideOutLeft;animation-name:slideOutLeft;}@-webkit-keyframes slideOutRight{0%{-webkit-transform:translateX(0);}100%{opacity:0;-webkit-transform:translateX(2000px);}}@-moz-keyframes slideOutRight{0%{-moz-transform:translateX(0);}100%{opacity:0;-moz-transform:translateX(2000px);}}@-o-keyframes slideOutRight{0%{-o-transform:translateX(0);}100%{opacity:0;-o-transform:translateX(2000px);}}@keyframes slideOutRight{0%{transform:translateX(0);}100%{opacity:0;transform:translateX(2000px);}}.slideOutRight{-webkit-animation-name:slideOutRight;-moz-animation-name:slideOutRight;-o-animation-name:slideOutRight;animation-name:slideOutRight;}@-webkit-keyframes slideOutUp{0%{-webkit-transform:translateY(0);}100%{opacity:0;-webkit-transform:translateY(-2000px);}}@-moz-keyframes slideOutUp{0%{-moz-transform:translateY(0);}100%{opacity:0;-moz-transform:translateY(-2000px);}}@-o-keyframes slideOutUp{0%{-o-transform:translateY(0);}100%{opacity:0;-o-transform:translateY(-2000px);}}@keyframes slideOutUp{0%{transform:translateY(0);}100%{opacity:0;transform:translateY(-2000px);}}.slideOutUp{-webkit-animation-name:slideOutUp;-moz-animation-name:slideOutUp;-o-animation-name:slideOutUp;animation-name:slideOutUp;}.animated.bounceIn{-webkit-animation-name:bounceIn;-moz-animation-name:bounceIn;-o-animation-name:bounceIn;animation-name:bounceIn;}@-webkit-keyframes bounceInUp{0%{opacity:0;-webkit-transform:translateY(2000px);}60%{opacity:1;-webkit-transform:translateY(-30px);}80%{-webkit-transform:translateY(10px);}100%{-webkit-transform:translateY(0);}}@-moz-keyframes bounceInUp{0%{opacity:0;-moz-transform:translateY(2000px);}60%{opacity:1;-moz-transform:translateY(-30px);}80%{-moz-transform:translateY(10px);}100%{-moz-transform:translateY(0);}}@-o-keyframes bounceInUp{0%{opacity:0;-o-transform:translateY(2000px);}60%{opacity:1;-o-transform:translateY(-30px);}80%{-o-transform:translateY(10px);}100%{-o-transform:translateY(0);}}@keyframes bounceInUp{0%{opacity:0;transform:translateY(2000px);}60%{opacity:1;transform:translateY(-30px);}80%{transform:translateY(10px);}100%{transform:translateY(0);}}.animated.bounceInUp{-webkit-animation-name:bounceInUp;-moz-animation-name:bounceInUp;-o-animation-name:bounceInUp;animation-name:bounceInUp;}@-webkit-keyframes bounceInDown{0%{opacity:0;-webkit-transform:translateY(-2000px);}60%{opacity:1;-webkit-transform:translateY(30px);}80%{-webkit-transform:translateY(-10px);}100%{-webkit-transform:translateY(0);}}@-moz-keyframes bounceInDown{0%{opacity:0;-moz-transform:translateY(-2000px);}60%{opacity:1;-moz-transform:translateY(30px);}80%{-moz-transform:translateY(-10px);}100%{-moz-transform:translateY(0);}}@-o-keyframes bounceInDown{0%{opacity:0;-o-transform:translateY(-2000px);}60%{opacity:1;-o-transform:translateY(30px);}80%{-o-transform:translateY(-10px);}100%{-o-transform:translateY(0);}}@keyframes bounceInDown{0%{opacity:0;transform:translateY(-2000px);}60%{opacity:1;transform:translateY(30px);}80%{transform:translateY(-10px);}100%{transform:translateY(0);}}.animated.bounceInDown{-webkit-animation-name:bounceInDown;-moz-animation-name:bounceInDown;-o-animation-name:bounceInDown;animation-name:bounceInDown;}@-webkit-keyframes bounceInLeft{0%{opacity:0;-webkit-transform:translateX(-2000px);}60%{opacity:1;-webkit-transform:translateX(30px);}80%{-webkit-transform:translateX(-10px);}100%{-webkit-transform:translateX(0);}}@-moz-keyframes bounceInLeft{0%{opacity:0;-moz-transform:translateX(-2000px);}60%{opacity:1;-moz-transform:translateX(30px);}80%{-moz-transform:translateX(-10px);}100%{-moz-transform:translateX(0);}}@-o-keyframes bounceInLeft{0%{opacity:0;-o-transform:translateX(-2000px);}60%{opacity:1;-o-transform:translateX(30px);}80%{-o-transform:translateX(-10px);}100%{-o-transform:translateX(0);}}@keyframes bounceInLeft{0%{opacity:0;transform:translateX(-2000px);}60%{opacity:1;transform:translateX(30px);}80%{transform:translateX(-10px);}100%{transform:translateX(0);}}.animated.bounceInLeft{-webkit-animation-name:bounceInLeft;-moz-animation-name:bounceInLeft;-o-animation-name:bounceInLeft;animation-name:bounceInLeft;}@-webkit-keyframes bounceInRight{0%{opacity:0;-webkit-transform:translateX(2000px);}60%{opacity:1;-webkit-transform:translateX(-30px);}80%{-webkit-transform:translateX(10px);}100%{-webkit-transform:translateX(0);}}@-moz-keyframes bounceInRight{0%{opacity:0;-moz-transform:translateX(2000px);}60%{opacity:1;-moz-transform:translateX(-30px);}80%{-moz-transform:translateX(10px);}100%{-moz-transform:translateX(0);}}@-o-keyframes bounceInRight{0%{opacity:0;-o-transform:translateX(2000px);}60%{opacity:1;-o-transform:translateX(-30px);}80%{-o-transform:translateX(10px);}100%{-o-transform:translateX(0);}}@keyframes bounceInRight{0%{opacity:0;transform:translateX(2000px);}60%{opacity:1;transform:translateX(-30px);}80%{transform:translateX(10px);}100%{transform:translateX(0);}}.animated.bounceInRight{-webkit-animation-name:bounceInRight;-moz-animation-name:bounceInRight;-o-animation-name:bounceInRight;animation-name:bounceInRight;}@-webkit-keyframes bounceOut{0%{-webkit-transform:scale(1);}25%{-webkit-transform:scale(.95);}50%{opacity:1;-webkit-transform:scale(1.1);}100%{opacity:0;-webkit-transform:scale(.3);}}@-moz-keyframes bounceOut{0%{-moz-transform:scale(1);}25%{-moz-transform:scale(.95);}50%{opacity:1;-moz-transform:scale(1.1);}100%{opacity:0;-moz-transform:scale(.3);}}@-o-keyframes bounceOut{0%{-o-transform:scale(1);}25%{-o-transform:scale(.95);}50%{opacity:1;-o-transform:scale(1.1);}100%{opacity:0;-o-transform:scale(.3);}}@keyframes bounceOut{0%{transform:scale(1);}25%{transform:scale(.95);}50%{opacity:1;transform:scale(1.1);}100%{opacity:0;transform:scale(.3);}}.animated.bounceOut{-webkit-animation-name:bounceOut;-moz-animation-name:bounceOut;-o-animation-name:bounceOut;animation-name:bounceOut;}@-webkit-keyframes bounceOutUp{0%{-webkit-transform:translateY(0);}20%{opacity:1;-webkit-transform:translateY(20px);}100%{opacity:0;-webkit-transform:translateY(-2000px);}}@-moz-keyframes bounceOutUp{0%{-moz-transform:translateY(0);}20%{opacity:1;-moz-transform:translateY(20px);}100%{opacity:0;-moz-transform:translateY(-2000px);}}@-o-keyframes bounceOutUp{0%{-o-transform:translateY(0);}20%{opacity:1;-o-transform:translateY(20px);}100%{opacity:0;-o-transform:translateY(-2000px);}}@keyframes bounceOutUp{0%{transform:translateY(0);}20%{opacity:1;transform:translateY(20px);}100%{opacity:0;transform:translateY(-2000px);}}.animated.bounceOutUp{-webkit-animation-name:bounceOutUp;-moz-animation-name:bounceOutUp;-o-animation-name:bounceOutUp;animation-name:bounceOutUp;}@-webkit-keyframes bounceOutDown{0%{-webkit-transform:translateY(0);}20%{opacity:1;-webkit-transform:translateY(-20px);}100%{opacity:0;-webkit-transform:translateY(2000px);}}@-moz-keyframes bounceOutDown{0%{-moz-transform:translateY(0);}20%{opacity:1;-moz-transform:translateY(-20px);}100%{opacity:0;-moz-transform:translateY(2000px);}}@-o-keyframes bounceOutDown{0%{-o-transform:translateY(0);}20%{opacity:1;-o-transform:translateY(-20px);}100%{opacity:0;-o-transform:translateY(2000px);}}@keyframes bounceOutDown{0%{transform:translateY(0);}20%{opacity:1;transform:translateY(-20px);}100%{opacity:0;transform:translateY(2000px);}}.animated.bounceOutDown{-webkit-animation-name:bounceOutDown;-moz-animation-name:bounceOutDown;-o-animation-name:bounceOutDown;animation-name:bounceOutDown;}@-webkit-keyframes bounceOutLeft{0%{-webkit-transform:translateX(0);}20%{opacity:1;-webkit-transform:translateX(20px);}100%{opacity:0;-webkit-transform:translateX(-2000px);}}@-moz-keyframes bounceOutLeft{0%{-moz-transform:translateX(0);}20%{opacity:1;-moz-transform:translateX(20px);}100%{opacity:0;-moz-transform:translateX(-2000px);}}@-o-keyframes bounceOutLeft{0%{-o-transform:translateX(0);}20%{opacity:1;-o-transform:translateX(20px);}100%{opacity:0;-o-transform:translateX(-2000px);}}@keyframes bounceOutLeft{0%{transform:translateX(0);}20%{opacity:1;transform:translateX(20px);}100%{opacity:0;transform:translateX(-2000px);}}.animated.bounceOutLeft{-webkit-animation-name:bounceOutLeft;-moz-animation-name:bounceOutLeft;-o-animation-name:bounceOutLeft;animation-name:bounceOutLeft;}@-webkit-keyframes bounceOutRight{0%{-webkit-transform:translateX(0);}20%{opacity:1;-webkit-transform:translateX(-20px);}100%{opacity:0;-webkit-transform:translateX(2000px);}}@-moz-keyframes bounceOutRight{0%{-moz-transform:translateX(0);}20%{opacity:1;-moz-transform:translateX(-20px);}100%{opacity:0;-moz-transform:translateX(2000px);}}@-o-keyframes bounceOutRight{0%{-o-transform:translateX(0);}20%{opacity:1;-o-transform:translateX(-20px);}100%{opacity:0;-o-transform:translateX(2000px);}}@keyframes bounceOutRight{0%{transform:translateX(0);}20%{opacity:1;transform:translateX(-20px);}100%{opacity:0;transform:translateX(2000px);}}.animated.bounceOutRight{-webkit-animation-name:bounceOutRight;-moz-animation-name:bounceOutRight;-o-animation-name:bounceOutRight;animation-name:bounceOutRight;}@-webkit-keyframes rotateIn{0%{-webkit-transform-origin:center center;-webkit-transform:rotate(-200deg);opacity:0;}100%{-webkit-transform-origin:center center;-webkit-transform:rotate(0);opacity:1;}}@-moz-keyframes rotateIn{0%{-moz-transform-origin:center center;-moz-transform:rotate(-200deg);opacity:0;}100%{-moz-transform-origin:center center;-moz-transform:rotate(0);opacity:1;}}@-o-keyframes rotateIn{0%{-o-transform-origin:center center;-o-transform:rotate(-200deg);opacity:0;}100%{-o-transform-origin:center center;-o-transform:rotate(0);opacity:1;}}@keyframes rotateIn{0%{transform-origin:center center;transform:rotate(-200deg);opacity:0;}100%{transform-origin:center center;transform:rotate(0);opacity:1;}}.animated.rotateIn{-webkit-animation-name:rotateIn;-moz-animation-name:rotateIn;-o-animation-name:rotateIn;animation-name:rotateIn;}@-webkit-keyframes rotateInUpLeft{0%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(90deg);opacity:0;}100%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(0);opacity:1;}}@-moz-keyframes rotateInUpLeft{0%{-moz-transform-origin:left bottom;-moz-transform:rotate(90deg);opacity:0;}100%{-moz-transform-origin:left bottom;-moz-transform:rotate(0);opacity:1;}}@-o-keyframes rotateInUpLeft{0%{-o-transform-origin:left bottom;-o-transform:rotate(90deg);opacity:0;}100%{-o-transform-origin:left bottom;-o-transform:rotate(0);opacity:1;}}@keyframes rotateInUpLeft{0%{transform-origin:left bottom;transform:rotate(90deg);opacity:0;}100%{transform-origin:left bottom;transform:rotate(0);opacity:1;}}.animated.rotateInUpLeft{-webkit-animation-name:rotateInUpLeft;-moz-animation-name:rotateInUpLeft;-o-animation-name:rotateInUpLeft;animation-name:rotateInUpLeft;}@-webkit-keyframes rotateInDownLeft{0%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(-90deg);opacity:0;}100%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(0);opacity:1;}}@-moz-keyframes rotateInDownLeft{0%{-moz-transform-origin:left bottom;-moz-transform:rotate(-90deg);opacity:0;}100%{-moz-transform-origin:left bottom;-moz-transform:rotate(0);opacity:1;}}@-o-keyframes rotateInDownLeft{0%{-o-transform-origin:left bottom;-o-transform:rotate(-90deg);opacity:0;}100%{-o-transform-origin:left bottom;-o-transform:rotate(0);opacity:1;}}@keyframes rotateInDownLeft{0%{transform-origin:left bottom;transform:rotate(-90deg);opacity:0;}100%{transform-origin:left bottom;transform:rotate(0);opacity:1;}}.animated.rotateInDownLeft{-webkit-animation-name:rotateInDownLeft;-moz-animation-name:rotateInDownLeft;-o-animation-name:rotateInDownLeft;animation-name:rotateInDownLeft;}@-webkit-keyframes rotateInUpRight{0%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(-90deg);opacity:0;}100%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(0);opacity:1;}}@-moz-keyframes rotateInUpRight{0%{-moz-transform-origin:right bottom;-moz-transform:rotate(-90deg);opacity:0;}100%{-moz-transform-origin:right bottom;-moz-transform:rotate(0);opacity:1;}}@-o-keyframes rotateInUpRight{0%{-o-transform-origin:right bottom;-o-transform:rotate(-90deg);opacity:0;}100%{-o-transform-origin:right bottom;-o-transform:rotate(0);opacity:1;}}@keyframes rotateInUpRight{0%{transform-origin:right bottom;transform:rotate(-90deg);opacity:0;}100%{transform-origin:right bottom;transform:rotate(0);opacity:1;}}.animated.rotateInUpRight{-webkit-animation-name:rotateInUpRight;-moz-animation-name:rotateInUpRight;-o-animation-name:rotateInUpRight;animation-name:rotateInUpRight;}@-webkit-keyframes rotateInDownRight{0%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(90deg);opacity:0;}100%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(0);opacity:1;}}@-moz-keyframes rotateInDownRight{0%{-moz-transform-origin:right bottom;-moz-transform:rotate(90deg);opacity:0;}100%{-moz-transform-origin:right bottom;-moz-transform:rotate(0);opacity:1;}}@-o-keyframes rotateInDownRight{0%{-o-transform-origin:right bottom;-o-transform:rotate(90deg);opacity:0;}100%{-o-transform-origin:right bottom;-o-transform:rotate(0);opacity:1;}}@keyframes rotateInDownRight{0%{transform-origin:right bottom;transform:rotate(90deg);opacity:0;}100%{transform-origin:right bottom;transform:rotate(0);opacity:1;}}.animated.rotateInDownRight{-webkit-animation-name:rotateInDownRight;-moz-animation-name:rotateInDownRight;-o-animation-name:rotateInDownRight;animation-name:rotateInDownRight;}@-webkit-keyframes rotateOut{0%{-webkit-transform-origin:center center;-webkit-transform:rotate(0);opacity:1;}100%{-webkit-transform-origin:center center;-webkit-transform:rotate(200deg);opacity:0;}}@-moz-keyframes rotateOut{0%{-moz-transform-origin:center center;-moz-transform:rotate(0);opacity:1;}100%{-moz-transform-origin:center center;-moz-transform:rotate(200deg);opacity:0;}}@-o-keyframes rotateOut{0%{-o-transform-origin:center center;-o-transform:rotate(0);opacity:1;}100%{-o-transform-origin:center center;-o-transform:rotate(200deg);opacity:0;}}@keyframes rotateOut{0%{transform-origin:center center;transform:rotate(0);opacity:1;}100%{transform-origin:center center;transform:rotate(200deg);opacity:0;}}.animated.rotateOut{-webkit-animation-name:rotateOut;-moz-animation-name:rotateOut;-o-animation-name:rotateOut;animation-name:rotateOut;}@-webkit-keyframes rotateOutUpLeft{0%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(0);opacity:1;}100%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(-90deg);opacity:0;}}@-moz-keyframes rotateOutUpLeft{0%{-moz-transform-origin:left bottom;-moz-transform:rotate(0);opacity:1;}100%{-moz-transform-origin:left bottom;-moz-transform:rotate(-90deg);opacity:0;}}@-o-keyframes rotateOutUpLeft{0%{-o-transform-origin:left bottom;-o-transform:rotate(0);opacity:1;}100%{-o-transform-origin:left bottom;-o-transform:rotate(-90deg);opacity:0;}}@keyframes rotateOutUpLeft{0%{transform-origin:left bottom;transform:rotate(0);opacity:1;}100%{transform-origin:left bottom;transform:rotate(-90deg);opacity:0;}}.animated.rotateOutUpLeft{-webkit-animation-name:rotateOutUpLeft;-moz-animation-name:rotateOutUpLeft;-o-animation-name:rotateOutUpLeft;animation-name:rotateOutUpLeft;}@-webkit-keyframes rotateOutDownLeft{0%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(0);opacity:1;}100%{-webkit-transform-origin:left bottom;-webkit-transform:rotate(90deg);opacity:0;}}@-moz-keyframes rotateOutDownLeft{0%{-moz-transform-origin:left bottom;-moz-transform:rotate(0);opacity:1;}100%{-moz-transform-origin:left bottom;-moz-transform:rotate(90deg);opacity:0;}}@-o-keyframes rotateOutDownLeft{0%{-o-transform-origin:left bottom;-o-transform:rotate(0);opacity:1;}100%{-o-transform-origin:left bottom;-o-transform:rotate(90deg);opacity:0;}}@keyframes rotateOutDownLeft{0%{transform-origin:left bottom;transform:rotate(0);opacity:1;}100%{transform-origin:left bottom;transform:rotate(90deg);opacity:0;}}.animated.rotateOutDownLeft{-webkit-animation-name:rotateOutDownLeft;-moz-animation-name:rotateOutDownLeft;-o-animation-name:rotateOutDownLeft;animation-name:rotateOutDownLeft;}@-webkit-keyframes rotateOutUpRight{0%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(0);opacity:1;}100%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(90deg);opacity:0;}}@-moz-keyframes rotateOutUpRight{0%{-moz-transform-origin:right bottom;-moz-transform:rotate(0);opacity:1;}100%{-moz-transform-origin:right bottom;-moz-transform:rotate(90deg);opacity:0;}}@-o-keyframes rotateOutUpRight{0%{-o-transform-origin:right bottom;-o-transform:rotate(0);opacity:1;}100%{-o-transform-origin:right bottom;-o-transform:rotate(90deg);opacity:0;}}@keyframes rotateOutUpRight{0%{transform-origin:right bottom;transform:rotate(0);opacity:1;}100%{transform-origin:right bottom;transform:rotate(90deg);opacity:0;}}.animated.rotateOutUpRight{-webkit-animation-name:rotateOutUpRight;-moz-animation-name:rotateOutUpRight;-o-animation-name:rotateOutUpRight;animation-name:rotateOutUpRight;}@-webkit-keyframes rotateOutDownRight{0%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(0);opacity:1;}100%{-webkit-transform-origin:right bottom;-webkit-transform:rotate(-90deg);opacity:0;}}@-moz-keyframes rotateOutDownRight{0%{-moz-transform-origin:right bottom;-moz-transform:rotate(0);opacity:1;}100%{-moz-transform-origin:right bottom;-moz-transform:rotate(-90deg);opacity:0;}}@-o-keyframes rotateOutDownRight{0%{-o-transform-origin:right bottom;-o-transform:rotate(0);opacity:1;}100%{-o-transform-origin:right bottom;-o-transform:rotate(-90deg);opacity:0;}}@keyframes rotateOutDownRight{0%{transform-origin:right bottom;transform:rotate(0);opacity:1;}100%{transform-origin:right bottom;transform:rotate(-90deg);opacity:0;}}.animated.rotateOutDownRight{-webkit-animation-name:rotateOutDownRight;-moz-animation-name:rotateOutDownRight;-o-animation-name:rotateOutDownRight;animation-name:rotateOutDownRight;}@-webkit-keyframes hinge{0%{-webkit-transform:rotate(0);-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;}20%,60%{-webkit-transform:rotate(80deg);-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;}40%{-webkit-transform:rotate(60deg);-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;}80%{-webkit-transform:rotate(60deg) translateY(0);opacity:1;-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;}100%{-webkit-transform:translateY(700px);opacity:0;}}@-moz-keyframes hinge{0%{-moz-transform:rotate(0);-moz-transform-origin:top left;-moz-animation-timing-function:ease-in-out;}20%,60%{-moz-transform:rotate(80deg);-moz-transform-origin:top left;-moz-animation-timing-function:ease-in-out;}40%{-moz-transform:rotate(60deg);-moz-transform-origin:top left;-moz-animation-timing-function:ease-in-out;}80%{-moz-transform:rotate(60deg) translateY(0);opacity:1;-moz-transform-origin:top left;-moz-animation-timing-function:ease-in-out;}100%{-moz-transform:translateY(700px);opacity:0;}}@-o-keyframes hinge{0%{-o-transform:rotate(0);-o-transform-origin:top left;-o-animation-timing-function:ease-in-out;}20%,60%{-o-transform:rotate(80deg);-o-transform-origin:top left;-o-animation-timing-function:ease-in-out;}40%{-o-transform:rotate(60deg);-o-transform-origin:top left;-o-animation-timing-function:ease-in-out;}80%{-o-transform:rotate(60deg) translateY(0);opacity:1;-o-transform-origin:top left;-o-animation-timing-function:ease-in-out;}100%{-o-transform:translateY(700px);opacity:0;}}@keyframes hinge{0%{transform:rotate(0);transform-origin:top left;animation-timing-function:ease-in-out;}20%,60%{transform:rotate(80deg);transform-origin:top left;animation-timing-function:ease-in-out;}40%{transform:rotate(60deg);transform-origin:top left;animation-timing-function:ease-in-out;}80%{transform:rotate(60deg) translateY(0);opacity:1;transform-origin:top left;animation-timing-function:ease-in-out;}100%{transform:translateY(700px);opacity:0;}}.animated.hinge{-webkit-animation-name:hinge;-moz-animation-name:hinge;-o-animation-name:hinge;animation-name:hinge;}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);}100%{opacity:1;-webkit-transform:translateX(0px) rotate(0deg);}}@-moz-keyframes rollIn{0%{opacity:0;-moz-transform:translateX(-100%) rotate(-120deg);}100%{opacity:1;-moz-transform:translateX(0px) rotate(0deg);}}@-o-keyframes rollIn{0%{opacity:0;-o-transform:translateX(-100%) rotate(-120deg);}100%{opacity:1;-o-transform:translateX(0px) rotate(0deg);}}@keyframes rollIn{0%{opacity:0;transform:translateX(-100%) rotate(-120deg);}100%{opacity:1;transform:translateX(0px) rotate(0deg);}}.animated.rollIn{-webkit-animation-name:rollIn;-moz-animation-name:rollIn;-o-animation-name:rollIn;animation-name:rollIn;}@-webkit-keyframes rollOut{0%{opacity:1;-webkit-transform:translateX(0px) rotate(0deg);}100%{opacity:0;-webkit-transform:translateX(100%) rotate(120deg);}}@-moz-keyframes rollOut{0%{opacity:1;-moz-transform:translateX(0px) rotate(0deg);}100%{opacity:0;-moz-transform:translateX(100%) rotate(120deg);}}@-o-keyframes rollOut{0%{opacity:1;-o-transform:translateX(0px) rotate(0deg);}100%{opacity:0;-o-transform:translateX(100%) rotate(120deg);}}@keyframes rollOut{0%{opacity:1;transform:translateX(0px) rotate(0deg);}100%{opacity:0;transform:translateX(100%) rotate(120deg);}}.animated.rollOut{-webkit-animation-name:rollOut;-moz-animation-name:rollOut;-o-animation-name:rollOut;animation-name:rollOut;}@-webkit-keyframes lightSpeedIn{0%{-webkit-transform:translateX(100%) skewX(-30deg);opacity:0;}60%{-webkit-transform:translateX(-20%) skewX(30deg);opacity:1;}80%{-webkit-transform:translateX(0%) skewX(-15deg);opacity:1;}100%{-webkit-transform:translateX(0%) skewX(0deg);opacity:1;}}@-moz-keyframes lightSpeedIn{0%{-moz-transform:translateX(100%) skewX(-30deg);opacity:0;}60%{-moz-transform:translateX(-20%) skewX(30deg);opacity:1;}80%{-moz-transform:translateX(0%) skewX(-15deg);opacity:1;}100%{-moz-transform:translateX(0%) skewX(0deg);opacity:1;}}@-o-keyframes lightSpeedIn{0%{-o-transform:translateX(100%) skewX(-30deg);opacity:0;}60%{-o-transform:translateX(-20%) skewX(30deg);opacity:1;}80%{-o-transform:translateX(0%) skewX(-15deg);opacity:1;}100%{-o-transform:translateX(0%) skewX(0deg);opacity:1;}}@keyframes lightSpeedIn{0%{transform:translateX(100%) skewX(-30deg);opacity:0;}60%{transform:translateX(-20%) skewX(30deg);opacity:1;}80%{transform:translateX(0%) skewX(-15deg);opacity:1;}100%{transform:translateX(0%) skewX(0deg);opacity:1;}}.animated.lightSpeedIn{-webkit-animation-name:lightSpeedIn;-moz-animation-name:lightSpeedIn;-o-animation-name:lightSpeedIn;animation-name:lightSpeedIn;-webkit-animation-timing-function:ease-out;-moz-animation-timing-function:ease-out;-o-animation-timing-function:ease-out;animation-timing-function:ease-out;}.animated.lightSpeedIn{-webkit-animation-duration:0.5s;-moz-animation-duration:0.5s;-o-animation-duration:0.5s;animation-duration:0.5s;}@-webkit-keyframes lightSpeedOut{0%{-webkit-transform:translateX(0%) skewX(0deg);opacity:1;}100%{-webkit-transform:translateX(100%) skewX(-30deg);opacity:0;}}@-moz-keyframes lightSpeedOut{0%{-moz-transform:translateX(0%) skewX(0deg);opacity:1;}100%{-moz-transform:translateX(100%) skewX(-30deg);opacity:0;}}@-o-keyframes lightSpeedOut{0%{-o-transform:translateX(0%) skewX(0deg);opacity:1;}100%{-o-transform:translateX(100%) skewX(-30deg);opacity:0;}}@keyframes lightSpeedOut{0%{transform:translateX(0%) skewX(0deg);opacity:1;}100%{transform:translateX(100%) skewX(-30deg);opacity:0;}}.animated.lightSpeedOut{-webkit-animation-name:lightSpeedOut;-moz-animation-name:lightSpeedOut;-o-animation-name:lightSpeedOut;animation-name:lightSpeedOut;-webkit-animation-timing-function:ease-in;-moz-animation-timing-function:ease-in;-o-animation-timing-function:ease-in;animation-timing-function:ease-in;}.animated.lightSpeedOut{-webkit-animation-duration:0.25s;-moz-animation-duration:0.25s;-o-animation-duration:0.25s;animation-duration:0.25s;} -------------------------------------------------------------------------------- /web/animate.title.css: -------------------------------------------------------------------------------- 1 | #title>span:nth-child(1) { 2 | -webkit-animation-duration: 5s; 3 | -webkit-animation-name: blink; 4 | -webkit-animation-iteration-count: infinite; 5 | -webkit-animation-delay: 1s; 6 | } 7 | 8 | #title>span:nth-child(2) { 9 | -webkit-animation-duration: 5s; 10 | -webkit-animation-name: blink; 11 | -webkit-animation-iteration-count: infinite; 12 | -webkit-animation-delay: 3.3s; 13 | } 14 | 15 | #title>span:nth-child(3) { 16 | -webkit-animation-duration: 5s; 17 | -webkit-animation-name: blink; 18 | -webkit-animation-iteration-count: infinite; 19 | -webkit-animation-delay: 2s; 20 | } 21 | 22 | #title>span:nth-child(4) { 23 | -webkit-animation-duration: 5s; 24 | -webkit-animation-name: blink; 25 | -webkit-animation-iteration-count: infinite; 26 | -webkit-animation-delay: 2.8s; 27 | } 28 | 29 | #title>span:nth-child(5) { 30 | -webkit-animation-duration: 5s; 31 | -webkit-animation-name: blink; 32 | -webkit-animation-iteration-count: infinite; 33 | -webkit-animation-delay: 2.9s; 34 | } 35 | 36 | #title>span:nth-child(6) { 37 | -webkit-animation-duration: 5s; 38 | -webkit-animation-name: blink; 39 | -webkit-animation-iteration-count: infinite; 40 | -webkit-animation-delay: 1.3s; 41 | } 42 | 43 | @-webkit-keyframes blink { 44 | 0% { 45 | opacity: 1; 46 | } 47 | 50% { 48 | opacity: 0.4; 49 | } 50 | 100% { 51 | opacity: 1; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /web/basic-title.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nevers/dart-webrtc/6ddb699b5ffe2e72dbdfb9a186e8241f72a6d71f/web/basic-title.ttf -------------------------------------------------------------------------------- /web/big-noodle.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nevers/dart-webrtc/6ddb699b5ffe2e72dbdfb9a186e8241f72a6d71f/web/big-noodle.ttf -------------------------------------------------------------------------------- /web/index.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: bigNoodle; 3 | src: url("big-noodle.ttf"); 4 | } 5 | 6 | @font-face { 7 | font-family: flexDisplay; 8 | src: url("basic-title.ttf"); 9 | } 10 | 11 | html { 12 | font-family: bigNoodle; 13 | background-color: #3d3d3d; 14 | } 15 | 16 | body { 17 | margin: 0px; 18 | } 19 | 20 | #title { 21 | font-size: 20vh; 22 | color: #97be0d; 23 | text-align: center; 24 | margin: 0; 25 | } 26 | 27 | #subtitle { 28 | font-family: flexDisplay; 29 | font-size: 4vh; 30 | color: #dddddd; 31 | text-align: center; 32 | margin: 0; 33 | padding: 5px; 34 | } 35 | 36 | .center { 37 | position: absolute; 38 | width: 100%; 39 | margin: 0 auto; 40 | bottom: 50%; 41 | text-align: center; 42 | } 43 | 44 | #videos { 45 | position: absolute; 46 | top: 0; 47 | left: 0; 48 | right: 0; 49 | bottom: 0; 50 | text-align: center; 51 | display: -webkit-box; 52 | -webkit-box-align: center; 53 | -webkit-box-pack: center; 54 | } 55 | 56 | .video { 57 | height: 55vh; 58 | padding: 2vh; 59 | } -------------------------------------------------------------------------------- /web/index.dart: -------------------------------------------------------------------------------- 1 | import "dart:html"; 2 | import "dart:async"; 3 | import "packages/dart_webrtc/webrtcstreammanager.dart"; 4 | 5 | var cssClassFadeOutUp = "animated fadeOutUp"; 6 | var cssClassFadeOutDown = "animated fadeOutDown"; 7 | var cssClassFadeInUp = "animated fadeInUp"; 8 | var cssClassFadeInDown = "animated fadeInDown"; 9 | var cssClassAddVideo = "animated flipInY"; 10 | var cssClassRemoveVideo = "animated bounceOutDown"; 11 | 12 | var receivingVideoElements = new Map(); 13 | 14 | void main() { 15 | WebRtcStreamManager webRtcStreamManager = new WebRtcStreamManager(); 16 | webRtcStreamManager.setStreamAddHandler((clientId, MediaStream stream) { 17 | hideTitle(() { 18 | addVideo(clientId, stream); 19 | }); 20 | }); 21 | 22 | webRtcStreamManager.setStreamRemoveHandler((clientId) { 23 | removeVideo(clientId); 24 | }); 25 | } 26 | 27 | void hideTitle(Function onAnimationEnd) { 28 | bool isCalledback = false; 29 | var title = querySelector("#title"); 30 | var subtitle = querySelector("#subtitle"); 31 | 32 | if(title.className == cssClassFadeOutUp || subtitle.className == cssClassFadeOutDown) { 33 | onAnimationEnd(); 34 | return; 35 | } 36 | 37 | title.className = cssClassFadeOutUp; 38 | subtitle.className = cssClassFadeOutDown; 39 | window.onAnimationEnd.listen((AnimationEvent event) { 40 | if(!isCalledback && (event.target == title || event.target == subtitle)) { 41 | onAnimationEnd(); 42 | isCalledback = true; 43 | } 44 | }); 45 | } 46 | 47 | void addVideo(int clientId, MediaStream stream) { 48 | print("Adding webcam: stream='${stream}'"); 49 | var video = new VideoElement(); 50 | video.src = Url.createObjectUrl(stream); 51 | video.onLoadedData.listen((Event event) { 52 | video.play(); 53 | querySelector("#videos").children.add(video); 54 | video.classes.add("video"); 55 | video.classes.add(cssClassAddVideo); 56 | }); 57 | receivingVideoElements[clientId] = video; 58 | } 59 | 60 | void removeVideo(int clientId) { 61 | //FIXME This should be handled in webRtcStreamManager! 62 | if(!receivingVideoElements.containsKey(clientId)) 63 | return; 64 | VideoElement video = receivingVideoElements[clientId]; 65 | video.className = cssClassRemoveVideo; 66 | window.onAnimationEnd.listen((AnimationEvent event) { 67 | if(event.target != video) return; 68 | video.pause(); 69 | video.remove(); 70 | receivingVideoElements.remove(clientId); 71 | if(receivingVideoElements.isEmpty) 72 | showTitle(); 73 | }); 74 | } 75 | 76 | void showTitle() { 77 | var title = querySelector("#title"); 78 | var subtitle = querySelector("#subtitle"); 79 | title.className = cssClassFadeInDown; 80 | subtitle.className = cssClassFadeInUp; 81 | } 82 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebRTC 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | W 15 | e 16 | b 17 | R 18 | T 19 | C 20 |
21 | 22 |
23 | Waiting for participants... 24 |
25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 | --------------------------------------------------------------------------------