├── .editorconfig ├── .gitignore ├── .travis.yml ├── dist ├── images │ ├── error.png │ ├── icon-128.png │ ├── icon-16.png │ ├── icon-48.png │ ├── icon.png │ ├── loader.png │ └── rays.png ├── manifest.json ├── options.html ├── popup.html ├── scripts │ └── options.js └── styles │ └── options.css ├── package.json ├── readme.md └── src ├── data └── budget.json ├── scripts ├── app.js ├── failure.js ├── loader.js ├── popup.js └── result.js └── styles ├── popup.scss ├── result.scss └── status.scss /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [package.json] 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/scripts/popup.js 3 | dist/styles/popup.css 4 | *.xpi 5 | *.zip 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: "5" 3 | script: npm start 4 | -------------------------------------------------------------------------------- /dist/images/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenorocha/browser-calories/706ae5d91dfc16f7b16bdf9f19e0f0c10a13dd0c/dist/images/error.png -------------------------------------------------------------------------------- /dist/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenorocha/browser-calories/706ae5d91dfc16f7b16bdf9f19e0f0c10a13dd0c/dist/images/icon-128.png -------------------------------------------------------------------------------- /dist/images/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenorocha/browser-calories/706ae5d91dfc16f7b16bdf9f19e0f0c10a13dd0c/dist/images/icon-16.png -------------------------------------------------------------------------------- /dist/images/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenorocha/browser-calories/706ae5d91dfc16f7b16bdf9f19e0f0c10a13dd0c/dist/images/icon-48.png -------------------------------------------------------------------------------- /dist/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenorocha/browser-calories/706ae5d91dfc16f7b16bdf9f19e0f0c10a13dd0c/dist/images/icon.png -------------------------------------------------------------------------------- /dist/images/loader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenorocha/browser-calories/706ae5d91dfc16f7b16bdf9f19e0f0c10a13dd0c/dist/images/loader.png -------------------------------------------------------------------------------- /dist/images/rays.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenorocha/browser-calories/706ae5d91dfc16f7b16bdf9f19e0f0c10a13dd0c/dist/images/rays.png -------------------------------------------------------------------------------- /dist/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Browser Calories", 4 | "description": "The easiest way to measure your performance budget", 5 | "homepage_url": "https://github.com/zenorocha/browser-calories", 6 | "version": "1.3.2", 7 | "applications": { 8 | "gecko": { 9 | "id": "calories@browserdiet.com", 10 | "strict_min_version": "45.0" 11 | } 12 | }, 13 | "browser_action": { 14 | "default_popup": "popup.html", 15 | "default_icon": "images/icon-48.png" 16 | }, 17 | "icons": { 18 | "16" : "images/icon-16.png", 19 | "48" : "images/icon-48.png", 20 | "128": "images/icon-128.png" 21 | }, 22 | "permissions": [ 23 | "activeTab", 24 | "storage", 25 | "https://www.googleapis.com/" 26 | ], 27 | "options_ui": { 28 | "page": "options.html", 29 | "chrome_style": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dist/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Browser Calories 6 | 7 | 8 | 9 |
10 |

Budget

11 |

