├── .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
--------------------------------------------------------------------------------