├── .leanignore ├── public ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── stylesheets │ ├── index.css │ └── font-awesome.css └── javascripts │ ├── index.js.map │ ├── draw.js.map │ ├── index.js │ ├── index.ts │ ├── draw.js │ └── draw.ts ├── @types ├── vue.d.ts └── whiteboard.d.ts ├── views ├── component │ ├── footer.ejs │ └── header.ejs ├── error.ejs └── index.ejs ├── README.md ├── tsconfig.json ├── routes ├── users.js ├── index.ts ├── index.js └── index.js.map ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── .gitignore ├── package.json ├── app.js.map ├── app.js ├── app.ts └── bin ├── www ├── whiteboard.js.map ├── whiteboard.js └── whiteboard.ts /.leanignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .avoscloud/ 3 | .leancloud/ 4 | node_modules/ -------------------------------------------------------------------------------- /public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxcanghai/whiteboard/HEAD/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxcanghai/whiteboard/HEAD/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxcanghai/whiteboard/HEAD/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /@types/vue.d.ts: -------------------------------------------------------------------------------- 1 | // import * as vuejs from "vue"; 2 | 3 | // declare class Vue extends vuejs { 4 | // //兼容在全局引用vue文件的用法 5 | // } -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxcanghai/whiteboard/HEAD/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxcanghai/whiteboard/HEAD/public/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /views/component/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | <% if("js" in locals) {%> 3 | 4 | <% } %> 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # whiteboard 2 | 基于webSocket的多人白板共享应用 socket.io+Node.js+canvas whiteboard 3 | 4 | # Demo 5 | [http://whiteboard-xxcanghai.leanapp.cn/](http://whiteboard-xxcanghai.leanapp.cn/) 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "watch": true, 5 | "sourceMap": true 6 | }, 7 | "exclude": [ 8 | "node_modules" 9 | ] 10 | } -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res, next) { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /views/error.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= message %> 5 | 6 | 7 |

<%= message %>

8 |

<%= error.status %>

9 |
10 |       <%= error.stack %>
11 |   
12 | 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // 将设置放入此文件中以覆盖默认值和用户设置。 2 | { 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/.svn": true, 6 | "**/.hg": true, 7 | "**/CVS": true, 8 | "**/.DS_Store": true, 9 | "**/.map": true 10 | } 11 | } -------------------------------------------------------------------------------- /routes/index.ts: -------------------------------------------------------------------------------- 1 | import express = require("express"); 2 | 3 | export default function (router: express.Router) { 4 | router.get('/', function (req, res) { 5 | res.render("index",{}); 6 | }); 7 | 8 | router.get('/index', function (req, res) { 9 | res.render("index",{}); 10 | }); 11 | }; -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "tsc", 6 | "isShellCommand": true, 7 | "args": ["-p", "."], 8 | "showOutput": "silent", 9 | "problemMatcher": "$tsc" 10 | } -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function default_1(router) { 3 | router.get('/', function (req, res) { 4 | res.render("index", {}); 5 | }); 6 | router.get('/index', function (req, res) { 7 | res.render("index", {}); 8 | }); 9 | } 10 | exports.__esModule = true; 11 | exports["default"] = default_1; 12 | ; 13 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /routes/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AAEA,mBAAyB,MAAsB;IAC3C,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,GAAG,EAAE,GAAG;QAC9B,GAAG,CAAC,MAAM,CAAC,OAAO,EAAC,EAAE,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,GAAG,EAAE,GAAG;QACnC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAC,EAAE,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AACP,CAAC;;AARD,+BAQC;AAAA,CAAC"} -------------------------------------------------------------------------------- /views/component/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= locals.title || "Whiteboard Collaboration" %> 5 | 6 | 7 | <% if(("css" in locals) && locals.css.length > 0) { %> 8 | 9 | <% } %> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | #leancloud配置 40 | .leancloud -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whiteboard", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "@types/body-parser": "^1.16.3", 10 | "@types/cookie-parser": "^1.3.30", 11 | "@types/express": "^4.0.35", 12 | "@types/morgan": "^1.7.32", 13 | "@types/node": "^7.0.14", 14 | "@types/serve-favicon": "^2.2.28", 15 | "@types/socket.io": "^1.4.29", 16 | "@types/socket.io-client": "^1.4.29", 17 | "@types/underscore": "^1.8.0", 18 | "@types/vue": "^2.0.0", 19 | "body-parser": "~1.13.2", 20 | "cookie-parser": "~1.3.5", 21 | "debug": "~2.2.0", 22 | "ejs": "^2.5.6", 23 | "express": "~4.13.1", 24 | "express-enrouten": "^1.3.0", 25 | "jade": "~1.11.0", 26 | "leanengine": "^2.0.1", 27 | "morgan": "~1.6.1", 28 | "serve-favicon": "~2.3.0", 29 | "socket.io": "^1.7.3", 30 | "underscore": "^1.8.3", 31 | "vue": "^2.3.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [{ 7 | "type": "node", 8 | "request": "launch", 9 | "name": "启动程序", 10 | "program": "${workspaceRoot}/bin/www", 11 | "cwd": "${workspaceRoot}", 12 | "outFiles": [], 13 | "sourceMaps": true, 14 | "preLaunchTask": null, 15 | "runtimeExecutable": null, 16 | "runtimeArgs": [ 17 | "--nolazy" 18 | ], 19 | "env": { 20 | "NODE_ENV": "development" 21 | }, 22 | "console": "internalConsole" 23 | }, { 24 | "type": "node", 25 | "request": "attach", 26 | "name": "附加到进程", 27 | "port": 5858, 28 | "outFiles": [], 29 | "sourceMaps": true 30 | }] 31 | } -------------------------------------------------------------------------------- /public/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html { 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | body { 11 | margin: 0; 12 | padding: 0; 13 | } 14 | 15 | #main { 16 | width: 100%; 17 | height: 100%; 18 | padding: 10px; 19 | } 20 | 21 | .main-left { 22 | width: 200px; 23 | height: 100%; 24 | float: left; 25 | background-color: #333; 26 | color: white; 27 | position: relative; 28 | } 29 | 30 | .main-right { 31 | height: 100%; 32 | margin-left: 200px; 33 | overflow: hidden; 34 | position: relative; 35 | } 36 | 37 | #canvas { 38 | background-color: whitesmoke; 39 | cursor: crosshair; 40 | } 41 | 42 | .userBox { 43 | border-top: 1px solid white; 44 | height: 200px; 45 | position: absolute; 46 | bottom: 200px; 47 | overflow: auto; 48 | width: 100%; 49 | } 50 | 51 | .logBox { 52 | border-top: 1px solid white; 53 | height: 200px; 54 | position: absolute; 55 | bottom: 0; 56 | overflow: auto; 57 | width: 100%; 58 | } 59 | 60 | .logBox .logItem { 61 | word-break: break-all; 62 | font-size: 12px; 63 | } 64 | .logItem:hover{ 65 | background-color: #2196f3; 66 | } 67 | 68 | .main-left .config div { 69 | padding: 0 10px; 70 | margin: 5px 0; 71 | } 72 | 73 | .main-left .username { 74 | width: 100%; 75 | font-size: 18px; 76 | } 77 | 78 | .cursor { 79 | position: absolute; 80 | z-index: 99; 81 | cursor: crosshair; 82 | user-select: none; 83 | } 84 | 85 | .cursor i { 86 | transform: rotateZ(90deg); 87 | } -------------------------------------------------------------------------------- /app.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"app.js","sourceRoot":"","sources":["app.ts"],"names":[],"mappings":";AAAA,iCAAmC;AACnC,2BAA6B;AAE7B,+BAAiC;AACjC,4CAA8C;AAC9C,wCAA0C;AAI1C,IAAI,QAAQ,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAC3C,0CAA0C;AAC1C,yCAAyC;AAIzC,IAAI,GAAG,GAAoB,OAAO,EAAE,CAAC;AAErC,oBAAoB;AACpB,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;AAChD,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;AAE9B,kDAAkD;AAClD,kEAAkE;AAClE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACvB,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;AAC3B,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AACpD,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;AACxB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;AACxD,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;AAE3C,wBAAwB;AACxB,4BAA4B;AAE5B,yCAAyC;AACzC,GAAG,CAAC,GAAG,CAAC,UAAU,GAAG,EAAE,GAAG,EAAE,IAAI;IAC5B,IAAI,GAAG,GAAG,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3B,GAAI,CAAC,MAAM,GAAG,GAAG,CAAC;IACxB,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC,CAAC,CAAC;AAEH,iBAAiB;AAEjB,4BAA4B;AAC5B,wBAAwB;AACxB,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC;IACnC,GAAG,CAAC,GAAG,CAAC,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI;QACjC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC;QAC9B,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE;YAChB,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,KAAK,EAAE,GAAG;SACb,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACP,CAAC;AAED,2BAA2B;AAC3B,gCAAgC;AAChC,GAAG,CAAC,GAAG,CAAC,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI;IACjC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC;IAC9B,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE;QAChB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,KAAK,EAAE,EAAE;KACZ,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAGH,iBAAS,GAAG,CAAC"} -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var express = require("express"); 3 | var path = require("path"); 4 | var logger = require("morgan"); 5 | var cookieParser = require("cookie-parser"); 6 | var bodyParser = require("body-parser"); 7 | var enrouten = require('express-enrouten'); 8 | // var routes = require('./routes/index'); 9 | // var users = require('./routes/users'); 10 | var app = express(); 11 | // view engine setup 12 | app.set('views', path.join(__dirname, 'views')); 13 | app.set('view engine', 'ejs'); 14 | // uncomment after placing your favicon in /public 15 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 16 | app.use(logger('dev')); 17 | app.use(bodyParser.json()); 18 | app.use(bodyParser.urlencoded({ extended: false })); 19 | app.use(cookieParser()); 20 | app.use(express.static(path.join(__dirname, 'public'))); 21 | app.use(enrouten({ directory: 'routes' })); 22 | // app.use('/', routes); 23 | // app.use('/users', users); 24 | // catch 404 and forward to error handler 25 | app.use(function (req, res, next) { 26 | var err = new Error('Not Found'); 27 | err.status = 404; 28 | next(err); 29 | }); 30 | // error handlers 31 | // development error handler 32 | // will print stacktrace 33 | if (app.get('env') === 'development') { 34 | app.use(function (err, req, res, next) { 35 | res.status(err.status || 500); 36 | res.render('error', { 37 | message: err.message, 38 | error: err 39 | }); 40 | }); 41 | } 42 | // production error handler 43 | // no stacktraces leaked to user 44 | app.use(function (err, req, res, next) { 45 | res.status(err.status || 500); 46 | res.render('error', { 47 | message: err.message, 48 | error: {} 49 | }); 50 | }); 51 | module.exports = app; 52 | //# sourceMappingURL=app.js.map -------------------------------------------------------------------------------- /app.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as path from 'path'; 3 | import * as favicon from 'serve-favicon'; 4 | import * as logger from 'morgan'; 5 | import * as cookieParser from 'cookie-parser'; 6 | import * as bodyParser from 'body-parser'; 7 | import * as http from 'http'; 8 | import * as socketio from 'socket.io';//websocket库 9 | import * as AV from 'leanengine'; 10 | var enrouten = require('express-enrouten'); 11 | // var routes = require('./routes/index'); 12 | // var users = require('./routes/users'); 13 | 14 | 15 | 16 | var app: express.Express = express(); 17 | 18 | // view engine setup 19 | app.set('views', path.join(__dirname, 'views')); 20 | app.set('view engine', 'ejs'); 21 | 22 | // uncomment after placing your favicon in /public 23 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 24 | app.use(logger('dev')); 25 | app.use(bodyParser.json()); 26 | app.use(bodyParser.urlencoded({ extended: false })); 27 | app.use(cookieParser()); 28 | app.use(express.static(path.join(__dirname, 'public'))); 29 | app.use(enrouten({ directory: 'routes' })); 30 | 31 | // app.use('/', routes); 32 | // app.use('/users', users); 33 | 34 | // catch 404 and forward to error handler 35 | app.use(function (req, res, next) { 36 | var err = new Error('Not Found'); 37 | (err).status = 404; 38 | next(err); 39 | }); 40 | 41 | // error handlers 42 | 43 | // development error handler 44 | // will print stacktrace 45 | if (app.get('env') === 'development') { 46 | app.use(function (err, req, res, next) { 47 | res.status(err.status || 500); 48 | res.render('error', { 49 | message: err.message, 50 | error: err 51 | }); 52 | }); 53 | } 54 | 55 | // production error handler 56 | // no stacktraces leaked to user 57 | app.use(function (err, req, res, next) { 58 | res.status(err.status || 500); 59 | res.render('error', { 60 | message: err.message, 61 | error: {} 62 | }); 63 | }); 64 | 65 | 66 | export = app; -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('whiteboard:server'); 9 | var http = require('http'); 10 | var whiteboard = require('./whiteboard'); 11 | 12 | /** 13 | * Get port from environment and store in Express. 14 | */ 15 | 16 | var port = normalizePort(process.env.PORT || '3000'); 17 | app.set('port', port); 18 | 19 | /** 20 | * Create HTTP server. 21 | */ 22 | 23 | var server = http.createServer(app); 24 | 25 | //初始化白板程序 26 | whiteboard(server); 27 | 28 | /** 29 | * Listen on provided port, on all network interfaces. 30 | */ 31 | server.listen(port); 32 | server.on('error', onError); 33 | server.on('listening', onListening); 34 | console.log("服务启动成功!端口号:", port); 35 | 36 | /** 37 | * Normalize a port into a number, string, or false. 38 | */ 39 | function normalizePort(val) { 40 | var port = parseInt(val, 10); 41 | 42 | if (isNaN(port)) { 43 | // named pipe 44 | return val; 45 | } 46 | 47 | if (port >= 0) { 48 | // port number 49 | return port; 50 | } 51 | 52 | return false; 53 | } 54 | 55 | /** 56 | * Event listener for HTTP server "error" event. 57 | */ 58 | function onError(error) { 59 | if (error.syscall !== 'listen') { 60 | throw error; 61 | } 62 | 63 | var bind = typeof port === 'string' ? 64 | 'Pipe ' + port : 65 | 'Port ' + port; 66 | 67 | // handle specific listen errors with friendly messages 68 | switch (error.code) { 69 | case 'EACCES': 70 | console.error(bind + ' requires elevated privileges'); 71 | process.exit(1); 72 | break; 73 | case 'EADDRINUSE': 74 | console.error(bind + ' is already in use'); 75 | process.exit(1); 76 | break; 77 | default: 78 | throw error; 79 | } 80 | } 81 | 82 | /** 83 | * Event listener for HTTP server "listening" event. 84 | */ 85 | function onListening() { 86 | var addr = server.address(); 87 | var bind = typeof addr === 'string' ? 88 | 'pipe ' + addr : 89 | 'port ' + addr.port; 90 | debug('Listening on ' + bind); 91 | } -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | <% locals.title = "多人共享画板"; locals.js = "index"; locals.css = "index" %> 2 | <% include component/header %> 3 | 4 |
5 |
6 |

7 | 多人实时画板 8 |

