├── dist
├── icon
│ ├── icon128.png
│ ├── icon16.png
│ ├── icon19.png
│ ├── icon38.png
│ └── icon48.png
├── css
│ ├── options.css
│ ├── content.css
│ └── skyblue.min.css
├── manifest.json
├── _locales
│ ├── zh_CN
│ │ └── messages.json
│ ├── zh_TW
│ │ └── messages.json
│ ├── ja
│ │ └── messages.json
│ └── en
│ │ └── messages.json
└── html
│ └── options.html
├── README.md
├── .vscode
└── settings.json
├── .babelrc
├── src
├── lib
│ ├── default-options.json
│ ├── extension-util.js
│ ├── messenger.js
│ ├── downloader.js
│ ├── px-content.js
│ ├── apng.js
│ ├── webp.js
│ └── px-background.js
└── js
│ ├── content.js
│ ├── background.js
│ └── options.js
├── LICENSE
├── .gitignore
├── package.json
└── webpack.config.babel.js
/dist/icon/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rndomhack/px-downloader/HEAD/dist/icon/icon128.png
--------------------------------------------------------------------------------
/dist/icon/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rndomhack/px-downloader/HEAD/dist/icon/icon16.png
--------------------------------------------------------------------------------
/dist/icon/icon19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rndomhack/px-downloader/HEAD/dist/icon/icon19.png
--------------------------------------------------------------------------------
/dist/icon/icon38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rndomhack/px-downloader/HEAD/dist/icon/icon38.png
--------------------------------------------------------------------------------
/dist/icon/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rndomhack/px-downloader/HEAD/dist/icon/icon48.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Px Downloader
2 | Download illust, manga, ugoira(animation) and novel from Pixiv
3 |
4 | ## Build
5 | ```sh
6 | npm i
7 | npm run build
8 | ```
9 |
--------------------------------------------------------------------------------
/dist/css/options.css:
--------------------------------------------------------------------------------
1 | h2 {
2 | padding-top: 20px;
3 | }
4 |
5 | .full {
6 | width: 100%;
7 | }
8 |
9 | option {
10 | background-color: #FbFbFb;
11 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "apng",
4 | "gifjs",
5 | "illust",
6 | "jszip",
7 | "pixiv",
8 | "pximg",
9 | "rndomhack",
10 | "ugoira",
11 | "ugoku",
12 | "webp"
13 | ]
14 | }
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["syntax-dynamic-import"],
3 | "presets": [
4 | [
5 | "@babel/preset-env",
6 | {
7 | "targets": {
8 | "node": "current"
9 | }
10 | }
11 | ]
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/src/lib/default-options.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleFilename": "PxDownloader/${userName}(${userId})/${title}(${id})",
3 | "multiFilename": "PxDownloader/${userName}(${userId})/${title}(${id})/${page2}",
4 | "novelFilename": "PxDownloader/${userName}(${userId})/${title}(${id})",
5 | "conflictAction": "uniquify",
6 | "forceFilename": 0,
7 | "disableShelf": 0,
8 | "singleSize": "original",
9 | "multiSize": "original",
10 | "ugoiraSize": "original",
11 | "convertMode": "none",
12 | "convertQuality": 0.9,
13 | "ugoiraMode": "gif",
14 | "ugoiraQuality": 0.9
15 | }
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2017 rndomhack
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # private
61 | dist/js/
62 | dist/lib/
63 | web-ext-artifacts/
64 |
--------------------------------------------------------------------------------
/src/lib/extension-util.js:
--------------------------------------------------------------------------------
1 | import browser from "webextension-polyfill";
2 |
3 | class ExtensionUtil {
4 | constructor() {
5 | this._storage = null;
6 | this._browser = null;
7 | }
8 |
9 | get storage() {
10 | if (this._storage !== null) return this._storage;
11 |
12 | let storage;
13 |
14 | if (browser.storage.hasOwnProperty("sync")) {
15 | storage = browser.storage.sync;
16 | } else {
17 | storage = browser.storage.local;
18 | }
19 |
20 | this._storage = storage;
21 |
22 | return storage;
23 | }
24 |
25 | get browser() {
26 | if (this._browser !== null) return this._browser;
27 |
28 | let _browser;
29 |
30 | if (navigator.userAgent.includes("Edge")) {
31 | _browser = "edge";
32 | } else if (navigator.userAgent.includes("Chrome")) {
33 | _browser = "chrome";
34 | } else if (navigator.userAgent.includes("Firefox")) {
35 | _browser = "firefox";
36 | } else {
37 | _browser = "unknown";
38 | }
39 |
40 | this._browser = _browser;
41 |
42 | return _browser;
43 | }
44 | }
45 |
46 | export default new ExtensionUtil();
47 |
--------------------------------------------------------------------------------
/dist/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Px Downloader",
4 | "version": "3.6.0",
5 | "default_locale": "en",
6 | "description": "__MSG_extDescription__",
7 | "author": "rndomhack",
8 | "icons": {
9 | "16": "icon/icon16.png",
10 | "48": "icon/icon48.png",
11 | "128": "icon/icon128.png"
12 | },
13 | "browser_action": {
14 | "default_icon": {
15 | "19": "icon/icon19.png",
16 | "38": "icon/icon38.png"
17 | },
18 | "default_title": "Px Downloader"
19 | },
20 | "background": {
21 | "scripts": ["lib/browser-polyfill.js", "lib/jszip.min.js", "lib/gif.js", "js/background.js"]
22 | },
23 | "content_scripts": [
24 | {
25 | "matches": ["*://*.pixiv.net/*"],
26 | "css": ["css/content.css"],
27 | "js": ["lib/browser-polyfill.js", "js/content.js"],
28 | "run_at": "document_start"
29 | }
30 | ],
31 | "options_ui": {
32 | "page": "html/options.html",
33 | "chrome_style": true
34 | },
35 | "permissions": [
36 | "storage",
37 | "downloads",
38 | "downloads.shelf",
39 | "webRequest",
40 | "webRequestBlocking",
41 | "*://*.pixiv.net/",
42 | "*://*.pximg.net/",
43 | "*://*.techorus-cdn.com/"
44 | ],
45 | "browser_specific_settings": {
46 | "gecko": {
47 | "id": "{a3650a89-273e-474b-9e32-46ba5397cf46}"
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "px-downloader",
3 | "version": "3.6.0",
4 | "description": "Download illust, manga, ugoira(animation) and novel from Pixiv",
5 | "main": "index.js",
6 | "scripts": {
7 | "chromium:start": "web-ext run -t chromium --source-dir ./dist/",
8 | "firefox:start": "web-ext run -t firefox-desktop --source-dir ./dist/",
9 | "watch": "webpack --watch",
10 | "build": "webpack",
11 | "bundle": "cross-env NODE_ENV=production webpack && web-ext build -o --source-dir ./dist/"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/rndomhack/px-downloader.git"
16 | },
17 | "keywords": [
18 | "pixiv",
19 | "ugoira"
20 | ],
21 | "author": "rndomhack",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/rndomhack/px-downloader/issues"
25 | },
26 | "homepage": "https://github.com/rndomhack/px-downloader#readme",
27 | "devDependencies": {
28 | "@babel/core": "^7.12.9",
29 | "@babel/preset-env": "^7.12.7",
30 | "@babel/register": "^7.12.1",
31 | "@ffmpeg/core": "^0.8.5",
32 | "@ffmpeg/ffmpeg": "^0.9.6",
33 | "babel-loader": "^8.2.2",
34 | "babel-plugin-syntax-dynamic-import": "^6.18.0",
35 | "clean-webpack-plugin": "^3.0.0",
36 | "copy-webpack-plugin": "^6.3.2",
37 | "cross-env": "^7.0.3",
38 | "gif.js": "^0.2.0",
39 | "jszip": "^3.5.0",
40 | "terser-webpack-plugin": "^5.0.3",
41 | "wasm-imagemagick": "^1.2.8",
42 | "web-ext": "^5.4.0",
43 | "webextension-polyfill": "^0.7.0",
44 | "webpack": "^5.10.0",
45 | "webpack-cli": "^4.2.0"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/js/content.js:
--------------------------------------------------------------------------------
1 | import PxContent from "../lib/px-content";
2 |
3 | document.addEventListener("DOMContentLoaded", () => {
4 | let pxContent = null;
5 |
6 | function init() {
7 | try {
8 | if (pxContent !== null) {
9 | pxContent.removeButton();
10 | }
11 |
12 | pxContent = new PxContent(location.href);
13 |
14 | if (pxContent.check()) {
15 | pxContent.addButton();
16 | }
17 | } catch(err) {
18 | console.error(err);
19 | }
20 | };
21 |
22 | window.addEventListener("message", async (event) => {
23 | const message = event.data;
24 |
25 | if (typeof message !== "object") return;
26 | if (message.type !== "pxPushState" && message.type !== "pxPopState") return;
27 |
28 | init();
29 | });
30 |
31 | const script = document.createElement("script");
32 |
33 | script.textContent = `
34 | "use strict";
35 |
36 | const nativePushState = window.history.pushState;
37 |
38 | window.history.pushState = (...args) => {
39 | nativePushState.apply(window.history, args);
40 |
41 | window.postMessage({
42 | type: "pxPushState",
43 | data: null
44 | }, "*");
45 | };
46 |
47 | window.addEventListener("popstate", () => {
48 | window.postMessage({
49 | type: "pxPopState",
50 | data: null
51 | }, "*");
52 | });
53 | `;
54 |
55 | document.body.appendChild(script);
56 |
57 | init();
58 | });
--------------------------------------------------------------------------------
/dist/css/content.css:
--------------------------------------------------------------------------------
1 | .px-button {
2 | margin-right: 20px;
3 | padding: 0;
4 | background: none;
5 | border: none;
6 | line-height: 32px;
7 | font-weight: 700;
8 | cursor: pointer;
9 | }
10 |
11 | .px-button:not(.mobile) {
12 | color: inherit;
13 | }
14 |
15 | .px-button.mobile {
16 | order: 0;
17 | margin-right: auto;
18 | line-height: 22px;
19 | font-size: 14px;
20 | }
21 |
22 | .px-button:disabled {
23 | cursor: default;
24 | }
25 |
26 | .px-button:disabled span {
27 | opacity: 0.5;
28 | }
29 |
30 | .px-button span {
31 | vertical-align: middle;
32 | }
33 |
34 | .px-button svg {
35 | width: 16px;
36 | height: 16px;
37 | vertical-align: middle;
38 | margin-right: 4px;
39 | fill: currentColor;
40 | }
41 |
42 | .px-button svg mask path {
43 | transition: fill 0.2s ease 0s;
44 | fill: rgb(0, 0, 0);
45 | }
46 |
47 | .px-button.downloaded svg mask path {
48 | fill: rgb(255, 255, 255);
49 | }
50 |
51 | .px-button + .work-interactions-like {
52 | order: -1;
53 | margin-right: 12px !important;
54 | }
55 |
56 | .px-icon {
57 | display: block;
58 | box-sizing: content-box;
59 | padding: 0px;
60 | color: rgb(31, 31, 31);
61 | background: rgba(0, 0, 0, 0) none repeat scroll 0% 0%;
62 | border: medium none;
63 | line-height: 1;
64 | height: 32px;
65 | cursor: pointer;
66 | }
67 |
68 | .px-icon svg {
69 | box-sizing: border-box;
70 | line-height: 0;
71 | font-size: 0px;
72 | vertical-align: top;
73 | transition: color 0.2s ease 0s, fill 0.2s ease 0s;
74 | fill: currentcolor;
75 | }
76 |
77 | .px-icon.downloaded svg {
78 | fill: rgb(64, 255, 128);
79 | }
80 |
81 | .px-icon:not(.downloaded) svg path:last-child {
82 | fill: rgb(255, 255, 255);
83 | }
84 |
--------------------------------------------------------------------------------
/src/js/background.js:
--------------------------------------------------------------------------------
1 | import browser from "webextension-polyfill";
2 |
3 | import extensionUtil from "../lib/extension-util";
4 | import messenger from "../lib/messenger";
5 | import PxBackground from "../lib/px-background";
6 |
7 | import defaultOptions from "../lib/default-options.json"
8 |
9 | (async () => {
10 | // Update options
11 | await extensionUtil.storage.set(
12 | Object.assign(
13 | defaultOptions,
14 | await extensionUtil.storage.get(Object.keys(defaultOptions))
15 | )
16 | );
17 |
18 | // Init browser action
19 | browser.browserAction.onClicked.addListener(() => {
20 | browser.runtime.openOptionsPage();
21 | });
22 |
23 | // Init webRequest.onBeforeSendHeaders listener
24 | browser.webRequest.onBeforeSendHeaders.addListener(
25 | (details) => {
26 | const requestHeader = details.requestHeaders.find(_requestHeader => _requestHeader.name.toLowerCase() === "referer");
27 |
28 | if (requestHeader === undefined) {
29 | details.requestHeaders.push({
30 | name: "Referer",
31 | value: "https://www.pixiv.net/"
32 | })
33 | } else {
34 | requestHeader.value = "https://www.pixiv.net/";
35 | }
36 |
37 | return { requestHeaders: details.requestHeaders };
38 | },
39 | { urls: ["*://*.pximg.net/*"] },
40 | ["blocking", "requestHeaders"].concat(extensionUtil.browser === "firefox" ? [] : ["extraHeaders"])
41 | );
42 |
43 | // Init message listeners
44 | messenger.addListener("download", async ({page, id}, callback) => {
45 | const options = await extensionUtil.storage.get(Object.keys(defaultOptions));
46 |
47 | const pxBackground = new PxBackground(page, id, options, callback);
48 |
49 | await pxBackground.download();
50 | });
51 | })();
52 |
--------------------------------------------------------------------------------
/src/lib/messenger.js:
--------------------------------------------------------------------------------
1 | import browser from "webextension-polyfill";
2 |
3 | class Messenger {
4 | constructor() {
5 | this.listeners = new Map();
6 |
7 | // Init runtime.onMessage listener
8 | browser.runtime.onMessage.addListener(async ({ type, data, callbackType }, { tab }) => {
9 | if (this.listeners.has(type)) {
10 | let response = {
11 | error: null,
12 | data: null
13 | };
14 |
15 | const callback = async (callbackData) => {
16 | if (!callbackType) return;
17 |
18 | await browser.tabs.sendMessage(tab.id, {
19 | type: callbackType,
20 | data: callbackData
21 | });
22 | }
23 |
24 | try {
25 | response.data = await this.listeners.get(type)(data, callback);
26 | } catch(err) {
27 | response.error = err.message;
28 | }
29 |
30 | return response;
31 | }
32 | });
33 | }
34 |
35 | addListener(type, listener) {
36 | if (this.listeners.has(type)) return false;
37 |
38 | this.listeners.set(type, listener);
39 |
40 | return true;
41 | }
42 |
43 | removeListener(type) {
44 | if (!this.listeners.has(type)) return false;
45 |
46 | this.listeners.delete(type);
47 |
48 | return true;
49 | }
50 |
51 | async sendMessage(type, data, callback) {
52 | let callbackType;
53 |
54 | if (callback) {
55 | callbackType = `callback_${Math.random().toString(16).slice(2)}`;
56 |
57 | this.addListener(callbackType, callback);
58 | }
59 |
60 | const response = await browser.runtime.sendMessage({ type, data, callbackType });
61 |
62 | if (response.error) {
63 | throw new Error(response.error);
64 | }
65 |
66 | if (response.callback) {
67 | this.removeListener(callbackType);
68 | }
69 |
70 | return response.data;
71 | }
72 | }
73 |
74 | export default new Messenger();
75 |
--------------------------------------------------------------------------------
/src/js/options.js:
--------------------------------------------------------------------------------
1 | import browser from "webextension-polyfill";
2 |
3 | import extensionUtil from "../lib/extension-util";
4 |
5 | import defaultOptions from "../lib/default-options";
6 |
7 | document.addEventListener("DOMContentLoaded", async () => {
8 | for (const elem of Array.from(document.querySelectorAll("[i18n]"))) {
9 | elem.textContent = browser.i18n.getMessage(`opt${elem.getAttribute("i18n").replace(/^[a-z]/, str => str.toUpperCase())}`);
10 | }
11 |
12 | document.querySelector("#reset").addEventListener("click", async () => {
13 | await extensionUtil.storage.set(defaultOptions);
14 |
15 | const options2 = await extensionUtil.storage.get(Object.keys(defaultOptions));
16 |
17 | for (const [key, value] of Object.entries(options2)) {
18 | document.querySelector(`#${key}`).value = value;
19 | }
20 | });
21 |
22 | const options = await extensionUtil.storage.get(Object.keys(defaultOptions));
23 |
24 | for (const [key, value] of Object.entries(options)) {
25 | document.querySelector(`#${key}`).value = value;
26 |
27 | switch (typeof value) {
28 | case "string": {
29 | document.querySelector(`#${key}`).addEventListener("change", ev => {
30 | extensionUtil.storage.set({ [key]: ev.currentTarget.value });
31 | });
32 |
33 | break;
34 | }
35 |
36 | case "number": {
37 | document.querySelector(`#${key}`).addEventListener("change", ev => {
38 | extensionUtil.storage.set({ [key]: Number.parseFloat(ev.currentTarget.value) });
39 | });
40 |
41 | break;
42 | }
43 | }
44 | }
45 |
46 | if (extensionUtil.browser !== "chrome") {
47 | document.querySelector("[i18n='settingsForceFilename']").style.display = "none";
48 | document.querySelector("[i18n='settingsDisableShelf']").style.display = "none";
49 | document.querySelector("#conflictAction option[value='prompt']").style.display = "none";
50 | document.querySelector("#forceFilename").style.display = "none";
51 | document.querySelector("#disableShelf").style.display = "none";
52 | document.querySelector("#convertMode option[value='webp']").style.display = "none";
53 | document.querySelector("#ugoiraMode option[value='webp']").style.display = "none";
54 | }
55 | });
56 |
--------------------------------------------------------------------------------
/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import webpack from "webpack";
3 | import { CleanWebpackPlugin } from "clean-webpack-plugin";
4 | import CopyWebpackPlugin from "copy-webpack-plugin";
5 | import TerserPlugin from "terser-webpack-plugin";
6 |
7 | const isProd = process.env.NODE_ENV === "production";
8 |
9 | module.exports = {
10 | mode: isProd ? "production" : "development",
11 | entry: {
12 | content: "./src/js/content.js",
13 | background: "./src/js/background.js",
14 | options: "./src/js/options.js"
15 | },
16 | output: {
17 | path: path.resolve(__dirname, "dist"),
18 | filename: "js/[name].js"
19 | },
20 | module: {
21 | rules: [
22 | {
23 | test: /\.m?js$/,
24 | exclude: /node_modules/,
25 | use: {
26 | loader: "babel-loader",
27 | options: {
28 | presets: [
29 | [
30 | "@babel/preset-env", {
31 | targets: {
32 | chrome: 72,
33 | firefox: 65
34 | }
35 | }
36 | ]
37 | ]
38 | }
39 | }
40 | }
41 | ]
42 | },
43 | devtool: isProd ? false : "source-map",
44 | externals: {
45 | "webextension-polyfill": "browser",
46 | "jszip": "JSZip",
47 | "gif.js": "GIF"
48 | },
49 | plugins: [
50 | new webpack.ProgressPlugin(),
51 | new CleanWebpackPlugin({
52 | verbose: true,
53 | cleanOnceBeforeBuildPatterns: ["js/*", "lib/*"]
54 | }),
55 | new CopyWebpackPlugin({
56 | patterns: [
57 | { from: "node_modules/webextension-polyfill/dist/browser-polyfill.js", to: "lib/"},
58 | { from: "node_modules/jszip/dist/jszip.min.js", to: "lib/" },
59 | { from: "node_modules/gif.js/dist/gif.js", to: "lib/" },
60 | { from: "node_modules/gif.js/dist/gif.worker.js", to: "lib/" },
61 | // { from: "node_modules/wasm-imagemagick/dist/magick.js", to: "js/" },
62 | // { from: "node_modules/wasm-imagemagick/dist/magick.wasm", to: "js/" },
63 | // { from: "node_modules/@ffmpeg/core/dist/ffmpeg-core.js", to: "lib/" },
64 | // { from: "node_modules/@ffmpeg/core/dist/ffmpeg-core.wasm", to: "lib/" },
65 | // { from: "node_modules/@ffmpeg/core/dist/ffmpeg-core.worker.js", to: "lib/" }
66 | ]
67 | })
68 | ],
69 | optimization: {
70 | minimizer: [
71 | new TerserPlugin()
72 | ]
73 | }
74 | }
--------------------------------------------------------------------------------
/src/lib/downloader.js:
--------------------------------------------------------------------------------
1 | import browser from "webextension-polyfill";
2 |
3 | class Downloader {
4 | constructor() {
5 | this._suggestions = null;
6 | }
7 |
8 | async download(options) {
9 | const blob = options.blob;
10 | const forceFilename = options.forceFilename;
11 | const disableShelf = options.disableShelf;
12 |
13 | delete options.blob;
14 | delete options.forceFilename;
15 | delete options.disableShelf;
16 |
17 | if (blob) {
18 | options.url = URL.createObjectURL(blob);
19 | }
20 |
21 | if (forceFilename && this._suggestions === null) {
22 | this._suggestions = new Map();
23 |
24 | // Init downloads.onDeterminingFilename listener
25 | browser.downloads.onDeterminingFilename.addListener((downloadItem, suggest) => {
26 | if (!this._suggestions.has(downloadItem.id)) return;
27 |
28 | const downloadItemSuggestion = this._suggestions.get(downloadItem.id);
29 |
30 | suggest(downloadItemSuggestion);
31 |
32 | this._suggestions.delete(downloadItem.id);
33 | });
34 | }
35 |
36 | if (disableShelf) {
37 | browser.downloads.setShelfEnabled(false);
38 | }
39 |
40 | const end = () => {
41 | if (blob) {
42 | URL.revokeObjectURL(options.url);
43 | }
44 |
45 | if (disableShelf) {
46 | browser.downloads.setShelfEnabled(true);
47 | }
48 | };
49 |
50 | let downloadId;
51 |
52 | try {
53 | downloadId = await browser.downloads.download(options);
54 | } catch (err) {
55 | end();
56 |
57 | throw err;
58 | }
59 |
60 | if (forceFilename) {
61 | this._suggestions.set(downloadId, {
62 | filename: options.filename,
63 | conflictAction: options.conflictAction
64 | });
65 | }
66 |
67 | if (blob || disableShelf) {
68 | this.wait(downloadId).then(end);
69 | }
70 |
71 | return downloadId;
72 | }
73 |
74 | async wait(downloadId) {
75 | return new Promise((resolve) => {
76 | browser.downloads.onChanged.addListener(function onChanged(downloadDelta) {
77 | if (downloadDelta.id !== downloadId) return;
78 | if (!downloadDelta.hasOwnProperty("state")) return;
79 | if (downloadDelta.state.current === "in_progress") return;
80 |
81 | browser.downloads.onChanged.removeListener(onChanged);
82 |
83 | const downloadItem = Object.fromEntries(
84 | Object.entries(downloadDelta).map(([key, value]) => [key, key === "id" ? value : value.current])
85 | );
86 |
87 | resolve(downloadItem);
88 | });
89 | });
90 | }
91 | }
92 |
93 | export default new Downloader();
94 |
--------------------------------------------------------------------------------
/dist/_locales/zh_CN/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "extDescription": {
3 | "message": "从Pixiv下载插画、漫画、动图和小说"
4 | },
5 | "weekdays": {
6 | "message": "日,一,二,三,四,五,六"
7 | },
8 | "optSettings": {
9 | "message": "设定"
10 | },
11 | "optSettingsReset": {
12 | "message": "回复预设值"
13 | },
14 | "optSettingsSingleFilename": {
15 | "message": "档案名称(单一档案用)"
16 | },
17 | "optSettingsMultiFilename": {
18 | "message": "档案名称(复数档案用)"
19 | },
20 | "optSettingsConflictAction": {
21 | "message": "出现档案冲突时的处理"
22 | },
23 | "optSettingsForceFilename": {
24 | "message": "确保档名依从标题 (要重新启动)"
25 | },
26 | "optSettingsDisableShelf": {
27 | "message": "Disable download shelf"
28 | },
29 | "optSettingsConflictActionUniquify": {
30 | "message": "另存新档"
31 | },
32 | "optSettingsConflictActionOverwrite": {
33 | "message": "覆写"
34 | },
35 | "optSettingsConflictActionPrompt": {
36 | "message": "询问"
37 | },
38 | "optSettingsSingleSize": {
39 | "message": "Image size (single illust)"
40 | },
41 | "optSettingsMultiSize": {
42 | "message": "Image size (multiple illust)"
43 | },
44 | "optSettingsUgoiraSize": {
45 | "message": "Image size (ugoira)"
46 | },
47 | "optSettingsSizeSmall": {
48 | "message": "Small"
49 | },
50 | "optSettingsSizeRegular": {
51 | "message": "Regular"
52 | },
53 | "optSettingsSizeOriginal": {
54 | "message": "Original"
55 | },
56 | "optSettingsConvertMode": {
57 | "message": "插画转换模式"
58 | },
59 | "optSettingsConvertQuality": {
60 | "message": "插画转换品质"
61 | },
62 | "optSettingsUgoiraMode": {
63 | "message": "动图转换模式"
64 | },
65 | "optSettingsUgoiraQuality": {
66 | "message": "动图转换品质"
67 | },
68 | "optDescription": {
69 | "message": "说明"
70 | },
71 | "optDescriptionMacros": {
72 | "message": "巨集"
73 | },
74 | "optDescriptionMacrosDescription": {
75 | "message": "可以使用以下巨集来代表目录、档案和页面的名称。"
76 | },
77 | "optDescriptionMacrosListMacros": {
78 | "message": "巨集"
79 | },
80 | "optDescriptionMacrosListDescription": {
81 | "message": "说明"
82 | },
83 | "optDescriptionMacrosList_id": {
84 | "message": "插图ID"
85 | },
86 | "optDescriptionMacrosList_title": {
87 | "message": "插图标题"
88 | },
89 | "optDescriptionMacrosList_userId": {
90 | "message": "用户ID"
91 | },
92 | "optDescriptionMacrosList_userName": {
93 | "message": "用户名"
94 | },
95 | "optDescriptionMacrosList_seriesId": {
96 | "message": "系列ID"
97 | },
98 | "optDescriptionMacrosList_seriesTitle": {
99 | "message": "系列标题"
100 | },
101 | "optDescriptionMacrosList_YYYY": {
102 | "message": "年"
103 | },
104 | "optDescriptionMacrosList_YY": {
105 | "message": "年(双位)"
106 | },
107 | "optDescriptionMacrosList_M": {
108 | "message": "月"
109 | },
110 | "optDescriptionMacrosList_MM": {
111 | "message": "月(双位)"
112 | },
113 | "optDescriptionMacrosList_D": {
114 | "message": "日"
115 | },
116 | "optDescriptionMacrosList_DD": {
117 | "message": "日(双位)"
118 | },
119 | "optDescriptionMacrosList_weekday": {
120 | "message": "星期天"
121 | },
122 | "optDescriptionMacrosList_h": {
123 | "message": "时"
124 | },
125 | "optDescriptionMacrosList_hh": {
126 | "message": "时(双位)"
127 | },
128 | "optDescriptionMacrosList_m_2": {
129 | "message": "分"
130 | },
131 | "optDescriptionMacrosList_mm_2": {
132 | "message": "分(双位)"
133 | },
134 | "optDescriptionMacrosList_page": {
135 | "message": "页数 *只限页名"
136 | },
137 | "optDescriptionMacrosList_page2": {
138 | "message": "页数(双位) *只限页名"
139 | },
140 | "optDescriptionMacrosList_page3": {
141 | "message": "页数(百位) *只限页名"
142 | },
143 | "optDescriptionMacrosList_page4": {
144 | "message": "页数(千位) *只限页名"
145 | },
146 | "phFetch": {
147 | "message": "获取"
148 | },
149 | "phLoad": {
150 | "message": "载入"
151 | },
152 | "phConvert": {
153 | "message": "转换"
154 | },
155 | "phProcess": {
156 | "message": "处理"
157 | },
158 | "phDownload": {
159 | "message": "下载"
160 | },
161 | "phDone": {
162 | "message": "完成"
163 | },
164 | "phRetry": {
165 | "message": "重试"
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/dist/_locales/zh_TW/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "extDescription": {
3 | "message": "從Pixiv下載插畫、漫畫、動圖和小說"
4 | },
5 | "weekdays": {
6 | "message": "日,一,二,三,四,五,六"
7 | },
8 | "optSettings": {
9 | "message": "設定"
10 | },
11 | "optSettingsReset": {
12 | "message": "回復預設值"
13 | },
14 | "optSettingsSingleFilename": {
15 | "message": "檔案名稱(單一檔案用)"
16 | },
17 | "optSettingsMultiFilename": {
18 | "message": "檔案名稱(複數檔案用)"
19 | },
20 | "optSettingsConflictAction": {
21 | "message": "出現檔案衝突時的處理"
22 | },
23 | "optSettingsForceFilename": {
24 | "message": "確保檔名依從標題 (要重新啟動)"
25 | },
26 | "optSettingsDisableShelf": {
27 | "message": "Disable download shelf"
28 | },
29 | "optSettingsConflictActionUniquify": {
30 | "message": "另存新檔"
31 | },
32 | "optSettingsConflictActionOverwrite": {
33 | "message": "覆寫"
34 | },
35 | "optSettingsConflictActionPrompt": {
36 | "message": "詢問"
37 | },
38 | "optSettingsSingleSize": {
39 | "message": "Image size (single illust)"
40 | },
41 | "optSettingsMultiSize": {
42 | "message": "Image size (multiple illust)"
43 | },
44 | "optSettingsUgoiraSize": {
45 | "message": "Image size (ugoira)"
46 | },
47 | "optSettingsSizeSmall": {
48 | "message": "Small"
49 | },
50 | "optSettingsSizeRegular": {
51 | "message": "Regular"
52 | },
53 | "optSettingsSizeOriginal": {
54 | "message": "Original"
55 | },
56 | "optSettingsConvertMode": {
57 | "message": "插畫轉換模式"
58 | },
59 | "optSettingsConvertQuality": {
60 | "message": "插畫轉換品質"
61 | },
62 | "optSettingsUgoiraMode": {
63 | "message": "動圖轉換模式"
64 | },
65 | "optSettingsUgoiraQuality": {
66 | "message": "動圖轉換品質"
67 | },
68 | "optDescription": {
69 | "message": "說明"
70 | },
71 | "optDescriptionMacros": {
72 | "message": "巨集"
73 | },
74 | "optDescriptionMacrosDescription": {
75 | "message": "可以使用以下巨集來代表目錄、檔案和頁面的名稱。"
76 | },
77 | "optDescriptionMacrosListMacros": {
78 | "message": "巨集"
79 | },
80 | "optDescriptionMacrosListDescription": {
81 | "message": "說明"
82 | },
83 | "optDescriptionMacrosList_id": {
84 | "message": "插圖ID"
85 | },
86 | "optDescriptionMacrosList_title": {
87 | "message": "插圖標題"
88 | },
89 | "optDescriptionMacrosList_userId": {
90 | "message": "用戶ID"
91 | },
92 | "optDescriptionMacrosList_userName": {
93 | "message": "用戶名"
94 | },
95 | "optDescriptionMacrosList_seriesId": {
96 | "message": "系列ID"
97 | },
98 | "optDescriptionMacrosList_seriesTitle": {
99 | "message": "系列標題"
100 | },
101 | "optDescriptionMacrosList_YYYY": {
102 | "message": "年"
103 | },
104 | "optDescriptionMacrosList_YY": {
105 | "message": "年(雙位)"
106 | },
107 | "optDescriptionMacrosList_M": {
108 | "message": "月"
109 | },
110 | "optDescriptionMacrosList_MM": {
111 | "message": "月(雙位)"
112 | },
113 | "optDescriptionMacrosList_D": {
114 | "message": "日"
115 | },
116 | "optDescriptionMacrosList_DD": {
117 | "message": "日(雙位)"
118 | },
119 | "optDescriptionMacrosList_weekday": {
120 | "message": "星期天"
121 | },
122 | "optDescriptionMacrosList_h": {
123 | "message": "時"
124 | },
125 | "optDescriptionMacrosList_hh": {
126 | "message": "時(雙位)"
127 | },
128 | "optDescriptionMacrosList_m_2": {
129 | "message": "分"
130 | },
131 | "optDescriptionMacrosList_mm_2": {
132 | "message": "分(雙位)"
133 | },
134 | "optDescriptionMacrosList_page": {
135 | "message": "頁數 *只限頁名"
136 | },
137 | "optDescriptionMacrosList_page2": {
138 | "message": "頁數(雙位) *只限頁名"
139 | },
140 | "optDescriptionMacrosList_page3": {
141 | "message": "頁數(百位) *只限頁名"
142 | },
143 | "optDescriptionMacrosList_page4": {
144 | "message": "頁數(千位) *只限頁名"
145 | },
146 | "phFetch": {
147 | "message": "獲取"
148 | },
149 | "phLoad": {
150 | "message": "載入"
151 | },
152 | "phConvert": {
153 | "message": "轉換"
154 | },
155 | "phProcess": {
156 | "message": "處理"
157 | },
158 | "phDownload": {
159 | "message": "下載"
160 | },
161 | "phDone": {
162 | "message": "完成"
163 | },
164 | "phRetry": {
165 | "message": "重試"
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/dist/_locales/ja/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "extDescription": {
3 | "message": "Pixivからイラスト、漫画、うごイラ、小説をダウンロード"
4 | },
5 | "weekdays": {
6 | "message": "日,月,火,水,木,金,土"
7 | },
8 | "optSettings": {
9 | "message": "設定"
10 | },
11 | "optSettingsReset": {
12 | "message": "設定をリセット"
13 | },
14 | "optSettingsSingleFilename": {
15 | "message": "ファイル名(単一ファイル)"
16 | },
17 | "optSettingsMultiFilename": {
18 | "message": "ファイル名(複数ファイル)"
19 | },
20 | "optSettingsNovelFilename": {
21 | "message": "ファイル名(小説)"
22 | },
23 | "optSettingsConflictAction": {
24 | "message": "ファイルが既に存在した場合の処理"
25 | },
26 | "optSettingsForceFilename": {
27 | "message": "ファイル名を変更できない場合、変更を強制する(リロード必須)"
28 | },
29 | "optSettingsDisableShelf": {
30 | "message": "ダウンロード時にシェルフを表示しない"
31 | },
32 | "optSettingsConflictActionUniquify": {
33 | "message": "別名で保存する"
34 | },
35 | "optSettingsConflictActionOverwrite": {
36 | "message": "上書きする"
37 | },
38 | "optSettingsConflictActionPrompt": {
39 | "message": "プロンプトを出す"
40 | },
41 | "optSettingsSingleSize": {
42 | "message": "画像サイズ(単一イラスト)"
43 | },
44 | "optSettingsMultiSize": {
45 | "message": "画像サイズ(複数イラスト)"
46 | },
47 | "optSettingsUgoiraSize": {
48 | "message": "画像サイズ(うごイラ)"
49 | },
50 | "optSettingsSizeSmall": {
51 | "message": "スモール"
52 | },
53 | "optSettingsSizeRegular": {
54 | "message": "レギュラー"
55 | },
56 | "optSettingsSizeOriginal": {
57 | "message": "オリジナル"
58 | },
59 | "optSettingsConvertMode": {
60 | "message": "イラストの変換形式"
61 | },
62 | "optSettingsConvertQuality": {
63 | "message": "イラストの変換品質"
64 | },
65 | "optSettingsUgoiraMode": {
66 | "message": "うごイラの変換形式"
67 | },
68 | "optSettingsUgoiraQuality": {
69 | "message": "うごイラの変換品質"
70 | },
71 | "optDescription": {
72 | "message": "説明"
73 | },
74 | "optDescriptionMacros": {
75 | "message": "マクロ"
76 | },
77 | "optDescriptionMacrosDescription": {
78 | "message": "ディレクトリ名、ファイル名、ページ名には以下のマクロが使用できます。"
79 | },
80 | "optDescriptionMacrosListMacros": {
81 | "message": "マクロ"
82 | },
83 | "optDescriptionMacrosListDescription": {
84 | "message": "説明"
85 | },
86 | "optDescriptionMacrosList_id": {
87 | "message": "イラストID"
88 | },
89 | "optDescriptionMacrosList_title": {
90 | "message": "イラストタイトル"
91 | },
92 | "optDescriptionMacrosList_userId": {
93 | "message": "ユーザーID"
94 | },
95 | "optDescriptionMacrosList_userName": {
96 | "message": "ユーザー名"
97 | },
98 | "optDescriptionMacrosList_seriesId": {
99 | "message": "シリーズID"
100 | },
101 | "optDescriptionMacrosList_seriesTitle": {
102 | "message": "シリーズタイトル"
103 | },
104 | "optDescriptionMacrosList_YYYY": {
105 | "message": "年"
106 | },
107 | "optDescriptionMacrosList_YY": {
108 | "message": "年(2桁)"
109 | },
110 | "optDescriptionMacrosList_M": {
111 | "message": "月"
112 | },
113 | "optDescriptionMacrosList_MM": {
114 | "message": "月(2桁)"
115 | },
116 | "optDescriptionMacrosList_D": {
117 | "message": "日"
118 | },
119 | "optDescriptionMacrosList_DD": {
120 | "message": "日(2桁)"
121 | },
122 | "optDescriptionMacrosList_weekday": {
123 | "message": "曜日"
124 | },
125 | "optDescriptionMacrosList_h": {
126 | "message": "時"
127 | },
128 | "optDescriptionMacrosList_hh": {
129 | "message": "時(2桁)"
130 | },
131 | "optDescriptionMacrosList_m_2": {
132 | "message": "分"
133 | },
134 | "optDescriptionMacrosList_mm_2": {
135 | "message": "分(2桁)"
136 | },
137 | "optDescriptionMacrosList_page": {
138 | "message": "ページ数 *ページ名のみ"
139 | },
140 | "optDescriptionMacrosList_page2": {
141 | "message": "ページ数(2桁) *ページ名のみ"
142 | },
143 | "optDescriptionMacrosList_page3": {
144 | "message": "ページ数(3桁) *ページ名のみ"
145 | },
146 | "optDescriptionMacrosList_page4": {
147 | "message": "ページ数(4桁) *ページ名のみ"
148 | },
149 | "phFetch": {
150 | "message": "取得"
151 | },
152 | "phLoad": {
153 | "message": "読み込み"
154 | },
155 | "phConvert": {
156 | "message": "変換"
157 | },
158 | "phProcess": {
159 | "message": "処理"
160 | },
161 | "phDownload": {
162 | "message": "ダウンロード"
163 | },
164 | "phDone": {
165 | "message": "完了"
166 | },
167 | "phRetry": {
168 | "message": "リトライ"
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/dist/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "extDescription": {
3 | "message": "Download illust, manga, ugoira(animation) and novel from Pixiv"
4 | },
5 | "weekdays": {
6 | "message": "Sun,Mon,Tue,Wed,Thu,Fri,Sat"
7 | },
8 | "optSettings": {
9 | "message": "Settings"
10 | },
11 | "optSettingsReset": {
12 | "message": "Reset settings"
13 | },
14 | "optSettingsSingleFilename": {
15 | "message": "Filename (single file)"
16 | },
17 | "optSettingsMultiFilename": {
18 | "message": "Filename (multiple files)"
19 | },
20 | "optSettingsNovelFilename": {
21 | "message": "Filename (novel)"
22 | },
23 | "optSettingsConflictAction": {
24 | "message": "File conflicts action"
25 | },
26 | "optSettingsForceFilename": {
27 | "message": "Ensure filename follows title (need restart)"
28 | },
29 | "optSettingsDisableShelf": {
30 | "message": "Disable download shelf"
31 | },
32 | "optSettingsConflictActionUniquify": {
33 | "message": "Save with other name"
34 | },
35 | "optSettingsConflictActionOverwrite": {
36 | "message": "Overwrite"
37 | },
38 | "optSettingsConflictActionPrompt": {
39 | "message": "Prompt"
40 | },
41 | "optSettingsSingleSize": {
42 | "message": "Image size (single illust)"
43 | },
44 | "optSettingsMultiSize": {
45 | "message": "Image size (multiple illust)"
46 | },
47 | "optSettingsUgoiraSize": {
48 | "message": "Image size (ugoira)"
49 | },
50 | "optSettingsSizeSmall": {
51 | "message": "Small"
52 | },
53 | "optSettingsSizeRegular": {
54 | "message": "Regular"
55 | },
56 | "optSettingsSizeOriginal": {
57 | "message": "Original"
58 | },
59 | "optSettingsConvertMode": {
60 | "message": "Convert mode for illust"
61 | },
62 | "optSettingsConvertQuality": {
63 | "message": "Convert quality for illust"
64 | },
65 | "optSettingsUgoiraMode": {
66 | "message": "Convert mode for Ugoira(Animations)"
67 | },
68 | "optSettingsUgoiraQuality": {
69 | "message": "Convert quality for Ugoira(Animations)"
70 | },
71 | "optDescription": {
72 | "message": "Description"
73 | },
74 | "optDescriptionMacros": {
75 | "message": "Macros"
76 | },
77 | "optDescriptionMacrosDescription": {
78 | "message": "You can use the macros below as names for directory, file and page."
79 | },
80 | "optDescriptionMacrosListMacros": {
81 | "message": "Macros"
82 | },
83 | "optDescriptionMacrosListDescription": {
84 | "message": "Description"
85 | },
86 | "optDescriptionMacrosList_id": {
87 | "message": "Illust ID"
88 | },
89 | "optDescriptionMacrosList_title": {
90 | "message": "Illust title"
91 | },
92 | "optDescriptionMacrosList_userId": {
93 | "message": "User ID"
94 | },
95 | "optDescriptionMacrosList_userName": {
96 | "message": "User name"
97 | },
98 | "optDescriptionMacrosList_seriesId": {
99 | "message": "Series ID"
100 | },
101 | "optDescriptionMacrosList_seriesTitle": {
102 | "message": "Series title"
103 | },
104 | "optDescriptionMacrosList_YYYY": {
105 | "message": "Year"
106 | },
107 | "optDescriptionMacrosList_YY": {
108 | "message": "Year(2 digits)"
109 | },
110 | "optDescriptionMacrosList_M": {
111 | "message": "Month"
112 | },
113 | "optDescriptionMacrosList_MM": {
114 | "message": "Month(2 digits)"
115 | },
116 | "optDescriptionMacrosList_D": {
117 | "message": "Day"
118 | },
119 | "optDescriptionMacrosList_DD": {
120 | "message": "Day(2 digits)"
121 | },
122 | "optDescriptionMacrosList_weekday": {
123 | "message": "Weekday"
124 | },
125 | "optDescriptionMacrosList_h": {
126 | "message": "Hours"
127 | },
128 | "optDescriptionMacrosList_hh": {
129 | "message": "Hours(2 digits)"
130 | },
131 | "optDescriptionMacrosList_m_2": {
132 | "message": "Minutes"
133 | },
134 | "optDescriptionMacrosList_mm_2": {
135 | "message": "Minutes(2 digits)"
136 | },
137 | "optDescriptionMacrosList_page": {
138 | "message": "Number of pages *Page only"
139 | },
140 | "optDescriptionMacrosList_page2": {
141 | "message": "Number of pages(2 digits) *Page name only"
142 | },
143 | "optDescriptionMacrosList_page3": {
144 | "message": "Number of pages(3 digits) *Page name only"
145 | },
146 | "optDescriptionMacrosList_page4": {
147 | "message": "Number of pages(4 digits) *Page name only"
148 | },
149 | "phFetch": {
150 | "message": "Fetch"
151 | },
152 | "phLoad": {
153 | "message": "Load"
154 | },
155 | "phConvert": {
156 | "message": "Convert"
157 | },
158 | "phProcess": {
159 | "message": "Process"
160 | },
161 | "phDownload": {
162 | "message": "Download"
163 | },
164 | "phDone": {
165 | "message": "Done"
166 | },
167 | "phRetry": {
168 | "message": "Retry"
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/dist/html/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Px Downloader - Options
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 |
32 |
33 |
37 |
38 |
43 |
44 |
49 |
50 |
54 |
55 |
61 |
62 |
63 |
64 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | |
80 | |
81 |
82 |
83 |
84 |
85 | | ${id} |
86 | |
87 |
88 |
89 | | ${title} |
90 | |
91 |
92 |
93 | | ${userId} |
94 | |
95 |
96 |
97 | | ${userName} |
98 | |
99 |
100 |
101 | | ${seriesId} |
102 | |
103 |
104 |
105 | | ${seriesTitle} |
106 | |
107 |
108 |
109 | | ${YYYY} |
110 | |
111 |
112 |
113 | | ${YY} |
114 | |
115 |
116 |
117 | | ${M} |
118 | |
119 |
120 |
121 | | ${MM} |
122 | |
123 |
124 |
125 | | ${D} |
126 | |
127 |
128 |
129 | | ${DD} |
130 | |
131 |
132 |
133 | | ${weekday} |
134 | |
135 |
136 |
137 | | ${h} |
138 | |
139 |
140 |
141 | | ${hh} |
142 | |
143 |
144 |
145 | | ${m} |
146 | |
147 |
148 |
149 | | ${mm} |
150 | |
151 |
152 |
153 | | ${page} |
154 | |
155 |
156 |
157 | | ${page2} |
158 | |
159 |
160 |
161 | | ${page3} |
162 | |
163 |
164 |
165 | | ${page4} |
166 | |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/src/lib/px-content.js:
--------------------------------------------------------------------------------
1 | import browser from "webextension-polyfill";
2 |
3 | import messenger from "./messenger";
4 |
5 | export default class PxContent {
6 | constructor(url) {
7 | this._url = url;
8 | this._page = PxContent.getPage(url);
9 | this._id = PxContent.getId(url);
10 | this._button = null;
11 | }
12 |
13 | get url() {
14 | return this._url;
15 | }
16 |
17 | get page() {
18 | return this._page;
19 | }
20 |
21 | get id() {
22 | return this._id;
23 | }
24 |
25 | check() {
26 | return this._page !== "";
27 | }
28 |
29 | addButton() {
30 | switch (this._page) {
31 | case "illust":
32 | case "novel": {
33 | this.addButtonWork();
34 |
35 | break;
36 | }
37 |
38 | case "imageList":
39 | case "imageSeries":
40 | case "novelList":
41 | case "novelSeries": {
42 | //this.addButtonList();
43 |
44 | break;
45 | }
46 | }
47 | }
48 |
49 | addButtonWork() {
50 | if (this._button !== null) return;
51 |
52 | const isMobile = document.querySelector("div#root") === null;
53 |
54 | // Button element
55 | const button = document.createElement("button");
56 |
57 | button.classList.add("px-button");
58 |
59 | if (isMobile) {
60 | button.classList.add("mobile");
61 | button.classList.add("c-gray-90");
62 | }
63 |
64 | // Button listener
65 | const listener = async () => {
66 | button.removeEventListener("click", listener);
67 | button.disabled = true;
68 |
69 | try {
70 | await this.download(({ state, progress }) => {
71 | let message = browser.i18n.getMessage(`ph${state.charAt(0).toUpperCase() + state.slice(1)}`);
72 |
73 | if (progress) {
74 | message += `: ${Math.floor(progress * 100)}%`;
75 | }
76 |
77 | span.textContent = message;
78 | });
79 |
80 | span.textContent = browser.i18n.getMessage("phDone");
81 | button.classList.add("downloaded");
82 | } catch (err) {
83 | span.textContent = browser.i18n.getMessage("phRetry");
84 | button.classList.remove("downloaded");
85 |
86 | alert(err.message);
87 | console.error(err);
88 | }
89 |
90 | button.addEventListener("click", listener);
91 | button.disabled = false;
92 | }
93 |
94 | button.addEventListener("click", listener);
95 |
96 | // Button icon
97 | const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
98 |
99 | icon.setAttributeNS(null, "viewBox", "0 0 32 32");
100 | icon.setAttributeNS(null, "width", "32");
101 | icon.setAttributeNS(null, "height", "32");
102 | icon.insertAdjacentHTML("beforeend", `
103 |
104 |
105 |
106 |
107 |
108 | `);
109 |
110 | button.appendChild(icon);
111 |
112 | // Button text
113 | const span = document.createElement("span");
114 |
115 | if (PxContent.getDownloaded(this._page, this._id)) {
116 | span.textContent = browser.i18n.getMessage("phDone");
117 | button.classList.add("downloaded");
118 | } else {
119 | span.textContent = "Px Downloader";
120 | }
121 |
122 | button.appendChild(span);
123 |
124 | // Add button
125 | const addButton = (parent) => {
126 | if (isMobile) {
127 | parent.insertBefore(button, parent.firstChild);
128 | } else {
129 | parent.appendChild(button);
130 | }
131 | };
132 |
133 | const parent = document.querySelector("main section div:first-child section, .work-interactions");
134 |
135 | if (parent !== null) {
136 | addButton(parent);
137 | }
138 |
139 | // Button observer
140 | const buttonObserver = new MutationObserver(mutations => {
141 | if (document.contains(button)) return;
142 |
143 | for (const mutation of mutations) {
144 | for (const addedNode of mutation.addedNodes) {
145 | if (addedNode.nodeType !== Node.ELEMENT_NODE) continue;
146 |
147 | const addedNodeParent = addedNode.querySelector("main section div:first-child section, .work-interactions");
148 |
149 | if (addedNodeParent !== null) addButton(addedNodeParent);
150 | }
151 | }
152 | });
153 |
154 | buttonObserver.observe(document, {
155 | childList: true,
156 | subtree: true
157 | });
158 |
159 | this._button = {
160 | element: button,
161 | observer: buttonObserver
162 | };
163 | }
164 |
165 | removeButton() {
166 | switch (this._page) {
167 | case "illust":
168 | case "novel": {
169 | this.removeButtonWork();
170 |
171 | break;
172 | }
173 |
174 | case "imageList":
175 | case "imageSeries":
176 | case "novelList":
177 | case "novelSeries": {
178 | //this.removeButtonList();
179 |
180 | break;
181 | }
182 | }
183 | }
184 |
185 | removeButtonWork() {
186 | if (this._button === null) return;
187 |
188 | // Button observer
189 | this._button.observer.disconnect();
190 |
191 | // Button element
192 | if (this._button.element.parentNode !== null) {
193 | this._button.element.parentNode.removeChild(this._button.element);
194 | }
195 |
196 | this._button = null;
197 | }
198 |
199 | async download(callback) {
200 | await messenger.sendMessage("download", {
201 | page: this._page,
202 | id: this._id
203 | }, callback);
204 |
205 | PxContent.setDownloaded(this._page, this._id);
206 | }
207 |
208 | static getDownloaded(page, id) {
209 | const key = `${page}_${id}`;
210 |
211 | const value = localStorage.getItem("pxDownloaded");
212 |
213 | if (value === null) return false;
214 |
215 | const downloaded = JSON.parse(value);
216 |
217 | if (!downloaded.includes(key)) return false;
218 |
219 | return true;
220 | }
221 |
222 | static setDownloaded(page, id) {
223 | const key = `${page}_${id}`;
224 |
225 | let value = localStorage.getItem("pxDownloaded");
226 |
227 | let downloaded = value === null ? [] : JSON.parse(value);
228 |
229 | if (downloaded.includes(key)) {
230 | downloaded.splice(downloaded.indexOf(key), 1);
231 | }
232 |
233 | downloaded.push(key);
234 |
235 | if (downloaded.length > 10000) {
236 | downloaded = downloaded.slice(-10000);
237 | }
238 |
239 | value = JSON.stringify(downloaded);
240 |
241 | localStorage.setItem("pxDownloaded", value);
242 | }
243 |
244 |
245 | static getPage(_url) {
246 | let page;
247 |
248 | const url = new URL(_url);
249 |
250 | if (/\/artworks\/\d+/.test(url.pathname)) {
251 | page = "illust";
252 | } else if (url.pathname === "/novel/show.php" && url.searchParams.get("id") !== null) {
253 | page = "novel";
254 | } else if (url.pathname === "/member_illust.php") {
255 | page = "imageList";
256 | } else if (/\/user\/\d+\/series\/\d+/.test(url.pathname)) {
257 | page = "imageSeries";
258 | } else if (url.pathname === "/novel/member.php") {
259 | page = "novelList";
260 | } else if (/\/novel\/series\/\d+/.test(url.pathname)) {
261 | page = "novelSeries";
262 | } else {
263 | page = "";
264 | }
265 |
266 | return page;
267 | }
268 |
269 | static getId(_url) {
270 | let id;
271 |
272 | const url = new URL(_url);
273 | const page = PxContent.getPage(_url);
274 |
275 | switch (page) {
276 | case "illust": {
277 | id = url.pathname.match(/\/artworks\/(\d+)/)[1];
278 |
279 | break;
280 | }
281 |
282 | case "novel": {
283 | id = url.searchParams.get("id");
284 |
285 | break;
286 | }
287 |
288 | case "novelSeries": {
289 | id = url.pathname.match(/\/novel\/series\/(\d+)/)[1]
290 |
291 | break;
292 | }
293 |
294 | default: {
295 | id = "";
296 |
297 | break;
298 | }
299 | }
300 |
301 | return id;
302 | }
303 | }
304 |
--------------------------------------------------------------------------------
/src/lib/apng.js:
--------------------------------------------------------------------------------
1 | const crc32Table = new Int32Array(256);
2 |
3 | for (let i = 0; i < crc32Table.length; i++) {
4 | let value = i;
5 |
6 | for (let j = 0; j < 8; j++) {
7 | value = (value & 1) ? (0xEDB88320 ^ (value >>> 1)) : value >>> 1;
8 | }
9 |
10 | crc32Table[i] = value;
11 | }
12 |
13 | class Crc32 {
14 | static calc(buffer) {
15 | let crc = -1;
16 |
17 | for (let i = 0, l = buffer.length; i < l; i++) {
18 | crc = crc32Table[(crc ^ buffer[i]) & 0xFF] ^ (crc >>> 8);
19 | }
20 |
21 | return crc ^ -1;
22 | }
23 | }
24 |
25 | class Reader {
26 | constructor(buffer, position, isLittleEndian) {
27 | this.buffer = buffer;
28 | this.position = position || 0;
29 | this.isLittleEndian = isLittleEndian || false;
30 | }
31 |
32 | readBits(length) {
33 | if (this.position + length > this.buffer.length << 3) {
34 | this.position += length;
35 | return 0;
36 | }
37 |
38 | let value = 0;
39 |
40 | if (this.isLittleEndian) {
41 | for (let i = 0; i < length; i++) {
42 | const index = this.position >> 3;
43 | const shift = this.position & 0x07;
44 |
45 | value |= (this.buffer[index] >> shift & 0x01) << i;
46 |
47 | this.position++;
48 | }
49 | } else {
50 | for (let i = length - 1; i >= 0; i--) {
51 | const index = this.position >> 3;
52 | const shift = this.position & 0x07 ^ 0x07;
53 |
54 | value <<= 1;
55 | value |= this.buffer[index] >> shift & 0x01;
56 |
57 | this.position++;
58 | }
59 | }
60 |
61 | return value;
62 | }
63 |
64 | readBytes(length) {
65 | if (this.position + (length << 3) > this.buffer.length << 3) {
66 | this.position += length << 3;
67 | return new Uint8Array(0);
68 | }
69 |
70 | const start = this.position >> 3;
71 |
72 | this.position += length << 3;
73 |
74 | return this.buffer.subarray(start, start + length);
75 | }
76 |
77 | next(length) {
78 | this.position += length;
79 | }
80 |
81 | previous(length) {
82 | this.position -= length;
83 | }
84 |
85 | readString(length) {
86 | if (this.position + (length << 3) > this.buffer.length << 3) {
87 | this.position += length << 3;
88 | return "";
89 | }
90 |
91 | const value = String.fromCharCode(...this.buffer.subarray(this.position >> 3, (this.position >> 3) + length));
92 |
93 | this.position += length << 3;
94 |
95 | return value;
96 | }
97 | }
98 |
99 | class Writer {
100 | constructor(buffer, position, isLittleEndian) {
101 | this.buffer = buffer;
102 | this.position = position || 0;
103 | this.isLittleEndian = isLittleEndian || false;
104 | }
105 |
106 | writeBits(length, value) {
107 | if (this.position + length > this.buffer.length << 3) {
108 | this.position += length;
109 | return;
110 | }
111 |
112 | if (this.isLittleEndian) {
113 | for (let i = 0; i < length; i++) {
114 | const index = this.position >> 3;
115 | const shift = this.position & 0x07;
116 |
117 | this.buffer[index] = (this.buffer[index] & ~(1 << shift)) | ((value >> i & 0x01) << shift);
118 |
119 | this.position++;
120 | }
121 | } else {
122 | for (let i = length - 1; i >= 0; i--) {
123 | const index = this.position >> 3;
124 | const shift = this.position & 0x07 ^ 0x07;
125 |
126 | this.buffer[index] = (this.buffer[index] & ~(1 << shift)) | ((value >> i & 0x01) << shift);
127 |
128 | this.position++;
129 | }
130 | }
131 | }
132 |
133 | writeBytes(length, value) {
134 | if (this.position + (length << 3) > this.buffer.length << 3) {
135 | this.position += length << 3;
136 | return;
137 | }
138 |
139 | const start = this.position >> 3;
140 |
141 | this.position += length << 3;
142 |
143 | this.buffer.set(value.subarray(0, length), start);
144 | }
145 |
146 | next(length) {
147 | this.position += length;
148 | }
149 |
150 | previous(length) {
151 | this.position -= length;
152 | }
153 |
154 | writeString(length, value) {
155 | if (this.position + (length << 3) > this.buffer.length << 3) {
156 | this.position += length << 3;
157 | return;
158 | }
159 |
160 | while (length > 0) {
161 | const index = this.position >> 3;
162 |
163 | this.buffer[index] = value.charCodeAt(value.length - length);
164 |
165 | this.position += 8;
166 | length--;
167 | }
168 | }
169 | }
170 |
171 | export default class Apng {
172 | constructor() {
173 | this.canvas = document.createElement("canvas");
174 | this.frames = [];
175 | }
176 |
177 | getSignature() {
178 | return new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
179 | }
180 |
181 | readChunks(buffer) {
182 | const chunks = [];
183 | const reader = new Reader(buffer, 0, false);
184 |
185 | while (reader.position < buffer.length << 3) {
186 | const chunk = {};
187 |
188 | const length = reader.readBits(32);
189 |
190 | const crc32Start = reader.position >> 3;
191 |
192 | chunk.type = reader.readString(4);
193 | chunk.data = reader.readBytes(length);
194 |
195 | const crc32End = reader.position >> 3;
196 |
197 | if (reader.readBits(32) !== Crc32.calc(buffer.subarray(crc32Start, crc32End))) {
198 | throw new Error("Incorrect CRC32");
199 | }
200 |
201 | chunks.push(chunk);
202 |
203 | if (chunk.type === "IEND") break;
204 | }
205 |
206 | return chunks;
207 | }
208 |
209 | writeChunks(chunks) {
210 | const buffer = new Uint8Array(chunks.reduce((prev, chunk) => prev + 4 + 4 + chunk.data.length + 4, 0));
211 | const writer = new Writer(buffer, 0, false);
212 |
213 | for (const chunk of chunks) {
214 | writer.writeBits(32, chunk.data.length);
215 |
216 | const crc32Start = writer.position >> 3;
217 |
218 | writer.writeString(4, chunk.type);
219 | writer.writeBytes(chunk.data.length, chunk.data);
220 |
221 | const crc32End = writer.position >> 3;
222 |
223 | writer.writeBits(32, Crc32.calc(buffer.subarray(crc32Start, crc32End)));
224 | }
225 |
226 | return buffer;
227 | }
228 |
229 | createActlChunk(options) {
230 | const buffer = new Uint8Array(8);
231 | const writer = new Writer(buffer, 0, false);
232 |
233 | writer.writeBits(32, options.numFrames);
234 | writer.writeBits(32, options.numPlays);
235 |
236 | return {
237 | type: "acTL",
238 | data: buffer
239 | };
240 | }
241 |
242 | createFctlChunk(options) {
243 | const buffer = new Uint8Array(26);
244 | const writer = new Writer(buffer, 0, false);
245 |
246 | writer.writeBits(32, options.sequenceNumber);
247 | writer.writeBits(32, options.width);
248 | writer.writeBits(32, options.height);
249 | writer.writeBits(32, options.xOffset || 0);
250 | writer.writeBits(32, options.yoffset || 0);
251 | writer.writeBits(16, options.delayNum);
252 | writer.writeBits(16, options.delayDen);
253 | writer.writeBits(8, options.disposeOp || 0);
254 | writer.writeBits(8, options.blendOp || 0);
255 |
256 | return {
257 | type: "fcTL",
258 | data: buffer
259 | };
260 | }
261 |
262 | createFdatChunk(options) {
263 | const buffer = new Uint8Array(4 + options.frameData.length);
264 | const writer = new Writer(buffer, 0, false);
265 |
266 | writer.writeBits(32, options.sequenceNumber);
267 | writer.writeBytes(options.frameData.length, options.frameData);
268 |
269 | return {
270 | type: "fdAT",
271 | data: buffer
272 | };
273 | }
274 |
275 | add(image, {duration = 1000} = {}) {
276 | let blob;
277 |
278 | if (image instanceof Blob) {
279 | blob = image;
280 | } else if (image instanceof HTMLImageElement || image instanceof HTMLCanvasElement) {
281 | let canvas;
282 |
283 | if (image instanceof HTMLImageElement) {
284 | canvas = this.canvas;
285 |
286 | const ctx = canvas.getContext("2d");
287 |
288 | canvas.width = image.width;
289 | canvas.height = image.height;
290 |
291 | ctx.clearRect(0, 0, image.width, image.height);
292 | ctx.drawImage(image, 0, 0);
293 | } else {
294 | canvas = image;
295 | }
296 |
297 | const binary = atob(canvas.toDataURL("image/png").split(",")[1]);
298 | const buffer = new Uint8Array(binary.split("").map(value => {
299 | return value.charCodeAt(0);
300 | }));
301 |
302 | blob = new Blob([buffer], { type: "image/png" });
303 | } else {
304 | throw new Error("Unsupported image");
305 | }
306 |
307 | this.frames.push({
308 | blob: blob,
309 | buffer: null,
310 | duration: duration
311 | });
312 | }
313 |
314 | async render({loop = 0} = {}) {
315 | const rebuiltChunks = [];
316 |
317 | let sequenceNumber = 0;
318 |
319 | for (const frame of this.frames) {
320 | const isFirst = this.frames.indexOf(frame) === 0;
321 |
322 | const blobUrl = URL.createObjectURL(frame.blob);
323 |
324 | const response = await fetch(blobUrl);
325 | const arrayBuffer = await response.arrayBuffer();
326 |
327 | URL.revokeObjectURL(blobUrl);
328 |
329 | const buffer = new Uint8Array(arrayBuffer);
330 |
331 | const chunks = this.readChunks(buffer.subarray(8));
332 |
333 | for (const chunk of chunks) {
334 | switch (chunk.type) {
335 | case "IHDR": {
336 | const reader = new Reader(chunk.data, 0, false);
337 |
338 | const width = reader.readBits(32);
339 | const height = reader.readBits(32);
340 |
341 | if (sequenceNumber === 0) {
342 | rebuiltChunks.push({
343 | type: "IHDR",
344 | data: chunk.data
345 | });
346 |
347 | rebuiltChunks.push(this.createActlChunk({
348 | numFrames: this.frames.length,
349 | numPlays: loop
350 | }));
351 | }
352 |
353 | rebuiltChunks.push(this.createFctlChunk({
354 | sequenceNumber: sequenceNumber++,
355 | width: width,
356 | height: height,
357 | delayNum: frame.duration,
358 | delayDen: 1000
359 | }));
360 |
361 | break;
362 | }
363 |
364 | case "IDAT": {
365 | if (isFirst) {
366 | rebuiltChunks.push({
367 | type: "IDAT",
368 | data: chunk.data
369 | });
370 | } else {
371 | rebuiltChunks.push(this.createFdatChunk({
372 | sequenceNumber: sequenceNumber++,
373 | frameData: chunk.data
374 | }));
375 | }
376 |
377 | break;
378 | }
379 | }
380 | }
381 | }
382 |
383 | rebuiltChunks.push({
384 | type: "IEND",
385 | data: new Uint8Array(0)
386 | });
387 |
388 | return new Blob([this.getSignature(), this.writeChunks(rebuiltChunks)], { "type": "image/png" });
389 | }
390 | }
391 |
--------------------------------------------------------------------------------
/src/lib/webp.js:
--------------------------------------------------------------------------------
1 | class Reader {
2 | constructor(buffer, position, isLittleEndian) {
3 | this.buffer = buffer;
4 | this.position = position || 0;
5 | this.isLittleEndian = isLittleEndian || false;
6 | }
7 |
8 | readBits(length) {
9 | if (this.position + length > this.buffer.length << 3) {
10 | this.position += length;
11 | return 0;
12 | }
13 |
14 | let value = 0;
15 |
16 | if (this.isLittleEndian) {
17 | for (let i = 0; i < length; i++) {
18 | const index = this.position >> 3;
19 | const shift = this.position & 0x07;
20 |
21 | value |= (this.buffer[index] >> shift & 0x01) << i;
22 |
23 | this.position++;
24 | }
25 | } else {
26 | for (let i = length - 1; i >= 0; i--) {
27 | const index = this.position >> 3;
28 | const shift = this.position & 0x07 ^ 0x07;
29 |
30 | value <<= 1;
31 | value |= this.buffer[index] >> shift & 0x01;
32 |
33 | this.position++;
34 | }
35 | }
36 |
37 | return value;
38 | }
39 |
40 | readBytes(length) {
41 | if (this.position + (length << 3) > this.buffer.length << 3) {
42 | this.position += length << 3;
43 | return new Uint8Array(0);
44 | }
45 |
46 | const start = this.position >> 3;
47 |
48 | this.position += length << 3;
49 |
50 | return this.buffer.subarray(start, start + length);
51 | }
52 |
53 | next(length) {
54 | this.position += length;
55 | }
56 |
57 | previous(length) {
58 | this.position -= length;
59 | }
60 |
61 | readString(length) {
62 | if (this.position + (length << 3) > this.buffer.length << 3) {
63 | this.position += length << 3;
64 | return "";
65 | }
66 |
67 | const value = String.fromCharCode(...this.buffer.subarray(this.position >> 3, (this.position >> 3) + length));
68 |
69 | this.position += length << 3;
70 |
71 | return value;
72 | }
73 | }
74 |
75 | class Writer {
76 | constructor(buffer, position, isLittleEndian) {
77 | this.buffer = buffer;
78 | this.position = position || 0;
79 | this.isLittleEndian = isLittleEndian || false;
80 | }
81 |
82 | writeBits(length, value) {
83 | if (this.position + length > this.buffer.length << 3) {
84 | this.position += length;
85 | return;
86 | }
87 |
88 | if (this.isLittleEndian) {
89 | for (let i = 0; i < length; i++) {
90 | const index = this.position >> 3;
91 | const shift = this.position & 0x07;
92 |
93 | this.buffer[index] = (this.buffer[index] & ~(1 << shift)) | ((value >> i & 0x01) << shift);
94 |
95 | this.position++;
96 | }
97 | } else {
98 | for (let i = length - 1; i >= 0; i--) {
99 | const index = this.position >> 3;
100 | const shift = this.position & 0x07 ^ 0x07;
101 |
102 | this.buffer[index] = (this.buffer[index] & ~(1 << shift)) | ((value >> i & 0x01) << shift);
103 |
104 | this.position++;
105 | }
106 | }
107 | }
108 |
109 | writeBytes(length, value) {
110 | if (this.position + (length << 3) > this.buffer.length << 3) {
111 | this.position += length << 3;
112 | return;
113 | }
114 |
115 | const start = this.position >> 3;
116 |
117 | this.position += length << 3;
118 |
119 | this.buffer.set(value.subarray(0, length), start);
120 | }
121 |
122 | next(length) {
123 | this.position += length;
124 | }
125 |
126 | previous(length) {
127 | this.position -= length;
128 | }
129 |
130 | writeString(length, value) {
131 | if (this.position + (length << 3) > this.buffer.length << 3) {
132 | this.position += length << 3;
133 | return;
134 | }
135 |
136 | while (length > 0) {
137 | const index = this.position >> 3;
138 |
139 | this.buffer[index] = value.charCodeAt(value.length - length);
140 |
141 | this.position += 8;
142 | length--;
143 | }
144 | }
145 | }
146 |
147 | export default class Webp {
148 | constructor() {
149 | this.canvas = document.createElement("canvas");
150 | this.frames = [];
151 | }
152 |
153 | readChunks(buffer) {
154 | const chunks = [];
155 | const reader = new Reader(buffer, 0, true);
156 |
157 | while (reader.position < buffer.length << 3) {
158 | const chunk = {};
159 |
160 | chunk.id = reader.readString(4);
161 | const length = reader.readBits(32);
162 | chunk.data = reader.readBytes(length);
163 |
164 | if (length % 2 === 1) {
165 | reader.next(8);
166 | }
167 |
168 | chunks.push(chunk);
169 | }
170 |
171 | return chunks;
172 | }
173 |
174 | writeChunks(chunks) {
175 | const buffer = new Uint8Array(chunks.reduce((prev, chunk) => prev + 4 + 4 + chunk.data.length + chunk.data.length % 2, 0));
176 | const writer = new Writer(buffer, 0, true);
177 |
178 | for (const chunk of chunks) {
179 | writer.writeString(4, chunk.id);
180 | writer.writeBits(32, chunk.data.length);
181 | writer.writeBytes(chunk.data.length, chunk.data);
182 |
183 | if (chunk.data.length % 2 === 1) {
184 | writer.next(8);
185 | }
186 | }
187 |
188 | return buffer;
189 | }
190 |
191 | createRiffChunk(options) {
192 | const buffer = new Uint8Array(4 + options.buffer.length);
193 | const writer = new Writer(buffer, 0, true);
194 |
195 | writer.writeString(4, options.type);
196 | writer.writeBytes(options.buffer.length, options.buffer);
197 |
198 | return {
199 | id: "RIFF",
200 | data: buffer
201 | };
202 | }
203 |
204 | createVp8xChunk(options) {
205 | const buffer = new Uint8Array(10);
206 | const writer = new Writer(buffer, 0, true);
207 |
208 | writer.next(1);
209 | writer.writeBits(1, options.animation ? 1 : 0);
210 | writer.writeBits(1, options.xmpMetadata ? 1 : 0);
211 | writer.writeBits(1, options.exifMetadata ? 1 : 0);
212 | writer.writeBits(1, options.alpha ? 1 : 0);
213 | writer.writeBits(1, options.iccProfile ? 1 : 0);
214 | writer.next(2);
215 | writer.next(24);
216 | writer.writeBits(24, options.canvasWidthMinusOne);
217 | writer.writeBits(24, options.canvasHeightMinusOne);
218 |
219 | return {
220 | id: "VP8X",
221 | data: buffer
222 | };
223 | }
224 |
225 | createAnimChunk(options) {
226 | const buffer = new Uint8Array(6);
227 | const writer = new Writer(buffer, 0, true);
228 |
229 | writer.writeBits(32, options.backgroundColor);
230 | writer.writeBits(16, options.loopCount);
231 |
232 | return {
233 | id: "ANIM",
234 | data: buffer
235 | };
236 | }
237 |
238 | createAnmfChunk(options) {
239 | const buffer = new Uint8Array(16 + options.frameData.length);
240 | const writer = new Writer(buffer, 0, true);
241 |
242 | writer.writeBits(24, options.frameX || 0);
243 | writer.writeBits(24, options.frameY || 0);
244 | writer.writeBits(24, options.frameWidthMinusOne);
245 | writer.writeBits(24, options.frameHeightMinusOne);
246 | writer.writeBits(24, options.frameDuration);
247 | writer.writeBits(1, options.disposalMethod ? 1 : 0);
248 | writer.writeBits(1, options.blendingMethod ? 1 : 0);
249 | writer.next(6);
250 | writer.writeBytes(options.frameData.length, options.frameData);
251 |
252 | return {
253 | id: "ANMF",
254 | data: buffer
255 | };
256 | }
257 |
258 | add(image, {duration = 1000} = {}) {
259 | let blob;
260 |
261 | if (image instanceof Blob) {
262 | blob = image;
263 | } else if (image instanceof HTMLImageElement || image instanceof HTMLCanvasElement) {
264 | let canvas;
265 |
266 | if (image instanceof HTMLImageElement) {
267 | canvas = this.canvas;
268 |
269 | const ctx = canvas.getContext("2d");
270 |
271 | canvas.width = image.width;
272 | canvas.height = image.height;
273 |
274 | ctx.clearRect(0, 0, image.width, image.height);
275 | ctx.drawImage(image, 0, 0);
276 | } else {
277 | canvas = image;
278 | }
279 |
280 | const binary = atob(canvas.toDataURL("image/webp").split(",")[1]);
281 | const buffer = new Uint8Array(binary.split("").map(value => {
282 | return value.charCodeAt(0);
283 | }));
284 |
285 | blob = new Blob([buffer], { type: "image/webp" });
286 | } else {
287 | throw new Error("Unsupported image");
288 | }
289 |
290 | this.frames.push({
291 | blob: blob,
292 | buffer: null,
293 | duration: duration
294 | });
295 | }
296 |
297 | async render({loop = 0} = {}) {
298 | const rebuiltChunks = [];
299 |
300 | for (const frame of this.frames) {
301 | const isFirst = this.frames.indexOf(frame) === 0;
302 |
303 | const blobUrl = URL.createObjectURL(frame.blob);
304 |
305 | const response = await fetch(blobUrl);
306 | const arrayBuffer = await response.arrayBuffer();
307 |
308 | URL.revokeObjectURL(blobUrl);
309 |
310 | const buffer = new Uint8Array(arrayBuffer);
311 |
312 | const riffChunks = this.readChunks(buffer);
313 |
314 | if (riffChunks.length !== 1 || riffChunks[0].id !== "RIFF") {
315 | throw new Error("Can't find RIFF chunk");
316 | }
317 |
318 | const chunks = this.readChunks(riffChunks[0].data.subarray(4));
319 |
320 | const frameDataChunks = [];
321 |
322 | for (const chunk of chunks) {
323 | switch (chunk.id) {
324 | case "ALPH": {
325 | frameDataChunks.push(chunk);
326 |
327 | break;
328 | }
329 |
330 | case "VP8 ":
331 | case "VP8L": {
332 | frameDataChunks.push(chunk);
333 |
334 | const reader = new Reader(chunk.data, 0, true);
335 |
336 | let widthMinusOne, heightMinusOne;
337 |
338 | switch (chunk.id) {
339 | case "VP8 ": {
340 | reader.next(48);
341 |
342 | widthMinusOne = (reader.readBits(16) & 0x3FFF) - 1;
343 | heightMinusOne = (reader.readBits(16) & 0x3FFF) - 1;
344 |
345 | break;
346 | }
347 |
348 | case "VP8L": {
349 | reader.next(8);
350 |
351 | widthMinusOne = reader.readBits(14);
352 | heightMinusOne = reader.readBits(14);
353 |
354 | break;
355 | }
356 | }
357 |
358 | if (isFirst) {
359 | rebuiltChunks.push(this.createVp8xChunk({
360 | animation: true,
361 | canvasWidthMinusOne: widthMinusOne,
362 | canvasHeightMinusOne: heightMinusOne
363 | }));
364 |
365 | rebuiltChunks.push(this.createAnimChunk({
366 | backgroundColor: 0x00000000,
367 | loopCount: loop
368 | }));
369 | }
370 |
371 | rebuiltChunks.push(this.createAnmfChunk({
372 | frameWidthMinusOne: widthMinusOne,
373 | frameHeightMinusOne: heightMinusOne,
374 | frameDuration: frame.duration,
375 | frameData: this.writeChunks(frameDataChunks)
376 | }));
377 |
378 | break;
379 | }
380 | }
381 | }
382 | }
383 |
384 | return new Blob([this.writeChunks([this.createRiffChunk({ type: "WEBP", buffer: this.writeChunks(rebuiltChunks)})])], { "type": "image/webp" });
385 | }
386 | }
387 |
--------------------------------------------------------------------------------
/src/lib/px-background.js:
--------------------------------------------------------------------------------
1 | import JSZip from "jszip";
2 | import GIF from "gif.js";
3 |
4 | import downloader from "./downloader";
5 | import Apng from "./apng";
6 | import Webp from "./webp";
7 | // import { buildInputFile, execute } from "wasm-imagemagick";
8 | // import { createFFmpeg, fetchFile } from "@ffmpeg/ffmpeg";
9 |
10 | // const ffmpeg = createFFmpeg();
11 |
12 | // let ffmpegPromise = ffmpeg.load();
13 |
14 | export default class PxBackground {
15 | constructor(page, id, options, callback) {
16 | this._page = page;
17 | this._id = id;
18 | this._options = options;
19 | this._callback = callback;
20 | }
21 |
22 | get page() {
23 | return this._page;
24 | }
25 |
26 | get id() {
27 | return this._id;
28 | }
29 |
30 | get options() {
31 | return this._options;
32 | }
33 |
34 | get callback() {
35 | return this._callback;
36 | }
37 |
38 | async download() {
39 | switch (this._page) {
40 | case "illust": {
41 | await this.downloadIllust();
42 |
43 | break;
44 | }
45 |
46 | case "novel": {
47 | await this.downloadNovel();
48 |
49 | break;
50 | }
51 |
52 | case "imageList":
53 | case "imageSeries": {
54 | await this.downloadImageList();
55 |
56 | break;
57 | }
58 |
59 | case "novelList": {
60 | await this.downloadNovelList();
61 |
62 | break;
63 | }
64 |
65 | case "novelSeries": {
66 | await this.downloadNovelSeries();
67 |
68 | break;
69 | }
70 | }
71 | }
72 |
73 | async downloadIllust() {
74 | this._callback({ state: "fetch" });
75 |
76 | const response = await fetch(`https://www.pixiv.net/ajax/illust/${this._id}`, { credentials: "include" });
77 | const json = await response.json();
78 |
79 | const data = json.body;
80 |
81 | if (data.illustType === 2) {
82 | // ugoira
83 | await this.downloadUgoira(data);
84 |
85 | } else if (data.pageCount !== 1) {
86 | // multiple
87 | await this.downloadMultiple(data);
88 |
89 | } else {
90 | // single
91 | await this.downloadSingle(data);
92 | }
93 | }
94 |
95 | async downloadSingle(data) {
96 | const imageUrl = data.urls[this._options.singleSize];
97 |
98 | const imageRespose = await fetch(imageUrl);
99 | let imageBlob = await imageRespose.blob();
100 |
101 | if (this._options.convertMode !== "none") {
102 | this._callback({ state: "convert" });
103 |
104 | imageBlob = await this.convert({
105 | blob: imageBlob,
106 | type: `image/${this._options.convertMode}`,
107 | quality: this._options.convertQuality
108 | });
109 | }
110 |
111 | this._callback({ state: "download" });
112 |
113 | const macro = PxBackground.getMacro(data);
114 |
115 | await downloader.download({
116 | blob: imageBlob,
117 | filename: PxBackground.getFilename(macro, this._options.singleFilename, PxBackground.getExt(imageBlob.type)),
118 | conflictAction: this._options.conflictAction,
119 | saveAs: false,
120 | forceFilename: this._options.forceFilename,
121 | disableShelf: this._options.disableShelf
122 | });
123 | }
124 |
125 | async downloadMultiple(data) {
126 | const response = await fetch(`https://www.pixiv.net/ajax/illust/${this._id}/pages`, { credentials: "include" });
127 | const json = await response.json();
128 |
129 | const pages = json.body;
130 |
131 | const imageUrls = pages.map(page => page.urls[this._options.multiSize]);
132 |
133 | let imageBlobs = [];
134 |
135 | for (let i = 0; i < imageUrls.length; i++) {
136 | const imageUrl = imageUrls[i];
137 |
138 | const imageRespose = await fetch(imageUrl);
139 | const imageBlob = await imageRespose.blob();
140 |
141 | this._callback({ state: "fetch", progress: (i + 1) / imageUrls.length });
142 |
143 | imageBlobs.push(imageBlob);
144 | }
145 |
146 | if (this._options.convertMode !== "none") {
147 | this._callback({ state: "convert" });
148 |
149 | const convertedImageBlobs = [];
150 |
151 | for (let i = 0; i < imageBlobs.length; i++) {
152 | const imageBlob = imageBlobs[i];
153 |
154 | const convertedImageBlob = await this.convert({
155 | blob: imageBlob,
156 | type: `image/${this._options.convertMode}`,
157 | quality: this._options.convertQuality
158 | });
159 |
160 | this._callback({ state: "convert", progress: (i + 1) / imageBlobs.length });
161 |
162 | convertedImageBlobs.push(convertedImageBlob);
163 | }
164 |
165 | imageBlobs = convertedImageBlobs;
166 | }
167 |
168 | this._callback({ state: "download" });
169 |
170 | const macro = PxBackground.getMacro(data);
171 |
172 | for (let i = 0; i < imageBlobs.length; i++) {
173 | const imageBlob = imageBlobs[i];
174 |
175 | await downloader.download({
176 | blob: imageBlob,
177 | filename: PxBackground.getFilename(macro, this._options.multiFilename, PxBackground.getExt(imageBlob.type), i),
178 | conflictAction: this._options.conflictAction,
179 | saveAs: false,
180 | forceFilename: this._options.forceFilename,
181 | disableShelf: this._options.disableShelf
182 | });
183 |
184 | this._callback({ state: "download", progress: (i + 1) / imageBlobs.length });
185 | }
186 | }
187 |
188 | async downloadUgoira(data) {
189 | const ugoiraResponse = await fetch(`https://www.pixiv.net/ajax/illust/${this._id}/ugoira_meta`, { credentials: "include" });
190 | const ugoiraJson = await ugoiraResponse.json();
191 | const ugoiraData = ugoiraJson.body;
192 |
193 | const zipUrl = ugoiraData[{regular: "src", original: "originalSrc"}[this._options.ugoiraSize]];
194 |
195 | const zipResponse = await fetch(zipUrl);
196 | const zipArrayBuffer = await zipResponse.arrayBuffer();
197 |
198 | let blob;
199 |
200 | switch (this._options.ugoiraMode) {
201 | case "zip": {
202 | blob = await this.convertUgoiraToZip(ugoiraData, zipArrayBuffer);
203 |
204 | break;
205 | }
206 |
207 | case "gif": {
208 | blob = await this.convertUgoiraToGif(ugoiraData, zipArrayBuffer);
209 |
210 | break;
211 | }
212 |
213 | case "apng": {
214 | blob = await this.convertUgoiraToApng(ugoiraData, zipArrayBuffer);
215 |
216 | break;
217 | }
218 |
219 | case "webp": {
220 | blob = await this.convertUgoiraToWebp(ugoiraData, zipArrayBuffer);
221 |
222 | break;
223 | }
224 | }
225 |
226 | this._callback({ state: "download" });
227 |
228 | const macro = PxBackground.getMacro(data);
229 |
230 | await downloader.download({
231 | blob: blob,
232 | filename: PxBackground.getFilename(macro, this._options.singleFilename, PxBackground.getExt(blob.type)),
233 | conflictAction: this._options.conflictAction,
234 | saveAs: false,
235 | forceFilename: this._options.forceFilename,
236 | disableShelf: this._options.disableShelf
237 | });
238 | }
239 |
240 | async convertUgoiraToZip(ugoiraData, zipArrayBuffer) {
241 | this._callback({ state: "load" });
242 |
243 | const zipObject = await JSZip.loadAsync(zipArrayBuffer);
244 |
245 | zipObject.file("animation.json", JSON.stringify({ ugokuIllustData: ugoiraData }));
246 |
247 | const zipBlob = await zipObject.generateAsync({ type: "blob" });
248 |
249 | return zipBlob;
250 | }
251 |
252 | async convertUgoiraToGif(ugoiraData, zipArrayBuffer) {
253 | this._callback({ state: "load" });
254 |
255 | const zipObject = await JSZip.loadAsync(zipArrayBuffer);
256 |
257 | const loadedImageElements = [];
258 |
259 | for (let i = 0; i < ugoiraData.frames.length; i++) {
260 | const frame = ugoiraData.frames[i];
261 |
262 | const sourceImageArrayBuffer = await zipObject.file(frame.file).async("arraybuffer");
263 | const sourceImageBlob = new Blob([sourceImageArrayBuffer], { "type": ugoiraData.mime_type });
264 | const sourceImageUrl = URL.createObjectURL(sourceImageBlob);
265 |
266 | const loadedImageElement = await PxBackground.getImageElement(sourceImageUrl);
267 |
268 | URL.revokeObjectURL(sourceImageUrl);
269 |
270 | this._callback({ state: "load", progress: (i + 1) / ugoiraData.frames.length });
271 |
272 | loadedImageElements.push(loadedImageElement);
273 | }
274 |
275 | this._callback({ state: "process" });
276 |
277 | const gif = new GIF({
278 | quality: 1,
279 | workers: 4,
280 | workerScript: "lib/gif.worker.js"
281 | });
282 |
283 | for (let i = 0; i < loadedImageElements.length; i++) {
284 | const loadedImageElement = loadedImageElements[i];
285 |
286 | gif.addFrame(loadedImageElement, { delay: ugoiraData.frames[i].delay });
287 | }
288 |
289 | const imageBlob = await new Promise(resolve => {
290 | gif.on("progress", ratio => {
291 | this._callback({ state: "process", progress: ratio });
292 | });
293 |
294 | gif.on("finished", blob => {
295 | resolve(blob);
296 | });
297 |
298 | gif.render();
299 | });
300 |
301 | return imageBlob;
302 | }
303 |
304 | async convertUgoiraToApng(ugoiraData, zipArrayBuffer) {
305 | this._callback({ state: "convert" });
306 |
307 | const zipObject = await JSZip.loadAsync(zipArrayBuffer);
308 |
309 | const convertedImageBlobs = [];
310 |
311 | for (let i = 0; i < ugoiraData.frames.length; i++) {
312 | const frame = ugoiraData.frames[i];
313 |
314 | const sourceImageArrayBuffer = await zipObject.file(frame.file).async("arraybuffer");
315 | const sourceImageBlob = new Blob([sourceImageArrayBuffer], { "type": ugoiraData.mime_type });
316 |
317 | const convertedImageBlob = await this.convert({
318 | blob: sourceImageBlob,
319 | type: "image/png",
320 | quality: this._options.ugoiraQuality
321 | });
322 |
323 | this._callback({ state: "convert", progress: (i + 1) / ugoiraData.frames.length });
324 |
325 | convertedImageBlobs.push(convertedImageBlob);
326 | }
327 |
328 | this._callback({ state: "process" });
329 |
330 | const apng = new Apng();
331 |
332 | for (let i = 0; i < convertedImageBlobs.length; i++) {
333 | const convertedImageBlob = convertedImageBlobs[i];
334 |
335 | apng.add(convertedImageBlob, { duration: ugoiraData.frames[i].delay });
336 | }
337 |
338 | const imageBlob = await apng.render();
339 |
340 | return imageBlob;
341 | }
342 |
343 | async convertUgoiraToWebp(ugoiraData, zipArrayBuffer) {
344 | this._callback({ state: "convert" });
345 |
346 | const zipObject = await JSZip.loadAsync(zipArrayBuffer);
347 |
348 | const convertedImageBlobs = [];
349 |
350 | for (let i = 0; i < ugoiraData.frames.length; i++) {
351 | const frame = ugoiraData.frames[i];
352 |
353 | const sourceImageArrayBuffer = await zipObject.file(frame.file).async("arraybuffer");
354 | const sourceImageBlob = new Blob([sourceImageArrayBuffer], { "type": ugoiraData.mime_type });
355 |
356 | const convertedImageBlob = await this.convert({
357 | blob: sourceImageBlob,
358 | type: "image/webp",
359 | quality: this._options.ugoiraQuality
360 | });
361 |
362 | this._callback({ state: "convert", progress: (i + 1) / ugoiraData.frames.length });
363 |
364 | convertedImageBlobs.push(convertedImageBlob);
365 | }
366 |
367 | this._callback({ state: "process" });
368 |
369 | const webp = new Webp();
370 |
371 | for (let i = 0; i < convertedImageBlobs.length; i++) {
372 | const convertedImageBlob = convertedImageBlobs[i];
373 |
374 | webp.add(convertedImageBlob, { duration: ugoiraData.frames[i].delay });
375 | }
376 |
377 | const imageBlob = await webp.render();
378 |
379 | return imageBlob;
380 | }
381 |
382 | async downloadNovel() {
383 | this._callback({ state: "fetch" });
384 |
385 | const response = await fetch(`https://www.pixiv.net/ajax/novel/${this._id}`, { credentials: "include" });
386 | const json = await response.json();
387 |
388 | const data = json.body;
389 |
390 | const textBlob = new Blob([data.content.replace(/\r\n|\r|\n/g, "\r\n")], { type: "text/plain" });
391 |
392 | this._callback({ state: "download" });
393 |
394 | const macro = PxBackground.getMacro(data);
395 |
396 | await downloader.download({
397 | blob: textBlob,
398 | filename: PxBackground.getFilename(macro, this._options.novelFilename, PxBackground.getExt(textBlob.type)),
399 | conflictAction: this._options.conflictAction,
400 | saveAs: false,
401 | forceFilename: this._options.forceFilename,
402 | disableShelf: this._options.disableShelf
403 | });
404 | }
405 |
406 | async convert(options) {
407 | const blobUrl = URL.createObjectURL(options.blob);
408 |
409 | return new Promise((resolve, reject) => {
410 | const canvas = document.createElement("canvas");
411 | const ctx = canvas.getContext("2d");
412 | const img = document.createElement("img");
413 |
414 | img.setAttribute("src", blobUrl);
415 |
416 | img.addEventListener("load", () => {
417 | URL.revokeObjectURL(blobUrl);
418 |
419 | canvas.width = img.width;
420 | canvas.height = img.height;
421 |
422 | ctx.clearRect(0, 0, img.width, img.height);
423 | ctx.drawImage(img, 0, 0);
424 |
425 | canvas.toBlob(blob => resolve(blob), options.type, options.quality);
426 | });
427 |
428 | img.addEventListener("error", err => {
429 | URL.revokeObjectURL(blobUrl);
430 |
431 | reject(err);
432 | });
433 | });
434 |
435 | // const imageName = `input.${PxBackground.getExt(options.blob.type)}`;
436 | // const convertedImageName = `output.${PxBackground.getExt(options.type)}`;
437 |
438 | // const args = [];
439 |
440 | // args.push("-i", imageName);
441 |
442 | // if (options.hasOwnProperty("quality")) {
443 | // switch (options.type) {
444 | // case "image/jpeg": {
445 | // args.push("-qmin", "1");
446 | // args.push("-qscale:v", `${Math.floor((1 - options.quality) * 30) + 1}`);
447 |
448 | // break;
449 | // }
450 |
451 | // case "image/png": {
452 | // args.push("-compression_level", `${Math.floor(options.quality * 100)}`);
453 |
454 | // break;
455 | // }
456 |
457 | // case "image/webp": {
458 | // args.push("-qmin", "1");
459 | // args.push("-qscale:v", `${Math.floor(options.quality * 100)}`);
460 |
461 | // break;
462 | // }
463 | // }
464 | // }
465 |
466 | // args.push(convertedImageName);
467 |
468 | // ffmpeg.FS("writeFile", imageName, await fetchFile(options.blob));
469 |
470 | // console.log(args);
471 |
472 | // ffmpegPromise = ffmpegPromise.then(() => {
473 | // return ffmpeg.run(...args);
474 | // });
475 |
476 | // await ffmpegPromise;
477 |
478 | // const data = ffmpeg.FS("readFile", convertedImageName);
479 |
480 | // ffmpeg.FS("unlink", imageName);
481 | // ffmpeg.FS("unlink", convertedImageName);
482 |
483 | // return new Blob([data], { type: options.type });
484 |
485 | // const args = [];
486 |
487 | // if (options.hasOwnProperty("quality")) {
488 | // switch (options.type) {
489 | // case "image/jpeg":
490 | // case "image/webp": {
491 | // args.push("-quality", `${Math.max(Math.floor(options.quality * 100), 1)}`);
492 |
493 | // break;
494 | // }
495 |
496 | // case "image/png": {
497 | // args.push("-quality", `${Math.max(Math.min(Math.floor(options.quality * 10), 9), 1)}6`);
498 |
499 | // break;
500 | // }
501 | // }
502 | // }
503 |
504 | // console.log(options.quality, `convert ${imageName} ${args.join(" ")} ${convertedImageName}`);
505 |
506 | // const { outputFiles, exitCode, stderr } = await execute({
507 | // inputFiles: [
508 | // await buildInputFile(blobUrl, imageName)
509 | // ],
510 | // commands: [
511 | // `convert ${imageName} ${args.join(" ")} ${convertedImageName}`
512 | // ]
513 | // });
514 |
515 | // if (exitCode !== 0) {
516 | // throw Error(stderr);
517 | // }
518 |
519 | // console.log(outputFiles);
520 |
521 | // return new Blob([outputFiles[0].blob], { type: options.type });
522 |
523 |
524 | }
525 |
526 | static toHalf(str) {
527 | return str.replace(/[\uff01-\uff5e]/g, function(s) {
528 | return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
529 | }).split("\u3000").join(" ");
530 | }
531 |
532 | static toFull(str) {
533 | return str.replace(/[!-~]/g, function(s) {
534 | return String.fromCharCode(s.charCodeAt(0) + 0xFEE0);
535 | }).split(" ").join("\u3000");
536 | }
537 |
538 | static escape(str, flag) {
539 | return str.replace(flag ? /([/?*:|"<>~\\])/g : /([/?*:|"<>~])/g, PxBackground.toFull);
540 | }
541 |
542 | static getImageElement(url) {
543 | return new Promise((resolve, reject) => {
544 | const img = document.createElement("img");
545 |
546 | img.src = url;
547 |
548 | img.addEventListener("load", () => {
549 | resolve(img);
550 | });
551 |
552 | img.addEventListener("error", err => {
553 | reject(err);
554 | });
555 | });
556 | }
557 |
558 | static getFilename(macro, base, ext, index) {
559 | let filename;
560 |
561 | if (index === undefined) {
562 | filename = `${PxBackground.replaceMacro(macro, base)}.${ext}`;
563 | } else {
564 | filename = `${PxBackground.replaceMacro(macro, base, index)}.${ext}`;
565 | }
566 |
567 | filename = filename.replace(/\/+/g, "/").replace(/(^|\/)\./g, "$1\uFF0E").replace(/\.($|\/)/g, "\uFF0E$1").replace(/^\//, "");
568 |
569 | return filename;
570 | }
571 |
572 | static getExt(type) {
573 | let ext = "";
574 |
575 | switch (type) {
576 | case "image/gif": {
577 | ext = "gif";
578 |
579 | break;
580 | }
581 |
582 | case "image/jpeg": {
583 | ext = "jpg";
584 |
585 | break;
586 | }
587 |
588 | case "image/png": {
589 | ext = "png";
590 |
591 | break;
592 | }
593 |
594 | case "image/webp": {
595 | ext = "webp";
596 |
597 | break;
598 | }
599 |
600 | case "text/plain": {
601 | ext = "txt";
602 |
603 | break;
604 | }
605 |
606 | case "application/zip": {
607 | ext = "zip";
608 |
609 | break;
610 | }
611 | }
612 |
613 | return ext;
614 | }
615 |
616 | static getMacro(data) {
617 | const macro = {};
618 |
619 | if (data.hasOwnProperty("id")) {
620 | macro.id = data.id;
621 | } else {
622 | macro.id = "";
623 | }
624 |
625 | if (data.hasOwnProperty("title")) {
626 | macro.title = data.title;
627 | } else {
628 | macro.title = "";
629 | }
630 |
631 | if (data.hasOwnProperty("userId")) {
632 | macro.userId = data.userId;
633 | } else {
634 | macro.userId = "";
635 | }
636 |
637 | if (data.hasOwnProperty("userName")) {
638 | macro.userName = data.userName;
639 | } else {
640 | macro.userName = "";
641 | }
642 |
643 | if (data.hasOwnProperty("seriesNavData") && data.seriesNavData !== null) {
644 | macro.seriesId = data.seriesNavData.seriesId;
645 | macro.seriesTitle = data.seriesNavData.title;
646 | } else {
647 | macro.seriesId = "";
648 | macro.seriesTitle = "";
649 | }
650 |
651 | if (data.hasOwnProperty("createDate")) {
652 | const date = new Date(data.createDate);
653 |
654 | macro.YYYY = date.getFullYear().toString();
655 | macro.YY = date.getFullYear().toString().slice(-2);
656 | macro.M = (date.getMonth() + 1).toString();
657 | macro.MM = (date.getMonth() + 1).toString().padStart(2, "0");
658 | macro.D = date.getDate().toString();
659 | macro.DD = date.getDate().toString().padStart(2, "0");
660 | macro.weekday = new Intl.DateTimeFormat(undefined, { weekday: "short" }).format(date);
661 | macro.h = date.getHours().toString();
662 | macro.hh = date.getHours().toString().padStart(2, "0");
663 | macro.m = date.getMinutes().toString();
664 | macro.mm = date.getMinutes().toString().padStart(2, "0");
665 | } else {
666 | macro.YYYY = "";
667 | macro.YY = "";
668 | macro.M = "";
669 | macro.MM = "";
670 | macro.D = "";
671 | macro.DD = "";
672 | macro.weekday = "";
673 | macro.h = "";
674 | macro.hh = "";
675 | macro.m = "";
676 | macro.mm = "";
677 | }
678 |
679 | return macro;
680 | }
681 |
682 | static replaceMacro(macro, str, index) {
683 | if (index !== undefined) {
684 | let _macro = {};
685 |
686 | _macro.index = index.toString();
687 | _macro.index2 = index.toString().padStart(2, "0");
688 | _macro.index3 = index.toString().padStart(3, "0");
689 | _macro.index4 = index.toString().padStart(4, "0");
690 |
691 | _macro.page = (index + 1).toString();
692 | _macro.page2 = (index + 1).toString().padStart(2, "0");
693 | _macro.page3 = (index + 1).toString().padStart(3, "0");
694 | _macro.page4 = (index + 1).toString().padStart(4, "0");
695 |
696 | macro = Object.assign({}, macro, _macro);
697 | }
698 |
699 | for (const [key, value] of Object.entries(macro)) {
700 | str = str.split("${" + key + "}").join(PxBackground.escape(value, true));
701 | }
702 |
703 | return str;
704 | }
705 | }
706 |
--------------------------------------------------------------------------------
/dist/css/skyblue.min.css:
--------------------------------------------------------------------------------
1 | a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,col-md-,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video{margin:0;padding:0;border:0;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:'';content:none}html{-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:transparent}@media screen and (max-device-width:480px){html{-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}}*{box-sizing:border-box}body>iframe{display:none}.container{margin-right:auto;margin-left:auto;padding-left:20px;padding-right:20px}.container:after,.container:before{content:" ";display:table;line-height:0}.container:after{clear:both}.row{margin-left:-20px;margin-right:-20px}.row:after,.row:before{content:" ";display:table;line-height:0}.row:after{clear:both}.col{min-height:1px;position:relative;padding-left:20px;padding-right:20px}.col:after,.col:before{content:" ";display:table;line-height:0}.col:after{clear:both}.col .float-right{float:right}.xs-0{width:0;float:left}.offset-xs-0{margin-left:0}.xs-1{width:8.33333%;float:left}.offset-xs-1{margin-left:8.33333%}.xs-2{width:16.66667%;float:left}.offset-xs-2{margin-left:16.66667%}.xs-3{width:25%;float:left}.offset-xs-3{margin-left:25%}.xs-4{width:33.33333%;float:left}.offset-xs-4{margin-left:33.33333%}.xs-5{width:41.66667%;float:left}.offset-xs-5{margin-left:41.66667%}.xs-6{width:50%;float:left}.offset-xs-6{margin-left:50%}.xs-7{width:58.33333%;float:left}.offset-xs-7{margin-left:58.33333%}.xs-8{width:66.66667%;float:left}.offset-xs-8{margin-left:66.66667%}.xs-9{width:75%;float:left}.offset-xs-9{margin-left:75%}.xs-10{width:83.33333%;float:left}.offset-xs-10{margin-left:83.33333%}.xs-11{width:91.66667%;float:left}.offset-xs-11{margin-left:91.66667%}.xs-12{width:100%;float:left}.offset-xs-12{margin-left:100%}@media screen and (min-width:480px){.sm-0{width:0;float:left}.offset-sm-0{margin-left:0}.sm-1{width:8.33333%;float:left}.offset-sm-1{margin-left:8.33333%}.sm-2{width:16.66667%;float:left}.offset-sm-2{margin-left:16.66667%}.sm-3{width:25%;float:left}.offset-sm-3{margin-left:25%}.sm-4{width:33.33333%;float:left}.offset-sm-4{margin-left:33.33333%}.sm-5{width:41.66667%;float:left}.offset-sm-5{margin-left:41.66667%}.sm-6{width:50%;float:left}.offset-sm-6{margin-left:50%}.sm-7{width:58.33333%;float:left}.offset-sm-7{margin-left:58.33333%}.sm-8{width:66.66667%;float:left}.offset-sm-8{margin-left:66.66667%}.sm-9{width:75%;float:left}.offset-sm-9{margin-left:75%}.sm-10{width:83.33333%;float:left}.offset-sm-10{margin-left:83.33333%}.sm-11{width:91.66667%;float:left}.offset-sm-11{margin-left:91.66667%}.sm-12{width:100%;float:left}.offset-sm-12{margin-left:100%}}@media screen and (min-width:768px){.container{max-width:720px}.md-0{width:0;float:left}.offset-md-0{margin-left:0}.md-1{width:8.33333%;float:left}.offset-md-1{margin-left:8.33333%}.md-2{width:16.66667%;float:left}.offset-md-2{margin-left:16.66667%}.md-3{width:25%;float:left}.offset-md-3{margin-left:25%}.md-4{width:33.33333%;float:left}.offset-md-4{margin-left:33.33333%}.md-5{width:41.66667%;float:left}.offset-md-5{margin-left:41.66667%}.md-6{width:50%;float:left}.offset-md-6{margin-left:50%}.md-7{width:58.33333%;float:left}.offset-md-7{margin-left:58.33333%}.md-8{width:66.66667%;float:left}.offset-md-8{margin-left:66.66667%}.md-9{width:75%;float:left}.offset-md-9{margin-left:75%}.md-10{width:83.33333%;float:left}.offset-md-10{margin-left:83.33333%}.md-11{width:91.66667%;float:left}.offset-md-11{margin-left:91.66667%}.md-12{width:100%;float:left}.offset-md-12{margin-left:100%}}@media screen and (min-width:970px){.container{max-width:940px}.lg-0{width:0;float:left}.offset-lg-0{margin-left:0}.lg-1{width:8.33333%;float:left}.offset-lg-1{margin-left:8.33333%}.lg-2{width:16.66667%;float:left}.offset-lg-2{margin-left:16.66667%}.lg-3{width:25%;float:left}.offset-lg-3{margin-left:25%}.lg-4{width:33.33333%;float:left}.offset-lg-4{margin-left:33.33333%}.lg-5{width:41.66667%;float:left}.offset-lg-5{margin-left:41.66667%}.lg-6{width:50%;float:left}.offset-lg-6{margin-left:50%}.lg-7{width:58.33333%;float:left}.offset-lg-7{margin-left:58.33333%}.lg-8{width:66.66667%;float:left}.offset-lg-8{margin-left:66.66667%}.lg-9{width:75%;float:left}.offset-lg-9{margin-left:75%}.lg-10{width:83.33333%;float:left}.offset-lg-10{margin-left:83.33333%}.lg-11{width:91.66667%;float:left}.offset-lg-11{margin-left:91.66667%}.lg-12{width:100%;float:left}.offset-lg-12{margin-left:100%}}@media screen and (min-width:1200px){.container{max-width:1150px}.xl-0{width:0;float:left}.offset-xl-0{margin-left:0}.xl-1{width:8.33333%;float:left}.offset-xl-1{margin-left:8.33333%}.xl-2{width:16.66667%;float:left}.offset-xl-2{margin-left:16.66667%}.xl-3{width:25%;float:left}.offset-xl-3{margin-left:25%}.xl-4{width:33.33333%;float:left}.offset-xl-4{margin-left:33.33333%}.xl-5{width:41.66667%;float:left}.offset-xl-5{margin-left:41.66667%}.xl-6{width:50%;float:left}.offset-xl-6{margin-left:50%}.xl-7{width:58.33333%;float:left}.offset-xl-7{margin-left:58.33333%}.xl-8{width:66.66667%;float:left}.offset-xl-8{margin-left:66.66667%}.xl-9{width:75%;float:left}.offset-xl-9{margin-left:75%}.xl-10{width:83.33333%;float:left}.offset-xl-10{margin-left:83.33333%}.xl-11{width:91.66667%;float:left}.offset-xl-11{margin-left:91.66667%}.xl-12{width:100%;float:left}.offset-xl-12{margin-left:100%}}h1,h2,h3,h4,h5,h6{line-height:1.2em;margin:0 0 .5em;font-weight:300;font-family:Dosis,'Helvetica Neue',Helvetica,sans-serif}h1{font-size:2.4em}h2{font-size:2em}h3{font-size:1.7em}h4{font-size:1.4em}h5{font-size:1.2em}h6{font-size:1em}p{line-height:1.5em;margin-bottom:1em}small{font-size:.75em}.big{font-size:1.25em}b,strong{font-weight:700}em,i{font-style:italic}a{color:#659ac9;-webkit-transition:all .25s;transition:all .25s;text-decoration:none}a.secundary{color:#16b6B6}a:active,a:focus,a:hover{color:#2e6392}a:active.secundary,a:focus.secundary,a:hover.secundary{color:#01a1a1}ol,ul{margin-bottom:1em;padding-left:22px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}ul li{list-style:disc}ul li li{list-style:circle}ol li{list-style:decimal}ol.unstyled,ul.unstyled{margin:0;padding:0}ol.unstyled li,ul.unstyled li{margin:0;padding:0;list-style:none}blockquote{border-left:2px solid #ddd;padding:.5em 1em;margin-bottom:1em;font-size:1.1em;color:#555}blockquote cite{font-size:.8em}blockquote cite:before{content:"— "}hr{border:0;border-bottom:1px solid #e9e9e9;margin:20px 0;clear:both}code,pre{font-family:"Deja-vu Sans Mono",Monaco,Menlo,Consolas,"Courier New",monospace;font-size:13px;line-height:1.5em;color:#333;background:#F7F7F9;border:1px solid #E1E1E8;border-radius:3px;padding:.8em 1em;white-space:pre-wrap;word-break:break-all;word-wrap:break-word}pre{margin-bottom:1em}code{display:inline-block;padding:.1em .3em}button,input[type=submit],input[type=button]{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:400;font-size:1em;-webkit-appearance:none;-webkit-font-smoothing:antialiased}.btn{display:inline-block;min-width:120px;padding:10px 25px;margin-bottom:1em;border:none;border-radius:3px;font-size:1em;line-height:1.4em;text-align:center;text-decoration:none;-webkit-transition:border-color .25s,background-color .25s,color .25s;transition:border-color .25s,background-color .25s,color .25s;background:#4378a7;color:#fff}.btn.btn-small{padding:5px 15px;min-width:30px;font-size:.8em;line-height:1.2em}.btn.btn-big{padding:12px 45px;min-width:200px;font-size:1.4em}.btn:hover{background-color:#2e6392;color:#fff}.btn:active{background-color:#194e7d;color:#fff;top:1px;position:relative}.btn.btn-success{background:#16b6B6;color:#fff}.btn.btn-success:hover{background-color:#01a1a1;color:#fff}.btn.btn-success:active{background-color:#008c8c;color:#fff;top:1px;position:relative}.btn.btn-error{background:#E74C3C;color:#fff}.btn.btn-error:hover{background-color:#d23727;color:#fff}.btn.btn-error:active{background-color:#bd2212;color:#fff;top:1px;position:relative}.btn.btn-warning{background:#F1C000;color:#fff}.btn.btn-warning:hover{background-color:#dcab00;color:#fff}.btn.btn-warning:active{background-color:#c79600;color:#fff;top:1px;position:relative}.btn.btn-light{background:#ecf0f1;color:#999}.btn.btn-light:hover{background-color:#d7dbdc;color:#fff}.btn.btn-light:active{background-color:#c2c6c7;color:#fff;top:1px;position:relative}.btn.btn-dark{background:#252428;color:#fff}.btn.btn-dark:hover{background-color:#100f13;color:#fff}.btn.btn-dark:active{background-color:#000;color:#fff;top:1px;position:relative}.btn.btn-empty{border-radius:1000px;padding:8px 25px;border:2px solid #4378a7;color:#4378a7;background:0 0}.btn.btn-empty:hover{color:#2e6392;border-color:#2e6392;background:0 0}.btn.btn-empty:active{color:#194e7d;border-color:#194e7d;background:0 0}.btn.btn-empty.btn-success{border:2px solid #16b6B6;color:#16b6B6;background:0 0}.btn.btn-empty.btn-success:hover{color:#01a1a1;border-color:#01a1a1;background:0 0}.btn.btn-empty.btn-success:active{color:#008c8c;border-color:#008c8c;background:0 0}.btn.btn-empty.btn-error{border:2px solid #E74C3C;color:#E74C3C;background:0 0}.btn.btn-empty.btn-error:hover{color:#d23727;border-color:#d23727;background:0 0}.btn.btn-empty.btn-error:active{color:#bd2212;border-color:#bd2212;background:0 0}.btn.btn-empty.btn-warning{border:2px solid #F1C000;color:#F1C000;background:0 0}.btn.btn-empty.btn-warning:hover{color:#dcab00;border-color:#dcab00;background:0 0}.btn.btn-empty.btn-warning:active{color:#c79600;border-color:#c79600;background:0 0}.btn.btn-empty.btn-light{border:2px solid #ddd;color:#999;background:0 0}.btn.btn-empty.btn-light:hover{color:#848484;border-color:#c8c8c8;background:0 0}.btn.btn-empty.btn-light:active{color:#6f6f6f;border-color:#b3b3b3;background:0 0}.btn.btn-empty.btn-dark{border:2px solid #252428;color:#252428;background:0 0}.btn.btn-empty.btn-dark:hover{color:#100f13;border-color:#100f13;background:0 0}.btn.btn-empty.btn-dark:active{color:#000;border-color:#000;background:0 0}input[type=text].form-control,input[type=password].form-control,input[type=date].form-control,input[type=datetime].form-control,input[type=email].form-control,input[type=number].form-control,input[type=search].form-control,input[type=tel].form-control,input[type=time].form-control,input[type=url].form-control,select.form-control,textarea.form-control{height:2.625em;width:100%;max-width:100%;padding:.5em;margin-bottom:1em;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:400;color:#555;font-size:1em;border:2px solid #ddd;border-radius:3px;-webkit-font-smoothing:antialiased;-webkit-appearance:none;-webkit-transition:border-color .25s,color .25s;transition:border-color .25s,color .25s}input[type=text].form-control:focus,input[type=password].form-control:focus,input[type=date].form-control:focus,input[type=datetime].form-control:focus,input[type=email].form-control:focus,input[type=number].form-control:focus,input[type=search].form-control:focus,input[type=tel].form-control:focus,input[type=time].form-control:focus,input[type=url].form-control:focus,select.form-control:focus,textarea.form-control:focus{border:2px solid #4378a7;color:#111;outline:0}.error input[type=text].form-control,.error input[type=password].form-control,.error input[type=date].form-control,.error input[type=datetime].form-control,.error input[type=email].form-control,.error input[type=number].form-control,.error input[type=search].form-control,.error input[type=tel].form-control,.error input[type=time].form-control,.error input[type=url].form-control,.error select.form-control,.error textarea.form-control,input[type=text].form-control.error,input[type=password].form-control.error,input[type=date].form-control.error,input[type=datetime].form-control.error,input[type=email].form-control.error,input[type=number].form-control.error,input[type=search].form-control.error,input[type=tel].form-control.error,input[type=time].form-control.error,input[type=url].form-control.error,select.form-control.error,textarea.form-control.error{border-color:#E74C3C}.warning input[type=text].form-control,.warning input[type=password].form-control,.warning input[type=date].form-control,.warning input[type=datetime].form-control,.warning input[type=email].form-control,.warning input[type=number].form-control,.warning input[type=search].form-control,.warning input[type=tel].form-control,.warning input[type=time].form-control,.warning input[type=url].form-control,.warning select.form-control,.warning textarea.form-control,input[type=text].form-control.warning,input[type=password].form-control.warning,input[type=date].form-control.warning,input[type=datetime].form-control.warning,input[type=email].form-control.warning,input[type=number].form-control.warning,input[type=search].form-control.warning,input[type=tel].form-control.warning,input[type=time].form-control.warning,input[type=url].form-control.warning,select.form-control.warning,textarea.form-control.warning{border-color:#F1C000}.success input[type=text].form-control,.success input[type=password].form-control,.success input[type=date].form-control,.success input[type=datetime].form-control,.success input[type=email].form-control,.success input[type=number].form-control,.success input[type=search].form-control,.success input[type=tel].form-control,.success input[type=time].form-control,.success input[type=url].form-control,.success select.form-control,.success textarea.form-control,input[type=text].form-control.success,input[type=password].form-control.success,input[type=date].form-control.success,input[type=datetime].form-control.success,input[type=email].form-control.success,input[type=number].form-control.success,input[type=search].form-control.success,input[type=tel].form-control.success,input[type=time].form-control.success,input[type=url].form-control.success,select.form-control.success,textarea.form-control.success{border-color:#16b6B6}textarea{height:auto;min-height:100px;line-height:1.5em}select{-webkit-appearance:none;-moz-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){select,select.form-control{background-color:#fff;background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPScxLjEnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZycgeG1sbnM6eGxpbms9J2h0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsnIHdpZHRoPSczNicgaGVpZ2h0PScyMCcgdmlld0JveD0nMCAwIDIwIDIwJz48cGF0aCBkPSdNMTcuNDE4IDYuMTA5YzAuMjcyLTAuMjY4IDAuNzA5LTAuMjY4IDAuOTc5IDBzMC4yNzEgMC43MDEgMCAwLjk2OWwtNy45MDggNy44M2MtMC4yNyAwLjI2OC0wLjcwNyAwLjI2OC0wLjk3OSAwbC03LjkwOC03LjgzYy0wLjI3LTAuMjY4LTAuMjctMC43MDEgMC0wLjk2OXMwLjcwOS0wLjI2OCAwLjk3OSAwbDcuNDE5IDcuMTQxIDcuNDE4LTcuMTQxeicgZmlsbD0nIzQ0NDQ0NCc+PC9wYXRoPjwvc3ZnPg==);background-repeat:no-repeat;background-position:right center;padding-right:36px}}@-moz-document url-prefix(){select,select.form-control{background-color:#fff;background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPScxLjEnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZycgeG1sbnM6eGxpbms9J2h0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsnIHdpZHRoPSczNicgaGVpZ2h0PScyMCcgdmlld0JveD0nMCAwIDIwIDIwJz48cGF0aCBkPSdNMTcuNDE4IDYuMTA5YzAuMjcyLTAuMjY4IDAuNzA5LTAuMjY4IDAuOTc5IDBzMC4yNzEgMC43MDEgMCAwLjk2OWwtNy45MDggNy44M2MtMC4yNyAwLjI2OC0wLjcwNyAwLjI2OC0wLjk3OSAwbC03LjkwOC03LjgzYy0wLjI3LTAuMjY4LTAuMjctMC43MDEgMC0wLjk2OXMwLjcwOS0wLjI2OCAwLjk3OSAwbDcuNDE5IDcuMTQxIDcuNDE4LTcuMTQxeicgZmlsbD0nIzQ0NDQ0NCc+PC9wYXRoPjwvc3ZnPg==);background-repeat:no-repeat;background-position:right center;padding-right:36px}}label{display:block;margin-bottom:.5em;line-height:1.5em}.error label,label.error{color:#E74C3C}.warning label,label.warning{color:#F1C000}.success label,label.success{color:#16b6B6}::-webkit-input-placeholder{color:#999}:-moz-placeholder{color:#999}::-moz-placeholder{color:#999}:-ms-input-placeholder{color:#999}[type=search]{-moz-appearance:textfield;-webkit-appearance:textfield;appearance:textfield}.form .checkbox-group,.form .radio-group{margin-bottom:1em}.form.form-horizontal label{display:inline-block;width:200px;max-width:100%;font-size:1em;vertical-align:top}.form.form-horizontal .checkbox-group,.form.form-horizontal .radio-group{display:inline-block;width:200px;max-width:100%}.form.form-horizontal .checkbox-group label,.form.form-horizontal .radio-group label{display:block}.fancy-checkbox,.fancy-radio{position:relative;cursor:pointer}.fancy-checkbox input,.fancy-radio input{opacity:0;position:absolute}.fancy-checkbox span:after,.fancy-checkbox span:before,.fancy-radio span:after,.fancy-radio span:before{box-sizing:border-box;content:'';display:block;position:absolute;-webkit-transition:all .25s;transition:all .25s}.fancy-checkbox{padding-left:44px}.fancy-checkbox span:before{width:38px;height:22px;border-radius:11px;top:1px;left:0;background:#c3c5c4;border:1px solid #c3c5c4}.fancy-checkbox span:after{width:20px;height:20px;border-radius:10px;background:#fff;top:2px;left:1px}.fancy-checkbox input:checked+span:after{left:17px}.fancy-checkbox input:checked+span:before{background:#16b6B6;border:1px solid #16b6B6}.fancy-checkbox input:focus+span:before{border:1px solid #8f9391}.fancy-checkbox input:focus:checked+span:before{border:1px solid #0b5b5b}.fancy-radio{padding-left:28px}.fancy-radio span:before{width:20px;height:20px;border-radius:50%;top:2px;left:0;background:#fff;border:1px solid #c3c5c4}.fancy-radio span:after{border-radius:50%;background:#fff;top:12px;left:10px;width:0;height:0}.fancy-radio input:checked+span:after{width:10px;height:10px;top:7px;left:5px}.fancy-radio input:checked+span:before{background:#16b6B6}.fancy-radio input:focus+span:before{border:1px solid #0b5b5b}table{max-width:100%;background-color:#fff;border-collapse:collapse;border-spacing:0}th{text-align:left}.table{width:100%;margin-bottom:1em}.table td,.table th{padding:8px;line-height:1.5em;vertical-align:top;border-top:1px solid #ddd}.table thead th{vertical-align:bottom;font-weight:700}.table caption+thead tr:first-child td,.table caption+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table thead:first-child tr:first-child td,.table thead:first-child tr:first-child th{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#FbFbFb}.table-condensed td,.table-condensed th{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;border-left:0;border-radius:3px}.table-bordered td,.table-bordered th{border-left:1px solid #ddd}.table-bordered caption+tbody tr:first-child td,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+thead tr:first-child th,.table-bordered tbody:first-child tr:first-child td,.table-bordered tbody:first-child tr:first-child th,.table-bordered thead:first-child tr:first-child th{border-top:0}.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child,.table-bordered thead:first-child tr:first-child>th:first-child{border-top-left-radius:3px}.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child,.table-bordered thead:first-child tr:first-child>th:last-child{border-top-right-radius:3px}.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child,.table-bordered thead:last-child tr:last-child>th:first-child{border-bottom-left-radius:3px}.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child,.table-bordered thead:last-child tr:last-child>th:last-child{border-bottom-right-radius:3px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{border-bottom-left-radius:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{border-bottom-right-radius:0}.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered caption+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child{border-top-left-radius:3px}.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered caption+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child{border-top-right-radius:3px}.table-striped tr:nth-child(odd)>td{background-color:#f7f7f7}.table-hover tr:hover>td{background-color:#5e92bf;border-left-color:#5e92bf;color:#fff}.table-hover tr:hover>td:first-child{border-left:1px solid #ddd}@font-face{font-family:Pe-icon-7-stroke;src:url(../fonts/pe-icon/Pe-icon-7-stroke.eot?-2irksn);src:url(../fonts/pe-icon/Pe-icon-7-stroke.eot?#iefix-2irksn) format('embedded-opentype'),url(../fonts/pe-icon/Pe-icon-7-stroke.woff?-2irksn) format('woff'),url(../fonts/pe-icon/Pe-icon-7-stroke.ttf?-2irksn) format('truetype'),url(../fonts/pe-icon/Pe-icon-7-stroke.svg?-2irksn#Pe-icon-7-stroke) format('svg');font-weight:400;font-style:normal}[class*=" icon-"],[class^=icon-]{display:inline-block;font-family:Pe-icon-7-stroke;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-album:before{content:"\e6aa"}.icon-arc:before{content:"\e6ab"}.icon-back-2:before{content:"\e6ac"}.icon-bandaid:before{content:"\e6ad"}.icon-car:before{content:"\e6ae"}.icon-diamond:before{content:"\e6af"}.icon-door-lock:before{content:"\e6b0"}.icon-eyedropper:before{content:"\e6b1"}.icon-female:before{content:"\e6b2"}.icon-gym:before{content:"\e6b3"}.icon-hammer:before{content:"\e6b4"}.icon-headphones:before{content:"\e6b5"}.icon-helm:before{content:"\e6b6"}.icon-hourglass:before{content:"\e6b7"}.icon-leaf:before{content:"\e6b8"}.icon-magic-wand:before{content:"\e6b9"}.icon-male:before{content:"\e6ba"}.icon-map-2:before{content:"\e6bb"}.icon-next-2:before{content:"\e6bc"}.icon-paint-bucket:before{content:"\e6bd"}.icon-pendrive:before{content:"\e6be"}.icon-photo:before{content:"\e6bf"}.icon-piggy:before{content:"\e6c0"}.icon-plugin:before{content:"\e6c1"}.icon-refresh-2:before{content:"\e6c2"}.icon-rocket:before{content:"\e6c3"}.icon-settings:before{content:"\e6c4"}.icon-shield:before{content:"\e6c5"}.icon-smile:before{content:"\e6c6"}.icon-usb:before{content:"\e6c7"}.icon-vector:before{content:"\e6c8"}.icon-wine:before{content:"\e6c9"}.icon-cloud-upload:before{content:"\e68a"}.icon-cash:before{content:"\e68c"}.icon-close:before{content:"\e680"}.icon-bluetooth:before{content:"\e68d"}.icon-cloud-download:before{content:"\e68b"}.icon-way:before{content:"\e68e"}.icon-close-circle:before{content:"\e681"}.icon-id:before{content:"\e68f"}.icon-angle-up:before{content:"\e682"}.icon-wristwatch:before{content:"\e690"}.icon-angle-up-circle:before{content:"\e683"}.icon-world:before{content:"\e691"}.icon-angle-right:before{content:"\e684"}.icon-volume:before{content:"\e692"}.icon-angle-right-circle:before{content:"\e685"}.icon-users:before{content:"\e693"}.icon-angle-left:before{content:"\e686"}.icon-user-female:before{content:"\e694"}.icon-angle-left-circle:before{content:"\e687"}.icon-up-arrow:before{content:"\e695"}.icon-angle-down:before{content:"\e688"}.icon-switch:before{content:"\e696"}.icon-angle-down-circle:before{content:"\e689"}.icon-scissors:before{content:"\e697"}.icon-wallet:before{content:"\e600"}.icon-safe:before{content:"\e698"}.icon-volume2:before{content:"\e601"}.icon-volume1:before{content:"\e602"}.icon-voicemail:before{content:"\e603"}.icon-video:before{content:"\e604"}.icon-user:before{content:"\e605"}.icon-upload:before{content:"\e606"}.icon-unlock:before{content:"\e607"}.icon-umbrella:before{content:"\e608"}.icon-trash:before{content:"\e609"}.icon-tools:before{content:"\e60a"}.icon-timer:before{content:"\e60b"}.icon-ticket:before{content:"\e60c"}.icon-target:before{content:"\e60d"}.icon-sun:before{content:"\e60e"}.icon-study:before{content:"\e60f"}.icon-stopwatch:before{content:"\e610"}.icon-star:before{content:"\e611"}.icon-speaker:before{content:"\e612"}.icon-signal:before{content:"\e613"}.icon-shuffle:before{content:"\e614"}.icon-shopbag:before{content:"\e615"}.icon-share:before{content:"\e616"}.icon-server:before{content:"\e617"}.icon-search:before{content:"\e618"}.icon-film:before{content:"\e6a5"}.icon-science:before{content:"\e619"}.icon-disk:before{content:"\e6a6"}.icon-ribbon:before{content:"\e61a"}.icon-repeat:before{content:"\e61b"}.icon-refresh:before{content:"\e61c"}.icon-add-user:before{content:"\e6a9"}.icon-refresh-cloud:before{content:"\e61d"}.icon-paperclip:before{content:"\e69c"}.icon-radio:before{content:"\e61e"}.icon-note2:before{content:"\e69d"}.icon-print:before{content:"\e61f"}.icon-network:before{content:"\e69e"}.icon-prev:before{content:"\e620"}.icon-mute:before{content:"\e69f"}.icon-power:before{content:"\e621"}.icon-medal:before{content:"\e6a0"}.icon-portfolio:before{content:"\e622"}.icon-like2:before{content:"\e6a1"}.icon-plus:before{content:"\e623"}.icon-left-arrow:before{content:"\e6a2"}.icon-play:before{content:"\e624"}.icon-key:before{content:"\e6a3"}.icon-plane:before{content:"\e625"}.icon-joy:before{content:"\e6a4"}.icon-photo-gallery:before{content:"\e626"}.icon-pin:before{content:"\e69b"}.icon-phone:before{content:"\e627"}.icon-plug:before{content:"\e69a"}.icon-pen:before{content:"\e628"}.icon-right-arrow:before{content:"\e699"}.icon-paper-plane:before{content:"\e629"}.icon-delete-user:before{content:"\e6a7"}.icon-paint:before{content:"\e62a"}.icon-bottom-arrow:before{content:"\e6a8"}.icon-notebook:before{content:"\e62b"}.icon-note:before{content:"\e62c"}.icon-next:before{content:"\e62d"}.icon-news-paper:before{content:"\e62e"}.icon-musiclist:before{content:"\e62f"}.icon-music:before{content:"\e630"}.icon-mouse:before{content:"\e631"}.icon-more:before{content:"\e632"}.icon-moon:before{content:"\e633"}.icon-monitor:before{content:"\e634"}.icon-micro:before{content:"\e635"}.icon-menu:before{content:"\e636"}.icon-map:before{content:"\e637"}.icon-map-marker:before{content:"\e638"}.icon-mail:before{content:"\e639"}.icon-mail-open:before{content:"\e63a"}.icon-mail-open-file:before{content:"\e63b"}.icon-magnet:before{content:"\e63c"}.icon-loop:before{content:"\e63d"}.icon-look:before{content:"\e63e"}.icon-lock:before{content:"\e63f"}.icon-lintern:before{content:"\e640"}.icon-link:before{content:"\e641"}.icon-like:before{content:"\e642"}.icon-light:before{content:"\e643"}.icon-less:before{content:"\e644"}.icon-keypad:before{content:"\e645"}.icon-junk:before{content:"\e646"}.icon-info:before{content:"\e647"}.icon-home:before{content:"\e648"}.icon-help2:before{content:"\e649"}.icon-help1:before{content:"\e64a"}.icon-graph3:before{content:"\e64b"}.icon-graph2:before{content:"\e64c"}.icon-graph1:before{content:"\e64d"}.icon-graph:before{content:"\e64e"}.icon-global:before{content:"\e64f"}.icon-gleam:before{content:"\e650"}.icon-glasses:before{content:"\e651"}.icon-gift:before{content:"\e652"}.icon-folder:before{content:"\e653"}.icon-flag:before{content:"\e654"}.icon-filter:before{content:"\e655"}.icon-file:before{content:"\e656"}.icon-expand1:before{content:"\e657"}.icon-exapnd2:before{content:"\e658"}.icon-edit:before{content:"\e659"}.icon-drop:before{content:"\e65a"}.icon-drawer:before{content:"\e65b"}.icon-download:before{content:"\e65c"}.icon-display2:before{content:"\e65d"}.icon-display1:before{content:"\e65e"}.icon-diskette:before{content:"\e65f"}.icon-date:before{content:"\e660"}.icon-cup:before{content:"\e661"}.icon-culture:before{content:"\e662"}.icon-crop:before{content:"\e663"}.icon-credit:before{content:"\e664"}.icon-copy-file:before{content:"\e665"}.icon-config:before{content:"\e666"}.icon-compass:before{content:"\e667"}.icon-comment:before{content:"\e668"}.icon-coffee:before{content:"\e669"}.icon-cloud:before{content:"\e66a"}.icon-clock:before{content:"\e66b"}.icon-check:before{content:"\e66c"}.icon-chat:before{content:"\e66d"}.icon-cart:before{content:"\e66e"}.icon-camera:before{content:"\e66f"}.icon-call:before{content:"\e670"}.icon-calculator:before{content:"\e671"}.icon-browser:before{content:"\e672"}.icon-box2:before{content:"\e673"}.icon-box1:before{content:"\e674"}.icon-bookmarks:before{content:"\e675"}.icon-bicycle:before{content:"\e676"}.icon-bell:before{content:"\e677"}.icon-battery:before{content:"\e678"}.icon-ball:before{content:"\e679"}.icon-back:before{content:"\e67a"}.icon-attention:before{content:"\e67b"}.icon-anchor:before{content:"\e67c"}.icon-albums:before{content:"\e67d"}.icon-alarm:before{content:"\e67e"}.icon-airplay:before{content:"\e67f"}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.5em;background-color:#FbFbFb;color:#555}img{max-width:100%}.noscroll{overflow:hidden}.margin-0{margin:0}.margin-top-0{margin-top:0}.margin-bottom-0{margin-bottom:0}.margin-left-0{margin-left:0}.margin-right-0{margin-right:0}.margin-y-0{margin-top:0;margin-bottom:0}.margin-x-0{margin-left:0;margin-right:0}.padding-0{padding:0}.padding-top-0{padding-top:0}.padding-bottom-0{padding-bottom:0}.padding-left-0{padding-left:0}.padding-right-0{padding-right:0}.padding-y-0{padding-top:0;padding-bottom:0}.padding-x-0{padding-left:0;padding-right:0}.margin-5{margin:5px}.margin-top-5{margin-top:5px}.margin-bottom-5{margin-bottom:5px}.margin-left-5{margin-left:5px}.margin-right-5{margin-right:5px}.margin-y-5{margin-top:5px;margin-bottom:5px}.margin-x-5{margin-left:5px;margin-right:5px}.padding-5{padding:5px}.padding-top-5{padding-top:5px}.padding-bottom-5{padding-bottom:5px}.padding-left-5{padding-left:5px}.padding-right-5{padding-right:5px}.padding-y-5{padding-top:5px;padding-bottom:5px}.padding-x-5{padding-left:5px;padding-right:5px}.margin-10{margin:10px}.margin-top-10{margin-top:10px}.margin-bottom-10{margin-bottom:10px}.margin-left-10{margin-left:10px}.margin-right-10{margin-right:10px}.margin-y-10{margin-top:10px;margin-bottom:10px}.margin-x-10{margin-left:10px;margin-right:10px}.padding-10{padding:10px}.padding-top-10{padding-top:10px}.padding-bottom-10{padding-bottom:10px}.padding-left-10{padding-left:10px}.padding-right-10{padding-right:10px}.padding-y-10{padding-top:10px;padding-bottom:10px}.padding-x-10{padding-left:10px;padding-right:10px}.margin-15{margin:15px}.margin-top-15{margin-top:15px}.margin-bottom-15{margin-bottom:15px}.margin-left-15{margin-left:15px}.margin-right-15{margin-right:15px}.margin-y-15{margin-top:15px;margin-bottom:15px}.margin-x-15{margin-left:15px;margin-right:15px}.padding-15{padding:15px}.padding-top-15{padding-top:15px}.padding-bottom-15{padding-bottom:15px}.padding-left-15{padding-left:15px}.padding-right-15{padding-right:15px}.padding-y-15{padding-top:15px;padding-bottom:15px}.padding-x-15{padding-left:15px;padding-right:15px}.margin-20{margin:20px}.margin-top-20{margin-top:20px}.margin-bottom-20{margin-bottom:20px}.margin-left-20{margin-left:20px}.margin-right-20{margin-right:20px}.margin-y-20{margin-top:20px;margin-bottom:20px}.margin-x-20{margin-left:20px;margin-right:20px}.padding-20{padding:20px}.padding-top-20{padding-top:20px}.padding-bottom-20{padding-bottom:20px}.padding-left-20{padding-left:20px}.padding-right-20{padding-right:20px}.padding-y-20{padding-top:20px;padding-bottom:20px}.padding-x-20{padding-left:20px;padding-right:20px}.margin-25{margin:25px}.margin-top-25{margin-top:25px}.margin-bottom-25{margin-bottom:25px}.margin-left-25{margin-left:25px}.margin-right-25{margin-right:25px}.margin-y-25{margin-top:25px;margin-bottom:25px}.margin-x-25{margin-left:25px;margin-right:25px}.padding-25{padding:25px}.padding-top-25{padding-top:25px}.padding-bottom-25{padding-bottom:25px}.padding-left-25{padding-left:25px}.padding-right-25{padding-right:25px}.padding-y-25{padding-top:25px;padding-bottom:25px}.padding-x-25{padding-left:25px;padding-right:25px}.margin-30{margin:30px}.margin-top-30{margin-top:30px}.margin-bottom-30{margin-bottom:30px}.margin-left-30{margin-left:30px}.margin-right-30{margin-right:30px}.margin-y-30{margin-top:30px;margin-bottom:30px}.margin-x-30{margin-left:30px;margin-right:30px}.padding-30{padding:30px}.padding-top-30{padding-top:30px}.padding-bottom-30{padding-bottom:30px}.padding-left-30{padding-left:30px}.padding-right-30{padding-right:30px}.padding-y-30{padding-top:30px;padding-bottom:30px}.padding-x-30{padding-left:30px;padding-right:30px}.margin-40{margin:40px}.margin-top-40{margin-top:40px}.margin-bottom-40{margin-bottom:40px}.margin-left-40{margin-left:40px}.margin-right-40{margin-right:40px}.margin-y-40{margin-top:40px;margin-bottom:40px}.margin-x-40{margin-left:40px;margin-right:40px}.padding-40{padding:40px}.padding-top-40{padding-top:40px}.padding-bottom-40{padding-bottom:40px}.padding-left-40{padding-left:40px}.padding-right-40{padding-right:40px}.padding-y-40{padding-top:40px;padding-bottom:40px}.padding-x-40{padding-left:40px;padding-right:40px}.margin-50{margin:50px}.margin-top-50{margin-top:50px}.margin-bottom-50{margin-bottom:50px}.margin-left-50{margin-left:50px}.margin-right-50{margin-right:50px}.margin-y-50{margin-top:50px;margin-bottom:50px}.margin-x-50{margin-left:50px;margin-right:50px}.padding-50{padding:50px}.padding-top-50{padding-top:50px}.padding-bottom-50{padding-bottom:50px}.padding-left-50{padding-left:50px}.padding-right-50{padding-right:50px}.padding-y-50{padding-top:50px;padding-bottom:50px}.padding-x-50{padding-left:50px;padding-right:50px}.margin-100{margin:100px}.margin-top-100{margin-top:100px}.margin-bottom-100{margin-bottom:100px}.margin-left-100{margin-left:100px}.margin-right-100{margin-right:100px}.margin-y-100{margin-top:100px;margin-bottom:100px}.margin-x-100{margin-left:100px;margin-right:100px}.padding-100{padding:100px}.padding-top-100{padding-top:100px}.padding-bottom-100{padding-bottom:100px}.padding-left-100{padding-left:100px}.padding-right-100{padding-right:100px}.padding-y-100{padding-top:100px;padding-bottom:100px}.padding-x-100{padding-left:100px;padding-right:100px}.float-left{float:left}.float-right{float:right}.float-none{float:none}.clearfix:after,.clearfix:before{content:" ";display:table;line-height:0}.clearfix:after{clear:both}.relative{position:relative}.absolute{position:absolute}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.text-justify{text-align:justify}.uppercase{text-transform:uppercase}.color-main{color:#4378a7}.color-main2{color:#659ac9}.color-success{color:#16b6B6}.color-error{color:#E74C3C}.color-warning{color:#F1C000}.color-light{color:#ecf0f1}.color-dark{color:#252428}.color-black{color:#111}.color-white{color:#fff}.bg-main{background-color:#4378a7;color:#fff}.bg-success{background-color:#16b6B6;color:#fff}.bg-error{background-color:#E74C3C;color:#fff}.bg-warning{background-color:#F1C000;color:#fff}.bg-light{background-color:#ecf0f1;color:#999}.bg-dark{background-color:#252428;color:#fff}.bg-white{background-color:#fff}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.hide{display:none}.full-width{width:100%}.middle{vertical-align:middle}.radius-3{border-radius:3px}.radius-5{border-radius:5px}.radius-10{border-radius:10px}.radius-15{border-radius:15px}.radius-big{border-radius:1000px}.no-border{border:none}.ellipsis{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@media screen and (min-width:480px){.sm-block{display:block}.sm-inline{display:inline}.sm-inline-block{display:inline-block}.sm-hide{display:none}.sm-margin-0{margin:0}.sm-margin-top-0{margin-top:0}.sm-margin-bottom-0{margin-bottom:0}.sm-margin-left-0{margin-left:0}.sm-margin-right-0{margin-right:0}.sm-margin-y-0{margin-top:0;margin-bottom:0}.sm-margin-x-0{margin-left:0;margin-right:0}.sm-padding-0{padding:0}.sm-padding-top-0{padding-top:0}.sm-padding-bottom-0{padding-bottom:0}.sm-padding-left-0{padding-left:0}.sm-padding-right-0{padding-right:0}.sm-padding-y-0{padding-top:0;padding-bottom:0}.sm-padding-x-0{padding-left:0;padding-right:0}.sm-margin-5{margin:5px}.sm-margin-top-5{margin-top:5px}.sm-margin-bottom-5{margin-bottom:5px}.sm-margin-left-5{margin-left:5px}.sm-margin-right-5{margin-right:5px}.sm-margin-y-5{margin-top:5px;margin-bottom:5px}.sm-margin-x-5{margin-left:5px;margin-right:5px}.sm-padding-5{padding:5px}.sm-padding-top-5{padding-top:5px}.sm-padding-bottom-5{padding-bottom:5px}.sm-padding-left-5{padding-left:5px}.sm-padding-right-5{padding-right:5px}.sm-padding-y-5{padding-top:5px;padding-bottom:5px}.sm-padding-x-5{padding-left:5px;padding-right:5px}.sm-margin-10{margin:10px}.sm-margin-top-10{margin-top:10px}.sm-margin-bottom-10{margin-bottom:10px}.sm-margin-left-10{margin-left:10px}.sm-margin-right-10{margin-right:10px}.sm-margin-y-10{margin-top:10px;margin-bottom:10px}.sm-margin-x-10{margin-left:10px;margin-right:10px}.sm-padding-10{padding:10px}.sm-padding-top-10{padding-top:10px}.sm-padding-bottom-10{padding-bottom:10px}.sm-padding-left-10{padding-left:10px}.sm-padding-right-10{padding-right:10px}.sm-padding-y-10{padding-top:10px;padding-bottom:10px}.sm-padding-x-10{padding-left:10px;padding-right:10px}.sm-margin-15{margin:15px}.sm-margin-top-15{margin-top:15px}.sm-margin-bottom-15{margin-bottom:15px}.sm-margin-left-15{margin-left:15px}.sm-margin-right-15{margin-right:15px}.sm-margin-y-15{margin-top:15px;margin-bottom:15px}.sm-margin-x-15{margin-left:15px;margin-right:15px}.sm-padding-15{padding:15px}.sm-padding-top-15{padding-top:15px}.sm-padding-bottom-15{padding-bottom:15px}.sm-padding-left-15{padding-left:15px}.sm-padding-right-15{padding-right:15px}.sm-padding-y-15{padding-top:15px;padding-bottom:15px}.sm-padding-x-15{padding-left:15px;padding-right:15px}.sm-margin-20{margin:20px}.sm-margin-top-20{margin-top:20px}.sm-margin-bottom-20{margin-bottom:20px}.sm-margin-left-20{margin-left:20px}.sm-margin-right-20{margin-right:20px}.sm-margin-y-20{margin-top:20px;margin-bottom:20px}.sm-margin-x-20{margin-left:20px;margin-right:20px}.sm-padding-20{padding:20px}.sm-padding-top-20{padding-top:20px}.sm-padding-bottom-20{padding-bottom:20px}.sm-padding-left-20{padding-left:20px}.sm-padding-right-20{padding-right:20px}.sm-padding-y-20{padding-top:20px;padding-bottom:20px}.sm-padding-x-20{padding-left:20px;padding-right:20px}.sm-margin-25{margin:25px}.sm-margin-top-25{margin-top:25px}.sm-margin-bottom-25{margin-bottom:25px}.sm-margin-left-25{margin-left:25px}.sm-margin-right-25{margin-right:25px}.sm-margin-y-25{margin-top:25px;margin-bottom:25px}.sm-margin-x-25{margin-left:25px;margin-right:25px}.sm-padding-25{padding:25px}.sm-padding-top-25{padding-top:25px}.sm-padding-bottom-25{padding-bottom:25px}.sm-padding-left-25{padding-left:25px}.sm-padding-right-25{padding-right:25px}.sm-padding-y-25{padding-top:25px;padding-bottom:25px}.sm-padding-x-25{padding-left:25px;padding-right:25px}.sm-margin-30{margin:30px}.sm-margin-top-30{margin-top:30px}.sm-margin-bottom-30{margin-bottom:30px}.sm-margin-left-30{margin-left:30px}.sm-margin-right-30{margin-right:30px}.sm-margin-y-30{margin-top:30px;margin-bottom:30px}.sm-margin-x-30{margin-left:30px;margin-right:30px}.sm-padding-30{padding:30px}.sm-padding-top-30{padding-top:30px}.sm-padding-bottom-30{padding-bottom:30px}.sm-padding-left-30{padding-left:30px}.sm-padding-right-30{padding-right:30px}.sm-padding-y-30{padding-top:30px;padding-bottom:30px}.sm-padding-x-30{padding-left:30px;padding-right:30px}.sm-margin-40{margin:40px}.sm-margin-top-40{margin-top:40px}.sm-margin-bottom-40{margin-bottom:40px}.sm-margin-left-40{margin-left:40px}.sm-margin-right-40{margin-right:40px}.sm-margin-y-40{margin-top:40px;margin-bottom:40px}.sm-margin-x-40{margin-left:40px;margin-right:40px}.sm-padding-40{padding:40px}.sm-padding-top-40{padding-top:40px}.sm-padding-bottom-40{padding-bottom:40px}.sm-padding-left-40{padding-left:40px}.sm-padding-right-40{padding-right:40px}.sm-padding-y-40{padding-top:40px;padding-bottom:40px}.sm-padding-x-40{padding-left:40px;padding-right:40px}.sm-margin-50{margin:50px}.sm-margin-top-50{margin-top:50px}.sm-margin-bottom-50{margin-bottom:50px}.sm-margin-left-50{margin-left:50px}.sm-margin-right-50{margin-right:50px}.sm-margin-y-50{margin-top:50px;margin-bottom:50px}.sm-margin-x-50{margin-left:50px;margin-right:50px}.sm-padding-50{padding:50px}.sm-padding-top-50{padding-top:50px}.sm-padding-bottom-50{padding-bottom:50px}.sm-padding-left-50{padding-left:50px}.sm-padding-right-50{padding-right:50px}.sm-padding-y-50{padding-top:50px;padding-bottom:50px}.sm-padding-x-50{padding-left:50px;padding-right:50px}.sm-margin-100{margin:100px}.sm-margin-top-100{margin-top:100px}.sm-margin-bottom-100{margin-bottom:100px}.sm-margin-left-100{margin-left:100px}.sm-margin-right-100{margin-right:100px}.sm-margin-y-100{margin-top:100px;margin-bottom:100px}.sm-margin-x-100{margin-left:100px;margin-right:100px}.sm-padding-100{padding:100px}.sm-padding-top-100{padding-top:100px}.sm-padding-bottom-100{padding-bottom:100px}.sm-padding-left-100{padding-left:100px}.sm-padding-right-100{padding-right:100px}.sm-padding-y-100{padding-top:100px;padding-bottom:100px}.sm-padding-x-100{padding-left:100px;padding-right:100px}}@media screen and (min-width:768px){.md-block{display:block}.md-inline{display:inline}.md-inline-block{display:inline-block}.md-hide{display:none}.md-margin-0{margin:0}.md-margin-top-0{margin-top:0}.md-margin-bottom-0{margin-bottom:0}.md-margin-left-0{margin-left:0}.md-margin-right-0{margin-right:0}.md-margin-y-0{margin-top:0;margin-bottom:0}.md-margin-x-0{margin-left:0;margin-right:0}.md-padding-0{padding:0}.md-padding-top-0{padding-top:0}.md-padding-bottom-0{padding-bottom:0}.md-padding-left-0{padding-left:0}.md-padding-right-0{padding-right:0}.md-padding-y-0{padding-top:0;padding-bottom:0}.md-padding-x-0{padding-left:0;padding-right:0}.md-margin-5{margin:5px}.md-margin-top-5{margin-top:5px}.md-margin-bottom-5{margin-bottom:5px}.md-margin-left-5{margin-left:5px}.md-margin-right-5{margin-right:5px}.md-margin-y-5{margin-top:5px;margin-bottom:5px}.md-margin-x-5{margin-left:5px;margin-right:5px}.md-padding-5{padding:5px}.md-padding-top-5{padding-top:5px}.md-padding-bottom-5{padding-bottom:5px}.md-padding-left-5{padding-left:5px}.md-padding-right-5{padding-right:5px}.md-padding-y-5{padding-top:5px;padding-bottom:5px}.md-padding-x-5{padding-left:5px;padding-right:5px}.md-margin-10{margin:10px}.md-margin-top-10{margin-top:10px}.md-margin-bottom-10{margin-bottom:10px}.md-margin-left-10{margin-left:10px}.md-margin-right-10{margin-right:10px}.md-margin-y-10{margin-top:10px;margin-bottom:10px}.md-margin-x-10{margin-left:10px;margin-right:10px}.md-padding-10{padding:10px}.md-padding-top-10{padding-top:10px}.md-padding-bottom-10{padding-bottom:10px}.md-padding-left-10{padding-left:10px}.md-padding-right-10{padding-right:10px}.md-padding-y-10{padding-top:10px;padding-bottom:10px}.md-padding-x-10{padding-left:10px;padding-right:10px}.md-margin-15{margin:15px}.md-margin-top-15{margin-top:15px}.md-margin-bottom-15{margin-bottom:15px}.md-margin-left-15{margin-left:15px}.md-margin-right-15{margin-right:15px}.md-margin-y-15{margin-top:15px;margin-bottom:15px}.md-margin-x-15{margin-left:15px;margin-right:15px}.md-padding-15{padding:15px}.md-padding-top-15{padding-top:15px}.md-padding-bottom-15{padding-bottom:15px}.md-padding-left-15{padding-left:15px}.md-padding-right-15{padding-right:15px}.md-padding-y-15{padding-top:15px;padding-bottom:15px}.md-padding-x-15{padding-left:15px;padding-right:15px}.md-margin-20{margin:20px}.md-margin-top-20{margin-top:20px}.md-margin-bottom-20{margin-bottom:20px}.md-margin-left-20{margin-left:20px}.md-margin-right-20{margin-right:20px}.md-margin-y-20{margin-top:20px;margin-bottom:20px}.md-margin-x-20{margin-left:20px;margin-right:20px}.md-padding-20{padding:20px}.md-padding-top-20{padding-top:20px}.md-padding-bottom-20{padding-bottom:20px}.md-padding-left-20{padding-left:20px}.md-padding-right-20{padding-right:20px}.md-padding-y-20{padding-top:20px;padding-bottom:20px}.md-padding-x-20{padding-left:20px;padding-right:20px}.md-margin-25{margin:25px}.md-margin-top-25{margin-top:25px}.md-margin-bottom-25{margin-bottom:25px}.md-margin-left-25{margin-left:25px}.md-margin-right-25{margin-right:25px}.md-margin-y-25{margin-top:25px;margin-bottom:25px}.md-margin-x-25{margin-left:25px;margin-right:25px}.md-padding-25{padding:25px}.md-padding-top-25{padding-top:25px}.md-padding-bottom-25{padding-bottom:25px}.md-padding-left-25{padding-left:25px}.md-padding-right-25{padding-right:25px}.md-padding-y-25{padding-top:25px;padding-bottom:25px}.md-padding-x-25{padding-left:25px;padding-right:25px}.md-margin-30{margin:30px}.md-margin-top-30{margin-top:30px}.md-margin-bottom-30{margin-bottom:30px}.md-margin-left-30{margin-left:30px}.md-margin-right-30{margin-right:30px}.md-margin-y-30{margin-top:30px;margin-bottom:30px}.md-margin-x-30{margin-left:30px;margin-right:30px}.md-padding-30{padding:30px}.md-padding-top-30{padding-top:30px}.md-padding-bottom-30{padding-bottom:30px}.md-padding-left-30{padding-left:30px}.md-padding-right-30{padding-right:30px}.md-padding-y-30{padding-top:30px;padding-bottom:30px}.md-padding-x-30{padding-left:30px;padding-right:30px}.md-margin-40{margin:40px}.md-margin-top-40{margin-top:40px}.md-margin-bottom-40{margin-bottom:40px}.md-margin-left-40{margin-left:40px}.md-margin-right-40{margin-right:40px}.md-margin-y-40{margin-top:40px;margin-bottom:40px}.md-margin-x-40{margin-left:40px;margin-right:40px}.md-padding-40{padding:40px}.md-padding-top-40{padding-top:40px}.md-padding-bottom-40{padding-bottom:40px}.md-padding-left-40{padding-left:40px}.md-padding-right-40{padding-right:40px}.md-padding-y-40{padding-top:40px;padding-bottom:40px}.md-padding-x-40{padding-left:40px;padding-right:40px}.md-margin-50{margin:50px}.md-margin-top-50{margin-top:50px}.md-margin-bottom-50{margin-bottom:50px}.md-margin-left-50{margin-left:50px}.md-margin-right-50{margin-right:50px}.md-margin-y-50{margin-top:50px;margin-bottom:50px}.md-margin-x-50{margin-left:50px;margin-right:50px}.md-padding-50{padding:50px}.md-padding-top-50{padding-top:50px}.md-padding-bottom-50{padding-bottom:50px}.md-padding-left-50{padding-left:50px}.md-padding-right-50{padding-right:50px}.md-padding-y-50{padding-top:50px;padding-bottom:50px}.md-padding-x-50{padding-left:50px;padding-right:50px}.md-margin-100{margin:100px}.md-margin-top-100{margin-top:100px}.md-margin-bottom-100{margin-bottom:100px}.md-margin-left-100{margin-left:100px}.md-margin-right-100{margin-right:100px}.md-margin-y-100{margin-top:100px;margin-bottom:100px}.md-margin-x-100{margin-left:100px;margin-right:100px}.md-padding-100{padding:100px}.md-padding-top-100{padding-top:100px}.md-padding-bottom-100{padding-bottom:100px}.md-padding-left-100{padding-left:100px}.md-padding-right-100{padding-right:100px}.md-padding-y-100{padding-top:100px;padding-bottom:100px}.md-padding-x-100{padding-left:100px;padding-right:100px}}@media screen and (min-width:970px){.lg-block{display:block}.lg-inline{display:inline}.lg-inline-block{display:inline-block}.lg-hide{display:none}.lg-margin-0{margin:0}.lg-margin-top-0{margin-top:0}.lg-margin-bottom-0{margin-bottom:0}.lg-margin-left-0{margin-left:0}.lg-margin-right-0{margin-right:0}.lg-margin-y-0{margin-top:0;margin-bottom:0}.lg-margin-x-0{margin-left:0;margin-right:0}.lg-padding-0{padding:0}.lg-padding-top-0{padding-top:0}.lg-padding-bottom-0{padding-bottom:0}.lg-padding-left-0{padding-left:0}.lg-padding-right-0{padding-right:0}.lg-padding-y-0{padding-top:0;padding-bottom:0}.lg-padding-x-0{padding-left:0;padding-right:0}.lg-margin-5{margin:5px}.lg-margin-top-5{margin-top:5px}.lg-margin-bottom-5{margin-bottom:5px}.lg-margin-left-5{margin-left:5px}.lg-margin-right-5{margin-right:5px}.lg-margin-y-5{margin-top:5px;margin-bottom:5px}.lg-margin-x-5{margin-left:5px;margin-right:5px}.lg-padding-5{padding:5px}.lg-padding-top-5{padding-top:5px}.lg-padding-bottom-5{padding-bottom:5px}.lg-padding-left-5{padding-left:5px}.lg-padding-right-5{padding-right:5px}.lg-padding-y-5{padding-top:5px;padding-bottom:5px}.lg-padding-x-5{padding-left:5px;padding-right:5px}.lg-margin-10{margin:10px}.lg-margin-top-10{margin-top:10px}.lg-margin-bottom-10{margin-bottom:10px}.lg-margin-left-10{margin-left:10px}.lg-margin-right-10{margin-right:10px}.lg-margin-y-10{margin-top:10px;margin-bottom:10px}.lg-margin-x-10{margin-left:10px;margin-right:10px}.lg-padding-10{padding:10px}.lg-padding-top-10{padding-top:10px}.lg-padding-bottom-10{padding-bottom:10px}.lg-padding-left-10{padding-left:10px}.lg-padding-right-10{padding-right:10px}.lg-padding-y-10{padding-top:10px;padding-bottom:10px}.lg-padding-x-10{padding-left:10px;padding-right:10px}.lg-margin-15{margin:15px}.lg-margin-top-15{margin-top:15px}.lg-margin-bottom-15{margin-bottom:15px}.lg-margin-left-15{margin-left:15px}.lg-margin-right-15{margin-right:15px}.lg-margin-y-15{margin-top:15px;margin-bottom:15px}.lg-margin-x-15{margin-left:15px;margin-right:15px}.lg-padding-15{padding:15px}.lg-padding-top-15{padding-top:15px}.lg-padding-bottom-15{padding-bottom:15px}.lg-padding-left-15{padding-left:15px}.lg-padding-right-15{padding-right:15px}.lg-padding-y-15{padding-top:15px;padding-bottom:15px}.lg-padding-x-15{padding-left:15px;padding-right:15px}.lg-margin-20{margin:20px}.lg-margin-top-20{margin-top:20px}.lg-margin-bottom-20{margin-bottom:20px}.lg-margin-left-20{margin-left:20px}.lg-margin-right-20{margin-right:20px}.lg-margin-y-20{margin-top:20px;margin-bottom:20px}.lg-margin-x-20{margin-left:20px;margin-right:20px}.lg-padding-20{padding:20px}.lg-padding-top-20{padding-top:20px}.lg-padding-bottom-20{padding-bottom:20px}.lg-padding-left-20{padding-left:20px}.lg-padding-right-20{padding-right:20px}.lg-padding-y-20{padding-top:20px;padding-bottom:20px}.lg-padding-x-20{padding-left:20px;padding-right:20px}.lg-margin-25{margin:25px}.lg-margin-top-25{margin-top:25px}.lg-margin-bottom-25{margin-bottom:25px}.lg-margin-left-25{margin-left:25px}.lg-margin-right-25{margin-right:25px}.lg-margin-y-25{margin-top:25px;margin-bottom:25px}.lg-margin-x-25{margin-left:25px;margin-right:25px}.lg-padding-25{padding:25px}.lg-padding-top-25{padding-top:25px}.lg-padding-bottom-25{padding-bottom:25px}.lg-padding-left-25{padding-left:25px}.lg-padding-right-25{padding-right:25px}.lg-padding-y-25{padding-top:25px;padding-bottom:25px}.lg-padding-x-25{padding-left:25px;padding-right:25px}.lg-margin-30{margin:30px}.lg-margin-top-30{margin-top:30px}.lg-margin-bottom-30{margin-bottom:30px}.lg-margin-left-30{margin-left:30px}.lg-margin-right-30{margin-right:30px}.lg-margin-y-30{margin-top:30px;margin-bottom:30px}.lg-margin-x-30{margin-left:30px;margin-right:30px}.lg-padding-30{padding:30px}.lg-padding-top-30{padding-top:30px}.lg-padding-bottom-30{padding-bottom:30px}.lg-padding-left-30{padding-left:30px}.lg-padding-right-30{padding-right:30px}.lg-padding-y-30{padding-top:30px;padding-bottom:30px}.lg-padding-x-30{padding-left:30px;padding-right:30px}.lg-margin-40{margin:40px}.lg-margin-top-40{margin-top:40px}.lg-margin-bottom-40{margin-bottom:40px}.lg-margin-left-40{margin-left:40px}.lg-margin-right-40{margin-right:40px}.lg-margin-y-40{margin-top:40px;margin-bottom:40px}.lg-margin-x-40{margin-left:40px;margin-right:40px}.lg-padding-40{padding:40px}.lg-padding-top-40{padding-top:40px}.lg-padding-bottom-40{padding-bottom:40px}.lg-padding-left-40{padding-left:40px}.lg-padding-right-40{padding-right:40px}.lg-padding-y-40{padding-top:40px;padding-bottom:40px}.lg-padding-x-40{padding-left:40px;padding-right:40px}.lg-margin-50{margin:50px}.lg-margin-top-50{margin-top:50px}.lg-margin-bottom-50{margin-bottom:50px}.lg-margin-left-50{margin-left:50px}.lg-margin-right-50{margin-right:50px}.lg-margin-y-50{margin-top:50px;margin-bottom:50px}.lg-margin-x-50{margin-left:50px;margin-right:50px}.lg-padding-50{padding:50px}.lg-padding-top-50{padding-top:50px}.lg-padding-bottom-50{padding-bottom:50px}.lg-padding-left-50{padding-left:50px}.lg-padding-right-50{padding-right:50px}.lg-padding-y-50{padding-top:50px;padding-bottom:50px}.lg-padding-x-50{padding-left:50px;padding-right:50px}.lg-margin-100{margin:100px}.lg-margin-top-100{margin-top:100px}.lg-margin-bottom-100{margin-bottom:100px}.lg-margin-left-100{margin-left:100px}.lg-margin-right-100{margin-right:100px}.lg-margin-y-100{margin-top:100px;margin-bottom:100px}.lg-margin-x-100{margin-left:100px;margin-right:100px}.lg-padding-100{padding:100px}.lg-padding-top-100{padding-top:100px}.lg-padding-bottom-100{padding-bottom:100px}.lg-padding-left-100{padding-left:100px}.lg-padding-right-100{padding-right:100px}.lg-padding-y-100{padding-top:100px;padding-bottom:100px}.lg-padding-x-100{padding-left:100px;padding-right:100px}}@media screen and (min-width:1200px){.xl-block{display:block}.xl-inline{display:inline}.xl-inline-block{display:inline-block}.xl-hide{display:none}.xl-margin-0{margin:0}.xl-margin-top-0{margin-top:0}.xl-margin-bottom-0{margin-bottom:0}.xl-margin-left-0{margin-left:0}.xl-margin-right-0{margin-right:0}.xl-margin-y-0{margin-top:0;margin-bottom:0}.xl-margin-x-0{margin-left:0;margin-right:0}.xl-padding-0{padding:0}.xl-padding-top-0{padding-top:0}.xl-padding-bottom-0{padding-bottom:0}.xl-padding-left-0{padding-left:0}.xl-padding-right-0{padding-right:0}.xl-padding-y-0{padding-top:0;padding-bottom:0}.xl-padding-x-0{padding-left:0;padding-right:0}.xl-margin-5{margin:5px}.xl-margin-top-5{margin-top:5px}.xl-margin-bottom-5{margin-bottom:5px}.xl-margin-left-5{margin-left:5px}.xl-margin-right-5{margin-right:5px}.xl-margin-y-5{margin-top:5px;margin-bottom:5px}.xl-margin-x-5{margin-left:5px;margin-right:5px}.xl-padding-5{padding:5px}.xl-padding-top-5{padding-top:5px}.xl-padding-bottom-5{padding-bottom:5px}.xl-padding-left-5{padding-left:5px}.xl-padding-right-5{padding-right:5px}.xl-padding-y-5{padding-top:5px;padding-bottom:5px}.xl-padding-x-5{padding-left:5px;padding-right:5px}.xl-margin-10{margin:10px}.xl-margin-top-10{margin-top:10px}.xl-margin-bottom-10{margin-bottom:10px}.xl-margin-left-10{margin-left:10px}.xl-margin-right-10{margin-right:10px}.xl-margin-y-10{margin-top:10px;margin-bottom:10px}.xl-margin-x-10{margin-left:10px;margin-right:10px}.xl-padding-10{padding:10px}.xl-padding-top-10{padding-top:10px}.xl-padding-bottom-10{padding-bottom:10px}.xl-padding-left-10{padding-left:10px}.xl-padding-right-10{padding-right:10px}.xl-padding-y-10{padding-top:10px;padding-bottom:10px}.xl-padding-x-10{padding-left:10px;padding-right:10px}.xl-margin-15{margin:15px}.xl-margin-top-15{margin-top:15px}.xl-margin-bottom-15{margin-bottom:15px}.xl-margin-left-15{margin-left:15px}.xl-margin-right-15{margin-right:15px}.xl-margin-y-15{margin-top:15px;margin-bottom:15px}.xl-margin-x-15{margin-left:15px;margin-right:15px}.xl-padding-15{padding:15px}.xl-padding-top-15{padding-top:15px}.xl-padding-bottom-15{padding-bottom:15px}.xl-padding-left-15{padding-left:15px}.xl-padding-right-15{padding-right:15px}.xl-padding-y-15{padding-top:15px;padding-bottom:15px}.xl-padding-x-15{padding-left:15px;padding-right:15px}.xl-margin-20{margin:20px}.xl-margin-top-20{margin-top:20px}.xl-margin-bottom-20{margin-bottom:20px}.xl-margin-left-20{margin-left:20px}.xl-margin-right-20{margin-right:20px}.xl-margin-y-20{margin-top:20px;margin-bottom:20px}.xl-margin-x-20{margin-left:20px;margin-right:20px}.xl-padding-20{padding:20px}.xl-padding-top-20{padding-top:20px}.xl-padding-bottom-20{padding-bottom:20px}.xl-padding-left-20{padding-left:20px}.xl-padding-right-20{padding-right:20px}.xl-padding-y-20{padding-top:20px;padding-bottom:20px}.xl-padding-x-20{padding-left:20px;padding-right:20px}.xl-margin-25{margin:25px}.xl-margin-top-25{margin-top:25px}.xl-margin-bottom-25{margin-bottom:25px}.xl-margin-left-25{margin-left:25px}.xl-margin-right-25{margin-right:25px}.xl-margin-y-25{margin-top:25px;margin-bottom:25px}.xl-margin-x-25{margin-left:25px;margin-right:25px}.xl-padding-25{padding:25px}.xl-padding-top-25{padding-top:25px}.xl-padding-bottom-25{padding-bottom:25px}.xl-padding-left-25{padding-left:25px}.xl-padding-right-25{padding-right:25px}.xl-padding-y-25{padding-top:25px;padding-bottom:25px}.xl-padding-x-25{padding-left:25px;padding-right:25px}.xl-margin-30{margin:30px}.xl-margin-top-30{margin-top:30px}.xl-margin-bottom-30{margin-bottom:30px}.xl-margin-left-30{margin-left:30px}.xl-margin-right-30{margin-right:30px}.xl-margin-y-30{margin-top:30px;margin-bottom:30px}.xl-margin-x-30{margin-left:30px;margin-right:30px}.xl-padding-30{padding:30px}.xl-padding-top-30{padding-top:30px}.xl-padding-bottom-30{padding-bottom:30px}.xl-padding-left-30{padding-left:30px}.xl-padding-right-30{padding-right:30px}.xl-padding-y-30{padding-top:30px;padding-bottom:30px}.xl-padding-x-30{padding-left:30px;padding-right:30px}.xl-margin-40{margin:40px}.xl-margin-top-40{margin-top:40px}.xl-margin-bottom-40{margin-bottom:40px}.xl-margin-left-40{margin-left:40px}.xl-margin-right-40{margin-right:40px}.xl-margin-y-40{margin-top:40px;margin-bottom:40px}.xl-margin-x-40{margin-left:40px;margin-right:40px}.xl-padding-40{padding:40px}.xl-padding-top-40{padding-top:40px}.xl-padding-bottom-40{padding-bottom:40px}.xl-padding-left-40{padding-left:40px}.xl-padding-right-40{padding-right:40px}.xl-padding-y-40{padding-top:40px;padding-bottom:40px}.xl-padding-x-40{padding-left:40px;padding-right:40px}.xl-margin-50{margin:50px}.xl-margin-top-50{margin-top:50px}.xl-margin-bottom-50{margin-bottom:50px}.xl-margin-left-50{margin-left:50px}.xl-margin-right-50{margin-right:50px}.xl-margin-y-50{margin-top:50px;margin-bottom:50px}.xl-margin-x-50{margin-left:50px;margin-right:50px}.xl-padding-50{padding:50px}.xl-padding-top-50{padding-top:50px}.xl-padding-bottom-50{padding-bottom:50px}.xl-padding-left-50{padding-left:50px}.xl-padding-right-50{padding-right:50px}.xl-padding-y-50{padding-top:50px;padding-bottom:50px}.xl-padding-x-50{padding-left:50px;padding-right:50px}.xl-margin-100{margin:100px}.xl-margin-top-100{margin-top:100px}.xl-margin-bottom-100{margin-bottom:100px}.xl-margin-left-100{margin-left:100px}.xl-margin-right-100{margin-right:100px}.xl-margin-y-100{margin-top:100px;margin-bottom:100px}.xl-margin-x-100{margin-left:100px;margin-right:100px}.xl-padding-100{padding:100px}.xl-padding-top-100{padding-top:100px}.xl-padding-bottom-100{padding-bottom:100px}.xl-padding-left-100{padding-left:100px}.xl-padding-right-100{padding-right:100px}.xl-padding-y-100{padding-top:100px;padding-bottom:100px}.xl-padding-x-100{padding-left:100px;padding-right:100px}}
--------------------------------------------------------------------------------