├── .eslintignore ├── .eslintrc.json ├── .github ├── deploy.yml └── workflows │ └── codeql.yml ├── .gitignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── config ├── config.js ├── purgecss.js ├── webpack.common.js ├── webpack.dev.js ├── webpack.dev.website.js ├── webpack.prod.js ├── webpack.prod.website.js └── webpack.website.js ├── package-lock.json ├── package.json ├── src ├── assets │ ├── png │ │ ├── icon152.png │ │ ├── icon20.png │ │ ├── icon256.png │ │ ├── icon48.png │ │ ├── icon512.png │ │ ├── icon64.png │ │ ├── icon96.png │ │ ├── preview1.png │ │ ├── preview2.png │ │ ├── preview3.png │ │ ├── preview4.png │ │ └── screenshot.png │ └── svg │ │ ├── floatly.svg │ │ ├── floatly_light.svg │ │ ├── floatly_r.svg │ │ └── icon.svg ├── css │ ├── fab │ │ ├── fab.css │ │ ├── fab_h.css │ │ ├── fab_r.css │ │ └── fab_s.css │ ├── main.css │ ├── options.css │ └── picker.css ├── data │ └── actions.json ├── html │ ├── index.html │ └── options.html ├── index.ejs ├── js │ ├── actions_div.js │ ├── background.js │ ├── components │ │ ├── actions.js │ │ ├── aos.js │ │ ├── blacklistManager.js │ │ ├── bottomsheet.js │ │ ├── dialog.js │ │ ├── dragElement.js │ │ ├── fullscreen.js │ │ ├── gotop.js │ │ ├── navbar.js │ │ ├── pagesRoute.js │ │ ├── rotate.js │ │ ├── snackbar.js │ │ ├── swipeManager.js │ │ ├── themeManager.js │ │ └── utilities.js │ ├── content.js │ ├── index.js │ ├── options.js │ └── service_worker.js ├── manifest.json ├── manifest_v3.json ├── partials │ ├── footer.ejs │ ├── gotop.ejs │ ├── head.ejs │ ├── header.ejs │ ├── support_me.ejs │ └── umami.ejs └── sass │ ├── base │ └── _basic.sass │ ├── components │ ├── _bottomsheet.sass │ ├── _card.sass │ ├── _details.sass │ ├── _dropdown.sass │ ├── _gotop_link.sass │ ├── _modal.sass │ ├── _snackbar.sass │ ├── _tabs.sass │ └── _toast.sass │ ├── elements │ ├── _button.sass │ ├── _code.sass │ ├── _figure.sass │ ├── _form.sass │ ├── _link.sass │ ├── _table.sass │ └── _typography.sass │ ├── extra │ ├── _alert.sass │ ├── _aos.sass │ ├── _carousel.sass │ ├── _loading.sass │ ├── _pages.sass │ ├── _tooltip.sass │ └── _wave.sass │ ├── fab │ ├── fab.sass │ ├── fab_h.sass │ ├── fab_r.sass │ └── fab_s.sass │ ├── index.sass │ ├── layout │ ├── aside.sass │ ├── container.sass │ ├── footer.sass │ ├── grid.sass │ ├── header.sass │ └── navbar.sass │ ├── options.sass │ ├── picker.sass │ ├── settings │ ├── _colors.sass │ └── _variables.sass │ ├── theme │ ├── dark.sass │ └── light.sass │ └── utilities │ ├── _alignment.sass │ ├── _background.sass │ ├── _border.sass │ ├── _display.sass │ ├── _general.sass │ ├── _margin.sass │ ├── _padding.sass │ ├── _position.sass │ ├── _spacer.sass │ └── _text.sass └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "commonjs": true 6 | }, 7 | "extends": ["eslint:recommended", "prettier"], 8 | "plugins": ["prettier"], 9 | "globals": { 10 | "chrome": "readonly", 11 | "Atomics": "readonly", 12 | "SharedArrayBuffer": "readonly" 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2021, 16 | "sourceType": "module" 17 | }, 18 | "rules": { 19 | "prettier/prettier": "error", 20 | "no-unused-vars": ["warn"], 21 | "no-undef": ["warn"], 22 | "no-console": ["warn"] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to gh-pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | jobs: 9 | gh-pages-deploy: 10 | name: Deploying to gh-pages 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout branch 14 | uses: actions/checkout@v4 15 | 16 | - name: Clean install dependencies 17 | run: npm install 18 | 19 | - name: Build 20 | run: npm run build 21 | 22 | - name: Files stats /dist 23 | run: | 24 | echo "Total file count in /dist:" 25 | find ./dist -type f | wc -l 26 | echo "Total size of files in /dist:" 27 | du -sh ./dist 28 | echo "Individual file sizes in /dist:" 29 | find ./dist -type f -exec du -h {} + 30 | 31 | - name: Purge 32 | run: npm run purge 33 | 34 | - name: deploy 35 | uses: peaceiris/actions-gh-pages@v3.9.3 36 | with: 37 | github_token: ${{ secrets.GITHUB_TOKEN }} 38 | publish_dir: ./dist 39 | env: 40 | ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | branches: [ "main" ] 19 | schedule: 20 | - cron: '28 14 * * 2' 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze 25 | # Runner size impacts CodeQL analysis time. To learn more, please see: 26 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 27 | # - https://gh.io/supported-runners-and-hardware-resources 28 | # - https://gh.io/using-larger-runners 29 | # Consider using larger runners for possible analysis time improvements. 30 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 31 | timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} 32 | permissions: 33 | # required for all workflows 34 | security-events: write 35 | 36 | # only required for workflows in private repositories 37 | actions: read 38 | contents: read 39 | 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | language: [ 'javascript-typescript' ] 44 | # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] 45 | # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both 46 | # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 47 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 48 | 49 | steps: 50 | - name: Checkout repository 51 | uses: actions/checkout@v4 52 | 53 | # Initializes the CodeQL tools for scanning. 54 | - name: Initialize CodeQL 55 | uses: github/codeql-action/init@v3 56 | with: 57 | languages: ${{ matrix.language }} 58 | # If you wish to specify custom queries, you can do so here or in a config file. 59 | # By default, queries listed here will override any specified in a config file. 60 | # Prefix the list here with "+" to use these queries and those in the config file. 61 | 62 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 63 | # queries: security-extended,security-and-quality 64 | 65 | 66 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). 67 | # If this step fails, then you should remove it and run the build manually (see below) 68 | - name: Autobuild 69 | uses: github/codeql-action/autobuild@v3 70 | 71 | # ℹ️ Command-line programs to run using the OS shell. 72 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 73 | 74 | # If the Autobuild fails above, remove it and uncomment the following three lines. 75 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 76 | 77 | # - run: | 78 | # echo "Run, Build Application using script" 79 | # ./location_of_script_within_repo/buildscript.sh 80 | 81 | - name: Perform CodeQL Analysis 82 | uses: github/codeql-action/analyze@v3 83 | with: 84 | category: "/language:${{matrix.language}}" 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | dist 4 | /.vscode 5 | .DS_Store 6 | 7 | .env 8 | .env.local 9 | .env.development 10 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | bracketSpacing: true, 4 | semi: false, 5 | singleQuote: true, 6 | tabWidth: 4, 7 | trailingComma: 'all', 8 | useTabs: true, 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Floatly 2 |

3 | Floatly 5 |

6 | 7 | Floatly is an awesome floating button that brings quick actions from any website.
8 | Made to be mobile friendly first, but it also works on desktop. 9 | 10 | On mobile you can use Floatly on browsers that supports extensions like [Kiwi Browser](https://kiwibrowser.com/) 11 | 12 | Download latest version -> [Releases](https://github.com/d3ward/floatly/releases) 13 | ## Actions 14 | 15 | - Close current tab 16 | - Duplicate current tab 17 | - Back to prevoius page 18 | - Go on top of page 19 | - Go on bottom of page 20 | - Enable fullscreen 21 | - Copy url of current tab 22 | - Open current page in incognito 23 | - Open Bookmarks 24 | - Open Downloads 25 | - Open chrome://flags 26 | - Open Extensions page 27 | - Open Homepage chrome://newtab 28 | - Open Settings chrome://settings 29 | - Open History 30 | - View source code of page 31 | - Reload page 32 | - Eruda - Console for Mobile Browser 33 | - Bookmarks current tab 34 | - Share current page 35 | 36 | ## Contributing 37 | 38 | If you have a suggestion feel free to share it by opening an issue
39 | You can suggest an action to be added to Floatly , if it's doable , it will be implemented 40 | 41 | ## Libraries & other projects included in Floatly 42 | 43 | - [Sortable 1.10.0-rc3](https://sortablejs.github.io/Sortable/) - JavaScript library for reorderable drag-and-drop lists | [MIT License](https://github.com/SortableJS/Sortable/blob/master/LICENSE) 44 | - [Vanilla-Picker v2.10.1](https://vanilla-picker.js.org) - A simple, easy to use vanilla JS color picker with alpha selection. | [ISC License](https://github.com/Sphinxxxx/vanilla-picker/blob/master/LICENSE.md) 45 | - [Eruda](https://eruda.liriliri.io/) - A console for Mobile Browsers | [MIT License](https://github.com/liriliri/eruda/blob/master/LICENSE) 46 | 47 | ## License 48 | 49 | Floatly is licensed under [(CC BY-NC-SA 4.0)](https://creativecommons.org/licenses/by-nc-sa/4.0/) 50 | -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | src: path.resolve(__dirname, '../src'), 5 | build: path.resolve(__dirname, '../dist'), 6 | extensions: ['background', 'content', 'options'], 7 | extensionsV3: ['service_worker', 'content', 'options'], 8 | pages: ['options'], 9 | website: ['index'], 10 | dist: path.resolve(__dirname, '../dist/website'), 11 | type: process.env.TYPE, 12 | mv: process.env.MV, 13 | } 14 | -------------------------------------------------------------------------------- /config/purgecss.js: -------------------------------------------------------------------------------- 1 | const { PurgeCSS } = require('purgecss') 2 | const path = require('path') 3 | const fs = require('fs') 4 | const chalk = require('chalk') 5 | const pages = ['options'] 6 | const config = require('./config') 7 | const options = pages.map((page) => { 8 | const css = path.join(config.build, `css/${page}.css`) 9 | console.log(css) 10 | const content = [path.join(config.build, `${page}.html`), path.join(config.build, `js/${page}.js`)] 11 | return { 12 | css: [css], 13 | content: content, 14 | } 15 | }) 16 | 17 | Promise.all(options.map((option) => new PurgeCSS().purge(option))).then((results) => { 18 | results.forEach((result, i) => { 19 | console.log(result) 20 | if (result.length > 0) { 21 | const css = result[0].css 22 | const cssFile = path.join(config.build, `css/${pages[i]}.css`) 23 | console.log(chalk.green(`File: ${cssFile}`)) 24 | console.log( 25 | `Original size: ${(fs.statSync(path.join(config.build, `css/${pages[i]}.css`)).size / 1024).toFixed( 26 | 2, 27 | )}KB`, 28 | ) 29 | console.log(`Optimized size: ${(css.length / 1024).toFixed(2)}KB`) 30 | fs.writeFileSync(cssFile, css) 31 | } else { 32 | console.log(chalk.red('Failed to find any unused CSS')) 33 | } 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /config/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CopyWebpackPlugin = require('copy-webpack-plugin') 3 | const HTMLWebpackPlugin = require('html-webpack-plugin') 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 5 | const { src, build, extensions, extensionsV3, pages, type, mv } = require('./config') 6 | 7 | var ext_entries, manifest_file 8 | if (mv == 2) { 9 | ext_entries = extensions 10 | manifest_file = { 11 | from: path.join(src, 'manifest.json'), 12 | to: path.join(build, `${type}_${mv}`), 13 | } 14 | } else { 15 | ext_entries = extensionsV3 16 | manifest_file = { 17 | from: path.join(src, 'manifest_v3.json'), 18 | to: path.join(build, `${type}_${mv}`, 'manifest.json'), 19 | } 20 | } 21 | 22 | module.exports = { 23 | context: src, 24 | entry: { 25 | ...ext_entries.reduce((acc, page) => { 26 | acc[page] = `./js/${page}.js` 27 | return acc 28 | }, {}), 29 | }, 30 | output: { 31 | clean: false, 32 | assetModuleFilename: '[path][name][ext]', 33 | path: path.join(build, `${type}_${mv}`), 34 | filename: './js/[name].js', 35 | }, 36 | plugins: [ 37 | new CopyWebpackPlugin({ 38 | patterns: [ 39 | { 40 | from: path.join(src, 'assets'), 41 | to: path.join(build, `${type}_${mv}`, 'assets'), 42 | }, 43 | manifest_file, 44 | ], 45 | }), 46 | new MiniCssExtractPlugin({ 47 | filename: './css/[name].css', 48 | chunkFilename: '[name].css', 49 | }), 50 | ...pages.map( 51 | (page) => 52 | new HTMLWebpackPlugin({ 53 | template: path.join('html', `${page}.html`), 54 | filename: `${page}.html`, 55 | chunks: [page], 56 | minify: false, 57 | sources: false, 58 | }), 59 | ), 60 | ], 61 | module: { 62 | rules: [ 63 | { 64 | test: /\.js$/, 65 | exclude: /node_modules/, 66 | use: { 67 | loader: 'babel-loader', 68 | options: { 69 | presets: ['@babel/preset-env'], 70 | plugins: ['@babel/plugin-transform-runtime'], 71 | }, 72 | }, 73 | }, 74 | { 75 | test: /\.(sa|sc)ss$/, 76 | use: [ 77 | MiniCssExtractPlugin.loader, // extract css from commonjs 78 | 'css-loader', // turn css into commonjs 79 | 'sass-loader', // turn scss into css 80 | ], 81 | }, 82 | { 83 | test: /\.css$/, 84 | use: [ 85 | 'style-loader', 86 | 'css-loader', // turn css into commonjs 87 | ], 88 | }, 89 | { 90 | test: /.(png|jpe?g|gif|svg)$/i, 91 | use: [ 92 | { 93 | loader: 'file-loader', 94 | options: { 95 | name: '[name].[ext]', 96 | outputPath: './assets', 97 | }, 98 | }, 99 | ], 100 | }, 101 | ], 102 | }, 103 | } 104 | -------------------------------------------------------------------------------- /config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge') 2 | const common = require('./webpack.common') 3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') 4 | 5 | module.exports = merge(common, { 6 | mode: 'development', 7 | devtool: 'cheap-module-source-map', 8 | optimization: { 9 | minimize: false, 10 | }, 11 | plugins: [new CleanWebpackPlugin()], 12 | }) 13 | -------------------------------------------------------------------------------- /config/webpack.dev.website.js: -------------------------------------------------------------------------------- 1 | const main = require('./webpack.website') 2 | const { merge } = require('webpack-merge') 3 | const config = require('./config') 4 | 5 | module.exports = merge(main, { 6 | mode: 'development', 7 | devServer: { 8 | static: { 9 | directory: config.dist, 10 | }, 11 | compress: true, 12 | port: 3000, 13 | hot: true, 14 | open: true, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge') 2 | const TerserPlugin = require('terser-webpack-plugin') 3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') 4 | const common = require('./webpack.common') 5 | 6 | module.exports = merge(common, { 7 | mode: 'production', 8 | optimization: { 9 | minimize: true, 10 | minimizer: [ 11 | new TerserPlugin({ 12 | test: /\.js(\?.*)?$/i, 13 | }), 14 | ], 15 | }, 16 | output: { 17 | filename: 'js/[name].js', 18 | }, 19 | plugins: [new CleanWebpackPlugin()], 20 | }) 21 | -------------------------------------------------------------------------------- /config/webpack.prod.website.js: -------------------------------------------------------------------------------- 1 | const main = require('./webpack.website') 2 | const { merge } = require('webpack-merge') 3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') 4 | const TerserPlugin = require('terser-webpack-plugin') 5 | 6 | module.exports = merge(main, { 7 | mode: 'production', 8 | optimization: { 9 | minimize: true, 10 | minimizer: [ 11 | new TerserPlugin({ 12 | test: /\.js(\?.*)?$/i 13 | }) 14 | ] 15 | }, 16 | plugins: [new CleanWebpackPlugin()] 17 | }) -------------------------------------------------------------------------------- /config/webpack.website.js: -------------------------------------------------------------------------------- 1 | const HTMLWebpackPlugin = require('html-webpack-plugin') 2 | const CopyWebpackPlugin = require('copy-webpack-plugin') 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 4 | const config = require('./config') 5 | const pages = config.website 6 | module.exports = { 7 | context: config.src, 8 | entry: { 9 | ...pages.reduce((acc, page) => { 10 | acc[page] = `./js/${page}.js` 11 | return acc 12 | }, {}), 13 | }, 14 | output: { 15 | filename: './js/[name].js', 16 | path: config.build, 17 | clean: false, 18 | assetModuleFilename: '[path][name][ext]', 19 | }, 20 | plugins: [ 21 | new CopyWebpackPlugin({ 22 | patterns: [ 23 | { 24 | from: './assets', 25 | to: 'assets', 26 | globOptions: { 27 | ignore: ['*.DS_Store', '**/css/*.css', '**/js/*.js', '**/*.html'], 28 | }, 29 | noErrorOnMissing: true, 30 | }, 31 | ], 32 | }), 33 | new MiniCssExtractPlugin({ 34 | filename: './css/[name].css', 35 | chunkFilename: '[name].css', 36 | }), 37 | ...pages.map( 38 | (page) => 39 | new HTMLWebpackPlugin({ 40 | template: `./${page}.ejs`, 41 | filename: `${page}.html`, 42 | chunks: [page], 43 | minify: false, 44 | sources: false, 45 | }), 46 | ), 47 | ], 48 | module: { 49 | rules: [ 50 | { 51 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 52 | type: 'asset/resource' 53 | }, 54 | { 55 | test: /\.ejs$/i, 56 | use: ['html-loader', 'template-ejs-loader'] 57 | }, 58 | 59 | { 60 | test: /\.js$/, 61 | exclude: /node_modules/, 62 | use: 'babel-loader' 63 | }, 64 | { 65 | test: /\.(sa|sc|c)ss$/, 66 | use: [ 67 | MiniCssExtractPlugin.loader, // extract css from commonjs 68 | 'css-loader', // turn css into commonjs 69 | 'sass-loader' // turn scss into css 70 | ] 71 | } 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "floatly", 3 | "version": "3.0.0", 4 | "description": "Floatly is an awesome floating button that brings quick browser actions from any website.", 5 | "scripts": { 6 | "purge": "node config/purgecss.js", 7 | "dev-website": "webpack serve --config config/webpack.dev.website.js", 8 | "dev": "npm run build-fab && cross-env TYPE=chrome MV=2 webpack --config config/webpack.dev.js", 9 | "dev:mv3": "npm run build-fab && cross-env TYPE=chrome MV=3 webpack --config config/webpack.dev.js", 10 | "dev:firefox": "cross-env TYPE=firefox MV=3 webpack --config config/webpack.dev.js", 11 | "build-website": "cross-env webpack --config config/webpack.prod.website.js", 12 | "build-fab": "node-sass src/sass/fab -o src/css/fab --output-style compressed", 13 | "build": "cross-env TYPE=chrome MV=2 webpack --config config/webpack.prod.js", 14 | "build:v3": "cross-env TYPE=chrome MV=3 webpack --config config/webpack.prod.js", 15 | "build:firefox": "cross-env TYPE=chrome MV=3 webpack --config config/webpack.prod.js", 16 | "build:firefox:v3": "cross-env TYPE=firefox MV=3 webpack --config config/webpack.prod.js", 17 | "lint": "eslint 'src/**/*.{js,ts}' && echo 'Linting complete!'", 18 | "lint-fix": "eslint 'src/**/*.{js,ts}' --fix" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/d3ward/floalty.git" 23 | }, 24 | "author": "Eduard Ursu", 25 | "license": "(CC BY-NC-SA 4.0)", 26 | "bugs": { 27 | "url": "https://github.com/d3ward/floatly/issues" 28 | }, 29 | "homepage": "https://github.com/d3ward/floatly#readme", 30 | "devDependencies": { 31 | "@babel/core": "^7.24.3", 32 | "@babel/plugin-transform-runtime": "^7.24.3", 33 | "@babel/preset-env": "^7.24.3", 34 | "autoprefixer": "^10.4.19", 35 | "babel-loader": "^9.1.3", 36 | "clean-webpack-plugin": "^4.0.0", 37 | "copy-webpack-plugin": "^12.0.2", 38 | "cross-env": "^7.0.3", 39 | "css-loader": "^6.10.0", 40 | "eslint": "^8.57.0", 41 | "file-loader": "^6.2.0", 42 | "html-webpack-plugin": "^5.6.0", 43 | "mini-css-extract-plugin": "^2.8.1", 44 | "node-sass": "^9.0.0", 45 | "postcss-loader": "^8.1.1", 46 | "prettier": "^3.2.5", 47 | "sass": "^1.72.0", 48 | "sass-loader": "^14.1.1", 49 | "style-loader": "^3.3.4", 50 | "terser-webpack-plugin": "^5.3.10", 51 | "webpack": "^5.94.0", 52 | "webpack-cli": "^5.1.4", 53 | "webpack-dev-server": "^5.0.4" 54 | }, 55 | "dependencies": { 56 | "a11y-dialog": "^8.0.4", 57 | "html-loader": "^5.0.0", 58 | "purgecss": "^5.0.0", 59 | "sortablejs": "^1.15.2", 60 | "template-ejs-loader": "^0.9.4", 61 | "vanilla-picker": "^2.12.2" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/assets/png/icon152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3ward/floatly/8f06a8b7fd8e7a2dd7a380d7a55c870be2757106/src/assets/png/icon152.png -------------------------------------------------------------------------------- /src/assets/png/icon20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3ward/floatly/8f06a8b7fd8e7a2dd7a380d7a55c870be2757106/src/assets/png/icon20.png -------------------------------------------------------------------------------- /src/assets/png/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3ward/floatly/8f06a8b7fd8e7a2dd7a380d7a55c870be2757106/src/assets/png/icon256.png -------------------------------------------------------------------------------- /src/assets/png/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3ward/floatly/8f06a8b7fd8e7a2dd7a380d7a55c870be2757106/src/assets/png/icon48.png -------------------------------------------------------------------------------- /src/assets/png/icon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3ward/floatly/8f06a8b7fd8e7a2dd7a380d7a55c870be2757106/src/assets/png/icon512.png -------------------------------------------------------------------------------- /src/assets/png/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3ward/floatly/8f06a8b7fd8e7a2dd7a380d7a55c870be2757106/src/assets/png/icon64.png -------------------------------------------------------------------------------- /src/assets/png/icon96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3ward/floatly/8f06a8b7fd8e7a2dd7a380d7a55c870be2757106/src/assets/png/icon96.png -------------------------------------------------------------------------------- /src/assets/png/preview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3ward/floatly/8f06a8b7fd8e7a2dd7a380d7a55c870be2757106/src/assets/png/preview1.png -------------------------------------------------------------------------------- /src/assets/png/preview2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3ward/floatly/8f06a8b7fd8e7a2dd7a380d7a55c870be2757106/src/assets/png/preview2.png -------------------------------------------------------------------------------- /src/assets/png/preview3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3ward/floatly/8f06a8b7fd8e7a2dd7a380d7a55c870be2757106/src/assets/png/preview3.png -------------------------------------------------------------------------------- /src/assets/png/preview4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3ward/floatly/8f06a8b7fd8e7a2dd7a380d7a55c870be2757106/src/assets/png/preview4.png -------------------------------------------------------------------------------- /src/assets/png/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3ward/floatly/8f06a8b7fd8e7a2dd7a380d7a55c870be2757106/src/assets/png/screenshot.png -------------------------------------------------------------------------------- /src/assets/svg/floatly.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 14 | 17 | -------------------------------------------------------------------------------- /src/assets/svg/floatly_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/svg/floatly_r.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/css/fab/fab.css: -------------------------------------------------------------------------------- 1 | .snackbar{position:fixed;top:-100%;left:50%;transform:translateX(-50%);padding:0 !important;margin:0 !important;border-radius:10px;width:400px !important;font-size:16px !important;transition:all .5s;z-index:9999999 !important}@media (max-width: 424px){.snackbar{width:calc(100% - 20px) !important}}.snackbar div{background:#0074D8 !important;color:#fafafa !important;box-shadow:rgba(99,99,99,0.2) 0px 2px 8px 0px !important;padding:20px 30px !important;margin:0 !important;text-align:center;width:auto !important}.fab_btn{background:var(--ftlcl0)}.fab_wrap{position:absolute;padding:0 !important;margin:0 !important;z-index:999999 !important;background:transparent !important;box-shadow:none !important}.fab_wrap *{border-radius:0;box-shadow:none !important;padding:0;margin:0;background:unset;color:unset}.fab_cnt{box-sizing:border-box}.fab_btn{position:fixed;z-index:1010;height:50px;width:50px;padding:0 !important;bottom:var(--fab-y);left:var(--fab-x);background-color:var(--ftlcl0) !important;color:var(--ftlcl1) !important;border-radius:50%;box-shadow:0px 5px 20px rgba(0,0,0,0.15);user-select:none;display:flex;justify-content:center;align-items:center;pointer-events:auto}.fab_btn svg{fill:currentColor;height:26px}.fab_wrap.open{pointer-events:unset !important}.fab_wrap.open .fab_cnt{opacity:1 !important;visibility:visible !important}.fab_cnt>[data-id="fs"].fullscreen svg:nth-child(2),.fab_cnt>[data-id="fs"] svg:nth-child(1){display:inline}.fab_cnt>[data-id="fs"].fullscreen svg:nth-child(1),.fab_cnt>[data-id="fs"] svg:nth-child(2){display:none}.fab_cnt>[data-id="bt"].bookmark svg:nth-child(2),.fab_cnt>[data-id="bt"] svg:nth-child(1){display:inline}.fab_cnt>[data-id="bt"].bookmark svg:nth-child(1),.fab_cnt>[data-id="bt"] svg:nth-child(2){display:none} 2 | -------------------------------------------------------------------------------- /src/css/fab/fab_h.css: -------------------------------------------------------------------------------- 1 | #fab_h_cnt{position:fixed;overflow:auto;display:flex;flex-direction:row;flex-wrap:nowrap;overflow-x:auto;gap:5px;bottom:calc(60px + var(--fab-y));padding:5px !important;left:5px;width:calc(100vw - 10px);min-height:50px;opacity:0;visibility:hidden;background-color:var(--ftlcl4) !important;border:solid 2px var(--ftlcl5);border-radius:50px;transition:all .3s ease-in-out}#fab_h_cnt div{display:inline-flex;justify-content:center;align-items:center;width:46px;flex:0 0 auto;height:46px;margin:0 !important;padding:0 !important;color:var(--ftlcl3);background-color:var(--ftlcl2) !important;border-radius:50%} 2 | -------------------------------------------------------------------------------- /src/css/fab/fab_r.css: -------------------------------------------------------------------------------- 1 | .fab_radial div>svg{height:25px}#fab_r{position:fixed;bottom:calc(-125px + var(--fab-y));left:calc(-125px + var(--fab-x));width:300px;height:300px;padding:0 !important;margin:0;pointer-events:none;z-index:1000}#fab_r_cnt{width:300px;height:300px;opacity:0;visibility:hidden;border-radius:50%;background-color:var(--ftlcl4) !important;border:solid 2px var(--ftlcl5) !important}.open #fab_r_cnt{opacity:1;visibility:visible !important;pointer-events:auto}.open #fab_r_cnt div{text-decoration:none;background-color:var(--ftlcl2) !important;color:var(--ftlcl3) !important;display:flex;align-items:center;justify-content:center;border-radius:50%;height:46px;width:46px;padding:0 !important;margin-left:-23px;margin-top:-23px;position:absolute;text-align:center}#fly_btn-r{bottom:calc(50% - 25px);left:calc(50% - 25px);position:absolute;z-index:1000;height:50px;width:50px;background-color:var(--ftlcl0) !important;color:var(--ftlcl1) !important;border-radius:50%;user-select:none;display:flex;justify-content:center;align-items:center;pointer-events:auto} 2 | -------------------------------------------------------------------------------- /src/css/fab/fab_s.css: -------------------------------------------------------------------------------- 1 | #fly_btn-sheet{bottom:calc(50% - 25px);left:calc(50% - 25px);position:absolute;z-index:1000;height:50px;width:50px;background-color:var(--ftlcl0) !important;color:var(--ftlcl1) !important;border-radius:50%;user-select:none;display:flex;justify-content:center;align-items:center;pointer-events:auto}#fab_s_cnt{border:2px solid var(--ftlcl2);display:flex;flex-wrap:wrap;justify-content:left;background:var(--ftlcl2);border-radius:.5rem}#fab_s_cnt div{color:var(--ftlcl3);height:46px;width:46px;margin:0 !important;padding:0 !important;display:flex;justify-content:center;align-items:center;background:var(--ftlcl2)}#fab_s_cnt div>svg{height:25px}.bottom-sheet{pointer-events:none;visibility:hidden;overflow:hidden;position:fixed;left:0;bottom:0;height:100vh;width:100vw;margin:0 !important;padding:0 !important;z-index:1015;transition:opacity, visibility 0.25s}.bottom-sheet.active{visibility:visible;pointer-events:unset}.bs-scrim{opacity:0;display:block;position:absolute;z-index:1;height:100vh;width:100vw;background-color:rgba(0,0,0,0.3);transition:opacity 0.3s;top:0}.active .bs-scrim{opacity:1}.bs-sheet{display:flex;flex-direction:column;gap:.5rem;position:fixed;left:0;bottom:0px;width:100%;border-radius:1rem 1rem 0 0;min-height:unset;background:var(--ftlcl4) !important;transition:transform 250ms cubic-bezier(0.4, 0, 0.2, 1);transform:translateY(100%);z-index:2}.bs-sheet>*{flex-grow:0}.active .bs-sheet{transform:var(--translateY)}.bs-handle{display:flex;flex-direction:column;align-items:center;padding:.5rem}.bs-handle>span{display:block;height:4px;width:32px;background-color:rgba(0,0,0,0.3);border-radius:2px}.bs-cnt{position:relative;margin:0;padding:0;display:flex;flex-grow:1;max-width:600px;margin:auto !important;flex-direction:column;align-items:center}.bs-footer{width:auto;padding:0.5rem;display:flex;align-items:center;justify-content:center;height:40px;border-radius:0;text-align:center;border-top:2px solid var(--ftlcl6) !important;color:var(--ftlcl6) !important}.bs-cnt>*{width:100%}.bs-handle>span{background:var(--ftlcl6) !important} 2 | -------------------------------------------------------------------------------- /src/css/picker.css: -------------------------------------------------------------------------------- 1 | .layout_default.picker_wrapper{ 2 | width: auto; 3 | background: transparent; 4 | box-shadow: none; 5 | gap: 10px; 6 | } 7 | .layout_default.picker_wrapper>* { 8 | margin: 1em; 9 | border-radius: 5px; 10 | } 11 | .layout_default .picker_editor{ 12 | width: auto; 13 | } -------------------------------------------------------------------------------- /src/data/actions.json: -------------------------------------------------------------------------------- 1 | { 2 | "ct": { 3 | "name" :"Close tab", 4 | "icon" :"" 5 | }, 6 | "ont" :{ 7 | "name":"Open new tab", 8 | "icon": "" 9 | }, 10 | "dt": { 11 | "name":"Duplicate tab", 12 | "icon" :"" 13 | }, 14 | "stt": { 15 | "name":"Scroll to top", 16 | "icon" :"" 17 | }, 18 | "stb": { 19 | "name":"Scroll to bottom", 20 | "icon" :"" 21 | }, 22 | "fs": { 23 | "name": "Enable/Disable fullscreen", 24 | "icon" :"", 25 | "icon_2":"" 26 | 27 | }, 28 | "ctu": { 29 | "name": "Copy tab url to clipboard", 30 | "icon" : "" 31 | }, 32 | "oui": { 33 | "name":"Open url on incognito", 34 | "icon" :"" 35 | }, 36 | "ob": { 37 | "name":"Open bookmarks", 38 | "icon" :"" 39 | }, 40 | "od": { 41 | "name":"Open downloads", 42 | "icon" :"" 43 | }, 44 | "of": { 45 | "name":"Open flags", 46 | "icon" :"" 47 | }, 48 | "oe": { 49 | "name": "Open extensions", 50 | "icon" :"" 51 | }, 52 | "os": { 53 | "name": "Open settings", 54 | "icon" :"" 55 | }, 56 | "ohp": { 57 | "name": "Open homepage", 58 | "icon" :"" 59 | }, 60 | "oh": { 61 | "name": "Open history", 62 | "icon" :"" 63 | }, 64 | "vsc": { 65 | "name":"View source code", 66 | "icon" :"" 67 | }, 68 | "rt": { 69 | "name" : "Reload tab", 70 | "icon" :"" 71 | }, 72 | "odc": { 73 | "name": "Open dev console", 74 | "icon" :"" 75 | }, 76 | "bt": { 77 | "name": "Bookmark tab", 78 | "icon" :"", 79 | "icon_2":"" 80 | }, 81 | "st": { 82 | "name":"Share tab url", 83 | "icon" :"" 84 | }, 85 | "gb": { 86 | "name":"Go back", 87 | "icon" :"" 88 | } 89 | } -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('partials/head.ejs', { 4 | page: 'index' , 5 | title:'Floatly', 6 | description:"", 7 | url: 'd3ward.github.io/floatly' , 8 | keywords:'d3ward,portfolio,projects,github,css,js,kiwi,html,extensions' }) %> 9 | 10 | 11 | 12 | 13 | 14 | 15 | <%- include('partials/header.ejs', {page:'index'}) %> 16 | <%- include('partials/support_me.ejs') %> 17 |
18 | 19 |
20 |
21 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 34 | 37 | 38 |

Simple.Powerful.Fast.

39 |

Kanban style homepage with your bookmarks and powerful search

40 |
41 |
42 | 43 | 44 | 46 | GitHub 47 | 49 | Github 50 |
51 | 52 |
53 |
54 |
55 |
56 |
57 |
58 | 60 | 63 | 64 |
65 |
Bookmarks
66 |
67 |

Visualize your bookmarks as a Kanban Board

68 |
69 |
70 |
71 |
72 |
73 | 74 |
75 |
Customizable
76 |
77 |

Edit everything to fit your style and colors

78 |
79 |
80 |
81 |
82 |
83 | 84 |
85 |
Available on Github
86 |
Privacy friendly , free and open source ! 87 |
88 |
89 |
90 |
91 |
92 | 94 | 96 | 97 | <%- include('partials/footer.ejs') %> 98 | <%- include('partials/gotop.ejs') %> 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/js/actions_div.js: -------------------------------------------------------------------------------- 1 | 2 | var actionsDiv = [ 3 | '
', 4 | '
', 5 | '
', 6 | '
', 7 | '
', 8 | '
', 9 | '
', 10 | '
', 11 | '
', 12 | '
', 13 | '
', 14 | '
', 15 | '
', 16 | '
', 17 | '
', 18 | '
', 19 | '
', 20 | '
', 21 | '
', 22 | '
' 23 | ]; 24 | -------------------------------------------------------------------------------- /src/js/background.js: -------------------------------------------------------------------------------- 1 | chrome.runtime.onInstalled.addListener(function (details) { 2 | if (details.reason == 'install') chrome.runtime.openOptionsPage() 3 | }) 4 | 5 | chrome.browserAction.onClicked.addListener(function () { 6 | chrome.runtime.openOptionsPage() 7 | }) 8 | chrome.runtime.onMessage.addListener(function (message, sender) { 9 | if (message.book) { 10 | chrome.bookmarks.search(message.url, function (result) { 11 | var t = result.length > 0 12 | if (message.book != 'check') { 13 | if (t) { 14 | chrome.bookmarks.remove(result[result.length - 1].id) 15 | } else { 16 | chrome.bookmarks.create({ 17 | url: message.url, 18 | title: message.title, 19 | }) 20 | } 21 | t = !t 22 | } 23 | chrome.tabs.sendMessage(sender.tab.id, { bookmarked: t }) 24 | }) 25 | } 26 | 27 | if (message.closeThis) chrome.tabs.remove(sender.tab.id) 28 | if (message.copyToClip) { 29 | chrome.tabs.query( 30 | { 31 | currentWindow: true, 32 | active: true, 33 | }, 34 | function (tabs) { 35 | var input = document.createElement('textarea') 36 | document.body.appendChild(input) 37 | input.value = tabs[0].url 38 | input.focus() 39 | input.select() 40 | document.execCommand('Copy') 41 | input.remove() 42 | }, 43 | ) 44 | } 45 | if (message.shareURL) 46 | chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { 47 | const tab = tabs[0] 48 | if (tab) { 49 | chrome.tabs.share(tab.id, function (shareStatus) { 50 | if (shareStatus) { 51 | console.log('Share action initiated successfully.') 52 | } else { 53 | console.log('Failed to initiate the share action.') 54 | } 55 | }) 56 | } 57 | }) 58 | if (message.chromeURL) 59 | if (message.chromeURL == 'newtab') chrome.tabs.create({}) 60 | else 61 | chrome.tabs.create({ 62 | url: message.chromeURL, 63 | }) 64 | if (message.newTabInc) 65 | chrome.tabs.query( 66 | { 67 | currentWindow: true, 68 | active: true, 69 | }, 70 | function (tabs) { 71 | chrome.windows.create({ 72 | url: tabs[0].url, 73 | incognito: true, 74 | }) 75 | }, 76 | ) 77 | if (message.settings) chrome.runtime.openOptionsPage() 78 | }) 79 | -------------------------------------------------------------------------------- /src/js/components/actions.js: -------------------------------------------------------------------------------- 1 | function scrollTo(element, to, duration) { 2 | if (duration <= 0) return 3 | let difference = to - element.scrollTop 4 | let perTick = (difference / duration) * 10 5 | setTimeout(function () { 6 | element.scrollTop = element.scrollTop + perTick 7 | if (element.scrollTop === to) return 8 | scrollTo(element, to, duration - 10) 9 | }, 10) 10 | } 11 | 12 | export const a_func = { 13 | ct: function () { 14 | chrome.runtime.sendMessage({ closeThis: true }) 15 | }, 16 | ont: function () { 17 | chrome.runtime.sendMessage({ chromeURL: 'newtab' }) 18 | }, 19 | dt: function () { 20 | window.open(window.location.href) 21 | }, 22 | stt: function (scroll_speed) { 23 | scrollTo(document.documentElement, 0, scroll_speed) 24 | }, 25 | stb: function (scroll_speed) { 26 | scrollTo(document.documentElement, document.body.clientHeight, scroll_speed) 27 | }, 28 | fs: function (callback) { 29 | callback() 30 | }, 31 | ctu: function () { 32 | chrome.runtime.sendMessage({ copyToClip: true }) 33 | }, 34 | oui: function () { 35 | chrome.runtime.sendMessage({ newTabInc: true }) 36 | }, 37 | ob: function () { 38 | chrome.runtime.sendMessage({ chromeURL: 'chrome://bookmarks' }) 39 | }, 40 | od: function () { 41 | chrome.runtime.sendMessage({ chromeURL: 'chrome://downloads' }) 42 | }, 43 | of: function () { 44 | chrome.runtime.sendMessage({ chromeURL: 'chrome://flags' }) 45 | }, 46 | oe: function () { 47 | chrome.runtime.sendMessage({ chromeURL: 'chrome://extensions' }) 48 | }, 49 | os: function () { 50 | chrome.runtime.sendMessage({ chromeURL: 'chrome://settings' }) 51 | }, 52 | ohp: function (url) { 53 | chrome.runtime.sendMessage({ chromeURL: url }) 54 | }, 55 | oh: function () { 56 | chrome.runtime.sendMessage({ chromeURL: 'chrome://history' }) 57 | }, 58 | vsc: function () { 59 | chrome.runtime.sendMessage({ chromeURL: 'view-source:' + window.location.href }) 60 | }, 61 | rt: function () { 62 | window.location.reload() 63 | }, 64 | bt: function () { 65 | chrome.runtime.sendMessage({ book: true, url: window.location.href, title: document.title }) 66 | }, 67 | st: function () { 68 | try { 69 | chrome.runtime.sendMessage({ shareURL: 'window.location.href' }) 70 | } catch (err) { 71 | console.log(err) 72 | navigator.share({ url: window.location.href }) 73 | } 74 | }, 75 | gb: function () { 76 | window.history.back() 77 | }, 78 | } 79 | -------------------------------------------------------------------------------- /src/js/components/aos.js: -------------------------------------------------------------------------------- 1 | export function aos() { 2 | //Get and observe all the items with the item class 3 | let items = document.querySelectorAll('[class*=_aos]') 4 | //Only Use the IntersectionObserver if it is supported and _aos elements exist 5 | if (IntersectionObserver && items) { 6 | //When the element is visible on the viewport, 7 | //add the _aos-done class so it creates the _aos animation. 8 | let callback = function (entries) { 9 | entries.forEach((entry) => { 10 | //if the element is visible, add the _aos-done class 11 | if (entry.isIntersecting && !entry.target.classList.contains('_aos-done')) { 12 | entry.target.classList.add('_aos-done') 13 | } else { 14 | //else the element do reverse animation 15 | entry.target.classList.remove('_aos-done') 16 | } 17 | }) 18 | } 19 | //Create the observer 20 | let observer = new IntersectionObserver(callback, { 21 | root: null, 22 | threshold: 0, 23 | }) 24 | //Add each _aos element to the observer 25 | items.forEach((item) => { 26 | observer.observe(item) 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/js/components/blacklistManager.js: -------------------------------------------------------------------------------- 1 | export class ItemManager { 2 | constructor(containerId) { 3 | this.containerId = containerId 4 | } 5 | 6 | generateID() { 7 | return Math.random().toString(36).substr(2, 9) 8 | } 9 | 10 | addToContainer(val) { 11 | if (val.length > 2) { 12 | const div = document.createElement('div') 13 | const id = this.generateID() 14 | div.innerHTML = `` 15 | document.getElementById(this.containerId).prepend(div) 16 | } 17 | } 18 | 19 | removeFromContainer() { 20 | const container = document.getElementById(this.containerId) 21 | const items = container.querySelectorAll("input[name='tbch']:checked") 22 | items.forEach((item) => { 23 | item.parentElement.remove() 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/js/components/bottomsheet.js: -------------------------------------------------------------------------------- 1 | export class BottomSheet { 2 | constructor(selector, options = {}) { 3 | this.bottomSheet = document.querySelector(selector) 4 | this.activeClass = 'active' 5 | this.options = { 6 | threshold: options.threshold || 100, 7 | eventMap: options.eventMap || { 8 | start: 'mousedown', 9 | move: 'mousemove', 10 | end: 'mouseup', 11 | }, 12 | } 13 | this.init() 14 | } 15 | 16 | init() { 17 | this.bottomSheet 18 | .querySelector('.bs-handle') 19 | .addEventListener(this.options.eventMap.start, this.startDrag.bind(this), { passive: false }) 20 | this.bottomSheet.querySelector('.bs-scrim').addEventListener('click', this.hide.bind(this), { passive: false }) 21 | this.bottomSheet.querySelector('.bs-footer').addEventListener('click', this.hide.bind(this), { passive: false }) 22 | } 23 | 24 | startDrag(event) { 25 | event.preventDefault() 26 | navigator.vibrate(100) 27 | const initialY = event.type === 'touchstart' ? event.touches[0].clientY : event.clientY 28 | let currentY = initialY 29 | 30 | const onMove = (moveEvent) => { 31 | moveEvent.preventDefault() 32 | currentY = moveEvent.type === 'touchmove' ? moveEvent.touches[0].clientY : moveEvent.clientY 33 | const offsetY = initialY - currentY 34 | console.log(offsetY) 35 | if (offsetY < 40) { 36 | var m = '' + offsetY * -1 37 | if (offsetY > 0) m = '-' + offsetY 38 | if (offsetY * -1 >= this.options.threshold) navigator.vibrate(100) 39 | this.bottomSheet.querySelector('.bs-sheet').style.transform = `translateY(${m}px)` 40 | } 41 | } 42 | 43 | const onEnd = (endEvent) => { 44 | endEvent.preventDefault() 45 | navigator.vibrate(100) 46 | const offsetY = initialY - currentY 47 | 48 | if (offsetY * -1 >= this.options.threshold) { 49 | this.hide() 50 | } else { 51 | this.show() 52 | } 53 | 54 | document.removeEventListener(this.options.eventMap.move, onMove) 55 | document.removeEventListener(this.options.eventMap.end, onEnd) 56 | } 57 | 58 | document.addEventListener(this.options.eventMap.move, onMove, { passive: false }) 59 | document.addEventListener(this.options.eventMap.end, onEnd, { passive: false }) 60 | } 61 | 62 | show() { 63 | this.bottomSheet.classList.add(this.activeClass) 64 | this.bottomSheet.querySelector('.bs-sheet').style.transform = 'translateY(0)' 65 | } 66 | 67 | hide() { 68 | this.bottomSheet.classList.remove(this.activeClass) 69 | this.bottomSheet.querySelector('.bs-sheet').style.transform = 'translateY(100%)' 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/js/components/dialog.js: -------------------------------------------------------------------------------- 1 | !(function (t, e) { 2 | 'object' == typeof exports && 'undefined' != typeof module 3 | ? (module.exports = e()) 4 | : 'function' == typeof define && define.amd 5 | ? define(e) 6 | : ((t = 7 | 'undefined' != typeof globalThis 8 | ? globalThis 9 | : t || self).A11yDialog = e()) 10 | })(this, function () { 11 | 'use strict' 12 | var t = [ 13 | 'a[href]:not([tabindex^="-"])', 14 | 'area[href]:not([tabindex^="-"])', 15 | 'input:not([type="hidden"]):not([type="radio"]):not([disabled]):not([tabindex^="-"])', 16 | 'input[type="radio"]:not([disabled]):not([tabindex^="-"])', 17 | 'select:not([disabled]):not([tabindex^="-"])', 18 | 'textarea:not([disabled]):not([tabindex^="-"])', 19 | 'button:not([disabled]):not([tabindex^="-"])', 20 | 'iframe:not([tabindex^="-"])', 21 | 'audio[controls]:not([tabindex^="-"])', 22 | 'video[controls]:not([tabindex^="-"])', 23 | '[contenteditable]:not([tabindex^="-"])', 24 | '[tabindex]:not([tabindex^="-"])' 25 | ] 26 | 27 | function e(t) { 28 | ;(this._show = this.show.bind(this)), 29 | (this._hide = this.hide.bind(this)), 30 | (this._maintainFocus = this._maintainFocus.bind(this)), 31 | (this._bindKeypress = this._bindKeypress.bind(this)), 32 | (this.$el = t), 33 | (this.shown = !1), 34 | (this._id = 35 | this.$el.getAttribute('data-a11y-dialog') || this.$el.id), 36 | (this._previouslyFocused = null), 37 | (this._listeners = {}), 38 | this.create() 39 | } 40 | 41 | function i(t, e) { 42 | return ( 43 | (i = (e || document).querySelectorAll(t)), 44 | Array.prototype.slice.call(i) 45 | ) 46 | var i 47 | } 48 | 49 | function n(t) { 50 | ;(t.querySelector('[autofocus]') || t).focus() 51 | } 52 | 53 | function s() { 54 | i('[data-a11y-dialog]').forEach(function (t) { 55 | new e(t) 56 | }) 57 | } 58 | return ( 59 | (e.prototype.create = function () { 60 | this.$el.setAttribute('aria-hidden', !0), 61 | this.$el.setAttribute('aria-modal', !0), 62 | this.$el.setAttribute('tabindex', -1), 63 | this.$el.hasAttribute('role') || 64 | this.$el.setAttribute('role', 'dialog'), 65 | (this._openers = i( 66 | '[data-a11y-dialog-show="' + this._id + '"]' 67 | )), 68 | this._openers.forEach( 69 | function (t) { 70 | t.addEventListener('click', this._show) 71 | }.bind(this) 72 | ) 73 | const t = this.$el 74 | return ( 75 | (this._closers = i('[data-a11y-dialog-hide]', this.$el) 76 | .filter(function (e) { 77 | return ( 78 | e.closest( 79 | '[aria-modal="true"], [data-a11y-dialog]' 80 | ) === t 81 | ) 82 | }) 83 | .concat(i('[data-a11y-dialog-hide="' + this._id + '"]'))), 84 | this._closers.forEach( 85 | function (t) { 86 | t.addEventListener('click', this._hide) 87 | }.bind(this) 88 | ), 89 | this._fire('create'), 90 | this 91 | ) 92 | }), 93 | (e.prototype.show = function (t) { 94 | return ( 95 | this.shown || 96 | ((document.documentElement.style.overflowY = 'hidden'), 97 | (this._previouslyFocused = document.activeElement), 98 | this.$el.removeAttribute('aria-hidden'), 99 | (this.shown = !0), 100 | n(this.$el), 101 | document.body.addEventListener( 102 | 'focus', 103 | this._maintainFocus, 104 | !0 105 | ), 106 | document.addEventListener('keydown', this._bindKeypress), 107 | this._fire('show', t)), 108 | this 109 | ) 110 | }), 111 | (e.prototype.hide = function (t) { 112 | return this.shown 113 | ? ((document.documentElement.style.overflowY = ''), 114 | (this.shown = !1), 115 | this.$el.setAttribute('aria-hidden', 'true'), 116 | this._previouslyFocused && 117 | this._previouslyFocused.focus && 118 | this._previouslyFocused.focus(), 119 | document.body.removeEventListener( 120 | 'focus', 121 | this._maintainFocus, 122 | !0 123 | ), 124 | document.removeEventListener('keydown', this._bindKeypress), 125 | this._fire('hide', t), 126 | this) 127 | : this 128 | }), 129 | (e.prototype.destroy = function () { 130 | return ( 131 | this.hide(), 132 | this._openers.forEach( 133 | function (t) { 134 | t.removeEventListener('click', this._show) 135 | }.bind(this) 136 | ), 137 | this._closers.forEach( 138 | function (t) { 139 | t.removeEventListener('click', this._hide) 140 | }.bind(this) 141 | ), 142 | this._fire('destroy'), 143 | (this._listeners = {}), 144 | this 145 | ) 146 | }), 147 | (e.prototype.on = function (t, e) { 148 | return ( 149 | void 0 === this._listeners[t] && (this._listeners[t] = []), 150 | this._listeners[t].push(e), 151 | this 152 | ) 153 | }), 154 | (e.prototype.off = function (t, e) { 155 | var i = (this._listeners[t] || []).indexOf(e) 156 | return i > -1 && this._listeners[t].splice(i, 1), this 157 | }), 158 | (e.prototype._fire = function (t, e) { 159 | var i = this._listeners[t] || [], 160 | n = new CustomEvent(t, { 161 | detail: e 162 | }) 163 | this.$el.dispatchEvent(n), 164 | i.forEach( 165 | function (t) { 166 | t(this.$el, e) 167 | }.bind(this) 168 | ) 169 | }), 170 | (e.prototype._bindKeypress = function (e) { 171 | const n = document.activeElement 172 | ;(n && n.closest('[aria-modal="true"]') !== this.$el) || 173 | (this.shown && 174 | 'Escape' === e.key && 175 | 'alertdialog' !== this.$el.getAttribute('role') && 176 | (e.preventDefault(), this.hide(e)), 177 | this.shown && 178 | 'Tab' === e.key && 179 | (function (e, n) { 180 | var s = (function (e) { 181 | return i(t.join(','), e).filter(function (t) { 182 | return !!( 183 | t.offsetWidth || 184 | t.offsetHeight || 185 | t.getClientRects().length 186 | ) 187 | }) 188 | })(e), 189 | o = s.indexOf(document.activeElement) 190 | n.shiftKey && 0 === o 191 | ? (s[s.length - 1].focus(), n.preventDefault()) 192 | : n.shiftKey || 193 | o !== s.length - 1 || 194 | (s[0].focus(), n.preventDefault()) 195 | })(this.$el, e)) 196 | }), 197 | (e.prototype._maintainFocus = function (t) { 198 | !this.shown || 199 | t.target.closest('[aria-modal="true"]') || 200 | t.target.closest('[data-a11y-dialog-ignore-focus-trap]') || 201 | n(this.$el) 202 | }), 203 | 'undefined' != typeof document && 204 | ('loading' === document.readyState 205 | ? document.addEventListener('DOMContentLoaded', s) 206 | : window.requestAnimationFrame 207 | ? window.requestAnimationFrame(s) 208 | : window.setTimeout(s, 16)), 209 | e 210 | ) 211 | }) 212 | -------------------------------------------------------------------------------- /src/js/components/dragElement.js: -------------------------------------------------------------------------------- 1 | export function dragElement(el) { 2 | let startPosX = 0, 3 | startPosY = 0 4 | 5 | function mouseMove(e) { 6 | const { clientX, clientY } = e.touches ? e.touches[0] : e 7 | const newPosX = startPosX - clientX 8 | startPosX = clientX 9 | startPosY = clientY 10 | 11 | document.body.style.setProperty('--fab-y', `${screen.height - startPosY - 25}px`) 12 | document.body.style.setProperty('--fab-x', `${el.offsetLeft - newPosX}px`) 13 | } 14 | 15 | el.addEventListener('mousedown', function (e) { 16 | e.preventDefault() 17 | startPosX = e.clientX 18 | startPosY = e.clientY 19 | 20 | document.addEventListener('mousemove', mouseMove) 21 | document.addEventListener('mouseup', function () { 22 | document.removeEventListener('mousemove', mouseMove) 23 | }) 24 | }) 25 | 26 | el.addEventListener('touchstart', function (e) { 27 | e.preventDefault() 28 | const { clientX, clientY } = e.touches[0] 29 | startPosX = clientX 30 | startPosY = clientY 31 | 32 | document.addEventListener('touchmove', mouseMove) 33 | document.addEventListener('touchend', function () { 34 | document.removeEventListener('touchmove', mouseMove) 35 | }) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /src/js/components/fullscreen.js: -------------------------------------------------------------------------------- 1 | export function openFullscreen() { 2 | var elem = document.documentElement; 3 | if (elem.requestFullscreen) { 4 | elem.requestFullscreen(); 5 | } else if (elem.webkitRequestFullscreen) { 6 | elem.webkitRequestFullscreen(); 7 | } else if (elem.msRequestFullscreen) { 8 | elem.msRequestFullscreen(); 9 | } 10 | } 11 | export function closeFullscreen() { 12 | if (document.exitFullscreen) { 13 | document.exitFullscreen() 14 | } else if (document.webkitExitFullscreen) { 15 | document.webkitExitFullscreen() 16 | } else if (document.msExitFullscreen) { 17 | document.msExitFullscreen() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/js/components/gotop.js: -------------------------------------------------------------------------------- 1 | export function gotop() { 2 | var el = this 3 | el.gt = document.getElementById('gt-link') 4 | el.scrollToTop = function () { 5 | window.scroll({ 6 | top: 0, 7 | left: 0, 8 | behavior: 'smooth' 9 | }) 10 | } 11 | el.listeners = function () { 12 | window.addEventListener('scroll', () => { 13 | let y = window.scrollY 14 | if (y > 0) { 15 | el.gt.classList.remove('hidden') 16 | } else { 17 | el.gt.classList.add('hidden') 18 | } 19 | }) 20 | el.gt.onclick = function (e) { 21 | e.preventDefault() 22 | if ( 23 | document.documentElement.scrollTop || 24 | document.body.scrollTop > 0 25 | ) { 26 | el.scrollToTop() 27 | } 28 | } 29 | } 30 | if (el.gt) { 31 | el.listeners() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/js/components/navbar.js: -------------------------------------------------------------------------------- 1 | export function navbar() { 2 | var t = this 3 | t.n = document.querySelector('nav') 4 | t.bo = document.body.style.overflow 5 | t.close = function () { 6 | t.bo = 'auto' 7 | t.n.classList.remove('active') 8 | } 9 | t.open = function () { 10 | t.bo = 'hidden' 11 | t.n.classList.add('active') 12 | } 13 | if (t.n) { 14 | document.querySelector('nav>button').addEventListener('click', function () { 15 | console.log('toggleNav') 16 | if (t.n.classList.contains('active')) t.close() 17 | else t.open() 18 | }) 19 | document.querySelectorAll('nav ul > a').forEach((n) => n.addEventListener('click', t.close())) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/js/components/pagesRoute.js: -------------------------------------------------------------------------------- 1 | export function pagesRoute() { 2 | var t = this 3 | t.links = Array.from(document.querySelectorAll('[topage]')) 4 | t.scrollTop = () => { 5 | document.querySelector('html').scrollTop = 0 6 | document.querySelector('body').scrollTop = 0 7 | } 8 | t.navigate = (id) => { 9 | //Hide current active page 10 | var activePage = document.querySelector('section.page-active') 11 | if (activePage) activePage.classList.remove('page-active') 12 | var activeLink = document.querySelector('[topage].active') 13 | if (activeLink) activeLink.classList.remove('active') 14 | //Show the next page 15 | var nextPage = document.querySelector(id) 16 | if (nextPage) nextPage.classList.add('page-active') 17 | var nextLink = document.querySelector("[topage='" + id + "']") 18 | if (nextLink) nextLink.classList.add('active') 19 | //Scroll to top 20 | t.scrollTop() 21 | //Set history state 22 | if (history.pushState) history.pushState(null, null, id) 23 | else location.hash = id 24 | } 25 | t.listeners = () => { 26 | t.links.forEach((page) => { 27 | var id = page.getAttribute('topage') 28 | page.addEventListener('click', () => { 29 | t.navigate(id) 30 | }) 31 | }) 32 | } 33 | if (t.links) { 34 | if (window.location.hash) t.navigate(window.location.hash) 35 | t.listeners() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/js/components/rotate.js: -------------------------------------------------------------------------------- 1 | //fab_radial rotate events 2 | export const rotate = (EL, ev_li) => { 3 | let ang = 0 4 | let angStart = 0 5 | let isStart = false 6 | 7 | const angXY = (ev) => { 8 | const bcr = EL.getBoundingClientRect() 9 | const radius = bcr.width / 2 10 | const { clientX, clientY } = ev.touches ? ev.touches[0] : ev 11 | const y = clientY - bcr.top - radius 12 | const x = clientX - bcr.left - radius 13 | return Math.atan2(y, x) 14 | } 15 | 16 | const mousedown = (ev) => { 17 | isStart = true 18 | angStart = angXY(ev) - ang 19 | } 20 | 21 | const mousemove = (ev) => { 22 | if (!isStart) return 23 | ev.preventDefault() 24 | ang = angXY(ev) - angStart 25 | EL.style.transform = `rotateZ(${ang}rad)` 26 | 27 | const childAng = (ang / (2 * Math.PI)) * 360 // change from negative to positive 28 | const children = EL.querySelectorAll('#fab_r_cnt > div') 29 | children.forEach((child) => { 30 | child.style.transform = `rotateZ(${-childAng}deg)` 31 | }) 32 | } 33 | 34 | const mouseup = () => { 35 | isStart = false 36 | } 37 | 38 | EL.addEventListener(ev_li['start'], mousedown, { passive: false }) 39 | document.addEventListener(ev_li['move'], mousemove, { passive: false }) 40 | document.addEventListener(ev_li['end'], mouseup, { passive: false }) 41 | } 42 | -------------------------------------------------------------------------------- /src/js/components/snackbar.js: -------------------------------------------------------------------------------- 1 | export function Snackbar(option) { 2 | const t = this 3 | t.snack = document.createElement('div') 4 | t.snack.className = 'snackbar' 5 | t.message = document.createElement('div') 6 | t.snack.appendChild(t.message) 7 | document.body.appendChild(t.snack) 8 | 9 | t.top = option.topPos 10 | t.classNames = option.classNames 11 | t.autoClose = true 12 | t.autoCloseTimeout = 3000 13 | 14 | let timeoutId // Variable to hold the timeout ID 15 | 16 | // Methods 17 | t.reset = function () { 18 | t.message.innerHTML = '' 19 | } 20 | 21 | t.show = function (msg) { 22 | t.hide() 23 | t.message.innerHTML = msg 24 | t.snack.style.top = t.top 25 | 26 | if (t.autoClose) { 27 | clearTimeout(timeoutId) // Clear any existing timeout 28 | timeoutId = setTimeout(function () { 29 | t.hide() 30 | }, t.autoCloseTimeout) 31 | } 32 | } 33 | 34 | t.hide = function () { 35 | t.snack.style.top = '-100%' 36 | t.reset() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/js/components/swipeManager.js: -------------------------------------------------------------------------------- 1 | export class SwipeManager { 2 | constructor(selector, options = {}) { 3 | this.element = document.querySelector(selector) 4 | 5 | this.thresholds = { 6 | up: options.thresholds.up ?? 0, 7 | down: options.thresholds.down ?? 0, 8 | left: options.thresholds.left ?? 0, 9 | right: options.thresholds.right ?? 0, 10 | } 11 | 12 | this.callbacks = { 13 | up: options.onup ?? null, 14 | down: options.ondown ?? null, 15 | left: options.onleft ?? null, 16 | right: options.onright ?? null, 17 | } 18 | 19 | this.events = options.events || { 20 | start: 'mousedown', 21 | move: 'mousemove', 22 | end: 'mouseup', 23 | } 24 | this.movementThreshold = 5 25 | this.currentDirection = null 26 | this.dragging = false 27 | this.startX = 0 28 | this.startY = 0 29 | 30 | this.handleStart = this.handleStart.bind(this) 31 | this.handleMove = this.handleMove.bind(this) 32 | this.handleEnd = this.handleEnd.bind(this) 33 | 34 | this.element.addEventListener(this.events.start, this.handleStart, { passive: false }) 35 | document.addEventListener(this.events.move, this.handleMove, { passive: false }) 36 | document.addEventListener(this.events.end, this.handleEnd) 37 | } 38 | 39 | handleStart(event) { 40 | this.dragging = true 41 | this.startX 42 | this.startX = event.type === 'touchstart' ? event.touches[0].clientX : event.clientX 43 | this.startY = event.type === 'touchstart' ? event.touches[0].clientY : event.clientY 44 | } 45 | 46 | handleMove(event) { 47 | if (!this.dragging) return 48 | 49 | const currentX = event.type === 'touchmove' ? event.touches[0].clientX : event.clientX 50 | const currentY = event.type === 'touchmove' ? event.touches[0].clientY : event.clientY 51 | 52 | const deltaX = currentX - this.startX 53 | const deltaY = currentY - this.startY 54 | 55 | // Check if the movement exceeds the movement threshold 56 | if (Math.abs(deltaX) >= this.movementThreshold || Math.abs(deltaY) >= this.movementThreshold) { 57 | event.preventDefault() 58 | 59 | if (this.currentDirection === null) { 60 | if (Math.abs(deltaX) > Math.abs(deltaY)) { 61 | this.currentDirection = deltaX > 0 ? 'right' : 'left' 62 | } else { 63 | this.currentDirection = deltaY > 0 ? 'down' : 'up' 64 | } 65 | } 66 | 67 | const threshold = this.thresholds[this.currentDirection] 68 | 69 | let translationX = 0 70 | let translationY = 0 71 | 72 | if (this.currentDirection === 'left' || this.currentDirection === 'right') { 73 | translationX = 74 | this.currentDirection === 'left' ? Math.max(deltaX, -threshold) : Math.min(deltaX, threshold) 75 | } else { 76 | translationY = 77 | this.currentDirection === 'up' ? Math.max(deltaY, -threshold) : Math.min(deltaY, threshold) 78 | } 79 | 80 | this.element.style.transform = `translate(${translationX}px, ${translationY}px)` 81 | } 82 | } 83 | 84 | handleEnd(event) { 85 | if (!this.dragging) return 86 | 87 | let translationX = 0 88 | let translationY = 0 89 | 90 | const transformValues = this.element.style.transform.match(/-?\d+\.?\d*px/g) 91 | 92 | if (transformValues) { 93 | translationX = parseFloat(transformValues[0]) 94 | translationY = parseFloat(transformValues[1]) 95 | } 96 | 97 | let distance = 0 98 | if (this.currentDirection === 'left' || this.currentDirection === 'right') { 99 | distance = Math.abs(translationX) 100 | } else { 101 | distance = Math.abs(translationY) 102 | } 103 | 104 | if (distance >= this.thresholds[this.currentDirection] && this.callbacks[this.currentDirection]) { 105 | this.callbacks[this.currentDirection](event) // Add event parameter to callback 106 | } 107 | this.element.style.transform = '' 108 | 109 | this.dragging = false 110 | this.currentDirection = null 111 | this.startX = 0 112 | this.startY = 0 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/js/components/themeManager.js: -------------------------------------------------------------------------------- 1 | export function themeManager() { 2 | //Theme Switcher 3 | var toggles = document.getElementsByClassName('theme-toggle') 4 | 5 | if (window.CSS && CSS.supports('color', 'var(--bg)') && toggles) { 6 | var storedTheme = 7 | localStorage.getItem('theme') || 8 | (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') 9 | if (storedTheme) document.documentElement.setAttribute('data-theme', storedTheme) 10 | for (var i = 0; i < toggles.length; i++) { 11 | toggles[i].onclick = function () { 12 | var currentTheme = document.documentElement.getAttribute('data-theme') 13 | var targetTheme = 'light' 14 | 15 | if (currentTheme === 'light') { 16 | targetTheme = 'dark' 17 | } 18 | 19 | document.documentElement.setAttribute('data-theme', targetTheme) 20 | localStorage.setItem('theme', targetTheme) 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/js/components/utilities.js: -------------------------------------------------------------------------------- 1 | export function getEventListeners() { 2 | const isTouchDevice = 'ontouchstart' in window 3 | 4 | const events = { 5 | start: isTouchDevice ? 'touchstart' : 'mousedown', 6 | move: isTouchDevice ? 'touchmove' : 'mousemove', 7 | end: isTouchDevice ? 'touchend' : 'mouseup', 8 | } 9 | 10 | return events 11 | } 12 | export function generateID() { 13 | return '_' + Math.random().toString(36).substr(2, 5) 14 | } 15 | -------------------------------------------------------------------------------- /src/js/content.js: -------------------------------------------------------------------------------- 1 | import actions from '../data/actions.json' 2 | import { BottomSheet } from './components/bottomsheet' 3 | import { SwipeManager } from './components/swipeManager' 4 | import { Snackbar } from './components/snackbar' 5 | import { getEventListeners } from './components/utilities' 6 | import { rotate } from './components/rotate' 7 | import { a_func } from './components/actions' 8 | const fab_css = require('../css/fab/fab.css').toString() 9 | const fab_h_css = require('../css/fab/fab_h.css').toString() 10 | const fab_r_css = require('../css/fab/fab_r.css').toString() 11 | const fab_s_css = require('../css/fab/fab_s.css').toString() 12 | var snackbar = new Snackbar({ 13 | topPos: '10px', 14 | classNames: 'success', 15 | }) 16 | var ev_li = getEventListeners() 17 | var actions_icon_div = {} 18 | for (var key in actions) { 19 | var value = actions[key] 20 | actions_icon_div[key] = 21 | '
' + value.icon + '
' 22 | } 23 | function toggleFS() { 24 | const fs = document.querySelector('.fab_cnt>[data-id="fs"]') 25 | const isFullscreen = fs.classList.contains('fullscreen') 26 | 27 | if (isFullscreen) { 28 | snackbar.show('Exit fullscreen mode') 29 | const exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen 30 | 31 | if (exitFullscreen) { 32 | exitFullscreen.call(document) 33 | } 34 | fs.classList.remove('fullscreen') 35 | } else { 36 | const elem = document.documentElement 37 | const requestFullscreen = elem.requestFullscreen || elem.webkitRequestFullscreen || elem.msRequestFullscreen 38 | 39 | if (requestFullscreen) { 40 | requestFullscreen.call(elem) 41 | } 42 | fs.classList.add('fullscreen') 43 | snackbar.show('Enable fullscreen mode') 44 | } 45 | } 46 | 47 | // Function to handle fullscreen change event 48 | function handleFullscreenChange() { 49 | const fs = document.querySelector('.fab_cnt>[data-id="fs"]') 50 | if (fs) { 51 | const isFullscreen = 52 | document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement 53 | 54 | if (isFullscreen) { 55 | fs.classList.add('fullscreen') 56 | } else { 57 | fs.classList.remove('fullscreen') 58 | } 59 | } 60 | } 61 | 62 | // Event listener for fullscreen change event 63 | document.addEventListener('fullscreenchange', handleFullscreenChange) 64 | document.addEventListener('webkitfullscreenchange', handleFullscreenChange) 65 | document.addEventListener('msfullscreenchange', handleFullscreenChange) 66 | function enableAutoHideOnScroll(s, f, fb) { 67 | var prevScrollpos = window.pageYOffset 68 | window.onscroll = function () { 69 | var currentScrollPos = window.pageYOffset 70 | if (prevScrollpos > currentScrollPos) { 71 | document.getElementById(s).style.bottom = f 72 | } else { 73 | document.getElementById(s).style.bottom = fb 74 | } 75 | prevScrollpos = currentScrollPos 76 | } 77 | } 78 | var splist, posf 79 | // Get use options and inject 80 | chrome.storage.local.get( 81 | { 82 | style: 'fab', 83 | colors: ['#0878A5', '#ffffff', '#0878A5', '#ffffff', '#ffffff', '#101010', '#101010', '#0878A5', '#ffffff'], 84 | fab_actions: [], 85 | bab_actions: [], 86 | fab_style: 'horizontal', 87 | fab_pos: { x: '20px', y: '20px' }, 88 | options: { 89 | scroll_hide: false, 90 | scroll_speed: 800, 91 | home_url: 'chrome://newtab', 92 | }, 93 | blacklist: ['github.com', 'youtube.com'], 94 | swipe_actions: ['0', '0', '0', '0', '0'], 95 | }, 96 | function (items) { 97 | let blist = items.blacklist 98 | let curl = window.location.href 99 | for (var b = 0; b < blist.length; b++) if (curl.indexOf(blist[b]) > -1) return 100 | //Set colors 101 | for (var i = 0; i < items.colors.length; i++) document.body.style.setProperty('--ftlcl' + i, items.colors[i]) 102 | //Set fab position 103 | document.body.style.setProperty('--fab-y', items.fab_pos['y']) 104 | document.body.style.setProperty('--fab-x', items.fab_pos['x']) 105 | 106 | //General configuration 107 | var fab_html = '' 108 | var home_url = items.options['home_url'] 109 | var scroll_speed = items.options['scroll_speed'] 110 | var scroll_hide = items.options['scroll_hide'] 111 | const sa = items.swipe_actions 112 | if (items.style == 'fab') { 113 | const SM_options = { 114 | events: ev_li, 115 | thresholds: { 116 | up: sa[1] != '0' ? 50 : 0, 117 | down: sa[2] != '0' ? 50 : 0, 118 | right: sa[3] != '0' ? 50 : 0, 119 | left: sa[4] != '0' ? 50 : 0, 120 | }, 121 | onup: () => sa[1] != '0' && a_func[sa[1]], 122 | ondown: () => sa[2] != '0' && a_func[sa[2]], 123 | onright: () => sa[3] != '0' && a_func[sa[3]], 124 | onleft: () => sa[4] != '0' && a_func[sa[4]], 125 | } 126 | const fa = items.fab_actions 127 | const f_style = items.fab_style 128 | const f_icon = 129 | '' 130 | 131 | for (let f = 0; f < fa.length; f++) if (actions_icon_div[fa[f]]) fab_html += actions_icon_div[fa[f]] 132 | if (f_style == 'horizontal') { 133 | //Inject fab html 134 | let content = document.createElement('div') 135 | content.innerHTML = 136 | '' + 140 | '
' + 141 | f_icon + 142 | '
' + 143 | fab_html + 144 | '
' 145 | document.body.appendChild(content) 146 | new SwipeManager('#fab_h_btn', SM_options) 147 | } else if (f_style == 'radial') { 148 | //Inject fab html 149 | let content = document.createElement('div') 150 | content.innerHTML = 151 | '' + 155 | '
' + 156 | fab_html + 157 | '
' + 158 | f_icon + 159 | '
' 160 | document.body.appendChild(content) 161 | //Set actions 162 | const u = document.querySelectorAll('#fab_r_cnt > *') 163 | let l = u.length 164 | if (l < 6) l = l * 4 - 5 165 | if (l > 18) l = 18 166 | u.forEach((el, i) => { 167 | const z = (50 - 40 * Math.cos(-0.5 * Math.PI - 2 * (1 / l) * i * Math.PI)).toFixed(4) 168 | const t = (50 + 40 * Math.sin(-0.5 * Math.PI - 2 * (1 / l) * i * Math.PI)).toFixed(4) 169 | const ang = 2 * (1 / l) * i * Math.PI 170 | const childAng = (ang / (2 * Math.PI)) * 360 // Calculate initial rotation angle for the child 171 | el.style.left = `${z}%` 172 | el.style.top = i < 19 ? `${t}%` : `calc(${t}% + 50px)` 173 | el.style.transform = `rotateZ(${-ang}rad) rotateZ(${childAng}deg)` // Apply initial rotation to the icon 174 | }) 175 | rotate(document.getElementById('fab_r_cnt'), ev_li) 176 | new SwipeManager('#fab_r_btn', SM_options) 177 | } else if (f_style == 'sheet') { 178 | //Inject fab html 179 | let content = document.createElement('div') 180 | content.innerHTML = 181 | '' + 185 | '
' + 186 | f_icon + 187 | '
' + 188 | fab_html + 189 | '
' 190 | document.body.appendChild(content) 191 | new SwipeManager('#fab_s_btn', SM_options) 192 | const bottomSheet = new BottomSheet('#fab_s_bs', { 193 | threshold: 50, 194 | eventMap: ev_li, 195 | }) 196 | document.getElementById('fab_s_btn').addEventListener('click', () => { 197 | bottomSheet.show() 198 | }) 199 | } 200 | if (scroll_hide) { 201 | enableAutoHideOnScroll('fly', 'calc(-125px + ' + posf[0] + ')', 'calc(-200px + ' + posf[0] + ')') 202 | } 203 | const f_btn = document.querySelector('.fab_btn') 204 | f_btn.addEventListener('click', () => { 205 | console.log('Single click on fab') 206 | let flt = f_btn.parentNode 207 | if (flt.classList.contains('open')) flt.classList.remove('open') 208 | else flt.classList.add('open') 209 | }) 210 | f_btn.addEventListener('contextmenu', function (event) { 211 | event.preventDefault() 212 | //snackbar.show(actions[el.dataset['id']].name) 213 | snackbar.show('Long click action') 214 | }) 215 | 216 | document.querySelectorAll('.fab_cnt>*').forEach((el) => { 217 | el.addEventListener('click', () => { 218 | if (el.dataset['id'] != 'bt' || el.dataset['id'] != 'bt') 219 | snackbar.show(actions[el.dataset['id']].name) 220 | if (el.dataset['id'] == 'stt' || el.dataset['id'] == 'stb') a_func[el.dataset['id']](scroll_speed) 221 | else if (el.dataset['id'] == 'fs') a_func[el.dataset['id']](toggleFS) 222 | else if (el.dataset['id'] == 'ohp') a_func[el.dataset['id']](home_url) 223 | else a_func[el.dataset['id']]() 224 | }) 225 | }) 226 | 227 | if (document.querySelector('.fab_cnt>[data-id="bt"]')) 228 | chrome.runtime.sendMessage({ book: 'check', url: window.location.href, title: document.title }) 229 | if (items.options['opt0']) { 230 | enableAutoHideOnScroll('fly', posf[0], 'calc(-60px + ' + posf[0] + ')') 231 | } 232 | } else { 233 | const ba = items.bab_actions 234 | for (let f = 0; f < ba.length; f++) if (actions_icon_div[ba[f]]) fab_html += actions_icon_div[ba[f]] 235 | let content = document.createElement('div') 236 | content.innerHTML = 237 | '' + 238 | '
' + 239 | fab_html + 240 | '
' 241 | document.body.appendChild(content) 242 | document.querySelectorAll('#bab>*').forEach((el) => { 243 | el.addEventListener('click', () => { 244 | console.log(el.dataset['id']) 245 | }) 246 | }) 247 | if (items.options['scroll_hide']) { 248 | enableAutoHideOnScroll('bab', 0, '-50px') 249 | } 250 | } 251 | //Check if current url is bookmarked 252 | //if (ua.includes(17) > -1) chrome.runtime.sendMessage({ book: 'check', url: window.location.href }) 253 | }, 254 | ) 255 | chrome.runtime.onMessage.addListener(function (request, sender) { 256 | if (request.bookmarked != undefined) { 257 | let bt = document.querySelector('.fab_cnt>[data-id="bt"]') 258 | if (bt) 259 | if (request.bookmarked) { 260 | bt.classList.add('bookmark') 261 | snackbar.show(actions['bt'].name) 262 | } else { 263 | bt.classList.remove('bookmark') 264 | snackbar.show('Removed bookmark') 265 | } 266 | } 267 | }) 268 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import '../sass/index.sass' 2 | import A11yDialog from 'a11y-dialog' 3 | import { navbar } from './components/navbar' 4 | import { themeManager } from './components/themeManager' 5 | import { gotop } from './components/gotop' 6 | import { pagesRoute } from './components/pagesRoute' 7 | import { aos } from './components/aos' 8 | 9 | // Call the function when the DOM is loaded 10 | document.addEventListener('DOMContentLoaded', () => { 11 | const dialog_support = new A11yDialog( 12 | document.querySelector('#dlg_support') 13 | ) 14 | new themeManager() 15 | new navbar() 16 | new gotop() 17 | new aos() 18 | new pagesRoute() 19 | }) 20 | -------------------------------------------------------------------------------- /src/js/options.js: -------------------------------------------------------------------------------- 1 | import Sortable from 'sortablejs' 2 | import Picker from 'vanilla-picker' 3 | //import '../css/options.css' 4 | import actions from '../data/actions.json' 5 | import A11yDialog from 'a11y-dialog' 6 | import '../sass/options.sass' 7 | import { themeManager } from './components/themeManager' 8 | import { openFullscreen, closeFullscreen } from './components/fullscreen' 9 | import { getEventListeners, generateID } from './components/utilities' 10 | import { BottomSheet } from './components/bottomsheet' 11 | import { dragElement } from './components/dragElement' 12 | import { SwipeManager } from './components/swipeManager' 13 | import { aos } from './components/aos' 14 | import { rotate } from './components/rotate' 15 | import { ItemManager } from './components/blacklistManager' 16 | 17 | const clvn_dialog = new A11yDialog(document.querySelector('#dlg_clvn')) 18 | const itemManager = new ItemManager('tbtb') 19 | var ev_li = getEventListeners() 20 | var actions_name = { null: 'None' } 21 | var actions_icon_div = {} 22 | var actions_options = [''] 23 | 24 | for (var key in actions) { 25 | var value = actions[key] 26 | actions_icon_div[key] = 27 | '
' + value.icon + '
' 28 | actions_name[key] = value.name 29 | actions_options.push('') 30 | } 31 | for (let j = 0; j < 5; j++) { 32 | document.getElementById('sa-' + j).innerHTML = actions_options 33 | document.getElementById('sa-' + j).value = null 34 | } 35 | for (let j = 0; j < 6; j++) { 36 | document.getElementById('bb_sa-' + j).innerHTML = actions_options 37 | document.getElementById('bb_sa-' + j).value = null 38 | } 39 | 40 | /* ----- Initialize bottom sheet component ---- */ 41 | 42 | const bottomSheet = new BottomSheet('#fab_s_bs', { 43 | threshold: 50, 44 | eventMap: ev_li, 45 | }) 46 | document.getElementById('fab_s_btn').addEventListener('click', () => { 47 | bottomSheet.show() 48 | }) 49 | const SM_options = { 50 | events: ev_li, 51 | thresholds: { 52 | up: 50, 53 | down: 50, 54 | left: 50, 55 | right: 50, 56 | }, 57 | onup: () => console.log('User swiped up!'), 58 | ondown: () => console.log('User swiped down!'), 59 | onleft: () => console.log('User swiped left!'), 60 | onright: () => console.log('User swiped right!'), 61 | } 62 | const fhb = new SwipeManager('#fab_h_btn', SM_options) 63 | const frb = new SwipeManager('#fab_r_btn', SM_options) 64 | const fsb = new SwipeManager('#fab_s_btn', SM_options) 65 | /* ============================================ */ 66 | /* Save User Config */ 67 | /* ============================================ */ 68 | function save_options() { 69 | var colors = [], 70 | bab_actions = [], 71 | fab_actions = [], 72 | blacklist = [], 73 | swipe_actions = [], 74 | fab_pos = {} 75 | var style = document.querySelector('input[name="f-style"]:checked').value 76 | var tbtb = document.querySelectorAll('#tbtb>div') 77 | var options = { 78 | scroll_hide: document.getElementById('scroll_hide').checked, 79 | scroll_speed: document.getElementById('scroll_speed').value, 80 | home_url: document.getElementById('home_url').value, 81 | } 82 | //Get colors 83 | for (var i = 0; i < 9; i++) colors.push(getComputedStyle(document.body).getPropertyValue('--ftlcl' + i)) 84 | //Blacklist 85 | for (let i = 0; i < tbtb.length; i++) blacklist.push(tbtb[i].textContent) 86 | 87 | if (style == 'fab') { 88 | var uc = document.getElementById('uv').querySelectorAll('div') 89 | fab_pos['y'] = getComputedStyle(document.body).getPropertyValue('--fab-y') 90 | fab_pos['x'] = getComputedStyle(document.body).getPropertyValue('--fab-x') 91 | var fab_style = document.getElementById('fab_style').value 92 | for (var a = 0; a < uc.length; a++) fab_actions.push(uc[a].getAttribute('data-id')) 93 | for (let j = 0; j < 5; j++) swipe_actions.push(document.getElementById('sa-' + j).value) 94 | chrome.storage.local.set( 95 | { 96 | style: style, 97 | colors: colors, 98 | fab_actions: fab_actions, 99 | fab_pos: fab_pos, 100 | options: options, 101 | fab_style: fab_style, 102 | blacklist: blacklist, 103 | swipe_actions: swipe_actions, 104 | }, 105 | function () { 106 | alert( 107 | 'Options saved.
Remember to reload the page where the Floatly is used to update the style and actions', 108 | ) 109 | }, 110 | ) 111 | } else { 112 | for (let j = 0; j < 6; j++) bab_actions.push(document.getElementById('bb_sa-' + j).value) 113 | chrome.storage.local.set( 114 | { 115 | style: style, 116 | colors: colors, 117 | blacklist: blacklist, 118 | options: options, 119 | bab_actions: bab_actions, 120 | }, 121 | function () { 122 | alert('Options saved.
Remember to reload the page where the bottom Bar is used to update actions') 123 | }, 124 | ) 125 | } 126 | } 127 | //Add fab listeners 128 | 129 | document.querySelectorAll('.fab_btn').forEach((el) => { 130 | el.addEventListener('click', () => { 131 | console.log('Single click on fab') 132 | let flt = el.parentNode 133 | if (flt.classList.contains('open')) flt.classList.remove('open') 134 | else flt.classList.add('open') 135 | }) 136 | el.oncontextmenu = () => { 137 | console.log('Long click on fab') 138 | } 139 | }) 140 | 141 | //Add rotate event listenter on fab_radial 142 | rotate(document.getElementById('fab_r_cnt'), ev_li) 143 | /* ============================================ */ 144 | function setup_preview(style, f_style) { 145 | var stf = document.querySelector('.f-preview.show') 146 | if (stf != undefined) stf.classList.remove('show') 147 | if (style == 'fab') { 148 | if (f_style == 'horizontal') { 149 | console.log('fab-horizontal') 150 | //Set actions 151 | document.getElementById('fab_h_cnt').innerHTML = document.querySelector('#uv').innerHTML 152 | //Show floatly preview 153 | document.getElementById('f-1').classList.add('show') 154 | } else if (f_style == 'radial') { 155 | document.querySelector('#fab_r_cnt').innerHTML = document.querySelector('#uv').innerHTML 156 | const u = document.querySelectorAll('#fab_r_cnt > *') 157 | let l = u.length 158 | if (l < 6) l = l * 4 - 5 159 | if (l > 18) l = 18 160 | u.forEach((el, i) => { 161 | const z = (50 - 40 * Math.cos(-0.5 * Math.PI - 2 * (1 / l) * i * Math.PI)).toFixed(4) 162 | const t = (50 + 40 * Math.sin(-0.5 * Math.PI - 2 * (1 / l) * i * Math.PI)).toFixed(4) 163 | const ang = 2 * (1 / l) * i * Math.PI 164 | const childAng = (ang / (2 * Math.PI)) * 360 // Calculate initial rotation angle for the child 165 | el.style.left = `${z}%` 166 | el.style.top = i < 19 ? `${t}%` : `calc(${t}% + 50px)` 167 | el.style.transform = `rotateZ(${-ang}rad) rotateZ(${childAng}deg)` // Apply initial rotation to the icon 168 | }) 169 | document.getElementById('f-2').classList.add('show') 170 | } else if (f_style == 'sheet') { 171 | document.getElementById('fab_s_cnt').innerHTML = document.querySelector('#uv').innerHTML 172 | document.getElementById('f-3').classList.add('show') 173 | } 174 | document.querySelectorAll('.f-preview.show .fab_cnt>*').forEach((el) => { 175 | el.addEventListener('click', () => { 176 | console.log(el.dataset['id']) 177 | }) 178 | }) 179 | } else { 180 | var list = [] 181 | for (let j = 0; j < 6; j++) list.push(document.getElementById('bb_sa-' + j).value) 182 | var actions = '' 183 | for (let j = 0; j < list.length; j++) { 184 | let k = list[j] 185 | let aic = actions_icon_div[k] 186 | if (aic != undefined && aic) { 187 | actions += aic 188 | } 189 | } 190 | document.getElementById('bab').innerHTML = actions 191 | document.getElementById('f-4').classList.add('show') 192 | document.querySelectorAll('#bab>*').forEach((el) => { 193 | el.addEventListener('click', () => { 194 | console.log(el.dataset['id']) 195 | }) 196 | }) 197 | } 198 | } 199 | 200 | /* ============================================ */ 201 | /* Restore User Config */ 202 | /* ============================================ */ 203 | function restore_options() { 204 | chrome.storage.local.get( 205 | { 206 | style: 'fab', 207 | colors: ['#0878A5', '#ffffff', '#0878A5', '#ffffff', '#ffffff', '#101010', '#101010', '#0878A5', '#ffffff'], 208 | 209 | fab_actions: [], 210 | bab_actions: [], 211 | fab_style: 'horizontal', 212 | fab_pos: { x: '20px', y: '20px' }, 213 | options: { 214 | scroll_hide: false, 215 | scroll_speed: 800, 216 | home_url: 'chrome://newtab', 217 | }, 218 | blacklist: ['github.com', 'youtube.com'], 219 | swipe_actions: ['0', '0', '0', '0', '0'], 220 | }, 221 | function (items) { 222 | let av = ' ', 223 | uv = ' ' 224 | const fab_style = items.fab_style, 225 | style = items.style, 226 | colors = items.colors, 227 | bab_actions = items.bab_actions, 228 | fab_actions = items.fab_actions, 229 | blacklist = items.blacklist, 230 | fab_pos = items.fab_pos, 231 | swipe_actions = items.swipe_actions, 232 | options = items.options 233 | 234 | //Set fab swipe actions 235 | for (let j = 0; j < 5; j++) 236 | document.getElementById('sa-' + j).value = swipe_actions[j] ? swipe_actions[j] : null 237 | //Set fab actions 238 | for (var k in actions_icon_div) { 239 | if (!fab_actions.includes(k)) { 240 | av += actions_icon_div[k] 241 | } 242 | } 243 | for (var f = 0; f < fab_actions.length; f++) uv += actions_icon_div[fab_actions[f]] 244 | //Set available actions and user actions 245 | document.getElementById('av').innerHTML = av 246 | document.getElementById('uv').innerHTML = uv 247 | 248 | //Set home url 249 | document.getElementById('home_url').value = options['home_url'] 250 | //Set on scroll hide 251 | document.getElementById('scroll_hide').checked = options['scroll_hide'] 252 | //Set scroll speed with slider 253 | const ss = document.getElementById('scroll_speed') 254 | const ssv = document.getElementById('scroll_speed_v') 255 | var ss_v = options['scroll_speed'] 256 | ssv.innerText = ss_v 257 | ss.value = ss_v 258 | ss.addEventListener('input', function () { 259 | ssv.innerText = parseInt(ss.value) 260 | }) 261 | //Set fab style 262 | document.getElementById('fab_style').value = fab_style 263 | 264 | //Set bab actions 265 | for (let j = 0; j < 6; j++) { 266 | var el = document.getElementById('bb_sa-' + j) 267 | el.value = bab_actions[j] ? bab_actions[j] : null 268 | el.onchange = () => { 269 | setup_preview('bab', fab_style, bab_actions) 270 | } 271 | } 272 | //Set fab position 273 | document.body.style.setProperty('--fab-y', fab_pos['y']) 274 | document.body.style.setProperty('--fab-x', fab_pos['x']) 275 | //Set Floatly style 276 | if (items.style == 'fab') { 277 | document.getElementById('tb2-1').checked = true 278 | setup_preview(style, fab_style) 279 | } else { 280 | document.getElementById('tb2-2').checked = true 281 | setup_preview(style, fab_style, bab_actions) 282 | } 283 | 284 | for (var i = 0; i < 7; i++) document.body.style.setProperty('--ftlcl' + i, colors[i]) 285 | for (let i = 0; i < blacklist.length; i++) itemManager.addToContainer(blacklist[i]) 286 | }, 287 | ) 288 | 289 | dragElement(document.getElementById('mm_move')) 290 | /* ============================================ */ 291 | /* Sortable Config */ 292 | /* ============================================ */ 293 | new Sortable(document.getElementById('av'), { 294 | group: 'shared', 295 | animation: 150, 296 | ghostClass: 'av_ghost', 297 | chosenClass: 'av_chosen', 298 | sort: false, 299 | onChoose: function (evt) { 300 | document.getElementById('sv').innerHTML = actions_name[evt.item.dataset.id] 301 | setup_preview('fab', document.getElementById('fab_style').value) 302 | }, 303 | }) 304 | new Sortable(document.getElementById('uv'), { 305 | group: 'shared', 306 | animation: 150, 307 | onChoose: function (evt) { 308 | document.getElementById('sv').innerHTML = actions_name[evt.item.dataset.id] 309 | }, 310 | onAdd: function () { 311 | setup_preview( 312 | document.getElementById('tb2-1').checked == true ? 'fab' : 'bab', 313 | document.getElementById('fab_style').value, 314 | ) 315 | }, 316 | }) 317 | /* ============================================ */ 318 | } 319 | 320 | /* ============================================ */ 321 | /* Config Color Picker */ 322 | /* ============================================ */ 323 | 324 | clvn_dialog.on('show', () => { 325 | document.body.style.overflow = 'hidden' 326 | }) 327 | clvn_dialog.on('hide', () => { 328 | document.body.style.overflow = '' 329 | }) 330 | 331 | let current_color, t_current_color 332 | 333 | var picker = new Picker({ 334 | parent: document.querySelector('#cp_v'), 335 | popup: false, 336 | }) 337 | picker.onChange = (color) => { 338 | current_color = color.rgbaString 339 | } 340 | // Reset the color value when clicking the cancel/close button 341 | document.getElementById('cancel-button').addEventListener('click', () => { 342 | picker.setColor('transparent', true) 343 | current_color = null 344 | }) 345 | 346 | // Log the color value when clicking the OK button 347 | document.getElementById('ok-button').addEventListener('click', () => { 348 | document.body.style.setProperty('--ftlcl' + t_current_color, current_color) 349 | clvn_dialog.hide() 350 | }) 351 | function f_cp_rgb(t) { 352 | t_current_color = t 353 | let color = getComputedStyle(document.body).getPropertyValue('--ftlcl' + t) 354 | picker.setColor(color, true) 355 | clvn_dialog.show() 356 | } 357 | /* ============================================ */ 358 | 359 | document.addEventListener('DOMContentLoaded', () => { 360 | restore_options() 361 | new themeManager() 362 | new aos() 363 | const elements = document.querySelectorAll('.stt_clfrt') 364 | elements.forEach((el, index) => { 365 | el.addEventListener('click', function () { 366 | f_cp_rgb(index) 367 | }) 368 | }) 369 | document.getElementById('save').addEventListener('click', save_options) 370 | //Exit from custom fab position mode 371 | document.getElementById('mm_exit').addEventListener('click', () => { 372 | document.getElementById('mm_bg').classList.remove('show') 373 | document.getElementById('mm_move').style.display = 'none' 374 | closeFullscreen() 375 | }) 376 | //Reset custom fab postion to default 377 | document.getElementById('mm_reset').addEventListener('click', () => { 378 | document.body.style.setProperty('--fab-y', '20px') 379 | document.body.style.setProperty('--fab-x', '20px') 380 | }) 381 | //Enable custom fab position mode 382 | document.querySelector('#mm_btn').addEventListener('click', () => { 383 | openFullscreen() 384 | document.getElementById('mm_bg').classList.add('show') 385 | document.getElementById('mm_move').style.display = 'flex' 386 | }) 387 | document.getElementById('tb2-1').onchange = () => { 388 | setup_preview('fab', document.getElementById('fab_style').value) 389 | } 390 | document.getElementById('tb2-2').onchange = () => { 391 | setup_preview('bab') 392 | } 393 | document.getElementById('fab_style').onchange = () => { 394 | setup_preview('fab', document.getElementById('fab_style').value) 395 | } 396 | 397 | //Blacklist Manager 398 | document.getElementById('b-tb').onclick = function () { 399 | itemManager.addToContainer(document.getElementById('i-tb').value) 400 | } 401 | document.getElementById('d-tb').onclick = function () { 402 | itemManager.removeFromContainer() 403 | } 404 | }) 405 | -------------------------------------------------------------------------------- /src/js/service_worker.js: -------------------------------------------------------------------------------- 1 | chrome.runtime.onInstalled.addListener(function (details) { 2 | if (details.reason == 'install') chrome.runtime.openOptionsPage() 3 | }) 4 | chrome.action.onClicked.addListener(() => { 5 | chrome.runtime.openOptionsPage() 6 | }) 7 | chrome.runtime.onMessage.addListener(function (message, sender) { 8 | if (message.book) { 9 | chrome.bookmarks.search(message.url, function (result) { 10 | var t = result.length > 0 11 | if (message.book != 'check') { 12 | if (t) { 13 | chrome.bookmarks.remove(result[result.length - 1].id) 14 | } else { 15 | chrome.bookmarks.create({ 16 | url: message.url, 17 | title: message.title, 18 | }) 19 | } 20 | t = !t 21 | } 22 | chrome.tabs.sendMessage(sender.tab.id, { bookmarked: t }) 23 | }) 24 | } 25 | 26 | if (message.closeThis) chrome.tabs.remove(sender.tab.id) 27 | if (message.copyToClip) { 28 | chrome.tabs.query( 29 | { 30 | currentWindow: true, 31 | active: true, 32 | }, 33 | function (tabs) { 34 | var input = document.createElement('textarea') 35 | document.body.appendChild(input) 36 | input.value = tabs[0].url 37 | input.focus() 38 | input.select() 39 | document.execCommand('Copy') 40 | input.remove() 41 | }, 42 | ) 43 | } 44 | if (message.shareURL) 45 | chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { 46 | const tab = tabs[0] 47 | if (tab) { 48 | chrome.tabs.share(tab.id) 49 | } 50 | }) 51 | if (message.chromeURL) 52 | if (message.chromeURL == 'newtab') chrome.tabs.create({}) 53 | else 54 | chrome.tabs.create({ 55 | url: message.chromeURL, 56 | }) 57 | if (message.newTabInc) 58 | chrome.tabs.query( 59 | { 60 | currentWindow: true, 61 | active: true, 62 | }, 63 | function (tabs) { 64 | chrome.windows.create({ 65 | url: tabs[0].url, 66 | incognito: true, 67 | }) 68 | }, 69 | ) 70 | if (message.settings) chrome.runtime.openOptionsPage() 71 | }) 72 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Floatly", 4 | "version": "3.0.0", 5 | "description": "Floatly is an awesome floating button that brings quick actions to any website.", 6 | "permissions": ["tabs", "clipboardWrite","storage","activeTab","http://*/","https://*/","bookmarks"], 7 | "background": { 8 | "scripts": ["js/background.js"], 9 | "persistent": false 10 | }, 11 | "options_page": "html/options.html", 12 | 13 | "content_scripts": [{ 14 | "matches": [ 15 | "" 16 | ], 17 | "js": ["js/content.js"] 18 | }], 19 | "browser_action": { 20 | "default_icon": "png/icon256.png" 21 | }, 22 | "icons": { 23 | "20": "png/icon20.png", 24 | "48": "png/icon48.png", 25 | "152": "png/icon152.png" 26 | } 27 | } -------------------------------------------------------------------------------- /src/manifest_v3.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Floatly", 4 | "version": "3.0.0", 5 | "description": "Floatly is an awesome floating button that brings quick actions to any website.", 6 | "permissions": ["tabs", "clipboardWrite","storage","activeTab","bookmarks"], 7 | "background": { 8 | "service_worker": "js/service_worker.js", 9 | "type": "module" 10 | }, 11 | "options_page": "options.html", 12 | 13 | "content_scripts": [{ 14 | "matches": [ 15 | "*://*/*" 16 | ], 17 | "js": ["js/content.js"] 18 | }], 19 | "action": { 20 | "default_icon": "assets/png/icon256.png" 21 | }, 22 | "icons": { 23 | "20": "assets/png/icon20.png", 24 | "48": "assets/png/icon48.png", 25 | "152": "assets/png/icon152.png" 26 | } 27 | } -------------------------------------------------------------------------------- /src/partials/footer.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/partials/gotop.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/partials/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | <%= title %> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/partials/header.ejs: -------------------------------------------------------------------------------- 1 |
2 | 94 | 129 | 130 |
-------------------------------------------------------------------------------- /src/partials/support_me.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/partials/umami.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/sass/base/_basic.sass: -------------------------------------------------------------------------------- 1 | /* ---------------- Basic resets and improvements --------------- */ 2 | *, 3 | *::before, 4 | *::after 5 | box-sizing: border-box 6 | *:focus:not(:focus-visible) 7 | outline: 0 8 | *:focus-visible 9 | outline: .1rem solid var(--focus) 10 | outline-offset: .1rem 11 | html 12 | text-rendering: optimizeLegibility 13 | font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif 14 | font-size: 12pt 15 | color: var(--txt) 16 | background: var(--bg) 17 | body 18 | min-height: 100vh 19 | display: flex 20 | flex-flow: wrap column 21 | gap: .5rem 22 | align-items: center 23 | justify-content: center 24 | 25 | @media (prefers-reduced-motion: no-preference) 26 | scroll-behavior: smooth 27 | body,html 28 | margin: 0 29 | padding: 0 30 | header>*,main>*,footer>* 31 | max-width: 60rem 32 | 33 | ol, ul 34 | list-style: none 35 | display: flex 36 | flex-direction: column 37 | gap: .3rem 38 | .keep-ls 39 | list-style: unset 40 | img 41 | max-width: 100% 42 | 43 | table 44 | border-collapse: collapse 45 | 46 | textarea 47 | white-space: revert 48 | 49 | hr 50 | border: 0 51 | border-top: .1rem solid var(--brd) 52 | 53 | section 54 | overflow-x: hidden 55 | display: flex 56 | flex-direction: column 57 | 58 | ._mh-100-5 59 | min-height: calc(100vh - 15em + 3rem) 60 | 61 | .no-js 62 | display: none -------------------------------------------------------------------------------- /src/sass/components/_bottomsheet.sass: -------------------------------------------------------------------------------- 1 | 2 | .bottom-sheet 3 | pointer-events: none 4 | visibility: hidden 5 | overflow: hidden 6 | position: fixed 7 | left: 0 8 | bottom: 0 9 | height: 100vh 10 | width: 100vw 11 | z-index: 1015 12 | transition: opacity, visibility 0.25s 13 | 14 | &.active 15 | visibility: visible 16 | pointer-events: unset 17 | 18 | .bs-scrim 19 | opacity: 0 20 | display: block 21 | position: absolute 22 | z-index: 1 23 | height: 100vh 24 | width: 100vw 25 | background-color: rgba(0, 0, 0, 0.3) 26 | transition: opacity 0.3s 27 | top: 0 28 | .active .bs-scrim 29 | opacity: 1 30 | 31 | .bs-sheet 32 | display: flex 33 | flex-direction: column 34 | gap: .5rem 35 | position: fixed 36 | left: 0 37 | bottom: 0px 38 | width: 100% 39 | min-height: 38vh 40 | border-radius: 12px 12px 0 0 41 | padding: 0 1.5rem 1rem 42 | transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1) 43 | transform: translateY(100%) 44 | z-index: 2 45 | &>* 46 | flex-grow: 0 47 | .active .bs-sheet 48 | transform: var(--translateY) 49 | 50 | .bs-handle 51 | display: flex 52 | flex-direction: column 53 | align-items: center 54 | padding: .5rem 55 | 56 | &>span 57 | display: block 58 | height: 4px 59 | width: 32px 60 | background-color: rgba(0, 0, 0, 0.3) 61 | border-radius: 2px 62 | 63 | .bs-cnt 64 | position: relative 65 | margin: 0 66 | padding: 0 67 | display: flex 68 | flex-grow: 1 69 | max-width: 600px 70 | margin: auto!important 71 | flex-direction: column 72 | align-items: center 73 | 74 | .bs-footer 75 | width: 100% 76 | padding: 0.5rem 77 | text-align: center 78 | color: rgb(1, 123, 223) 79 | border-top: 2px solid rgba(0, 0, 0, 0.3) 80 | 81 | .bs-cnt>* 82 | width: 100% 83 | -------------------------------------------------------------------------------- /src/sass/components/_card.sass: -------------------------------------------------------------------------------- 1 | /* ------------------- Card ------------------- */ 2 | .card 3 | display: flex 4 | flex-wrap: wrap 5 | flex-direction: column 6 | gap: 1rem 7 | padding: 1rem 8 | background: var(--bg-card) 9 | border-radius: var(--radius) 10 | border: 0.1rem solid var(--brd) 11 | &> * 12 | width: 100% 13 | margin: 0 14 | &> img 15 | height: auto 16 | border-radius: var(--radius) 17 | width: 100% 18 | &.full 19 | width: calc( 100% + 2rem ) 20 | max-width: unset 21 | &.full:first-child 22 | border-radius: var(--radius) var(--radius) 0 0 23 | margin: -1rem -1rem 0 -1rem 24 | &.full:last-child 25 | border-radius: 0 0 var(--radius) var(--radius) 26 | margin: 0 -1rem -1rem -1rem 27 | &>.img-w 28 | padding: 1rem 29 | box-shadow: 0 8px 20px -4px var(--b-shadow) 30 | max-width: 100px 31 | border-radius: 50% 32 | margin: 15px auto 15px 33 | width: 100px 34 | height: 100px 35 | &> img 36 | height: auto 37 | border-radius: 50% 38 | width: 100% 39 | .img-w200 40 | padding: 40px 41 | border-radius: 50% 42 | width: 200px 43 | height: 200px 44 | &> img 45 | height: auto 46 | border-radius: 50% 47 | width: 100% 48 | -------------------------------------------------------------------------------- /src/sass/components/_details.sass: -------------------------------------------------------------------------------- 1 | 2 | /* ------------------ Details ----------------- */ 3 | details 4 | width: 100% 5 | overflow-x: hidden 6 | display: flex 7 | flex-flow: wrap column 8 | border-radius: var(--radius) 9 | user-select: none 10 | background: var(--bg-details) 11 | & > summary 12 | outline-color: initial 13 | padding: .6rem 14 | border-radius: var(--radius) 15 | outline: 0 16 | cursor: pointer 17 | font-weight: 500 18 | &:hover 19 | color: var(--primary) 20 | background-color: var(--bg-details) 21 | & > div 22 | padding: .5rem 23 | details[open] 24 | summary 25 | background-color: var(--bg-details-open) 26 | color: var(--txt-details-open) 27 | details+details 28 | margin-top: .5rem 29 | -------------------------------------------------------------------------------- /src/sass/components/_dropdown.sass: -------------------------------------------------------------------------------- 1 | .dropdown 2 | display: inline-flex 3 | position: relative 4 | padding-bottom: .5rem 5 | &>a,&>button 6 | margin: 0 7 | &:after 8 | content: "" 9 | width: .5rem 10 | height: .5rem 11 | margin-left: 1rem 12 | transform-origin: color 13 | transform: rotate(45deg) 14 | display: block 15 | border: .1875rem solid currentColor 16 | border-bottom: 0 17 | border-left: 0 18 | pointer-events: none 19 | >[data-dropdown-id] 20 | display: none 21 | flex-direction: column 22 | left: 0 23 | list-style: none 24 | width: 14rem 25 | margin: 0 26 | padding: 0 27 | transform-origin: right top 28 | position: absolute 29 | border:1px solid var(--bg2) 30 | border-radius: var(--radius) 31 | background-color: var(--bg) 32 | box-shadow: var(--shadow) 33 | top: 100% 34 | z-index: 20 35 | &>a:hover,&>a.link-active 36 | padding: .3rem 37 | border: 0 38 | background: var(--bg2) 39 | color: var(--primary) 40 | >* 41 | width: 100% 42 | display: flex 43 | margin: 0 44 | border-radius: var(--radius) 45 | padding: .3rem 46 | &.dropdown-active 47 | &>a:after,&>button:after 48 | border-top: 0 49 | border-bottom: .1875rem solid currentColor 50 | &>[data-dropdown-id] 51 | display: flex 52 | -------------------------------------------------------------------------------- /src/sass/components/_gotop_link.sass: -------------------------------------------------------------------------------- 1 | /* ---------------- Go Top Link --------------- */ 2 | .gt-link 3 | transition: all .25s ease-in-out 4 | position: fixed 5 | bottom: 0 6 | right: 0 7 | z-index: 1 8 | min-width: 2.6rem 9 | padding: .4rem 10 | cursor: pointer 11 | visibility: visible 12 | opacity: 1 13 | &.hidden 14 | visibility: hidden 15 | opacity: 0 -------------------------------------------------------------------------------- /src/sass/components/_modal.sass: -------------------------------------------------------------------------------- 1 | 2 | .dialog,.dialog-overlay 3 | position: fixed 4 | top: 0 5 | right: 0 6 | bottom: 0 7 | left: 0 8 | .dialog 9 | display: flex 10 | z-index: 50 11 | padding: .5rem 12 | .dialog[aria-hidden='true'] 13 | display: none 14 | .dialog-overlay 15 | background: rgba(43, 46, 56, 0.9) 16 | 17 | .dialog-content 18 | z-index: 50 19 | margin: auto 20 | display: flex 21 | flex-direction: column 22 | align-items: start 23 | max-block-size: 80vh 24 | max-block-size: 80dvb 25 | border-radius: 0.5em 26 | width: 100% 27 | max-width: 42rem 28 | overflow: hidden 29 | background: var(--bg) 30 | * 31 | margin: 0 32 | >* 33 | padding: 1rem 34 | >footer 35 | border-top: 1px solid var(--brd) 36 | display: flex 37 | gap: .5rem 38 | width: 100% 39 | >header 40 | border-bottom: 1px solid var(--brd) 41 | display: flex 42 | align-items: center 43 | justify-content: space-between 44 | >section 45 | width: 100% 46 | @keyframes dialog-fade-in 47 | from 48 | opacity: 0 49 | 50 | @keyframes dialog-slide-up 51 | from 52 | transform: translateY(10%) 53 | .dialog-sm .dialog-content 54 | max-width: 320px 55 | .dialog-lg .dialog-content 56 | max-width: unset 57 | .dialog-overlay 58 | animation: dialog-fade-in 200ms both 59 | .dialog-content 60 | animation: dialog-fade-in 400ms 200ms both, dialog-slide-up 400ms 200ms both 61 | 62 | @media (prefers-reduced-motion: reduce) 63 | .dialog-close 64 | transition: none 65 | .dialog-content 66 | animation: none 67 | -------------------------------------------------------------------------------- /src/sass/components/_snackbar.sass: -------------------------------------------------------------------------------- 1 | .snackbar 2 | position: fixed 3 | top: -100% 4 | left: 50% 5 | transform: translateX(-50%) 6 | background-color: var(--blue) 7 | border-radius: 10px 8 | width: 400px 9 | font-size: 16px 10 | transition: all .5s 11 | z-index: 9999 12 | box-shadow: var(--shadow) 13 | 14 | @media (max-width: 424px) 15 | .snackbar 16 | width: calc(100% - 20px) 17 | 18 | .snackbar.success 19 | background: var(--blue) 20 | 21 | .snackbar div 22 | color: var(--txt-on-blue) 23 | padding: 20px 30px 24 | text-align: center 25 | -------------------------------------------------------------------------------- /src/sass/components/_tabs.sass: -------------------------------------------------------------------------------- 1 | /* ------------------- Tabs ------------------- */ 2 | .tabs 3 | display: flex 4 | flex-wrap: wrap 5 | background: var(--bg-tabs) 6 | border: 0.1rem solid var(--brd) 7 | border-radius: var(--radius) 8 | margin-bottom: .5rem 9 | > label 10 | order: 1 11 | display: flex 12 | justify-content: center 13 | align-items: center 14 | padding: 0.7rem 1rem 15 | margin-right: 0.2rem 16 | cursor: pointer 17 | border-bottom: 0.2rem solid transparent 18 | font-weight: bold 19 | transition: background ease 0.2s 20 | > div 21 | width: 100% 22 | display: none 23 | padding: 1rem 24 | order: 99 25 | border-radius: var(--radius) 26 | flex-grow: 1 27 | & > * 28 | margin: .4rem 29 | & > input 30 | display: none 31 | &:checked 32 | & + label.ghost 33 | background: var(--bg-btn)!important 34 | color: var(--txt-btn)!important 35 | border-color: var(--bg-btn)!important 36 | & + label 37 | border-color: var(--brd-tabs-l) 38 | & + div 39 | display: block 40 | -------------------------------------------------------------------------------- /src/sass/components/_toast.sass: -------------------------------------------------------------------------------- 1 | .toast 2 | position: fixed 3 | z-index: 20 4 | bottom: 0 5 | padding: .5rem 6 | display: flex 7 | flex-direction: column 8 | justify-items: center 9 | justify-content: center 10 | gap: .5rem 11 | &[toast-pos^="top"] 12 | bottom: unset 13 | top: 0 14 | flex-direction: column-reverse 15 | .toast-item 16 | --_travel-distance: -5vh 17 | &[toast-pos^="bottom"] 18 | bottom: 0 19 | &[toast-pos$="left"] 20 | left: 0 21 | &[toast-pos$="right"] 22 | right: 0 23 | .toast-item 24 | max-inline-size: min(25ch, 90vw) 25 | border-radius: var(--radius) 26 | color: var(--txt) 27 | background: var(--toast-bg) 28 | --toast-bg: var(--bg2) 29 | --_duration: 3s 30 | --_travel-distance: 5vh 31 | will-change: transform 32 | display: flex 33 | gap: 0.5rem 34 | justify-content: center 35 | align-items: center 36 | flex: 1 37 | padding: 1rem 38 | box-shadow: var(--shadow) 39 | animation: toast-in 500ms ease forwards 1 40 | 41 | &.success 42 | --toast-bg: var(--green-l) 43 | --toast-bg2: var(--green-d) 44 | &.error 45 | --toast-bg: var(--red-l) 46 | --toast-bg2: var(--red-d) 47 | &.warn 48 | --toast-bg: var(--orange-l) 49 | --toast-bg2: var(--orange-d) 50 | &.info 51 | --toast-bg: var(--blue-l) 52 | --toast-bg2: var(--blue-d) 53 | &.toast-out 54 | animation: toast-out .3s ease 55 | &>svg 56 | height: 20px 57 | width: 20px 58 | fill: var(--toast-bg2) 59 | &._closeIcon 60 | border-radius: 3px 61 | margin-left: 2rem 62 | @keyframes toast-out 63 | to 64 | transform: translateY(--_travel-distance) 65 | opacity: 0 66 | 67 | @keyframes toast-in 68 | from 69 | transform: translateY(var(--_travel-distance)) 70 | opacity: 0 71 | -------------------------------------------------------------------------------- /src/sass/elements/_button.sass: -------------------------------------------------------------------------------- 1 | /* ------------------ Buttons ----------------- */ 2 | button, 3 | input[type="button"], 4 | input[type="submit"], 5 | input[type="reset"], 6 | input[type="file"], 7 | input[type="file"]::file-selector-button, 8 | .btn 9 | display: inline-flex 10 | align-items: center 11 | text-align: center 12 | justify-content: center 13 | cursor: pointer 14 | gap: .4rem 15 | line-height: 1.5 16 | font-size: 1rem 17 | border: 0.1rem solid var(--btn-brd) 18 | border-radius: var(--btn-radius) 19 | color: var(--btn-txt) 20 | background-color: var(--btn-bg) 21 | min-width: 100px 22 | cursor: pointer 23 | padding: .4rem .7rem 24 | outline-color: var(--primary) 25 | box-shadow: 0 0 0 var(--btn-hs) var(--btn-f) 26 | transition: all 145ms ease 27 | user-select: none 28 | -webkit-touch-callout: none 29 | touch-action: manipulation 30 | -webkit-tap-highlight-color: transparent 31 | input[type="file"]::file-selector-button 32 | margin: 0 .4rem 0 0 33 | input[type="file"] 34 | inline-size: 100% 35 | padding: 0 36 | max-inline-size: max-content 37 | background-color: var(--btn-brd) 38 | border: 0 39 | input[type="file"]::file-selector-button 40 | height: 2.6rem 41 | border-radius: var(--btn-radius) 0 0 var(--btn-radius) 42 | 43 | button, 44 | input[type="button"], 45 | input[type="submit"], 46 | input[type="reset"], 47 | .btn 48 | height: 2.6rem 49 | &:active,&:focus 50 | --btn-hs: .3rem 51 | &:focus-visible 52 | outline: .1rem solid var(--btn-brd) 53 | outline-offset: .1rem 54 | &:not(active):hover 55 | --btn-bg: var(--btn-bg-h) 56 | --btn-brd: var(--btn-bg-h) 57 | 58 | &.mw-auto 59 | min-width: unset 60 | &[disabled] 61 | opacity: .5 62 | pointer-events: none 63 | cursor: not-allowed 64 | &.pill 65 | --btn-radius: 2rem 66 | &.outline 67 | --btn-bg: transparent 68 | color: var(--btn-brd) 69 | border: 2px solid var(--focus) 70 | &.outline:hover 71 | color: var(--btn-txt) 72 | &.fill:hover 73 | background: var(--btn-bg) 74 | color: var(--btn-txt)!important 75 | &>svg 76 | display: block 77 | height: 1.5rem 78 | width: 1.5rem 79 | max-height: 1.7rem 80 | 81 | .group-btn 82 | position: relative 83 | display: flex 84 | margin: .5rem 85 | >* 86 | margin: 0 87 | border-radius: 0 88 | &:first-child 89 | border-top-left-radius: var(--btn-radius) 90 | border-bottom-left-radius: var(--btn-radius) 91 | border-right: 1px 92 | &:last-child 93 | border-top-right-radius: var(--btn-radius) 94 | border-bottom-right-radius: var(--btn-radius) 95 | border-left: 1px 96 | .btn-transparent,._btn_t 97 | --btn-f: var(--primary-l) 98 | --btn-bg-h: transparent 99 | --btn-bg: transparent 100 | .btn-inverted 101 | --btn-f: var(--txt-h) 102 | --btn-brd: var(--txt) 103 | --btn-bg-h: var(--txt-l) 104 | --btn-bg: var(--txt) 105 | --btn-txt: var(--bg) 106 | .btn-black 107 | --btn-f: var(--black-h) 108 | --btn-brd: var(--black) 109 | --btn-bg-h: var(--black-l) 110 | --btn-bg: var(--black-l) 111 | --btn-txt: var(--white) 112 | .btn-white 113 | --btn-f: var(--white-h) 114 | --btn-brd: var(--white-l) 115 | --btn-bg-h: var(--white-l) 116 | --btn-bg: var(--white-l) 117 | --btn-txt: var(--black) 118 | .btn-p 119 | --btn-f: var(--primary-h) 120 | --btn-brd: var(--primary) 121 | --btn-bg: var(--primary) 122 | --btn-bg-h: var(--primary-l) 123 | --btn-txt: var(--txt-on-p) 124 | .btn-blue 125 | --btn-f: var(--blue-h) 126 | --btn-brd: var(--blue) 127 | --btn-bg: var(--blue) 128 | --btn-bg-h: var(--blue-l) 129 | --btn-txt: var(--txt-on-blue) 130 | .btn-red,[type="reset"] 131 | --btn-f: var(--red-h) 132 | --btn-txt: var(--txt-on-red) 133 | --btn-brd: var(--red) 134 | --btn-bg-h: var(--red-l) 135 | --btn-bg: var(--red) 136 | .btn-green 137 | --btn-f: var(--green-h) 138 | --btn-brd: var(--green) 139 | --btn-bg-h: var(--green-l) 140 | --btn-bg: var(--green) 141 | --btn-txt: var(--txt-on-green) 142 | .btn-orange 143 | --btn-f: var(--orange-h) 144 | --btn-brd: var(--orange) 145 | --btn-bg-h: var(--orange-l) 146 | --btn-bg: var(--orange) 147 | --btn-txt: var(--txt-on-orange) 148 | -------------------------------------------------------------------------------- /src/sass/elements/_code.sass: -------------------------------------------------------------------------------- 1 | /* ------------------- Code ------------------- */ 2 | code 3 | color: var(--primary) 4 | background: var(--bg2) 5 | pre 6 | display: flex 7 | box-shadow: var(--shadow) 8 | border-radius: var(--radius)!important 9 | border-left: var(--radius) solid var(--primary) 10 | background-color: var(--bg) 11 | & > code 12 | margin: 0 13 | padding: 1.5rem 1.0rem 14 | background-color: transparent 15 | code, 16 | kbd, 17 | samp 18 | padding: .1rem .3rem 19 | background: var(--bg2) 20 | white-space: pre 21 | font-size: 90% 22 | border-radius: 6px 23 | kbd 24 | border: 1px solid var(--txt) 25 | border-bottom: 3px solid var(--txt) 26 | -------------------------------------------------------------------------------- /src/sass/elements/_figure.sass: -------------------------------------------------------------------------------- 1 | /* ------------- Image and Figure ------------- */ 2 | img 3 | max-width: 100% 4 | border-radius: var(--radius) 5 | &.circle 6 | border-radius: 50% 7 | &.square 8 | border-radius: 0 9 | figure 10 | text-align: center 11 | & > img 12 | display: block 13 | margin: 0.5em auto 14 | figcaption 15 | color: var(--brd) 16 | margin-bottom: 1rem -------------------------------------------------------------------------------- /src/sass/elements/_form.sass: -------------------------------------------------------------------------------- 1 | .field 2 | display: flex 3 | justify-content: center 4 | gap: .5rem 5 | flex-direction: column 6 | [class^="with-icon"] 7 | position: relative 8 | [class^="with-icon"]>svg 9 | position: absolute 10 | top: 50% 11 | transform: translateY(-50%) 12 | .with-icon-left>svg 13 | left: 1rem 14 | .with-icon-right>svg 15 | left: 1rem 16 | .with-icon-left>input 17 | padding-left: 3rem!important 18 | .with-icon-right>input 19 | padding-right: 3rem!important 20 | 21 | input[type="color"] 22 | -webkit-appearance: none 23 | border: none 24 | border-radius: var(--radius) 25 | width: 32px 26 | height: 32px 27 | input[type="color"]::-webkit-color-swatch-wrapper 28 | padding: 0 29 | input[type="color"]::-webkit-color-swatch 30 | border: none 31 | border-radius: var(--radius) 32 | textarea, 33 | input:not([type=color]):not([type=file]):not([type=button]):not([type=range]):not([type=radio]):not([type=checkbox]), 34 | select 35 | display: block 36 | width: 100% 37 | font-size: .875rem 38 | line-height: 1.25rem 39 | height: 2.6rem 40 | padding: .6rem 41 | border: .1rem solid var(--input-brd) 42 | border-radius: var(--radius) 43 | background-color: var(--input-bg) 44 | background-clip: padding-box 45 | color: var(--input-txt) 46 | &:focus 47 | outline: 0 48 | border-color: var(--primary) 49 | box-shadow: 0 0 0 5px var(--primary-h) 50 | &[disabled] 51 | cursor: not-allowed 52 | &:disabled, 53 | &[readonly] 54 | background-color: var(--input-brd) 55 | opacity: 1 56 | select 57 | -webkit-appearance: none 58 | -moz-appearance: none 59 | color: var(--txt) 60 | background-image: url('data:image/svg+xml;utf8,') 61 | background-position: right center 62 | background-size: 18px 18px, 18px 18px, 2px 1.6rem 63 | background-repeat: no-repeat 64 | 65 | fieldset 66 | margin-top: 1rem 67 | border-radius: var(--radius) 68 | border: .1rem solid var(--input-brd) 69 | 70 | @supports (-webkit-appearance: none) or (-moz-appearance: none) 71 | input[type="checkbox"], 72 | input[type="radio"] 73 | --active: var(--primary) 74 | --active-inner: #fff 75 | --focus: 3px var(--primary-h) 76 | --border: var(--input-brd) 77 | --border-hover: var(--primary) 78 | --background: var(--bg) 79 | -webkit-appearance: none 80 | -moz-appearance: none 81 | height: 1.3rem 82 | outline: none 83 | display: inline-block 84 | vertical-align: top 85 | position: relative 86 | margin: 0 87 | cursor: pointer 88 | border: .1rem solid var(--bc, var(--border)) 89 | background: var(--b, var(--background)) 90 | transition: background 0.3s, border-color 0.3s, box-shadow 0.2s 91 | 92 | input[type="checkbox"]:after, 93 | input[type="radio"]:after 94 | content: "" 95 | display: block 96 | left: .1rem 97 | top: .1rem 98 | position: absolute 99 | transition: transform var(--d-t, 0.3s) var(--d-t-e, ease),opacity var(--d-o, 0.2s) 100 | 101 | input[type="checkbox"]:checked, 102 | input[type="radio"]:checked 103 | --b: var(--active) 104 | --bc: var(--active) 105 | --d-o: 0.3s 106 | --d-t: 0.6s 107 | --d-t-e: cubic-bezier(0.2, 0.85, 0.32, 1.2) 108 | 109 | input[type="checkbox"]:disabled, 110 | input[type="radio"]:disabled 111 | --b: var(--disabled) 112 | cursor: not-allowed 113 | opacity: 0.9 114 | 115 | input[type="checkbox"]:disabled:checked, 116 | input[type="radio"]:disabled:checked 117 | --b: var(--disabled-inner) 118 | --bc: var(--border) 119 | 120 | input[type="checkbox"]:disabled + label, 121 | input[type="radio"]:disabled + label 122 | cursor: not-allowed 123 | 124 | input[type="checkbox"]:hover:not(:checked):not(:disabled), 125 | input[type="radio"]:hover:not(:checked):not(:disabled) 126 | --bc: var(--border-hover) 127 | 128 | input[type="checkbox"]:focus, 129 | input[type="radio"]:focus 130 | --bc: var(--active) 131 | box-shadow: 0 0 0 var(--focus) 132 | 133 | input[type="checkbox"]:not(.toggle), 134 | input[type="radio"]:not(.toggle) 135 | width: 1.3rem 136 | 137 | input[type="checkbox"]:not(.toggle):after, 138 | input[type="radio"]:not(.toggle):after 139 | opacity: var(--o, 0) 140 | 141 | input[type="checkbox"]:not(.toggle):checked, 142 | input[type="radio"]:not(.toggle):checked 143 | --o: 1 144 | 145 | input[type="checkbox"] + label, 146 | input[type="radio"] + label 147 | line-height: 1.3rem 148 | display: inline-block 149 | vertical-align: top 150 | cursor: pointer 151 | margin-left: .3rem 152 | 153 | input[type="checkbox"]:not(.toggle) 154 | border-radius: .4rem 155 | 156 | input[type="checkbox"]:not(.toggle):after 157 | width: .4rem 158 | height: .6rem 159 | border: .2rem solid var(--active-inner) 160 | border-top: 0 161 | border-left: 0 162 | left: .4rem 163 | top: .2rem 164 | transform: rotate(var(--r, 20deg)) 165 | 166 | input[type="checkbox"]:not(.toggle):checked 167 | --r: 43deg 168 | 169 | input[type="checkbox"].toggle 170 | width: 38px 171 | border-radius: 11px 172 | 173 | input[type="checkbox"].toggle:after 174 | left: .1rem 175 | top: .1rem 176 | border-radius: 50% 177 | width: .9rem 178 | height: .9rem 179 | background: var(--ab, var(--border)) 180 | transform: translateX(var(--x, 0)) 181 | 182 | input[type="checkbox"].toggle:checked 183 | --ab: var(--active-inner) 184 | --x: 1.1rem 185 | 186 | input[type="checkbox"].toggle:disabled:not(:checked):after 187 | opacity: 0.6 188 | 189 | input[type="radio"] 190 | border-radius: 50% 191 | 192 | input[type="radio"]:after 193 | width: .9rem 194 | height: .9rem 195 | border-radius: 50% 196 | background: var(--active-inner) 197 | opacity: 0 198 | transform: scale(var(--s, 0.7)) 199 | 200 | input[type="radio"]:checked 201 | --s: 0.8 202 | 203 | input[type="color"] 204 | -webkit-appearance: none 205 | border: none 206 | border-radius: var(--radius) 207 | width: 32px 208 | height: 32px 209 | 210 | input[type="color"]::-webkit-color-swatch-wrapper 211 | padding: 0 212 | 213 | input[type="color"]::-webkit-color-swatch 214 | border: none 215 | border-radius: var(--radius) 216 | 217 | input[type="range"] 218 | -webkit-appearance: none 219 | margin: 10px 0 220 | width: 100% 221 | 222 | input[type="range"]:focus 223 | outline: none 224 | 225 | input[type="range"]::-webkit-slider-runnable-track 226 | height: 6px 227 | background: #ddd 228 | border-radius: 3px 229 | 230 | input[type="range"]::-webkit-slider-thumb 231 | -webkit-appearance: none 232 | height: 20px 233 | width: 20px 234 | background: #fff 235 | border-radius: 50% 236 | border: 1px solid #ccc 237 | margin-top: -8px 238 | 239 | input[type="range"]::-webkit-slider-runnable-track 240 | background: var(--brd) 241 | 242 | input[type="range"]::-webkit-slider-thumb 243 | background: var(--primary) 244 | -------------------------------------------------------------------------------- /src/sass/elements/_link.sass: -------------------------------------------------------------------------------- 1 | .link 2 | color: var(--primary) 3 | background-color: transparent 4 | border-color: transparent 5 | &:hover 6 | color: var(--primary-l) 7 | text-decoration: underline 8 | background-color: transparent 9 | &:focus 10 | color: var(--primary-d) 11 | text-decoration: underline 12 | background-color: transparent 13 | box-shadow: 0 0 0 .25rem var(--primary-h) 14 | &:active 15 | color: var(--primary-d) 16 | text-decoration: underline 17 | background-color: transparent 18 | -------------------------------------------------------------------------------- /src/sass/elements/_table.sass: -------------------------------------------------------------------------------- 1 | /* ------------------- Table ------------------ */ 2 | table 3 | width: 100% 4 | padding: 0 5 | border-collapse: collapse 6 | overflow: hidden 7 | border-radius: var(--radius) 8 | box-shadow: var(--fake-brd-table) 9 | caption 10 | padding: 0.5rem 1.5rem 11 | thead 12 | background: var(--primary) 13 | color: var(--txt-r) 14 | th:first-child 15 | border-top-left-radius: var(--radius) 16 | th:last-child 17 | border-top-right-radius: var(--radius) 18 | td,th 19 | padding: 0.5rem 1.5rem 20 | tr 21 | text-align: left 22 | padding: .3rem 23 | border-bottom: 1px solid var(--brd-table) 24 | &:hover 25 | background-color: var(--bbg-table-hover) 26 | &:last-child 27 | border-bottom: none 28 | &:first-child 29 | & > th 30 | border-bottom: 1px solid var(--primary) 31 | tfoot 32 | border-top: 1px solid var(--brd-table) 33 | @media (max-width: 500px) 34 | table, thead, tbody, th, td, tr 35 | display: block 36 | table 37 | border: 0 38 | thead 39 | display: none 40 | caption 41 | display: block 42 | font-size: 1.3em 43 | tr 44 | border-bottom: 2px solid var(--brd-table) 45 | display: block 46 | td 47 | border-bottom: 1px solid var(--brd-table) 48 | font-size: .8em 49 | display: flex 50 | align-items: center 51 | td::before 52 | content: attr(data-column) 53 | text-align: left 54 | width: 45% 55 | font-weight: bold 56 | text-transform: uppercase 57 | td:last-child 58 | border-bottom: 0 59 | 60 | -------------------------------------------------------------------------------- /src/sass/elements/_typography.sass: -------------------------------------------------------------------------------- 1 | /* ------------------ Heading ----------------- */ 2 | 3 | h1, h2, h3, h4, h5, h6 4 | margin-top: 1.5rem 5 | h1 6 | font-size: 2.6em 7 | h2 8 | font-size: 2.0em 9 | h3 10 | font-size: 1.7em 11 | h4 12 | font-size: 1.5em 13 | h5 14 | font-size: 1.2em 15 | h6 16 | font-size: 1em 17 | p 18 | margin-bottom: 1.5rem 19 | p strong, p b 20 | color: var(--txt) 21 | h1 + h2, 22 | h2 + h3, 23 | h3 + h4, 24 | h4 + h5, 25 | h5 + h6 26 | margin: 0 27 | blockquote 28 | border-left: var(--radius) solid var(--primary) 29 | padding: 1rem 1.5rem 30 | q 31 | &:before 32 | content: "\201C" 33 | q 34 | &:before 35 | content: "\2018" 36 | &:after 37 | content: "\2019" 38 | &:after 39 | content: "\201D" 40 | summary 41 | font-weight: bold 42 | cursor: pointer 43 | time 44 | color: var(--txt) 45 | opacity: .7 46 | mark 47 | background-color: var(--primary) 48 | color: var(--txt-r) 49 | padding: .1rem .2rem 50 | font-size: 90% 51 | small, 52 | sub, 53 | sup 54 | font-size: 75% 55 | var 56 | color: var(--yellow) 57 | ins 58 | color: var(--green) 59 | del 60 | color: var(--red) 61 | /* ------------------- Links ------------------ */ 62 | a 63 | color: var(--primary) 64 | text-decoration: none 65 | cursor: pointer -------------------------------------------------------------------------------- /src/sass/extra/_alert.sass: -------------------------------------------------------------------------------- 1 | /* --------------- Toast Message -------------- */ 2 | .alert-item 3 | transition: all .3s 4 | max-width: 200px 5 | padding: .8rem 6 | margin-bottom: .3rem 7 | display: flex 8 | background: var(--primary) 9 | border-radius: var(--radius) 10 | box-shadow: var(--shadow) 11 | animation: fade-in .3s 12 | animation-timing-function: cubic-bezier(.6,.04,.98,.34) 13 | &.animate-out 14 | animation: fade-out .3s 15 | animation-timing-function: cubic-bezier(.6,.04,.98,.34) 16 | &.success 17 | background: var(--green) 18 | &.error 19 | background: var(--red) 20 | &.warn 21 | background: var(--yellow) 22 | &.info 23 | background: var(--blue) 24 | 25 | .alert-container 26 | position: fixed 27 | padding: .3rem 28 | display: flex 29 | align-items: center 30 | flex-direction: column 31 | z-index: 9999 32 | &._top 33 | flex-direction: column-reverse 34 | &._bottom 35 | bottom:0 36 | &._left 37 | left: 0 38 | &._right 39 | right: 0 40 | &._center 41 | left: 50% 42 | transform: translateX(-50%) 43 | 44 | 45 | @keyframes fade-out 46 | from 47 | opacity: 1 48 | transform: scaleY(1) 49 | to 50 | opacity: 0 51 | transform: scaleY(0) 52 | @keyframes fade-in 53 | from 54 | opacity: 0 55 | transform: scaleY(0) 56 | to 57 | opacity: 1 58 | transform: scaleY(1) 59 | 60 | -------------------------------------------------------------------------------- /src/sass/extra/_aos.sass: -------------------------------------------------------------------------------- 1 | /* ------------ Animation On Scroll ----------- */ 2 | [class*=_aos],._aos 3 | opacity: 0 4 | transition: opacity 1s, transform 1.3s 5 | ._aos-zoom 6 | transform: scale(.4) 7 | ._aos-left 8 | transform: translate3d(-100px,0,0) 9 | ._aos-right 10 | transform: translate3d(100px,0,0) 11 | ._aos-top 12 | transform: translate3d(0,-100px, 0) 13 | ._aos-bottom 14 | transform: translate3d(0,100px, 0) 15 | ._aos-done 16 | opacity: 1 17 | transform: translateZ(0) scale(1) 18 | -------------------------------------------------------------------------------- /src/sass/extra/_carousel.sass: -------------------------------------------------------------------------------- 1 | .carousel 2 | position: relative 3 | .slide 4 | display: none 5 | .prev-btn,.next-btn 6 | position: absolute 7 | top: 50% 8 | transform: translateY(-50%) 9 | .prev-btn 10 | left: 0 11 | .next-btn 12 | right: 0 13 | .numbertext 14 | color: #f2f2f2 15 | font-size: 12px 16 | padding: 8px 12px 17 | position: absolute 18 | top: 0 19 | right: 0 20 | .dot 21 | cursor: pointer 22 | height: 15px 23 | width: 15px 24 | margin: 0 2px 25 | background-color: var(--bg2) 26 | border-radius: 50% 27 | display: inline-block 28 | transition: background-color 0.6s ease 29 | .active,.dot:hover 30 | background-color: var(--primary) 31 | -------------------------------------------------------------------------------- /src/sass/extra/_loading.sass: -------------------------------------------------------------------------------- 1 | // Loading ([aria-busy=true]) 2 | 3 | // Cursor 4 | [aria-busy="true"] 5 | cursor: progress 6 | 7 | // Everyting except form elements 8 | [aria-busy="true"]:not(input):not(select):not(textarea) 9 | &::before 10 | display: inline-block 11 | width: 1em 12 | height: 1em 13 | border: 0.1875em solid currentColor 14 | border-radius: 1em 15 | border-right-color: transparent 16 | vertical-align: text-bottom 17 | vertical-align: -.125em // Visual alignment 18 | animation: spinner 0.75s linear infinite 19 | content: '' 20 | opacity: .5 21 | 22 | &:not(:empty) 23 | &::before 24 | margin-right: calc(var(--spacing) * 0.5) 25 | margin-left: 0 26 | margin-inline-end: calc(var(--spacing) * 0.5) 27 | margin-inline-start: 0 28 | &:empty 29 | text-align: center 30 | 31 | // Buttons and links 32 | button, 33 | input[type="submit"], 34 | input[type="button"], 35 | input[type="reset"], 36 | a 37 | &[aria-busy="true"] 38 | pointer-events: none 39 | 40 | // Animation: rotate 41 | @keyframes spinner 42 | to 43 | transform: rotate(360deg) 44 | 45 | -------------------------------------------------------------------------------- /src/sass/extra/_pages.sass: -------------------------------------------------------------------------------- 1 | .pages 2 | > * 3 | display: none 4 | >.page-active 5 | display: flex -------------------------------------------------------------------------------- /src/sass/extra/_tooltip.sass: -------------------------------------------------------------------------------- 1 | /* ------------------ Tooltip ----------------- */ 2 | [data-tooltip] 3 | cursor: pointer 4 | position: relative 5 | text-decoration: none 6 | [data-tooltip]::after 7 | background-color: var(--primary) 8 | color: var(--txt-r) 9 | font-size: 14px 10 | padding: 8px 12px 11 | height: fit-content 12 | width: fit-content 13 | border-radius: 6px 14 | position: absolute 15 | text-align: center 16 | bottom: 0px 17 | left: 50% 18 | content: attr(data-tooltip) 19 | transform: translate(-50%, 110%) scale(0) 20 | transform-origin: top 21 | transition: 0.14s 22 | box-shadow: var(--shadow) 23 | [data-tooltip]:hover 24 | &:after 25 | display: block 26 | transform: translate(-50%, 110%) scale(1) 27 | -------------------------------------------------------------------------------- /src/sass/extra/_wave.sass: -------------------------------------------------------------------------------- 1 | svg.wave 2 | display: block 3 | width: 100% 4 | height: 15em 5 | max-height: 100vh 6 | margin: 0 7 | 8 | > path 9 | fill: var(--primary) 10 | animation: wave 10s linear infinite 11 | 12 | @keyframes wave 13 | 0% 14 | transform: translateX(0) 15 | 100% 16 | transform: translateX(-100%) 17 | -------------------------------------------------------------------------------- /src/sass/fab/fab.sass: -------------------------------------------------------------------------------- 1 | .snackbar 2 | position: fixed 3 | top: -100% 4 | left: 50% 5 | transform: translateX(-50%) 6 | padding: 0 !important 7 | margin: 0 !important 8 | border-radius: 10px 9 | width: 400px !important 10 | font-size: 16px !important 11 | transition: all .5s 12 | z-index: 9999999 !important 13 | 14 | @media (max-width: 424px) 15 | .snackbar 16 | width: calc(100% - 20px)!important 17 | 18 | .snackbar div 19 | background: #0074D8 !important 20 | color: #fafafa !important 21 | box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px !important 22 | padding: 20px 30px !important 23 | margin: 0!important 24 | text-align: center 25 | width: auto !important 26 | 27 | .fab_btn 28 | background: var(--ftlcl0) 29 | 30 | .fab_wrap 31 | position: absolute 32 | padding: 0 !important 33 | margin: 0 !important 34 | z-index: 999999 !important 35 | background: transparent !important 36 | box-shadow: none !important 37 | * 38 | border-radius: 0 39 | box-shadow: none !important 40 | padding: 0 41 | margin: 0 42 | background: unset 43 | color: unset 44 | 45 | .fab_cnt 46 | box-sizing: border-box 47 | 48 | .fab_btn 49 | position: fixed 50 | z-index: 1010 51 | height: 50px 52 | width: 50px 53 | padding: 0 !important 54 | bottom: var(--fab-y) 55 | left: var(--fab-x) 56 | background-color: var(--ftlcl0) !important 57 | color: var(--ftlcl1) !important 58 | border-radius: 50% 59 | box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.15) 60 | user-select: none 61 | display: flex 62 | justify-content: center 63 | align-items: center 64 | pointer-events: auto 65 | & svg 66 | fill: currentColor 67 | height: 26px 68 | .fab_wrap.open 69 | pointer-events: unset !important 70 | & .fab_cnt 71 | opacity: 1 !important 72 | visibility: visible !important 73 | .fab_cnt>[data-id="fs"].fullscreen svg:nth-child(2), 74 | .fab_cnt>[data-id="fs"] svg:nth-child(1) 75 | display: inline 76 | 77 | .fab_cnt>[data-id="fs"].fullscreen svg:nth-child(1), 78 | .fab_cnt>[data-id="fs"] svg:nth-child(2) 79 | display: none 80 | 81 | .fab_cnt>[data-id="bt"].bookmark svg:nth-child(2), 82 | .fab_cnt>[data-id="bt"] svg:nth-child(1) 83 | display: inline 84 | 85 | .fab_cnt>[data-id="bt"].bookmark svg:nth-child(1), 86 | .fab_cnt>[data-id="bt"] svg:nth-child(2) 87 | display: none 88 | -------------------------------------------------------------------------------- /src/sass/fab/fab_h.sass: -------------------------------------------------------------------------------- 1 | #fab_h_cnt 2 | position: fixed 3 | overflow: auto 4 | display: flex 5 | flex-direction: row 6 | flex-wrap: nowrap 7 | overflow-x: auto 8 | gap: 5px 9 | bottom: calc(60px + var(--fab-y)) 10 | padding: 5px !important 11 | left: 5px 12 | width: calc(100vw - 10px) 13 | min-height: 50px 14 | opacity: 0 15 | visibility: hidden 16 | background-color: var(--ftlcl4) !important 17 | border: solid 2px var(--ftlcl5) 18 | border-radius: 50px 19 | transition: all .3s ease-in-out 20 | 21 | & div 22 | display: inline-flex 23 | justify-content: center 24 | align-items: center 25 | width: 46px 26 | flex: 0 0 auto 27 | height: 46px 28 | margin: 0 !important 29 | padding: 0 !important 30 | color: var(--ftlcl3) 31 | background-color: var(--ftlcl2) !important 32 | border-radius: 50% -------------------------------------------------------------------------------- /src/sass/fab/fab_r.sass: -------------------------------------------------------------------------------- 1 | .fab_radial div>svg 2 | height: 25px 3 | 4 | #fab_r 5 | position: fixed 6 | bottom: calc(-125px + var(--fab-y) ) 7 | left: calc(-125px + var(--fab-x) ) 8 | width: 300px 9 | height: 300px 10 | padding: 0 !important 11 | margin: 0 12 | pointer-events: none 13 | z-index: 1000 14 | 15 | #fab_r_cnt 16 | width: 300px 17 | height: 300px 18 | opacity: 0 19 | visibility: hidden 20 | border-radius: 50% 21 | background-color: var(--ftlcl4) !important 22 | border: solid 2px var(--ftlcl5) !important 23 | 24 | .open #fab_r_cnt 25 | opacity: 1 26 | visibility: visible !important 27 | pointer-events: auto 28 | 29 | & div 30 | text-decoration: none 31 | background-color: var(--ftlcl2) !important 32 | color: var(--ftlcl3) !important 33 | display: flex 34 | align-items: center 35 | justify-content: center 36 | border-radius: 50% 37 | height: 46px 38 | width: 46px 39 | padding: 0 !important 40 | margin-left: -23px 41 | margin-top: -23px 42 | position: absolute 43 | text-align: center 44 | 45 | #fly_btn-r 46 | bottom: calc(50% - 25px) 47 | left: calc(50% - 25px) 48 | position: absolute 49 | z-index: 1000 50 | height: 50px 51 | width: 50px 52 | background-color: var(--ftlcl0) !important 53 | color: var(--ftlcl1) !important 54 | border-radius: 50% 55 | user-select: none 56 | display: flex 57 | justify-content: center 58 | align-items: center 59 | pointer-events: auto 60 | -------------------------------------------------------------------------------- /src/sass/fab/fab_s.sass: -------------------------------------------------------------------------------- 1 | #fly_btn-sheet 2 | bottom: calc(50% - 25px) 3 | left: calc(50% - 25px) 4 | position: absolute 5 | z-index: 1000 6 | height: 50px 7 | width: 50px 8 | background-color: var(--ftlcl0) !important 9 | color: var(--ftlcl1) !important 10 | border-radius: 50% 11 | user-select: none 12 | display: flex 13 | justify-content: center 14 | align-items: center 15 | pointer-events: auto 16 | 17 | #fab_s_cnt 18 | border: 2px solid var(--ftlcl2) 19 | display: flex 20 | flex-wrap: wrap 21 | justify-content: left 22 | background: var(--ftlcl2) 23 | border-radius: .5rem 24 | 25 | #fab_s_cnt div 26 | color: var(--ftlcl3) 27 | height: 46px 28 | width: 46px 29 | margin: 0!important 30 | padding: 0!important 31 | display: flex 32 | justify-content: center 33 | align-items: center 34 | background: var(--ftlcl2) 35 | 36 | #fab_s_cnt div>svg 37 | height: 25px 38 | 39 | .bottom-sheet 40 | pointer-events: none 41 | visibility: hidden 42 | overflow: hidden 43 | position: fixed 44 | left: 0 45 | bottom: 0 46 | height: 100vh 47 | width: 100vw 48 | margin: 0 !important 49 | padding: 0 !important 50 | z-index: 1015 51 | transition: opacity, visibility 0.25s 52 | 53 | &.active 54 | visibility: visible 55 | pointer-events: unset 56 | 57 | .bs-scrim 58 | opacity: 0 59 | display: block 60 | position: absolute 61 | z-index: 1 62 | height: 100vh 63 | width: 100vw 64 | background-color: rgba(0, 0, 0, 0.3) 65 | transition: opacity 0.3s 66 | top: 0 67 | .active .bs-scrim 68 | opacity: 1 69 | 70 | .bs-sheet 71 | display: flex 72 | flex-direction: column 73 | gap: .5rem 74 | position: fixed 75 | left: 0 76 | bottom: 0px 77 | width: 100% 78 | border-radius: 1rem 1rem 0 0 79 | min-height: unset 80 | background: var(--ftlcl4)!important 81 | transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1) 82 | transform: translateY(100%) 83 | z-index: 2 84 | &>* 85 | flex-grow: 0 86 | .active .bs-sheet 87 | transform: var(--translateY) 88 | 89 | .bs-handle 90 | display: flex 91 | flex-direction: column 92 | align-items: center 93 | padding: .5rem 94 | 95 | &>span 96 | display: block 97 | height: 4px 98 | width: 32px 99 | background-color: rgba(0, 0, 0, 0.3) 100 | border-radius: 2px 101 | 102 | .bs-cnt 103 | position: relative 104 | margin: 0 105 | padding: 0 106 | display: flex 107 | flex-grow: 1 108 | max-width: 600px 109 | margin: auto!important 110 | flex-direction: column 111 | align-items: center 112 | 113 | .bs-footer 114 | width: auto 115 | padding: 0.5rem 116 | display: flex 117 | align-items: center 118 | justify-content: center 119 | height: 40px 120 | border-radius: 0 121 | text-align: center 122 | border-top: 2px solid var(--ftlcl6) !important 123 | color: var(--ftlcl6) !important 124 | 125 | .bs-cnt>* 126 | width: 100% 127 | .bs-handle>span 128 | background: var(--ftlcl6)!important 129 | -------------------------------------------------------------------------------- /src/sass/index.sass: -------------------------------------------------------------------------------- 1 | @charset "utf-8" 2 | 3 | @import "./settings/variables" 4 | @import "./theme/light" 5 | @import "./theme/dark" 6 | 7 | @import "./base/basic" 8 | @import "./extra/aos" 9 | @import "./elements/typography" 10 | 11 | @import "./layout/navbar" 12 | @import "./layout/header" 13 | @import "./layout/container" 14 | @import "./layout/grid" 15 | @import "./layout/footer" 16 | 17 | @import "./elements/button" 18 | @import "./components/card" 19 | @import "./elements/form" 20 | @import "./components/modal" 21 | @import "./components/gotop_link" 22 | @import "./extra/tooltip" 23 | @import "./extra/alert" 24 | @import "./extra/wave" 25 | 26 | @import "./utilities/alignment" 27 | @import "./utilities/background" 28 | @import "./utilities/position" 29 | @import "./utilities/border" 30 | @import "./utilities/display" 31 | @import "./utilities/margin" 32 | @import "./utilities/general" 33 | @import "./utilities/padding" 34 | @import "./utilities/spacer" 35 | @import "./utilities/text" 36 | 37 | main 38 | margin-top: 8rem 39 | -------------------------------------------------------------------------------- /src/sass/layout/aside.sass: -------------------------------------------------------------------------------- 1 | /* ------------ Aside menu on right ----------- */ 2 | aside 3 | position: sticky 4 | top: 6rem 5 | width: 100% 6 | font-size: smaller 7 | background: var(--aside-bg) 8 | padding: .5rem 9 | border-radius: var(--radius) 10 | box-shadow: var(--aside-bs) 11 | ul 12 | padding: .5rem 13 | margin: 0 14 | a 15 | display: flex 16 | align-items: center 17 | justify-content: flex-start 18 | width: 100% 19 | text-decoration: none 20 | padding: 0.5rem 21 | border-radius: var(--radius) 22 | border: 0!important 23 | &:hover,&.active 24 | background: var(--aside-bg-h) 25 | -------------------------------------------------------------------------------- /src/sass/layout/container.sass: -------------------------------------------------------------------------------- 1 | /* ------- Main container and .cnt class ------ */ 2 | .cnt,main 3 | margin: auto 4 | width: 100% 5 | max-width: 60rem 6 | padding: 0 .5rem 7 | &.full 8 | max-width: auto 9 | .links-cnt 10 | display: flex 11 | flex-flow: row wrap 12 | gap: 1rem 13 | ._2-col 14 | display: grid 15 | grid-template-columns: 1fr 1fr 16 | gap: .5rem 17 | margin-top: .5rem -------------------------------------------------------------------------------- /src/sass/layout/footer.sass: -------------------------------------------------------------------------------- 1 | /* ------------------ Footer ------------------ */ 2 | footer 3 | width: 100% 4 | padding: 4rem .5rem 5 | margin-top: -1px 6 | footer>div 7 | border-radius: calc(var(--radius) + 0.2rem) 8 | padding: .4rem -------------------------------------------------------------------------------- /src/sass/layout/grid.sass: -------------------------------------------------------------------------------- 1 | /* ----------- Grid columns and row ----------- */ 2 | .grid 3 | flex-wrap: wrap 4 | .row ,.grid 5 | display: flex 6 | align-items: stretch 7 | &>[class*="col"],&>div 8 | flex: 0 0 auto 9 | flex-shrink: 0 10 | width: 100% 11 | max-width: 100% 12 | padding: .5rem 13 | &>[class*="col"]>*,&>div>* 14 | margin: 0 15 | .col,&>div 16 | flex: 1 1 100% 17 | .col-2 18 | width: calc((100%) / (12/2)) 19 | .col-3 20 | width: calc((100%) / (12/3)) 21 | .col-4 22 | width: calc((100%) / (12/4)) 23 | .col-6 24 | width: calc((100%) / (12/6)) 25 | .col-8 26 | width: calc((100%) / (12/8)) 27 | .col-9 28 | width: calc((100%) / (12/9)) 29 | .col-10 30 | width: calc((100%) / (12/10)) 31 | 32 | @media (max-width:40em) 33 | .row:not(.keep-width),.grid:not(.keep-width) 34 | flex-direction: column !important 35 | &>[class*="col"],&>div 36 | width: auto -------------------------------------------------------------------------------- /src/sass/layout/header.sass: -------------------------------------------------------------------------------- 1 | /* ------------------ Header ------------------ */ 2 | header 3 | & + .hero 4 | margin-top: 4rem 5 | .hero 6 | display: flex 7 | flex-direction: column 8 | justify-content: center 9 | align-items: center 10 | padding: 2rem 1rem 11 | text-align: center 12 | border-radius: var(--radius) -------------------------------------------------------------------------------- /src/sass/layout/navbar.sass: -------------------------------------------------------------------------------- 1 | /* ------------------ Navbar ------------------ */ 2 | header 3 | width: 100% 4 | z-index: 2 5 | top: 0 6 | .navbar-wrap 7 | padding: .5rem .5rem .5rem .5rem 8 | margin: auto 9 | header.scrolled .navbar-wrap 10 | background: var(--bg-nav) 11 | 12 | nav 13 | display: flex 14 | justify-content: space-between 15 | align-items: center 16 | background: var(--bg-nav) 17 | border-radius: calc( var(--radius) + .2rem) 18 | padding: .4rem 19 | width: 100% 20 | margin: auto 21 | max-width: 60rem 22 | >.nav-overlay 23 | display: none 24 | position: fixed 25 | top: 0 26 | left: 0 27 | width: 100% 28 | height: 100% 29 | background: rgba(17, 17, 17, 0.6) 30 | z-index: -1 31 | * 32 | margin: 0 33 | padding: 0 34 | display: flex 35 | align-items: center 36 | >button 37 | display: none 38 | margin: 0 !important 39 | min-width: auto 40 | font-size: 0.875em 41 | padding: .5rem 42 | >svg line 43 | stroke: currentColor 44 | stroke-width: 4 45 | stroke-linecap: round 46 | transform-origin: 12px 12px 47 | transition: all .4s 48 | >a 49 | gap: .5rem 50 | font-size: 1.6rem 51 | padding: 0.4rem 52 | >svg 53 | height: 1.6rem !important 54 | width: 1.6rem !important 55 | a:hover 56 | border: none !important 57 | >ul 58 | flex-direction: row 59 | justify-content: space-between 60 | list-style: none 61 | width: auto 62 | gap: .5rem 63 | >li 64 | gap: .5rem 65 | >li >a,>li .nav-item 66 | padding: .4rem 67 | border-radius: var(--radius) 68 | color: var(--txt) 69 | cursor: pointer 70 | &:hover,&.active 71 | background: var(--bg-nav-h) 72 | &.active 73 | color: var(--primary) 74 | 75 | @media only screen and ( max-width: 768px ) 76 | nav 77 | &>ul 78 | position: fixed 79 | top: 5rem 80 | padding: .5rem 81 | right: -100% 82 | flex-direction: column 83 | background: var(--bg-nav) 84 | width: calc(100% - 1rem) 85 | border-radius: 10px 86 | text-align: center 87 | transition: .3s 88 | box-shadow: var(--shadow) 89 | &>button 90 | display: flex 91 | cursor: pointer 92 | &.active 93 | &>.nav-overlay 94 | display: flex 95 | >ul 96 | right: .5rem 97 | 98 | >button svg line:nth-child(1) 99 | opacity: 0 100 | transform: translateY(-100%) 101 | >button svg line:nth-child(4) 102 | opacity: 0 103 | transform: translateY(100%) 104 | >button svg line:nth-child(2) 105 | transform: rotate(45deg) 106 | >button svg line:nth-child(3) 107 | transform: rotate(-45deg) 108 | -------------------------------------------------------------------------------- /src/sass/options.sass: -------------------------------------------------------------------------------- 1 | @charset "utf-8" 2 | 3 | @import "./settings/variables" 4 | 5 | @import "./theme/light" 6 | @import "./theme/dark" 7 | 8 | html 9 | --rv: 6 10 | --fab-y: 20px 11 | --fab-x: 20px 12 | 13 | @import "./base/basic" 14 | 15 | @import "./layout/navbar" 16 | @import "./layout/grid" 17 | @import "./layout/container" 18 | 19 | @import "./elements/button" 20 | @import "./elements/form" 21 | @import "./elements/typography" 22 | 23 | @import "./components/tabs" 24 | @import "./components/card" 25 | @import "./components/modal" 26 | @import "./components/bottomsheet" 27 | @import "./extra/aos" 28 | @import "./utilities/alignment" 29 | @import "./utilities/background" 30 | @import "./utilities/margin" 31 | @import "./utilities/position" 32 | @import "./utilities/display" 33 | @import "./utilities/general" 34 | @import "./picker" 35 | .tabs>label 36 | width: calc(50% - 0.5rem ) 37 | text-align: center 38 | 39 | body 40 | --ftlcl0: #0878A5 41 | --ftlcl1: #fff 42 | --ftlcl2: #0878A5 43 | --ftlcl3: #fff 44 | --ftlcl4: #fff 45 | --ftlcl5: #101010 46 | --ftlcl6: #101010 47 | --ftlcl7: #0878A5 48 | --ftlcl8: #fff 49 | 50 | .bab 51 | display: flex 52 | width: 100% 53 | margin: auto 54 | height: 50px 55 | align-items: center 56 | justify-content: space-evenly 57 | border-radius: 0 58 | background: var(--ftlcl7) 59 | position: fixed 60 | bottom: 0 61 | left: 0 62 | z-index: 999 63 | &>div 64 | position: relative 65 | display: inline-flex 66 | justify-content: center 67 | align-items: center 68 | width: 46px 69 | flex: 0 0 auto 70 | height: 46px 71 | padding: 0 !important 72 | text-decoration: none 73 | color: var(--ftlcl8) 74 | 75 | #av,#fab_s_cnt 76 | border: 2px solid var(--ftlcl2) 77 | display: flex 78 | flex-wrap: wrap 79 | justify-content: left 80 | min-height: 50px 81 | 82 | #fab_s_cnt div, 83 | #av div, 84 | #uv div, 85 | .fly_btn 86 | color: var(--ftlcl3) 87 | height: 46px 88 | width: 46px 89 | display: flex 90 | justify-content: center 91 | align-items: center 92 | 93 | #av div>svg, 94 | #fab_s_cnt div>svg, 95 | #uv div>svg ,.fab_radial div>svg 96 | height: 25px 97 | 98 | #sv option, 99 | #sv 100 | color: var(--ftlcl2) 101 | font-weight: 700 102 | 103 | .av_ghost 104 | background-color: var(--ftlcl2) !important 105 | filter: invert(100%) 106 | 107 | ::-webkit-scrollbar 108 | width: 12px 109 | 110 | ::-webkit-scrollbar-track 111 | background-color: #ddd 112 | 113 | #uv, 114 | #fab_s_cnt, 115 | #av, 116 | .sw, 117 | #uv::-webkit-scrollbar-track, 118 | #uv::-webkit-scrollbar-thumb, 119 | .sw::-webkit-scrollbar-track, 120 | .sw::-webkit-scrollbar-thumb 121 | border-radius: var(--radius) 122 | 123 | #uv::-webkit-scrollbar 124 | height: 20px !important 125 | 126 | #uv 127 | border: 2px solid var(--ftlcl2) 128 | padding-bottom: 25px 129 | display: flex 130 | min-height: 70px 131 | gap: .5rem 132 | flex-wrap: wrap 133 | padding: .5rem 134 | 135 | #uv div, 136 | .fly_btn 137 | border-radius: 50% 138 | 139 | #pth1 140 | fill: var(--ftlcl1) 141 | 142 | .sw-wrapper 143 | position: relative 144 | 145 | .sw 146 | border: 2px var(--ftlcl2) solid 147 | height: 200px 148 | overflow-x: hidden 149 | overflow-y: scroll 150 | padding: 10px 151 | display: flex 152 | flex-direction: column 153 | gap: 10px 154 | 155 | &>div 156 | display: flex 157 | align-items: center 158 | 159 | .user-list 160 | width: 100% 161 | 162 | #tbtb>tr>div 163 | border: 2px solid var(--ftlcl2) 164 | padding: .5rem 165 | border-radius: var(--radius) 166 | 167 | #tbtb label 168 | display: inline-block 169 | margin-left: 10px 170 | 171 | .stt_clfrt 172 | background: transparent 173 | border: 2px solid #000 174 | border-radius: var(--radius) 175 | display: inline-block 176 | height: 30px 177 | position: relative 178 | width: 30px 179 | 180 | #stt_cl0, 181 | .fab_btn 182 | background: var(--ftlcl0) 183 | 184 | #stt_cl1, 185 | #lrt_fl .lrt_cnt 186 | background: var(--ftlcl1) 187 | 188 | #stt_cl3 189 | background: var(--ftlcl3) 190 | 191 | #stt_cl4 192 | background: var(--ftlcl4) 193 | 194 | #stt_cl5 195 | background: var(--ftlcl5) 196 | 197 | #stt_cl6 198 | background: var(--ftlcl6) 199 | #stt_cl7 200 | background: var(--ftlcl7) 201 | #stt_cl8 202 | background: var(--ftlcl8) 203 | 204 | ::-webkit-scrollbar-thumb, 205 | ::-webkit-scrollbar-thumb:hover, 206 | #stt_cl2, 207 | #uv div, 208 | #av div, 209 | #fab_s_cnt div, 210 | #fab_s_cnt, 211 | #av 212 | background: var(--ftlcl2) 213 | 214 | #pth1 215 | fill: '+c[1]+' !important 216 | 217 | .fab_wrap 218 | padding: 0 !important 219 | margin: 0 220 | z-index: 1000 221 | .fab_btn 222 | position: fixed 223 | z-index: 1010 224 | height: 50px 225 | width: 50px 226 | bottom: var(--fab-y) 227 | left: var(--fab-x) 228 | background-color: var(--ftlcl0) !important 229 | color: var(--ftlcl1) !important 230 | border-radius: 50% 231 | box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.15) 232 | user-select: none 233 | display: flex 234 | justify-content: center 235 | align-items: center 236 | pointer-events: auto 237 | & svg 238 | fill: currentColor 239 | height: 26px 240 | .fab_wrap.open 241 | pointer-events: unset !important 242 | & .fab_cnt 243 | opacity: 1 !important 244 | visibility: visible !important 245 | 246 | #ac4.fullscreen svg:nth-child(2), 247 | #ac4 svg:nth-child(1) 248 | display: inline 249 | 250 | #ac4.fullscreen svg:nth-child(1), 251 | #ac4 svg:nth-child(2) 252 | display: none 253 | 254 | ::-webkit-scrollbar 255 | display: none 256 | 257 | #fab_h_cnt 258 | position: fixed 259 | overflow: auto 260 | display: flex 261 | flex-direction: row 262 | flex-wrap: nowrap 263 | overflow-x: auto 264 | gap: 5px 265 | bottom: calc(60px + var(--fab-y)) 266 | padding: 5px !important 267 | left: 5px 268 | width: calc(100vw - 10px) 269 | min-height: 50px 270 | opacity: 0 271 | visibility: hidden 272 | background-color: var(--ftlcl4) !important 273 | border: solid 2px var(--ftlcl5) 274 | border-radius: 50px 275 | transition: all .3s ease-in-out 276 | 277 | & div 278 | display: inline-flex 279 | justify-content: center 280 | align-items: center 281 | width: 46px 282 | flex: 0 0 auto 283 | height: 46px 284 | padding: 0 !important 285 | color: var(--ftlcl3) 286 | background-color: var(--ftlcl2) !important 287 | border-radius: 50% 288 | 289 | #fab_r 290 | position: fixed 291 | bottom: calc(-125px + var(--fab-y) ) 292 | left: calc(-125px + var(--fab-x) ) 293 | width: 300px 294 | height: 300px 295 | padding: 0 !important 296 | margin: 0 297 | pointer-events: none 298 | z-index: 1000 299 | 300 | #mm_bg 301 | visibility: hidden 302 | opacity: 0 303 | position: fixed 304 | top: 0 305 | left: 0 306 | height: 100vh 307 | width: 100vw 308 | z-index: 10000 309 | backdrop-filter: blur(4px) 310 | display: none 311 | align-items: center 312 | justify-content: center 313 | background: rgba(0, 0, 0, 0.63) 314 | transition: all .5s cubic-bezier(0.55, 0.055, 0.675, 0.19) 315 | 316 | &.show 317 | display: flex 318 | visibility: visible 319 | opacity: 1 320 | 321 | #fab_r_cnt 322 | width: 300px 323 | height: 300px 324 | opacity: 0 325 | visibility: hidden 326 | border-radius: 50% 327 | background-color: var(--ftlcl4) !important 328 | border: solid 2px var(--ftlcl5) !important 329 | 330 | .open #fab_r_cnt 331 | opacity: 1 332 | visibility: visible !important 333 | pointer-events: auto 334 | 335 | & div 336 | text-decoration: none 337 | background-color: var(--ftlcl2) !important 338 | color: var(--ftlcl3) !important 339 | display: flex 340 | align-items: center 341 | justify-content: center 342 | border-radius: 50% 343 | height: 46px 344 | width: 46px 345 | margin-left: -23px 346 | margin-top: -23px 347 | position: absolute 348 | text-align: center 349 | 350 | #fly_btn-r ,#mm_move,#fly_btn-sheet 351 | bottom: calc(50% - 25px) 352 | left: calc(50% - 25px) 353 | position: absolute 354 | z-index: 1000 355 | height: 50px 356 | width: 50px 357 | background-color: var(--ftlcl0) !important 358 | color: var(--ftlcl1) !important 359 | border-radius: 50% 360 | box-shadow: 0 5px 20px rgb(0 0 0 / 15%) 361 | user-select: none 362 | display: flex 363 | justify-content: center 364 | align-items: center 365 | pointer-events: auto 366 | 367 | #mm_move 368 | display: none 369 | bottom: var(--fab-y) 370 | left: var(--fab-x) 371 | z-index: 1000001 372 | &>svg 373 | height: 30px 374 | 375 | .f-preview 376 | display: none 377 | &.show 378 | display: block 379 | 380 | .chk 381 | width: 100% 382 | 383 | .layout_default.picker_wrapper 384 | width: auto 385 | background: transparent 386 | box-shadow: none 387 | gap: 10px 388 | &>* 389 | margin: 1em 390 | border-radius: 5px 391 | 392 | .layout_default .picker_editor 393 | width: auto 394 | 395 | .bs-sheet 396 | min-height: unset 397 | background: var(--ftlcl4)!important 398 | 399 | .bs-handle>span 400 | background: var(--ftlcl6)!important 401 | 402 | .bs-footer 403 | border-color: var(--ftlcl6)!important 404 | color: var(--ftlcl6)!important 405 | -------------------------------------------------------------------------------- /src/sass/picker.sass: -------------------------------------------------------------------------------- 1 | 2 | .picker_wrapper 3 | background: transparent!important 4 | box-shadow: none!important 5 | margin: 0 auto!important 6 | 7 | &>* 8 | box-shadow: none!important 9 | margin: 1em!important 10 | border-radius: 5px!important 11 | border: 0.1rem solid var(--brd) 12 | 13 | .picker_editor 14 | width: 100%!important 15 | order: 2!important 16 | border: none 17 | 18 | .picker_wrapper button, .picker_wrapper input 19 | box-shadow: none!important 20 | 21 | .picker_sample 22 | width: 100% 23 | height: 2.6rem 24 | 25 | .picker_cancel,.picker_done 26 | display: flex 27 | margin: 0!important 28 | 29 | .picker_cancel 30 | width: 50% 31 | order: 3!important 32 | 33 | & button,.picker_done button 34 | width: 100% 35 | 36 | .picker_done 37 | display: none!important 38 | 39 | .picker_sample::before 40 | border-radius: 5px 41 | 42 | .picker_wrapper button 43 | background-image: none!important 44 | background: var(--c22)!important 45 | color: var(--c20)!important 46 | -------------------------------------------------------------------------------- /src/sass/settings/_colors.sass: -------------------------------------------------------------------------------- 1 | //Gray 2 | 3 | $gray-50:#f9fafb 4 | $gray-100:#f3f4f6 5 | $gray-200:#e5e7eb 6 | $gray-300:#d1d5db 7 | $gray-400:#9ca3af 8 | $gray-500:#6b7280 9 | $gray-600:#4b5563 10 | $gray-700:#374151 11 | $gray-800:#1f2937 12 | $gray-900:#111827 13 | //Blue 14 | $blue-50: #E0F1FF 15 | $blue-100: #C2E2FF 16 | $blue-200: #8AC8FF 17 | $blue-300: #4DACFF 18 | $blue-400: #1492FF 19 | $blue-500: #0074D8 20 | $blue-600: #005CAD 21 | $blue-700: #004480 22 | $blue-800: #002E57 23 | $blue-900: #001629 24 | //Green 25 | $green-50: #E9FBF0!default 26 | $green-100: #CFF7DE!default 27 | $green-200: #9FEFBC!default 28 | $green-300: #6FE69B!default 29 | $green-400: #40DE7A!default 30 | $green-500: #22C55E!default 31 | $green-600: #1B9D4B!default 32 | $green-700: #147538!default 33 | $green-800: #0D4E25!default 34 | $green-900: #072713!default 35 | //Red 36 | $red-50: #FFECEB 37 | $red-100: #FFD9D6 38 | $red-200: #FFB3AD 39 | $red-300: #FF8D85 40 | $red-400: #FF675C 41 | $red-500: #FF4132 42 | $red-600: #F51000 43 | $red-700: #B80C00 44 | $red-800: #7A0800 45 | $red-900: #3D0400 46 | //Orange 47 | $orange-50: #FFF4EB 48 | $orange-100: #FFE7D1 49 | $orange-200: #FFCEA3 50 | $orange-300: #FFB675 51 | $orange-400: #FF9D47 52 | $orange-500: #FF851B 53 | $orange-600: #E06900 54 | $orange-700: #A84F00 55 | $orange-800: #703400 56 | $orange-900: #381A00 57 | //Purple 58 | $purple-50: #f7f2fc 59 | $purple-100: #dfccf4 60 | $purple-200: #c7a6ec 61 | $purple-300: #b080e5 62 | $purple-400: #985add 63 | $purple-500: #8034d5 64 | $purple-600: #6b2bb2 65 | $purple-700: #55228f 66 | $purple-800: #40186b 67 | $purple-900: #2a0f48 68 | 69 | $white-0: #fff 70 | $white-50: #fafafa 71 | $white-100:#dbdbdb 72 | 73 | $black-0: #000 74 | $black-50: #101010 75 | $black-100: #4d4d4d 76 | -------------------------------------------------------------------------------- /src/sass/settings/_variables.sass: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------- 2 | // Variables 3 | // -------------------------------------------------- 4 | @import colors 5 | // Typography 6 | $line-height-base: 1.5 !default 7 | $font-size-base: 1rem !default 8 | 9 | $sizes: ( "0": 0,"05": 0.5rem,"1": 1rem,"2": 2rem,"3": 3rem,"4": 4rem,"5": 5rem) 10 | 11 | // Grid 12 | $grid-columns: 12 !default 13 | $grid-gap: .5rem !default 14 | $grid-gutter: 1.5rem !default 15 | 16 | // Button 17 | $btn-border-width: 2px !default 18 | $btn-border-radius: .5rem !default 19 | $btn-font-size: $font-size-base !default 20 | $btn-line-height: $line-height-base !default -------------------------------------------------------------------------------- /src/sass/theme/dark.sass: -------------------------------------------------------------------------------- 1 | [data-theme="dark"] 2 | //Color Palette 3 | --blue: #{$blue-500} 4 | --blue-l: #{$blue-400} 5 | --blue-h: #{$blue-800} 6 | --blue-d: #{$blue-600} 7 | --purple: #{$purple-500} 8 | --purple-l: #{$purple-400} 9 | --purple-h: #{$purple-800} 10 | --purple-d: #{$purple-600} 11 | --green: #{$green-500} 12 | --green-h: #{$green-800} 13 | --green-l: #{$green-400} 14 | --green-d: #{$green-300} 15 | --red: #{$red-500} 16 | --red-h: #{$red-800} 17 | --red-l: #{$red-400} 18 | --red-d: #{$red-300} 19 | --orange: #{$orange-500} 20 | --orange-h: #{$orange-800} 21 | --orange-l: #{$orange-400} 22 | --orange-d: #{$orange-600} 23 | --txt-l:var(--white-l) 24 | --txt-p : var(--white) 25 | --txt-red : var(--white) 26 | --txt-green : var(--white) 27 | --txt-blue : var(--white) 28 | --txt-orange : var(--white) 29 | //Text Colors 30 | --txt: var(--white) 31 | --txt-r: var(--black) 32 | //Background Colors 33 | --bg3: #{$gray-700} 34 | --bg2: #{$gray-800} 35 | --bg: #{$gray-900} 36 | 37 | //Border 38 | --brd: #{$gray-600} 39 | --aside-bg-h: #{$gray-700} 40 | --bg-card: var(--bg) 41 | --bg-details: var(--bg2) 42 | --bg-details2: var(--bg3) 43 | --txt-details-open: var(--txt) 44 | --shadow: none 45 | --b-shadow: var(--black) 46 | 47 | [data-theme="light"] .light-icon,[data-theme="dark"] .dark-icon 48 | display: block !important 49 | .theme-icon 50 | display: none 51 | -------------------------------------------------------------------------------- /src/sass/theme/light.sass: -------------------------------------------------------------------------------- 1 | html , [data-theme='light'] 2 | //Color Palette 3 | --blue: #{$blue-500} 4 | --blue-l: #{$blue-400} 5 | --blue-h: #{$blue-50} 6 | --blue-d: #{$blue-800} 7 | --purple: #{$purple-500} 8 | --purple-l: #{$purple-400} 9 | --purple-h: #{$purple-50} 10 | --purple-d: #{$purple-800} 11 | --green: #{$green-500} 12 | --green-h: #{$green-100} 13 | --green-l: #{$green-400} 14 | --green-d: #{$green-800} 15 | --red: #{$red-500} 16 | --red-h: #{$red-100} 17 | --red-l: #{$red-400} 18 | --red-d: #{$red-800} 19 | --orange: #{$orange-500} 20 | --orange-h: #{$orange-100} 21 | --orange-l: #{$orange-400} 22 | --orange-d: #{$orange-800} 23 | --white: #{$white-50} 24 | --white-h:#{$white-100} 25 | --white-l:#{$white-0} 26 | 27 | --black: #{$black-50} 28 | --black-l: #{$black-0} 29 | --black-h: #{$black-100} 30 | 31 | --txt-on-p: var(--white) 32 | --txt-on-red: var(--white) 33 | --txt-on-green: var(--white) 34 | --txt-on-purple: var(--white) 35 | --txt-on-blue: var(--white) 36 | --txt-on-orange: var(--white) 37 | --gray: #{$gray-200} 38 | --gray-l: #{$gray-100} 39 | --gray-d: #{$gray-400} 40 | //Key Colors 41 | --primary: var(--purple) 42 | --primary-h: var(--purple-h) 43 | --primary-l: var(--purple-l) 44 | --primary-d: var(--purple-d) 45 | --accent: #E3F2FD 46 | --active: #ECEFF1 47 | --focus: var(--primary) 48 | --hover: #ECF4FD 49 | //Text Color 50 | --txt: var(--black) 51 | --txt-l:var(--black-l) 52 | --txt-h:var(--black-h) 53 | --txt-r: var(--white) 54 | //Background Colors 55 | --bg: #{$gray-50} 56 | --bg2: #{$gray-100} 57 | --bg3: #{$gray-200} 58 | //Border 59 | --brd: #{$gray-300} 60 | 61 | //Navbar 62 | --bg-nav: var(--bg) 63 | --bg-nav-h: var(--bg2) 64 | --bb-nav: var(--primary) 65 | //Form 66 | --input-bg: var(--bg) 67 | --input-txt: var(--txt) 68 | --input-focus: var(--primary-t) 69 | --input-brd: var(--brd) 70 | //Buttons 71 | 72 | --btn-bg: var(--bg) 73 | --btn-ac: var(--primary) 74 | --btn-txt: var(--txt) 75 | --btn-brd: var(--brd) 76 | --btn-hs: 0 77 | --btn-f: var(--bg2) 78 | --btn-bg-h: var(--bg3) 79 | --btn-sd: 0 1px var(--btn-brd) 80 | --btn-radius: .5rem 81 | 82 | //Card 83 | --bg-card: var(--bg) 84 | //Details 85 | --bg-details: var(--bg2) 86 | --bg-details-open: var(--primary) 87 | --txt-details-open: var(--white) 88 | //Aside 89 | --aside-bg: transparent 90 | --aside-bg-h: var(--bg3) 91 | --aside-bs: none 92 | 93 | //Tabs 94 | --bg-tabs: var(--bg) 95 | --brd-tabs-l: var(--primary) 96 | --brd-tabs: var(--bg2) 97 | //table 98 | --bg-table: var(--bg) 99 | --fake-brd-table: inset 0 0px 0px 1px var(--bg3) 100 | --bg-table-hover: var(--bg3) 101 | --brd-table: var(--bg3) 102 | --shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px 103 | --b-shadow: #95abbb 104 | --radius: .5rem 105 | -------------------------------------------------------------------------------- /src/sass/utilities/_alignment.sass: -------------------------------------------------------------------------------- 1 | /* ------------ Alignement Utility ------------ */ 2 | $alignments: 'center' 'left' 'right' 3 | @each $align in $alignments 4 | ._ta-#{$align} 5 | text-align: #{$align} !important -------------------------------------------------------------------------------- /src/sass/utilities/_background.sass: -------------------------------------------------------------------------------- 1 | /* --------- Background Color Utility --------- */ 2 | $bgcolors: ("primary","black","white","blue","red","green","yellow","orange")!default 3 | @each $var in $bgcolors 4 | ._bg-#{$var} 5 | background-color: var(--#{$var})!important 6 | ._bg 7 | background-color: var(--bg)!important 8 | ._bg2 9 | background-color: var(--bg2)!important 10 | ._bg3 11 | background-color: var(--bg3)!important -------------------------------------------------------------------------------- /src/sass/utilities/_border.sass: -------------------------------------------------------------------------------- 1 | 2 | /* -------------- Border Utility -------------- */ 3 | ._radius 4 | border-radius: var(--radius) !important 5 | ._radiusless 6 | border-radius: 0 !important 7 | ._brd-none 8 | border: none !important 9 | /* ----------- Border Color Utility ----------- */ 10 | $brdcolors: ("primary","txt-r","txt","black","white","blue","bg","bg2","bg3","red","green","yellow","orange")!default 11 | @each $var in $brdcolors 12 | ._brd-#{$var} 13 | border: 2px solid var(--#{$var})!important 14 | -------------------------------------------------------------------------------- /src/sass/utilities/_display.sass: -------------------------------------------------------------------------------- 1 | /* -------------- Display Utility ------------- */ 2 | $displays: 'block' 'flex' 'inline' 'none' 3 | @each $display in $displays 4 | ._d-#{$display} 5 | display: #{$display} !important 6 | 7 | @media only screen and (min-width: 768px) 8 | ._d-m-only 9 | display: none 10 | 11 | @media only screen and (max-width: 390px) 12 | ._d-md-only 13 | display: none -------------------------------------------------------------------------------- /src/sass/utilities/_general.sass: -------------------------------------------------------------------------------- 1 | /* ------------- General utilities ------------ */ 2 | 3 | ._clearfix 4 | clear: both !important 5 | 6 | ._floatleft 7 | float: left !important 8 | 9 | ._floatright 10 | float: right !important 11 | 12 | ._shadowless 13 | box-shadow: none !important 14 | 15 | ._shadow 16 | box-shadow: var(--shadow) !important 17 | 18 | ._overflowauto 19 | overflow: auto !important 20 | 21 | ._overflowhidden 22 | overflow: hidden !important 23 | 24 | ._f-center 25 | display: flex 26 | flex-flow: wrap 27 | justify-content: center 28 | align-items: center 29 | 30 | ._f-column 31 | display: flex 32 | flex-direction: column 33 | ._f-wrap 34 | display: flex 35 | flex-wrap: wrap 36 | ._icon 37 | display: block 38 | height: 1.4rem 39 | width: 1.4rem 40 | fill: currentColor 41 | margin: 0 auto 42 | 43 | ._ratio32 44 | //height: max(18vh, 12rem)!important 45 | apsect-ratio: 3/2 46 | 47 | ._fit-cover 48 | object-fit: cover !important 49 | 50 | ._no-select 51 | -webkit-user-select: none 52 | -moz-user-select: none 53 | user-select: none 54 | -------------------------------------------------------------------------------- /src/sass/utilities/_margin.sass: -------------------------------------------------------------------------------- 1 | /* -------------- Margin Utility ------------- */ 2 | @each $size, $value in $sizes 3 | ._m-#{$size} 4 | margin: $value 5 | ._mt-#{$size} 6 | margin-top: $value 7 | ._mr-#{$size} 8 | margin-right: $value 9 | ._mb-#{$size} 10 | margin-bottom: $value 11 | ._ml-#{$size} 12 | margin-left: $value 13 | ._mx-#{$size} 14 | margin-left: $value 15 | margin-right: $value 16 | ._my-#{$size} 17 | margin-left: $value 18 | margin-right: $value 19 | -------------------------------------------------------------------------------- /src/sass/utilities/_padding.sass: -------------------------------------------------------------------------------- 1 | /* -------------- Padding Utility ------------- */ 2 | 3 | @each $size, $value in $sizes 4 | ._p-#{$size} 5 | padding: $value 6 | ._pt-#{$size} 7 | padding-top: $value 8 | ._pr-#{$size} 9 | padding-right: $value 10 | ._pb-#{$size} 11 | padding-bottom: $value 12 | ._pl-#{$size} 13 | padding-left: $value 14 | ._px-#{$size} 15 | padding-left: $value 16 | padding-right: $value 17 | ._py-#{$size} 18 | padding-left: $value 19 | padding-right: $value -------------------------------------------------------------------------------- /src/sass/utilities/_position.sass: -------------------------------------------------------------------------------- 1 | ._sticky 2 | position: sticky !important 3 | ._fixed 4 | position: fixed !important 5 | ._absolute 6 | position: absolute !important 7 | ._float-top 8 | position: absolute 9 | top: 0 10 | -------------------------------------------------------------------------------- /src/sass/utilities/_spacer.sass: -------------------------------------------------------------------------------- 1 | /* -------------- Spacer Utility -------------- */ 2 | $values: ("0": 0,"1": 1rem, "2": 2rem, "3": 3rem, "4": 4rem) 3 | 4 | @each $name, $value in $values 5 | [class*="-#{$name}"] 6 | --spacer: #{$value}!important -------------------------------------------------------------------------------- /src/sass/utilities/_text.sass: -------------------------------------------------------------------------------- 1 | /* ------------ Text Color Utility ------------ */ 2 | $txtcolors: ("primary","black","white","blue","red","green","yellow","orange")!default 3 | @each $var in $txtcolors 4 | ._txt-#{$var} 5 | color: var(--#{$var})!important 6 | ._txt 7 | color: var(--txt)!important 8 | ._txt-r 9 | color: var(--txt-r)!important 10 | ._mono 11 | font-family: monospace 12 | ._txt-center 13 | text-align: center 14 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"), 2 | MiniCssExtractPlugin = require('mini-css-extract-plugin'), 3 | CleanWebpackPlugin = require("clean-webpack-plugin").CleanWebpackPlugin, 4 | CopyWebpackPlugin = require("copy-webpack-plugin"), 5 | HtmlWebpackPlugin = require("html-webpack-plugin"), 6 | MiniCssExtractPlugin = require("mini-css-extract-plugin"); 7 | WriteFilePlugin = require("write-file-webpack-plugin"); 8 | 9 | module.exports = { 10 | mode: "production", 11 | context: path.resolve(__dirname, './src'), 12 | entry: { 13 | content: "./js/content.js", 14 | options: "./js/options.js", 15 | background: "./js/background.js" 16 | }, 17 | output: { 18 | filename: './js/[name].js', 19 | path: path.resolve(__dirname, './dist'), 20 | clean: true 21 | }, 22 | module: { 23 | rules: [{ 24 | test: /\.css$/i, 25 | use: [MiniCssExtractPlugin.loader, "css-loader"], 26 | }, 27 | { 28 | test: /\.html$/, 29 | loader: "html-loader", 30 | exclude: /node_modules/ 31 | } 32 | ] 33 | }, 34 | plugins: [ 35 | new MiniCssExtractPlugin({ 36 | // Options similar to the same options in webpackOptions.output 37 | // both options are optional 38 | filename: "css/[name].css", 39 | chunkFilename: "[name].css", 40 | }), 41 | new CopyWebpackPlugin({ 42 | patterns: [ 43 | {from: "manifest.json",to : "manifest.json"}, 44 | {from: "png",to:"png"}, 45 | {from: "svg",to:"svg"} 46 | ] 47 | }), 48 | 49 | new HtmlWebpackPlugin({ 50 | template: path.join(__dirname, "src", "html", "options.html"), 51 | filename: "html/options.html", 52 | sources: false, 53 | minify: false, 54 | chunks: ["options"] 55 | }), 56 | ] 57 | }; --------------------------------------------------------------------------------