├── .gitattributes ├── assets ├── img │ ├── screen.png │ ├── screen.min.png │ ├── tail-spin.min.svg │ ├── rings.min.svg │ ├── tail-spin.svg │ ├── rings.svg │ ├── clickpress_logo_weiss.min.svg │ └── bg_poly.min.svg ├── icons │ ├── png │ │ ├── 16x16.png │ │ ├── 32x32.png │ │ ├── 128x128.png │ │ ├── 16x16@2x.png │ │ ├── 256x256.png │ │ ├── 32x32@2x.png │ │ ├── 512x512.png │ │ ├── 128x128@2x.png │ │ ├── 256x256@2x.png │ │ └── 512x512@2x.png │ ├── ico │ │ └── 512x512@2x.ico │ └── mac │ │ ├── icon_image-shrinker.icns │ │ └── icon_image-shrinker.svg.icns ├── fonts │ ├── FiraSans-Italic.woff2 │ ├── FiraSans-Regular.woff2 │ ├── FiraSans-BoldItalic.woff2 │ └── LICENSE └── css │ ├── spectre.scss │ ├── _variables.scss │ ├── imageshrinker.css │ ├── imageshrinker.scss │ └── spectre.css ├── .gitignore ├── lib ├── cutfoldername.js └── notarize.js ├── extend.plist ├── .eslintrc.json ├── menu └── mainmenu.js ├── README.md ├── package.json ├── index.html ├── LICENSE.md ├── renderer.js └── main.js /.gitattributes: -------------------------------------------------------------------------------- 1 | assets/* linguist-vendored -------------------------------------------------------------------------------- /assets/img/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/img/screen.png -------------------------------------------------------------------------------- /assets/icons/png/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/icons/png/16x16.png -------------------------------------------------------------------------------- /assets/icons/png/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/icons/png/32x32.png -------------------------------------------------------------------------------- /assets/img/screen.min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/img/screen.min.png -------------------------------------------------------------------------------- /assets/icons/png/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/icons/png/128x128.png -------------------------------------------------------------------------------- /assets/icons/png/16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/icons/png/16x16@2x.png -------------------------------------------------------------------------------- /assets/icons/png/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/icons/png/256x256.png -------------------------------------------------------------------------------- /assets/icons/png/32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/icons/png/32x32@2x.png -------------------------------------------------------------------------------- /assets/icons/png/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/icons/png/512x512.png -------------------------------------------------------------------------------- /assets/icons/ico/512x512@2x.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/icons/ico/512x512@2x.ico -------------------------------------------------------------------------------- /assets/icons/png/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/icons/png/128x128@2x.png -------------------------------------------------------------------------------- /assets/icons/png/256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/icons/png/256x256@2x.png -------------------------------------------------------------------------------- /assets/icons/png/512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/icons/png/512x512@2x.png -------------------------------------------------------------------------------- /assets/fonts/FiraSans-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/fonts/FiraSans-Italic.woff2 -------------------------------------------------------------------------------- /assets/fonts/FiraSans-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/fonts/FiraSans-Regular.woff2 -------------------------------------------------------------------------------- /assets/fonts/FiraSans-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/fonts/FiraSans-BoldItalic.woff2 -------------------------------------------------------------------------------- /assets/icons/mac/icon_image-shrinker.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/icons/mac/icon_image-shrinker.icns -------------------------------------------------------------------------------- /assets/icons/mac/icon_image-shrinker.svg.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefansl/image-shrinker/HEAD/assets/icons/mac/icon_image-shrinker.svg.icns -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | release-builds 4 | .DS_Store 5 | /.gitignore 6 | /package-lock.json 7 | build/ 8 | script.sh 9 | /Mac_Distribution_Provisioning_Profiles_.provisionprofile 10 | Image Shrinker-linux-x64/ 11 | !/build/ 12 | /electron-builder.yml 13 | /dist/ 14 | /assets/css/imageshrinker.css.map 15 | /assets/css/spectre.css.map 16 | /.env 17 | -------------------------------------------------------------------------------- /lib/cutfoldername.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Cut path from beginning, if necessary 3 | * @param {string} path Filepath 4 | * @param {integer} length max length 5 | * @return {string} truncated string 6 | */ 7 | const cutFolderName = (path, length = 20) => { 8 | return path.length >= length ? '... ' + path.substr(path.length - length) : path; 9 | }; 10 | 11 | module.exports = cutFolderName; -------------------------------------------------------------------------------- /extend.plist: -------------------------------------------------------------------------------- 1 | 2 | CFBundleDocumentTypes 3 | 4 | 5 | CFBundleTypeExtensions 6 | 7 | png 8 | jpg 9 | jpeg 10 | svg 11 | 12 | CFBundleTypeName 13 | png 14 | CFBundleTypeRole 15 | Editor 16 | CFBundleTypeIconFile 17 | Image Shrinker.icns 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/notarize.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const { notarize } = require('electron-notarize'); 3 | 4 | exports.default = async function notarizing(context) { 5 | const { electronPlatformName, appOutDir } = context; 6 | if (electronPlatformName !== 'darwin') { 7 | return; 8 | } 9 | 10 | const appName = context.packager.appInfo.productFilename; 11 | 12 | return await notarize({ 13 | appBundleId: 'de.clickpress.image-shrinker', 14 | appPath: `${appOutDir}/${appName}.app`, 15 | appleId: process.env.APPLEID, 16 | appleIdPassword: process.env.APPLEIDPASS, 17 | }); 18 | }; -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "sourceType": "module", 10 | "ecmaVersion": 6 11 | }, 12 | "rules": { 13 | "indent": [ 14 | "error", 15 | 4, { "SwitchCase": 1 } 16 | ], 17 | "linebreak-style": [ 18 | "error", 19 | "unix" 20 | ], 21 | "quotes": [ 22 | "error", 23 | "single" 24 | ], 25 | "semi": [ 26 | "error", 27 | "always" 28 | ] 29 | } 30 | } -------------------------------------------------------------------------------- /assets/img/tail-spin.min.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/rings.min.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/tail-spin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /menu/mainmenu.js: -------------------------------------------------------------------------------- 1 | const {app, Menu} = require('electron'); 2 | 3 | const template = [ 4 | { 5 | label: 'Edit', 6 | submenu: [ 7 | { 8 | label: 'Reload', 9 | accelerator: 'Cmd+R', 10 | role: 'reload' 11 | } 12 | ] 13 | }, 14 | { 15 | role: 'window', 16 | submenu: [ 17 | {role: 'minimize'}, 18 | {role: 'close'} 19 | ] 20 | } 21 | ]; 22 | 23 | if (process.platform === 'darwin') { 24 | template.unshift({ 25 | label: app.getName(), 26 | submenu: [ 27 | {role: 'about'}, 28 | {type: 'separator'}, 29 | { 30 | label: 'Preferences', 31 | click: (item, focusedWindow) => { 32 | focusedWindow.webContents.send('openSettings'); 33 | }, 34 | accelerator: 'Cmd+,', 35 | }, 36 | 37 | {type: 'separator'}, 38 | {role: 'quit'} 39 | ] 40 | }); 41 | 42 | // Window menu 43 | template[2].submenu = [ 44 | {role: 'minimize'}, 45 | {role: 'zoom'}, 46 | {type: 'separator'}, 47 | {role: 'front'}, 48 | ]; 49 | 50 | const menu = Menu.buildFromTemplate(template); 51 | Menu.setApplicationMenu(menu); 52 | 53 | if (global.debug.devTools === 1) { 54 | template[2].submenu.push( 55 | {type: 'separator'}, 56 | { 57 | label: 'Open Dev-Tools', 58 | click: (item, focusedWindow) => { 59 | if (focusedWindow) 60 | focusedWindow.toggleDevTools(); 61 | } 62 | } 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /assets/img/rings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 14 | 18 | 19 | 20 | 25 | 29 | 33 | 34 | 35 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Image Shrinker 2 | 3 | Image Shrinker is a tool to minify images and graphics using the best available libraries for image processing: [pngquant](https://pngquant.org/), [mozjpg](https://github.com/mozilla/mozjpeg), [SVGO](https://github.com/svg/svgo) and [Gifsicle](https://github.com/kohler/gifsicle). Built with web technologies in [Electron](https://electronjs.org) 4 | 5 | ![Screenrecording Imageshrinker](https://user-images.githubusercontent.com/1564251/40296606-61863e56-5cdd-11e8-9f43-3a74c48d21a0.gif) 6 | 7 | ## How to use 8 | Drag your image file onto the Image Shrinker window and it will saved in the same or in a predefined folder as reduced image. 9 | The original graphic will be not replaced. 10 | 11 | ## Download and Installation on macOS 12 | Download Image Shrinker here: 13 | https://github.com/stefansl/image-shrinker/releases/download/v1.6.5/image-shrinker-1.6.5.dmg 14 | 15 | Unpack and copy or drag the app into your macOS application folder. 16 | For uninstalling, just drop the app into the bin. 17 | 18 | ## Build your own 19 | Get the repo 20 | ```shell 21 | git clone https://github.com/stefansl/image-shrinker.git 22 | ``` 23 | Install dependencies 24 | ```shell 25 | $ cd image-shrinker 26 | $ npm install 27 | ``` 28 | Generate your macOS package 29 | ```shell 30 | electron-builder build --mac 31 | ``` 32 | 33 | Generate your Linux package 34 | ```shell 35 | electron-builder build --linux 36 | ``` 37 | 38 | Generate your Windows package 39 | ```shell 40 | electron-builder build --win 41 | ``` 42 | 43 | Notice: I did not test Windows and Linux. Feel free to commit a pull request. 44 | 45 | ## Credits 46 | Thank you, guys! 47 | * Electron: 48 | * pngquant: 49 | * mozjpg: 50 | * SVGO: 51 | * Settings framework: 52 | * Poly background: 53 | * CSS: [Spectre Css](https://picturepan2.github.io/spectre/) 54 | * Font: [Mozillas Fira Sans](https://github.com/mozilla/Fira) 55 | * gifsicle: 56 | -------------------------------------------------------------------------------- /assets/img/clickpress_logo_weiss.min.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/css/spectre.scss: -------------------------------------------------------------------------------- 1 | // Variables and mixins 2 | @import "variables"; 3 | 4 | @import "../../node_modules/spectre.css/src/mixins"; 5 | 6 | /*! Spectre.css v#{$version} | MIT License | github.com/picturepan2/spectre */ 7 | // Reset and dependencies 8 | @import "../../node_modules/spectre.css/src/normalize"; 9 | @import "../../node_modules/spectre.css/src/base"; 10 | 11 | // Elements 12 | @import "../../node_modules/spectre.css/src/typography"; 13 | // @import "../../node_modules/spectre.css/src/asian"; 14 | // @import "../../node_modules/spectre.css/src/tables"; 15 | @import "../../node_modules/spectre.css/src/buttons"; 16 | @import "../../node_modules/spectre.css/src/forms"; 17 | @import "../../node_modules/spectre.css/src/labels"; 18 | // @import "../../node_modules/spectre.css/src/codes"; 19 | // @import "../../node_modules/spectre.css/src/media"; 20 | 21 | // Layout 22 | @import "../../node_modules/spectre.css/src/layout"; 23 | //@import "../../node_modules/spectre.css/src/navbar"; 24 | 25 | // Components 26 | //@import "../../node_modules/spectre.css/src/accordions"; 27 | //@import "../../node_modules/spectre.css/src/autocomplete"; 28 | //@import "../../node_modules/spectre.css/src/avatars"; 29 | //@import "../../node_modules/spectre.css/src/badges"; 30 | //@import "../../node_modules/spectre.css/src/breadcrumbs"; 31 | //@import "../../node_modules/spectre.css/src/bars"; 32 | //@import "../../node_modules/spectre.css/src/cards"; 33 | //@import "../../node_modules/spectre.css/src/chips"; 34 | //@import "../../node_modules/spectre.css/src/dropdowns"; 35 | //@import "../../node_modules/spectre.css/src/empty"; 36 | //@import "../../node_modules/spectre.css/src/menus"; 37 | //@import "../../node_modules/spectre.css/src/modals"; 38 | //@import "../../node_modules/spectre.css/src/navs"; 39 | //@import "../../node_modules/spectre.css/src/pagination"; 40 | //@import "../../node_modules/spectre.css/src/panels"; 41 | //@import "../../node_modules/spectre.css/src/popovers"; 42 | //@import "../../node_modules/spectre.css/src/steps"; 43 | //@import "../../node_modules/spectre.css/src/tabs"; 44 | //@import "../../node_modules/spectre.css/src/tiles"; 45 | //@import "../../node_modules/spectre.css/src/toasts"; 46 | //@import "../../node_modules/spectre.css/src/tooltips"; 47 | 48 | // Utility classes 49 | @import "../../node_modules/spectre.css/src/animations"; 50 | @import "../../node_modules/spectre.css/src/utilities"; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "image-shrinker", 3 | "version": "1.6.5", 4 | "description": "Minify your images and graphics with just one drop", 5 | "main": "main.js", 6 | "title": "Image Shrinker", 7 | "productName": "Image Shrinker", 8 | "icon": "build/icon.icns", 9 | "background": "test-background.png", 10 | "target": "mac", 11 | "contents": [ 12 | { 13 | "x": 448, 14 | "y": 344, 15 | "type": "link", 16 | "path": "/Applications" 17 | }, 18 | { 19 | "x": 192, 20 | "y": 344, 21 | "type": "file", 22 | "path": "ImageShrinker.app" 23 | } 24 | ], 25 | "scripts": { 26 | "start": "electron .", 27 | "build": "electron-builder build --dir", 28 | "build-mac": "electron-builder build --mac", 29 | "build-linux": "electron-builder build --linux", 30 | "build-win": "electron-builder build --win", 31 | "build-all": "electron-builder -mwl", 32 | "publish": "electron-builder build -p always" 33 | }, 34 | "repository": "https://github.com/stefansl/image-shrinker", 35 | "keywords": [ 36 | "SVG", 37 | "svgo", 38 | "SVG Shrinking", 39 | "shrinking tool", 40 | "image minifying", 41 | "image", 42 | "png", 43 | "jpg", 44 | "minifying" 45 | ], 46 | "author": "Stefan Schulz-Lauterbach", 47 | "license": "CC0-1.0", 48 | "devDependencies": { 49 | "electron": "^11.2.1", 50 | "electron-builder": "^22.9.1", 51 | "electron-installer-dmg": "^3.0.0", 52 | "electron-notarize": "^1.0.0", 53 | "fs-extra": "^9.1.0" 54 | }, 55 | "dependencies": { 56 | "dotenv": "^8.2.0", 57 | "electron-log": "^4.2.2", 58 | "electron-settings": "^4.0.2", 59 | "electron-updater": "^4.3.1", 60 | "gifsicle": "^5.1.0", 61 | "make-dir": "^3.1.0", 62 | "mozjpeg": "^7.0.0", 63 | "pngquant-bin": "^6.0.0", 64 | "spectre.css": "^0.5.9", 65 | "svgo": "^1.3.0" 66 | }, 67 | "build": { 68 | "appId": "de.clickpress.image-shrinker", 69 | "afterSign": "lib/notarize.js", 70 | "mac": { 71 | "category": "public.app-category.developer-tools", 72 | "target": [], 73 | "type": "distribution", 74 | "hardenedRuntime": true, 75 | "gatekeeperAssess": false, 76 | "entitlements": "build/entitlements.mac.plist", 77 | "entitlementsInherit": "build/entitlements.mac.plist" 78 | }, 79 | "win": { 80 | "icon": "256x256.png" 81 | }, 82 | "asar": true, 83 | "files": [ 84 | "**/*", 85 | "!node-modules/*", 86 | "!release-builds/*" 87 | ], 88 | "extraResources": [ 89 | "build/*" 90 | ], 91 | "fileAssociations": [ 92 | { 93 | "name": "SVG", 94 | "ext": "svg" 95 | }, 96 | { 97 | "name": "PNG", 98 | "ext": "png" 99 | }, 100 | { 101 | "name": "GIF", 102 | "ext": "gif" 103 | }, 104 | { 105 | "name": "JPG", 106 | "ext": "jpg" 107 | } 108 | ], 109 | "dmg": { 110 | "background": "build/bg_dmg.tiff", 111 | "icon": "build/icon_dmg.icns", 112 | "format": "ULFO", 113 | "contents": [ 114 | { 115 | "x": 168, 116 | "y": 240 117 | }, 118 | { 119 | "x": 372, 120 | "y": 240, 121 | "type": "link", 122 | "path": "/Applications" 123 | } 124 | ] 125 | }, 126 | "pkg": {}, 127 | "publish": { 128 | "provider": "github", 129 | "owner": "stefansl", 130 | "repo": "image-shrinker" 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /assets/css/_variables.scss: -------------------------------------------------------------------------------- 1 | // Core variables 2 | $version: "0.5.0"; 3 | 4 | // Core colors 5 | $primary-color: #46c2fe !default; 6 | $primary-color-dark: darken($primary-color, 3%) !default; 7 | $primary-color-light: lighten($primary-color, 3%) !default; 8 | $secondary-color: #d86550 !default; 9 | $secondary-color-dark: darken($secondary-color, 3%) !default; 10 | $secondary-color-light: lighten($secondary-color, 3%) !default; 11 | 12 | $main-color: #F3FFE2; 13 | 14 | // Gray colors 15 | $dark-color: #111 !default; 16 | $light-color: #fff !default; 17 | $gray-color: lighten($dark-color, 40%) !default; 18 | $gray-color-dark: darken($gray-color, 25%) !default; 19 | $gray-color-light: lighten($gray-color, 20%) !default; 20 | 21 | $border-color: lighten($dark-color, 60%) !default; 22 | $border-color-dark: darken($border-color, 10%) !default; 23 | $bg-color: lighten($dark-color, 66%) !default; 24 | $bg-color-dark: darken($bg-color, 3%) !default; 25 | $bg-color-light: $light-color !default; 26 | 27 | // Control colors 28 | $success-color: #32b643 !default; 29 | $warning-color: #ffb700 !default; 30 | $error-color: #e85600 !default; 31 | 32 | // Other colors 33 | $code-color: #e06870 !default; 34 | $highlight-color: #ffe9b3 !default; 35 | $body-bg: $bg-color-light !default; 36 | $body-font-color: lighten($dark-color, 5%) !default; 37 | $link-color: $primary-color !default; 38 | $link-color-dark: darken($link-color, 5%) !default; 39 | 40 | // Fonts 41 | // Credit: https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/ 42 | $base-font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto !default; 43 | $mono-font-family: "SF Mono", "Segoe UI Mono", "Roboto Mono", Menlo, Courier, monospace !default; 44 | $fallback-font-family: "Helvetica Neue", sans-serif !default; 45 | $cjk-zh-font-family: $base-font-family, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", $fallback-font-family !default; 46 | $cjk-jp-font-family: $base-font-family, "Hiragino Sans", "Hiragino Kaku Gothic Pro", "Yu Gothic", YuGothic, Meiryo, $fallback-font-family !default; 47 | $cjk-ko-font-family: $base-font-family, "Malgun Gothic", $fallback-font-family !default; 48 | $body-font-family: $base-font-family, $fallback-font-family !default; 49 | 50 | // Unit sizes 51 | $unit-o: .05rem !default; 52 | $unit-h: .1rem !default; 53 | $unit-1: .2rem !default; 54 | $unit-2: .4rem !default; 55 | $unit-3: .6rem !default; 56 | $unit-4: .8rem !default; 57 | $unit-5: 1rem !default; 58 | $unit-6: 1.2rem !default; 59 | $unit-7: 1.4rem !default; 60 | $unit-8: 1.6rem !default; 61 | $unit-9: 1.8rem !default; 62 | $unit-10: 2rem !default; 63 | $unit-12: 2.4rem !default; 64 | $unit-16: 3.2rem !default; 65 | 66 | // Font sizes 67 | $html-font-size: 20px !default; 68 | $html-line-height: 1.5 !default; 69 | $font-size: .8rem !default; 70 | $font-size-sm: .7rem !default; 71 | $font-size-lg: .9rem !default; 72 | $line-height: 1rem !default; 73 | 74 | // Sizes 75 | $layout-spacing: $unit-2 !default; 76 | $layout-spacing-sm: $unit-1 !default; 77 | $layout-spacing-lg: $unit-4 !default; 78 | $border-radius: $unit-h !default; 79 | $border-width: $unit-o !default; 80 | $border-width-lg: $unit-h !default; 81 | $control-size: $unit-9 !default; 82 | $control-size-sm: $unit-7 !default; 83 | $control-size-lg: $unit-10 !default; 84 | $control-padding-x: $unit-2 !default; 85 | $control-padding-x-sm: $unit-2 * .75 !default; 86 | $control-padding-x-lg: $unit-2 * 1.5 !default; 87 | $control-padding-y: ($control-size - $line-height) / 2 - $border-width !default; 88 | $control-padding-y-sm: ($control-size-sm - $line-height) / 2 - $border-width !default; 89 | $control-padding-y-lg: ($control-size-lg - $line-height) / 2 - $border-width !default; 90 | $control-icon-size: .8rem !default; 91 | 92 | $control-width-xs: 180px !default; 93 | $control-width-sm: 320px !default; 94 | $control-width-md: 640px !default; 95 | $control-width-lg: 960px !default; 96 | $control-width-xl: 1280px !default; 97 | 98 | // Responsive breakpoints 99 | $size-xs: 480px !default; 100 | $size-sm: 600px !default; 101 | $size-md: 840px !default; 102 | $size-lg: 960px !default; 103 | $size-xl: 1280px !default; 104 | $size-2x: 1440px !default; 105 | 106 | $responsive-breakpoint: $size-xs !default; 107 | 108 | // Z-index 109 | $zindex-0: 1 !default; 110 | $zindex-1: 100 !default; 111 | $zindex-2: 200 !default; 112 | $zindex-3: 300 !default; 113 | $zindex-4: 400 !default; 114 | -------------------------------------------------------------------------------- /assets/fonts/LICENSE: -------------------------------------------------------------------------------- 1 | Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. 2 | with Reserved Font Name < Fira >, 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION & CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. 95 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Image Shrinker 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 |
17 |

Drag files here

18 | only SVG, JPG, GIF and PNG allowed 19 |
20 |
21 | 22 | 23 | 121 |
122 |
123 |
124 |
125 | 126 | 129 | 130 | -------------------------------------------------------------------------------- /assets/css/imageshrinker.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Fira Sans'; 3 | src: local("Fira Sans Italic"), local("FiraSans-Italic"), url(../fonts/FiraSans-Italic.woff2) format("woff2"); 4 | font-weight: 400; 5 | font-style: italic; } 6 | @font-face { 7 | font-family: 'Fira Sans'; 8 | src: local("Fira Sans"), local("FiraSans"), url(../fonts/FiraSans-Regular.woff2) format("woff2"); 9 | font-weight: 400; 10 | font-style: normal; } 11 | @font-face { 12 | font-family: 'Fira Sans'; 13 | src: local("Fira Sans Bold Italic"), local("FiraSans-BoldItalic"), url(../fonts/FiraSans-BoldItalic.woff2) format("woff2"); 14 | font-weight: 700; 15 | font-style: italic; } 16 | * { 17 | box-sizing: border-box; } 18 | 19 | html { 20 | -webkit-app-region: drag; } 21 | 22 | body { 23 | color: #111; 24 | font-family: 'Fira Sans', sans-serif; 25 | font-weight: 400; 26 | overflow: hidden; 27 | padding: 2rem .5rem; } 28 | 29 | #wrapper { 30 | max-height: calc(100vh - 4rem); } 31 | 32 | #background { 33 | background-image: url("../img/bg_poly.min.svg"); 34 | background-size: 120% 120%; 35 | height: 100vh; 36 | left: 0; 37 | position: absolute; 38 | top: 0; 39 | transform: scale(1.1); 40 | transition: transform .7s ease-out; 41 | width: 100vw; 42 | z-index: -1; 43 | perspective: 8000px; 44 | transform-style: preserve-3d; } 45 | 46 | #dragzone { 47 | border: 2px dashed #111; 48 | color: #111; 49 | cursor: pointer; 50 | font-style: italic; 51 | opacity: .5; 52 | padding: 1em; 53 | position: relative; 54 | text-align: center; 55 | transition: opacity .1s ease-out; } 56 | #dragzone.drag-active { 57 | opacity: .9; } 58 | #dragzone::after { 59 | background-color: transparent; 60 | content: " "; 61 | display: block; 62 | height: 100%; 63 | left: 0; 64 | opacity: 0; 65 | position: absolute; 66 | top: 0; 67 | transition: all .4s ease-out; 68 | width: 100%; } 69 | #dragzone.is--processing::after { 70 | background: rgba(216, 101, 80, 0.5) url("../img/tail-spin.min.svg") no-repeat center center; 71 | background-size: 15%; 72 | opacity: 1; } 73 | #dragzone:hover { 74 | opacity: .75; } 75 | #dragzone strong { 76 | font-style: italic; 77 | font-weight: 400; } 78 | #dragzone h1 { 79 | font-size: 1.6rem; 80 | font-weight: 700; 81 | margin: 0 0 .5rem; } 82 | 83 | #result { 84 | max-height: 400px; 85 | overflow-y: scroll; 86 | -webkit-mask-image: -webkit-linear-gradient(bottom, transparent 1%, #fff 20%, #fff 90%, transparent 99%); } 87 | 88 | .resLine { 89 | border-bottom: 1px solid rgba(17, 17, 17, 0.3); 90 | padding: 1rem .25rem; } 91 | .resLine span { 92 | color: rgba(17, 17, 17, 0.7); 93 | font-size: 8px; 94 | font-weight: 400; } 95 | .resLine a { 96 | color: rgba(17, 17, 17, 0.9); 97 | display: block; 98 | font-size: 10px; 99 | font-style: italic; } 100 | 101 | #menuSettings { 102 | background-color: rgba(17, 17, 17, 0.9); 103 | color: rgba(255, 255, 255, 0.95); 104 | font-size: .675rem; 105 | height: 100vh; 106 | left: 0; 107 | padding: 3rem 1rem; 108 | position: absolute; 109 | top: 0; 110 | transform: translate3d(-100%, 0, 0); 111 | transition: transform 450ms cubic-bezier(0.23, 1, 0.32, 1); 112 | width: 100vw; 113 | z-index: 10; } 114 | #menuSettings.is--open { 115 | transform: translate3d(0, 0, 0); } 116 | #menuSettings li { 117 | border-bottom: 1px solid rgba(255, 255, 255, 0.2); 118 | padding: .5rem 1rem; } 119 | #menuSettings li:after { 120 | clear: both; 121 | content: " "; } 122 | #menuSettings .columns { 123 | border-bottom: 1px solid rgba(255, 255, 255, 0.1); 124 | padding: .5rem 0; } 125 | #menuSettings .column { 126 | align-self: center; } 127 | #menuSettings .column span { 128 | line-height: 1rem; } 129 | 130 | #btnOpenSettings { 131 | z-index: 5; } 132 | #btnOpenSettings:hover { 133 | color: rgba(17, 17, 17, 0.5); } 134 | 135 | #btnCloseSettings:hover { 136 | color: rgba(255, 255, 255, 0.75); } 137 | 138 | #btnOpenSettings, #btnCloseSettings { 139 | bottom: 1rem; 140 | position: absolute; 141 | right: 1rem; } 142 | 143 | .icon { 144 | cursor: pointer; } 145 | 146 | .copyright { 147 | bottom: 1rem; 148 | color: #fff; 149 | display: table; 150 | left: 1.5rem; 151 | margin: 0 auto; 152 | position: absolute; } 153 | .copyright span { 154 | display: table-cell; 155 | font-size: .6em; 156 | opacity: .4; 157 | padding-right: .75em; 158 | vertical-align: middle; } 159 | .copyright img { 160 | opacity: .4; 161 | transition: opacity 200ms cubic-bezier(0.79, 0.14, 0.15, 0.86); } 162 | .copyright a { 163 | display: block; 164 | line-height: 0; } 165 | .copyright a:hover img { 166 | opacity: 1; } 167 | 168 | .text-small { 169 | font-size: .5rem; } 170 | 171 | .btn.btn-sm { 172 | font-size: .5rem; } 173 | 174 | #overlay { 175 | height: 100vh; 176 | left: 0; 177 | position: absolute; 178 | top: 0; 179 | width: 100vw; 180 | z-index: 10; } 181 | 182 | #btnSavepath { 183 | width: calc(100vw - 3rem); } 184 | 185 | /*# sourceMappingURL=imageshrinker.css.map */ 186 | -------------------------------------------------------------------------------- /assets/css/imageshrinker.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | // Fira Sans 3 | @font-face { 4 | font-family: 'Fira Sans'; 5 | src: local('Fira Sans Italic'), local('FiraSans-Italic'), url(../fonts/FiraSans-Italic.woff2) format('woff2'); 6 | font-weight: 400; 7 | font-style: italic; 8 | } 9 | 10 | @font-face { 11 | font-family: 'Fira Sans'; 12 | src: local('Fira Sans'), local('FiraSans'), url(../fonts/FiraSans-Regular.woff2) format('woff2'); 13 | font-weight: 400; 14 | font-style: normal; 15 | } 16 | 17 | @font-face { 18 | font-family: 'Fira Sans'; 19 | src: local('Fira Sans Bold Italic'), local('FiraSans-BoldItalic'), url(../fonts/FiraSans-BoldItalic.woff2) format('woff2'); 20 | font-weight: 700; 21 | font-style: italic; 22 | } 23 | 24 | * { 25 | box-sizing: border-box; 26 | } 27 | 28 | html { 29 | -webkit-app-region: drag; 30 | } 31 | 32 | body { 33 | color: $dark-color; 34 | font-family: 'Fira Sans', sans-serif; 35 | font-weight: 400; 36 | overflow: hidden; 37 | padding: 2rem .5rem; 38 | } 39 | 40 | #wrapper { 41 | max-height: calc(100vh - 4rem); 42 | } 43 | 44 | #background { 45 | background-image: url('../img/bg_poly.min.svg'); 46 | background-size: 120% 120%; 47 | height: 100vh; 48 | left: 0; 49 | position: absolute; 50 | top: 0; 51 | transform: scale(1.1); 52 | transition: transform .7s ease-out; 53 | width: 100vw; 54 | z-index: -1; 55 | perspective: 8000px; 56 | transform-style: preserve-3d; 57 | 58 | } 59 | 60 | #dragzone { 61 | border: 2px dashed $dark-color; 62 | color: $dark-color; 63 | cursor: pointer; 64 | font-style: italic; 65 | opacity: .5; 66 | padding: 1em; 67 | position: relative; 68 | text-align: center; 69 | transition: opacity .1s ease-out; 70 | &.drag-active { 71 | opacity: .9; 72 | } 73 | &::after { 74 | background-color: transparent; 75 | content: " "; 76 | display: block; 77 | height: 100%; 78 | left: 0; 79 | opacity: 0; 80 | position: absolute; 81 | top: 0; 82 | transition: all .4s ease-out; 83 | width: 100%; 84 | } 85 | &.is--processing::after { 86 | background: rgba($secondary-color, .5) url("../img/tail-spin.min.svg") no-repeat center center; 87 | background-size: 15%; 88 | opacity: 1; 89 | } 90 | &:hover { 91 | opacity: .75; 92 | } 93 | strong { 94 | font-style: italic; 95 | font-weight: 400; 96 | } 97 | h1 { 98 | font-size: 1.6rem; 99 | font-weight: 700; 100 | margin: 0 0 .5rem; 101 | } 102 | } 103 | 104 | #result { 105 | max-height: 400px; 106 | overflow-y: scroll; 107 | -webkit-mask-image: -webkit-linear-gradient(bottom, transparent 1%, $light-color 20%, $light-color 90%, transparent 99%); 108 | 109 | } 110 | 111 | .resLine { 112 | border-bottom: 1px solid rgba($dark-color, .3); 113 | padding: 1rem .25rem; 114 | span { 115 | color: rgba($dark-color, .7); 116 | font-size: 8px; 117 | font-weight: 400; 118 | } 119 | a { 120 | color: rgba($dark-color, .9); 121 | display: block; 122 | font-size: 10px; 123 | font-style: italic; 124 | } 125 | } 126 | 127 | #menuSettings { 128 | background-color: rgba($dark-color, .9); 129 | color: rgba($light-color, .95); 130 | font-size: .675rem; 131 | height: 100vh; 132 | left: 0; 133 | padding: 3rem 1rem; 134 | position: absolute; 135 | top: 0; 136 | transform: translate3d(-100%, 0, 0); 137 | transition: transform 450ms cubic-bezier(.23, 1, .32, 1); 138 | width: 100vw; 139 | z-index: 10; 140 | &.is--open { 141 | transform: translate3d(0, 0, 0); 142 | } 143 | li { 144 | border-bottom: 1px solid rgba($light-color, .2); 145 | padding: .5rem 1rem; 146 | &:after { 147 | clear: both; 148 | content: " "; 149 | 150 | } 151 | } 152 | .columns { 153 | border-bottom: 1px solid rgba($light-color, .1); 154 | padding: .5rem 0; 155 | } 156 | .column { 157 | align-self: center; 158 | span { 159 | line-height: 1rem; 160 | } 161 | } 162 | } 163 | 164 | #btnOpenSettings { 165 | z-index: 5; 166 | &:hover { 167 | color: rgba($dark-color, .50); 168 | } 169 | } 170 | 171 | #btnCloseSettings { 172 | &:hover { 173 | color: rgba($light-color, .75); 174 | } 175 | 176 | } 177 | 178 | #btnOpenSettings, #btnCloseSettings { 179 | bottom: 1rem; 180 | position: absolute; 181 | right: 1rem; 182 | } 183 | 184 | .icon { 185 | cursor: pointer; 186 | } 187 | 188 | .copyright { 189 | bottom: 1rem; 190 | color: $light-color; 191 | display: table; 192 | left: 1.5rem; 193 | margin: 0 auto; 194 | position: absolute; 195 | span { 196 | display: table-cell; 197 | font-size: .6em; 198 | opacity: .4; 199 | padding-right: .75em; 200 | vertical-align: middle; 201 | } 202 | img { 203 | opacity: .4; 204 | transition: opacity 200ms cubic-bezier(.79, .14, .15, .86); 205 | } 206 | a { 207 | display: block; 208 | line-height: 0; 209 | &:hover img { 210 | opacity: 1; 211 | } 212 | } 213 | } 214 | 215 | .text-small { 216 | font-size: .5rem; 217 | } 218 | 219 | .btn.btn-sm { 220 | font-size: .5rem; 221 | 222 | } 223 | 224 | #overlay { 225 | height: 100vh; 226 | left: 0; 227 | position: absolute; 228 | top: 0; 229 | width: 100vw; 230 | z-index: 10; 231 | } 232 | 233 | #btnSavepath { 234 | width: calc(100vw - 3rem); 235 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | ================== 3 | 4 | Statement of Purpose 5 | --------------------- 6 | 7 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). 8 | 9 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. 10 | 11 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 12 | 13 | 1. Copyright and Related Rights. 14 | -------------------------------- 15 | A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: 16 | 17 | i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; 18 | ii. moral rights retained by the original author(s) and/or performer(s); 19 | iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; 20 | iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; 21 | v. rights protecting the extraction, dissemination, use and reuse of data in a Work; 22 | vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and 23 | vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 24 | 25 | 2. Waiver. 26 | ----------- 27 | To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 28 | 29 | 3. Public License Fallback. 30 | ---------------------------- 31 | Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 32 | 33 | 4. Limitations and Disclaimers. 34 | -------------------------------- 35 | 36 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. 37 | b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. 38 | c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. 39 | d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. 40 | -------------------------------------------------------------------------------- /renderer.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer, shell } = require('electron'); 2 | const settings = require('electron-settings'); 3 | const { dialog } = require('electron').remote; 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const log = require('electron-log'); 7 | 8 | const dragzone = document.getElementById('dragzone'), 9 | resultBox = document.getElementById('result'), 10 | btnOpenSettings = document.getElementById('btnOpenSettings'), 11 | btnCloseSettings = document.getElementById('btnCloseSettings'), 12 | menuSettings = document.getElementById('menuSettings'), 13 | switches = document.getElementsByTagName('input'), 14 | openInBrowserLink = document.getElementsByClassName('openInBrowser'), 15 | btnSavepath = document.getElementById('btnSavepath'), 16 | wrapperSavePath = document.getElementById('wrapperSavePath'), 17 | folderswitch = document.getElementById('folderswitch'), 18 | clearlist = document.getElementById('clearlist'), 19 | updatecheck = document.getElementById('updatecheck'), 20 | suffix = document.getElementById('suffix'), 21 | subfolder = document.getElementById('subfolder'), 22 | notification = document.getElementById('notification'); 23 | 24 | /* 25 | * Settings 26 | */ 27 | let userSetting = settings.getSync(); 28 | notification.checked = userSetting.notification; 29 | clearlist.checked = userSetting.clearlist; 30 | updatecheck.checked = userSetting.updatecheck; 31 | suffix.checked = userSetting.suffix; 32 | subfolder.checked = userSetting.subfolder; 33 | 34 | if (userSetting.folderswitch === false) 35 | { 36 | folderswitch.checked = false; 37 | wrapperSavePath.classList.remove('d-none'); 38 | } else 39 | { 40 | folderswitch.checked = true; 41 | } 42 | 43 | 44 | /* 45 | * Cut path from beginning, if necessary 46 | * @param {string} path Filepath 47 | * @param {integer} length max length 48 | * @return {string} truncated string 49 | */ 50 | const cutFolderName = (path, length = 20) => { 51 | return path.length >= length ? '... ' + path.substr(path.length - length) : path; 52 | }; 53 | 54 | 55 | /** 56 | * @param {{savepath:string}} userSetting 57 | */ 58 | if (userSetting.savepath) 59 | btnSavepath.innerText = cutFolderName(userSetting.savepath[0], 48); 60 | 61 | /* 62 | * Open filepicker 63 | */ 64 | dragzone.onclick = () => { 65 | dialog.showOpenDialog( 66 | { 67 | properties: ['openFile', 'multiSelections'] 68 | }).then(result => { 69 | 70 | if (result.canceled) 71 | { 72 | return; 73 | } 74 | 75 | // Add loader 76 | dragzone.classList.add('is--processing'); 77 | if (settings.getSync('clearlist') === true) 78 | { 79 | resultBox.innerHTML = ''; 80 | } 81 | 82 | for (let f of result.filePaths) 83 | { 84 | let filename = path.parse(f).base; 85 | ipcRenderer.send('shrinkImage', filename, f); 86 | } 87 | 88 | }).catch(err => { 89 | log.error(err); 90 | }); 91 | 92 | }; 93 | 94 | document.ondragover = () => { 95 | dragzone.classList.add('drag-active'); 96 | return false; 97 | }; 98 | 99 | document.ondragleave = () => { 100 | dragzone.classList.remove('drag-active'); 101 | return false; 102 | }; 103 | 104 | document.ondragend = () => { 105 | dragzone.classList.remove('drag-active'); 106 | return false; 107 | }; 108 | 109 | /* 110 | * Action on drag drop 111 | */ 112 | document.ondrop = e => { 113 | e.preventDefault(); 114 | 115 | let items = e.dataTransfer.items; 116 | for (let i = 0; i < items.length; i++) 117 | { 118 | // webkitGetAsEntry is where the magic happens 119 | let item = items[i].webkitGetAsEntry(); 120 | if (item) 121 | { 122 | traverseFileTree(item); 123 | } 124 | } 125 | 126 | if (settings.getSync('clearlist')) 127 | { 128 | resultBox.innerHTML = ''; 129 | } 130 | 131 | dragzone.classList.add('is--processing'); 132 | dragzone.classList.remove('drag-active'); 133 | 134 | return false; 135 | }; 136 | 137 | /* 138 | * Choose folder for saving shrunken images 139 | */ 140 | btnSavepath.onclick = () => { 141 | dialog.showOpenDialog( 142 | { 143 | properties: ['openDirectory', 'createDirectory'] 144 | }).then(result => { 145 | if (result.filePaths) 146 | { 147 | btnSavepath.innerText = cutFolderName(result.filePaths[0], 48); 148 | settings.set('savepath', result.filePaths); 149 | } 150 | }).catch(err => { 151 | log.error(err); 152 | }); 153 | }; 154 | 155 | /* 156 | * Save settings 157 | */ 158 | Array.from(switches).forEach(switchEl => { 159 | switchEl.onchange = e => { 160 | settings.setSync(e.target['name'], e.target['checked']); 161 | if (e.target['name'] === 'folderswitch') 162 | { 163 | if (!e.target['checked']) 164 | { 165 | wrapperSavePath.classList.remove('d-none'); 166 | } else 167 | { 168 | wrapperSavePath.classList.add('d-none'); 169 | } 170 | } 171 | }; 172 | }); 173 | /* 174 | * Settings menu 175 | */ 176 | // Open 177 | btnOpenSettings.onclick = e => { 178 | e.preventDefault(); 179 | menuSettings.classList.add('is--open'); 180 | }; 181 | 182 | // Close on pressing close icon 183 | btnCloseSettings.onclick = e => { 184 | e.preventDefault(); 185 | menuSettings.classList.remove('is--open'); 186 | }; 187 | 188 | // Close on pressing ESC 189 | document.onkeyup = e => { 190 | if (e.key === 'Escape') 191 | { 192 | menuSettings.classList.remove('is--open'); 193 | } 194 | }; 195 | 196 | /* 197 | * Renderer process 198 | */ 199 | ipcRenderer.on('isShrinked', (event, path, sizeBefore, sizeAfter) => { 200 | const percent = Math.round((100 / sizeBefore) * (sizeBefore - sizeAfter)); 201 | 202 | // Remove loader 203 | dragzone.classList.remove('is--processing'); 204 | 205 | // Create container 206 | const resContainer = document.createElement('div'); 207 | resContainer.className = 'resLine'; 208 | resContainer.innerHTML = 209 | 'You saved ' + 210 | percent + 211 | '%. Your shrunken image is here:
'; 212 | 213 | // Create link 214 | let resElement = document.createElement('a'); 215 | resElement.setAttribute('href', '#'); 216 | let resText = document.createTextNode(path); 217 | resElement.appendChild(resText); 218 | 219 | // Add click event 220 | resElement.onclick = el => { 221 | el.preventDefault(); 222 | shell.showItemInFolder(path); 223 | }; 224 | 225 | resContainer.appendChild(resElement); 226 | resultBox.prepend(resContainer); 227 | 228 | // Notification 229 | 230 | if (settings.getSync('notification')) 231 | { 232 | new window.Notification('Image shrunk, pal!', { 233 | body: path, 234 | silent: true 235 | }); 236 | } 237 | }).on('openSettings', () => { 238 | menuSettings.classList.add('is--open'); 239 | }).on('error', () => { 240 | // Remove loader 241 | dragzone.classList.remove('is--processing'); 242 | }); 243 | 244 | /* 245 | * Parallax background 246 | */ 247 | let bg = document.getElementById('background'), 248 | winX = window.innerWidth / 2, 249 | winY = window.innerHeight / 2; 250 | 251 | // Fix window size on resize 252 | window.onresize = () => { 253 | setTimeout(() => { 254 | winX = window.innerWidth / 2; 255 | winY = window.innerHeight / 2; 256 | }, 700); 257 | }; 258 | 259 | // Let's do some parallax stuff 260 | document.onmousemove = e => { 261 | let transX = e.clientX - winX, 262 | transY = e.clientY - winY, 263 | tiltX = transX / winY, 264 | tiltY = -(transY / winX), 265 | radius = Math.sqrt(Math.pow(tiltX, 2) + Math.pow(tiltY, 2)), 266 | transformX = Math.floor(tiltX * Math.PI), 267 | transformY = Math.floor(tiltY * Math.PI), 268 | degree = radius * 15, 269 | transform; 270 | 271 | transform = 'scale(1.15)'; 272 | transform += ' rotate3d(' + tiltX + ', ' + tiltY + ', 0, ' + degree + 'deg)'; 273 | transform += ' translate3d(' + transformX + 'px, ' + transformY + 'px, 0)'; 274 | 275 | bg.style.transform = transform; 276 | }; 277 | 278 | // Reset, if mouse leaves window 279 | document.onmouseleave = () => { 280 | bg.style.transform = ''; 281 | }; 282 | 283 | // (opt) event, text as return value 284 | ipcRenderer.on('updateReady', () => { 285 | // changes the text of the button 286 | const container = document.getElementById('ready'); 287 | container.innerHTML = 'new version available!'; 288 | }); 289 | 290 | /* 291 | * Open external links in browser 292 | */ 293 | Array.from(openInBrowserLink).forEach((el) => { 294 | el.addEventListener('click', (event) => { 295 | event.preventDefault(); 296 | shell.openExternal(el.getAttribute('href')).catch((error) => { 297 | log.error(error); 298 | }); 299 | }); 300 | }); 301 | 302 | /** 303 | * Traverse down the folders and exclude files in `exclude` array 304 | */ 305 | function traverseFileTree(item, path) 306 | { 307 | const exclude = ['.DS_Store']; 308 | path = path || ''; 309 | 310 | if (item.isFile) 311 | { 312 | // Get file 313 | item.file(function(f) { 314 | if (fs.statSync(f.path).isDirectory() || exclude.includes(f.name)) 315 | { 316 | dragzone.classList.remove('drag-active'); 317 | 318 | return false; 319 | } 320 | 321 | ipcRenderer.send('shrinkImage', f.name, f.path, f.lastModified); 322 | }); 323 | } else if (item.isDirectory) 324 | { 325 | // Get folder contents 326 | const dirReader = item.createReader(); 327 | dirReader.readEntries(function(entries) { 328 | for (let i in entries) 329 | { 330 | traverseFileTree(entries[i], path + item.name + '/'); 331 | } 332 | }); 333 | } 334 | } -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, ipcMain, dialog, TouchBar } = require( 2 | 'electron'); 3 | const dotenv = require('dotenv').config(); 4 | const nativeImage = require('electron').nativeImage; 5 | const { autoUpdater } = require('electron-updater'); 6 | const log = require('electron-log'); 7 | const fs = require('fs'); 8 | const path = require('path'); 9 | const settings = require('electron-settings'); 10 | const svgo = require('svgo'); 11 | const execFile = require('child_process').execFile; 12 | const mozjpeg = require('mozjpeg'); 13 | const pngquant = require('pngquant-bin'); 14 | const makeDir = require('make-dir'); 15 | const { TouchBarButton } = TouchBar; 16 | const gifsicle = require('gifsicle'); 17 | 18 | global.debug = { 19 | devTools: 0 20 | }; 21 | 22 | /** 23 | * Support for .env 24 | */ 25 | if(!dotenv.error){ 26 | global.debug = { 27 | devTools: process.env.ELECTRON_DEBUG 28 | }; 29 | } 30 | 31 | /** 32 | * Start logging in os log 33 | */ 34 | autoUpdater.logger = log; 35 | 36 | autoUpdater.logger['transports'].file.level = 'info'; 37 | log.info('App starting...'); 38 | 39 | /** 40 | * Init vars 41 | */ 42 | let svg = new svgo(); 43 | let mainWindow; 44 | 45 | 46 | /** 47 | * Create the browser window 48 | */ 49 | const createWindow = () => { 50 | 51 | /** Create the browser window. */ 52 | mainWindow = new BrowserWindow({ 53 | titleBarStyle: 'hiddenInset', 54 | width: 340, 55 | height: 550, 56 | minWidth: 340, 57 | minHeight: 550, 58 | frame: false, 59 | backgroundColor: '#F7F7F7', 60 | resizable: true, 61 | show: false, 62 | //icon: path.join(__dirname, 'assets/icons/png/64x64.png'), 63 | webPreferences: { 64 | nodeIntegration: true, 65 | enableRemoteModule: true 66 | } 67 | }); 68 | 69 | /** Show window when ready */ 70 | mainWindow.on('ready-to-show', () => { 71 | mainWindow.show(); 72 | }); 73 | 74 | /** Load index.html of the app. */ 75 | mainWindow.loadURL(path.join('file://', __dirname, '/index.html')).then( 76 | () => { 77 | /** Open the DevTools. */ 78 | global['debug'].devTools === 0 || 79 | mainWindow.webContents.openDevTools(); 80 | } 81 | ).catch( 82 | (error) => { 83 | log.error(error); 84 | } 85 | ); 86 | 87 | /** Open the DevTools. */ 88 | global['debug'].devTools === 0 || mainWindow.webContents.openDevTools(); 89 | 90 | /** Window closed */ 91 | mainWindow.on('closed', () => { 92 | mainWindow = null; 93 | }); 94 | 95 | /** Default settings */ 96 | const defaultSettings = { 97 | notification: true, 98 | folderswitch: true, 99 | clearlist: false, 100 | suffix: true, 101 | updatecheck: true, 102 | subfolder: false, 103 | }; 104 | 105 | /** set missing settings */ 106 | const newSettings = Object.assign({}, defaultSettings, settings.getSync()); 107 | settings.setSync(newSettings); 108 | mainWindow.setTouchBar(touchBar); 109 | require('./menu/mainmenu'); 110 | }; 111 | 112 | /** Touchbar support */ 113 | let touchBarResult = new TouchBarButton({ 114 | label: 'Let me shrink some images!', 115 | backgroundColor: '#000000', 116 | click: () => { 117 | dialog.showOpenDialog({ 118 | properties: ['openFile', 'multiSelections'] 119 | }).then(result => { 120 | if (result.canceled) 121 | { 122 | return; 123 | } 124 | for (let filePath of result.filePaths) 125 | { 126 | processFile(filePath, path.basename(filePath)); 127 | } 128 | }).catch(err => { 129 | log.error(err); 130 | }); 131 | } 132 | }); 133 | 134 | let touchBarIcon = new TouchBarButton({ 135 | backgroundColor: '#000000', 136 | 'icon': nativeImage.createFromPath(path.join(__dirname, 'assets/icons/png/16x16.png')), 137 | iconPosition: 'center' 138 | }); 139 | 140 | const touchBar = new TouchBar({ 141 | items: [ 142 | touchBarResult 143 | ] 144 | }); 145 | 146 | /** Add Touchbar icon */ 147 | touchBar.escapeItem = touchBarIcon; 148 | 149 | app.on('will-finish-launching', () => { 150 | app.on('open-file', (event, filePath) => { 151 | event.preventDefault(); 152 | processFile(filePath, path.basename(filePath)); 153 | }); 154 | }); 155 | 156 | /** Start app */ 157 | app.on('ready', () => { 158 | createWindow(); 159 | if (settings.getSync('updatecheck') === true) 160 | { 161 | autoUpdater.checkForUpdatesAndNotify().catch((error) => { 162 | log.error(error); 163 | }); 164 | } 165 | }); 166 | 167 | /** Quit when all windows are closed. */ 168 | app.on('window-all-closed', () => { 169 | if (process.platform !== 'darwin') 170 | { 171 | app.quit(); 172 | } 173 | }); 174 | 175 | app.on('activate', () => { 176 | if (mainWindow === null) 177 | { 178 | createWindow(); 179 | } 180 | }); 181 | 182 | /** When the update has been downloaded and is ready to be installed, notify the BrowserWindow */ 183 | autoUpdater.on('update-downloaded', (info) => { 184 | log.info(info); 185 | mainWindow.webContents.send('updateReady'); 186 | }); 187 | 188 | /** When receiving a quitAndInstall signal, quit and install the new version ;) */ 189 | ipcMain.on('quitAndInstall', (event, arg) => { 190 | log.info(event); 191 | log.info(arg); 192 | autoUpdater.quitAndInstall(); 193 | }); 194 | 195 | /** Main logic */ 196 | ipcMain.on( 197 | 'shrinkImage', (event, fileName, filePath) => { 198 | processFile(filePath, fileName); 199 | } 200 | ); 201 | 202 | /** 203 | * Shrinking the image 204 | * @param {string} filePath Filepath 205 | * @param {string} fileName Filename 206 | */ 207 | const processFile = (filePath, fileName) => { 208 | 209 | /** Focus window on drag */ 210 | !mainWindow || mainWindow.focus(); 211 | 212 | /** Change Touchbar */ 213 | touchBarResult.label = 'I am shrinking for you'; 214 | 215 | /** Get filesize */ 216 | let sizeOrig = getFileSize(filePath, false); 217 | 218 | /** Process image(s) */ 219 | fs.readFile(filePath, 'utf8', (err, data) => { 220 | 221 | if (err) 222 | { 223 | throw err; 224 | } 225 | 226 | app.addRecentDocument(filePath); 227 | const newFile = generateNewPath(filePath); 228 | 229 | switch (path.extname(fileName).toLowerCase()) 230 | { 231 | case '.svg': 232 | { 233 | svg.optimize(data).then((result) => { 234 | fs.writeFile(newFile, result.data, (err) => { 235 | touchBarResult.label = 'Your shrunken image: ' + 236 | newFile; 237 | 238 | sendToRenderer(err, newFile, sizeOrig); 239 | }); 240 | }).catch((error) => { 241 | dialog.showErrorBox('Error', error.message); 242 | }); 243 | break; 244 | } 245 | case '.jpg': 246 | case '.jpeg': 247 | { 248 | /** Create temp file from original, see #54 **/ 249 | let origFile; 250 | let addTmpFile = !settings.getSync('suffix') && !settings.getSync('subfolder'); 251 | 252 | if (addTmpFile) { 253 | origFile = newFile + '.tmp'; 254 | fs.copyFileSync(filePath, origFile); 255 | }else { 256 | origFile = filePath; 257 | } 258 | 259 | execFile(mozjpeg, ['-outfile', newFile, origFile], (err) => { 260 | 261 | /** Delete tmp file **/ 262 | !addTmpFile || fs.unlinkSync(origFile); 263 | 264 | touchBarResult.label = 'Your shrunken image: ' + newFile; 265 | sendToRenderer(err, newFile, sizeOrig); 266 | }); 267 | 268 | break; 269 | } 270 | case '.png': 271 | { 272 | execFile(pngquant, ['-fo', newFile, filePath], (err) => { 273 | touchBarResult.label = 'Your shrunken image: ' + newFile; 274 | sendToRenderer(err, newFile, sizeOrig); 275 | }); 276 | break; 277 | } 278 | case '.gif': 279 | { 280 | execFile(gifsicle, ['-o', newFile, filePath, '-O=2', '-i'], 281 | err => { 282 | touchBarResult.label = 'Your shrunken image: ' + 283 | newFile; 284 | sendToRenderer(err, newFile, sizeOrig); 285 | }); 286 | break; 287 | } 288 | default: 289 | mainWindow.webContents.send('error'); 290 | dialog.showMessageBoxSync({ 291 | 'type': 'error', 292 | 'message': 'Only PNG SVG, JPG and GIF allowed' 293 | }); 294 | } 295 | }); 296 | }; 297 | 298 | /** 299 | * Generate new path to shrunken file 300 | * @param {string} pathName Filepath 301 | * @return {object} filepath object 302 | */ 303 | const generateNewPath = (pathName) => { 304 | 305 | let objPath = path.parse(pathName); 306 | 307 | if ( settings.getSync('folderswitch') === false && 308 | typeof settings.getSync('savepath') !== 'undefined') 309 | { 310 | objPath.dir = settings.getSync('savepath')[0]; 311 | } 312 | 313 | if (settings.getSync('subfolder')) 314 | { 315 | objPath.dir = objPath.dir + '/minified'; 316 | } 317 | 318 | makeDir.sync(objPath.dir); 319 | 320 | /** Suffix setting */ 321 | const suffix = settings.getSync('suffix') ? '.min' : ''; 322 | objPath.base = objPath.name + suffix + objPath.ext; 323 | 324 | return path.format(objPath); 325 | }; 326 | 327 | /** 328 | * Calculate filesize 329 | * @param {string} filePath Filepath 330 | * @param {boolean} mb If true return as MB 331 | * @return {number} filesize in MB or KB 332 | */ 333 | const getFileSize = (filePath, mb) => { 334 | const stats = fs.statSync(filePath); 335 | 336 | if (mb) 337 | { 338 | return stats.size / 1024; 339 | } 340 | 341 | return stats.size; 342 | }; 343 | 344 | /** 345 | * Send data to renderer script 346 | * @param {object} err Error message 347 | * @param {string} newFile New filename 348 | * @param {number} sizeOrig Original filesize 349 | */ 350 | const sendToRenderer = (err, newFile, sizeOrig) => { 351 | 352 | if (!err) 353 | { 354 | let sizeShrinked = getFileSize(newFile, false); 355 | 356 | mainWindow.webContents.send( 357 | 'isShrinked', 358 | newFile, 359 | sizeOrig, 360 | sizeShrinked 361 | ); 362 | } else 363 | { 364 | log.error(err); 365 | mainWindow.webContents.send('error'); 366 | dialog.showMessageBoxSync({ 367 | 'type': 'error', 368 | 'message': 'I\'m not able to write your new image. Sorry! Error: ' + err 369 | }); 370 | } 371 | }; 372 | -------------------------------------------------------------------------------- /assets/img/bg_poly.min.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/css/spectre.css: -------------------------------------------------------------------------------- 1 | /*! Spectre.css v0.5.0 | MIT License | github.com/picturepan2/spectre */ 2 | /* Manually forked from Normalize.css */ 3 | /* normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */ 4 | /** 5 | * 1. Change the default font family in all browsers (opinionated). 6 | * 2. Correct the line height in all browsers. 7 | * 3. Prevent adjustments of font size after orientation changes in 8 | * IE on Windows Phone and in iOS. 9 | */ 10 | /* Document 11 | ========================================================================== */ 12 | html { 13 | font-family: sans-serif; 14 | /* 1 */ 15 | -ms-text-size-adjust: 100%; 16 | /* 3 */ 17 | -webkit-text-size-adjust: 100%; 18 | /* 3 */ } 19 | 20 | /* Sections 21 | ========================================================================== */ 22 | /** 23 | * Remove the margin in all browsers (opinionated). 24 | */ 25 | body { 26 | margin: 0; } 27 | 28 | /** 29 | * Add the correct display in IE 9-. 30 | */ 31 | article, 32 | aside, 33 | footer, 34 | header, 35 | nav, 36 | section { 37 | display: block; } 38 | 39 | /** 40 | * Correct the font size and margin on `h1` elements within `section` and 41 | * `article` contexts in Chrome, Firefox, and Safari. 42 | */ 43 | h1 { 44 | font-size: 2em; 45 | margin: 0.67em 0; } 46 | 47 | /* Grouping content 48 | ========================================================================== */ 49 | /** 50 | * Add the correct display in IE 9-. 51 | * 1. Add the correct display in IE. 52 | */ 53 | figcaption, 54 | figure, 55 | main { 56 | /* 1 */ 57 | display: block; } 58 | 59 | /** 60 | * Add the correct margin in IE 8 (removed). 61 | */ 62 | /** 63 | * 1. Add the correct box sizing in Firefox. 64 | * 2. Show the overflow in Edge and IE. 65 | */ 66 | hr { 67 | box-sizing: content-box; 68 | /* 1 */ 69 | height: 0; 70 | /* 1 */ 71 | overflow: visible; 72 | /* 2 */ } 73 | 74 | /** 75 | * 1. Correct the inheritance and scaling of font size in all browsers. (removed) 76 | * 2. Correct the odd `em` font sizing in all browsers. 77 | */ 78 | /* Text-level semantics 79 | ========================================================================== */ 80 | /** 81 | * 1. Remove the gray background on active links in IE 10. 82 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 83 | */ 84 | a { 85 | background-color: transparent; 86 | /* 1 */ 87 | -webkit-text-decoration-skip: objects; 88 | /* 2 */ } 89 | 90 | /** 91 | * Remove the outline on focused links when they are also active or hovered 92 | * in all browsers (opinionated). 93 | */ 94 | a:active, 95 | a:hover { 96 | outline-width: 0; } 97 | 98 | /** 99 | * Modify default styling of address. 100 | */ 101 | address { 102 | font-style: normal; } 103 | 104 | /** 105 | * 1. Remove the bottom border in Firefox 39-. 106 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. (removed) 107 | */ 108 | /** 109 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 110 | */ 111 | b, 112 | strong { 113 | font-weight: inherit; } 114 | 115 | /** 116 | * Add the correct font weight in Chrome, Edge, and Safari. 117 | */ 118 | b, 119 | strong { 120 | font-weight: bolder; } 121 | 122 | /** 123 | * 1. Correct the inheritance and scaling of font size in all browsers. 124 | * 2. Correct the odd `em` font sizing in all browsers. 125 | */ 126 | code, 127 | kbd, 128 | pre, 129 | samp { 130 | font-family: "SF Mono", "Segoe UI Mono", "Roboto Mono", Menlo, Courier, monospace; 131 | /* 1 (changed) */ 132 | font-size: 1em; 133 | /* 2 */ } 134 | 135 | /** 136 | * Add the correct font style in Android 4.3-. 137 | */ 138 | dfn { 139 | font-style: italic; } 140 | 141 | /** 142 | * Add the correct background and color in IE 9-. (Removed) 143 | */ 144 | /** 145 | * Add the correct font size in all browsers. 146 | */ 147 | small { 148 | font-size: 80%; 149 | font-weight: 400; 150 | /* (added) */ } 151 | 152 | /** 153 | * Prevent `sub` and `sup` elements from affecting the line height in 154 | * all browsers. 155 | */ 156 | sub, 157 | sup { 158 | font-size: 75%; 159 | line-height: 0; 160 | position: relative; 161 | vertical-align: baseline; } 162 | 163 | sub { 164 | bottom: -0.25em; } 165 | 166 | sup { 167 | top: -0.5em; } 168 | 169 | /* Embedded content 170 | ========================================================================== */ 171 | /** 172 | * Add the correct display in IE 9-. 173 | */ 174 | audio, 175 | video { 176 | display: inline-block; } 177 | 178 | /** 179 | * Add the correct display in iOS 4-7. 180 | */ 181 | audio:not([controls]) { 182 | display: none; 183 | height: 0; } 184 | 185 | /** 186 | * Remove the border on images inside links in IE 10-. 187 | */ 188 | img { 189 | border-style: none; } 190 | 191 | /** 192 | * Hide the overflow in IE. 193 | */ 194 | svg:not(:root) { 195 | overflow: hidden; } 196 | 197 | /* Forms 198 | ========================================================================== */ 199 | /** 200 | * 1. Change the font styles in all browsers (opinionated). 201 | * 2. Remove the margin in Firefox and Safari. 202 | */ 203 | button, 204 | input, 205 | optgroup, 206 | select, 207 | textarea { 208 | font-family: inherit; 209 | /* 1 (changed) */ 210 | font-size: inherit; 211 | /* 1 (changed) */ 212 | line-height: inherit; 213 | /* 1 (changed) */ 214 | margin: 0; 215 | /* 2 */ } 216 | 217 | /** 218 | * Show the overflow in IE. 219 | * 1. Show the overflow in Edge. 220 | */ 221 | button, 222 | input { 223 | /* 1 */ 224 | overflow: visible; } 225 | 226 | /** 227 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 228 | * 1. Remove the inheritance of text transform in Firefox. 229 | */ 230 | button, 231 | select { 232 | /* 1 */ 233 | text-transform: none; } 234 | 235 | /** 236 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 237 | * controls in Android 4. 238 | * 2. Correct the inability to style clickable types in iOS and Safari. 239 | */ 240 | button, 241 | html [type="button"], 242 | [type="reset"], 243 | [type="submit"] { 244 | -webkit-appearance: button; 245 | /* 2 */ } 246 | 247 | /** 248 | * Remove the inner border and padding in Firefox. 249 | */ 250 | button::-moz-focus-inner, 251 | [type="button"]::-moz-focus-inner, 252 | [type="reset"]::-moz-focus-inner, 253 | [type="submit"]::-moz-focus-inner { 254 | border-style: none; 255 | padding: 0; } 256 | 257 | /** 258 | * Restore the focus styles unset by the previous rule (removed). 259 | */ 260 | /** 261 | * Change the border, margin, and padding in all browsers (opinionated) (changed). 262 | */ 263 | fieldset { 264 | border: 0; 265 | margin: 0; 266 | padding: 0; } 267 | 268 | /** 269 | * 1. Correct the text wrapping in Edge and IE. 270 | * 2. Correct the color inheritance from `fieldset` elements in IE. 271 | * 3. Remove the padding so developers are not caught out when they zero out 272 | * `fieldset` elements in all browsers. 273 | */ 274 | legend { 275 | box-sizing: border-box; 276 | /* 1 */ 277 | color: inherit; 278 | /* 2 */ 279 | display: table; 280 | /* 1 */ 281 | max-width: 100%; 282 | /* 1 */ 283 | padding: 0; 284 | /* 3 */ 285 | white-space: normal; 286 | /* 1 */ } 287 | 288 | /** 289 | * 1. Add the correct display in IE 9-. 290 | * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. 291 | */ 292 | progress { 293 | display: inline-block; 294 | /* 1 */ 295 | vertical-align: baseline; 296 | /* 2 */ } 297 | 298 | /** 299 | * Remove the default vertical scrollbar in IE. 300 | */ 301 | textarea { 302 | overflow: auto; } 303 | 304 | /** 305 | * 1. Add the correct box sizing in IE 10-. 306 | * 2. Remove the padding in IE 10-. 307 | */ 308 | [type="checkbox"], 309 | [type="radio"] { 310 | box-sizing: border-box; 311 | /* 1 */ 312 | padding: 0; 313 | /* 2 */ } 314 | 315 | /** 316 | * Correct the cursor style of increment and decrement buttons in Chrome. 317 | */ 318 | [type="number"]::-webkit-inner-spin-button, 319 | [type="number"]::-webkit-outer-spin-button { 320 | height: auto; } 321 | 322 | /** 323 | * 1. Correct the odd appearance in Chrome and Safari. 324 | * 2. Correct the outline style in Safari. 325 | */ 326 | [type="search"] { 327 | -webkit-appearance: textfield; 328 | /* 1 */ 329 | outline-offset: -2px; 330 | /* 2 */ } 331 | 332 | /** 333 | * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. 334 | */ 335 | [type="search"]::-webkit-search-cancel-button, 336 | [type="search"]::-webkit-search-decoration { 337 | -webkit-appearance: none; } 338 | 339 | /** 340 | * 1. Correct the inability to style clickable types in iOS and Safari. 341 | * 2. Change font properties to `inherit` in Safari. 342 | */ 343 | ::-webkit-file-upload-button { 344 | -webkit-appearance: button; 345 | /* 1 */ 346 | font: inherit; 347 | /* 2 */ } 348 | 349 | /* Interactive 350 | ========================================================================== */ 351 | /* 352 | * Add the correct display in IE 9-. 353 | * 1. Add the correct display in Edge, IE, and Firefox. 354 | */ 355 | details, 356 | menu { 357 | display: block; } 358 | 359 | /* 360 | * Add the correct display in all browsers. 361 | */ 362 | summary { 363 | display: list-item; 364 | outline: none; } 365 | 366 | /* Scripting 367 | ========================================================================== */ 368 | /** 369 | * Add the correct display in IE 9-. 370 | */ 371 | canvas { 372 | display: inline-block; } 373 | 374 | /** 375 | * Add the correct display in IE. 376 | */ 377 | template { 378 | display: none; } 379 | 380 | /* Hidden 381 | ========================================================================== */ 382 | /** 383 | * Add the correct display in IE 10-. 384 | */ 385 | [hidden] { 386 | display: none; } 387 | 388 | *, 389 | *::before, 390 | *::after { 391 | box-sizing: inherit; } 392 | 393 | html { 394 | box-sizing: border-box; 395 | font-size: 20px; 396 | line-height: 1.5; 397 | -webkit-tap-highlight-color: transparent; } 398 | 399 | body { 400 | background: #fff; 401 | color: #1e1e1e; 402 | font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; 403 | font-size: 0.8rem; 404 | overflow-x: hidden; 405 | text-rendering: optimizeLegibility; } 406 | 407 | a { 408 | color: #46c2fe; 409 | outline: none; 410 | text-decoration: none; } 411 | a:focus { 412 | box-shadow: 0 0 0 0.1rem rgba(70, 194, 254, 0.2); } 413 | a:focus, a:hover, a:active, a.active { 414 | color: #2dbafe; 415 | text-decoration: underline; } 416 | 417 | h1, 418 | h2, 419 | h3, 420 | h4, 421 | h5, 422 | h6 { 423 | color: inherit; 424 | font-weight: 500; 425 | line-height: 1.2; 426 | margin-bottom: .5em; 427 | margin-top: 0; } 428 | 429 | .h1, 430 | .h2, 431 | .h3, 432 | .h4, 433 | .h5, 434 | .h6 { 435 | font-weight: 500; } 436 | 437 | h1, 438 | .h1 { 439 | font-size: 2rem; } 440 | 441 | h2, 442 | .h2 { 443 | font-size: 1.6rem; } 444 | 445 | h3, 446 | .h3 { 447 | font-size: 1.4rem; } 448 | 449 | h4, 450 | .h4 { 451 | font-size: 1.2rem; } 452 | 453 | h5, 454 | .h5 { 455 | font-size: 1rem; } 456 | 457 | h6, 458 | .h6 { 459 | font-size: .8rem; } 460 | 461 | p { 462 | margin: 0 0 1rem; } 463 | 464 | a, 465 | ins, 466 | u { 467 | text-decoration-skip: ink edges; } 468 | 469 | abbr[title] { 470 | border-bottom: 0.05rem dotted; 471 | cursor: help; 472 | text-decoration: none; } 473 | 474 | kbd { 475 | border-radius: 0.1rem; 476 | line-height: 1.2; 477 | padding: .1rem .15rem; 478 | background: #111; 479 | color: #fff; 480 | font-size: 0.7rem; } 481 | 482 | mark { 483 | background: #ffe9b3; 484 | color: #1e1e1e; 485 | border-radius: 0.1rem; 486 | padding: .05rem; } 487 | 488 | blockquote { 489 | border-left: 0.1rem solid #aaaaaa; 490 | margin-left: 0; 491 | padding: 0.4rem 0.8rem; } 492 | blockquote p:last-child { 493 | margin-bottom: 0; } 494 | 495 | ul, 496 | ol { 497 | margin: 0.8rem 0 0.8rem 0.8rem; 498 | padding: 0; } 499 | ul ul, 500 | ul ol, 501 | ol ul, 502 | ol ol { 503 | margin: 0.8rem 0 0.8rem 0.8rem; } 504 | ul li, 505 | ol li { 506 | margin-top: 0.4rem; } 507 | 508 | ul { 509 | list-style: disc inside; } 510 | ul ul { 511 | list-style-type: circle; } 512 | 513 | ol { 514 | list-style: decimal inside; } 515 | ol ol { 516 | list-style-type: lower-alpha; } 517 | 518 | dl dt { 519 | font-weight: bold; } 520 | dl dd { 521 | margin: 0.4rem 0 0.8rem 0; } 522 | 523 | .btn { 524 | transition: all .2s ease; 525 | appearance: none; 526 | background: #fff; 527 | border: 0.05rem solid #46c2fe; 528 | border-radius: 0.1rem; 529 | color: #46c2fe; 530 | cursor: pointer; 531 | display: inline-block; 532 | font-size: 0.8rem; 533 | height: 1.8rem; 534 | line-height: 1rem; 535 | outline: none; 536 | padding: 0.35rem 0.4rem; 537 | text-align: center; 538 | text-decoration: none; 539 | user-select: none; 540 | vertical-align: middle; 541 | white-space: nowrap; } 542 | .btn:focus { 543 | box-shadow: 0 0 0 0.1rem rgba(70, 194, 254, 0.2); } 544 | .btn:focus, .btn:hover { 545 | background: #d86550; 546 | border-color: #37bdfe; 547 | text-decoration: none; } 548 | .btn:active, .btn.active { 549 | background: #37bdfe; 550 | border-color: #1db5fe; 551 | color: #fff; 552 | text-decoration: none; } 553 | .btn:active.loading::after, .btn.active.loading::after { 554 | border-bottom-color: #fff; 555 | border-left-color: #fff; } 556 | .btn[disabled], .btn:disabled, .btn.disabled { 557 | cursor: default; 558 | opacity: .5; 559 | pointer-events: none; } 560 | .btn.btn-primary { 561 | background: #46c2fe; 562 | border-color: #37bdfe; 563 | color: #fff; } 564 | .btn.btn-primary:focus, .btn.btn-primary:hover { 565 | background: #2dbafe; 566 | border-color: #1db5fe; 567 | color: #fff; } 568 | .btn.btn-primary:active, .btn.btn-primary.active { 569 | background: #22b6fe; 570 | border-color: #13b1fe; 571 | color: #fff; } 572 | .btn.btn-primary.loading::after { 573 | border-bottom-color: #fff; 574 | border-left-color: #fff; } 575 | .btn.btn-success { 576 | background: #32b643; 577 | border-color: #2faa3f; 578 | color: #fff; } 579 | .btn.btn-success:focus { 580 | box-shadow: 0 0 0 0.1rem rgba(50, 182, 67, 0.2); } 581 | .btn.btn-success:focus, .btn.btn-success:hover { 582 | background: #30ae40; 583 | border-color: #2da23c; 584 | color: #fff; } 585 | .btn.btn-success:active, .btn.btn-success.active { 586 | background: #2a9a39; 587 | border-color: #278e34; 588 | color: #fff; } 589 | .btn.btn-success.loading::after { 590 | border-bottom-color: #fff; 591 | border-left-color: #fff; } 592 | .btn.btn-error { 593 | background: #e85600; 594 | border-color: #d95000; 595 | color: #fff; } 596 | .btn.btn-error:focus { 597 | box-shadow: 0 0 0 0.1rem rgba(232, 86, 0, 0.2); } 598 | .btn.btn-error:focus, .btn.btn-error:hover { 599 | background: #de5200; 600 | border-color: #cf4d00; 601 | color: #fff; } 602 | .btn.btn-error:active, .btn.btn-error.active { 603 | background: #c44900; 604 | border-color: #b54300; 605 | color: #fff; } 606 | .btn.btn-error.loading::after { 607 | border-bottom-color: #fff; 608 | border-left-color: #fff; } 609 | .btn.btn-link { 610 | background: transparent; 611 | border-color: transparent; 612 | color: #46c2fe; } 613 | .btn.btn-link:focus, .btn.btn-link:hover, .btn.btn-link:active, .btn.btn-link.active { 614 | color: #2dbafe; } 615 | .btn.btn-sm { 616 | font-size: 0.7rem; 617 | height: 1.4rem; 618 | padding: 0.15rem 0.3rem; } 619 | .btn.btn-lg { 620 | font-size: 0.9rem; 621 | height: 2rem; 622 | padding: 0.45rem 0.6rem; } 623 | .btn.btn-block { 624 | display: block; 625 | width: 100%; } 626 | .btn.btn-action { 627 | width: 1.8rem; 628 | padding-left: 0; 629 | padding-right: 0; } 630 | .btn.btn-action.btn-sm { 631 | width: 1.4rem; } 632 | .btn.btn-action.btn-lg { 633 | width: 2rem; } 634 | .btn.btn-clear { 635 | background: transparent; 636 | border: 0; 637 | color: currentColor; 638 | height: 0.8rem; 639 | line-height: 0.8rem; 640 | margin-left: 0.2rem; 641 | margin-right: -2px; 642 | opacity: 1; 643 | padding: 0; 644 | text-decoration: none; 645 | width: 0.8rem; } 646 | .btn.btn-clear:hover { 647 | opacity: .95; } 648 | .btn.btn-clear::before { 649 | content: "\2715"; } 650 | 651 | .btn-group { 652 | display: inline-flex; 653 | flex-wrap: wrap; } 654 | .btn-group .btn { 655 | flex: 1 0 auto; } 656 | .btn-group .btn:first-child:not(:last-child) { 657 | border-bottom-right-radius: 0; 658 | border-top-right-radius: 0; } 659 | .btn-group .btn:not(:first-child):not(:last-child) { 660 | border-radius: 0; 661 | margin-left: -0.05rem; } 662 | .btn-group .btn:last-child:not(:first-child) { 663 | border-bottom-left-radius: 0; 664 | border-top-left-radius: 0; 665 | margin-left: -0.05rem; } 666 | .btn-group .btn:focus, .btn-group .btn:hover, .btn-group .btn:active, .btn-group .btn.active { 667 | z-index: 1; } 668 | .btn-group.btn-group-block { 669 | display: flex; } 670 | .btn-group.btn-group-block .btn { 671 | flex: 1 0 0; } 672 | 673 | .form-group:not(:last-child) { 674 | margin-bottom: 0.4rem; } 675 | 676 | fieldset { 677 | margin-bottom: 0.8rem; } 678 | 679 | legend { 680 | font-size: 0.9rem; 681 | font-weight: 500; 682 | margin-bottom: 0.8rem; } 683 | 684 | .form-label { 685 | display: block; 686 | line-height: 1rem; 687 | padding: 0.4rem 0; } 688 | .form-label.label-sm { 689 | font-size: 0.7rem; 690 | padding: 0.2rem 0; } 691 | .form-label.label-lg { 692 | font-size: 0.9rem; 693 | padding: 0.5rem 0; } 694 | 695 | .form-input { 696 | transition: all .2s ease; 697 | appearance: none; 698 | background: #fff; 699 | background-image: none; 700 | border: 0.05rem solid #919191; 701 | border-radius: 0.1rem; 702 | color: #1e1e1e; 703 | display: block; 704 | font-size: 0.8rem; 705 | height: 1.8rem; 706 | line-height: 1rem; 707 | max-width: 100%; 708 | outline: none; 709 | padding: 0.35rem 0.4rem; 710 | position: relative; 711 | width: 100%; } 712 | .form-input:focus { 713 | box-shadow: 0 0 0 0.1rem rgba(70, 194, 254, 0.2); 714 | border-color: #46c2fe; } 715 | .form-input::placeholder { 716 | color: #777777; } 717 | .form-input.input-sm { 718 | font-size: 0.7rem; 719 | height: 1.4rem; 720 | padding: 0.15rem 0.3rem; } 721 | .form-input.input-lg { 722 | font-size: 0.9rem; 723 | height: 2rem; 724 | padding: 0.45rem 0.6rem; } 725 | .form-input.input-inline { 726 | display: inline-block; 727 | vertical-align: middle; 728 | width: auto; } 729 | .form-input[type="file"] { 730 | height: auto; } 731 | 732 | textarea.form-input { 733 | height: auto; } 734 | 735 | .form-input-hint { 736 | color: #777777; 737 | font-size: 0.7rem; 738 | margin-top: 0.2rem; } 739 | .has-success .form-input-hint, .is-success + .form-input-hint { 740 | color: #32b643; } 741 | .has-error .form-input-hint, .is-error + .form-input-hint { 742 | color: #e85600; } 743 | 744 | .form-select { 745 | appearance: none; 746 | border: 0.05rem solid #919191; 747 | border-radius: 0.1rem; 748 | color: inherit; 749 | font-size: 0.8rem; 750 | height: 1.8rem; 751 | line-height: 1rem; 752 | outline: none; 753 | padding: 0.35rem 0.4rem; 754 | vertical-align: middle; 755 | width: 100%; } 756 | .form-select[size], .form-select[multiple] { 757 | height: auto; } 758 | .form-select[size] option, .form-select[multiple] option { 759 | padding: 0.1rem 0.2rem; } 760 | .form-select:not([multiple]):not([size]) { 761 | background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right 0.35rem center/0.4rem 0.5rem; 762 | padding-right: 1.2rem; } 763 | .form-select:focus { 764 | box-shadow: 0 0 0 0.1rem rgba(70, 194, 254, 0.2); 765 | border-color: #46c2fe; } 766 | .form-select::-ms-expand { 767 | display: none; } 768 | .form-select.select-sm { 769 | font-size: 0.7rem; 770 | height: 1.4rem; 771 | padding: 0.15rem 1.1rem 0.15rem 0.3rem; } 772 | .form-select.select-lg { 773 | font-size: 0.9rem; 774 | height: 2rem; 775 | padding: 0.45rem 1.4rem 0.45rem 0.6rem; } 776 | 777 | .has-icon-left, 778 | .has-icon-right { 779 | position: relative; } 780 | .has-icon-left .form-icon, 781 | .has-icon-right .form-icon { 782 | height: 0.8rem; 783 | margin: 0 0.35rem; 784 | position: absolute; 785 | top: 50%; 786 | transform: translateY(-50%); 787 | width: 0.8rem; 788 | z-index: 2; } 789 | 790 | .has-icon-left .form-icon { 791 | left: 0.05rem; } 792 | .has-icon-left .form-input { 793 | padding-left: 1.5rem; } 794 | 795 | .has-icon-right .form-icon { 796 | right: 0.05rem; } 797 | .has-icon-right .form-input { 798 | padding-right: 1.5rem; } 799 | 800 | .form-checkbox, 801 | .form-radio, 802 | .form-switch { 803 | display: inline-block; 804 | line-height: 1rem; 805 | margin: 0.2rem 0; 806 | min-height: 1.2rem; 807 | padding: 0.2rem 0.4rem 0.2rem 1.2rem; 808 | position: relative; } 809 | .form-checkbox input, 810 | .form-radio input, 811 | .form-switch input { 812 | clip: rect(0, 0, 0, 0); 813 | height: 1px; 814 | margin: -1px; 815 | overflow: hidden; 816 | position: absolute; 817 | width: 1px; } 818 | .form-checkbox input:focus + .form-icon, 819 | .form-radio input:focus + .form-icon, 820 | .form-switch input:focus + .form-icon { 821 | box-shadow: 0 0 0 0.1rem rgba(70, 194, 254, 0.2); 822 | border-color: #46c2fe; } 823 | .form-checkbox input:checked + .form-icon, 824 | .form-radio input:checked + .form-icon, 825 | .form-switch input:checked + .form-icon { 826 | background: #46c2fe; 827 | border-color: #46c2fe; } 828 | .form-checkbox .form-icon, 829 | .form-radio .form-icon, 830 | .form-switch .form-icon { 831 | transition: all .2s ease; 832 | border: 0.05rem solid #919191; 833 | cursor: pointer; 834 | display: inline-block; 835 | position: absolute; } 836 | .form-checkbox.input-sm, 837 | .form-radio.input-sm, 838 | .form-switch.input-sm { 839 | font-size: 0.7rem; 840 | margin: 0; } 841 | .form-checkbox.input-lg, 842 | .form-radio.input-lg, 843 | .form-switch.input-lg { 844 | font-size: 0.9rem; 845 | margin: 0.3rem 0; } 846 | 847 | .form-checkbox .form-icon, 848 | .form-radio .form-icon { 849 | background: #fff; 850 | height: 0.8rem; 851 | left: 0; 852 | top: 0.3rem; 853 | width: 0.8rem; } 854 | .form-checkbox input:active + .form-icon, 855 | .form-radio input:active + .form-icon { 856 | background: #b2b2b2; } 857 | 858 | .form-checkbox .form-icon { 859 | border-radius: 0.1rem; } 860 | .form-checkbox input:checked + .form-icon::before { 861 | background-clip: padding-box; 862 | border: 0.1rem solid #fff; 863 | border-left-width: 0; 864 | border-top-width: 0; 865 | content: ""; 866 | height: 12px; 867 | left: 50%; 868 | margin-left: -4px; 869 | margin-top: -8px; 870 | position: absolute; 871 | top: 50%; 872 | transform: rotate(45deg); 873 | width: 8px; } 874 | .form-checkbox input:indeterminate + .form-icon { 875 | background: #46c2fe; 876 | border-color: #46c2fe; } 877 | .form-checkbox input:indeterminate + .form-icon::before { 878 | background: #fff; 879 | content: ""; 880 | height: 2px; 881 | left: 50%; 882 | margin-left: -5px; 883 | margin-top: -1px; 884 | position: absolute; 885 | top: 50%; 886 | width: 10px; } 887 | 888 | .form-radio .form-icon { 889 | border-radius: 50%; } 890 | .form-radio input:checked + .form-icon::before { 891 | background: #fff; 892 | border-radius: 50%; 893 | content: ""; 894 | height: 4px; 895 | left: 50%; 896 | position: absolute; 897 | top: 50%; 898 | transform: translate(-50%, -50%); 899 | width: 4px; } 900 | 901 | .form-switch { 902 | padding-left: 2rem; } 903 | .form-switch .form-icon { 904 | background: #aaaaaa; 905 | background-clip: padding-box; 906 | border-radius: 0.45rem; 907 | height: 0.9rem; 908 | left: 0; 909 | top: 0.25rem; 910 | width: 1.6rem; } 911 | .form-switch .form-icon::before { 912 | transition: all .2s ease; 913 | background: #fff; 914 | border-radius: 50%; 915 | content: ""; 916 | display: block; 917 | height: 0.8rem; 918 | left: 0; 919 | position: absolute; 920 | top: 0; 921 | width: 0.8rem; } 922 | .form-switch input:checked + .form-icon::before { 923 | left: 14px; } 924 | .form-switch input:active + .form-icon::before { 925 | background: #b9b9b9; } 926 | 927 | .input-group { 928 | display: flex; } 929 | .input-group .input-group-addon { 930 | background: #b9b9b9; 931 | border: 0.05rem solid #919191; 932 | border-radius: 0.1rem; 933 | line-height: 1rem; 934 | padding: 0.35rem 0.4rem; 935 | white-space: nowrap; } 936 | .input-group .input-group-addon.addon-sm { 937 | font-size: 0.7rem; 938 | padding: 0.15rem 0.3rem; } 939 | .input-group .input-group-addon.addon-lg { 940 | font-size: 0.9rem; 941 | padding: 0.45rem 0.6rem; } 942 | .input-group .form-input, 943 | .input-group .form-select { 944 | flex: 1 1 auto; } 945 | .input-group .input-group-btn { 946 | z-index: 1; } 947 | .input-group .form-input:first-child:not(:last-child), 948 | .input-group .form-select:first-child:not(:last-child), 949 | .input-group .input-group-addon:first-child:not(:last-child), 950 | .input-group .input-group-btn:first-child:not(:last-child) { 951 | border-bottom-right-radius: 0; 952 | border-top-right-radius: 0; } 953 | .input-group .form-input:not(:first-child):not(:last-child), 954 | .input-group .form-select:not(:first-child):not(:last-child), 955 | .input-group .input-group-addon:not(:first-child):not(:last-child), 956 | .input-group .input-group-btn:not(:first-child):not(:last-child) { 957 | border-radius: 0; 958 | margin-left: -0.05rem; } 959 | .input-group .form-input:last-child:not(:first-child), 960 | .input-group .form-select:last-child:not(:first-child), 961 | .input-group .input-group-addon:last-child:not(:first-child), 962 | .input-group .input-group-btn:last-child:not(:first-child) { 963 | border-bottom-left-radius: 0; 964 | border-top-left-radius: 0; 965 | margin-left: -0.05rem; } 966 | .input-group .form-input:focus, 967 | .input-group .form-select:focus, 968 | .input-group .input-group-addon:focus, 969 | .input-group .input-group-btn:focus { 970 | z-index: 2; } 971 | .input-group .form-select { 972 | width: auto; } 973 | .input-group.input-inline { 974 | display: inline-flex; } 975 | 976 | .has-success .form-input, .form-input.is-success, 977 | .has-success .form-select, .form-select.is-success { 978 | border-color: #32b643; } 979 | .has-success .form-input:focus, .form-input.is-success:focus, 980 | .has-success .form-select:focus, .form-select.is-success:focus { 981 | box-shadow: 0 0 0 0.1rem rgba(50, 182, 67, 0.2); } 982 | .has-error .form-input, .form-input.is-error, .has-error .form-select, .form-select.is-error { 983 | border-color: #e85600; } 984 | .has-error .form-input:focus, .form-input.is-error:focus, .has-error .form-select:focus, .form-select.is-error:focus { 985 | box-shadow: 0 0 0 0.1rem rgba(232, 86, 0, 0.2); } 986 | 987 | .has-error .form-checkbox .form-icon, .form-checkbox.is-error .form-icon, 988 | .has-error .form-radio .form-icon, .form-radio.is-error .form-icon, 989 | .has-error .form-switch .form-icon, .form-switch.is-error .form-icon { 990 | border-color: #e85600; } 991 | .has-error .form-checkbox input:checked + .form-icon, .form-checkbox.is-error input:checked + .form-icon, 992 | .has-error .form-radio input:checked + .form-icon, .form-radio.is-error input:checked + .form-icon, 993 | .has-error .form-switch input:checked + .form-icon, .form-switch.is-error input:checked + .form-icon { 994 | background: #e85600; 995 | border-color: #e85600; } 996 | .has-error .form-checkbox input:focus + .form-icon, .form-checkbox.is-error input:focus + .form-icon, 997 | .has-error .form-radio input:focus + .form-icon, .form-radio.is-error input:focus + .form-icon, 998 | .has-error .form-switch input:focus + .form-icon, .form-switch.is-error input:focus + .form-icon { 999 | box-shadow: 0 0 0 0.1rem rgba(232, 86, 0, 0.2); 1000 | border-color: #e85600; } 1001 | 1002 | .form-input:not(:placeholder-shown):invalid { 1003 | border-color: #e85600; } 1004 | .form-input:not(:placeholder-shown):invalid:focus { 1005 | box-shadow: 0 0 0 0.1rem rgba(232, 86, 0, 0.2); } 1006 | .form-input:not(:placeholder-shown):invalid + .form-input-hint { 1007 | color: #e85600; } 1008 | 1009 | .form-input:disabled, .form-input.disabled, 1010 | .form-select:disabled, 1011 | .form-select.disabled { 1012 | background-color: #b2b2b2; 1013 | cursor: not-allowed; 1014 | opacity: .5; } 1015 | 1016 | .form-input[readonly] { 1017 | background-color: #b9b9b9; } 1018 | 1019 | input:disabled + .form-icon, input.disabled + .form-icon { 1020 | background: #b2b2b2; 1021 | cursor: not-allowed; 1022 | opacity: .5; } 1023 | 1024 | .form-switch input:disabled + .form-icon::before, .form-switch input.disabled + .form-icon::before { 1025 | background: #fff; } 1026 | 1027 | .form-horizontal { 1028 | padding: 0.4rem 0; } 1029 | .form-horizontal .form-group { 1030 | display: flex; 1031 | flex-wrap: wrap; } 1032 | 1033 | .label { 1034 | border-radius: 0.1rem; 1035 | line-height: 1.2; 1036 | padding: .1rem .15rem; 1037 | background: #b2b2b2; 1038 | color: #2b2b2b; 1039 | display: inline-block; } 1040 | .label.label-rounded { 1041 | border-radius: 5rem; 1042 | padding-left: .4rem; 1043 | padding-right: .4rem; } 1044 | .label.label-primary { 1045 | background: #46c2fe; 1046 | color: #fff; } 1047 | .label.label-secondary { 1048 | background: #d86550; 1049 | color: #46c2fe; } 1050 | .label.label-success { 1051 | background: #32b643; 1052 | color: #fff; } 1053 | .label.label-warning { 1054 | background: #ffb700; 1055 | color: #fff; } 1056 | .label.label-error { 1057 | background: #e85600; 1058 | color: #fff; } 1059 | 1060 | .container { 1061 | margin-left: auto; 1062 | margin-right: auto; 1063 | padding-left: 0.4rem; 1064 | padding-right: 0.4rem; 1065 | width: 100%; } 1066 | .container.grid-xl { 1067 | max-width: 1296px; } 1068 | .container.grid-lg { 1069 | max-width: 976px; } 1070 | .container.grid-md { 1071 | max-width: 856px; } 1072 | .container.grid-sm { 1073 | max-width: 616px; } 1074 | .container.grid-xs { 1075 | max-width: 496px; } 1076 | 1077 | .show-xs, 1078 | .show-sm, 1079 | .show-md, 1080 | .show-lg, 1081 | .show-xl { 1082 | display: none !important; } 1083 | 1084 | .columns { 1085 | display: flex; 1086 | flex-wrap: wrap; 1087 | margin-left: -0.4rem; 1088 | margin-right: -0.4rem; } 1089 | .columns.col-gapless { 1090 | margin-left: 0; 1091 | margin-right: 0; } 1092 | .columns.col-gapless > .column { 1093 | padding-left: 0; 1094 | padding-right: 0; } 1095 | .columns.col-oneline { 1096 | flex-wrap: nowrap; 1097 | overflow-x: auto; } 1098 | 1099 | .column { 1100 | flex: 1; 1101 | max-width: 100%; 1102 | padding-left: 0.4rem; 1103 | padding-right: 0.4rem; } 1104 | .column.col-12, .column.col-11, .column.col-10, .column.col-9, .column.col-8, .column.col-7, .column.col-6, .column.col-5, .column.col-4, .column.col-3, .column.col-2, .column.col-1 { 1105 | flex: none; } 1106 | 1107 | .col-12 { 1108 | width: 100%; } 1109 | 1110 | .col-11 { 1111 | width: 91.66666667%; } 1112 | 1113 | .col-10 { 1114 | width: 83.33333333%; } 1115 | 1116 | .col-9 { 1117 | width: 75%; } 1118 | 1119 | .col-8 { 1120 | width: 66.66666667%; } 1121 | 1122 | .col-7 { 1123 | width: 58.33333333%; } 1124 | 1125 | .col-6 { 1126 | width: 50%; } 1127 | 1128 | .col-5 { 1129 | width: 41.66666667%; } 1130 | 1131 | .col-4 { 1132 | width: 33.33333333%; } 1133 | 1134 | .col-3 { 1135 | width: 25%; } 1136 | 1137 | .col-2 { 1138 | width: 16.66666667%; } 1139 | 1140 | .col-1 { 1141 | width: 8.33333333%; } 1142 | 1143 | .col-auto { 1144 | flex: 0 0 auto; 1145 | max-width: none; 1146 | width: auto; } 1147 | 1148 | .col-mx-auto { 1149 | margin-left: auto; 1150 | margin-right: auto; } 1151 | 1152 | .col-ml-auto { 1153 | margin-left: auto; } 1154 | 1155 | .col-mr-auto { 1156 | margin-right: auto; } 1157 | 1158 | @media (max-width: 1280px) { 1159 | .col-xl-12, 1160 | .col-xl-11, 1161 | .col-xl-10, 1162 | .col-xl-9, 1163 | .col-xl-8, 1164 | .col-xl-7, 1165 | .col-xl-6, 1166 | .col-xl-5, 1167 | .col-xl-4, 1168 | .col-xl-3, 1169 | .col-xl-2, 1170 | .col-xl-1 { 1171 | flex: none; } 1172 | 1173 | .col-xl-12 { 1174 | width: 100%; } 1175 | 1176 | .col-xl-11 { 1177 | width: 91.66666667%; } 1178 | 1179 | .col-xl-10 { 1180 | width: 83.33333333%; } 1181 | 1182 | .col-xl-9 { 1183 | width: 75%; } 1184 | 1185 | .col-xl-8 { 1186 | width: 66.66666667%; } 1187 | 1188 | .col-xl-7 { 1189 | width: 58.33333333%; } 1190 | 1191 | .col-xl-6 { 1192 | width: 50%; } 1193 | 1194 | .col-xl-5 { 1195 | width: 41.66666667%; } 1196 | 1197 | .col-xl-4 { 1198 | width: 33.33333333%; } 1199 | 1200 | .col-xl-3 { 1201 | width: 25%; } 1202 | 1203 | .col-xl-2 { 1204 | width: 16.66666667%; } 1205 | 1206 | .col-xl-1 { 1207 | width: 8.33333333%; } 1208 | 1209 | .hide-xl { 1210 | display: none !important; } 1211 | 1212 | .show-xl { 1213 | display: block !important; } } 1214 | @media (max-width: 960px) { 1215 | .col-lg-12, 1216 | .col-lg-11, 1217 | .col-lg-10, 1218 | .col-lg-9, 1219 | .col-lg-8, 1220 | .col-lg-7, 1221 | .col-lg-6, 1222 | .col-lg-5, 1223 | .col-lg-4, 1224 | .col-lg-3, 1225 | .col-lg-2, 1226 | .col-lg-1 { 1227 | flex: none; } 1228 | 1229 | .col-lg-12 { 1230 | width: 100%; } 1231 | 1232 | .col-lg-11 { 1233 | width: 91.66666667%; } 1234 | 1235 | .col-lg-10 { 1236 | width: 83.33333333%; } 1237 | 1238 | .col-lg-9 { 1239 | width: 75%; } 1240 | 1241 | .col-lg-8 { 1242 | width: 66.66666667%; } 1243 | 1244 | .col-lg-7 { 1245 | width: 58.33333333%; } 1246 | 1247 | .col-lg-6 { 1248 | width: 50%; } 1249 | 1250 | .col-lg-5 { 1251 | width: 41.66666667%; } 1252 | 1253 | .col-lg-4 { 1254 | width: 33.33333333%; } 1255 | 1256 | .col-lg-3 { 1257 | width: 25%; } 1258 | 1259 | .col-lg-2 { 1260 | width: 16.66666667%; } 1261 | 1262 | .col-lg-1 { 1263 | width: 8.33333333%; } 1264 | 1265 | .hide-lg { 1266 | display: none !important; } 1267 | 1268 | .show-lg { 1269 | display: block !important; } } 1270 | @media (max-width: 840px) { 1271 | .col-md-12, 1272 | .col-md-11, 1273 | .col-md-10, 1274 | .col-md-9, 1275 | .col-md-8, 1276 | .col-md-7, 1277 | .col-md-6, 1278 | .col-md-5, 1279 | .col-md-4, 1280 | .col-md-3, 1281 | .col-md-2, 1282 | .col-md-1 { 1283 | flex: none; } 1284 | 1285 | .col-md-12 { 1286 | width: 100%; } 1287 | 1288 | .col-md-11 { 1289 | width: 91.66666667%; } 1290 | 1291 | .col-md-10 { 1292 | width: 83.33333333%; } 1293 | 1294 | .col-md-9 { 1295 | width: 75%; } 1296 | 1297 | .col-md-8 { 1298 | width: 66.66666667%; } 1299 | 1300 | .col-md-7 { 1301 | width: 58.33333333%; } 1302 | 1303 | .col-md-6 { 1304 | width: 50%; } 1305 | 1306 | .col-md-5 { 1307 | width: 41.66666667%; } 1308 | 1309 | .col-md-4 { 1310 | width: 33.33333333%; } 1311 | 1312 | .col-md-3 { 1313 | width: 25%; } 1314 | 1315 | .col-md-2 { 1316 | width: 16.66666667%; } 1317 | 1318 | .col-md-1 { 1319 | width: 8.33333333%; } 1320 | 1321 | .hide-md { 1322 | display: none !important; } 1323 | 1324 | .show-md { 1325 | display: block !important; } } 1326 | @media (max-width: 600px) { 1327 | .col-sm-12, 1328 | .col-sm-11, 1329 | .col-sm-10, 1330 | .col-sm-9, 1331 | .col-sm-8, 1332 | .col-sm-7, 1333 | .col-sm-6, 1334 | .col-sm-5, 1335 | .col-sm-4, 1336 | .col-sm-3, 1337 | .col-sm-2, 1338 | .col-sm-1 { 1339 | flex: none; } 1340 | 1341 | .col-sm-12 { 1342 | width: 100%; } 1343 | 1344 | .col-sm-11 { 1345 | width: 91.66666667%; } 1346 | 1347 | .col-sm-10 { 1348 | width: 83.33333333%; } 1349 | 1350 | .col-sm-9 { 1351 | width: 75%; } 1352 | 1353 | .col-sm-8 { 1354 | width: 66.66666667%; } 1355 | 1356 | .col-sm-7 { 1357 | width: 58.33333333%; } 1358 | 1359 | .col-sm-6 { 1360 | width: 50%; } 1361 | 1362 | .col-sm-5 { 1363 | width: 41.66666667%; } 1364 | 1365 | .col-sm-4 { 1366 | width: 33.33333333%; } 1367 | 1368 | .col-sm-3 { 1369 | width: 25%; } 1370 | 1371 | .col-sm-2 { 1372 | width: 16.66666667%; } 1373 | 1374 | .col-sm-1 { 1375 | width: 8.33333333%; } 1376 | 1377 | .hide-sm { 1378 | display: none !important; } 1379 | 1380 | .show-sm { 1381 | display: block !important; } } 1382 | @media (max-width: 480px) { 1383 | .col-xs-12, 1384 | .col-xs-11, 1385 | .col-xs-10, 1386 | .col-xs-9, 1387 | .col-xs-8, 1388 | .col-xs-7, 1389 | .col-xs-6, 1390 | .col-xs-5, 1391 | .col-xs-4, 1392 | .col-xs-3, 1393 | .col-xs-2, 1394 | .col-xs-1 { 1395 | flex: none; } 1396 | 1397 | .col-xs-12 { 1398 | width: 100%; } 1399 | 1400 | .col-xs-11 { 1401 | width: 91.66666667%; } 1402 | 1403 | .col-xs-10 { 1404 | width: 83.33333333%; } 1405 | 1406 | .col-xs-9 { 1407 | width: 75%; } 1408 | 1409 | .col-xs-8 { 1410 | width: 66.66666667%; } 1411 | 1412 | .col-xs-7 { 1413 | width: 58.33333333%; } 1414 | 1415 | .col-xs-6 { 1416 | width: 50%; } 1417 | 1418 | .col-xs-5 { 1419 | width: 41.66666667%; } 1420 | 1421 | .col-xs-4 { 1422 | width: 33.33333333%; } 1423 | 1424 | .col-xs-3 { 1425 | width: 25%; } 1426 | 1427 | .col-xs-2 { 1428 | width: 16.66666667%; } 1429 | 1430 | .col-xs-1 { 1431 | width: 8.33333333%; } 1432 | 1433 | .hide-xs { 1434 | display: none !important; } 1435 | 1436 | .show-xs { 1437 | display: block !important; } } 1438 | @keyframes loading { 1439 | 0% { 1440 | transform: rotate(0deg); } 1441 | 100% { 1442 | transform: rotate(360deg); } } 1443 | @keyframes slide-down { 1444 | 0% { 1445 | opacity: 0; 1446 | transform: translateY(-1.6rem); } 1447 | 100% { 1448 | opacity: 1; 1449 | transform: translateY(0); } } 1450 | .text-primary { 1451 | color: #46c2fe; } 1452 | 1453 | a.text-primary:focus, a.text-primary:hover { 1454 | color: #2dbafe; } 1455 | 1456 | .text-secondary { 1457 | color: #d55a43; } 1458 | 1459 | a.text-secondary:focus, a.text-secondary:hover { 1460 | color: #d1482f; } 1461 | 1462 | .text-gray { 1463 | color: #777777; } 1464 | 1465 | a.text-gray:focus, a.text-gray:hover { 1466 | color: #6a6a6a; } 1467 | 1468 | .text-light { 1469 | color: #fff; } 1470 | 1471 | a.text-light:focus, a.text-light:hover { 1472 | color: #f2f2f2; } 1473 | 1474 | .text-success { 1475 | color: #32b643; } 1476 | 1477 | a.text-success:focus, a.text-success:hover { 1478 | color: #2da23c; } 1479 | 1480 | .text-warning { 1481 | color: #ffb700; } 1482 | 1483 | a.text-warning:focus, a.text-warning:hover { 1484 | color: #e6a500; } 1485 | 1486 | .text-error { 1487 | color: #e85600; } 1488 | 1489 | a.text-error:focus, a.text-error:hover { 1490 | color: #cf4d00; } 1491 | 1492 | .bg-primary { 1493 | background: #46c2fe; } 1494 | 1495 | .bg-secondary { 1496 | background: #d86550; 1497 | color: #fff; } 1498 | 1499 | .bg-dark { 1500 | background: #111; 1501 | color: #fff; } 1502 | 1503 | .bg-gray { 1504 | background: #b9b9b9; } 1505 | 1506 | .bg-success { 1507 | background: #32b643; 1508 | color: #fff; } 1509 | 1510 | .bg-warning { 1511 | background: #ffb700; 1512 | color: #fff; } 1513 | 1514 | .bg-error { 1515 | background: #e85600; 1516 | color: #fff; } 1517 | 1518 | .c-hand { 1519 | cursor: pointer; } 1520 | 1521 | .c-move { 1522 | cursor: move; } 1523 | 1524 | .c-zoom-in { 1525 | cursor: zoom-in; } 1526 | 1527 | .c-zoom-out { 1528 | cursor: zoom-out; } 1529 | 1530 | .c-not-allowed { 1531 | cursor: not-allowed; } 1532 | 1533 | .c-auto { 1534 | cursor: auto; } 1535 | 1536 | .d-block { 1537 | display: block; } 1538 | 1539 | .d-inline { 1540 | display: inline; } 1541 | 1542 | .d-inline-block { 1543 | display: inline-block; } 1544 | 1545 | .d-flex { 1546 | display: flex; } 1547 | 1548 | .d-inline-flex { 1549 | display: inline-flex; } 1550 | 1551 | .d-none, 1552 | .d-hide { 1553 | display: none !important; } 1554 | 1555 | .d-visible { 1556 | visibility: visible; } 1557 | 1558 | .d-invisible { 1559 | visibility: hidden; } 1560 | 1561 | .text-hide { 1562 | background: transparent; 1563 | border: 0; 1564 | color: transparent; 1565 | font-size: 0; 1566 | line-height: 0; 1567 | text-shadow: none; } 1568 | 1569 | .text-assistive { 1570 | border: 0; 1571 | clip: rect(0, 0, 0, 0); 1572 | height: 1px; 1573 | margin: -1px; 1574 | overflow: hidden; 1575 | padding: 0; 1576 | position: absolute; 1577 | width: 1px; } 1578 | 1579 | .divider, 1580 | .divider-vert { 1581 | display: block; 1582 | position: relative; } 1583 | .divider[data-content]::after, 1584 | .divider-vert[data-content]::after { 1585 | background: #fff; 1586 | color: #777777; 1587 | content: attr(data-content); 1588 | display: inline-block; 1589 | font-size: 0.7rem; 1590 | padding: 0 0.4rem; 1591 | transform: translateY(-0.65rem); } 1592 | 1593 | .divider { 1594 | border-top: 0.05rem solid #aaaaaa; 1595 | height: 0.05rem; 1596 | margin: 0.4rem 0; } 1597 | .divider[data-content] { 1598 | margin: 0.8rem 0; } 1599 | 1600 | .divider-vert { 1601 | display: block; 1602 | padding: 0.8rem; } 1603 | .divider-vert::before { 1604 | border-left: 0.05rem solid #aaaaaa; 1605 | bottom: 0.4rem; 1606 | content: ""; 1607 | display: block; 1608 | left: 50%; 1609 | position: absolute; 1610 | top: 0.4rem; 1611 | transform: translateX(-50%); } 1612 | .divider-vert[data-content]::after { 1613 | left: 50%; 1614 | padding: 0.2rem 0; 1615 | position: absolute; 1616 | top: 50%; 1617 | transform: translate(-50%, -50%); } 1618 | 1619 | .loading { 1620 | color: transparent !important; 1621 | min-height: 0.8rem; 1622 | pointer-events: none; 1623 | position: relative; } 1624 | .loading::after { 1625 | animation: loading 500ms infinite linear; 1626 | border: 0.1rem solid #46c2fe; 1627 | border-radius: 50%; 1628 | border-right-color: transparent; 1629 | border-top-color: transparent; 1630 | content: ""; 1631 | display: block; 1632 | height: 0.8rem; 1633 | left: 50%; 1634 | margin-left: -0.4rem; 1635 | margin-top: -0.4rem; 1636 | position: absolute; 1637 | top: 50%; 1638 | width: 0.8rem; 1639 | z-index: 1; } 1640 | .loading.loading-lg { 1641 | min-height: 2rem; } 1642 | .loading.loading-lg::after { 1643 | height: 1.6rem; 1644 | margin-left: -0.8rem; 1645 | margin-top: -0.8rem; 1646 | width: 1.6rem; } 1647 | 1648 | .clearfix::after, .container::after { 1649 | clear: both; 1650 | content: ""; 1651 | display: table; } 1652 | 1653 | .float-left { 1654 | float: left !important; } 1655 | 1656 | .float-right { 1657 | float: right !important; } 1658 | 1659 | .relative { 1660 | position: relative; } 1661 | 1662 | .absolute { 1663 | position: absolute; } 1664 | 1665 | .fixed { 1666 | position: fixed; } 1667 | 1668 | .centered { 1669 | display: block; 1670 | float: none; 1671 | margin-left: auto; 1672 | margin-right: auto; } 1673 | 1674 | .flex-centered { 1675 | align-items: center; 1676 | display: flex; 1677 | justify-content: center; } 1678 | 1679 | .m-0 { 1680 | margin: 0; } 1681 | 1682 | .mb-0 { 1683 | margin-bottom: 0; } 1684 | 1685 | .ml-0 { 1686 | margin-left: 0; } 1687 | 1688 | .mr-0 { 1689 | margin-right: 0; } 1690 | 1691 | .mt-0 { 1692 | margin-top: 0; } 1693 | 1694 | .mx-0 { 1695 | margin-left: 0; 1696 | margin-right: 0; } 1697 | 1698 | .my-0 { 1699 | margin-bottom: 0; 1700 | margin-top: 0; } 1701 | 1702 | .m-1 { 1703 | margin: 0.2rem; } 1704 | 1705 | .mb-1 { 1706 | margin-bottom: 0.2rem; } 1707 | 1708 | .ml-1 { 1709 | margin-left: 0.2rem; } 1710 | 1711 | .mr-1 { 1712 | margin-right: 0.2rem; } 1713 | 1714 | .mt-1 { 1715 | margin-top: 0.2rem; } 1716 | 1717 | .mx-1 { 1718 | margin-left: 0.2rem; 1719 | margin-right: 0.2rem; } 1720 | 1721 | .my-1 { 1722 | margin-bottom: 0.2rem; 1723 | margin-top: 0.2rem; } 1724 | 1725 | .m-2 { 1726 | margin: 0.4rem; } 1727 | 1728 | .mb-2 { 1729 | margin-bottom: 0.4rem; } 1730 | 1731 | .ml-2 { 1732 | margin-left: 0.4rem; } 1733 | 1734 | .mr-2 { 1735 | margin-right: 0.4rem; } 1736 | 1737 | .mt-2 { 1738 | margin-top: 0.4rem; } 1739 | 1740 | .mx-2 { 1741 | margin-left: 0.4rem; 1742 | margin-right: 0.4rem; } 1743 | 1744 | .my-2 { 1745 | margin-bottom: 0.4rem; 1746 | margin-top: 0.4rem; } 1747 | 1748 | .p-0 { 1749 | padding: 0; } 1750 | 1751 | .pb-0 { 1752 | padding-bottom: 0; } 1753 | 1754 | .pl-0 { 1755 | padding-left: 0; } 1756 | 1757 | .pr-0 { 1758 | padding-right: 0; } 1759 | 1760 | .pt-0 { 1761 | padding-top: 0; } 1762 | 1763 | .px-0 { 1764 | padding-left: 0; 1765 | padding-right: 0; } 1766 | 1767 | .py-0 { 1768 | padding-bottom: 0; 1769 | padding-top: 0; } 1770 | 1771 | .p-1 { 1772 | padding: 0.2rem; } 1773 | 1774 | .pb-1 { 1775 | padding-bottom: 0.2rem; } 1776 | 1777 | .pl-1 { 1778 | padding-left: 0.2rem; } 1779 | 1780 | .pr-1 { 1781 | padding-right: 0.2rem; } 1782 | 1783 | .pt-1 { 1784 | padding-top: 0.2rem; } 1785 | 1786 | .px-1 { 1787 | padding-left: 0.2rem; 1788 | padding-right: 0.2rem; } 1789 | 1790 | .py-1 { 1791 | padding-bottom: 0.2rem; 1792 | padding-top: 0.2rem; } 1793 | 1794 | .p-2 { 1795 | padding: 0.4rem; } 1796 | 1797 | .pb-2 { 1798 | padding-bottom: 0.4rem; } 1799 | 1800 | .pl-2 { 1801 | padding-left: 0.4rem; } 1802 | 1803 | .pr-2 { 1804 | padding-right: 0.4rem; } 1805 | 1806 | .pt-2 { 1807 | padding-top: 0.4rem; } 1808 | 1809 | .px-2 { 1810 | padding-left: 0.4rem; 1811 | padding-right: 0.4rem; } 1812 | 1813 | .py-2 { 1814 | padding-bottom: 0.4rem; 1815 | padding-top: 0.4rem; } 1816 | 1817 | .rounded { 1818 | border-radius: 0.1rem; } 1819 | 1820 | .circle { 1821 | border-radius: 50%; } 1822 | 1823 | .text-left { 1824 | text-align: left; } 1825 | 1826 | .text-right { 1827 | text-align: right; } 1828 | 1829 | .text-center { 1830 | text-align: center; } 1831 | 1832 | .text-justify { 1833 | text-align: justify; } 1834 | 1835 | .text-lowercase { 1836 | text-transform: lowercase; } 1837 | 1838 | .text-uppercase { 1839 | text-transform: uppercase; } 1840 | 1841 | .text-capitalize { 1842 | text-transform: capitalize; } 1843 | 1844 | .text-normal { 1845 | font-weight: normal; } 1846 | 1847 | .text-bold { 1848 | font-weight: bold; } 1849 | 1850 | .text-italic { 1851 | font-style: italic; } 1852 | 1853 | .text-large { 1854 | font-size: 1.2em; } 1855 | 1856 | .text-ellipsis { 1857 | overflow: hidden; 1858 | text-overflow: ellipsis; 1859 | white-space: nowrap; } 1860 | 1861 | .text-clip { 1862 | overflow: hidden; 1863 | text-overflow: clip; 1864 | white-space: nowrap; } 1865 | 1866 | .text-break { 1867 | hyphens: auto; 1868 | word-break: break-word; 1869 | word-wrap: break-word; } 1870 | 1871 | /*# sourceMappingURL=spectre.css.map */ 1872 | --------------------------------------------------------------------------------