create-berna-stencil 1.0.24 → 1.0.26

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.
Files changed (37) hide show
  1. package/.eleventy.js +1 -0
  2. package/.gitignore +2 -2
  3. package/_tools/assistant.js +6 -6
  4. package/_tools/modules/updateOutputPath.js +1 -4
  5. package/_tools/modules/updatePage.js +33 -72
  6. package/_tools/res/templates/template.js +23 -0
  7. package/_tools/res/templates/template.njk +8 -0
  8. package/_tools/res/templates/template.scss +17 -0
  9. package/bin/create.js +1 -1
  10. package/package.json +1 -1
  11. package/src/api/.htaccess +6 -11
  12. package/src/api/config.example.php +5 -20
  13. package/src/api/config.php +5 -20
  14. package/src/api/core/composer.lock +492 -492
  15. package/src/api/{index.php → core/index.php} +53 -24
  16. package/src/api/core/init.php +0 -13
  17. package/src/api/core/vendor/composer/installed.php +2 -2
  18. package/src/api/database/Database.php +24 -0
  19. package/src/api/database/migrations/create_example_db.sql +1 -0
  20. package/src/api/database/migrations/create_users_table.sql +9 -0
  21. package/src/api/database/models/User.php +61 -0
  22. package/src/api/database/seeds/users.php +0 -0
  23. package/src/api/database/services/UserService.php +0 -0
  24. package/src/api/endpoints/protected/auth-system.php +63 -0
  25. package/src/api/endpoints/protected/example-protected.php +17 -0
  26. package/src/api/endpoints/public/auth/login.php +34 -0
  27. package/src/api/endpoints/public/auth/register.php +39 -0
  28. package/src/api/endpoints/public/example-public.php +17 -0
  29. package/src/api/web.config +6 -24
  30. package/src/components/exampleComponent.njk +5 -2
  31. package/src/components/layouts/base.njk +26 -1
  32. package/src/data/site.json +53 -53
  33. package/src/js/modules/langSwitcher.js +1 -1
  34. package/src/web.config +30 -33
  35. package/_tools/res/templates.json +0 -56
  36. package/src/api/endpoints/protected/example-protected-endpoint.php +0 -28
  37. package/src/api/endpoints/public/example-public-endpoint.php +0 -28
@@ -5,43 +5,71 @@ declare(strict_types=1);
5
5
  define('CORE_ACCESS', true);
6
6
 
7
7
  /**
8
- * Caricamento delle dipendenze e configurazione iniziale.
8
+ * Load dependencies and initial configuration.
9
9
  */
10
- require_once __DIR__ . '/core/init.php';
11
- require_once __DIR__ . '/core/modules/Response.php';
10
+ require_once __DIR__ . '/init.php';
11
+ require_once __DIR__ . '/modules/Response.php';
12
12
 
13
13
  // =====================================================
14
- // 1. ANALISI DELLA RICHIESTA (REQUEST PARSING)
14
+ // 1. REQUEST PARSING
15
15
  // =====================================================
16
16
 
17
17
  $method = $_SERVER['REQUEST_METHOD'];
18
18
  $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
19
19
 
20
- // Pulizia URI: rimuoviamo /api e eventuali slash finali
21
20
  $uri = rtrim(preg_replace('#^/api#', '', $uri), '/') ?: '/';
22
21
  $parts = array_values(array_filter(explode('/', $uri)));
23
22
 
24
- $resource = $parts[0] ?? null;
25
-
26
23
  // =====================================================
27
- // 2. RISOLUZIONE ENDPOINT (ROUTING)
24
+ // 2. ENDPOINT RESOLUTION (ROUTING WITH SUBDIRECTORIES)
28
25
  // =====================================================
29
26
 
