pandorga 1.7.6 → 1.8.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5073aa0e7217490eff99e42008ea7eb10fdd6d5f7218d924d7ede563a77c2f4
4
- data.tar.gz: 38b8947ef41f97e1e1c6c6c2d2048fd5a16ebd1a441d4dc6ae62a9b5237582c1
3
+ metadata.gz: 120307b06e8e2a6083eb4684bef760944c0fa334155262ea202be58493e0cdd6
4
+ data.tar.gz: e17010ea2587987f38dc6236944c99c4790a380d87fcd994cc1462f5d184bee0
5
5
  SHA512:
6
- metadata.gz: 73d9948f8662e076b4f652697969ed7da53c108b336efea4b91c497310c7332f3c82f3d4ee854f6630a248c27356d219b6d50ffec70e437b098340a1153b1ee0
7
- data.tar.gz: b2dde560408d681b3cfcf3a4f5bc377542d1179b28279a19bbd065b8f7f9d04a994ebe7800a20645a18eb6562c9335d6e785f22f952d6c4b605c4f61f1ad912e
6
+ metadata.gz: ef13097e5f083e7741ecfddbce6b994b84697f1a5bc593e43cdce93bd41bab6b94da9864555a12908a419d4e4ff0836b2230085f495f30d155810bac87f0ef0b
7
+ data.tar.gz: 1c0e9c4a2e1f8d3cc6fdf3cd58103261f6271e445629e5f4e661f5d402f3525cc36fe717f1b86204640a0ffaeb25cb5171086645df15bab958781226f5fb2e75
data/CHANGELOG.md CHANGED
@@ -1,5 +1,85 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.8.4] — 2026-10-12
4
+
5
+ ### Fixed
6
+
7
+ - Metric-matched local fallbacks for Cinzel Decorative, Cinzel, Newsreader,
8
+ Ancizar Serif, and Marcellus SC so `display=swap` does not reflow the home
9
+ hero (CLS)
10
+ - Home hero `portraits.json` includes `srcset` / `sizes` when
11
+ `variants/<stem>-{480,960,1440}.jpg` exist under the hero folder
12
+
13
+ No `pandorga:` config keys were added or renamed. Font roles and the
14
+ ledger fallbacks are unchanged.
15
+
16
+ ## [1.8.3] — 2026-10-12
17
+
18
+ ### Fixed
19
+
20
+ - Detail pages load MathJax only when a TeX block nears the viewport (not on
21
+ every article open), then typeset visible blocks first; below-fold formulas
22
+ follow via IntersectionObserver with yields between blocks
23
+ - Listing layouts (articles / blog) no longer pull the MathJax script
24
+ - Long detail bodies mount in batches so the opening can paint before the
25
+ full DOM lands
26
+ - Figure and cut-in images use `decoding="async"` plus width/height hints
27
+ with existing `loading="lazy"`
28
+
29
+ No `pandorga:` config keys were added or renamed. Font roles and the
30
+ ledger fallbacks are unchanged.
31
+
32
+
33
+ ## [1.8.2] — 2026-10-12
34
+
35
+ ### Fixed
36
+
37
+ - Home listing CTAs: more space between the label and its underline
38
+ - Home index panels (Sources / Network / Bibliography): shared plate flex and
39
+ card-scale titles so the three cards align
40
+ - Articles archive: `random` sort shows **Order: random** (was falling back to
41
+ last revised)
42
+ - Listing masthead tag lines (Rascunhos, Sources, peers) use body size instead
43
+ of micro / mono
44
+ - CV contact tiles: quieter badge and name type
45
+ - Dates / meta (`--type-meta`) +2pt; content titles (`--type-title`) +10%;
46
+ top-bar wordmark +25%
47
+ - Theorem / proof heads sit inline with the opening sentence (no line break
48
+ after the name / **Proof.**)
49
+ - Proof QED square (`□`) is +2pt over the body size
50
+
51
+ No `pandorga:` config keys were added or renamed. Font roles and the
52
+ ledger fallbacks are unchanged.
53
+
54
+ ## [1.8.1] — 2026-10-12
55
+
56
+ ### Fixed
57
+
58
+ - Theorems are bare (no gray plate); only proofs carry the surface band
59
+ - Proof gray bleeds to the edges of `.text-page-main` while body text stays
60
+ on the reading measure
61
+ - Proofs prefix **Proof.** from the runtime, like theorem heads — authors
62
+ no longer write `*Proof.*` in the body
63
+
64
+ No `pandorga:` config keys were added or renamed. Font roles and the
65
+ ledger fallbacks are unchanged.
66
+
67
+ ## [1.8.0] — 2026-10-12
68
+
69
+ ### Added
70
+
71
+ - Theorem and proof Markdown wrappers in the content runtime, same
72
+ `markdown="1"` contract as `article-note` and `poem`:
73
+ - `<div class="theorem" name="Corollary 1" description="nadir camera" markdown="1">…</div>`
74
+ builds the head from `name` / `description` and italicizes the body
75
+ - `<div class="proof" markdown="1">…</div>` wraps a proof and places a
76
+ QED square on the last line
77
+ - Full-column styles for `.theorem` and `.proof` in `article.css`
78
+ - Render-parity fixtures `theorem.json` and `proof.json`
79
+
80
+ No `pandorga:` config keys were added or renamed. Font roles and the
81
+ ledger fallbacks are unchanged.
82
+
3
83
  ## [1.7.6] — 2026-10-12
4
84
 
5
85
  ### Fixed
data/DESIGN.md CHANGED
@@ -779,12 +779,12 @@ those tokens — no one-off rem sizes for roles that already have a step.
779
779
  | `--type-display-lg` | 3.6515 | ~64 | display-hero |
780
780
  | `--type-page-lg` | 2.9015 | ~51 | headline-lg |
781
781
  | `--type-section-lg` | 2.1515 | ~38 | headline-md |
782
- | `--type-title-lg` | 2.4015 | ~42 | detail H1 |
782
+ | `--type-title-lg` | 2.64165 | ~46 | detail H1 |
783
783
  | `--type-card` | 1.4015 | ~25 | headline-sm |
784
784
  | `--type-prose` | 1.2 | ~21.1 | reading body, under 1024px |
785
785
  | `--type-prose` (≥1024px) | 1.25 | ~22 | reading body, desktop |
786
786
  | `--type-body` | 1.0758 | ~19 | body-md |
