├── .gitignore ├── static ├── re_blocked.json ├── blocked.json ├── js │ └── ui.js ├── about.html ├── home.html ├── api.html └── css │ ├── side-menu.css │ └── pure-min.css ├── package.json ├── LICENSE ├── api.js ├── README.md └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /static/re_blocked.json: -------------------------------------------------------------------------------- 1 | ["^\\/catalog\\/json.+Keyword=$", "^\\/catalog\\/json\\?PageNumber=\\d+&ResultsPerPage=42&Category=Clothing&SubCategory=[^&]*$", "^\\/catalog\\/json\\?PageNumber=\\d+&ResultsPerPage=42&Category=Accessories$", "^\\/catalog\\/json"] 2 | -------------------------------------------------------------------------------- /static/blocked.json: -------------------------------------------------------------------------------- 1 | ["/headshot-thumbnail/json?userId=-1&width=180&height=180", "/catalog/json?PageNumber=1&ResultsPerPage=42&Category=Accessories", "/catalog/json?PageNumber=1&ResultsPerPage=42&Category=Clothing&SubCategory=Faces", "/catalog/json?PageNumber=1&ResultsPerPage=42&Category=Clothing&SubCategory=Shirts", "/catalog/json?PageNumber=1&ResultsPerPage=42&Category=Clothing&SubCategory=Pants"] 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rprxy", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "cheerio": "^1.0.0-rc.2", 14 | "express": "^4.15.3", 15 | "http-proxy": "^1.16.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /static/js/ui.js: -------------------------------------------------------------------------------- 1 | (function (window, document) { 2 | 3 | var layout = document.getElementById('layout'), 4 | menu = document.getElementById('menu'), 5 | menuLink = document.getElementById('menuLink'); 6 | 7 | function toggleClass(element, className) { 8 | var classes = element.className.split(/\s+/), 9 | length = classes.length, 10 | i = 0; 11 | 12 | for(; i < length; i++) { 13 | if (classes[i] === className) { 14 | classes.splice(i, 1); 15 | break; 16 | } 17 | } 18 | // The className is not found 19 | if (length === classes.length) { 20 | classes.push(className); 21 | } 22 | 23 | element.className = classes.join(' '); 24 | } 25 | 26 | menuLink.onclick = function (e) { 27 | var active = 'active'; 28 | 29 | e.preventDefault(); 30 | toggleClass(layout, active); 31 | toggleClass(menu, active); 32 | toggleClass(menuLink, active); 33 | }; 34 | 35 | }(this, this.document)); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Joshua Lanese 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /api.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var https = require('https'); 3 | var parser = require('cheerio'); 4 | var router = express.Router(); 5 | 6 | router.get('/api/searchmusic/:music', function (req, res, next) { 7 | https.get('https://search.roblox.com/catalog/json?Category=9&Keyword=' + encodeURI(req.params.music), function (search) { 8 | search.pipe(res); 9 | }); 10 | }); 11 | 12 | router.get('/api/usernames/:userId*?', function (req, res, next) { 13 | var userId = req.params.userId || req.query.userId; 14 | if (!userId) { 15 | res.end('Parameter userId is required.'); 16 | return; 17 | } 18 | https.get('https://www.roblox.com/users/' + encodeURI(userId) + '/profile', function (user) { 19 | if (user.statusCode !== 200) { 20 | res.end('Request failed, make sure the userId is valid'); 21 | } 22 | 23 | var raw = ''; 24 | user.on('data', function (chunk) { 25 | raw += chunk; 26 | }); 27 | 28 | user.on('end', function () { 29 | var $ = parser.load(raw); 30 | var past = $('.tooltip-pastnames'); 31 | var names = []; 32 | if (past.length > 0) { 33 | names = past.attr('title').split(', '); 34 | } 35 | names.unshift($('.header-title').find('h2').text()); 36 | res.end(JSON.stringify(names)); 37 | }); 38 | }); 39 | }); 40 | 41 | module.exports = router; 42 | -------------------------------------------------------------------------------- /static/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Roblox Proxy and APIs 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 32 | 33 |
34 |
35 |

About

36 |
37 | 38 |
39 |

40 | Created by Froast.
41 | CSS is courtesy of purecss.io. Specifically, the side menu layout.
42 | This site's source is available at https://github.com/sentanos/rprxy. 43 |

44 |
45 |
46 |
47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /static/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Roblox Proxy and APIs 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 32 | 33 |
34 |
35 |

Roblox Proxy and APIs

36 |
37 | 38 |
39 |

Quick About

40 |

41 | This is an open-source roblox proxy for accessing roblox.com from in-game with HttpService, it also hosts some custom APIs. 42 |

43 |

Usage

44 |

45 | Just like roproxy, simply enter the roblox link you want to go to with roblox.com replaced to this domain. 46 | For example, if you wanted to get user information by username: https://api.rprxy.xyz/users/get-by-username?username=Froast. 47 | Subdomain support is managed manually: if there's a subdomain that isn't available you want me to add just let me know. 48 |

49 |
50 |
51 |
52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rprxy 2 | 3 | Because Roblox does not allow HttpService requests to roblox.com an external proxy is needed for access to site APIs. 4 | This will proxy all requests to Roblox via `server.js` except when the path is `/proxy` and a static file exists. 5 | 6 | A limited number of APIs are available via the `/proxy/api` path via `api.js`. 7 | 8 | ## Installation 9 | 10 | Simply clone the project and type `npm install` when in the folder to install all dependencies. You can now start the server with `node server.js`. 11 | 12 | The default configuration does not have to be changed for a working server and it will automatically work with any subdomain your DNS supports. 13 | 14 | ## Configuration 15 | 16 | The only configuration files you may want to edit are `blocked.json` and `re_blocked.json`. 17 | All paths that match any in `blocked.json` _exactly_ will be blocked. If you want to match with regex, which can also be used for partial matching, add the pattern to `re_blocked.json`. If _anything matches at all_ the URL will be blocked. 18 | 19 | There are a few settings you can change directly in the `server.js` file. If `serveHomepage` is `true` the rprxy home page will be at the root of the domain. If `serveHomepageOnAllSubdomains` is `true` this will apply to all subdomains, otherwise it will only apply to the root domain. 20 | 21 | If you have your own domain point all the subdomains you need at the server and it will work automatically when you visit those subdomains on your own site. 22 | If you are not able to add subdomains for any reason (usually if you just don't have a domain name) you can set `subdomainsAsPath` to `true` in the `server.js` file. 23 | When this is enabled you can request a specific subdomain by using it first in the path (see the examples below). 24 | 25 | ## Examples 26 | In every example the main domain is `rprxy.xyz`. 27 | 28 | To search the catalog (search subdomain): 29 | [https://search.rprxy.xyz/catalog/json](#) 30 | 31 | If you have `subdomainsAsPath` enabled: 32 | [https://rprxy.xyz/search/catalog/json](#) 33 | 34 | Get recommended username (no subdomain): 35 | [https://rprxy.xyz/UserCheck/GetRecommendedUsername?usernameToTry=Froast]() 36 | 37 | If you have `subdomainsAsPath` enabled: 38 | [https://rprxy.xyz/www/UserCheck/GetRecommendedUsername?usernameToTry=Froast]() 39 | 40 | Having no subdomain is the same as having a www subdomain, below is exactly the same: 41 | [https://www.rprxy.xyz/UserCheck/GetRecommendedUsername?usernameToTry=Froast]() 42 | 43 | If you have `subdomainsAsPath` enabled it doesn't matter what the actual subdomain is: 44 | [https://www.rprxy.xyz/www/UserCheck/GetRecommendedUsername?usernameToTry=Froast]() 45 | -------------------------------------------------------------------------------- /static/api.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Roblox Proxy and APIs 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 32 | 33 |
34 |
35 |

API

36 |
37 | 38 |

39 | The base for all APIs is /proxy/api.
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
NameDescriptionUsageExample
Search MusicSearches the catalog API for keywordGET /searchmusic/{keyword}https://rprxy.xyz/proxy/api/searchmusic/rat%20twist
Usernames From IdentityGets all usernames that a certain player has had before in order of newest to oldestGET /usernames/{userId}https://rprxy.xyz/proxy/api/usernames/31342830
66 |
67 |
68 |
69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var proxy = require('http-proxy'); 2 | var express = require('express'); 3 | var https = require('https'); 4 | var url = require('url'); 5 | var path = require('path'); 6 | 7 | var api = require('./api.js'); 8 | var blocked = require('./static/blocked.json'); 9 | var reBlocked = require('./static/re_blocked.json'); 10 | 11 | var port = process.env.PORT || 80; 12 | var subdomainsAsPath = false; 13 | var serveHomepage = true; 14 | var serveHomepageOnAllSubdomains = false; 15 | 16 | var httpsProxy = proxy.createProxyServer({ 17 | agent: new https.Agent({ 18 | checkServerIdentity: function (host, cert) { 19 | return undefined; 20 | } 21 | }), 22 | changeOrigin: true 23 | }); 24 | 25 | var httpProxy = proxy.createProxyServer({ 26 | changeOrigin: true 27 | }); 28 | 29 | function stripSub (link) { 30 | var original = url.parse(link); 31 | var sub = ''; 32 | var path = original.path; 33 | if (subdomainsAsPath) { 34 | var split = path.split('/'); 35 | sub = split[1] && split[1] + '.'; 36 | split.splice(1, 1); 37 | path = split.join('/'); 38 | } 39 | return [path || '/', sub]; 40 | } 41 | 42 | function getSubdomain (req, rewrite) { 43 | var sub; 44 | if (subdomainsAsPath) { 45 | var res = stripSub(req.url); 46 | if (rewrite) { 47 | req.url = res[0]; 48 | } 49 | sub = res[1]; 50 | } else { 51 | var domain = req.headers.host; 52 | sub = domain.slice(0, domain.lastIndexOf('.', domain.lastIndexOf('.') - 1) + 1); 53 | } 54 | return sub; 55 | } 56 | 57 | function onProxyError (err, req, res) { 58 | console.error(err); 59 | 60 | res.writeHead(500, { 61 | 'Content-Type': 'text/plain' 62 | }); 63 | 64 | res.end('Proxying failed.'); 65 | } 66 | 67 | function onProxyReq (proxyReq, req, res, options) { 68 | proxyReq.setHeader('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'); 69 | proxyReq.removeHeader('roblox-id'); 70 | } 71 | 72 | httpsProxy.on('error', onProxyError); 73 | httpsProxy.on('proxyReq', onProxyReq); 74 | httpProxy.on('error', onProxyError); 75 | httpProxy.on('proxyReq', onProxyReq); 76 | 77 | var app = express(); 78 | 79 | app.use('/proxy', express.static('./static')); 80 | app.use('/proxy', api); 81 | 82 | app.use(function (req, res, next) { 83 | if (serveHomepage && stripSub(req.url)[0] === '/') { 84 | if (serveHomepageOnAllSubdomains || !getSubdomain(req)) { 85 | res.sendFile(path.join(__dirname, '/static/home.html')); 86 | return; 87 | } 88 | } 89 | next(); 90 | }); 91 | 92 | app.use(function (req, res, next) { 93 | for (var i = 0; i < blocked.length; i++) { 94 | if (req.url === blocked[i]) { 95 | res.end('URL blocked.'); 96 | return; 97 | } 98 | } 99 | for (i = 0; i < reBlocked.length; i++) { 100 | if (req.url.match(reBlocked[i])) { 101 | res.end('URL blocked.'); 102 | return; 103 | } 104 | } 105 | next(); 106 | }); 107 | 108 | app.use(function (req, res, next) { 109 | console.log('PROXY REQUEST; HOST: ' + req.headers.host + '; URL: ' + req.url + '; OPT: ' + req.body + '; COOKIE: ' + req.headers.cookie + ';'); 110 | var subdomain = getSubdomain(req, true); 111 | var proto = subdomain === 'wiki.' ? 'http' : 'https'; 112 | var options = { 113 | target: proto + '://' + (subdomain || 'www.') + 'roblox.com' 114 | }; 115 | if (proto === 'https') { 116 | httpsProxy.web(req, res, options); 117 | } else if (proto === 'http') { 118 | httpProxy.web(req, res, options); 119 | } 120 | }); 121 | 122 | app.use(function (err, req, res, next) { 123 | console.error(err); 124 | 125 | res.writeHead(500, { 126 | 'Content-Type': 'text/plain' 127 | }); 128 | 129 | res.end('Proxy handler failed.'); 130 | }); 131 | 132 | app.listen(port, function () { 133 | console.log('Listening on port ' + port); 134 | }); 135 | -------------------------------------------------------------------------------- /static/css/side-menu.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #777; 3 | } 4 | 5 | .pure-img-responsive { 6 | max-width: 100%; 7 | height: auto; 8 | } 9 | 10 | /* 11 | Add transition to containers so they can push in and out. 12 | */ 13 | #layout, 14 | #menu, 15 | .menu-link { 16 | -webkit-transition: all 0.2s ease-out; 17 | -moz-transition: all 0.2s ease-out; 18 | -ms-transition: all 0.2s ease-out; 19 | -o-transition: all 0.2s ease-out; 20 | transition: all 0.2s ease-out; 21 | } 22 | 23 | /* 24 | This is the parent `
` that contains the menu and the content area. 25 | */ 26 | #layout { 27 | position: relative; 28 | padding-left: 0; 29 | } 30 | #layout.active #menu { 31 | left: 150px; 32 | width: 150px; 33 | } 34 | 35 | #layout.active .menu-link { 36 | left: 150px; 37 | } 38 | /* 39 | The content `
` is where all your content goes. 40 | */ 41 | .content { 42 | margin: 0 auto; 43 | padding: 0 2em; 44 | max-width: 800px; 45 | margin-bottom: 50px; 46 | line-height: 1.6em; 47 | } 48 | 49 | .header { 50 | margin: 0; 51 | color: #333; 52 | text-align: center; 53 | padding: 2.5em 2em 0; 54 | border-bottom: 1px solid #eee; 55 | } 56 | .header h1 { 57 | margin: 0.2em 0; 58 | font-size: 3em; 59 | font-weight: 300; 60 | } 61 | .header h2 { 62 | font-weight: 300; 63 | color: #ccc; 64 | padding: 0; 65 | margin-top: 0; 66 | } 67 | 68 | .content-subhead { 69 | margin: 50px 0 20px 0; 70 | font-weight: 300; 71 | color: #888; 72 | } 73 | 74 | 75 | 76 | /* 77 | The `#menu` `
` is the parent `
` that contains the `.pure-menu` that 78 | appears on the left side of the page. 79 | */ 80 | 81 | #menu { 82 | margin-left: -150px; /* "#menu" width */ 83 | width: 150px; 84 | position: fixed; 85 | top: 0; 86 | left: 0; 87 | bottom: 0; 88 | z-index: 1000; /* so the menu or its navicon stays above all content */ 89 | background: #191818; 90 | overflow-y: auto; 91 | -webkit-overflow-scrolling: touch; 92 | } 93 | /* 94 | All anchors inside the menu should be styled like this. 95 | */ 96 | #menu a { 97 | color: #999; 98 | border: none; 99 | padding: 0.6em 0 0.6em 0.6em; 100 | } 101 | 102 | /* 103 | Remove all background/borders, since we are applying them to #menu. 104 | */ 105 | #menu .pure-menu, 106 | #menu .pure-menu ul { 107 | border: none; 108 | background: transparent; 109 | } 110 | 111 | /* 112 | Add that light border to separate items into groups. 113 | */ 114 | #menu .pure-menu ul, 115 | #menu .pure-menu .menu-item-divided { 116 | border-top: 1px solid #333; 117 | } 118 | /* 119 | Change color of the anchor links on hover/focus. 120 | */ 121 | #menu .pure-menu li a:hover, 122 | #menu .pure-menu li a:focus { 123 | background: #333; 124 | } 125 | 126 | /* 127 | This styles the selected menu item `
  • `. 128 | */ 129 | #menu .pure-menu-selected, 130 | #menu .pure-menu-heading { 131 | background: #1f8dd6; 132 | } 133 | /* 134 | This styles a link within a selected menu item `
  • `. 135 | */ 136 | #menu .pure-menu-selected a { 137 | color: #fff; 138 | } 139 | 140 | /* 141 | This styles the menu heading. 142 | */ 143 | #menu .pure-menu-heading { 144 | font-size: 110%; 145 | color: #fff; 146 | margin: 0; 147 | } 148 | 149 | /* -- Dynamic Button For Responsive Menu -------------------------------------*/ 150 | 151 | /* 152 | The button to open/close the Menu is custom-made and not part of Pure. Here's 153 | how it works: 154 | */ 155 | 156 | /* 157 | `.menu-link` represents the responsive menu toggle that shows/hides on 158 | small screens. 159 | */ 160 | .menu-link { 161 | position: fixed; 162 | display: block; /* show this only on small screens */ 163 | top: 0; 164 | left: 0; /* "#menu width" */ 165 | background: #000; 166 | background: rgba(0,0,0,0.7); 167 | font-size: 10px; /* change this value to increase/decrease button size */ 168 | z-index: 10; 169 | width: 2em; 170 | height: auto; 171 | padding: 2.1em 1.6em; 172 | } 173 | 174 | .menu-link:hover, 175 | .menu-link:focus { 176 | background: #000; 177 | } 178 | 179 | .menu-link span { 180 | position: relative; 181 | display: block; 182 | } 183 | 184 | .menu-link span, 185 | .menu-link span:before, 186 | .menu-link span:after { 187 | background-color: #fff; 188 | width: 100%; 189 | height: 0.2em; 190 | } 191 | 192 | .menu-link span:before, 193 | .menu-link span:after { 194 | position: absolute; 195 | margin-top: -0.6em; 196 | content: " "; 197 | } 198 | 199 | .menu-link span:after { 200 | margin-top: 0.6em; 201 | } 202 | 203 | 204 | /* -- Responsive Styles (Media Queries) ------------------------------------- */ 205 | 206 | /* 207 | Hides the menu at `48em`, but modify this based on your app's needs. 208 | */ 209 | @media (min-width: 48em) { 210 | 211 | .header, 212 | .content { 213 | padding-left: 2em; 214 | padding-right: 2em; 215 | } 216 | 217 | #layout { 218 | padding-left: 150px; /* left col width "#menu" */ 219 | left: 0; 220 | } 221 | #menu { 222 | left: 150px; 223 | } 224 | 225 | .menu-link { 226 | position: fixed; 227 | left: 150px; 228 | display: none; 229 | } 230 | 231 | #layout.active .menu-link { 232 | left: 150px; 233 | } 234 | } 235 | 236 | @media (max-width: 48em) { 237 | /* Only apply this when the window is small. Otherwise, the following 238 | case results in extra padding on the left: 239 | * Make the window small. 240 | * Tap the menu to trigger the active state. 241 | * Make the window large again. 242 | */ 243 | #layout.active { 244 | position: relative; 245 | left: 150px; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /static/css/pure-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v^3.0 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-link,.pure-menu-heading{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-disabled,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} --------------------------------------------------------------------------------