jekyll-theme-zer0 1.26.0 → 1.28.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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +558 -5
  3. data/README.md +10 -27
  4. data/_data/authors.yml +4 -3
  5. data/_data/backlog.yml +28 -0
  6. data/_data/consumers.yml +147 -0
  7. data/_data/features.yml +142 -23
  8. data/_data/i18n/fr.yml +98 -0
  9. data/_data/i18n/languages.yml +36 -0
  10. data/_data/i18n/manifest.yml +1502 -0
  11. data/_data/navigation/docs.yml +2 -0
  12. data/_data/navigation/main.yml +6 -0
  13. data/_data/series.yml +19 -0
  14. data/_data/theme-manifest.yml +348 -268
  15. data/_data/ui-text.yml +36 -246
  16. data/_includes/README.md +25 -2
  17. data/_includes/analytics/google-tag-manager-body.html +11 -2
  18. data/_includes/analytics/google-tag-manager-head.html +16 -6
  19. data/_includes/analytics/posthog.html +33 -4
  20. data/_includes/components/abc-letter.html +43 -0
  21. data/_includes/components/author-avatar-url.html +4 -2
  22. data/_includes/components/book-card.html +42 -0
  23. data/_includes/components/book-nav.html +80 -0
  24. data/_includes/components/book-plate.html +31 -0
  25. data/_includes/components/book-toc.html +47 -0
  26. data/_includes/components/bookshelf.html +68 -0
  27. data/_includes/components/card-grid.html +60 -0
  28. data/_includes/components/data-card.html +95 -0
  29. data/_includes/components/env-switcher.html +3 -1
  30. data/_includes/components/halfmoon.html +5 -1
  31. data/_includes/components/language-toggle.html +81 -0
  32. data/_includes/components/page-feedback.html +65 -2
  33. data/_includes/components/search-modal.html +2 -2
  34. data/_includes/components/shortcuts-modal.html +1 -1
  35. data/_includes/components/theme-controls-bar.html +10 -2
  36. data/_includes/components/theme-customizer.html +8 -2
  37. data/_includes/components/translation-notice.html +27 -0
  38. data/_includes/content/intro.html +16 -15
  39. data/_includes/content/seo.html +9 -3
  40. data/_includes/core/color-mode-init.html +13 -4
  41. data/_includes/core/favicon.html +46 -0
  42. data/_includes/core/footer.html +9 -4
  43. data/_includes/core/head.html +18 -0
  44. data/_includes/core/header.html +9 -4
  45. data/_includes/core/hreflang.html +33 -0
  46. data/_includes/core/i18n.html +36 -0
  47. data/_includes/custom/body-end.html +18 -0
  48. data/_includes/custom/body-start.html +17 -0
  49. data/_includes/custom/footer.html +18 -0
  50. data/_includes/custom/head.html +18 -0
  51. data/_includes/navigation/breadcrumbs.html +1 -1
  52. data/_includes/navigation/local-graph.html +28 -2
  53. data/_includes/navigation/nav-tree.html +3 -3
  54. data/_includes/navigation/navbar.html +5 -4
  55. data/_includes/navigation/sidebar-config.html +21 -0
  56. data/_includes/navigation/sidebar-nav.html +4 -0
  57. data/_includes/navigation/sidebar-pagetree.html +150 -0
  58. data/_includes/navigation/sidebar-right.html +3 -2
  59. data/_includes/navigation/unified-drawer.html +9 -3
  60. data/_includes/obsidian/full-graph.html +165 -136
  61. data/_layouts/404.html +260 -0
  62. data/_layouts/README.md +2 -0
  63. data/_layouts/article.html +7 -1
  64. data/_layouts/book-abc.html +106 -0
  65. data/_layouts/book-story.html +91 -0
  66. data/_layouts/book.html +113 -0
  67. data/_layouts/collection.html +17 -5
  68. data/_layouts/default.html +5 -3
  69. data/_layouts/home.html +5 -2
  70. data/_layouts/landing.html +9 -0
  71. data/_layouts/news.html +159 -33
  72. data/_layouts/root.html +29 -9
  73. data/_layouts/section.html +61 -17
  74. data/_sass/components/_book.scss +423 -0
  75. data/_sass/core/_navbar.scss +13 -0
  76. data/_sass/core/_obsidian.scss +295 -7
  77. data/_sass/layouts/_navbar-extras.scss +6 -1
  78. data/_sass/theme/_backgrounds.scss +21 -8
  79. data/assets/css/main.scss +1 -0
  80. data/assets/js/auto-hide-nav.js +5 -1
  81. data/assets/js/halfmoon.js +26 -0
  82. data/assets/js/obsidian-graph.js +707 -265
  83. data/assets/js/obsidian-local-graph.js +161 -54
  84. data/assets/js/search-modal.js +4 -1
  85. data/scripts/README.md +20 -26
  86. data/scripts/bin/audit-consumer +1 -1
  87. data/scripts/bin/manifest +31 -5
  88. data/scripts/bin/sync-plugins +0 -1
  89. data/scripts/bin/validate +5 -1
  90. data/scripts/dev/rasterize-svg.js +65 -0
  91. data/scripts/features/generate-preview-images +49 -1390
  92. data/scripts/features/install-preview-generator +55 -33
  93. data/scripts/install/README.md +55 -25
  94. data/scripts/install/ai/client.sh +302 -93
  95. data/scripts/install/ai/prompts/spec.schema.json +1 -1
  96. data/scripts/install/ai/prompts/wizard.system.md +8 -17
  97. data/scripts/install/ai/wizard.sh +10 -5
  98. data/scripts/install/apply.sh +7 -3
  99. data/scripts/install/cli.sh +54 -4
  100. data/scripts/install/config.sh +167 -0
  101. data/scripts/install/doctor.sh +38 -0
  102. data/scripts/install/plan.sh +10 -0
  103. data/scripts/install/spec.sh +15 -7
  104. data/scripts/install/template.sh +4 -0
  105. data/scripts/lib/README.md +1 -5
  106. data/scripts/lib/install/deploy/README.md +3 -9
  107. data/scripts/lib/preview_generator.py +2261 -1341
  108. data/scripts/propagate.rb +277 -0
  109. data/scripts/translate.rb +1204 -0
  110. metadata +34 -3
  111. data/_plugins/preview_image_generator.rb +0 -351
