├── .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 |
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 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
65 |
Bookmarks
66 |
67 |
Visualize your bookmarks as a Kanban Board
68 |
69 |
70 |
71 |
72 |
75 |
Customizable
76 |
77 |
Edit everything to fit your style and colors
78 |
79 |
80 |
81 |
82 |
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 = `${val} `
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 = ['None ']
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('' + value.name + ' ')
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 |
2 |
3 |
4 |
7 |
8 | This website use
Umami for
9 | privacy-focused analytics
10 |
11 |
15 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/partials/gotop.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/partials/support_me.ejs:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Support me
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
Thank you!
23 |
24 |
25 |
26 | Until now, the number of people who got interested in my projects went way beyond
27 | my expectations, I'm so happy to see it grow daily!
28 |
29 | If you use any of them or find them valuable, I'll be grateful for your support!
30 |
31 |
32 |
33 |
34 |
35 |
39 |
40 |
41 |
43 |
44 |
45 |
46 |
47 |
48 | Free
49 |
50 |
51 |
You can support me for free in these ways :
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | Share the projects with others (friends, family or community)
62 |
63 |
64 |
65 | Star and follow my projects
66 |
67 |
68 |
69 | Contribute by opening issues or making PRs
70 |
71 |
72 |
73 |
74 |
75 |
76 |
79 |
80 |
81 |
83 |
84 |
85 |
86 | Donations & Referrals
87 |
88 |
89 |
90 |
91 | Some of my projects have some costs to be kept online, which I can thankfully afford,
92 | but if you feel you want to contribute, here are some ways to do so.
93 |
94 |
95 |
96 |
97 | Support me on Ko-fi
98 |
99 | If, on the other hand, you are a developer, try out Railway with the below link. For every referral who incurs more than $10 worth of usage and pays their first bill, I get credits to use on my projects. In addition, Railway provides $5 in credit each month for free!
100 |
101 | Railway
102 | Railway
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | Close
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/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 | };
--------------------------------------------------------------------------------