pandorga 1.14.0 → 2.0.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/DESIGN.md +15 -30
  4. data/_includes/content-runtime/43-blocks.html +0 -3
  5. data/_includes/content-runtime/60-fragments.html +83 -149
  6. data/_includes/content-runtime/90-bootstrap.html +3 -0
  7. data/_includes/cv/cv-box.html +10 -4
  8. data/_includes/home/band-index.html +22 -7
  9. data/_includes/home/band-listing.html +7 -3
  10. data/_includes/page/timeline-section.html +8 -3
  11. data/_includes/theme/theme-vars-block.liquid +3 -0
  12. data/_layouts/articles.html +0 -2
  13. data/_layouts/bibliography.html +0 -2
  14. data/_layouts/blog.html +0 -5
  15. data/_layouts/cv.html +27 -8
  16. data/_layouts/home.html +2 -13
  17. data/_layouts/projects.html +17 -9
  18. data/_layouts/resources.html +0 -9
  19. data/_layouts/shell.html +1 -7
  20. data/assets/css/article.css +10 -2
  21. data/assets/css/base.css +68 -44
  22. data/assets/css/ledger/bibliography.css +0 -31
  23. data/assets/css/ledger/chrome.css +51 -2
  24. data/assets/css/ledger/cv.css +0 -40
  25. data/assets/css/ledger/detail.css +20 -6
  26. data/assets/css/ledger/home.css +261 -402
  27. data/assets/css/ledger/projects.css +17 -19
  28. data/assets/css/ledger/rascunhos.css +0 -27
  29. data/assets/css/ledger/sources.css +0 -57
  30. data/assets/css/ledger/writing.css +0 -62
  31. data/lib/pandorga/commands/new.rb +2 -2
  32. data/lib/pandorga/commands/thumbnails.rb +3 -2
  33. data/lib/pandorga/registry/schema.yml +0 -3
  34. data/lib/pandorga/thumbnail_variants.rb +29 -3
  35. data/lib/pandorga/version.rb +1 -1
  36. data/scripts/check_home_lists.mjs +33 -5
  37. data/scripts/content/hero_portraits.rb +59 -11
  38. data/scripts/test/test-css-structure.rb +0 -1
  39. data/scripts/test/test-hero-16x9.rb +379 -0
  40. data/scripts/test/test-hero-portraits.mjs +10 -20
  41. data/scripts/test/test-hero-portraits.rb +31 -25
  42. data/scripts/test/test-highlighter-pills.rb +12 -2
  43. data/scripts/test/test-home-lists.rb +5 -0
  44. data/scripts/test/test-new.rb +6 -6
  45. data/scripts/test/test-shell-a11y.rb +2 -1
  46. data/scripts/test/test-studio-section-texts.rb +625 -0
  47. data/scripts/test/test-theme-contrast.rb +13 -2
  48. data/scripts/test/test-v3-layout.rb +559 -0
  49. data/scripts/validate.sh +3 -0
  50. data/studio/assets/{index-BJyN-kB3.js → index-B9h1WUYY.js} +1 -1
  51. data/studio/index.html +1 -1
  52. data/studio/schema.yml +126 -2
  53. data/studio-app/src/lib/yaml-doc.js +6 -0
  54. data/templates/network/studio.yml +10 -0
  55. metadata +5 -2
@@ -161,23 +161,16 @@
161
161
  overflow: hidden;
162
162
  display: grid;
163
163
  background: var(--color-surface-card);
164
- border: 1px solid var(--border-gentle-subtle);
165
- border-left: 3px solid var(--color-primary);
164
+ border: 1px solid var(--border-ink);
165
+ border-top: 2px solid var(--color-primary);
166
166
  margin-bottom: var(--space-3xl);
167
167
  transition: border-color var(--motion-duration-fast) var(--motion-ease-default);
168
168
  }
169
169
 
170
170
  .projects-page .projects-lead:hover {
171
171
  border-color: var(--border-gentle-hover);
172
- border-left-color: var(--color-primary);
173
- }
174
-
175
- .projects-page .projects-lead-shard {
176
- right: -5rem;
177
- top: -5rem;
178
- width: 18rem;
179
- height: 18rem;
180
- background: var(--color-primary);
172
+ /* Keep the primary top rule; the shorthand above would repaint it. */
173
+ border-top-color: var(--color-primary);
181
174
  }