Values are defined in bytes

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 | Budget saved! 41 |
42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /dist/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Browser Calories 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /dist/scripts/options.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', restoreBudget); 2 | 3 | // Firefox has no sync yet. 4 | // Cue: https://bugzilla.mozilla.org/show_bug.cgi?id=1220494 5 | var chromeStorage = chrome.storage.sync || chrome.storage.local; 6 | 7 | var defaultBudget = { 8 | "html" : 62000, 9 | "image" : 1001000, 10 | "css" : 67000, 11 | "js" : 357000, 12 | "other" : 189000, 13 | "total" : 1678000 14 | }; 15 | 16 | function restoreBudget() { 17 | var form = document.querySelector('form'); 18 | var reset = document.querySelector('#reset'); 19 | 20 | form.addEventListener('submit', saveBudget); 21 | reset.addEventListener('click', resetBudget); 22 | 23 | chromeStorage.get(defaultBudget, function(data) { 24 | form.html.value = data.html; 25 | form.image.value = data.image; 26 | form.css.value = data.css; 27 | form.js.value = data.js; 28 | form.other.value = data.other; 29 | }); 30 | } 31 | 32 | function saveBudget(e) { 33 | e.preventDefault(); 34 | 35 | var budget = { 36 | html : parseInt(e.target.html.value, 10), 37 | image : parseInt(e.target.image.value, 10), 38 | css : parseInt(e.target.css.value, 10), 39 | js : parseInt(e.target.js.value, 10), 40 | other : parseInt(e.target.other.value, 10) 41 | }; 42 | 43 | budget.total = budget.html + budget.image + budget.css + budget.js + budget.other; 44 | 45 | chromeStorage.set(budget, function() { 46 | var status = document.querySelector('.status'); 47 | status.style.display = 'inline-block'; 48 | 49 | setTimeout(function() { 50 | status.style.display = 'none'; 51 | }, 750); 52 | }); 53 | } 54 | 55 | function resetBudget() { 56 | var form = document.querySelector('form'); 57 | 58 | form.html.value = defaultBudget.html; 59 | form.image.value = defaultBudget.image; 60 | form.css.value = defaultBudget.css; 61 | form.js.value = defaultBudget.js; 62 | form.other.value = defaultBudget.other; 63 | } 64 | -------------------------------------------------------------------------------- /dist/styles/options.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", "Lucida Grande", sans-serif; 3 | } 4 | 5 | .description { 6 | color: grey; 7 | font-size: 13px; 8 | font-style: italic; 9 | } 10 | 11 | .input-container { 12 | clear: both; 13 | display: block; 14 | margin-bottom: 10px; 15 | } 16 | 17 | .input-container label { 18 | display: inline-block; 19 | font-size: 12px; 20 | width: 65px; 21 | } 22 | 23 | .input-container input[type='number'] { 24 | width: 100px; 25 | } 26 | 27 | .button { 28 | cursor: pointer; 29 | margin-top: 10px; 30 | margin-right: 5px; 31 | } 32 | 33 | .status { 34 | display: none; 35 | font-size: 12px; 36 | font-style: italic; 37 | margin-left: 5px; 38 | } 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browser-calories", 3 | "version": "1.3.2", 4 | "description": "The easiest way to measure your performance budget", 5 | "main": "src/scripts/popup.js", 6 | "repository": "zenorocha/browser-calories", 7 | "license": "MIT", 8 | "dependencies": { 9 | "byte-size": "2.0.0", 10 | "metal-component": "1.0.0-rc.16", 11 | "metal-jsx": "1.0.0-rc.12" 12 | }, 13 | "devDependencies": { 14 | "babel-preset-es2015": "6.9.0", 15 | "babel-preset-metal-jsx": "0.0.3", 16 | "babelify": "7.3.0", 17 | "browserify": "13.0.1", 18 | "envify": "3.4.0", 19 | "node-sass": "3.7.0", 20 | "onchange": "2.4.0", 21 | "parallelshell": "2.0.0", 22 | "watchify": "3.7.0" 23 | }, 24 | "scripts": { 25 | "start": "npm run build", 26 | "build": "npm run build:scripts && npm run build:styles", 27 | "build:scripts": "browserify src/scripts/popup.js -t [ babelify --presets [ es2015 metal-jsx ] ] -t [ envify ] -o dist/scripts/popup.js", 28 | "build:styles": "node-sass src/styles/popup.scss dist/styles/popup.css -q", 29 | "watch": "parallelshell 'npm run watch:scripts' 'npm run watch:styles'", 30 | "watch:scripts": "onchange 'src/scripts/*.js' -- npm run build:scripts", 31 | "watch:styles": "onchange 'src/styles/*.scss' -- npm run build:styles", 32 | "package": "npm run package:blink && npm run package:gecko", 33 | "package:blink": "cd dist && zip -r ../browser-calories.zip * && cd ..", 34 | "package:gecko": "cd dist && zip -r ../browser-calories.xpi * && cd .." 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Browser Calories 2 | 3 | [![Build Status](http://img.shields.io/travis/zenorocha/browser-calories/master.svg?style=flat)](https://travis-ci.org/zenorocha/browser-calories) 4 | ![Perf Matters](https://img.shields.io/badge/perf-matters-brightgreen.svg?style=flat) 5 | 6 | > The easiest way to measure your performance budget. 7 | 8 | [![Browser Calories](https://cloud.githubusercontent.com/assets/398893/15591790/09e9a616-2354-11e6-9c43-4eb3b336cff1.jpg)](https://browserdiet.com/calories/) 9 | 10 | ## Install 11 | 12 | This browser extension available for: 13 | 14 | | Chrome logo | Firefox logo | Opera logo | 15 | |:---:|:---:|:---:| 16 | | [Chrome](https://chrome.google.com/webstore/detail/browser-calories/pdkibgfjegigkoaleelbkdpkgceljfco) | [Firefox](https://addons.mozilla.org/en-US/firefox/addon/browser-calories/) | [Opera](https://addons.opera.com/en/extensions/details/browser-calories) | 17 | 18 | ## Setup 19 | 20 | Install dependencies: 21 | 22 | ``` 23 | npm install 24 | ``` 25 | 26 | Compile scripts and styles: 27 | 28 | ``` 29 | npm start 30 | ``` 31 | 32 | ## Testing 33 | 34 | ###### Chrome 35 | 36 | 1. Navigate to `chrome://extensions` 37 | 38 | 2. Click on `Load unpacked extension...` 39 | 40 | 3. Select the `dist` folder 41 | 42 | ###### Firefox 43 | 44 | 1. Navigate to `about:debugging` 45 | 46 | 2. Click on `Load temporary Add-on` 47 | 48 | 3. Select the `manifest.json` inside the `dist` folder 49 | 50 | ###### Opera 51 | 52 | 1. Navigate to `extensions` 53 | 54 | 2. Click on `Developer Mode` 55 | 56 | 3. Click on `Load unpacked extension...` 57 | 58 | 4. Select the `dist` folder 59 | 60 | ## Credits 61 | 62 | * Illustrations by [Scott Johnson](https://twitter.com/scottjohnson) 63 | * CSS-based Nutrition Facts table by [Chris Coyier](https://twitter.com/chriscoyier) 64 | 65 | ## License 66 | 67 | [MIT License](http://zenorocha.mit-license.org/) © Zeno Rocha 68 | -------------------------------------------------------------------------------- /src/data/budget.json: -------------------------------------------------------------------------------- 1 | { 2 | "html" : 62000, 3 | "image" : 1001000, 4 | "css" : 67000, 5 | "js" : 357000, 6 | "other" : 189000, 7 | "total" : 1678000 8 | } 9 | -------------------------------------------------------------------------------- /src/scripts/app.js: -------------------------------------------------------------------------------- 1 | import JSXComponent from 'metal-jsx'; 2 | import Loader from './loader'; 3 | import Result from './result'; 4 | import Failure from './failure'; 5 | import defaultBudget from '../data/budget'; 6 | 7 | class App extends JSXComponent { 8 | attached() { 9 | this.getURL(); 10 | this.getBudget(); 11 | } 12 | 13 | getURL() { 14 | chrome.tabs.query({ 15 | active: true, 16 | currentWindow: true 17 | }, (tabs) => { 18 | this.setState({ 19 | url: tabs[0].url 20 | }, this.fetchURL); 21 | }); 22 | } 23 | 24 | getBudget() { 25 | // Firefox has no sync yet. 26 | // Cue: https://bugzilla.mozilla.org/show_bug.cgi?id=1220494 27 | let chromeStorage = chrome.storage.sync || chrome.storage.local; 28 | 29 | chromeStorage.get(defaultBudget, (data) => { 30 | this.setState({ 31 | budget: data 32 | }); 33 | }); 34 | } 35 | 36 | fetchURL() { 37 | let endpoint = 'https://www.googleapis.com/pagespeedonline/v2/runPagespeed?url=' + encodeURIComponent(this.url); 38 | 39 | if (process.env.API_KEY) { 40 | endpoint += `&key=${process.env.API_KEY}`; 41 | } 42 | 43 | fetch(endpoint) 44 | .then((response) => { 45 | return response.json(); 46 | }) 47 | .then((response) => { 48 | if (response.error) { 49 | this.setState({ 50 | error: response.error 51 | }); 52 | } else { 53 | this.setState({ 54 | success: response.pageStats 55 | }); 56 | } 57 | }); 58 | } 59 | 60 | render() { 61 | if (this.success && this.budget) { 62 | return ; 63 | } 64 | else if (this.error) { 65 | return ; 66 | } 67 | else { 68 | return ; 69 | } 70 | } 71 | } 72 | 73 | App.STATE = { 74 | url: {}, 75 | budget: {}, 76 | error: {}, 77 | success: {} 78 | }; 79 | 80 | export default App; 81 | -------------------------------------------------------------------------------- /src/scripts/failure.js: -------------------------------------------------------------------------------- 1 | import JSXComponent from 'metal-jsx'; 2 | 3 | class Failure extends JSXComponent { 4 | render() { 5 | let title = `Error ${this.config.error.code}`; 6 | 7 | if (this.config.error.code === 400) { 8 | title = 'URL can\'t be reached'; 9 | } 10 | 11 | return ( 12 |
13 | Geek 14 | Rays 15 |
16 |

