datalog-theme 0.6.1 → 0.8.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 (147) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +175 -0
  3. data/CITATION.cff +2 -2
  4. data/README.md +26 -19
  5. data/_data/cdn-integrity.yml +0 -30
  6. data/_includes/analytics/dashboard.html +3 -1
  7. data/_includes/components/api-function.html +20 -1
  8. data/_includes/components/author-bio.html +2 -2
  9. data/_includes/components/enhanced-code-block.html +1 -1
  10. data/_includes/components/hero.html +19 -1
  11. data/_includes/csp-meta.html +115 -11
  12. data/_includes/footer.html +19 -20
  13. data/_includes/head.html +61 -29
  14. data/_includes/header/navigation.html +12 -15
  15. data/_includes/header.html +29 -20
  16. data/_includes/layouts/default/article.html +5 -1
  17. data/_includes/meta/math-config.html +27 -1
  18. data/_includes/meta/schema.html +5 -2
  19. data/_includes/meta/scripts-loader.html +16 -31
  20. data/_includes/post/related-posts.html +4 -7
  21. data/_includes/scripts.html +1 -9
  22. data/_includes/search/index-data.json +131 -0
  23. data/_includes/search/page.html +136 -0
  24. data/_layouts/dataset.html +1 -0
  25. data/_layouts/default.html +16 -8
  26. data/_layouts/home.html +6 -0
  27. data/_layouts/notebook.html +1 -0
  28. data/_layouts/package.html +1 -0
  29. data/_layouts/portfolio.html +1 -0
  30. data/_layouts/post.html +20 -5
  31. data/_layouts/project.html +2 -1
  32. data/_plugins/analytics_dashboard.rb +9 -3
  33. data/_plugins/config_validator.rb +12 -7
  34. data/_plugins/csp_generator.rb +18 -28
  35. data/_plugins/datalog_bibliography.rb +9 -7
  36. data/_plugins/datalog_comments.rb +8 -5
  37. data/_plugins/datalog_slides.rb +9 -8
  38. data/_plugins/i18n.rb +7 -8
  39. data/_plugins/image_optimizer.rb +17 -6
  40. data/_plugins/math_preprocessor.rb +36 -3
  41. data/_plugins/notebook_converter.rb +23 -43
  42. data/_plugins/plugin_loader.rb +3 -1
  43. data/_plugins/publications_generator.rb +8 -2
  44. data/_plugins/rouge_highlight_filter.rb +42 -0
  45. data/_plugins/search_code_blocks.rb +30 -0
  46. data/_plugins/search_normalizer.rb +15 -53
  47. data/_plugins/search_pages.rb +72 -0
  48. data/_sass/_academic-dashboard.scss +262 -0
  49. data/_sass/_base.scss +19 -1
  50. data/_sass/_components.scss +77 -1144
  51. data/_sass/_features.scss +17 -0
  52. data/_sass/_font-fallbacks.scss +43 -0
  53. data/_sass/_header.scss +10 -0
  54. data/_sass/_layout.scss +18 -12
  55. data/_sass/_mathematical.scss +1 -1
  56. data/_sass/_notebooks.scss +322 -0
  57. data/_sass/_open-science-badges.scss +56 -0
  58. data/_sass/{_phase1-enhancements.scss → _post-components.scss} +46 -1
  59. data/_sass/_search-page.scss +530 -0
  60. data/_sass/_search.scss +47 -1
  61. data/_sass/_syntax-highlighting.scss +212 -97
  62. data/_sass/_theme.scss +42 -18
  63. data/_sass/_typography.scss +14 -0
  64. data/_sass/_variables.scss +7 -4
  65. data/assets/css/main.scss +14 -0
  66. data/assets/img/hero-detail-640.webp +0 -0
  67. data/assets/img/hero-detail.webp +0 -0
  68. data/assets/img/social-card.png +0 -0
  69. data/assets/js/dist/academic.js +1 -0
  70. data/assets/js/dist/analytics-dashboard.js +1 -0
  71. data/assets/js/dist/chunks/chunk-225H5YXE.js +1 -0
  72. data/assets/js/dist/core.js +1 -0
  73. data/assets/js/dist/loader.js +1 -0
  74. data/assets/js/dist/math.js +1 -0
  75. data/assets/js/dist/notebook.js +1 -0
  76. data/assets/js/dist/search.js +1 -0
  77. data/assets/js/dist/visualizations.js +11 -0
  78. data/assets/js/loader.js +3 -1
  79. data/datalog-theme.gemspec +43 -22
  80. data/lib/datalog/cli.rb +53 -15
  81. data/lib/datalog/plugin_system/dependency_resolver.rb +0 -2
  82. data/lib/datalog/theme/package.rb +57 -0
  83. data/lib/datalog/theme/repository_checkout.rb +90 -0
  84. data/lib/datalog/theme/version.rb +1 -1
  85. data/lib/datalog/warning_filter.rb +5 -11
  86. data/lib/datalog-theme.rb +21 -0
  87. metadata +75 -146
  88. data/_data/academic.yml +0 -217
  89. data/_data/config/author.yml +0 -121
  90. data/_data/config/features.yml +0 -262
  91. data/_data/config/site.yml +0 -42
  92. data/_data/config/theme.yml +0 -181
  93. data/_data/datasets.yml +0 -28
  94. data/_data/js_meta.json +0 -371
  95. data/_data/navigation.yml +0 -145
  96. data/_data/projects.yml +0 -41
  97. data/_data/publications.yml +0 -28
  98. data/_data/social.yml +0 -73
  99. data/_data/visualizations.yml +0 -51
  100. data/_includes/components/advanced-search.html +0 -682
  101. data/_includes/components/bookmark-system.html +0 -96
  102. data/_includes/components/comments.html +0 -244
  103. data/_includes/components/content-recommendations.html +0 -228
  104. data/_includes/components/email-preferences.html +0 -200
  105. data/_includes/components/enhanced-metadata.html +0 -228
  106. data/_includes/components/language-switcher.html +0 -396
  107. data/_includes/components/navigation-enhancements.html +0 -454
  108. data/_includes/components/newsletter-signup.html +0 -178
  109. data/_includes/components/popular-posts.html +0 -233
  110. data/_includes/components/reading-progress.html +0 -133
  111. data/_includes/components/reading-time.html +0 -121
  112. data/_includes/components/series-navigation.html +0 -124
  113. data/_includes/components/social-proof.html +0 -34
  114. data/_includes/components/user-preferences.html +0 -566
  115. data/_layouts/archive.html +0 -282
  116. data/_layouts/post-sidebar.html +0 -183
  117. data/_sass/_phase3-enhancements.scss +0 -874
  118. data/_sass/_phase4-enhancements.scss +0 -1214
  119. data/_sass/_phase5-enhancements.scss +0 -414
  120. data/assets/img/20220607123041_detail.001.png +0 -0
  121. data/assets/js/academic.js +0 -262
  122. data/assets/js/analytics-dashboard.js +0 -382
  123. data/assets/js/core/dark-mode.js +0 -79
  124. data/assets/js/core/github-cards.js +0 -123
  125. data/assets/js/core/language-filter.js +0 -69
  126. data/assets/js/core/navigation.js +0 -184
  127. data/assets/js/core/scroll-progress.js +0 -45
  128. data/assets/js/core/search-hotkeys.js +0 -62
  129. data/assets/js/core/skip-links.js +0 -62
  130. data/assets/js/main.js +0 -23
  131. data/assets/js/math.js +0 -818
  132. data/assets/js/notebook.js +0 -158
  133. data/assets/js/search/analytics.js +0 -91
  134. data/assets/js/search/app.js +0 -271
  135. data/assets/js/search/autocomplete.js +0 -120
  136. data/assets/js/search/engine.js +0 -260
  137. data/assets/js/search/filters.js +0 -38
  138. data/assets/js/search/render.js +0 -217
  139. data/assets/js/search/utils.js +0 -99
  140. data/assets/js/search.js +0 -354
  141. data/assets/js/visualizations.js +0 -816
  142. data/assets/publications/datalog-publications.bib +0 -8
  143. data/assets/publications/datalog-publications.ris +0 -9
  144. data/assets/publications/publications.bib +0 -30
  145. data/assets/templates/diogo-ribeiro-cv.md +0 -31
  146. data/assets/templates/diogo-ribeiro-cv.tex +0 -32
  147. data/lib/datalog/theme/theme.rb +0 -18
