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,338 @@
1
+ ---
2
+ layout: default
3
+ schema_type: TechnicalArticle
4
+ ---
5
+
6
+ {% assign localization_config = site.theme_options.localization %}
7
+
8
+ {%- comment -%} ========= Derived values & formatting ========= {%- endcomment -%}
9
+ {% assign date_format = page.date_format | default: localization_config.date_format | default: 'long' %}
10
+ {% assign reading_minutes = page.reading_minutes | default: page.content | strip_html | reading_time %}
11
+ {% if reading_minutes == 1 %}
12
+ {% capture reading_label %}{% t 'post.reading_time.one' %}{% endcapture %}
13
+ {% else %}
14
+ {% capture reading_label %}{% t 'post.reading_time.many' minutes: reading_minutes %}{% endcapture %}
15
+ {% endif %}
16
+ {% assign reading_label = reading_label | strip %}
17
+
18
+ {%- comment -%} Difficulty with safe fallbacks {%- endcomment -%}
19
+ {% capture difficulty_default %}{% t 'post.difficulty_default' %}{% endcapture %}
20
+ {% assign difficulty = page.difficulty | default: site.post_defaults.difficulty | default: difficulty_default | strip %}
21
+
22
+ {%- comment -%} Notebook path + external flag {%- endcomment -%}
23
+ {% assign notebook_path = page.notebook | default: page.notebook_path | default: page.notebook_url %}
24
+ {% assign notebook_is_external = false %}
25
+ {% if notebook_path and notebook_path contains '://' %}
26
+ {% assign notebook_is_external = true %}
27
+ {% endif %}
28
+
29
+ {%- comment -%} TOC settings {%- endcomment -%}
30
+ {% capture toc_default_label %}{% t 'post.toc_heading' %}{% endcapture %}
31
+ {% assign toc_heading = page.toc_label | default: toc_default_label | strip %}
32
+ {% assign toc_min = page.toc_h_min | default: 2 %}
33
+ {% assign toc_max = page.toc_h_max | default: 4 %}
34
+
35
+ {%- comment -%} Canonical URL & author info {%- endcomment -%}
36
+ {% assign canonical_url = page.url | absolute_url %}
37
+ {% assign author_name = page.author | default: site.author.name %}
38
+ {% assign author_affiliation = page.author_affiliation | default: site.author.affiliation %}
39
+
40
+ {%- comment -%} Tag/Category flags {%- endcomment -%}
41
+ {% assign post_tags = page.tags %}
42
+ {% assign has_tags = false %}
43
+ {% if post_tags and post_tags.size > 0 %}{% assign has_tags = true %}{% endif %}
44
+ {% assign has_categories = false %}
45
+ {% if page.categories and page.categories.size > 0 %}{% assign has_categories = true %}{% endif %}
46
+
47
+ {%- comment -%} ISO 8601 duration for reading time {%- endcomment -%}
48
+ {% assign time_required = 'PT' | append: reading_minutes | append: 'M' %}
49
+
50
+ {%- comment -%} Breadcrumb navigation {%- endcomment -%}
51
+ {% include components/breadcrumbs.html %}
52
+
53
+ <div class="post-wrapper" itemscope itemtype="https://schema.org/TechnicalArticle">
54
+ <meta itemprop="url" content="{{ canonical_url }}" />
55
+ <meta itemprop="datePublished" content="{{ page.date | date_to_xmlschema }}" />
56
+ {% if page.last_modified_at or page.updated %}
57
+ <meta itemprop="dateModified" content="{{ page.last_modified_at | default: page.updated | date_to_xmlschema }}" />
58
+ {% endif %}
59
+ <meta itemprop="author" content="{{ author_name }}" />
60
+ <meta itemprop="publisher" content="{{ site.author.affiliation | default: site.title }}" />
61
+ <meta itemprop="timeRequired" content="{{ time_required }}" />
62
+ {% if difficulty %}<meta itemprop="learningResourceType" content="{{ difficulty }}" />{% endif %}
63
+ {% if has_tags %}<meta itemprop="keywords" content="{{ post_tags | join: ', ' }}" />{% endif %}
64
+
65
+ <section class="post-meta" aria-labelledby="post-info-heading">
66
+ <h2 id="post-info-heading" class="visually-hidden">{% t 'post.article_information' %}</h2>
67
+ <dl class="post-meta-list">
68
+ <div>
69
+ <dt class="post-meta-term">{% t 'post.meta.published' %}</dt>
70
+ <dd class="post-meta-definition">{{ page.date | localize_date: date_format }}</dd>
71
+ </div>
72
+ <div>
73
+ <dt class="post-meta-term">{% t 'post.meta.author' %}</dt>
74
+ <dd class="post-meta-definition">
75
+ <span itemprop="author" itemscope itemtype="https://schema.org/Person">
76
+ <span itemprop="name">{{ author_name }}</span>
77
+ {% if author_affiliation %}
78
+ <span class="post-author-affiliation" itemprop="affiliation">{{ author_affiliation }}</span>
79
+ {% endif %}
80
+ </span>
81
+ </dd>
82
+ </div>
83
+ <div>
84
+ <dt class="post-meta-term">{% t 'post.meta.reading_time' %}</dt>
85
+ <dd class="post-meta-definition"><span data-reading-time>{{ reading_label }}</span></dd>
86
+ </div>
87
+ {% if difficulty %}
88
+ <div>
89
+ <dt class="post-meta-term">{% t 'post.meta.difficulty' %}</dt>
90
+ <dd class="post-meta-definition">{% include components/difficulty-badge.html level=difficulty %}</dd>
91
+ </div>
92
+ {% endif %}
93
+ {% if page.estimated_runtime %}
94
+ <div>
95
+ <dt class="post-meta-term">{% t 'post.meta.runtime' %}</dt>
96
+ <dd class="post-meta-definition">{{ page.estimated_runtime }}</dd>
97
+ </div>
98
+ {% endif %}
99
+ {% if notebook_path %}
100
+ <div>
101
+ <dt class="post-meta-term">{% t 'post.meta.notebook' %}</dt>
102
+ <dd class="post-meta-definition">
103
+ {% if notebook_is_external %}
104
+ <a class="post-notebook-link" href="{{ notebook_path }}" rel="noopener noreferrer" target="_blank">{% t 'post.notebook.open_external' %}</a>
105
+ {% else %}
106
+ <a class="post-notebook-link" href="{{ notebook_path | relative_url }}" download>{% t 'post.notebook.download' %}</a>
107
+ {% endif %}
108
+ </dd>
109
+ </div>
110
+ {% endif %}
111
+ </dl>
112
+ </section>
113
+
114
+ {% if has_tags or has_categories %}
115
+ <section class="post-taxonomy" aria-labelledby="post-taxonomy-heading">
116
+ <h2 id="post-taxonomy-heading" class="visually-hidden">{% t 'post.topics_heading' %}</h2>
117
+ <ul class="post-tag-list">
118
+ {% if has_tags %}
119
+ {% for tag in post_tags %}
120
+ {% assign tag_slug = tag | slugify %}
121
+ <li><a class="post-tag" href="{{ site.tag_archive.path | default: '/tags/' | append: '#' | append: tag_slug | relative_url }}">{{ tag }}</a></li>
122
+ {% endfor %}
123
+ {% endif %}
124
+ {% if has_categories %}
125
+ {% for category in page.categories %}
126
+ {% assign cat_slug = category | slugify %}
127
+ <li><a class="post-tag post-category" href="{{ site.category_archive.path | default: '/categories/' | append: '#' | append: cat_slug | relative_url }}">{{ category }}</a></li>
128
+ {% endfor %}
129
+ {% endif %}
130
+ </ul>
131
+ </section>
132
+ {% endif %}
133
+
134
+ {%- comment -%} Editorial provenance (why_this_exists, evidence, methodology, reviewed_at) {%- endcomment -%}
135
+ {% include components/content-provenance.html page=page date_format=date_format %}
136
+
137
+ {%- comment -%} Enhanced social sharing {%- endcomment -%}
138
+ {% include components/social-share.html url=canonical_url title=page.title description=page.excerpt %}
139
+
140
+ <div class="post-body-wrapper">
141
+ {% if page.toc != false %}
142
+ {%- comment -%} Enhanced TOC with progress indicator {%- endcomment -%}
143
+ {% include components/enhanced-toc.html html=content h_min=toc_min h_max=toc_max heading=toc_heading %}
144
+ {% endif %}
145
+
146
+ <div class="post-content" itemprop="articleBody">
147
+ {{ content }}
148
+ </div>
149
+ </div>
150
+
151
+ <section class="post-interactive" aria-label="{% t 'post.interactive.label' %}">
152
+ {% if page.interactive_components and page.interactive_components.size > 0 %}
153
+ <ul class="post-interactive-list">
154
+ {% for demo in page.interactive_components %}
155
+ <li>
156
+ <strong>{{ demo.title }}</strong>
157
+ {% if demo.description %}<p>{{ demo.description }}</p>{% endif %}
158
+ {% if demo.url %}<p><a href="{{ demo.url | relative_url }}" target="_blank" rel="noopener noreferrer">{% t 'post.interactive.launch' %}</a></p>{% endif %}
159
+ </li>
160
+ {% endfor %}
161
+ </ul>
162
+ {% elsif page.interactive_embed %}
163
+ <div class="interactive-embed" aria-live="polite">
164
+ {{ page.interactive_embed }}
165
+ </div>
166
+ {% else %}
167
+ <p>{% t 'post.interactive.guidance' %}</p>
168
+ {% endif %}
169
+ </section>
170
+
171
+ {% if page.datalog_slides %}
172
+ <section class="post-slides" aria-labelledby="post-slides-heading">
173
+ <h2 id="post-slides-heading">{% t 'post.slides.label' %}</h2>
174
+ {% datalog_slides %}
175
+ </section>
176
+ {% endif %}
177
+
178
+ {% if page.datalog_bibliography and page.datalog_bibliography.size > 0 %}
179
+ {% if page.datalog_bibliography_title %}
180
+ {% assign bibliography_heading = page.datalog_bibliography_title %}
181
+ {% else %}
182
+ {% capture bibliography_heading %}{% t 'post.bibliography.label' %}{% endcapture %}
183
+ {% endif %}
184
+ <section class="post-bibliography" aria-labelledby="post-bibliography-heading">
185
+ <h2 id="post-bibliography-heading">{{ bibliography_heading | strip }}</h2>
186
+ {% datalog_bibliography %}
187
+ </section>
188
+ {% endif %}
189
+
190
+ {% if page.datalog_comments %}
191
+ <section class="post-comments" aria-labelledby="post-comments-heading">
192
+ <h2 id="post-comments-heading">{% t 'post.comments.label' %}</h2>
193
+ <p class="post-comments-intro">{% t 'post.comments.cta' %}</p>
194
+ {% datalog_comments %}
195
+ </section>
196
+ {% endif %}
197
+
198
+ {%- comment -%} ===== Citation tools & badges ===== {%- endcomment -%}
199
+ {% assign citation_key = page.citation_key | default: page.slug | default: page.title | slugify %}
200
+ {% assign citation_authors = page.authors | default: page.author | default: author_name %}
201
+ {% include components/citation-tools.html
202
+ title=page.title
203
+ authors=citation_authors
204
+ date=page.date
205
+ url=canonical_url
206
+ citation_key=citation_key
207
+ publisher=site.author.affiliation
208
+ citation_type='article'
209
+ doi=page.doi %}
210
+ {% include components/open-science-badges.html %}
211
+
212
+ {%- comment -%} Author bio card {%- endcomment -%}
213
+ {% include components/author-bio.html author=author_name %}
214
+
215
+ {%- comment -%} ===== Robust related posts (no where_exp, no nil sort) ===== {%- endcomment -%}
216
+ {% include post/related-posts.html page=page date_format=date_format %}
217
+
218
+ <nav class="post-pagination" aria-label="Post navigation">
219
+ {% if page.previous %}
220
+ <div class="post-prev">
221
+ <span class="post-pagination-label">Previous</span>
222
+ <a href="{{ page.previous.url | relative_url }}">{{ page.previous.title }}</a>
223
+ {% if page.previous.tags and page.tags %}
224
+ {% assign shared_prev = '' %}
225
+ {% for tag in page.tags %}
226
+ {% if page.previous.tags contains tag %}
227
+ {% if shared_prev != '' %}{% assign shared_prev = shared_prev | append: ', ' %}{% endif %}
228
+ {% assign shared_prev = shared_prev | append: tag %}
229
+ {% endif %}
230
+ {% endfor %}
231
+ {% if shared_prev != '' %}
232
+ <p class="post-pagination-context">Shared topics: {{ shared_prev }}</p>
233
+ {% endif %}
234
+ {% endif %}
235
+ </div>
236
+ {% endif %}
237
+
238
+ {% if page.next %}
239
+ <div class="post-next">
240
+ <span class="post-pagination-label">Next</span>
241
+ <a href="{{ page.next.url | relative_url }}">{{ page.next.title }}</a>
242
+ {% if page.next.tags and page.tags %}
243
+ {% assign shared_next = '' %}
244
+ {% for tag in page.tags %}
245
+ {% if page.next.tags contains tag %}
246
+ {% if shared_next != '' %}{% assign shared_next = shared_next | append: ', ' %}{% endif %}
247
+ {% assign shared_next = shared_next | append: tag %}
248
+ {% endif %}
249
+ {% endfor %}
250
+ {% if shared_next != '' %}
251
+ <p class="post-pagination-context">Shared topics: {{ shared_next }}</p>
252
+ {% endif %}
253
+ {% endif %}
254
+ </div>
255
+ {% endif %}
256
+ </nav>
257
+ </div>
258
+
259
+ <script nonce="{{ page.csp_nonce }}">
260
+ // Enhance code blocks with a copy button and language badge.
261
+ document.addEventListener('DOMContentLoaded', function () {
262
+ const codeBlocks = document.querySelectorAll('.post-content pre > code');
263
+ codeBlocks.forEach(function (codeBlock) {
264
+ const pre = codeBlock.parentElement;
265
+ if (!pre || pre.querySelector('.code-copy')) return;
266
+
267
+ const wrapper = document.createElement('div');
268
+ wrapper.className = 'code-block-wrapper';
269
+ pre.parentNode.insertBefore(wrapper, pre);
270
+ wrapper.appendChild(pre);
271
+
272
+ const metaBar = document.createElement('div');
273
+ metaBar.className = 'code-block-meta';
274
+
275
+ const lang = codeBlock.getAttribute('class');
276
+ if (lang) {
277
+ const m = lang.match(/language-([a-z0-9+#]+)/i);
278
+ if (m) {
279
+ const langBadge = document.createElement('span');
280
+ langBadge.className = 'code-language';
281
+ langBadge.textContent = m[1].toUpperCase();
282
+ metaBar.appendChild(langBadge);
283
+ }
284
+ }
285
+
286
+ const button = document.createElement('button');
287
+ button.type = 'button';
288
+ button.className = 'code-copy';
289
+ button.setAttribute('aria-label', 'Copy code to clipboard');
290
+ button.textContent = 'Copy';
291
+
292
+ if (navigator.clipboard && navigator.clipboard.writeText) {
293
+ button.addEventListener('click', function () {
294
+ navigator.clipboard.writeText(codeBlock.textContent).then(function () {
295
+ button.textContent = 'Copied!';
296
+ button.classList.add('is-copied');
297
+ setTimeout(function () {
298
+ button.textContent = 'Copy';
299
+ button.classList.remove('is-copied');
300
+ }, 2000);
301
+ }).catch(function () {
302
+ button.textContent = 'Error';
303
+ });
304
+ });
305
+ } else {
306
+ button.disabled = true;
307
+ button.title = 'Clipboard copying is not supported in this browser';
308
+ }
309
+
310
+ metaBar.appendChild(button);
311
+ wrapper.insertBefore(metaBar, pre);
312
+ });
313
+
314
+ // Make equation references focus their targets for accessibility.
315
+ if (window.MathJax && window.MathJax.startup) {
316
+ window.MathJax.startup.promise.then(function () {
317
+ const equations = document.querySelectorAll('.post-content mjx-container[display="true"]');
318
+ let eqIndex = 1;
319
+ equations.forEach(function (container) {
320
+ if (!container.id) {
321
+ const eqId = 'eq-' + eqIndex++;
322
+ container.id = eqId;
323
+ container.setAttribute('tabindex', '-1');
324
+ const number = container.querySelector('mjx-mrow mjx-tag');
325
+ if (number) number.setAttribute('aria-label', 'Equation ' + (eqIndex - 1));
326
+ }
327
+ });
328
+ const refs = document.querySelectorAll('a[href^="#eq-"]');
329
+ refs.forEach(function (ref) {
330
+ ref.addEventListener('click', function () {
331
+ const target = document.querySelector(this.getAttribute('href'));
332
+ if (target) { target.focus(); }
333
+ });
334
+ });
335
+ });
336
+ }
337
+ });
338
+ </script>
@@ -0,0 +1,252 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+ {% assign localization_config = site.theme_options.localization %}
5
+ {% assign date_format = localization_config.date_format | default: 'long' %}
6
+ <article class="project-case" itemscope itemtype="https://schema.org/SoftwareApplication">
7
+ <header class="project-case__header">
8
+ <div class="project-case__intro">
9
+ <p class="project-case__eyebrow">{% t 'project.header.eyebrow' %}</p>
10
+ <h1 class="project-case__title" itemprop="name">{{ page.title }}</h1>
11
+ {% if page.description %}
12
+ <p class="project-case__description" itemprop="description">{{ page.description }}</p>
13
+ {% endif %}
14
+ </div>
15
+ <div class="project-case__meta">
16
+ {% if page.key_technologies %}
17
+ <section class="project-case__technologies" aria-labelledby="tech-stack-heading">
18
+ <h2 id="tech-stack-heading">{% t 'project.header.key_technologies' %}</h2>
19
+ <ul class="project-case__technology-list">
20
+ {% for tech in page.key_technologies %}
21
+ <li class="project-case__technology" itemprop="applicationCategory">
22
+ {% if tech.icon %}<span class="project-case__technology-icon" aria-hidden="true">{{ tech.icon }}</span>{% endif %}
23
+ <span class="project-case__technology-name">{{ tech.name | default: tech }}</span>
24
+ {% if tech.description %}<span class="project-case__technology-description">{{ tech.description }}</span>{% endif %}
25
+ </li>
26
+ {% endfor %}
27
+ </ul>
28
+ </section>
29
+ {% endif %}
30
+ {% if page.github %}
31
+ <section class="project-case__github" aria-labelledby="github-heading">
32
+ <h2 id="github-heading">{% t 'project.header.github_repository' %}</h2>
33
+ <div class="project-case__github-card" data-github-repo="{{ page.github.repo }}" data-github-owner="{{ page.github.owner }}">
34
+ <div class="project-case__github-meta">
35
+ <p class="project-case__github-name">
36
+ <a href="https://github.com/{{ page.github.owner }}/{{ page.github.repo }}" itemprop="url">{{ page.github.owner }}/{{ page.github.repo }}</a>
37
+ </p>
38
+ {% if page.github.description %}
39
+ <p class="project-case__github-description">{{ page.github.description }}</p>
40
+ {% endif %}
41
+ </div>
42
+ <dl class="project-case__github-stats" aria-live="polite">
43
+ <div>
44
+ <dt>{% t 'project.github.stats.stars' %}</dt>
45
+ <dd data-github-stat="stargazers_count">—</dd>
46
+ </div>
47
+ <div>
48
+ <dt>{% t 'project.github.stats.forks' %}</dt>
49
+ <dd data-github-stat="forks_count">—</dd>
50
+ </div>
51
+ <div>
52
+ <dt>{% t 'project.github.stats.open_issues' %}</dt>
53
+ <dd data-github-stat="open_issues_count">—</dd>
54
+ </div>
55
+ {% if page.github.primary_language %}
56
+ <div>
57
+ <dt>{% t 'project.github.stats.primary_language' %}</dt>
58
+ <dd>{{ page.github.primary_language }}</dd>
59
+ </div>
60
+ {% endif %}
61
+ </dl>
62
+ </div>
63
+ </section>
64
+ {% endif %}
65
+ </div>
66
+ </header>
67
+
68
+ {% if page.demo_url or page.visualization_embed or page.interactive_component %}
69
+ <section class="project-case__demo" aria-labelledby="demo-heading">
70
+ <h2 id="demo-heading">{% t 'project.demo.title' %}</h2>
71
+ {% if page.visualization_embed %}
72
+ <div class="project-case__embed" aria-live="polite">
73
+ {{ page.visualization_embed }}
74
+ </div>
75
+ {% endif %}
76
+ {% if page.demo_url %}
77
+ <p class="project-case__demo-link">
78
+ <a class="button" href="{{ page.demo_url }}" rel="noopener" target="_blank">{% t 'project.demo.launch' %}</a>
79
+ </p>
80
+ {% endif %}
81
+ {% if page.interactive_component %}
82
+ <div class="project-case__interactive">
83
+ {{ page.interactive_component }}
84
+ </div>
85
+ {% endif %}
86
+ </section>
87
+ {% endif %}
88
+
89
+ <div class="project-case__body">
90
+ {% if page.problem_statement %}
91
+ <section class="project-case__section" aria-labelledby="problem-heading">
92
+ <h2 id="problem-heading">{% t 'project.sections.problem_statement' %}</h2>
93
+ <div class="project-case__content">{{ page.problem_statement | markdownify }}</div>
94
+ </section>
95
+ {% endif %}
96
+
97
+ {% if page.solution_approach %}
98
+ <section class="project-case__section" aria-labelledby="solution-heading">
99
+ <h2 id="solution-heading">{% t 'project.sections.solution_approach' %}</h2>
100
+ <div class="project-case__content">{{ page.solution_approach | markdownify }}</div>
101
+ </section>
102
+ {% endif %}
103
+
104
+ {% if page.results %}
105
+ <section class="project-case__section" aria-labelledby="results-heading">
106
+ <h2 id="results-heading">{% t 'project.sections.results_findings' %}</h2>
107
+ <div class="project-case__content">{{ page.results | markdownify }}</div>
108
+ {% if page.metrics %}
109
+ <div class="project-case__metrics" role="group" aria-label="{% t 'project.metrics.aria_label' %}">
110
+ {% for metric in page.metrics %}
111
+ <figure class="project-case__metric">
112
+ <figcaption>{{ metric.label }}</figcaption>
113
+ <strong>{{ metric.value }}</strong>
114
+ {% if metric.description %}<p>{{ metric.description }}</p>{% endif %}
115
+ </figure>
116
+ {% endfor %}
117
+ </div>
118
+ {% endif %}
119
+ </section>
120
+ {% endif %}
121
+
122
+ {% if page.challenges %}
123
+ <section class="project-case__section" aria-labelledby="challenges-heading">
124
+ <h2 id="challenges-heading">{% t 'project.sections.challenges' %}</h2>
125
+ <div class="project-case__content">{{ page.challenges | markdownify }}</div>
126
+ </section>
127
+ {% endif %}
128
+
129
+ {% if page.dataset_information %}
130
+ <section class="project-case__section" aria-labelledby="dataset-heading">
131
+ <h2 id="dataset-heading">{% t 'project.sections.datasets_sources' %}</h2>
132
+ <div class="project-case__content">{{ page.dataset_information | markdownify }}</div>
133
+ {% if page.datasets %}
134
+ <ul class="project-case__datasets">
135
+ {% for dataset in page.datasets %}
136
+ <li>
137
+ {% if dataset.url %}
138
+ <a href="{{ dataset.url | relative_url }}" target="_blank" rel="noopener">{{ dataset.name | default: dataset }}</a>
139
+ {% else %}
140
+ <span>{{ dataset.name | default: dataset }}</span>
141
+ {% endif %}
142
+ {% if dataset.description %}<p>{{ dataset.description }}</p>{% endif %}
143
+ </li>
144
+ {% endfor %}
145
+ </ul>
146
+ {% endif %}
147
+ </section>
148
+ {% endif %}
149
+
150
+ {% if page.model_performance or page.model_visualizations %}
151
+ <section class="project-case__section" aria-labelledby="model-performance-heading">
152
+ <h2 id="model-performance-heading">{% t 'project.sections.model_performance' %}</h2>
153
+ {% if page.model_performance %}
154
+ <div class="project-case__content">{{ page.model_performance | markdownify }}</div>
155
+ {% endif %}
156
+ {% if page.model_visualizations %}
157
+ <div class="project-case__visualizations">
158
+ {% for viz in page.model_visualizations %}
159
+ <figure>
160
+ {{ viz.embed }}
161
+ {% if viz.caption %}<figcaption>{{ viz.caption }}</figcaption>{% endif %}
162
+ </figure>
163
+ {% endfor %}
164
+ </div>
165
+ {% endif %}
166
+ </section>
167
+ {% endif %}
168
+
169
+ {% if page.future_work %}
170
+ <section class="project-case__section" aria-labelledby="future-work-heading">
171
+ <h2 id="future-work-heading">{% t 'project.sections.future_work' %}</h2>
172
+ <div class="project-case__content">{{ page.future_work | markdownify }}</div>
173
+ </section>
174
+ {% endif %}
175
+
176
+ {% if page.collaboration %}
177
+ <section class="project-case__section" aria-labelledby="collaboration-heading">
178
+ <h2 id="collaboration-heading">{% t 'project.sections.collaboration_contributions' %}</h2>
179
+ <div class="project-case__content">{{ page.collaboration | markdownify }}</div>
180
+ {% if page.contribution_guidelines %}
181
+ <p class="project-case__contribution-link">
182
+ <a href="{{ page.contribution_guidelines }}" target="_blank" rel="noopener">{% t 'project.sections.read_guidelines' %}</a>
183
+ </p>
184
+ {% endif %}
185
+ </section>
186
+ {% endif %}
187
+
188
+ {% if page.related_projects %}
189
+ <section class="project-case__section" aria-labelledby="related-projects-heading">
190
+ <h2 id="related-projects-heading">{% t 'project.sections.related_projects' %}</h2>
191
+ <ul class="project-case__related">
192
+ {% for project in page.related_projects %}
193
+ <li>
194
+ {% if project.url %}<a href="{{ project.url | relative_url }}">{{ project.title | default: project }}</a>{% else %}<span>{{ project.title | default: project }}</span>{% endif %}
195
+ {% if project.description %}<p>{{ project.description }}</p>{% endif %}
196
+ </li>
197
+ {% endfor %}
198
+ </ul>
199
+ </section>
200
+ {% endif %}
201
+ </div>
202
+
203
+ {% if page.similar_work %}
204
+ <aside class="project-case__similar" aria-labelledby="similar-work-heading">
205
+ <h2 id="similar-work-heading">{% t 'project.sections.similar_work' %}</h2>
206
+ <div class="project-case__content">{{ page.similar_work | markdownify }}</div>
207
+ </aside>
208
+ {% endif %}
209
+
210
+ {% assign citation_key = page.citation_key | default: page.slug | default: page.title | slugify %}
211
+ {% assign citation_authors = page.authors | default: page.author | default: site.author.name %}
212
+ {% assign citation_date = page.date | default: page.last_updated %}
213
+ {% assign citation_url = page.url | absolute_url %}
214
+ {% include components/citation-tools.html title=page.title authors=citation_authors date=citation_date url=citation_url citation_key=citation_key publisher=site.author.affiliation citation_type='software' doi=page.doi %}
215
+
216
+ {% include components/open-science-badges.html %}
217
+
218
+ <footer class="project-case__footer">
219
+ {% if page.last_updated %}
220
+ <p class="project-case__updated">{% t 'project.footer.last_updated' %} <time datetime="{{ page.last_updated | date_to_xmlschema }}">{{ page.last_updated | localize_date: date_format }}</time></p>
221
+ {% endif %}
222
+ {% if page.content %}
223
+ <div class="project-case__supplemental">{{ content }}</div>
224
+ {% else %}
225
+ {{ content }}
226
+ {% endif %}
227
+ </footer>
228
+ </article>
229
+
230
+ <script nonce="{{ page.csp_nonce }}">
231
+ (function() {
232
+ const card = document.querySelector('.project-case__github-card');
233
+ if (!card) return;
234
+ const owner = card.dataset.githubOwner;
235
+ const repo = card.dataset.githubRepo;
236
+ if (!owner || !repo) return;
237
+
238
+ fetch(`https://api.github.com/repos/${owner}/${repo}`)
239
+ .then(response => response.ok ? response.json() : Promise.reject(response))
240
+ .then(data => {
241
+ card.querySelector('[data-github-stat="stargazers_count"]').textContent = data.stargazers_count;
242
+ card.querySelector('[data-github-stat="forks_count"]').textContent = data.forks_count;
243
+ card.querySelector('[data-github-stat="open_issues_count"]').textContent = data.open_issues_count;
244
+ })
245
+ .catch(() => {
246
+ const unavailable = {{ 'project.github.unavailable' | t | jsonify }};
247
+ card.querySelectorAll('[data-github-stat]').forEach(el => {
248
+ el.textContent = unavailable;
249
+ });
250
+ });
251
+ })();
252
+ </script>