datalog-theme 0.6.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 (180) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +162 -0
  3. data/CITATION.cff +29 -0
  4. data/LICENSE +21 -0
  5. data/README.md +257 -0
  6. data/_data/academic.yml +217 -0
  7. data/_data/cdn-integrity.yml +51 -0
  8. data/_data/config/author.yml +121 -0
  9. data/_data/config/features.yml +262 -0
  10. data/_data/config/site.yml +42 -0
  11. data/_data/config/theme.yml +181 -0
  12. data/_data/datasets.yml +28 -0
  13. data/_data/i18n/en.yml +438 -0
  14. data/_data/i18n/es.yml +438 -0
  15. data/_data/i18n/pt.yml +438 -0
  16. data/_data/js_manifest.json +22 -0
  17. data/_data/js_meta.json +371 -0
  18. data/_data/navigation.yml +145 -0
  19. data/_data/projects.yml +41 -0
  20. data/_data/publications.yml +28 -0
  21. data/_data/social.yml +73 -0
  22. data/_data/visualizations.yml +51 -0
  23. data/_includes/analytics/dashboard.html +149 -0
  24. data/_includes/analytics.html +9 -0
  25. data/_includes/components/academic-dashboard.html +347 -0
  26. data/_includes/components/advanced-search.html +682 -0
  27. data/_includes/components/api-function.html +94 -0
  28. data/_includes/components/author-bio.html +166 -0
  29. data/_includes/components/bookmark-system.html +96 -0
  30. data/_includes/components/breadcrumbs.html +89 -0
  31. data/_includes/components/citation-tools.html +94 -0
  32. data/_includes/components/comments.html +244 -0
  33. data/_includes/components/content-provenance.html +43 -0
  34. data/_includes/components/content-recommendations.html +228 -0
  35. data/_includes/components/difficulty-badge.html +84 -0
  36. data/_includes/components/email-preferences.html +200 -0
  37. data/_includes/components/enhanced-code-block.html +212 -0
  38. data/_includes/components/enhanced-metadata.html +228 -0
  39. data/_includes/components/enhanced-toc.html +209 -0
  40. data/_includes/components/hero.html +15 -0
  41. data/_includes/components/language-switcher.html +396 -0
  42. data/_includes/components/math-fallback.html +19 -0
  43. data/_includes/components/navigation-enhancements.html +454 -0
  44. data/_includes/components/newsletter-signup.html +178 -0
  45. data/_includes/components/open-science-badges.html +16 -0
  46. data/_includes/components/package-install.html +194 -0
  47. data/_includes/components/performance-monitor.html +170 -0
  48. data/_includes/components/popular-posts.html +233 -0
  49. data/_includes/components/post-hero.html +62 -0
  50. data/_includes/components/reading-progress.html +133 -0
  51. data/_includes/components/reading-time.html +121 -0
  52. data/_includes/components/responsive-image.html +63 -0
  53. data/_includes/components/search-facets.html +307 -0
  54. data/_includes/components/series-navigation.html +124 -0
  55. data/_includes/components/social-proof.html +34 -0
  56. data/_includes/components/social-share.html +119 -0
  57. data/_includes/components/user-preferences.html +566 -0
  58. data/_includes/components/visualization-card.html +24 -0
  59. data/_includes/components/viz-table-fallback.html +19 -0
  60. data/_includes/critical-css/default.html +0 -0
  61. data/_includes/critical-css/home.html +0 -0
  62. data/_includes/critical-css/post.html +0 -0
  63. data/_includes/csp-meta.html +17 -0
  64. data/_includes/footer/nav-column.html +31 -0
  65. data/_includes/footer.html +76 -0
  66. data/_includes/head.html +337 -0
  67. data/_includes/header/navigation.html +48 -0
  68. data/_includes/header.html +70 -0
  69. data/_includes/helpers/array-to-sentence.html +21 -0
  70. data/_includes/helpers/date-format.html +16 -0
  71. data/_includes/helpers/link-with-icon.html +26 -0
  72. data/_includes/layouts/default/article.html +67 -0
  73. data/_includes/meta/language-attributes.html +25 -0
  74. data/_includes/meta/math-config.html +21 -0
  75. data/_includes/meta/schema.html +153 -0
  76. data/_includes/meta/scripts-loader.html +52 -0
  77. data/_includes/post/related-posts.html +63 -0
  78. data/_includes/scripts.html +72 -0
  79. data/_includes/skip-link.html +1 -0
  80. data/_includes/toc.html +2 -0
  81. data/_layouts/archive.html +282 -0
  82. data/_layouts/dataset.html +52 -0
  83. data/_layouts/default.html +33 -0
  84. data/_layouts/home.html +48 -0
  85. data/_layouts/notebook.html +173 -0
  86. data/_layouts/package.html +238 -0
  87. data/_layouts/page.html +16 -0
  88. data/_layouts/portfolio.html +41 -0
  89. data/_layouts/post-sidebar.html +183 -0
  90. data/_layouts/post.html +338 -0
  91. data/_layouts/project.html +252 -0
  92. data/_layouts/research.html +504 -0
  93. data/_plugins/analytics_dashboard.rb +315 -0
  94. data/_plugins/config_validator.rb +397 -0
  95. data/_plugins/csp_generator.rb +115 -0
  96. data/_plugins/datalog_bibliography.rb +19 -0
  97. data/_plugins/datalog_comments.rb +18 -0
  98. data/_plugins/datalog_slides.rb +20 -0
  99. data/_plugins/front_matter_compat.rb +109 -0
  100. data/_plugins/i18n.rb +160 -0
  101. data/_plugins/image_optimizer.rb +445 -0
  102. data/_plugins/math_preprocessor.rb +185 -0
  103. data/_plugins/notebook_converter.rb +1107 -0
  104. data/_plugins/plugin_loader.rb +185 -0
  105. data/_plugins/publications_generator.rb +234 -0
  106. data/_plugins/reading_time.rb +15 -0
  107. data/_plugins/search_normalizer.rb +74 -0
  108. data/_plugins/sri_filter.rb +18 -0
  109. data/_plugins/toc_filter.rb +68 -0
  110. data/_plugins/warning_filter.rb +3 -0
  111. data/_sass/_base.scss +142 -0
  112. data/_sass/_components.scss +2380 -0
  113. data/_sass/_header.scss +470 -0
  114. data/_sass/_interactive.scss +344 -0
  115. data/_sass/_layout.scss +1760 -0
  116. data/_sass/_mathematical.scss +368 -0
  117. data/_sass/_mixins.scss +99 -0
  118. data/_sass/_package-docs.scss +715 -0
  119. data/_sass/_phase1-enhancements.scss +721 -0
  120. data/_sass/_phase3-enhancements.scss +874 -0
  121. data/_sass/_phase4-enhancements.scss +1214 -0
  122. data/_sass/_phase5-enhancements.scss +414 -0
  123. data/_sass/_search.scss +654 -0
  124. data/_sass/_syntax-highlighting.scss +128 -0
  125. data/_sass/_theme.scss +79 -0
  126. data/_sass/_typography.scss +148 -0
  127. data/_sass/_utilities.scss +334 -0
  128. data/_sass/_variables.scss +153 -0
  129. data/_sass/_visualizations.scss +242 -0
  130. data/assets/css/main.scss +4 -0
  131. data/assets/img/20220607123041_detail.001.png +0 -0
  132. data/assets/img/favicons/android-chrome-192x192.png +0 -0
  133. data/assets/img/favicons/android-chrome-512x512.png +0 -0
  134. data/assets/img/favicons/apple-touch-icon.png +0 -0
  135. data/assets/img/favicons/favicon-16x16.png +0 -0
  136. data/assets/img/favicons/favicon-32x32.png +0 -0
  137. data/assets/img/favicons/favicon.ico +0 -0
  138. data/assets/img/favicons/site.webmanifest +21 -0
  139. data/assets/img/portfolio-placeholder.svg +20 -0
  140. data/assets/js/academic.js +262 -0
  141. data/assets/js/analytics-dashboard.js +382 -0
  142. data/assets/js/core/dark-mode.js +79 -0
  143. data/assets/js/core/github-cards.js +123 -0
  144. data/assets/js/core/language-filter.js +69 -0
  145. data/assets/js/core/navigation.js +184 -0
  146. data/assets/js/core/scroll-progress.js +45 -0
  147. data/assets/js/core/search-hotkeys.js +62 -0
  148. data/assets/js/core/skip-links.js +62 -0
  149. data/assets/js/loader.js +163 -0
  150. data/assets/js/main.js +23 -0
  151. data/assets/js/math.js +818 -0
  152. data/assets/js/notebook.js +158 -0
  153. data/assets/js/search/analytics.js +91 -0
  154. data/assets/js/search/app.js +271 -0
  155. data/assets/js/search/autocomplete.js +120 -0
  156. data/assets/js/search/engine.js +260 -0
  157. data/assets/js/search/filters.js +38 -0
  158. data/assets/js/search/render.js +217 -0
  159. data/assets/js/search/utils.js +99 -0
  160. data/assets/js/search.js +354 -0
  161. data/assets/js/visualizations.js +816 -0
  162. data/assets/publications/datalog-publications.bib +8 -0
  163. data/assets/publications/datalog-publications.ris +9 -0
  164. data/assets/publications/publications.bib +30 -0
  165. data/assets/templates/diogo-ribeiro-cv.md +31 -0
  166. data/assets/templates/diogo-ribeiro-cv.tex +32 -0
  167. data/bin/datalog +26 -0
  168. data/datalog-theme.gemspec +72 -0
  169. data/lib/datalog/cli.rb +563 -0
  170. data/lib/datalog/plugin_system/dependency_resolver.rb +223 -0
  171. data/lib/datalog/plugin_system.rb +237 -0
  172. data/lib/datalog/plugins/citations.rb +182 -0
  173. data/lib/datalog/plugins/comments.rb +183 -0
  174. data/lib/datalog/plugins/search.rb +45 -0
  175. data/lib/datalog/plugins/slides.rb +121 -0
  176. data/lib/datalog/theme/theme.rb +18 -0
  177. data/lib/datalog/theme/version.rb +7 -0
  178. data/lib/datalog/warning_filter.rb +28 -0
  179. data/lib/datalog-theme.rb +17 -0
  180. metadata +555 -0
