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,454 @@
1
+ {%- comment -%}
2
+ Navigation Enhancements Component
3
+ Provides back-to-top button and keyboard shortcuts
4
+
5
+ Parameters:
6
+ - show_back_to_top: Show back-to-top button (default: true)
7
+ - show_keyboard_shortcuts: Enable keyboard shortcuts (default: true)
8
+ - back_to_top_threshold: Scroll threshold in pixels (default: 300)
9
+ {%- endcomment -%}
10
+
11
+ {%- assign show_back_to_top = include.show_back_to_top | default: true -%}
12
+ {%- assign show_keyboard_shortcuts = include.show_keyboard_shortcuts | default: true -%}
13
+ {%- assign back_to_top_threshold = include.back_to_top_threshold | default: 300 -%}
14
+
15
+ {%- if show_back_to_top -%}
16
+ <button type="button"
17
+ class="back-to-top"
18
+ data-back-to-top
19
+ aria-label="Back to top"
20
+ hidden>
21
+ <svg class="back-to-top__icon"
22
+ xmlns="http://www.w3.org/2000/svg"
23
+ width="20"
24
+ height="20"
25
+ viewBox="0 0 24 24"
26
+ fill="none"
27
+ stroke="currentColor"
28
+ stroke-width="2"
29
+ stroke-linecap="round"
30
+ stroke-linejoin="round"
31
+ aria-hidden="true">
32
+ <line x1="12" y1="19" x2="12" y2="5"></line>
33
+ <polyline points="5 12 12 5 19 12"></polyline>
34
+ </svg>
35
+ <span class="back-to-top__text">Top</span>
36
+ <svg class="back-to-top__progress"
37
+ width="52"
38
+ height="52"
39
+ viewBox="0 0 52 52"
40
+ aria-hidden="true">
41
+ <circle class="back-to-top__progress-bg"
42
+ cx="26"
43
+ cy="26"
44
+ r="24"
45
+ fill="none"
46
+ stroke="currentColor"
47
+ stroke-width="2"
48
+ opacity="0.2"></circle>
49
+ <circle class="back-to-top__progress-indicator"
50
+ cx="26"
51
+ cy="26"
52
+ r="24"
53
+ fill="none"
54
+ stroke="currentColor"
55
+ stroke-width="2"
56
+ stroke-linecap="round"
57
+ data-progress-circle></circle>
58
+ </svg>
59
+ </button>
60
+ {%- endif -%}
61
+
62
+ {%- if show_keyboard_shortcuts -%}
63
+ <div class="keyboard-shortcuts"
64
+ data-keyboard-shortcuts
65
+ role="dialog"
66
+ aria-modal="true"
67
+ aria-labelledby="shortcuts-title"
68
+ hidden>
69
+
70
+ <div class="keyboard-shortcuts__content">
71
+ <div class="keyboard-shortcuts__header">
72
+ <h3 class="keyboard-shortcuts__title" id="shortcuts-title">
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
+ <rect x="2" y="4" width="20" height="16" rx="2"></rect>
82
+ <path d="M6 8h.01M10 8h.01M14 8h.01M18 8h.01M8 12h.01M12 12h.01M16 12h.01M7 16h10"></path>
83
+ </svg>
84
+ Keyboard Shortcuts
85
+ </h3>
86
+
87
+ <button type="button"
88
+ class="keyboard-shortcuts__close"
89
+ data-shortcuts-close
90
+ aria-label="Close keyboard shortcuts">
91
+ <svg xmlns="http://www.w3.org/2000/svg"
92
+ width="20"
93
+ height="20"
94
+ viewBox="0 0 24 24"
95
+ fill="none"
96
+ stroke="currentColor"
97
+ stroke-width="2"
98
+ aria-hidden="true">
99
+ <line x1="18" y1="6" x2="6" y2="18"></line>
100
+ <line x1="6" y1="6" x2="18" y2="18"></line>
101
+ </svg>
102
+ </button>
103
+ </div>
104
+
105
+ <div class="keyboard-shortcuts__body">
106
+ <div class="shortcuts-section">
107
+ <h4 class="shortcuts-section__title">Navigation</h4>
108
+ <dl class="shortcuts-list">
109
+ <div class="shortcuts-list__item">
110
+ <dt class="shortcuts-list__keys">
111
+ <kbd>g</kbd> <kbd>h</kbd>
112
+ </dt>
113
+ <dd class="shortcuts-list__description">Go to home page</dd>
114
+ </div>
115
+
116
+ <div class="shortcuts-list__item">
117
+ <dt class="shortcuts-list__keys">
118
+ <kbd>g</kbd> <kbd>a</kbd>
119
+ </dt>
120
+ <dd class="shortcuts-list__description">Go to archive</dd>
121
+ </div>
122
+
123
+ <div class="shortcuts-list__item">
124
+ <dt class="shortcuts-list__keys">
125
+ <kbd>←</kbd> / <kbd>→</kbd>
126
+ </dt>
127
+ <dd class="shortcuts-list__description">Previous / Next post</dd>
128
+ </div>
129
+
130
+ <div class="shortcuts-list__item">
131
+ <dt class="shortcuts-list__keys">
132
+ <kbd>↑</kbd> / <kbd>↓</kbd>
133
+ </dt>
134
+ <dd class="shortcuts-list__description">Scroll up / down</dd>
135
+ </div>
136
+ </dl>
137
+ </div>
138
+
139
+ <div class="shortcuts-section">
140
+ <h4 class="shortcuts-section__title">Actions</h4>
141
+ <dl class="shortcuts-list">
142
+ <div class="shortcuts-list__item">
143
+ <dt class="shortcuts-list__keys">
144
+ <kbd>/</kbd>
145
+ </dt>
146
+ <dd class="shortcuts-list__description">Focus search</dd>
147
+ </div>
148
+
149
+ <div class="shortcuts-list__item">
150
+ <dt class="shortcuts-list__keys">
151
+ <kbd>?</kbd>
152
+ </dt>
153
+ <dd class="shortcuts-list__description">Show this help</dd>
154
+ </div>
155
+
156
+ <div class="shortcuts-list__item">
157
+ <dt class="shortcuts-list__keys">
158
+ <kbd>Esc</kbd>
159
+ </dt>
160
+ <dd class="shortcuts-list__description">Close dialogs</dd>
161
+ </div>
162
+
163
+ <div class="shortcuts-list__item">
164
+ <dt class="shortcuts-list__keys">
165
+ <kbd>s</kbd>
166
+ </dt>
167
+ <dd class="shortcuts-list__description">Share current page</dd>
168
+ </div>
169
+ </dl>
170
+ </div>
171
+
172
+ <div class="shortcuts-section">
173
+ <h4 class="shortcuts-section__title">Reading</h4>
174
+ <dl class="shortcuts-list">
175
+ <div class="shortcuts-list__item">
176
+ <dt class="shortcuts-list__keys">
177
+ <kbd>r</kbd>
178
+ </dt>
179
+ <dd class="shortcuts-list__description">Toggle reading mode</dd>
180
+ </div>
181
+
182
+ <div class="shortcuts-list__item">
183
+ <dt class="shortcuts-list__keys">
184
+ <kbd>t</kbd>
185
+ </dt>
186
+ <dd class="shortcuts-list__description">Toggle theme</dd>
187
+ </div>
188
+
189
+ <div class="shortcuts-list__item">
190
+ <dt class="shortcuts-list__keys">
191
+ <kbd>+</kbd> / <kbd>-</kbd>
192
+ </dt>
193
+ <dd class="shortcuts-list__description">Increase / decrease font size</dd>
194
+ </div>
195
+
196
+ <div class="shortcuts-list__item">
197
+ <dt class="shortcuts-list__keys">
198
+ <kbd>0</kbd>
199
+ </dt>
200
+ <dd class="shortcuts-list__description">Reset font size</dd>
201
+ </div>
202
+ </dl>
203
+ </div>
204
+ </div>
205
+
206
+ <div class="keyboard-shortcuts__footer">
207
+ <p class="keyboard-shortcuts__hint">
208
+ Press <kbd>?</kbd> anytime to show shortcuts
209
+ </p>
210
+ </div>
211
+ </div>
212
+
213
+ <div class="keyboard-shortcuts__backdrop"
214
+ data-shortcuts-backdrop></div>
215
+ </div>
216
+ {%- endif -%}
217
+
218
+ <script>
219
+ (function() {
220
+ 'use strict';
221
+
222
+ // Back to Top functionality
223
+ const initBackToTop = () => {
224
+ const button = document.querySelector('[data-back-to-top]');
225
+ if (!button) return;
226
+
227
+ const progressCircle = button.querySelector('[data-progress-circle]');
228
+ const threshold = {{ back_to_top_threshold }};
229
+ const circumference = 2 * Math.PI * 24; // radius is 24
230
+
231
+ if (progressCircle) {
232
+ progressCircle.style.strokeDasharray = circumference;
233
+ progressCircle.style.strokeDashoffset = circumference;
234
+ }
235
+
236
+ let ticking = false;
237
+
238
+ const updateButton = () => {
239
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
240
+ const docHeight = document.documentElement.scrollHeight - window.innerHeight;
241
+ const scrollPercent = (scrollTop / docHeight) * 100;
242
+
243
+ // Show/hide button
244
+ button.hidden = scrollTop < threshold;
245
+
246
+ // Update progress circle
247
+ if (progressCircle) {
248
+ const offset = circumference - (scrollPercent / 100) * circumference;
249
+ progressCircle.style.strokeDashoffset = offset;
250
+ }
251
+
252
+ ticking = false;
253
+ };
254
+
255
+ const requestTick = () => {
256
+ if (!ticking) {
257
+ requestAnimationFrame(updateButton);
258
+ ticking = true;
259
+ }
260
+ };
261
+
262
+ window.addEventListener('scroll', requestTick, { passive: true });
263
+
264
+ button.addEventListener('click', () => {
265
+ window.scrollTo({
266
+ top: 0,
267
+ behavior: 'smooth'
268
+ });
269
+ });
270
+
271
+ // Initial check
272
+ updateButton();
273
+ };
274
+
275
+ // Keyboard Shortcuts functionality
276
+ const initKeyboardShortcuts = () => {
277
+ const panel = document.querySelector('[data-keyboard-shortcuts]');
278
+ if (!panel) return;
279
+
280
+ const backdrop = panel.querySelector('[data-shortcuts-backdrop]');
281
+ const closeBtn = panel.querySelector('[data-shortcuts-close]');
282
+
283
+ const openPanel = () => {
284
+ panel.hidden = false;
285
+ document.body.style.overflow = 'hidden';
286
+ closeBtn?.focus();
287
+ };
288
+
289
+ const closePanel = () => {
290
+ panel.hidden = true;
291
+ document.body.style.overflow = '';
292
+ };
293
+
294
+ closeBtn?.addEventListener('click', closePanel);
295
+ backdrop?.addEventListener('click', closePanel);
296
+
297
+ // Keyboard shortcut handling
298
+ let keySequence = [];
299
+ let sequenceTimeout;
300
+
301
+ document.addEventListener('keydown', (e) => {
302
+ // Ignore if typing in input/textarea
303
+ if (e.target.matches('input, textarea, select')) {
304
+ return;
305
+ }
306
+
307
+ // ESC to close dialogs
308
+ if (e.key === 'Escape') {
309
+ if (!panel.hidden) {
310
+ closePanel();
311
+ e.preventDefault();
312
+ }
313
+ return;
314
+ }
315
+
316
+ // ? to show shortcuts
317
+ if (e.key === '?' && !e.ctrlKey && !e.metaKey && !e.altKey) {
318
+ openPanel();
319
+ e.preventDefault();
320
+ return;
321
+ }
322
+
323
+ // Handle key sequences (g + h, g + a)
324
+ clearTimeout(sequenceTimeout);
325
+ keySequence.push(e.key.toLowerCase());
326
+ keySequence = keySequence.slice(-2); // Keep last 2 keys
327
+
328
+ sequenceTimeout = setTimeout(() => {
329
+ keySequence = [];
330
+ }, 1000);
331
+
332
+ const sequence = keySequence.join(' ');
333
+
334
+ // Navigation shortcuts
335
+ if (sequence === 'g h') {
336
+ window.location.href = '{{ "/" | relative_url }}';
337
+ e.preventDefault();
338
+ } else if (sequence === 'g a') {
339
+ window.location.href = '{{ "/archive/" | relative_url }}';
340
+ e.preventDefault();
341
+ }
342
+
343
+ // Single key shortcuts
344
+ switch (e.key) {
345
+ case '/':
346
+ // Focus search
347
+ const searchInput = document.querySelector('[data-search-input], input[type="search"]');
348
+ if (searchInput) {
349
+ searchInput.focus();
350
+ e.preventDefault();
351
+ }
352
+ break;
353
+
354
+ case 'ArrowLeft':
355
+ // Previous post
356
+ const prevLink = document.querySelector('.post-navigation__prev a, a[rel="prev"]');
357
+ if (prevLink && !e.ctrlKey && !e.metaKey) {
358
+ window.location.href = prevLink.href;
359
+ e.preventDefault();
360
+ }
361
+ break;
362
+
363
+ case 'ArrowRight':
364
+ // Next post
365
+ const nextLink = document.querySelector('.post-navigation__next a, a[rel="next"]');
366
+ if (nextLink && !e.ctrlKey && !e.metaKey) {
367
+ window.location.href = nextLink.href;
368
+ e.preventDefault();
369
+ }
370
+ break;
371
+
372
+ case 'r':
373
+ // Toggle reading mode
374
+ if (!e.ctrlKey && !e.metaKey) {
375
+ const readingModeToggle = document.querySelector('[data-reading-mode-toggle]');
376
+ if (readingModeToggle) {
377
+ readingModeToggle.click();
378
+ e.preventDefault();
379
+ }
380
+ }
381
+ break;
382
+
383
+ case 't':
384
+ // Toggle theme
385
+ if (!e.ctrlKey && !e.metaKey) {
386
+ const currentTheme = document.body.classList.contains('dark-mode') ? 'light' : 'dark';
387
+ const themeButton = document.querySelector(`[data-theme="${currentTheme}"]`);
388
+ if (themeButton) {
389
+ themeButton.click();
390
+ e.preventDefault();
391
+ }
392
+ }
393
+ break;
394
+
395
+ case '+':
396
+ case '=':
397
+ // Increase font size
398
+ if (!e.ctrlKey && !e.metaKey) {
399
+ const increaseBtn = document.querySelector('[data-font-size-control] [data-action="increase"]');
400
+ if (increaseBtn) {
401
+ increaseBtn.click();
402
+ e.preventDefault();
403
+ }
404
+ }
405
+ break;
406
+
407
+ case '-':
408
+ // Decrease font size
409
+ if (!e.ctrlKey && !e.metaKey) {
410
+ const decreaseBtn = document.querySelector('[data-font-size-control] [data-action="decrease"]');
411
+ if (decreaseBtn) {
412
+ decreaseBtn.click();
413
+ e.preventDefault();
414
+ }
415
+ }
416
+ break;
417
+
418
+ case '0':
419
+ // Reset font size
420
+ if (!e.ctrlKey && !e.metaKey) {
421
+ const resetBtn = document.querySelector('[data-font-size-control] [data-action="reset"]');
422
+ if (resetBtn) {
423
+ resetBtn.click();
424
+ e.preventDefault();
425
+ }
426
+ }
427
+ break;
428
+
429
+ case 's':
430
+ // Share (if native share available)
431
+ if (!e.ctrlKey && !e.metaKey && navigator.share) {
432
+ navigator.share({
433
+ title: document.title,
434
+ url: window.location.href
435
+ }).catch(() => {});
436
+ e.preventDefault();
437
+ }
438
+ break;
439
+ }
440
+ });
441
+ };
442
+
443
+ // Initialize when DOM is ready
444
+ if (document.readyState === 'loading') {
445
+ document.addEventListener('DOMContentLoaded', () => {
446
+ initBackToTop();
447
+ initKeyboardShortcuts();
448
+ });
449
+ } else {
450
+ initBackToTop();
451
+ initKeyboardShortcuts();
452
+ }
453
+ })();
454
+ </script>
@@ -0,0 +1,178 @@
1
+ {%- comment -%}
2
+ Newsletter Signup Component
3
+ Integrates with popular email services (Mailchimp, ConvertKit, Buttondown, etc.)
4
+
5
+ Parameters:
6
+ - compact: Compact layout (default: false)
7
+ - inline: Inline layout (default: false)
8
+ - heading: Custom heading text
9
+ - description: Custom description text
10
+ - provider: Email provider (mailchimp, convertkit, buttondown, custom)
11
+ {%- endcomment -%}
12
+
13
+ {%- assign compact = include.compact | default: false -%}
14
+ {%- assign inline = include.inline | default: false -%}
15
+ {%- assign heading = include.heading | default: "Stay Updated" -%}
16
+ {%- assign description = include.description | default: "Get the latest posts delivered right to your inbox" -%}
17
+ {%- assign provider = include.provider | default: site.phase2_features.newsletter.provider | default: "custom" -%}
18
+
19
+ {%- comment -%} Get provider settings from config {%- endcomment -%}
20
+ {%- assign newsletter_config = site.phase2_features.newsletter -%}
21
+ {%- assign action_url = newsletter_config.action_url -%}
22
+ {%- assign success_message = newsletter_config.success_message | default: "Thanks for subscribing!" -%}
23
+
24
+ <div class="newsletter-signup{% if compact %} newsletter-signup--compact{% endif %}{% if inline %} newsletter-signup--inline{% endif %}"
25
+ data-newsletter>
26
+ {% unless compact %}
27
+ <div class="newsletter-signup__icon" aria-hidden="true">
28
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
29
+ <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
30
+ <polyline points="22,6 12,13 2,6"></polyline>
31
+ </svg>
32
+ </div>
33
+ {% endunless %}
34
+
35
+ <div class="newsletter-signup__content">
36
+ <h3 class="newsletter-signup__heading">{{ heading }}</h3>
37
+ <p class="newsletter-signup__description">{{ description }}</p>
38
+
39
+ <form class="newsletter-signup__form"
40
+ action="{{ action_url }}"
41
+ method="post"
42
+ target="_blank"
43
+ data-newsletter-form
44
+ novalidate>
45
+
46
+ <div class="newsletter-signup__input-group">
47
+ <label for="newsletter-email-{% if compact %}compact{% else %}full{% endif %}"
48
+ class="visually-hidden">
49
+ Email address
50
+ </label>
51
+ <input type="email"
52
+ id="newsletter-email-{% if compact %}compact{% else %}full{% endif %}"
53
+ name="email"
54
+ class="newsletter-signup__input"
55
+ placeholder="your@email.com"
56
+ required
57
+ aria-required="true"
58
+ aria-label="Email address">
59
+
60
+ <button type="submit"
61
+ class="newsletter-signup__button"
62
+ aria-label="Subscribe to newsletter">
63
+ <span class="newsletter-signup__button-text">Subscribe</span>
64
+ <svg class="newsletter-signup__button-icon"
65
+ xmlns="http://www.w3.org/2000/svg"
66
+ width="20"
67
+ height="20"
68
+ viewBox="0 0 24 24"
69
+ fill="none"
70
+ stroke="currentColor"
71
+ stroke-width="2"
72
+ stroke-linecap="round"
73
+ stroke-linejoin="round"
74
+ aria-hidden="true">
75
+ <line x1="5" y1="12" x2="19" y2="12"></line>
76
+ <polyline points="12 5 19 12 12 19"></polyline>
77
+ </svg>
78
+ </button>
79
+ </div>
80
+
81
+ {%- comment -%} Error message {%- endcomment -%}
82
+ <div class="newsletter-signup__message newsletter-signup__message--error"
83
+ data-error-message
84
+ role="alert"
85
+ aria-live="assertive">
86
+ </div>
87
+
88
+ {%- comment -%} Success message {%- endcomment -%}
89
+ <div class="newsletter-signup__message newsletter-signup__message--success"
90
+ data-success-message
91
+ role="status"
92
+ aria-live="polite">
93
+ </div>
94
+
95
+ {%- comment -%} Privacy notice {%- endcomment -%}
96
+ <p class="newsletter-signup__privacy">
97
+ {% if newsletter_config.privacy_url %}
98
+ By subscribing, you agree to our
99
+ <a href="{{ newsletter_config.privacy_url }}" target="_blank" rel="noopener">privacy policy</a>.
100
+ {% else %}
101
+ We respect your privacy. Unsubscribe at any time.
102
+ {% endif %}
103
+ </p>
104
+
105
+ {%- comment -%} Honeypot field for spam protection {%- endcomment -%}
106
+ <div style="position: absolute; left: -5000px;" aria-hidden="true">
107
+ <input type="text" name="b_honeypot" tabindex="-1" value="">
108
+ </div>
109
+ </form>
110
+ </div>
111
+ </div>
112
+
113
+ <script>
114
+ (function() {
115
+ 'use strict';
116
+
117
+ document.addEventListener('DOMContentLoaded', function() {
118
+ const forms = document.querySelectorAll('[data-newsletter-form]');
119
+
120
+ forms.forEach(form => {
121
+ const newsletter = form.closest('[data-newsletter]');
122
+ const emailInput = form.querySelector('input[type="email"]');
123
+ const submitButton = form.querySelector('button[type="submit"]');
124
+ const errorMessage = form.querySelector('[data-error-message]');
125
+ const successMessage = form.querySelector('[data-success-message]');
126
+
127
+ form.addEventListener('submit', function(e) {
128
+ // Clear previous messages
129
+ if (errorMessage) errorMessage.textContent = '';
130
+ if (successMessage) successMessage.textContent = '';
131
+
132
+ // Validate email
133
+ if (!emailInput.validity.valid) {
134
+ e.preventDefault();
135
+ if (errorMessage) {
136
+ errorMessage.textContent = 'Please enter a valid email address.';
137
+ }
138
+ emailInput.focus();
139
+ return false;
140
+ }
141
+
142
+ // Show loading state
143
+ if (submitButton) {
144
+ const originalText = submitButton.querySelector('.newsletter-signup__button-text').textContent;
145
+ submitButton.disabled = true;
146
+ submitButton.querySelector('.newsletter-signup__button-text').textContent = 'Subscribing...';
147
+
148
+ // Re-enable after delay (for services that open in new tab)
149
+ setTimeout(() => {
150
+ submitButton.disabled = false;
151
+ submitButton.querySelector('.newsletter-signup__button-text').textContent = originalText;
152
+
153
+ // Show success message
154
+ if (successMessage) {
155
+ successMessage.textContent = '{{ success_message }}';
156
+ }
157
+
158
+ // Reset form
159
+ setTimeout(() => {
160
+ form.reset();
161
+ if (successMessage) successMessage.textContent = '';
162
+ }, 5000);
163
+ }, 2000);
164
+ }
165
+
166
+ // For custom implementations, you can prevent default and handle via AJAX
167
+ // e.preventDefault();
168
+ // handleAjaxSubmit(emailInput.value);
169
+ });
170
+
171
+ // Clear error on input
172
+ emailInput.addEventListener('input', function() {
173
+ if (errorMessage) errorMessage.textContent = '';
174
+ });
175
+ });
176
+ });
177
+ })();
178
+ </script>
@@ -0,0 +1,16 @@
1
+ {% assign badges = include.badges | default: site.data.academic.badges %}
2
+ {% if badges and badges.size > 0 %}
3
+ <section class="open-science-badges" aria-labelledby="open-science-badges-heading">
4
+ <h2 id="open-science-badges-heading">{% t 'open_science_badges.title' %}</h2>
5
+ <p class="open-science-badges__intro">{% t 'open_science_badges.description' %}</p>
6
+ <ul class="open-science-badges__list">
7
+ {% for badge in badges %}
8
+ <li class="open-science-badge" data-badge-label="{{ badge.label | slugify }}">
9
+ <span class="open-science-badge__label">{{ badge.label }}</span>
10
+ {% if badge.description %}<span class="open-science-badge__description">{{ badge.description }}</span>{% endif %}
11
+ {% if badge.criteria %}<span class="open-science-badge__criteria">{{ badge.criteria }}</span>{% endif %}
12
+ </li>
13
+ {% endfor %}
14
+ </ul>
15
+ </section>
16
+ {% endif %}