787
- | `--type-meta` | 0.8125 | ~14 | body-sm |
787
+ | `--type-meta` | calc(0.8125rem + 2pt) | ~16 | body-sm |
788
788
  | `--type-label` | 0.8485 | ~15 | label-md |
789
789
  | `--type-mono` | 0.8504 | ~15 | notation |
790
790
 
@@ -109,6 +109,43 @@
109
109
  return parts.join('');
110
110
  }
111
111
 
112
+ function attrValue(openTag, name) {
113
+ var re = new RegExp('\\b' + name + '\\s*=\\s*(?:"([^"]*)"|\'([^\']*)\')', 'i');
114
+ var m = String(openTag || '').match(re);
115
+ return m ? String(m[1] != null ? m[1] : m[2] || '') : '';
116
+ }
117
+
118
+ /* Put the head inside the first block so "Corollary 1." / "Proof." sit on
119
+ the same line as the opening sentence (paper style), not above it. */
120
+ function prependInlineHead(html, head) {
121
+ var body = String(html || '');
122
+ var m = body.match(/^(\s*<p\b[^>]*>)/i);
123
+ if (m) return body.replace(m[1], m[1] + head);
124
+ return head + body;
125
+ }
126
+
127
+ function renderTheoremHtml(openTag, inner) {
128
+ var name = attrValue(openTag, 'name').trim();
129
+ var description = attrValue(openTag, 'description').trim();
130
+ var head = '';
131
+ if (name) {
132
+ head = '<span class="theorem-head"><span class="theorem-name">' + escapeHtml(name) + '</span>';
133
+ if (description) {
134
+ head += ' <span class="theorem-description">(' + escapeHtml(description) + ')</span>';
135
+ }
136
+ head += '. </span>';
137
+ }
138
+ var body = parseInlineMarkdown(inner);
139
+ if (head) body = prependInlineHead(body, head);
140
+ return '<div class="theorem"><div class="theorem-body">' + body + '</div></div>';
141
+ }
142
+
143
+ function renderProofHtml(inner) {
144
+ var head = '<span class="proof-head"><span class="proof-name">Proof</span>. </span>';
145
+ var body = prependInlineHead(parseInlineMarkdown(inner), head);
146
+ return '<div class="proof"><div class="proof-body">' + body + '</div></div>';
147
+ }
148
+
112
149
  function preprocessMarkdownDivs(markdown) {
113
150
  var text = String(markdown || '');
114
151
  text = text.replace(/<div\s+([^>]*class="[^"]*\barticle-note\b[^"]*"[^>]*)>([\s\S]*?)<\/div>/gi, function (match, openTag, inner) {
@@ -119,6 +156,16 @@
119
156
  if (!hasMarkdownAttr(openTag)) return match;
120
157
  return renderPoemHtml(inner);
121
158
  });
159
+ // Theorem / proof: same markdown="1" contract as article-note. Without
160
+ // this pass, CommonMark ends the HTML block at the first blank line.
161
+ text = text.replace(/<div\s+([^>]*class="[^"]*\btheorem\b[^"]*"[^>]*)>([\s\S]*?)<\/div>/gi, function (match, openTag, inner) {
162
+ if (!hasMarkdownAttr(openTag)) return match;
163
+ return renderTheoremHtml(openTag, inner);
164
+ });
165
+ text = text.replace(/<div\s+([^>]*class="[^"]*\bproof\b[^"]*"[^>]*)>([\s\S]*?)<\/div>/gi, function (match, openTag, inner) {
166
+ if (!hasMarkdownAttr(openTag)) return match;
167
+ return renderProofHtml(inner);
168
+ });
122
169
  return text;
123
170
  }
124
171
 
@@ -149,7 +196,104 @@
149
196
  return html;
150
197
  }
151
198
 