{title}

17 |

Try again on another tab or fill an issue on GitHub

18 |
19 |
20 | ) 21 | } 22 | } 23 | 24 | export default Failure; 25 | -------------------------------------------------------------------------------- /src/scripts/loader.js: -------------------------------------------------------------------------------- 1 | import JSXComponent from 'metal-jsx'; 2 | 3 | class Loader extends JSXComponent { 4 | render() { 5 | return ( 6 |
7 | Geek 8 | Rays 9 |
10 |

Measuring

11 |

{this.config.url}

12 |
13 |
14 | ) 15 | } 16 | } 17 | 18 | export default Loader; 19 | -------------------------------------------------------------------------------- /src/scripts/popup.js: -------------------------------------------------------------------------------- 1 | import App from './app'; 2 | 3 | new App({}, document.querySelector('#wrapper')); 4 | -------------------------------------------------------------------------------- /src/scripts/result.js: -------------------------------------------------------------------------------- 1 | import JSXComponent from 'metal-jsx'; 2 | import bytes from 'byte-size'; 3 | 4 | class Result extends JSXComponent { 5 | toInt(data) { 6 | var result = { 7 | html : parseInt(data.htmlResponseBytes, 10) || 0, 8 | css : parseInt(data.cssResponseBytes, 10) || 0, 9 | image : parseInt(data.imageResponseBytes, 10) || 0, 10 | js : parseInt(data.javascriptResponseBytes, 10) || 0, 11 | other : parseInt(data.otherResponseBytes, 10) || 0 12 | }; 13 | 14 | result.total = result.html + result.image + result.css + result.js + result.other; 15 | 16 | return result; 17 | } 18 | 19 | toBytes(data) { 20 | var obj = {}; 21 | 22 | for (var prop in data) { 23 | if (data.hasOwnProperty(prop)) { 24 | obj[prop] = bytes(data[prop], { precision: 1 }); 25 | } 26 | } 27 | 28 | return obj; 29 | } 30 | 31 | toPercentage(a, b) { 32 | var obj = {}; 33 | 34 | for (var prop in a) { 35 | if (a.hasOwnProperty(prop) && b.hasOwnProperty(prop)) { 36 | if (a[prop] === 0 || b[prop] === 0 ) { 37 | obj[prop] = 0; 38 | } else { 39 | obj[prop] = Math.round((a[prop] / b[prop]) * 100); 40 | } 41 | } 42 | } 43 | 44 | return obj; 45 | } 46 | 47 | isPositive(number) { 48 | let className = 'facts-percentage '; 49 | 50 | if (number >= 100) { 51 | className += 'bad'; 52 | } else { 53 | className += 'good'; 54 | } 55 | 56 | return className; 57 | } 58 | 59 | openSettings() { 60 | // Firefox has not completed the implementation of runtime 61 | // Cue: https://bugzilla.mozilla.org/show_bug.cgi?id=1213473 62 | let chromeRuntime = chrome.runtime || runtime; 63 | 64 | chromeRuntime.openOptionsPage(); 65 | } 66 | 67 | render() { 68 | let cleanUrl = this.config.url.replace(/^http(s)?\:\/\/(www.)?/i, "").replace(/\/$/, ""); 69 | let siteStats = this.toInt(this.config.success); 70 | let siteBytes = this.toBytes(siteStats); 71 | let budgetBytes = this.toBytes(this.config.budget); 72 | let dailyPercentage = this.toPercentage(siteStats, this.config.budget); 73 | 74 | return ( 75 |
76 |
77 |

Performance Facts

78 |

79 | Serving Size 1 website 80 | {cleanUrl} 81 |

82 |
83 | 84 | 85 | 86 | 89 | 92 | 93 | 94 | 95 | 96 | 100 | 103 | 104 | 105 | 109 | 112 | 113 | 114 | 118 | 121 | 122 | 123 | 127 | 130 | 131 | 132 | 136 | 139 | 140 | 141 | 145 | 148 | 149 | 150 |
87 | Amount Per Serving 88 | 90 | % Per Load * 91 |
97 | HTML 98 | {siteBytes.html} 99 | 101 | {dailyPercentage.html}% 102 |
106 | Images 107 | {siteBytes.image} 108 | 110 | {dailyPercentage.image}% 111 |
115 | CSS 116 | {siteBytes.css} 117 | 119 | {dailyPercentage.css}% 120 |
124 | JavaScript 125 | {siteBytes.js} 126 | 128 | {dailyPercentage.js}% 129 |
133 | Other 134 | {siteBytes.other} 135 | 137 | {dailyPercentage.other}% 138 |
142 | Total Size 143 | {siteBytes.total} 144 | 146 | {dailyPercentage.total}% 147 |
151 |

* Values are based on the top 100 websites, but feel free to change your performance budget. These values may be higher or lower depending on your needs:

152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 |
HTMLLess than{budgetBytes.html}
ImagesLess than{budgetBytes.image}
CSSLess than{budgetBytes.css}
JavaScriptLess than{budgetBytes.js}
OtherLess than{budgetBytes.other}
Total SizeLess than{budgetBytes.total}
186 | 190 |
191 | ) 192 | } 193 | } 194 | 195 | export default Result; 196 | -------------------------------------------------------------------------------- /src/styles/popup.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background: #ffde00; 3 | font-family: sans-serif; 4 | font-size: small; 5 | line-height: 1.4; 6 | margin: 0; 7 | overflow-x: hidden; 8 | } 9 | 10 | a { 11 | color: black; 12 | cursor: pointer; 13 | text-decoration: underline; 14 | } 15 | 16 | p { 17 | margin: 0; 18 | } 19 | 20 | #wrapper { 21 | width: 325px; 22 | } 23 | 24 | @import 'result.scss'; 25 | @import 'status.scss'; 26 | -------------------------------------------------------------------------------- /src/styles/result.scss: -------------------------------------------------------------------------------- 1 | .facts { 2 | background: white; 3 | border: 1px solid black; 4 | float: left; 5 | padding: 5px; 6 | 7 | table { 8 | border-collapse: collapse; 9 | } 10 | } 11 | 12 | .facts-header { 13 | padding: 0 0 0.25rem 0; 14 | margin: 0 0 0.5rem 0; 15 | 16 | p { 17 | margin: 0; 18 | } 19 | } 20 | 21 | .facts-title { 22 | font-weight: bold; 23 | font-size: 2rem; 24 | margin: 0; 25 | } 26 | 27 | .facts-url { 28 | background: #e5e5e5; 29 | border-radius: 3px; 30 | color: #797979; 31 | display: inline-block; 32 | padding: 1px 6px; 33 | max-width: 100px; 34 | text-decoration: none; 35 | text-overflow: ellipsis; 36 | white-space: nowrap; 37 | overflow: hidden; 38 | position: relative; 39 | top: 5px; 40 | left: 10px; 41 | } 42 | 43 | .facts-table { 44 | width: 100%; 45 | 46 | thead tr { 47 | th, td { 48 | border: 0; 49 | } 50 | } 51 | 52 | th, td { 53 | font-weight: normal; 54 | text-align: left; 55 | padding: 0.25rem 0; 56 | border-top: 1px solid black; 57 | white-space: nowrap; 58 | } 59 | 60 | td:last-child { 61 | text-align: right; 62 | } 63 | } 64 | 65 | .facts-table-main { 66 | border-top: 10px solid black; 67 | border-bottom: 10px solid black; 68 | margin: 0 0 0.7rem 0; 69 | 70 | tr:last-child { 71 | th, td { 72 | border-top-width: 5px; 73 | } 74 | } 75 | 76 | .facts-percentage { 77 | border-radius: 3px; 78 | color: white; 79 | display: inline-block; 80 | min-width: 34px; 81 | padding: 0px 4px; 82 | text-align: center; 83 | 84 | &.good { 85 | background: #6bc500; 86 | } 87 | 88 | &.bad { 89 | background: #e60014; 90 | } 91 | } 92 | } 93 | 94 | .facts-table-small { 95 | color: #797979; 96 | margin: 0.3rem 0; 97 | 98 | tr { 99 | &:first-child { 100 | margin-top: 0.3rem; 101 | } 102 | 103 | &:last-child { 104 | margin-bottom: 0.3rem; 105 | } 106 | } 107 | 108 | td:last-child { 109 | text-align: left; 110 | } 111 | 112 | th, td { 113 | border: 0; 114 | padding: 0; 115 | } 116 | } 117 | 118 | .facts-table-small-header { 119 | border-bottom: 1px solid #999; 120 | color: #797979; 121 | margin-top: 0.3rem; 122 | padding-bottom: 0.3rem; 123 | } 124 | 125 | .facts-table-small-footer { 126 | border-top: 1px solid #999; 127 | margin-top: 0.3rem; 128 | padding-top: 0.3rem; 129 | text-align: center; 130 | } 131 | 132 | .small-info { 133 | font-size: 0.7rem; 134 | } 135 | -------------------------------------------------------------------------------- /src/styles/status.scss: -------------------------------------------------------------------------------- 1 | .status { 2 | overflow: hidden; 3 | position: relative; 4 | } 5 | 6 | .status-geek { 7 | display: block; 8 | margin: 0 auto; 9 | padding: 20px; 10 | height: 315px; 11 | } 12 | 13 | .status-rays { 14 | -webkit-animation: 20s rays infinite linear; 15 | animation: 20s rays infinite linear; 16 | position: absolute; 17 | left: -100px; 18 | top: -100px; 19 | z-index: -1; 20 | } 21 | 22 | .status-msg { 23 | background: #e60014; 24 | color: #ffde00; 25 | padding: 10px; 26 | text-align: center; 27 | 28 | p { 29 | overflow: hidden; 30 | text-overflow: ellipsis; 31 | white-space: nowrap; 32 | 33 | &:first-child { 34 | font-size: 18px; 35 | } 36 | } 37 | 38 | a { 39 | color: #ffde00; 40 | border-bottom: 1px solid #ffde00; 41 | text-decoration: none; 42 | } 43 | } 44 | 45 | @-webkit-keyframes rays { 46 | to { 47 | -webkit-transform: rotate(360deg); 48 | transform: rotate(360deg) 49 | } 50 | } 51 | 52 | @keyframes rays { 53 | to { 54 | -webkit-transform: rotate(360deg); 55 | transform: rotate(360deg) 56 | } 57 | } 58 | --------------------------------------------------------------------------------