pandorga 2.0.3 → 2.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 221dacb578ada5bb29237157b9e2fcffc4789053fe0566eae9f626967334b291
4
- data.tar.gz: eedd7e320f83cf654525e469eddf66f013f7229324304d13ed983ad38a0b58eb
3
+ metadata.gz: 3dd3df9d18ed1ac1a99e07aecfe17e5776ccf46e35ca30cb4c87dc8e9ec6a00f
4
+ data.tar.gz: c87fa62d1dcdd8ddc1b1f00671b83558e0114b4d28a2de376eb653251740a7a4
5
5
  SHA512:
6
- metadata.gz: 41b852a904d013dbc020d1d14fe33b56d9355a5cf201255fc14df14ae095c2457da292ba2f357cce007b45a886fd047b2c9312a4bc1d3eb9f2f69b995270845c
7
- data.tar.gz: d48976bdd3f548a0480766c9e6449566ce3527c5e78bc603711681d32c0ab144d6a8c5c7c60c02fd65553a1160c02eb3f10a8791e284ec0e3c98cdd4019f7f19
6
+ metadata.gz: f75cd5a291e0678b90997a207a73bdd3401e76e3de3d4bcbd732323f46e87f11b4aa0075a46a61e35378a49d79ae7acb97d521793ca04a5705e5fe4a5d6d7381
7
+ data.tar.gz: 279e4ed500471c6b7a74129bb9a18648c4c21054aa9b02af7f2794f9eada23afe7249c23b2de2f6555301d3d520b4caba60c7c30e3620eae8aaf8409905900d2
data/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.1.0] — 2026-10-28
6
+
7
+ ### Added
8
+
9
+ - Site-configurable body weights under `pandorga.fonts.body_weight` (desktop default 300) and `pandorga.fonts.body_weight_mobile` (mobile default 350) wired into CSS custom properties (`--weight-body` and `--weight-body-mobile`) (PLT-AC-57).
10
+ - Static Marcellus SC label **“Explore projects”** above the Home Projects inventory (PLT-AC-61).
11
+
12
+ ### Changed
13
+
14
+ - Default resolved typography stack switches to four families: Ancizar Serif for titles and top-bar wordmark, Ancizar Sans for body and taglines, Marcellus SC for labels, and Courier Prime for code. Cinzel Decorative and Newsreader defaults are removed from the default stylesheet request (PLT-AC-56).
15
+ - `--font-content-title` uses `--font-title` (Ancizar Serif) instead of body face (PLT-AC-56).
16
+ - On the home hero, `.home-hero-family` renders in italic, primary color, with a highlighter mark wash scaled to ~40% intensity of detail action buttons, while `.home-hero-given` stays upright in default ink (PLT-AC-58).
17
+ - Heading and title selectors in ledger CSS no longer apply `text-transform: uppercase`, and title italics are restricted to `.home-hero-family` and `.section-title em` / `.page-header-title em` (PLT-AC-59, PLT-AC-60).
18
+ - On viewports at or above 900px, the Home Projects bench places the inventory column to the left of the specimen. Inventory buttons become logo-only with enlarged icon marks, keeping accessible labels (`aria-label`) and tooltips on hover/focus (`title`) (PLT-AC-61).
19
+
20
+ ## [2.0.4] — 2026-10-28
21
+
22
+ ### Fixed
23
+
24
+ - Mobile article top compresses dates and action buttons into one band and closes the gap before the reading column.
25
+ - Mobile top bar is shorter and hides on scroll-down (shows on scroll-up, focus, or Explore open).
26
+ - Home hero summary uses a ~65ch measure on desktop.
27
+ - Home section "View all" sits beside the section title instead of at the far right of the band.
28
+
5
29
  ## [2.0.3] — 2026-10-25
6
30
 
7
31
  ### Fixed
@@ -5,27 +5,27 @@
5
5
  # Consumed by theme-context.liquid → theme-vars-block.liquid / font-loader.
6
6
 
7
7
  fonts:
8
- title: "Cinzel Decorative"
9
- subtitle: "Newsreader"
8
+ title: "Ancizar Serif"
9
+ subtitle: "Ancizar Sans"
10
10
  label: "Marcellus SC"
11
- body: "Newsreader"
11
+ body: "Ancizar Sans"
12
12
  mono: "Courier Prime"
13
13
  code: "Courier Prime"
