├── .editorconfig ├── .env.example ├── .gitignore ├── .ncurc.json ├── .nvmrc ├── .prettierignore ├── LICENSE.md ├── README.md ├── app.vue ├── components ├── Button.vue ├── Grid.vue ├── Icon │ ├── Icons │ │ └── .gitkeep │ └── index.vue ├── InView.vue └── LanguageSwitcher.vue ├── error.vue ├── glxp └── .gitkeep ├── i18n.config.js ├── layouts └── default.vue ├── locales └── en.json ├── nuxt.config.js ├── package-lock.json ├── package.json ├── pages ├── grid-demo.vue └── index.vue ├── plugins ├── real-height.client.js └── say-hello.client.js ├── public ├── assets │ ├── images │ │ └── share.png │ └── videos │ │ └── .gitkeep ├── favicon.ico ├── glxp │ └── .gitkeep └── vendors │ └── modernizr.js ├── server └── api │ └── hello.js ├── stores └── .gitkeep ├── 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 ├── easing.js ├── helpers ├── grid.js └── i18n.js └── raf.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://nuxt.com/docs/guide/going-further/runtime-config#environment-variables 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Testing 5 | /coverage 6 | 7 | # Nuxt 8 | **/.nuxt 9 | **/.output 10 | **/out 11 | 12 | # Build 13 | **/dist 14 | **/build 15 | 16 | # Misc 17 | .DS_Store 18 | 19 | # Logs 20 | *.log 21 | 22 | # Local `env` Files 23 | .env 24 | .env.test 25 | .env.local 26 | .env.*.local 27 | .env.production 28 | .env.development 29 | 30 | # IDEs 31 | /.idea 32 | /.vscode 33 | 34 | # Vercel 35 | .vercel 36 | 37 | # Samples 38 | sample.* 39 | -------------------------------------------------------------------------------- /.ncurc.json: -------------------------------------------------------------------------------- 1 | { 2 | "upgrade": true, 3 | "interactive": true, 4 | "reject": [ 5 | "sass-loader" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.20.5 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Nuxt 2 | /.nuxt 3 | /.output 4 | /out 5 | 6 | # Build 7 | /dist 8 | /build 9 | 10 | # Front-End 11 | public/**/*.ts 12 | public/**/*.js 13 | public/**/*.tsx 14 | public/**/*.jsx 15 | public/**/*.vue 16 | 17 | glxp/**/*.ts 18 | glxp/**/*.js 19 | glxp/**/*.tsx 20 | glxp/**/*.jsx 21 | glxp/**/*.vue 22 | 23 | vendors/**/*.ts 24 | vendors/**/*.js 25 | vendors/**/*.tsx 26 | vendors/**/*.jsx 27 | vendors/**/*.vue 28 | 29 | # Utilities 30 | utils/helpers/**/*.ts 31 | utils/helpers/**/*.js 32 | -------------------------------------------------------------------------------- /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 Nuxt Starter 2 | 3 | The repository has all the features needed to build blazing-fast experience. 4 | 5 | - Nuxt 3 6 | - Prettier 7 | - SCSS/SASS 8 | 9 | ## Prerequisites 10 | 11 | - [Node.js - LTS](https://nodejs.org/en/download/) 12 | 13 | ## Getting Started 14 | 15 | ```bash 16 | git clone git@github.com:Dogstudio/dogstudio-nuxt-starter.git my-new-app 17 | ``` 18 | 19 | ```bash 20 | cd my-new-app 21 | cp .env.example .env 22 | ``` 23 | 24 | ```bash 25 | npm i 26 | npm run dev 27 | ``` 28 | 29 | ## Documentation 30 | 31 | We recommend the following documentations to master the stack: 32 | 33 | - [Vue](https://vuejs.org/guide/introduction.html) 34 | - [Nuxt](https://v3.nuxtjs.org/getting-started/introduction) 35 | - [Pinia](https://pinia.vuejs.org/ssr/nuxt.html#installation) 36 | - [Style Guide](https://vuejs.org/style-guide/) 37 | - [Atomic Design Pattern](https://atomicdesign.bradfrost.com/table-of-contents/) 38 | 39 | ## References 40 | 41 | ```bash 42 | # Run Nuxt in development-mode with a local server. 43 | # Visit http://localhost:3000 to see your application running. 44 | npm run dev 45 | 46 | # Run Nuxt in production-mode and creates a production build. 47 | npm run build 48 | 49 | # Run Nuxt in production-mode with a local server. 50 | # Visit http://localhost:3000 to see your production build. 51 | npm run start 52 | 53 | # Run Nuxt in production-mode and generates static files. 54 | npm run generate 55 | 56 | # Run ESLint for JS & Vue files 57 | npm run lint 58 | 59 | # Upgrade the dependencies and devDependencies with interactive mode 60 | # See: https://www.npmjs.com/package/npm-check-updates 61 | npm run upgrade 62 | ``` 63 | 64 | ## Example 65 | 66 | An example is available on the `demo` branch to test all the features included in the repository: 67 | 68 | ```bash 69 | git checkout demo 70 | ``` 71 | 72 | ```bash 73 | npm i 74 | npm run dev 75 | ``` 76 | 77 | ## Contributing 78 | 79 | Please submit issues, pull requests or [contact us](devops+nuxt-starter@dogstudio.be). We are open to all kind of contributions. 80 | 81 | ## License 82 | 83 | The `dogstudio-nuxt-starter` repository is [MIT licensed](/LICENSE.md). 84 | -------------------------------------------------------------------------------- /app.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /components/Button.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 52 | 53 | 59 | -------------------------------------------------------------------------------- /components/Grid.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 67 | 68 | 294 | -------------------------------------------------------------------------------- /components/Icon/Icons/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-nuxt-starter/14f97b36e49c29f5603922251c2d35bab5b8d99c/components/Icon/Icons/.gitkeep -------------------------------------------------------------------------------- /components/Icon/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 25 | 26 | 40 | -------------------------------------------------------------------------------- /components/InView.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 64 | 65 | 70 | -------------------------------------------------------------------------------- /components/LanguageSwitcher.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 64 | -------------------------------------------------------------------------------- /error.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | -------------------------------------------------------------------------------- /glxp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-nuxt-starter/14f97b36e49c29f5603922251c2d35bab5b8d99c/glxp/.gitkeep -------------------------------------------------------------------------------- /i18n.config.js: -------------------------------------------------------------------------------- 1 | import en from "@/locales/en.json"; 2 | 3 | // Export i18n instance 4 | export default { 5 | // you must set `false`, to use Composition API 6 | legacy: false, 7 | messages: { 8 | en, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcome": "Hello World!" 3 | } 4 | -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | // For options see: https://nuxt.com/docs/api/nuxt-config 2 | import glsl from "vite-plugin-glsl"; 3 | import * as fs from "fs"; 4 | import * as path from "path"; 5 | 6 | function getI18nLocales() { 7 | const files = fs.readdirSync("./locales"); 8 | return files 9 | .filter((file) => path.extname(file) === ".json") 10 | .map((file) => path.basename(file, ".json")); 11 | } 12 | 13 | import { BREAKPOINTS } from "./utils/constants.js"; 14 | 15 | export default defineNuxtConfig({ 16 | devtools: { enabled: false }, 17 | ssr: false, 18 | devServer: { 19 | host: "0.0.0.0", 20 | }, 21 | app: { 22 | head: { 23 | title: "", 24 | meta: [ 25 | { 26 | name: "description", 27 | content: "", 28 | }, 29 | 30 | // Facebook / Open Graph 31 | { 32 | property: "og:url", 33 | content: "", 34 | }, 35 | { 36 | property: "og:type", 37 | content: "website", 38 | }, 39 | { 40 | property: "og:title", 41 | content: "", 42 | }, 43 | { 44 | property: "og:description", 45 | content: "", 46 | }, 47 | { 48 | property: "og:image", 49 | content: "/share.jpg", // 1200x628 50 | }, 51 | 52 | // Twitter / Open Graph 53 | { 54 | name: "twitter:card", 55 | content: "summary", 56 | }, 57 | { 58 | property: "twitter:domain", 59 | content: "", 60 | }, 61 | { 62 | property: "twitter:url", 63 | content: "", 64 | }, 65 | { 66 | name: "twitter:title", 67 | content: "", 68 | }, 69 | { 70 | name: "twitter:description", 71 | content: "", 72 | }, 73 | { 74 | name: "twitter:image", 75 | content: "/share-twitter.jpg", // 506×254 76 | }, 77 | ], 78 | script: [], 79 | link: [], 80 | }, 81 | }, 82 | modules: ["@nuxtjs/i18n", "@pinia/nuxt", "nuxt-viewport"], 83 | i18n: { 84 | defaultLocale: "en", 85 | locales: getI18nLocales(), 86 | strategy: "prefix_except_default", 87 | vueI18n: "./i18n.config.js", 88 | detectBrowserLanguage: { 89 | useCookie: false, 90 | redirectOn: "root", 91 | }, 92 | compilation: { 93 | strictMessage: false, 94 | }, 95 | }, 96 | pinia: { 97 | storesDirs: ["~/stores/**"], 98 | }, 99 | viewport: { 100 | breakpoints: BREAKPOINTS, 101 | fallbackBreakpoint: "L", 102 | defaultBreakpoints: { 103 | desktop: "L", 104 | tablet: "M", 105 | mobile: "XXXS", 106 | }, 107 | }, 108 | css: ["~/styles/global.scss"], 109 | vite: { 110 | css: { 111 | preprocessorOptions: { 112 | scss: { 113 | additionalData: ` 114 | @import "~/styles/vendors/sass-mq/_mq.scss"; 115 | @import "~/styles/utils/_utilities.scss"; 116 | @import "~/styles/utils/_easings.scss"; 117 | @import "~/styles/utils/_variables.scss"; 118 | @import "~/styles/utils/_functions.scss"; 119 | `, 120 | }, 121 | }, 122 | }, 123 | plugins: [glsl()], 124 | }, 125 | compatibilityDate: "2025-01-07", 126 | }); 127 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dogstudio/dogstudio-nuxt-starter", 3 | "type": "module", 4 | "version": "2.1.0", 5 | "license": "MIT", 6 | "private": true, 7 | "homepage": "https://github.com/Dogstudio/dogstudio-nuxt-starter", 8 | "description": "Opinionated Nuxt starter by Dogstudio", 9 | "scripts": { 10 | "dev": "nuxi dev", 11 | "start": "nuxi preview", 12 | "build": "nuxi build", 13 | "generate": "nuxi generate", 14 | "upgrade": "ncu" 15 | }, 16 | "engineStrict": true, 17 | "engines": { 18 | "node": "18.x.x || 19.x.x" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/Dogstudio/dogstudio-nuxt-starter/issues", 22 | "email": "devops@dogstudio.be" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/Dogstudio/dogstudio-nuxt-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 | "@nuxtjs/i18n": "^8.5.5", 39 | "@pinia/nuxt": "^0.9.0", 40 | "fast-deep-equal": "^3.1.3", 41 | "nuxt": "^3.4.1", 42 | "nuxt-viewport": "^2.2.0", 43 | "ua-parser-js": "^1.0.35" 44 | }, 45 | "devDependencies": { 46 | "npm-check-updates": "^16.10.8", 47 | "sass": "^1.62.0", 48 | "vite-plugin-glsl": "^1.1.2" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pages/grid-demo.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 29 | 30 | 64 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /plugins/real-height.client.js: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | export default defineNuxtPlugin(() => { 3 | const onResize = () => { 4 | document.documentElement.setAttribute('style', `--real-height: ${window.innerHeight}px`) 5 | } 6 | 7 | // Resize Event 8 | window.addEventListener('resize', onResize) 9 | 10 | // Initial Resize 11 | onResize() 12 | }) 13 | -------------------------------------------------------------------------------- /plugins/say-hello.client.js: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | export default defineNuxtPlugin(() => { 3 | // Chrome Log 4 | if (navigator.userAgent.toLowerCase().includes('chrome')) { 5 | const args = [ 6 | '\n %c Made with ♥ by Dogstudio %c %c %c http://www.dogstudio.co/ %c %c \n', 7 | 'color: #fff; background: #e43333; padding:5px 0;', 8 | 'background: #131419; padding:5px 0;', 9 | 'background: #131419; padding:5px 0;', 10 | 'color: #fff; background: #1c1c1c; padding:5px 0;', 11 | 'background: #fff; padding:5px 0;', 12 | 'color: #e43333; background: #fff; padding:5px 0;', 13 | ] 14 | 15 | // Log Message 16 | window.console.log.apply(console, args) 17 | } else if (window.console) { 18 | // Default Message 19 | window.console.log('Made with love ♥ Dogstudio - http://www.dogstudio.co/') 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /public/assets/images/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-nuxt-starter/14f97b36e49c29f5603922251c2d35bab5b8d99c/public/assets/images/share.png -------------------------------------------------------------------------------- /public/assets/videos/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-nuxt-starter/14f97b36e49c29f5603922251c2d35bab5b8d99c/public/assets/videos/.gitkeep -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-nuxt-starter/14f97b36e49c29f5603922251c2d35bab5b8d99c/public/favicon.ico -------------------------------------------------------------------------------- /public/glxp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-nuxt-starter/14f97b36e49c29f5603922251c2d35bab5b8d99c/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 | return { 3 | hello: 'world', 4 | } 5 | }) 6 | -------------------------------------------------------------------------------- /stores/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-nuxt-starter/14f97b36e49c29f5603922251c2d35bab5b8d99c/stores/.gitkeep -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /styles/base/_fonts.scss: -------------------------------------------------------------------------------- 1 | // Silence is golden. 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /styles/global.scss: -------------------------------------------------------------------------------- 1 | @import "base/fonts"; 2 | @import "base/reset"; 3 | @import "base/base"; 4 | @import "base/typography"; 5 | -------------------------------------------------------------------------------- /styles/utils/_animations.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dogstudio/dogstudio-nuxt-starter/14f97b36e49c29f5603922251c2d35bab5b8d99c/styles/utils/_animations.scss -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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})) / #{$grid-columns} 28 | ); 29 | $unit-gutter: $grid-gutter; 30 | 31 | $width-column: calc(#{$unit-column} * #{$column}); 32 | $width-gutter: calc(#{$unit-gutter} * #{$gutter}); 33 | 34 | @return calc(#{$width-column} + #{$width-gutter}); 35 | } 36 | 37 | /** 38 | * Returns the CSS equation for the property based on the grid system configuration. 39 | * 40 | * @param {Number} $column - Number of columns to use or span over. 41 | * @param {Number} $gutter - Number of gutters to use or span over. 42 | * @param {String} $use - The key of the configuration to use. 43 | * 44 | * @return {String} CSS equation for the property. 45 | */ 46 | @function gs($column: 0, $gutter: 0, $use) { 47 | @if (map.has-key($config, $use)) { 48 | // Get the configuration to use. 49 | $props: map-get($config, $use); 50 | 51 | // Build the SCSS variables for the `gs-calc` function. 52 | $grid-size: map-get($props, "grid-size"); 53 | $grid-gutter: map-get($props, "grid-gutter"); 54 | $grid-columns: map-get($props, "grid-columns"); 55 | 56 | // Compute the value. 57 | $value: gs-calc($column, $gutter, $grid-size, $grid-gutter, $grid-columns); 58 | 59 | // Return the CSS equation. 60 | @return $value; 61 | } 62 | 63 | @warn 'Unknown key in grid configuration: #{$use}'; 64 | @return 0; 65 | } 66 | 67 | /** 68 | * Generates grid system values for a given breakpoint and up. 69 | * 70 | * @param {String} $bpFrom - The breakpoint to start from. 71 | * 72 | * @return {Mixin} CSS rules for the given breakpoint and up. 73 | * 74 | * @example 75 | * @include mq-gs-from('l') { 76 | * width: gs(6, 5, $from); 77 | * } 78 | */ 79 | @mixin mq-gs-from($bpFrom) { 80 | $keyList: map.keys($config); 81 | $fromIndex: list.index($keyList, $bpFrom); 82 | 83 | @if not $fromIndex { 84 | @warn "Invalid breakpoint: #{$bpFrom}"; 85 | } 86 | 87 | @for $i from $fromIndex through length($keyList) { 88 | $key: nth($keyList, $i); 89 | @include mq($from: $key) using ($from) { 90 | @content ($from); 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * Fetches nested keys from a map. 97 | * 98 | * @param {Map} $map - The map to fetch from. 99 | * @param {Arglist} $keys - The keys to fetch. 100 | * 101 | * @return {*} The value associated with the keys. 102 | */ 103 | @function map-deep-get($map, $keys...) { 104 | @each $key in $keys { 105 | @if type-of($map) != "map" { 106 | @warn '`#{$map}` is not a map.'; 107 | @return false; 108 | } 109 | 110 | $map: map-get($map, $key); 111 | } 112 | 113 | @return $map; 114 | } 115 | 116 | /** 117 | * Gets a z-index value from a given layers map. 118 | * 119 | * @param {Arglist} $layers - The layers to fetch. 120 | * 121 | * @requires {Variable} $z-layers 122 | * 123 | * @example 124 | * z-index: layer(below); 125 | * z-index: layer(modal); 126 | * z-index: layer(modal, header); 127 | * 128 | * @return {Number} The z-index value. 129 | */ 130 | @function layer($layers...) { 131 | $value: map-deep-get($z-layers, $layers...); 132 | 133 | @if type-of($value) == "map" { 134 | $value: map-get($value, base); 135 | } 136 | 137 | @return $value; 138 | } 139 | 140 | /** 141 | * Strips the unit from a number. 142 | * 143 | * @param {Number} $value - The value with a unit. 144 | * 145 | * @example 146 | * $dimension: strip-units(10em); 147 | * 148 | * @return {Number} The unitless number. 149 | */ 150 | @function strip-units($value) { 151 | @return math.div($value, $value * 0 + 1); 152 | } 153 | 154 | /** 155 | * Converts pixels to rems based on the base font size. 156 | * 157 | * @access private 158 | * 159 | * @param {Number} $pxval - The value to convert. 160 | * 161 | * @example 162 | * $dimension: rem(12); // $base-font-size: 16px 163 | * 164 | * @requires {Variable} $base-font-size 165 | * 166 | * @return {Number} The value in rems. 167 | */ 168 | @function rem($pxval) { 169 | @if not unitless($pxval) { 170 | $pxval: strip-units($pxval); 171 | } 172 | 173 | $base: $base-font-size; 174 | @if not unitless($base) { 175 | $base: strip-units($base); 176 | } 177 | 178 | @return math.div($pxval, $base) * 1rem; 179 | } 180 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /utils/constants.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import UAParser from "ua-parser-js"; 3 | 4 | // Server vs Browser 5 | // See: https://github.com/vercel/next.js/issues/5354#issuecomment-520305040 6 | export const IS_SERVER = import.meta.server; 7 | export const IS_BROWSER = import.meta.client; 8 | 9 | // UA Constants 10 | // UA Parser 11 | const PARSER = new UAParser(); 12 | 13 | // Set UA Parser 14 | if (IS_BROWSER) { 15 | PARSER.setUA(navigator.userAgent); 16 | } 17 | 18 | export const OS = PARSER.getOS(); 19 | export const DEVICE = PARSER.getDevice(); 20 | export const BROWSER = PARSER.getBrowser(); 21 | 22 | export const IS_MOBILE = DEVICE.type === "mobile"; 23 | export const IS_TABLET = DEVICE.type === "tablet"; 24 | 25 | // Environment Constants 26 | export const DEV = import.meta.env.DEV; 27 | export const PROD = import.meta.env.PROD; 28 | 29 | // Responsive Constants 30 | export const BREAKPOINTS = { 31 | XXXS: 0, 32 | M: 768, 33 | L: 1024, 34 | XL: 1200, 35 | XXL: 1440, 36 | XXXL: 1920, 37 | }; 38 | -------------------------------------------------------------------------------- /utils/easing.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // no easing, no acceleration 3 | linear: (t) => t, 4 | // accelerating from zero velocity 5 | easeInQuad: (t) => t * t, 6 | // decelerating to zero velocity 7 | easeOutQuad: (t) => t * (2 - t), 8 | // acceleration until halfway, then deceleration 9 | easeInOutQuad: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t), 10 | // accelerating from zero velocity 11 | easeInCubic: (t) => t * t * t, 12 | // decelerating to zero velocity 13 | easeOutCubic: (t) => --t * t * t + 1, 14 | // acceleration until halfway, then deceleration 15 | easeInOutCubic: (t) => 16 | t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1, 17 | // accelerating from zero velocity 18 | easeInQuart: (t) => t * t * t * t, 19 | // decelerating to zero velocity 20 | easeOutQuart: (t) => 1 - --t * t * t * t, 21 | // acceleration until halfway, then deceleration 22 | easeInOutQuart: (t) => 23 | t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t, 24 | // accelerating from zero velocity 25 | easeInQuint: (t) => t * t * t * t * t, 26 | // decelerating to zero velocity 27 | easeOutQuint: (t) => 1 + --t * t * t * t * t, 28 | // acceleration until halfway, then deceleration 29 | easeInOutQuint: (t) => 30 | t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t, 31 | }; 32 | -------------------------------------------------------------------------------- /utils/helpers/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 | build() { 35 | const columns = Number(this.values['--grid-columns']) 36 | const gutters = Number(this.values['--grid-gutters']) 37 | 38 | this.root.innerHTML = '' 39 | 40 | for (let i = 0; i < columns; i++) { 41 | const el = document.createElement('div') 42 | 43 | el.id = `site-grid-column-${i}` 44 | el.className = `site-grid__column site-grid__column--${i}` 45 | 46 | this.root.append(el) 47 | } 48 | 49 | for (let i = 0; i < gutters; i++) { 50 | const el = document.createElement('div') 51 | const column = document.querySelector(`#site-grid-column-${i + 1}`) 52 | 53 | el.id = `site-grid-gutter-${i}` 54 | el.className = `site-grid__gutter site-grid__gutter--${i}` 55 | 56 | this.root.insertBefore(el, column) 57 | } 58 | 59 | const edge0 = document.createElement('div') 60 | const edge1 = document.createElement('div') 61 | 62 | edge0.id = 'site-grid-edge-0' 63 | edge0.className = 'site-grid__edge site-grid__edge--0' 64 | 65 | edge1.id = 'site-grid-edge-1' 66 | edge1.className = 'site-grid__edge site-grid__edge--1' 67 | 68 | this.root.append(edge1) 69 | this.root.prepend(edge0) 70 | } 71 | toggle(option, value) { 72 | switch (option) { 73 | case 'edges': { 74 | this.root.classList.toggle('site-grid--no-edges', !value) 75 | break 76 | } 77 | 78 | case 'columns': { 79 | this.root.classList.toggle('site-grid--no-columns', !value) 80 | break 81 | } 82 | 83 | case 'gutters': { 84 | this.root.classList.toggle('site-grid--no-gutters', !value) 85 | break 86 | } 87 | } 88 | } 89 | observe(el, variables) { 90 | const crawler = () => { 91 | const values = {} 92 | 93 | for (const variable of variables) { 94 | values[variable] = getComputedStyle(el).getPropertyValue(variable) 95 | } 96 | 97 | if (!equal(values, this.values)) { 98 | this.values = values 99 | this.build() 100 | } 101 | 102 | this.timeout ? clearTimeout(this.timeout) : null 103 | this.timeout = setTimeout(crawler, this.timeoutDuration) 104 | } 105 | 106 | crawler() 107 | } 108 | unobserve() { 109 | this.timeout ? clearTimeout(this.timeout) : null 110 | this.timeout = null 111 | } 112 | } 113 | 114 | export default Grid 115 | -------------------------------------------------------------------------------- /utils/helpers/i18n.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Return the direction of the layout based on the active locale 3 | * 4 | * @param {String} locale - Active locale 5 | * @return {String} Layout direction 6 | */ 7 | export const getLocaleDirection = (locale) => { 8 | return ['ar', 'az', 'dv', 'he', 'ku', 'fa', 'ur'].includes(locale) 9 | ? 'rtl' 10 | : 'ltr' 11 | } 12 | -------------------------------------------------------------------------------- /utils/raf.js: -------------------------------------------------------------------------------- 1 | Date.now = 2 | Date.now || 3 | function () { 4 | // thanks IE8 5 | return new Date().getTime(); 6 | }; 7 | 8 | if (typeof window !== "undefined") { 9 | if ("performance" in window === false) { 10 | window.performance = {}; 11 | } 12 | if ("now" in window.performance === false) { 13 | var nowOffset = Date.now(); 14 | 15 | if (performance.timing && performance.timing.navigationStart) { 16 | nowOffset = performance.timing.navigationStart; 17 | } 18 | 19 | window.performance.now = function now() { 20 | return Date.now() - nowOffset; 21 | }; 22 | } 23 | } 24 | 25 | class RAF { 26 | constructor() { 27 | this.funcs = {}; 28 | this.lastPass = {}; 29 | this.framerates = {}; 30 | this.nextFramePendingFuncs = []; 31 | this.nextFrameFuncs = []; 32 | this.postFrameFuncs = []; 33 | this.dt = Infinity; 34 | this.timeElapsed = 0; 35 | this.frame = 0; 36 | this.dictonary = []; 37 | this.last = performance.now(); 38 | this.initTime = performance.now(); 39 | this.isBlurred = false; 40 | this.onBefore = function () {}; 41 | this.onAfter = function () {}; 42 | 43 | if (typeof window !== "undefined") { 44 | this.init(); 45 | } 46 | } 47 | 48 | subscribe(id, func, framerate = null) { 49 | if (this.funcs[id]) { 50 | // console.warn('RAF - A listener with this id already exists.') 51 | return; 52 | } 53 | 54 | this.dictonary.push(id); 55 | this.funcs[id] = func; 56 | this.lastPass[id] = Date.now(); 57 | 58 | if (framerate !== null) { 59 | this.framerates[id] = 1 / framerate; 60 | } else { 61 | this.framerates[id] = framerate; 62 | // this.framerates[id] = framerate 63 | } 64 | } 65 | 66 | unsubscribe(id) { 67 | if (this.funcs[id]) { 68 | this.dictonary.splice(this.dictonary.indexOf(id), 1); 69 | delete this.funcs[id]; 70 | } 71 | } 72 | 73 | init() { 74 | window.addEventListener("focus", () => { 75 | this.last = performance.now(); 76 | }); 77 | 78 | window.addEventListener("visibilitychange", (e) => { 79 | if (document.visibilityState === "visible") { 80 | this.isBlurred = false; 81 | } else { 82 | this.isBlurred = true; 83 | } 84 | }); 85 | 86 | this.update = this.update.bind(this); 87 | this.update(); 88 | } 89 | 90 | nextFrame(func) { 91 | this.nextFramePendingFuncs.push(func); 92 | } 93 | 94 | postFrame(func) { 95 | this.postFrameFuncs.push(func); 96 | } 97 | 98 | update() { 99 | requestAnimationFrame(this.update); 100 | if (this.isBlurred) return; 101 | 102 | this.onBefore(); 103 | 104 | this.frame++; 105 | this.dt = performance.now() - this.last; 106 | this.timeElapsed += this.dt; 107 | 108 | while (this.nextFrameFuncs.length > 0) { 109 | this.nextFrameFuncs.splice(0, 1)[0](); 110 | } 111 | while (this.nextFramePendingFuncs.length > 0) { 112 | this.nextFrameFuncs.push(this.nextFramePendingFuncs.splice(0, 1)[0]); 113 | } 114 | 115 | // CHECK: Possible to use performance.now ? 116 | const now = Date.now(); 117 | 118 | for (let i = 0; i < this.dictonary.length; i++) { 119 | if ( 120 | this.framerates[this.dictonary[i]] !== null && 121 | now - this.lastPass[this.dictonary[i]] < 122 | this.framerates[this.dictonary[i]] * 1000 123 | ) { 124 | continue; 125 | } 126 | if (typeof this.funcs[this.dictonary[i]] === "function") { 127 | this.lastPass[this.dictonary[i]] = now; 128 | this.funcs[this.dictonary[i]](); 129 | } 130 | } 131 | 132 | while (this.postFrameFuncs.length > 0) { 133 | this.postFrameFuncs.splice(0, 1)[0](); 134 | } 135 | 136 | this.onAfter(); 137 | 138 | this.last = performance.now(); 139 | } 140 | } 141 | const out = new RAF(); 142 | export default out; 143 | --------------------------------------------------------------------------------