30
- $publicPath = __DIR__ . '/endpoints/public/' . $resource . '.php';
31
- $protectedPath = __DIR__ . '/endpoints/protected/' . $resource . '.php';
27
+ $basePublic = __DIR__ . '/../endpoints/public/';
28
+ $baseProtected = __DIR__ . '/../endpoints/protected/';
29
+
30
+ $endpointFile = null;
31
+ $isProtected = false;
32
+ $requestParams = [];
33
+
34
+ // Temporary variables for the lookup loop
35
+ $checkParts = $parts;
36
+ $params = [];
37
+
38
+ /**
39
+ * Dynamic routing logic: find the deepest matching endpoint file.
40
+ * At each iteration, the last URL segment is peeled off and stored
41
+ * as a route parameter until a matching file is found.
42
+ */
43
+ while (count($checkParts) > 0) {
44
+ $relativePath = implode('/', $checkParts) . '.php';
45
+
46
+ // Check first whether this is a public route
47
+ if (file_exists($basePublic . $relativePath)) {
48
+ $endpointFile = $basePublic . $relativePath;
49
+ $isProtected = false;
50
+ break;
51
+ }
52
+
53
+ // Then check whether this is a protected route
54
+ if (file_exists($baseProtected . $relativePath)) {
55
+ $endpointFile = $baseProtected . $relativePath;
56
+ $isProtected = true;
57
+ break;
58
+ }
32
59
 
33
- $isPublic = $resource !== null && file_exists($publicPath);
34
- $isProtected = $resource !== null && file_exists($protectedPath);
60
+ // No file matched: treat the last segment as a route parameter and try again
61
+ array_unshift($params, array_pop($checkParts));
62
+ }
35
63
 
36
64
  /**
37
- * SE L'ENDPOINT NON ESISTE (RITORNO HTML 404)
65
+ * IF THE ENDPOINT DOES NOT EXIST, RETURN AN HTML 404 PAGE.
38
66
  */
