@profitlich/template-toolkit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,112 @@
1
+ import ftp from 'basic-ftp';
2
+ import dotenv from 'dotenv';
3
+ import { glob } from 'glob';
4
+ import path from 'path';
5
+ import readline from 'readline';
6
+ import cliProgress from 'cli-progress';
7
+
8
+ // Helper for making sure the upload progress bars go to 100%
9
+ const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
10
+
11
+ export async function runDeploy(mode, uploadTasks) {
12
+ const client = new ftp.Client();
13
+ client.ftp.verbose = false;
14
+ let activeProgressBar = null;
15
+
16
+ try {
17
+ const modeUpper = mode.toUpperCase();
18
+ console.log(`šŸš€ Starting deployment for: ${modeUpper}`);
19
+
20
+ await client.access({
21
+ host: process.env[`FTP_HOST_${modeUpper}`],
22
+ user: process.env[`FTP_USER_${modeUpper}`],
23
+ password: process.env[`FTP_PASSWORD_${modeUpper}`],
24
+ secure: true
25
+ });
26
+
27
+ for (const task of uploadTasks) {
28
+ console.log(`\nProcessing Task: ${task.name}`);
29
+
30
+ const files = await glob(task.localPattern, {
31
+ nodir: true,
32
+ dot: true,
33
+ ignore: task.ignore
34
+ });
35
+
36
+ if (files.length === 0) {
37
+ console.log('No files found for this task.');
38
+ continue;
39
+ }
40
+
41
+ const newBar = new cliProgress.SingleBar({
42
+ format: ' Upload |{bar}| {percentage}% {value}/{total} files {duration_formatted}',
43
+ barCompleteChar: 'ā–ˆ',
44
+ barIncompleteChar: 'ā–‘',
45
+ hideCursor: true
46
+ });
47
+
48
+ activeProgressBar = newBar;
49
+ activeProgressBar.start(files.length, 0);
50
+
51
+ for (const file of files) {
52
+ const relativeFile = path.relative(task.localBase, file);
53
+ const remotePath = path.join(task.remoteDir, relativeFile).replace(/\\/g, '/');
54
+
55
+ await client.ensureDir(path.dirname(remotePath));
56
+ await client.uploadFrom(file, remotePath);
57
+
58
+ activeProgressBar.increment();
59
+ }
60
+
61
+ activeProgressBar.stop();
62
+ activeProgressBar = null;
63
+ await delay(50);
64
+ }
65
+
66
+ console.log('\nāœ… Deployment completed successfully!');
67
+
68
+ } catch (err) {
69
+ if (activeProgressBar) {
70
+ activeProgressBar.stop();
71
+ }
72
+ console.error('Deployment failed:', err);
73
+ } finally {
74
+ client.close();
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Run the deploy script.
80
+ * @param {Array} uploadTasks - array of { name, localPattern, localBase, remoteDir, ignore? }
81
+ */
82
+ export function run(uploadTasks) {
83
+ const mode = process.argv[2];
84
+ if (!mode || (mode !== 'staging' && mode !== 'production')) {
85
+ console.error('Error: a mode needs to be given, either "staging" or "production"');
86
+ process.exit(1);
87
+ }
88
+
89
+ dotenv.config();
90
+
91
+ // In production mode prompt for confirmation
92
+ if (mode === 'production') {
93
+ const rl = readline.createInterface({
94
+ input: process.stdin,
95
+ output: process.stdout
96
+ });
97
+
98
+ rl.question('šŸ”“ Do you really want to deploy to PRODUCTION? Type "yes" for confirmation: ', (answer) => {
99
+ rl.close();
100
+ if (answer.toLowerCase() === 'yes') {
101
+ console.log('Confirmed. Starting upload...');
102
+ runDeploy(mode, uploadTasks);
103
+ } else {
104
+ console.log('āŒ Deployment aborted.');
105
+ process.exit(0);
106
+ }
107
+ });
108
+ // In all other modes run deploy without prompt
109
+ } else {
110
+ runDeploy(mode, uploadTasks);
111
+ }
112
+ }
@@ -0,0 +1,153 @@
1
+ @mixin hamburger($hamburger-breite, $hamburger-hoehe, $hamburger-line-width, $hamburger-color, $hamburger-color-active) {
2
+
3
+ // Settings
4
+ // ==================================================
5
+ $hamburger-padding-x : 15px !default;
6
+ $hamburger-padding-y : 15px !default;
7
+ $hamburger-line-gap : calc(($hamburger-hoehe - 3 * $hamburger-line-width) / 2) !default;
8
+ $hamburger-color : #000 !default;
9
+ $hamburger-layer-border-radius : 0 !default;
10
+ $hamburger-hover-opacity : 1 !default;
11
+ $hamburger-active-layer-color : $hamburger-color-active !default;
12
+ $hamburger-active-hover-opacity: $hamburger-hover-opacity !default;
13
+ $hamburger-animation-timing: 0.195s;
14
+ $hamburger-compression-speed: 0.12s;
15
+ $hamburger-turn-speed: $hamburger-animation-timing - $hamburger-compression-speed;
16
+
17
+ // To use CSS filters as the hover effect instead of opacity,
18
+ // set $hamburger-hover-use-filter as true and
19
+ // change the value of $hamburger-hover-filter accordingly.
20
+ $hamburger-hover-use-filter : false !default;
21
+ $hamburger-hover-filter : opacity(50%) !default;
22
+ $hamburger-active-hover-filter: $hamburger-hover-filter !default;
23
+
24
+
25
+ // padding: $hamburger-padding-y $hamburger-padding-x;
26
+
27
+
28
+ &:hover {
29
+ @if $hamburger-hover-use-filter==true {
30
+ filter: $hamburger-hover-filter;
31
+ }
32
+
33
+ @else {
34
+ opacity: $hamburger-hover-opacity;
35
+ }
36
+ }
37
+
38
+ body[data-menu-active='true'] & {
39
+ &:hover {
40
+ @if $hamburger-hover-use-filter==true {
41
+ filter: $hamburger-active-hover-filter;
42
+ }
43
+
44
+ @else {
45
+ opacity: $hamburger-active-hover-opacity;
46
+ }
47
+ }
48
+
49
+ .hamburger-inner,
50
+ .hamburger-inner::before,
51
+ .hamburger-inner::after {
52
+ background-color: $hamburger-active-layer-color;
53
+ transition: 0s $hamburger-compression-speed linear;
54
+ }
55
+ }
56
+
57
+ .hamburger-box {
58
+ width: $hamburger-breite;
59
+ height: calc($hamburger-line-width * 3 + $hamburger-line-gap * 2);
60
+ display: inline-block;
61
+ position: relative;
62
+ }
63
+
64
+ .hamburger-inner {
65
+ display: block;
66
+ top: 50%;
67
+ margin-top: calc($hamburger-line-width / -2);
68
+
69
+ &,
70
+ &::before,
71
+ &::after {
72
+ width: $hamburger-breite;
73
+ height: $hamburger-line-width;
74
+ background-color: $hamburger-color;
75
+ border-radius: $hamburger-layer-border-radius;
76
+ position: absolute;
77
+ transition-property: transform;
78
+ transition-duration: 0.15s;
79
+ transition-timing-function: ease;
80
+ }
81
+
82
+ &::before,
83
+ &::after {
84
+ content: "";
85
+ display: block;
86
+ }
87
+
88
+ &::before {
89
+ top: calc(($hamburger-line-gap + $hamburger-line-width) * -1);
90
+ }
91
+
92
+ &::after {
93
+ bottom: calc(($hamburger-line-gap + $hamburger-line-width) * -1);
94
+ }
95
+ }
96
+
97
+
98
+
99
+ // Aus Basis
100
+ display: block;
101
+ cursor: pointer;
102
+
103
+ transition-property: opacity, filter;
104
+ transition-duration: 0.15s;
105
+ transition-timing-function: linear;
106
+
107
+ // Normalize (<button>)
108
+ font: inherit;
109
+ color: inherit;
110
+ text-transform: none;
111
+ background-color: transparent;
112
+ border: 0;
113
+ margin: 0;
114
+ overflow: visible;
115
+ line-height: 0;
116
+
117
+ .hamburger-inner {
118
+ transition-duration: $hamburger-turn-speed;
119
+ transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
120
+
121
+ &::before {
122
+ transition: top $hamburger-turn-speed $hamburger-compression-speed ease,
123
+ opacity $hamburger-turn-speed ease;
124
+ }
125
+
126
+ &::after {
127
+ transition: bottom $hamburger-turn-speed $hamburger-compression-speed ease,
128
+ transform $hamburger-turn-speed cubic-bezier(0.55, 0.055, 0.675, 0.19);
129
+ }
130
+ }
131
+
132
+ body[data-menu-active='true'] & {
133
+ .hamburger-inner {
134
+ transform: rotate(45deg);
135
+ transition-delay: $hamburger-compression-speed;
136
+ transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
137
+
138
+ &::before {
139
+ top: 0;
140
+ opacity: 0;
141
+ transition: top $hamburger-turn-speed ease,
142
+ opacity $hamburger-turn-speed $hamburger-compression-speed ease;
143
+ }
144
+
145
+ &::after {
146
+ bottom: 0;
147
+ transform: rotate(-90deg);
148
+ transition: bottom $hamburger-turn-speed ease,
149
+ transform $hamburger-turn-speed $hamburger-compression-speed cubic-bezier(0.215, 0.61, 0.355, 1);
150
+ }
151
+ }
152
+ }
153
+ }
@@ -0,0 +1,238 @@
1
+ @use "config";
2
+ @use "sass:list";
3
+ @use "sass:map";
4
+ @use "sass:meta";
5
+ @use "sass:math";
6
+
7
+ // Media queries get the breakpoints
8
+ // Breakpints get the layouts
9
+ // Depending on the project, only a part of the layout names are used,
10
+ // so there needs to be an intervention in the breakpoints to deliver a number even if a layout is not present.
11
+ // Example: There are only the layouts smartphone, tablet and desktop.
12
+ // Then tabletPortrait must deliver the value of tablet
13
+
14
+ $tabletPortrait: null;
15
+ @if map.get(config.$layouts, tabletPortrait) {
16
+ $tabletPortrait: tabletPortrait;
17
+ // no tabletPortrait size is defined
18
+ } @else {
19
+ @if map.get(config.$layouts, tablet) {
20
+ $tabletPortrait: tablet;
21
+ // neither tablet size is defined
22
+ } @else {
23
+ $tabletPortrait: desktop;
24
+ }
25
+ }
26
+
27
+ $bigFrom: null;
28
+ @if map.get(config.$layouts, big) {
29
+ $bigFrom: big;
30
+ } @else {
31
+ $bigFrom: 999999px;
32
+ }
33
+
34
+ $breakpointsVariables: (
35
+ smartphoneTo: map.get(config.$breakpoints, $tabletPortrait) - 1px,
36
+ tabletPortraitFrom: map.get(config.$breakpoints, $tabletPortrait) + 0px,
37
+ tabletPortraitTo: map.get(config.$breakpoints, tabletLandscape) - 1px,
38
+ tabletLandscapeFrom:map.get(config.$breakpoints, tabletLandscape) + 0px,
39
+ tabletLandscapeTo: map.get(config.$breakpoints, desktop) - 1px,
40
+ desktopFrom: map.get(config.$breakpoints, desktop) + 0px,
41
+ desktopTo: map.get(config.$breakpoints, big) - 1px,
42
+ bigFrom: map.get(config.$breakpoints, big) + 0px,
43
+ );
44
+
45
+ // Gibt es kein Layout big, soll die media query desktop keine max-width haben
46
+ $mediaqueryDesktop: null;
47
+ @if map.get(config.$layouts, big) {
48
+ $mediaqueryDesktop: (min-width: map.get($breakpointsVariables, desktopFrom), max-width: map.get($breakpointsVariables, desktopTo));
49
+ } @else {
50
+ $mediaqueryDesktop: (min-width: map.get($breakpointsVariables, desktopFrom));
51
+ }
52
+
53
+ $mediaqueries: (
54
+ smartphone: (max-width: map.get($breakpointsVariables, smartphoneTo)),
55
+ tablet: (min-width: map.get($breakpointsVariables, tabletPortraitFrom), max-width: map.get($breakpointsVariables, tabletLandscapeTo)),
56
+ tabletFrom: (min-width: map.get($breakpointsVariables, tabletPortraitFrom)),
57
+ tabletTo: (max-width: map.get($breakpointsVariables, tabletLandscapeTo)),
58
+ tabletPortraitFrom: (min-width: map.get($breakpointsVariables, tabletPortraitFrom)),
59
+ tabletPortrait: (min-width: map.get($breakpointsVariables, tabletPortraitFrom), max-width: map.get($breakpointsVariables, tabletPortraitTo)),
60
+ tabletPortraitTo: (max-width: map.get($breakpointsVariables, tabletPortraitTo)),
61
+ tabletLandscapeFrom:(min-width: map.get($breakpointsVariables, tabletLandscapeFrom)),
62
+ tabletLandscape: (min-width: map.get($breakpointsVariables, tabletLandscapeFrom), max-width: map.get($breakpointsVariables, tabletLandscapeTo)),
63
+ tabletLandscapeTo: (max-width: map.get($breakpointsVariables, tabletLandscapeTo)),
64
+ desktopFrom: (min-width: map.get($breakpointsVariables, desktopFrom)),
65
+ desktop: $mediaqueryDesktop,
66
+ desktopTo: (max-width: map.get($breakpointsVariables, desktopTo)),
67
+ big: (min-width: map.get($breakpointsVariables, bigFrom)),
68
+ );
69
+
70
+
71
+ // Font
72
+ @mixin font($layout, $fontSize, $lineHeight, $bold:'false') {
73
+
74
+ font-size: size($layout, $fontSize);
75
+ line-height: size($layout, $lineHeight);
76
+
77
+ @if ($bold != 'false') {
78
+ font-weight: $bold;
79
+ }
80
+
81
+ }
82
+
83
+
84
+ // columns
85
+ // Calculates the width (as a fraction of 100%)
86
+ // Per default the function assumes that the given space does not contain column gaps;
87
+ // with the variable $columnGapsAddedBasis they can be specified.
88
+ // $columnGapsAddedBasis is to be used when the space to be calculated contains additional column gaps outside the $columnsBasis.
89
+ // $columnGapsAdditional is to be used when additionally to the $columns column gaps are to be added (e.g left and/or right).
90
+ // $shift is for adding a value to the result, for example a margin
91
+ // $inverse is for calculating the width as a negative number, for example for a negative shift in a stage system
92
+ @function columns($layout, $columns, $columnsBasis, $columnGapsAddedBasis:0, $columnGapsAdditional:0, $margin:0, $shift:0, $invers:false) {
93
+ $columnGap: size($layout, map.get(config.$gutter, $layout) );
94
+ $margin: size($layout, $margin);
95
+ $shift: size($layout, $shift);
96
+ @if ( $invers == false ) {
97
+ $invers: 1;
98
+ } @else {
99
+ $invers: -1;
100
+ }
101
+
102
+ // available space
103
+ // minus column gaps within the space
104
+ // equals space for content
105
+ // divided by number of columns in space
106
+ // equals width of each column
107
+ // times number of desired columns
108
+ // equals width of desired columns
109
+ // plus column gaps between desired columns
110
+ // equals width of desired columns including gaps
111
+ // plus additional column gaps
112
+ @return calc(
113
+ (
114
+ (
115
+ // available space
116
+ 100%
117
+ // all column gaps in the given space
118
+ - $margin - $columnGap * ($columnsBasis + $columnGapsAddedBasis - 1)
119
+ )
120
+ // number of columns in the given space
121
+ / $columnsBasis
122
+ // desired columns
123
+ * ($columns * $invers)
124
+ // columns gaps between desired columns plus additional column gaps
125
+ // inverse to get a negative value
126
+ + ($columnGap * ($columns - 1 + $columnGapsAdditional) * $invers)
127
+ // Shift
128
+ + ($shift * $invers)
129
+ )
130
+ );
131
+ }
132
+
133
+
134
+
135
+ // Size
136
+ // converts a number to a size in px or vw
137
+ // depending on the unit set for the layout
138
+ // accepts single value or a list of values
139
+ @function size($layout, $numbers) {
140
+ // Check whether second argument is a number or a list
141
+ @if (meta.type-of($numbers) == 'list') {
142
+ $result-list: (); // Empty list to store results
143
+
144
+ // Iterate over each element in the list
145
+ @each $number in $numbers {
146
+ // Test if the current element is a number
147
+ @if (meta.type-of($number) == 'number') {
148
+ // add calculated value to the result list
149
+ $result-list: list.append($result-list, _calculate-single-size($number, $layout));
150
+ } @else {
151
+ // Error if the current element is not a number
152
+ @error "Listenelement '#{$number}' übergeben an size() ist keine Zahl. Alle Elemente müssen Zahlen sein.";
153
+ }
154
+ }
155
+ @return $result-list; // Return the list of calculated sizes
156
+ } @else if (meta.type-of($numbers) == 'number') {
157
+ // If second argument is a single number, calculate and return the size
158
+ @return _calculate-single-size($numbers, $layout);
159
+ } @else {
160
+ // Error if the second argument is neither a number nor a list
161
+ @error "Das zweite Argument der Funktion size() muss eine Zahl oder eine Liste von Zahlen sein. Empfangen: #{type-of($numbers)} '#{$numbers}'.";
162
+ }
163
+ }
164
+
165
+ // Round a number to specified decimal places
166
+ @function _decimal-round($number, $digits: 0) {
167
+ $n: math.pow(10, $digits);
168
+ @return math.div(math.round($number * $n), $n);
169
+ }
170
+
171
+ // Helper function to calculate a single size value (px or vw)
172
+ // This function is used by the main size() function
173
+ @function _calculate-single-size($number, $layout) {
174
+ // Get the width and unit type from the config
175
+ $width: map.get(config.$layouts, $layout);
176
+ $unit-type: map.get(config.$units, $layout);
177
+
178
+ // Error handling for missing width or unit type
179
+ @if ($width == null) {
180
+ @error "Layout '#{$layout}' ist nicht in 'config.$layouts' definiert.";
181
+ }
182
+ @if ($unit-type == null) {
183
+ @error "Der Einheitentyp für Layout '#{$layout}' ist nicht in 'config.$units' definiert.";
184
+ }
185
+
186
+
187
+ @if ($unit-type == 'px') {
188
+ @return $number * 1px;
189
+ } @else if ($unit-type == 'vw') {
190
+ @return _decimal-round(math.div($number, $width) * 100, 2) * 1vw;
191
+ } @else {
192
+ @error "Nicht unterstützter Einheitstyp '#{$unit-type}' für Layout '#{$layout}'. Nur 'px' oder 'vw' werden unterstützt.";
193
+ }
194
+ }
195
+
196
+
197
+ // Distance
198
+ @mixin marginPadding($layout, $mode, $position, $top: 0, $right: 0, $bottom: 0, $left: 0) {
199
+
200
+ // @include mode($layout, padding/margin, all/top/right/bottom/left, 0, 0, 0, 0)
201
+
202
+ @if ( map.get(config.$units, $layout) == 'px' ) {
203
+ $top: $top + px;
204
+ $right: $right + px;
205
+ $bottom: $bottom + px;
206
+ $left: $left + px;
207
+ } @else {
208
+
209
+ @if meta.type-of($top) == 'number'{
210
+ $top: size($layout, $top);
211
+ }
212
+ @if meta.type-of($right) == 'number'{
213
+ $right: size($layout, $right);
214
+ }
215
+ @if meta.type-of($bottom) == 'number'{
216
+ $bottom: size($layout, $bottom);
217
+ }
218
+ @if meta.type-of($left) == 'number'{
219
+ $left: size($layout, $left);
220
+ }
221
+ }
222
+
223
+ @if $position == all {
224
+ #{$mode}: $top $right $bottom $left;
225
+ }
226
+ @if $position == top {
227
+ #{$mode}-top: $top;
228
+ }
229
+ @if $position == right {
230
+ #{$mode}-right: $top;
231
+ }
232
+ @if $position == bottom {
233
+ #{$mode}-bottom: $top;
234
+ }
235
+ @if $position == left {
236
+ #{$mode}-left: $top;
237
+ }
238
+ }
@@ -0,0 +1,42 @@
1
+ @use "layout";
2
+
3
+ @use "sass:map";
4
+ @use "sass:list";
5
+
6
+ // Zusammenlegen aller Meda Queries in eine
7
+ // https://www.sitepoint.com/sass-mixin-media-merging
8
+
9
+ @mixin mediaquery($mediaquery) {
10
+ // Hole die map $mediaquery aus der map $mediaqueries
11
+ $queries: map.get(layout.$mediaqueries, $mediaquery);
12
+
13
+ // Wenn es keine $mediaquery in der map gibt, gebe einen Fehler aus
14
+ @if not $queries {
15
+ @error "No value could be retrieved from `#{$mediaquery}`. "
16
+ + "Please make sure it is defined in `$mediaqueries` map.";
17
+ }
18
+
19
+ // Include the media mixin with $queries
20
+ @include media($queries) {
21
+ @content($mediaquery);
22
+ }
23
+ }
24
+
25
+ @mixin media($queries) {
26
+ // Erst wenn alle mediaqueries geschachtelt geschrieben sind,
27
+ // wird der @content ausgegeben
28
+ @if list.length($queries) == 0 {
29
+ @content;
30
+ } @else {
31
+ $first-key: list.nth(map.keys($queries), 1);
32
+
33
+ // Schreibt nur die mediaquery, nicht den Inhalt
34
+ @media ($first-key: map.get($queries, $first-key)) {
35
+ $queries: map.remove($queries, $first-key);
36
+
37
+ @include media($queries) {
38
+ @content;
39
+ }
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,4 @@
1
+ @forward "config";
2
+ @forward "core/mediaqueries";
3
+ @forward "core/layout";
4
+ @forward "core/hamburger";
@@ -0,0 +1,31 @@
1
+ /**
2
+ * after scroll set body attribute data-scrolled to true
3
+ */
4
+
5
+ export class BodyScrolled {
6
+ static #instance;
7
+
8
+ constructor() {
9
+ document.addEventListener('scroll', function setScrolled() {
10
+ document.body.setAttribute('data-body-scrolled', 'true');
11
+ this.removeEventListener('scroll', setScrolled);
12
+
13
+ // create event
14
+ let event = new CustomEvent('eventBodyScrolled', {
15
+ detail: {
16
+ scrolled: true
17
+ }
18
+ });
19
+ // dispatch the event
20
+ window.dispatchEvent(event);
21
+ });
22
+
23
+ }
24
+
25
+ static getInstance() {
26
+ if (!BodyScrolled.#instance) {
27
+ BodyScrolled.#instance = new BodyScrolled();
28
+ }
29
+ return BodyScrolled.#instance;
30
+ }
31
+ }
@@ -0,0 +1,56 @@
1
+ /**Media queries
2
+ * https://kinsta.com/blog/javamediaqueryipt-media-query/
3
+ * option 3 on the linked page
4
+ */
5
+
6
+ export class MediaQueries {
7
+ static #instance;
8
+ layout = 'desktop';
9
+ #breakpoints;
10
+
11
+ constructor(breakpoints) {
12
+ this.#breakpoints = breakpoints;
13
+ this.layout = 'desktop';
14
+ this.changeLayout();
15
+ this.#matchmedia();
16
+ }
17
+
18
+ static getInstance(breakpoints) {
19
+ if (!MediaQueries.#instance) {
20
+ if (!breakpoints) {
21
+ throw new Error("MediaQueries muss beim ersten Aufruf mit breakpoints initialisiert werden.");
22
+ }
23
+ MediaQueries.#instance = new MediaQueries(breakpoints);
24
+ }
25
+ return MediaQueries.#instance;
26
+ }
27
+
28
+ #matchmedia() {
29
+ for (let [layout, minSize] of Object.entries(this.#breakpoints)) {
30
+ if (minSize) {
31
+ var matchmedia = window.matchMedia('(min-width: ' + minSize + 'px)');
32
+ matchmedia.addEventListener('change', (e) => this.changeLayout());
33
+ }
34
+ }
35
+ }
36
+
37
+ // media query handler function
38
+ changeLayout() {
39
+ for (let [layout, minSize] of Object.entries(this.#breakpoints)) {
40
+ var matchmedia = window.matchMedia('(min-width: ' + minSize + 'px)');
41
+ if (!matchmedia || matchmedia.matches) {
42
+ this.layout = layout;
43
+ }
44
+ }
45
+ document.body.setAttribute('data-layout', this.layout);
46
+
47
+ // create event
48
+ let event = new CustomEvent('eventLayoutchange', {
49
+ detail: {
50
+ layout: this.layout
51
+ }
52
+ });
53
+ // dispatch the event
54
+ window.dispatchEvent(event);
55
+ }
56
+ }
package/utils/Vh100.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 100vh problem
3
+ * https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
4
+ */
5
+
6
+ export class Vh100 {
7
+ static #instance;
8
+ vh = 0;
9
+
10
+ constructor() {
11
+ this.#calculate();
12
+ // We listen to the resize event
13
+ window.addEventListener('resize', () => {
14
+ this.#calculate();
15
+ });
16
+ }
17
+
18
+ #calculate() {
19
+ // First we get the viewport height and we multiple it by 1% to get a value for a vh unit
20
+ this.vh = window.innerHeight * 0.01;
21
+ // Then we set the value in the --vh custom property to the root of the document
22
+ document.documentElement.style.setProperty('--vh', `${this.vh}px`);
23
+ }
24
+
25
+ static getInstance() {
26
+ if (!Vh100.#instance) {
27
+ Vh100.#instance = new Vh100();
28
+ }
29
+ return Vh100.#instance;
30
+ }
31
+ }