199
+ var MATHJAX_SRC = 'https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-mml-chtml.js';
200
+ var mathJaxLoadPromise = null;
201
+
202
+ function rootHasTeX(root) {
203
+ var text = (root && root.textContent) || '';
204
+ return text.indexOf('$') !== -1 || text.indexOf('\\(') !== -1 || text.indexOf('\\[') !== -1;
205
+ }
206
+
207
+ function elementNeedsMath(el) {
208
+ if (!el || el.nodeType !== 1) return false;
209
+ if (el.getAttribute('data-math-done') === '1') return false;
210
+ if (el.querySelector && el.querySelector('mjx-container')) return false;
211
+ return rootHasTeX(el);
212
+ }
213
+
214
+ function collectMathBlocks(root) {
215
+ var out = [];
216
+ if (!root) return out;
217
+ Array.prototype.forEach.call(root.children || [], function (el) {
218
+ if (elementNeedsMath(el)) out.push(el);
219
+ });
220
+ return out;
221
+ }
222
+
223
+ function blockNearViewport(el, marginPx) {
224
+ var rect = el.getBoundingClientRect();
225
+ var vh = window.innerHeight || 800;
226
+ var margin = typeof marginPx === 'number' ? marginPx : vh;
227
+ return rect.top < vh + margin && rect.bottom > -margin;
228
+ }
229
+
230
+ function yieldToMain() {
231
+ return new Promise(function (resolve) {
232
+ if (typeof window.requestIdleCallback === 'function') {
233
+ window.requestIdleCallback(function () { resolve(); }, { timeout: 48 });
234
+ } else {
235
+ window.setTimeout(resolve, 0);
236
+ }
237
+ });
238
+ }
239
+
240
+ function ensureMathJaxConfig() {
241
+ if (window.MathJax && window.MathJax.tex) return;
242
+ window.MathJax = {
243
+ startup: { typeset: false },
244
+ tex: {
245
+ inlineMath: [['$', '$'], ['\\(', '\\)']],
246
+ displayMath: [['$$', '$$'], ['\\[', '\\]']]
247
+ }
248
+ };
249
+ }
250
+
251
+ function loadMathJaxScript() {
252
+ if (mathJaxLoadPromise) return mathJaxLoadPromise;
253
+ ensureMathJaxConfig();
254
+ if (window.MathJax && typeof window.MathJax.typesetPromise === 'function') {
255
+ mathJaxLoadPromise = Promise.resolve(window.MathJax);
256
+ return mathJaxLoadPromise;
257
+ }
258
+ mathJaxLoadPromise = new Promise(function (resolve, reject) {
259
+ var existing = document.getElementById('MathJax-script') ||
260
+ document.querySelector('script[src*="mathjax"][src*="tex-mml-chtml"]');
261
+ if (existing) {
262
+ if (window.MathJax && typeof window.MathJax.typesetPromise === 'function') {
263
+ resolve(window.MathJax);
264
+ return;
265
+ }
266
+ existing.addEventListener('load', function () { resolve(window.MathJax); });
267
+ existing.addEventListener('error', reject);
268
+ return;
269
+ }
270
+ var script = document.createElement('script');
271
+ script.id = 'MathJax-script';
272
+ script.src = MATHJAX_SRC;
273
+ script.async = true;
274
+ script.onload = function () {
275
+ var mj = window.MathJax;
276
+ if (mj && mj.startup && mj.startup.promise) {
277
+ mj.startup.promise.then(function () { resolve(mj); }).catch(reject);
278
+ } else {
279
+ resolve(mj);
280
+ }
281
+ };
282
+ script.onerror = function () {
283
+ reject(new Error('MathJax failed to load'));
284
+ };
285
+ document.head.appendChild(script);
286
+ });
287
+ return mathJaxLoadPromise;
288
+ }
289
+
152
290
  async function waitForMathJax() {
291
+ try {
292
+ await loadMathJaxScript();
293
+ } catch (error) {
294
+ console.error(error);
295
+ return false;
296
+ }
153
297
  var mj = window.MathJax;
154
298
  if (!mj) return false;
155
299
  if (mj.startup && mj.startup.promise) {
@@ -163,10 +307,89 @@
163
307
  return typeof mj.typesetPromise === 'function';
164
308
  }
165
309
 
310
+ async function typesetMathBlock(el) {
311
+ if (!el || el.getAttribute('data-math-done') === '1') return;
312
+ if (el.querySelector && el.querySelector('mjx-container')) {
313
+ el.setAttribute('data-math-done', '1');
314
+ return;
315
+ }
316
+ if (!rootHasTeX(el)) return;
317
+ if (!(await waitForMathJax())) return;
318
+ try {
319
+ await window.MathJax.typesetPromise([el]);
320
+ } catch (error) {
321
+ console.error(error);
322
+ return;
323
+ }
324
+ el.setAttribute('data-math-done', '1');
325
+ }
326
+
327
+ function observeMathBlocks(blocks) {
328
+ if (!blocks.length) return;
329
+ if (typeof IntersectionObserver === 'undefined') {
330
+ (async function () {
331
+ for (var i = 0; i < blocks.length; i++) {
332
+ await yieldToMain();
333
+ await typesetMathBlock(blocks[i]);
334
+ }
335
+ })();
336
+ return;
337
+ }
338
+ var queue = [];
339
+ var draining = false;
340
+
341
+ async function drain() {
342
+ if (draining) return;
343
+ draining = true;
344
+ while (queue.length) {
345
+ var el = queue.shift();
346
+ await typesetMathBlock(el);
347
+ if (queue.length) await yieldToMain();
348
+ }
349
+ draining = false;
350
+ }
351
+
352
+ var observer = new IntersectionObserver(function (entries) {
353
+ entries.forEach(function (entry) {
354
+ if (!entry.isIntersecting) return;
355
+ observer.unobserve(entry.target);
356
+ queue.push(entry.target);
357
+ drain();
358
+ });
359
+ }, { root: null, rootMargin: '120% 0px', threshold: 0 });
360
+
361
+ blocks.forEach(function (el) { observer.observe(el); });
362
+ }
363
+
364
+ /* Load MathJax only when a TeX block is near the viewport. Typeset those
365
+ blocks first, then the rest as the reader scrolls. Returns after the
366
+ visible pass so the detail shell can paint without waiting on below-fold
367
+ formulas. */
166
368
  async function typesetMath(root) {
167
369
  try {
168
- if (!(await waitForMathJax())) return;
169
- await window.MathJax.typesetPromise(root ? [root] : undefined);
370
+ if (!root || !rootHasTeX(root)) return;
371
+
372
+ var blocks = collectMathBlocks(root);
373
+ if (!blocks.length) {
374
+ if (!(await waitForMathJax())) return;
375
+ await window.MathJax.typesetPromise([root]);
376
+ return;
377
+ }
378
+
379
+ var immediate = [];
380
+ var deferred = [];
381
+ var margin = (window.innerHeight || 800) * 1.25;
382
+ blocks.forEach(function (el) {
383
+ if (blockNearViewport(el, margin)) immediate.push(el);
384
+ else deferred.push(el);
385
+ });
386
+
387
+ for (var i = 0; i < immediate.length; i++) {
388
+ await typesetMathBlock(immediate[i]);
389
+ if (i < immediate.length - 1) await yieldToMain();
390
+ }
391
+
392
+ observeMathBlocks(deferred);
170
393
  } catch (error) {
171
394
  console.error(error);
172
395
  }
@@ -26,7 +26,7 @@
26
26
  var src = resolveAssetUrl(attrs.src);
27
27
  var sideClass = attrs.side === 'right' ? ' article-cut-in--right' : '';
28
28
  return '<figure class="article-cut-in-plate' + sideClass + '">' +
29
- '<img class="article-cut-in' + sideClass + '" src="' + escapeAttr(src) + '" alt="' + escapeAttr(attrs.alt || '') + '" width="120" loading="lazy">' +
29
+ '<img class="article-cut-in' + sideClass + '" src="' + escapeAttr(src) + '" alt="' + escapeAttr(attrs.alt || '') + '" width="120" loading="lazy" decoding="async">' +
30
30
  '</figure>';
31
31
  }
32
32
 
@@ -36,9 +36,12 @@
36
36
  var caption = attrs.caption || '';
37
37
  var lbCaption = figureLightboxCaption(caption, attrs.source);
38
38
  var lbAttr = lbCaption ? ' data-lightbox-caption="' + escapeAttr(lbCaption) + '"' : '';
39
+ /* Reserve a 16:9 box until the file paints. Diagrams vary; a stable
40
+ ratio beats an unsized image for CLS on long articles. */
41
+ var sizeAttr = ' width="960" height="540"';
39
42
  return [
40
43
  '<figure class="my-8 mx-auto text-center w-full" style="display:block;margin-left:auto;margin-right:auto;' + width + '">',
41
- '<img src="' + escapeAttr(src) + '" alt="' + escapeAttr(attrs.alt || caption) + '" class="figure-lightbox-trigger mx-auto max-w-full h-auto block" loading="lazy" tabindex="0" role="button" aria-label="Enlarge image"' + lbAttr + '>',
44
+ '<img src="' + escapeAttr(src) + '" alt="' + escapeAttr(attrs.alt || caption) + '" class="figure-lightbox-trigger mx-auto max-w-full h-auto block" loading="lazy" decoding="async"' + sizeAttr + ' tabindex="0" role="button" aria-label="Enlarge image"' + lbAttr + '>',
42
45
  caption || attrs.source ? '<figcaption class="mt-3 font-body">' + includeCaptionHtml(caption) + figureSourceHtml(attrs.source) + '</figcaption>' : '',
43
46
  '</figure>'
44
47
  ].join('');
@@ -813,12 +813,42 @@
813
813
 
814
814
  var body = document.createElement('div');
815
815
  body.className = 'article-content';
816
- body.innerHTML = await renderRichContent(options.bodyMarkdown, fm);
817
- applyDropCapClass(body, fm);
816
+ /* Mount the opening blocks first so title-adjacent prose can paint before
817
+ the rest of a long article lands in the DOM (mobile LCP / TBT). */
818
+ var staging = document.createElement('div');
819
+ staging.innerHTML = await renderRichContent(options.bodyMarkdown, fm);
820
+ var nodes = Array.prototype.slice.call(staging.childNodes);
818
821
  section.appendChild(body);
819
-
820
822
  if (options.mainColumn) options.mainColumn.appendChild(section);
821
823
 
824
+ var firstBatch = 6;
825
+ var batchSize = 12;
826
+ var ni = 0;
827
+ for (; ni < nodes.length && ni < firstBatch; ni++) {
828
+ body.appendChild(nodes[ni]);
829
+ }
830
+ applyDropCapClass(body, fm);
831
+ await new Promise(function (resolve) {
832
+ if (typeof window.requestAnimationFrame === 'function') {
833
+ window.requestAnimationFrame(function () { window.setTimeout(resolve, 0); });
834
+ } else {
835
+ window.setTimeout(resolve, 0);
836
+ }
837
+ });
838
+ while (ni < nodes.length) {
839
+ var end = Math.min(ni + batchSize, nodes.length);
840
+ for (; ni < end; ni++) body.appendChild(nodes[ni]);
841
+ if (ni < nodes.length) {
842
+ await new Promise(function (resolve) {
843
+ if (typeof window.requestIdleCallback === 'function') {
844
+ window.requestIdleCallback(function () { resolve(); }, { timeout: 48 });
845
+ } else {
846
+ window.setTimeout(resolve, 0);
847
+ }
848
+ });
849
+ }
850
+ }
851
+
822
852
  await enhanceArticleContent(body, fm);
823
853
 
824
854
  assignHeadingIds(body);
@@ -885,11 +915,14 @@
885
915
 
886
916
  async function renderRichContent(markdown, frontMatter) {
887
917
  var fm = frontMatter || {};
888
- var index = await ensureXciteIndex();
889
- var bibIndex = await ensureBibliographyIndex();
918
+ /* Kick index fetches immediately so they overlap with front-matter strip. */
919
+ var indexPromise = ensureXciteIndex();
920
+ var bibPromise = ensureBibliographyIndex();
890
921
  var assignPrep = preprocessTranscriptionAssigns(stripFrontMatter(markdown));
891
922
  var prepared = assignPrep.text;
892
923
  var transcriptionAssigns = assignPrep.assignMap;
924
+ var index = await indexPromise;
925
+ var bibIndex = await bibPromise;
893
926
  var replacements = [];
894
927
  prepared = prepared.replace(/\{%-?\s*include\s+transcription\.html\s+([\s\S]*?)-?%\}/g, function (match, raw) {
895
928
  var replacementIndex = replacements.length;
@@ -31,7 +31,7 @@
31
31
  {%- else -%}
32
32
  <figure class="home-index-plate" data-content-band-art="{{ _art_key }}" hidden></figure>
33
33
  {%- endif -%}
34
- <h3 class="section-title home-index-title">{{ _panel_title }}</h3>
34
+ <h3 class="home-index-title">{{ _panel_title }}</h3>
35
35
  <a class="ledger-cta" href="{{ _panel_path }}">
36
36
  <span>Open</span>
37
37
  <span class="material-symbols-outlined ledger-cta-arrow" aria-hidden="true">chevron_right</span>
@@ -1,10 +1,32 @@
1
- <!-- MathJax configuration for Jekyll with inline math support -->
1
+ <!-- MathJax: config only. The content runtime loads the library on demand when
2
+ a detail page actually contains TeX, then typesets visible blocks first. -->
2
3
  <script>
3
4
  window.MathJax = {
4
- tex: {
5
- inlineMath: [['$', '$'], ['\\(', '\\)']],
6
- displayMath: [['$$', '$$'], ['\\[', '\\]']]
7
- }
5
+ startup: { typeset: false },
6
+ tex: {
7
+ inlineMath: [['$', '$'], ['\\(', '\\)']],
8
+ displayMath: [['$$', '$$'], ['\\[', '\\]']]
9
+ }
8
10
  };
11
+
12
+ /* Studio / private text layout: typeset once the runtime exists. Public detail
13
+ pages call typesetMath from enhanceArticleContent instead. */
14
+ (function () {
15
+ function runStaticMath() {
16
+ var root = document.querySelector('.article-content');
17
+ if (!root || !window.ContentRuntime || typeof window.ContentRuntime.typesetMath !== 'function') {
18
+ return;
19
+ }
20
+ var text = root.textContent || '';
21
+ if (text.indexOf('$') === -1 && text.indexOf('\\(') === -1 && text.indexOf('\\[') === -1) {
22
+ return;
23
+ }
24
+ window.ContentRuntime.typesetMath(root);
25
+ }
26
+ if (window.ContentRuntime) {
27
+ runStaticMath();
28
+ } else {
29
+ document.addEventListener('pandorga:runtime', runStaticMath, { once: true });
30
+ }
31
+ })();
9
32
  </script>
10
- <script src="https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-mml-chtml.js"></script>
@@ -126,8 +126,6 @@ layout: shell
126
126
  </div>
127
127
 
128
128
  </main>
129
- {% include mathjax.html %}
130
-
131
129
  <script>
132
130
  (function bootArticles() {
133
131
  'use strict';
@@ -168,7 +166,8 @@ layout: shell
168
166
 
169
167
  var SORT_LABELS = {
170
168
  updated: 'Order: last revised',
171
- published: 'Order: date published'
169
+ published: 'Order: date published',
170
+ random: 'Order: random'
172
171
  };
173
172
 
174
173
  var allArticles = [];
data/_layouts/blog.html CHANGED
@@ -90,8 +90,6 @@ layout: shell
90
90
  </div>
91
91
 
92
92
  </main>
93
- {% include mathjax.html %}
94
-
95
93
  <script>
96
94
  (function bootBlog() {
97
95
  'use strict';
@@ -1007,6 +1007,97 @@ a.xcite-ref:hover {
1007
1007
  margin-bottom: 0;
1008
1008
  }
1009
1009
 
1010
+ /* ── Theorem and proof blocks ──
1011
+ Authored as `<div class="theorem" name="…" description="…" markdown="1">`
1012
+ and `<div class="proof" markdown="1">`. Theorems are bare (no plate): the
1013
+ runtime builds the head from name / description and italicizes the body.
1014
+ Proofs sit on a gray band that bleeds to the edges of `.text-page-main`
1015
+ while the text keeps the body measure; the runtime prefixes "Proof." into
1016
+ the first paragraph (same line as the opening sentence) and a QED square
1017
+ lands on the last line of the body. */
1018
+ .article-content .theorem {
1019
+ display: block;
1020
+ width: 100%;
1021
+ box-sizing: border-box;
1022
+ margin: 1.5em 0;
1023
+ font-size: inherit;
1024
+ line-height: inherit;
1025
+ color: var(--color-on-surface);
1026
+ }
1027
+
1028
+ .article-content .theorem-head,
1029
+ .article-content .proof-head {
1030
+ font-style: normal;
1031
+ font-weight: var(--weight-body);
1032
+ }
1033
+
1034
+ .article-content .theorem-name,
1035
+ .article-content .proof-name {
1036
+ font-weight: var(--weight-emphasis);
1037
+ font-style: normal;
1038
+ }
1039
+
1040
+ .article-content .theorem-description {
1041
+ font-weight: var(--weight-body);
1042
+ font-style: normal;
1043
+ }
1044
+
1045
+ .article-content .theorem-body {
1046
+ font-style: italic;
1047
+ }
1048
+
1049
+ /* Head is injected into the first paragraph; keep it upright against the
1050
+ italic theorem body. */
1051
+ .article-content .theorem-body .theorem-head {
1052
+ font-style: normal;
1053
+ }
1054
+
1055
+ .article-content .theorem-body > :first-child,
1056
+ .article-content .proof-body > :first-child {
1057
+ margin-top: 0;
1058
+ }
1059
+
1060
+ .article-content .theorem-body > :last-child,
1061
+ .article-content .proof-body > :last-child {
1062
+ margin-bottom: 0;
1063
+ }
1064
+
1065
+ .article-content .proof {
1066
+ display: flow-root;
1067
+ box-sizing: border-box;
1068
+ position: relative;
1069
+ width: auto;
1070
+ max-width: none;
1071
+ margin: 1.5em calc(-1 * var(--space-md));
1072
+ padding: 1rem var(--space-md);
1073
+ background: var(--color-surface-container-low);
1074
+ font-size: inherit;
1075
+ line-height: inherit;
1076
+ color: var(--color-on-surface);
1077
+ -webkit-print-color-adjust: exact;
1078
+ print-color-adjust: exact;
1079
+ }
1080
+
1081
+ @media (min-width: 768px) {
1082
+ .article-content .proof {
1083
+ margin-left: calc(-1 * var(--space-2xl));
1084
+ margin-right: calc(-1 * var(--space-2xl));
1085
+ padding-left: var(--space-2xl);
1086
+ padding-right: var(--space-2xl);
1087
+ }
1088
+ }
1089
+
1090
+ .article-content .proof-body > :last-child::after {
1091
+ content: "□";
1092
+ float: right;
1093
+ margin-left: 0.35em;
1094
+ font-size: calc(1em + 2pt);
1095
+ line-height: inherit;
1096
+ color: var(--color-on-surface);
1097
+ pointer-events: none;
1098
+ user-select: none;
1099
+ }
1100
+
1010
1101
  /* ══ Code blocks ══
1011
1102
  A fence used to render as a bare grey box: no indication of what language
1012
1103
  was in it, and nothing to do with it but select the text by hand. It now
data/assets/css/base.css CHANGED
@@ -1,3 +1,53 @@
1
+ /* ── Metric-matched fallbacks (font-display: swap) ──
2
+ Local Times/Liberation faces scaled to the web fonts that paint the
3
+ home hero before the Google kit arrives. Without these, swap CLS on
4
+ home is ~0.15. Metrics from UPM-normalised average glyph width vs
5
+ Liberation Serif Regular (Times New Roman metric twin). */
6
+ @font-face {
7
+ font-family: "Pandorga Title Fallback";
8
+ src: local("Times New Roman"), local("Times"), local("Liberation Serif");
9
+ size-adjust: 148.98%;
10
+ ascent-override: 65.51%;
11
+ descent-override: 24.97%;
12
+ line-gap-override: 0%;
13
+ }
14
+
15
+ @font-face {
16
+ font-family: "Pandorga Cinzel Fallback";
17
+ src: local("Times New Roman"), local("Times"), local("Liberation Serif");
18
+ size-adjust: 139.21%;
19
+ ascent-override: 70.11%;
20
+ descent-override: 26.72%;
21
+ line-gap-override: 0%;
22
+ }
23
+
24
+ @font-face {
25
+ font-family: "Pandorga Body Fallback";
26
+ src: local("Times New Roman"), local("Times"), local("Liberation Serif");
27
+ size-adjust: 103.26%;
28
+ ascent-override: 71.18%;
29
+ descent-override: 25.66%;
30
+ line-gap-override: 0%;
31
+ }
32
+
33
+ @font-face {
34
+ font-family: "Pandorga Ancizar Fallback";
35
+ src: local("Times New Roman"), local("Times"), local("Liberation Serif");
36
+ size-adjust: 101.28%;
37
+ ascent-override: 88.86%;
38
+ descent-override: 29.62%;
39
+ line-gap-override: 0%;
40
+ }
41
+
42
+ @font-face {
43
+ font-family: "Pandorga Label Fallback";
44
+ src: local("Times New Roman"), local("Times"), local("Liberation Serif");
45
+ size-adjust: 112.98%;
46
+ ascent-override: 86.22%;
47
+ descent-override: 24.76%;
48
+ line-gap-override: 0%;
49
+ }
50
+
1
51
  /* ── Material Symbols ── */
2
52
  /* Doubled class so this beats the Google stylesheet, which arrives later
3
53
  and sets font-size: 24px. That late size change moves the hero. */
@@ -122,8 +172,8 @@ html {
122
172
  --type-section-lg: 2.1515rem;
123
173
 
124
174
  /* Content titles — Newsreader Regular 400 */
125
- --type-title: 2.0015rem;
126
- --type-title-lg: 2.4015rem;
175
+ --type-title: 2.20165rem;
176
+ --type-title-lg: 2.64165rem;
127
177
  --type-card: 1.4015rem;
128
178
  --type-card-lg: 1.5515rem;
129
179
 
@@ -140,7 +190,8 @@ html {
140
190
  /* Card/row blurbs, standfirsts, project/source copy — same as body. */
141
191
  --type-subtitle: var(--type-body);
142
192
  --type-dek: 0.9375rem;
143
- --type-meta: 0.8125rem;
193
+ /* Dates, kickers, quiet meta — +2pt over the prior 0.8125rem step. */
194
+ --type-meta: calc(0.8125rem + 2pt);
144
195
 
145
196
  /* Chrome — quiet relative to body (tuned for Marcellus SC cap-height to match icons) */
146
197
  --type-label: 0.8485rem;
@@ -405,10 +456,13 @@ html body {
405
456
  font-variant-numeric: tabular-nums;
406
457
  }
407
458
 
408
- /* Masthead tag line under the page H1. */
459
+ /* Masthead tag line under the page H1. Body size so "Channels. Sites." /
460
+ "Blog pessoal. …" reads with the standfirst, not as micro-label chrome.
461
+ Beats `.code-technical` when a listing also puts that class on the span. */
409
462
  [data-content-header-tags],
410
- .page-header-tags {
411
- font-size: var(--type-dek);
463
+ .page-header-tags,
464
+ [data-content-header-tags].code-technical {
465
+ font-size: var(--type-body);
412
466
  line-height: 1.45;
413
467
  letter-spacing: 0.04em;
414
468
  }
@@ -950,7 +1004,7 @@ a.ledger-chip:hover {
950
1004
  text-decoration: underline;
951
1005
  text-decoration-color: color-mix(in srgb, var(--color-primary) 50%, transparent);
952
1006
  text-decoration-thickness: 1.5px;
953
- text-underline-offset: 0.28em;
1007
+ text-underline-offset: 0.48em;
954
1008
  text-underline-position: under;
955
1009
  }
956
1010
 
@@ -43,7 +43,7 @@
43
43
  .site-wordmark {
44
44
  display: none;
45
45
  font-family: var(--font-title);
46
- font-size: var(--type-dek);
46
+ font-size: calc(var(--type-dek) * 1.25);
47
47
  line-height: 1;
48
48
  white-space: nowrap;
49
49
  }
@@ -137,6 +137,7 @@ html[data-theme="light"] .cv-product {
137
137
  margin-top: var(--space-md);
138
138
  color: var(--color-on-surface-variant);
139
139
  letter-spacing: 0.06em;
140
+ font-size: var(--type-body);
140
141
  }
141
142
 
142
143
  .cv-dossier-standfirst {
@@ -219,7 +220,19 @@ html[data-theme="light"] .cv-product {
219
220
  }
220
221
 
221
222
  .cv-contacts .group .material-symbols-outlined {
222
- font-size: var(--type-card);
223
+ font-size: var(--type-label-lg);
224
+ }
225
+
226
+ /* Badge (Network / Code) and name (LinkedIn / GitHub) were inheriting body /
227
+ nav sizes and reading louder than the dossier. Quiet label register. */
228
+ .cv-contacts .group .marginalia {
229
+ font-size: var(--type-meta);
230
+ letter-spacing: 0.06em;
231
+ }
232
+
233
+ .cv-contacts .group .font-label {
234
+ font-size: var(--type-label);
235
+ letter-spacing: 0.03em;
223
236
  }
224
237
 
225
238
  /* ── Section register ──────────────────────────────────────────────────
@@ -818,9 +818,10 @@ aside:has(> .text-detail-sidebar) {
818
818
  font-weight: inherit;
819
819
  }
820
820
 
821
- /* ── Theorem / proof / key-takeaways callout ──
822
- Authored per article as `<div class="article-note" markdown="1">`; styled
823
- here, never injected. Ochre wash + tertiary spine = scannable field note. */
821
+ /* ── Field-note callout ──
822
+ Authored as `<div class="article-note" markdown="1">`; styled here, never
823
+ injected. Ochre wash + tertiary spine = scannable field note. Formal
824
+ theorem / proof blocks use `.theorem` / `.proof` in article.css instead. */
824
825
  .article-content .article-note {
825
826
  position: relative;
826
827
  background: color-mix(in srgb, var(--color-tertiary) 10%, var(--color-panel-contrast));
@@ -487,6 +487,7 @@ html[data-theme="light"] .home-hero-plate {
487
487
  vertical tick so each band reads as a ruled plate rather than a soft fade. */
488
488
  .home-band:not(.home-band--hero) .ledger-section-banner {
489
489
  padding-top: var(--space-lg);
490
+ padding-bottom: var(--space-sm);
490
491
  border-top: 1px solid var(--border-gentle);
491
492
  box-shadow: 0 4px 0 -3px var(--border-gentle-faint);
492
493
  position: relative;
@@ -1674,20 +1675,28 @@ button.home-project-inventory-row {
1674
1675
  margin-inline: var(--space-lg);
1675
1676
  }
1676
1677
 
1678
+ /* Card-scale title, not `.section-title`: that face is for band banners and
1679
+ was oversized once the three index panels sat in one row. Locked here so
1680
+ Sources / Network / Bibliography share one title register. */
1677
1681
  .home-index-title {
1678
1682
  margin: 0;
1683
+ font-family: var(--font-title);
1679
1684
  font-size: var(--type-card);
1685
+ font-weight: var(--weight-display);
1686
+ line-height: 1.2;
1687
+ color: var(--color-on-surface);
1688
+ font-synthesis: none;
1689
+ font-optical-sizing: auto;
1680
1690
  }
1681
1691
 
1682
- @media (min-width: 1024px) {
1683
- .home-index-title {
1684
- font-size: var(--type-card-lg);
1685
- }
1686
- }
1687
-
1688
- /* ── The plate ── */
1692
+ /* ── The plate ──
1693
+ Every panel grows its plate so the title + Open row lands on one baseline
1694
+ across Sources, Network, and Bibliography — the bib plate already flexed;
1695
+ the other two used to sit shorter and leave their titles higher. */
1689
1696
  .home-index-plate {
1690
1697
  align-self: stretch;
1698
+ flex: 1 1 auto;
1699
+ min-height: 0;
1691
1700
  margin: 0;
1692
1701
  background: var(--color-background);
1693
1702
  border-bottom: 1px solid var(--border-gentle-subtle);
@@ -1952,8 +1961,6 @@ html[data-theme="light"] .home-featured .ledger-poster {
1952
1961
  #home-bib-panel .home-index-plate {
1953
1962
  display: flex;
1954
1963
  flex-direction: column;
1955
- flex: 1 1 auto;
1956
- min-height: 0;
1957
1964
  }
1958
1965
 
1959
1966
  #home-bib-panel .home-index-plate-head {
@@ -58,6 +58,7 @@
58
58
  is set in. */
59
59
  color: var(--color-primary);
60
60
  letter-spacing: 0.06em;
61
+ font-size: var(--type-body);
61
62
  }
62
63
 
63
64
  .projects-page .projects-state {
@@ -37,6 +37,7 @@
37
37
  the tag line should follow whatever `.page-accent--*` sets. */
38
38
  color: var(--color-primary);
39
39
  letter-spacing: 0.06em;
40
+ font-size: var(--type-body);
40
41
  }
41
42
 
42
43
  /* ══ Featured panel ══
@@ -24,7 +24,7 @@ module Pandorga
24
24
 
25
25
  gem "pandorga", "~> 1.3"
26
26
  # Local checkout: gem "pandorga", path: "../pandorga"
27
- # GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v1.7.6"
27
+ # GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v1.8.4"
28
28
  gem "csv"
29
29
  gem "base64"
30
30
  RUBY
@@ -15,6 +15,15 @@ module Pandorga
15
15
  FOLLOW = { subtitle: :body, code: :mono, cv_body: :body }.freeze
16
16
  MONO_FALLBACK = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, monospace"
17
17
  FAMILY_NAME = /\A[\p{L}\p{N}][\p{L}\p{N} .+\-]*\z/
18
+ # Named faces defined in assets/css/base.css with size-adjust / ascent
19
+ # overrides so display=swap does not reflow the hero (home CLS).
20
+ METRIC_FALLBACK = {
21
+ "Cinzel Decorative" => "Pandorga Title Fallback",
22
+ "Cinzel" => "Pandorga Cinzel Fallback",
23
+ "Newsreader" => "Pandorga Body Fallback",
24
+ "Ancizar Serif" => "Pandorga Ancizar Fallback",
25
+ "Marcellus SC" => "Pandorga Label Fallback"
26
+ }.freeze
18
27
 
19
28
  DEFAULTS = {
20
29
  title: { family: "Cinzel Decorative", spec: "wght@400;700;900", fallback: "serif" },
@@ -78,8 +87,14 @@ module Pandorga
78
87
  face = resolved[role.to_sym]
79
88
  return "" unless face
80
89
 
81
- quoted = face[:family].to_s.empty? ? nil : "'#{escape_css(face[:family])}'"
82
- [quoted, face[:fallback].to_s.strip].compact.reject(&:empty?).join(", ")
90
+ family = face[:family].to_s
91
+ parts = []
92
+ parts << "'#{escape_css(family)}'" unless family.empty?
93
+ metric = METRIC_FALLBACK[family]
94
+ parts << "'#{escape_css(metric)}'" if metric
95
+ fallback = face[:fallback].to_s.strip
96
+ parts << fallback unless fallback.empty?
97
+ parts.join(", ")
83
98
  end
84
99
 
85
100
  def resolve_role(role, override, theme)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pandorga
4
- VERSION = "1.7.6"
4
+ VERSION = "1.8.4"
5
5
  end
@@ -3,17 +3,31 @@
3
3
  # The home hero pool is the folder. The content export writes the public
4
4
  # paths to pages/home/portraits.json. The author uploads a photograph under
5
5
  # content/media/images/hero/ and does not keep a second list.
6
+ # Optional width variants live under variants/ so they are not extra draws.
6
7
 
7
8
  require "pathname"
8
9
 
9
10
  module HeroPortraits
10
11
  IMAGE_EXTENSIONS = %w[.jpg .jpeg .png .webp .gif].freeze
12
+ WIDTHS = [480, 960, 1440].freeze
13
+ SIZES = "(max-width: 767px) 100vw, (max-width: 1023px) 14rem, 16rem"
11
14
 
12
15
  module_function
13
16
 
14
17
  def entries(content_root)
15
- image_files(Pathname.new(content_root)).map do |file|
16
- { "src" => "/media/images/hero/#{file.basename}" }
18
+ root = Pathname.new(content_root)
19
+ image_files(root).map do |file|
20
+ entry = { "src" => "/media/images/hero/#{file.basename}" }
21
+ stem = file.basename.to_s.sub(/\.[^.]+\z/, "")
22
+ parts = WIDTHS.filter_map do |width|
23
+ variant = root.join("media/images/hero/variants/#{stem}-#{width}.jpg")
24
+ next unless variant.file?
25
+
26
+ "/media/images/hero/variants/#{variant.basename} #{width}w"
27
+ end
28
+ next entry if parts.empty?
29
+
30
+ entry.merge("srcset" => parts.join(", "), "sizes" => SIZES)
17
31
  end
18
32
  end
19
33
 
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "proof",
3
+ "markdown": "<div class=\"proof\" markdown=\"1\">\n\nA short claim with **bold**.\n\n</div>",
4
+ "selectors": ["proof", "proof-head", "proof-name", "proof-body"]
5
+ }
@@ -128,6 +128,25 @@ function preprocessMarkdownDivs(markdown) {
128
128
  if (!hasMarkdownAttr(openTag)) return match;
129
129
  return `<div class="poem"><p>${inner.trim()}</p></div>`;
130
130
  });
131
+ text = text.replace(/<div\s+([^>]*class="[^"]*\btheorem\b[^"]*"[^>]*)>([\s\S]*?)<\/div>/gi, (match, openTag, inner) => {
132
+ if (!hasMarkdownAttr(openTag)) return match;
133
+ const nameMatch = openTag.match(/\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i);
134
+ const descMatch = openTag.match(/\bdescription\s*=\s*(?:"([^"]*)"|'([^']*)')/i);
135
+ const n = nameMatch ? (nameMatch[1] ?? nameMatch[2] ?? '') : '';
136
+ const d = descMatch ? (descMatch[1] ?? descMatch[2] ?? '') : '';
137
+ let head = '';
138
+ if (n) {
139
+ head = `<p class="theorem-head"><span class="theorem-name">${escapeAttr(n)}</span>`;
140
+ if (d) head += ` <span class="theorem-description">(${escapeAttr(d)})</span>`;
141
+ head += '.</p>';
142
+ }
143
+ return `<div class="theorem">${head}<div class="theorem-body">${marked.parse(inner.trim())}</div></div>`;
144
+ });
145
+ text = text.replace(/<div\s+([^>]*class="[^"]*\bproof\b[^"]*"[^>]*)>([\s\S]*?)<\/div>/gi, (match, openTag, inner) => {
146
+ if (!hasMarkdownAttr(openTag)) return match;
147
+ const head = '<p class="proof-head"><span class="proof-name">Proof</span>.</p>';
148
+ return `<div class="proof">${head}<div class="proof-body">${marked.parse(inner.trim())}</div></div>`;
149
+ });
131
150
  return text;
132
151
  }
133
152
 
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "theorem",
3
+ "markdown": "<div class=\"theorem\" name=\"Corollary 1\" description=\"nadir camera\" markdown=\"1\">\n\nLet the camera look straight down.\n\n</div>",
4
+ "selectors": ["theorem", "theorem-head", "theorem-name", "theorem-description", "theorem-body"]
5
+ }
@@ -152,7 +152,7 @@ end
152
152
  warn "FAIL test-examples-build: #{name} home dropped the ledger font request"
153
153
  exit 1
154
154
  end
155
- unless html.include?("--font-title: 'Cinzel Decorative', serif")
155
+ unless html.include?("--font-title: 'Cinzel Decorative', 'Pandorga Title Fallback', serif")
156
156
  warn "FAIL test-examples-build: #{name} home dropped the title face"
157
157
  exit 1
158
158
  end
@@ -28,10 +28,23 @@ from_theme = Pandorga::Fonts.resolve(nil, theme)
28
28
  fail!("unset config dropped the ledger faces") unless Pandorga::Fonts.stylesheet_href(bare) == DEFAULT_HREF
29
29
  fail!("theme tokens drifted from the ledger faces") unless Pandorga::Fonts.stylesheet_href(from_theme) == DEFAULT_HREF
30
30
 
31
- fail!("title stack dropped Cinzel") unless Pandorga::Fonts.css_stack(from_theme, "title") == "'Cinzel Decorative', serif"
32
- fail!("body stack dropped the serif fallback") unless Pandorga::Fonts.css_stack(from_theme, "body") == "'Newsreader', Georgia, serif"
31
+ fail!("title stack dropped Cinzel") unless Pandorga::Fonts.css_stack(from_theme, "title") ==
32
+ "'Cinzel Decorative', 'Pandorga Title Fallback', serif"
33
+ fail!("body stack dropped the serif fallback") unless Pandorga::Fonts.css_stack(from_theme, "body") ==
34
+ "'Newsreader', 'Pandorga Body Fallback', Georgia, serif"
35
+ fail!("label stack dropped the metric fallback") unless Pandorga::Fonts.css_stack(from_theme, "label") ==
36
+ "'Marcellus SC', 'Pandorga Label Fallback', serif"
33
37
  fail!("mono stack dropped Courier Prime") unless Pandorga::Fonts.css_stack(from_theme, "mono").start_with?("'Courier Prime', ui-monospace")
34
38
 
39
+ site_faces = Pandorga::Fonts.resolve(
40
+ { "title" => "Cinzel", "body" => { "family" => "Ancizar Serif", "weights" => [300, 400, 600] } },
41
+ theme
42
+ )
43
+ fail!("Cinzel stack missed its metric fallback") unless Pandorga::Fonts.css_stack(site_faces, "title") ==
44
+ "'Cinzel', 'Pandorga Cinzel Fallback', serif"
45
+ fail!("Ancizar stack missed its metric fallback") unless Pandorga::Fonts.css_stack(site_faces, "body") ==
46
+ "'Ancizar Serif', 'Pandorga Ancizar Fallback', Georgia, serif"
47
+
35
48
  custom = Pandorga::Fonts.resolve(
36
49
  {
37
50
  "title" => "Fraunces",
@@ -54,6 +67,7 @@ broken = []
54
67
  Dir.glob(ROOT.join("**/*.{css,html,rb,js,mjs,md,liquid,yml}")).each do |path|
55
68
  next if path.include?("/studio/assets/")
56
69
  next if path.include?("/node_modules/")
70
+ next if path.include?("/_site/")
57
71
  next if path.end_with?("test-fonts.rb")
58
72
  text = File.read(path)
59
73
  next unless text.include?(token)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pandorga
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.6
4
+ version: 1.8.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - The Pandorga contributors
@@ -393,12 +393,14 @@ files:
393
393
  - scripts/test/render-parity/ongoing-product.json
394
394
  - scripts/test/render-parity/plotly.json
395
395
  - scripts/test/render-parity/poem.json
396
+ - scripts/test/render-parity/proof.json
396
397
  - scripts/test/render-parity/run-absent-field-checks.mjs
397
398
  - scripts/test/render-parity/run-block-fixture.mjs
398
399
  - scripts/test/render-parity/run-detail-parity-checks.mjs
399
400
  - scripts/test/render-parity/run-field-flow-checks.mjs
400
401
  - scripts/test/render-parity/run-fixture.mjs
401
402
  - scripts/test/render-parity/selectors.mjs
403
+ - scripts/test/render-parity/theorem.json
402
404
  - scripts/test/render-parity/video.json
403
405
  - scripts/test/render-parity/youtube-short.json
404
406
  - scripts/test/render-parity/youtube.json