create-berna-stencil 1.0.58 → 2.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/README.md +1 -1
- package/package.json +1 -1
- package/src/frontend/components/welcome.njk +404 -206
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-berna-stencil",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Eleventy boilerplate with per-page SCSS/JS pipeline, esbuild bundling, multi-framework CSS support and a built-in page management CLI",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"author": "Michele Garofalo",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div class="container my-3">
|
|
2
2
|
<h1>Welcome to <span class="berna-stencil">Berna-Stencil</span></h1>
|
|
3
3
|
<div class="slogan">The boilerplate you need, simplified</div>
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
<div class="guide-filter">
|
|
6
6
|
<label>
|
|
7
7
|
<input type="radio" name="guide-filter" value="welcome" checked />
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<input type="radio" name="guide-filter" value="javascript" />
|
|
20
20
|
<span class="filter-pill">Javascript</span>
|
|
21
21
|
</label>
|
|
22
|
-
<label>
|
|
22
|
+
<label>
|
|
23
23
|
<input type="radio" name="guide-filter" value="creating-pages" />
|
|
24
24
|
<span class="filter-pill">Creating Pages</span>
|
|
25
25
|
</label>
|
|
@@ -37,88 +37,50 @@
|
|
|
37
37
|
</label>
|
|
38
38
|
</div>
|
|
39
39
|
|
|
40
|
-
<div class="tabs-container">
|
|
40
|
+
<div class="tabs-container">
|
|
41
|
+
|
|
41
42
|
<div id="content-welcome" class="tab-content active">
|
|
42
43
|
<div class="grid" style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
|
43
|
-
<a
|
|
44
|
-
href="https://bernastencil.com"
|
|
45
|
-
class="card"
|
|
46
|
-
target="_blank"
|
|
47
|
-
rel="noopener noreferrer"
|
|
48
|
-
style="flex: 1; min-width: 250px;"
|
|
49
|
-
>
|
|
44
|
+
<a href="https://bernastencil.com" class="card" target="_blank" rel="noopener noreferrer" style="flex: 1; min-width: 250px;">
|
|
50
45
|
<i class="bi bi-book card-icon" aria-hidden="true"></i>
|
|
51
46
|
<h3>Documentation</h3>
|
|
52
|
-
<p>
|
|
53
|
-
|
|
54
|
-
customizations
|
|
55
|
-
</p>
|
|
56
|
-
<span class="card-link"
|
|
57
|
-
>Go to documentation <span class="bi bi-arrow-right"></span
|
|
58
|
-
></span>
|
|
47
|
+
<p>Everything you need to get started, from setup to advanced topics and customizations</p>
|
|
48
|
+
<span class="card-link">Go to documentation <span class="bi bi-arrow-right"></span></span>
|
|
59
49
|
</a>
|
|
60
|
-
|
|
61
|
-
<a
|
|
62
|
-
href="https://github.com/rhaastrake/berna-stencil"
|
|
63
|
-
class="card"
|
|
64
|
-
target="_blank"
|
|
65
|
-
rel="noopener noreferrer"
|
|
66
|
-
style="flex: 1; min-width: 250px;"
|
|
67
|
-
>
|
|
50
|
+
<a href="https://github.com/rhaastrake/berna-stencil" class="card" target="_blank" rel="noopener noreferrer" style="flex: 1; min-width: 250px;">
|
|
68
51
|
<i class="bi bi-github card-icon" aria-hidden="true"></i>
|
|
69
52
|
<h3>Github repository</h3>
|
|
70
53
|
<p>Community-driven. Contributions, issues and PRs are welcome.</p>
|
|
71
|
-
<span class="card-link"
|
|
72
|
-
>Open the repository <span class="bi bi-arrow-right"></span
|
|
73
|
-
></span>
|
|
54
|
+
<span class="card-link">Open the repository <span class="bi bi-arrow-right"></span></span>
|
|
74
55
|
</a>
|
|
75
56
|
</div>
|
|
76
57
|
</div>
|
|
77
|
-
|
|
58
|
+
|
|
78
59
|
<div id="content-assistant" class="tab-content">
|
|
79
60
|
<div class="markdown-body">
|
|
80
61
|
<h2>Assistant CLI</h2>
|
|
81
62
|
<p>An interactive CLI to manage pages without touching files manually.</p>
|
|
82
|
-
|
|
83
63
|
<pre><code>npm run assistant</code></pre>
|
|
84
|
-
|
|
85
64
|
<h3>Menu</h3>
|
|
86
65
|
<pre><code>1. Create page
|
|
87
66
|
2. Remove page
|
|
88
67
|
3. Rename page
|
|
89
68
|
4. Configure output path</code></pre>
|
|
90
|
-
|
|
91
69
|
<p>Use <code>CTRL/CMD + C</code> to exit.</p>
|
|
92
|
-
|
|
93
70
|
<h3>Create page</h3>
|
|
94
71
|
<p>Enter a page name in any format — the CLI converts it to kebab-case automatically.</p>
|
|
95
72
|
<p>For a page named <code>my-page</code>, the following files are created:</p>
|
|
96
|
-
|
|
97
73
|
<table>
|
|
98
74
|
<thead>
|
|
99
|
-
<tr>
|
|
100
|
-
<th>File</th>
|
|
101
|
-
<th>Purpose</th>
|
|
102
|
-
</tr>
|
|
75
|
+
<tr><th>File</th><th>Purpose</th></tr>
|
|
103
76
|
</thead>
|
|
104
77
|
<tbody>
|
|
105
|
-
<tr>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
</tr>
|
|
109
|
-
<tr>
|
|
110
|
-
<td><code>src/frontend/js/pages/myPage.js</code></td>
|
|
111
|
-
<td>JS entry point</td>
|
|
112
|
-
</tr>
|
|
113
|
-
<tr>
|
|
114
|
-
<td><code>src/frontend/_routes/my-page.njk</code></td>
|
|
115
|
-
<td>Nunjucks template</td>
|
|
116
|
-
</tr>
|
|
78
|
+
<tr><td><code>src/frontend/scss/pages/myPage.scss</code></td><td>SCSS entry point</td></tr>
|
|
79
|
+
<tr><td><code>src/frontend/js/pages/myPage.js</code></td><td>JS entry point</td></tr>
|
|
80
|
+
<tr><td><code>src/frontend/_routes/my-page.njk</code></td><td>Nunjucks template</td></tr>
|
|
117
81
|
</tbody>
|
|
118
82
|
</table>
|
|
119
|
-
|
|
120
83
|
<p>It also adds an <code>elif</code> block in <code>includes.njk</code> and a stub entry in <code>site.json</code>:</p>
|
|
121
|
-
|
|
122
84
|
<pre><code>"myPage": {
|
|
123
85
|
"seo": {
|
|
124
86
|
"title": "My Page",
|
|
@@ -129,29 +91,23 @@
|
|
|
129
91
|
"js": []
|
|
130
92
|
}
|
|
131
93
|
}</code></pre>
|
|
132
|
-
|
|
133
94
|
<h3>Remove page</h3>
|
|
134
95
|
<p>Deletes all source files for the page and cleans up the output directory, <code>includes.njk</code>, and <code>site.json</code>.</p>
|
|
135
|
-
|
|
136
96
|
<h3>Rename page</h3>
|
|
137
97
|
<p>Renames all three source files, updates the <code>elif</code> block in <code>includes.njk</code>, and renames the record in <code>site.json</code> while preserving all existing fields.</p>
|
|
138
|
-
|
|
139
98
|
<h3>Configure output path</h3>
|
|
140
99
|
<p>Updates the output directory across <code>.eleventy.js</code> and all relevant <code>package.json</code> scripts in one shot. The old output folder is deleted automatically.</p>
|
|
141
|
-
|
|
142
100
|
<blockquote>⚠️ <code>homepage</code> and <code>404</code> are protected — they cannot be created, removed, or renamed via the CLI.</blockquote>
|
|
143
101
|
</div>
|
|
144
102
|
</div>
|
|
145
|
-
|
|
103
|
+
|
|
146
104
|
<div id="content-styling" class="tab-content">
|
|
147
105
|
<div class="markdown-body">
|
|
148
106
|
<h2>Styling with SCSS</h2>
|
|
149
|
-
|
|
150
107
|
<h3>Page CSS</h3>
|
|
151
108
|
<p>Each page has its own SCSS entry point in <code>src/frontend/scss/pages/</code></p>
|
|
152
109
|
<p>It must contain <code>_root.scss</code> + other modules like <code>_global.scss</code> or any other one that you need and its own specific css rules.</p>
|
|
153
110
|
<p><code>_root.scss</code> uses <code>@use</code> to enable namespaced access (<code>root.$var</code>); other modules use <code>@import</code> as they don't expose variables.</p>
|
|
154
|
-
|
|
155
111
|
<h4>examplePage.scss <small>(<code>src/frontend/scss/pages/</code>)</small></h4>
|
|
156
112
|
<pre><code>//==========================
|
|
157
113
|
// CSS MODULES IMPORTS
|
|
@@ -170,10 +126,8 @@
|
|
|
170
126
|
body {
|
|
171
127
|
background-color: root.$primary;
|
|
172
128
|
}</code></pre>
|
|
173
|
-
|
|
174
129
|
<h3>Global Variables</h3>
|
|
175
130
|
<p>Instead of using <code>:root</code> in your custom modules or pages, the best thing to do is to centralize all your variables in a single file (that will be tree-shaken automatically by Sass).</p>
|
|
176
|
-
|
|
177
131
|
<h4>_root.scss <small>(<code>src/frontend/scss/modules/</code>)</small></h4>
|
|
178
132
|
<pre><code>$header-height: 10vh;
|
|
179
133
|
|
|
@@ -181,18 +135,15 @@ body {
|
|
|
181
135
|
header {
|
|
182
136
|
height: root.$header-height;
|
|
183
137
|
}</code></pre>
|
|
184
|
-
|
|
185
138
|
<h3>Scss modules</h3>
|
|
186
139
|
<p>You can create your custom css modules by creating a new <code>.scss</code> file in <code>src/frontend/scss/modules/</code> (the name of the file must start with <code>_</code>).</p>
|
|
187
140
|
<p>You can create subfolders if you want to refactor the structure, but be sure to update the relative paths in the pages that import them.</p>
|
|
188
|
-
|
|
189
141
|
<h4>_yourModule.scss <small>(<code>src/frontend/scss/modules/subfolder/</code>)</small></h4>
|
|
190
142
|
<pre><code>@use '../root' as root;
|
|
191
143
|
|
|
192
144
|
body {
|
|
193
145
|
background-color: root.$primary;
|
|
194
146
|
}</code></pre>
|
|
195
|
-
|
|
196
147
|
<h4>examplePage.scss</h4>
|
|
197
148
|
<pre><code>@import "../modules/subfolder/yourModule";
|
|
198
149
|
|
|
@@ -201,79 +152,44 @@ body {
|
|
|
201
152
|
body {
|
|
202
153
|
color: root.$dark;
|
|
203
154
|
}</code></pre>
|
|
204
|
-
|
|
205
155
|
<h4>Pre-existing modules</h4>
|
|
206
156
|
<table>
|
|
207
157
|
<thead>
|
|
208
|
-
<tr>
|
|
209
|
-
<th>File</th>
|
|
210
|
-
<th>Purpose</th>
|
|
211
|
-
</tr>
|
|
158
|
+
<tr><th>File</th><th>Purpose</th></tr>
|
|
212
159
|
</thead>
|
|
213
160
|
<tbody>
|
|
214
|
-
<tr>
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
</tr>
|
|
218
|
-
<tr>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
</tr>
|
|
222
|
-
<tr>
|
|
223
|
-
<td><code>_typography.scss</code></td>
|
|
224
|
-
<td>Font rules</td>
|
|
225
|
-
</tr>
|
|
226
|
-
<tr>
|
|
227
|
-
<td><code>_header.scss</code></td>
|
|
228
|
-
<td>Header styles</td>
|
|
229
|
-
</tr>
|
|
230
|
-
<tr>
|
|
231
|
-
<td><code>_footer.scss</code></td>
|
|
232
|
-
<td>Footer styles</td>
|
|
233
|
-
</tr>
|
|
234
|
-
<tr>
|
|
235
|
-
<td><code>_mobile.scss</code></td>
|
|
236
|
-
<td>Media query rules</td>
|
|
237
|
-
</tr>
|
|
238
|
-
<tr>
|
|
239
|
-
<td><code>_buttons.scss</code></td>
|
|
240
|
-
<td>Style and hovers for buttons</td>
|
|
241
|
-
</tr>
|
|
242
|
-
<tr>
|
|
243
|
-
<td><code>_animations.scss</code></td>
|
|
244
|
-
<td>Keyframe animations (<code>fade-in</code>, <code>spin</code>)</td>
|
|
245
|
-
</tr>
|
|
246
|
-
<tr>
|
|
247
|
-
<td><code>_notification.scss</code></td>
|
|
248
|
-
<td>Notification component style</td>
|
|
249
|
-
</tr>
|
|
161
|
+
<tr><td><code>_root.scss</code></td><td>Global variables (colors, spacing)</td></tr>
|
|
162
|
+
<tr><td><code>_global.scss</code></td><td>Site-wide base rules and frameworks</td></tr>
|
|
163
|
+
<tr><td><code>_typography.scss</code></td><td>Font rules</td></tr>
|
|
164
|
+
<tr><td><code>_header.scss</code></td><td>Header styles</td></tr>
|
|
165
|
+
<tr><td><code>_footer.scss</code></td><td>Footer styles</td></tr>
|
|
166
|
+
<tr><td><code>_mobile.scss</code></td><td>Media query rules</td></tr>
|
|
167
|
+
<tr><td><code>_buttons.scss</code></td><td>Style and hovers for buttons</td></tr>
|
|
168
|
+
<tr><td><code>_animations.scss</code></td><td>Keyframe animations (<code>fade-in</code>, <code>spin</code>)</td></tr>
|
|
169
|
+
<tr><td><code>_notification.scss</code></td><td>Notification component style</td></tr>
|
|
250
170
|
</tbody>
|
|
251
171
|
</table>
|
|
252
|
-
|
|
253
172
|
<h3>CSS Framework</h3>
|
|
254
173
|
<p>Some of the most popular css frameworks that supports scss with modules are already installed in <code>node_modules</code>.</p>
|
|
255
174
|
<p>You can choose one or none of them (more than 1 works, but you may get in various conflicts).</p>
|
|
256
175
|
<p>To enable/disable them you have to modify 3 files around the project by just commenting them.</p>
|
|
257
|
-
|
|
258
176
|
<h4>1. _global.scss <small>(<code>src/frontend/scss/modules/</code>)</small></h4>
|
|
259
177
|
<pre><code>@import "../modules/frameworks/bootstrap";
|
|
260
178
|
// @import "../modules/frameworks/bulma";
|
|
261
179
|
// @import "../modules/frameworks/foundation";
|
|
262
180
|
// @import "../modules/frameworks/uikit";</code></pre>
|
|
263
|
-
|
|
264
181
|
<h4>2. base.njk <small>(<code>src/frontend/components/layouts/</code>)</small></h4>
|
|
265
|
-
<pre><code
|
|
266
|
-
<script src="/js/bootstrap.bundle.min.js" defer></script>
|
|
267
|
-
|
|
268
|
-
{# Foundation JS #}
|
|
269
|
-
{# <script src="/js/foundation.min.js" defer></script> #}
|
|
182
|
+
<pre><code><!-- Bootstrap -->
|
|
183
|
+
<!-- <script src="/js/bootstrap.bundle.min.js" defer></script> -->
|
|
270
184
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
{# <script src="/js/uikit-icons.min.js" defer></script> #}
|
|
185
|
+
<!-- Foundation -->
|
|
186
|
+
<!-- <script src="/js/foundation.min.js" defer></script> -->
|
|
274
187
|
|
|
275
|
-
|
|
188
|
+
<!-- UIkit -->
|
|
189
|
+
<!-- <script src="/js/uikit.min.js" defer></script> -->
|
|
190
|
+
<!-- <script src="/js/uikit-icons.min.js" defer></script> -->
|
|
276
191
|
|
|
192
|
+
<!-- Bulma — no JS needed --></code></pre>
|
|
277
193
|
<h4>3. .eleventy.js</h4>
|
|
278
194
|
<pre><code>eleventyConfig.addPassthroughCopy({
|
|
279
195
|
// Bootstrap
|
|
@@ -289,133 +205,406 @@ body {
|
|
|
289
205
|
|
|
290
206
|
// Bulma — CSS only, no JS passthrough needed
|
|
291
207
|
});</code></pre>
|
|
292
|
-
|
|
293
208
|
<h4>Reducing bundle size</h4>
|
|
294
209
|
<p>To reduce the bundle size, open the corresponding framework file (<code>src/frontend/scss/modules/frameworks/</code>) and comment out any modules you don't need.</p>
|
|
295
210
|
<pre><code>@import "bootstrap/scss/card"; // Cards
|
|
296
211
|
@import "bootstrap/scss/carousel"; // Carousel</code></pre>
|
|
297
212
|
</div>
|
|
298
213
|
</div>
|
|
299
|
-
|
|
214
|
+
|
|
300
215
|
<div id="content-javascript" class="tab-content">
|
|
301
216
|
<div class="markdown-body">
|
|
302
217
|
<h2>JavaScript</h2>
|
|
303
|
-
|
|
304
218
|
<h3>Page JS</h3>
|
|
305
219
|
<p>Each page has its own JS entry point in <code>src/frontend/js/pages/</code></p>
|
|
306
220
|
<p>It is bundled and minified by esbuild and loaded automatically by <code>base.njk</code></p>
|
|
307
221
|
<p>Import only what the page needs.</p>
|
|
308
|
-
|
|
309
222
|
<h4>examplePage.js <small>(<code>src/frontend/js/pages/</code>)</small></h4>
|
|
310
|
-
<pre><code>
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
document.addEventListener("DOMContentLoaded", () => {
|
|
314
|
-
initLangSwitcher();
|
|
315
|
-
});
|
|
223
|
+
<pre><code>
|
|
224
|
+
import { initLangSwitcher } from '../modules/langSwitcher.js';
|
|
225
|
+
import { showNotification } from '../modules/notification.js';
|
|
316
226
|
|
|
317
|
-
|
|
227
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
228
|
+
initLangSwitcher();
|
|
229
|
+
});
|
|
318
230
|
|
|
231
|
+
showNotification("Page loaded", "success", 3000);</code></pre>
|
|
319
232
|
<h3>Modules</h3>
|
|
320
233
|
<p>Modules live in <code>src/frontend/js/modules/</code>. Some must be called inside <code>DOMContentLoaded</code> as they interact with the DOM; others create elements dynamically and can be called anywhere.</p>
|
|
321
|
-
|
|
322
234
|
<h4>Call inside <code>DOMContentLoaded</code></h4>
|
|
323
235
|
<table>
|
|
324
236
|
<thead>
|
|
325
|
-
<tr>
|
|
326
|
-
<th>Module</th>
|
|
327
|
-
<th>Function</th>
|
|
328
|
-
</tr>
|
|
237
|
+
<tr><th>Module</th><th>Function</th></tr>
|
|
329
238
|
</thead>
|
|
330
239
|
<tbody>
|
|
331
|
-
<tr>
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
</tr>
|
|
335
|
-
<tr>
|
|
336
|
-
<td><code>modules/forms/form.js</code></td>
|
|
337
|
-
<td><code>initFormListener()</code></td>
|
|
338
|
-
</tr>
|
|
339
|
-
<tr>
|
|
340
|
-
<td><code>modules/forms/textAreaAutoExpand.js</code></td>
|
|
341
|
-
<td><code>initTextAreaAutoExpand()</code></td>
|
|
342
|
-
</tr>
|
|
343
|
-
<tr>
|
|
344
|
-
<td><code>modules/forms/normalizePhoneNumber.js</code></td>
|
|
345
|
-
<td><code>initNormalizePhoneNumber()</code></td>
|
|
346
|
-
</tr>
|
|
240
|
+
<tr><td><code>modules/langSwitcher.js</code></td><td><code>initLangSwitcher()</code></td></tr>
|
|
241
|
+
<tr><td><code>modules/forms/form.js</code></td><td><code>initFormListener()</code></td></tr>
|
|
242
|
+
<tr><td><code>modules/forms/textAreaAutoExpand.js</code></td><td><code>initTextAreaAutoExpand()</code></td></tr>
|
|
243
|
+
<tr><td><code>modules/forms/normalizePhoneNumber.js</code></td><td><code>initNormalizePhoneNumber()</code></td></tr>
|
|
347
244
|
</tbody>
|
|
348
245
|
</table>
|
|
349
|
-
|
|
350
246
|
<h4>Call anywhere</h4>
|
|
351
247
|
<table>
|
|
352
248
|
<thead>
|
|
353
|
-
<tr>
|
|
354
|
-
<th>Module</th>
|
|
355
|
-
<th>Function</th>
|
|
356
|
-
</tr>
|
|
249
|
+
<tr><th>Module</th><th>Function</th></tr>
|
|
357
250
|
</thead>
|
|
358
251
|
<tbody>
|
|
359
|
-
<tr>
|
|
360
|
-
<td><code>modules/notification.js</code></td>
|
|
361
|
-
<td><code>showNotification(text, type, duration)</code></td>
|
|
362
|
-
</tr>
|
|
252
|
+
<tr><td><code>modules/notification.js</code></td><td><code>showNotification(text, type, duration)</code></td></tr>
|
|
363
253
|
</tbody>
|
|
364
254
|
</table>
|
|
365
|
-
|
|
366
255
|
<h4><code>showNotification</code> parameters</h4>
|
|
367
256
|
<table>
|
|
368
257
|
<thead>
|
|
369
|
-
<tr>
|
|
370
|
-
<th>Parameter</th>
|
|
371
|
-
<th>Type</th>
|
|
372
|
-
<th>Default</th>
|
|
373
|
-
<th>Values</th>
|
|
374
|
-
</tr>
|
|
258
|
+
<tr><th>Parameter</th><th>Type</th><th>Default</th><th>Values</th></tr>
|
|
375
259
|
</thead>
|
|
376
260
|
<tbody>
|
|
377
|
-
<tr>
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
<td>—</td>
|
|
381
|
-
<td>Any string</td>
|
|
382
|
-
</tr>
|
|
383
|
-
<tr>
|
|
384
|
-
<td><code>type</code></td>
|
|
385
|
-
<td>string</td>
|
|
386
|
-
<td><code>"info"</code></td>
|
|
387
|
-
<td><code>"success"</code>, <code>"info"</code>, <code>"error"</code></td>
|
|
388
|
-
</tr>
|
|
389
|
-
<tr>
|
|
390
|
-
<td><code>duration</code></td>
|
|
391
|
-
<td>number</td>
|
|
392
|
-
<td><code>5000</code></td>
|
|
393
|
-
<td>ms, or <code>-1</code> for persistent with spinner</td>
|
|
394
|
-
</tr>
|
|
261
|
+
<tr><td><code>text</code></td><td>string</td><td>—</td><td>Any string</td></tr>
|
|
262
|
+
<tr><td><code>type</code></td><td>string</td><td><code>"info"</code></td><td><code>"success"</code>, <code>"info"</code>, <code>"error"</code></td></tr>
|
|
263
|
+
<tr><td><code>duration</code></td><td>number</td><td><code>5000</code></td><td>ms, or <code>-1</code> for persistent with spinner</td></tr>
|
|
395
264
|
</tbody>
|
|
396
265
|
</table>
|
|
397
|
-
|
|
398
266
|
<h3>Adding a module</h3>
|
|
399
267
|
<p>Create a new <code>.js</code> file in <code>src/frontend/js/modules/</code>. You can organize them into subfolders freely.</p>
|
|
400
268
|
<p>Use ESM syntax — esbuild handles the bundling:</p>
|
|
401
|
-
|
|
402
269
|
<pre><code>// _yourModule.js
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
270
|
+
export function yourFunction() {
|
|
271
|
+
// ...
|
|
272
|
+
}</code></pre>
|
|
407
273
|
<p>Then import it in the pages that need it:</p>
|
|
408
|
-
|
|
409
274
|
<pre><code>import { yourFunction } from '../modules/yourModule.js';</code></pre>
|
|
410
|
-
|
|
411
275
|
<blockquote>⚠️ Files inside <code>_tools/</code> run directly in Node.js without a bundler — use CommonJS (<code>require</code> / <code>module.exports</code>) there, not ESM.</blockquote>
|
|
412
276
|
</div>
|
|
413
277
|
</div>
|
|
414
278
|
|
|
415
|
-
<div id="content-
|
|
279
|
+
<div id="content-creating-pages" class="tab-content">
|
|
280
|
+
<div class="markdown-body">
|
|
281
|
+
<h2>Creating Pages</h2>
|
|
282
|
+
<p>The recommended way is via the <a href="#" class="nav-to-assistant">Assistant CLI</a>.</p>
|
|
283
|
+
<h3>What gets created</h3>
|
|
284
|
+
<p>For a page named <code>my-page</code>:</p>
|
|
285
|
+
<table>
|
|
286
|
+
<thead>
|
|
287
|
+
<tr><th>File</th><th>Purpose</th></tr>
|
|
288
|
+
</thead>
|
|
289
|
+
<tbody>
|
|
290
|
+
<tr><td><code>src/pages/my-page.njk</code></td><td>Template with front matter</td></tr>
|
|
291
|
+
<tr><td><code>src/scss/pages/myPage.scss</code></td><td>Imports framework + modules</td></tr>
|
|
292
|
+
<tr><td><code>src/js/pages/myPage.js</code></td><td>Imports JS modules</td></tr>
|
|
293
|
+
</tbody>
|
|
294
|
+
</table>
|
|
295
|
+
<h3>Adding content</h3>
|
|
296
|
+
<ol>
|
|
297
|
+
<li>Create a component in <code>src/components/</code> (e.g., <code>_myPage.njk</code>)</li>
|
|
298
|
+
<li>Include it in <code>src/layouts/includes.njk</code> inside the generated <code>elif</code> block:</li>
|
|
299
|
+
</ol>
|
|
300
|
+
<pre><code>{% elif title == "myPage" %}
|
|
301
|
+
{% include "_myPage.njk" %}</code></pre>
|
|
302
|
+
<p>See <a href="components.html">components.md</a> for details.</p>
|
|
303
|
+
<h3>URL and title</h3>
|
|
304
|
+
<p>The URL is the kebab-case name (<code>/my-page/</code>). The <code>title</code> in the front matter is camelCase (<code>myPage</code>) and is used internally to load the correct CSS and JS files — <strong>do not change it</strong>.</p>
|
|
305
|
+
<h3>SEO</h3>
|
|
306
|
+
<p>The CLI creates a stub entry in <code>src/data/site.json</code>. Fill it in:</p>
|
|
307
|
+
<pre><code>"myPage": {
|
|
308
|
+
"seo": {
|
|
309
|
+
"title": "My Page | Site Name",
|
|
310
|
+
"description": "Page description"
|
|
311
|
+
}
|
|
312
|
+
}</code></pre>
|
|
313
|
+
<p>See <a href="head-and-seo.html">head-and-seo.md</a> for all available options.</p>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
<div id="content-components" class="tab-content">
|
|
416
318
|
<div class="markdown-body">
|
|
417
|
-
<h2>
|
|
418
|
-
<
|
|
319
|
+
<h2>Nunjucks (HTML) Components</h2>
|
|
320
|
+
<h3>What is Nunjucks</h3>
|
|
321
|
+
<p>Nunjucks (<code>.njk</code>) is an HTML file that supports logic like variables, <code>if</code> statements, and <code>for</code> loops. It can extend a base layout and include other <code>.njk</code> components.</p>
|
|
322
|
+
<h3>Create a component</h3>
|
|
323
|
+
<p>Create a new <code>.njk</code> file anywhere inside <code>src/frontend/components/</code>. You can organize them into subfolders freely:</p>
|
|
324
|
+
<pre><code>src/frontend/components/
|
|
325
|
+
├── global/
|
|
326
|
+
├── layouts/
|
|
327
|
+
├── modals/
|
|
328
|
+
│ └── privacyModal.njk
|
|
329
|
+
└── welcome.njk</code></pre>
|
|
330
|
+
<h3>Include a component</h3>
|
|
331
|
+
<p>To render a component inside a page, navigate to <code>src/frontend/components/layouts/</code> and edit <code>includes.njk</code>.</p>
|
|
332
|
+
<h4>includes.njk <small>(<code>src/frontend/components/layouts/</code>)</small></h4>
|
|
333
|
+
<pre><code>{% if title == "homepage" %}
|
|
334
|
+
{% include "welcome.njk" %}
|
|
335
|
+
|
|
336
|
+
{% elif title == "examplePage" %}
|
|
337
|
+
{% include "exampleComponent1.njk" %}
|
|
338
|
+
{% include "subfolder/exampleComponent2.njk" %}
|
|
339
|
+
|
|
340
|
+
{% else %}
|
|
341
|
+
{% include "404/_404.njk" %}
|
|
342
|
+
{{ content | safe }}
|
|
343
|
+
{% endif %}</code></pre>
|
|
344
|
+
<p>Add a new <code>{% elif %}</code> block for each page, listing its components in order. If a component lives in a subfolder, specify the relative path accordingly.</p>
|
|
345
|
+
<blockquote>⚠️ A new <code>elif</code> block is automatically added when you create a page via the Assistant CLI.</blockquote>
|
|
346
|
+
<blockquote>⚠️ If you move or delete a component, always update <code>includes.njk</code> or the site will break.</blockquote>
|
|
347
|
+
<h3>Nest components</h3>
|
|
348
|
+
<p>A component can include other components. This is useful for breaking complex sections into smaller, reusable pieces.</p>
|
|
349
|
+
<h4>exampleComponent.njk</h4>
|
|
350
|
+
<pre><code><section class="hero">
|
|
351
|
+
{% include "ui/heroTitle.njk" %}
|
|
352
|
+
{% include "ui/heroButton.njk" %}
|
|
353
|
+
</section></code></pre>
|
|
354
|
+
<blockquote>The same path rules apply: if the included component is in a subfolder, specify the full relative path.</blockquote>
|
|
355
|
+
<h3>Global components</h3>
|
|
356
|
+
<p>Header and footer live in <code>src/frontend/components/global/</code> and are automatically included in every page via <code>base.njk</code>. Edit them to change the site-wide layout.</p>
|
|
357
|
+
<h3>Site data in components</h3>
|
|
358
|
+
<p>All values defined in <code>src/data/site.json</code> are globally available in every component via <code>{{ site.* }}</code>.</p>
|
|
359
|
+
<h4>site.json <small>(<code>src/data/</code>)</small></h4>
|
|
360
|
+
<pre><code>{
|
|
361
|
+
"title": "My Site",
|
|
362
|
+
"logo": "/img/logo.png",
|
|
363
|
+
"legal": {
|
|
364
|
+
"privacy": "/privacy"
|
|
365
|
+
}
|
|
366
|
+
}</code></pre>
|
|
367
|
+
<h4>Usage in any <code>.njk</code> file</h4>
|
|
368
|
+
<pre><code><p>{{ site.title }}</p>
|
|
369
|
+
<a href="{{ site.legal.privacy }}">Privacy Policy</a>
|
|
370
|
+
<img src="{{ site.logo }}" alt="{{ site.title }}"></code></pre>
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
|
|
374
|
+
<div id="content-head-seo" class="tab-content">
|
|
375
|
+
<div class="markdown-body">
|
|
376
|
+
<h2>Head & SEO</h2>
|
|
377
|
+
<p>This json holds global settings used across all pages in <code>base.njk</code> and other components:</p>
|
|
378
|
+
<h3>site.json <small>(<code>src/frontend/data/</code>)</small></h3>
|
|
379
|
+
<pre><code>{
|
|
380
|
+
"site_name": "Site name",
|
|
381
|
+
"title": "Site title",
|
|
382
|
+
"description": "Site description",
|
|
383
|
+
"keywords": "keyword1, keyword2, keyword3",
|
|
384
|
+
"domain": "yoursite.com",
|
|
385
|
+
"url": "https://yoursite.com",
|
|
386
|
+
"lang": "en",
|
|
387
|
+
"author": "Name and surname",
|
|
388
|
+
"data_bs_theme": "dark",
|
|
389
|
+
"favicon": "/assets/brand/favicon.svg",
|
|
390
|
+
"logo": "/assets/brand/logo.svg"
|
|
391
|
+
}</code></pre>
|
|
392
|
+
<h2>Per-page SEO and CDN</h2>
|
|
393
|
+
<p>Each page entry is keyed by its camelCase <code>title</code> from the front matter.</p>
|
|
394
|
+
<p>If you don't want to use a particular CDN inserting it in <code>base.njk</code> for all pages, you can add extra specific CDN (CSS, JS) by inserting the link in each page of <code>site.json</code> separating them with a <code>,</code> and setting them in <code>""</code>.</p>
|
|
395
|
+
<h3>site.json <small>(<code>src/frontend/data/</code>)</small></h3>
|
|
396
|
+
<pre><code>"pages": {
|
|
397
|
+
"examplePage": {
|
|
398
|
+
"seo": {
|
|
399
|
+
"title": "Example Page",
|
|
400
|
+
"description": "description"
|
|
401
|
+
},
|
|
402
|
+
"cdn": {
|
|
403
|
+
// You can leave the [] empty
|
|
404
|
+
"css": ["https://example1.com/lib.min.css", "https://example2.com/lib.min.css"],
|
|
405
|
+
"js": ["https://example1.com/lib.min.js", "https://example2.com/lib.min.js"]
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}</code></pre>
|
|
409
|
+
<h2>AI & SEO bots</h2>
|
|
410
|
+
<p><code>llms.txt</code> and <code>robots.txt</code> are generated automatically from <code>site.json</code> via their respective <code>.njk</code> files — no manual editing needed.</p>
|
|
411
|
+
<table>
|
|
412
|
+
<thead>
|
|
413
|
+
<tr><th>File</th><th>Purpose</th><th>Reachable at</th></tr>
|
|
414
|
+
</thead>
|
|
415
|
+
<tbody>
|
|
416
|
+
<tr><td><code>llms.njk</code></td><td>Tells AI models what your site is about</td><td><code>yoursite.com/llms.txt</code></td></tr>
|
|
417
|
+
<tr><td><code>robots.njk</code></td><td>Controls search engine crawling</td><td><code>yoursite.com/robots.txt</code></td></tr>
|
|
418
|
+
</tbody>
|
|
419
|
+
</table>
|
|
420
|
+
<p>To customize them, edit <code>src/llms.njk</code> or <code>src/robots.njk</code> directly.</p>
|
|
421
|
+
<h2>Configuration field description</h2>
|
|
422
|
+
<table>
|
|
423
|
+
<thead>
|
|
424
|
+
<tr><th>Field</th><th>Purpose</th></tr>
|
|
425
|
+
</thead>
|
|
426
|
+
<tbody>
|
|
427
|
+
<tr><td><code>site_name</code></td><td>Brand name (used in meta tags)</td></tr>
|
|
428
|
+
<tr><td><code>title</code></td><td>Default page title</td></tr>
|
|
429
|
+
<tr><td><code>description</code></td><td>Default meta description</td></tr>
|
|
430
|
+
<tr><td><code>keywords</code></td><td>Default meta keywords</td></tr>
|
|
431
|
+
<tr><td><code>domain</code> / <code>url</code></td><td>Used for canonical URLs and og:url</td></tr>
|
|
432
|
+
<tr><td><code>lang</code></td><td>HTML <code>lang</code> attribute</td></tr>
|
|
433
|
+
<tr><td><code>author</code></td><td>Meta author tag</td></tr>
|
|
434
|
+
<tr><td><code>data_bs_theme</code></td><td>Bootstrap color scheme (<code>light</code> / <code>dark</code>)</td></tr>
|
|
435
|
+
<tr><td><code>favicon</code></td><td>Path to the favicon</td></tr>
|
|
436
|
+
<tr><td><code>logo</code></td><td>Path to the logo (available as <code>{{ site.logo }}</code>)</td></tr>
|
|
437
|
+
</tbody>
|
|
438
|
+
</table>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
|
|
442
|
+
<div id="content-backend" class="tab-content">
|
|
443
|
+
<div class="markdown-body">
|
|
444
|
+
<h2>Backend</h2>
|
|
445
|
+
<p>The backend is a PHP REST API located in <code>src/backend/</code>, copied to the output directory automatically at build time.</p>
|
|
446
|
+
<h3>Structure</h3>
|
|
447
|
+
<pre><code>src/backend/
|
|
448
|
+
├── api/
|
|
449
|
+
│ ├── public/ # Endpoints accessible without an API key
|
|
450
|
+
│ └── protected/ # Endpoints requiring X-Api-Key header
|
|
451
|
+
├── database/
|
|
452
|
+
│ ├── Database.php
|
|
453
|
+
│ ├── models/
|
|
454
|
+
│ └── migrations/
|
|
455
|
+
├── config.php # Your local config — never commit this
|
|
456
|
+
└── config.example.php</code></pre>
|
|
457
|
+
<h3>Configuration</h3>
|
|
458
|
+
<p><code>config.php</code> works like a <code>.env</code> file — it holds secrets and environment settings that stay local and out of version control.</p>
|
|
459
|
+
<p>Copy <code>config.example.php</code> to <code>config.php</code> and fill in your values:</p>
|
|
460
|
+
<h4>config.php <small>(<code>src/backend/</code>)</small></h4>
|
|
461
|
+
<pre><code>return [
|
|
462
|
+
'APP_ENV' => 'development', // or 'production'
|
|
463
|
+
'API_KEY' => 'your-default-key',
|
|
464
|
+
|
|
465
|
+
'ENDPOINT_KEYS' => [
|
|
466
|
+
'subfolder/example-protected' => 'specific-key',
|
|
467
|
+
],
|
|
468
|
+
|
|
469
|
+
'DB_HOST' => '127.0.0.1',
|
|
470
|
+
'DB_NAME' => 'example_db',
|
|
471
|
+
'DB_USER' => 'root',
|
|
472
|
+
'DB_PASS' => '',
|
|
473
|
+
];</code></pre>
|
|
474
|
+
<p><code>API_KEY</code> is the fallback key for all protected endpoints. Use <code>ENDPOINT_KEYS</code> to assign a different key to a specific endpoint — for subfolder endpoints, use the relative path as the key.</p>
|
|
475
|
+
<h3>How routing works</h3>
|
|
476
|
+
<p>The file path inside <code>api/</code> maps directly to the URL. Extra URL segments become route parameters available as <code>$requestParams[]</code>.</p>
|
|
477
|
+
<p>Every endpoint file has access to:</p>
|
|
478
|
+
<table>
|
|
479
|
+
<thead>
|
|
480
|
+
<tr><th>Variable</th><th>Description</th></tr>
|
|
481
|
+
</thead>
|
|
482
|
+
<tbody>
|
|
483
|
+
<tr><td><code>$method</code></td><td>HTTP method (<code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>PATCH</code>, <code>DELETE</code>)</td></tr>
|
|
484
|
+
<tr><td><code>$requestParams</code></td><td>Extra URL segments (e.g. <code>/api/posts/42</code> → <code>['42']</code>)</td></tr>
|
|
485
|
+
</tbody>
|
|
486
|
+
</table>
|
|
487
|
+
<h3>Creating a public endpoint</h3>
|
|
488
|
+
<p>Create a <code>.php</code> file anywhere inside <code>api/public/</code></p>
|
|
489
|
+
<pre><code><?php
|
|
490
|
+
declare(strict_types=1);
|
|
491
|
+
|
|
492
|
+
require_once CORE_PATH . '/modules/Response.php';
|
|
493
|
+
|
|
494
|
+
if ($method !== 'GET') {
|
|
495
|
+
Response::error('Method not allowed', 405);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
$id = isset($requestParams[0]) ? (int)$requestParams[0] : null;
|
|
499
|
+
|
|
500
|
+
Response::success(['id' => $id]);</code></pre>
|
|
501
|
+
<p>Reachable at <code>/api/posts</code> or <code>/api/posts/42</code></p>
|
|
502
|
+
<h3>Creating a protected endpoint</h3>
|
|
503
|
+
<p>Create a <code>.php</code> file inside <code>api/protected/</code>. The API key check happens automatically before your file runs.</p>
|
|
504
|
+
<pre><code><?php
|
|
505
|
+
declare(strict_types=1);
|
|
506
|
+
|
|
507
|
+
require_once CORE_PATH . '/modules/Response.php';
|
|
508
|
+
|
|
509
|
+
if ($method !== 'GET') {
|
|
510
|
+
Response::error('Method not allowed', 405);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
Response::success(['visits' => 1024]);</code></pre>
|
|
514
|
+
<p>To assign a dedicated key, add it to <code>config.php</code>:</p>
|
|
515
|
+
<pre><code>'ENDPOINT_KEYS' => [
|
|
516
|
+
'admin/stats' => 'secret-stats-key',
|
|
517
|
+
],</code></pre>
|
|
518
|
+
<h3>The Response helper</h3>
|
|
519
|
+
<pre><code>Response::success($data, $code); // default 200
|
|
520
|
+
Response::error($message, $code, $details); // default 400
|
|
521
|
+
Response::noContent(); // 204</code></pre>
|
|
522
|
+
<h3>Handling multiple methods</h3>
|
|
523
|
+
<pre><code>$id = isset($requestParams[0]) ? (int)$requestParams[0] : null;
|
|
524
|
+
$input = json_decode(file_get_contents('php://input'), true) ?? [];
|
|
525
|
+
|
|
526
|
+
switch ($method) {
|
|
527
|
+
case 'GET':
|
|
528
|
+
Response::success(['id' => $id]);
|
|
529
|
+
break;
|
|
530
|
+
|
|
531
|
+
case 'POST':
|
|
532
|
+
if (empty($input['title'])) Response::error('Missing title', 400);
|
|
533
|
+
Response::success(['message' => 'Created'], 201);
|
|
534
|
+
break;
|
|
535
|
+
|
|
536
|
+
case 'DELETE':
|
|
537
|
+
if (!$id) Response::error('ID required', 400);
|
|
538
|
+
Response::success(['message' => 'Deleted']);
|
|
539
|
+
break;
|
|
540
|
+
|
|
541
|
+
default:
|
|
542
|
+
Response::error('Method not allowed', 405);
|
|
543
|
+
}</code></pre>
|
|
544
|
+
<h3>Using the database</h3>
|
|
545
|
+
<h4>database/models/Post.php</h4>
|
|
546
|
+
<pre><code><?php
|
|
547
|
+
declare(strict_types=1);
|
|
548
|
+
|
|
549
|
+
require_once __DIR__ . '/../Database.php';
|
|
550
|
+
|
|
551
|
+
class Post {
|
|
552
|
+
private PDO $db;
|
|
553
|
+
|
|
554
|
+
public function __construct() {
|
|
555
|
+
$this->db = Database::getInstance();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
public function getAll(): array {
|
|
559
|
+
return $this->db->query("SELECT * FROM posts")->fetchAll();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
public function getById(int $id): ?array {
|
|
563
|
+
$stmt = $this->db->prepare("SELECT * FROM posts WHERE id = :id");
|
|
564
|
+
$stmt->execute(['id' => $id]);
|
|
565
|
+
return $stmt->fetch() ?: null;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
public function create(string $title): int {
|
|
569
|
+
$stmt = $this->db->prepare("INSERT INTO posts (title) VALUES (:title)");
|
|
570
|
+
$stmt->execute(['title' => htmlspecialchars(strip_tags(trim($title)))]);
|
|
571
|
+
return (int)$this->db->lastInsertId();
|
|
572
|
+
}
|
|
573
|
+
}</code></pre>
|
|
574
|
+
<p>Then use it inside an endpoint:</p>
|
|
575
|
+
<pre><code>require_once __DIR__ . '/../../database/models/Post.php';
|
|
576
|
+
|
|
577
|
+
$post = new Post();
|
|
578
|
+
Response::success($post->getAll());</code></pre>
|
|
579
|
+
<p>Migrations live in <code>database/migrations/</code> as plain SQL files — run them manually against your database.</p>
|
|
580
|
+
<h3>Calling endpoints from the frontend</h3>
|
|
581
|
+
<pre><code>// Public
|
|
582
|
+
const res = await fetch('/api/posts/42');
|
|
583
|
+
|
|
584
|
+
// Protected
|
|
585
|
+
const res = await fetch('/api/admin/stats', {
|
|
586
|
+
headers: { 'X-Api-Key': 'secret-stats-key' }
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// POST
|
|
590
|
+
const res = await fetch('/api/posts', {
|
|
591
|
+
method: 'POST',
|
|
592
|
+
headers: { 'Content-Type': 'application/json', 'X-Api-Key': 'your-key' },
|
|
593
|
+
body: JSON.stringify({ title: 'Hello world' })
|
|
594
|
+
});</code></pre>
|
|
595
|
+
<h3>Pre-built endpoints</h3>
|
|
596
|
+
<table>
|
|
597
|
+
<thead>
|
|
598
|
+
<tr><th>Route</th><th>Auth</th><th>Methods</th><th>Description</th></tr>
|
|
599
|
+
</thead>
|
|
600
|
+
<tbody>
|
|
601
|
+
<tr><td><code>/api/example-public</code></td><td>No</td><td><code>GET</code></td><td>Smoke test for public routing</td></tr>
|
|
602
|
+
<tr><td><code>/api/subfolder/example-protected</code></td><td>Yes</td><td><code>GET</code></td><td>Smoke test for protected routing</td></tr>
|
|
603
|
+
<tr><td><code>/api/auth/register</code></td><td>No</td><td><code>POST</code></td><td>Register a new user</td></tr>
|
|
604
|
+
<tr><td><code>/api/auth/login</code></td><td>No</td><td><code>POST</code></td><td>Login and retrieve user data</td></tr>
|
|
605
|
+
<tr><td><code>/api/auth-system</code></td><td>Yes</td><td><code>GET POST PUT PATCH DELETE</code></td><td>Full CRUD on users</td></tr>
|
|
606
|
+
</tbody>
|
|
607
|
+
</table>
|
|
419
608
|
</div>
|
|
420
609
|
</div>
|
|
421
610
|
</div>
|
|
@@ -425,10 +614,10 @@ body {
|
|
|
425
614
|
h1 {
|
|
426
615
|
text-align: center;
|
|
427
616
|
font-weight: 400;
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
617
|
+
}
|
|
618
|
+
h1 .berna-stencil {
|
|
619
|
+
color: #42b883;
|
|
620
|
+
font-weight: 600;
|
|
432
621
|
}
|
|
433
622
|
.slogan {
|
|
434
623
|
text-align: center;
|
|
@@ -506,14 +695,12 @@ body {
|
|
|
506
695
|
flex-wrap: wrap;
|
|
507
696
|
justify-content: center;
|
|
508
697
|
margin-bottom: 2rem;
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
}
|
|
698
|
+
}
|
|
699
|
+
.guide-filter label {
|
|
700
|
+
cursor: pointer;
|
|
701
|
+
}
|
|
702
|
+
.guide-filter label input[type="radio"] {
|
|
703
|
+
display: none;
|
|
517
704
|
}
|
|
518
705
|
|
|
519
706
|
.filter-pill {
|
|
@@ -637,4 +824,15 @@ body {
|
|
|
637
824
|
}
|
|
638
825
|
});
|
|
639
826
|
});
|
|
827
|
+
document.querySelector('.nav-to-assistant').addEventListener('click', (e) => {
|
|
828
|
+
e.preventDefault();
|
|
829
|
+
|
|
830
|
+
const assistantRadio = document.querySelector('input[name="guide-filter"][value="assistant"]');
|
|
831
|
+
|
|
832
|
+
if (assistantRadio) {
|
|
833
|
+
assistantRadio.checked = true;
|
|
834
|
+
assistantRadio.dispatchEvent(new Event('change'));
|
|
835
|
+
document.querySelector('.tabs-container');
|
|
836
|
+
}
|
|
837
|
+
});
|
|
640
838
|
</script>
|