├── .gitignore ├── src ├── component │ ├── Body │ │ ├── index.js │ │ └── index.css │ ├── Fontawesome │ │ └── index.js │ ├── utility │ │ ├── randomNumber.js │ │ ├── applyCSSVar.js │ │ ├── clearChildNode.js │ │ ├── isJson.js │ │ ├── trimString.js │ │ ├── isValidString.js │ │ ├── averageColor.js │ │ ├── minMax.js │ │ ├── ordinalNumber.js │ │ ├── dateTime.js │ │ ├── sortArrayOfObject.js │ │ ├── makePath.js │ │ ├── isElementVisible.js │ │ ├── set.js │ │ ├── get.js │ │ ├── complexNode.js │ │ ├── ordinalWord.js │ │ ├── node.js │ │ ├── wordNumber.js │ │ ├── convertColor.js │ │ └── randomString.js │ ├── Base │ │ └── index.js │ ├── Bookmark │ │ ├── index.css │ │ └── index.js │ ├── Background │ │ ├── index.css │ │ └── index.js │ ├── Theme │ │ ├── index.css │ │ └── index.js │ ├── BookmarkLink │ │ ├── index.css │ │ └── index.js │ ├── BookmarkOpenAll │ │ ├── index.js │ │ └── index.css │ └── BookmarkGroup │ │ ├── index.css │ │ └── index.js ├── icon │ ├── icon-16.png │ ├── icon-48.png │ ├── icon-128.png │ ├── icon-512.png │ ├── favicon.svg │ └── icon-16.svg ├── index.js ├── app │ ├── index.css │ └── index.js ├── font │ └── fa │ │ ├── fa-brands-400.ttf │ │ ├── fa-regular-400.ttf │ │ ├── fa-solid-900.ttf │ │ ├── fa-solid-900.woff2 │ │ ├── fa-brands-400.woff2 │ │ └── fa-regular-400.woff2 ├── style │ ├── zindex │ │ └── index.css │ ├── typography │ │ └── index.css │ └── reset │ │ └── index.css ├── manifest.json ├── index.html └── config.js ├── asset └── screenshot │ └── screenshot-001.gif ├── webpack.dev.js ├── .github └── workflows │ └── gh-pages-deploy.yml ├── package.json ├── readme.md ├── webpack.common.js ├── webpack.prod.js └── license /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist 3 | node_modules 4 | -------------------------------------------------------------------------------- /src/component/Body/index.js: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | 3 | export const Body = function() {} -------------------------------------------------------------------------------- /src/icon/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zombieFox/voltTab/HEAD/src/icon/icon-16.png -------------------------------------------------------------------------------- /src/icon/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zombieFox/voltTab/HEAD/src/icon/icon-48.png -------------------------------------------------------------------------------- /src/icon/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zombieFox/voltTab/HEAD/src/icon/icon-128.png -------------------------------------------------------------------------------- /src/icon/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zombieFox/voltTab/HEAD/src/icon/icon-512.png -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { App } from './app'; 2 | 3 | const app = new App(); 4 | 5 | app.render(); 6 | -------------------------------------------------------------------------------- /src/app/index.css: -------------------------------------------------------------------------------- 1 | .app { 2 | min-width: 100vw; 3 | min-height: 100vh; 4 | display: grid; 5 | } 6 | -------------------------------------------------------------------------------- /src/component/Fontawesome/index.js: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | 3 | export const Fontawesome = function() {} 4 | -------------------------------------------------------------------------------- /src/font/fa/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zombieFox/voltTab/HEAD/src/font/fa/fa-brands-400.ttf -------------------------------------------------------------------------------- /src/font/fa/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zombieFox/voltTab/HEAD/src/font/fa/fa-regular-400.ttf -------------------------------------------------------------------------------- /src/font/fa/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zombieFox/voltTab/HEAD/src/font/fa/fa-solid-900.ttf -------------------------------------------------------------------------------- /src/font/fa/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zombieFox/voltTab/HEAD/src/font/fa/fa-solid-900.woff2 -------------------------------------------------------------------------------- /src/style/zindex/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --z-index-background: 1000; 3 | --z-index-bookmark: 2000; 4 | } 5 | -------------------------------------------------------------------------------- /src/font/fa/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zombieFox/voltTab/HEAD/src/font/fa/fa-brands-400.woff2 -------------------------------------------------------------------------------- /src/font/fa/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zombieFox/voltTab/HEAD/src/font/fa/fa-regular-400.woff2 -------------------------------------------------------------------------------- /asset/screenshot/screenshot-001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zombieFox/voltTab/HEAD/asset/screenshot/screenshot-001.gif -------------------------------------------------------------------------------- /src/component/utility/randomNumber.js: -------------------------------------------------------------------------------- 1 | export const randomNumber = (min, max) => { 2 | 3 | return Math.floor(Math.random() * (max - min + 1) + min); 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /src/component/utility/applyCSSVar.js: -------------------------------------------------------------------------------- 1 | export const applyCSSVar = (name, value) => { 2 | 3 | const html = document.querySelector('html'); 4 | 5 | html.style.setProperty(name, value); 6 | 7 | }; -------------------------------------------------------------------------------- /src/component/utility/clearChildNode.js: -------------------------------------------------------------------------------- 1 | export const clearChildNode = (element) => { 2 | 3 | while (element.lastChild) { 4 | element.removeChild(element.lastChild); 5 | } 6 | 7 | }; 8 | -------------------------------------------------------------------------------- /src/component/utility/isJson.js: -------------------------------------------------------------------------------- 1 | export const isJson = (string) => { 2 | 3 | try { 4 | JSON.parse(string); 5 | } catch (error) { 6 | return false; 7 | } 8 | 9 | return true; 10 | 11 | }; 12 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | module.exports = merge(common, { 5 | mode: 'development', 6 | devtool: 'inline-source-map' 7 | }); 8 | -------------------------------------------------------------------------------- /src/component/utility/trimString.js: -------------------------------------------------------------------------------- 1 | export const trimString = (value) => { 2 | 3 | if (typeof value == 'string') { 4 | return value.trim().replace(/\s\s+/g, ' '); 5 | } else { 6 | return value; 7 | } 8 | 9 | }; 10 | -------------------------------------------------------------------------------- /src/component/Base/index.js: -------------------------------------------------------------------------------- 1 | // must be loaded first 2 | import '../../style/reset/index.css'; 3 | 4 | // base styles for all components 5 | import '../../style/typography/index.css'; 6 | import '../../style/zindex/index.css'; 7 | 8 | export const Base = function() {} 9 | -------------------------------------------------------------------------------- /src/component/utility/isValidString.js: -------------------------------------------------------------------------------- 1 | export const isValidString = (value) => { 2 | let result = false; 3 | 4 | if (typeof value == 'string') { 5 | value = value.trim().replace(/\s/g, ''); 6 | if (value != '') { 7 | result = true; 8 | } 9 | } 10 | 11 | return result; 12 | }; 13 | -------------------------------------------------------------------------------- /src/component/utility/averageColor.js: -------------------------------------------------------------------------------- 1 | export const averageColor = function(rgb1, rgb2) { 2 | 3 | return { 4 | r: Math.round(Math.sqrt(Math.pow(rgb1.r, 1.75) + Math.pow(rgb2.r, 1.75) / 2)), 5 | g: Math.round(Math.sqrt(Math.pow(rgb1.g, 1.75) + Math.pow(rgb2.g, 1.75) / 2)), 6 | b: Math.round(Math.sqrt(Math.pow(rgb1.b, 1.75) + Math.pow(rgb2.b, 1.75) / 2)) 7 | }; 8 | 9 | }; 10 | -------------------------------------------------------------------------------- /src/component/utility/minMax.js: -------------------------------------------------------------------------------- 1 | export const minMax = ({ 2 | min = 0, 3 | max = 0, 4 | value = 0 5 | } = {}) => { 6 | 7 | if (value > max) { 8 | 9 | return max; 10 | 11 | } else if (value < min) { 12 | 13 | return min; 14 | 15 | } else if (isNaN(value)) { 16 | 17 | return min; 18 | 19 | } else { 20 | 21 | return value; 22 | 23 | } 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Volt Tab", 3 | "short_name": "Volt Tab", 4 | "description": "", 5 | "version": "1.0.0", 6 | "manifest_version": 2, 7 | "chrome_url_overrides": { 8 | "newtab": "index.html" 9 | }, 10 | "icons": { 11 | "16": "icon/icon-16.png", 12 | "48": "icon/icon-48.png", 13 | "128": "icon/icon-128.png", 14 | "512": "icon/icon-512.png" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/component/utility/ordinalNumber.js: -------------------------------------------------------------------------------- 1 | export const ordinalNumber = (number) => { 2 | 3 | var j = number % 10; 4 | 5 | var k = number % 100; 6 | 7 | if (j == 1 && k != 11) { 8 | return number + 'st'; 9 | } 10 | 11 | if (j == 2 && k != 12) { 12 | return number + 'nd'; 13 | } 14 | 15 | if (j == 3 && k != 13) { 16 | return number + 'rd'; 17 | } 18 | 19 | return number + 'th'; 20 | 21 | }; 22 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Volt Tab 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/component/Body/index.css: -------------------------------------------------------------------------------- 1 | ::selection { 2 | background-color: rgb(var(--theme-accent)); 3 | color: hsl(var(--theme-text)); 4 | } 5 | 6 | html, 7 | body { 8 | background-color: hsl(var(--background-color-hsl)); 9 | font-size: var(--font-size); 10 | line-height: 1.6; 11 | font-family: var(--theme-font); 12 | font-weight: var(--font-weight-regular); 13 | font-style: normal; 14 | color: hsl(var(--theme-text)); 15 | } 16 | 17 | body { 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | justify-content: center; 22 | align-items: center; 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages-deploy.yml: -------------------------------------------------------------------------------- 1 | name: gh-pages-deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build-and-deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2.3.1 14 | with: 15 | persist-credentials: false 16 | - name: Install and Build 17 | run: | 18 | npm install 19 | npm run build 20 | - name: Deploy 21 | uses: JamesIves/github-pages-deploy-action@4.1.3 22 | with: 23 | branch: gh-pages 24 | folder: dist/web -------------------------------------------------------------------------------- /src/icon/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icon/icon-16.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/component/utility/dateTime.js: -------------------------------------------------------------------------------- 1 | export const dateTime = () => { 2 | 3 | const date = new Date(); 4 | 5 | const month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; 6 | 7 | return { 8 | // string: date.constructor(), 9 | // time: date.getTime(), 10 | date: date.getDate(), 11 | day: date.getDay(), 12 | year: date.getFullYear(), 13 | hours: date.getHours(), 14 | milliseconds: date.getMilliseconds(), 15 | minutes: date.getMinutes(), 16 | month: date.getMonth(), 17 | monthString: month[date.getMonth()], 18 | seconds: date.getSeconds() 19 | }; 20 | 21 | }; 22 | -------------------------------------------------------------------------------- /src/component/utility/sortArrayOfObject.js: -------------------------------------------------------------------------------- 1 | import { get } from './get'; 2 | 3 | export const sortArrayOfObject = (array, key) => { 4 | 5 | array.sort((a, b) => { 6 | 7 | let textA = get({ 8 | object: a, 9 | path: key 10 | }); 11 | 12 | if (typeof textA == 'string') { 13 | textA = textA.toLowerCase(); 14 | } 15 | 16 | let textB = get({ 17 | object: b, 18 | path: key 19 | }); 20 | 21 | if (typeof textB == 'string') { 22 | textB = textB.toLowerCase(); 23 | } 24 | 25 | if (textA < textB) { 26 | return -1; 27 | } else if (textA > textB) { 28 | return 1; 29 | } else { 30 | return 0; 31 | } 32 | 33 | }); 34 | 35 | return array; 36 | 37 | }; 38 | -------------------------------------------------------------------------------- /src/component/utility/makePath.js: -------------------------------------------------------------------------------- 1 | export const makePath = (string) => { 2 | 3 | if (string) { 4 | 5 | let array; 6 | 7 | if (string.indexOf('[') != -1 && string.indexOf(']') != -1) { 8 | 9 | array = string.split('.').join(',').split('[').join(',').split(']').join(',').split(','); 10 | 11 | for (var i = 0; i < array.length; i++) { 12 | if (array[i] == '') { 13 | array.splice(i, 1); 14 | } 15 | if (!isNaN(parseInt(array[i], 10))) { 16 | array[i] = parseInt(array[i], 10); 17 | } 18 | } 19 | 20 | } else { 21 | 22 | array = string.split('.'); 23 | 24 | } 25 | 26 | return array; 27 | 28 | } else { 29 | 30 | return false; 31 | 32 | } 33 | 34 | }; 35 | -------------------------------------------------------------------------------- /src/component/utility/isElementVisible.js: -------------------------------------------------------------------------------- 1 | export const isElementVisible = (element) => { 2 | 3 | var rect = element.getBoundingClientRect(); 4 | 5 | const vWidth = window.innerWidth; 6 | 7 | const vHeight = window.innerHeight; 8 | 9 | const efp = (x, y) => { 10 | return document.elementFromPoint(x, y); 11 | }; 12 | 13 | // Return false if element is not in the viewport 14 | if ( 15 | rect.right < 0 || 16 | rect.bottom < 0 || 17 | rect.left > vWidth || 18 | rect.top > vHeight 19 | ) { 20 | return false; 21 | } 22 | 23 | // Return true if any of the element four corners are visible 24 | return ( 25 | element.contains(efp(rect.left, rect.top)) || 26 | element.contains(efp(rect.right, rect.top)) || 27 | element.contains(efp(rect.right, rect.bottom)) || 28 | element.contains(efp(rect.left, rect.bottom)) 29 | ); 30 | 31 | }; 32 | -------------------------------------------------------------------------------- /src/component/Bookmark/index.css: -------------------------------------------------------------------------------- 1 | .bookmark { 2 | grid-column: 1 / -1; 3 | grid-row: 1 / -1; 4 | display: grid; 5 | grid-template-columns: calc(var(--bookmark-panel) * 1%) 1fr calc(var(--bookmark-panel) * 1%); 6 | align-items: center; 7 | z-index: var(--z-index-bookmark); 8 | } 9 | 10 | .bookmark-panel { 11 | grid-row: 1 / -1; 12 | align-self: normal; 13 | background-color: 14 | hsla(var(--theme-bookmark-background-color-hsla-h), 15 | calc(var(--theme-bookmark-background-color-hsla-s) * 1%), 16 | calc(var(--theme-bookmark-background-color-hsla-l) * 1%), 17 | var(--theme-bookmark-background-color-hsla-a)); 18 | backdrop-filter: blur(calc(var(--theme-bookmark-group-background-blur) * 1px)); 19 | pointer-events: none; 20 | z-index: 1; 21 | } 22 | 23 | .bookmark-panel-left { 24 | grid-column: 1 / 2; 25 | } 26 | 27 | .bookmark-panel-right { 28 | grid-column: 3 / 4; 29 | } 30 | -------------------------------------------------------------------------------- /src/app/index.js: -------------------------------------------------------------------------------- 1 | import { Base } from '../component/Base'; 2 | import { Body } from '../component/Body'; 3 | import { Background } from '../component/Background'; 4 | import { Bookmark } from '../component/Bookmark'; 5 | import { Theme } from '../component/Theme'; 6 | import { Fontawesome } from '../component/Fontawesome'; 7 | 8 | import './index.css'; 9 | 10 | export const App = function() { 11 | 12 | this.node = { 13 | app: document.createElement('div') 14 | } 15 | 16 | this.base = new Base(); 17 | 18 | this.body = new Body(); 19 | 20 | this.theme = new Theme(); 21 | 22 | this.fontawesome = new Fontawesome(); 23 | 24 | this.background = new Background(); 25 | 26 | this.bookmark = new Bookmark(); 27 | 28 | this.render = () => { 29 | 30 | this.node.app.classList.add('app'); 31 | 32 | this.theme.render(); 33 | 34 | this.background.render(this.node.app); 35 | 36 | this.bookmark.render(this.node.app); 37 | 38 | document.querySelector('body').appendChild(this.node.app); 39 | 40 | } 41 | 42 | }; 43 | -------------------------------------------------------------------------------- /src/component/utility/set.js: -------------------------------------------------------------------------------- 1 | import { makePath } from './makePath.js'; 2 | 3 | export const set = ({ 4 | object = null, 5 | path = null, 6 | value = null 7 | } = {}) => { 8 | 9 | const address = makePath(path); 10 | 11 | const setValue = () => { 12 | 13 | while (address.length > 1) { 14 | 15 | // shift off and store the first 16 | let currentKey = address.shift(); 17 | 18 | // if the key is not found make a new object 19 | if (!(currentKey in object)) { 20 | // make an empty object in the current object level 21 | if (isNaN(currentKey)) { 22 | object[currentKey] = {}; 23 | } else { 24 | object[currentKey] = []; 25 | } 26 | } 27 | 28 | // drill down the object with the first key 29 | object = object[currentKey]; 30 | 31 | } 32 | 33 | let finalKey = address.shift(); 34 | 35 | object[finalKey] = value; 36 | 37 | }; 38 | 39 | if (object != null && path != null && value != null) { 40 | setValue(); 41 | } else { 42 | return false; 43 | } 44 | 45 | }; 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Volt Tab", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack serve --open --config webpack.dev.js", 8 | "build": "webpack --config webpack.prod.js" 9 | }, 10 | "keywords": [ 11 | "startpage", 12 | "start-page", 13 | "newtabpage", 14 | "new-tab-page", 15 | "tab", 16 | "chrome-extension", 17 | "extension", 18 | "bookmarks", 19 | "links", 20 | "voltTab", 21 | "volt tab" 22 | ], 23 | "author": "zombieFox", 24 | "license": "GPL-3", 25 | "devDependencies": { 26 | "copy-webpack-plugin": "^11.0.0", 27 | "css-loader": "^6.7.1", 28 | "css-minimizer-webpack-plugin": "^4.0.0", 29 | "html-webpack-plugin": "^5.5.0", 30 | "mini-css-extract-plugin": "^2.6.0", 31 | "sortablejs": "^1.15.0", 32 | "style-loader": "^3.3.1", 33 | "webfontloader": "^1.6.28", 34 | "webpack": "^5.72.1", 35 | "webpack-cli": "^4.9.2", 36 | "webpack-dev-server": "^4.9.0", 37 | "webpack-merge": "^5.8.0", 38 | "zip-webpack-plugin": "^4.0.1" 39 | } 40 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Volt Tab 2 | 3 | A new tab page with bookmark groups and open all controls. 4 | 5 | [![voltTab screenshot](asset/screenshot/screenshot-001.gif)](https://zombiefox.github.io/voltTab/) 6 | 7 | ## [See the demo in action](https://zombiefox.github.io/voltTab/) 8 | 9 | * * * 10 | 11 | ## Development 12 | 13 | When developing use: 14 | 15 | - `npm start` 16 | 17 | A development server will automatically open the project in your browser. Normally here: `http://localhost:8080`. 18 | 19 | To build the project use: 20 | 21 | - `npm run build` 22 | 23 | A web ready folder will be created in `/dist/web/`. 24 | A browser addon/extension ready zip will be created in `/dist/extension/`. 25 | 26 | ## Customise 27 | 28 | Edit the [`src/config.js`](src/config.js) file to change the bookmarks, colours and background. 29 | 30 | All colours are defined as [HSL](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl) values in an array. Eg: 31 | 32 | A colour defined as: 33 | 34 | ``` 35 | hsl: [204, 100, 72] 36 | ``` 37 | 38 | Will be converted to CSS as: 39 | 40 | ``` 41 | selector { 42 | color: hsl(204, 100%, 72%); 43 | } 44 | ``` -------------------------------------------------------------------------------- /src/component/utility/get.js: -------------------------------------------------------------------------------- 1 | import { makePath } from './makePath.js'; 2 | 3 | export const get = ({ 4 | object = null, 5 | path = null 6 | } = {}) => { 7 | 8 | const address = makePath(path); 9 | 10 | const getValue = () => { 11 | 12 | while (address.length > 1) { 13 | 14 | // shift off and store the first key 15 | let currentKey = address.shift(); 16 | 17 | // if the key is not found make a new object 18 | if (!(currentKey in object)) { 19 | // make an empty object in the current object level 20 | if (isNaN(currentKey)) { 21 | object[currentKey] = {}; 22 | } else { 23 | object[currentKey] = []; 24 | } 25 | } 26 | 27 | // drill down the object with the first key 28 | object = object[currentKey]; 29 | 30 | } 31 | 32 | let finalKey = address.shift(); 33 | 34 | if (!(finalKey in object)) { 35 | return ''; 36 | } else { 37 | return object[finalKey]; 38 | } 39 | 40 | }; 41 | 42 | if (object != null && path != null) { 43 | return getValue(); 44 | } else { 45 | return false; 46 | } 47 | 48 | }; 49 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const CopyPlugin = require('copy-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: { 7 | index: path.resolve(__dirname, 'src', 'index.js') 8 | }, 9 | output: { 10 | filename: '[name].[contenthash].js', 11 | path: path.resolve(__dirname, 'dist/web'), 12 | clean: true 13 | }, 14 | module: { 15 | rules: [{ 16 | test: /\.css$/i, 17 | use: ['style-loader', 'css-loader'] 18 | }, { 19 | test: /\.(ttf|woff|woff2)$/, 20 | type: 'asset/resource', 21 | generator: { 22 | filename: 'font/[name][ext]', 23 | } 24 | }, { 25 | test: /\.(jpe?g|png|gif|svg)$/i, 26 | type: 'asset/resource', 27 | generator: { 28 | filename: 'image/[name][ext]', 29 | } 30 | }] 31 | }, 32 | plugins: [ 33 | new HtmlWebpackPlugin({ 34 | template: './src/index.html' 35 | }), 36 | new CopyPlugin({ 37 | patterns: [{ 38 | from: './src/manifest.json', 39 | to: './manifest.json' 40 | }, { 41 | from: './src/icon/', 42 | to: './icon/' 43 | }] 44 | }) 45 | ] 46 | }; 47 | -------------------------------------------------------------------------------- /src/component/Background/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background-color-hsl: 3 | var(--background-color-hsl-h), 4 | calc(var(--background-color-hsl-s) * 1%), 5 | calc(var(--background-color-hsl-l) * 1%); 6 | } 7 | 8 | .background { 9 | display: block; 10 | position: relative; 11 | grid-column: 1 / -1; 12 | grid-row: 1 / -1; 13 | pointer-events: none; 14 | z-index: var(--z-index-background); 15 | } 16 | 17 | .background-color, 18 | .background-image, 19 | .background-gradient { 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | width: 100%; 24 | height: 100%; 25 | } 26 | 27 | .background-color { 28 | background-color: hsl(var(--background-color-hsl)); 29 | z-index: 1; 30 | } 31 | 32 | .background-image { 33 | background-image: var(--background-image-url); 34 | background-attachment: fixed; 35 | background-size: cover; 36 | background-position: center; 37 | opacity: var(--background-image-opacity); 38 | filter: blur(calc(var(--background-image-blur) * 1px)) grayscale(var(--background-image-grayscale)); 39 | z-index: 2; 40 | } 41 | 42 | .background-gradient { 43 | background: linear-gradient(var(--background-gradient-degree), var(--background-gradient-color)); 44 | z-index: 3; 45 | } 46 | -------------------------------------------------------------------------------- /src/component/utility/complexNode.js: -------------------------------------------------------------------------------- 1 | export const complexNode = ({ 2 | tag = 'div', 3 | text = false, 4 | complexText = false, 5 | attr = [], 6 | node = [] 7 | } = {}) => { 8 | 9 | const element = document.createElement(tag); 10 | 11 | if (text) { 12 | 13 | if (complexText) { 14 | 15 | element.innerHTML = text; 16 | 17 | } else { 18 | 19 | let textNode = document.createTextNode(text); 20 | 21 | element.appendChild(textNode); 22 | 23 | } 24 | 25 | } 26 | 27 | if (attr.length > 0) { 28 | attr.forEach((item) => { 29 | 30 | if ('key' in item && 'value' in item) { 31 | element.setAttribute(item.key, item.value); 32 | } else if ('key' in item) { 33 | element.setAttribute(item.key, ''); 34 | } 35 | 36 | }); 37 | } 38 | 39 | if (node) { 40 | if (typeof node != 'string') { 41 | if (node.length > 0) { 42 | 43 | node.forEach((item) => { 44 | if (item instanceof HTMLElement) { 45 | element.appendChild(item); 46 | } 47 | }); 48 | 49 | } else { 50 | 51 | if (node instanceof HTMLElement) { 52 | element.appendChild(node); 53 | } 54 | 55 | } 56 | } 57 | } 58 | 59 | return element; 60 | 61 | }; 62 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | const path = require('path'); 4 | const ZipPlugin = require('zip-webpack-plugin'); 5 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); 6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 7 | const TerserPlugin = require('terser-webpack-plugin'); 8 | 9 | const version = require('./src/manifest.json').version; 10 | const name = require('./src/manifest.json').name; 11 | 12 | module.exports = merge(common, { 13 | mode: 'production', 14 | optimization: { 15 | minimize: true, 16 | minimizer: [ 17 | new CssMinimizerPlugin({ 18 | minify: CssMinimizerPlugin.cleanCssMinify 19 | }), 20 | new TerserPlugin({ 21 | terserOptions: { 22 | format: { 23 | comments: false, 24 | }, 25 | }, 26 | extractComments: false, 27 | }) 28 | ] 29 | }, 30 | module: { 31 | rules: [{ 32 | test: /\.css$/i, 33 | use: [MiniCssExtractPlugin.loader, 'css-loader'], 34 | }], 35 | }, 36 | plugins: [ 37 | new MiniCssExtractPlugin({ 38 | filename: '[name].[contenthash].css' 39 | }), 40 | new ZipPlugin({ 41 | path: path.resolve(__dirname, 'dist/extension'), 42 | filename: name + ' ' + version + '.zip' 43 | }) 44 | ] 45 | }); 46 | -------------------------------------------------------------------------------- /src/component/utility/ordinalWord.js: -------------------------------------------------------------------------------- 1 | export const ordinalWord = (word) => { 2 | 3 | const endsWithDoubleZeroPattern = /(hundred|thousand|(m|b|tr|quadr)illion)$/; 4 | 5 | const endsWithTeenPattern = /teen$/; 6 | 7 | const endsWithYPattern = /y$/; 8 | 9 | const endsWithZeroThroughTwelvePattern = /(Zero|One|Two|Three|Four|Five|Six|Seven|Eight|Nine|Ten|Eleven|Twelve)$/; 10 | 11 | const ordinalLessThanThirteen = { 12 | Zero: 'Zeroth', 13 | One: 'First', 14 | Two: 'Second', 15 | Three: 'Third', 16 | Four: 'Fourth', 17 | Five: 'Fifth', 18 | Six: 'Sixth', 19 | Seven: 'Seventh', 20 | Eight: 'Eighth', 21 | Nine: 'Ninth', 22 | Ten: 'Tenth', 23 | Eleven: 'Eleventh', 24 | Twelve: 'Twelfth' 25 | }; 26 | 27 | const replaceWithOrdinalVariant = (match, numberWord) => { 28 | return ordinalLessThanThirteen[numberWord]; 29 | }; 30 | 31 | // Ends with *00 (100, 1000, etc.) or *teen (13, 14, 15, 16, 17, 18, 19) 32 | if (endsWithDoubleZeroPattern.test(word) || endsWithTeenPattern.test(word)) { 33 | return word + 'th'; 34 | // Ends with *y (20, 30, 40, 50, 60, 70, 80, 90) 35 | } else if (endsWithYPattern.test(word)) { 36 | return word.replace(endsWithYPattern, 'ieth'); 37 | // Ends with one through twelve 38 | } else if (endsWithZeroThroughTwelvePattern.test(word)) { 39 | return word.replace(endsWithZeroThroughTwelvePattern, replaceWithOrdinalVariant); 40 | } 41 | 42 | return word; 43 | 44 | }; 45 | -------------------------------------------------------------------------------- /src/component/Theme/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --font-size: calc(var(--theme-scale) * 0.025vmax); 3 | --font-weight-light: 300; 4 | --font-weight-regular: 400; 5 | --font-weight-medium: 500; 6 | --font-weight-bold: 700; 7 | } 8 | 9 | :root { 10 | --theme-accent: 11 | hsl(var(--theme-accent-hsl-h), 12 | calc(var(--theme-accent-hsl-s) * 1%), 13 | calc(var(--theme-accent-hsl-l) * 1%)); 14 | --theme-text: 15 | hsl(var(--theme-text-hsl-h), 16 | calc(var(--theme-text-hsl-s) * 1%), 17 | calc(var(--theme-text-hsl-l) * 1%)); 18 | } 19 | 20 | :root { 21 | --theme-transition-xfast-bounce: calc(var(--theme-transition-speed-xfast) * 1s) cubic-bezier(var(--theme-easing-bounce)); 22 | --theme-transition-fast-bounce: calc(var(--theme-transition-speed-fast) * 1s) cubic-bezier(var(--theme-easing-bounce)); 23 | --theme-transition-medium-bounce: calc(var(--theme-transition-speed-medium) * 1s) cubic-bezier(var(--theme-easing-bounce)); 24 | --theme-transition-slow-bounce: calc(var(--theme-transition-speed-slow) * 1s) cubic-bezier(var(--theme-easing-bounce)); 25 | --theme-transition-xslow-bounce: calc(var(--theme-transition-speed-xslow) * 1s) cubic-bezier(var(--theme-easing-bounce)); 26 | } 27 | 28 | :root { 29 | --theme-transition-xfast-ease: calc(var(--theme-transition-speed-xfast) * 1s) ease-in-out; 30 | --theme-transition-fast-ease: calc(var(--theme-transition-speed-fast) * 1s) ease-in-out; 31 | --theme-transition-medium-ease: calc(var(--theme-transition-speed-medium) * 1s) ease-in-out; 32 | --theme-transition-slow-ease: calc(var(--theme-transition-speed-slow) * 1s) ease-in-out; 33 | --theme-transition-xslow-ease: calc(var(--theme-transition-speed-xslow) * 1s) ease-in-out; 34 | } 35 | -------------------------------------------------------------------------------- /src/component/BookmarkLink/index.css: -------------------------------------------------------------------------------- 1 | .bookmark-link:link, 2 | .bookmark-link:visited, 3 | .bookmark-link:hover, 4 | .bookmark-link:focus, 5 | .bookmark-link:active { 6 | font-size: calc((var(--bookmark-scale) / 10) * 1em); 7 | color: hsl(var(--bookmark-group-color-primary-hsl-h), 8 | calc(var(--bookmark-group-color-primary-hsl-s) * 1%), 9 | calc(var(--bookmark-group-color-primary-hsl-l) * 1%)); 10 | text-decoration: none; 11 | transform: scale(var(--bookmark-icon-scale-hidden)); 12 | transition: transform var(--theme-transition-medium-ease) calc(var(--bookmark-link-delay) * 1s); 13 | } 14 | 15 | .bookmark-group-item:focus-within .bookmark-link, 16 | .bookmark-group-active .bookmark-link { 17 | transform: scale(var(--bookmark-icon-scale-visible)); 18 | transition: transform var(--theme-transition-slow-bounce) 0s; 19 | } 20 | 21 | .bookmark-group-item:focus-within .bookmark-link:hover, 22 | .bookmark-group-item:focus-within .bookmark-link:focus, 23 | .bookmark-group-active .bookmark-link:hover, 24 | .bookmark-group-active .bookmark-link:focus { 25 | transform: scale(var(--bookmark-icon-scale-hover)); 26 | transition: transform var(--theme-transition-fast-bounce); 27 | } 28 | 29 | .bookmark-group-item:focus-within .bookmark-link:active, 30 | .bookmark-group-active .bookmark-link:active { 31 | transform: scale(var(--bookmark-icon-scale-active)); 32 | transition: transform var(--theme-transition-fast-bounce); 33 | } 34 | 35 | .bookmark-visual { 36 | min-width: 2em; 37 | min-height: 2em; 38 | display: flex; 39 | align-items: center; 40 | justify-content: center; 41 | } 42 | 43 | .bookmark-letter { 44 | line-height: 1; 45 | } 46 | 47 | .bookmark-image { 48 | max-height: 1em; 49 | } 50 | -------------------------------------------------------------------------------- /src/component/BookmarkLink/index.js: -------------------------------------------------------------------------------- 1 | import { config } from '../../config'; 2 | 3 | import './index.css'; 4 | 5 | export const BookmarkLink = function(linkData, listCount, index) { 6 | 7 | this.node = { 8 | link: document.createElement('a'), 9 | visual: document.createElement('div'), 10 | letter: document.createElement('div'), 11 | icon: document.createElement('span'), 12 | image: document.createElement('img') 13 | } 14 | 15 | this.render = () => { 16 | 17 | this.node.link.classList.add('bookmark-link'); 18 | 19 | switch (config.bookmark.direction) { 20 | 21 | case 'left': 22 | this.node.link.style.setProperty('--bookmark-link-delay', ((listCount - index) / 20)); 23 | break; 24 | 25 | case 'right': 26 | this.node.link.style.setProperty('--bookmark-link-delay', ((listCount - (listCount - index)) / 20)); 27 | break; 28 | 29 | } 30 | 31 | this.node.link.href = linkData.url; 32 | 33 | if (config.bookmark.newTab) { 34 | 35 | this.node.link.setAttribute('target', '_blank'); 36 | 37 | }; 38 | 39 | this.node.visual.classList.add('bookmark-visual'); 40 | 41 | this.node.letter.classList.add('bookmark-letter'); 42 | 43 | this.node.icon.classList.add('bookmark-icon'); 44 | 45 | this.node.image.classList.add('bookmark-image'); 46 | 47 | if ('letter' in linkData) { 48 | 49 | this.node.letter.textContent = linkData.letter; 50 | 51 | this.node.visual.appendChild(this.node.letter); 52 | 53 | }; 54 | 55 | if ('icon' in linkData) { 56 | 57 | const iconClassList = linkData.icon.split(' '); 58 | 59 | iconClassList.forEach(className => { 60 | 61 | this.node.icon.classList.add(className); 62 | 63 | }); 64 | 65 | this.node.visual.appendChild(this.node.icon); 66 | 67 | }; 68 | 69 | if ('image' in linkData) { 70 | 71 | this.node.image.src = linkData.image; 72 | 73 | this.node.visual.appendChild(this.node.image); 74 | 75 | }; 76 | 77 | this.node.link.appendChild(this.node.visual); 78 | 79 | } 80 | 81 | this.link = () => this.node.link; 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/component/BookmarkOpenAll/index.js: -------------------------------------------------------------------------------- 1 | import { config } from '../../config'; 2 | 3 | import './index.css'; 4 | 5 | export const BookmarkOpenAll = function(bookmarkGroupData) { 6 | 7 | this.node = { 8 | openAll: document.createElement('div'), 9 | button: document.createElement('button'), 10 | icon: document.createElement('span'), 11 | line: document.createElement('div'), 12 | lineTop: document.createElement('span'), 13 | lineBottom: document.createElement('span') 14 | } 15 | 16 | this.open = () => { 17 | 18 | if ('tabs' in chrome) { 19 | 20 | if (config.bookmark.newTab) { 21 | 22 | bookmarkGroupData.list.forEach(item => { 23 | chrome.tabs.create({ url: item.url, active: false }); 24 | }); 25 | 26 | } else { 27 | 28 | const first = bookmarkGroupData.list.shift(); 29 | 30 | bookmarkGroupData.list.forEach(item => { 31 | chrome.tabs.create({ url: item.url, active: false }); 32 | }); 33 | 34 | window.location.href = first.url; 35 | 36 | } 37 | 38 | } else { 39 | 40 | bookmarkGroupData.list.forEach((link, index) => { 41 | window.open(link.url, '_blank'); 42 | }); 43 | 44 | } 45 | 46 | } 47 | 48 | this.render = () => { 49 | 50 | this.node.openAll.classList.add('bookmark-open-all'); 51 | 52 | this.node.button.classList.add('bookmark-open-all-button'); 53 | 54 | this.node.button.addEventListener('click', () => { 55 | 56 | this.open(); 57 | 58 | }); 59 | 60 | this.node.icon.classList.add('fa-bolt'); 61 | 62 | this.node.icon.classList.add('fa-solid'); 63 | 64 | this.node.line.classList.add('bookmark-open-all-line'); 65 | 66 | this.node.lineTop.classList.add('bookmark-open-all-line-top'); 67 | 68 | this.node.lineBottom.classList.add('bookmark-open-all-line-bottom'); 69 | 70 | this.node.line.appendChild(this.node.lineTop); 71 | 72 | this.node.line.appendChild(this.node.lineBottom); 73 | 74 | this.node.button.appendChild(this.node.icon); 75 | 76 | this.node.openAll.appendChild(this.node.line); 77 | 78 | this.node.openAll.appendChild(this.node.button); 79 | 80 | } 81 | 82 | this.openAll = () => this.node.openAll; 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/component/Theme/index.js: -------------------------------------------------------------------------------- 1 | import { config } from '../../config'; 2 | import { applyCSSVar } from '../utility/applyCSSVar'; 3 | import { trimString } from '../utility/trimString'; 4 | 5 | import WebFont from 'webfontloader'; 6 | 7 | import './index.css'; 8 | 9 | export const Theme = function() { 10 | 11 | this.style = () => { 12 | 13 | applyCSSVar('--theme-scale', config.theme.scale); 14 | 15 | applyCSSVar('--theme-text-hsl-h', config.theme.text.hsl[0]); 16 | applyCSSVar('--theme-text-hsl-s', config.theme.text.hsl[1]); 17 | applyCSSVar('--theme-text-hsl-l', config.theme.text.hsl[2]); 18 | 19 | applyCSSVar('--theme-accent-hsl-h', config.theme.accent.hsl[0]); 20 | applyCSSVar('--theme-accent-hsl-s', config.theme.accent.hsl[1]); 21 | applyCSSVar('--theme-accent-hsl-l', config.theme.accent.hsl[2]); 22 | 23 | applyCSSVar('--theme-transition-speed-xfast', (config.theme.transition.speed.xfast / 100)); 24 | applyCSSVar('--theme-transition-speed-fast', (config.theme.transition.speed.fast / 100)); 25 | applyCSSVar('--theme-transition-speed-medium', (config.theme.transition.speed.medium / 100)); 26 | applyCSSVar('--theme-transition-speed-slow', (config.theme.transition.speed.slow / 100)); 27 | applyCSSVar('--theme-transition-speed-xslow', (config.theme.transition.speed.xslow / 100)); 28 | 29 | applyCSSVar('--theme-easing-bounce', `${config.theme.easing.bounce[0]}, ${config.theme.easing.bounce[1]}, ${config.theme.easing.bounce[2]}, ${config.theme.easing.bounce[3]}`); 30 | 31 | applyCSSVar('--theme-font', `"${config.theme.font}"`); 32 | 33 | applyCSSVar('--theme-bookmark-background-color-hsla-h', config.theme.bookmark.background.color.hsla[0]); 34 | applyCSSVar('--theme-bookmark-background-color-hsla-s', config.theme.bookmark.background.color.hsla[1]); 35 | applyCSSVar('--theme-bookmark-background-color-hsla-l', config.theme.bookmark.background.color.hsla[2]); 36 | applyCSSVar('--theme-bookmark-background-color-hsla-a', config.theme.bookmark.background.color.hsla[3]); 37 | 38 | applyCSSVar('--theme-bookmark-group-background-blur', config.theme.bookmark.background.blur); 39 | 40 | } 41 | 42 | this.render = () => { 43 | 44 | this.style(); 45 | 46 | WebFont.load({ 47 | // fontloading: (familyName, fvd) => { console.log('fontloading:', familyName); }, 48 | google: { families: [trimString(config.theme.font) + ':100,100i,200,200i,300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i'] } 49 | }); 50 | 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/component/Background/index.js: -------------------------------------------------------------------------------- 1 | import { config } from '../../config'; 2 | import { isValidString } from '../utility/isValidString'; 3 | import { applyCSSVar } from '../utility/applyCSSVar'; 4 | 5 | import './index.css'; 6 | 7 | export const Background = function() { 8 | 9 | this.node = { 10 | background: document.createElement('div'), 11 | color: document.createElement('div'), 12 | image: document.createElement('div'), 13 | gradient: document.createElement('div') 14 | } 15 | 16 | this.style = () => { 17 | 18 | applyCSSVar('--background-color-hsl-h', config.background.color.hsl[0]); 19 | applyCSSVar('--background-color-hsl-s', config.background.color.hsl[1]); 20 | applyCSSVar('--background-color-hsl-l', config.background.color.hsl[2]); 21 | 22 | if (isValidString(config.background.image.url)) { 23 | 24 | applyCSSVar('--background-image-url', `url(${config.background.image.url})`); 25 | 26 | applyCSSVar('--background-image-opacity', config.background.image.opacity); 27 | 28 | applyCSSVar('--background-image-grayscale', config.background.image.grayscale); 29 | 30 | applyCSSVar('--background-image-blur', config.background.image.blur); 31 | 32 | } 33 | 34 | if (config.background.gradient.color.length > 0) { 35 | 36 | applyCSSVar('--background-gradient-degree', `${config.background.gradient.degree}deg`); 37 | 38 | let gradientColor = ''; 39 | 40 | config.background.gradient.color.forEach((gradientColorItem, index) => { 41 | 42 | gradientColor = gradientColor + `hsla(${gradientColorItem.hsla[0]}, ${gradientColorItem.hsla[1]}%, ${gradientColorItem.hsla[2]}%, ${gradientColorItem.hsla[3]}) ${gradientColorItem.position}%`; 43 | 44 | if (index < config.background.gradient.color.length - 1) { 45 | 46 | gradientColor = gradientColor + ', '; 47 | 48 | }; 49 | 50 | }); 51 | 52 | applyCSSVar('--background-gradient-color', gradientColor); 53 | 54 | } 55 | 56 | } 57 | 58 | this.render = (element) => { 59 | 60 | this.style(); 61 | 62 | this.node.background.classList.add('background'); 63 | 64 | this.node.color.classList.add('background-color'); 65 | 66 | this.node.image.classList.add('background-image'); 67 | 68 | this.node.gradient.classList.add('background-gradient'); 69 | 70 | this.node.background.appendChild(this.node.color); 71 | 72 | this.node.background.appendChild(this.node.image); 73 | 74 | this.node.background.appendChild(this.node.gradient); 75 | 76 | element.appendChild(this.node.background); 77 | 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/component/utility/node.js: -------------------------------------------------------------------------------- 1 | export const node = (string, node) => { 2 | 3 | // set element 4 | let tag; 5 | 6 | if (string.indexOf('|') > 0) { 7 | tag = string.slice(0, string.indexOf('|')); 8 | } else { 9 | tag = string; 10 | } 11 | 12 | let text = false; 13 | 14 | if (tag.indexOf(':') > 0) { 15 | // regex 16 | // find all : and split 17 | // ignore all \: 18 | let pair = tag.split(/:(?!.*:\\)/); 19 | tag = pair[0]; 20 | // replace \: with : 21 | text = pair[1].replace('\\', ':'); 22 | } 23 | 24 | let element = document.createElement(tag); 25 | 26 | if (text && text != '') { 27 | element.innerHTML = text; 28 | } 29 | 30 | let attributes = string.slice(string.indexOf('|') + 1, string.length).split(','); 31 | 32 | // set attributes 33 | if (string.indexOf('|') > 0 && string.indexOf('|') < string.length - 1) { 34 | 35 | attributes.forEach((item, i) => { 36 | if (item.indexOf(':') > 0) { 37 | // if key and value 38 | var pair = item.substring(0, item.indexOf(':')) + ',' + item.substring(item.indexOf(':') + 1, item.length); 39 | pair = pair.split(','); 40 | attributes[i] = { 41 | key: pair[0], 42 | value: pair[1] 43 | }; 44 | } else { 45 | // if key only 46 | attributes[i] = { 47 | key: item, 48 | value: undefined 49 | }; 50 | } 51 | }); 52 | 53 | attributes.forEach((item) => { 54 | if ('key' in item && item.key != undefined && 'value' in item && item.value != undefined) { 55 | element.setAttribute(item.key, item.value); 56 | } else if ('key' in item && item.key != undefined) { 57 | element.setAttribute(item.key, ''); 58 | } 59 | }); 60 | 61 | } 62 | 63 | if (node) { 64 | 65 | if (typeof node != 'string') { 66 | 67 | if (node.length > 0) { 68 | 69 | node.forEach((item) => { 70 | 71 | if (item instanceof HTMLElement) { 72 | 73 | element.appendChild(item); 74 | 75 | } else { 76 | 77 | let div = document.createElement('div'); 78 | 79 | div.innerHTML = item; 80 | 81 | element.appendChild(div.firstChild); 82 | 83 | } 84 | 85 | }); 86 | 87 | } else { 88 | 89 | if (node instanceof HTMLElement) { 90 | 91 | element.appendChild(node); 92 | 93 | } else { 94 | 95 | let div = document.createElement('div'); 96 | 97 | div.innerHTML = node; 98 | 99 | element.appendChild(div.firstChild); 100 | 101 | } 102 | 103 | } 104 | 105 | } 106 | 107 | } 108 | 109 | return element; 110 | }; 111 | -------------------------------------------------------------------------------- /src/component/Bookmark/index.js: -------------------------------------------------------------------------------- 1 | import { config } from '../../config'; 2 | import { BookmarkGroup } from '../BookmarkGroup'; 3 | import { applyCSSVar } from '../utility/applyCSSVar'; 4 | 5 | import './index.css'; 6 | 7 | export const Bookmark = function() { 8 | 9 | this.node = { 10 | bookmark: document.createElement('div'), 11 | group: document.createElement('div'), 12 | panel: document.createElement('div'), 13 | allGroup: [] 14 | } 15 | 16 | this.style = () => { 17 | 18 | applyCSSVar('--bookmark-panel', config.bookmark.panel); 19 | 20 | applyCSSVar('--bookmark-scale', config.bookmark.scale); 21 | 22 | applyCSSVar('--bookmark-icon-scale-visible', config.bookmark.iconScaleVisible); 23 | 24 | applyCSSVar('--bookmark-icon-scale-hidden', config.bookmark.iconScaleHidden); 25 | 26 | applyCSSVar('--bookmark-icon-scale-hover', config.bookmark.iconScaleHover); 27 | 28 | applyCSSVar('--bookmark-icon-scale-active', config.bookmark.iconScaleActive); 29 | 30 | applyCSSVar('--bookmark-icon-spacing', config.bookmark.iconSpacing); 31 | 32 | applyCSSVar('--bookmark-group-spacing', config.bookmark.groupSpacing); 33 | 34 | applyCSSVar('--bookmark-tab-spacing', config.bookmark.tabSpacing); 35 | 36 | } 37 | 38 | this.populateGroup = () => { 39 | 40 | config.bookmark.group.forEach(groupItem => { 41 | 42 | const bookmarkGroup = new BookmarkGroup(groupItem, this.node.allGroup); 43 | 44 | bookmarkGroup.render(); 45 | 46 | this.node.allGroup.push(bookmarkGroup); 47 | 48 | this.node.group.appendChild(bookmarkGroup.group()); 49 | 50 | }); 51 | 52 | } 53 | 54 | this.render = (element) => { 55 | 56 | this.style(); 57 | 58 | this.populateGroup(); 59 | 60 | this.node.bookmark.classList.add('bookmark'); 61 | 62 | this.node.group.classList.add('bookmark-group'); 63 | 64 | if (!config.bookmark.alwaysVisible) { 65 | 66 | this.node.group.addEventListener('mouseleave', () => { 67 | 68 | config.bookmark.group.forEach(bookmarkGroup => { 69 | 70 | bookmarkGroup.active = false; 71 | 72 | }); 73 | 74 | this.node.allGroup.forEach(group => { 75 | 76 | group.renderActive(); 77 | 78 | }); 79 | 80 | }); 81 | 82 | } 83 | 84 | this.node.panel.classList.add('bookmark-panel'); 85 | 86 | switch (config.bookmark.direction) { 87 | 88 | case 'left': 89 | this.node.panel.classList.add('bookmark-panel-left'); 90 | break; 91 | 92 | case 'right': 93 | this.node.panel.classList.add('bookmark-panel-right'); 94 | break; 95 | 96 | } 97 | 98 | this.node.bookmark.appendChild(this.node.panel); 99 | 100 | this.node.bookmark.appendChild(this.node.group); 101 | 102 | element.appendChild(this.node.bookmark); 103 | 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/component/utility/wordNumber.js: -------------------------------------------------------------------------------- 1 | export const wordNumber = (number) => { 2 | 3 | const ten = 10; 4 | 5 | const oneHundred = 100; 6 | 7 | const oneThousand = 1000; 8 | 9 | const oneMillion = 1000000; 10 | 11 | const oneBillion = 1000000000; // 1,000,000,000 (9) 12 | 13 | const oneTrillion = 1000000000000; // 1,000,000,000,000 (12) 14 | 15 | const oneQuadrillion = 1000000000000000; // 1,000,000,000,000,000 (15) 16 | 17 | const max = 9007199254740992; // 9,007,199,254,740,992 (15) 18 | 19 | const lessThanTwenty = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Eleven', 'Twelve', 'Thirteen', 'Fourteen', 'Fifteen', 'Sixteen', 'Seventeen', 'Eighteen', 'Nineteen']; 20 | 21 | const tenthsLessThanHundred = ['Zero', 'Ten', 'Twenty', 'Thirty', 'Forty', 'Fifty', 'Sixty', 'Seventy', 'Eighty', 'Ninety']; 22 | 23 | 24 | const generateWords = function(number) { 25 | 26 | let remainder; 27 | 28 | let word; 29 | 30 | let words = arguments[1]; 31 | 32 | // We’re done 33 | if (number === 0) { 34 | return !words ? 'Zero' : words.join(' ').replace(/,$/, ''); 35 | } 36 | 37 | // First run 38 | if (!words) { 39 | words = []; 40 | } 41 | 42 | // If negative, prepend “minus” 43 | if (number < 0) { 44 | words.push('minus'); 45 | number = Math.abs(number); 46 | } 47 | 48 | if (number < 20) { 49 | remainder = 0; 50 | word = lessThanTwenty[number]; 51 | } else if (number < oneHundred) { 52 | remainder = number % ten; 53 | word = tenthsLessThanHundred[Math.floor(number / ten)]; 54 | // In case of remainder, we need to handle it here to be able to add the “-” 55 | if (remainder) { 56 | word += '-' + lessThanTwenty[remainder]; 57 | remainder = 0; 58 | } 59 | } else if (number < oneThousand) { 60 | remainder = number % oneHundred; 61 | word = generateWords(Math.floor(number / oneHundred)) + ' Hundred'; 62 | } else if (number < oneMillion) { 63 | remainder = number % oneThousand; 64 | word = generateWords(Math.floor(number / oneThousand)) + ' Thousand,'; 65 | } else if (number < oneBillion) { 66 | remainder = number % oneMillion; 67 | word = generateWords(Math.floor(number / oneMillion)) + ' Million,'; 68 | } else if (number < oneTrillion) { 69 | remainder = number % oneBillion; 70 | word = generateWords(Math.floor(number / oneBillion)) + ' Billion,'; 71 | } else if (number < oneQuadrillion) { 72 | remainder = number % oneTrillion; 73 | word = generateWords(Math.floor(number / oneTrillion)) + ' Trillion,'; 74 | } else if (number <= max) { 75 | remainder = number % oneQuadrillion; 76 | word = generateWords(Math.floor(number / oneQuadrillion)) + ' Quadrillion,'; 77 | } 78 | 79 | words.push(word); 80 | 81 | return generateWords(remainder, words); 82 | }; 83 | 84 | var num = parseInt(number, 10); 85 | 86 | return generateWords(num); 87 | }; 88 | -------------------------------------------------------------------------------- /src/component/BookmarkOpenAll/index.css: -------------------------------------------------------------------------------- 1 | .bookmark-open-all { 2 | align-self: stretch; 3 | position: relative; 4 | } 5 | 6 | .bookmark-open-all-button { 7 | background-color: transparent; 8 | border: 0; 9 | position: absolute; 10 | top: 50%; 11 | left: 50%; 12 | font-size: 1.5em; 13 | color: hsl(var(--bookmark-group-color-secondary-hsl-h), 14 | calc(var(--bookmark-group-color-secondary-hsl-s) * 1%), 15 | calc(var(--bookmark-group-color-secondary-hsl-l) * 1%)); 16 | cursor: pointer; 17 | transform-origin: center; 18 | transform: translate(-50%, -50%) scale(0); 19 | transition: color var(--theme-transition-xfast-ease), transform var(--theme-transition-fast-ease) calc(var(--theme-transition-speed-fast) * 1s); 20 | } 21 | 22 | .bookmark-group-item:focus-within .bookmark-open-all-button, 23 | .bookmark-group-active .bookmark-open-all-button { 24 | transform: translate(-50%, -50%) scale(1); 25 | transition: color var(--theme-transition-xfast-ease), transform var(--theme-transition-xslow-bounce) 0s; 26 | } 27 | 28 | .bookmark-group-item:focus-within .bookmark-open-all-button:hover, 29 | .bookmark-group-item:focus-within .bookmark-open-all-button:focus, 30 | .bookmark-group-active .bookmark-open-all-button:hover, 31 | .bookmark-group-active .bookmark-open-all-button:focus { 32 | outline: none; 33 | transform: translate(-50%, -50%) scale(1.4); 34 | transition: transform var(--theme-transition-fast-bounce); 35 | } 36 | 37 | .bookmark-group-item:focus-within .bookmark-open-all-button:active, 38 | .bookmark-group-active .bookmark-open-all-button:active { 39 | transform: translate(-50%, -50%) scale(1); 40 | transition: transform var(--theme-transition-fast-bounce); 41 | } 42 | 43 | .bookmark-open-all-line { 44 | flex-shrink: 0; 45 | position: relative; 46 | display: block; 47 | width: 0.25em; 48 | height: 100%; 49 | } 50 | 51 | .bookmark-open-all-line-top, 52 | .bookmark-open-all-line-bottom { 53 | width: 100%; 54 | height: 0; 55 | display: block; 56 | position: absolute; 57 | left: 50%; 58 | border-radius: 1em; 59 | transform: translateX(-50%); 60 | background-color: hsl(var(--bookmark-group-color-secondary-hsl-h), 61 | calc((var(--bookmark-group-color-secondary-hsl-s) - 30) * 1%), 62 | calc((var(--bookmark-group-color-secondary-hsl-l) - 20) * 1%)); 63 | transition: height var(--theme-transition-fast-ease), background-color var(--theme-transition-medium-ease); 64 | } 65 | 66 | .bookmark-open-all-line-top { 67 | bottom: calc(50% + 1.5em); 68 | } 69 | 70 | .bookmark-open-all-line-bottom { 71 | top: calc(50% + 1.5em); 72 | } 73 | 74 | .bookmark-group-item:focus-within .bookmark-open-all-line-top, 75 | .bookmark-group-item:focus-within .bookmark-open-all-line-bottom, 76 | .bookmark-group-active .bookmark-open-all-line-top, 77 | .bookmark-group-active .bookmark-open-all-line-bottom { 78 | background-color: hsl(var(--bookmark-group-color-secondary-hsl-h), 79 | calc(var(--bookmark-group-color-secondary-hsl-s) * 1%), 80 | calc(var(--bookmark-group-color-secondary-hsl-l) * 1%)); 81 | height: 40%; 82 | transition: height var(--theme-transition-medium-bounce) calc((var(--theme-transition-speed-xfast) / 2) * 1s), background-color var(--theme-transition-medium-ease); 83 | } 84 | -------------------------------------------------------------------------------- /src/component/utility/convertColor.js: -------------------------------------------------------------------------------- 1 | export const convertColor = { 2 | rgb: {}, 3 | hsl: {}, 4 | hex: {} 5 | }; 6 | 7 | convertColor.rgb.hsl = (rgb) => { 8 | var r = rgb.r / 255; 9 | var g = rgb.g / 255; 10 | var b = rgb.b / 255; 11 | var min = Math.min(r, g, b); 12 | var max = Math.max(r, g, b); 13 | var delta = max - min; 14 | var h; 15 | var s; 16 | 17 | if (max === min) { 18 | h = 0; 19 | } else if (r === max) { 20 | h = (g - b) / delta; 21 | } else if (g === max) { 22 | h = 2 + (b - r) / delta; 23 | } else if (b === max) { 24 | h = 4 + (r - g) / delta; 25 | } 26 | 27 | h = Math.min(h * 60, 360); 28 | 29 | if (h < 0) { 30 | h += 360; 31 | } 32 | 33 | var l = (min + max) / 2; 34 | 35 | if (max === min) { 36 | s = 0; 37 | } else if (l <= 0.5) { 38 | s = delta / (max + min); 39 | } else { 40 | s = delta / (2 - max - min); 41 | } 42 | 43 | return { 44 | h: Math.round(h), 45 | s: Math.round(s * 100), 46 | l: Math.round(l * 100) 47 | }; 48 | }; 49 | 50 | convertColor.rgb.hex = (args) => { 51 | var integer = ((Math.round(args.r) & 0xFF) << 16) + 52 | ((Math.round(args.g) & 0xFF) << 8) + 53 | (Math.round(args.b) & 0xFF); 54 | 55 | var string = integer.toString(16); 56 | 57 | return '#' + '000000'.substring(string.length) + string; 58 | }; 59 | 60 | convertColor.hsl.rgb = (hsl) => { 61 | var h = hsl.h / 360; 62 | var s = hsl.s / 100; 63 | var l = hsl.l / 100; 64 | var t2; 65 | var t3; 66 | var val; 67 | 68 | if (s === 0) { 69 | val = l * 255; 70 | return { 71 | r: Math.round(val), 72 | g: Math.round(val), 73 | b: Math.round(val) 74 | }; 75 | } 76 | 77 | if (l < 0.5) { 78 | t2 = l * (1 + s); 79 | } else { 80 | t2 = l + s - l * s; 81 | } 82 | 83 | var t1 = 2 * l - t2; 84 | 85 | var rgb = [0, 0, 0]; 86 | 87 | for (var i = 0; i < 3; i++) { 88 | t3 = h + 1 / 3 * -(i - 1); 89 | 90 | if (t3 < 0) { 91 | t3++; 92 | } 93 | 94 | if (t3 > 1) { 95 | t3--; 96 | } 97 | 98 | if (6 * t3 < 1) { 99 | val = t1 + (t2 - t1) * 6 * t3; 100 | } else if (2 * t3 < 1) { 101 | val = t2; 102 | } else if (3 * t3 < 2) { 103 | val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; 104 | } else { 105 | val = t1; 106 | } 107 | 108 | rgb[i] = val * 255; 109 | } 110 | 111 | return { 112 | r: Math.round(rgb[0]), 113 | g: Math.round(rgb[1]), 114 | b: Math.round(rgb[2]) 115 | }; 116 | }; 117 | 118 | convertColor.hex.rgb = (args) => { 119 | var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); 120 | 121 | if (!match) { 122 | return { 123 | r: 0, 124 | g: 0, 125 | b: 0 126 | }; 127 | } 128 | 129 | var colorString = match[0]; 130 | 131 | if (match[0].length === 3) { 132 | colorString = colorString.split('').map((char) => { 133 | return char + char; 134 | }).join(''); 135 | } 136 | 137 | var integer = parseInt(colorString, 16); 138 | var r = (integer >> 16) & 0xFF; 139 | var g = (integer >> 8) & 0xFF; 140 | var b = integer & 0xFF; 141 | 142 | return { 143 | r: r, 144 | g: g, 145 | b: b 146 | }; 147 | }; 148 | -------------------------------------------------------------------------------- /src/component/BookmarkGroup/index.css: -------------------------------------------------------------------------------- 1 | .bookmark-group { 2 | grid-column: 1 / -1; 3 | grid-row: 1 / -1; 4 | display: flex; 5 | flex-direction: column; 6 | gap: calc((var(--bookmark-group-spacing) / 10) * 1em); 7 | padding: calc((var(--bookmark-group-spacing) / 10) * 1em) 0; 8 | z-index: 2; 9 | } 10 | 11 | .bookmark-group-item { 12 | display: grid; 13 | grid-template-columns: calc(var(--bookmark-panel) * 1%) 1fr calc(var(--bookmark-panel) * 1%); 14 | position: relative; 15 | } 16 | 17 | .bookmark-group-tab { 18 | grid-row: 1 / 2; 19 | display: flex; 20 | align-items: center; 21 | justify-content: flex-end; 22 | color: hsl(var(--bookmark-group-color-primary-hsl-h), 23 | calc(var(--bookmark-group-color-primary-hsl-s) * 1%), 24 | calc(var(--bookmark-group-color-primary-hsl-l) * 1%)); 25 | gap: calc((var(--bookmark-tab-spacing) / 10) * 1em); 26 | padding: 0 calc((var(--bookmark-tab-spacing) / 10) * 1em); 27 | } 28 | 29 | .bookmark-group-item-left .bookmark-group-tab { 30 | grid-column: 1 / 2; 31 | flex-direction: row; 32 | text-align: right; 33 | } 34 | 35 | .bookmark-group-item-right .bookmark-group-tab { 36 | grid-column: 3 / 4; 37 | flex-direction: row-reverse; 38 | text-align: left; 39 | } 40 | 41 | .bookmark-group-label { 42 | display: flex; 43 | flex-direction: column; 44 | justify-content: center; 45 | overflow: hidden; 46 | user-select: none; 47 | } 48 | 49 | .bookmark-group-name { 50 | font-size: 2em; 51 | margin: 0; 52 | padding: 0; 53 | font-family: var(--theme-font), sans-serif; 54 | font-weight: var(--font-weight-light); 55 | color: hsl(var(--bookmark-group-color-primary-hsl-h), 56 | calc((var(--bookmark-group-color-primary-hsl-s) - 30) * 1%), 57 | calc((var(--bookmark-group-color-primary-hsl-l) - 20) * 1%)); 58 | overflow: hidden; 59 | text-overflow: ellipsis; 60 | transition: color var(--theme-transition-fast-ease); 61 | } 62 | 63 | .bookmark-group-active .bookmark-group-name { 64 | color: hsl(var(--bookmark-group-color-primary-hsl-h), 65 | calc(var(--bookmark-group-color-primary-hsl-s) * 1%), 66 | calc(var(--bookmark-group-color-primary-hsl-l) * 1%)); 67 | } 68 | 69 | .bookmark-group-description { 70 | font-size: 1em; 71 | margin: 0; 72 | padding: 0; 73 | font-family: var(--theme-font), sans-serif; 74 | font-weight: var(--font-weight-medium); 75 | color: hsl(var(--bookmark-group-color-secondary-hsl-h), 76 | calc((var(--bookmark-group-color-secondary-hsl-s) - 30) * 1%), 77 | calc((var(--bookmark-group-color-secondary-hsl-l) - 20) * 1%)); 78 | overflow: hidden; 79 | text-overflow: ellipsis; 80 | transition: color var(--theme-transition-fast-ease); 81 | } 82 | 83 | .bookmark-group-active .bookmark-group-description { 84 | color: hsl(var(--bookmark-group-color-secondary-hsl-h), 85 | calc(var(--bookmark-group-color-secondary-hsl-s) * 1%), 86 | calc(var(--bookmark-group-color-secondary-hsl-l) * 1%)); 87 | } 88 | 89 | .bookmark-group-list { 90 | grid-row: 1 / 2; 91 | display: flex; 92 | flex-wrap: wrap; 93 | flex-direction: row; 94 | gap: calc((var(--bookmark-icon-spacing) / 10) * 1em); 95 | padding: 0 calc((var(--bookmark-icon-spacing) / 10) * 1em); 96 | align-items: center; 97 | } 98 | 99 | .bookmark-group-item-left .bookmark-group-list { 100 | grid-column: 2 / 4; 101 | justify-content: flex-start; 102 | } 103 | 104 | .bookmark-group-item-right .bookmark-group-list { 105 | grid-column: 1 / 3; 106 | justify-content: flex-end; 107 | } 108 | -------------------------------------------------------------------------------- /src/style/typography/index.css: -------------------------------------------------------------------------------- 1 | h1, 2 | h2, 3 | h3, 4 | h4, 5 | h5, 6 | h6 { 7 | margin: 0 0 1em 0; 8 | font-weight: normal; 9 | line-height: 1.6; 10 | color: hsl(var(--theme-text)); 11 | } 12 | 13 | h1 { 14 | font-size: 1.5em; 15 | font-family: var(--theme-font); 16 | font-weight: var(--font-weight-light); 17 | font-style: normal; 18 | } 19 | 20 | h2 { 21 | font-size: 1.3em; 22 | font-family: var(--theme-font); 23 | font-weight: var(--font-weight-light); 24 | font-style: normal; 25 | } 26 | 27 | h3 { 28 | font-size: 1.1em; 29 | font-family: var(--theme-font); 30 | font-weight: var(--font-weight-light); 31 | font-style: normal; 32 | } 33 | 34 | h4 { 35 | font-size: 1em; 36 | font-family: var(--theme-font); 37 | font-weight: var(--font-weight-medium); 38 | font-style: normal; 39 | } 40 | 41 | h5 { 42 | font-size: 1em; 43 | font-family: var(--theme-font); 44 | font-weight: var(--font-weight-medium); 45 | font-style: normal; 46 | font-weight: var(--font-weight-bold); 47 | } 48 | 49 | h6 { 50 | font-size: 0.75em; 51 | font-family: var(--theme-font); 52 | font-weight: var(--font-weight-medium); 53 | font-style: normal; 54 | font-weight: var(--font-weight-bold); 55 | } 56 | 57 | p { 58 | color: hsl(var(--theme-text)); 59 | margin: 0; 60 | line-height: 1.6; 61 | } 62 | 63 | hr { 64 | border: 0; 65 | border-top: 2px; 66 | border-radius: 0.5em; 67 | margin: 1em; 68 | clear: both; 69 | transition: border-color var(--theme-transition-xfast-ease); 70 | } 71 | 72 | b, 73 | caption, 74 | strong { 75 | color: hsl(var(--theme-text)); 76 | font-family: var(--theme-font); 77 | font-weight: var(--font-weight-bold); 78 | } 79 | 80 | i { 81 | font-style: italic; 82 | } 83 | 84 | a { 85 | color: hsl(var(--theme-text)); 86 | text-decoration: underline; 87 | transition: text-decoration var(--theme-transition-xfast-ease); 88 | } 89 | 90 | a:link, 91 | a:visited { 92 | color: hsl(var(--theme-text)); 93 | } 94 | 95 | a:focus { 96 | text-decoration-color: hsl(var(--theme-text)); 97 | outline: none; 98 | } 99 | 100 | a:hover { 101 | color: hsl(var(--theme-text)); 102 | text-decoration-color: rgb(var(--theme-accent)); 103 | } 104 | 105 | a:active { 106 | color: hsl(var(--theme-text)); 107 | text-decoration-color: hsl(var(--theme-text)); 108 | } 109 | 110 | ol, 111 | ul { 112 | margin: 0; 113 | padding: 0 0 0 1.5em; 114 | } 115 | 116 | ol:not(:last-child), 117 | ul:not(:last-child) { 118 | margin-bottom: 1em; 119 | } 120 | 121 | li { 122 | margin: 0; 123 | } 124 | 125 | li>ul, 126 | li>ol { 127 | margin: 0; 128 | } 129 | 130 | li:not(:last-child) { 131 | margin-bottom: 0.5em; 132 | } 133 | 134 | li>ul:not(:last-child), 135 | li>ol:not(:last-child) { 136 | margin-bottom: 0.5em; 137 | } 138 | 139 | table { 140 | border: 0; 141 | margin: 0 0 1em; 142 | padding: 0; 143 | width: 100%; 144 | border-spacing: 0; 145 | } 146 | 147 | table thead tr td, 148 | table thead tr th { 149 | background-color: hsl(var(--theme-text)); 150 | border: 0; 151 | border-bottom: 1px solid hsl(var(--theme-text)); 152 | padding: 0.5em; 153 | margin: 0; 154 | text-align: left; 155 | font-family: var(--theme-font); 156 | font-weight: var(--font-weight-bold); 157 | font-style: normal; 158 | box-sizing: border-box; 159 | } 160 | 161 | table tr:nth-child(odd) { 162 | background-color: hsl(var(--theme-text)); 163 | } 164 | 165 | table tbody tr td, 166 | table tbody tr th { 167 | padding: 0.25em 0.5em; 168 | margin: 0; 169 | border: 0; 170 | text-align: left; 171 | box-sizing: border-box; 172 | } 173 | 174 | code { 175 | background-color: hsl(var(--theme-text)); 176 | padding: 0.2em 0.5em; 177 | border-radius: 0.5em; 178 | } 179 | -------------------------------------------------------------------------------- /src/component/BookmarkGroup/index.js: -------------------------------------------------------------------------------- 1 | import { config } from '../../config'; 2 | import { BookmarkLink } from '../BookmarkLink'; 3 | import { BookmarkOpenAll } from '../BookmarkOpenAll'; 4 | 5 | import './index.css'; 6 | 7 | export const BookmarkGroup = function(bookmarkGroupData, allBookmarkGroup) { 8 | 9 | this.node = { 10 | groupItem: document.createElement('div'), 11 | tab: document.createElement('div'), 12 | label: document.createElement('div'), 13 | name: document.createElement('p'), 14 | description: document.createElement('p'), 15 | list: document.createElement('div'), 16 | openAll: null, 17 | allList: [] 18 | } 19 | 20 | this.style = () => { 21 | 22 | this.node.groupItem.style.setProperty('--bookmark-group-color-primary-hsl-h', bookmarkGroupData.color.primary.hsl[0]); 23 | this.node.groupItem.style.setProperty('--bookmark-group-color-primary-hsl-s', bookmarkGroupData.color.primary.hsl[1]); 24 | this.node.groupItem.style.setProperty('--bookmark-group-color-primary-hsl-l', bookmarkGroupData.color.primary.hsl[2]); 25 | 26 | this.node.groupItem.style.setProperty('--bookmark-group-color-secondary-hsl-h', bookmarkGroupData.color.secondary.hsl[0]); 27 | this.node.groupItem.style.setProperty('--bookmark-group-color-secondary-hsl-s', bookmarkGroupData.color.secondary.hsl[1]); 28 | this.node.groupItem.style.setProperty('--bookmark-group-color-secondary-hsl-l', bookmarkGroupData.color.secondary.hsl[2]); 29 | 30 | } 31 | 32 | this.toggleActiveState = () => { 33 | 34 | config.bookmark.group.forEach(bookmarkGroup => { 35 | 36 | bookmarkGroup.active = false; 37 | 38 | }); 39 | 40 | bookmarkGroupData.active = true; 41 | 42 | } 43 | 44 | this.renderActive = () => { 45 | 46 | bookmarkGroupData.active ? this.active() : this.inactive(); 47 | 48 | } 49 | 50 | this.active = () => this.node.groupItem.classList.add('bookmark-group-active') 51 | 52 | this.inactive = () => this.node.groupItem.classList.remove('bookmark-group-active') 53 | 54 | this.render = () => { 55 | 56 | this.node.groupItem.classList.add('bookmark-group-item'); 57 | 58 | switch (config.bookmark.direction) { 59 | 60 | case 'left': 61 | this.node.groupItem.classList.add('bookmark-group-item-left'); 62 | break; 63 | 64 | case 'right': 65 | this.node.groupItem.classList.add('bookmark-group-item-right'); 66 | break; 67 | 68 | } 69 | 70 | if (config.bookmark.alwaysVisible) { 71 | 72 | this.node.groupItem.classList.add('bookmark-group-active'); 73 | 74 | } else { 75 | 76 | this.node.groupItem.addEventListener('mouseenter', () => { 77 | 78 | this.toggleActiveState(); 79 | 80 | allBookmarkGroup.forEach(group => { 81 | 82 | group.renderActive(); 83 | 84 | }); 85 | 86 | }); 87 | 88 | this.renderActive(); 89 | 90 | }; 91 | 92 | this.node.tab.classList.add('bookmark-group-tab'); 93 | 94 | this.node.list.classList.add('bookmark-group-list'); 95 | 96 | this.node.tab.href = "#"; 97 | 98 | this.style(); 99 | 100 | this.node.label.classList.add('bookmark-group-label'); 101 | 102 | this.node.name.classList.add('bookmark-group-name'); 103 | 104 | this.node.description.classList.add('bookmark-group-description'); 105 | 106 | this.node.name.textContent = bookmarkGroupData.name; 107 | 108 | this.node.description.textContent = bookmarkGroupData.description; 109 | 110 | bookmarkGroupData.list.forEach((listItem, index) => { 111 | 112 | const bookmarkLink = new BookmarkLink(listItem, bookmarkGroupData.list.length, index); 113 | 114 | bookmarkLink.render(); 115 | 116 | this.node.allList.push(bookmarkLink.link()); 117 | 118 | this.node.list.appendChild(bookmarkLink.link()); 119 | 120 | }); 121 | 122 | this.node.openAll = new BookmarkOpenAll(bookmarkGroupData); 123 | 124 | this.node.openAll.render(); 125 | 126 | this.node.label.appendChild(this.node.name); 127 | 128 | this.node.label.appendChild(this.node.description); 129 | 130 | this.node.tab.appendChild(this.node.label); 131 | 132 | this.node.tab.appendChild(this.node.openAll.openAll()); 133 | 134 | this.node.groupItem.appendChild(this.node.tab); 135 | 136 | this.node.groupItem.appendChild(this.node.list); 137 | 138 | } 139 | 140 | this.group = () => this.node.groupItem; 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/style/reset/index.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: border-box; 5 | } 6 | 7 | html { 8 | font-family: sans-serif; 9 | line-height: 1.15; 10 | -webkit-text-size-adjust: 100%; 11 | -ms-overflow-style: scrollbar; 12 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 13 | } 14 | 15 | article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { 16 | display: block; 17 | } 18 | 19 | body { 20 | margin: 0; 21 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, Noto Sans, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 22 | font-size: 1rem; 23 | font-weight: 400; 24 | line-height: 1.5; 25 | color: #212529; 26 | text-align: left; 27 | background-color: #000; 28 | } 29 | 30 | [tabindex="-1"]:focus { 31 | outline: none !important; 32 | } 33 | 34 | hr { 35 | box-sizing: content-box; 36 | height: 0; 37 | overflow: visible; 38 | } 39 | 40 | h1, h2, h3, h4, h5, h6 { 41 | margin-top: 0; 42 | margin-bottom: 0.5rem; 43 | } 44 | 45 | p { 46 | margin-top: 0; 47 | margin-bottom: 1rem; 48 | } 49 | 50 | abbr[title], 51 | abbr[data-original-title] { 52 | text-decoration: underline; 53 | -webkit-text-decoration: underline dotted; 54 | text-decoration: underline dotted; 55 | cursor: help; 56 | border-bottom: 0; 57 | text-decoration-skip-ink: none; 58 | } 59 | 60 | address { 61 | margin-bottom: 1rem; 62 | font-style: normal; 63 | line-height: inherit; 64 | } 65 | 66 | ol, 67 | ul, 68 | dl { 69 | margin-top: 0; 70 | margin-bottom: 1rem; 71 | } 72 | 73 | ol ol, 74 | ul ul, 75 | ol ul, 76 | ul ol { 77 | margin-bottom: 0; 78 | } 79 | 80 | dt { 81 | font-weight: 700; 82 | } 83 | 84 | dd { 85 | margin-bottom: .5rem; 86 | margin-left: 0; 87 | } 88 | 89 | blockquote { 90 | margin: 0 0 1rem; 91 | } 92 | 93 | b, 94 | strong { 95 | font-weight: bolder; 96 | } 97 | 98 | small { 99 | font-size: 80%; 100 | } 101 | 102 | sub, 103 | sup { 104 | position: relative; 105 | font-size: 75%; 106 | line-height: 0; 107 | vertical-align: baseline; 108 | } 109 | 110 | sub { 111 | bottom: -.25em; 112 | } 113 | 114 | sup { 115 | top: -.5em; 116 | } 117 | 118 | a { 119 | color: #007bff; 120 | text-decoration: none; 121 | background-color: transparent; 122 | } 123 | 124 | a:hover { 125 | color: #0056b3; 126 | text-decoration: underline; 127 | } 128 | 129 | a:not([href]):not([tabindex]) { 130 | color: inherit; 131 | text-decoration: none; 132 | } 133 | 134 | a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { 135 | color: inherit; 136 | text-decoration: none; 137 | } 138 | 139 | a:not([href]):not([tabindex]):focus { 140 | outline: none; 141 | } 142 | 143 | pre, 144 | code, 145 | kbd, 146 | samp { 147 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 148 | font-size: 1em; 149 | } 150 | 151 | pre { 152 | margin-top: 0; 153 | margin-bottom: 1rem; 154 | overflow: auto; 155 | -ms-overflow-style: scrollbar; 156 | } 157 | 158 | figure { 159 | margin: 0 0 1rem; 160 | } 161 | 162 | img { 163 | vertical-align: middle; 164 | border-style: none; 165 | } 166 | 167 | svg { 168 | overflow: hidden; 169 | vertical-align: middle; 170 | } 171 | 172 | table { 173 | border-collapse: collapse; 174 | } 175 | 176 | caption { 177 | padding-top: 0.75rem; 178 | padding-bottom: 0.75rem; 179 | color: #6c757d; 180 | text-align: left; 181 | caption-side: bottom; 182 | } 183 | 184 | th { 185 | text-align: inherit; 186 | } 187 | 188 | label { 189 | display: inline-block; 190 | margin-bottom: 0; 191 | } 192 | 193 | button { 194 | border-radius: 0; 195 | } 196 | 197 | button:focus { 198 | outline: 1px dotted; 199 | outline: 5px auto -webkit-focus-ring-color; 200 | } 201 | 202 | input, 203 | button, 204 | select, 205 | optgroup, 206 | textarea { 207 | margin: 0; 208 | font-family: inherit; 209 | font-size: inherit; 210 | line-height: inherit; 211 | } 212 | 213 | button, 214 | input { 215 | overflow: visible; 216 | } 217 | 218 | button, 219 | select { 220 | text-transform: none; 221 | } 222 | 223 | button, 224 | [type="button"], 225 | [type="reset"], 226 | [type="submit"] { 227 | -webkit-appearance: button; 228 | } 229 | 230 | button::-moz-focus-inner, 231 | [type="button"]::-moz-focus-inner, 232 | [type="reset"]::-moz-focus-inner, 233 | [type="submit"]::-moz-focus-inner { 234 | padding: 0; 235 | border-style: none; 236 | } 237 | 238 | input[type="radio"], 239 | input[type="checkbox"] { 240 | box-sizing: border-box; 241 | padding: 0; 242 | } 243 | 244 | input[type="date"], 245 | input[type="time"], 246 | input[type="datetime-local"], 247 | input[type="month"] { 248 | -webkit-appearance: listbox; 249 | } 250 | 251 | textarea { 252 | overflow: auto; 253 | resize: vertical; 254 | } 255 | 256 | fieldset { 257 | min-width: 0; 258 | padding: 0; 259 | margin: 0; 260 | border: 0; 261 | } 262 | 263 | legend { 264 | display: block; 265 | width: 100%; 266 | max-width: 100%; 267 | padding: 0; 268 | margin-bottom: .5rem; 269 | font-size: 1.5rem; 270 | line-height: inherit; 271 | color: inherit; 272 | white-space: normal; 273 | } 274 | 275 | progress { 276 | vertical-align: baseline; 277 | } 278 | 279 | [type="number"]::-webkit-inner-spin-button, 280 | [type="number"]::-webkit-outer-spin-button { 281 | height: auto; 282 | } 283 | 284 | [type="search"] { 285 | outline-offset: -2px; 286 | -webkit-appearance: none; 287 | } 288 | 289 | [type="search"]::-webkit-search-decoration { 290 | -webkit-appearance: none; 291 | } 292 | 293 | ::-webkit-file-upload-button { 294 | font: inherit; 295 | -webkit-appearance: button; 296 | } 297 | 298 | output { 299 | display: inline-block; 300 | } 301 | 302 | summary { 303 | display: list-item; 304 | cursor: pointer; 305 | } 306 | 307 | template { 308 | display: none; 309 | } 310 | 311 | [hidden] { 312 | display: none !important; 313 | } 314 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | 3 | theme: { 4 | 5 | // global scale 6 | scale: 38, // range: min:0|max:100 7 | 8 | // accent colours 9 | // not used for much yet, this should be developed as the project grows 10 | // colour defined with HSL: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl 11 | accent: { hsl: [204, 100, 72] }, 12 | 13 | // text colours 14 | // not used for much yet, this should be developed as the project grows 15 | // colour defined with HSL: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl 16 | text: { hsl: [0, 0, 0] }, 17 | 18 | // text font 19 | // works with Google Fonts: https://fonts.google.com/ 20 | // enter the full name of a font as it is displayed on Google Fonts 21 | font: 'Ubuntu', 22 | 23 | // the speed of the different transitions 24 | transition: { speed: { xfast: 10, fast: 20, medium: 30, slow: 40, xslow: 50 } }, // range: min:0|max:* 25 | 26 | // the bounce transition easing bezier curve 27 | easing: { bounce: [0.8, 0.5, 0.2, 2] }, // range: min:0|max:* 28 | 29 | bookmark: { 30 | background: { 31 | 32 | // bookmark left side panel background blur amount 33 | blur: 14, // range: min:0|max:* 34 | 35 | // bookmark left side panel background colour 36 | // colour defined with HSLA: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsla 37 | color: { hsla: [200, 180, 25, 0.2] } 38 | } 39 | } 40 | 41 | }, 42 | 43 | bookmark: { 44 | 45 | // open the bookmarks in a new tab: true or false 46 | newTab: true, // boolean: true|false 47 | 48 | // the bookmark panel and group name position and direction the bookmarks flow 49 | direction: 'left', // string: left|right 50 | 51 | // bookmark icons show when a bookmark group gains focus or on cursor hover 52 | // if set to true bookmarks are always shown 53 | alwaysVisible: false, // boolean: true|false 54 | 55 | // bookmark left side panel size 56 | // this area contains bookmark group name, description and open all button 57 | panel: 35, // range: min:0|max:100 58 | 59 | // bookmark size 60 | scale: 20, // range: min:0|max:* 61 | 62 | // bookmark icon size the bookmark group is in focus 63 | iconScaleVisible: 1, // range: min:*|max:* 64 | 65 | // bookmark icon size when the bookmark group is not in focus 66 | // if alwaysVisible is set to true this setting will not have an effect 67 | iconScaleHidden: 0, // range: min:*|max:* 68 | 69 | // bookmark icon size when the bookmark icon is hovered (:hover) 70 | iconScaleHover: 1.4, // range: min:*|max:* 71 | 72 | // bookmark icon size when the bookmark icon is clicked (:active) 73 | iconScaleActive: 1, // range: min:*|max:* 74 | 75 | // spacing between bookmarks 76 | iconSpacing: 10, // range: min:0|max:* 77 | 78 | // spacing between bookmark group rows 79 | groupSpacing: 30, // range: min:0|max:* 80 | 81 | // spacing between bookmark group name, description and open all button 82 | tabSpacing: 30, // range: min:0|max:* 83 | 84 | // bookmark grouping 85 | // each object in this array definesa group 86 | // add as many objects as needed 87 | // each group contains any number of bookmarks 88 | group: [{ 89 | 90 | // bookmark group name 91 | name: 'Productivity', 92 | 93 | // bookmark group description 94 | description: 'Daily apps', 95 | 96 | // bookmark colours 97 | // colour defined with HSL: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl 98 | color: { 99 | primary: { hsl: [0, 0, 100] }, // colour for group name and icons 100 | secondary: { hsl: [200, 60, 70] } // colour for group description and the open all button 101 | }, 102 | 103 | list: [ 104 | // list of bookmarks in this group 105 | // add as many objects as needed 106 | // the first key defines the visual element of the bookmark 107 | // string: icon|letter|image 108 | 109 | // icon eg: 110 | // use a Font Awesome class name found on https://fontawesome.com/icons 111 | // { icon: 'fa-solid fa-envelope', url: 'https://website.com/' }, 112 | 113 | // letter eg: 114 | // { letter: 'ABC', url: 'https://website.com/' }, 115 | 116 | // image eg: 117 | // { image: 'https://example.com/image.jpg', url: 'https://website.com/' }, 118 | 119 | { icon: 'fa-solid fa-envelope', url: 'https://mail.google.com/' }, 120 | { icon: 'fa-brands fa-slack', url: 'https://slack.com/signin/' }, 121 | { icon: 'fa-brands fa-github', url: 'https://github.com/' }, 122 | { icon: 'fa-brands fa-codepen', url: 'https://codepen.io/' }, 123 | { icon: 'fa-solid fa-diamond', url: 'https://whimsical.com/login/' }, 124 | { icon: 'fa-brands fa-figma', url: 'https://figma.com/' }, 125 | { icon: 'fa-brands fa-dropbox', url: 'https://dropbox.com/' }, 126 | { icon: 'fa-brands fa-google-drive', url: 'https://drive.google.com/' }, 127 | { icon: 'fa-solid fa-calendar-day', url: 'https://calendar.google.com/calendar/' }, 128 | ] 129 | 130 | }, { 131 | name: 'Cool stuff', 132 | description: 'Downtime and media', 133 | color: { primary: { hsl: [0, 0, 100] }, secondary: { hsl: [250, 60, 70] } }, 134 | list: [ 135 | { icon: 'fa-brands fa-reddit-alien', url: 'https://reddit.com/' }, 136 | { icon: 'fa-brands fa-artstation', url: 'https://www.artstation.com/' }, 137 | { icon: 'fa-brands fa-discord', url: 'https://discord.com/' }, 138 | { icon: 'fa-solid fa-paperclip', url: 'https://www.decisionproblem.com/paperclips/' }, 139 | { icon: 'fa-solid fa-dice-d20', url: 'https://zombiefox.github.io/diceRoller/' }, 140 | { icon: 'fa-brands fa-dribbble', url: 'https://dribbble.com/' }, 141 | ] 142 | }, { 143 | name: 'Entertainment', 144 | description: 'Films, videos, streams', 145 | color: { primary: { hsl: [0, 0, 100] }, secondary: { hsl: [0, 60, 70] } }, 146 | list: [ 147 | { icon: 'fa-brands fa-vimeo', url: 'https://vimeo.com/' }, 148 | { icon: 'fa-brands fa-youtube', url: 'https://youtube.com/' }, 149 | { icon: 'fa-solid fa-clapperboard', url: 'https://netflix.com/' }, 150 | { icon: 'fa-brands fa-twitch', url: 'https://www.twitch.tv/' }, 151 | ] 152 | }, { 153 | name: 'Ref', 154 | description: 'Docs, code + specs', 155 | color: { primary: { hsl: [0, 0, 100] }, secondary: { hsl: [40, 60, 70] } }, 156 | list: [ 157 | { icon: 'fa-solid fa-code', url: 'https://devdocs.io/' }, 158 | { icon: 'fa-brands fa-css3-alt', url: 'https://developer.mozilla.org/en-US/docs/Learn/CSS/' }, 159 | { icon: 'fa-brands fa-stack-overflow', url: 'https://stackoverflow.com/' }, 160 | { icon: 'fa-brands fa-bootstrap', url: 'https://getbootstrap.com/docs/5.2/getting-started/introduction/' }, 161 | { icon: 'fa-brands fa-npm', url: 'https://www.npmjs.com/' }, 162 | ] 163 | }] 164 | 165 | }, 166 | 167 | background: { 168 | 169 | // the background is made of three layers 170 | // colour at the bottom 171 | // an image above the colour layer 172 | // a gradient above the image layer 173 | 174 | // background layer 1 (bottom) 175 | // colour defined with HSL: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl 176 | color: { 177 | hsl: [220, 30, 50] 178 | }, 179 | 180 | // background layer 2 (middle) 181 | image: { 182 | 183 | url: 'https://i.redd.it/niecy4jmlmh81.png', // background image url 184 | 185 | // opacity of background image 186 | opacity: 1, // range: min:0|max:1 187 | 188 | // grayscale of background image 189 | grayscale: 0.1, // range: min:0|max:1 190 | 191 | // blur of background image 192 | blur: 0 // range: min:0|max:* 193 | 194 | }, 195 | 196 | // background layer 3 (top) 197 | gradient: { 198 | 199 | // gradient angle 200 | degree: 90, // range: min:0|max:360 201 | 202 | color: [ 203 | // gradient colours 204 | // each object is a single colour and position in the gradient layer 205 | // the position value defines the location of the colour stop 206 | // each stop should not be a lower value than the previous stop 207 | // eg: {..., position: 40}, {..., position: 60}, {..., position: 90}, 208 | // add as many objects as needed 209 | { 210 | // colour defined with HSLA: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsla 211 | hsla: [220, 80, 18, 0.6], 212 | position: 30 // range: min:0|max:100 213 | }, 214 | { hsla: [240, 85, 25, 0.4], position: 40 }, 215 | { hsla: [280, 40, 25, 0.1], position: 100 } 216 | ] 217 | 218 | } 219 | 220 | } 221 | 222 | }; 223 | -------------------------------------------------------------------------------- /src/component/utility/randomString.js: -------------------------------------------------------------------------------- 1 | export const randomString = ({ 2 | letter = false, 3 | adjectivesCount = false 4 | } = {}) => { 5 | 6 | const alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; 7 | 8 | const adjectives = { 9 | a: ['Aback', 'Abaft', 'Abandoned', 'Abashed', 'Aberrant', 'Abhorrent', 'Abiding', 'Abject', 'Ablaze', 'Able', 'Abnormal', 'Aboriginal', 'Abortive', 'Abounding', 'Abrasive', 'Abrupt', 'Absent', 'Absorbed', 'Absorbing', 'Abstracted', 'Absurd', 'Abundant', 'Abusive', 'Acceptable', 'Accessible', 'Accidental', 'Accurate', 'Acid', 'Acidic', 'Acoustic', 'Acrid', 'Adamant', 'Adaptable', 'Adhesive', 'Adjoining', 'Adorable', 'Adventurous', 'Afraid', 'Aggressive', 'Agonizing', 'Agreeable', 'Ahead', 'Ajar', 'Alert', 'Alike', 'Alive', 'Alleged', 'Alluring', 'Aloof', 'Amazing', 'Ambiguous', 'Ambitious', 'Amuck', 'Amused', 'Amusing', 'Ancient', 'Angry', 'Animated', 'Annoyed', 'Annoying', 'Anxious', 'Apathetic', 'Aquatic', 'Aromatic', 'Arrogant', 'Ashamed', 'Aspiring', 'Assorted', 'Astonishing', 'Attractive', 'Auspicious', 'Automatic', 'Available', 'Average', 'Aware', 'Awesome', 'Axiomatic'], 10 | b: ['Bad', 'Barbarous', 'Bashful', 'Bawdy', 'Beautiful', 'Befitting', 'Belligerent', 'Beneficial', 'Bent', 'Berserk', 'Bewildered', 'Big', 'Billowy', 'Bitter', 'Bizarre', 'Black', 'Bloody', 'Blue', 'Blushing', 'Boiling', 'Boorish', 'Bored', 'Boring', 'Bouncy', 'Boundless', 'Brainy', 'Brash', 'Brave', 'Brawny', 'Breakable', 'Breezy', 'Brief', 'Bright', 'Broad', 'Broken', 'Brown', 'Bumpy', 'Burly', 'Bustling', 'Busy'], 11 | c: ['Cagey', 'Calculating', 'Callous', 'Calm', 'Capable', 'Capricious', 'Careful', 'Careless', 'Caring', 'Cautious', 'Ceaseless', 'Certain', 'Changeable', 'Charming', 'Cheap', 'Cheerful', 'Chemical', 'Chief', 'Childlike', 'Chilly', 'Chivalrous', 'Chubby', 'Chunky', 'Clammy', 'Classy', 'Clean', 'Clear', 'Clever', 'Cloistered', 'Cloudy', 'Closed', 'Clumsy', 'Cluttered', 'Coherent', 'Cold', 'Colorful', 'Colossal', 'Combative', 'Comfortable', 'Common', 'Complete', 'Complex', 'Concerned', 'Condemned', 'Confused', 'Conscious', 'Cooing', 'Cool', 'Cooperative', 'Coordinated', 'Courageous', 'Cowardly', 'Crabby', 'Craven', 'Crazy', 'Creepy', 'Crooked', 'Crowded', 'Cruel', 'Cuddly', 'Cultured', 'Cumbersome', 'Curious', 'Curly', 'Curved', 'Curvy', 'Cut', 'Cute', 'Cynical'], 12 | d: ['Daffy', 'Daily', 'Damaged', 'Damaging', 'Damp', 'Dangerous', 'Dapper', 'Dark', 'Dashing', 'Dazzling', 'Deadpan', 'Deafening', 'Dear', 'Debonair', 'Decisive', 'Decorous', 'Deep', 'Deeply', 'Defeated', 'Defective', 'Defiant', 'Delicate', 'Delicious', 'Delightful', 'Demonic', 'Delirious', 'Dependent', 'Depressed', 'Deranged', 'Descriptive', 'Deserted', 'Detailed', 'Determined', 'Devilish', 'Didactic', 'Different', 'Difficult', 'Diligent', 'Direful', 'Dirty', 'Disagreeable', 'Disastrous', 'Discreet', 'Disgusted', 'Disgusting', 'Disillusioned', 'Dispensable', 'Distinct', 'Disturbed', 'Divergent', 'Dizzy', 'Domineering', 'Doubtful', 'Drab', 'Draconian', 'Dramatic', 'Dreary', 'Drunk', 'Dry', 'Dull', 'Dusty', 'Dynamic', 'Dysfunctional'], 13 | e: ['Eager', 'Early', 'Earsplitting', 'Earthy', 'Easy', 'Eatable', 'Economic', 'Educated', 'Efficacious', 'Efficient', 'Elastic', 'Elated', 'Elderly', 'Electric', 'Elegant', 'Elfin', 'Elite', 'Embarrassed', 'Eminent', 'Empty', 'Enchanted', 'Enchanting', 'Encouraging', 'Endurable', 'Energetic', 'Enormous', 'Entertaining', 'Enthusiastic', 'Envious', 'Equable', 'Equal', 'Erratic', 'Ethereal', 'Evanescent', 'Evasive', 'Even', 'Excellent', 'Excited', 'Exciting', 'Exclusive', 'Exotic', 'Expensive', 'Exuberant', 'Exultant'], 14 | f: ['Fabulous', 'Faded', 'Faint', 'Fair', 'Faithful', 'Fallacious', 'False', 'Familiar', 'Famous', 'Fanatical', 'Fancy', 'Fantastic', 'Far', 'Fascinated', 'Fast', 'Fat', 'Faulty', 'Fearful', 'Fearless', 'Feeble', 'Feigned', 'Fertile', 'Festive', 'Few', 'Fierce', 'Filthy', 'Fine', 'Finicky', 'First', 'Fixed', 'Flagrant', 'Flaky', 'Flashy', 'Flat', 'Flawless', 'Flimsy', 'Flippant', 'Flowery', 'Fluffy', 'Fluttering', 'Foamy', 'Foolish', 'Foregoing', 'Forgetful', 'Fortunate', 'Frail', 'Fragile', 'Frantic', 'Free', 'Freezing', 'Frequent', 'Fresh', 'Fretful', 'Friendly', 'Frightened', 'Frightening', 'Full', 'Fumbling', 'Functional', 'Funny', 'Furry', 'Furtive', 'Future', 'Futuristic', 'Fuzzy'], 15 | g: ['Gabby', 'Gainful', 'Gamy', 'Garrulous', 'Gaudy', 'General', 'Gentle', 'Giant', 'Giddy', 'Gifted', 'Gigantic', 'Glamorous', 'Gleaming', 'Glib', 'Glistening', 'Glorious', 'Glossy', 'Good', 'Goofy', 'Gorgeous', 'Graceful', 'Grandiose', 'Grateful', 'Gratis', 'Gray', 'Greasy', 'Great', 'Greedy', 'Green', 'Grey', 'Grieving', 'Groovy', 'Grotesque', 'Grouchy', 'Grubby', 'Gruesome', 'Grumpy', 'Guarded', 'Guiltless', 'Gullible', 'Gusty', 'Guttural'], 16 | h: ['Habitual', 'Half', 'Hallowed', 'Halting', 'Handsome', 'Handy', 'Hapless', 'Happy', 'Hard', 'Harmonious', 'Harsh', 'Hateful', 'Heady', 'Healthy', 'Heartbreaking', 'Heavenly', 'Heavy', 'Hellish', 'Helpful', 'Helpless', 'Hesitant', 'Hideous', 'High', 'Highfalutin', 'Hilarious', 'Hissing', 'Historical', 'Holistic', 'Hollow', 'Homeless', 'Homely', 'Honorable', 'Horrible', 'Hospitable', 'Hot', 'Huge', 'Hulking', 'Humdrum', 'Humorous', 'Hungry', 'Hurried', 'Hurt', 'Hushed', 'Husky', 'Hypnotic', 'Hysterical'], 17 | i: ['Icky', 'Icy', 'Idiotic', 'Ignorant', 'Ill', 'Illegal', 'Illustrious', 'Imaginary', 'Immense', 'Imminent', 'Impartial', 'Imperfect', 'Impolite', 'Important', 'Imported', 'Impossible', 'Incandescent', 'Incompetent', 'Inconclusive', 'Industrious', 'Incredible', 'Inexpensive', 'Infamous', 'Innate', 'Innocent', 'Inquisitive', 'Insidious', 'Instinctive', 'Intelligent', 'Interesting', 'Internal', 'Invincible', 'Irate', 'Irritating', 'Itchy'], 18 | j: ['Jaded', 'Jagged', 'Jazzy', 'Jealous', 'Jesting', 'Jinxed', 'Jittery', 'Jobless', 'Jolly', 'Joyous', 'Judicious', 'Juicy', 'Jumbled', 'Jumpy', 'Juvenile'], 19 | k: ['Keen', 'Kind', 'Kindhearted', 'Kindly', 'Knotty', 'Knowing', 'Knowledgeable', 'Known'], 20 | l: ['Labored', 'Lackadaisical', 'Lacking', 'Lame', 'Lamentable', 'Languid', 'Large', 'Last', 'Late', 'Laughable', 'Lavish', 'Lazy', 'Lean', 'Learned', 'Left', 'Legal', 'Lethal', 'Level', 'Lewd', 'Light', 'Like', 'Likeable', 'Limping', 'Literate', 'Little', 'Lively', 'Living', 'Lonely', 'Long', 'Longing', 'Loose', 'Lopsided', 'Loud', 'Loutish', 'Lovely', 'Loving', 'Low', 'Lowly', 'Lucky', 'Ludicrous', 'Lumpy', 'Lush', 'Luxuriant', 'Lying', 'Lyrical'], 21 | m: ['Macabre', 'Macho', 'Maddening', 'Madly', 'Magenta', 'Magical', 'Magnificent', 'Majestic', 'Makeshift', 'Malicious', 'Mammoth', 'Maniacal', 'Many', 'Marked', 'Massive', 'Married', 'Marvelous', 'Material', 'Materialistic', 'Mature', 'Mean', 'Measly', 'Meaty', 'Medical', 'Meek', 'Mellow', 'Melodic', 'Melted', 'Merciful', 'Mere', 'Messy', 'Mighty', 'Military', 'Milky', 'Mindless', 'Miniature', 'Minor', 'Miscreant', 'Misty', 'Mixed', 'Moaning', 'Modern', 'Moldy', 'Momentous', 'Motionless', 'Mountainous', 'Muddled', 'Mundane', 'Murky', 'Mushy', 'Mute', 'Mysterious'], 22 | n: ['Naive', 'Nappy', 'Narrow', 'Nasty', 'Natural', 'Naughty', 'Nauseating', 'Near', 'Neat', 'Nebulous', 'Necessary', 'Needless', 'Needy', 'Neighborly', 'Nervous', 'New', 'Next', 'Nice', 'Nifty', 'Nimble', 'Nippy', 'Noiseless', 'Noisy', 'Nonchalant', 'Nondescript', 'Nonstop', 'Normal', 'Nostalgic', 'Nosy', 'Noxious', 'Numberless', 'Numerous', 'Nutritious', 'Nutty'], 23 | o: ['Oafish', 'Obedient', 'Obeisant', 'Obese', 'Obnoxious', 'Obscene', 'Obsequious', 'Observant', 'Obsolete', 'Obtainable', 'Oceanic', 'Odd', 'Offbeat', 'Old', 'Omniscient', 'Onerous', 'Open', 'Opposite', 'Optimal', 'Orange', 'Ordinary', 'Organic', 'Ossified', 'Outgoing', 'Outrageous', 'Outstanding', 'Oval', 'Overconfident', 'Overjoyed', 'Overrated', 'Overt', 'Overwrought'], 24 | p: ['Painful', 'Painstaking', 'Pale', 'Paltry', 'Panicky', 'Panoramic', 'Parallel', 'Parched', 'Parsimonious', 'Past', 'Pastoral', 'Pathetic', 'Peaceful', 'Penitent', 'Perfect', 'Periodic', 'Permissible', 'Perpetual', 'Petite', 'Phobic', 'Physical', 'Picayune', 'Pink', 'Piquant', 'Placid', 'Plain', 'Plant', 'Plastic', 'Plausible', 'Pleasant', 'Plucky', 'Pointless', 'Poised', 'Polite', 'Political', 'Poor', 'Possessive', 'Possible', 'Powerful', 'Precious', 'Premium', 'Present', 'Pretty', 'Previous', 'Pricey', 'Prickly', 'Private', 'Probable', 'Productive', 'Profuse', 'Protective', 'Proud', 'Psychedelic', 'Psychotic', 'Public', 'Puffy', 'Pumped', 'Puny', 'Purple', 'Purring', 'Pushy', 'Puzzled', 'Puzzling'], 25 | q: ['Quaint', 'Quality', 'Quarrelsome', 'Questionable', 'Questioning', 'Quick', 'Quiet', 'Quirky', 'Quixotic', 'Quizzical'], 26 | r: ['Rabid', 'Ragged', 'Rainy', 'Rambunctious', 'Rampant', 'Rapid', 'Rare', 'Raspy', 'Ratty', 'Ready', 'Real', 'Rebel', 'Receptive', 'Recondite', 'Red', 'Redundant', 'Reflective', 'Regular', 'Relieved', 'Remarkable', 'Reminiscent', 'Repulsive', 'Resolute', 'Resonant', 'Responsible', 'Rhetorical', 'Rich', 'Right', 'Righteous', 'Rightful', 'Rigid', 'Ripe', 'Ritzy', 'Roasted', 'Robust', 'Romantic', 'Roomy', 'Rotten', 'Rough', 'Round', 'Royal', 'Ruddy', 'Rude', 'Rural', 'Rustic', 'Ruthless'], 27 | s: ['Sable', 'Sad', 'Safe', 'Salty', 'Same', 'Sassy', 'Satisfying', 'Savory', 'Scandalous', 'Scarce', 'Scared', 'Scary', 'Scattered', 'Scientific', 'Scintillating', 'Scrawny', 'Screeching', 'Second', 'Secret', 'Secretive', 'Sedate', 'Seemly', 'Selective', 'Selfish', 'Separate', 'Serious', 'Shaggy', 'Shaky', 'Shallow', 'Sharp', 'Shiny', 'Shivering', 'Shocking', 'Short', 'Shrill', 'Shut', 'Shy', 'Sick', 'Silent', 'Silky', 'Silly', 'Simple', 'Simplistic', 'Sincere', 'Skillful', 'Skinny', 'Sleepy', 'Slim', 'Slimy', 'Slippery', 'Sloppy', 'Slow', 'Small', 'Smart', 'Smelly', 'Smiling', 'Smoggy', 'Smooth', 'Sneaky', 'Snobbish', 'Snotty', 'Soft', 'Soggy', 'Solid', 'Somber', 'Sophisticated', 'Sordid', 'Sore', 'Sour', 'Sparkling', 'Special', 'Spectacular', 'Spicy', 'Spiffy', 'Spiky', 'Spiritual', 'Spiteful', 'Splendid', 'Spooky', 'Spotless', 'Spotted', 'Spotty', 'Spurious', 'Squalid', 'Square', 'Squealing', 'Squeamish', 'Staking', 'Stale', 'Standing', 'Statuesque', 'Steadfast', 'Steady', 'Steep', 'Stereotyped', 'Sticky', 'Stiff', 'Stimulating', 'Stingy', 'Stormy', 'Straight', 'Strange', 'Striped', 'Strong', 'Stupendous', 'Sturdy', 'Subdued', 'Subsequent', 'Substantial', 'Successful', 'Succinct', 'Sudden', 'Sulky', 'Super', 'Superb', 'Superficial', 'Supreme', 'Swanky', 'Sweet', 'Sweltering', 'Swift', 'Symptomatic', 'Synonymous'], 28 | t: ['Taboo', 'Tacit', 'Tacky', 'Talented', 'Tall', 'Tame', 'Tan', 'Tangible', 'Tangy', 'Tart', 'Tasteful', 'Tasteless', 'Tasty', 'Tawdry', 'Tearful', 'Tedious', 'Teeny', 'Telling', 'Temporary', 'Ten', 'Tender', 'Tense', 'Tenuous', 'Terrific', 'Tested', 'Testy', 'Thankful', 'Therapeutic', 'Thick', 'Thin', 'Thinkable', 'Third', 'Thirsty', 'Thoughtful', 'Thoughtless', 'Threatening', 'Thundering', 'Tidy', 'Tight', 'Tightfisted', 'Tiny', 'Tired', 'Tiresome', 'Toothsome', 'Torpid', 'Tough', 'Towering', 'Tranquil', 'Trashy', 'Tremendous', 'Tricky', 'Trite', 'Troubled', 'Truculent', 'True', 'Truthful', 'Typical'], 29 | u: ['Ubiquitous', 'Ultra', 'Unable', 'Unaccountable', 'Unadvised', 'Unarmed', 'Unbecoming', 'Unbiased', 'Uncovered', 'Understood', 'Undesirable', 'Unequal', 'Unequaled', 'Uneven', 'Unhealthy', 'Uninterested', 'Unique', 'Unkempt', 'Unknown', 'Unnatural', 'Unruly', 'Unsightly', 'Unsuitable', 'Untidy', 'Unused', 'Unusual', 'Unwieldy', 'Unwritten', 'Upbeat', 'Uppity', 'Upset', 'Uptight', 'Used', 'Useful', 'Useless', 'Utopian'], 30 | v: ['Vacuous', 'Vagabond', 'Vague', 'Valuable', 'Various', 'Vast', 'Vengeful', 'Venomous', 'Verdant', 'Versed', 'Victorious', 'Vigorous', 'Violent', 'Violet', 'Vivacious', 'Voiceless', 'Volatile', 'Voracious', 'Vulgar'], 31 | w: ['Wacky', 'Waggish', 'Waiting', 'Wakeful', 'Wandering', 'Wanting', 'Warlike', 'Warm', 'Wary', 'Wasteful', 'Watery', 'Weak', 'Wealthy', 'Weary', 'Wet', 'Whimsical', 'Whispering', 'White', 'Whole', 'Wholesale', 'Wicked', 'Wide', 'Wiggly', 'Wild', 'Willing', 'Windy', 'Wiry', 'Wise', 'Wistful', 'Witty', 'Woebegone', 'Wonderful', 'Wooden', 'Woozy', 'Workable', 'Worried', 'Worthless', 'Wrathful', 'Wretched', 'Wrong', 'Wry'], 32 | x: ['Xenial', 'Xenodochial', 'Xenophobic'], 33 | y: ['Yellow', 'Yielding', 'Young', 'Youthful', 'Yummy'], 34 | z: ['Zany', 'Zealous', 'Zesty', 'Zippy', 'Zombiesque', 'Zombie', 'Zonked'] 35 | }; 36 | 37 | const animals = { 38 | a: ['Aardvark', 'Albatross', 'Alligator', 'Alpaca', 'Ant', 'Anteater', 'Antelope', 'Ape', 'Armadillo'], 39 | b: ['Baboon', 'Badger', 'Barracuda', 'Bat', 'Bear', 'Beaver', 'Bee', 'Bison', 'Boar', 'Buffalo', 'Butterfly'], 40 | c: ['Camel', 'Capybara', 'Caribou', 'Cassowary', 'Cat', 'Caterpillar', 'Cattle', 'Chamois', 'Cheetah', 'Chicken', 'Chimpanzee', 'Chinchilla', 'Chough', 'Clam', 'Cobra', 'Cockroach', 'Cod', 'Cormorant', 'Coyote', 'Crab', 'Crane', 'Crocodile', 'Crow', 'Curlew'], 41 | d: ['Deer', 'Dinosaur', 'Dog', 'Dogfish', 'Dolphin', 'Donkey', 'Dotterel', 'Dove', 'Dragonfly', 'Duck', 'Dugong', 'Dunlin'], 42 | e: ['Eagle', 'Echidna', 'Eel', 'Eland', 'Elephant', 'Elephant Seal', 'Elk', 'Emu'], 43 | f: ['Falcon', 'Ferret', 'Finch', 'Fish', 'Flamingo', 'Fly', 'Fox', 'Frog'], 44 | g: ['Gaur', 'Gazelle', 'Gerbil', 'Giant Panda', 'Giraffe', 'Gnat', 'Gnu', 'Goat', 'Goose', 'Goldfinch', 'Goldfish', 'Gorilla', 'Goshawk', 'Grasshopper', 'Grouse', 'Guanaco', 'Guinea Fowl', 'Guinea Pig', 'Gull'], 45 | h: ['Hamster', 'Hare', 'Hawk', 'Hedgehog', 'Heron', 'Herring', 'Hippopotamus', 'Hornet', 'Horse', 'Human', 'Hummingbird', 'Hyena'], 46 | i: ['Ibex', 'Ibis', 'Iguana', 'Impala', 'Isopod'], 47 | j: ['Jackal', 'Jaguar', 'Jay', 'Jellyfish'], 48 | k: ['Kangaroo', 'Kingfisher', 'Koala', 'Komodo Dragon', 'Kookabura', 'Kouprey', 'Kudu'], 49 | l: ['Lapwing', 'Lark', 'Lemur', 'Leopard', 'Lima', 'Lion', 'Llama', 'Lobster', 'Locust', 'Loris', 'Louse', 'Lyrebird'], 50 | m: ['Magpie', 'Mallard', 'Manatee', 'Mandrill', 'Mantis', 'Marten', 'Meerkat', 'Mink', 'Mole', 'Mongoose', 'Monkey', 'Moose', 'Mouse', 'Mosquito', 'Mule'], 51 | n: ['Narwhal', 'Newt', 'Nightingale', 'Nyala'], 52 | o: ['Octopus', 'Okapi', 'Opossum', 'Oryx', 'Ostrich', 'Otter', 'Owl', 'Ox', 'Oyster'], 53 | p: ['Panther', 'Parrot', 'Partridge', 'Peafowl', 'Pelican', 'Penguin', 'Pheasant', 'Pig', 'Pigeon', 'Polar Bear', 'Pony', 'Porcupine', 'Porpoise'], 54 | q: ['Quail', 'Quelea', 'Quetzal'], 55 | r: ['Rabbit', 'Raccoon', 'Rail', 'Ram', 'Rat', 'Raven', 'Red Deer', 'Red Panda', 'Reindeer', 'Rhinoceros', 'Rook'], 56 | s: ['Salamander', 'Salmon', 'Sand Dollar', 'Sandpiper', 'Sardine', 'Scorpion', 'Sea Lion', 'Sea Urchin', 'Seahorse', 'Seal', 'Shark', 'Sheep', 'Shrew', 'Skunk', 'Snail', 'Snake', 'Sparrow', 'Spider', 'Spoonbill', 'Squid', 'Squirrel', 'Starling', 'Stingray', 'Stinkbug', 'Stork', 'Swallow', 'Swan'], 57 | t: ['Tapir', 'Tarsier', 'Termite', 'Tiger', 'Toad', 'Trout', 'Turkey', 'Turtle'], 58 | u: ['Uakari', 'Unau', 'Urial', 'Urchin', 'Umbrellabird', 'Unicornfish', 'Uromastyx', 'Uguisu'], 59 | v: ['Vampire Bat', 'Viper', 'Vole', 'Vulture'], 60 | w: ['Wallaby', 'Walrus', 'Wasp', 'Weasel', 'Whale', 'Wolf', 'Wolverine', 'Wombat', 'Woodcock', 'Woodpecker', 'Worm', 'Wren'], 61 | x: ['Xaviers Greenbul', 'Xeme', 'Xingu Corydoras', 'Xolo'], 62 | y: ['Yabby', 'Yak', 'Yellowhammer', 'Yellowjacket'], 63 | z: ['Zebra', 'Zebu', 'Zokor', 'Zorilla'] 64 | }; 65 | 66 | const action = { 67 | alliteration: { 68 | short: () => { 69 | 70 | const randomAdjective = adjectives[letter.toLowerCase()][Math.floor(Math.random() * adjectives[letter.toLowerCase()].length)]; 71 | 72 | const randomAnimal = animals[letter.toLowerCase()][Math.floor(Math.random() * animals[letter.toLowerCase()].length)]; 73 | 74 | return randomAdjective + ' ' + randomAnimal; 75 | 76 | }, 77 | long: () => { 78 | 79 | let randomAdjective = ''; 80 | 81 | for (let i = 1; i <= adjectivesCount; i++) { 82 | 83 | if (adjectives[letter.toLowerCase()].length > 0) { 84 | if (randomAdjective.length > 0) { 85 | randomAdjective = randomAdjective + ' '; 86 | } 87 | randomAdjective = randomAdjective + adjectives[letter.toLowerCase()].splice(Math.floor(Math.random() * adjectives[letter.toLowerCase()].length), 1); 88 | } 89 | 90 | } 91 | 92 | const randomAnimal = animals[letter.toLowerCase()][Math.floor(Math.random() * animals[letter.toLowerCase()].length)]; 93 | 94 | return randomAdjective + ' ' + randomAnimal; 95 | } 96 | }, 97 | mix: { 98 | short: () => { 99 | 100 | const adjectivesSeed = alphabet[Math.floor(Math.random() * (alphabet.length - 1))]; 101 | 102 | const animalsSeed = alphabet[Math.floor(Math.random() * (alphabet.length - 1))]; 103 | 104 | const randomAdjective = adjectives[adjectivesSeed][Math.floor(Math.random() * adjectives[adjectivesSeed].length)]; 105 | 106 | const randomAnimal = animals[animalsSeed][Math.floor(Math.random() * animals[animalsSeed].length)]; 107 | 108 | return randomAdjective + ' ' + randomAnimal; 109 | 110 | }, 111 | long: () => { 112 | 113 | var randomAdjective = ''; 114 | 115 | for (let i = 1; i <= adjectivesCount; i++) { 116 | 117 | var adjectiveLetter = alphabet[Math.floor(Math.random() * (alphabet.length - 1))]; 118 | 119 | if (adjectiveLetter in adjectives && adjectives[adjectiveLetter].length > 0) { 120 | 121 | if (randomAdjective.length > 0) { 122 | randomAdjective = randomAdjective + ' '; 123 | } 124 | 125 | randomAdjective = randomAdjective + adjectives[adjectiveLetter].splice(Math.floor(Math.random() * adjectives[adjectiveLetter].length), 1); 126 | 127 | if (adjectives[adjectiveLetter].length == 0) { 128 | delete adjectives[adjectiveLetter]; 129 | } 130 | 131 | } 132 | } 133 | 134 | var randomAnimalArray = animals[alphabet[Math.floor(Math.random() * (alphabet.length - 1))]]; 135 | 136 | var randomAnimal = randomAnimalArray[Math.floor(Math.random() * (randomAnimalArray.length - 1))]; 137 | 138 | return randomAdjective + ' ' + randomAnimal; 139 | 140 | } 141 | } 142 | }; 143 | 144 | if (letter && alphabet.includes(letter.toLowerCase())) { 145 | 146 | if (adjectivesCount && adjectivesCount > 0) { 147 | return action.alliteration.long(); 148 | } else { 149 | return action.alliteration.short(); 150 | } 151 | 152 | } else { 153 | 154 | if (adjectivesCount && adjectivesCount > 0) { 155 | return action.mix.long(); 156 | } else { 157 | return action.mix.short(); 158 | } 159 | 160 | } 161 | 162 | }; 163 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------