182
175
 
183
176
  .projects-page .projects-lead-media {
@@ -420,14 +413,6 @@
420
413
  margin-bottom: var(--space-2xl);
421
414
  }
422
415
 
423
- .projects-page .projects-hero-shard {
424
- right: -4rem;
425
- bottom: -6rem;
426
- width: 16rem;
427
- height: 16rem;
428
- background: var(--color-secondary);
429
- }
430
-
431
416
  .projects-page .projects-hero-body {
432
417
  position: relative;
433
418
  z-index: 1;
@@ -542,6 +527,19 @@
542
527
  reason recorded in the layout: at tablet width the dossier is narrow enough
543
528
  to break a repository slug across three lines mid-word. */
544
529
  @media (max-width: 767px) {
530
+ .projects-page .projects-lead-body {
531
+ padding: var(--space-md);
532
+ }
533
+
534
+ .projects-page .projects-grid {
535
+ gap: var(--space-md);
536
+ }
537
+
538
+ .projects-page .ledger-system-card>.ledger-poster,
539
+ .projects-page .ledger-system-card>.projects-plate {
540
+ margin: calc(var(--space-md) * -1) calc(var(--space-md) * -1) 0;
541
+ }
542
+
545
543
  .projects-page .projects-hero-body {
546
544
  padding: var(--space-lg);
547
545
  }
@@ -25,33 +25,6 @@
25
25
  margin-bottom: var(--space-2xl);
26
26
  }
27
27
 
28
- /* Sized to the masthead it accents. It used to be 24rem x 15rem, which fitted
29
- while the filter deck was a strip under the title and made the masthead
30
- 260px tall; with the filter moved into the rail the masthead is 107px and a
31
- 15rem shard hung 50px into the body, cutting across the rail's keyword box.
32
- An accent that reaches the content it is accenting is not an accent. */
33
- .rascunhos-shard {
34
- top: calc(-1 * var(--space-2xl));
35
- right: calc(-1 * var(--space-xl));
36
- width: 16rem;
37
- height: 9rem;
38
- /* Page accent (ochre via `.page-accent--ochre`), not the triad secondary. */
39
- background: var(--color-primary);
40
- }
41
-
42
- /* Between the 640px shard cut-off and the desktop layout a 24rem shard is
43
- wider than the masthead is tall: at 768px it covered the title and the
44
- standfirst and read as a mis-clipped slab rather than a corner accent. The
45
- writing lead and the CV dossier carry the same correction. */
46
- @media (max-width: 1023px) {
47
- .rascunhos-shard {
48
- top: calc(-1 * var(--space-xl));
49
- right: calc(-1 * var(--space-lg));
50
- width: 12rem;
51
- height: 8rem;
52
- }
53
- }
54
-
55
28
  .rascunhos-header {
56
29
  position: relative;
57
30
  z-index: 1;
@@ -7,32 +7,11 @@
7
7
  `language`, and the publisher host from `link`. Every colour is a token, so
8
8
  both palettes resolve from the same rules. */
9
9
 
10
- /* ── Shards ──
11
- The page had no depth treatment of any kind, and it is the wrong shape for
12
- the ambient pool the home bands carry: a pool behind a grid of many small
13
- cards only darkens one corner of it. Shards instead, the two canonical
14
- polygons from base.css, one at each end of the run — top right above the
15
- grid, bottom left below it, the diagonal pair the CV dossier uses inside
16
- one panel, opened out across the page. In dark the source cards stay
17
- opaque so the shard reads in the gutter, not through the poster.
18
-
19
- Neither container takes `overflow: hidden`: the masthead holds the filter
20
- popover, which would be clipped by it. The offsets are therefore sized to
21
- stay inside the page's own gutter at every width the shards survive, the
22
- way the rascunhos masthead does it. The shared utility already unmounts
23
- both below 640px. */
24
- /* The masthead outranks the grid because the filter popover opens out of it.
25
- Lifting the header over its own shard puts the popover inside a stacking
26
- context, and the grid below has to be lifted over the foot shard in the
27
- same way — without this the cards would be painted over an open filter
28
- panel. */
29
10
  .sources-masthead {
30
11
  position: relative;
31
12
  z-index: 2;
32
13
  }
33
14
 
34
-
35
-
36
15
  .sources-header {
37
16
  position: relative;
38
17
  z-index: 1;
@@ -43,14 +22,6 @@
43
22
  opacity: 0.85;
44
23
  }
45
24
 
46
- .sources-masthead-shard {
47
- top: calc(-1 * var(--space-2xl));
48
- right: calc(-1 * var(--space-xl));
49
- width: 16rem;
50
- height: 9rem;
51
- background: var(--color-primary);
52
- }
53
-
54
25
  .sources-body {
55
26
  position: relative;
56
27
  }
@@ -65,34 +36,6 @@
65
36
  z-index: 1;
66
37
  }
67
38
 
68
- /* The foot of the page, beside the pagination. It reaches up behind the last
69
- row of cards. In dark those cards are opaque again, so what shows is the
70
- wedge in the gutter and the band across the pagination strip. */
71
- .sources-foot-shard {
72
- left: calc(-1 * var(--space-xl));
73
- bottom: calc(-1 * var(--space-lg));
74
- width: 14rem;
75
- height: 8rem;
76
- background: color-mix(in srgb, var(--color-primary) 70%, transparent);
77
- }
78
-
79
- /* Below the desktop layout the page gutter halves, so the shards have to come
80
- back inside it or they widen the document. */
81
- @media (max-width: 1023px) {
82
- .sources-masthead-shard {
83
- top: calc(-1 * var(--space-xl));
84
- right: calc(-1 * var(--space-lg));
85
- width: 12rem;
86
- height: 8rem;
87
- }
88
-
89
- .sources-foot-shard {
90
- left: calc(-1 * var(--space-lg));
91
- width: 10rem;
92
- height: 7rem;
93
- }
94
- }
95
-
96
39
  /* Pagination hides rather than unmounts, so the cards keep their identity
97
40
  between pages and the filter only has to toggle a class. */
98
41
  .resource-hidden {
@@ -59,40 +59,6 @@
59
59
  }
60
60
  }
