pandorga 2.0.3 → 2.0.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 +4 -4
- data/CHANGELOG.md +9 -0
- data/_layouts/shell.html +25 -0
- data/assets/css/ledger/chrome.css +12 -0
- data/assets/css/ledger/detail.css +28 -7
- data/assets/css/ledger/home.css +7 -1
- data/lib/pandorga/commands/new.rb +1 -1
- data/lib/pandorga/version.rb +1 -1
- data/scripts/test/test-mobile-density-home-measure.rb +161 -0
- data/scripts/test/test-v3-layout.rb +1 -1
- data/scripts/validate.sh +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2dc703ecd304ea907703cb189abd6ef09f0747c42e36766b365b6cab275f50f5
|
|
4
|
+
data.tar.gz: 1ae4bbec818b879199980c53e8c00e46d91c611b984f56f4d31fb5046b0fb1b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a87bd7621049333217620dcb59f44abda1d072edec0ea4aa06c362a60fee39d9c2b2ff4d75530c2b346caac34b715dd06bee92f0c4b880ce7308921a5cd2eed
|
|
7
|
+
data.tar.gz: f033221a651aae72e83efefb0839c2eafc4ce5ddfb2405ef13c4359c326fea85c7489224ef8e5d2e31f8a934cb7b7efd5c598481e51a6423de61d281f6f44b3d
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.0.4] — 2026-10-28
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Mobile article top compresses dates and action buttons into one band and closes the gap before the reading column.
|
|
10
|
+
- Mobile top bar is shorter and hides on scroll-down (shows on scroll-up, focus, or Explore open).
|
|
11
|
+
- Home hero summary uses a ~65ch measure on desktop.
|
|
12
|
+
- Home section "View all" sits beside the section title instead of at the far right of the band.
|
|
13
|
+
|
|
5
14
|
## [2.0.3] — 2026-10-25
|
|
6
15
|
|
|
7
16
|
### Fixed
|
data/_layouts/shell.html
CHANGED
|
@@ -287,11 +287,13 @@
|
|
|
287
287
|
var wrap = document.getElementById('site-menu');
|
|
288
288
|
var btn = document.getElementById('mobile-menu-btn');
|
|
289
289
|
var menu = document.getElementById('mobile-menu');
|
|
290
|
+
var topbar = document.querySelector('.site-topbar');
|
|
290
291
|
if (!wrap || !btn || !menu) return;
|
|
291
292
|
|
|
292
293
|
function setOpen(open) {
|
|
293
294
|
menu.classList.toggle('hidden', !open);
|
|
294
295
|
btn.setAttribute('aria-expanded', String(open));
|
|
296
|
+
if (open && topbar) topbar.classList.remove('topbar--hidden');
|
|
295
297
|
}
|
|
296
298
|
|
|
297
299
|
btn.addEventListener('click', function (event) {
|
|
@@ -304,6 +306,29 @@
|
|
|
304
306
|
document.addEventListener('keydown', function (event) {
|
|
305
307
|
if (event.key === 'Escape') setOpen(false);
|
|
306
308
|
});
|
|
309
|
+
|
|
310
|
+
if (!topbar) return;
|
|
311
|
+
var lastY = window.scrollY || 0;
|
|
312
|
+
var threshold = 8;
|
|
313
|
+
|
|
314
|
+
function onScroll() {
|
|
315
|
+
var y = window.scrollY || 0;
|
|
316
|
+
var diff = y - lastY;
|
|
317
|
+
|
|
318
|
+
if (y <= 32 || !menu.classList.contains('hidden')) {
|
|
319
|
+
topbar.classList.remove('topbar--hidden');
|
|
320
|
+
} else if (diff > threshold) {
|
|
321
|
+
topbar.classList.add('topbar--hidden');
|
|
322
|
+
} else if (diff < -threshold) {
|
|
323
|
+
topbar.classList.remove('topbar--hidden');
|
|
324
|
+
}
|
|
325
|
+
lastY = y;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
window.addEventListener('scroll', onScroll, { passive: true });
|
|
329
|
+
topbar.addEventListener('focusin', function () {
|
|
330
|
+
topbar.classList.remove('topbar--hidden');
|
|
331
|
+
});
|
|
307
332
|
})();
|
|
308
333
|
</script>
|
|
309
334
|
|
|
@@ -35,6 +35,18 @@
|
|
|
35
35
|
-webkit-backdrop-filter: blur(10px);
|
|
36
36
|
backdrop-filter: blur(10px);
|
|
37
37
|
border-bottom: 1px solid var(--border-gentle-divider);
|
|
38
|
+
transition: transform var(--motion-duration-fast) var(--motion-ease-default);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@media (max-width: 767px) {
|
|
42
|
+
.site-topbar > div {
|
|
43
|
+
padding-top: 0.625rem;
|
|
44
|
+
padding-bottom: 0.625rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.site-topbar.topbar--hidden {
|
|
48
|
+
transform: translateY(-100%);
|
|
49
|
+
}
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
/* Boxed controls for theme and mobile menu toggles. */
|
|
@@ -694,7 +694,8 @@ aside:has(> .text-detail-sidebar) {
|
|
|
694
694
|
|
|
695
695
|
.text-detail-sidebar .article-toc--disclosure,
|
|
696
696
|
.projects-dossier .article-toc--disclosure {
|
|
697
|
-
margin-top: var(--space-
|
|
697
|
+
margin-top: var(--space-xs);
|
|
698
|
+
margin-bottom: var(--space-xs);
|
|
698
699
|
}
|
|
699
700
|
}
|
|
700
701
|
|
|
@@ -1184,7 +1185,7 @@ nav.text-page-nav:not(:has(a)) {
|
|
|
1184
1185
|
}
|
|
1185
1186
|
|
|
1186
1187
|
.grid.text-detail-grid {
|
|
1187
|
-
gap: var(--space-
|
|
1188
|
+
gap: var(--space-xs);
|
|
1188
1189
|
}
|
|
1189
1190
|
|
|
1190
1191
|
/* The reading column used to add its own top margin on top of this gap,
|
|
@@ -1232,12 +1233,32 @@ nav.text-page-nav:not(:has(a)) {
|
|
|
1232
1233
|
padding-bottom: var(--space-md);
|
|
1233
1234
|
}
|
|
1234
1235
|
|
|
1235
|
-
/*
|
|
1236
|
-
flex gap was stacking another --space-md on the action rule, so the
|
|
1237
|
-
date sat high in that band. Drop the gap and the action margin is the
|
|
1238
|
-
matching --space-sm under the text. */
|
|
1236
|
+
/* On mobile, date row and action buttons share one compact horizontal band. */
|
|
1239
1237
|
.text-detail-sidebar .article-meta {
|
|
1240
|
-
|
|
1238
|
+
display: flex;
|
|
1239
|
+
flex-direction: row;
|
|
1240
|
+
flex-wrap: wrap;
|
|
1241
|
+
align-items: center;
|
|
1242
|
+
justify-content: space-between;
|
|
1243
|
+
gap: var(--space-xs) var(--space-sm);
|
|
1244
|
+
margin-bottom: 0;
|
|
1245
|
+
padding-bottom: 0;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.text-detail-sidebar .article-meta > div {
|
|
1249
|
+
margin: 0;
|
|
1250
|
+
padding: 0;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
.text-detail-sidebar .article-meta .date-evolution > span {
|
|
1254
|
+
border-top: 0;
|
|
1255
|
+
padding-top: 0;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
.text-detail-sidebar .article-meta .detail-actions {
|
|
1259
|
+
margin-top: 0;
|
|
1260
|
+
padding-top: 0;
|
|
1261
|
+
border-top: 0;
|
|
1241
1262
|
}
|
|
1242
1263
|
|
|
1243
1264
|
.text-detail-sidebar .article-sidebar-description,
|
data/assets/css/ledger/home.css
CHANGED
|
@@ -220,6 +220,12 @@
|
|
|
220
220
|
line-height: var(--leading-body);
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
@media (min-width: 768px) {
|
|
224
|
+
.home-hero-summary {
|
|
225
|
+
max-width: 65ch;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
223
229
|
/* No empty min-height. A guessed line count shifts the call to action
|
|
224
230
|
when the summary is shorter or taller than the reserve. The paragraph
|
|
225
231
|
is in the HTML when content/pages/home/summary.md or
|
|
@@ -415,7 +421,7 @@ html[data-theme="light"] .home-hero-plate {
|
|
|
415
421
|
The banner is a 2-column grid so the lead's children join the template. */
|
|
416
422
|
.home-band:not(.home-band--hero) .ledger-section-banner {
|
|
417
423
|
display: grid;
|
|
418
|
-
grid-template-columns:
|
|
424
|
+
grid-template-columns: auto auto 1fr;
|
|
419
425
|
column-gap: var(--space-md);
|
|
420
426
|
row-gap: var(--space-xs);
|
|
421
427
|
align-items: baseline;
|
|
@@ -24,7 +24,7 @@ module Pandorga
|
|
|
24
24
|
|
|
25
25
|
gem "pandorga", "~> 2.0"
|
|
26
26
|
# Local checkout: gem "pandorga", path: "../pandorga"
|
|
27
|
-
# GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v2.0.
|
|
27
|
+
# GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v2.0.4"
|
|
28
28
|
gem "csv"
|
|
29
29
|
gem "base64"
|
|
30
30
|
RUBY
|
data/lib/pandorga/version.rb
CHANGED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Acceptance tests for Mobile density + home measure (pandorga theme UI).
|
|
5
|
+
#
|
|
6
|
+
# 1. Article page top on mobile: meta date row and action buttons share one band,
|
|
7
|
+
# and TOC disclosure / reading column gaps are bounded.
|
|
8
|
+
# 2. Fixed top bar on mobile: shorter vertical padding (≤56-60px height) and
|
|
9
|
+
# hide-on-scroll functionality.
|
|
10
|
+
# 3. Home hero summary measure on desktop: constrained to ~60-70ch (e.g. 65ch).
|
|
11
|
+
# 4. Home section "View all" next to title: grid-template-columns places CTA
|
|
12
|
+
# beside section title, not at far right of wide band.
|
|
13
|
+
|
|
14
|
+
require "pathname"
|
|
15
|
+
|
|
16
|
+
ROOT = Pathname.new(__dir__).join("../..").expand_path
|
|
17
|
+
CSS = ROOT.join("assets/css")
|
|
18
|
+
|
|
19
|
+
$failures = []
|
|
20
|
+
|
|
21
|
+
def fail!(msg)
|
|
22
|
+
$failures << "FAIL test-mobile-density-home-measure: #{msg}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def strip_comments(text)
|
|
26
|
+
text.gsub(%r{/\*.*?\*/}m, "")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def parse_css_rules(css_text)
|
|
30
|
+
rules = []
|
|
31
|
+
i = 0
|
|
32
|
+
head = +""
|
|
33
|
+
while i < css_text.length
|
|
34
|
+
c = css_text[i]
|
|
35
|
+
if c == "{"
|
|
36
|
+
depth = 1
|
|
37
|
+
j = i + 1
|
|
38
|
+
while j < css_text.length && depth.positive?
|
|
39
|
+
depth += 1 if css_text[j] == "{"
|
|
40
|
+
depth -= 1 if css_text[j] == "}"
|
|
41
|
+
j += 1
|
|
42
|
+
end
|
|
43
|
+
body = css_text[(i + 1)...(j - 1)]
|
|
44
|
+
h = head.strip
|
|
45
|
+
if h.start_with?("@media")
|
|
46
|
+
media_cond = h.sub(/\A@media\s*/, "").gsub(/\s+/, "")
|
|
47
|
+
sub_rules = parse_css_rules(body)
|
|
48
|
+
sub_rules.each do |r|
|
|
49
|
+
m = r[:media] ? "#{media_cond} and #{r[:media]}" : media_cond
|
|
50
|
+
rules << r.merge(media: m)
|
|
51
|
+
end
|
|
52
|
+
elsif h.start_with?("@supports", "@layer", "@container")
|
|
53
|
+
rules.concat(parse_css_rules(body))
|
|
54
|
+
elsif !h.start_with?("@")
|
|
55
|
+
decls = body.split(";").filter_map do |d|
|
|
56
|
+
prop, val = d.split(":", 2)
|
|
57
|
+
next unless val
|
|
58
|
+
|
|
59
|
+
[prop.strip.downcase, val.gsub(/\s+/, " ").strip]
|
|
60
|
+
end
|
|
61
|
+
h.split(",").each do |sel|
|
|
62
|
+
s = sel.gsub(/\s+/, " ").strip
|
|
63
|
+
rules << { sel: s, decls: decls, media: nil }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
head = +""
|
|
67
|
+
i = j
|
|
68
|
+
elsif c == "}"
|
|
69
|
+
i += 1
|
|
70
|
+
else
|
|
71
|
+
head << c
|
|
72
|
+
i += 1
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
rules
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# 1. Article page top on mobile
|
|
79
|
+
detail_css = strip_comments(File.read(CSS.join("ledger/detail.css")))
|
|
80
|
+
detail_rules = parse_css_rules(detail_css)
|
|
81
|
+
|
|
82
|
+
meta_mobile_rules = detail_rules.select do |r|
|
|
83
|
+
r[:media] && r[:media].include?("max-width:767px") && r[:sel] == ".text-detail-sidebar .article-meta"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
has_row_layout = meta_mobile_rules.any? do |r|
|
|
87
|
+
r[:decls].any? { |p, v| p == "flex-direction" && v == "row" }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
unless has_row_layout
|
|
91
|
+
fail!("detail.css must set flex-direction: row on .text-detail-sidebar .article-meta under @media (max-width: 767px)")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
toc_disclosure_mobile = detail_rules.select do |r|
|
|
95
|
+
r[:media] && r[:media].include?("max-width:767px") && r[:sel].include?("article-toc--disclosure")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
has_bounded_toc_margin = toc_disclosure_mobile.any? do |r|
|
|
99
|
+
r[:decls].any? { |p, v| (p == "margin-top" || p == "margin-bottom" || p == "margin") && (v.include?("var(--space-xs)") || v.include?("var(--space-sm)") || v.include?("0")) }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
unless has_bounded_toc_margin
|
|
103
|
+
fail!("detail.css must bound margins on .article-toc--disclosure under @media (max-width: 767px)")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# 2. Fixed top bar on mobile
|
|
107
|
+
chrome_css = strip_comments(File.read(CSS.join("ledger/chrome.css")))
|
|
108
|
+
chrome_rules = parse_css_rules(chrome_css)
|
|
109
|
+
|
|
110
|
+
topbar_mobile_padding = chrome_rules.any? do |r|
|
|
111
|
+
r[:media] && r[:media].include?("max-width:767px") && (r[:sel] == ".site-topbar > div" || r[:sel] == ".site-topbar") &&
|
|
112
|
+
r[:decls].any? { |p, v| (p == "padding-top" || p == "padding-bottom" || p == "padding" || p == "padding-block") && (v.include?("0.625rem") || v.include?("0.5rem") || v.include?("0.75rem") || v.include?("10px") || v.include?("12px")) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
unless topbar_mobile_padding
|
|
116
|
+
fail!("chrome.css must reduce vertical padding on .site-topbar under @media (max-width: 767px)")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
topbar_hide_rule = chrome_rules.any? do |r|
|
|
120
|
+
(r[:sel].include?(".site-topbar.topbar--hidden") || r[:sel].include?(".site-topbar[data-topbar-hidden]")) &&
|
|
121
|
+
r[:decls].any? { |p, v| p == "transform" && v.include?("translateY(-100%)") }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
unless topbar_hide_rule
|
|
125
|
+
fail!("chrome.css must define transform: translateY(-100%) on .site-topbar.topbar--hidden")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
shell_html = File.read(ROOT.join("_layouts/shell.html"))
|
|
129
|
+
unless shell_html.include?("topbar--hidden") || shell_html.include?("data-topbar-hidden")
|
|
130
|
+
fail!("_layouts/shell.html must contain hide-on-scroll logic for .site-topbar")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# 3. Home hero summary measure on desktop
|
|
134
|
+
home_css = strip_comments(File.read(CSS.join("ledger/home.css")))
|
|
135
|
+
home_rules = parse_css_rules(home_css)
|
|
136
|
+
|
|
137
|
+
hero_summary_desktop = home_rules.any? do |r|
|
|
138
|
+
r[:media] && r[:media].include?("min-width:768px") && r[:sel] == ".home-hero-summary" &&
|
|
139
|
+
r[:decls].any? { |p, v| p == "max-width" && (v == "65ch" || v == "60ch" || v == "70ch") }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
unless hero_summary_desktop
|
|
143
|
+
fail!("home.css must constrain .home-hero-summary max-width to ~65ch under @media (min-width: 768px)")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# 4. Home section "View all" next to title
|
|
147
|
+
banner_grid_col = home_rules.any? do |r|
|
|
148
|
+
r[:sel] == ".home-band:not(.home-band--hero) .ledger-section-banner" &&
|
|
149
|
+
r[:decls].any? { |p, v| p == "grid-template-columns" && (v.start_with?("auto auto") || v.start_with?("minmax(0, auto) auto") || v.start_with?("max-content max-content")) }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
unless banner_grid_col
|
|
153
|
+
fail!("home.css must set grid-template-columns on .ledger-section-banner to place CTA beside title (e.g. auto auto 1fr)")
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
if $failures.empty?
|
|
157
|
+
puts "PASS test-mobile-density-home-measure"
|
|
158
|
+
else
|
|
159
|
+
$failures.each { |f| puts f }
|
|
160
|
+
exit 1
|
|
161
|
+
end
|
|
@@ -290,7 +290,7 @@ end
|
|
|
290
290
|
item(5) do
|
|
291
291
|
h = "ledger/home.css"
|
|
292
292
|
expect(h, ".home-band:not(.home-band--hero) .ledger-section-banner", {
|
|
293
|
-
"display" => "grid", "grid-template-columns" => "
|
|
293
|
+
"display" => "grid", "grid-template-columns" => "auto auto 1fr"
|
|
294
294
|
})
|
|
295
295
|
expect(h, ".home-band .ledger-section-lead", { "display" => "contents" })
|
|
296
296
|
expect(h, ".home-band .ledger-section-banner .ledger-kicker", { "grid-column" => "1 / -1", "grid-row" => "1" })
|
data/scripts/validate.sh
CHANGED
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: 2.0.
|
|
4
|
+
version: 2.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- The Pandorga contributors
|
|
@@ -453,6 +453,7 @@ files:
|
|
|
453
453
|
- scripts/test/test-listing-filter-contract.rb
|
|
454
454
|
- scripts/test/test-math-row-breaks.mjs
|
|
455
455
|
- scripts/test/test-math-row-breaks.rb
|
|
456
|
+
- scripts/test/test-mobile-density-home-measure.rb
|
|
456
457
|
- scripts/test/test-new.rb
|
|
457
458
|
- scripts/test/test-no-personal-data.rb
|
|
458
459
|
- scripts/test/test-parallel-content-publish.rb
|