├── .env ├── .env.production ├── .eslintrc.json ├── .gitignore ├── Icon ├── LICENSE.md ├── README.md ├── babel.config.js ├── jsconfig.json ├── package.json ├── postcss.config.js ├── public ├── img │ ├── icons │ │ ├── favicon-52.png │ │ ├── icon-192.png │ │ └── icon-512.png │ └── meta │ │ ├── facebook.png │ │ └── twitter.png ├── index.html ├── manifest.json └── robots.txt ├── src ├── App.vue ├── assets │ ├── branding │ │ ├── logo-dark.png │ │ ├── logo-dark.svg │ │ ├── logo-light.png │ │ └── logo-light.svg │ ├── color_blind_filters.svg │ ├── fonts │ │ ├── inter.ttf │ │ ├── sourceCode.ttf │ │ └── sourceSerif.ttf │ └── projects │ │ ├── ceev.png │ │ └── tidycamp.png ├── components │ ├── filters.js │ ├── mixins │ │ ├── keyboard.js │ │ └── screenResizeMixin.js │ ├── plugins │ │ ├── Dayjs.js │ │ ├── logging.js │ │ └── plugins.js │ └── ui │ │ ├── Common │ │ ├── Alert.vue │ │ ├── Callout.vue │ │ ├── Toast.vue │ │ └── Tooltip.vue │ │ ├── Forms │ │ └── Selections.vue │ │ ├── Modals │ │ ├── Confirm.vue │ │ ├── ConfirmLeave.vue │ │ └── Modal.vue │ │ └── Single │ │ ├── Launcher.vue │ │ ├── NavBar.vue │ │ └── ThemeEditor.vue ├── definitions │ ├── changelog.js │ └── characters.js ├── main.js ├── registerServiceWorker.js ├── router.js ├── routes.js ├── service-worker.js ├── store │ ├── database.js │ ├── modules │ │ ├── device.store.js │ │ ├── hold.store.js │ │ ├── site.store.js │ │ └── user.store.js │ └── store.js ├── styles │ ├── animations.scss │ ├── base.scss │ ├── custom-forms.scss │ ├── forms.scss │ ├── helpers.scss │ ├── layout.scss │ ├── main.scss │ ├── modal.scss │ ├── print.scss │ ├── reset.scss │ ├── themes │ │ ├── default.scss │ │ ├── greys.scss │ │ ├── muted.scss │ │ └── primary.scss │ ├── typography.scss │ ├── ui.scss │ └── variables.scss └── views │ ├── Home.vue │ ├── Settings.vue │ ├── apps │ ├── Animations.vue │ ├── AppTemplate.vue │ ├── Characters.vue │ ├── Colors.vue │ └── Shadows.vue │ └── other │ ├── docs │ ├── Changelog.vue │ ├── PrivacyPolicy.vue │ ├── Sponsor.vue │ ├── TermsOfService.vue │ └── design │ │ ├── Design.vue │ │ ├── DesignColors.vue │ │ ├── DesignForms.vue │ │ ├── DesignText.vue │ │ └── DesignUI.vue │ └── error.vue └── vue.config.js /.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VUE_APP_BASE_URL=localhost:8080 3 | VUE_APP_BASE_PATH=/ 4 | VUE_APP_FULL_URL=dev.keyframes.app 5 | VUE_APP_COMPANY_NAME=Keyframes.app -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NODE_ENV=test 2 | VUE_APP_BASE_URL=keyframes.app 3 | VUE_APP_BASE_PATH=/ 4 | VUE_APP_FULL_URL=keyframes.app 5 | VUE_APP_COMPANY_NAME=Keyframes.app 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "root": true, 4 | "parserOptions": { 5 | "parser": "babel-eslint" 6 | }, 7 | "env": { 8 | "browser": true 9 | }, 10 | "extends": [ 11 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 12 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 13 | "plugin:vue/essential" 14 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 15 | // "standard" 16 | ], 17 | // required to lint *.vue files 18 | "plugins": [ 19 | "vue" 20 | ], 21 | // add your custom rules here 22 | "rules": { 23 | // allow async-await 24 | "generator-star-spacing": "off", 25 | // allow debugger during development 26 | // "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", 27 | "semi": ["warn", "always"], 28 | "quotes": ["warn", "double"] 29 | 30 | } 31 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | package-lock.json 3 | 4 | # Build/Dist 5 | /dist 6 | /tmp 7 | /out-tsc 8 | # Only exists if Bazel was run 9 | /bazel-out 10 | 11 | # Don't commit local configs 12 | *.local 13 | 14 | # dependencies 15 | /node_modules 16 | bower_components 17 | 18 | # profiling files 19 | chrome-profiler-events.json 20 | speed-measure-plugin.json 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | .history/* 38 | 39 | # misc 40 | /.sass-cache 41 | /connect.lock 42 | /coverage 43 | /libpeerconnection.log 44 | npm-debug.log 45 | yarn-error.log 46 | testem.log 47 | /typings 48 | **/*npm-debug.log.* 49 | **/*yarn-error.log.* 50 | 51 | # System Files 52 | .DS_Store 53 | Thumbs.db 54 | -------------------------------------------------------------------------------- /Icon : -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchas/Keyframes/ec3065e7e968ad7433db42cf809ea5c7ace10484/Icon -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Keyframes.app v2 2 | 3 | A collection of simple tools for frontend development. 4 | - Visual CSS @keyframe editor. 5 | - Color palette creator with gradients, conversion, contrast, and color blindness. 6 | - CSS box-shadow generator 7 | - Special character search 8 | 9 | ## Built With 10 | 11 | * [Vue](https://vuejs.org) - Framework 12 | * [{scss}](https://sass-lang.com/) - Styling 13 | * No frontend/CSS frameworks or plugins 14 | 15 | ### Third-party packages: 16 | * [DayJS](https://day.js.org/) for Dates 17 | * [Numeral.js](http://numeraljs.com/) for Number stuff 18 | * [Font Awesome](https://fontawesome.com/) icons 19 | 20 |   21 | 22 |   23 | 24 | # Project Installation & Development 25 | 26 | #### You need config values in your .env files. 27 | 28 | ## Packages 29 | ```bash 30 | npm install 31 | ``` 32 | 33 | ## NPM Commands 34 | 35 | ### **Run** 36 | #### Serve on [localhost:8080](https://localhost:8080) 37 | ```bash 38 | npm run serve 39 | ``` 40 | Bundle Analyzer will also be running at :8888 41 | 42 | ### **Build** 43 | #### Build with test configuration 44 | ```bash 45 | npm run build 46 | ``` 47 | #### Build with live configuration 48 | ```bash 49 | npm run build-prod 50 | ``` 51 | 52 | ### **Deploy** 53 | After running `npm run build` or `npm run build-prod`, the compiled project will be in the `/dist` folder. 54 | 55 | 56 | ### **Run your tests** 57 | ```bash 58 | npm run test 59 | ``` 60 | ### **Lints and fixes files** 61 | ```bash 62 | npm run lint 63 | ``` 64 |   65 | 66 |   67 | 68 | # Authors 69 | 70 | * **Mitch Samuels** - [hotdi.sh](https://hotdi.sh/) 71 | 72 | Also view the [contributors](https://github.com/mitchas/keyframes/graphs/contributors) for others who have contributed. 73 | 74 |   75 | 76 |   77 | 78 | # License 79 | This project is licensed under the GNU General Public License - see the [LICENSE.md](LICENSE.md) file for details 80 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | "@vue/app" 4 | ] 5 | }; 6 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "./src/**/*" 4 | ], 5 | "compilerOptions": { 6 | "allowJs": true 7 | } 8 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "keyframes", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve --mode test", 7 | "serve-prod": "vue-cli-service serve --mode production", 8 | "serve-build": "cd dist && serve", 9 | "lint": "vue-cli-service lint", 10 | "build": "npm run lint && vue-cli-service build --mode test", 11 | "build-prod": "npm run lint && vue-cli-service build --mode production" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.21.4", 15 | "core-js": "^2.6.12", 16 | "dayjs": "^1.10.4", 17 | "eslint-loader": "^2.1.2", 18 | "lodash": "^4.17.21", 19 | "numeral": "^2.0.6", 20 | "register-service-worker": "^1.7.2", 21 | "vue": "^2.6.12", 22 | "vue-object-merge": "^1.0.0", 23 | "vue-router": "^3.5.1", 24 | "vue2-touch-events": "^2.3.2", 25 | "vuex": "^3.6.2" 26 | }, 27 | "devDependencies": { 28 | "@vue/cli-plugin-babel": "^3.8.0", 29 | "@vue/cli-plugin-eslint": "^3.8.0", 30 | "@vue/cli-plugin-pwa": "^3.8.0", 31 | "@vue/cli-service": "^3.8.0", 32 | "babel-eslint": "^10.1.0", 33 | "eslint": "^5.16.0", 34 | "eslint-plugin-vue": "^5.2.3", 35 | "imagemin-webpack-plugin": "^2.4.2", 36 | "sass": "^1.32.8", 37 | "sass-loader": "^10.1.1", 38 | "vue-cli-plugin-html-replace": "^1.3.0", 39 | "vue-template-compiler": "^2.6.12" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | }; -------------------------------------------------------------------------------- /public/img/icons/favicon-52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchas/Keyframes/ec3065e7e968ad7433db42cf809ea5c7ace10484/public/img/icons/favicon-52.png -------------------------------------------------------------------------------- /public/img/icons/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchas/Keyframes/ec3065e7e968ad7433db42cf809ea5c7ace10484/public/img/icons/icon-192.png -------------------------------------------------------------------------------- /public/img/icons/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchas/Keyframes/ec3065e7e968ad7433db42cf809ea5c7ace10484/public/img/icons/icon-512.png -------------------------------------------------------------------------------- /public/img/meta/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchas/Keyframes/ec3065e7e968ad7433db42cf809ea5c7ace10484/public/img/meta/facebook.png -------------------------------------------------------------------------------- /public/img/meta/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchas/Keyframes/ec3065e7e968ad7433db42cf809ea5c7ace10484/public/img/meta/twitter.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Keyframes.app 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 72 | 73 | 74 | 75 | 76 | 85 | 86 | 87 |
88 | 89 | 90 |
91 |
92 | Made at Keyframes.app 93 |
94 | 95 | 96 | 97 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Keyframes.app", 3 | "short_name": "Keyframes", 4 | "start_url": "/", 5 | "display": "standalone", 6 | "background_color": "#483BF3", 7 | "theme_color": "#483BF3", 8 | "description": ".", 9 | 10 | "icons": [ 11 | { 12 | "src": "./img/icons/icon-192.png", 13 | "sizes": "192x192", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "./img/icons/icon-512.png", 18 | "sizes": "512x512", 19 | "type": "image/png" 20 | } 21 | ], 22 | 23 | "shortcuts": [ 24 | { 25 | "name": "Settings", 26 | "short_name": "Settings", 27 | "url": "/settings/", 28 | "icons": [{ 29 | "src": "./img/icons/android-chrome-192x192.png", 30 | "sizes": "192x192" 31 | }] 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 81 | 82 | 83 | 153 | 154 | 226 | -------------------------------------------------------------------------------- /src/assets/branding/logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchas/Keyframes/ec3065e7e968ad7433db42cf809ea5c7ace10484/src/assets/branding/logo-dark.png -------------------------------------------------------------------------------- /src/assets/branding/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/branding/logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchas/Keyframes/ec3065e7e968ad7433db42cf809ea5c7ace10484/src/assets/branding/logo-light.png -------------------------------------------------------------------------------- /src/assets/branding/logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/color_blind_filters.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 14 | 15 | 16 | 23 | 24 | 25 | 32 | 33 | 34 | 41 | 42 | 43 | 50 | 51 | 52 | 59 | 60 | 61 | 68 | 69 | 70 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/assets/fonts/inter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchas/Keyframes/ec3065e7e968ad7433db42cf809ea5c7ace10484/src/assets/fonts/inter.ttf -------------------------------------------------------------------------------- /src/assets/fonts/sourceCode.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchas/Keyframes/ec3065e7e968ad7433db42cf809ea5c7ace10484/src/assets/fonts/sourceCode.ttf -------------------------------------------------------------------------------- /src/assets/fonts/sourceSerif.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchas/Keyframes/ec3065e7e968ad7433db42cf809ea5c7ace10484/src/assets/fonts/sourceSerif.ttf -------------------------------------------------------------------------------- /src/assets/projects/ceev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchas/Keyframes/ec3065e7e968ad7433db42cf809ea5c7ace10484/src/assets/projects/ceev.png -------------------------------------------------------------------------------- /src/assets/projects/tidycamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchas/Keyframes/ec3065e7e968ad7433db42cf809ea5c7ace10484/src/assets/projects/tidycamp.png -------------------------------------------------------------------------------- /src/components/filters.js: -------------------------------------------------------------------------------- 1 | // Filters 2 | // Various for modifying data 3 | // 4 | // titleCase(string): 5 | // numberFormat(value, type): 6 | // 7 | 8 | import Vue from "vue"; 9 | var numeral = require("numeral"); 10 | 11 | // Title case 12 | // Capitalizes each word of string 13 | // Also replaces _underscores_ with spaces 14 | Vue.filter("titleCase", function (value) { 15 | 16 | // If value exists 17 | if(value){ 18 | var splitStr = value.replace(/_/g, " "); 19 | 20 | splitStr = splitStr.toLowerCase().split(" "); 21 | // Capitalize Every Letter 22 | for (var i = 0; i < splitStr.length; i++) { 23 | splitStr[i] = splitStr[i].charAt(0).toUpperCase() + splitStr[i].substring(1); 24 | } 25 | // Return the joined string 26 | return splitStr.join(" "); 27 | 28 | }else{ 29 | // No value received, no value returned 30 | return ""; 31 | } 32 | 33 | }); 34 | 35 | // Number Format 36 | // Formats numbers to commas, 37 | // options: comma, decimal, dollar, percent, short (ie 1k) 38 | Vue.filter("numberFormat", function (value, type) { 39 | if(type == "comma"){ 40 | return numeral(value).format("0,0"); 41 | }else if(type == "decimal"){ 42 | return numeral(value).format("0,0.00"); 43 | }else if(type == "dollar"){ 44 | return numeral(value).format("$ 0,0[.]00"); 45 | }else if(type == "percent"){ 46 | return numeral(value).format("0.000 %"); 47 | }else if(type == "short"){ // ie 1,000 = 1k 48 | return numeral(value).format("0a"); 49 | }else{ 50 | return numeral(value).format("0,0"); 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /src/components/mixins/keyboard.js: -------------------------------------------------------------------------------- 1 | // Keyboard Shortcut Mixin 2 | // from https://gist.github.com/loilo/2a1834dc20d842f63bd048ffbcf3dc19 3 | /** 4 | * @group Mixins 5 | * Mixin to use keyboard shortcuts in a view. 6 | */ 7 | 8 | // Modifier keys 9 | export const CTRL = 0b000001; 10 | export const ALT = 0b000010; 11 | export const SHIFT = 0b000100; 12 | export const META = 0b001000; 13 | // Windows key 14 | export const WIN = 0b010000; 15 | // The CMD key 16 | export const CMD = 0b100000; 17 | // Check for macOS 18 | const isMac = navigator.appVersion.includes("Macintosh"); 19 | // Determine the primary modifier key 20 | export const PRIMARY = isMac ? CMD : CTRL; 21 | 22 | // /** 23 | // * Create a mixin for simple keyboard shortcuts 24 | // * 25 | // * @param {string|string[]} matcher The key name(s) to react to 26 | // * @param {number} modifierKeys A bitmask of modifier keys 27 | // * @returns {object} 28 | // */ 29 | 30 | 31 | export default function runShortcut(matcher, ...args) { 32 | // If only one remaining argument, treat it as callback 33 | if (args.length === 1) { 34 | return runShortcut(matcher, 0b0000, args[0]); 35 | } 36 | 37 | // The key the listener function will be stored at 38 | const LISTENER = Symbol("keydown listener"); 39 | 40 | let [modifierKeys, callback] = args; 41 | 42 | // Check modifier keys for WIN or CMD 43 | let excludedByOS = false; 44 | if (modifierKeys & (WIN | CMD)) { 45 | // Add META to modifier keys if OS matches 46 | if (modifierKeys & (isMac ? CMD : WIN)) { 47 | modifierKeys = modifierKeys | META; 48 | } else { 49 | excludedByOS = true; 50 | } 51 | } 52 | 53 | // Normalize matcher towards a function 54 | if (typeof matcher === "string") { 55 | matcher = [matcher]; 56 | } 57 | if (Array.isArray(matcher)) { 58 | const lowerKeys = matcher.map(key => key.toLowerCase()); 59 | matcher = event => lowerKeys.includes(event.key.toLowerCase()); 60 | } 61 | 62 | return { 63 | mounted() { 64 | this[LISTENER] = event => { 65 | if ( 66 | // Check for exclusion by OS 67 | !excludedByOS && 68 | // No explicitely focused element 69 | // document.activeElement === document.body && 70 | // Control key matches requirement 71 | event.ctrlKey === Boolean(modifierKeys & CTRL) && 72 | // Alt key matches requirement 73 | event.altKey === Boolean(modifierKeys & ALT) && 74 | // Shift key matches requirement 75 | event.shiftKey === Boolean(modifierKeys & SHIFT) && 76 | // Meta key matches requirement 77 | event.metaKey === Boolean(modifierKeys & META) && 78 | // Key name is the requested one 79 | matcher(event) 80 | ) { 81 | callback.call(this, event); 82 | } 83 | }; 84 | 85 | document.addEventListener("keydown", this[LISTENER]); 86 | }, 87 | destroyed() { 88 | document.removeEventListener("keydown", this[LISTENER]); 89 | } 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /src/components/mixins/screenResizeMixin.js: -------------------------------------------------------------------------------- 1 | //! Screen Resize Mixin 2 | // 3 | // mounted(): Checks window for orientation property to determine if it's a mobile device. 4 | // orientation is *usually* undefined on desktop, so if it's defined, it's mobile 5 | // - if it's a mobile device, it adds an event listener to watch for screen resizing. 6 | // - Calls watchSoftKeyboard() on resize. 7 | // 8 | // watchResize(): 9 | // This function has two parts 10 | // 11 | // - First, checks width on resize to see if it's mobile (less than 768px) 12 | // 13 | // - Then checks height change for mobile devices 14 | // - Calculates the percentage change in height using the original size and the end size, 15 | // - if the difference is greater than 20%, it's likely because an on screen keyboard is showing 16 | // - Commits to the store softKeyboard true/false 17 | // 18 | // beforeDestroy() 19 | // - removes event listener on screen size and scroll 20 | // 21 | 22 | /** 23 | * @group Mixins 24 | * Watches for screen resize and updates store. Also calculates change to determine if mobile keyboard is visible. 25 | */ 26 | 27 | export default { 28 | data() { 29 | return { 30 | windowHeightOnLoad: window.innerHeight, 31 | windowWidthOnLoad: window.innerHeight, 32 | resizeTimer: null, 33 | }; 34 | }, 35 | watch: { 36 | }, 37 | mounted() { 38 | window.addEventListener("resize", this.watchResize); 39 | this.watchResize(); 40 | }, 41 | beforeDestroy() { 42 | // Remove resize listener 43 | window.removeEventListener("resize", this.watchResize); 44 | }, 45 | methods: { 46 | 47 | /** 48 | * @vuese 49 | * Watch screen for resize - If height is 20% shorter than original height, keyboard is visible 50 | * @arg The string to check 51 | */ 52 | watchResize: function() { 53 | var _this = this; 54 | 55 | // Clear timeout so it only runs after 300ms delay 56 | clearTimeout(this.resizeTimer); 57 | 58 | // Timer to prevent many firings 59 | this.resizeTimer = setTimeout(function() { 60 | 61 | var newScreenWidth = window.innerWidth; 62 | 63 | // Check mobile (768px or narrower) 64 | if(newScreenWidth <= 768){ 65 | // Is mobile 66 | _this.$store.dispatch("Device/SCREEN_SIZE_CHANGE", true); 67 | 68 | // Now check if keyboard is visible 69 | let softKeyboardOn = _this.$store.getters["Device/softKeyboardVisible"]; 70 | 71 | var newScreenHeight = window.innerHeight; 72 | var originalHeight = _this.windowHeightOnLoad; 73 | // Calculate percentage changed 74 | var changePercent = parseInt(100 - ((newScreenHeight / originalHeight) * 100)); 75 | // If percent greater than 20, and keyboard isn't currently open 76 | if(changePercent > 20 && !softKeyboardOn){ 77 | softKeyboardOn = true; 78 | // Only commit to store if change occurs 79 | _this.$store.dispatch("Device/SOFT_KEYBOARD_CHANGE", true); 80 | }else if(softKeyboardOn && changePercent < 20){ 81 | // If keyboard is open, hide it 82 | softKeyboardOn = false; 83 | _this.$store.dispatch("Device/SOFT_KEYBOARD_CHANGE", false); 84 | } 85 | }else{ 86 | _this.$store.dispatch("Device/SOFT_KEYBOARD_CHANGE", false); 87 | _this.$store.dispatch("Device/SCREEN_SIZE_CHANGE", false); 88 | } 89 | 90 | 91 | }, 50); 92 | 93 | }, 94 | 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /src/components/plugins/Dayjs.js: -------------------------------------------------------------------------------- 1 | // Register Dayjs plugin with Vue 2 | 3 | import Vue from "vue"; 4 | import dayjs from "dayjs"; 5 | import relativeTime from "dayjs/plugin/relativeTime"; 6 | import isSameOrAfter from "dayjs/plugin/isSameOrAfter"; 7 | 8 | dayjs.extend(isSameOrAfter); 9 | dayjs.extend(relativeTime); 10 | 11 | Object.defineProperties(Vue.prototype, { 12 | $date: { 13 | get() { 14 | return dayjs; 15 | } 16 | } 17 | }); -------------------------------------------------------------------------------- /src/components/plugins/logging.js: -------------------------------------------------------------------------------- 1 | // Logging, 2 | // Pretty replacement for default logging 3 | 4 | export default { 5 | 6 | install(Vue, options){ 7 | 8 | 9 | Vue.prototype.$ope = function(message) { 10 | 11 | return { 12 | data: function(message){ 13 | console.log(message); 14 | }, 15 | table: function(message){ 16 | console.table(message); 17 | }, 18 | log: function(message){ 19 | console.log( 20 | "%c%s%c%s", 21 | "color: #00172E; border: 3px solid #00172E; font-size: 14px; padding: 12px 10px; font-weight:bold;", 22 | "👀 log: 👀", 23 | "background-color: #F1F5FA; color: black; padding: 12px 10px; font-weight:bold; text-align:center; margin: 0 5px; border-radius: 6px;", 24 | message, 25 | ); 26 | }, 27 | info: function (message) { 28 | console.info( 29 | "%c%s%c%s", 30 | "color: #0B02CE; border: 3px solid #0B02CE; font-size: 14px; padding: 12px 10px; font-weight:bold;", 31 | "👋 info: 👋", 32 | "background-color: #E2EDFF; color: #0B02CE; padding: 12px 10px; font-weight:bold; text-align:center; margin: 0 5px; border-radius: 6px;", 33 | message, 34 | ); 35 | }, 36 | success: function (message) { 37 | console.info( 38 | "%c%s%c%s", 39 | "color: #00C299; border: 3px solid #00C299; font-size: 14px; padding: 12px 10px; font-weight:bold;", 40 | "🥳 success: 🎉", 41 | "background-color: #E9FFF1; color: #00C299; padding: 12px 10px; font-weight:bold; text-align:center; margin: 0 5px; border-radius: 6px;", 42 | message, 43 | ); 44 | }, 45 | warn: function (message) { 46 | console.warn( 47 | "%c%s%c%s", 48 | "color: #f0c83e; border: 3px solid #f0c83e; font-size: 14px; padding: 12px 10px; font-weight:bold;", 49 | "📣 warn: 📣", 50 | "padding: 12px 10px; color: #f0c83e;", 51 | message, 52 | ); 53 | }, 54 | error: function (message) { 55 | // Error specific with message, code, 56 | if(message.code){ 57 | console.error( 58 | "%c%s%c%s%c%s", 59 | "color: #D93D49; border: 3px solid #D93D49; font-size: 14px; padding: 12px 10px; font-weight:bold;", 60 | "🚨 error: 🚨", 61 | "color: #D93D49; border: 3px solid #D93D49; font-size: 12px; margin: 0 5px; padding: 12px 10px; font-weight:bold;", 62 | message.code, 63 | "padding: 12px 10px; color: #D93D49;", 64 | message.message, 65 | ); 66 | }else{ 67 | // Regular error log 68 | console.error( 69 | "%c%s%c%s", 70 | "color: #D93D49; border: 3px solid #D93D49; font-size: 14px; padding: 12px 10px; font-weight:bold;", 71 | "🚨 error: 🚨", 72 | "padding: 12px 10px; color: #D93D49;", 73 | message, 74 | ); 75 | } 76 | 77 | } 78 | }; 79 | 80 | }; 81 | } 82 | 83 | }; -------------------------------------------------------------------------------- /src/components/plugins/plugins.js: -------------------------------------------------------------------------------- 1 | //! Plugins 2 | // Global functions to use in any mixin or component 3 | 4 | import { _ } from "core-js"; 5 | 6 | export default { 7 | 8 | install(Vue, options){ 9 | 10 | // Toast & Hello 11 | // Sends notifications to root component to display 12 | Vue.prototype.toast = function(title, body, color, icon, path, info) { 13 | this.$root.$children[0].$refs.toastComponent.showToast(title, body, color, icon, path, info); 14 | if(window.navigator.vibrate){ 15 | navigator.vibrate(15); 16 | } 17 | }; 18 | Vue.prototype.hello = function(message, icon, color) { 19 | this.$root.$children[0].$refs.alertComponent.showAlert(message, icon, color); 20 | if(window.navigator.vibrate ){ 21 | navigator.vibrate(15); 22 | } 23 | }; 24 | // Random Greething 25 | Vue.prototype.greet = function() { 26 | var messages = ["Howdy", "Hello!", "No.", "Boop.", "What's up?", "Why'd you do that?", "*click*", "push it again."]; 27 | var icons = ["fas fa-hand", "fas fa-alien", "fas fa-fire", "fas fa-poop", "fas fa-worm", "fas fa-cat-space"]; 28 | var colors = ["green", "blue", "yellow", "grey", "orange", "purple", "pink"]; 29 | 30 | var m = messages[Math.floor(Math.random() * messages.length)]; 31 | var i = icons[Math.floor(Math.random() * icons.length)]; 32 | var c = colors[Math.floor(Math.random() * colors.length)]; 33 | 34 | this.$root.$children[0].$refs.alertComponent.showAlert(m, i, c); 35 | if(window.navigator.vibrate ){ 36 | navigator.vibrate(15); 37 | } 38 | }; 39 | 40 | // Scroll up 41 | // Simply scrolls to top of page 42 | Vue.prototype.scrollUp = function() { 43 | document.getElementById("mainScrollView").scrollTo({ 44 | top: 0, 45 | left: 0, 46 | behavior: "smooth" 47 | }); 48 | }; 49 | 50 | // Replace string with only letters and numbers 51 | Vue.prototype.lettersAndNumbers = function(string) { 52 | return string.toString().replace(/[^a-z0-9 ]/gi,""); 53 | }; 54 | // Replace underscore with space 55 | Vue.prototype.spaceUnderscore = function(string) { 56 | return string.toString().replace(/ /g,"_"); 57 | }; 58 | Vue.prototype.underscoreSpace = function(string) { 59 | return string.toString().replace(/_/g," "); 60 | }; 61 | 62 | // Navigate 63 | // Function to navigate with -> navigate("/route") 64 | // then use @click to navigate() 65 | // Checks if Confirm Leave is enabled 66 | Vue.prototype.navigate = function(route, confirm) { 67 | 68 | // If same route, don't navigate 69 | if(route == this.$route.path){ 70 | return; 71 | }else{ 72 | 73 | var confirmLeave = this.$store.getters["Hold/showConfirmLeave"]; 74 | 75 | // If confirmLeave is true 76 | if(confirmLeave){ 77 | // Show confirmation 78 | this.$root.$children[0].$refs.confirmLeaveComponent.confirmLeave(route); 79 | }else{ 80 | // Else no confirmation needed 81 | this.$router.push(route); 82 | // document.documentElement.scrollTop = 0; 83 | } 84 | } 85 | }; 86 | 87 | // Open url in new tab 88 | Vue.prototype.tab = function(url) { 89 | window.open(url, "_blank"); 90 | }; 91 | 92 | 93 | // Native Sharing 94 | // Falls back to copy to clipboard 95 | Vue.prototype.share = function(toShare, kind ) { 96 | let _this = this; 97 | 98 | if (navigator.share) { 99 | if(kind.toLowerCase() == "link"){ 100 | navigator.share({url: toShare}).catch((error) => _this.$ope().error("Error sharing", error)); 101 | }else{ 102 | navigator.share({text: toShare}).catch((error) => _this.$ope().error("Error sharing", error)); 103 | } 104 | }else{ 105 | this.copyToClipboard(kind, toShare); 106 | } 107 | }; 108 | 109 | // Copy value to device clipboard 110 | Vue.prototype.copyToClipboard = function(name, value) { 111 | // Create input element, append text, copy text, remove element 112 | if(!value){ 113 | value = name; 114 | } 115 | var inp =document.createElement("input"); 116 | document.body.appendChild(inp); 117 | inp.value = value; 118 | inp.select(); 119 | document.execCommand("copy",false); 120 | inp.remove(); 121 | // Toast 122 | if(name){ 123 | this.hello(name + " copied to clipboard. ", "far fa-copy", "blue"); 124 | } 125 | }; 126 | 127 | 128 | // Get Geolocation 129 | Vue.prototype.getGeolocation = function(noAlerts) { 130 | let _this = this; 131 | 132 | return new Promise((resolve, reject) => { 133 | 134 | var geoloc = null; 135 | 136 | var showAlert = true; 137 | if(noAlerts && noAlerts == true){ 138 | showAlert = false; 139 | } 140 | 141 | if(showAlert){ 142 | _this.hello("Getting Location...", "far fa-location"); 143 | } 144 | 145 | navigator.geolocation.getCurrentPosition( 146 | function(position) { 147 | geoloc = { 148 | lat: position.coords.latitude, 149 | lon: position.coords.longitude, 150 | updated: new Date(), 151 | }; 152 | _this.$store.commit("Hold/UPDATE_GEOLOCATION", geoloc); 153 | resolve(geoloc); 154 | if(showAlert){ 155 | _this.hello("Geolocation Updated", "far fa-map-marker-smile", "green"); 156 | } 157 | }, 158 | function(error){ 159 | _this.$ope().error(error.message); 160 | if(showAlert){ 161 | if(error.code == 3){ 162 | _this.hello("Location Timed Out", "far fa-location-slash", "red"); 163 | }else{ 164 | _this.hello("Unable to access your location", "far fa-location-slash", "red"); 165 | } 166 | } 167 | reject(error); 168 | }, { 169 | enableHighAccuracy: true, 170 | timeout : 5000 171 | } 172 | ); 173 | 174 | }); 175 | }; 176 | 177 | } 178 | 179 | }; -------------------------------------------------------------------------------- /src/components/ui/Common/Alert.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 76 | 77 | -------------------------------------------------------------------------------- /src/components/ui/Common/Callout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 61 | 62 | -------------------------------------------------------------------------------- /src/components/ui/Common/Toast.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 40 | 41 | 111 | 112 | -------------------------------------------------------------------------------- /src/components/ui/Common/Tooltip.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 27 | 28 | 29 | 30 | 90 | 91 | -------------------------------------------------------------------------------- /src/components/ui/Modals/Confirm.vue: -------------------------------------------------------------------------------- 1 | 6 | 50 | 51 | 52 | 53 | 105 | 106 | -------------------------------------------------------------------------------- /src/components/ui/Modals/ConfirmLeave.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 50 | 51 | 88 | 89 | -------------------------------------------------------------------------------- /src/components/ui/Modals/Modal.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 59 | 60 | 124 | 125 | -------------------------------------------------------------------------------- /src/components/ui/Single/Launcher.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 37 | 38 | 83 | 84 | -------------------------------------------------------------------------------- /src/components/ui/Single/NavBar.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 102 | 103 | 147 | 148 | -------------------------------------------------------------------------------- /src/components/ui/Single/ThemeEditor.vue: -------------------------------------------------------------------------------- 1 | 4 | 78 | 79 | 167 | 168 | -------------------------------------------------------------------------------- /src/definitions/changelog.js: -------------------------------------------------------------------------------- 1 | // Define changelog changes 2 | 3 | module.exports = { 4 | changes: [ 5 | { 6 | date: "August 25th, 2022", 7 | title: "Initial Deployment", 8 | description: "Deploying v2 for the first time.", 9 | icon: "fas fa-check-double", 10 | color: "red", 11 | tag: "", 12 | items: [ 13 | "Entirely new site.", 14 | "No changes, it's all new." 15 | ] 16 | }, 17 | ] 18 | }; -------------------------------------------------------------------------------- /src/definitions/characters.js: -------------------------------------------------------------------------------- 1 | // Define changelog changes 2 | 3 | module.exports = { 4 | all: [ 5 | { 6 | name: "exclamation point", 7 | css: "21", 8 | html: "33", 9 | character: "!", 10 | }, 11 | { 12 | name: "double quotes", 13 | css: "22", 14 | html: "34", 15 | htmlName: "quot", 16 | character: "\"", 17 | }, 18 | { 19 | name: "number sign", 20 | css: "23", 21 | html: "35", 22 | character: "#", 23 | }, 24 | { 25 | name: "dollar sign", 26 | css: "24", 27 | html: "36", 28 | character: "$", 29 | }, 30 | { 31 | name: "percent sign", 32 | css: "25", 33 | html: "37", 34 | character: "%", 35 | }, 36 | { 37 | name: "ampersand", 38 | css: "26", 39 | html: "38", 40 | htmlName: "amp", 41 | character: "&", 42 | }, 43 | { 44 | name: "single quote", 45 | css: "27", 46 | html: "39", 47 | character: "'", 48 | }, 49 | { 50 | name: "opening parenthesis", 51 | css: "28", 52 | html: "40", 53 | character: "(", 54 | }, 55 | { 56 | name: "closing parenthesis", 57 | css: "29", 58 | html: "41", 59 | character: ")", 60 | }, 61 | { 62 | name: "asterisk", 63 | css: "2A", 64 | html: "42", 65 | character: "*", 66 | }, 67 | { 68 | name: "plus sign", 69 | css: "2B", 70 | html: "43", 71 | character: "+", 72 | }, 73 | { 74 | name: "comma", 75 | css: "2C", 76 | html: "44", 77 | character: ",", 78 | }, 79 | { 80 | name: "minus / hyphen", 81 | css: "2D", 82 | html: "45", 83 | character: "-", 84 | }, 85 | { 86 | name: "period", 87 | css: "2E", 88 | html: "46", 89 | character: ".", 90 | }, 91 | { 92 | name: "slash", 93 | css: "2F", 94 | html: "47", 95 | character: "/", 96 | }, 97 | { 98 | name: "colon", 99 | css: "3A", 100 | html: "58", 101 | character: ":", 102 | }, 103 | { 104 | name: "semicolon", 105 | css: "3B", 106 | html: "59", 107 | character: ";", 108 | }, 109 | { 110 | name: "less than sign", 111 | css: "3C", 112 | html: "60", 113 | htmlName: "lt", 114 | character: "<", 115 | }, 116 | { 117 | name: "equal sign", 118 | css: "3D", 119 | html: "61", 120 | character: "=", 121 | }, 122 | { 123 | name: "greater than sign", 124 | css: "3E", 125 | html: "62", 126 | htmlName: "gt", 127 | character: ">", 128 | }, 129 | { 130 | name: "question mark", 131 | css: "3F", 132 | html: "63", 133 | character: "?", 134 | }, 135 | { 136 | name: "at symbol", 137 | css: "40", 138 | html: "64", 139 | character: "@", 140 | }, 141 | { 142 | name: "opening bracket", 143 | css: "5B", 144 | html: "91", 145 | character: "[", 146 | }, 147 | { 148 | name: "backslash", 149 | css: "5C", 150 | html: "92", 151 | character: "\\", 152 | }, 153 | { 154 | name: "closing bracket", 155 | css: "5D", 156 | html: "93", 157 | character: "]", 158 | }, 159 | { 160 | name: "caret - circumflex", 161 | css: "5E", 162 | html: "94", 163 | character: "^", 164 | }, 165 | { 166 | name: "underscore", 167 | css: "5F", 168 | html: "95", 169 | character: "_", 170 | }, 171 | { 172 | name: "grave accent", 173 | css: "60", 174 | html: "96", 175 | character: "`", 176 | }, 177 | { 178 | name: "opening brace", 179 | css: "7B", 180 | html: "123", 181 | character: "{", 182 | }, 183 | { 184 | name: "vertical bar", 185 | css: "7C", 186 | html: "124", 187 | character: "|", 188 | }, 189 | { 190 | name: "closing brace", 191 | css: "7D", 192 | html: "125", 193 | character: "}", 194 | }, 195 | { 196 | name: "equivalency / tilde", 197 | css: "7E", 198 | html: "126", 199 | character: "~", 200 | }, 201 | { 202 | name: "non-breaking space", 203 | css: "A0", 204 | html: "160", 205 | htmlName: "nbsp", 206 | character: " ", 207 | }, 208 | { 209 | name: "inverted exclamation", 210 | css: "A1", 211 | html: "161", 212 | htmlName: "iexcl", 213 | character: "¡", 214 | }, 215 | { 216 | name: "cent", 217 | css: "A2", 218 | html: "162", 219 | htmlName: "cent", 220 | character: "¢", 221 | }, 222 | { 223 | name: "pound", 224 | css: "A3", 225 | html: "163", 226 | htmlName: "pound", 227 | character: "£", 228 | }, 229 | { 230 | name: "currency", 231 | css: "A4", 232 | html: "164", 233 | htmlName: "curren", 234 | character: "¤", 235 | }, 236 | { 237 | name: "yen", 238 | css: "A5", 239 | html: "165", 240 | htmlName: "yen", 241 | character: "¥", 242 | }, 243 | { 244 | name: "broken vertical bar", 245 | css: "A6", 246 | html: "166", 247 | htmlName: "brvbar", 248 | character: "¦", 249 | }, 250 | { 251 | name: "section", 252 | css: "A7", 253 | html: "167", 254 | htmlName: "sect", 255 | character: "§", 256 | }, 257 | { 258 | name: "spacing diaeresis - umlaut", 259 | css: "A8", 260 | html: "168", 261 | htmlName: "uml", 262 | character: "¨", 263 | }, 264 | { 265 | name: "copyright", 266 | css: "A9", 267 | html: "169", 268 | htmlName: "copy", 269 | character: "©", 270 | }, 271 | { 272 | name: "feminine ordinal", 273 | css: "AA", 274 | html: "170", 275 | htmlName: "ordf", 276 | character: "ª", 277 | }, 278 | { 279 | name: "masculine ordinal", 280 | css: "BA", 281 | html: "186", 282 | htmlName: "ordm", 283 | character: "º", 284 | }, 285 | { 286 | name: "left double angle", 287 | css: "AB", 288 | html: "171", 289 | htmlName: "laquo", 290 | character: "«", 291 | }, 292 | { 293 | name: "not sign", 294 | css: "AC", 295 | html: "172", 296 | htmlName: "not", 297 | character: "¬", 298 | }, 299 | { 300 | name: "registered trade mark", 301 | css: "AE", 302 | html: "174", 303 | htmlName: "reg", 304 | character: "®", 305 | }, 306 | { 307 | name: "macron - overline", 308 | css: "AF", 309 | html: "175", 310 | htmlName: "macr", 311 | character: "¯", 312 | }, 313 | { 314 | name: "degree", 315 | css: "B0", 316 | html: "176", 317 | htmlName: "deg", 318 | character: "°", 319 | }, 320 | { 321 | name: "plus-or-minus", 322 | css: "B1", 323 | html: "177", 324 | htmlName: "plusmn", 325 | character: "±", 326 | }, 327 | { 328 | name: "superscript - one", 329 | css: "B9", 330 | html: "185", 331 | htmlName: "sup1", 332 | character: "¹", 333 | }, 334 | { 335 | name: "superscript - squared", 336 | css: "B2", 337 | html: "178", 338 | htmlName: "sup2", 339 | character: "²", 340 | }, 341 | { 342 | name: "superscript - cubed", 343 | css: "B3", 344 | html: "179", 345 | htmlName: "sup3", 346 | character: "³", 347 | }, 348 | { 349 | name: "micro", 350 | css: "B5", 351 | html: "181", 352 | htmlName: "micro", 353 | character: "µ", 354 | }, 355 | { 356 | name: "pilcrow - paragraph", 357 | css: "B6", 358 | html: "182", 359 | htmlName: "para", 360 | character: "¶", 361 | }, 362 | { 363 | name: "middle dot", 364 | css: "B7", 365 | html: "183", 366 | htmlName: "middot", 367 | character: "·", 368 | }, 369 | { 370 | name: "per thousand sign", 371 | css: "2030", 372 | html: "8240", 373 | character: "‰", 374 | }, 375 | { 376 | name: "fraction one quarter", 377 | css: "BC", 378 | html: "188", 379 | htmlName: "frac14", 380 | character: "¼", 381 | }, 382 | { 383 | name: "fraction one half", 384 | css: "BD", 385 | html: "189", 386 | htmlName: "frac12", 387 | character: "½", 388 | }, 389 | { 390 | name: "fraction three quarters", 391 | css: "BE", 392 | html: "190", 393 | htmlName: "frac34", 394 | character: "¾", 395 | }, 396 | { 397 | name: "inverted question", 398 | css: "BF", 399 | html: "191", 400 | htmlName: "iquest", 401 | character: "¿ ", 402 | }, 403 | { 404 | name: "multiplication", 405 | css: "D7", 406 | html: "215", 407 | htmlName: "times", 408 | character: "×", 409 | }, 410 | { 411 | name: "en dash", 412 | css: "2013", 413 | html: "8211", 414 | character: "–", 415 | }, 416 | { 417 | name: "em dash", 418 | css: "2014", 419 | html: "8212", 420 | character: "—", 421 | }, 422 | { 423 | name: "dagger", 424 | css: " 2020", 425 | html: "8224", 426 | character: "†", 427 | }, 428 | { 429 | name: "double dagger", 430 | css: "2021", 431 | html: "8225", 432 | character: "‡", 433 | }, 434 | { 435 | name: "bullet", 436 | css: "2022", 437 | html: "8226", 438 | character: "•", 439 | }, 440 | { 441 | name: "horizontal ellipsis", 442 | css: "2026", 443 | html: "8230", 444 | character: "…", 445 | }, 446 | { 447 | name: "euro sign", 448 | css: "20AC", 449 | html: "8364", 450 | htmlName: "euro", 451 | character: "€", 452 | }, 453 | { 454 | name: "trade mark", 455 | css: "2122", 456 | html: "8482", 457 | character: "™", 458 | }, 459 | // { 460 | // name: "xxxxxx", 461 | // css: "xxxxxx", 462 | // html: "xxxxxx", 463 | // htmlName: "xxxxxx", 464 | // character: "xxxxxx", 465 | // }, 466 | ] 467 | }; -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | //! Main 2 | // 3 | //* Sets up vue app 4 | // - Sets up app, imports, filters, 5 | // - Waits for firebase to return user or not before page load 6 | // 7 | 8 | // Imports 9 | import Vue from "vue"; 10 | import App from "./App.vue"; 11 | import router from "./router"; 12 | import "./registerServiceWorker"; 13 | import store from "@/store/store"; 14 | 15 | // Import Plugins 16 | import Vue2TouchEvents from "vue2-touch-events"; 17 | 18 | 19 | // Custom Plugins 20 | import plugins from "@/components/plugins/plugins.js"; 21 | import "@/components/plugins/Dayjs"; 22 | import logging from "@/components/plugins/logging.js"; 23 | 24 | // Set configs 25 | Vue.config.productionTip = false; 26 | 27 | // Use 28 | Vue.use(logging); 29 | Vue.use(plugins); 30 | Vue.use(Vue2TouchEvents); 31 | 32 | // Main styles 33 | import "./styles/main.scss"; 34 | 35 | 36 | 37 | // App 38 | let app = ""; 39 | 40 | 41 | // Conditional Style component 42 | Vue.component("v-style", { 43 | render: function (createElement) { 44 | return createElement("style", this.$slots.default); 45 | } 46 | }); 47 | 48 | 49 | // Wait for firebase to determine if user is logged in before creating app 50 | 51 | // Create vue app - call after processing user 52 | var createApp = function(){ 53 | if (!app) { 54 | app = new Vue({ 55 | router, 56 | store, 57 | data: { 58 | }, 59 | render: h => h(App) 60 | }).$mount("#app"); 61 | 62 | } 63 | }; 64 | 65 | // Anonymous - no user auth 66 | store.dispatch("User/INITIAL_LOAD", false).then(function() { 67 | // Set user auth, page ready to load 68 | store.commit("User/FINISH_INITIAL_LOAD", false); 69 | // Success 70 | createApp(); 71 | }); 72 | 73 | 74 | // Get device details, save in store for later 75 | // Touch Screen 76 | if(("ontouchstart" in window) || (navigator.MaxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) { 77 | store.commit("Device/HAS_TOUCH", true); 78 | } 79 | // PWA / Standalone app display 80 | if (window.matchMedia("(display-mode: standalone)").matches) { 81 | store.commit("Device/SET_PWA", true); 82 | } 83 | 84 | // Check device permission status 85 | store.dispatch("Device/CHECK_PERMISSIONS", true); 86 | 87 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // 2 | // registers /public/service-worker.js if production 3 | // 4 | // Reloads if new content found. 5 | // 6 | 7 | import { register } from "register-service-worker"; 8 | 9 | if (process.env.NODE_ENV === "production") { 10 | navigator.serviceWorker.getRegistrations().then( function(registrations) { for(let registration of registrations) { registration.unregister(); } }); 11 | 12 | // register(`${process.env.BASE_URL}service-worker.js`, { 13 | // ready () { 14 | // console.log("Site is ready"); 15 | // }, 16 | // cached () { 17 | // console.log("Content has been cached for offline use."); 18 | // }, 19 | // updatefound () { 20 | // console.log("New content is downloading."); 21 | // }, 22 | // updated () { 23 | // console.log("New content is available; Refresh..."); 24 | // setTimeout(() => { 25 | // window.location.reload(true); 26 | // }, 1000); 27 | // }, 28 | // offline () { 29 | // console.log("No internet connection found. App is running in offline mode."); 30 | // }, 31 | // error (error) { 32 | // console.error("Error during service worker registration:", error); 33 | // }, 34 | // }); 35 | }else{ 36 | 37 | // Never cache - and delete old caches in non-prod 38 | // navigator.serviceWorker.getRegistrations().then( 39 | // activate 40 | // function(registrations) { 41 | 42 | // for(let registration of registrations) { 43 | // registration.unregister(); 44 | // } 45 | 46 | // } 47 | // ); 48 | } -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | // ! Router 2 | // Defines available routes and views 3 | // 4 | // * Route Meta 5 | // - title - Page title set on route change 6 | // - routeRequiresUser - user must be signed in to view 7 | // - routeBlocksUser - user must be signed out to view 8 | // - routeRequiresAdmin - different admin level numbers block different pages 9 | // for example, to view the admin page, a user's admin level must be 1 or greater 10 | // 11 | 12 | 13 | // Imports 14 | import Vue from "vue"; 15 | import Router from "vue-router"; 16 | import store from "@/store/store"; 17 | import { routes } from "./routes.js"; 18 | 19 | 20 | 21 | Vue.use(Router); 22 | 23 | 24 | const router = new Router({ 25 | mode: "history", 26 | linkExactActiveClass: "is-active", 27 | base: process.env.VUE_APP_BASE_PATH, 28 | // Return to top after route change 29 | scrollBehavior(to, from, savedPostition) { 30 | if (to.hash) { 31 | return { 32 | selector: to.hash, 33 | // behavior: 'smooth', 34 | }; 35 | }else{ 36 | return { x: 0, y: 0, }; 37 | } 38 | }, 39 | routes 40 | }); 41 | 42 | 43 | 44 | router.afterEach((to, from) => { 45 | // Scroll Up 46 | document.getElementById("mainScrollView").scrollTo({ 47 | top: 0, 48 | left: 0, 49 | behavior: "smooth" 50 | }); 51 | }); 52 | 53 | router.beforeEach((to, from, next) => { 54 | 55 | // Signed in (or not) user 56 | // Meta definitions to check 57 | // const routeRequiresUser = to.matched.some(record => record.meta.routeRequiresUser); 58 | // Path 59 | const toPath = to.path; 60 | const fromPath = from.path; 61 | next(); 62 | 63 | 64 | }); 65 | 66 | 67 | export default router; 68 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | // routes.js 2 | // Defined routes for Vue router to use 3 | let AppName = process.env.VUE_APP_COMPANY_NAME; 4 | 5 | 6 | export const routes = [ 7 | // * Default Page 8 | { 9 | path: "/", 10 | name: "home", 11 | component: () => import(/* webpackChunkName: "home" */ "./views/Home.vue"), 12 | meta: { 13 | title: AppName + " — Home", 14 | } 15 | }, 16 | // * Settings Page 17 | { 18 | path: "/settings", 19 | name: "settings", 20 | component: () => import(/* webpackChunkName: "settings" */ "./views/Settings.vue"), 21 | meta: { 22 | title: AppName + " — Settings", 23 | } 24 | }, 25 | // Apps 26 | // Animate 27 | { 28 | path: "/animate", 29 | name: "animations", 30 | component: () => import(/* webpackChunkName: "animations" */ "./views/apps/Animations.vue"), 31 | meta: { 32 | title: "Animations — " + AppName, 33 | } 34 | }, 35 | // Colors 36 | { 37 | path: "/colors/:urlColors?", 38 | name: "colors", 39 | component: () => import(/* webpackChunkName: "colors" */ "./views/apps/Colors.vue"), 40 | meta: { 41 | title: "Colors — " + AppName, 42 | } 43 | }, 44 | // Shadows 45 | { 46 | path: "/shadows/", 47 | name: "shadows", 48 | component: () => import(/* webpackChunkName: "shadows" */ "./views/apps/Shadows.vue"), 49 | meta: { 50 | title: "Shadows — " + AppName, 51 | } 52 | }, 53 | // Characters 54 | { 55 | path: "/characters", 56 | name: "characters", 57 | component: () => import(/* webpackChunkName: "colors" */ "./views/apps/Characters.vue"), 58 | meta: { 59 | title: "Colors — " + AppName, 60 | } 61 | }, 62 | // Template 63 | { 64 | path: "/template/", 65 | name: "template", 66 | component: () => import(/* webpackChunkName: "colors" */ "./views/apps/AppTemplate.vue"), 67 | meta: { 68 | title: "Template — " + AppName, 69 | } 70 | }, 71 | 72 | // 73 | // ! Static Docs / Technical 74 | // 75 | // 76 | //* Sponsor 77 | { 78 | path: "/sponsor", 79 | name: "sponsor", 80 | component: () => import(/* webpackChunkName: "sponsor" */ "./views/other/docs/Sponsor.vue"), 81 | meta: { 82 | title: "Sponsor — " + AppName, 83 | } 84 | }, 85 | //* Changelog 86 | { 87 | path: "/changelog", 88 | name: "changelog", 89 | component: () => import(/* webpackChunkName: "changelog" */ "./views/other/docs/Changelog.vue"), 90 | meta: { 91 | title: "Changelog — " + AppName, 92 | } 93 | }, 94 | //* TOS 95 | { 96 | path: "/terms", 97 | name: "terms-of-service", 98 | component: () => import(/* webpackChunkName: "terms-of-service" */ "./views/other/docs/TermsOfService.vue"), 99 | meta: { 100 | title: "Terms of Service — " + AppName, 101 | } 102 | }, 103 | //* Privacy Policy 104 | { 105 | path: "/privacy", 106 | name: "privacy-policy", 107 | component: () => import(/* webpackChunkName: "privacy-policy" */ "./views/other/docs/PrivacyPolicy.vue"), 108 | meta: { 109 | title: "Privacy Policy — " + AppName, 110 | } 111 | }, 112 | //* Design 113 | // Example page for seeing UI Elements 114 | { 115 | path: "/design", 116 | name: "design", 117 | component: () => import(/* webpackChunkName: "design" */ "./views/other/docs/design/Design.vue"), 118 | meta: { 119 | title: "Design — " + AppName, 120 | } 121 | }, 122 | // 123 | // ! Other Pages 124 | // 125 | // * 404 Page 126 | { 127 | path: "/404/", 128 | name: "error-404", 129 | component: () => import(/* webpackChunkName: "error-404" */ "./views/other/error.vue"), 130 | meta: { 131 | title: "404 - Page not Found — " + AppName, 132 | } 133 | }, 134 | // 135 | // ! Redirect to 404 if route does not exist 136 | { 137 | path: "*", 138 | redirect: "/404/" 139 | } 140 | ]; -------------------------------------------------------------------------------- /src/service-worker.js: -------------------------------------------------------------------------------- 1 | // service-worker.js 2 | 3 | // workbox.core.setCacheNameDetails({ prefix: "d4" }); 4 | 5 | // Change this value every time before you build 6 | // Change this value every time before you build 7 | // Change this value every time before you build 8 | // const LATEST_VERSION = "v0.5.00"; 9 | 10 | // self.addEventListener("activate", (event) => { 11 | // console.log(`%c ${LATEST_VERSION} `, "background: #ddd; color: #0000ff"); 12 | // if (caches) { 13 | // caches.keys().then((arr) => { 14 | // arr.forEach((key) => { 15 | // if (key.indexOf("d4-precache") < -1) { 16 | // caches.delete(key).then(() => console.log(`%c Cleared ${key}`, "background: #333; color: #ff0000")); 17 | // } else { 18 | // caches.open(key).then((cache) => { 19 | // cache.match("version").then((res) => { 20 | // if (!res) { 21 | // cache.put("version", new Response(LATEST_VERSION, { status: 200, statusText: LATEST_VERSION })); 22 | // } else if (res.statusText !== LATEST_VERSION) { 23 | // caches.delete(key).then(() => console.log(`%c Cleared Cache ${LATEST_VERSION}`, "background: #333; color: #ff0000")); 24 | // } else console.log(`%c Great you have the latest version ${LATEST_VERSION}`, "background: #333; color: #00ff00"); 25 | // }); 26 | // }); 27 | // } 28 | // }); 29 | // }); 30 | // } 31 | // }); 32 | 33 | // workbox.skipWaiting(); 34 | // workbox.clientsClaim(); 35 | 36 | // self.__precacheManifest = [].concat(self.__precacheManifest || []); 37 | // workbox.precaching.suppressWarnings(); 38 | // workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); -------------------------------------------------------------------------------- /src/store/database.js: -------------------------------------------------------------------------------- 1 | // No database currently used -------------------------------------------------------------------------------- /src/store/modules/device.store.js: -------------------------------------------------------------------------------- 1 | //! Device Store 2 | // Holds device settings and info 3 | 4 | import debounce from "lodash/debounce"; 5 | 6 | const state = { 7 | hasTouch: false, 8 | isMac: navigator.appVersion.includes("Macintosh"), 9 | is_pwa: false, 10 | // mobile - less than 768px 11 | smallScreen: false, 12 | // True when soft keyboard visible on mobile 13 | softKeyboard: false, 14 | // Permissions statuses 15 | permissions: { 16 | geolocation: null, 17 | }, 18 | 19 | // Development Mode 20 | hostName: window.location.hostname, 21 | }; 22 | 23 | 24 | const getters = { 25 | hasTouch( state ) { 26 | return state.hasTouch; 27 | }, 28 | isMac( state ) { 29 | return state.isMac; 30 | }, 31 | isPwa( state ) { 32 | return state.is_pwa; 33 | }, 34 | hasSmallScreen( state ) { 35 | return state.smallScreen; 36 | }, 37 | softKeyboardVisible( state ) { 38 | return state.softKeyboard; 39 | }, 40 | // Permissions 41 | permissions( state ) { 42 | return state.permissions; 43 | }, 44 | geolocationPermissions( state ) { 45 | return state.permissions.geolocation; 46 | }, 47 | // Hostname 48 | getHostName( state ) { 49 | return state.hostName; 50 | }, 51 | }; 52 | 53 | 54 | const mutations = { 55 | HAS_TOUCH(state, val) { 56 | state.hasTouch = val; 57 | }, 58 | SET_PWA(state, val) { 59 | state.is_pwa = val; 60 | }, 61 | SET_SMALL_SCREEN(state, val) { 62 | state.smallScreen = val; 63 | }, 64 | SET_SOFT_KEYBOARD(state, val) { 65 | state.softKeyboard = val; 66 | }, 67 | // Permissions 68 | SET_GEOLOCATION_PERMISSIONS(state, val) { 69 | state.permissions.geolocation = val; 70 | }, 71 | }; 72 | 73 | 74 | const actions = { 75 | 76 | // Set screen size 77 | // Debounce with 500ms delay to prevent a billion toggles 78 | SCREEN_SIZE_CHANGE: debounce(({ commit }, value) => { 79 | commit("SET_SMALL_SCREEN", value); 80 | }, 500), 81 | 82 | // Set keyboard visibility 83 | // Debounce with 500ms delay to prevent a billion toggles 84 | SOFT_KEYBOARD_CHANGE: debounce(({ commit }, value) => { 85 | commit("SET_SOFT_KEYBOARD", value); 86 | }, 500), 87 | 88 | // Permissions 89 | // Check for geolocation permissions 90 | CHECK_PERMISSIONS({ commit, dispatch }, value) { 91 | // navigator.permissions.query({ name: "geolocation" }).then(function(permissionStatus) { 92 | // commit("SET_GEOLOCATION_PERMISSIONS", permissionStatus.state); 93 | // }); 94 | }, 95 | }; 96 | 97 | 98 | export default { 99 | namespaced: true, 100 | state, 101 | getters, 102 | actions, 103 | mutations 104 | }; -------------------------------------------------------------------------------- /src/store/modules/hold.store.js: -------------------------------------------------------------------------------- 1 | //! Hold Store 2 | // Holds misc data for use throughout app 3 | 4 | import { stateMerge } from "vue-object-merge"; 5 | import debounce from "lodash/debounce"; 6 | import router from "@/router"; 7 | 8 | 9 | const state = { 10 | // Data loaded. 0 = none, 1 = user, 2= user + profoile 11 | dataLoaded: 0, 12 | // Show loaders 13 | loading: false, 14 | // If true, confirm leave dialog appears before next navigate() 15 | confirmLeave: false, 16 | // If true, scrolling will lock (ie when modals are visible) 17 | scrollLock: false, 18 | // User device geolocation lat/lng 19 | geolocation: null, 20 | }; 21 | 22 | const getters = { 23 | dataLoaded( state ) { 24 | return state.dataLoaded; 25 | }, 26 | isLoading( state ) { 27 | return state.loading; 28 | }, 29 | showConfirmLeave( state ) { 30 | return state.confirmLeave; 31 | }, 32 | scrollLock( state ) { 33 | return state.scrollLock; 34 | }, 35 | geolocation( state ) { 36 | return state.geolocation; 37 | }, 38 | }; 39 | 40 | const mutations = { 41 | DATA_LOADED(state, val) { 42 | state.dataLoaded++; 43 | }, 44 | SET_LOADING(state, val) { 45 | state.loading = val; 46 | }, 47 | SET_CONFIRM_LEAVE(state, val) { 48 | state.confirmLeave = val; 49 | }, 50 | SCROLL_LOCK(state, val) { 51 | state.scrollLock = val; 52 | }, 53 | UPDATE_GEOLOCATION(state, val) { 54 | state.geolocation = val; 55 | }, 56 | }; 57 | 58 | 59 | const actions = { 60 | 61 | // Loading 62 | LOADING({ commit, getters, dispatch }, value) { 63 | var type = "data"; 64 | commit("SET_LOADING", type); 65 | 66 | // Auto timeout after 20s if it's not false 67 | setTimeout(function(){ 68 | if(getters.isLoading != false){ 69 | dispatch("STOP_LOAD"); 70 | } 71 | }, 20000); 72 | }, 73 | // Stop all loading indicators 74 | STOP_LOAD({ commit, getters, dispatch }) { 75 | if(getters.isLoading){ 76 | commit("SET_LOADING", false); 77 | } 78 | }, 79 | 80 | // Toggle leave confirmation 81 | CONFIRM_LEAVE({ commit }, value) { 82 | var x = true; 83 | if(!value){ 84 | x = false; 85 | } 86 | commit("SET_CONFIRM_LEAVE", x); 87 | }, 88 | 89 | 90 | }; 91 | 92 | 93 | export default { 94 | namespaced: true, 95 | state, 96 | getters, 97 | actions, 98 | mutations 99 | }; -------------------------------------------------------------------------------- /src/store/modules/site.store.js: -------------------------------------------------------------------------------- 1 | //! Site Store 2 | 3 | const state = { 4 | 5 | // Project info 6 | data: { 7 | 8 | // Active apps 9 | apps: { 10 | animations: { 11 | id: "animations", 12 | title: "Animations", 13 | headline: "CSS @keyframe creator", 14 | description: "Create CSS @keyframe animations with a video-editor like timeline", 15 | path: "/animate", 16 | icon: "fas fa-clapperboard", 17 | color: "red", 18 | category: "CSS", 19 | }, 20 | shadows: { 21 | id: "shadows", 22 | title: "Shadows", 23 | headline: "CSS Box Shadows", 24 | description: "Easily design simple or complex CSS box-shadows.", 25 | path: "/shadows", 26 | icon: "fas fa-sun", 27 | color: "sky", 28 | category: "CSS", 29 | }, 30 | colors: { 31 | id: "colors", 32 | title: "Colors", 33 | headline: "Simple color palettes", 34 | description: "A color palette or conversion tool for RGBA and HEX.", 35 | path: "/colors", 36 | icon: "fas fa-palette", 37 | color: "purple", 38 | category: "Design", 39 | }, 40 | characters: { 41 | id: "characters", 42 | title: "Characters", 43 | headline: "Special character search", 44 | description: "Search special characters and find their CSS or HTML values.", 45 | path: "/characters", 46 | icon: "fas fa-font", 47 | color: "orange", 48 | category: "Development", 49 | }, 50 | settings: { 51 | id: "settings", 52 | title: "Settings", 53 | headline: "Adjust your preferences", 54 | description: "Adjust how Keyframes.app looks, behaves, and manages your data.", 55 | path: "/settings", 56 | icon: "fas fa-gear", 57 | class: "settings", 58 | color: "blue", 59 | persistent: true, 60 | category: "system", 61 | }, 62 | } 63 | }, 64 | 65 | }; 66 | 67 | const getters = { 68 | apps(state){ 69 | return state.data.apps; 70 | } 71 | }; 72 | 73 | 74 | const mutations = { 75 | }; 76 | 77 | 78 | const actions = { 79 | }; 80 | 81 | 82 | export default { 83 | namespaced: true, 84 | state, 85 | getters, 86 | actions, 87 | mutations 88 | }; -------------------------------------------------------------------------------- /src/store/modules/user.store.js: -------------------------------------------------------------------------------- 1 | // User Store 2 | // Holds user settings. 3 | // Also app data 4 | // Synced with Local Storage 5 | 6 | import Vue from "vue"; 7 | 8 | import { stateMerge } from "vue-object-merge"; 9 | import debounce from "lodash/debounce"; 10 | import { dispatch } from "rxjs/internal/observable/pairs"; 11 | import router from "../../router"; 12 | 13 | const state = { 14 | // Auth state - default null means not yet determined 15 | auth: null, 16 | 17 | // User Preferences 18 | preferences: { 19 | confirm_action: true, 20 | tooltips: true, 21 | animations: true, 22 | outlines: true, 23 | notifications: false, 24 | app_launcher: false, 25 | start: "/", 26 | theme: "default", 27 | dark_mode: false, 28 | round_borders: true, 29 | font: "sans", 30 | }, 31 | 32 | // Individual app data and preferences 33 | apps: { 34 | animations: { 35 | enabled: true, 36 | data: {}, 37 | }, 38 | shadows: { 39 | enabled: true, 40 | data: {}, 41 | }, 42 | colors: { 43 | enabled: true, 44 | data: {}, 45 | }, 46 | characters: { 47 | enabled: true, 48 | data: {}, 49 | }, 50 | settings: { 51 | enabled: true, 52 | data: {}, 53 | } 54 | }, 55 | 56 | }; 57 | 58 | const getters = { 59 | // Users uid if authenticated 60 | auth( state ) { 61 | return state.auth; 62 | }, 63 | 64 | // User app data and individual prefs 65 | apps( state ) { 66 | return state.apps; 67 | }, 68 | // Colors app data 69 | colorsData( state ) { 70 | return state.apps.colors.data["palettes"] || []; 71 | }, 72 | 73 | // User app preferences 74 | preferences( state ) { 75 | return state.preferences; 76 | }, 77 | 78 | // Shortcuts 79 | darkMode( state ) { 80 | return state.preferences.dark_mode; 81 | }, 82 | app_launcher( state ) { 83 | return state.preferences.app_launcher; 84 | }, 85 | theme( state ) { 86 | return state.preferences.theme; 87 | }, 88 | 89 | 90 | }; 91 | 92 | 93 | const mutations = { 94 | FINISH_INITIAL_LOAD(state, val) { 95 | state.auth = val; 96 | }, 97 | 98 | SET_PREFERENCES(state, data) { 99 | stateMerge(state.preferences, data); 100 | }, 101 | SET_SINGLE_PREFERENCE(state, data) { 102 | state.preferences[data.key] = data.value; 103 | }, 104 | 105 | // App Data 106 | SET_APP_DATA(state, data) { 107 | stateMerge(state.apps, data); 108 | }, 109 | SET_SINGLE_APP_DATA(state, data) { 110 | state.apps[data.key].data = data.value; 111 | }, 112 | // Set single field within data. ie data["palettes"] 113 | SET_APP_DATA_FIELD(state, field) { 114 | state.apps[field.app].data[field.key] = field.value; 115 | }, 116 | 117 | 118 | 119 | SET_TOGGLE_APP(state, data) { 120 | state.apps[data.key].enabled = data.value; 121 | }, 122 | SET_CLEAR_APP_DATA(state, key) { 123 | state.apps[key].data = {}; 124 | }, 125 | 126 | }; 127 | 128 | 129 | 130 | 131 | 132 | const actions = { 133 | 134 | // False means loaded with no user 135 | // Initial load, check user (if auth enabled), get local storage 136 | INITIAL_LOAD({ commit, getters, dispatch}, user) { 137 | return new Promise((resolve, reject) => { 138 | let _this = this; 139 | 140 | // Get local storage 141 | // Loop through all local storage, save sttorage names of animations 142 | // Animations begin with "animation_", Palettes are "palette_" 143 | var appData = null; 144 | var userPreferences = null; 145 | var keys = Object.keys(localStorage); 146 | var i = keys.length; 147 | while( i-- ){ 148 | 149 | // Get App Data from Storage 150 | if(keys[i] == ("appData")){ 151 | appData = localStorage.getItem(keys[i]); 152 | appData = JSON.parse(appData); 153 | commit("SET_APP_DATA", appData); 154 | } 155 | 156 | // Get Preferences 157 | if(keys[i] == ("preferences")){ 158 | userPreferences = localStorage.getItem(keys[i]); 159 | userPreferences = JSON.parse(userPreferences); 160 | commit("SET_PREFERENCES", userPreferences); 161 | 162 | // Set theme based on prefs 163 | dispatch("CHANGE_THEME"); 164 | } 165 | 166 | } 167 | 168 | // No prefs saved, determine dark mode based on device 169 | if(!userPreferences){ 170 | if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { 171 | commit("SET_SINGLE_PREFERENCE", {key: "dark_mode", value: true}); 172 | dispatch("CHANGE_THEME"); 173 | }else{ 174 | commit("SET_SINGLE_PREFERENCE", {key: "dark_mode", value: false}); 175 | } 176 | 177 | } 178 | 179 | // Navigate to default page 180 | setTimeout(function(){ 181 | if(router.currentRoute.path == "/" && userPreferences && userPreferences.start){ 182 | setTimeout(function(){ 183 | router.push(userPreferences.start); 184 | }, 200); 185 | } 186 | }, 200); 187 | 188 | 189 | // Resolve 190 | resolve(); 191 | }); 192 | }, 193 | 194 | 195 | // Save preferences into local storage 196 | SAVE_APP_DATA({ commit, getters }, key) { 197 | localStorage.setItem("appData", JSON.stringify(getters.apps)); 198 | }, 199 | // Debounce with 3s delay 200 | DEBOUNCE_SAVE_APP_DATA: debounce(({ dispatch }) => { 201 | dispatch("SAVE_APP_DATA"); 202 | }, 1000), 203 | 204 | // Update all App Data 205 | UPDATE_APP_DATA({ commit, dispatch }, data) { 206 | commit("SET_SINGLE_APP_DATA", data); 207 | // Store changes 208 | dispatch("DEBOUNCE_SAVE_APP_DATA"); 209 | }, 210 | 211 | 212 | 213 | // Toggle App on/off 214 | TOGGLE_APP({ commit, getters, dispatch }, key) { 215 | var switchValue = !getters.apps[key].enabled; 216 | var data = {key: key, value: switchValue}; 217 | commit("SET_TOGGLE_APP", data); 218 | 219 | // Store changes 220 | dispatch("DEBOUNCE_SAVE_APP_DATA"); 221 | }, 222 | 223 | // Clears data for one specific app 224 | CLEAR_APP_DATA({ commit, dispatch }, key) { 225 | commit("SET_CLEAR_APP_DATA", key); 226 | // Store changes 227 | dispatch("DEBOUNCE_SAVE_APP_DATA"); 228 | }, 229 | 230 | 231 | 232 | // Save preferences into local storage 233 | SAVE_PREFERENCES({ commit, getters }, key) { 234 | localStorage.setItem("preferences", JSON.stringify(getters.preferences)); 235 | }, 236 | // Debounce with 3s delay 237 | DEBOUNCE_SAVE_PREFERENCES: debounce(({ dispatch }) => { 238 | dispatch("SAVE_PREFERENCES"); 239 | }, 3000), 240 | 241 | 242 | // Update Preference 243 | UPDATE_PREFERENCE({ commit, dispatch }, data) { 244 | commit("SET_SINGLE_PREFERENCE", data); 245 | 246 | // Store changes 247 | dispatch("DEBOUNCE_SAVE_PREFERENCES"); 248 | }, 249 | // Toggle (inverse) Preference 250 | TOGGLE_PREFERENCE({ commit, getters, dispatch }, key) { 251 | var switchValue = !getters.preferences[key]; 252 | var data = {key: key, value: switchValue}; 253 | commit("SET_SINGLE_PREFERENCE", data); 254 | 255 | // Store changes 256 | dispatch("DEBOUNCE_SAVE_PREFERENCES"); 257 | 258 | // If changing dark mode, update theme 259 | if(key == "dark_mode"){ 260 | dispatch("CHANGE_THEME"); 261 | } 262 | }, 263 | 264 | 265 | // Preferences 266 | CHANGE_THEME({ commit, getters, dispatch }, theme) { 267 | 268 | var darkModeOn = getters.darkMode; 269 | 270 | // If no theme provided, use theme from preferences 271 | if(!theme){ 272 | var newTheme = getters.theme; 273 | }else{ 274 | var newTheme = theme; 275 | } 276 | 277 | // Name to set on documentElement 278 | var setTheme = "light"; 279 | // Special case for default theme - must be called "light/dark" 280 | if(newTheme == "default"){ 281 | if(darkModeOn){ 282 | setTheme = "dark"; 283 | } 284 | }else{ 285 | // Other named themes 286 | setTheme = newTheme; 287 | if(darkModeOn){ 288 | setTheme = setTheme + "_dark"; 289 | } 290 | } 291 | 292 | // Store change 293 | var data = {key: "theme", value: newTheme}; 294 | commit("SET_SINGLE_PREFERENCE", data); 295 | 296 | // Change document theme 297 | document.documentElement.setAttribute("data-theme", setTheme); 298 | // document.querySelector("meta[name=\"theme-color\"]").setAttribute("content", "#07070f"); 299 | 300 | // Save changes 301 | dispatch("DEBOUNCE_SAVE_PREFERENCES"); 302 | 303 | }, 304 | 305 | 306 | }; 307 | 308 | 309 | export default { 310 | namespaced: true, 311 | state, 312 | getters, 313 | actions, 314 | mutations 315 | }; -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | //! Store 2 | // Store and get values across components 3 | // Main store file imports others from ./modules 4 | 5 | import Vue from "vue"; 6 | import Vuex from "vuex"; 7 | import createLogger from "vuex/dist/logger"; 8 | 9 | // Store Modules 10 | import Device from "./modules/device.store"; 11 | import Hold from "./modules/hold.store"; 12 | import Site from "./modules/site.store"; 13 | import User from "./modules/user.store"; 14 | 15 | const debug = process.env.NODE_ENV !== "production"; 16 | 17 | Vue.use(Vuex); 18 | 19 | export default new Vuex.Store({ 20 | modules: { 21 | Device, 22 | Hold, 23 | Site, 24 | User, 25 | }, 26 | strict: debug, 27 | plugins: debug? [ createLogger() ] : [], 28 | }); 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/styles/animations.scss: -------------------------------------------------------------------------------- 1 | // Animations 2 | // Various animations/keyframes for vue transitions (or others) 3 | // "load" - On page load, fade in 4 | // "fade" - Fast fade 5 | // "page" - slide animation between pages 6 | // "basic" - Simple animation translates 15px from bottom while fading 7 | // "basicup" - Simple animation translates from top 8 | // "list" - TranslateX from right while fading, staggered for children, so great for lists 9 | // "modal" - Modal enter and leave animations 10 | // "lightbox" - Lightbox enter and leave animations 11 | 12 | 13 | // Load 14 | .load-enter-active {animation: page-load .2s ease-out;} 15 | .load-leave-active {animation: page-load .2s ease-in reverse;} 16 | @keyframes page-load { 17 | 0% {opacity: 0;} 18 | 100% {opacity: 1;} 19 | } 20 | 21 | 22 | // Fade 23 | .fade-enter-active {animation: page-fade .08s ease-out;} 24 | .fade-leave-active {animation: page-fade .08s ease-in reverse;} 25 | @keyframes page-fade { 26 | 0% {opacity: 0;} 27 | 100% {opacity: 1;} 28 | } 29 | 30 | 31 | // Page 32 | // On page transition / route change 33 | .page-enter-active { 34 | animation: page-in .08s ease-out; 35 | } 36 | .page-leave-active { 37 | animation: page-out .08s ease-in; 38 | } 39 | // Translates from right of page into pace while also 0% -> 100% opacity 40 | @keyframes page-in { 41 | 0% { 42 | opacity: 0; 43 | transform: translateX(50vw); 44 | } 45 | 50%{ 46 | opacity: 1; 47 | } 48 | 100% { 49 | opacity: 1; 50 | transform: translateX(0px); 51 | } 52 | } 53 | // Translates from other direction to look like horizontal scroll 54 | @keyframes page-out { 55 | 0% { 56 | opacity: 1; 57 | transform: translateX(0px); 58 | } 59 | 50%{ 60 | opacity: 0; 61 | } 62 | 100% { 63 | opacity: 0; 64 | transform: translateX(-50vw); 65 | } 66 | } 67 | 68 | 69 | // Basic 70 | // fade and translate from bottom 71 | .basic-enter-active { 72 | transition: all .08s ease 0.08s; 73 | } 74 | .basic-leave-active { 75 | transition: all .08s ease; 76 | } 77 | .basic-enter, .basic-leave-to{ 78 | transform: translateY(15px); 79 | opacity: 0; 80 | } 81 | // Basicup 82 | // fade and translate from top 83 | .basicup-enter-active { 84 | transition: all .08s ease 0.08s; 85 | } 86 | .basicup-leave-active { 87 | transition: all .08s ease; 88 | } 89 | .basicup-enter, .basicup-leave-to{ 90 | transform: translateY(-15px); 91 | opacity: 0; 92 | } 93 | 94 | 95 | // List 96 | // Use for blocks of several items/lists 97 | .list-enter-active { 98 | animation: list-animation .2s ease both; 99 | // Stagger children for cool effect 100 | &:nth-child(1){animation-delay: 0.0s;} 101 | &:nth-child(2){animation-delay: 0.2s;} 102 | &:nth-child(3){animation-delay: 0.4s;} 103 | &:nth-child(4){animation-delay: 0.6s;} 104 | &:nth-child(5){animation-delay: 0.8s;} 105 | } 106 | .list-leave-active { 107 | animation: list-animation .2s ease reverse both; 108 | // Stagger children for cool effect 109 | &:nth-child(1){animation-delay: 0.0s;} 110 | &:nth-child(2){animation-delay: 0.2s;} 111 | &:nth-child(3){animation-delay: 0.4s;} 112 | &:nth-child(4){animation-delay: 0.6s;} 113 | &:nth-child(5){animation-delay: 0.8s;} 114 | } 115 | @keyframes list-animation { 116 | 0% { 117 | opacity: 0; 118 | transform: translateX(60px); 119 | } 120 | 100% { 121 | transform: translateX(0px); 122 | opacity: 1; 123 | } 124 | } 125 | 126 | // Modal 127 | // Modal animation 128 | .modal-enter-active { 129 | transition: all .06s 0.08s; 130 | } 131 | .modal-leave-active { 132 | transition: all .06s; 133 | } 134 | .modal-enter, .modal-leave-to{ 135 | transform: translateY(105px); 136 | opacity: 0; 137 | } 138 | 139 | // Lightbox 140 | .lightbox-enter-active { 141 | transition: all .08s ease 0.08s; 142 | } 143 | .lightbox-leave-active { 144 | transition: all .08s ease; 145 | } 146 | .lightbox-enter, .lightbox-leave-to{ 147 | opacity: 0; 148 | } -------------------------------------------------------------------------------- /src/styles/base.scss: -------------------------------------------------------------------------------- 1 | //! Base 2 | // Base body, html, and other global styles 3 | 4 | // Universal 5 | body, html{ 6 | margin: 0 auto; 7 | font-family: var(--primaryFont); 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | font-size: var(--baseFontSize); 11 | color: var(--text); 12 | overflow-x: hidden; 13 | background-color: var(--layer); 14 | // background-color: var(--background); 15 | max-height: 100vh; 16 | max-height: -webkit-fill-available; 17 | overscroll-behavior: none; 18 | touch-action: pan-x pan-y; 19 | } 20 | 21 | // Disable default browser tap highlighting, smooth text 22 | // Disable text selection on mobile 23 | *{ 24 | -webkit-tap-highlight-color: rgba(255, 255, 255, 0); 25 | -webkit-overflow-scrolling: touch; 26 | -webkit-overflow-scrolling: touch; 27 | text-rendering: optimizeLegibility; 28 | } 29 | 30 | input, input:before, input:after { 31 | -webkit-user-select: initial; 32 | -khtml-user-select: initial; 33 | -moz-user-select: initial; 34 | -ms-user-select: initial; 35 | user-select: initial; 36 | box-shadow: none; 37 | } 38 | 39 | // Image - prevent drag 40 | img{ 41 | pointer-events: none; 42 | user-select: none; 43 | } 44 | 45 | // Hide scrollbars 46 | .no-scrollbars, 47 | .no-scrollbars *{ 48 | scrollbar-width: none; 49 | // Hidden 50 | &::-webkit-scrollbar{ 51 | width: 0px; 52 | display: none; 53 | } 54 | // Scrollbar handle/thumb 55 | &::-webkit-scrollbar-thumb{ 56 | display: none; 57 | width: 0px; 58 | } 59 | } 60 | 61 | 62 | // Text Selection // 63 | ::selection { 64 | background: var(--primary); 65 | color: var(--primaryText); 66 | } 67 | ::-moz-selection { 68 | background: var(--blue); // Gecko Brooowsers 69 | color: var(--background); 70 | } 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/styles/custom-forms.scss: -------------------------------------------------------------------------------- 1 | //! Custom Forms 2 | // Custom non-standard form elements 3 | 4 | // Tabs 5 | // Navigation or selects 6 | .tabs{ 7 | display: flex; 8 | box-sizing: border-box; 9 | width: fit-content; 10 | overflow-y: auto; 11 | z-index: 5; 12 | overflow: visible; 13 | max-width: calc(100vw - 100px); 14 | 15 | // Full width mobile 16 | @media (max-width: $screenSM) { 17 | overflow-x: auto; 18 | width: 100%; 19 | max-width: 100%; 20 | } 21 | 22 | .tab{ 23 | height: 36px; 24 | padding: 0 16px 0px 18px; 25 | font-size: 0.8rem; 26 | flex-grow: 3; 27 | font-weight: 600; 28 | color: var(--text); 29 | text-align: center; 30 | white-space: pre; 31 | background-color: var(--background); 32 | // box-shadow: var(--shadow); 33 | 34 | &:first-child{ 35 | margin-left: var(--sidePadding); 36 | } 37 | &:last-child{ 38 | margin-right: 100px !important; 39 | position: relative; 40 | 41 | &:after{ 42 | content: ' '; 43 | width: 100px; 44 | position: absolute; 45 | } 46 | } 47 | 48 | // Icon 49 | i{ 50 | margin-right: 8px; 51 | transform: scale(1.2); 52 | } 53 | 54 | // Hover 55 | &:hover{ 56 | color: var(--text); 57 | background-color: var(--background); 58 | text-decoration: underline; 59 | } 60 | 61 | // Active tab 62 | &.active{ 63 | color: var(--blueText); 64 | background-color: var(--blue); 65 | border-color: var(--border); 66 | border-radius: 10px; 67 | i{ 68 | color: var(--blueText); 69 | } 70 | &:hover{ 71 | border-color: var(--border); 72 | span{ 73 | text-decoration: none; 74 | } 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | //! Main 2 | // Main SCSS file to import all others 3 | 4 | // Then variables 5 | @import 'variables.scss'; 6 | // Then reset 7 | @import 'reset.scss'; 8 | // Then base 9 | @import 'base.scss'; 10 | // Then everything else 11 | @import 'modal.scss'; 12 | @import 'typography.scss'; 13 | @import 'animations.scss'; 14 | @import 'forms.scss'; 15 | @import 'custom-forms.scss'; 16 | @import 'layout.scss'; 17 | @import 'helpers.scss'; 18 | @import 'ui.scss'; 19 | @import 'print.scss'; 20 | 21 | // Themes 22 | @import 'themes/default.scss'; 23 | @import 'themes/primary.scss'; 24 | @import 'themes/muted.scss'; 25 | @import 'themes/greys.scss'; 26 | -------------------------------------------------------------------------------- /src/styles/modal.scss: -------------------------------------------------------------------------------- 1 | //! Modal element styles 2 | 3 | // Universal Lightbox 4 | // Blank div with lightbox class 5 | .lightbox{ 6 | background-color: var(--backdrop); 7 | // backdrop-filter: blur(3px); 8 | // backdrop-filter: grayscale(80%) contrast(150%); 9 | display: block; 10 | height: 100vh; 11 | max-height: -webkit-fill-available; 12 | width: 100vw; 13 | position: fixed; 14 | top: 0; 15 | left: 0; 16 | z-index:50; 17 | } 18 | 19 | // Modals 20 | // Flex wrapper - click outside body to close 21 | .modal-wrapper{ 22 | display: flex; 23 | flex-direction: column; 24 | justify-content: center; 25 | height: 100vh; 26 | max-height: -webkit-fill-available; 27 | width: 100vw; 28 | position: fixed; 29 | bottom: 0; 30 | left: 0; 31 | transform: scaleX(1); 32 | transition: var(--transition); 33 | transform-origin: bottom center; 34 | z-index: 200; 35 | 36 | // Absolute bottom, larger on mobile 37 | @media (max-width: $screenSM) { 38 | justify-content: flex-end; 39 | } 40 | 41 | // Modal body 42 | .modal{ 43 | display: flex; 44 | flex-direction: column; 45 | background-color: var(--layer); 46 | margin: 0 auto; 47 | width: 90%; 48 | max-width: 600px; 49 | border-radius: var(--borderRadius); 50 | border-radius: calc(var(--borderRadius) * 3); 51 | max-height: 90vh; 52 | box-shadow: var(--shadow); 53 | @media (max-width: $screenSM) { 54 | border-bottom-right-radius: 0; 55 | border-bottom-left-radius: 0; 56 | } 57 | 58 | // Small, confirmation modal 59 | &.small{ 60 | max-width: 380px; 61 | 62 | // Absolute bottom, larger on mobile 63 | @media (max-width: $screenSM) { 64 | width: 100%; 65 | max-width: 100%; 66 | } 67 | } 68 | 69 | // Absolute bottom, larger on mobile 70 | @media (max-width: $screenSM) { 71 | width: 100%; 72 | position: absolute; 73 | left: 0; 74 | max-width: 100%; 75 | max-height: 88vh; 76 | border-top-left-radius: calc(var(--borderRadius) * 3); 77 | border-top-right-radius: calc(var(--borderRadius) * 3); 78 | padding-bottom: 15px; 79 | 80 | // No scrollbars 81 | *{ 82 | scrollbar-width: none; 83 | // Hidden 84 | &::-webkit-scrollbar{ 85 | width: 0px; 86 | display: none; 87 | } 88 | // Scrollbar handle/thumb 89 | &::-webkit-scrollbar-thumb{ 90 | display: none; 91 | width: 0px; 92 | } 93 | } 94 | 95 | } 96 | 97 | 98 | // Modal Title 99 | .modal-title{ 100 | display: flex; 101 | justify-content: space-between; 102 | box-sizing: border-box; 103 | padding: 35px 35px 35px 35px; 104 | border-top-right-radius: var(--borderRadius); 105 | border-top-left-radius: var(--borderRadius); 106 | color: var(--text); 107 | font-weight: 500; 108 | letter-spacing: 0.15px; 109 | font-size: 20px; 110 | line-height: 20px; 111 | 112 | // Increase size and padding on mobile 113 | @media (max-width: $screenSM) { 114 | padding: 30px 25px 24px 25px; 115 | font-size: 24px; 116 | text-align: center; 117 | } 118 | 119 | // Shrink a bit for longer text 120 | &.small{ 121 | @media (max-width: $screenSM) { 122 | font-size: 18px; 123 | } 124 | } 125 | 126 | span, i{ 127 | display: flex; 128 | flex-direction: column; 129 | justify-content: center; 130 | } 131 | 132 | i{ 133 | padding-bottom: 4px; 134 | transform: scale(1.3); 135 | } 136 | 137 | &.center{ 138 | justify-content: center; 139 | } 140 | } 141 | 142 | // Main body/content 143 | .modal-body{ 144 | box-sizing: border-box; 145 | padding: 5px 35px 10px 35px; 146 | overflow: scroll; 147 | flex-grow: 3; 148 | 149 | &.overflow{ 150 | overflow: visible; 151 | } 152 | 153 | // Increase size and padding on mobile 154 | @media (max-width: $screenSM) { 155 | padding: 0px 30px 20px 30px; 156 | } 157 | 158 | } 159 | 160 | // Modal footer/buttons 161 | .modal-footer{ 162 | display: flex; 163 | justify-content: space-between; 164 | box-sizing: border-box; 165 | padding: 20px; 166 | max-height: 100px; 167 | gap: 15px; 168 | 169 | // Remove bottom and side padding 170 | @media (max-width: $screenSM) { 171 | padding: 10px 25px 20px 25px; 172 | } 173 | 174 | // Center buttons 175 | &.center{ 176 | // Space-around centers all elements with gaps between 177 | justify-content: space-around; 178 | } 179 | &.right{ 180 | justify-content: flex-end; 181 | } 182 | &.reverse{ 183 | flex-direction: row-reverse; 184 | } 185 | } 186 | } 187 | 188 | } 189 | 190 | 191 | // On mobile, hide modal header and footer 192 | // When soft keyboarrd is visible 193 | // #app.softkeyboard-visible .modal-footer{ 194 | 195 | // @media (max-width: $screenSM) { 196 | // padding: 0px 30px 0px 30px; 197 | // transition: all 0.2s ease; 198 | // max-height: 0px; 199 | // } 200 | 201 | // } 202 | // #app.softkeyboard-visible .modal-title{ 203 | // @media (max-width: $screenSM) { 204 | // font-size: 20px; 205 | // transition: all 0.2s ease; 206 | // padding: 20px 25px 10px 25px; 207 | // } 208 | // } 209 | 210 | 211 | // Element adjustmens in modals 212 | .modal{ 213 | 214 | .selections-wrapper{ 215 | width: 100%; 216 | max-width: 100%; 217 | 218 | .selections-button{ 219 | width: 100%; 220 | } 221 | .selections-popover{ 222 | max-width: 100%; 223 | border-radius: var(--borderRadius); 224 | 225 | // Limit height above sm 226 | // @media (max-width: $screenSM) { 227 | // max-height: 220px; 228 | // } 229 | } 230 | } 231 | 232 | 233 | } -------------------------------------------------------------------------------- /src/styles/print.scss: -------------------------------------------------------------------------------- 1 | // CSS Print Styles 2 | 3 | #printArea{ 4 | display: block; 5 | width: 100%; 6 | height: 100%; 7 | } 8 | // Footer / Branding for print 9 | #printFooter{ 10 | display: none; 11 | justify-content: flex-end; 12 | position: fixed; 13 | bottom: 0; 14 | margin: 0 auto; 15 | box-sizing: border-box; 16 | padding: 18px; 17 | font-size: 0.6em; 18 | font-weight: 600; 19 | color: rgba(0,0,0,0.8); 20 | font-family: var(--monospace); 21 | width: 100%; 22 | 23 | span{ 24 | display: inline-block; 25 | border-radius: 20px; 26 | padding: 6px 11px 4px 11px; 27 | background-color: rgba(255,255,255,1); 28 | } 29 | 30 | img{ 31 | height: 16px; 32 | margin-left: 2px; 33 | opacity: 0.8; 34 | width: auto; 35 | position: relative; 36 | top: -2px; 37 | } 38 | } 39 | 40 | // Only show on print 41 | .print-only{ 42 | display: none; 43 | } 44 | 45 | @media print{ 46 | body, html{ 47 | display: block; 48 | height: fit-content; 49 | width: 100%; 50 | min-height: 100%; 51 | } 52 | #app{ 53 | display: none; 54 | } 55 | 56 | .print-only{ 57 | display: block; 58 | } 59 | .hide-print{ 60 | display: none !important; 61 | } 62 | 63 | // Footer, show when print 64 | #printFooter{ 65 | display: flex; 66 | } 67 | } 68 | 69 | 70 | // Color Page print styles 71 | // Sizing has to be off colors.vue so it is picked up by print area 72 | #colorGrid{ 73 | // Color block sizing based on count 74 | .color_block{flex-basis: 33%;} 75 | &[data-count="1"] .color_block {flex-basis: 100%;} 76 | &[data-count="2"] .color_block {flex-basis: calc(50% - 8px);} 77 | &[data-count="3"] .color_block {flex-basis: calc(33.33% - 16px);} 78 | &[data-count="4"] .color_block {flex-basis: calc(25% - 24px);} 79 | &[data-count="5"] .color_block {flex-basis: 20%;} 80 | &[data-count="6"] .color_block {flex-basis: 33.33%;} 81 | &[data-count="7"] .color_block {flex-basis: 14.28%;} 82 | &[data-count="8"] .color_block {flex-basis: 25%;} 83 | &[data-count="9"] .color_block {flex-basis: 33%;} 84 | &[data-count="10"] .color_block {flex-basis: 20%;} 85 | 86 | @media print{ 87 | &[data-count="1"] .color_block {flex-basis: 100%;} 88 | &[data-count="2"] .color_block {flex-basis: 50%;} 89 | &[data-count="3"] .color_block {flex-basis: 33%;} 90 | &[data-count="4"] .color_block {flex-basis: 50%;} 91 | &[data-count="5"] .color_block {flex-basis: 50%;} 92 | &[data-count="6"] .color_block {flex-basis: 33%;} 93 | &[data-count="7"] .color_block {flex-basis: 33%;} 94 | &[data-count="8"] .color_block {flex-basis: 50%;} 95 | &[data-count="9"] .color_block {flex-basis: 33%;} 96 | &[data-count="10"] .color_block {flex-basis: 50%;} 97 | 98 | .color_block_info{ 99 | padding-top: 15px !important; 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/styles/reset.scss: -------------------------------------------------------------------------------- 1 | //! Reset 2 | // http://meyerweb.com/eric/tools/css/reset/ 3 | // v2.0-modified | 20110126 4 | // License: none (public domain) 5 | // 6 | 7 | html, body, div, span, applet, object, iframe, 8 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 9 | a, abbr, acronym, address, big, cite, code, 10 | del, dfn, em, img, ins, kbd, q, s, samp, 11 | small, strike, strong, sub, sup, tt, var, 12 | b, u, i, center, 13 | dl, dt, dd, ol, ul, li, 14 | fieldset, form, label, legend, 15 | table, caption, tbody, tfoot, thead, tr, th, td, 16 | article, aside, canvas, details, embed, 17 | figure, figcaption, footer, header, hgroup, 18 | menu, nav, output, ruby, section, summary, 19 | time, mark, audio, video { 20 | margin: 0; 21 | padding: 0; 22 | border: 0; 23 | font-size: 100%; 24 | font: inherit; 25 | vertical-align: baseline; 26 | } 27 | 28 | /* make sure to set some focus styles for accessibility */ 29 | :focus { 30 | outline: 0; 31 | } 32 | 33 | /* HTML5 display-role reset for older browsers */ 34 | article, aside, details, figcaption, figure, 35 | footer, header, hgroup, menu, nav, section { 36 | display: block; 37 | } 38 | 39 | body { 40 | line-height: 1; 41 | } 42 | 43 | ol, ul { 44 | list-style: none; 45 | } 46 | 47 | blockquote, q { 48 | quotes: none; 49 | } 50 | 51 | blockquote:before, blockquote:after, 52 | q:before, q:after { 53 | content: ''; 54 | content: none; 55 | } 56 | 57 | table { 58 | border-collapse: collapse; 59 | border-spacing: 0; 60 | } 61 | 62 | input[type=search]::-webkit-search-cancel-button, 63 | input[type=search]::-webkit-search-decoration, 64 | input[type=search]::-webkit-search-results-button, 65 | input[type=search]::-webkit-search-results-decoration { 66 | -webkit-appearance: none; 67 | -moz-appearance: none; 68 | } 69 | 70 | input[type=search] { 71 | -webkit-appearance: none; 72 | -moz-appearance: none; 73 | -webkit-box-sizing: content-box; 74 | -moz-box-sizing: content-box; 75 | box-sizing: content-box; 76 | } 77 | 78 | textarea { 79 | overflow: auto; 80 | vertical-align: top; 81 | resize: vertical; 82 | } 83 | 84 | /** 85 | * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. 86 | */ 87 | 88 | audio, 89 | canvas, 90 | video { 91 | display: inline-block; 92 | *display: inline; 93 | max-width: 100%; 94 | } 95 | 96 | /** 97 | * Prevent modern browsers from displaying `audio` without controls. 98 | * Remove excess height in iOS 5 devices. 99 | */ 100 | 101 | audio:not([controls]) { 102 | display: none; 103 | height: 0; 104 | } 105 | 106 | /** 107 | * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4. 108 | * Known issue: no IE 6 support. 109 | */ 110 | 111 | [hidden] { 112 | display: none; 113 | } 114 | 115 | /** 116 | * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using 117 | * `em` units. 118 | * 2. Prevent iOS text size adjust after orientation change, without disabling 119 | * user zoom. 120 | */ 121 | 122 | html { 123 | font-size: 100%; /* 1 */ 124 | -webkit-text-size-adjust: 100%; /* 2 */ 125 | -ms-text-size-adjust: 100%; /* 2 */ 126 | } 127 | 128 | /** 129 | * Address `outline` inconsistency between Chrome and other browsers. 130 | */ 131 | 132 | a:focus { 133 | outline: thin dotted; 134 | } 135 | 136 | /** 137 | * Improve readability when focused and also mouse hovered in all browsers. 138 | */ 139 | 140 | a:active, 141 | a:hover { 142 | outline: 0; 143 | } 144 | 145 | /** 146 | * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3. 147 | * 2. Improve image quality when scaled in IE 7. 148 | */ 149 | 150 | img { 151 | border: 0; /* 1 */ 152 | -ms-interpolation-mode: bicubic; /* 2 */ 153 | } 154 | 155 | /** 156 | * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11. 157 | */ 158 | 159 | figure { 160 | margin: 0; 161 | } 162 | 163 | /** 164 | * Correct margin displayed oddly in IE 6/7. 165 | */ 166 | 167 | form { 168 | margin: 0; 169 | } 170 | 171 | /** 172 | * Define consistent border, margin, and padding. 173 | */ 174 | 175 | fieldset { 176 | border: 1px solid #c0c0c0; 177 | margin: 0 2px; 178 | padding: 0.35em 0.625em 0.75em; 179 | } 180 | 181 | /** 182 | * 1. Correct color not being inherited in IE 6/7/8/9. 183 | * 2. Correct text not wrapping in Firefox 3. 184 | * 3. Correct alignment displayed oddly in IE 6/7. 185 | */ 186 | 187 | legend { 188 | border: 0; /* 1 */ 189 | padding: 0; 190 | white-space: normal; /* 2 */ 191 | *margin-left: -7px; /* 3 */ 192 | } 193 | 194 | /** 195 | * 1. Correct font size not being inherited in all browsers. 196 | * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5, 197 | * and Chrome. 198 | * 3. Improve appearance and consistency in all browsers. 199 | */ 200 | 201 | button, 202 | input, 203 | select, 204 | textarea { 205 | font-size: 100%; /* 1 */ 206 | margin: 0; /* 2 */ 207 | vertical-align: baseline; /* 3 */ 208 | *vertical-align: middle; /* 3 */ 209 | } 210 | 211 | /** 212 | * Address Firefox 3+ setting `line-height` on `input` using `!important` in 213 | * the UA stylesheet. 214 | */ 215 | 216 | button, 217 | input { 218 | line-height: normal; 219 | } 220 | 221 | /** 222 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 223 | * All other form control elements do not inherit `text-transform` values. 224 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+. 225 | * Correct `select` style inheritance in Firefox 4+ and Opera. 226 | */ 227 | 228 | button, 229 | select { 230 | text-transform: none; 231 | } 232 | 233 | /** 234 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 235 | * and `video` controls. 236 | * 2. Correct inability to style clickable `input` types in iOS. 237 | * 3. Improve usability and consistency of cursor style between image-type 238 | * `input` and others. 239 | * 4. Remove inner spacing in IE 7 without affecting normal text inputs. 240 | * Known issue: inner spacing remains in IE 6. 241 | */ 242 | 243 | button, 244 | html input[type="button"], /* 1 */ 245 | input[type="reset"], 246 | input[type="submit"] { 247 | -webkit-appearance: button; /* 2 */ 248 | cursor: pointer; /* 3 */ 249 | *overflow: visible; /* 4 */ 250 | } 251 | 252 | /** 253 | * Re-set default cursor for disabled elements. 254 | */ 255 | 256 | button[disabled], 257 | html input[disabled] { 258 | cursor: default; 259 | } 260 | 261 | /** 262 | * 1. Address box sizing set to content-box in IE 8/9. 263 | * 2. Remove excess padding in IE 8/9. 264 | * 3. Remove excess padding in IE 7. 265 | * Known issue: excess padding remains in IE 6. 266 | */ 267 | 268 | input[type="checkbox"], 269 | input[type="radio"] { 270 | box-sizing: border-box; /* 1 */ 271 | padding: 0; /* 2 */ 272 | *height: 13px; /* 3 */ 273 | *width: 13px; /* 3 */ 274 | } 275 | 276 | /** 277 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 278 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 279 | * (include `-moz` to future-proof). 280 | */ 281 | 282 | input[type="search"] { 283 | -webkit-appearance: textfield; /* 1 */ 284 | -moz-box-sizing: content-box; 285 | -webkit-box-sizing: content-box; /* 2 */ 286 | box-sizing: content-box; 287 | } 288 | 289 | /** 290 | * Remove inner padding and search cancel button in Safari 5 and Chrome 291 | * on OS X. 292 | */ 293 | 294 | input[type="search"]::-webkit-search-cancel-button, 295 | input[type="search"]::-webkit-search-decoration { 296 | -webkit-appearance: none; 297 | } 298 | 299 | /** 300 | * Remove inner padding and border in Firefox 3+. 301 | */ 302 | 303 | button::-moz-focus-inner, 304 | input::-moz-focus-inner { 305 | border: 0; 306 | padding: 0; 307 | } 308 | 309 | /** 310 | * 1. Remove default vertical scrollbar in IE 6/7/8/9. 311 | * 2. Improve readability and alignment in all browsers. 312 | */ 313 | 314 | textarea { 315 | overflow: auto; /* 1 */ 316 | vertical-align: top; /* 2 */ 317 | } 318 | 319 | /** 320 | * Remove most spacing between table cells. 321 | */ 322 | 323 | table { 324 | border-collapse: collapse; 325 | border-spacing: 0; 326 | } 327 | 328 | html, 329 | button, 330 | input, 331 | select, 332 | textarea { 333 | color: #222; 334 | } 335 | 336 | 337 | ::-moz-selection { 338 | background: #b3d4fc; 339 | text-shadow: none; 340 | } 341 | 342 | ::selection { 343 | background: #b3d4fc; 344 | text-shadow: none; 345 | } 346 | 347 | img { 348 | vertical-align: middle; 349 | } 350 | 351 | fieldset { 352 | border: 0; 353 | margin: 0; 354 | padding: 0; 355 | } 356 | 357 | textarea { 358 | resize: vertical; 359 | } 360 | 361 | .chromeframe { 362 | margin: 0.2em 0; 363 | background: #ccc; 364 | color: #000; 365 | padding: 0.2em 0; 366 | } 367 | 368 | -------------------------------------------------------------------------------- /src/styles/themes/default.scss: -------------------------------------------------------------------------------- 1 | // Default Theme 2 | 3 | :root { 4 | --background: #F9F9FB; 5 | --layer: #FFFFFF; 6 | --backdrop: rgba(200,200,215,0.65); 7 | --outline: var(--primary); 8 | 9 | --primary: var(--blue); 10 | --primaryText: var(--blueText); 11 | --red: #FF6168; 12 | --redText: #150001; 13 | --orange: #FF6A3E; 14 | --orangeText: #320B06; 15 | --yellow: #FDD352; 16 | --yellowText: #4B3B06; 17 | --green: #46D284; 18 | --greenText: #14291D; 19 | --blue: #0047DB; 20 | --blueText: #EAF6FF; 21 | --sky: #00B6FF; 22 | --skyText: #002738; 23 | --purple: #5B2DAB; 24 | --purpleText: #F6F2FD; 25 | --pink: #FF9EB8; 26 | --pinkText: #200005; 27 | 28 | 29 | --black: #07071A; 30 | --white: #FFFFFF; 31 | --grey: #E4E5EE; 32 | --greyText: var(--text); 33 | --greyFade: rgba(236, 237, 247, 0.7); 34 | --border: rgba(24, 48, 98, 0.65); 35 | --borderText: var(--background); 36 | --borderFade: rgba(23, 23, 51, 0.2); 37 | 38 | --text: #030A1D; 39 | --textFade: rgba(3, 9, 50, 0.5); 40 | 41 | --shadow: rgba(0, 0, 50, 0.08) 0px 4px 12px; 42 | --shadow-Hover: 0px 2px 20px -5px rgba(30, 30, 60, 0.15); 43 | } 44 | 45 | // Dark Theme 46 | [data-theme="dark"] { 47 | --background: #191923; 48 | --layer: var(--black); 49 | --backdrop: rgba(50,50,60,0.6); 50 | --outline: var(--yellow); 51 | 52 | --primary: var(--blue); 53 | --primaryText: var(--blueText); 54 | --red: #FF414D; 55 | --redText: #150001; 56 | --orange: #FF7247; 57 | --orangeText: #320B06; 58 | --yellow: #FDD352; 59 | --yellowText: #4B3B06; 60 | --green: #3CC878; 61 | --greenText: #14291D; 62 | --blue: #0047DB; 63 | --blueText: #E0EBFF; 64 | --sky: #00B6FF; 65 | --skyText: #002738; 66 | --purple: #5325A1; 67 | --purpleText: #F3ECFD; 68 | --pink: #FF9EB8; 69 | --pinkText: #61001A; 70 | 71 | --black: #000000; 72 | --white: #FFFFFF; 73 | --text: #F5F5FF; 74 | --textFade: rgba(255,255,255, 0.5); 75 | --grey: #2E2F3F; 76 | --greyFade: rgba(46, 47, 63, 0.7); 77 | --greyText: var(--white); 78 | --border: rgba(215,215,255,0.35); 79 | --borderFade: rgba(215,215,255,0.15); 80 | --borderText: var(--background); 81 | 82 | --shadow: rgba(0,0,0, .25) 0px 4px 12px; 83 | --shadow-Hover: 0px 2px 20px -5px rgba(30, 30, 60, 0.1); 84 | 85 | } 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/styles/themes/greys.scss: -------------------------------------------------------------------------------- 1 | // Primary Colors Theme 2 | 3 | [data-theme="greys"] { 4 | --background: rgba(255,255,255,1); 5 | --layer: rgba(255,255,255,1); 6 | --backdrop: rgba(0,0,0,0.25); 7 | --outline: var(--text); 8 | 9 | --primary: var(--blue); 10 | --primaryText: var(--blueText); 11 | --blue: rgba(0,0,0,1); 12 | --blueText: rgba(255,255,255,1); 13 | --orange: rgba(30,30,30,1);; 14 | --orangeText: rgba(255,255,255,1); 15 | --yellow: rgba(60,60,60,1); 16 | --yellowText: rgba(255,255,255,1); 17 | --green: rgba(90,90,90,1); 18 | --greenText: rgba(255,255,255,1); 19 | --red: rgba(120,120,120,1); 20 | --redText: rgba(0,0,0,1); 21 | --sky: rgba(150,150,150,1); 22 | --skyText: rgba(0,0,0,1); 23 | --purple: rgba(180,180,180,1); 24 | --purpleText: rgba(0,0,0,1); 25 | --pink: rgba(210,210,210,1); 26 | --pinkText: rgba(0,0,0,1); 27 | 28 | --black: rgba(0,0,0,1); 29 | --white: rgba(255,255,255,1); 30 | --grey: rgba(200,200,200,1); 31 | --greyFade: rgba(127,127,127,0.25); 32 | --greyText: rgba(0,0,0,1); 33 | --border: rgba(0,0,0,1); 34 | --borderFade: rgba(0,0,0,0.25); 35 | --borderText: rgba(255,255,255,1); 36 | 37 | --text: rgba(0,0,0,1); 38 | --textFade: rgba(0,0,0,0.75); 39 | 40 | --shadow: rgba(0, 0, 50, 0.08) 0px 4px 12px; 41 | --shadow-Hover: 0px 2px 20px -5px rgba(30, 30, 60, 0.15); 42 | } 43 | 44 | 45 | 46 | [data-theme="greys_dark"] { 47 | --background: rgba(0,0,0,1); 48 | --layer: rgba(14,14,14,1); 49 | --backdrop: rgba(140,140,140,0.35); 50 | --outline: var(--text); 51 | 52 | --primary: var(--blue); 53 | --primaryText: var(--blueText); 54 | --blue: rgba(255,255,255,1); 55 | --blueText: rgba(0,0,0,1); 56 | --orange: rgba(220,220,220,1);; 57 | --orangeText: rgba(0,0,0,1); 58 | --yellow: rgba(190,190,190,1); 59 | --yellowText: rgba(0,0,0,1); 60 | --green: rgba(160,160,160,1); 61 | --greenText: rgba(0,0,0,1); 62 | --red: rgba(130,130,130,1); 63 | --redText: rgba(0,0,0,1); 64 | --sky: rgba(100,100,100,1); 65 | --skyText: rgba(255,255,255,1); 66 | --purple: rgba(70,70,70,1); 67 | --purpleText: rgba(255,255,255,1); 68 | --pink: rgba(40,40,40,1); 69 | --pinkText: rgba(255,255,255,1); 70 | 71 | --black: rgba(0,0,0,1); 72 | --white: rgba(255,255,255,1); 73 | --grey: rgba(127,127,127,1); 74 | --greyFade: rgba(127,127,127,0.25); 75 | --greyText: rgba(0,0,0,1); 76 | --border: rgba(255,255,255,1); 77 | --borderFade: rgba(255,255,255,0.25); 78 | --borderText: rgba(0,0,0,1); 79 | 80 | --text: #FFFFFF; 81 | --textFade: rgba(255,255,255,0.75); 82 | 83 | --shadow: rgba(0, 0, 50, 0.08) 0px 4px 12px; 84 | --shadow-Hover: 0px 2px 20px -5px rgba(30, 30, 60, 0.15); 85 | } 86 | -------------------------------------------------------------------------------- /src/styles/themes/muted.scss: -------------------------------------------------------------------------------- 1 | // Muted Theme 2 | 3 | 4 | [data-theme="muted"] { 5 | --background: #ffffff; 6 | --layer: #FFFFFF; 7 | --backdrop: rgba(0,0,65,0.25); 8 | --outline: var(--primary); 9 | 10 | --primary: var(--blue); 11 | --primaryText: var(--blueText); 12 | --red: #FF8088; 13 | --redText: #420004; 14 | --orange: #FFA480; 15 | --orangeText: #571900; 16 | --yellow: #ffd84f; 17 | --yellowText: #523F00; 18 | --green: #0DF4CD; 19 | --greenText: #064B38; 20 | --blue: #85B0F9; 21 | --blueText: #062151; 22 | --sky: #6BE9FF; 23 | --skyText: #004857; 24 | --purple: #D399FA; 25 | --purpleText: #330651; 26 | --pink: #FEC8C8; 27 | --pinkText: #5E0303; 28 | 29 | --black: #07071A; 30 | --white: #FFFFFF; 31 | --grey: #ECEDFD; 32 | --greyFade: rgba(236, 237, 247, 0.7); 33 | --greyText: var(--text); 34 | --border: #183062; 35 | --borderFade: rgba(3, 3, 51, 0.2); 36 | --borderText: var(--background); 37 | 38 | --text: #030932; 39 | --textFade: rgba(3, 9, 50, 0.5); 40 | 41 | --shadow: rgba(0, 0, 50, 0.08) 0px 4px 12px; 42 | --shadow-Hover: 0px 2px 20px -5px rgba(30, 30, 60, 0.15); 43 | } 44 | 45 | // Dark Theme 46 | [data-theme="muted_dark"] { 47 | --background: var(--black); 48 | --layer: #121220; 49 | --backdrop: rgba(135,135,150,0.3); 50 | --outline: var(--yellow); 51 | 52 | --primary: var(--blue); 53 | --primaryText: var(--blueText); 54 | --red: #b45a5f; 55 | --redText: #420004; 56 | --orange: #b37359; 57 | --orangeText: #571900; 58 | --yellow: #b39838; 59 | --yellowText: #523F00; 60 | --green: #13ac90; 61 | --greenText: #064B38; 62 | --blue: #5e7caf; 63 | --blueText: #062151; 64 | --sky: #4ca4b3; 65 | --skyText: #004857; 66 | --purple: #946baf; 67 | --purpleText: #330651; 68 | --pink: #b28d8c; 69 | --pinkText: #5E0303; 70 | 71 | --black: #000000; 72 | --white: #FFFFFF; 73 | --text: #FFFFFF; 74 | --textFade: rgba(255,255,255, 0.5); 75 | --grey: #242535; 76 | --greyFade: rgba(36, 37, 53, 0.7); 77 | --greyText: var(--white); 78 | --border: #4a5876; 79 | --borderText: var(--background); 80 | --borderFade: rgba(124, 148, 198, 0.2); 81 | 82 | --shadow: rgba(0,0,0, .25) 0px 4px 12px; 83 | --shadow-Hover: 0px 2px 20px -5px rgba(30, 30, 60, 0.1); 84 | 85 | } -------------------------------------------------------------------------------- /src/styles/themes/primary.scss: -------------------------------------------------------------------------------- 1 | // Primary Colors Theme 2 | 3 | [data-theme="primary"] { 4 | --background: #FAFAFF; 5 | --layer: #FFFFFF; 6 | --backdrop: rgba(0,0,50,0.25); 7 | --outline: var(--primary); 8 | 9 | --primary: var(--blue); 10 | --primaryText: var(--blueText); 11 | --red: #FF0000; 12 | --redText: #000000; 13 | --orange: #FF6600; 14 | --orangeText: #000000; 15 | --yellow: #FFFF00; 16 | --yellowText: #000000; 17 | --green: #00FF00; 18 | --greenText: #000000; 19 | --blue: #0000FF; 20 | --blueText: #FFFFFF; 21 | --sky: #00CAFF; 22 | --skyText: #000000; 23 | --purple: #6600FF; 24 | --purpleText: #FFFFFF; 25 | --pink: #FFC0CB; 26 | --pinkText: #000000; 27 | 28 | --black: #000000; 29 | --white: #FFFFFF; 30 | --grey: #E0E1F1; 31 | --greyFade: rgba(236, 237, 247, 0.7); 32 | --greyText: var(--text); 33 | --border: #000000; 34 | --borderFade: rgba(0,0,0, 0.2); 35 | --borderText: var(--white); 36 | 37 | --text: #000; 38 | --textFade: rgba(0,0,0, 0.5); 39 | 40 | --shadow: rgba(0, 0, 0, 0.08) 0px 4px 12px; 41 | --shadow-Hover: 0px 2px 20px -5px rgba(30, 30, 60, 0.15); 42 | } 43 | 44 | 45 | 46 | [data-theme="primary_dark"] { 47 | --background: #000000; 48 | --layer: #141414; 49 | --backdrop: rgba(150,150,150,0.25); 50 | --outline: var(--yellow); 51 | 52 | --primary: var(--blue); 53 | --primaryText: var(--blueText); 54 | --red: #FF0000; 55 | --redText: #000000; 56 | --orange: #FF6600; 57 | --orangeText: #000000; 58 | --yellow: #FFFF00; 59 | --yellowText: #000000; 60 | --green: #00FF00; 61 | --greenText: #000000; 62 | --blue: #0000FF; 63 | --blueText: #FFFFFF; 64 | --sky: #00CAFF; 65 | --skyText: #000000; 66 | --purple: #6600FF; 67 | --purpleText: #FFFFFF; 68 | --pink: #FFC0CB; 69 | --pinkText: #000000; 70 | 71 | --black: #000000; 72 | --white: #FFFFFF; 73 | --grey: #282828; 74 | --greyFade: rgba(40,40,40, 0.7); 75 | --greyText: var(--white); 76 | --border: #949494; 77 | --borderFade: rgba(148, 148, 148, 0.2); 78 | --borderText: var(--background); 79 | 80 | --text: #FFFFFF; 81 | --textFade: rgba(255,255,255, 0.6); 82 | 83 | --shadow: rgba(0,0,0, .25) 0px 4px 12px; 84 | --shadow-Hover: 0px 2px 20px -5px rgba(30, 30, 60, 0.15); 85 | } 86 | -------------------------------------------------------------------------------- /src/styles/typography.scss: -------------------------------------------------------------------------------- 1 | //! Typography 2 | // Header, Paragraph, and other text styles 3 | 4 | h1, 5 | h2, 6 | h3, 7 | h4, 8 | h5, 9 | h6 { 10 | margin-top: 1.5rem; 11 | margin-bottom: 0; 12 | line-height: 1.5rem; 13 | } 14 | h1 { 15 | font-size: 4.042rem; 16 | line-height: 4.5rem; 17 | margin-top: 3rem; 18 | margin-bottom: 1rem; 19 | font-weight: 700; 20 | letter-spacing: -1.5px; 21 | } 22 | h2 { 23 | font-size: 3.228rem; 24 | line-height: 3.5rem; 25 | margin-top: 3rem; 26 | margin-bottom: 0.9rem; 27 | font-weight: 700; 28 | } 29 | h3 { 30 | font-size: 2.214rem; 31 | margin-bottom: 0.8rem; 32 | line-height: 2.5rem; 33 | font-weight: 700; 34 | } 35 | h4 { 36 | font-size: 1.407rem; 37 | margin-bottom: 0.7rem; 38 | line-height: 1.5rem; 39 | font-weight: 700; 40 | } 41 | h5 { 42 | font-size: 1.01333333rem; 43 | font-weight: 700; 44 | margin-bottom: 0.6rem; 45 | } 46 | h6 { 47 | font-size: 0.89rem; 48 | font-weight: 700; 49 | margin-bottom: 0.5rem; 50 | color: var(--textFade) 51 | } 52 | 53 | h1 small, 54 | h2 small, 55 | h3 small, 56 | h4 small, 57 | h5 small, 58 | h6 small{ 59 | font-size: 0.55em; 60 | display: block; 61 | font-weight: 500; 62 | } 63 | 64 | 65 | // Text Elements 66 | // Paragraphs 67 | p, 68 | .paragraph{ 69 | font-size: 1rem; 70 | line-height: 1.75rem; 71 | max-width: 700px; 72 | padding: 12px 0; 73 | color: var(--text); 74 | font-weight: 400; 75 | 76 | &.tight{ 77 | line-height: 1.25rem; 78 | } 79 | 80 | // Decrease sizing 81 | &.small{ 82 | font-size: 0.8rem; 83 | line-height: 1.4rem; 84 | 85 | &.tight{ 86 | line-height: 1.1em; 87 | } 88 | } 89 | // Increase sizing 90 | &.big{ 91 | font-size: 1.2rem; 92 | line-height: 2.4rem; 93 | } 94 | 95 | // Bold 96 | b{ 97 | font-weight: 700; 98 | } 99 | 100 | &.no-padding{ 101 | padding-top: 0; 102 | padding-bottom: 0; 103 | } 104 | } 105 | // Emphasis 106 | em, 107 | .italic{ 108 | font-style: italic; 109 | } 110 | 111 | // Links 112 | a{ 113 | color: var(--primary); 114 | transition: var(--transition); 115 | letter-spacing: -0.1px; 116 | font-weight: bold; 117 | text-decoration: none; 118 | 119 | &:hover{ 120 | cursor: pointer; 121 | transition: var(--transition); 122 | outline-color: transparent; 123 | 124 | :not(i) :not(p){ 125 | text-decoration: underline; 126 | } 127 | } 128 | 129 | } 130 | 131 | 132 | // Lists 133 | ul, 134 | ol{ 135 | display: block; 136 | box-sizing: border-box; 137 | padding: 6px 0 6px 25px; 138 | 139 | li{ 140 | line-height: 1.4rem; 141 | padding: 2px 0; 142 | } 143 | } 144 | ol{ 145 | list-style-type: decimal; 146 | } 147 | ul{ 148 | list-style-type: disc; 149 | } 150 | 151 | 152 | 153 | // Modifiers 154 | 155 | .text-small{ 156 | font-size: 0.8em; 157 | } 158 | .text-smaller{ 159 | font-size: 0.7em !important; 160 | } 161 | .text-large{ 162 | font-size: 1.2em; 163 | } 164 | .text-larger{ 165 | font-size: 1.45em; 166 | } 167 | .text-light{ 168 | font-weight: 400; 169 | color: var(--textFade); 170 | } 171 | .text-fade{ 172 | color: var(--textFade); 173 | } 174 | .text-bright{ 175 | color: var(--text); 176 | } 177 | // Colored Text 178 | .text-primary{ 179 | color: var(--blue); 180 | } 181 | .text-red{ 182 | color: var(--red); 183 | } 184 | .text-blue{ 185 | color: var(--blue); 186 | } 187 | .text-green{ 188 | color: var(--greenDark); 189 | } 190 | .text-yellow{ 191 | color: var(--yellowDark); 192 | } 193 | .text-orange{ 194 | color: var(--orange); 195 | } 196 | .text-purple{ 197 | color: var(--purple); 198 | } 199 | .text-pink{ 200 | color: var(--pink); 201 | } 202 | .text-grey{ 203 | color: var(--greyDark); 204 | } 205 | 206 | 207 | // Horizontal Rule 208 | hr{ 209 | border: none; 210 | border-top: 1px solid; 211 | border-color: var(--borderFade); 212 | height: 1px; 213 | overflow: hidden; 214 | margin: 8px auto; 215 | } 216 | 217 | // Small 218 | small{ 219 | font-size: 0.85em; 220 | line-height: 1.4em; 221 | } 222 | 223 | // Code & Pre 224 | code{ 225 | width: fit-content; 226 | background-color: var(--greyLight); 227 | font-family: var(--monospace); 228 | box-sizing: border-box; 229 | padding: 4px; 230 | background-color: var(--grey); 231 | border-radius: calc(var(--borderRadius) / 2); 232 | 233 | b{ 234 | font-weight: 700; 235 | } 236 | i{ 237 | font-style: italic; 238 | } 239 | 240 | &.full-width{ 241 | width: 100%; 242 | } 243 | } 244 | pre{ 245 | max-width: 80vw; 246 | overflow: auto; 247 | display: block; 248 | scrollbar-width: none; 249 | // Hidden 250 | &::-webkit-scrollbar{ 251 | width: 0px; 252 | display: none; 253 | } 254 | // Scrollbar handle/thumb 255 | &::-webkit-scrollbar-thumb{ 256 | display: none; 257 | width: 0px; 258 | } 259 | } -------------------------------------------------------------------------------- /src/styles/ui.scss: -------------------------------------------------------------------------------- 1 | //! UI Elements 2 | // Styles for misc global UI elements 3 | // 4 | // Tags 5 | // Breadcrumbs 6 | // Tooltips 7 | // Page Footer 8 | // Notification Dots 9 | // Keyboard Shortcut Preview 10 | 11 | // Tag 12 | .tag{ 13 | background-color: var(--background); 14 | color: var(--text); 15 | font-size: 0.85rem; 16 | padding: 0.3rem 8px; 17 | font-weight: 600; 18 | border-radius: var(--borderRadius); 19 | border-radius: calc(var(--borderRadius) * 2); 20 | white-space: nowrap; 21 | border: 1px solid var(--text); 22 | text-decoration: none !important; 23 | 24 | i:first-child{ 25 | position: relative; 26 | left: -4px; 27 | } 28 | i:last-child{ 29 | margin-left: 4px; 30 | } 31 | 32 | &.small{ 33 | font-size: .65em; 34 | padding: 0.2rem 6px; 35 | font-weight: bold; 36 | border-width: 2px; 37 | } 38 | 39 | &.red{ 40 | // background-color: var(--red); 41 | // color: var(--redText); 42 | border-color: var(--red); 43 | background-color: var(--background); 44 | color: var(--red); 45 | } 46 | &.orange{ 47 | border-color: var(--orange); 48 | background-color: var(--background); 49 | color: var(--orange); 50 | } 51 | &.yellow{ 52 | border-color: var(--yellow); 53 | background-color: var(--background); 54 | color: var(--yellow); 55 | } 56 | &.green{ 57 | border-color: var(--green); 58 | background-color: var(--background); 59 | color: var(--green); 60 | } 61 | &.blue{ 62 | border-color: var(--blue); 63 | background-color: var(--background); 64 | color: var(--blue); 65 | } 66 | &.sky{ 67 | border-color: var(--sky); 68 | background-color: var(--background); 69 | color: var(--sky); 70 | } 71 | &.pink{ 72 | border-color: var(--pink); 73 | background-color: var(--background); 74 | color: var(--pink); 75 | } 76 | &.purple{ 77 | border-color: var(--purple); 78 | background-color: var(--background); 79 | color: var(--purple); 80 | } 81 | &.grey{ 82 | border-color: var(--border); 83 | background-color: var(--background); 84 | color: var(--border); 85 | } 86 | 87 | } 88 | 89 | 90 | // Breadcrumbs 91 | .breadcrumbs{ 92 | display: flex; 93 | width: 100%; 94 | box-sizing: border-box; 95 | justify-content: flex-start; 96 | flex-wrap: wrap; 97 | 98 | @media (max-width: $screenSM) { 99 | // flex-wrap: nowrap; 100 | // overflow: auto; 101 | } 102 | 103 | // Hide scrollbars 104 | // Hidden 105 | &::-webkit-scrollbar{ 106 | width: 0px; 107 | display: none; 108 | } 109 | // Scrollbar handle/thumb 110 | &::-webkit-scrollbar-thumb{ 111 | display: none; 112 | width: 0px; 113 | } 114 | 115 | .crumb{ 116 | display: flex; 117 | font-size: 14px; 118 | font-weight: 600; 119 | letter-spacing: 0.35px; 120 | color: var(--textFade); 121 | transition: var(--transition); 122 | text-transform: capitalize; 123 | width: fit-content; 124 | 125 | // Shrink on mobile 126 | @media (max-width: $screenSM) { 127 | margin-bottom: 6px; 128 | } 129 | @media (max-width: $screenSM) { 130 | font-size: 13px; 131 | text-align: center; 132 | } 133 | 134 | span{ 135 | height: 100%; 136 | display: flex; 137 | flex-direction: column; 138 | justify-content: center; 139 | margin-right: 8px; 140 | white-space: pre; 141 | text-transform: capitalize; 142 | } 143 | 144 | // Add arrow before each child 145 | &:before{ 146 | content: '\f30b'; 147 | font-family: var(--fontAwesome); 148 | display: block; 149 | padding: 0px 8px 0 0; 150 | color: var(--textFadeest); 151 | position: relative; 152 | top: 1px; 153 | font-weight: 500; 154 | display: flex; 155 | flex-direction: column; 156 | justify-content: center; 157 | } 158 | 159 | // Remove arrow before first child 160 | &:first-child{ 161 | &:before{ 162 | display: none; 163 | } 164 | } 165 | 166 | // Hover 167 | &:hover{ 168 | text-decoration: none; 169 | color: var(--textFade); 170 | transition: var(--transition); 171 | cursor: pointer; 172 | } 173 | 174 | // Highlight last (current) crumb 175 | &.active{ 176 | color: var(--text); 177 | } 178 | 179 | } 180 | 181 | &.no-hover .crumb:hover{ 182 | cursor: default; 183 | } 184 | } 185 | 186 | 187 | 188 | 189 | // Shadow and hover stuff 190 | // Shadow and hover stuff 191 | // Box shadow 192 | .box-shadow{ 193 | box-shadow: var(--shadow); 194 | 195 | &.hoverable:hover{ 196 | box-shadow: var(--shadow-Hover); 197 | } 198 | } 199 | .hover-shadow{ 200 | box-shadow: var(--shadow); 201 | 202 | &:hover{ 203 | cursor: pointer; 204 | box-shadow: var(--shadow-Hover); 205 | transform: scale(1.015); 206 | } 207 | } 208 | 209 | // Thicker shadow normally, hover shrink 210 | .reverse-hover-shadow{ 211 | box-shadow: var(--shadow-Hover); 212 | 213 | &:hover{ 214 | cursor: pointer; 215 | box-shadow: var(--shadow); 216 | transform: scale(0.98); 217 | } 218 | } 219 | 220 | 221 | // Notification Dot 222 | // Floats little dot relative top right on element 223 | .notification-dot{ 224 | position: relative; 225 | &:after{ 226 | content: ""; 227 | display: block; 228 | height: 6px; 229 | width: 6px; 230 | position: absolute; 231 | bottom: 0px; 232 | left: 50%; 233 | margin-left: -3px; 234 | background-color: var(--red); 235 | border-radius: calc(var(--borderRadius) * 4) 236 | } 237 | } 238 | 239 | 240 | // Button Grid 241 | .button-grid{ 242 | display: flex; 243 | flex-wrap: wrap; 244 | gap: 15px; 245 | flex-basis: 33%; 246 | 247 | button{ 248 | flex-grow: 3; 249 | } 250 | } 251 | 252 | 253 | // Keyboard Shortcut preview 254 | .keyboard-shortcut{ 255 | display: flex; 256 | gap: 5px; 257 | 258 | b{ 259 | position: relative; 260 | padding: 5px; 261 | border: 1px solid var(--borderFade); 262 | border-radius: 4px; 263 | font-size: 0.75em; 264 | box-shadow: 0px 3px 0px 2px rgba(150, 150, 150, 0.24); 265 | &:hover{ 266 | top: 3px; 267 | box-shadow: 0px 1px 0px 1px rgba(150, 150, 150, 0.24) 268 | } 269 | } 270 | span,i{ 271 | display: flex; 272 | flex-direction: column; 273 | justify-content: center; 274 | font-size: 0.8em; 275 | } 276 | span{ 277 | padding-left: 10px; 278 | } 279 | } -------------------------------------------------------------------------------- /src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | //! Variables 2 | 3 | // Responsive Variables must be sass variables 4 | // because CSS variables can't be used in @media 5 | 6 | // Custom Fonts - Variable 7 | // Sans - Inter 8 | @font-face { 9 | font-family: 'Inter'; 10 | src: url('~@/assets/fonts/inter.ttf') format('truetype'); 11 | font-weight: 100 1000; 12 | } 13 | // Serif - Soruce Serif 14 | @font-face { 15 | font-family: 'Source Serif'; 16 | src: url('~@/assets/fonts/sourceSerif.ttf') format('truetype'); 17 | font-weight: 100 1000; 18 | } 19 | // Mono - Source Code 20 | @font-face { 21 | font-family: 'Source Code'; 22 | src: url('~@/assets/fonts/sourceCode.ttf') format('truetype'); 23 | font-weight: 100 1000; 24 | } 25 | 26 | 27 | $screenXS: 380px; 28 | $screenSM: 768px; 29 | $screenLG: 1020px; 30 | $screenXL: 1400px; 31 | 32 | // Root variables can be toggled by themes 33 | :root { 34 | 35 | // Fonts 36 | // --sans: var(--systemFont); 37 | // --monospace: monospace; 38 | // --serif: serif; 39 | --sans: "Inter", sans-serif; 40 | --monospace: "Source Code", monospace; 41 | --serif: "Source Serif", serif; 42 | --fontAwesome: 'Font Awesome 6 Free'; 43 | --fontAwesomeBrands: 'Font Awesome 6 Brands'; 44 | --systemFont: -apple-system, BlinkMacSystemFont, "Segoe UI", "Verdana", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 45 | --baseFontSize: 1rem; 46 | 47 | // Transitions 48 | --transition: 0.07s; 49 | // Measurements 50 | --maxWidthSmall: 450px; 51 | --maxWidth: 700px; 52 | --maxWidthLarge: 1000px; 53 | 54 | // Sizes 55 | --inputHeight: 44px; 56 | --topBarHeight: 60px; 57 | --sidePadding: 60px; 58 | @media (max-width: $screenSM) {--sidePadding: 15px;} 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/views/apps/AppTemplate.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 74 | 75 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/views/other/docs/Changelog.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 41 | 42 | 70 | 71 | -------------------------------------------------------------------------------- /src/views/other/docs/PrivacyPolicy.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 78 | 79 | 106 | 107 | -------------------------------------------------------------------------------- /src/views/other/docs/Sponsor.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 83 | 84 | 108 | 109 | -------------------------------------------------------------------------------- /src/views/other/docs/TermsOfService.vue: -------------------------------------------------------------------------------- 1 | 5 | 89 | 90 | 114 | 115 | -------------------------------------------------------------------------------- /src/views/other/docs/design/Design.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 54 | 55 | 85 | 86 | -------------------------------------------------------------------------------- /src/views/other/docs/design/DesignColors.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 59 | 60 | 100 | 101 | -------------------------------------------------------------------------------- /src/views/other/docs/design/DesignForms.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 244 | 245 | 271 | 272 | -------------------------------------------------------------------------------- /src/views/other/docs/design/DesignText.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 68 | 69 | 90 | 91 | -------------------------------------------------------------------------------- /src/views/other/docs/design/DesignUI.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 208 | 209 | 266 | 267 | -------------------------------------------------------------------------------- /src/views/other/error.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 35 | 36 | 49 | 50 | 82 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | require = require("esm")(module); 2 | const { routes } = require("./src/routes.js"); 3 | const ImageminPlugin = require("imagemin-webpack-plugin").default; 4 | 5 | module.exports = { 6 | chainWebpack: (config) => { 7 | config.resolve.symlinks(false); 8 | }, 9 | configureWebpack: { 10 | plugins: [ 11 | new ImageminPlugin({ 12 | disable: process.env.NODE_ENV !== "production", 13 | }) 14 | ] 15 | }, 16 | devServer: {// Environment configuration 17 | host: "0.0.0.0", 18 | socket: "socket", 19 | port: 8080, 20 | https: false, 21 | hotOnly: false, 22 | public: "0.0.0.0:8080", 23 | disableHostCheck:true, 24 | open: true // Configure to automatically start the browser 25 | }, 26 | pwa: { 27 | name: process.env.VUE_APP_COMPANY_NAME, 28 | workboxOptions: { 29 | skipWaiting: true, 30 | } 31 | }, 32 | css: { 33 | loaderOptions: { 34 | scss: { 35 | additionalData: "@import \"~@/styles/variables.scss\";" 36 | }, 37 | } 38 | }, 39 | pluginOptions: { 40 | webpackBundleAnalyzer: { 41 | openAnalyzer: false, 42 | analyzerMode: process.env.NODE_ENV === "development" ? "server" : "static", 43 | reportFilename: "build_analysis.html" 44 | }, 45 | sitemap: { 46 | baseURL: "https://keyframes.app", 47 | outputDir: "dist", 48 | trailingSlash: true, 49 | defaults: { 50 | lastmod: new Date(), 51 | changefreq: "daily", 52 | priority: 0.5, 53 | }, 54 | routes, 55 | }, 56 | }, 57 | productionSourceMap: false, 58 | publicPath: process.env.VUE_APP_BASE_PATH 59 | }; --------------------------------------------------------------------------------