61
61
 
62
- /* The mockup filled the right of this panel with a fabricated proof figure.
63
- A shard bleeding off the corner holds that weight instead.
64
-
65
- This used to be a single 19rem wedge at the top right, stood down whenever
66
- the entry carried a thumbnail — and the panel picks the first entry that
67
- *has* a thumbnail, so the condition was always true and the shard never
68
- rendered on a built page. It is a pair now, placed around the plate rather
69
- than instead of it.
70
-
71
- The alpha sits bottom left, under the foot of the copy, the one corner the
72
- plate never reaches at any width. The beta is a true corner accent at the
73
- top right: it is offset far enough up that its visible part is the triangle
74
- above the plate's top edge, and it tucks behind the plate as it descends
75
- rather than fighting it. Neither corner repeats the home panel's pair, and
76
- the hues are the only two this page has — `.page-accent--green` rebinds
77
- primary onto secondary, so green and ochre is the whole palette here. */
78
- .writing-index .writing-lead-shard {
79
- left: -3.5rem;
80
- bottom: -4rem;
81
- width: 12rem;
82
- height: 12rem;
83
- background: var(--color-primary);
84
- }
85
-
86
- .writing-index .writing-lead-shard--beta {
87
- left: auto;
88
- right: -4rem;
89
- bottom: auto;
90
- top: -5rem;
91
- width: 10rem;
92
- height: 10rem;
93
- background: var(--color-tertiary);
94
- }
95
-
96
62
  .writing-index .writing-lead-inner {
97
63
  position: relative;
98
64
  z-index: 1;
@@ -739,34 +705,6 @@ html[data-theme="light"] .writing-index .writing-lead-media .ledger-poster {
739
705
  }
740
706
  }
741
707
 
742
- /* Between the 640px shard cut-off and the desktop layout the panel is narrow
743
- enough that a shard sized for the wide card reaches the middle of it: at
744
- 768px the 19rem one this started as ran straight through the lead headline.
745
- Back to corner accents. The CV dossier and the rascunhos masthead carry the
746
- same correction. Both corners are restated, not just the size, because the
747
- base rule sets one of `top`/`bottom` and `left`/`right` to `auto` on the
748
- beta and a size-only override would leave the pair half-placed: the beta
749
- carries both class names, so the alpha rule matches it too, and an element
750
- left with all four offsets set resolves to top and left, which drops the
751
- pair into a single corner. */
752
- @media (max-width: 1023px) {
753
- .writing-index .writing-lead-shard {
754
- left: -2.5rem;
755
- bottom: -3rem;
756
- width: 8.5rem;
757
- height: 8.5rem;
758
- }
759
-
760
- .writing-index .writing-lead-shard--beta {
761
- left: auto;
762
- bottom: auto;
763
- right: -3rem;
764
- top: -3.5rem;
765
- width: 7.5rem;
766
- height: 7.5rem;
767
- }
768
- }
769
-
770
708
  /* Pagination count face/size live on `.pagination-count` in base.css. */
