bulma-turbo-themes 0.10.7 → 0.11.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.
@@ -1,377 +0,0 @@
1
- <!doctype html>
2
- <html lang="{{ site.lang | default: 'en' }}" data-theme="auto" data-baseurl="{{ site.baseurl }}">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <meta
7
- name="app-i18n"
8
- data-theme-label="{{ site.data.i18n.en.theme_label }}"
9
- data-current-theme-title-template="{{ site.data.i18n.en.current_theme_title }}"
10
- />
11
- <!--
12
- CSP Security Trade-off: 'unsafe-inline' for script-src
13
- ========================================================
14
- Why required: The inline theme-blocking script (lines 19-87 below) must run
15
- synchronously before first paint to prevent Flash of Unstyled Content (FOUC).
16
- Without it, users would see the wrong theme briefly before it snaps to their
17
- saved preference — a jarring experience.
18
-
19
- Why not use nonces/hashes:
20
- - Nonces require server-side generation (not possible with static Jekyll/GitHub Pages)
21
- - Hashes are brittle and break on any script change, causing CI failures
22
-
23
- Mitigations in place:
24
- - Inline script validates theme IDs against allowlist (prevents class injection)
25
- - URL sanitization prevents XSS via manipulated data-baseurl
26
- - No eval() or dynamic code execution
27
- - All other scripts are external files
28
-
29
- Future tightening: Service worker precomputed theme class, or migration to SSR
30
- with nonce-based CSP.
31
-
32
- See: docs/adr/0004-csp-unsafe-inline-for-theme-blocking-script.md
33
- -->
34
- <!-- Note: frame-ancestors can only be set via HTTP header, not meta tag, so it's omitted here -->
35
- <meta
36
- http-equiv="Content-Security-Policy"
37
- content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https://fonts.googleapis.com https://fonts.gstatic.com; object-src 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests"
38
- />
39
- <title>{{ page.title | default: site.title }}</title>
40
- {% seo %}
41
- <!-- Blocking script to apply theme immediately from localStorage -->
42
- <!-- This prevents FOUC by setting theme class before first paint -->
43
- <script>
44
- (function () {
45
- try {
46
- var STORAGE_KEY = 'bulma-theme-flavor';
47
- var DEFAULT_THEME = 'catppuccin-mocha';
48
- // Valid theme IDs - must match THEMES array in theme-selector.js
49
- var VALID_THEMES = [
50
- 'bulma-light',
51
- 'bulma-dark',
52
- 'catppuccin-latte',
53
- 'catppuccin-frappe',
54
- 'catppuccin-macchiato',
55
- 'catppuccin-mocha',
56
- 'dracula',
57
- 'github-light',
58
- 'github-dark',
59
- ];
60
- var savedTheme = localStorage.getItem(STORAGE_KEY) || DEFAULT_THEME;
61
- // Validate theme ID against allowed list to prevent invalid class/URL injection
62
- if (VALID_THEMES.indexOf(savedTheme) === -1) {
63
- savedTheme = DEFAULT_THEME;
64
- }
65
- var themeClass = 'theme-' + savedTheme;
66
-
67
- // Apply theme class using classList to preserve existing classes
68
- // First remove any existing theme classes
69
- var classList = document.documentElement.classList;
70
- var classesToRemove = [];
71
- for (var i = 0; i < classList.length; i++) {
72
- if (classList[i].indexOf('theme-') === 0) {
73
- classesToRemove.push(classList[i]);
74
- }
75
- }
76
- classesToRemove.forEach(function (cls) {
77
- classList.remove(cls);
78
- });
79
- // Add the new theme class
80
- classList.add(themeClass);
81
-
82
- // Preload theme CSS for faster loading
83
- var baseUrl = document.documentElement.getAttribute('data-baseurl') || '';
84
- // Sanitize baseUrl to prevent XSS attacks
85
- function sanitizeUrlPath(path) {
86
- if (!path) return '';
87
- // Remove or encode dangerous characters that could be interpreted as HTML/JS
88
- return path.replace(/[<>"'`&]/g, '').replace(/\\/g, '');
89
- }
90
- var sanitizedBaseUrl = sanitizeUrlPath(baseUrl);
91
- // Resolve path relative to site root
92
- var base = sanitizedBaseUrl
93
- ? window.location.origin + sanitizedBaseUrl + '/'
94
- : window.location.origin + '/';
95
- var themePath = new URL('assets/css/themes/' + savedTheme + '.css', base).pathname;
96
-
97
- // Create preload link
98
- var preloadLink = document.createElement('link');
99
- preloadLink.rel = 'preload';
100
- preloadLink.as = 'style';
101
- preloadLink.href = themePath;
102
- document.head.appendChild(preloadLink);
103
-
104
- // Store theme info for later use
105
- window.__INITIAL_THEME__ = savedTheme;
106
- } catch (e) {
107
- // Silently fail if localStorage is not available
108
- console.warn('Unable to load saved theme:', e);
109
- }
110
- })();
111
- </script>
112
- <link rel="icon" href="{{ '/favicon.ico' | relative_url }}" />
113
- <link
114
- rel="icon"
115
- type="image/png"
116
- href="{{ '/assets/img/bulma-logo.png' | relative_url }}"
117
- media="(prefers-color-scheme: light)"
118
- />
119
- <link
120
- rel="icon"
121
- type="image/png"
122
- href="{{ '/assets/img/bulma-logo-dark.png' | relative_url }}"
123
- media="(prefers-color-scheme: dark)"
124
- />
125
- <link
126
- rel="mask-icon"
127
- href="{{ '/assets/img/bulma-logo.png' | relative_url }}"
128
- color="#111827"
129
- />
130
- <!--
131
- Critical CSS removed - was causing theme override issues.
132
- Theme CSS uses CSS variables for colors, but hardcoded critical CSS
133
- was overriding them. The theme CSS is preloaded for fast loading.
134
- -->
135
- <!-- Base CSS (shared styles, loaded asynchronously) -->
136
- <link
137
- id="theme-base-css"
138
- rel="preload"
139
- as="style"
140
- href="{{ '/assets/css/themes/base.css' | relative_url }}"
141
- onload="this.rel = 'stylesheet'"
142
- />
143
- <noscript>
144
- <link rel="stylesheet" href="{{ '/assets/css/themes/base.css' | relative_url }}" />
145
- </noscript>
146
- <link rel="stylesheet" href="{{ '/assets/css/custom.css' | relative_url }}" />
147
- <!-- Use minified JS in production for ~50% smaller payload -->
148
- {% if jekyll.environment == 'production' %}
149
- <script type="module" src="{{ '/assets/js/theme-selector.min.js' | relative_url }}"></script>
150
- {% else %}
151
- <script type="module" src="{{ '/assets/js/theme-selector.js' | relative_url }}"></script>
152
- {% endif %}
153
- <script>
154
- document.addEventListener('DOMContentLoaded', () => {
155
- // Initialize navbar highlighting
156
- if (window.initNavbar) {
157
- window.initNavbar(document);
158
- }
159
-
160
- // Initialize burger menu toggle
161
- const burgers = document.querySelectorAll('.navbar-burger');
162
- burgers.forEach((burger) => {
163
- burger.addEventListener('click', () => {
164
- const targetId = burger.dataset.target;
165
- const target = document.getElementById(targetId);
166
- if (target) {
167
- burger.classList.toggle('is-active');
168
- target.classList.toggle('is-active');
169
- // Update aria-expanded
170
- const isExpanded = burger.classList.contains('is-active');
171
- burger.setAttribute('aria-expanded', String(isExpanded));
172
- }
173
- });
174
- });
175
- });
176
- </script>
177
- </head>
178
- <body>
179
- <nav class="navbar is-spaced" role="navigation" aria-label="main navigation">
180
- <div class="container">
181
- <div class="navbar-brand">
182
- <a class="navbar-item" href="{{ '/' | relative_url }}" data-testid="navbar-brand">
183
- {{ site.title }}
184
- </a>
185
- <button
186
- class="navbar-burger"
187
- aria-label="menu"
188
- aria-expanded="false"
189
- data-target="navbarBasicExample"
190
- type="button"
191
- data-testid="navbar-burger"
192
- style="min-width: 24px; min-height: 24px"
193
- >
194
- <span aria-hidden="true"></span>
195
- <span aria-hidden="true"></span>
196
- <span aria-hidden="true"></span>
197
- </button>
198
- </div>
199
-
200
- <div id="navbarBasicExample" class="navbar-menu">
201
- <div class="navbar-start">
202
- <a class="navbar-item" href="{{ '/' | relative_url }}" data-testid="nav-home">Home</a>
203
- <a
204
- class="navbar-item"
205
- href="{{ '/components/' | relative_url }}"
206
- data-testid="nav-components"
207
- >
208
- Components
209
- </a>
210
- <a class="navbar-item" href="{{ '/forms/' | relative_url }}" data-testid="nav-forms"
211
- >Forms</a
212
- >
213
- <div class="navbar-item has-dropdown is-hoverable">
214
- <button
215
- class="navbar-link"
216
- id="nav-reports-trigger"
217
- type="button"
218
- aria-haspopup="true"
219
- aria-expanded="false"
220
- aria-controls="nav-reports-menu"
221
- data-testid="nav-reports"
222
- >
223
- Reports
224
- </button>
225
- <div
226
- class="navbar-dropdown"
227
- id="nav-reports-menu"
228
- aria-labelledby="nav-reports-trigger"
229
- >
230
- <a
231
- class="navbar-item"
232
- href="{{ '/coverage/' | relative_url }}"
233
- target="_blank"
234
- rel="noopener noreferrer"
235
- aria-label="Coverage (opens in new tab)"
236
- data-testid="nav-reports-coverage"
237
- >
238
- Coverage
239
- <span
240
- class="icon is-small"
241
- style="margin-left: 0.25rem; display: inline-flex"
242
- aria-hidden="true"
243
- >
244
- <svg
245
- xmlns="http://www.w3.org/2000/svg"
246
- width="12"
247
- height="12"
248
- viewBox="0 0 24 24"
249
- fill="none"
250
- stroke="currentColor"
251
- stroke-width="2"
252
- stroke-linecap="round"
253
- stroke-linejoin="round"
254
- >
255
- <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
256
- <polyline points="15 3 21 3 21 9"></polyline>
257
- <line x1="10" y1="14" x2="21" y2="3"></line>
258
- </svg>
259
- </span>
260
- </a>
261
- <a
262
- class="navbar-item"
263
- href="{{ '/playwright/' | relative_url }}"
264
- target="_blank"
265
- rel="noopener noreferrer"
266
- aria-label="Playwright (opens in new tab)"
267
- data-testid="nav-reports-playwright"
268
- >
269
- Playwright
270
- <span
271
- class="icon is-small"
272
- style="margin-left: 0.25rem; display: inline-flex"
273
- aria-hidden="true"
274
- >
275
- <svg
276
- xmlns="http://www.w3.org/2000/svg"
277
- width="12"
278
- height="12"
279
- viewBox="0 0 24 24"
280
- fill="none"
281
- stroke="currentColor"
282
- stroke-width="2"
283
- stroke-linecap="round"
284
- stroke-linejoin="round"
285
- >
286
- <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
287
- <polyline points="15 3 21 3 21 9"></polyline>
288
- <line x1="10" y1="14" x2="21" y2="3"></line>
289
- </svg>
290
- </span>
291
- </a>
292
- <a
293
- class="navbar-item"
294
- href="{{ '/lighthouse/' | relative_url }}"
295
- target="_blank"
296
- rel="noopener noreferrer"
297
- aria-label="Lighthouse (opens in new tab)"
298
- data-testid="nav-reports-lighthouse"
299
- >
300
- Lighthouse
301
- <span
302
- class="icon is-small"
303
- style="margin-left: 0.25rem; display: inline-flex"
304
- aria-hidden="true"
305
- >
306
- <svg
307
- xmlns="http://www.w3.org/2000/svg"
308
- width="12"
309
- height="12"
310
- viewBox="0 0 24 24"
311
- fill="none"
312
- stroke="currentColor"
313
- stroke-width="2"
314
- stroke-linecap="round"
315
- stroke-linejoin="round"
316
- >
317
- <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
318
- <polyline points="15 3 21 3 21 9"></polyline>
319
- <line x1="10" y1="14" x2="21" y2="3"></line>
320
- </svg>
321
- </span>
322
- </a>
323
- </div>
324
- </div>
325
- </div>
326
- <div class="navbar-end">
327
- <div class="navbar-item has-dropdown is-hoverable" data-testid="theme-dropdown">
328
- <button
329
- class="theme-trigger-icon"
330
- id="theme-flavor-trigger"
331
- type="button"
332
- aria-haspopup="true"
333
- aria-expanded="false"
334
- aria-controls="theme-flavor-menu"
335
- aria-label="Select theme"
336
- data-testid="theme-trigger"
337
- >
338
- <img
339
- id="theme-flavor-trigger-icon"
340
- src="{{ '/assets/img/catppuccin-logo-macchiato.png' | relative_url }}"
341
- alt="Current theme"
342
- width="32"
343
- height="32"
344
- />
345
- </button>
346
- <div
347
- class="navbar-dropdown is-right"
348
- id="theme-flavor-menu"
349
- aria-labelledby="theme-flavor-trigger"
350
- data-testid="theme-menu"
351
- role="menu"
352
- >
353
- <!-- Theme items populated by JS -->
354
- </div>
355
- </div>
356
- <div class="select is-rounded is-small is-hidden">
357
- <select id="theme-flavor-select" aria-label="Theme flavor" disabled></select>
358
- </div>
359
- </div>
360
- </div>
361
- </div>
362
- </nav>
363
-
364
- <section class="section">
365
- <div class="container content" data-testid="main-content">
366
- <h1 class="title">{{ page.title | default: site.title }}</h1>
367
- {{ content }}
368
- </div>
369
- </section>
370
-
371
- <footer class="footer">
372
- <div class="content has-text-centered">
373
- <p><strong>{{ site.title }}</strong> — Bulma demo with theme switch.</p>
374
- </div>
375
- </footer>
376
- </body>
377
- </html>
@@ -1,62 +0,0 @@
1
- /* SPDX-License-Identifier: MIT */
2
-
3
- /* Custom tweaks and dark mode helpers */
4
-
5
- :root {
6
- color-scheme: light dark;
7
-
8
- --bulma-primary-h: 171;
9
- --bulma-primary-s: 100%;
10
- --bulma-primary-l: 41%;
11
- }
12
-
13
- /* Keep buttons readable across themes */
14
-
15
- /* Toggle buttons */
16
-
17
- .buttons.has-addons .button.is-rounded {
18
- background: transparent;
19
- border: 1px solid rgb(0 0 0 / 15%);
20
- }
21
-
22
- .buttons.has-addons .button.is-rounded[aria-pressed='true'],
23
- .buttons.has-addons .button.is-rounded.is-active {
24
- border-color: transparent;
25
- background-color: #00d1b2;
26
- color: #fff;
27
- }
28
-
29
- .buttons.has-addons .button img {
30
- width: 1rem;
31
- height: 1rem;
32
- }
33
-
34
- /* Theme switch loading indicator */
35
-
36
- .theme-flavor-trigger.is-loading {
37
- opacity: 0.7;
38
- pointer-events: none;
39
- position: relative;
40
- }
41
-
42
- .theme-flavor-trigger.is-loading::after {
43
- content: '';
44
- position: absolute;
45
- width: 16px;
46
- height: 16px;
47
- top: 50%;
48
- left: 50%;
49
- margin-left: -8px;
50
- margin-top: -8px;
51
- border: 2px solid transparent;
52
- border-top-color: currentcolor;
53
- border-radius: 50%;
54
- animation: theme-loading-spin 0.6s linear infinite;
55
- }
56
-
57
- @keyframes theme-loading-spin {
58
-
59
- to {
60
- transform: rotate(360deg);
61
- }
62
- }