14
- cv_body: "Newsreader"
14
+ cv_body: "Ancizar Sans"
15
15
  cv_body_weight: 400
16
- # Fallback after the named face. Body is Newsreader, a serif. A sans
17
- # fallback reflows the hero summary when the webfont swaps in.
18
- body_fallback: "Georgia, serif"
19
- subtitle_fallback: "serif"
16
+ body_weight: 300
17
+ body_weight_mobile: 350
18
+ body_fallback: "sans-serif"
19
+ subtitle_fallback: "sans-serif"
20
20
  title_fallback: "serif"
21
21
  label_fallback: "serif"
22
- google_title_spec: "wght@400;700;900"
23
- google_subtitle_spec: "ital,opsz,wght@0,6..72,300;0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,300;1,6..72,400;1,6..72,500"
22
+ google_title_spec: "ital,wght@0,400;0,600;1,400"
23
+ google_subtitle_spec: "wght@300;350;400;600"
24
24
  google_label_spec: "wght@400;700"
25
- google_body_spec: "wght@300;400;500;600;700"
25
+ google_body_spec: "wght@300;350;400;600"
26
26
  google_mono_spec: "ital,wght@0,400;0,700;1,400;1,700"
27
27
  google_code_spec: "ital,wght@0,300;0,400;0,700;1,400;1,700"
28
- google_cv_body_spec: "wght@300;400;500;600;700"
28
+ google_cv_body_spec: "wght@300;350;400;600"
29
29
 
30
30
  motion:
31
31
  duration_fast: "0.15s"
@@ -375,6 +375,7 @@
375
375
  if (selectable) {
376
376
  row.setAttribute('aria-pressed', selected ? 'true' : 'false');
377
377
  row.setAttribute('aria-label', name);
378
+ row.setAttribute('title', name);
378
379
  }
379
380
 
380
381
  var initial = String(name || '?').charAt(0).toUpperCase();
@@ -55,8 +55,11 @@
55
55
  data-home-list
56
56
  data-tag-base="{{ _path }}"
57
57
  >
58
+ <div class="home-projects-inventory-wrap">
59
+ <span class="home-projects-inventory-label font-label">Explore projects</span>
60
+ <div class="home-project-inventory" data-home-inventory role="region" aria-label="Project inventory"></div>
61
+ </div>
58
62
  <div class="home-project-specimen-slot" data-home-specimen></div>
59
- <div class="home-project-inventory" data-home-inventory role="region" aria-label="Project inventory"></div>
60
63
  </div>
61
64
  {%- else -%}
62
65
  <div
@@ -19,6 +19,9 @@
19
19
  {% assign color_secondary = tc.secondary | default: "#287445" %}
20
20
  {% assign color_tertiary = tc.tertiary | default: "#746f30" %}
21
21
  {% assign font_cv_body_weight = sf.cv_body_weight | default: 400 %}
22
+ {% assign site_fonts = site.pandorga.fonts %}
23
+ {% assign font_body_weight = site_fonts.body_weight | default: sf.body_weight | default: 300 %}
24
+ {% assign font_body_weight_mobile = site_fonts.body_weight_mobile | default: sf.body_weight_mobile | default: 350 %}
22
25
  {% assign bg_secondary = tn.background_secondary | default: tn.surface_container_low | default: "#f5f5f5" %}
23
26
  {% assign surface_value = tn.surface | default: "#ffffff" %}
24
27
  {% assign surface_variant_value = tn.surface_variant | default: "#e2e2e2" %}
@@ -30,10 +33,11 @@
30
33
  --font-body: {{ site | pandorga_font_stack: "body" }};
31
34
  --font-cv-body: {{ site | pandorga_font_stack: "cv_body" }};
32
35
  --font-cv-body-weight: {{ font_cv_body_weight }};
36
+ --weight-body: {{ font_body_weight }};
37
+ --weight-body-mobile: {{ font_body_weight_mobile }};
33
38
  --font-serif: {{ site | pandorga_font_stack: "title" }};
34
- /* Content titles share the body face at 600 — Cinzel Decorative is
35
- reserved for page/display titles. */
36
- --font-content-title: var(--font-body);
39
+ /* Content titles use the title face (Ancizar Serif). */
40
+ --font-content-title: var(--font-title);
37
41
  --font-sans: var(--font-body);
38
42
  --font-mono: {{ site | pandorga_font_stack: "mono" }};
39
43
  --font-code: {{ site | pandorga_font_stack: "code" }};
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
 