771
709
 
772
710
  /* global-link.css underlines any <a> hovered inside an <article>, which would
@@ -22,9 +22,9 @@ module Pandorga
22
22
 
23
23
  source "https://rubygems.org"
24
24
 
25
- gem "pandorga", "~> 1.3"
25
+ gem "pandorga", "~> 2.0"
26
26
  # Local checkout: gem "pandorga", path: "../pandorga"
27
- # GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v1.14.0"
27
+ # GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v2.0.0"
28
28
  gem "csv"
29
29
  gem "base64"
30
30
  RUBY
@@ -5,8 +5,9 @@ require_relative "../thumbnail_variants"
5
5
 
6
6
  module Pandorga
7
7
  module Commands
8
- # Generate 16:9 center-cropped width variants for local project thumbnails.
9
- # Refs: PLT-AC-44
8
+ # Generate 16:9 center-cropped width variants for local project thumbnails
9
+ # and the home hero photographs.
10
+ # Refs: PLT-AC-44, PLT-AC-51
10
11
  class Thumbnails
11
12
  def run(argv)
12
13
  site_root = Pathname.new(argv[0] || Dir.pwd).expand_path
@@ -49,9 +49,6 @@ home:
49
49
  hero_art:
50
50
  type: string
51
51
  notes: Include path for hero decoration (default `page/hero-default.html`)
52
- hero_landscape:
53
- type: string
54
- notes: Optional URL of a 16:9 image. At or below 767px it is used only when no `pages/home/portraits.json` entry has its own `landscape` URL. Home front matter `hero_landscape` overrides this key. Omitted, and with no entry landscape, the phone plate stays 3:4
55
52
 
56
53
  fonts:
57
54
  title: "`pandorga.fonts`"
@@ -12,8 +12,8 @@ require_relative "registry"
12
12
 
13
13
  module Pandorga
14
14
  # Generates 16:9 center-cropped width variants for local project thumbnails
15
- # and computes their srcset strings.
16
- # Refs: PLT-AC-44
15
+ # and the home hero photographs, and computes their srcset strings.
16
+ # Refs: PLT-AC-44, PLT-AC-51
17
17
  module ThumbnailVariants
18
18
  WIDTHS = [480, 960, 1440].freeze
19
19
  IMAGE_EXTENSIONS = %w[.jpg .jpeg .png .webp].freeze
@@ -25,6 +25,9 @@ module Pandorga
25
25
  root = Pathname.new(site_root).expand_path
26
26
  content_root = resolve_content_root(root)
27
27
  sources = project_thumbnail_sources(root, content_root)
28
+ hero_sources(content_root).each do |src|
29
+ sources << src unless sources.any? { |known| known.expand_path == src.expand_path }
30
+ end
28
31
 
29
32
  summary = { sources: sources.length, written: 0, skipped: 0, failed: 0 }
30
33
  return summary if sources.empty?
@@ -55,7 +58,10 @@ module Pandorga
55
58
  end
56
59
 
57
60
  dest = variants_dir.join("#{stem}-#{width}.jpg")
58
- if dest.file? && dest.mtime >= src_mtime
61
+ # A newer variant is kept only when it is already this 16:9 size.
62
+ # Hero variants used to be 3:4 under the same names.
63
+ if dest.file? && dest.mtime >= src_mtime &&
64
+ identify_size(bins[:identify], dest) == [width, (width * 9) / 16]
59
65
  summary[:skipped] += 1
60
66
  next
61
67
  end
@@ -159,6 +165,16 @@ module Pandorga
159
165
  sources
160
166
  end
161
167
 
168
+ # Every image directly in media/images/hero/ (the hero pool, PLT-AC-51).
169
+ def hero_sources(content_root)
170
+ dir = Pathname.new(content_root).join("media/images/hero")
171
+ return [] unless dir.directory?
172
+
173
+ dir.children.select do |path|
174
+ path.file? && IMAGE_EXTENSIONS.include?(path.extname.downcase)
175
+ end.sort_by { |path| path.basename.to_s }
176
+ end
177
+
162
178
  def normalize_local_media_path(path)
163
179
  value = path.to_s.strip
164
180
  return nil if value.empty?
@@ -237,6 +253,16 @@ module Pandorga
237
253
  nil
238
254
  end
239
255
 
256
+ def identify_size(identify_cmd, src)
257
+ cmd = [*identify_cmd, "-ping", "-format", "%w %h", "#{src}[0]"]
258
+ out, _err, status = Open3.capture3(*cmd)
259
+ return nil unless status.success?
260
+
261
+ out.to_s.split.map(&:to_i)
262
+ rescue StandardError
263
+ nil
264
+ end
265
+
240
266
  def write_variant(convert_cmd, src, dest, width)
241
267
  height = (width * 9) / 16
242
268
  FileUtils.mkdir_p(dest.dirname)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pandorga
4
- VERSION = "1.14.0"
4
+ VERSION = "2.0.0"
5
5
  end
@@ -172,7 +172,12 @@ function filled(result) {
172
172
  return result.projects > 0 && result.articles > 0 && result.posts > 0;
173
173
  }
174
174
 
175
- async function main() {
175
+ // On the runner Chrome sometimes never opens its DevTools port (the dbus
176
+ // noise in its stderr is unrelated). A second launch with a fresh profile
177
+ // and port gets past that; the list checks below do not change.
178
+ const LAUNCH_ATTEMPTS = 2;
179
+
180
+ async function launchChrome() {
176
181
  const profile = await mkdtemp(join(tmpdir(), 'pandorga-home-'));
177
182
  const port = await freePort();
178
183
  const chrome = spawn(chromePath, [
@@ -187,10 +192,35 @@ async function main() {
187
192
  `--user-data-dir=${profile}`,
188
193
  'about:blank'
189
194
  ], { stdio: ['ignore', 'ignore', 'pipe'] });
195
+ return { chrome, profile, port };
196
+ }
197
+
198
+ async function stopChrome(chrome, profile) {
199
+ chrome.kill('SIGKILL');
200
+ await delay(100);
201
+ await rm(profile, { recursive: true, force: true });
202
+ }
203
+
204
+ async function main() {
205
+ let chrome;
206
+ let profile;
207
+ let version;
208
+ for (let launch = 1; launch <= LAUNCH_ATTEMPTS; launch += 1) {
209
+ const started = await launchChrome();
210
+ chrome = started.chrome;
211
+ profile = started.profile;
212
+ try {
213
+ version = await waitForDevtools(started.port, chrome, profile);
214
+ break;
215
+ } catch (error) {
216
+ await stopChrome(chrome, profile);
217
+ if (launch === LAUNCH_ATTEMPTS) throw error;
218
+ console.error(`WARN: ${error.message.split('\n')[0].slice(0, 200)}; launching Chrome again`);
219
+ }
220
+ }
190
221
 
191
222
  let failed = false;
192
223
  try {
193
- const version = await waitForDevtools(port, chrome, profile);
194
224
  const ws = await openSocket(version.webSocketDebuggerUrl);
195
225
  const browser = new Cdp(ws);
196
226
  const created = await browser.send('Target.createTarget', { url: 'about:blank' });
@@ -223,9 +253,7 @@ async function main() {
223
253
  }
224
254
  ws.close();
225
255
  } finally {
226
- chrome.kill('SIGKILL');
227
- await delay(100);
228
- await rm(profile, { recursive: true, force: true });
256
+ await stopChrome(chrome, profile);
229
257
  }
230
258
 
231
259
  if (failed) process.exit(1);
@@ -3,22 +3,20 @@
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.
7
- # Variants should be 3:4 portrait fills (not landscape width-only resizes):
8
- # the plate is object-fit cover at 3:4, including on a phone, so a 480×270
9
- # landscape candidate is too short and looks soft. A 16:9 file for a narrow
10
- # viewport is the optional `landscape` field on a portraits.json entry.
11
- # pandorga.home.hero_landscape is only the fallback when no entry has one.
6
+ # The plate is 16:9 at every width (PLT-AC-51). The export writes 16:9 width
7
+ # variants under variants/ with the project thumbnail generator
8
+ # (Pandorga::ThumbnailVariants), so they are not extra draws. An entry lists
9
+ # only the variants that are 16:9: the old 3:4 hero variants had the same
10
+ # names and must not reach srcset when ImageMagick could not replace them.
12
11
 
13
12
  require "pathname"
14
13
 
15
14
  module HeroPortraits
16
15
  IMAGE_EXTENSIONS = %w[.jpg .jpeg .png .webp .gif].freeze
17
16
  WIDTHS = [480, 960, 1440].freeze
18
- # Slightly above the plate CSS width (14/16rem at html 110%) so 1.75–2×
19
- # DPR picks the next 3:4 variant instead of a soft 480w. Variants must be
20
- # portrait fills; landscape width-only resizes are too short under cover.
21
- SIZES = "(max-width: 767px) 100vw, (max-width: 1023px) 18rem, 20rem"
17
+ # Below 1024px the plate spans the column (edge to edge on a phone); from
18
+ # 1024px it takes a little under half the row.
19
+ SIZES = "(max-width: 1023px) 100vw, 50vw"
22
20
 
23
21
  module_function
24
22
 
@@ -29,7 +27,7 @@ module HeroPortraits
29
27
  stem = file.basename.to_s.sub(/\.[^.]+\z/, "")
30
28
  parts = WIDTHS.filter_map do |width|
31
29
  variant = root.join("media/images/hero/variants/#{stem}-#{width}.jpg")
32
- next unless variant.file?
30
+ next unless variant.file? && wide?(variant)
33
31
 
34
32
  "/media/images/hero/variants/#{variant.basename} #{width}w"
35
33
  end
@@ -47,4 +45,54 @@ module HeroPortraits
47
45
  path.file? && IMAGE_EXTENSIONS.include?(path.extname.downcase)
48
46
  end.sort_by { |path| path.basename.to_s }
49
47
  end
48
+
49
+ # True when the file is 16:9 to within a pixel.
50
+ def wide?(path)
51
+ size = image_size(path)
52
+ return false unless size
53
+
54
+ width, height = size
55
+ width.positive? && (height - (width * 9 / 16.0)).abs <= 1
56
+ end
57
+
58
+ # [width, height] from a JPEG or PNG header, sniffed by content, or nil.
59
+ def image_size(path)
60
+ File.open(path, "rb") do |io|
61
+ head = io.read(24).to_s
62
+ if head.start_with?("\x89PNG\r\n\x1A\n".b) && head.byteslice(12, 4) == "IHDR"
63
+ return head.byteslice(16, 8).unpack("NN")
64
+ end
65
+ return nil unless head.start_with?("\xFF\xD8".b)
66
+
67
+ jpeg_size(io)
68
+ end
69
+ rescue StandardError
70
+ nil
71
+ end
72
+
73
+ def jpeg_size(io)
74
+ io.seek(2)
75
+ loop do
76
+ byte = io.read(1)
77
+ return nil unless byte
78
+ next unless byte.ord == 0xFF
79
+
80
+ marker = io.read(1)&.ord
81
+ return nil unless marker
82
+ next if marker == 0xFF || marker == 0x01 || (0xD0..0xD8).cover?(marker)
83
+ return nil if marker == 0xD9 || marker == 0xDA
84
+
85
+ length = io.read(2)&.unpack1("n")
86
+ return nil unless length && length >= 2
87
+
88
+ if (0xC0..0xCF).cover?(marker) && ![0xC4, 0xC8, 0xCC].include?(marker)
89
+ data = io.read(5)
90
+ return nil unless data && data.bytesize == 5
91
+
92
+ height, width = data.byteslice(1, 4).unpack("nn")
93
+ return [width, height]
94
+ end
95
+ io.seek(length - 2, IO::SEEK_CUR)
96
+ end
97
+ end
50
98
  end
@@ -84,7 +84,6 @@ sheets.each do |path|
84
84
  ledger-section-rule
85
85
  home-band--ruled
86
86
  home-index-shard--gamma
87
- home-index-note
88
87
  post-content
89
88
  home-hero-pandorga-svg
90
89
  table-csv