├── .gitignore ├── .parcelrc ├── README.md ├── package.json ├── src ├── index.html ├── js │ ├── classes │ │ └── Component.js │ ├── components │ │ └── SpiralScroll.js │ ├── index.js │ └── utils │ │ ├── bind.js │ │ ├── dom.js │ │ └── math.js └── scss │ ├── components │ └── _nav.scss │ ├── core │ ├── _fonts.scss │ ├── _globals.scss │ ├── _reset.scss │ └── _variables.scss │ ├── index.scss │ ├── pages │ └── _home.scss │ ├── shared │ ├── _fonts.scss │ └── _links.scss │ └── utils │ ├── _breakpoints.scss │ ├── _functions.scss │ └── _mixins.scss ├── static └── .gitkeep └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /public 2 | .env 3 | .cache 4 | /node_modules 5 | /dist 6 | /.parcel-cache 7 | .env -------------------------------------------------------------------------------- /.parcelrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@parcel/config-default"], 3 | "reporters": ["...", "parcel-reporter-static-files-copy"] 4 | } 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### To kickstart the boilerplate, run the following commands: 2 | 3 | ```bash 4 | yarn 5 | 6 | yarn dev 7 | ``` 8 | 9 | ### If you do not have yarn installed, delete the `yarn.lock` file and install via npm, 10 | 11 | ```bash 12 | npm install 13 | 14 | npm run dev 15 | ``` 16 | 17 | ### Or install yarn: 18 | 19 | ```bash 20 | npm install --global yarn 21 | ``` 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "profile-rebuild", 3 | "version": "1.0.0", 4 | "source": "src/index.html", 5 | "license": "MIT", 6 | "scripts": { 7 | "dev": "rm -rf .parcel-cache && parcel src/index.html -p 3000 --host 0.0.0.0", 8 | "build": "parcel build src/index.html --dist-dir dist" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "devDependencies": { 13 | "@parcel/config-default": "^2.0.0", 14 | "@parcel/transformer-sass": "^2.1.1", 15 | "auto-bind": "^4.0.0", 16 | "normalize-wheel": "^1.0.1", 17 | "parcel": "^2.3.2", 18 | "parcel-reporter-static-files-copy": "^1.3.0", 19 | "prefix": "^1.0.0", 20 | "sass": "^1.26.8" 21 | } 22 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Parcel Boilerplate 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/js/classes/Component.js: -------------------------------------------------------------------------------- 1 | import AutoBind from "../utils/bind"; 2 | 3 | export default class Component { 4 | constructor({ element, elements }) { 5 | AutoBind(this); 6 | 7 | this.addEventListeners(); 8 | 9 | this.selector = element; 10 | this.selectorChildren = { ...elements }; 11 | this.create(); 12 | this.innerWidth = window.innerWidth; 13 | } 14 | 15 | create() { 16 | if (this.selector instanceof HTMLElement) { 17 | this.element = this.selector; 18 | } else { 19 | this.element = document.querySelector(this.selector); 20 | } 21 | 22 | this.elements = {}; 23 | 24 | Object.keys(this.selectorChildren).forEach((key) => { 25 | const entry = this.selectorChildren[key]; 26 | 27 | if ( 28 | entry instanceof HTMLElement || 29 | entry instanceof NodeList || 30 | Array.isArray(entry) 31 | ) { 32 | this.elements[key] = entry; 33 | } else { 34 | this.elements[key] = this.element.querySelectorAll(entry); 35 | 36 | if (this.elements[key].length === 0) { 37 | this.elements[key] = null; 38 | } else if (this.elements[key].length === 1) { 39 | this.elements[key] = this.element.querySelector(entry); 40 | } 41 | } 42 | }); 43 | } 44 | 45 | update() { 46 | this.animationFrame = window.requestAnimationFrame(this.update); 47 | } 48 | 49 | destroy() { 50 | window.cancelAnimationFrame(this.animationFrame); 51 | } 52 | 53 | /** 54 | * Events 55 | */ 56 | 57 | onResize() {} 58 | 59 | onScroll(event) {} 60 | 61 | addEventListeners() { 62 | // window.addEventListener("scroll", this.onScroll, { passive: true }); 63 | window.addEventListener("wheel", this.onScroll, { passive: true }); 64 | 65 | window.addEventListener( 66 | "resize", 67 | () => { 68 | // Safari check 69 | if (this.innerWidth === window.innerWidth) return; 70 | this.onResize(); 71 | this.innerWidth = window.innerWidth; 72 | }, 73 | { passive: true } 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/js/components/SpiralScroll.js: -------------------------------------------------------------------------------- 1 | import Component from "../classes/Component"; 2 | import AutoBind from "../utils/bind"; 3 | import NormalizeWheel from "normalize-wheel"; 4 | import { lerp, clamp } from "../utils/math"; 5 | 6 | export default class extends Component { 7 | constructor(element) { 8 | super({ 9 | element, 10 | elements: { 11 | spiralSections: "[data-spiral-section]", 12 | }, 13 | }); 14 | 15 | AutoBind(this); 16 | 17 | this.scroll = { 18 | ease: 0.1, 19 | position: 0, 20 | current: 0, 21 | target: 0, 22 | limit: 1500, 23 | }; 24 | 25 | this.goldenRatio = 0.61723; 26 | this.widthToTransformOriginRatio = 0.7236; 27 | this.elementsLength = this.elements.spiralSections.length; 28 | this.currentSection = 0; 29 | this.scrollTimeout; 30 | this.activeClass = "c-spiral__section--active"; 31 | 32 | this.onResize(); 33 | this.update(); 34 | 35 | this.colors = []; 36 | } 37 | 38 | onResize() { 39 | this.width = Math.floor(window.innerWidth * this.goldenRatio); 40 | this.height = this.width; 41 | this.originX = Math.floor( 42 | window.innerWidth * this.widthToTransformOriginRatio 43 | ); 44 | this.originY = Math.floor( 45 | window.innerWidth * this.goldenRatio * this.widthToTransformOriginRatio 46 | ); 47 | 48 | this.initSpiral(); 49 | } 50 | 51 | onScroll(event) { 52 | const normalized = NormalizeWheel(event); 53 | const speed = normalized.pixelY * -0.1; 54 | 55 | this.scroll.target += speed; 56 | 57 | this.startTimeout(); 58 | // this.destroy(); 59 | } 60 | 61 | scrollElement() { 62 | const { spiralSections } = this.elements; 63 | 64 | const scale = Math.pow(this.goldenRatio, this.rotation / 90); 65 | 66 | this.element.style.transform = `rotate(${this.rotation}deg) scale(${scale})`; 67 | 68 | this.currentSection = Math.floor((this.rotation - 30) / -90); 69 | 70 | spiralSections.forEach((section, index) => { 71 | if (index === this.currentSection) { 72 | section.classList.add(this.activeClass); 73 | } else { 74 | section.classList.remove(this.activeClass); 75 | } 76 | 77 | section.style.backgroundColor = `rgba( 78 | ${50 * (this.currentSection + 1)}, 79 | ${30 * (this.currentSection + 1)}, 80 | 160, 81 | ${1 - index / this.elementsLength} 82 | )`; 83 | }); 84 | } 85 | 86 | snapScroll(currentRotation) { 87 | this.scroll.target = currentRotation; 88 | } 89 | 90 | startTimeout() { 91 | clearTimeout(this.scrollTimeout); 92 | 93 | this.scrollTimeout = setTimeout(() => { 94 | this.snapScroll(this.currentSection * -90); 95 | }, 200); 96 | } 97 | 98 | initSpiral() { 99 | const { spiralSections } = this.elements; 100 | this.element.style.transformOrigin = `${this.originX}px ${this.originY}px`; 101 | 102 | spiralSections.forEach((section, index) => { 103 | const rotation = Math.floor(90 * index); 104 | const scale = Math.pow(this.goldenRatio, index); 105 | 106 | section.style.width = `${this.width}px`; 107 | section.style.height = `${this.height}px`; 108 | section.style.transformOrigin = `${this.originX}px ${this.originY}px`; 109 | section.style.transform = `rotate(${rotation}deg) scale(${scale})`; 110 | 111 | section.textContent = `This is section ${index + 1}`; 112 | }); 113 | } 114 | 115 | update() { 116 | // this.scroll.target = clamp(0, this.scroll.limit, this.scroll.target); 117 | super.update(); 118 | 119 | this.scroll.current = lerp( 120 | this.scroll.current, 121 | this.scroll.target, 122 | this.scroll.ease 123 | ); 124 | 125 | this.rotation = this.scroll.current; 126 | 127 | this.scrollElement(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import SpiralScroll from "./components/SpiralScroll"; 2 | 3 | new SpiralScroll("[data-spiral]"); 4 | -------------------------------------------------------------------------------- /src/js/utils/bind.js: -------------------------------------------------------------------------------- 1 | // Gets all non-builtin properties up the prototype chain. 2 | const getAllProperties = (object) => { 3 | const properties = new Set(); 4 | 5 | do { 6 | for (const key of Reflect.ownKeys(object)) { 7 | properties.add([object, key]); 8 | } 9 | } while ( 10 | (object = Reflect.getPrototypeOf(object)) && 11 | object !== Object.prototype 12 | ); 13 | 14 | return properties; 15 | }; 16 | 17 | export default function autoBind(self, { include, exclude } = {}) { 18 | const filter = (key) => { 19 | const match = (pattern) => 20 | typeof pattern === "string" ? key === pattern : pattern.test(key); 21 | 22 | if (include) { 23 | return include.some(match); // eslint-disable-line unicorn/no-array-callback-reference 24 | } 25 | 26 | if (exclude) { 27 | return !exclude.some(match); // eslint-disable-line unicorn/no-array-callback-reference 28 | } 29 | 30 | return true; 31 | }; 32 | 33 | for (const [object, key] of getAllProperties(self.constructor.prototype)) { 34 | if (key === "constructor" || !filter(key)) { 35 | continue; 36 | } 37 | 38 | const descriptor = Reflect.getOwnPropertyDescriptor(object, key); 39 | if (descriptor && typeof descriptor.value === "function") { 40 | self[key] = self[key].bind(self); 41 | } 42 | } 43 | 44 | return self; 45 | } 46 | -------------------------------------------------------------------------------- /src/js/utils/dom.js: -------------------------------------------------------------------------------- 1 | export const mapElements = (element, object) => { 2 | const elements = {}; 3 | 4 | Object.keys(object).forEach((key) => { 5 | const entry = object[key]; 6 | 7 | if ( 8 | entry instanceof HTMLElement || 9 | entry instanceof NodeList || 10 | Array.isArray(entry) 11 | ) { 12 | elements[key] = entry; 13 | } else { 14 | elements[key] = element.querySelectorAll(entry); 15 | 16 | if (elements[key].length === 0) { 17 | elements[key] = null; 18 | } else if (elements[key].length === 1) { 19 | elements[key] = element.querySelector(entry); 20 | } 21 | } 22 | }); 23 | 24 | return elements; 25 | }; 26 | -------------------------------------------------------------------------------- /src/js/utils/math.js: -------------------------------------------------------------------------------- 1 | export function lerp(p1, p2, t) { 2 | return p1 + (p2 - p1) * t; 3 | } 4 | 5 | export function clamp(min, max, number) { 6 | return Math.max(min, Math.min(number, max)); 7 | } 8 | -------------------------------------------------------------------------------- /src/scss/components/_nav.scss: -------------------------------------------------------------------------------- 1 | .c-nav { 2 | } 3 | -------------------------------------------------------------------------------- /src/scss/core/_fonts.scss: -------------------------------------------------------------------------------- 1 | // @font-face { 2 | // font-family: "Suisse"; 3 | // src: url("../fonts/SuisseIntl-Book.woff2") format("woff2"), 4 | // url("../fonts/SuisseIntl-Book.woff") format("woff"); 5 | // font-weight: 500; 6 | // font-style: normal; 7 | // font-display: swap; 8 | // } 9 | 10 | // @font-face { 11 | // font-family: "Suisse"; 12 | // src: url("../fonts/SuisseIntl-SemiBold.woff2") format("woff2"), 13 | // url("../fonts/SuisseIntl-SemiBold.woff") format("woff"); 14 | // font-weight: 700; 15 | // font-style: normal; 16 | // font-display: swap; 17 | // } 18 | -------------------------------------------------------------------------------- /src/scss/core/_globals.scss: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: calc(100vw / 1440 * 10); 3 | -webkit-font-smoothing: antialiased; 4 | text-rendering: optimizeLegibility; 5 | -webkit-text-size-adjust: 100%; 6 | } 7 | 8 | // resets 9 | body { 10 | font-family: -apple-system, BlinkMacSystemFont, "Roboto", "Droid Sans", 11 | "Helvetica Neue", Helvetica, Arial, sans-serif; 12 | background: $black; 13 | color: $white; 14 | } 15 | -------------------------------------------------------------------------------- /src/scss/core/_reset.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | div, 4 | span, 5 | applet, 6 | object, 7 | iframe, 8 | h1, 9 | h2, 10 | h3, 11 | h4, 12 | h5, 13 | h6, 14 | p, 15 | blockquote, 16 | pre, 17 | a, 18 | abbr, 19 | acronym, 20 | address, 21 | big, 22 | cite, 23 | code, 24 | del, 25 | dfn, 26 | em, 27 | img, 28 | ins, 29 | kbd, 30 | q, 31 | s, 32 | samp, 33 | small, 34 | strike, 35 | strong, 36 | sub, 37 | sup, 38 | tt, 39 | var, 40 | b, 41 | u, 42 | i, 43 | center, 44 | dl, 45 | dt, 46 | dd, 47 | ol, 48 | ul, 49 | li, 50 | fieldset, 51 | form, 52 | label, 53 | legend, 54 | table, 55 | caption, 56 | tbody, 57 | tfoot, 58 | thead, 59 | tr, 60 | th, 61 | td, 62 | article, 63 | aside, 64 | canvas, 65 | details, 66 | embed, 67 | figure, 68 | figcaption, 69 | footer, 70 | header, 71 | hgroup, 72 | menu, 73 | nav, 74 | output, 75 | ruby, 76 | section, 77 | summary, 78 | time, 79 | mark, 80 | audio, 81 | video { 82 | margin: 0; 83 | padding: 0; 84 | border: 0; 85 | font-size: 100%; 86 | font: inherit; 87 | vertical-align: baseline; 88 | } 89 | 90 | article, 91 | aside, 92 | details, 93 | figcaption, 94 | figure, 95 | footer, 96 | header, 97 | hgroup, 98 | menu, 99 | nav, 100 | section { 101 | display: block; 102 | } 103 | 104 | body { 105 | line-height: 1; 106 | } 107 | 108 | ol, 109 | ul { 110 | list-style: none; 111 | } 112 | 113 | blockquote, 114 | q { 115 | quotes: none; 116 | } 117 | 118 | blockquote:before, 119 | blockquote:after, 120 | q:before, 121 | q:after { 122 | content: ""; 123 | content: none; 124 | } 125 | 126 | table { 127 | border-collapse: collapse; 128 | border-spacing: 0; 129 | } 130 | 131 | * { 132 | box-sizing: border-box; 133 | -webkit-font-smoothing: antialiased; 134 | text-rendering: optimizeLegibility; 135 | 136 | &::before, 137 | &::after { 138 | box-sizing: border-box; 139 | -webkit-font-smoothing: antialiased; 140 | text-rendering: optimizeLegibility; 141 | } 142 | } 143 | 144 | a { 145 | color: inherit; 146 | text-decoration: none; 147 | } 148 | -------------------------------------------------------------------------------- /src/scss/core/_variables.scss: -------------------------------------------------------------------------------- 1 | $black: #000000; 2 | $white: #ffffff; 3 | 4 | $ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1); 5 | -------------------------------------------------------------------------------- /src/scss/index.scss: -------------------------------------------------------------------------------- 1 | @import "utils/breakpoints"; 2 | @import "utils/functions"; 3 | @import "utils/mixins"; 4 | 5 | @import "core/reset"; 6 | @import "core/variables"; 7 | @import "core/fonts"; 8 | @import "core/globals"; 9 | 10 | @import "shared/fonts"; 11 | @import "shared/links"; 12 | 13 | @import "components/nav"; 14 | 15 | @import "pages/home"; 16 | -------------------------------------------------------------------------------- /src/scss/pages/_home.scss: -------------------------------------------------------------------------------- 1 | .c-home { 2 | .c-spiral { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | width: 100%; 7 | height: 100%; 8 | // transition: transform 0.3s ease; 9 | will-change: transform; 10 | backface-visibility: hidden; 11 | 12 | &__section { 13 | position: absolute; 14 | transition: 0.2s border-radius ease, background-color 0.2s ease; 15 | font-size: 24px; 16 | display: flex; 17 | justify-content: center; 18 | align-items: center; 19 | backface-visibility: hidden; 20 | 21 | &:not(&--active) { 22 | &:hover { 23 | border-radius: 40px; 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/scss/shared/_fonts.scss: -------------------------------------------------------------------------------- 1 | %title-2 { 2 | font-size: 32px; 3 | line-height: 0.7; 4 | letter-spacing: calc(-0.015 * 32px); 5 | } 6 | -------------------------------------------------------------------------------- /src/scss/shared/_links.scss: -------------------------------------------------------------------------------- 1 | %link__wrapper { 2 | display: inline-block; 3 | overflow: hidden; 4 | position: relative; 5 | vertical-align: top; 6 | } 7 | 8 | %link__line { 9 | // @media (any-pointer: fine) { 10 | background: currentColor; 11 | bottom: 0; 12 | content: ""; 13 | height: 1px; 14 | left: 0; 15 | position: absolute; 16 | transition: transform 0.7s $ease-out-expo; 17 | width: 100%; 18 | // } 19 | } 20 | 21 | %link__line--visible { 22 | transform: scaleX(1); 23 | transform-origin: left center; 24 | } 25 | 26 | %link__line--hidden { 27 | transform: scaleX(0); 28 | transform-origin: right center; 29 | } 30 | 31 | %link { 32 | @extend %link__wrapper; 33 | 34 | display: inline-block; 35 | 36 | &:after { 37 | @extend %link__line; 38 | @extend %link__line--visible; 39 | } 40 | 41 | &:hover { 42 | &:after { 43 | @extend %link__line--hidden; 44 | } 45 | } 46 | } 47 | 48 | %link--hidden { 49 | @extend %link__wrapper; 50 | 51 | display: inline-block; 52 | 53 | &:after { 54 | @extend %link__line; 55 | @extend %link__line--hidden; 56 | } 57 | 58 | &:hover { 59 | &:after { 60 | @extend %link__line--visible; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/scss/utils/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | $breakpoints: ( 2 | "phone": 425px, 3 | "tablet": 768px, 4 | "desktop": 1366px, 5 | "LGdesktop": 1920px 6 | ) !default; 7 | 8 | /// 9 | /// Creates a list of static expressions or media types 10 | /// 11 | /// @author Eduardo Boucas 12 | /// 13 | /// @example scss - Creates a single media type (screen) 14 | /// $media-expressions: ('screen': 'screen'); 15 | /// 16 | /// @example scss - Creates a static expression with logical disjunction (OR operator) 17 | /// $media-expressions: ( 18 | /// 'retina2x': ( 19 | /// '(-webkit-min-device-pixel-ratio: 2)', 20 | /// '(min-resolution: 192dpi)' 21 | /// ) 22 | /// ); 23 | /// 24 | $media-expressions: ( 25 | "screen": "screen", 26 | "print": "print", 27 | "handheld": "handheld", 28 | "retina2x": ( 29 | "(-webkit-min-device-pixel-ratio: 2)", 30 | "(min-resolution: 192dpi)" 31 | ), 32 | "retina3x": ( 33 | "(-webkit-min-device-pixel-ratio: 3)", 34 | "(min-resolution: 350dpi)" 35 | ) 36 | ) !default; 37 | 38 | /// 39 | /// Defines a number to be added or subtracted from each unit when declaring breakpoints with exclusive intervals 40 | /// 41 | /// @author Eduardo Boucas 42 | /// 43 | /// @example scss - Interval for pixels is defined as `1` by default 44 | /// @include media(">128px") {} 45 | /// 46 | /// /* Generates: */ 47 | /// @media (min-width: 129px) {} 48 | /// 49 | /// @example scss - Interval for ems is defined as `0.01` by default 50 | /// @include media(">20em") {} 51 | /// 52 | /// /* Generates: */ 53 | /// @media (min-width: 20.01em) {} 54 | /// 55 | /// @example scss - Interval for rems is defined as `0.1` by default, to be used with `font-size: 62.5%;` 56 | /// @include media(">2.0rem") {} 57 | /// 58 | /// /* Generates: */ 59 | /// @media (min-width: 2.1rem) {} 60 | /// 61 | $unit-intervals: ( 62 | "px": 1, 63 | "em": 0.01, 64 | "rem": 0.1 65 | ) !default; 66 | /// 67 | /// Generates a media query based on a list of conditions 68 | /// 69 | /// @author Eduardo Boucas 70 | /// 71 | /// @param {List} $conditions - Media query conditions 72 | /// 73 | /// @example scss - With a single set breakpoint 74 | /// @include media(">phone") { } 75 | /// 76 | /// @example scss - With two set breakpoints 77 | /// @include media(">phone", "<=tablet") { } 78 | /// 79 | /// @example scss - With custom values 80 | /// @include media(">=358px", "<850px") { } 81 | /// 82 | /// @example scss - With set breakpoints with custom values 83 | /// @include media(">desktop", "<=1350px") { } 84 | /// 85 | /// @example scss - With a static expression 86 | /// @include media("retina2x") { } 87 | /// 88 | /// @example scss - Mixing everything 89 | /// @include media(">=350px", "") { 224 | $element: "(min-width: #{$result + $interval})"; 225 | } @else if ($operator == "<") { 226 | $element: "(max-width: #{$result - $interval})"; 227 | } @else if ($operator == ">=") { 228 | $element: "(min-width: #{$result})"; 229 | } @else if ($operator == "<=") { 230 | $element: "(max-width: #{$result})"; 231 | } @else { 232 | @warn '#{$expression} is missing an operator.'; 233 | } 234 | } @else { 235 | $element: $result; 236 | } 237 | 238 | @return $element; 239 | } 240 | 241 | /// 242 | /// Replaces the first occurence of the string with the replacement string 243 | /// 244 | /// @author Eduardo Boucas 245 | /// 246 | /// @param {String} $search - The value being searched for 247 | /// @param {String} $replace - The replacement string 248 | /// @param {String} $subject - The string being replaced on 249 | /// 250 | /// @return {String | Null} 251 | /// 252 | @function str-replace-first($search, $replace, $subject) { 253 | $search-start: str-index($subject, $search); 254 | 255 | @if $search-start == null { 256 | @return $subject; 257 | } 258 | 259 | $result: str-slice($subject, 0, $search-start - 1); 260 | $result: $result + $replace; 261 | $result: $result + str-slice($subject, $search-start + str-length($search)); 262 | 263 | @return $result; 264 | } 265 | 266 | /// 267 | /// Casts a number to a string 268 | /// 269 | /// @author Hugo Giraudel 270 | /// 271 | /// @param {String} $string - Number to be parsed 272 | /// 273 | /// @return {List | Null} 274 | /// 275 | @function to-number($string) { 276 | // Matrices 277 | $strings: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"; 278 | $numbers: 0 1 2 3 4 5 6 7 8 9; 279 | 280 | // Result 281 | $result: 0; 282 | $divider: 0; 283 | $minus: false; 284 | 285 | // Looping through all characters 286 | @for $i from 1 through str-length($string) { 287 | $character: str-slice($string, $i, $i); 288 | $index: index($strings, $character); 289 | 290 | @if $character == "-" { 291 | $minus: true; 292 | } @else if $character == "." { 293 | $divider: 1; 294 | } @else { 295 | @if type-of($index) != "number" { 296 | $result: if($minus, $result * -1, $result); 297 | @return _length($result, str-slice($string, $i)); 298 | } 299 | 300 | $number: nth($numbers, $index); 301 | 302 | @if $divider == 0 { 303 | $result: $result * 10; 304 | } @else { 305 | // Move the decimal dot to the left 306 | $divider: $divider * 10; 307 | $number: $number / $divider; 308 | } 309 | 310 | $result: $result + $number; 311 | } 312 | } 313 | 314 | @return if($minus, $result * -1, $result); 315 | } 316 | 317 | @function _length($number, $unit) { 318 | $strings: "px" "cm" "mm" "%" "ch" "pica" "in" "em" "rem" "pt" "pc" "ex" "vw" 319 | "vh" "vmin" "vmax"; 320 | $units: 1px 1cm 1mm 1% 1ch 1pica 1in 1em 1rem 1pt 1pc 1ex 1vw 1vh 1vmin 1vmax; 321 | $index: index($strings, $unit); 322 | 323 | @if type-of($index) != "number" { 324 | @warn 'Unknown unit `#{$unit}`.'; 325 | @return false; 326 | } 327 | 328 | @return $number * nth($units, $index); 329 | } 330 | -------------------------------------------------------------------------------- /src/scss/utils/_functions.scss: -------------------------------------------------------------------------------- 1 | @function z($name) { 2 | @if index($z-indexes, $name) { 3 | @return (length($z-indexes) - index($z-indexes, $name)) + 1; 4 | } @else { 5 | @warn 'There is no item "#{$name}" in this list; Choose one of: #{$z-indexes}'; 6 | 7 | @return null; 8 | } 9 | } 10 | 11 | @function toRem($value) { 12 | $remValue: calc($value / 10) + rem; 13 | @return $remValue; 14 | } 15 | -------------------------------------------------------------------------------- /src/scss/utils/_mixins.scss: -------------------------------------------------------------------------------- 1 | %cover { 2 | height: 100%; 3 | left: 0; 4 | object-fit: cover; 5 | position: absolute; 6 | top: 0; 7 | width: 100%; 8 | } 9 | 10 | @mixin placeholder { 11 | &.placeholder { 12 | @content; 13 | } 14 | &::-webkit-input-placeholder { 15 | @content; 16 | } 17 | &::-moz-placeholder { 18 | @content; 19 | } 20 | &:-moz-placeholder { 21 | @content; 22 | } 23 | &:-ms-input-placeholder { 24 | @content; 25 | } 26 | } 27 | 28 | @mixin cursor { 29 | @media (any-pointer: fine) { 30 | @content; 31 | } 32 | } 33 | 34 | @mixin cursor-mobile { 35 | @media (pointer: coarse) { 36 | @content; 37 | } 38 | 39 | @include media(">tablet") { 40 | @content; 41 | } 42 | } 43 | 44 | @mixin ratio($height, $width) { 45 | font-size: 0; 46 | overflow: hidden; 47 | position: relative; 48 | aspect-ratio: calc($width / $height); 49 | 50 | @supports not (aspect-ratio: calc($width / $height)) { 51 | &:after { 52 | content: ""; 53 | display: inline-block; 54 | padding-top: calc($height / $width) * 100%; 55 | width: 100%; 56 | } 57 | } 58 | 59 | img, 60 | video { 61 | @extend %img; 62 | position: absolute; 63 | top: 0; 64 | left: 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oluwadareseyi/narrowdesign-rebuild/a25edf9160414a862434a82583bae4fa39a2c627/static/.gitkeep --------------------------------------------------------------------------------