@@ -1,566 +0,0 @@
1
- {%- comment -%}
2
- User Preferences Panel Component
3
- Provides customizable reading experience controls
4
-
5
- Parameters:
6
- - show_theme_toggle: Show dark/light theme toggle (default: true)
7
- - show_font_size: Show font size controls (default: true)
8
- - show_reading_mode: Show reading mode toggle (default: true)
9
- - show_layout: Show layout preferences (default: true)
10
- - position: Panel position (floating, sidebar) (default: floating)
11
- {%- endcomment -%}
12
-
13
- {%- assign show_theme_toggle = include.show_theme_toggle | default: true -%}
14
- {%- assign show_font_size = include.show_font_size | default: true -%}
15
- {%- assign show_reading_mode = include.show_reading_mode | default: true -%}
16
- {%- assign show_layout = include.show_layout | default: true -%}
17
- {%- assign position = include.position | default: "floating" -%}
18
-
19
- <div class="user-preferences user-preferences--{{ position }}"
20
- data-user-preferences
21
- aria-label="User preferences">
22
-
23
- <button type="button"
24
- class="user-preferences__trigger"
25
- data-preferences-trigger
26
- aria-label="Open preferences"
27
- aria-expanded="false"
28
- aria-controls="preferences-panel">
29
- <svg xmlns="http://www.w3.org/2000/svg"
30
- width="24"
31
- height="24"
32
- viewBox="0 0 24 24"
33
- fill="none"
34
- stroke="currentColor"
35
- stroke-width="2"
36
- stroke-linecap="round"
37
- stroke-linejoin="round"
38
- aria-hidden="true">
39
- <circle cx="12" cy="12" r="3"></circle>
40
- <path d="M12 1v6m0 6v6m7.071-13.071l-4.243 4.243m0 5.656l4.243 4.243M1 12h6m6 0h6m-13.071 7.071l4.243-4.243m5.656 0l4.243 4.243"></path>
41
- </svg>
42
- <span class="user-preferences__trigger-text">Preferences</span>
43
- </button>
44
-
45
- <div class="user-preferences__panel"
46
- id="preferences-panel"
47
- data-preferences-panel
48
- role="dialog"
49
- aria-modal="true"
50
- aria-labelledby="preferences-title"
51
- hidden>
52
-
53
- <div class="user-preferences__header">
54
- <h3 class="user-preferences__title" id="preferences-title">
55
- <svg xmlns="http://www.w3.org/2000/svg"
56
- width="20"
57
- height="20"
58
- viewBox="0 0 24 24"
59
- fill="none"
60
- stroke="currentColor"
61
- stroke-width="2"
62
- aria-hidden="true">
63
- <circle cx="12" cy="12" r="3"></circle>
64
- <path d="M12 1v6m0 6v6m7.071-13.071l-4.243 4.243m0 5.656l4.243 4.243M1 12h6m6 0h6m-13.071 7.071l4.243-4.243m5.656 0l4.243 4.243"></path>
65
- </svg>
66
- Customize Your Experience
67
- </h3>
68
-
69
- <button type="button"
70
- class="user-preferences__close"
71
- data-preferences-close
72
- aria-label="Close preferences">
73
- <svg xmlns="http://www.w3.org/2000/svg"
74
- width="20"
75
- height="20"
76
- viewBox="0 0 24 24"
77
- fill="none"
78
- stroke="currentColor"
79
- stroke-width="2"
80
- aria-hidden="true">
81
- <line x1="18" y1="6" x2="6" y2="18"></line>
82
- <line x1="6" y1="6" x2="18" y2="18"></line>
83
- </svg>
84
- </button>
85
- </div>
86
-
87
- <div class="user-preferences__content">
88
-
89
- {%- if show_theme_toggle -%}
90
- <div class="preference-section">
91
- <h4 class="preference-section__title">
92
- <svg xmlns="http://www.w3.org/2000/svg"
93
- width="18"
94
- height="18"
95
- viewBox="0 0 24 24"
96
- fill="none"
97
- stroke="currentColor"
98
- stroke-width="2"
99
- aria-hidden="true">
100
- <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
101
- </svg>
102
- Theme
103
- </h4>
104
-
105
- <div class="preference-section__control">
106
- <div class="theme-toggle" data-theme-control>
107
- <button type="button"
108
- class="theme-toggle__option"
109
- data-theme="light"
110
- aria-label="Light theme">
111
- <svg xmlns="http://www.w3.org/2000/svg"
112
- width="20"
113
- height="20"
114
- viewBox="0 0 24 24"
115
- fill="none"
116
- stroke="currentColor"
117
- stroke-width="2">
118
- <circle cx="12" cy="12" r="5"></circle>
119
- <line x1="12" y1="1" x2="12" y2="3"></line>
120
- <line x1="12" y1="21" x2="12" y2="23"></line>
121
- <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
122
- <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
123
- <line x1="1" y1="12" x2="3" y2="12"></line>
124
- <line x1="21" y1="12" x2="23" y2="12"></line>
125
- <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
126
- <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
127
- </svg>
128
- <span>Light</span>
129
- </button>
130
-
131
- <button type="button"
132
- class="theme-toggle__option"
133
- data-theme="dark"
134
- aria-label="Dark theme">
135
- <svg xmlns="http://www.w3.org/2000/svg"
136
- width="20"
137
- height="20"
138
- viewBox="0 0 24 24"
139
- fill="none"
140
- stroke="currentColor"
141
- stroke-width="2">
142
- <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
143
- </svg>
144
- <span>Dark</span>
145
- </button>
146
-
147
- <button type="button"
148
- class="theme-toggle__option"
149
- data-theme="auto"
150
- aria-label="Auto theme (system)">
151
- <svg xmlns="http://www.w3.org/2000/svg"
152
- width="20"
153
- height="20"
154
- viewBox="0 0 24 24"
155
- fill="none"
156
- stroke="currentColor"
157
- stroke-width="2">
158
- <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
159
- <line x1="8" y1="21" x2="16" y2="21"></line>
160
- <line x1="12" y1="17" x2="12" y2="21"></line>
161
- </svg>
162
- <span>Auto</span>
163
- </button>
164
- </div>
165
- </div>
166
- </div>
167
- {%- endif -%}
168
-
169
- {%- if show_font_size -%}
170
- <div class="preference-section">
171
- <h4 class="preference-section__title">
172
- <svg xmlns="http://www.w3.org/2000/svg"
173
- width="18"
174
- height="18"
175
- viewBox="0 0 24 24"
176
- fill="none"
177
- stroke="currentColor"
178
- stroke-width="2"
179
- aria-hidden="true">
180
- <polyline points="4 7 4 4 20 4 20 7"></polyline>
181
- <line x1="9" y1="20" x2="15" y2="20"></line>
182
- <line x1="12" y1="4" x2="12" y2="20"></line>
183
- </svg>
184
- Font Size
185
- </h4>
186
-
187
- <div class="preference-section__control">
188
- <div class="font-size-control" data-font-size-control>
189
- <button type="button"
190
- class="font-size-control__button"
191
- data-action="decrease"
192
- aria-label="Decrease font size">
193
- <span class="font-size-control__label">A-</span>
194
- </button>
195
-
196
- <div class="font-size-control__display">
197
- <span data-font-size-value>100</span>%
198
- </div>
199
-
200
- <button type="button"
201
- class="font-size-control__button"
202
- data-action="increase"
203
- aria-label="Increase font size">
204
- <span class="font-size-control__label">A+</span>
205
- </button>
206
-
207
- <button type="button"
208
- class="font-size-control__reset"
209
- data-action="reset"
210
- aria-label="Reset font size">
211
- Reset
212
- </button>
213
- </div>
214
- </div>
215
- </div>
216
- {%- endif -%}
217
-
218
- {%- if show_reading_mode -%}
219
- <div class="preference-section">
220
- <h4 class="preference-section__title">
221
- <svg xmlns="http://www.w3.org/2000/svg"
222
- width="18"
223
- height="18"
224
- viewBox="0 0 24 24"
225
- fill="none"
226
- stroke="currentColor"
227
- stroke-width="2"
228
- aria-hidden="true">
229
- <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
230
- <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
231
- </svg>
232
- Reading Mode
233
- </h4>
234
-
235
- <div class="preference-section__control">
236
- <label class="toggle-switch">
237
- <input type="checkbox"
238
- data-reading-mode-toggle
239
- aria-label="Enable reading mode (focus mode)">
240
- <span class="toggle-switch__slider"></span>
241
- <span class="toggle-switch__label">
242
- Focus mode (minimal distractions)
243
- </span>
244
- </label>
245
- </div>
246
- </div>
247
- {%- endif -%}
248
-
249
- {%- if show_layout -%}
250
- <div class="preference-section">
251
- <h4 class="preference-section__title">
252
- <svg xmlns="http://www.w3.org/2000/svg"
253
- width="18"
254
- height="18"
255
- viewBox="0 0 24 24"
256
- fill="none"
257
- stroke="currentColor"
258
- stroke-width="2"
259
- aria-hidden="true">
260
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
261
- <line x1="9" y1="3" x2="9" y2="21"></line>
262
- </svg>
263
- Layout
264
- </h4>
265
-
266
- <div class="preference-section__control">
267
- <div class="layout-toggle" data-layout-control>
268
- <button type="button"
269
- class="layout-toggle__option"
270
- data-layout="default"
271
- aria-label="Default layout">
272
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
273
- <rect x="3" y="3" width="18" height="18" rx="2"></rect>
274
- </svg>
275
- <span>Default</span>
276
- </button>
277
-
278
- <button type="button"
279
- class="layout-toggle__option"
280
- data-layout="wide"
281
- aria-label="Wide layout">
282
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
283
- <rect x="2" y="6" width="20" height="12" rx="2"></rect>
284
- </svg>
285
- <span>Wide</span>
286
- </button>
287
- </div>
288
- </div>
289
- </div>
290
- {%- endif -%}
291
-
292
- </div>
293
-
294
- <div class="user-preferences__footer">
295
- <button type="button"
296
- class="user-preferences__reset-all"
297
- data-reset-all-preferences
298
- aria-label="Reset all preferences to defaults">
299
- <svg xmlns="http://www.w3.org/2000/svg"
300
- width="16"
301
- height="16"
302
- viewBox="0 0 24 24"
303
- fill="none"
304
- stroke="currentColor"
305
- stroke-width="2"
306
- aria-hidden="true">
307
- <polyline points="1 4 1 10 7 10"></polyline>
308
- <path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path>
309
- </svg>
310
- Reset All to Defaults
311
- </button>
312
- </div>
313
-
314
- </div>
315
-
316
- <div class="user-preferences__backdrop"
317
- data-preferences-backdrop
318
- hidden></div>
319
- </div>
320
-
321
- <script>
322
- (function() {
323
- 'use strict';
324
-
325
- const STORAGE_KEY = 'userPreferences';
326
- const DEFAULTS = {
327
- theme: 'auto',
328
- fontSize: 100,
329
- readingMode: false,
330
- layout: 'default'
331
- };
332
-
333
- class UserPreferences {
334
- constructor() {
335
- this.preferences = this.loadPreferences();
336
- this.panel = document.querySelector('[data-user-preferences]');
337
- if (!this.panel) return;
338
-
339
- this.initElements();
340
- this.attachEventListeners();
341
- this.applyPreferences();
342
- }
343
-
344
- initElements() {
345
- this.trigger = this.panel.querySelector('[data-preferences-trigger]');
346
- this.panelContent = this.panel.querySelector('[data-preferences-panel]');
347
- this.backdrop = this.panel.querySelector('[data-preferences-backdrop]');
348
- this.closeBtn = this.panel.querySelector('[data-preferences-close]');
349
- }
350
-
351
- attachEventListeners() {
352
- // Open/close panel
353
- this.trigger?.addEventListener('click', () => this.togglePanel());
354
- this.closeBtn?.addEventListener('click', () => this.closePanel());
355
- this.backdrop?.addEventListener('click', () => this.closePanel());
356
-
357
- // ESC key to close
358
- document.addEventListener('keydown', (e) => {
359
- if (e.key === 'Escape' && !this.panelContent?.hidden) {
360
- this.closePanel();
361
- }
362
- });
363
-
364
- // Theme control
365
- const themeButtons = this.panel.querySelectorAll('[data-theme]');
366
- themeButtons.forEach(btn => {
367
- btn.addEventListener('click', (e) => {
368
- const theme = e.currentTarget.dataset.theme;
369
- this.setTheme(theme);
370
- });
371
- });
372
-
373
- // Font size control
374
- const fontSizeControl = this.panel.querySelector('[data-font-size-control]');
375
- if (fontSizeControl) {
376
- fontSizeControl.addEventListener('click', (e) => {
377
- const button = e.target.closest('button');
378
- if (!button) return;
379
-
380
- const action = button.dataset.action;
381
- if (action === 'increase') this.changeFontSize(10);
382
- else if (action === 'decrease') this.changeFontSize(-10);
383
- else if (action === 'reset') this.resetFontSize();
384
- });
385
- }
386
-
387
- // Reading mode toggle
388
- const readingModeToggle = this.panel.querySelector('[data-reading-mode-toggle]');
389
- readingModeToggle?.addEventListener('change', (e) => {
390
- this.setReadingMode(e.target.checked);
391
- });
392
-
393
- // Layout control
394
- const layoutButtons = this.panel.querySelectorAll('[data-layout]');
395
- layoutButtons.forEach(btn => {
396
- btn.addEventListener('click', (e) => {
397
- const layout = e.currentTarget.dataset.layout;
398
- this.setLayout(layout);
399
- });
400
- });
401
-
402
- // Reset all
403
- const resetAllBtn = this.panel.querySelector('[data-reset-all-preferences]');
404
- resetAllBtn?.addEventListener('click', () => this.resetAll());
405
- }
406
-
407
- togglePanel() {
408
- const isHidden = this.panelContent.hidden;
409
- if (isHidden) {
410
- this.openPanel();
411
- } else {
412
- this.closePanel();
413
- }
414
- }
415
-
416
- openPanel() {
417
- this.panelContent.hidden = false;
418
- this.backdrop.hidden = false;
419
- this.trigger?.setAttribute('aria-expanded', 'true');
420
- document.body.style.overflow = 'hidden';
421
-
422
- // Focus first interactive element
423
- const firstButton = this.panelContent.querySelector('button');
424
- firstButton?.focus();
425
- }
426
-
427
- closePanel() {
428
- this.panelContent.hidden = true;
429
- this.backdrop.hidden = true;
430
- this.trigger?.setAttribute('aria-expanded', 'false');
431
- document.body.style.overflow = '';
432
- this.trigger?.focus();
433
- }
434
-
435
- setTheme(theme) {
436
- this.preferences.theme = theme;
437
- this.savePreferences();
438
- this.applyTheme();
439
- this.updateThemeButtons();
440
- }
441
-
442
- applyTheme() {
443
- const { theme } = this.preferences;
444
- const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
445
- const isDark = theme === 'auto' ? prefersDark : theme === 'dark';
446
-
447
- document.body.classList.toggle('dark-mode', isDark);
448
- document.body.dataset.theme = isDark ? 'dark' : 'light';
449
- document.documentElement.dataset.theme = isDark ? 'dark' : 'light';
450
- }
451
-
452
- updateThemeButtons() {
453
- const buttons = this.panel.querySelectorAll('[data-theme]');
454
- buttons.forEach(btn => {
455
- const isActive = btn.dataset.theme === this.preferences.theme;
456
- btn.classList.toggle('theme-toggle__option--active', isActive);
457
- btn.setAttribute('aria-pressed', isActive);
458
- });
459
- }
460
-
461
- changeFontSize(delta) {
462
- const newSize = Math.max(80, Math.min(140, this.preferences.fontSize + delta));
463
- this.preferences.fontSize = newSize;
464
- this.savePreferences();
465
- this.applyFontSize();
466
- }
467
-
468
- resetFontSize() {
469
- this.preferences.fontSize = DEFAULTS.fontSize;
470
- this.savePreferences();
471
- this.applyFontSize();
472
- }
473
-
474
- applyFontSize() {
475
- const { fontSize } = this.preferences;
476
- document.documentElement.style.setProperty('--user-font-size', `${fontSize}%`);
477
-
478
- const display = this.panel.querySelector('[data-font-size-value]');
479
- if (display) display.textContent = fontSize;
480
- }
481
-
482
- setReadingMode(enabled) {
483
- this.preferences.readingMode = enabled;
484
- this.savePreferences();
485
- this.applyReadingMode();
486
- }
487
-
488
- applyReadingMode() {
489
- document.body.classList.toggle('reading-mode', this.preferences.readingMode);
490
-
491
- const toggle = this.panel.querySelector('[data-reading-mode-toggle]');
492
- if (toggle) toggle.checked = this.preferences.readingMode;
493
- }
494
-
495
- setLayout(layout) {
496
- this.preferences.layout = layout;
497
- this.savePreferences();
498
- this.applyLayout();
499
- this.updateLayoutButtons();
500
- }
501
-
502
- applyLayout() {
503
- document.body.dataset.layout = this.preferences.layout;
504
- }
505
-
506
- updateLayoutButtons() {
507
- const buttons = this.panel.querySelectorAll('[data-layout]');
508
- buttons.forEach(btn => {
509
- const isActive = btn.dataset.layout === this.preferences.layout;
510
- btn.classList.toggle('layout-toggle__option--active', isActive);
511
- btn.setAttribute('aria-pressed', isActive);
512
- });
513
- }
514
-
515
- resetAll() {
516
- if (confirm('Reset all preferences to defaults?')) {
517
- this.preferences = { ...DEFAULTS };
518
- this.savePreferences();
519
- this.applyPreferences();
520
- }
521
- }
522
-
523
- applyPreferences() {
524
- this.applyTheme();
525
- this.applyFontSize();
526
- this.applyReadingMode();
527
- this.applyLayout();
528
- this.updateThemeButtons();
529
- this.updateLayoutButtons();
530
- }
531
-
532
- loadPreferences() {
533
- try {
534
- const stored = localStorage.getItem(STORAGE_KEY);
535
- return stored ? { ...DEFAULTS, ...JSON.parse(stored) } : { ...DEFAULTS };
536
- } catch (e) {
537
- console.error('Failed to load preferences:', e);
538
- return { ...DEFAULTS };
539
- }
540
- }
541
-
542
- savePreferences() {
543
- try {
544
- localStorage.setItem(STORAGE_KEY, JSON.stringify(this.preferences));
545
- } catch (e) {
546
- console.error('Failed to save preferences:', e);
547
- }
548
- }
549
- }
550
-
551
- // Initialize when DOM is ready
552
- if (document.readyState === 'loading') {
553
- document.addEventListener('DOMContentLoaded', () => new UserPreferences());
554
- } else {
555
- new UserPreferences();
556
- }
557
-
558
- // Listen for system theme changes
559
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
560
- const prefs = new UserPreferences();
561
- if (prefs.preferences.theme === 'auto') {
562
- prefs.applyTheme();
563
- }
564
- });
565
- })();
566
- </script>