create-berna-stencil 1.0.1
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.
- package/.eleventy.js +81 -0
- package/.eleventyignore +4 -0
- package/.gitignore +2 -0
- package/README.md +31 -0
- package/bin/create.js +96 -0
- package/package.json +71 -0
- package/src/404.njk +16 -0
- package/src/_routes/another-page.njk +9 -0
- package/src/api/configExample.php +28 -0
- package/src/api/sendEmail.php +131 -0
- package/src/assets/brand/favicon.svg +37 -0
- package/src/assets/brand/logo.svg +37 -0
- package/src/components/exampleComponent.njk +12 -0
- package/src/components/global/footer.njk +32 -0
- package/src/components/global/header.njk +15 -0
- package/src/components/layouts/base.njk +100 -0
- package/src/components/layouts/includes.njk +16 -0
- package/src/data/lang.json +32 -0
- package/src/data/site.json +54 -0
- package/src/index.njk +9 -0
- package/src/js/modules/forms/form.js +45 -0
- package/src/js/modules/forms/normalizePhoneNumber.js +42 -0
- package/src/js/modules/forms/textAreaAutoExpand.js +38 -0
- package/src/js/modules/langSwitcher.js +62 -0
- package/src/js/modules/notification.js +39 -0
- package/src/js/pages/404.js +23 -0
- package/src/js/pages/anotherPage.js +23 -0
- package/src/js/pages/homepage.js +25 -0
- package/src/robots.txt +4 -0
- package/src/scss/modules/_animations.scss +25 -0
- package/src/scss/modules/_footer.scss +12 -0
- package/src/scss/modules/_global.scss +40 -0
- package/src/scss/modules/_header.scss +32 -0
- package/src/scss/modules/_mobile.scss +30 -0
- package/src/scss/modules/_notification.scss +56 -0
- package/src/scss/modules/_root.scss +37 -0
- package/src/scss/modules/_typography.scss +15 -0
- package/src/scss/modules/frameworks/_bootstrap.scss +111 -0
- package/src/scss/modules/frameworks/_bulma.scss +110 -0
- package/src/scss/modules/frameworks/_foundation.scss +140 -0
- package/src/scss/modules/frameworks/_uikit.scss +111 -0
- package/src/scss/pages/404.scss +20 -0
- package/src/scss/pages/anotherPage.scss +21 -0
- package/src/scss/pages/homepage.scss +21 -0
- package/src/sitemap.njk +17 -0
- package/tools/assistant.js +127 -0
- package/tools/cleanOutput.js +24 -0
- package/tools/modules/updateData.js +58 -0
- package/tools/modules/updateIncludes.js +45 -0
- package/tools/modules/updateOutputPath.js +91 -0
- package/tools/modules/updatePage.js +150 -0
- package/tools/res/templates.json +56 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!-- This is the base of everypage that you will create -->
|
|
2
|
+
|
|
3
|
+
<!DOCTYPE html>
|
|
4
|
+
<html lang="{{ site.lang or 'en' }}" data-bs-theme="{{ site.data_bs_theme or 'light' }}">
|
|
5
|
+
|
|
6
|
+
<head>
|
|
7
|
+
<meta charset="UTF-8">
|
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
9
|
+
|
|
10
|
+
<!-- Page Data based on Page Title -->
|
|
11
|
+
{% set pageKey = title %}
|
|
12
|
+
{% set pageData = site.pages[pageKey] %}
|
|
13
|
+
|
|
14
|
+
<!-- SEO Meta Tags -->
|
|
15
|
+
<link rel="canonical" href="{{ site.url }}{{ page.url }}"/>
|
|
16
|
+
<title>{{ pageData.seo.title or title or site.title }}</title>
|
|
17
|
+
<meta name="description" content="{{ pageData.seo.description or site.description }}">
|
|
18
|
+
<meta name="keywords" content="{{ site.keywords }}">
|
|
19
|
+
<meta name="author" content="{{ site.author }}">
|
|
20
|
+
<meta name="theme-color" content="" >
|
|
21
|
+
|
|
22
|
+
<!-- Open Graph -->
|
|
23
|
+
<meta property="og:title" content="{{ pageData.seo.title }}">
|
|
24
|
+
<meta property="og:description" content="{{ pageData.seo.description }}">
|
|
25
|
+
<meta property="og:type" content="website">
|
|
26
|
+
<meta property="og:url" content="{{ site.url }}{{ page.url }}">
|
|
27
|
+
<meta property="og:image" content="{{ site.url }}{{ site.logo }}">
|
|
28
|
+
<meta property="og:site_name" content="{{ site.site_name }}">
|
|
29
|
+
|
|
30
|
+
<!-- Twitter Card -->
|
|
31
|
+
<meta name="twitter:card" content="summary_large_image">
|
|
32
|
+
<meta name="twitter:title" content="{{ pageData.seo.title }}">
|
|
33
|
+
<meta name="twitter:description" content="{{ pageData.seo.description }}">
|
|
34
|
+
<meta name="twitter:image" content="{{ site.url }}{{ site.logo }}">
|
|
35
|
+
|
|
36
|
+
<!-- Favicon -->
|
|
37
|
+
<link rel="icon" type="image/svg+xml" href="{{ site.favicon }}">
|
|
38
|
+
<!-- (If you want to use a PNG favicon) -->
|
|
39
|
+
{#
|
|
40
|
+
<link rel="icon" type="image/png" href="/assets/favicon.png">
|
|
41
|
+
<link rel="apple-touch-icon" href="/assets/favicon.png">
|
|
42
|
+
#}
|
|
43
|
+
|
|
44
|
+
<!-- CSS -->
|
|
45
|
+
{# preconnect to jsdelivr — only needed if loading scripts from CDN #}
|
|
46
|
+
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin data-iub-purposes="1" data-cmp-ab="1">
|
|
47
|
+
<link rel="stylesheet" href="/css/pages/{{ title }}.css" />
|
|
48
|
+
|
|
49
|
+
{% if pageData and pageData.cdn and pageData.cdn.css %}
|
|
50
|
+
{% for link in pageData.cdn.css %}
|
|
51
|
+
<link rel="stylesheet" href="{{ link }}">
|
|
52
|
+
{% endfor %}
|
|
53
|
+
{% endif %}
|
|
54
|
+
</head>
|
|
55
|
+
|
|
56
|
+
<body>
|
|
57
|
+
<!-- Header -->
|
|
58
|
+
{% include "global/header.njk" %}
|
|
59
|
+
|
|
60
|
+
<!-- Main -->
|
|
61
|
+
<main>
|
|
62
|
+
{{ content | safe }}
|
|
63
|
+
<br>
|
|
64
|
+
</main>
|
|
65
|
+
|
|
66
|
+
<!-- Footer -->
|
|
67
|
+
{% include "global/footer.njk" %}
|
|
68
|
+
|
|
69
|
+
<!-- SCRIPTS -->
|
|
70
|
+
|
|
71
|
+
{#
|
|
72
|
+
╔═══════════════════════════════════════════╗
|
|
73
|
+
║ FRAMEWORK SCRIPTS ║
|
|
74
|
+
║ Uncomment the framework JS you are using ║
|
|
75
|
+
║ Bulma requires no JS — CSS only ║
|
|
76
|
+
╚═══════════════════════════════════════════╝
|
|
77
|
+
#}
|
|
78
|
+
|
|
79
|
+
{# Bootstrap JS — comment out if using Bulma or Foundation #}
|
|
80
|
+
<script src="/js/bootstrap.bundle.min.js" defer></script>
|
|
81
|
+
|
|
82
|
+
{# Foundation JS — uncomment if using Foundation #}
|
|
83
|
+
{# <script src="/js/foundation.min.js" defer></script> #}
|
|
84
|
+
|
|
85
|
+
{# UIkit JS — uncomment if using UIkit #}
|
|
86
|
+
{# <script src="/js/uikit.min.js" defer></script> #}
|
|
87
|
+
{# <script src="/js/uikit-icons.min.js" defer></script> #}
|
|
88
|
+
|
|
89
|
+
{# Bulma — no JS needed #}
|
|
90
|
+
|
|
91
|
+
{% if pageData and pageData.cdn and pageData.cdn.js %}
|
|
92
|
+
{% for script in pageData.cdn.js %}
|
|
93
|
+
<script src="{{ script }}" defer></script>
|
|
94
|
+
{% endfor %}
|
|
95
|
+
{% endif %}
|
|
96
|
+
|
|
97
|
+
<script src="/js/pages/{{ title }}.js"></script>
|
|
98
|
+
</body>
|
|
99
|
+
|
|
100
|
+
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: base.njk
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- NJK (HTML) components per page -->
|
|
6
|
+
|
|
7
|
+
{% if title == "homepage" %}
|
|
8
|
+
{% include "exampleComponent.njk" %}
|
|
9
|
+
|
|
10
|
+
{% elif title == "anotherPage" %}
|
|
11
|
+
{% include "exampleComponent.njk" %}
|
|
12
|
+
|
|
13
|
+
{% else %}
|
|
14
|
+
{% include "404/_404.njk" %}
|
|
15
|
+
{{ content | safe }}
|
|
16
|
+
{% endif %}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"en": {
|
|
3
|
+
"enLangName": "English",
|
|
4
|
+
"itLangName": "Italian",
|
|
5
|
+
"forms": {
|
|
6
|
+
"formInvalidFields": "Please fill in all required fields correctly",
|
|
7
|
+
"emailSending": "Sending email",
|
|
8
|
+
"emailSuccess": "Email sent successfully",
|
|
9
|
+
"emailError": "An error occurred while sending the email"
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
"error404Message": "Oops! Page not found",
|
|
13
|
+
"error404Return": "Return to homepage",
|
|
14
|
+
|
|
15
|
+
"test": "This is the english translation for the test key"
|
|
16
|
+
},
|
|
17
|
+
"it": {
|
|
18
|
+
"enLangName": "Inglese",
|
|
19
|
+
"itLangName": "Italiano",
|
|
20
|
+
"forms": {
|
|
21
|
+
"formInvalidFields": "Per favore, compila correttamente tutti i campi obbligatori",
|
|
22
|
+
"emailSending": "Invio in corso",
|
|
23
|
+
"emailSuccess": "Email inviata con successo",
|
|
24
|
+
"emailError": "Si è verificato un errore durante l'invio dell'email"
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
"error404Message": "Oops! Pagina non trovata",
|
|
28
|
+
"error404Return": "Ritorna alla homepage",
|
|
29
|
+
|
|
30
|
+
"test": "Questa è la traduzione italiana per la chiave test"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"site_name": "Site name",
|
|
3
|
+
"title": "Site title",
|
|
4
|
+
"description": "Site description",
|
|
5
|
+
"keywords": "keyword1, keyword2, keyword3",
|
|
6
|
+
"domain": "yoursite.com",
|
|
7
|
+
"url": "https://yoursite.com",
|
|
8
|
+
"lang": "en",
|
|
9
|
+
"author": "Name and surname",
|
|
10
|
+
"data_bs_theme": "dark",
|
|
11
|
+
"favicon": "/assets/brand/favicon.svg",
|
|
12
|
+
"logo": "/assets/brand/logo.svg",
|
|
13
|
+
"copyright": {
|
|
14
|
+
"year": "2026",
|
|
15
|
+
"text": "Copyright text"
|
|
16
|
+
},
|
|
17
|
+
"legal": {
|
|
18
|
+
"privacy": "",
|
|
19
|
+
"cookie": "",
|
|
20
|
+
"cookieControls": "",
|
|
21
|
+
"terms": ""
|
|
22
|
+
},
|
|
23
|
+
"pages": {
|
|
24
|
+
"404": {
|
|
25
|
+
"seo": {
|
|
26
|
+
"title": "404 - Not found"
|
|
27
|
+
},
|
|
28
|
+
"cdn": {
|
|
29
|
+
"css": [],
|
|
30
|
+
"js": []
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"homepage": {
|
|
34
|
+
"seo": {
|
|
35
|
+
"title": "Homepage",
|
|
36
|
+
"description": "Description"
|
|
37
|
+
},
|
|
38
|
+
"cdn": {
|
|
39
|
+
"css": [],
|
|
40
|
+
"js": []
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"anotherPage": {
|
|
44
|
+
"seo": {
|
|
45
|
+
"title": "Another Page",
|
|
46
|
+
"description": "description"
|
|
47
|
+
},
|
|
48
|
+
"cdn": {
|
|
49
|
+
"css": [],
|
|
50
|
+
"js": []
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/index.njk
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { showNotification } from "../notification.js";
|
|
2
|
+
import { langKeys, currentLang } from "../langSwitcher.js";
|
|
3
|
+
|
|
4
|
+
//==========================
|
|
5
|
+
// FORM SUBMISSION MODULE
|
|
6
|
+
//==========================
|
|
7
|
+
|
|
8
|
+
export function initFormListener() {
|
|
9
|
+
document.querySelectorAll("form").forEach((form) => {
|
|
10
|
+
form.addEventListener("submit", (e) => {
|
|
11
|
+
e.preventDefault();
|
|
12
|
+
|
|
13
|
+
const lang = langKeys[currentLang].forms;
|
|
14
|
+
|
|
15
|
+
if (!form.checkValidity()) {
|
|
16
|
+
form.classList.add("was-validated");
|
|
17
|
+
showNotification(lang.formInvalidFields, "error");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
showNotification(lang.emailSending, "info", -1);
|
|
22
|
+
fetch("/php/sendEmail.php", {
|
|
23
|
+
method: "POST",
|
|
24
|
+
body: new FormData(form),
|
|
25
|
+
})
|
|
26
|
+
.then((response) => {
|
|
27
|
+
return response.text();
|
|
28
|
+
})
|
|
29
|
+
.then((data) => {
|
|
30
|
+
if (data.trim() === "success") {
|
|
31
|
+
showNotification(lang.emailSuccess, "success");
|
|
32
|
+
form.reset();
|
|
33
|
+
form.classList.remove("was-validated");
|
|
34
|
+
} else {
|
|
35
|
+
console.error(data);
|
|
36
|
+
showNotification(lang.emailError, "error");
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
.catch((error) => {
|
|
40
|
+
console.error(error);
|
|
41
|
+
showNotification(lang.emailError, "error");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// PHONE NUMBER NORMALIZATION MODULE
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
// Normalizes and attaches normalization to all existing and future tel inputs
|
|
6
|
+
// Must be called inside DOMContentLoaded in the page's JS file
|
|
7
|
+
export function initNormalizePhoneNumber() {
|
|
8
|
+
|
|
9
|
+
// Formats digits in groups of 3, 3 and 4 (e.g. 333 123 4567)
|
|
10
|
+
function formatDigits(digits) {
|
|
11
|
+
let formatted = "";
|
|
12
|
+
if (digits.length > 0) formatted += digits.slice(0, 3);
|
|
13
|
+
if (digits.length > 3) formatted += " " + digits.slice(3, 6);
|
|
14
|
+
if (digits.length > 6) formatted += " " + digits.slice(6, 10);
|
|
15
|
+
return formatted;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalize(input) {
|
|
19
|
+
if (!input) return;
|
|
20
|
+
|
|
21
|
+
let value = input.value.replace(/[^0-9+]/g, "");
|
|
22
|
+
|
|
23
|
+
if (value.startsWith("+")) {
|
|
24
|
+
value = "+" + value.slice(1).replace(/\+/g, "");
|
|
25
|
+
const prefix = value.slice(0, 3);
|
|
26
|
+
const digits = value.slice(3);
|
|
27
|
+
|
|
28
|
+
value = prefix + (digits ? " " + formatDigits(digits) : "");
|
|
29
|
+
} else {
|
|
30
|
+
value = value.replace(/\+/g, "");
|
|
31
|
+
value = formatDigits(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
input.value = value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
document.querySelectorAll('input[type="tel"]').forEach(normalize);
|
|
38
|
+
|
|
39
|
+
document.addEventListener("input", ({ target }) => {
|
|
40
|
+
if (target.matches('input[type="tel"]')) normalize(target);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// AUTO-EXPAND TEXTAREA MODULE
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
export function initTextAreaAutoExpand() {
|
|
6
|
+
const MAX_ROWS = 10;
|
|
7
|
+
|
|
8
|
+
function setup(element) {
|
|
9
|
+
if (element.dataset.autoExpand) return;
|
|
10
|
+
element.dataset.autoExpand = "true";
|
|
11
|
+
element.style.resize = "none";
|
|
12
|
+
element.style.overflow = "hidden";
|
|
13
|
+
element.dataset.minRows = element.rows || 1;
|
|
14
|
+
element.addEventListener("input", () => expand(element));
|
|
15
|
+
if (element.value) expand(element);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function expand(element) {
|
|
19
|
+
element.rows = element.dataset.minRows;
|
|
20
|
+
while (element.scrollHeight > element.clientHeight && element.rows < MAX_ROWS) {
|
|
21
|
+
element.rows += 1;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
document.querySelectorAll("textarea").forEach(setup);
|
|
26
|
+
|
|
27
|
+
const observer = new MutationObserver((mutations) => {
|
|
28
|
+
for (const mutation of mutations) {
|
|
29
|
+
for (const node of mutation.addedNodes) {
|
|
30
|
+
if (node.nodeType !== 1) continue;
|
|
31
|
+
if (node.matches("textarea")) setup(node);
|
|
32
|
+
node.querySelectorAll?.("textarea").forEach(setup);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
38
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// LANG-SWITCHER MODULE
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
export let currentLang = null;
|
|
6
|
+
export let langKeys = null;
|
|
7
|
+
|
|
8
|
+
async function loadLangKeys() {
|
|
9
|
+
if (langKeys) return langKeys;
|
|
10
|
+
const response = await fetch("/data/lang.json");
|
|
11
|
+
langKeys = await response.json();
|
|
12
|
+
return langKeys;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function initLangSwitcher() {
|
|
16
|
+
const data = await loadLangKeys();
|
|
17
|
+
const availableLangs = Object.keys(data);
|
|
18
|
+
const fallbackLang = document.documentElement.lang || availableLangs[0];
|
|
19
|
+
const initialLang = await getInitialLang(availableLangs, fallbackLang);
|
|
20
|
+
|
|
21
|
+
applyLanguage(initialLang, data);
|
|
22
|
+
|
|
23
|
+
document.querySelectorAll("input[name='lang-switcher']").forEach((radio) => {
|
|
24
|
+
if (radio.value === initialLang) radio.checked = true;
|
|
25
|
+
|
|
26
|
+
radio.addEventListener("change", () => {
|
|
27
|
+
const newLang = radio.value;
|
|
28
|
+
localStorage.setItem("language", newLang);
|
|
29
|
+
applyLanguage(newLang, data);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function getInitialLang(availableLangs, fallbackLang) {
|
|
35
|
+
if (localStorage.getItem("language")) return localStorage.getItem("language");
|
|
36
|
+
const browserLang = navigator.language.slice(0, 2);
|
|
37
|
+
return availableLangs.includes(browserLang) ? browserLang : fallbackLang;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getNestedValue(obj, path) {
|
|
41
|
+
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function applyLanguage(lang, data) {
|
|
45
|
+
currentLang = lang;
|
|
46
|
+
langKeys = data;
|
|
47
|
+
const langData = data[lang];
|
|
48
|
+
|
|
49
|
+
document.querySelectorAll("[data-lang-key]").forEach((element) => {
|
|
50
|
+
const key = element.dataset.langKey;
|
|
51
|
+
const value = getNestedValue(langData, key);
|
|
52
|
+
if (value) element.textContent = value;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
document.querySelectorAll("[data-lang-placeholder]").forEach((element) => {
|
|
56
|
+
const key = element.dataset.langPlaceholder;
|
|
57
|
+
const value = getNestedValue(langData, key);
|
|
58
|
+
if (value) element.setAttribute("placeholder", value);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
document.documentElement.lang = lang;
|
|
62
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// NOTIFICATION MODULE
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
let currentNotification = null;
|
|
6
|
+
|
|
7
|
+
export function showNotification(text, type = "info", duration = 5000) {
|
|
8
|
+
if (currentNotification) {
|
|
9
|
+
currentNotification.remove();
|
|
10
|
+
currentNotification = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const notificationBox = document.createElement("div");
|
|
14
|
+
notificationBox.classList.add("notificationBox", type);
|
|
15
|
+
|
|
16
|
+
const notificationText = document.createElement("span");
|
|
17
|
+
notificationText.textContent = text;
|
|
18
|
+
notificationBox.appendChild(notificationText);
|
|
19
|
+
|
|
20
|
+
if (duration === -1) {
|
|
21
|
+
const spinner = document.createElement("div");
|
|
22
|
+
spinner.classList.add("spinner");
|
|
23
|
+
notificationBox.appendChild(spinner);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
document.body.appendChild(notificationBox);
|
|
27
|
+
currentNotification = notificationBox;
|
|
28
|
+
|
|
29
|
+
if (duration !== -1) {
|
|
30
|
+
setTimeout(() => {
|
|
31
|
+
if (!notificationBox.parentElement) return;
|
|
32
|
+
notificationBox.classList.add("fade-out");
|
|
33
|
+
notificationBox.addEventListener("transitionend", () => {
|
|
34
|
+
notificationBox.remove();
|
|
35
|
+
if (currentNotification === notificationBox) currentNotification = null;
|
|
36
|
+
}, { once: true });
|
|
37
|
+
}, duration);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// JAVASCRIPT MODULES IMPORTS
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
// Call inside DOMContentLoaded
|
|
6
|
+
import { initLangSwitcher } from '../modules/langSwitcher.js';
|
|
7
|
+
|
|
8
|
+
// Call anywhere
|
|
9
|
+
import { showNotification } from '../modules/notification.js';
|
|
10
|
+
|
|
11
|
+
// Uncomment to enable optional modules (call inside DOMContentLoaded)
|
|
12
|
+
// import { initTextAreaAutoExpand } from '../modules/textAreaAutoExpand.js';
|
|
13
|
+
// import { initNormalizePhoneNumber } from '../modules/normalizePhoneNumber.js';
|
|
14
|
+
|
|
15
|
+
//==========================
|
|
16
|
+
// "404" PAGE CUSTOM JAVASCRIPT
|
|
17
|
+
//==========================
|
|
18
|
+
|
|
19
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
20
|
+
initLangSwitcher();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
showNotification("404 notification", "success", 3000);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// JAVASCRIPT MODULES IMPORTS
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
// Call inside DOMContentLoaded
|
|
6
|
+
import { initLangSwitcher } from '../modules/langSwitcher.js';
|
|
7
|
+
|
|
8
|
+
// Call anywhere
|
|
9
|
+
import { showNotification } from '../modules/notification.js';
|
|
10
|
+
|
|
11
|
+
// Uncomment to enable optional modules (call inside DOMContentLoaded)
|
|
12
|
+
// import { initTextAreaAutoExpand } from '../modules/textAreaAutoExpand.js';
|
|
13
|
+
// import { initNormalizePhoneNumber } from '../modules/normalizePhoneNumber.js';
|
|
14
|
+
|
|
15
|
+
//==========================
|
|
16
|
+
// "another-page" PAGE CUSTOM JAVASCRIPT
|
|
17
|
+
//==========================
|
|
18
|
+
|
|
19
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
20
|
+
initLangSwitcher();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
showNotification("another-page notification", "success", 3000);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// JAVASCRIPT MODULES IMPORTS
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
// Call inside DOMContentLoaded
|
|
6
|
+
import { initFormListener } from '../modules/forms/form.js';
|
|
7
|
+
import { initLangSwitcher } from '../modules/langSwitcher.js';
|
|
8
|
+
|
|
9
|
+
// Call anywhere
|
|
10
|
+
import { showNotification } from '../modules/notification.js';
|
|
11
|
+
|
|
12
|
+
// Uncomment to enable optional modules (call inside DOMContentLoaded)
|
|
13
|
+
// import { initTextAreaAutoExpand } from '../modules/textAreaAutoExpand.js';
|
|
14
|
+
// import { initNormalizePhoneNumber } from '../modules/normalizePhoneNumber.js';
|
|
15
|
+
|
|
16
|
+
//==========================
|
|
17
|
+
// "homepage" PAGE CUSTOM JAVASCRIPT
|
|
18
|
+
//==========================
|
|
19
|
+
|
|
20
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
21
|
+
initLangSwitcher();
|
|
22
|
+
initFormListener();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
showNotification("homepage notification", "success", 3000);
|
package/src/robots.txt
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// CSS RULES FOR ANIMATIONS
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
// Getting root rules from _root.scss
|
|
6
|
+
@use 'root' as root;
|
|
7
|
+
|
|
8
|
+
.fade-in {
|
|
9
|
+
animation: fadeIn 1s;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@keyframes fadeIn {
|
|
13
|
+
from {
|
|
14
|
+
opacity: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
to {
|
|
18
|
+
opacity: 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@keyframes spin {
|
|
23
|
+
from { transform: rotate(0deg); }
|
|
24
|
+
to { transform: rotate(360deg); }
|
|
25
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// CSS RULES FOR FOOTER
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
// Getting root rules from _root.scss
|
|
6
|
+
@use 'root' as root;
|
|
7
|
+
|
|
8
|
+
footer {
|
|
9
|
+
width: 100%;
|
|
10
|
+
padding: root.$header-padding-y root.$header-padding-x;
|
|
11
|
+
background-color: root.$primary;
|
|
12
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// CSS RULES FOR THE ENTIRE SITE
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
// Getting root rules from _root.scss
|
|
6
|
+
@use 'root' as root;
|
|
7
|
+
|
|
8
|
+
// ╔═════════════════════════════════════════════════════════════╗
|
|
9
|
+
// ║ CSS FRAMEWORK ║
|
|
10
|
+
// ║ Uncomment the framework you want to use (only one at time) ║
|
|
11
|
+
// ║ To reduce or filter modules, edit the corresponding file: ║
|
|
12
|
+
// ║ _bootstrap.scss / _bulma.scss ║
|
|
13
|
+
// ║ _uikit.scss / _foundation.scss ║
|
|
14
|
+
// ╚═════════════════════════════════════════════════════════════╝
|
|
15
|
+
|
|
16
|
+
@import "../modules/frameworks/bootstrap";
|
|
17
|
+
// @import "../modules/frameworks/bulma";
|
|
18
|
+
// @import "../modules/frameworks/foundation";
|
|
19
|
+
// @import "../modules/frameworks/uikit";
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@import "typography";
|
|
23
|
+
|
|
24
|
+
@import "header";
|
|
25
|
+
@import "footer";
|
|
26
|
+
|
|
27
|
+
@import "animations";
|
|
28
|
+
@import "mobile";
|
|
29
|
+
|
|
30
|
+
*,
|
|
31
|
+
*::before,
|
|
32
|
+
*::after {
|
|
33
|
+
margin: 0;
|
|
34
|
+
padding: 0;
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
button {
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// CSS RULES FOR HEADER
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
// Getting root rules from _root.scss
|
|
6
|
+
@use 'root' as root;
|
|
7
|
+
|
|
8
|
+
header {
|
|
9
|
+
height: root.$header-height;
|
|
10
|
+
width: 100%;
|
|
11
|
+
display: flex;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
align-items: center;
|
|
14
|
+
padding: root.$header-padding-y root.$header-padding-x;
|
|
15
|
+
background-color: root.$primary;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
nav {
|
|
19
|
+
display: flex;
|
|
20
|
+
justify-content: space-between;
|
|
21
|
+
align-items: center;
|
|
22
|
+
width: 100%;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
nav a {
|
|
26
|
+
padding: 10px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.nav-links{
|
|
30
|
+
display: flex;
|
|
31
|
+
gap: 15px;
|
|
32
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//==========================
|
|
2
|
+
// CSS RULES FOR RESPONSIVE
|
|
3
|
+
//==========================
|
|
4
|
+
|
|
5
|
+
// Getting root rules from _root.scss
|
|
6
|
+
@use 'root' as root;
|
|
7
|
+
|
|
8
|
+
//==========================
|
|
9
|
+
// MEDIA QUERIES
|
|
10
|
+
//==========================
|
|
11
|
+
|
|
12
|
+
@media (max-width: 1400px) {
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@media (max-width: 1200px) {
|
|
17
|
+
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@media (max-width: 992px) {
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@media (max-width: 768px) {
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@media (max-width: 576px) {
|
|
29
|
+
|
|
30
|
+
}
|