├── .editorconfig ├── .env.example ├── .gitignore ├── .ncurc.json ├── .prettierignore ├── LICENSE.md ├── README.md ├── package-lock.json ├── package.json ├── pages ├── error.vue └── index.vue ├── public ├── assets │ ├── .gitkeep │ └── images │ │ └── share.png ├── favicon.ico ├── glxp │ └── .gitkeep └── vendors │ └── modernizr.js ├── src ├── app.vue ├── assets │ ├── audios │ │ └── .gitkeep │ ├── fonts │ │ └── .gitkeep │ ├── images │ │ └── .gitkeep │ └── videos │ │ └── .gitkeep ├── components │ ├── Button.vue │ ├── Grid.vue │ ├── Icon │ │ ├── Icons │ │ │ └── .gitkeep │ │ └── index.vue │ ├── InView.vue │ └── LanguageSwitcher.vue ├── glxp │ └── .gitkeep ├── index.html ├── index.js ├── locales │ ├── en.json │ ├── fr.json │ └── index.js ├── router │ ├── index.js │ ├── index.routes.js │ └── index.vue ├── stores │ └── counter.js ├── styles │ ├── base │ │ ├── _base.scss │ │ ├── _fonts.scss │ │ ├── _reset.scss │ │ └── _typography.scss │ ├── global.scss │ ├── utils │ │ ├── _animations.scss │ │ ├── _breakpoints.scss │ │ ├── _easings.scss │ │ ├── _functions.scss │ │ ├── _utilities.scss │ │ └── _variables.scss │ └── vendors │ │ └── sass-mq │ │ └── _mq.scss └── utils │ ├── constants.js │ ├── grid.js │ ├── say-hello.js │ └── translation.js └── vite.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # See: https://nuxtjs.org/docs/directory-structure/nuxt-config#runtimeconfig 2 | 3 | # Application Mode - `app` or `webgl` or... 4 | # `app` - Mixes UI and WebGL 5 | # `webgl` - Can be used to isolate WebGL and remove UI 6 | VUE_APP_MODE="app" 7 | 8 | # Preview Mode 9 | # Can be used to hide features on production but enable them on the staging. 10 | # It has nothing to do with the preview mode of a CMS. 11 | VUE_APP_PREVIEW="true" 12 | 13 | # Server Port 14 | # Local Server Port 15 | # See: https://stackoverflow.com/a/66389044 16 | VITE_SERVER_PORT="3000" 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Testing 5 | /coverage 6 | 7 | # Build 8 | /dist 9 | /build 10 | 11 | # Misc 12 | .DS_Store 13 | 14 | # Logs 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Local `env` Files 20 | .env 21 | .env.test 22 | .env.local 23 | .env.*.local 24 | .env.production 25 | .env.development 26 | 27 | # IDEs 28 | /.idea 29 | /.vscode 30 | 31 | # Vercel 32 | .vercel 33 | 34 | # Samples 35 | sample.* 36 | -------------------------------------------------------------------------------- /.ncurc.json: -------------------------------------------------------------------------------- 1 | { 2 | "upgrade": true, 3 | "interactive": true, 4 | "reject": [] 5 | } 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Vite 2 | vite.config.js 3 | 4 | # Build 5 | /dist 6 | /build 7 | 8 | # Front-End 9 | src/index.html 10 | 11 | public/**/*.ts 12 | public/**/*.js 13 | public/**/*.tsx 14 | public/**/*.jsx 15 | public/**/*.vue 16 | 17 | src/glxp/**/*.ts 18 | src/glxp/**/*.js 19 | src/glxp/**/*.tsx 20 | src/glxp/**/*.jsx 21 | src/glxp/**/*.vue 22 | 23 | src/vendors/**/*.ts 24 | src/vendors/**/*.js 25 | src/vendors/**/*.tsx 26 | src/vendors/**/*.jsx 27 | src/vendors/**/*.vue 28 | 29 | # Locales 30 | src/locales/**/*.ts 31 | src/locales/**/*.js 32 | 33 | # Utilities 34 | src/utils/helpers/**/*.ts 35 | src/utils/helpers/**/*.js 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Dogstudio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Dogstudio Vue Starter 2 | 3 | The repository has all the features needed to build blazing-fast experience. 4 | 5 | - Vue 3 6 | - Vite 7 | - Prettier 8 | - SCSS/SASS 9 | 10 | ## Prerequisites 11 | 12 | - [Node.js - LTS](https://nodejs.org/en/download/) 13 | 14 | ## Getting Started 15 | 16 | ```bash 17 | git clone git@github.com:Dogstudio/dogstudio-vue-starter.git my-new-app 18 | ``` 19 | 20 | ```bash 21 | cd my-new-app 22 | cp .env.example .env.local 23 | ``` 24 | 25 | ```bash 26 | npm i 27 | npm run dev 28 | ``` 29 | 30 | ## Documentation 31 | 32 | We recommend the following documentations to master the stack: 33 | 34 | - [Vue](https://vuejs.org/guide/introduction.html) 35 | - [Vite](https://vitejs.dev/) 36 | - [Pinia](https://pinia.vuejs.org/introduction.html) 37 | - [Style Guide](https://vuejs.org/v2/style-guide/) 38 | - [Atomic Design Pattern](https://atomicdesign.bradfrost.com/table-of-contents/) 39 | 40 | ## References 41 | 42 | ```bash 43 | # Run Vue in development-mode with a local server. 44 | # Visit http://localhost:3000 to see your application running. 45 | npm run dev 46 | 47 | # Run Vue in production-mode and creates a production build. 48 | npm run build 49 | 50 | # Run Vue in production-mode with a local server. 51 | # Visit http://localhost:3000 to see your production build. 52 | npm run start 53 | 54 | # Run ESLint for JS & Vue files 55 | npm run lint 56 | 57 | # Upgrade the dependencies and devDependencies with interactive mode 58 | # See: https://www.npmjs.com/package/npm-check-updates 59 | npm run upgrade 60 | ``` 61 | 62 | ## Example 63 | 64 | An example is available on the `demo` branch to test all the features included in the repository: 65 | 66 | ```bash 67 | git checkout demo 68 | ``` 69 | 70 | ```bash 71 | npm i 72 | npm run dev 73 | ``` 74 | 75 | ## Contributing 76 | 77 | Please submit issues, pull requests or [contact us](devops+vue-starter@dogstudio.be). We are open to all kind of contributions. 78 | 79 | ## License 80 | 81 | The `dogstudio-vue-starter` repository is [MIT licensed](/LICENSE.md). 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dogstudio/dogstudio-vue-starter", 3 | "type": "module", 4 | "version": "1.0.2", 5 | "license": "MIT", 6 | "private": true, 7 | "homepage": "https://github.com/Dogstudio/dogstudio-vue-starter", 8 | "description": "Opinionated Vue starter by Dogstudio", 9 | "scripts": { 10 | "dev": "vite --host", 11 | "build": "vite build", 12 | "preview": "vite preview", 13 | "upgrade": "ncu" 14 | }, 15 | "engineStrict": true, 16 | "engines": { 17 | "npm": ">=6.14.0", 18 | "node": ">=14.0.0" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/Dogstudio/dogstudio-vue-starter/issues", 22 | "email": "devops@dogstudio.be" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/Dogstudio/dogstudio-vue-starter.git" 27 | }, 28 | "browserslist": [ 29 | "last 2 version", 30 | "not ie > 0", 31 | "not bb > 0", 32 | "not kaios > 0", 33 | "not baidu > 0", 34 | "not ie_mob > 0", 35 | "not op_mini all" 36 | ], 37 | "dependencies": { 38 | "fast-deep-equal": "^3.1.3", 39 | "ua-parser-js": "^1.0.35", 40 | "pinia": "^2.3.1", 41 | "vue": "^3.5.13", 42 | "vue-i18n": "^9.9.1", 43 | "vue-router": "^4.5.0" 44 | }, 45 | "devDependencies": { 46 | "@vitejs/plugin-vue": "^5.2.1", 47 | "npm-check-updates": "^16.10.8", 48 | "sass": "^1.62.0", 49 | "vite": "^6.0.11", 50 | "vite-plugin-glsl": "^1.1.2" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pages/error.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | -------------------------------------------------------------------------------- /public/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-vue-starter/1717142463ab17da6163f1de2b04c26fd488d153/public/assets/.gitkeep -------------------------------------------------------------------------------- /public/assets/images/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-vue-starter/1717142463ab17da6163f1de2b04c26fd488d153/public/assets/images/share.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-vue-starter/1717142463ab17da6163f1de2b04c26fd488d153/public/favicon.ico -------------------------------------------------------------------------------- /public/glxp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-vue-starter/1717142463ab17da6163f1de2b04c26fd488d153/public/glxp/.gitkeep -------------------------------------------------------------------------------- /public/vendors/modernizr.js: -------------------------------------------------------------------------------- 1 | /*! modernizr 3.6.0 (Custom Build) | MIT * 2 | * https://modernizr.com/download/?-touchevents-webp-setclasses !*/ 3 | !function(e,n,t){function o(e,n){return typeof e===n}function A(){var e,n,t,A,a,i,s;for(var r in u)if(u.hasOwnProperty(r)){if(e=[],n=u[r],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(t=0;t 2 | import { computed, onMounted } from "vue"; 3 | 4 | // Components 5 | import Grid from "@/components/Grid.vue"; 6 | 7 | // Router 8 | import { useRoute } from "vue-router"; 9 | const route = useRoute(); 10 | 11 | // Utils 12 | import { DEV } from "@/utils/constants"; 13 | 14 | // Computed 15 | const mode = computed(() => import.meta.env.VUE_APP_MODE || "app"); 16 | const debug = computed(() => DEV && route.query.debug === "grid"); 17 | 18 | // Methods 19 | const onResize = () => { 20 | // prettier-ignore 21 | document.documentElement.style.setProperty('--real-height', `${window.innerHeight}px`) 22 | }; 23 | 24 | // Lifecycle 25 | onMounted(() => { 26 | // Forget the scroll position on refresh 27 | history.scrollRestoration = "manual"; 28 | 29 | onResize(); 30 | window.addEventListener("resize", onResize); 31 | }); 32 | 33 | 34 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/assets/audios/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-vue-starter/1717142463ab17da6163f1de2b04c26fd488d153/src/assets/audios/.gitkeep -------------------------------------------------------------------------------- /src/assets/fonts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-vue-starter/1717142463ab17da6163f1de2b04c26fd488d153/src/assets/fonts/.gitkeep -------------------------------------------------------------------------------- /src/assets/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-vue-starter/1717142463ab17da6163f1de2b04c26fd488d153/src/assets/images/.gitkeep -------------------------------------------------------------------------------- /src/assets/videos/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-vue-starter/1717142463ab17da6163f1de2b04c26fd488d153/src/assets/videos/.gitkeep -------------------------------------------------------------------------------- /src/components/Button.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 49 | 50 | 56 | -------------------------------------------------------------------------------- /src/components/Grid.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 69 | 70 | 296 | -------------------------------------------------------------------------------- /src/components/Icon/Icons/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-vue-starter/1717142463ab17da6163f1de2b04c26fd488d153/src/components/Icon/Icons/.gitkeep -------------------------------------------------------------------------------- /src/components/Icon/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 27 | 28 | 42 | -------------------------------------------------------------------------------- /src/components/InView.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 66 | 67 | 72 | -------------------------------------------------------------------------------- /src/components/LanguageSwitcher.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 84 | -------------------------------------------------------------------------------- /src/glxp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-vue-starter/1717142463ab17da6163f1de2b04c26fd488d153/src/glxp/.gitkeep -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Dogstudio | Vue Starter 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 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // Global Styles 2 | import "@/styles/global.scss"; 3 | 4 | import { createApp } from "vue"; 5 | import { createPinia } from "pinia"; 6 | 7 | // Application 8 | import App from "@/app.vue"; 9 | 10 | // Dependencies 11 | import i18n from "@/locales"; 12 | import router from "@/router"; 13 | 14 | // Create Application 15 | const app = createApp(App); 16 | 17 | // Tools 18 | app.use(router); 19 | app.use(i18n); 20 | app.use(createPinia()); 21 | 22 | // Mount 23 | app.mount("#app"); 24 | -------------------------------------------------------------------------------- /src/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "Hello World!" 3 | } 4 | -------------------------------------------------------------------------------- /src/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "Bonjour tout le monde !" 3 | } 4 | -------------------------------------------------------------------------------- /src/locales/index.js: -------------------------------------------------------------------------------- 1 | // Vue i18n 2 | import { createI18n } from 'vue-i18n' 3 | 4 | import { DEFAULT_LANGUAGE, FALLBACK_LANGUAGE } from '@/utils/constants' 5 | 6 | // Messages 7 | import en from './en.json' 8 | import fr from './fr.json' 9 | 10 | const i18n = createI18n({ 11 | legacy: false, // you must set `false`, to use Composition API 12 | globalInjection: true, // injects the translate function globally 13 | locale: DEFAULT_LANGUAGE, 14 | fallbackLocale: FALLBACK_LANGUAGE, 15 | messages: { 16 | en, 17 | fr, 18 | }, 19 | }) 20 | 21 | // Export Locales 22 | export default i18n 23 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | // Vue Router 2 | import { createRouter, createWebHistory } from 'vue-router' 3 | 4 | // Routes 5 | import routes from './index.routes' 6 | 7 | // Router 8 | const router = createRouter({ 9 | routes, 10 | history: createWebHistory(), 11 | }) 12 | 13 | // Export Router 14 | export default router 15 | -------------------------------------------------------------------------------- /src/router/index.routes.js: -------------------------------------------------------------------------------- 1 | // Utils 2 | import Translation from "@/utils/translation"; 3 | import { SUPPORTED_LANGUAGES } from "@/utils/constants"; 4 | 5 | // Root 6 | import Root from "./index.vue"; 7 | 8 | // Pages 9 | import Home from "@@/pages/index.vue"; 10 | // import Styleguide from "@@/pages/styleguide.vue"; 11 | import Error from "@@/pages/error.vue"; 12 | 13 | // Routes 14 | const routes = [ 15 | { 16 | path: `/:locale(${SUPPORTED_LANGUAGES})?`, 17 | beforeEnter: Translation.routeMiddleware, 18 | component: Root, 19 | children: [ 20 | { 21 | path: "", 22 | name: "Home", 23 | component: Home, 24 | }, 25 | // { 26 | // path: "styleguide", 27 | // name: "Styleguide", 28 | // component: Styleguide, 29 | // }, 30 | ], 31 | }, 32 | { 33 | path: "/:route(.*)*", 34 | component: Error, 35 | }, 36 | ]; 37 | 38 | export default routes; 39 | -------------------------------------------------------------------------------- /src/router/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/stores/counter.js: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue' 2 | import { defineStore } from 'pinia' 3 | 4 | export const useCounterStore = defineStore('counter', () => { 5 | const count = ref(0) 6 | const doubleCount = computed(() => count.value * 2) 7 | function increment() { 8 | count.value++ 9 | } 10 | 11 | return { count, doubleCount, increment } 12 | }) 13 | -------------------------------------------------------------------------------- /src/styles/base/_base.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | 3 | // Breakpoints List 4 | @use "../utils/breakpoints" as *; 5 | 6 | // Set up a decent box model on the root element 7 | // ----------------------------------------------------------------------------- 8 | html { 9 | box-sizing: border-box; 10 | } 11 | 12 | html, 13 | body { 14 | -webkit-touch-callout: none; 15 | -webkit-tap-highlight-color: transparent; 16 | } 17 | 18 | // Make all elements from the DOM inherit from the parent box-sizing 19 | // Since `*` has a specificity of 0, it does not override the `html` value 20 | // making all elements inheriting from the root box-sizing value 21 | // See: https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/ 22 | * { 23 | &, 24 | &::before, 25 | &::after { 26 | box-sizing: inherit; 27 | } 28 | } 29 | 30 | // Set up body size to fill screen 31 | // ----------------------------------------------------------------------------- 32 | body { 33 | width: 100%; 34 | } 35 | 36 | // Set up global SVG container 37 | // ----------------------------------------------------------------------------- 38 | .svg { 39 | display: inline-block; 40 | 41 | svg { 42 | display: block; 43 | } 44 | } 45 | 46 | // Create CSS variables and add global styles based on grid configuration 47 | // ----------------------------------------------------------------------------- 48 | @each $use, $value in $breakpoints { 49 | @if map.has-key($config, $use) { 50 | /** 51 | * Configuration: 52 | * 53 | * We get the configuration to use. 54 | */ 55 | $props: map-get($config, $use); 56 | 57 | @include mq($from: $use) using ($from) { 58 | /** 59 | * SCSS Variables: 60 | * 61 | * We build our SCSS variables to store then in CSS ones for further usage. 62 | */ 63 | $grid-columns: map-get($props, "grid-columns"); 64 | $grid-gutters: $grid-columns - 1; 65 | 66 | $grid-size: map-get($props, "grid-size"); 67 | $grid-edge: map-get($props, "grid-edge"); 68 | $grid-gutter: map-get($props, "grid-gutter"); 69 | 70 | /** 71 | * CSS Variables: 72 | * 73 | * We create CSS variables and fill them SCSS value in order to reuse them in JS. 74 | */ 75 | :root { 76 | --grid-columns: #{$grid-columns}; 77 | --grid-gutters: #{$grid-gutters}; 78 | 79 | --grid-edge: #{$grid-edge}; 80 | --grid-gutter: #{$grid-gutter}; 81 | 82 | --grid-size: #{$grid-size}; 83 | --grid-size-max: calc(var(--grid-size) + (2 * var(--grid-edge))); 84 | } 85 | 86 | /** 87 | * Global Styles: 88 | * 89 | * We apply some global styles to the DOM elements based on our configuration of the grid system. 90 | */ 91 | .container { 92 | max-width: var(--grid-size); 93 | margin-right: auto; 94 | margin-left: auto; 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/styles/base/_fonts.scss: -------------------------------------------------------------------------------- 1 | // Silence is golden. 2 | -------------------------------------------------------------------------------- /src/styles/base/_reset.scss: -------------------------------------------------------------------------------- 1 | p, 2 | a, 3 | q, 4 | s, 5 | b, 6 | u, 7 | i, 8 | h1, 9 | h2, 10 | h3, 11 | h4, 12 | h5, 13 | h6, 14 | em, 15 | tt, 16 | dl, 17 | dt, 18 | dd, 19 | ol, 20 | ul, 21 | li, 22 | tr, 23 | th, 24 | td, 25 | pre, 26 | div, 27 | big, 28 | del, 29 | dfn, 30 | img, 31 | ins, 32 | kbd, 33 | sub, 34 | sup, 35 | var, 36 | nav, 37 | html, 38 | body, 39 | span, 40 | abbr, 41 | cite, 42 | code, 43 | samp, 44 | form, 45 | main, 46 | menu, 47 | ruby, 48 | time, 49 | mark, 50 | audio, 51 | video, 52 | small, 53 | label, 54 | table, 55 | tbody, 56 | tfoot, 57 | thead, 58 | aside, 59 | embed, 60 | output, 61 | center, 62 | strike, 63 | strong, 64 | legend, 65 | applet, 66 | object, 67 | iframe, 68 | canvas, 69 | figure, 70 | footer, 71 | header, 72 | hgroup, 73 | section, 74 | summary, 75 | caption, 76 | acronym, 77 | address, 78 | article, 79 | details, 80 | fieldset, 81 | blockquote, 82 | figcaption { 83 | border: 0; 84 | margin: 0; 85 | padding: 0; 86 | font: inherit; 87 | font-size: 100%; 88 | vertical-align: baseline; 89 | } 90 | 91 | nav, 92 | main, 93 | menu, 94 | aside, 95 | figure, 96 | footer, 97 | header, 98 | hgroup, 99 | article, 100 | details, 101 | section, 102 | figcaption { 103 | display: block; 104 | } 105 | 106 | body { 107 | line-height: 1; 108 | } 109 | 110 | a { 111 | text-decoration: none; 112 | } 113 | 114 | ol, 115 | ul { 116 | list-style: none; 117 | } 118 | 119 | q, 120 | blockquote { 121 | quotes: none; 122 | } 123 | 124 | q::after, 125 | q::before, 126 | blockquote::after, 127 | blockquote::before { 128 | content: ''; 129 | content: none; 130 | } 131 | 132 | table { 133 | border-spacing: 0; 134 | border-collapse: collapse; 135 | } 136 | 137 | button { 138 | cursor: pointer; 139 | border: 0; 140 | padding: 0; 141 | appearance: none; 142 | background: transparent; 143 | } 144 | -------------------------------------------------------------------------------- /src/styles/base/_typography.scss: -------------------------------------------------------------------------------- 1 | @use "sass:math"; 2 | 3 | html { 4 | /* prettier-ignore */ 5 | font-size: math.div(math.div($base-font-size, ($base-font-size * 0 + 1)), 16) * 100%; 6 | -ms-text-size-adjust: 100%; 7 | -webkit-text-size-adjust: 100%; 8 | -moz-osx-font-smoothing: grayscale; 9 | -webkit-font-smoothing: antialiased; 10 | } 11 | 12 | // Default text styles 13 | // ----------------------------------------------------------------------------- 14 | body { 15 | font-family: $fallback-font-sans-serif; 16 | font-size: 1rem; 17 | line-height: $base-line-height; 18 | } 19 | -------------------------------------------------------------------------------- /src/styles/global.scss: -------------------------------------------------------------------------------- 1 | @import "base/fonts"; 2 | @import "base/reset"; 3 | @import "base/base"; 4 | @import "base/typography"; 5 | -------------------------------------------------------------------------------- /src/styles/utils/_animations.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-vue-starter/1717142463ab17da6163f1de2b04c26fd488d153/src/styles/utils/_animations.scss -------------------------------------------------------------------------------- /src/styles/utils/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | $breakpoints: ( 2 | xxxs: 0px, 3 | m: 768px, 4 | l: 1024px, 5 | xl: 1200px, 6 | xxl: 1440px, 7 | xxxl: 1920px, 8 | ); 9 | -------------------------------------------------------------------------------- /src/styles/utils/_easings.scss: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | 3 | /// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie) 4 | /// 5 | /// Timing functions are the same as demoed here: http://jqueryui.com/resources/demos/effect/easing.html 6 | /// 7 | /// @type cubic-bezier 8 | 9 | $ease-in-quad: cubic-bezier(0.55, 0.085, 0.68, 0.53); 10 | $ease-in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19); 11 | $ease-in-quart: cubic-bezier(0.895, 0.03, 0.685, 0.22); 12 | $ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06); 13 | $ease-in-sine: cubic-bezier(0.47, 0, 0.745, 0.715); 14 | $ease-in-expo: cubic-bezier(0.95, 0.05, 0.795, 0.035); 15 | $ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.335); 16 | $ease-in-back: cubic-bezier(0.6, -0.28, 0.735, 0.045); 17 | 18 | $ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94); 19 | $ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1); 20 | $ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1); 21 | $ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1); 22 | $ease-out-sine: cubic-bezier(0.39, 0.575, 0.565, 1); 23 | $ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1); 24 | $ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1); 25 | $ease-out-back: cubic-bezier(0.175, 0.885, 0.32, 1.275); 26 | 27 | $ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955); 28 | $ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1); 29 | $ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1); 30 | $ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1); 31 | $ease-in-out-sine: cubic-bezier(0.445, 0, 0.55, 0.95); 32 | $ease-in-out-expo: cubic-bezier(1, 0, 0, 1); 33 | $ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86); 34 | $ease-in-out-back: cubic-bezier(0.68, -0.55, 0.265, 1.55); 35 | -------------------------------------------------------------------------------- /src/styles/utils/_functions.scss: -------------------------------------------------------------------------------- 1 | @use "sass:math"; 2 | @use "sass:map"; 3 | @use "sass:list"; 4 | 5 | /** 6 | * Calculates the property value based on the grid system configuration. 7 | * 8 | * @param {Number} $column - Number of columns to use or span over. 9 | * @param {Number} $gutter - Number of gutters to use or span over. 10 | * @param {Number} $grid-size - Width of the grid. 11 | * @param {Number} $grid-gutter - Width of the gutters. 12 | * @param {Number} $grid-columns - Maximum number of columns. 13 | * 14 | * @return {String} CSS equation for the property. 15 | */ 16 | @function gs-calc( 17 | $column, 18 | $gutter, 19 | $grid-size: 100vw, 20 | $grid-gutter: 20px, 21 | $grid-columns: 12 22 | ) { 23 | $column: math.clamp(0, $column, $grid-columns); 24 | $gutter: math.clamp(0, $gutter, $grid-columns - 1); 25 | 26 | $unit-column: calc( 27 | (min(100vw, #{$grid-size}) - ((#{$grid-columns} - 1) * #{$grid-gutter})) / 28 | #{$grid-columns} 29 | ); 30 | $unit-gutter: $grid-gutter; 31 | 32 | $width-column: calc(#{$unit-column} * #{$column}); 33 | $width-gutter: calc(#{$unit-gutter} * #{$gutter}); 34 | 35 | @return calc(#{$width-column} + #{$width-gutter}); 36 | } 37 | 38 | /** 39 | * Returns the CSS equation for the property based on the grid system configuration. 40 | * 41 | * @param {Number} $column - Number of columns to use or span over. 42 | * @param {Number} $gutter - Number of gutters to use or span over. 43 | * @param {String} $use - The key of the configuration to use. 44 | * 45 | * @return {String} CSS equation for the property. 46 | */ 47 | @function gs($column: 0, $gutter: 0, $use) { 48 | @if (map.has-key($config, $use)) { 49 | // Get the configuration to use. 50 | $props: map-get($config, $use); 51 | 52 | // Build the SCSS variables for the `gs-calc` function. 53 | $grid-size: map-get($props, "grid-size"); 54 | $grid-gutter: map-get($props, "grid-gutter"); 55 | $grid-columns: map-get($props, "grid-columns"); 56 | 57 | // Compute the value. 58 | $value: gs-calc($column, $gutter, $grid-size, $grid-gutter, $grid-columns); 59 | 60 | // Return the CSS equation. 61 | @return $value; 62 | } 63 | 64 | @warn 'Unknown key in grid configuration: #{$use}'; 65 | @return 0; 66 | } 67 | 68 | /** 69 | * Generates grid system values for a given breakpoint and up. 70 | * 71 | * @param {String} $bpFrom - The breakpoint to start from. 72 | * 73 | * @return {Mixin} CSS rules for the given breakpoint and up. 74 | * 75 | * @example 76 | * @include mq-gs-from('l') { 77 | * width: gs(6, 5, $from); 78 | * } 79 | */ 80 | @mixin mq-gs-from($bpFrom) { 81 | $keyList: map.keys($config); 82 | $fromIndex: list.index($keyList, $bpFrom); 83 | 84 | @if not $fromIndex { 85 | @warn "Invalid breakpoint: #{$bpFrom}"; 86 | } 87 | 88 | @for $i from $fromIndex through length($keyList) { 89 | $key: nth($keyList, $i); 90 | @include mq($from: $key) using ($from) { 91 | @content ($from); 92 | } 93 | } 94 | } 95 | 96 | /** 97 | * Fetches nested keys from a map. 98 | * 99 | * @param {Map} $map - The map to fetch from. 100 | * @param {Arglist} $keys - The keys to fetch. 101 | * 102 | * @return {*} The value associated with the keys. 103 | */ 104 | @function map-deep-get($map, $keys...) { 105 | @each $key in $keys { 106 | @if type-of($map) != "map" { 107 | @warn '`#{$map}` is not a map.'; 108 | @return false; 109 | } 110 | 111 | $map: map-get($map, $key); 112 | } 113 | 114 | @return $map; 115 | } 116 | 117 | /** 118 | * Gets a z-index value from a given layers map. 119 | * 120 | * @param {Arglist} $layers - The layers to fetch. 121 | * 122 | * @requires {Variable} $z-layers 123 | * 124 | * @example 125 | * z-index: layer(below); 126 | * z-index: layer(modal); 127 | * z-index: layer(modal, header); 128 | * 129 | * @return {Number} The z-index value. 130 | */ 131 | @function layer($layers...) { 132 | $value: map-deep-get($z-layers, $layers...); 133 | 134 | @if type-of($value) == "map" { 135 | $value: map-get($value, base); 136 | } 137 | 138 | @return $value; 139 | } 140 | 141 | /** 142 | * Strips the unit from a number. 143 | * 144 | * @param {Number} $value - The value with a unit. 145 | * 146 | * @example 147 | * $dimension: strip-units(10em); 148 | * 149 | * @return {Number} The unitless number. 150 | */ 151 | @function strip-units($value) { 152 | @return math.div($value, $value * 0 + 1); 153 | } 154 | 155 | /** 156 | * Converts pixels to rems based on the base font size. 157 | * 158 | * @access private 159 | * 160 | * @param {Number} $pxval - The value to convert. 161 | * 162 | * @example 163 | * $dimension: rem(12); // $base-font-size: 16px 164 | * 165 | * @requires {Variable} $base-font-size 166 | * 167 | * @return {Number} The value in rems. 168 | */ 169 | @function rem($pxval) { 170 | @if not unitless($pxval) { 171 | $pxval: strip-units($pxval); 172 | } 173 | 174 | $base: $base-font-size; 175 | @if not unitless($base) { 176 | $base: strip-units($base); 177 | } 178 | 179 | @return math.div($pxval, $base) * 1rem; 180 | } 181 | -------------------------------------------------------------------------------- /src/styles/utils/_utilities.scss: -------------------------------------------------------------------------------- 1 | // Hide text while making it readable for screen readers 2 | // 1. Needed in WebKit-based browsers because of an implementation bug; 3 | // See: https://code.google.com/p/chromium/issues/detail?id=457146 4 | .u-hide-text { 5 | padding: 0; /* 1 */ 6 | overflow: hidden; 7 | text-indent: 101%; 8 | white-space: nowrap; 9 | } 10 | 11 | // Hide element only visually, but have it available for screen readers: 12 | // http://snook.ca/archives/html_and_css/hiding-content-for-accessibility 13 | .u-visually-hidden { 14 | position: absolute; 15 | width: 1px; 16 | height: 1px; 17 | margin: -1px; 18 | padding: 0; 19 | overflow: hidden; 20 | clip: rect(0 0 0 0); 21 | border: 0; 22 | } 23 | -------------------------------------------------------------------------------- /src/styles/utils/_variables.scss: -------------------------------------------------------------------------------- 1 | // CSS Variables 2 | // ----------------------------------------------------------------------------- 3 | :root { 4 | --real-height: 100vh; 5 | } 6 | 7 | // Color Palettes 8 | // ----------------------------------------------------------------------------- 9 | $brand-black: #000; 10 | $brand-white: #fff; 11 | 12 | // Indexes 13 | // ----------------------------------------------------------------------------- 14 | $z-layers: ( 15 | base: 1, 16 | under-base: -1, 17 | above-base: 2, 18 | ); 19 | 20 | // Fonts Variables 21 | // ----------------------------------------------------------------------------- 22 | $base-font-size: 16px; 23 | $base-line-height: 1; 24 | 25 | $fallback-font-serif: 'Times New Roman', 'Georgia', serif; 26 | $fallback-font-sans-serif: 'Helvetica', 'Arial', sans-serif; 27 | 28 | // Grid System Settings 29 | // ----------------------------------------------------------------------------- 30 | $config: ( 31 | // only put a new breakpoint in here if it has different grid settings 32 | xxxs: ( 33 | grid-size: calc(100vw - 48px), 34 | grid-edge: 24px, 35 | grid-gutter: 16px, 36 | grid-columns: 6, 37 | ), 38 | l: ( 39 | grid-size: calc(100vw - 60px), 40 | grid-edge: 20px, 41 | grid-gutter: 20px, 42 | grid-columns: 12, 43 | ), 44 | xxxl: ( 45 | grid-size: 1812px, 46 | grid-edge: 48px, 47 | grid-gutter: 24px, 48 | grid-columns: 12, 49 | ), 50 | ); 51 | 52 | // Random Variables 53 | // ----------------------------------------------------------------------------- 54 | $PI: 3.1415926536; 55 | -------------------------------------------------------------------------------- /src/styles/vendors/sass-mq/_mq.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:map'; 2 | @use 'sass:math'; 3 | @use 'sass:list'; 4 | 5 | /// Breakpoints List 6 | @use '../../utils/breakpoints' as *; 7 | 8 | /// Change the media type (all, print, screen,...) 9 | $media-type: all !default; 10 | 11 | /// Convert pixels to ems 12 | /// 13 | /// Parameters: 14 | /// @param {Number} $px - value to convert 15 | /// 16 | /// Return Values: 17 | /// @returns {Number} Value in `em` 18 | /// 19 | @function px2em($px) { 20 | @if math.is-unitless($px) { 21 | @warn "Assuming #{$px} to be in pixels, attempting to convert it into pixels."; 22 | @return px2em($px * 1px); 23 | } 24 | 25 | // if $px is compatible with em units, then return value unchanged 26 | @if math.compatible($px, 1em) { 27 | @return $px; 28 | } 29 | 30 | @return math.div($px, 16px) * 1em; 31 | } 32 | 33 | /// Get a breakpoint's width 34 | /// 35 | /// Parameters: 36 | /// @param {String} $name - Name of the breakpoint. One of $breakpoints 37 | /// 38 | /// Required Variables: 39 | /// @requires {Variable} $breakpoints 40 | /// 41 | /// Returned Values: 42 | /// @returns {Number} Value in pixels 43 | /// 44 | @function get-breakpoint-width($name, $breakpoints: $breakpoints) { 45 | @if map.has-key($breakpoints, $name) { 46 | @return map.get($breakpoints, $name); 47 | } @else { 48 | @warn "Breakpoint #{$name} wasn't found in $breakpoints."; 49 | @return null; 50 | } 51 | } 52 | 53 | /// Media Query mixin 54 | /// 55 | /// Parameters: 56 | /// @param {String | Boolean} $from [false] - One of $breakpoints 57 | /// @param {String | Boolean} $until [false] - One of $breakpoints 58 | /// @param {String | Boolean} $and [false] - Additional media query parameters 59 | /// @param {String} $media-type [$media-type] - Media type: screen, print… 60 | /// 61 | /// Ignored Values: 62 | /// @ignore Undocumented API, for advanced use only: 63 | /// @ignore @param {Map} $breakpoints [$breakpoints] 64 | /// 65 | /// Returned Content: 66 | /// @content styling rules, wrapped into a @media query when $responsive is true 67 | /// 68 | /// Required Variables & Functions: 69 | /// @requires {Variable} $media-type 70 | /// @requires {Variable} $breakpoints 71 | /// @requires {Function} px2em 72 | /// @requires {Function} get-breakpoint-width 73 | /// 74 | /// Further Reading: 75 | /// @link https://github.com/sass-mq/sass-mq#responsive-mode-on-default Full documentation and examples 76 | /// 77 | @mixin mq( 78 | $from: false, 79 | $until: false, 80 | $and: false, 81 | $media-type: $media-type, 82 | $breakpoints: $breakpoints 83 | ) { 84 | $min-width: 0; 85 | $max-width: 0; 86 | $media-query: ''; 87 | 88 | // From: this breakpoint (inclusive) 89 | @if $from { 90 | @if type-of($from) == number { 91 | $min-width: px2em($from); 92 | } @else { 93 | $min-width: px2em(get-breakpoint-width($from, $breakpoints)); 94 | } 95 | } 96 | 97 | // Until: that breakpoint (exclusive) 98 | @if $until { 99 | @if type-of($until) == number { 100 | $max-width: px2em($until); 101 | } @else { 102 | $max-width: px2em(get-breakpoint-width($until, $breakpoints)) - 0.01em; 103 | } 104 | } 105 | 106 | @if $min-width != 0 { 107 | $media-query: '#{$media-query} and (min-width: #{$min-width})'; 108 | } 109 | 110 | @if $max-width != 0 { 111 | $media-query: '#{$media-query} and (max-width: #{$max-width})'; 112 | } 113 | 114 | @if $and { 115 | $media-query: '#{$media-query} and #{$and}'; 116 | } 117 | 118 | // Remove unnecessary media query prefix 'all and ' 119 | @if ($media-type == 'all' and $media-query != '') { 120 | $media-type: ''; 121 | $media-query: str-slice(unquote($media-query), 6); 122 | } 123 | 124 | @media #{$media-type + $media-query} { 125 | @if ($from and not $until) { 126 | @content ($from); 127 | } @else if ($until and not $from) { 128 | @content ($until); 129 | } @else if ($from and $until) { 130 | @content ($from, $until); 131 | } @else { 132 | @content; 133 | } 134 | } 135 | } 136 | 137 | /// Quick sort 138 | /// 139 | /// Access Type: 140 | /// @access private 141 | /// 142 | /// Parameters: 143 | /// @param {List} $list - List to sort 144 | /// 145 | /// Returned Values: 146 | /// @returns {List} Sorted List 147 | /// 148 | @function _quick-sort($list) { 149 | $less: (); 150 | $equal: (); 151 | $large: (); 152 | 153 | @if length($list) > 1 { 154 | $seed: list.nth($list, math.ceil(math.div(length($list), 2))); 155 | 156 | @each $item in $list { 157 | @if ($item == $seed) { 158 | $equal: list.append($equal, $item); 159 | } @else if ($item < $seed) { 160 | $less: list.append($less, $item); 161 | } @else if ($item > $seed) { 162 | $large: list.append($large, $item); 163 | } 164 | } 165 | 166 | @return join(join(_quick-sort($less), $equal), _quick-sort($large)); 167 | } 168 | 169 | @return $list; 170 | } 171 | 172 | /// Sort a map by values (works with numbers only) 173 | /// 174 | /// Access Type: 175 | /// @access private 176 | /// 177 | /// Parameters: 178 | /// @param {Map} $map - Map to sort 179 | /// 180 | /// Returned Values: 181 | /// @returns {Map} Map sorted by value 182 | /// 183 | @function _map-sort-by-value($map) { 184 | $map-sorted: (); 185 | $map-keys: map.keys($map); 186 | $map-values: map.values($map); 187 | $map-values-sorted: _quick-sort($map-values); 188 | 189 | // Reorder key/value pairs based on key values 190 | @each $value in $map-values-sorted { 191 | $index: index($map-values, $value); 192 | $key: list.nth($map-keys, $index); 193 | $map-sorted: map.merge( 194 | $map-sorted, 195 | ( 196 | $key: $value, 197 | ) 198 | ); 199 | 200 | // Unset the value in $map-values to prevent the loop 201 | // from finding the same index twice 202 | $map-values: list.set-nth($map-values, $index, 0); 203 | } 204 | 205 | @return $map-sorted; 206 | } 207 | 208 | /// Add a breakpoint 209 | /// 210 | /// Parameters: 211 | /// @param {String} $name - Name of the breakpoint 212 | /// @param {Number} $width - Width of the breakpoint 213 | /// 214 | /// Required Variables: 215 | /// @requires {Variable} $breakpoints 216 | /// 217 | @mixin add-breakpoint($name, $width) { 218 | $new-breakpoint: ( 219 | $name: $width, 220 | ); 221 | $breakpoints: map.merge($breakpoints, $new-breakpoint) !global; 222 | $breakpoints: _map-sort-by-value($breakpoints) !global; 223 | } 224 | -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import UAParser from "ua-parser-js"; 3 | 4 | // UA Constants 5 | // UA Parser 6 | const PARSER = new UAParser(); 7 | 8 | // Set UA Parser 9 | PARSER.setUA(navigator.userAgent); 10 | 11 | export const OS = PARSER.getOS(); 12 | export const DEVICE = PARSER.getDevice(); 13 | export const BROWSER = PARSER.getBrowser(); 14 | 15 | export const IS_MOBILE = DEVICE.type === "mobile"; 16 | export const IS_TABLET = DEVICE.type === "tablet"; 17 | 18 | // Environment Constants 19 | export const DEV = import.meta.env.DEV; 20 | export const PROD = import.meta.env.PROD; 21 | 22 | // i18n 23 | export const DEFAULT_LANGUAGE = "en"; 24 | export const FALLBACK_LANGUAGE = "en"; 25 | export const SUPPORTED_LANGUAGES = "en|fr"; 26 | 27 | // Responsive Constants 28 | export const BREAKPOINTS = { 29 | XXXS: 0, 30 | M: 768, 31 | L: 1024, 32 | XL: 1200, 33 | XXL: 1440, 34 | XXXL: 1920, 35 | }; 36 | -------------------------------------------------------------------------------- /src/utils/grid.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import equal from 'fast-deep-equal' 3 | 4 | class Grid { 5 | constructor(grid, toolbar) { 6 | this.root = grid 7 | this.toolbar = toolbar 8 | 9 | this.values = {} 10 | 11 | this.toggleEdges = this.toolbar.querySelector('#toggle-edges') 12 | this.toggleColumns = this.toolbar.querySelector('#toggle-columns') 13 | this.toggleGutters = this.toolbar.querySelector('#toggle-gutters') 14 | 15 | this.timeout = null 16 | this.timeoutDuration = 150 17 | 18 | // Init 19 | this.toggle('edges', this.toggleEdges.checked) 20 | this.toggle('columns', this.toggleColumns.checked) 21 | this.toggle('gutters', this.toggleGutters.checked) 22 | 23 | // Events 24 | this.toggleEdges.addEventListener('change', (e) => 25 | this.toggle('edges', e.currentTarget.checked) 26 | ) 27 | this.toggleColumns.addEventListener('change', (e) => 28 | this.toggle('columns', e.currentTarget.checked) 29 | ) 30 | this.toggleGutters.addEventListener('change', (e) => 31 | this.toggle('gutters', e.currentTarget.checked) 32 | ) 33 | } 34 | 35 | build() { 36 | const columns = Number(this.values['--grid-columns']) 37 | const gutters = Number(this.values['--grid-gutters']) 38 | 39 | this.root.innerHTML = '' 40 | 41 | for (let i = 0; i < columns; i++) { 42 | const el = document.createElement('div') 43 | 44 | el.id = `site-grid-column-${i}` 45 | el.className = `site-grid__column site-grid__column--${i}` 46 | 47 | this.root.append(el) 48 | } 49 | 50 | for (let i = 0; i < gutters; i++) { 51 | const el = document.createElement('div') 52 | const column = document.querySelector(`#site-grid-column-${i + 1}`) 53 | 54 | el.id = `site-grid-gutter-${i}` 55 | el.className = `site-grid__gutter site-grid__gutter--${i}` 56 | 57 | this.root.insertBefore(el, column) 58 | } 59 | 60 | const edge0 = document.createElement('div') 61 | const edge1 = document.createElement('div') 62 | 63 | edge0.id = 'site-grid-edge-0' 64 | edge0.className = 'site-grid__edge site-grid__edge--0' 65 | 66 | edge1.id = 'site-grid-edge-1' 67 | edge1.className = 'site-grid__edge site-grid__edge--1' 68 | 69 | this.root.append(edge1) 70 | this.root.prepend(edge0) 71 | } 72 | 73 | toggle(option, value) { 74 | switch (option) { 75 | case 'edges': { 76 | this.root.classList.toggle('site-grid--no-edges', !value) 77 | break 78 | } 79 | 80 | case 'columns': { 81 | this.root.classList.toggle('site-grid--no-columns', !value) 82 | break 83 | } 84 | 85 | case 'gutters': { 86 | this.root.classList.toggle('site-grid--no-gutters', !value) 87 | break 88 | } 89 | } 90 | } 91 | 92 | observe(el, variables) { 93 | const crawler = () => { 94 | const values = {} 95 | 96 | for (const variable of variables) { 97 | values[variable] = getComputedStyle(el).getPropertyValue(variable) 98 | } 99 | 100 | if (!equal(values, this.values)) { 101 | this.values = values 102 | this.build() 103 | } 104 | 105 | this.timeout ? clearTimeout(this.timeout) : null 106 | this.timeout = setTimeout(crawler, this.timeoutDuration) 107 | } 108 | 109 | crawler() 110 | } 111 | 112 | unobserve() { 113 | this.timeout ? clearTimeout(this.timeout) : null 114 | this.timeout = null 115 | } 116 | } 117 | 118 | export default Grid 119 | -------------------------------------------------------------------------------- /src/utils/say-hello.js: -------------------------------------------------------------------------------- 1 | function sayHello() { 2 | // Chrome Log 3 | if (navigator.userAgent.toLowerCase().includes('chrome')) { 4 | const args = [ 5 | '\n %c Made with ♥ by Dogstudio %c %c %c http://www.dogstudio.co/ %c %c \n', 6 | 'color: #fff; background: #e43333; padding:5px 0;', 7 | 'background: #131419; padding:5px 0;', 8 | 'background: #131419; padding:5px 0;', 9 | 'color: #fff; background: #1c1c1c; padding:5px 0;', 10 | 'background: #fff; padding:5px 0;', 11 | 'color: #e43333; background: #fff; padding:5px 0;', 12 | ] 13 | 14 | // Log Message 15 | window.console.log.apply(console, args) 16 | } else if (window.console) { 17 | // Default Message 18 | window.console.log('Made with love ♥ Dogstudio - http://www.dogstudio.co/') 19 | } 20 | } 21 | 22 | export default sayHello() 23 | -------------------------------------------------------------------------------- /src/utils/translation.js: -------------------------------------------------------------------------------- 1 | // https://lokalise.com/blog/vue-i18n/ 2 | import { nextTick } from "vue"; 3 | 4 | // i18n 5 | import i18n from "@/locales"; 6 | 7 | // Utils 8 | import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "@/utils/constants"; 9 | 10 | const Trans = { 11 | get defaultLocale() { 12 | return DEFAULT_LANGUAGE; 13 | }, 14 | 15 | get supportedLocales() { 16 | return SUPPORTED_LANGUAGES.split("|"); 17 | }, 18 | 19 | get currentLocale() { 20 | return i18n.global.locale.value === DEFAULT_LANGUAGE 21 | ? "" 22 | : i18n.global.locale.value; 23 | }, 24 | 25 | set currentLocale(newLocale) { 26 | i18n.global.locale.value = newLocale; 27 | }, 28 | 29 | async switchLanguage(newLocale = DEFAULT_LANGUAGE) { 30 | console.log("Switching language to", newLocale); 31 | await Trans.loadLocaleMessages(newLocale); 32 | Trans.currentLocale = newLocale; 33 | document.querySelector("html").setAttribute("lang", newLocale); 34 | localStorage.setItem("user-locale", newLocale); 35 | }, 36 | 37 | async loadLocaleMessages(locale) { 38 | if (!i18n.global.availableLocales.includes(locale)) { 39 | const messages = await import(`@/i18n/locales/${locale}.json`); 40 | i18n.global.setLocaleMessage(locale, messages.default); 41 | } 42 | 43 | return nextTick(); 44 | }, 45 | 46 | isLocaleSupported(locale) { 47 | return Trans.supportedLocales.includes(locale); 48 | }, 49 | 50 | getUserLocale() { 51 | const locale = 52 | window.navigator.language || 53 | window.navigator.userLanguage || 54 | Trans.defaultLocale; 55 | 56 | return { 57 | locale: locale, 58 | localeNoRegion: locale.split("-")[0], 59 | }; 60 | }, 61 | 62 | getPersistedLocale() { 63 | const persistedLocale = localStorage.getItem("user-locale"); 64 | 65 | if (Trans.isLocaleSupported(persistedLocale)) { 66 | return persistedLocale; 67 | } else { 68 | return null; 69 | } 70 | }, 71 | 72 | guessDefaultLocale() { 73 | const userPersistedLocale = Trans.getPersistedLocale(); 74 | if (userPersistedLocale) { 75 | return userPersistedLocale; 76 | } 77 | 78 | const userPreferredLocale = Trans.getUserLocale(); 79 | 80 | if (Trans.isLocaleSupported(userPreferredLocale.locale)) { 81 | return userPreferredLocale.locale; 82 | } 83 | 84 | if (Trans.isLocaleSupported(userPreferredLocale.localeNoRegion)) { 85 | return userPreferredLocale.localeNoRegion; 86 | } 87 | 88 | return Trans.defaultLocale; 89 | }, 90 | 91 | async routeMiddleware(to, _from, next) { 92 | let paramLocale = to.params.locale || DEFAULT_LANGUAGE; 93 | 94 | if (!Trans.isLocaleSupported(paramLocale)) { 95 | return next(Trans.guessDefaultLocale()); 96 | } 97 | 98 | await Trans.switchLanguage(paramLocale); 99 | 100 | return next(); 101 | }, 102 | 103 | i18nRoute(to) { 104 | return { 105 | ...to, 106 | params: { 107 | locale: Trans.currentLocale, 108 | ...to.params, 109 | }, 110 | }; 111 | }, 112 | }; 113 | 114 | export default Trans; 115 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import path from 'path' 3 | 4 | // Configuration 5 | import { defineConfig, loadEnv } from 'vite' 6 | 7 | // Plugins 8 | import vue from '@vitejs/plugin-vue' 9 | import glsl from 'vite-plugin-glsl' 10 | 11 | // Custom Configuration 12 | export default defineConfig(({ mode }) => { 13 | process.env = { ...process.env, ...loadEnv(mode, process.cwd()) } 14 | 15 | return { 16 | root: path.join(__dirname, 'src'), 17 | base: "/", // Affects import.meta.env.BASE_URL in the glxp manifest 18 | build: { 19 | outDir: path.join(__dirname, 'dist'), 20 | }, 21 | server: { 22 | port: process.env.VITE_SERVER_PORT || 3000, 23 | }, 24 | resolve: { 25 | alias: { 26 | '@': path.join(__dirname, 'src'), 27 | '@@': path.join(__dirname), 28 | }, 29 | }, 30 | envDir: process.cwd(), 31 | publicDir: path.resolve(__dirname, 'public'), 32 | css: { 33 | preprocessorOptions: { 34 | scss: { 35 | additionalData: ` 36 | @import "@/styles/vendors/sass-mq/_mq.scss"; 37 | @import "@/styles/utils/_utilities.scss"; 38 | @import "@/styles/utils/_easings.scss"; 39 | @import "@/styles/utils/_variables.scss"; 40 | @import "@/styles/utils/_functions.scss"; 41 | `, 42 | }, 43 | }, 44 | }, 45 | plugins: [vue(), glsl()], 46 | } 47 | }) 48 | --------------------------------------------------------------------------------