39
- if (!$isPublic && !$isProtected) {
67
+ if (!$endpointFile) {
40
68
  http_response_code(404);
41
-
42
- // Cerchiamo il file 404.html generato da Eleventy nella root di Laragon
69
+
70
+ // Look for the 404.html file generated by Eleventy in the server document root
43
71
  $errorPage = $_SERVER['DOCUMENT_ROOT'] . '/404.html';
44
-
72
+
45
73
  if (file_exists($errorPage)) {
46
74
  header('Content-Type: text/html; charset=UTF-8');
47
75
  echo file_get_contents($errorPage);
@@ -53,7 +81,7 @@ if (!$isPublic && !$isProtected) {
53
81
  }
54
82
 
55
83
  // =====================================================
56
- // 3. HEADERS E CORS (Solo se l'endpoint esiste)
84
+ // 3. HEADERS AND CORS (Only if the endpoint exists)
57
85
  // =====================================================
58
86
 
59
87
  header('Content-Type: application/json; charset=UTF-8');
@@ -75,7 +103,7 @@ if ($method === 'OPTIONS') {
75
103
  }
76
104
 
77
105
  // =====================================================
78
- // 4. GUARDIA DI AUTENTICAZIONE
106
+ // 4. AUTHENTICATION GUARD
79
107
  // =====================================================
80
108
 
81
109
  if ($isProtected) {
@@ -83,15 +111,16 @@ if ($isProtected) {
83
111
  $validKey = $config['API_KEY'] ?? '';
84
112
 
85
113
  if ($validKey === '' || $apiKey !== $validKey) {
86
- Response::error('Unauthorized', 401);
114
+ Response::error('Unauthorized. X_API_KEY is incorrect or missing', 401);
87
115
  }
88
116
  }
89
117
 
90
118
  // =====================================================
91
- // 5. ESECUZIONE (DISPATCH)
119
+ // 5. DISPATCH
92
120
  // =====================================================
93
121
 
94
- $requestParams = array_slice($parts, 1);
122
+ // Parameters are the remaining URL segments not consumed during endpoint resolution
123
+ $requestParams = $params;
95
124
 
96
- // Carica il file dell'endpoint richiesto
97
- require $isProtected ? $protectedPath : $publicPath;
125
+ // Load and execute the matched endpoint file
126
+ require $endpointFile;
@@ -2,19 +2,6 @@
2
2
 
3
3
  declare(strict_types=1);
4
4
 
5
- // Impedisce l'accesso diretto a questo file
6
- if (!defined('CORE_ACCESS')) {
7
- $errorPage = $_SERVER['DOCUMENT_ROOT'] . '/404.html';
8
- http_response_code(404);
9
- if (file_exists($errorPage)) {
10
- header('Content-Type: text/html; charset=UTF-8');
11
- echo file_get_contents($errorPage);
12
- } else {
13
- echo "404 Not Found";
14
- }
15
- exit;
16
- }
17
-
18
5
  require_once __DIR__ . '/vendor/autoload.php';
19
6
  require_once __DIR__ . '/modules/Response.php';
20
7
 
@@ -3,7 +3,7 @@
3
3
  'name' => '__root__',
4
4
  'pretty_version' => 'dev-main',
5
5
  'version' => 'dev-main',
6
- 'reference' => '7a680244883ce58f9dc698ec645fb54b20742eaf',
6
+ 'reference' => 'a6cf6a6a677f9f341c6206144990bb04047857bf',
7
7
  'type' => 'library',
8
8
  'install_path' => __DIR__ . '/../../',
9
9
  'aliases' => array(),
@@ -13,7 +13,7 @@
13
13
  '__root__' => array(
14
14
  'pretty_version' => 'dev-main',
15
15
  'version' => 'dev-main',
16
- 'reference' => '7a680244883ce58f9dc698ec645fb54b20742eaf',
16
+ 'reference' => 'a6cf6a6a677f9f341c6206144990bb04047857bf',
17
17
  'type' => 'library',
18
18
  'install_path' => __DIR__ . '/../../',
19
19
  'aliases' => array(),
@@ -0,0 +1,24 @@
1
+ <?php
2
+ declare(strict_types=1);
3
+
4
+ class Database {
5
+ private static ?PDO $instance = null;
6
+
7
+ private function __construct() {}
8
+
9
+ public static function getInstance(): PDO {
10
+ if (self::$instance === null) {
11
+ $config = require __DIR__ . '/../config.php';
12
+
13
+ $dsn = "mysql:host={$config['DB_HOST']};dbname={$config['DB_NAME']};charset=utf8mb4";
14
+ $options = [
15
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
16
+ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
17
+ PDO::ATTR_EMULATE_PREPARES => false,
18
+ ];
19
+
20
+ self::$instance = new PDO($dsn, $config['DB_USER'], $config['DB_PASS'], $options);
21
+ }
22
+ return self::$instance;
23
+ }
24
+ }
@@ -0,0 +1 @@
1
+ CREATE DATABASE example_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
@@ -0,0 +1,9 @@
1
+ USE example_db;
2
+
3
+ CREATE TABLE IF NOT EXISTS users (
4
+ id INT AUTO_INCREMENT PRIMARY KEY,
5
+ nickname VARCHAR(50) NOT NULL UNIQUE,
6
+ email VARCHAR(255) NOT NULL UNIQUE,
7
+ password VARCHAR(255) NOT NULL,
8
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
9
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -0,0 +1,61 @@
1
+ <?php
2
+ declare(strict_types=1);
3
+
4
+ require_once __DIR__ . '/../Database.php';
5
+
6
+ class User {
7
+ private PDO $db;
8
+
9
+ public function __construct() {
10
+ $this->db = Database::getInstance();
11
+ }
12
+
13
+ public function getAll(): array {
14
+ return $this->db->query("SELECT id, nickname, email, created_at FROM users")->fetchAll();
15
+ }
16
+
17
+ public function getById(int $id): ?array {
18
+ $stmt = $this->db->prepare("SELECT id, nickname, email, created_at FROM users WHERE id = :id");
19
+ $stmt->execute(['id' => $id]);
20
+ return $stmt->fetch() ?: null;
21
+ }
22
+
23
+ public function findByEmail(string $email): ?array {
24
+ $stmt = $this->db->prepare("SELECT id, nickname, email, password, created_at FROM users WHERE email = :email");
25
+ $stmt->execute(['email' => filter_var(trim($email), FILTER_SANITIZE_EMAIL)]);
26
+ return $stmt->fetch() ?: null;
27
+ }
28
+
29
+ public function create(string $nickname, string $email, string $password = ''): int {
30
+ $stmt = $this->db->prepare("INSERT INTO users (nickname, email, password) VALUES (:nickname, :email, :password)");
31
+ $stmt->execute([
32
+ 'nickname' => htmlspecialchars(strip_tags(trim($nickname))),
33
+ 'email' => filter_var(trim($email), FILTER_SANITIZE_EMAIL),
34
+ 'password' => $password !== '' ? password_hash($password, PASSWORD_BCRYPT) : '',
35
+ ]);
36
+ return (int)$this->db->lastInsertId();
37
+ }
38
+
39
+ public function update(int $id, array $data): bool {
40
+ $fields = [];
41
+ $params = ['id' => $id];
42
+
43
+ if (isset($data['nickname'])) {
44
+ $fields[] = 'nickname = :nickname';
45
+ $params['nickname'] = htmlspecialchars(strip_tags($data['nickname']));
46
+ }
47
+ if (isset($data['email'])) {
48
+ $fields[] = 'email = :email';
49
+ $params['email'] = filter_var($data['email'], FILTER_SANITIZE_EMAIL);
50
+ }
51
+
52
+ if (empty($fields)) return false;
53
+
54
+ $sql = "UPDATE users SET " . implode(', ', $fields) . " WHERE id = :id";
55
+ return $this->db->prepare($sql)->execute($params);
56
+ }
57
+
58
+ public function delete(int $id): bool {
59
+ return $this->db->prepare("DELETE FROM users WHERE id = :id")->execute(['id' => $id]);
60
+ }
61
+ }
File without changes
File without changes
@@ -0,0 +1,63 @@
1
+ <?php
2
+ declare(strict_types=1);
3
+
4
+
5
+ // 2. Richiamo il tuo modulo Response e il Modello
6
+ require_once __DIR__ . '/../../core/modules/Response.php';
7
+ require_once __DIR__ . '/../../database/models/User.php';
8
+
9
+ $user = new User();
10
+ $id = isset($requestParams[0]) ? (int)$requestParams[0] : null;
11
+ $input = json_decode(file_get_contents('php://input'), true) ?? [];
12
+
13
+ try {
14
+ switch ($method) {
15
+ case 'GET':
16
+ $data = $id ? $user->getById($id) : $user->getAll();
17
+ if ($id && !$data) {
18
+ Response::error('User not found', 404);
19
+ }
20
+ // Sostituito con Response::success()
21
+ Response::success($data);
22
+ break;
23
+
24
+ case 'POST':
25
+ if (empty($input['nickname']) || empty($input['email'])) {
26
+ Response::error('Missing fields', 400);
27
+ }
28
+ if (!filter_var($input['email'], FILTER_VALIDATE_EMAIL)) {
29
+ Response::error('Invalid email', 400);
30
+ }
31
+
32
+ $newId = $user->create($input['nickname'], $input['email']);
33
+ http_response_code(201);
34
+ Response::success(['message' => 'Created', 'id' => $newId]);
35
+ break;
36
+
37
+ case 'PUT':
38
+ case 'PATCH':
39
+ if (!$id) Response::error('ID required', 400);
40
+ if (!$user->update($id, $input)) {
41
+ Response::error('Not found or no changes', 404);
42
+ }
43
+ Response::success(['message' => 'Updated']);
44
+ break;
45
+
46
+ case 'DELETE':
47
+ if (!$id) Response::error('ID required', 400);
48
+ if (!$user->delete($id)) {
49
+ Response::error('Not found', 404);
50
+ }
51
+ Response::success(['message' => 'Deleted']);
52
+ break;
53
+
54
+ default:
55
+ Response::error('Method not allowed', 405);
56
+ break;
57
+ }
58
+ } catch (PDOException $e) {
59
+ if ($e->getCode() === '23000') {
60
+ Response::error('Nickname or email already exists', 409);
61
+ }
62
+ Response::error('Database error', 500);
63
+ }
@@ -0,0 +1,17 @@
1
+ <?php
2
+ declare(strict_types=1);
3
+
4
+ require_once __DIR__ . '/../../core/modules/Response.php';
5
+
6
+ if ($method !== 'GET') {
7
+ Response::error('Method not allowed', 405);
8
+ }
9
+
10
+ Response::success([
11
+ 'message' => 'Protected endpoint is working',
12
+ 'params' => $requestParams,
13
+ ]);
14
+
15
+ Response::error([
16
+ 'message' => 'Error text',
17
+ ]);
@@ -0,0 +1,34 @@
1
+ <?php
2
+ declare(strict_types=1);
3
+
4
+ require_once __DIR__ . '/../../../core/modules/Response.php';
5
+ require_once __DIR__ . '/../../../database/models/User.php';
6
+
7
+ if ($method !== 'POST') {
8
+ Response::error('Method not allowed', 405);
9
+ }
10
+
11
+ $input = json_decode(file_get_contents('php://input'), true) ?? [];
12
+
13
+ $email = trim(filter_var($input['email'] ?? '', FILTER_SANITIZE_EMAIL));
14
+ $password = trim($input['password'] ?? '');
15
+
16
+ if (empty($email) || empty($password)) {
17
+ Response::error('Missing fields', 400);
18
+ }
19
+
20
+ if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
21
+ Response::error('Invalid email', 400);
22
+ }
23
+
24
+ $user = new User();
25
+ $found = $user->findByEmail($email);
26
+
27
+ if (!$found || !password_verify($password, $found['password'])) {
28
+ Response::error('Invalid credentials', 401);
29
+ }
30
+
31
+ unset($found['password']);
32
+ Response::success([
33
+ 'user' => $found,
34
+ ]);
@@ -0,0 +1,39 @@
1
+ <?php
2
+ declare(strict_types=1);
3
+
4
+ require_once __DIR__ . '/../../../core/modules/Response.php';
5
+ require_once __DIR__ . '/../../../database/models/User.php';
6
+
7
+ if ($method !== 'POST') {
8
+ Response::error('Method not allowed', 405);
9
+ }
10
+
11
+ $input = json_decode(file_get_contents('php://input'), true) ?? [];
12
+
13
+ $nickname = htmlspecialchars(strip_tags(trim($input['nickname'] ?? '')));
14
+ $email = trim(filter_var($input['email'] ?? '', FILTER_SANITIZE_EMAIL));
15
+ $password = trim($input['password'] ?? '');
16
+
17
+ if (empty($nickname) || empty($email) || empty($password)) {
18
+ Response::error('Missing fields', 400);
19
+ }
20
+
21
+ if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
22
+ Response::error('Invalid email', 400);
23
+ }
24
+
25
+ if (strlen($password) < 8) {
26
+ Response::error('Password must be at least 8 characters', 400);
27
+ }
28
+
29
+ try {
30
+ $user = new User();
31
+ $newId = $user->create($nickname, $email, $password);
32
+ http_response_code(201);
33
+ Response::success(['id' => $newId]);
34
+ } catch (PDOException $e) {
35
+ if ($e->getCode() === '23000') {
36
+ Response::error('Nickname or email already exists', 409);
37
+ }
38
+ Response::error('Database error', 500);
39
+ }
@@ -0,0 +1,17 @@
1
+ <?php
2
+ declare(strict_types=1);
3
+
4
+ require_once __DIR__ . '/../../core/modules/Response.php';
5
+
6
+ if ($method !== 'GET') {
7
+ Response::error('Method not allowed', 405);
8
+ }
9
+
10
+ Response::success([
11
+ 'message' => 'Public endpoint is working',
12
+ 'params' => $requestParams,
13
+ ]);
14
+
15
+ Response::error([
16
+ 'message' => 'Error text',
17
+ ]);
@@ -4,35 +4,17 @@
4
4
  <rewrite>
5
5
  <rules>
6
6
 
7
- <!-- 1. Permetti index.php (- [L]) -->
7
+ <!-- 1. Permetti l'esecuzione diretta solo a index.php -->
8
8
  <rule name="Allow API index.php" stopProcessing="true">
9
- <match url="^index\.php$" ignoreCase="true" />
9
+ <match url="^core/index\.php$" ignoreCase="true" />
10
10
  <action type="None" />
11
11
  </rule>
12
12
 
13
- <!-- 2a. Manda tutti gli altri file .php a index.php -->
14
- <rule name="Route other PHP to index" stopProcessing="true">
15
- <match url="\.php$" ignoreCase="true" />
16
- <action type="Rewrite" url="index.php" />
17
- </rule>
18
-
19
- <!-- 2b. Manda le richieste per le sottocartelle protette a index.php -->
20
- <rule name="Protect Core API Directories" stopProcessing="true">
21
- <match url="^(core|endpoints|modules|vendor)($|/)" ignoreCase="true" />
22
- <action type="Rewrite" url="index.php" />
23
- </rule>
24
-
25
- <!-- 3. Regola per gli endpoint (se non è un file o una cartella, vai a index.php) -->
26
- <rule name="API Endpoints Front Controller" stopProcessing="true">
13
+ <!-- 2. Front Controller: Invia TUTTO il resto a index.php -->
14
+ <!-- Nessun controllo sull'esistenza di file o directory. -->
15
+ <rule name="API Endpoints Front Controller Catch-All" stopProcessing="true">
27
16
  <match url="^(.*)$" ignoreCase="true" />
28
- <conditions logicalGrouping="MatchAll">
29
- <!-- !-f -->
30
- <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
31
- <!-- !-d -->
32
- <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
33
- </conditions>
34
- <!-- appendQueryString="true" equivale al flag [QSA] di Apache -->
35
- <action type="Rewrite" url="index.php" appendQueryString="true" />
17
+ <action type="Rewrite" url="core/index.php" appendQueryString="true" />
36
18
  </rule>
37
19
 
38
20
  </rules>
@@ -3,10 +3,13 @@
3
3
  <h2 data-lang-key="test"></h2>
4
4
 
5
5
  <div class="btn-group" role="group">
6
- <input type="radio" class="btn-check" name="lang-switcher" id="lang-en" value="en"/>
6
+
7
+ {# enLangName is the key that contains the language name accordint to the current language #}
8
+ <input type="radio" class="btn-check" name="lang-button" id="lang-en" value="en"/>
7
9
  <label class="btn btn-outline-primary" for="lang-en" data-lang-key="enLangName"></label>
8
10
 
9
- <input type="radio" class="btn-check" name="lang-switcher" id="lang-it" value="it"/>
11
+ {# itLangName is the key that contains the language name accordint to the current language #}
12
+ <input type="radio" class="btn-check" name="lang-button" id="lang-it" value="it"/>
10
13
  <label class="btn btn-outline-primary" for="lang-it" data-lang-key="itLangName"></label>
11
14
  </div>
12
15
  </div>
@@ -25,7 +25,7 @@
25
25
  <meta property="og:type" content="website">
26
26
  <meta property="og:url" content="{{ site.url }}{{ page.url }}">
27
27
  <meta property="og:image" content="{{ site.url }}{{ site.logo }}">
28
- <meta property="og:site_name" content="{{ site.site_name }}">
28
+ <meta property="og:site-name" content="{{ site.site-name }}">
29
29
 
30
30
  <!-- Twitter Card -->
31
31
  <meta name="twitter:card" content="summary_large_image">
@@ -33,6 +33,31 @@
33
33
  <meta name="twitter:description" content="{{ pageData.seo.description }}">
34
34
  <meta name="twitter:image" content="{{ site.url }}{{ site.logo }}">
35
35
 
36
+ <!-- ========================================== -->
37
+ <!-- JSON-LD / Structured data for SEO and AI -->
38
+ <!-- ========================================== -->
39
+ <script type="application/ld+json">
40
+ {
41
+ "@context": "https://schema.org",
42
+ "@type": "WebPage",
43
+ "name": "{{ pageData.seo.title or title or site.title }}",
44
+ "description": "{{ pageData.seo.description or site.description }}",
45
+ "url": "{{ site.url }}{{ page.url }}",
46
+ "author": {
47
+ "@type": "Person",
48
+ "name": "{{ site.author }}"
49
+ },
50
+ "publisher": {
51
+ "@type": "Organization",
52
+ "name": "{{ site.site_name }}",
53
+ "logo": {
54
+ "@type": "ImageObject",
55
+ "url": "{{ site.url }}{{ site.logo }}"
56
+ }
57
+ }
58
+ }
59
+ </script>
60
+
36
61
  <!-- Favicon -->
37
62
  <link rel="icon" type="image/svg+xml" href="{{ site.favicon }}">
38
63
  <!-- (If you want to use a PNG favicon) -->
@@ -1,54 +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
- }
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
54
  }