data/assets/css/base.css CHANGED
@@ -39,6 +39,15 @@
39
39
  line-gap-override: 0%;
40
40
  }
41
41
 
42
+ @font-face {
43
+ font-family: "Pandorga Ancizar Sans Fallback";
44
+ src: local("Arial"), local("Helvetica"), local("Liberation Sans"), local("sans-serif");
45
+ size-adjust: 100%;
46
+ ascent-override: 90%;
47
+ descent-override: 25%;
48
+ line-gap-override: 0%;
49
+ }
50
+
42
51
  @font-face {
43
52
  font-family: "Pandorga Label Fallback";
44
53
  src: local("Times New Roman"), local("Times"), local("Liberation Serif");
@@ -163,7 +172,7 @@
163
172
  html {
164
173
  font-size: 110%;
165
174
 
166
- /* Display — Cinzel Decorative 400 */
175
+ /* Display — Ancizar Serif */
167
176
  --type-display: 2.9015rem;
168
177
  --type-display-lg: 3.6515rem;
169
178
  --type-page: 2.4015rem;
@@ -173,7 +182,7 @@ html {
173
182
  /* Phone steps for home band titles and card titles (PLT-AC-49). */
174
183
  --type-section-sm: 1.375rem;
175
184
 
176
- /* Content titles — Newsreader Regular 400 */
185
+ /* Content titles — Ancizar Serif */
177
186
  --type-title: 2.20165rem;
178
187
  --type-title-lg: 2.64165rem;
179
188
  --type-card: 1.4015rem;
@@ -226,6 +235,12 @@ html {
226
235
  }
227
236
  }
228
237
 
238
+ @media (max-width: 767px) {
239
+ :root, [data-theme] {
240
+ --weight-body: var(--weight-body-mobile, 350);
241
+ }
242
+ }
243
+
229
244
  /* `html body`, not `body`: Tailwind's Preflight ships `body { line-height:
230
245
  inherit }` from the CDN <style> that loads after this file, so a bare `body`
231
246
  selector here loses the tie on source order and the leading silently becomes
@@ -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-sm);
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-md);
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
- /* The date row's own top rule leaves --space-sm above the text. The
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 on one non-wrapping row. */
1239
1237
  .text-detail-sidebar .article-meta {
1240
- gap: 0;
1238
+ display: flex;
1239
+ flex-direction: row;
1240
+ flex-wrap: nowrap;
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,
@@ -1284,7 +1305,6 @@ nav.text-page-nav:not(:has(a)) {
1284
1305
  font-family: var(--font-label);
1285
1306
  font-size: var(--type-label-lg);
1286
1307
  letter-spacing: 0.08em;
1287
- text-transform: uppercase;
1288
1308
  color: var(--color-on-surface);
1289
1309
  font-weight: 600;
1290
1310
  margin: 0 0 var(--space-md);
@@ -169,13 +169,22 @@
169
169
  max-width: 100%;
170
170
  }
171
171
 
172
- .home-hero-given,
173
- .home-hero-family {
172
+ .home-hero-given {
174
173
  display: inline;
175
174
  }
176
175
 
177
176
  .home-hero-family {
177
+ display: inline;
178
+ font-style: italic;
178
179
  color: var(--color-primary);
180
+ --mark-ink: var(--color-primary);
181
+ --mark-1: calc(var(--mark-quiet-a) * 0.4);
182
+ --mark-2: calc(var(--mark-quiet-b) * 0.4);
183
+ --mark-3: calc(var(--mark-quiet-c) * 0.4);
184
+ background: linear-gradient(var(--mk-angle, 104deg), transparent var(--mk-s0, 0.9%), color-mix(in srgb, var(--mark-ink) var(--mark-1), transparent) var(--mk-s1, 2.6%), color-mix(in srgb, var(--mark-ink) var(--mark-2), transparent) var(--mk-mid, 58%), color-mix(in srgb, var(--mark-ink) var(--mark-3), transparent) var(--mk-end, 98%), transparent 100%) 0 var(--mk-y, 50%) / 100% var(--mk-h, 100%) no-repeat;
185
+ -webkit-box-decoration-break: clone;
186
+ box-decoration-break: clone;
187
+ padding: 0 0.1em;
179
188
  }
180
189
 
181
190
  @media (min-width: 640px) {
@@ -220,6 +229,12 @@
220
229
  line-height: var(--leading-body);
221
230
  }
222
231
 
232
+ @media (min-width: 768px) {
233
+ .home-hero-summary {
234
+ max-width: 65ch;
235
+ }
236
+ }
237
+
223
238
  /* No empty min-height. A guessed line count shifts the call to action
224
239
  when the summary is shorter or taller than the reserve. The paragraph
225
240
  is in the HTML when content/pages/home/summary.md or
@@ -415,7 +430,7 @@ html[data-theme="light"] .home-hero-plate {
415
430
  The banner is a 2-column grid so the lead's children join the template. */
416
431
  .home-band:not(.home-band--hero) .ledger-section-banner {
417
432
  display: grid;
418
- grid-template-columns: minmax(0, 1fr) auto;
433
+ grid-template-columns: auto auto 1fr;
419
434
  column-gap: var(--space-md);
420
435
  row-gap: var(--space-xs);
421
436
  align-items: baseline;
@@ -640,7 +655,7 @@ html[data-theme="light"] .home-hero-plate {
640
655
 
641
656
  [data-home-kind="articles"] .home-card-grid .card-title,
642
657
  [data-home-kind="articles"] .home-card-grid .ledger-card-title {
643
- font-size: calc(var(--type-card) + 2pt);
658
+ font-size: var(--type-card);
644
659
  }
645
660
 
646
661
  /* Phone only: the featured card's text sat against the top rule. One
@@ -658,6 +673,7 @@ html[data-theme="light"] .home-hero-plate {
658
673
  .home-project-specimen .ledger-card-title,
659
674
  .home-project-inventory-title {
660
675
  font-family: var(--font-content-title);
676
+ font-size: var(--type-card);
661
677
  font-weight: var(--weight-title);
662
678
  font-synthesis: none;
663
679
  }
@@ -983,6 +999,16 @@ html[data-theme="light"] .home-hero-plate {
983
999
  gap: var(--space-md);
984
1000
  }
985
1001
 
1002
+ .home-projects-inventory-label {
1003
+ display: block;
1004
+ font-family: var(--font-label);
1005
+ font-size: var(--type-label);
1006
+ font-weight: var(--weight-label);
1007
+ letter-spacing: 0.04em;
1008
+ color: var(--color-on-surface-variant);
1009
+ margin-bottom: var(--space-xs);
1010
+ }
1011
+
986
1012
  .home-project-inventory {
987
1013
  order: -1;
988
1014
  }
@@ -1307,10 +1333,7 @@ button.home-project-inventory-row {
1307
1333
 
1308
1334
  @media (min-width: 900px) {
1309
1335
 
1310
- /* The specimen column is 35% of the band (at least 20rem), which keeps the
1311
- 16:9 plate at 60% of the 7:5 split's height; the rack is 45% (at least
1312
- 21rem); the pair starts at the band's left edge and leaves the rest of
1313
- the row blank. The specimen sets the bench height; rack rows share it. */
1336
+ /* The inventory rack sits on the left (45%, min 21rem), and the specimen on the right (35%, min 20rem). */
1314
1337
  .home-projects-bench {
1315
1338
  --home-inventory-mark-size: 3rem;
1316
1339
  --home-inventory-row-min: calc(
@@ -1320,12 +1343,19 @@ button.home-project-inventory-row {
1320
1343
  single row to the full plate. */
1321
1344
  --home-inventory-row-max: calc(1.6 * var(--home-inventory-row-min));
1322
1345
  display: grid;
1323
- grid-template-columns: max(35%, 20rem) max(45%, 21rem);
1346
+ grid-template-columns: max(45%, 21rem) max(35%, 20rem);
1324
1347
  column-gap: var(--space-xl);
1325
1348
  justify-content: start;
1326
1349
  align-items: stretch;
1327
1350
  }
1328
1351
 
1352
+ .home-projects-inventory-wrap {
1353
+ display: flex;
1354
+ flex-direction: column;
1355
+ gap: var(--space-xs);
1356
+ width: 100%;
1357
+ }
1358
+
1329
1359
  .home-project-inventory {
1330
1360
  order: 0;
1331
1361
  display: flex;
@@ -1335,7 +1365,6 @@ button.home-project-inventory-row {
1335
1365
  width: 100%;
1336
1366
  min-height: var(--home-specimen-height);
1337
1367
  min-width: 0;
1338
- min-height: 0;
1339
1368
  overflow: hidden;
1340
1369
  padding: var(--space-md);
1341
1370
  background: var(--color-panel-contrast);
@@ -1348,7 +1377,7 @@ button.home-project-inventory-row {
1348
1377
  .home-project-inventory-row {
1349
1378
  flex: 1 1 0;
1350
1379
  align-items: center;
1351
- justify-content: flex-start;
1380
+ justify-content: center;
1352
1381
  gap: var(--space-md);
1353
1382
  height: auto;
1354
1383
  min-height: var(--home-inventory-row-min);
@@ -1367,7 +1396,12 @@ button.home-project-inventory-row {
1367
1396
  box-shadow: inset 3px 0 0 var(--color-primary);
1368
1397
  }
1369
1398
 
1370
- /* Square icon mark beside the two-line code + title body. */
1399
+ /* Logo-only buttons on desktop: hide text body visually. */
1400
+ .home-project-inventory-body {
1401
+ display: none;
1402
+ }
1403
+
1404
+ /* Square icon mark enlarged. */
1371
1405
  .home-project-inventory-media {
1372
1406
  display: block;
1373
1407
  flex: 0 0 auto;
@@ -1396,50 +1430,17 @@ button.home-project-inventory-row {
1396
1430
  }
1397
1431
 
1398
1432
  .home-project-inventory-mark .material-symbols-outlined {
1399
- font-size: 1.5rem;
1433
+ font-size: 1.75rem;
1400
1434
  }
1401
1435
 
1402
1436
  .home-project-inventory-mark--initial {
1403
1437
  font-family: var(--font-content-title);
1404
- font-size: var(--type-card);
1438
+ font-size: var(--type-card-lg);
1405
1439
  font-weight: var(--weight-title);
1406
1440
  line-height: 1;
1407
1441
  color: var(--color-on-surface);
1408
1442
  }
1409
1443
 
1410
- .home-project-inventory-body {
1411
- display: flex;
1412
- flex-direction: column;
1413
- justify-content: center;
1414
- align-self: center;
1415
- gap: 0.15rem;
1416
- flex: 1 1 0;
1417
- min-width: 0;
1418
- padding-top: 0;
1419
- }
1420
-
1421
- .home-project-inventory-code .ledger-meta {
1422
- font-size: var(--type-label-lg);
1423
- }
1424
-
1425
- .home-project-inventory-title {
1426
- margin: 0;
1427
- font-family: var(--font-content-title);
1428
- font-size: var(--type-card);
1429
- font-weight: var(--weight-title);
1430
- line-height: 1.2;
1431
- color: var(--color-on-surface);
1432
- overflow: hidden;
1433
- text-overflow: ellipsis;
1434
- white-space: nowrap;
1435
- }
1436
-
1437
- .home-project-inventory-row:hover .home-project-inventory-title,
1438
- .home-project-inventory-row:focus-visible .home-project-inventory-title,
1439
- .home-project-inventory-row.is-selected .home-project-inventory-title {
1440
- color: var(--color-primary);
1441
- }
1442
-
1443
1444
  /* Two classes: the glyph also carries .material-symbols-outlined, whose
1444
1445
  font stylesheet loads after this one and sets display. */
1445
1446
  .home-project-inventory-row .home-project-inventory-glyph {
@@ -1732,7 +1733,7 @@ button.home-project-inventory-row {
1732
1733
  that names the person, says what he does and offers the full record, rather
1733
1734
  than a name band followed by a summary band saying the same thing twice. */
1734
1735
 
1735
- .home-hero-summary {
1736
+ .home-hero .home-hero-summary {
1736
1737
  max-width: var(--reading-measure);
1737
1738
  margin-top: 0;
1738
1739
  font-size: var(--type-body);
@@ -1740,6 +1741,12 @@ button.home-project-inventory-row {
1740
1741
  color: var(--color-legend);
1741
1742
  }
1742
1743
 
1744
+ @media (min-width: 768px) {
1745
+ .home-hero .home-hero-summary {
1746
+ max-width: 65ch;
1747
+ }
1748
+ }
1749
+
1743
1750
  .home-hero-summary p {
1744
1751
  margin-bottom: var(--space-md);
1745
1752
  }
@@ -601,7 +601,6 @@
601
601
  font-family: var(--font-label);
602
602
  font-size: var(--type-label-lg);
603
603
  letter-spacing: 0.1em;
604
- text-transform: uppercase;
605
604
  font-weight: 600;
606
605
  color: var(--color-on-surface);
607
606
  }
@@ -19,10 +19,10 @@ pandorga:
19
19
  home:
20
20
  hero_art: page/hero-default.html
21
21
  fonts:
22
- title: "Cinzel Decorative"
22
+ title: "Ancizar Serif"
23
23
  body:
24
- family: "Newsreader"
25
- weights: [300, 400, 500, 600, 700]
24
+ family: "Ancizar Sans"
25
+ weights: [300, 350, 400, 600]
26
26
  mono:
27
27
  family: "Courier Prime"
28
28
  weights: [400, 700]
@@ -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.3"
27
+ # GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v2.1.0"
28
28
  gem "csv"
29
29
  gem "base64"
30
30
  RUBY
@@ -22,21 +22,18 @@ module Pandorga
22
22
  "Cinzel" => "Pandorga Cinzel Fallback",
23
23
  "Newsreader" => "Pandorga Body Fallback",
24
24
  "Ancizar Serif" => "Pandorga Ancizar Fallback",
25
+ "Ancizar Sans" => "Pandorga Ancizar Sans Fallback",
25
26
  "Marcellus SC" => "Pandorga Label Fallback"
26
27
  }.freeze
27
28
 
28
29
  DEFAULTS = {
29
- title: { family: "Cinzel Decorative", spec: "wght@400;700;900", fallback: "serif" },
30
- subtitle: {
31
- family: "Newsreader",
32
- spec: "ital,opsz,wght@0,6..72,300;0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,300;1,6..72,400;1,6..72,500",
33
- fallback: "serif"
34
- },
30
+ title: { family: "Ancizar Serif", spec: "ital,wght@0,400;0,600;1,400", fallback: "serif" },
31
+ subtitle: { family: "Ancizar Sans", spec: "wght@300;350;400;600", fallback: "sans-serif" },
35
32
  label: { family: "Marcellus SC", spec: "wght@400;700", fallback: "serif" },
36
- body: { family: "Newsreader", spec: "wght@300;400;500;600;700", fallback: "Georgia, serif" },
33
+ body: { family: "Ancizar Sans", spec: "wght@300;350;400;600", fallback: "sans-serif" },
37
34
  mono: { family: "Courier Prime", spec: "ital,wght@0,400;0,700;1,400;1,700", fallback: MONO_FALLBACK },
38
35
  code: { family: "Courier Prime", spec: "ital,wght@0,300;0,400;0,700;1,400;1,700", fallback: MONO_FALLBACK },
39
- cv_body: { family: "Newsreader", spec: "wght@300;400;500;600;700", fallback: "Georgia, serif" }
36
+ cv_body: { family: "Ancizar Sans", spec: "wght@300;350;400;600", fallback: "sans-serif" }
40
37
  }.freeze
41
38
 
42
39
  def resolve(site_fonts, theme_fonts = nil)
@@ -57,10 +57,10 @@ fonts:
57
57
  keys:
58
58
  title:
59
59
  type: string or map
60
- notes: Display face. Default family `Cinzel Decorative`, weights 400, 700, and 900
60
+ notes: Display face. Default family `Ancizar Serif`, weights 400, 600, italic 400
61
61
  body:
62
62
  type: string or map
63
- notes: Reading face. Default family `Newsreader`, weights 300, 400, 500, 600, and 700
63
+ notes: Reading face. Default family `Ancizar Sans`, weights 300, 350, 400, and 600
64
64
  mono:
65
65
  type: string or map
66
66
  notes: Monospace face. Default family `Courier Prime`, roman and italic at 400 and 700
@@ -70,6 +70,12 @@ fonts:
70
70
  subtitle:
71
71
  type: string or map
72
72
  notes: Optional tagline face. Omitted, it follows `body` when the theme used the same family
73
+ body_weight:
74
+ type: integer
75
+ notes: Desktop body weight (default 300)
76
+ body_weight_mobile:
77
+ type: integer
78
+ notes: Mobile body weight (default 350)
73
79
 
74
80
  taxonomies:
75
81
  title: "`pandorga.taxonomies`"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pandorga
4
- VERSION = "2.0.3"
4
+ VERSION = "2.1.0"
5
5
  end
@@ -45,6 +45,7 @@
45
45
  ">ARCO<",
46
46
  ">route<",
47
47
  "aria-pressed=\"false\"",
48
+ "title=\"ARCO\"",
48
49
  "data-slug=\"arco\""
49
50
  ]
50
51
  }
@@ -247,7 +247,7 @@ require_declarations(
247
247
  )
248
248
  require_declarations(
249
249
  errors, "assets/css/ledger/home.css", home_sheet,
250
- ".home-projects-bench", ["grid-template-columns: max(35%, 20rem) max(45%, 21rem)", "column-gap: var(--space-xl)", "justify-content: start", "--home-inventory-row-max: calc(1.6 * var(--home-inventory-row-min))"]
250
+ ".home-projects-bench", ["grid-template-columns: max(45%, 21rem) max(35%, 20rem)", "column-gap: var(--space-xl)", "justify-content: start", "--home-inventory-row-max: calc(1.6 * var(--home-inventory-row-min))"]
251
251
  )
252
252
  require_declarations(
253
253
  errors, "assets/css/ledger/home.css", home_sheet,
@@ -272,6 +272,24 @@ require_declarations(
272
272
  end
273
273
  end
274
274
 
275
+ # PLT-AC-60. Heading and title selectors in ledger CSS do not use text-transform: uppercase.
276
+ title_selector_pattern = /^\.(?:page-header-title|section-title|home-hero-title|card-title|ledger-card-title|text-title|home-featured-title|ledger-row-title|source-card-title|bib-card-title|home-project-inventory-title|home-index-title|article-title|article-related-title|projects-dossier-title)\b/
277
+
278
+ sheets.each do |path|
279
+ relative = Pathname.new(path).relative_path_from(REPO_ROOT)
280
+ cleaned = strip_noise(File.read(path))
281
+ cleaned.scan(/([^{}]+)\{([^{}]*)\}/m) do |sel, body|
282
+ next unless body.match?(/text-transform\s*:\s*uppercase/i)
283
+
284
+ sel.split(",").each do |part|
285
+ name = part.gsub(/\s+/, " ").strip
286
+ next unless name.match?(title_selector_pattern)
287
+
288
+ errors << "#{relative}: title selector #{name} applies text-transform: uppercase (PLT-AC-60)"
289
+ end
290
+ end
291
+ end
292
+
275
293
  # PLT-AC-23. The home hero summary uses --color-legend, the ink of the other
276
294
  # description texts.
277
295
  require_declarations(
@@ -147,12 +147,12 @@ end
147
147
  warn "FAIL test-examples-build: #{name} home is missing JSON-LD"
148
148
  exit 1
149
149
  end
150
- ledger_faces = "family=Cinzel+Decorative:wght@400;700;900"
150
+ ledger_faces = "family=Ancizar+Serif:ital,wght@0,400;0,600;1,400"
151
151
  unless html.include?(ledger_faces) && html.include?("&display=swap") && html.include?("__pandorgaFontsHref")
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', 'Pandorga Title Fallback', serif")
155
+ unless html.include?("--font-title: 'Ancizar Serif', 'Pandorga Ancizar Fallback', serif")
156
156
  warn "FAIL test-examples-build: #{name} home dropped the title face"
157
157
  exit 1
158
158
  end
@@ -16,9 +16,9 @@ end
16
16
 
17
17
  require_relative "../../lib/pandorga/fonts"
18
18
 
19
- DEFAULT_HREF = "https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@400;700;900" \
19
+ DEFAULT_HREF = "https://fonts.googleapis.com/css2?family=Ancizar+Serif:ital,wght@0,400;0,600;1,400" \
20
20
  "&family=Marcellus+SC:wght@400;700" \
21
- "&family=Newsreader:wght@300;400;500;600;700" \
21
+ "&family=Ancizar+Sans:wght@300;350;400;600" \
22
22
  "&family=Courier+Prime:ital,wght@0,400;0,700;1,400;1,700" \
23
23
  "&display=swap"
24
24
 
@@ -27,11 +27,12 @@ bare = Pandorga::Fonts.resolve(nil, nil)
27
27
  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
+ fail!("default stylesheet href contained Cinzel") if DEFAULT_HREF.include?("Cinzel")
30
31
 
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"
32
+ fail!("title stack dropped Ancizar Serif") unless Pandorga::Fonts.css_stack(from_theme, "title") ==
33
+ "'Ancizar Serif', 'Pandorga Ancizar Fallback', serif"
34
+ fail!("body stack dropped Ancizar Sans") unless Pandorga::Fonts.css_stack(from_theme, "body") ==
35
+ "'Ancizar Sans', 'Pandorga Ancizar Sans Fallback', sans-serif"
35
36
  fail!("label stack dropped the metric fallback") unless Pandorga::Fonts.css_stack(from_theme, "label") ==
36
37
  "'Marcellus SC', 'Pandorga Label Fallback', serif"
37
38
  fail!("mono stack dropped Courier Prime") unless Pandorga::Fonts.css_stack(from_theme, "mono").start_with?("'Courier Prime', ui-monospace")
@@ -43,7 +44,7 @@ site_faces = Pandorga::Fonts.resolve(
43
44
  fail!("Cinzel stack missed its metric fallback") unless Pandorga::Fonts.css_stack(site_faces, "title") ==
44
45
  "'Cinzel', 'Pandorga Cinzel Fallback', serif"
45
46
  fail!("Ancizar stack missed its metric fallback") unless Pandorga::Fonts.css_stack(site_faces, "body") ==
46
- "'Ancizar Serif', 'Pandorga Ancizar Fallback', Georgia, serif"
47
+ "'Ancizar Serif', 'Pandorga Ancizar Fallback', sans-serif"
47
48
 
48
49
  custom = Pandorga::Fonts.resolve(
49
50
  {
@@ -77,7 +78,7 @@ end
77
78
  fail!("corrupted 'behind' left by a font rename (#{broken.size} files): #{broken.join(', ')}") unless broken.empty?
78
79
  fail!("code face did not follow mono") unless Pandorga::Fonts.css_stack(custom, "code").start_with?("'IBM Plex Mono'")
79
80
 
80
- kept = Pandorga::Fonts.resolve({ "title" => "Cinzel Decorative" }, theme)
81
+ kept = Pandorga::Fonts.resolve({ "title" => "Ancizar Serif" }, theme)
81
82
  fail!("naming the default title changed the stylesheet") unless Pandorga::Fonts.stylesheet_href(kept) == DEFAULT_HREF
82
83
 
83
84
  ignored = Pandorga::Fonts.resolve({ "title" => "Fraunces</style>" }, theme)
@@ -104,6 +104,6 @@ fail!("font loader missing") unless font_at && script_at && base_at
104
104
  fail!("font stylesheet precedes the preload script") unless script_at < font_at
105
105
  fail!("font stylesheet precedes local CSS") unless base_at < font_at
106
106
  fail!("font host is not preconnected before the preload script") unless preconnect_at && preconnect_at < script_at
107
- fail!("body fallback is still a sans") unless shared.include?('body_fallback: "Georgia, serif"')
107
+ fail!("body fallback is not sans-serif") unless shared.include?('body_fallback: "sans-serif"')
108
108
 
109
109
  puts "PASS test-hero-summary"
@@ -19,8 +19,8 @@ fail!("featured article title is not 2pt larger") unless home.include?(
19
19
  '[data-home-kind="articles"] .home-featured-title'
20
20
  ) && home.include?("calc(var(--home-featured-title-size) + 2pt)")
21
21
 
22
- fail!("listed article titles are not 2pt larger") unless home.include?(
23
- "calc(var(--type-card) + 2pt)"
22
+ fail!("listed article titles are not on --type-card step") unless home.include?(
23
+ "font-size: var(--type-card);"
24
24
  ) && home.include?('[data-home-kind="articles"] .home-card-grid .card-title')
25
25
 
26
26
  phone = home[/^@media \(max-width: 767px\) \{\n \[data-home-kind="articles"\] \.home-featured \{.*?\n \}/m]
@@ -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" => "minmax(0, 1fr) auto"
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
@@ -80,6 +80,7 @@ TESTS=(
80
80
  scripts/test/test-studio-section-texts.rb
81
81
  scripts/test/test-home-article-cards.rb
82
82
  scripts/test/test-parallel-content-publish.rb
83
+ scripts/test/test-mobile-density-home-measure.rb
83
84
  )
84
85
 
85
86
  failed=0
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.3
4
+ version: 2.1.0
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
@@ -570,7 +571,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
570
571
  requirements:
571
572
  - - ">="
572
573
  - !ruby/object:Gem::Version
573
- version: 3.3.0
574
+ version: 3.2.0
574
575
  required_rubygems_version: !ruby/object:Gem::Requirement
575
576
  requirements:
576
577
  - - ">="