9 |
10 |
11 | 用户名:
12 | 13 |
14 |
15 | 画笔宽度:
16 | 17 | 18 | 19 | 20 |
21 |
22 | 画笔颜色:
23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 32 |
33 |
34 | 35 |
36 |
【在线用户列表】 共
37 |
38 |
39 |
40 |
【日志】
41 |
42 |
43 |
44 |
45 | 46 | 您的浏览器不支持canvas 47 | 48 | 49 |
50 |
53 | 54 | 55 |
56 |
57 |
58 |
59 | 60 | <% include component/footer %> -------------------------------------------------------------------------------- /bin/whiteboard.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"whiteboard.js","sourceRoot":"","sources":["whiteboard.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /public/javascripts/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /@types/whiteboard.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace wb { 2 | /** 3 | * 用户socket对象 4 | */ 5 | interface socketClient extends SocketIO.Socket { 6 | /** 7 | * 当前用户GUID 8 | * 9 | * @type {string} 10 | */ 11 | uid: string; 12 | } 13 | 14 | /** 15 | * 用户实体基类 16 | */ 17 | interface baseUser { 18 | /** 19 | * 用户名 20 | * 21 | * @type {string} 22 | */ 23 | name: string; 24 | 25 | /** 26 | * 用户的uid,随机字符串 27 | * 28 | * @type {string} 29 | */ 30 | uid: string; 31 | /** 32 | * 当前用户鼠标光标位置 33 | * 34 | * @type {Point} 35 | */ 36 | position: Point; 37 | } 38 | 39 | /** 40 | * 在服务器端的一个用户实体 41 | * 42 | * @interface serverUser 43 | * @extends {baseUser} 44 | */ 45 | interface serverUser extends baseUser { 46 | /** 47 | * 当前用户的socket连接对象 48 | * 49 | * @type {wb.socketClient} 50 | */ 51 | socket: wb.socketClient; 52 | } 53 | 54 | /** 55 | * 在浏览器端的一个用户实体 56 | * 57 | * @interface clientUser 58 | * @extends {baseUser} 59 | */ 60 | interface clientUser extends baseUser { 61 | 62 | } 63 | 64 | /** 65 | * 用户发起的login事件传输实体。用户登录操作 66 | * 67 | * @interface clientLogin 68 | */ 69 | interface clientEmitLogin { 70 | /** 71 | * 当前要登录的用户名 72 | * 73 | * @type {string} 74 | */ 75 | name: string; 76 | } 77 | 78 | /** 79 | * 服务器端发起的login事件传输实体。有新用户登录操作 80 | * 81 | * @interface serverLogin 82 | */ 83 | interface serverEmitLogin { 84 | /** 85 | * 只包含用户名和uid的在线用户数组 86 | * 87 | * @type {wb.user[]} 88 | */ 89 | onLineUserArr: wb.clientUser[]; 90 | 91 | /** 92 | * 当前登录的用户对象 93 | * 94 | * @type {clientUser} 95 | */ 96 | user: clientUser; 97 | } 98 | 99 | /** 100 | * 服务器端发起的login确认事件传输实体。通知当前用户登录成功 101 | * 102 | * @interface serverLogin 103 | */ 104 | interface serverEmitLoginACK extends serverEmitLogin { 105 | } 106 | 107 | /** 108 | * 浏览器端发起Logout操作传输实体,有用户主动退出登录 109 | * 110 | * @interface clientLogout 111 | */ 112 | interface clientEmitLogout { 113 | 114 | } 115 | 116 | /** 117 | * 服务器端发起的Logout事件传输实体,有用户退出登录操作 118 | * 119 | * @interface serverLogout 120 | */ 121 | interface serverEmitLogout extends serverEmitLogin { 122 | } 123 | 124 | /** 125 | * 客户端发起的drawLine事件传输实体,发送画线数据 126 | * 127 | * @interface clientEmitDrawLine 128 | */ 129 | interface clientEmitDrawLine extends DrawLine { 130 | } 131 | 132 | /** 133 | * 服务器端发起的drawLine事件的传输实体,向所有人通知有人发送了划线数据 134 | * 135 | * @interface serverEmitDrawLine 136 | */ 137 | interface serverEmitDrawLine extends DrawLine { 138 | /** 139 | * 当前发出画线的用户对象 140 | * 141 | * @type {wb.clientUser} 142 | */ 143 | user: wb.clientUser; 144 | } 145 | 146 | interface clientEmitPenMove { 147 | /** 148 | * 画笔坐标X 149 | * 150 | * @type {number} 151 | */ 152 | x: number; 153 | /** 154 | * 画笔坐标Y 155 | * 156 | * @type {number} 157 | */ 158 | y: number; 159 | } 160 | 161 | interface serverEmitPenMove { 162 | /** 163 | * 当前发出画线的用户对象 164 | * 165 | * @type {wb.clientUser} 166 | */ 167 | user: wb.clientUser; 168 | } 169 | 170 | interface DrawLine { 171 | 172 | /** 173 | * 绘制线条开始坐标点 174 | * 175 | * @type {Point} 176 | */ 177 | startPoint: Point; 178 | /** 179 | * 绘制线条结束坐标点 180 | * 181 | * @type {Point} 182 | */ 183 | endPoint: Point; 184 | /** 185 | * 线条颜色 186 | * 187 | * @type {string} 188 | */ 189 | color: string; 190 | /** 191 | * 线条宽度 192 | * 193 | * @type {number} 194 | */ 195 | width: number; 196 | } 197 | 198 | interface Point { 199 | x: number; 200 | y: number; 201 | } 202 | 203 | } -------------------------------------------------------------------------------- /bin/whiteboard.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var socketio = require("socket.io"); 3 | var _ = require("underscore"); 4 | module.exports = function chatroomio(httpServer) { 5 | /** 所有房间字典,内为当前房间的在线登录用户数组 */ 6 | var onLineUserArr = []; 7 | var server = socketio(httpServer); 8 | server.on("connection", function (client) { 9 | console.log("socket.io on connection!一个用户进入"); 10 | //监听新用户加入 11 | client.on('login', function (data, ack) { 12 | //创建全新的一个服务器端User用户 13 | var user = createServerUser(data.name, client, getGUID()); 14 | //将uid写入socket对象中 15 | client.uid = user.uid; 16 | onLineUserArr.push(user); 17 | //向当前用户所在的房间的所有用户广播有新用户加入 18 | var serverLoginData = { 19 | onLineUserArr: onLineUserArr.map(function (u) { return getClientUserByServerUser(u); }), 20 | user: getClientUserByServerUser(getUser(client)) 21 | }; 22 | ack(serverLoginData); 23 | client.broadcast.emit('login', serverLoginData); //向所有连接进来的客户端发送有新用户登录通知 24 | // client.broadcast.emit("login",serverLoginData);//向除了自己以外的所有客户端发送事件 25 | console.log(data.name + ' 加入了多人白板'); 26 | }); 27 | //监听用户链接断开 28 | client.on('disconnect', function (data) { 29 | console.log("disconnect"); 30 | var user = getUser(client); 31 | if (user == null) 32 | return; 33 | logout(user.socket, function () { }); 34 | client.leaveAll(); 35 | }); 36 | //监听用户退出 37 | client.on('logout', function (data, ack) { 38 | console.log("logout"); 39 | var user = getUser(client); 40 | if (user == null) 41 | return; 42 | logout(user.socket, ack); 43 | }); 44 | //监听用户发送的画线数据 45 | client.on('drawLine', function (data, ack) { 46 | //向所有客户端广播发布的消息 47 | var sendData = _.extend({ 48 | user: getClientUserByServerUser(getUser(client)) 49 | }, data); 50 | ack(sendData); 51 | client.broadcast.emit('drawLine', sendData); 52 | }); 53 | client.on("penMove", function (data) { 54 | //向所有客户端广播发布的消息 55 | var sendData = { 56 | user: getClientUserByServerUser(getUser(client)) 57 | }; 58 | sendData.user.position.x = data.x; 59 | sendData.user.position.y = data.y; 60 | client.broadcast.emit('penMove', sendData); 61 | }); 62 | }); 63 | /** 64 | * 根据Socket返回当前用户信息 65 | * 66 | * @param {wb.socketClient|string} client 67 | * @returns {wb.user} 68 | */ 69 | function getUser(client) { 70 | var userArr = []; 71 | var uid; 72 | if (client.uid === undefined) 73 | return null; 74 | uid = client.uid; 75 | userArr = onLineUserArr.filter(function (u) { return u.socket === client; }); 76 | if (userArr.length > 0) { 77 | return userArr[0]; 78 | } 79 | else { 80 | return null; 81 | } 82 | } 83 | /** 84 | * 创建一个服务器的用户实体 85 | * 86 | * @param {string} name 87 | * @param {wb.socketClient} socket 88 | * @param {string} uid 89 | * @returns 90 | */ 91 | function createServerUser(name, socket, uid) { 92 | var user = { 93 | name: name, 94 | socket: socket, 95 | uid: uid, 96 | position: { x: -9999, y: -9999 } 97 | }; 98 | return user; 99 | } 100 | /** 101 | * 根据一个服务器用户返回一个浏览器用户对象 102 | * 103 | * @param {wb.serverUser} serverUser 服务器用户对象 104 | * @returns {wb.clientUser} 105 | */ 106 | function getClientUserByServerUser(serverUser) { 107 | if (serverUser == null) 108 | throw new Error("serverUser is null!"); 109 | var clientUser = _.extend({}, serverUser); 110 | delete clientUser.socket; 111 | // clientUser.position = { x: 0, y: 0 }; 112 | return clientUser; 113 | } 114 | function logout(client, ack) { 115 | var user = getUser(client); 116 | if (user == null) 117 | return false; 118 | onLineUserArr.splice(onLineUserArr.indexOf(user), 1); 119 | //向所有客户端广播有用户退出 120 | var logoData = { 121 | onLineUserArr: onLineUserArr.map(function (u) { return getClientUserByServerUser(u); }), 122 | user: getClientUserByServerUser(user) 123 | }; 124 | ack(logoData); 125 | client.broadcast.emit('logout', logoData); 126 | client.disconnect(); 127 | console.log(user.name + '退出了多人白板'); 128 | return true; 129 | } 130 | /** 131 | * 获得一个不重复的随机数字符串 132 | */ 133 | function getGUID() { 134 | return new Date().getTime() + "" + Math.floor(Math.random() * 89999 + 10000); 135 | } 136 | ; 137 | }; 138 | //# sourceMappingURL=whiteboard.js.map -------------------------------------------------------------------------------- /public/javascripts/draw.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"draw.js","sourceRoot":"","sources":["draw.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /public/javascripts/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var server; 3 | var vm; 4 | /** socket服务器地址 */ 5 | var socketServerUrl = location.protocol + "//" + location.host; 6 | var vmData = { 7 | /** 登录用户名 */ 8 | userName: new Date().getTime().toString().substr(5), 9 | loginUserName: "", 10 | /** 当前用户是否已经登录 */ 11 | isLogin: false, 12 | /** 在线用户列表 */ 13 | onLineUserArr: [], 14 | /** 日志消息数组 */ 15 | logArr: [], 16 | penWidth: draw.penWidth, 17 | penColor: draw.penColor, 18 | JSON: JSON 19 | }; 20 | var vmMethod = { 21 | /** 修改用户名 */ 22 | changeUserName: function () { 23 | if (vm.userName == vm.loginUserName) 24 | return; 25 | if (vm.userName.trim().length == 0) { 26 | alert("请填写用户名"); 27 | vm.userName = vm.loginUserName; 28 | return; 29 | } 30 | clientLogout(); 31 | clinetLogin(); 32 | } 33 | }; 34 | var vmComputed = {}; 35 | var vmWatch = { 36 | penWidth: function (v) { 37 | draw.penWidth = vm.penWidth; 38 | }, 39 | penColor: function (v) { 40 | draw.penColor = vm.penColor; 41 | } 42 | }; 43 | vm = new Vue({ 44 | el: "#main", 45 | data: vmData, 46 | methods: vmMethod, 47 | watch: vmWatch, 48 | computed: vmComputed, 49 | mounted: function () { 50 | }, 51 | created: function () { 52 | console.log("created"); 53 | }, 54 | beforeMount: function () { 55 | console.log("beforeMount"); 56 | } 57 | }); 58 | clinetLogin(); 59 | draw.onDrawLine = function (startPoint, endPoint) { 60 | server.emit("drawLine", { 61 | startPoint: startPoint, 62 | endPoint: endPoint, 63 | color: draw.penColor, 64 | width: draw.penWidth 65 | }, function () { }); 66 | }; 67 | draw.onMouseMove = debounce(drawOnMouseMove, 100, 100); 68 | /** 69 | * 浏览器端发起用户登录 70 | * 71 | * @param {string} username 用户名 72 | */ 73 | function clinetLogin() { 74 | server = io(socketServerUrl, {}); 75 | server.emit("login", { 76 | name: vm.userName 77 | }, function (data) { 78 | log("当前用户登录成功"); 79 | // vm.currentUser = data.user; 80 | vm.onLineUserArr = data.onLineUserArr; 81 | vm.isLogin = true; 82 | vm.loginUserName = data.user.name; 83 | }); 84 | server.on("login", onServerLogin); 85 | server.on("logout", onServerLogout); 86 | server.on("drawLine", onServerDrawLine); 87 | server.on("penMove", onServerPenMove); 88 | } 89 | /** 90 | * 当画笔在canvas上移动时发送给服务器 91 | * 92 | * @param {MouseEvent} e 93 | */ 94 | function drawOnMouseMove(e) { 95 | server.emit("penMove", { 96 | x: e.offsetX, 97 | y: e.offsetY 98 | }); 99 | } 100 | /** 101 | * 当接收到其他用户发来的画笔坐标位置时 102 | * 103 | * @param {wb.serverEmitPenMove} d 104 | */ 105 | function onServerPenMove(d) { 106 | // console.log("onServerPenMove", d); 107 | var user = vm.onLineUserArr.filter(function (u) { return u.uid == d.user.uid; })[0]; 108 | if (user == undefined) 109 | return; 110 | user.position = d.user.position; 111 | } 112 | function clientLogout() { 113 | server.emit("logout", {}, function () { }); 114 | } 115 | /** 116 | * 接受到其他用户登录操作 117 | * 118 | * @param {wb.serverEmitLogin} d 119 | */ 120 | function onServerLogin(d) { 121 | console.log("onServerLogin", d); 122 | vm.onLineUserArr = d.onLineUserArr; 123 | log("新用户<" + d.user.name + ">登录"); 124 | } 125 | /** 126 | * 接收到其他用户退出操作 127 | * 128 | * @param {wb.serverEmitLogout} d 129 | */ 130 | function onServerLogout(d) { 131 | console.log("onServerLogout", d); 132 | vm.onLineUserArr = d.onLineUserArr; 133 | log("用户<" + d.user.name + ">退出"); 134 | } 135 | /** 136 | * 接收到其他用户的划线操作 137 | * 138 | * @param {wb.serverEmitDrawLine} d 139 | */ 140 | function onServerDrawLine(d) { 141 | console.log("onServerDrawLine", d); 142 | var user = vm.onLineUserArr.filter(function (u) { return u.uid == d.user.uid; })[0]; 143 | if (user != undefined) { 144 | user.position = d.endPoint; 145 | } 146 | draw.serverDrawLine(d.startPoint, d.endPoint, d.color, d.width); 147 | } 148 | function log(str) { 149 | vm.logArr.unshift(str); 150 | console.log(str); 151 | } 152 | /** 153 | * 函数防抖 154 | * 155 | * @param {Function} fn 要执行的函数 156 | * @param {number} delay 多少毫秒内的重复调用都不触发 157 | * @param {number} mustRunDelay 多少毫秒以上必须触发一次 158 | * @returns 159 | */ 160 | function debounce(fn, delay, mustRunDelay) { 161 | var timer = null; 162 | var t_start; 163 | return function () { 164 | var context = this, args = arguments, t_curr = +new Date(); 165 | clearTimeout(timer); 166 | if (!t_start) { 167 | t_start = t_curr; 168 | } 169 | if (t_curr - t_start >= mustRunDelay) { 170 | fn.apply(context, args); 171 | t_start = t_curr; 172 | } 173 | else { 174 | timer = setTimeout(function () { 175 | fn.apply(context, args); 176 | }, delay); 177 | } 178 | }; 179 | } 180 | ; 181 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /bin/whiteboard.ts: -------------------------------------------------------------------------------- 1 | import * as socketio from 'socket.io'; 2 | import * as http from 'http'; 3 | import * as _ from "underscore"; 4 | 5 | export = function chatroomio(httpServer: http.Server) { 6 | /** 所有房间字典,内为当前房间的在线登录用户数组 */ 7 | var onLineUserArr: wb.serverUser[] = []; 8 | 9 | var server: SocketIO.Server = socketio(httpServer); 10 | server.on("connection", function (client: wb.socketClient) { 11 | console.log("socket.io on connection!一个用户进入"); 12 | 13 | //监听新用户加入 14 | client.on('login', function (data: wb.clientEmitLogin, ack: (data: wb.serverEmitLoginACK) => void) { 15 | //创建全新的一个服务器端User用户 16 | var user = createServerUser(data.name, client, getGUID()); 17 | 18 | //将uid写入socket对象中 19 | client.uid = user.uid; 20 | 21 | onLineUserArr.push(user); 22 | 23 | //向当前用户所在的房间的所有用户广播有新用户加入 24 | var serverLoginData: wb.serverEmitLogin = { 25 | onLineUserArr: onLineUserArr.map(u => getClientUserByServerUser(u)), 26 | user: getClientUserByServerUser(getUser(client)) 27 | }; 28 | ack(serverLoginData); 29 | client.broadcast.emit('login', serverLoginData);//向所有连接进来的客户端发送有新用户登录通知 30 | // client.broadcast.emit("login",serverLoginData);//向除了自己以外的所有客户端发送事件 31 | console.log(data.name + ' 加入了多人白板'); 32 | 33 | }); 34 | 35 | //监听用户链接断开 36 | client.on('disconnect', function (data: string) { 37 | console.log("disconnect"); 38 | var user = getUser(client); 39 | if (user == null) return; 40 | logout(user.socket, function () { }); 41 | client.leaveAll(); 42 | }); 43 | 44 | //监听用户退出 45 | client.on('logout', function (data: wb.clientEmitLogout, ack: (data) => void) { 46 | console.log("logout"); 47 | var user = getUser(client); 48 | if (user == null) return; 49 | logout(user.socket, ack); 50 | }); 51 | 52 | //监听用户发送的画线数据 53 | client.on('drawLine', function (data: wb.clientEmitDrawLine, ack?: (data) => void) { 54 | //向所有客户端广播发布的消息 55 | var sendData: wb.serverEmitDrawLine = _.extend({ 56 | user: getClientUserByServerUser(getUser(client)) 57 | }, data); 58 | ack(sendData); 59 | client.broadcast.emit('drawLine', sendData); 60 | }); 61 | 62 | client.on("penMove", function (data: wb.clientEmitPenMove) { 63 | //向所有客户端广播发布的消息 64 | var sendData: wb.serverEmitPenMove = { 65 | user: getClientUserByServerUser(getUser(client)) 66 | }; 67 | sendData.user.position.x = data.x; 68 | sendData.user.position.y = data.y; 69 | 70 | client.broadcast.emit('penMove', sendData); 71 | }); 72 | }); 73 | 74 | /** 75 | * 根据Socket返回当前用户信息 76 | * 77 | * @param {wb.socketClient|string} client 78 | * @returns {wb.user} 79 | */ 80 | function getUser(client: wb.socketClient): wb.serverUser { 81 | var userArr: wb.serverUser[] = []; 82 | var uid: string; 83 | if (client.uid === undefined) return null; 84 | uid = client.uid; 85 | userArr = onLineUserArr.filter(u => u.socket === client); 86 | 87 | if (userArr.length > 0) { 88 | return userArr[0]; 89 | } else { 90 | return null; 91 | } 92 | } 93 | 94 | /** 95 | * 创建一个服务器的用户实体 96 | * 97 | * @param {string} name 98 | * @param {wb.socketClient} socket 99 | * @param {string} uid 100 | * @returns 101 | */ 102 | function createServerUser(name: string, socket: wb.socketClient, uid: string) { 103 | var user: wb.serverUser = { 104 | name: name, 105 | socket: socket, 106 | uid: uid, 107 | position: { x: -9999, y: -9999 } 108 | }; 109 | return user; 110 | } 111 | 112 | /** 113 | * 根据一个服务器用户返回一个浏览器用户对象 114 | * 115 | * @param {wb.serverUser} serverUser 服务器用户对象 116 | * @returns {wb.clientUser} 117 | */ 118 | function getClientUserByServerUser(serverUser: wb.serverUser): wb.clientUser { 119 | if(serverUser==null) throw new Error("serverUser is null!"); 120 | var clientUser: wb.clientUser = _.extend({}, serverUser); 121 | delete (clientUser).socket; 122 | // clientUser.position = { x: 0, y: 0 }; 123 | return clientUser; 124 | } 125 | 126 | function logout(client: wb.socketClient, ack: (data) => void): boolean { 127 | var user = getUser(client); 128 | if (user == null) return false; 129 | onLineUserArr.splice(onLineUserArr.indexOf(user), 1); 130 | 131 | //向所有客户端广播有用户退出 132 | var logoData: wb.serverEmitLogout = { 133 | onLineUserArr: onLineUserArr.map(u => getClientUserByServerUser(u)), 134 | user: getClientUserByServerUser(user) 135 | }; 136 | ack(logoData); 137 | client.broadcast.emit('logout', logoData); 138 | client.disconnect(); 139 | console.log(user.name + '退出了多人白板'); 140 | return true; 141 | } 142 | 143 | /** 144 | * 获得一个不重复的随机数字符串 145 | */ 146 | function getGUID(): string { 147 | return new Date().getTime() + "" + Math.floor(Math.random() * 89999 + 10000); 148 | }; 149 | 150 | } 151 | -------------------------------------------------------------------------------- /public/javascripts/index.ts: -------------------------------------------------------------------------------- 1 | import * as vuejs from "vue"; 2 | 3 | var server: SocketIOClient.Socket; 4 | var vm: vuejs & typeof vmData & typeof vmMethod & typeof vmComputed & typeof vmWatch; 5 | /** socket服务器地址 */ 6 | var socketServerUrl = location.protocol + "//" + location.host; 7 | 8 | var vmData = { 9 | /** 登录用户名 */ 10 | userName: new Date().getTime().toString().substr(5), 11 | loginUserName: "", 12 | /** 当前用户是否已经登录 */ 13 | isLogin: false, 14 | /** 在线用户列表 */ 15 | onLineUserArr: [], 16 | /** 日志消息数组 */ 17 | logArr: [], 18 | penWidth: draw.penWidth, 19 | penColor: draw.penColor, 20 | JSON: JSON 21 | }; 22 | var vmMethod = { 23 | /** 修改用户名 */ 24 | changeUserName: function () { 25 | if (vm.userName == vm.loginUserName) return; 26 | if (vm.userName.trim().length == 0) { 27 | alert("请填写用户名"); 28 | vm.userName = vm.loginUserName; 29 | return; 30 | } 31 | clientLogout(); 32 | clinetLogin(); 33 | } 34 | }; 35 | var vmComputed = {}; 36 | var vmWatch = { 37 | penWidth: function (v) { 38 | draw.penWidth = vm.penWidth; 39 | }, 40 | penColor: function (v) { 41 | draw.penColor = vm.penColor; 42 | }, 43 | }; 44 | 45 | vm = new Vue({ 46 | el: "#main", 47 | data: vmData, 48 | methods: vmMethod, 49 | watch: vmWatch, 50 | computed: vmComputed, 51 | mounted(this: typeof vm) { 52 | }, 53 | created(this: typeof vm) { 54 | console.log("created"); 55 | }, 56 | beforeMount() { 57 | console.log("beforeMount"); 58 | } 59 | }); 60 | 61 | clinetLogin(); 62 | draw.onDrawLine = function (startPoint: wb.Point, endPoint: wb.Point) { 63 | server.emit("drawLine", { 64 | startPoint: startPoint, 65 | endPoint: endPoint, 66 | color: draw.penColor, 67 | width: draw.penWidth, 68 | }, function () { }); 69 | } 70 | draw.onMouseMove = debounce(drawOnMouseMove, 100, 100); 71 | 72 | 73 | /** 74 | * 浏览器端发起用户登录 75 | * 76 | * @param {string} username 用户名 77 | */ 78 | function clinetLogin() { 79 | server = io(socketServerUrl, {}); 80 | server.emit("login", { 81 | name: vm.userName 82 | }, function (data: wb.serverEmitLoginACK) { 83 | log("当前用户登录成功"); 84 | // vm.currentUser = data.user; 85 | vm.onLineUserArr = data.onLineUserArr; 86 | vm.isLogin = true; 87 | vm.loginUserName = data.user.name; 88 | }); 89 | 90 | server.on("login", onServerLogin); 91 | server.on("logout", onServerLogout); 92 | server.on("drawLine", onServerDrawLine); 93 | server.on("penMove", onServerPenMove); 94 | } 95 | 96 | /** 97 | * 当画笔在canvas上移动时发送给服务器 98 | * 99 | * @param {MouseEvent} e 100 | */ 101 | function drawOnMouseMove(e: MouseEvent) { 102 | server.emit("penMove", { 103 | x: e.offsetX, 104 | y: e.offsetY, 105 | }); 106 | } 107 | 108 | /** 109 | * 当接收到其他用户发来的画笔坐标位置时 110 | * 111 | * @param {wb.serverEmitPenMove} d 112 | */ 113 | function onServerPenMove(d: wb.serverEmitPenMove) { 114 | // console.log("onServerPenMove", d); 115 | var user: wb.clientUser = vm.onLineUserArr.filter(u => u.uid == d.user.uid)[0]; 116 | if (user == undefined) return; 117 | user.position = d.user.position; 118 | } 119 | 120 | function clientLogout() { 121 | server.emit("logout", {}, function () { }); 122 | } 123 | 124 | /** 125 | * 接受到其他用户登录操作 126 | * 127 | * @param {wb.serverEmitLogin} d 128 | */ 129 | function onServerLogin(d: wb.serverEmitLogin) { 130 | console.log("onServerLogin", d); 131 | vm.onLineUserArr = d.onLineUserArr; 132 | log("新用户<" + d.user.name + ">登录"); 133 | } 134 | 135 | /** 136 | * 接收到其他用户退出操作 137 | * 138 | * @param {wb.serverEmitLogout} d 139 | */ 140 | function onServerLogout(d: wb.serverEmitLogout) { 141 | console.log("onServerLogout", d); 142 | vm.onLineUserArr = d.onLineUserArr; 143 | log("用户<" + d.user.name + ">退出"); 144 | } 145 | 146 | /** 147 | * 接收到其他用户的划线操作 148 | * 149 | * @param {wb.serverEmitDrawLine} d 150 | */ 151 | function onServerDrawLine(d: wb.serverEmitDrawLine) { 152 | console.log("onServerDrawLine", d); 153 | var user: wb.clientUser = vm.onLineUserArr.filter(u => u.uid == d.user.uid)[0]; 154 | if (user != undefined) { 155 | user.position = d.endPoint; 156 | } 157 | draw.serverDrawLine(d.startPoint, d.endPoint, d.color, d.width); 158 | } 159 | 160 | function log(str: any) { 161 | vm.logArr.unshift(str); 162 | console.log(str); 163 | } 164 | 165 | /** 166 | * 函数防抖 167 | * 168 | * @param {Function} fn 要执行的函数 169 | * @param {number} delay 多少毫秒内的重复调用都不触发 170 | * @param {number} mustRunDelay 多少毫秒以上必须触发一次 171 | * @returns 172 | */ 173 | function debounce(fn: Function, delay: number, mustRunDelay: number) { 174 | var timer = null; 175 | var t_start; 176 | return function () { 177 | var context = this, args = arguments, t_curr = +new Date(); 178 | clearTimeout(timer); 179 | if (!t_start) { 180 | t_start = t_curr; 181 | } 182 | if (t_curr - t_start >= mustRunDelay) { 183 | fn.apply(context, args); 184 | t_start = t_curr; 185 | } 186 | else { 187 | timer = setTimeout(function () { 188 | fn.apply(context, args); 189 | }, delay); 190 | } 191 | }; 192 | }; 193 | //-----------------------------ts定义---------------------------- 194 | 195 | interface VueType extends vuejs { 196 | new (option: vuejs.ComponentOptions): vuejs; 197 | } 198 | declare var Vue: VueType; 199 | -------------------------------------------------------------------------------- /public/javascripts/draw.js: -------------------------------------------------------------------------------- 1 | var draw; 2 | (function (draw) { 3 | //-------配置对象------ 4 | /** 画笔颜色 */ 5 | draw.penColor = "#000000"; 6 | /** 画笔宽度直径 */ 7 | draw.penWidth = 4; 8 | /** 每次绘制长度阈值(值越小绘制频率越高,曲线越平滑) */ 9 | draw.drawDistance = draw.penWidth * 2; 10 | /** 线帽类型 */ 11 | draw.lineCap = "round"; 12 | //------全局变量------- 13 | var canvas = document.getElementById("canvas"); 14 | var context = canvas.getContext("2d"); 15 | /** 最后一次绘制的坐标点 */ 16 | var lastPoint = createPoint(); 17 | /** 当前绘制的坐标点 */ 18 | var currPoint = createPoint(); 19 | /** 是否鼠标按下 */ 20 | var isMouseDown = false; 21 | //----可由外部注册的事件---- 22 | draw.onMouseDown = function (e) { }; 23 | draw.onMouseMove = function (e) { }; 24 | draw.onMouseUp = function (e) { }; 25 | draw.onDrawCurveStart = function (startPoint) { }; 26 | draw.onDrawCurveEnd = function (endPoint) { }; 27 | draw.onDrawLine = function (startPoint, endPoint) { }; 28 | init(); 29 | function init() { 30 | bind(); 31 | resetCanvasSize(); 32 | } 33 | // canvas.addEventListener("mouseout", mouseout, false); 34 | function bind() { 35 | //canvas鼠标事件 36 | canvas.addEventListener("mousedown", mousedown, false); 37 | canvas.addEventListener("mousemove", mousemove, false); 38 | canvas.addEventListener("mouseup", mouseup, false); 39 | // canvas.addEventListener("mouseleave", mouseleave, false); 40 | // 监听浏览器窗口宽高变更 41 | window.onresize = resetCanvasSize; 42 | } 43 | /** 44 | * 创建一个Point点 45 | * 46 | * @returns {Point} 47 | */ 48 | function createPoint() { 49 | return { x: 0, y: 0 }; 50 | } 51 | /** 52 | * 重设canvas的宽高,会清空canvas 53 | * 54 | */ 55 | function resetCanvasSize() { 56 | var canvasBox = document.querySelector(".main-right"); 57 | canvas.width = canvasBox.clientWidth; 58 | canvas.height = canvasBox.clientHeight; 59 | } 60 | /** 61 | * 计算两点间距离 62 | * 63 | * @param {Point} pt 64 | * @param {Point} pt2 65 | * @returns 66 | */ 67 | function distance(pt, pt2) { 68 | return Math.sqrt(Math.pow(pt2.x - pt.x, 2) + Math.pow(pt2.y - pt.y, 2)); 69 | } 70 | /** 71 | * 绘制一条线 72 | * 73 | * @param {boolean} [isForce=false] 是否强制绘制,会忽略最小绘制间距设定 74 | * @returns 75 | */ 76 | function drawLine(isForce) { 77 | if (isForce === void 0) { isForce = false; } 78 | if (!isMouseDown) 79 | return; 80 | var d = distance(lastPoint, currPoint); 81 | // console.log(d); 82 | if (isForce || d >= draw.drawDistance) { 83 | draw.onDrawLine(lastPoint, currPoint); 84 | context.beginPath(); 85 | context.lineCap = draw.lineCap; 86 | context.strokeStyle = draw.penColor; 87 | context.lineWidth = draw.penWidth; 88 | context.moveTo(lastPoint.x, lastPoint.y); 89 | context.lineTo(currPoint.x, currPoint.y); 90 | context.stroke(); 91 | context.closePath(); 92 | lastPoint.x = currPoint.x; 93 | lastPoint.y = currPoint.y; 94 | } 95 | } 96 | function dratPoint(pt) { 97 | context.beginPath(); 98 | context.fillStyle = draw.penColor; 99 | context.arc(pt.x, pt.y, draw.penWidth / 2, 0, 2 * Math.PI); 100 | context.fill(); 101 | context.closePath(); 102 | } 103 | function mousemove(e) { 104 | draw.onMouseMove(e); 105 | // debounce(drawOnMouseMove, 100, 300); 106 | currPoint.x = e.offsetX; 107 | currPoint.y = e.offsetY; 108 | drawLine(); 109 | } 110 | function mousedown(e) { 111 | draw.onMouseDown(e); 112 | isMouseDown = true; 113 | currPoint.x = lastPoint.x = e.offsetX; 114 | currPoint.y = lastPoint.y = e.offsetY; 115 | drawCurveStart(new Point(e.offsetX, e.offsetY)); 116 | } 117 | function mouseup(e) { 118 | draw.onMouseUp(e); 119 | drawCurveEnd(new Point(e.offsetX, e.offsetY)); 120 | } 121 | function mouseleave(e) { 122 | // console.log(e); 123 | // var leaveElement: Element = (e.relatedTarget || e.toElement); 124 | // if (leaveElement.parentElement || leaveElement.parentElement.className.indexOf("cursor") >= 0) { 125 | // // leaveElement.parentElement.style.display="none"; 126 | // return; 127 | // }; 128 | drawCurveEnd(new Point(e.offsetX, e.offsetY)); 129 | } 130 | function mouseout(e) { 131 | drawCurveEnd(new Point(e.offsetX, e.offsetY)); 132 | } 133 | /** 134 | * 开始绘制一条曲线,即连续的线(从按下鼠标开始绘制到抬起鼠标结束) 135 | * 136 | */ 137 | function drawCurveStart(startPoint) { 138 | draw.onDrawCurveStart(startPoint); 139 | drawLine(true); 140 | } 141 | /** 142 | * 结束绘制一条连续的线 143 | * 144 | */ 145 | function drawCurveEnd(endPoint) { 146 | draw.onDrawCurveEnd(endPoint); 147 | isMouseDown = false; 148 | } 149 | /** 150 | * 函数防抖 151 | * 152 | * @param {Function} fn 要执行的函数 153 | * @param {number} delay 多少毫秒内的重复调用都不触发 154 | * @param {number} mustRunDelay 多少毫秒以上必须触发一次 155 | * @returns 156 | */ 157 | function debounce(fn, delay, mustRunDelay) { 158 | var timer = null; 159 | var t_start; 160 | return function () { 161 | var context = this, args = arguments, t_curr = +new Date(); 162 | clearTimeout(timer); 163 | if (!t_start) { 164 | t_start = t_curr; 165 | } 166 | if (t_curr - t_start >= mustRunDelay) { 167 | fn.apply(context, args); 168 | t_start = t_curr; 169 | } 170 | else { 171 | timer = setTimeout(function () { 172 | fn.apply(context, args); 173 | }, delay); 174 | } 175 | }; 176 | } 177 | ; 178 | //-----------------外部调用方法----------------- 179 | /** 180 | * 绘制一条从服务器发来线段 181 | * 182 | * @param {Point} startPoint 开始绘制点 183 | * @param {Point} endPoint 结束绘制点 184 | * @param {string} color 颜色 185 | * @param {number} width 宽度 186 | */ 187 | function serverDrawLine(startPoint, endPoint, color, width) { 188 | context.beginPath(); 189 | context.lineCap = draw.lineCap; 190 | context.strokeStyle = color; 191 | context.lineWidth = width; 192 | context.moveTo(startPoint.x, startPoint.y); 193 | context.lineTo(endPoint.x, endPoint.y); 194 | context.stroke(); 195 | context.closePath(); 196 | } 197 | draw.serverDrawLine = serverDrawLine; 198 | /** 199 | * 一个坐标点 200 | * 201 | * @interface Point 202 | */ 203 | var Point = (function () { 204 | function Point(x, y) { 205 | if (x === void 0) { x = 0; } 206 | if (y === void 0) { y = 0; } 207 | this.x = x; 208 | this.y = y; 209 | } 210 | return Point; 211 | }()); 212 | draw.Point = Point; 213 | })(draw || (draw = {})); 214 | //# sourceMappingURL=draw.js.map -------------------------------------------------------------------------------- /public/javascripts/draw.ts: -------------------------------------------------------------------------------- 1 | namespace draw { 2 | //-------配置对象------ 3 | /** 画笔颜色 */ 4 | export var penColor = "#000000"; 5 | /** 画笔宽度直径 */ 6 | export var penWidth = 4; 7 | /** 每次绘制长度阈值(值越小绘制频率越高,曲线越平滑) */ 8 | export var drawDistance = penWidth * 2; 9 | /** 线帽类型 */ 10 | export var lineCap = "round"; 11 | 12 | 13 | //------全局变量------- 14 | var canvas: HTMLCanvasElement = document.getElementById("canvas"); 15 | var context: CanvasRenderingContext2D = canvas.getContext("2d"); 16 | /** 最后一次绘制的坐标点 */ 17 | var lastPoint: Point = createPoint(); 18 | /** 当前绘制的坐标点 */ 19 | var currPoint: Point = createPoint(); 20 | /** 是否鼠标按下 */ 21 | var isMouseDown = false; 22 | 23 | //----可由外部注册的事件---- 24 | export var onMouseDown = function (e: MouseEvent) { }; 25 | export var onMouseMove = function (e: MouseEvent) { }; 26 | export var onMouseUp = function (e: MouseEvent) { }; 27 | export var onDrawCurveStart = function (startPoint: Point) { }; 28 | export var onDrawCurveEnd = function (endPoint: Point) { }; 29 | export var onDrawLine = function (startPoint: Point, endPoint: Point) { } 30 | 31 | 32 | init(); 33 | 34 | function init() { 35 | bind(); 36 | resetCanvasSize(); 37 | } 38 | 39 | // canvas.addEventListener("mouseout", mouseout, false); 40 | function bind() { 41 | //canvas鼠标事件 42 | canvas.addEventListener("mousedown", mousedown, false); 43 | canvas.addEventListener("mousemove", mousemove, false); 44 | canvas.addEventListener("mouseup", mouseup, false); 45 | // canvas.addEventListener("mouseleave", mouseleave, false); 46 | 47 | // 监听浏览器窗口宽高变更 48 | window.onresize = resetCanvasSize; 49 | } 50 | 51 | /** 52 | * 创建一个Point点 53 | * 54 | * @returns {Point} 55 | */ 56 | function createPoint(): Point { 57 | return { x: 0, y: 0 } 58 | } 59 | 60 | /** 61 | * 重设canvas的宽高,会清空canvas 62 | * 63 | */ 64 | function resetCanvasSize() { 65 | var canvasBox = document.querySelector(".main-right"); 66 | canvas.width = canvasBox.clientWidth; 67 | canvas.height = canvasBox.clientHeight; 68 | } 69 | 70 | /** 71 | * 计算两点间距离 72 | * 73 | * @param {Point} pt 74 | * @param {Point} pt2 75 | * @returns 76 | */ 77 | function distance(pt: Point, pt2: Point) { 78 | return Math.sqrt(Math.pow(pt2.x - pt.x, 2) + Math.pow(pt2.y - pt.y, 2)); 79 | } 80 | 81 | /** 82 | * 绘制一条线 83 | * 84 | * @param {boolean} [isForce=false] 是否强制绘制,会忽略最小绘制间距设定 85 | * @returns 86 | */ 87 | function drawLine(isForce: boolean = false) { 88 | if (!isMouseDown) return; 89 | var d = distance(lastPoint, currPoint); 90 | // console.log(d); 91 | 92 | if (isForce || d >= drawDistance) { 93 | onDrawLine(lastPoint, currPoint); 94 | context.beginPath(); 95 | context.lineCap = lineCap; 96 | context.strokeStyle = penColor; 97 | context.lineWidth = penWidth; 98 | context.moveTo(lastPoint.x, lastPoint.y); 99 | context.lineTo(currPoint.x, currPoint.y); 100 | context.stroke(); 101 | context.closePath(); 102 | lastPoint.x = currPoint.x; 103 | lastPoint.y = currPoint.y; 104 | } 105 | } 106 | 107 | function dratPoint(pt: Point) { 108 | context.beginPath(); 109 | context.fillStyle = penColor; 110 | context.arc(pt.x, pt.y, penWidth / 2, 0, 2 * Math.PI); 111 | context.fill(); 112 | context.closePath(); 113 | } 114 | 115 | function mousemove(e: MouseEvent) { 116 | onMouseMove(e); 117 | // debounce(drawOnMouseMove, 100, 300); 118 | currPoint.x = e.offsetX; 119 | currPoint.y = e.offsetY; 120 | drawLine(); 121 | } 122 | 123 | function mousedown(e: MouseEvent) { 124 | onMouseDown(e); 125 | isMouseDown = true; 126 | currPoint.x = lastPoint.x = e.offsetX; 127 | currPoint.y = lastPoint.y = e.offsetY; 128 | drawCurveStart(new Point(e.offsetX, e.offsetY)); 129 | } 130 | 131 | function mouseup(e: MouseEvent) { 132 | onMouseUp(e); 133 | drawCurveEnd(new Point(e.offsetX, e.offsetY)); 134 | } 135 | function mouseleave(e: MouseEvent) { 136 | // console.log(e); 137 | // var leaveElement: Element = (e.relatedTarget || e.toElement); 138 | // if (leaveElement.parentElement || leaveElement.parentElement.className.indexOf("cursor") >= 0) { 139 | // // leaveElement.parentElement.style.display="none"; 140 | // return; 141 | // }; 142 | drawCurveEnd(new Point(e.offsetX, e.offsetY)); 143 | } 144 | function mouseout(e: MouseEvent) { 145 | drawCurveEnd(new Point(e.offsetX, e.offsetY)); 146 | } 147 | 148 | /** 149 | * 开始绘制一条曲线,即连续的线(从按下鼠标开始绘制到抬起鼠标结束) 150 | * 151 | */ 152 | function drawCurveStart(startPoint: Point) { 153 | onDrawCurveStart(startPoint); 154 | drawLine(true); 155 | } 156 | 157 | /** 158 | * 结束绘制一条连续的线 159 | * 160 | */ 161 | function drawCurveEnd(endPoint: Point) { 162 | onDrawCurveEnd(endPoint); 163 | isMouseDown = false; 164 | } 165 | 166 | 167 | /** 168 | * 函数防抖 169 | * 170 | * @param {Function} fn 要执行的函数 171 | * @param {number} delay 多少毫秒内的重复调用都不触发 172 | * @param {number} mustRunDelay 多少毫秒以上必须触发一次 173 | * @returns 174 | */ 175 | function debounce(fn: Function, delay: number, mustRunDelay: number) { 176 | var timer = null; 177 | var t_start; 178 | return function () { 179 | var context = this, args = arguments, t_curr = +new Date(); 180 | clearTimeout(timer); 181 | if (!t_start) { 182 | t_start = t_curr; 183 | } 184 | if (t_curr - t_start >= mustRunDelay) { 185 | fn.apply(context, args); 186 | t_start = t_curr; 187 | } 188 | else { 189 | timer = setTimeout(function () { 190 | fn.apply(context, args); 191 | }, delay); 192 | } 193 | }; 194 | }; 195 | //-----------------外部调用方法----------------- 196 | 197 | /** 198 | * 绘制一条从服务器发来线段 199 | * 200 | * @param {Point} startPoint 开始绘制点 201 | * @param {Point} endPoint 结束绘制点 202 | * @param {string} color 颜色 203 | * @param {number} width 宽度 204 | */ 205 | export function serverDrawLine(startPoint: Point, endPoint: Point, color: string, width: number) { 206 | context.beginPath(); 207 | context.lineCap = lineCap; 208 | context.strokeStyle = color; 209 | context.lineWidth = width; 210 | context.moveTo(startPoint.x, startPoint.y); 211 | context.lineTo(endPoint.x, endPoint.y); 212 | context.stroke(); 213 | context.closePath(); 214 | } 215 | 216 | /** 217 | * 一个坐标点 218 | * 219 | * @interface Point 220 | */ 221 | export class Point { 222 | public x: number; 223 | public y: number; 224 | constructor(x: number = 0, y: number = 0) { 225 | this.x = x; 226 | this.y = y; 227 | } 228 | } 229 | } -------------------------------------------------------------------------------- /public/stylesheets/font-awesome.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | /* FONT PATH 6 | * -------------------------- */ 7 | @font-face { 8 | font-family: 'FontAwesome'; 9 | src: url('../fonts/fontawesome-webfont.eot?v=4.7.0'); 10 | src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg'); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | .fa { 15 | display: inline-block; 16 | font: normal normal normal 14px/1 FontAwesome; 17 | font-size: inherit; 18 | text-rendering: auto; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | /* makes the font 33% larger relative to the icon container */ 23 | .fa-lg { 24 | font-size: 1.33333333em; 25 | line-height: 0.75em; 26 | vertical-align: -15%; 27 | } 28 | .fa-2x { 29 | font-size: 2em; 30 | } 31 | .fa-3x { 32 | font-size: 3em; 33 | } 34 | .fa-4x { 35 | font-size: 4em; 36 | } 37 | .fa-5x { 38 | font-size: 5em; 39 | } 40 | .fa-fw { 41 | width: 1.28571429em; 42 | text-align: center; 43 | } 44 | .fa-ul { 45 | padding-left: 0; 46 | margin-left: 2.14285714em; 47 | list-style-type: none; 48 | } 49 | .fa-ul > li { 50 | position: relative; 51 | } 52 | .fa-li { 53 | position: absolute; 54 | left: -2.14285714em; 55 | width: 2.14285714em; 56 | top: 0.14285714em; 57 | text-align: center; 58 | } 59 | .fa-li.fa-lg { 60 | left: -1.85714286em; 61 | } 62 | .fa-border { 63 | padding: .2em .25em .15em; 64 | border: solid 0.08em #eeeeee; 65 | border-radius: .1em; 66 | } 67 | .fa-pull-left { 68 | float: left; 69 | } 70 | .fa-pull-right { 71 | float: right; 72 | } 73 | .fa.fa-pull-left { 74 | margin-right: .3em; 75 | } 76 | .fa.fa-pull-right { 77 | margin-left: .3em; 78 | } 79 | /* Deprecated as of 4.4.0 */ 80 | .pull-right { 81 | float: right; 82 | } 83 | .pull-left { 84 | float: left; 85 | } 86 | .fa.pull-left { 87 | margin-right: .3em; 88 | } 89 | .fa.pull-right { 90 | margin-left: .3em; 91 | } 92 | .fa-spin { 93 | -webkit-animation: fa-spin 2s infinite linear; 94 | animation: fa-spin 2s infinite linear; 95 | } 96 | .fa-pulse { 97 | -webkit-animation: fa-spin 1s infinite steps(8); 98 | animation: fa-spin 1s infinite steps(8); 99 | } 100 | @-webkit-keyframes fa-spin { 101 | 0% { 102 | -webkit-transform: rotate(0deg); 103 | transform: rotate(0deg); 104 | } 105 | 100% { 106 | -webkit-transform: rotate(359deg); 107 | transform: rotate(359deg); 108 | } 109 | } 110 | @keyframes fa-spin { 111 | 0% { 112 | -webkit-transform: rotate(0deg); 113 | transform: rotate(0deg); 114 | } 115 | 100% { 116 | -webkit-transform: rotate(359deg); 117 | transform: rotate(359deg); 118 | } 119 | } 120 | .fa-rotate-90 { 121 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; 122 | -webkit-transform: rotate(90deg); 123 | -ms-transform: rotate(90deg); 124 | transform: rotate(90deg); 125 | } 126 | .fa-rotate-180 { 127 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; 128 | -webkit-transform: rotate(180deg); 129 | -ms-transform: rotate(180deg); 130 | transform: rotate(180deg); 131 | } 132 | .fa-rotate-270 { 133 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; 134 | -webkit-transform: rotate(270deg); 135 | -ms-transform: rotate(270deg); 136 | transform: rotate(270deg); 137 | } 138 | .fa-flip-horizontal { 139 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; 140 | -webkit-transform: scale(-1, 1); 141 | -ms-transform: scale(-1, 1); 142 | transform: scale(-1, 1); 143 | } 144 | .fa-flip-vertical { 145 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; 146 | -webkit-transform: scale(1, -1); 147 | -ms-transform: scale(1, -1); 148 | transform: scale(1, -1); 149 | } 150 | :root .fa-rotate-90, 151 | :root .fa-rotate-180, 152 | :root .fa-rotate-270, 153 | :root .fa-flip-horizontal, 154 | :root .fa-flip-vertical { 155 | filter: none; 156 | } 157 | .fa-stack { 158 | position: relative; 159 | display: inline-block; 160 | width: 2em; 161 | height: 2em; 162 | line-height: 2em; 163 | vertical-align: middle; 164 | } 165 | .fa-stack-1x, 166 | .fa-stack-2x { 167 | position: absolute; 168 | left: 0; 169 | width: 100%; 170 | text-align: center; 171 | } 172 | .fa-stack-1x { 173 | line-height: inherit; 174 | } 175 | .fa-stack-2x { 176 | font-size: 2em; 177 | } 178 | .fa-inverse { 179 | color: #ffffff; 180 | } 181 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 182 | readers do not read off random characters that represent icons */ 183 | .fa-glass:before { 184 | content: "\f000"; 185 | } 186 | .fa-music:before { 187 | content: "\f001"; 188 | } 189 | .fa-search:before { 190 | content: "\f002"; 191 | } 192 | .fa-envelope-o:before { 193 | content: "\f003"; 194 | } 195 | .fa-heart:before { 196 | content: "\f004"; 197 | } 198 | .fa-star:before { 199 | content: "\f005"; 200 | } 201 | .fa-star-o:before { 202 | content: "\f006"; 203 | } 204 | .fa-user:before { 205 | content: "\f007"; 206 | } 207 | .fa-film:before { 208 | content: "\f008"; 209 | } 210 | .fa-th-large:before { 211 | content: "\f009"; 212 | } 213 | .fa-th:before { 214 | content: "\f00a"; 215 | } 216 | .fa-th-list:before { 217 | content: "\f00b"; 218 | } 219 | .fa-check:before { 220 | content: "\f00c"; 221 | } 222 | .fa-remove:before, 223 | .fa-close:before, 224 | .fa-times:before { 225 | content: "\f00d"; 226 | } 227 | .fa-search-plus:before { 228 | content: "\f00e"; 229 | } 230 | .fa-search-minus:before { 231 | content: "\f010"; 232 | } 233 | .fa-power-off:before { 234 | content: "\f011"; 235 | } 236 | .fa-signal:before { 237 | content: "\f012"; 238 | } 239 | .fa-gear:before, 240 | .fa-cog:before { 241 | content: "\f013"; 242 | } 243 | .fa-trash-o:before { 244 | content: "\f014"; 245 | } 246 | .fa-home:before { 247 | content: "\f015"; 248 | } 249 | .fa-file-o:before { 250 | content: "\f016"; 251 | } 252 | .fa-clock-o:before { 253 | content: "\f017"; 254 | } 255 | .fa-road:before { 256 | content: "\f018"; 257 | } 258 | .fa-download:before { 259 | content: "\f019"; 260 | } 261 | .fa-arrow-circle-o-down:before { 262 | content: "\f01a"; 263 | } 264 | .fa-arrow-circle-o-up:before { 265 | content: "\f01b"; 266 | } 267 | .fa-inbox:before { 268 | content: "\f01c"; 269 | } 270 | .fa-play-circle-o:before { 271 | content: "\f01d"; 272 | } 273 | .fa-rotate-right:before, 274 | .fa-repeat:before { 275 | content: "\f01e"; 276 | } 277 | .fa-refresh:before { 278 | content: "\f021"; 279 | } 280 | .fa-list-alt:before { 281 | content: "\f022"; 282 | } 283 | .fa-lock:before { 284 | content: "\f023"; 285 | } 286 | .fa-flag:before { 287 | content: "\f024"; 288 | } 289 | .fa-headphones:before { 290 | content: "\f025"; 291 | } 292 | .fa-volume-off:before { 293 | content: "\f026"; 294 | } 295 | .fa-volume-down:before { 296 | content: "\f027"; 297 | } 298 | .fa-volume-up:before { 299 | content: "\f028"; 300 | } 301 | .fa-qrcode:before { 302 | content: "\f029"; 303 | } 304 | .fa-barcode:before { 305 | content: "\f02a"; 306 | } 307 | .fa-tag:before { 308 | content: "\f02b"; 309 | } 310 | .fa-tags:before { 311 | content: "\f02c"; 312 | } 313 | .fa-book:before { 314 | content: "\f02d"; 315 | } 316 | .fa-bookmark:before { 317 | content: "\f02e"; 318 | } 319 | .fa-print:before { 320 | content: "\f02f"; 321 | } 322 | .fa-camera:before { 323 | content: "\f030"; 324 | } 325 | .fa-font:before { 326 | content: "\f031"; 327 | } 328 | .fa-bold:before { 329 | content: "\f032"; 330 | } 331 | .fa-italic:before { 332 | content: "\f033"; 333 | } 334 | .fa-text-height:before { 335 | content: "\f034"; 336 | } 337 | .fa-text-width:before { 338 | content: "\f035"; 339 | } 340 | .fa-align-left:before { 341 | content: "\f036"; 342 | } 343 | .fa-align-center:before { 344 | content: "\f037"; 345 | } 346 | .fa-align-right:before { 347 | content: "\f038"; 348 | } 349 | .fa-align-justify:before { 350 | content: "\f039"; 351 | } 352 | .fa-list:before { 353 | content: "\f03a"; 354 | } 355 | .fa-dedent:before, 356 | .fa-outdent:before { 357 | content: "\f03b"; 358 | } 359 | .fa-indent:before { 360 | content: "\f03c"; 361 | } 362 | .fa-video-camera:before { 363 | content: "\f03d"; 364 | } 365 | .fa-photo:before, 366 | .fa-image:before, 367 | .fa-picture-o:before { 368 | content: "\f03e"; 369 | } 370 | .fa-pencil:before { 371 | content: "\f040"; 372 | } 373 | .fa-map-marker:before { 374 | content: "\f041"; 375 | } 376 | .fa-adjust:before { 377 | content: "\f042"; 378 | } 379 | .fa-tint:before { 380 | content: "\f043"; 381 | } 382 | .fa-edit:before, 383 | .fa-pencil-square-o:before { 384 | content: "\f044"; 385 | } 386 | .fa-share-square-o:before { 387 | content: "\f045"; 388 | } 389 | .fa-check-square-o:before { 390 | content: "\f046"; 391 | } 392 | .fa-arrows:before { 393 | content: "\f047"; 394 | } 395 | .fa-step-backward:before { 396 | content: "\f048"; 397 | } 398 | .fa-fast-backward:before { 399 | content: "\f049"; 400 | } 401 | .fa-backward:before { 402 | content: "\f04a"; 403 | } 404 | .fa-play:before { 405 | content: "\f04b"; 406 | } 407 | .fa-pause:before { 408 | content: "\f04c"; 409 | } 410 | .fa-stop:before { 411 | content: "\f04d"; 412 | } 413 | .fa-forward:before { 414 | content: "\f04e"; 415 | } 416 | .fa-fast-forward:before { 417 | content: "\f050"; 418 | } 419 | .fa-step-forward:before { 420 | content: "\f051"; 421 | } 422 | .fa-eject:before { 423 | content: "\f052"; 424 | } 425 | .fa-chevron-left:before { 426 | content: "\f053"; 427 | } 428 | .fa-chevron-right:before { 429 | content: "\f054"; 430 | } 431 | .fa-plus-circle:before { 432 | content: "\f055"; 433 | } 434 | .fa-minus-circle:before { 435 | content: "\f056"; 436 | } 437 | .fa-times-circle:before { 438 | content: "\f057"; 439 | } 440 | .fa-check-circle:before { 441 | content: "\f058"; 442 | } 443 | .fa-question-circle:before { 444 | content: "\f059"; 445 | } 446 | .fa-info-circle:before { 447 | content: "\f05a"; 448 | } 449 | .fa-crosshairs:before { 450 | content: "\f05b"; 451 | } 452 | .fa-times-circle-o:before { 453 | content: "\f05c"; 454 | } 455 | .fa-check-circle-o:before { 456 | content: "\f05d"; 457 | } 458 | .fa-ban:before { 459 | content: "\f05e"; 460 | } 461 | .fa-arrow-left:before { 462 | content: "\f060"; 463 | } 464 | .fa-arrow-right:before { 465 | content: "\f061"; 466 | } 467 | .fa-arrow-up:before { 468 | content: "\f062"; 469 | } 470 | .fa-arrow-down:before { 471 | content: "\f063"; 472 | } 473 | .fa-mail-forward:before, 474 | .fa-share:before { 475 | content: "\f064"; 476 | } 477 | .fa-expand:before { 478 | content: "\f065"; 479 | } 480 | .fa-compress:before { 481 | content: "\f066"; 482 | } 483 | .fa-plus:before { 484 | content: "\f067"; 485 | } 486 | .fa-minus:before { 487 | content: "\f068"; 488 | } 489 | .fa-asterisk:before { 490 | content: "\f069"; 491 | } 492 | .fa-exclamation-circle:before { 493 | content: "\f06a"; 494 | } 495 | .fa-gift:before { 496 | content: "\f06b"; 497 | } 498 | .fa-leaf:before { 499 | content: "\f06c"; 500 | } 501 | .fa-fire:before { 502 | content: "\f06d"; 503 | } 504 | .fa-eye:before { 505 | content: "\f06e"; 506 | } 507 | .fa-eye-slash:before { 508 | content: "\f070"; 509 | } 510 | .fa-warning:before, 511 | .fa-exclamation-triangle:before { 512 | content: "\f071"; 513 | } 514 | .fa-plane:before { 515 | content: "\f072"; 516 | } 517 | .fa-calendar:before { 518 | content: "\f073"; 519 | } 520 | .fa-random:before { 521 | content: "\f074"; 522 | } 523 | .fa-comment:before { 524 | content: "\f075"; 525 | } 526 | .fa-magnet:before { 527 | content: "\f076"; 528 | } 529 | .fa-chevron-up:before { 530 | content: "\f077"; 531 | } 532 | .fa-chevron-down:before { 533 | content: "\f078"; 534 | } 535 | .fa-retweet:before { 536 | content: "\f079"; 537 | } 538 | .fa-shopping-cart:before { 539 | content: "\f07a"; 540 | } 541 | .fa-folder:before { 542 | content: "\f07b"; 543 | } 544 | .fa-folder-open:before { 545 | content: "\f07c"; 546 | } 547 | .fa-arrows-v:before { 548 | content: "\f07d"; 549 | } 550 | .fa-arrows-h:before { 551 | content: "\f07e"; 552 | } 553 | .fa-bar-chart-o:before, 554 | .fa-bar-chart:before { 555 | content: "\f080"; 556 | } 557 | .fa-twitter-square:before { 558 | content: "\f081"; 559 | } 560 | .fa-facebook-square:before { 561 | content: "\f082"; 562 | } 563 | .fa-camera-retro:before { 564 | content: "\f083"; 565 | } 566 | .fa-key:before { 567 | content: "\f084"; 568 | } 569 | .fa-gears:before, 570 | .fa-cogs:before { 571 | content: "\f085"; 572 | } 573 | .fa-comments:before { 574 | content: "\f086"; 575 | } 576 | .fa-thumbs-o-up:before { 577 | content: "\f087"; 578 | } 579 | .fa-thumbs-o-down:before { 580 | content: "\f088"; 581 | } 582 | .fa-star-half:before { 583 | content: "\f089"; 584 | } 585 | .fa-heart-o:before { 586 | content: "\f08a"; 587 | } 588 | .fa-sign-out:before { 589 | content: "\f08b"; 590 | } 591 | .fa-linkedin-square:before { 592 | content: "\f08c"; 593 | } 594 | .fa-thumb-tack:before { 595 | content: "\f08d"; 596 | } 597 | .fa-external-link:before { 598 | content: "\f08e"; 599 | } 600 | .fa-sign-in:before { 601 | content: "\f090"; 602 | } 603 | .fa-trophy:before { 604 | content: "\f091"; 605 | } 606 | .fa-github-square:before { 607 | content: "\f092"; 608 | } 609 | .fa-upload:before { 610 | content: "\f093"; 611 | } 612 | .fa-lemon-o:before { 613 | content: "\f094"; 614 | } 615 | .fa-phone:before { 616 | content: "\f095"; 617 | } 618 | .fa-square-o:before { 619 | content: "\f096"; 620 | } 621 | .fa-bookmark-o:before { 622 | content: "\f097"; 623 | } 624 | .fa-phone-square:before { 625 | content: "\f098"; 626 | } 627 | .fa-twitter:before { 628 | content: "\f099"; 629 | } 630 | .fa-facebook-f:before, 631 | .fa-facebook:before { 632 | content: "\f09a"; 633 | } 634 | .fa-github:before { 635 | content: "\f09b"; 636 | } 637 | .fa-unlock:before { 638 | content: "\f09c"; 639 | } 640 | .fa-credit-card:before { 641 | content: "\f09d"; 642 | } 643 | .fa-feed:before, 644 | .fa-rss:before { 645 | content: "\f09e"; 646 | } 647 | .fa-hdd-o:before { 648 | content: "\f0a0"; 649 | } 650 | .fa-bullhorn:before { 651 | content: "\f0a1"; 652 | } 653 | .fa-bell:before { 654 | content: "\f0f3"; 655 | } 656 | .fa-certificate:before { 657 | content: "\f0a3"; 658 | } 659 | .fa-hand-o-right:before { 660 | content: "\f0a4"; 661 | } 662 | .fa-hand-o-left:before { 663 | content: "\f0a5"; 664 | } 665 | .fa-hand-o-up:before { 666 | content: "\f0a6"; 667 | } 668 | .fa-hand-o-down:before { 669 | content: "\f0a7"; 670 | } 671 | .fa-arrow-circle-left:before { 672 | content: "\f0a8"; 673 | } 674 | .fa-arrow-circle-right:before { 675 | content: "\f0a9"; 676 | } 677 | .fa-arrow-circle-up:before { 678 | content: "\f0aa"; 679 | } 680 | .fa-arrow-circle-down:before { 681 | content: "\f0ab"; 682 | } 683 | .fa-globe:before { 684 | content: "\f0ac"; 685 | } 686 | .fa-wrench:before { 687 | content: "\f0ad"; 688 | } 689 | .fa-tasks:before { 690 | content: "\f0ae"; 691 | } 692 | .fa-filter:before { 693 | content: "\f0b0"; 694 | } 695 | .fa-briefcase:before { 696 | content: "\f0b1"; 697 | } 698 | .fa-arrows-alt:before { 699 | content: "\f0b2"; 700 | } 701 | .fa-group:before, 702 | .fa-users:before { 703 | content: "\f0c0"; 704 | } 705 | .fa-chain:before, 706 | .fa-link:before { 707 | content: "\f0c1"; 708 | } 709 | .fa-cloud:before { 710 | content: "\f0c2"; 711 | } 712 | .fa-flask:before { 713 | content: "\f0c3"; 714 | } 715 | .fa-cut:before, 716 | .fa-scissors:before { 717 | content: "\f0c4"; 718 | } 719 | .fa-copy:before, 720 | .fa-files-o:before { 721 | content: "\f0c5"; 722 | } 723 | .fa-paperclip:before { 724 | content: "\f0c6"; 725 | } 726 | .fa-save:before, 727 | .fa-floppy-o:before { 728 | content: "\f0c7"; 729 | } 730 | .fa-square:before { 731 | content: "\f0c8"; 732 | } 733 | .fa-navicon:before, 734 | .fa-reorder:before, 735 | .fa-bars:before { 736 | content: "\f0c9"; 737 | } 738 | .fa-list-ul:before { 739 | content: "\f0ca"; 740 | } 741 | .fa-list-ol:before { 742 | content: "\f0cb"; 743 | } 744 | .fa-strikethrough:before { 745 | content: "\f0cc"; 746 | } 747 | .fa-underline:before { 748 | content: "\f0cd"; 749 | } 750 | .fa-table:before { 751 | content: "\f0ce"; 752 | } 753 | .fa-magic:before { 754 | content: "\f0d0"; 755 | } 756 | .fa-truck:before { 757 | content: "\f0d1"; 758 | } 759 | .fa-pinterest:before { 760 | content: "\f0d2"; 761 | } 762 | .fa-pinterest-square:before { 763 | content: "\f0d3"; 764 | } 765 | .fa-google-plus-square:before { 766 | content: "\f0d4"; 767 | } 768 | .fa-google-plus:before { 769 | content: "\f0d5"; 770 | } 771 | .fa-money:before { 772 | content: "\f0d6"; 773 | } 774 | .fa-caret-down:before { 775 | content: "\f0d7"; 776 | } 777 | .fa-caret-up:before { 778 | content: "\f0d8"; 779 | } 780 | .fa-caret-left:before { 781 | content: "\f0d9"; 782 | } 783 | .fa-caret-right:before { 784 | content: "\f0da"; 785 | } 786 | .fa-columns:before { 787 | content: "\f0db"; 788 | } 789 | .fa-unsorted:before, 790 | .fa-sort:before { 791 | content: "\f0dc"; 792 | } 793 | .fa-sort-down:before, 794 | .fa-sort-desc:before { 795 | content: "\f0dd"; 796 | } 797 | .fa-sort-up:before, 798 | .fa-sort-asc:before { 799 | content: "\f0de"; 800 | } 801 | .fa-envelope:before { 802 | content: "\f0e0"; 803 | } 804 | .fa-linkedin:before { 805 | content: "\f0e1"; 806 | } 807 | .fa-rotate-left:before, 808 | .fa-undo:before { 809 | content: "\f0e2"; 810 | } 811 | .fa-legal:before, 812 | .fa-gavel:before { 813 | content: "\f0e3"; 814 | } 815 | .fa-dashboard:before, 816 | .fa-tachometer:before { 817 | content: "\f0e4"; 818 | } 819 | .fa-comment-o:before { 820 | content: "\f0e5"; 821 | } 822 | .fa-comments-o:before { 823 | content: "\f0e6"; 824 | } 825 | .fa-flash:before, 826 | .fa-bolt:before { 827 | content: "\f0e7"; 828 | } 829 | .fa-sitemap:before { 830 | content: "\f0e8"; 831 | } 832 | .fa-umbrella:before { 833 | content: "\f0e9"; 834 | } 835 | .fa-paste:before, 836 | .fa-clipboard:before { 837 | content: "\f0ea"; 838 | } 839 | .fa-lightbulb-o:before { 840 | content: "\f0eb"; 841 | } 842 | .fa-exchange:before { 843 | content: "\f0ec"; 844 | } 845 | .fa-cloud-download:before { 846 | content: "\f0ed"; 847 | } 848 | .fa-cloud-upload:before { 849 | content: "\f0ee"; 850 | } 851 | .fa-user-md:before { 852 | content: "\f0f0"; 853 | } 854 | .fa-stethoscope:before { 855 | content: "\f0f1"; 856 | } 857 | .fa-suitcase:before { 858 | content: "\f0f2"; 859 | } 860 | .fa-bell-o:before { 861 | content: "\f0a2"; 862 | } 863 | .fa-coffee:before { 864 | content: "\f0f4"; 865 | } 866 | .fa-cutlery:before { 867 | content: "\f0f5"; 868 | } 869 | .fa-file-text-o:before { 870 | content: "\f0f6"; 871 | } 872 | .fa-building-o:before { 873 | content: "\f0f7"; 874 | } 875 | .fa-hospital-o:before { 876 | content: "\f0f8"; 877 | } 878 | .fa-ambulance:before { 879 | content: "\f0f9"; 880 | } 881 | .fa-medkit:before { 882 | content: "\f0fa"; 883 | } 884 | .fa-fighter-jet:before { 885 | content: "\f0fb"; 886 | } 887 | .fa-beer:before { 888 | content: "\f0fc"; 889 | } 890 | .fa-h-square:before { 891 | content: "\f0fd"; 892 | } 893 | .fa-plus-square:before { 894 | content: "\f0fe"; 895 | } 896 | .fa-angle-double-left:before { 897 | content: "\f100"; 898 | } 899 | .fa-angle-double-right:before { 900 | content: "\f101"; 901 | } 902 | .fa-angle-double-up:before { 903 | content: "\f102"; 904 | } 905 | .fa-angle-double-down:before { 906 | content: "\f103"; 907 | } 908 | .fa-angle-left:before { 909 | content: "\f104"; 910 | } 911 | .fa-angle-right:before { 912 | content: "\f105"; 913 | } 914 | .fa-angle-up:before { 915 | content: "\f106"; 916 | } 917 | .fa-angle-down:before { 918 | content: "\f107"; 919 | } 920 | .fa-desktop:before { 921 | content: "\f108"; 922 | } 923 | .fa-laptop:before { 924 | content: "\f109"; 925 | } 926 | .fa-tablet:before { 927 | content: "\f10a"; 928 | } 929 | .fa-mobile-phone:before, 930 | .fa-mobile:before { 931 | content: "\f10b"; 932 | } 933 | .fa-circle-o:before { 934 | content: "\f10c"; 935 | } 936 | .fa-quote-left:before { 937 | content: "\f10d"; 938 | } 939 | .fa-quote-right:before { 940 | content: "\f10e"; 941 | } 942 | .fa-spinner:before { 943 | content: "\f110"; 944 | } 945 | .fa-circle:before { 946 | content: "\f111"; 947 | } 948 | .fa-mail-reply:before, 949 | .fa-reply:before { 950 | content: "\f112"; 951 | } 952 | .fa-github-alt:before { 953 | content: "\f113"; 954 | } 955 | .fa-folder-o:before { 956 | content: "\f114"; 957 | } 958 | .fa-folder-open-o:before { 959 | content: "\f115"; 960 | } 961 | .fa-smile-o:before { 962 | content: "\f118"; 963 | } 964 | .fa-frown-o:before { 965 | content: "\f119"; 966 | } 967 | .fa-meh-o:before { 968 | content: "\f11a"; 969 | } 970 | .fa-gamepad:before { 971 | content: "\f11b"; 972 | } 973 | .fa-keyboard-o:before { 974 | content: "\f11c"; 975 | } 976 | .fa-flag-o:before { 977 | content: "\f11d"; 978 | } 979 | .fa-flag-checkered:before { 980 | content: "\f11e"; 981 | } 982 | .fa-terminal:before { 983 | content: "\f120"; 984 | } 985 | .fa-code:before { 986 | content: "\f121"; 987 | } 988 | .fa-mail-reply-all:before, 989 | .fa-reply-all:before { 990 | content: "\f122"; 991 | } 992 | .fa-star-half-empty:before, 993 | .fa-star-half-full:before, 994 | .fa-star-half-o:before { 995 | content: "\f123"; 996 | } 997 | .fa-location-arrow:before { 998 | content: "\f124"; 999 | } 1000 | .fa-crop:before { 1001 | content: "\f125"; 1002 | } 1003 | .fa-code-fork:before { 1004 | content: "\f126"; 1005 | } 1006 | .fa-unlink:before, 1007 | .fa-chain-broken:before { 1008 | content: "\f127"; 1009 | } 1010 | .fa-question:before { 1011 | content: "\f128"; 1012 | } 1013 | .fa-info:before { 1014 | content: "\f129"; 1015 | } 1016 | .fa-exclamation:before { 1017 | content: "\f12a"; 1018 | } 1019 | .fa-superscript:before { 1020 | content: "\f12b"; 1021 | } 1022 | .fa-subscript:before { 1023 | content: "\f12c"; 1024 | } 1025 | .fa-eraser:before { 1026 | content: "\f12d"; 1027 | } 1028 | .fa-puzzle-piece:before { 1029 | content: "\f12e"; 1030 | } 1031 | .fa-microphone:before { 1032 | content: "\f130"; 1033 | } 1034 | .fa-microphone-slash:before { 1035 | content: "\f131"; 1036 | } 1037 | .fa-shield:before { 1038 | content: "\f132"; 1039 | } 1040 | .fa-calendar-o:before { 1041 | content: "\f133"; 1042 | } 1043 | .fa-fire-extinguisher:before { 1044 | content: "\f134"; 1045 | } 1046 | .fa-rocket:before { 1047 | content: "\f135"; 1048 | } 1049 | .fa-maxcdn:before { 1050 | content: "\f136"; 1051 | } 1052 | .fa-chevron-circle-left:before { 1053 | content: "\f137"; 1054 | } 1055 | .fa-chevron-circle-right:before { 1056 | content: "\f138"; 1057 | } 1058 | .fa-chevron-circle-up:before { 1059 | content: "\f139"; 1060 | } 1061 | .fa-chevron-circle-down:before { 1062 | content: "\f13a"; 1063 | } 1064 | .fa-html5:before { 1065 | content: "\f13b"; 1066 | } 1067 | .fa-css3:before { 1068 | content: "\f13c"; 1069 | } 1070 | .fa-anchor:before { 1071 | content: "\f13d"; 1072 | } 1073 | .fa-unlock-alt:before { 1074 | content: "\f13e"; 1075 | } 1076 | .fa-bullseye:before { 1077 | content: "\f140"; 1078 | } 1079 | .fa-ellipsis-h:before { 1080 | content: "\f141"; 1081 | } 1082 | .fa-ellipsis-v:before { 1083 | content: "\f142"; 1084 | } 1085 | .fa-rss-square:before { 1086 | content: "\f143"; 1087 | } 1088 | .fa-play-circle:before { 1089 | content: "\f144"; 1090 | } 1091 | .fa-ticket:before { 1092 | content: "\f145"; 1093 | } 1094 | .fa-minus-square:before { 1095 | content: "\f146"; 1096 | } 1097 | .fa-minus-square-o:before { 1098 | content: "\f147"; 1099 | } 1100 | .fa-level-up:before { 1101 | content: "\f148"; 1102 | } 1103 | .fa-level-down:before { 1104 | content: "\f149"; 1105 | } 1106 | .fa-check-square:before { 1107 | content: "\f14a"; 1108 | } 1109 | .fa-pencil-square:before { 1110 | content: "\f14b"; 1111 | } 1112 | .fa-external-link-square:before { 1113 | content: "\f14c"; 1114 | } 1115 | .fa-share-square:before { 1116 | content: "\f14d"; 1117 | } 1118 | .fa-compass:before { 1119 | content: "\f14e"; 1120 | } 1121 | .fa-toggle-down:before, 1122 | .fa-caret-square-o-down:before { 1123 | content: "\f150"; 1124 | } 1125 | .fa-toggle-up:before, 1126 | .fa-caret-square-o-up:before { 1127 | content: "\f151"; 1128 | } 1129 | .fa-toggle-right:before, 1130 | .fa-caret-square-o-right:before { 1131 | content: "\f152"; 1132 | } 1133 | .fa-euro:before, 1134 | .fa-eur:before { 1135 | content: "\f153"; 1136 | } 1137 | .fa-gbp:before { 1138 | content: "\f154"; 1139 | } 1140 | .fa-dollar:before, 1141 | .fa-usd:before { 1142 | content: "\f155"; 1143 | } 1144 | .fa-rupee:before, 1145 | .fa-inr:before { 1146 | content: "\f156"; 1147 | } 1148 | .fa-cny:before, 1149 | .fa-rmb:before, 1150 | .fa-yen:before, 1151 | .fa-jpy:before { 1152 | content: "\f157"; 1153 | } 1154 | .fa-ruble:before, 1155 | .fa-rouble:before, 1156 | .fa-rub:before { 1157 | content: "\f158"; 1158 | } 1159 | .fa-won:before, 1160 | .fa-krw:before { 1161 | content: "\f159"; 1162 | } 1163 | .fa-bitcoin:before, 1164 | .fa-btc:before { 1165 | content: "\f15a"; 1166 | } 1167 | .fa-file:before { 1168 | content: "\f15b"; 1169 | } 1170 | .fa-file-text:before { 1171 | content: "\f15c"; 1172 | } 1173 | .fa-sort-alpha-asc:before { 1174 | content: "\f15d"; 1175 | } 1176 | .fa-sort-alpha-desc:before { 1177 | content: "\f15e"; 1178 | } 1179 | .fa-sort-amount-asc:before { 1180 | content: "\f160"; 1181 | } 1182 | .fa-sort-amount-desc:before { 1183 | content: "\f161"; 1184 | } 1185 | .fa-sort-numeric-asc:before { 1186 | content: "\f162"; 1187 | } 1188 | .fa-sort-numeric-desc:before { 1189 | content: "\f163"; 1190 | } 1191 | .fa-thumbs-up:before { 1192 | content: "\f164"; 1193 | } 1194 | .fa-thumbs-down:before { 1195 | content: "\f165"; 1196 | } 1197 | .fa-youtube-square:before { 1198 | content: "\f166"; 1199 | } 1200 | .fa-youtube:before { 1201 | content: "\f167"; 1202 | } 1203 | .fa-xing:before { 1204 | content: "\f168"; 1205 | } 1206 | .fa-xing-square:before { 1207 | content: "\f169"; 1208 | } 1209 | .fa-youtube-play:before { 1210 | content: "\f16a"; 1211 | } 1212 | .fa-dropbox:before { 1213 | content: "\f16b"; 1214 | } 1215 | .fa-stack-overflow:before { 1216 | content: "\f16c"; 1217 | } 1218 | .fa-instagram:before { 1219 | content: "\f16d"; 1220 | } 1221 | .fa-flickr:before { 1222 | content: "\f16e"; 1223 | } 1224 | .fa-adn:before { 1225 | content: "\f170"; 1226 | } 1227 | .fa-bitbucket:before { 1228 | content: "\f171"; 1229 | } 1230 | .fa-bitbucket-square:before { 1231 | content: "\f172"; 1232 | } 1233 | .fa-tumblr:before { 1234 | content: "\f173"; 1235 | } 1236 | .fa-tumblr-square:before { 1237 | content: "\f174"; 1238 | } 1239 | .fa-long-arrow-down:before { 1240 | content: "\f175"; 1241 | } 1242 | .fa-long-arrow-up:before { 1243 | content: "\f176"; 1244 | } 1245 | .fa-long-arrow-left:before { 1246 | content: "\f177"; 1247 | } 1248 | .fa-long-arrow-right:before { 1249 | content: "\f178"; 1250 | } 1251 | .fa-apple:before { 1252 | content: "\f179"; 1253 | } 1254 | .fa-windows:before { 1255 | content: "\f17a"; 1256 | } 1257 | .fa-android:before { 1258 | content: "\f17b"; 1259 | } 1260 | .fa-linux:before { 1261 | content: "\f17c"; 1262 | } 1263 | .fa-dribbble:before { 1264 | content: "\f17d"; 1265 | } 1266 | .fa-skype:before { 1267 | content: "\f17e"; 1268 | } 1269 | .fa-foursquare:before { 1270 | content: "\f180"; 1271 | } 1272 | .fa-trello:before { 1273 | content: "\f181"; 1274 | } 1275 | .fa-female:before { 1276 | content: "\f182"; 1277 | } 1278 | .fa-male:before { 1279 | content: "\f183"; 1280 | } 1281 | .fa-gittip:before, 1282 | .fa-gratipay:before { 1283 | content: "\f184"; 1284 | } 1285 | .fa-sun-o:before { 1286 | content: "\f185"; 1287 | } 1288 | .fa-moon-o:before { 1289 | content: "\f186"; 1290 | } 1291 | .fa-archive:before { 1292 | content: "\f187"; 1293 | } 1294 | .fa-bug:before { 1295 | content: "\f188"; 1296 | } 1297 | .fa-vk:before { 1298 | content: "\f189"; 1299 | } 1300 | .fa-weibo:before { 1301 | content: "\f18a"; 1302 | } 1303 | .fa-renren:before { 1304 | content: "\f18b"; 1305 | } 1306 | .fa-pagelines:before { 1307 | content: "\f18c"; 1308 | } 1309 | .fa-stack-exchange:before { 1310 | content: "\f18d"; 1311 | } 1312 | .fa-arrow-circle-o-right:before { 1313 | content: "\f18e"; 1314 | } 1315 | .fa-arrow-circle-o-left:before { 1316 | content: "\f190"; 1317 | } 1318 | .fa-toggle-left:before, 1319 | .fa-caret-square-o-left:before { 1320 | content: "\f191"; 1321 | } 1322 | .fa-dot-circle-o:before { 1323 | content: "\f192"; 1324 | } 1325 | .fa-wheelchair:before { 1326 | content: "\f193"; 1327 | } 1328 | .fa-vimeo-square:before { 1329 | content: "\f194"; 1330 | } 1331 | .fa-turkish-lira:before, 1332 | .fa-try:before { 1333 | content: "\f195"; 1334 | } 1335 | .fa-plus-square-o:before { 1336 | content: "\f196"; 1337 | } 1338 | .fa-space-shuttle:before { 1339 | content: "\f197"; 1340 | } 1341 | .fa-slack:before { 1342 | content: "\f198"; 1343 | } 1344 | .fa-envelope-square:before { 1345 | content: "\f199"; 1346 | } 1347 | .fa-wordpress:before { 1348 | content: "\f19a"; 1349 | } 1350 | .fa-openid:before { 1351 | content: "\f19b"; 1352 | } 1353 | .fa-institution:before, 1354 | .fa-bank:before, 1355 | .fa-university:before { 1356 | content: "\f19c"; 1357 | } 1358 | .fa-mortar-board:before, 1359 | .fa-graduation-cap:before { 1360 | content: "\f19d"; 1361 | } 1362 | .fa-yahoo:before { 1363 | content: "\f19e"; 1364 | } 1365 | .fa-google:before { 1366 | content: "\f1a0"; 1367 | } 1368 | .fa-reddit:before { 1369 | content: "\f1a1"; 1370 | } 1371 | .fa-reddit-square:before { 1372 | content: "\f1a2"; 1373 | } 1374 | .fa-stumbleupon-circle:before { 1375 | content: "\f1a3"; 1376 | } 1377 | .fa-stumbleupon:before { 1378 | content: "\f1a4"; 1379 | } 1380 | .fa-delicious:before { 1381 | content: "\f1a5"; 1382 | } 1383 | .fa-digg:before { 1384 | content: "\f1a6"; 1385 | } 1386 | .fa-pied-piper-pp:before { 1387 | content: "\f1a7"; 1388 | } 1389 | .fa-pied-piper-alt:before { 1390 | content: "\f1a8"; 1391 | } 1392 | .fa-drupal:before { 1393 | content: "\f1a9"; 1394 | } 1395 | .fa-joomla:before { 1396 | content: "\f1aa"; 1397 | } 1398 | .fa-language:before { 1399 | content: "\f1ab"; 1400 | } 1401 | .fa-fax:before { 1402 | content: "\f1ac"; 1403 | } 1404 | .fa-building:before { 1405 | content: "\f1ad"; 1406 | } 1407 | .fa-child:before { 1408 | content: "\f1ae"; 1409 | } 1410 | .fa-paw:before { 1411 | content: "\f1b0"; 1412 | } 1413 | .fa-spoon:before { 1414 | content: "\f1b1"; 1415 | } 1416 | .fa-cube:before { 1417 | content: "\f1b2"; 1418 | } 1419 | .fa-cubes:before { 1420 | content: "\f1b3"; 1421 | } 1422 | .fa-behance:before { 1423 | content: "\f1b4"; 1424 | } 1425 | .fa-behance-square:before { 1426 | content: "\f1b5"; 1427 | } 1428 | .fa-steam:before { 1429 | content: "\f1b6"; 1430 | } 1431 | .fa-steam-square:before { 1432 | content: "\f1b7"; 1433 | } 1434 | .fa-recycle:before { 1435 | content: "\f1b8"; 1436 | } 1437 | .fa-automobile:before, 1438 | .fa-car:before { 1439 | content: "\f1b9"; 1440 | } 1441 | .fa-cab:before, 1442 | .fa-taxi:before { 1443 | content: "\f1ba"; 1444 | } 1445 | .fa-tree:before { 1446 | content: "\f1bb"; 1447 | } 1448 | .fa-spotify:before { 1449 | content: "\f1bc"; 1450 | } 1451 | .fa-deviantart:before { 1452 | content: "\f1bd"; 1453 | } 1454 | .fa-soundcloud:before { 1455 | content: "\f1be"; 1456 | } 1457 | .fa-database:before { 1458 | content: "\f1c0"; 1459 | } 1460 | .fa-file-pdf-o:before { 1461 | content: "\f1c1"; 1462 | } 1463 | .fa-file-word-o:before { 1464 | content: "\f1c2"; 1465 | } 1466 | .fa-file-excel-o:before { 1467 | content: "\f1c3"; 1468 | } 1469 | .fa-file-powerpoint-o:before { 1470 | content: "\f1c4"; 1471 | } 1472 | .fa-file-photo-o:before, 1473 | .fa-file-picture-o:before, 1474 | .fa-file-image-o:before { 1475 | content: "\f1c5"; 1476 | } 1477 | .fa-file-zip-o:before, 1478 | .fa-file-archive-o:before { 1479 | content: "\f1c6"; 1480 | } 1481 | .fa-file-sound-o:before, 1482 | .fa-file-audio-o:before { 1483 | content: "\f1c7"; 1484 | } 1485 | .fa-file-movie-o:before, 1486 | .fa-file-video-o:before { 1487 | content: "\f1c8"; 1488 | } 1489 | .fa-file-code-o:before { 1490 | content: "\f1c9"; 1491 | } 1492 | .fa-vine:before { 1493 | content: "\f1ca"; 1494 | } 1495 | .fa-codepen:before { 1496 | content: "\f1cb"; 1497 | } 1498 | .fa-jsfiddle:before { 1499 | content: "\f1cc"; 1500 | } 1501 | .fa-life-bouy:before, 1502 | .fa-life-buoy:before, 1503 | .fa-life-saver:before, 1504 | .fa-support:before, 1505 | .fa-life-ring:before { 1506 | content: "\f1cd"; 1507 | } 1508 | .fa-circle-o-notch:before { 1509 | content: "\f1ce"; 1510 | } 1511 | .fa-ra:before, 1512 | .fa-resistance:before, 1513 | .fa-rebel:before { 1514 | content: "\f1d0"; 1515 | } 1516 | .fa-ge:before, 1517 | .fa-empire:before { 1518 | content: "\f1d1"; 1519 | } 1520 | .fa-git-square:before { 1521 | content: "\f1d2"; 1522 | } 1523 | .fa-git:before { 1524 | content: "\f1d3"; 1525 | } 1526 | .fa-y-combinator-square:before, 1527 | .fa-yc-square:before, 1528 | .fa-hacker-news:before { 1529 | content: "\f1d4"; 1530 | } 1531 | .fa-tencent-weibo:before { 1532 | content: "\f1d5"; 1533 | } 1534 | .fa-qq:before { 1535 | content: "\f1d6"; 1536 | } 1537 | .fa-wechat:before, 1538 | .fa-weixin:before { 1539 | content: "\f1d7"; 1540 | } 1541 | .fa-send:before, 1542 | .fa-paper-plane:before { 1543 | content: "\f1d8"; 1544 | } 1545 | .fa-send-o:before, 1546 | .fa-paper-plane-o:before { 1547 | content: "\f1d9"; 1548 | } 1549 | .fa-history:before { 1550 | content: "\f1da"; 1551 | } 1552 | .fa-circle-thin:before { 1553 | content: "\f1db"; 1554 | } 1555 | .fa-header:before { 1556 | content: "\f1dc"; 1557 | } 1558 | .fa-paragraph:before { 1559 | content: "\f1dd"; 1560 | } 1561 | .fa-sliders:before { 1562 | content: "\f1de"; 1563 | } 1564 | .fa-share-alt:before { 1565 | content: "\f1e0"; 1566 | } 1567 | .fa-share-alt-square:before { 1568 | content: "\f1e1"; 1569 | } 1570 | .fa-bomb:before { 1571 | content: "\f1e2"; 1572 | } 1573 | .fa-soccer-ball-o:before, 1574 | .fa-futbol-o:before { 1575 | content: "\f1e3"; 1576 | } 1577 | .fa-tty:before { 1578 | content: "\f1e4"; 1579 | } 1580 | .fa-binoculars:before { 1581 | content: "\f1e5"; 1582 | } 1583 | .fa-plug:before { 1584 | content: "\f1e6"; 1585 | } 1586 | .fa-slideshare:before { 1587 | content: "\f1e7"; 1588 | } 1589 | .fa-twitch:before { 1590 | content: "\f1e8"; 1591 | } 1592 | .fa-yelp:before { 1593 | content: "\f1e9"; 1594 | } 1595 | .fa-newspaper-o:before { 1596 | content: "\f1ea"; 1597 | } 1598 | .fa-wifi:before { 1599 | content: "\f1eb"; 1600 | } 1601 | .fa-calculator:before { 1602 | content: "\f1ec"; 1603 | } 1604 | .fa-paypal:before { 1605 | content: "\f1ed"; 1606 | } 1607 | .fa-google-wallet:before { 1608 | content: "\f1ee"; 1609 | } 1610 | .fa-cc-visa:before { 1611 | content: "\f1f0"; 1612 | } 1613 | .fa-cc-mastercard:before { 1614 | content: "\f1f1"; 1615 | } 1616 | .fa-cc-discover:before { 1617 | content: "\f1f2"; 1618 | } 1619 | .fa-cc-amex:before { 1620 | content: "\f1f3"; 1621 | } 1622 | .fa-cc-paypal:before { 1623 | content: "\f1f4"; 1624 | } 1625 | .fa-cc-stripe:before { 1626 | content: "\f1f5"; 1627 | } 1628 | .fa-bell-slash:before { 1629 | content: "\f1f6"; 1630 | } 1631 | .fa-bell-slash-o:before { 1632 | content: "\f1f7"; 1633 | } 1634 | .fa-trash:before { 1635 | content: "\f1f8"; 1636 | } 1637 | .fa-copyright:before { 1638 | content: "\f1f9"; 1639 | } 1640 | .fa-at:before { 1641 | content: "\f1fa"; 1642 | } 1643 | .fa-eyedropper:before { 1644 | content: "\f1fb"; 1645 | } 1646 | .fa-paint-brush:before { 1647 | content: "\f1fc"; 1648 | } 1649 | .fa-birthday-cake:before { 1650 | content: "\f1fd"; 1651 | } 1652 | .fa-area-chart:before { 1653 | content: "\f1fe"; 1654 | } 1655 | .fa-pie-chart:before { 1656 | content: "\f200"; 1657 | } 1658 | .fa-line-chart:before { 1659 | content: "\f201"; 1660 | } 1661 | .fa-lastfm:before { 1662 | content: "\f202"; 1663 | } 1664 | .fa-lastfm-square:before { 1665 | content: "\f203"; 1666 | } 1667 | .fa-toggle-off:before { 1668 | content: "\f204"; 1669 | } 1670 | .fa-toggle-on:before { 1671 | content: "\f205"; 1672 | } 1673 | .fa-bicycle:before { 1674 | content: "\f206"; 1675 | } 1676 | .fa-bus:before { 1677 | content: "\f207"; 1678 | } 1679 | .fa-ioxhost:before { 1680 | content: "\f208"; 1681 | } 1682 | .fa-angellist:before { 1683 | content: "\f209"; 1684 | } 1685 | .fa-cc:before { 1686 | content: "\f20a"; 1687 | } 1688 | .fa-shekel:before, 1689 | .fa-sheqel:before, 1690 | .fa-ils:before { 1691 | content: "\f20b"; 1692 | } 1693 | .fa-meanpath:before { 1694 | content: "\f20c"; 1695 | } 1696 | .fa-buysellads:before { 1697 | content: "\f20d"; 1698 | } 1699 | .fa-connectdevelop:before { 1700 | content: "\f20e"; 1701 | } 1702 | .fa-dashcube:before { 1703 | content: "\f210"; 1704 | } 1705 | .fa-forumbee:before { 1706 | content: "\f211"; 1707 | } 1708 | .fa-leanpub:before { 1709 | content: "\f212"; 1710 | } 1711 | .fa-sellsy:before { 1712 | content: "\f213"; 1713 | } 1714 | .fa-shirtsinbulk:before { 1715 | content: "\f214"; 1716 | } 1717 | .fa-simplybuilt:before { 1718 | content: "\f215"; 1719 | } 1720 | .fa-skyatlas:before { 1721 | content: "\f216"; 1722 | } 1723 | .fa-cart-plus:before { 1724 | content: "\f217"; 1725 | } 1726 | .fa-cart-arrow-down:before { 1727 | content: "\f218"; 1728 | } 1729 | .fa-diamond:before { 1730 | content: "\f219"; 1731 | } 1732 | .fa-ship:before { 1733 | content: "\f21a"; 1734 | } 1735 | .fa-user-secret:before { 1736 | content: "\f21b"; 1737 | } 1738 | .fa-motorcycle:before { 1739 | content: "\f21c"; 1740 | } 1741 | .fa-street-view:before { 1742 | content: "\f21d"; 1743 | } 1744 | .fa-heartbeat:before { 1745 | content: "\f21e"; 1746 | } 1747 | .fa-venus:before { 1748 | content: "\f221"; 1749 | } 1750 | .fa-mars:before { 1751 | content: "\f222"; 1752 | } 1753 | .fa-mercury:before { 1754 | content: "\f223"; 1755 | } 1756 | .fa-intersex:before, 1757 | .fa-transgender:before { 1758 | content: "\f224"; 1759 | } 1760 | .fa-transgender-alt:before { 1761 | content: "\f225"; 1762 | } 1763 | .fa-venus-double:before { 1764 | content: "\f226"; 1765 | } 1766 | .fa-mars-double:before { 1767 | content: "\f227"; 1768 | } 1769 | .fa-venus-mars:before { 1770 | content: "\f228"; 1771 | } 1772 | .fa-mars-stroke:before { 1773 | content: "\f229"; 1774 | } 1775 | .fa-mars-stroke-v:before { 1776 | content: "\f22a"; 1777 | } 1778 | .fa-mars-stroke-h:before { 1779 | content: "\f22b"; 1780 | } 1781 | .fa-neuter:before { 1782 | content: "\f22c"; 1783 | } 1784 | .fa-genderless:before { 1785 | content: "\f22d"; 1786 | } 1787 | .fa-facebook-official:before { 1788 | content: "\f230"; 1789 | } 1790 | .fa-pinterest-p:before { 1791 | content: "\f231"; 1792 | } 1793 | .fa-whatsapp:before { 1794 | content: "\f232"; 1795 | } 1796 | .fa-server:before { 1797 | content: "\f233"; 1798 | } 1799 | .fa-user-plus:before { 1800 | content: "\f234"; 1801 | } 1802 | .fa-user-times:before { 1803 | content: "\f235"; 1804 | } 1805 | .fa-hotel:before, 1806 | .fa-bed:before { 1807 | content: "\f236"; 1808 | } 1809 | .fa-viacoin:before { 1810 | content: "\f237"; 1811 | } 1812 | .fa-train:before { 1813 | content: "\f238"; 1814 | } 1815 | .fa-subway:before { 1816 | content: "\f239"; 1817 | } 1818 | .fa-medium:before { 1819 | content: "\f23a"; 1820 | } 1821 | .fa-yc:before, 1822 | .fa-y-combinator:before { 1823 | content: "\f23b"; 1824 | } 1825 | .fa-optin-monster:before { 1826 | content: "\f23c"; 1827 | } 1828 | .fa-opencart:before { 1829 | content: "\f23d"; 1830 | } 1831 | .fa-expeditedssl:before { 1832 | content: "\f23e"; 1833 | } 1834 | .fa-battery-4:before, 1835 | .fa-battery:before, 1836 | .fa-battery-full:before { 1837 | content: "\f240"; 1838 | } 1839 | .fa-battery-3:before, 1840 | .fa-battery-three-quarters:before { 1841 | content: "\f241"; 1842 | } 1843 | .fa-battery-2:before, 1844 | .fa-battery-half:before { 1845 | content: "\f242"; 1846 | } 1847 | .fa-battery-1:before, 1848 | .fa-battery-quarter:before { 1849 | content: "\f243"; 1850 | } 1851 | .fa-battery-0:before, 1852 | .fa-battery-empty:before { 1853 | content: "\f244"; 1854 | } 1855 | .fa-mouse-pointer:before { 1856 | content: "\f245"; 1857 | } 1858 | .fa-i-cursor:before { 1859 | content: "\f246"; 1860 | } 1861 | .fa-object-group:before { 1862 | content: "\f247"; 1863 | } 1864 | .fa-object-ungroup:before { 1865 | content: "\f248"; 1866 | } 1867 | .fa-sticky-note:before { 1868 | content: "\f249"; 1869 | } 1870 | .fa-sticky-note-o:before { 1871 | content: "\f24a"; 1872 | } 1873 | .fa-cc-jcb:before { 1874 | content: "\f24b"; 1875 | } 1876 | .fa-cc-diners-club:before { 1877 | content: "\f24c"; 1878 | } 1879 | .fa-clone:before { 1880 | content: "\f24d"; 1881 | } 1882 | .fa-balance-scale:before { 1883 | content: "\f24e"; 1884 | } 1885 | .fa-hourglass-o:before { 1886 | content: "\f250"; 1887 | } 1888 | .fa-hourglass-1:before, 1889 | .fa-hourglass-start:before { 1890 | content: "\f251"; 1891 | } 1892 | .fa-hourglass-2:before, 1893 | .fa-hourglass-half:before { 1894 | content: "\f252"; 1895 | } 1896 | .fa-hourglass-3:before, 1897 | .fa-hourglass-end:before { 1898 | content: "\f253"; 1899 | } 1900 | .fa-hourglass:before { 1901 | content: "\f254"; 1902 | } 1903 | .fa-hand-grab-o:before, 1904 | .fa-hand-rock-o:before { 1905 | content: "\f255"; 1906 | } 1907 | .fa-hand-stop-o:before, 1908 | .fa-hand-paper-o:before { 1909 | content: "\f256"; 1910 | } 1911 | .fa-hand-scissors-o:before { 1912 | content: "\f257"; 1913 | } 1914 | .fa-hand-lizard-o:before { 1915 | content: "\f258"; 1916 | } 1917 | .fa-hand-spock-o:before { 1918 | content: "\f259"; 1919 | } 1920 | .fa-hand-pointer-o:before { 1921 | content: "\f25a"; 1922 | } 1923 | .fa-hand-peace-o:before { 1924 | content: "\f25b"; 1925 | } 1926 | .fa-trademark:before { 1927 | content: "\f25c"; 1928 | } 1929 | .fa-registered:before { 1930 | content: "\f25d"; 1931 | } 1932 | .fa-creative-commons:before { 1933 | content: "\f25e"; 1934 | } 1935 | .fa-gg:before { 1936 | content: "\f260"; 1937 | } 1938 | .fa-gg-circle:before { 1939 | content: "\f261"; 1940 | } 1941 | .fa-tripadvisor:before { 1942 | content: "\f262"; 1943 | } 1944 | .fa-odnoklassniki:before { 1945 | content: "\f263"; 1946 | } 1947 | .fa-odnoklassniki-square:before { 1948 | content: "\f264"; 1949 | } 1950 | .fa-get-pocket:before { 1951 | content: "\f265"; 1952 | } 1953 | .fa-wikipedia-w:before { 1954 | content: "\f266"; 1955 | } 1956 | .fa-safari:before { 1957 | content: "\f267"; 1958 | } 1959 | .fa-chrome:before { 1960 | content: "\f268"; 1961 | } 1962 | .fa-firefox:before { 1963 | content: "\f269"; 1964 | } 1965 | .fa-opera:before { 1966 | content: "\f26a"; 1967 | } 1968 | .fa-internet-explorer:before { 1969 | content: "\f26b"; 1970 | } 1971 | .fa-tv:before, 1972 | .fa-television:before { 1973 | content: "\f26c"; 1974 | } 1975 | .fa-contao:before { 1976 | content: "\f26d"; 1977 | } 1978 | .fa-500px:before { 1979 | content: "\f26e"; 1980 | } 1981 | .fa-amazon:before { 1982 | content: "\f270"; 1983 | } 1984 | .fa-calendar-plus-o:before { 1985 | content: "\f271"; 1986 | } 1987 | .fa-calendar-minus-o:before { 1988 | content: "\f272"; 1989 | } 1990 | .fa-calendar-times-o:before { 1991 | content: "\f273"; 1992 | } 1993 | .fa-calendar-check-o:before { 1994 | content: "\f274"; 1995 | } 1996 | .fa-industry:before { 1997 | content: "\f275"; 1998 | } 1999 | .fa-map-pin:before { 2000 | content: "\f276"; 2001 | } 2002 | .fa-map-signs:before { 2003 | content: "\f277"; 2004 | } 2005 | .fa-map-o:before { 2006 | content: "\f278"; 2007 | } 2008 | .fa-map:before { 2009 | content: "\f279"; 2010 | } 2011 | .fa-commenting:before { 2012 | content: "\f27a"; 2013 | } 2014 | .fa-commenting-o:before { 2015 | content: "\f27b"; 2016 | } 2017 | .fa-houzz:before { 2018 | content: "\f27c"; 2019 | } 2020 | .fa-vimeo:before { 2021 | content: "\f27d"; 2022 | } 2023 | .fa-black-tie:before { 2024 | content: "\f27e"; 2025 | } 2026 | .fa-fonticons:before { 2027 | content: "\f280"; 2028 | } 2029 | .fa-reddit-alien:before { 2030 | content: "\f281"; 2031 | } 2032 | .fa-edge:before { 2033 | content: "\f282"; 2034 | } 2035 | .fa-credit-card-alt:before { 2036 | content: "\f283"; 2037 | } 2038 | .fa-codiepie:before { 2039 | content: "\f284"; 2040 | } 2041 | .fa-modx:before { 2042 | content: "\f285"; 2043 | } 2044 | .fa-fort-awesome:before { 2045 | content: "\f286"; 2046 | } 2047 | .fa-usb:before { 2048 | content: "\f287"; 2049 | } 2050 | .fa-product-hunt:before { 2051 | content: "\f288"; 2052 | } 2053 | .fa-mixcloud:before { 2054 | content: "\f289"; 2055 | } 2056 | .fa-scribd:before { 2057 | content: "\f28a"; 2058 | } 2059 | .fa-pause-circle:before { 2060 | content: "\f28b"; 2061 | } 2062 | .fa-pause-circle-o:before { 2063 | content: "\f28c"; 2064 | } 2065 | .fa-stop-circle:before { 2066 | content: "\f28d"; 2067 | } 2068 | .fa-stop-circle-o:before { 2069 | content: "\f28e"; 2070 | } 2071 | .fa-shopping-bag:before { 2072 | content: "\f290"; 2073 | } 2074 | .fa-shopping-basket:before { 2075 | content: "\f291"; 2076 | } 2077 | .fa-hashtag:before { 2078 | content: "\f292"; 2079 | } 2080 | .fa-bluetooth:before { 2081 | content: "\f293"; 2082 | } 2083 | .fa-bluetooth-b:before { 2084 | content: "\f294"; 2085 | } 2086 | .fa-percent:before { 2087 | content: "\f295"; 2088 | } 2089 | .fa-gitlab:before { 2090 | content: "\f296"; 2091 | } 2092 | .fa-wpbeginner:before { 2093 | content: "\f297"; 2094 | } 2095 | .fa-wpforms:before { 2096 | content: "\f298"; 2097 | } 2098 | .fa-envira:before { 2099 | content: "\f299"; 2100 | } 2101 | .fa-universal-access:before { 2102 | content: "\f29a"; 2103 | } 2104 | .fa-wheelchair-alt:before { 2105 | content: "\f29b"; 2106 | } 2107 | .fa-question-circle-o:before { 2108 | content: "\f29c"; 2109 | } 2110 | .fa-blind:before { 2111 | content: "\f29d"; 2112 | } 2113 | .fa-audio-description:before { 2114 | content: "\f29e"; 2115 | } 2116 | .fa-volume-control-phone:before { 2117 | content: "\f2a0"; 2118 | } 2119 | .fa-braille:before { 2120 | content: "\f2a1"; 2121 | } 2122 | .fa-assistive-listening-systems:before { 2123 | content: "\f2a2"; 2124 | } 2125 | .fa-asl-interpreting:before, 2126 | .fa-american-sign-language-interpreting:before { 2127 | content: "\f2a3"; 2128 | } 2129 | .fa-deafness:before, 2130 | .fa-hard-of-hearing:before, 2131 | .fa-deaf:before { 2132 | content: "\f2a4"; 2133 | } 2134 | .fa-glide:before { 2135 | content: "\f2a5"; 2136 | } 2137 | .fa-glide-g:before { 2138 | content: "\f2a6"; 2139 | } 2140 | .fa-signing:before, 2141 | .fa-sign-language:before { 2142 | content: "\f2a7"; 2143 | } 2144 | .fa-low-vision:before { 2145 | content: "\f2a8"; 2146 | } 2147 | .fa-viadeo:before { 2148 | content: "\f2a9"; 2149 | } 2150 | .fa-viadeo-square:before { 2151 | content: "\f2aa"; 2152 | } 2153 | .fa-snapchat:before { 2154 | content: "\f2ab"; 2155 | } 2156 | .fa-snapchat-ghost:before { 2157 | content: "\f2ac"; 2158 | } 2159 | .fa-snapchat-square:before { 2160 | content: "\f2ad"; 2161 | } 2162 | .fa-pied-piper:before { 2163 | content: "\f2ae"; 2164 | } 2165 | .fa-first-order:before { 2166 | content: "\f2b0"; 2167 | } 2168 | .fa-yoast:before { 2169 | content: "\f2b1"; 2170 | } 2171 | .fa-themeisle:before { 2172 | content: "\f2b2"; 2173 | } 2174 | .fa-google-plus-circle:before, 2175 | .fa-google-plus-official:before { 2176 | content: "\f2b3"; 2177 | } 2178 | .fa-fa:before, 2179 | .fa-font-awesome:before { 2180 | content: "\f2b4"; 2181 | } 2182 | .fa-handshake-o:before { 2183 | content: "\f2b5"; 2184 | } 2185 | .fa-envelope-open:before { 2186 | content: "\f2b6"; 2187 | } 2188 | .fa-envelope-open-o:before { 2189 | content: "\f2b7"; 2190 | } 2191 | .fa-linode:before { 2192 | content: "\f2b8"; 2193 | } 2194 | .fa-address-book:before { 2195 | content: "\f2b9"; 2196 | } 2197 | .fa-address-book-o:before { 2198 | content: "\f2ba"; 2199 | } 2200 | .fa-vcard:before, 2201 | .fa-address-card:before { 2202 | content: "\f2bb"; 2203 | } 2204 | .fa-vcard-o:before, 2205 | .fa-address-card-o:before { 2206 | content: "\f2bc"; 2207 | } 2208 | .fa-user-circle:before { 2209 | content: "\f2bd"; 2210 | } 2211 | .fa-user-circle-o:before { 2212 | content: "\f2be"; 2213 | } 2214 | .fa-user-o:before { 2215 | content: "\f2c0"; 2216 | } 2217 | .fa-id-badge:before { 2218 | content: "\f2c1"; 2219 | } 2220 | .fa-drivers-license:before, 2221 | .fa-id-card:before { 2222 | content: "\f2c2"; 2223 | } 2224 | .fa-drivers-license-o:before, 2225 | .fa-id-card-o:before { 2226 | content: "\f2c3"; 2227 | } 2228 | .fa-quora:before { 2229 | content: "\f2c4"; 2230 | } 2231 | .fa-free-code-camp:before { 2232 | content: "\f2c5"; 2233 | } 2234 | .fa-telegram:before { 2235 | content: "\f2c6"; 2236 | } 2237 | .fa-thermometer-4:before, 2238 | .fa-thermometer:before, 2239 | .fa-thermometer-full:before { 2240 | content: "\f2c7"; 2241 | } 2242 | .fa-thermometer-3:before, 2243 | .fa-thermometer-three-quarters:before { 2244 | content: "\f2c8"; 2245 | } 2246 | .fa-thermometer-2:before, 2247 | .fa-thermometer-half:before { 2248 | content: "\f2c9"; 2249 | } 2250 | .fa-thermometer-1:before, 2251 | .fa-thermometer-quarter:before { 2252 | content: "\f2ca"; 2253 | } 2254 | .fa-thermometer-0:before, 2255 | .fa-thermometer-empty:before { 2256 | content: "\f2cb"; 2257 | } 2258 | .fa-shower:before { 2259 | content: "\f2cc"; 2260 | } 2261 | .fa-bathtub:before, 2262 | .fa-s15:before, 2263 | .fa-bath:before { 2264 | content: "\f2cd"; 2265 | } 2266 | .fa-podcast:before { 2267 | content: "\f2ce"; 2268 | } 2269 | .fa-window-maximize:before { 2270 | content: "\f2d0"; 2271 | } 2272 | .fa-window-minimize:before { 2273 | content: "\f2d1"; 2274 | } 2275 | .fa-window-restore:before { 2276 | content: "\f2d2"; 2277 | } 2278 | .fa-times-rectangle:before, 2279 | .fa-window-close:before { 2280 | content: "\f2d3"; 2281 | } 2282 | .fa-times-rectangle-o:before, 2283 | .fa-window-close-o:before { 2284 | content: "\f2d4"; 2285 | } 2286 | .fa-bandcamp:before { 2287 | content: "\f2d5"; 2288 | } 2289 | .fa-grav:before { 2290 | content: "\f2d6"; 2291 | } 2292 | .fa-etsy:before { 2293 | content: "\f2d7"; 2294 | } 2295 | .fa-imdb:before { 2296 | content: "\f2d8"; 2297 | } 2298 | .fa-ravelry:before { 2299 | content: "\f2d9"; 2300 | } 2301 | .fa-eercast:before { 2302 | content: "\f2da"; 2303 | } 2304 | .fa-microchip:before { 2305 | content: "\f2db"; 2306 | } 2307 | .fa-snowflake-o:before { 2308 | content: "\f2dc"; 2309 | } 2310 | .fa-superpowers:before { 2311 | content: "\f2dd"; 2312 | } 2313 | .fa-wpexplorer:before { 2314 | content: "\f2de"; 2315 | } 2316 | .fa-meetup:before { 2317 | content: "\f2e0"; 2318 | } 2319 | .sr-only { 2320 | position: absolute; 2321 | width: 1px; 2322 | height: 1px; 2323 | padding: 0; 2324 | margin: -1px; 2325 | overflow: hidden; 2326 | clip: rect(0, 0, 0, 0); 2327 | border: 0; 2328 | } 2329 | .sr-only-focusable:active, 2330 | .sr-only-focusable:focus { 2331 | position: static; 2332 | width: auto; 2333 | height: auto; 2334 | margin: 0; 2335 | overflow: visible; 2336 | clip: auto; 2337 | } 2338 | --------------------------------------------------------------------------------