@@ -1,3 +1,4 @@
1
+ // Feature: ZER0-045
1
2
  // ============================================================================
2
3
  // _obsidian.scss — Styles for Obsidian-flavoured Markdown features.
3
4
  // Imported from _sass/custom.scss.
@@ -8,6 +9,9 @@
8
9
  // - Note transclusion / image embeds
9
10
  // - Callout cards beyond what Bootstrap's `.alert` provides
10
11
  // - Backlinks panel
12
+ // - Full graph page (canvas shell, Obsidian-style controls, zoom bar,
13
+ // hover preview) — markup in _includes/obsidian/full-graph.html
14
+ // - Local graph side panel (FAB + offcanvas)
11
15
  // ============================================================================
12
16
 
13
17
  // --- Wiki-links ------------------------------------------------------------
@@ -196,23 +200,307 @@
196
200
  }
197
201
  }
198
202
 
203
+ // ----------------------------------------------------------------------------
204
+ // Full graph page — Obsidian-style site knowledge graph.
205
+ // Markup: _includes/obsidian/full-graph.html (included by
206
+ // pages/_docs/obsidian/graph.md); renderer: assets/js/obsidian-graph.js.
207
+ // The shell is the positioning context for the floating controls card, the
208
+ // zoom bar, and the hover preview card that overlay the cytoscape canvas.
209
+ // ----------------------------------------------------------------------------
210
+ .obsidian-graph-shell {
211
+ position: relative;
212
+
213
+ #obsidian-graph {
214
+ width: 100%;
215
+ height: 82vh;
216
+ min-height: 620px;
217
+ border: 1px solid rgba(var(--bs-border-color-rgb, 222, 226, 230), 0.55);
218
+ border-radius: var(--bs-border-radius-lg, 0.5rem);
219
+ background: rgba(var(--bs-body-bg-rgb, 255, 255, 255), 0.45);
220
+ -webkit-backdrop-filter: blur(14px) saturate(1.15);
221
+ backdrop-filter: blur(14px) saturate(1.15);
222
+ box-shadow:
223
+ 0 1px 2px rgba(0, 0, 0, 0.03),
224
+ 0 8px 24px rgba(0, 0, 0, 0.06),
225
+ inset 0 1px 0 rgba(255, 255, 255, 0.04);
226
+ position: relative;
227
+ overflow: hidden;
228
+ }
229
+
230
+ [data-bs-theme="dark"] & #obsidian-graph {
231
+ background: rgba(var(--bs-body-bg-rgb, 33, 37, 41), 0.38);
232
+ box-shadow:
233
+ 0 1px 2px rgba(0, 0, 0, 0.12),
234
+ 0 8px 28px rgba(0, 0, 0, 0.22),
235
+ inset 0 1px 0 rgba(255, 255, 255, 0.03);
236
+ }
237
+ }
238
+
239
+ // CSS-overlay fallback when the Fullscreen API is unavailable/blocked, plus
240
+ // the native :fullscreen state — both fill the viewport with a solid ground.
241
+ .obsidian-graph-shell-expanded {
242
+ position: fixed;
243
+ inset: 0;
244
+ z-index: var(--zer0-layer-offcanvas, 1045);
245
+ background: var(--bs-body-bg, #fff);
246
+ padding: 0.75rem;
247
+ }
248
+
249
+ .obsidian-graph-shell-expanded #obsidian-graph,
250
+ .obsidian-graph-shell:fullscreen #obsidian-graph {
251
+ height: 100%;
252
+ min-height: 0;
253
+ }
254
+
255
+ .obsidian-graph-shell:fullscreen {
256
+ background: var(--bs-body-bg, #fff);
257
+ padding: 0.75rem;
258
+ }
259
+
260
+ // --- Floating "Graph settings" card (Filters / Display / Forces) ------------
261
+ .obsidian-graph-controls {
262
+ position: absolute;
263
+ top: 0.75rem;
264
+ right: 0.75rem;
265
+ z-index: 20;
266
+ width: min(17.5rem, calc(100% - 1.5rem));
267
+ max-height: calc(100% - 1.5rem);
268
+ display: flex;
269
+ flex-direction: column;
270
+ background: rgba(var(--bs-body-bg-rgb, 255, 255, 255), 0.86);
271
+ -webkit-backdrop-filter: blur(10px) saturate(1.1);
272
+ backdrop-filter: blur(10px) saturate(1.1);
273
+ border: 1px solid var(--bs-border-color, #dee2e6);
274
+ border-radius: var(--bs-border-radius, 0.375rem);
275
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
276
+ overflow: hidden;
277
+
278
+ .obsidian-graph-controls-toggle {
279
+ display: flex;
280
+ align-items: center;
281
+ width: 100%;
282
+ text-align: left;
283
+ font-weight: 600;
284
+ color: var(--bs-body-color, #212529);
285
+ border: 0;
286
+ border-radius: 0;
287
+
288
+ &:focus-visible {
289
+ outline: 2px solid var(--bs-primary);
290
+ outline-offset: -2px;
291
+ }
292
+ }
293
+
294
+ .obsidian-graph-controls-chevron {
295
+ transition: transform 0.2s ease;
296
+ }
297
+
298
+ .obsidian-graph-controls-toggle[aria-expanded="true"] .obsidian-graph-controls-chevron {
299
+ transform: rotate(180deg);
300
+ }
301
+
302
+ .obsidian-graph-controls-body {
303
+ overflow-y: auto;
304
+ border-top: 1px solid var(--bs-border-color, #dee2e6);
305
+ }
306
+
307
+ .accordion {
308
+ --bs-accordion-bg: transparent;
309
+ font-size: 0.875rem;
310
+ }
311
+
312
+ .accordion-button {
313
+ font-size: 0.875rem;
314
+ font-weight: 600;
315
+ }
316
+
317
+ .form-label {
318
+ margin-bottom: 0.125rem;
319
+ color: var(--bs-secondary-color, #6c757d);
320
+ }
321
+
322
+ .form-range {
323
+ margin-bottom: 0.375rem;
324
+ }
325
+ }
326
+
327
+ // Collection toggles double as the legend: color swatch + name + page count.
328
+ .obsidian-graph-collections {
329
+ max-height: 12.5rem;
330
+ overflow-y: auto;
331
+
332
+ .obsidian-graph-collection-filter {
333
+ margin-bottom: 0.125rem;
334
+
335
+ .form-check-label {
336
+ width: 100%;
337
+ cursor: pointer;
338
+ }
339
+
340
+ .swatch {
341
+ display: inline-block;
342
+ flex: 0 0 auto;
343
+ width: 0.7rem;
344
+ height: 0.7rem;
345
+ border-radius: 50%;
346
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12);
347
+ }
348
+
349
+ .badge {
350
+ font-size: 0.7rem;
351
+ }
352
+ }
353
+ }
354
+
355
+ // --- Zoom / fit / fullscreen button stack -----------------------------------
356
+ .obsidian-graph-zoombar {
357
+ position: absolute;
358
+ right: 0.75rem;
359
+ bottom: 0.75rem;
360
+ z-index: 20;
361
+ border-radius: var(--bs-border-radius, 0.375rem);
362
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
363
+ overflow: hidden;
364
+
365
+ > .btn {
366
+ background: rgba(var(--bs-body-bg-rgb, 255, 255, 255), 0.88);
367
+ color: var(--bs-body-color, #212529);
368
+ border: 1px solid var(--bs-border-color, #dee2e6);
369
+ line-height: 1;
370
+
371
+ &:hover,
372
+ &:focus-visible {
373
+ background: var(--bs-tertiary-bg, #f8f9fa);
374
+ }
375
+
376
+ &[aria-pressed="true"] {
377
+ background: rgba(var(--bs-primary-rgb), 0.12);
378
+ color: var(--bs-primary);
379
+ }
380
+ }
381
+ }
382
+
383
+ // --- Hover preview card (Obsidian-style page preview) -----------------------
384
+ .obsidian-graph-preview {
385
+ position: absolute;
386
+ z-index: 30;
387
+ width: min(17rem, calc(100% - 1.5rem));
388
+ pointer-events: none;
389
+ background: rgba(var(--bs-body-bg-rgb, 255, 255, 255), 0.94);
390
+ -webkit-backdrop-filter: blur(8px);
391
+ backdrop-filter: blur(8px);
392
+ border: 1px solid var(--bs-border-color, #dee2e6);
393
+
394
+ &[hidden] {
395
+ display: none;
396
+ }
397
+
398
+ .obsidian-graph-preview-swatch {
399
+ display: inline-block;
400
+ flex: 0 0 auto;
401
+ width: 0.65rem;
402
+ height: 0.65rem;
403
+ border-radius: 50%;
404
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12);
405
+ }
406
+
407
+ .obsidian-graph-preview-excerpt {
408
+ display: -webkit-box;
409
+ -webkit-line-clamp: 3;
410
+ -webkit-box-orient: vertical;
411
+ overflow: hidden;
412
+ color: var(--bs-secondary-color, #6c757d);
413
+ }
414
+ }
415
+
416
+ // --- Stats badges, status line, tips box ------------------------------------
417
+ #obsidian-graph-stats {
418
+ display: flex;
419
+ flex-wrap: wrap;
420
+ gap: 0.375rem;
421
+
422
+ .badge {
423
+ font-size: 0.8125rem;
424
+ font-weight: 500;
425
+ padding: 0.35rem 0.6rem;
426
+ }
427
+ }
428
+
429
+ .obsidian-graph-status {
430
+ font-size: 0.8125rem;
431
+ color: var(--bs-secondary-color, #6c757d);
432
+ min-height: 1.25rem;
433
+ margin: 0.5rem 0 0;
434
+ }
435
+
436
+ .obsidian-graph-tips {
437
+ margin-top: 0.75rem;
438
+ padding: 0.625rem 0.75rem;
439
+ font-size: 0.8125rem;
440
+ color: var(--bs-secondary-color, #6c757d);
441
+ background: var(--bs-tertiary-bg, #f8f9fa);
442
+ border-left: 3px solid var(--bs-primary, #0d6efd);
443
+ border-radius: 0.25rem;
444
+
445
+ strong {
446
+ color: var(--bs-body-color, #212529);
447
+ }
448
+ }
449
+
450
+ // Small screens: the floating card would cover most of the canvas — dock the
451
+ // controls above the graph instead, and shorten the canvas.
452
+ @media (max-width: 767.98px) {
453
+ .obsidian-graph-shell {
454
+ display: flex;
455
+ flex-direction: column;
456
+
457
+ #obsidian-graph {
458
+ height: 62vh;
459
+ min-height: 420px;
460
+ }
461
+ }
462
+
463
+ .obsidian-graph-controls {
464
+ position: static;
465
+ order: -1;
466
+ width: 100%;
467
+ max-height: none;
468
+ margin-bottom: 0.5rem;
469
+ backdrop-filter: none;
470
+ -webkit-backdrop-filter: none;
471
+ background: var(--bs-body-bg, #fff);
472
+
473
+ .obsidian-graph-controls-body {
474
+ max-height: 45vh;
475
+ }
476
+ }
477
+ }
478
+
199
479
  // ----------------------------------------------------------------------------
200
480
  // Local graph side panel — Obsidian-style local graph view.
201
481
  // Rendered by _includes/navigation/local-graph.html (body-level, after footer)
202
482
  // and _includes/navigation/local-graph-fab.html (footer) via
203
483
  // assets/js/obsidian-local-graph.js.
204
484
  // ----------------------------------------------------------------------------
205
- // ID selector (not the class) is REQUIRED for the fixed position to stick:
206
- // the FAB is a direct child of `.zer0-bg-body`, which matches the elevation
207
- // rule `.zer0-bg-body > *:not(.fixed-top):not(.position-fixed):not(.offcanvas):not(.modal)`
208
- // in _sass/theme/_backgrounds.scss (specificity 0,5,0) and forces
209
- // `position: relative; z-index: 1` — dropping the FAB into page flow at the
210
- // bottom. An id (1,0,0) wins, mirroring `#aiChatToggle`. See ai-chat.html.
485
+ // ID selector kept for historical safety: `.zer0-bg-body` used to force
486
+ // `position: relative; z-index: 1` on every direct child (specificity 0,5,0),
487
+ // which beat the class rule and dropped the FAB into page flow. That elevation
488
+ // rule is gone (the body now isolates with the noise layer at z-index: -1 —
489
+ // see _sass/theme/_backgrounds.scss), but the id keeps the FAB immune to any
490
+ // future generic child rule. Mirrors `#aiChatToggle`. See ai-chat.html.
491
+ //
492
+ // Slot 2 on the left-side FAB stack: sits above the page-feedback FAB
493
+ // (slot 1 = var(--zer0-space-fab-offset)) — same stacking formula as
494
+ // .bd-toc-fab above #backToTopBtn on the right side.
211
495
  #obsidianLocalGraphFab.obsidian-local-graph-fab {
212
496
  position: fixed;
213
497
  left: var(--zer0-space-fab-offset, 1rem);
214
498
  right: auto;
215
- bottom: var(--zer0-space-fab-offset, 1rem);
499
+ bottom: calc(
500
+ var(--zer0-space-fab-offset, 1rem) +
501
+ var(--zer0-space-fab-size, 3.5rem) +
502
+ var(--zer0-space-fab-gap, 0.75rem)
503
+ );
216
504
  z-index: var(--zer0-layer-fab-local-graph);
217
505
 
218
506
  &[hidden] {
@@ -80,7 +80,12 @@
80
80
  &.bd-sidebar-fab--restore,
81
81
  html.bd-sidebar-pref-hidden &.bd-sidebar-fab {
82
82
  display: block !important;
83
- bottom: var(--zer0-space-fab-offset, 1rem);
83
+ // Stay at slot 2 (above the feedback FAB at slot 1) so neither overlaps.
84
+ bottom: calc(
85
+ var(--zer0-space-fab-offset, 1rem) +
86
+ var(--zer0-space-fab-size, 3.5rem) +
87
+ var(--zer0-space-fab-gap, 0.75rem)
88
+ );
84
89
  }
85
90
 
86
91
  @media (min-width: 992px) {
@@ -107,14 +107,27 @@
107
107
  .zer0-bg-body {
108
108
  @include zer0-bg-noise;
109
109
 
110
- // Elevate children above the ::before / ::after pseudo-element backgrounds,
111
- // but exclude fixed-position, offcanvas, and modal elements whose
112
- // positioning must not be overridden. `.position-fixed` matters as much as
113
- // `.fixed-top`: without it this rule (specificity 0,4,0) flattens the
114
- // cookie-consent banner and the TOC FAB to z-index 1, letting the chat and
115
- // local-graph FABs paint over — and steal taps from — the banner's buttons
116
- // on mobile (their `--zer0-layer-*` tokens exist precisely to prevent this).
117
- > *:not(.fixed-top):not(.position-fixed):not(.offcanvas):not(.modal) { position: relative; z-index: 1; }
110
+ // The body forms its own stacking context (isolation) and the noise overlay
111
+ // is pushed into the NEGATIVE layer, so in-flow content naturally paints
112
+ // above the texture with no per-child elevation at all.
113
+ //
114
+ // History: this block used to elevate every direct child instead
115
+ // (`> *:not(.fixed-top):not(.position-fixed):not(.offcanvas):not(.modal)
116
+ // { position: relative; z-index: 1; }`). That rule (specificity 0,5,0)
117
+ // caused three consumer-reported mobile bugs:
118
+ // 1. it beat FAB class rules' `position: fixed` (0,1,0), dropping FABs
119
+ // into page flow and adding 16px of horizontal scroll;
120
+ // 2. it made <main> a z-index:1 stacking context, trapping the nested
121
+ // #bdSidebar offcanvas (z-index 1045) below the fixed header (1030) —
122
+ // its close button was unreachable on mobile;
123
+ // 3. every fixed/overlay descendant needed a bespoke carve-out or an
124
+ // id-selector specificity escape (see _sass/core/_obsidian.scss).
125
+ // Do NOT reintroduce a child z-index elevation here.
126
+ isolation: isolate;
127
+
128
+ &::after {
129
+ z-index: -1;
130
+ }
118
131
  }
119
132
 
120
133
  .zer0-bg-surface {
data/assets/css/main.scss CHANGED
@@ -40,6 +40,7 @@
40
40
  @import "components/content-tables";
41
41
  @import "components/search-modal";
42
42
  @import "components/page-feedback";
43
+ @import "components/book";
43
44
 
44
45
  // 6. Layout partials
45
46
  @import "layouts/landing";
@@ -30,10 +30,14 @@
30
30
  // Check for reduced motion preference
31
31
  const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
32
32
 
33
- // Calculate and set body padding to prevent content jump
33
+ // Calculate and set body padding to prevent content jump.
34
+ // The measured height is also published as --zer0-header-height so
35
+ // stylesheets (theme or consumer) can align overlays with the real
36
+ // header instead of hard-coding its pixel height.
34
37
  function updateBodyPadding() {
35
38
  const navbarHeight = navbar.offsetHeight;
36
39
  document.body.style.paddingTop = navbarHeight + 'px';
40
+ document.documentElement.style.setProperty('--zer0-header-height', navbarHeight + 'px');
37
41
  }
38
42
 
39
43
  // Initial padding setup
@@ -7,15 +7,35 @@
7
7
  (() => {
8
8
  'use strict'
9
9
 
10
+ // Site config surfaced by root.html as <html> data attributes:
11
+ // - data-color-mode-lock: "true" pins the configured mode; visitor
12
+ // preference and OS scheme are ignored and the toggles are inert
13
+ // (core/color-mode-init.html applies the same rules pre-paint).
14
+ // - data-color-mode-default: the site's configured starting mode. It
15
+ // must rank ABOVE the OS preference here — otherwise this script
16
+ // overrides the init script's choice on first visit and the page
17
+ // flips modes after load.
18
+ const rootEl = document.documentElement
19
+ const isLocked = rootEl.getAttribute('data-color-mode-lock') === 'true'
20
+ const configDefault = rootEl.getAttribute('data-color-mode-default') || 'auto'
21
+
10
22
  const getStoredTheme = () => localStorage.getItem('theme')
11
23
  const setStoredTheme = theme => localStorage.setItem('theme', theme)
12
24
 
13
25
  const getPreferredTheme = () => {
26
+ if (isLocked) {
27
+ return configDefault === 'auto' ? 'dark' : configDefault
28
+ }
29
+
14
30
  const storedTheme = getStoredTheme()
15
31
  if (storedTheme) {
16
32
  return storedTheme
17
33
  }
18
34
 
35
+ if (configDefault !== 'auto') {
36
+ return configDefault
37
+ }
38
+
19
39
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
20
40
  }
21
41
 
@@ -65,6 +85,9 @@
65
85
  }
66
86
 
67
87
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
88
+ if (isLocked) {
89
+ return
90
+ }
68
91
  const storedTheme = getStoredTheme()
69
92
  if (storedTheme !== 'light' && storedTheme !== 'dark') {
70
93
  setTheme(getPreferredTheme())
@@ -77,6 +100,9 @@
77
100
  document.querySelectorAll('[data-bs-theme-value]')
78
101
  .forEach(toggle => {
79
102
  toggle.addEventListener('click', () => {
103
+ if (isLocked) {
104
+ return
105
+ }
80
106
  const theme = toggle.getAttribute('data-bs-theme-value')
81
107
  setStoredTheme(theme)
82
108
  setTheme(theme)