@@ -0,0 +1,566 @@
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>
@@ -0,0 +1,24 @@
1
+ {% assign tech = visualization.tech | default: 'plotly' %}
2
+ <article class="card viz-card" data-viz-card data-viz-tech="{{ tech }}" data-viz-tags="{{ visualization.tags | join: ',' }}">
3
+ <p class="card-meta">
4
+ <span class="post-meta__badge">{{ tech }}</span>
5
+ {% if visualization.version %}<span>v{{ visualization.version }}</span>{% endif %}
6
+ </p>
7
+ <h3>{{ visualization.title }}</h3>
8
+ <p>{{ visualization.summary }}</p>
9
+ {% if visualization.tags %}
10
+ <ul class="post-list-item__tags">
11
+ {% for tag in visualization.tags %}
12
+ <li>{{ tag }}</li>
13
+ {% endfor %}
14
+ </ul>
15
+ {% endif %}
16
+ <div class="project-links">
17
+ {% if visualization.demo %}
18
+ <a class="btn btn-primary" href="{{ visualization.demo }}" target="_blank" rel="noopener">Live demo</a>
19
+ {% endif %}
20
+ {% if visualization.source %}
21
+ <a class="btn btn-secondary" href="{{ visualization.source }}" target="_blank" rel="noopener">Source</a>
22
+ {% endif %}
23
+ </div>
24
+ </article>
@@ -0,0 +1,19 @@
1
+ <template id="viz-table-fallback-template" data-component="viz-table-fallback">
2
+ <div class="viz-table-container" data-viz-table-container>
3
+ <button
4
+ type="button"
5
+ class="viz-table-toggle"
6
+ data-viz-table-toggle
7
+ aria-expanded="false"
8
+ >
9
+ Show data table
10
+ </button>
11
+ <div class="viz-table-wrapper" data-viz-table-wrapper hidden>
12
+ <table class="viz-table" data-viz-table>
13
+ <caption data-viz-table-caption></caption>
14
+ <thead data-viz-table-head></thead>
15
+ <tbody data-viz-table-body></tbody>
16
+ </table>
17
+ </div>
18
+ </div>
19
+ </template>
File without changes
File without changes
File without changes
@@ -0,0 +1,17 @@
1
+ {% assign csp_registry = site.data.csp | default: {} %}
2
+ {% assign nonce_source = page.csp_nonce | default: csp_registry.nonces[page.path] | default: csp_registry.nonces[page.url] | default: '' %}
3
+ {% assign script_hashes = page.csp_hashes | default: [] %}
4
+ {% capture script_hash_directives -%}
5
+ {%- for hash in script_hashes -%}
6
+ 'sha256-{{ hash }}'{% unless forloop.last %} {% endunless %}
7
+ {%- endfor -%}
8
+ {%- endcapture -%}
9
+ <meta http-equiv="Content-Security-Policy" content="
10
+ default-src 'self';
11
+ script-src 'self' 'nonce-{{ nonce_source }}'{% if script_hash_directives != '' %} {{ script_hash_directives | strip }}{% endif %} https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://www.googletagmanager.com https://www.google-analytics.com;
12
+ style-src 'self' 'nonce-{{ nonce_source }}' https://fonts.googleapis.com https://cdn.jsdelivr.net;
13
+ img-src 'self' data: https:;
14
+ font-src 'self' https://fonts.gstatic.com data:;
15
+ connect-src 'self' https://api.github.com https://www.google-analytics.com https://cdn.jsdelivr.net;
16
+ frame-src 'self' https://observablehq.com;
17
+ " />
@@ -0,0 +1,31 @@
1
+ {%- comment -%}
2
+ ---
3
+ required:
4
+ - title
5
+ - items
6
+ optional:
7
+ - show_descriptions
8
+ ---
9
+ Renders a footer navigation column with optional item descriptions.
10
+ {%- endcomment -%}
11
+ {%- assign column_items = include.items | default: '' -%}
12
+ {%- if column_items == '' -%}
13
+ {%- assign column_items = '' | split: '' -%}
14
+ {%- endif -%}
15
+ <div>
16
+ <h4>{{ include.title | strip }}</h4>
17
+ <ul>
18
+ {%- for item in column_items -%}
19
+ {%- capture item_label -%}{% if item.label %}{{ item.label }}{% elsif item.label_key %}{% t item.label_key %}{% endif %}{%- endcapture -%}
20
+ {%- capture item_description -%}{% if item.description %}{{ item.description }}{% elsif item.description_key %}{% t item.description_key %}{% endif %}{%- endcapture -%}
21
+ <li>
22
+ <a href="{{ item.url | relative_url }}">
23
+ <span>{{ item_label | strip }}</span>
24
+ {%- if include.show_descriptions and item_description != '' -%}
25
+ <span class="footer-nav__description">{{ item_description | strip }}</span>
26
+ {%- endif -%}
27
+ </a>
28
+ </li>
29
+ {%- endfor -%}
30
+ </ul>
31
+ </div>