pandorga 1.3.5 → 1.3.7
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 +20 -0
- data/DESIGN.md +14 -5
- data/_includes/content-runtime/43-blocks.html +104 -16
- data/_includes/content-runtime/90-bootstrap.html +1 -0
- data/_includes/home/band-listing.html +1 -1
- data/_layouts/home.html +28 -23
- data/assets/css/ledger/home.css +217 -164
- data/lib/pandorga/commands/new.rb +1 -1
- data/lib/pandorga/version.rb +1 -1
- data/scripts/test/render-parity/blocks/project-inventory-row-icon.json +29 -0
- data/scripts/test/render-parity/blocks/project-inventory-row.json +46 -0
- data/scripts/test/render-parity/blocks/project-specimen-glyph.json +32 -0
- data/scripts/test/render-parity/blocks/project-specimen-initial-badge.json +31 -0
- data/scripts/test/render-parity/blocks/project-specimen-initial.json +32 -0
- data/scripts/test/render-parity/blocks/project-specimen.json +69 -0
- data/scripts/test/render-parity/run-absent-field-checks.mjs +40 -0
- data/scripts/test/test-ledger-blocks.rb +32 -0
- data/scripts/validate.sh +1 -0
- data/templates/portfolio/README.md +36 -0
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea6a5c5a2e3a25b789f30f4ade187c45f3167de07673934e29ee608923e4b1a9
|
|
4
|
+
data.tar.gz: 211bcfc642bd1b0f1c587fca41a225f264d2121bb914bc469d66c6e82fb58337
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 87f3c7a0881d7a68b901be98353bebc45419482c25334bd721eaaa989bc25ceeb362d36bb3dcdff6f380c3a1b7c359e3679e4b78d6b1b1b84fb1ff2c7b1a8575
|
|
7
|
+
data.tar.gz: 25e0640b55107ff5c5f3c948547c3d91662a70b36e8bf40d717e6a7ca6af0056ac53114de574fb0db1c577cfea41b6602ac45c397234e9a2bfbc724973cbf301
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.7] — 2026-10-06
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Home projects band specimen bench: project specimen, rack card, and tape
|
|
8
|
+
blocks, with inventory rows and glyph/initial fallbacks when an icon is
|
|
9
|
+
missing
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Specimen stays half-width and 16:9; title always sits below the plate with
|
|
14
|
+
roomier copy and links; drafting tape removed from the home layout
|
|
15
|
+
|
|
16
|
+
## [1.3.6] — 2026-10-05
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Home specimen is taller so project tags are not clipped, with more space
|
|
21
|
+
between the poster and the project title
|
|
22
|
+
|
|
3
23
|
## [1.3.5] — 2026-10-05
|
|
4
24
|
|
|
5
25
|
### Fixed
|
data/DESIGN.md
CHANGED
|
@@ -935,11 +935,20 @@ Letter-spacing in use, by frequency: `0.1em` (12), `0.08em` (10), `0.12em` (7),
|
|
|
935
935
|
writing, rascunhos, systems, ideas graph. The featured band carries a 6rem ×
|
|
936
936
|
3px `primary` rule at its top-left corner as a structural mark. The three
|
|
937
937
|
writing cards take `primary` / `secondary` / `tertiary` 2px top rules in
|
|
938
|
-
rotation. The systems band is a specimen
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
938
|
+
rotation. The systems band is a specimen bench. The specimen is a
|
|
939
|
+
fixed-height system card (2px `primary` top rule; strip with folio, label,
|
|
940
|
+
start year and status; a 16:9 plate, the poster with an icon badge or the
|
|
941
|
+
icon plate, the name's initial standing in for a missing icon; the title
|
|
942
|
+
always below the plate; a tagline clamped to the
|
|
943
|
+
height the plate leaves; tags, outbound links and an emphasis "Open
|
|
944
|
+
project" cue). From 900px it takes 50% of the band, beside a 34%
|
|
945
|
+
`panelContrast` filing rack of equal-flex project cards (16:9 thumb, code
|
|
946
|
+
line, title, tagline; the selected card steps to `surfaceHigh` with a 3px
|
|
947
|
+
`primary` inset rule), the rest left blank. Rack click swaps the specimen
|
|
948
|
+
in place; specimen click opens the project. Below 900px the rack is an
|
|
949
|
+
icon strip above the specimen, one equal column per project (up to six)
|
|
950
|
+
so it spans the band. Contract: `PLT-AC-13`–`17` in
|
|
951
|
+
`templates/portfolio/README.md`. Below 768px the hero portrait runs to the screen edge, pulled
|
|
943
952
|
out of the band's gutter by exactly `--gutter-mobile` on each side and losing
|
|
944
953
|
its side hairlines, which at the edge would read as a frame cut off.
|
|
945
954
|
* **Writing index.** Masthead over a hairline, then a `panelContrast` filter
|
|
@@ -324,9 +324,15 @@
|
|
|
324
324
|
].join('');
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
327
|
+
function twoDigits(value) {
|
|
328
|
+
var label = String(Number(value));
|
|
329
|
+
return label.length < 2 ? '0' + label : label;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* Rack card for the home portfolio bench.
|
|
333
|
+
* Desktop: thumb (or icon, or initial) beside a code line, title and
|
|
334
|
+
* tagline. The mobile strip shows the project glyph only. Selectable rows
|
|
335
|
+
* update the specimen. */
|
|
330
336
|
function renderProjectInventoryRow(item, options) {
|
|
331
337
|
options = options || {};
|
|
332
338
|
var f = blockFields(item);
|
|
@@ -352,22 +358,28 @@
|
|
|
352
358
|
|
|
353
359
|
var media = renderPosterMedia(fm.thumbnail, name);
|
|
354
360
|
var initial = String(name || '?').charAt(0).toUpperCase();
|
|
355
|
-
if (!media) {
|
|
361
|
+
if (!media && fm.icon) {
|
|
362
|
+
media = '<div class="home-project-inventory-mark" aria-hidden="true">' +
|
|
363
|
+
'<span class="material-symbols-outlined">' + escapeHtml(fm.icon) + '</span></div>';
|
|
364
|
+
} else if (!media) {
|
|
356
365
|
media = '<div class="home-project-inventory-mark home-project-inventory-mark--initial" aria-hidden="true">' +
|
|
357
366
|
escapeHtml(initial) + '</div>';
|
|
358
367
|
}
|
|
359
368
|
|
|
360
|
-
var html = '';
|
|
361
|
-
if (index != null && index !== '') {
|
|
362
|
-
var label = String(Number(index));
|
|
363
|
-
if (label.length < 2) label = '0' + label;
|
|
364
|
-
html += '<span class="home-project-inventory-index" aria-hidden="true">' +
|
|
365
|
-
escapeHtml(label) + '</span>';
|
|
366
|
-
}
|
|
367
|
-
if (media) {
|
|
368
|
-
html += '<div class="home-project-inventory-media">' + media + '</div>';
|
|
369
|
-
}
|
|
369
|
+
var html = '<div class="home-project-inventory-media">' + media + '</div>';
|
|
370
370
|
html += '<div class="home-project-inventory-body">';
|
|
371
|
+
|
|
372
|
+
var code = renderMetaStrip([
|
|
373
|
+
index != null && index !== ''
|
|
374
|
+
? '<span class="code-technical home-project-inventory-index">' + escapeHtml(twoDigits(index)) + '</span>'
|
|
375
|
+
: '',
|
|
376
|
+
fm.label ? escapeHtml(fm.label) : ''
|
|
377
|
+
]);
|
|
378
|
+
var status = fm.status ? '<span class="ledger-status">' + escapeHtml(fm.status) + '</span>' : '';
|
|
379
|
+
if (code || status) {
|
|
380
|
+
html += '<div class="home-project-inventory-code">' + code + status + '</div>';
|
|
381
|
+
}
|
|
382
|
+
|
|
371
383
|
if (selectable) {
|
|
372
384
|
html += '<h3 class="home-project-inventory-title card-title">' +
|
|
373
385
|
escapeHtml(name) + '</h3>';
|
|
@@ -376,6 +388,9 @@
|
|
|
376
388
|
'<a class="ledger-card-stretch" href="' + escapeAttr(href) + '">' +
|
|
377
389
|
escapeHtml(name) + '</a></h3>';
|
|
378
390
|
}
|
|
391
|
+
if (fm.tagline) {
|
|
392
|
+
html += '<p class="home-project-inventory-copy font-body">' + escapeHtml(fm.tagline) + '</p>';
|
|
393
|
+
}
|
|
379
394
|
html += '</div>';
|
|
380
395
|
if (fm.icon) {
|
|
381
396
|
html += '<span class="home-project-inventory-glyph material-symbols-outlined" aria-hidden="true">' +
|
|
@@ -441,12 +456,85 @@
|
|
|
441
456
|
// Outbound links only. `.ledger-card-foot` draws a rule above itself, so
|
|
442
457
|
// an empty one rules off nothing; renderLedgerRow and renderArchiveCard
|
|
443
458
|
// guard it the same way.
|
|
459
|
+
var links = renderProjectLinks(fm, options);
|
|
460
|
+
if (links) {
|
|
461
|
+
html += '<div class="ledger-card-foot ledger-card-links">' + links + '</div>';
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
card.innerHTML = html;
|
|
465
|
+
return card;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function renderProjectLinks(fm, options) {
|
|
444
469
|
var links = [];
|
|
445
470
|
if (fm.github) links.push(renderCtaLink(fm.github, options.repoLabel || 'Repository', { icon: 'code' }));
|
|
446
471
|
if (fm.article_url) links.push(renderCtaLink(fm.article_url, options.articleLabel || 'Article', { icon: 'article' }));
|
|
447
472
|
if (fm.external_url) links.push(renderCtaLink(fm.external_url, options.externalLabel || 'Visit', { icon: 'public' }));
|
|
448
|
-
|
|
449
|
-
|
|
473
|
+
return links.join('');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/* Home specimen — the featured project on the portfolio bench. A system
|
|
477
|
+
* card whose strip carries folio, label, start year and status, with the
|
|
478
|
+
* title below a 16:9 plate: the thumbnail (icon badge on it) or else the
|
|
479
|
+
* icon plate. `openLabel` adds a visual "open" cue; it is not a second
|
|
480
|
+
* link, the stretched title is. */
|
|
481
|
+
function renderProjectSpecimen(item, options) {
|
|
482
|
+
options = options || {};
|
|
483
|
+
var f = blockFields(item);
|
|
484
|
+
var fm = f.fm;
|
|
485
|
+
var href = options.href || '#';
|
|
486
|
+
var name = fm.project || f.title;
|
|
487
|
+
|
|
488
|
+
var card = document.createElement('article');
|
|
489
|
+
card.className = 'ledger-system-card home-project-specimen group';
|
|
490
|
+
|
|
491
|
+
var year = String(fm.start_date || '').match(/^\d{4}/);
|
|
492
|
+
var meta = renderMetaStrip([
|
|
493
|
+
options.folio ? '<span class="code-technical home-specimen-folio">#' + escapeHtml(twoDigits(options.folio)) + '</span>' : '',
|
|
494
|
+
fm.label ? escapeHtml(fm.label) : '',
|
|
495
|
+
year ? '<span>' + escapeHtml(year[0]) + '</span>' : ''
|
|
496
|
+
]);
|
|
497
|
+
var status = fm.status ? '<span class="ledger-status">' + escapeHtml(fm.status) + '</span>' : '';
|
|
498
|
+
|
|
499
|
+
var html = '';
|
|
500
|
+
if (meta || status) {
|
|
501
|
+
html += '<div class="home-specimen-strip">' + meta + status + '</div>';
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// The name's initial stands in for a missing icon, so every project
|
|
505
|
+
// carries the same badge and plate elements.
|
|
506
|
+
var mark = fm.icon
|
|
507
|
+
? { cls: 'material-symbols-outlined', text: fm.icon }
|
|
508
|
+
: { cls: 'is-initial', text: String(name || '').charAt(0).toUpperCase() };
|
|
509
|
+
var poster = renderPosterMedia(fm.thumbnail, name);
|
|
510
|
+
html += '<div class="home-specimen-figure">';
|
|
511
|
+
if (poster) {
|
|
512
|
+
html += poster + '<span class="home-specimen-badge ' + mark.cls + '" aria-hidden="true">' +
|
|
513
|
+
escapeHtml(mark.text) + '</span>';
|
|
514
|
+
} else {
|
|
515
|
+
html += '<div class="projects-plate projects-plate--mark" aria-hidden="true">' +
|
|
516
|
+
'<span class="' + mark.cls + ' projects-plate-glyph">' + escapeHtml(mark.text) + '</span></div>';
|
|
517
|
+
}
|
|
518
|
+
html += '<h3 class="card-title ledger-card-title home-specimen-title"><a class="ledger-card-stretch" href="' +
|
|
519
|
+
escapeAttr(href) + '">' + escapeHtml(name) + '</a></h3>';
|
|
520
|
+
html += '</div>';
|
|
521
|
+
|
|
522
|
+
var summary = fm.tagline || f.preview;
|
|
523
|
+
if (summary) {
|
|
524
|
+
html += '<p class="ledger-card-copy font-body">' + escapeHtml(summary) + '</p>';
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
html += renderTagChips(f.tags, { hrefFor: options.tagHref });
|
|
528
|
+
|
|
529
|
+
var links = renderProjectLinks(fm, options);
|
|
530
|
+
var open = options.openLabel
|
|
531
|
+
? '<span class="ledger-cta ledger-cta--emphasis home-specimen-open" aria-hidden="true"><span>' +
|
|
532
|
+
escapeHtml(options.openLabel) +
|
|
533
|
+
'</span><span class="material-symbols-outlined ledger-cta-arrow">chevron_right</span></span>'
|
|
534
|
+
: '';
|
|
535
|
+
if (links || open) {
|
|
536
|
+
html += '<div class="ledger-card-foot home-specimen-foot">' +
|
|
537
|
+
(links ? '<div class="ledger-card-links">' + links + '</div>' : '') + open + '</div>';
|
|
450
538
|
}
|
|
451
539
|
|
|
452
540
|
card.innerHTML = html;
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
renderArchiveCard: renderArchiveCard,
|
|
65
65
|
renderSystemCard: renderSystemCard,
|
|
66
66
|
renderProjectInventoryRow: renderProjectInventoryRow,
|
|
67
|
+
renderProjectSpecimen: renderProjectSpecimen,
|
|
67
68
|
renderPosterMedia: renderPosterMedia,
|
|
68
69
|
ensureBibliographyIndex: ensureBibliographyIndex,
|
|
69
70
|
bibField: bibField,
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
data-home-list
|
|
47
47
|
data-tag-base="{{ _path }}"
|
|
48
48
|
>
|
|
49
|
-
<div data-home-specimen></div>
|
|
49
|
+
<div class="home-project-specimen-slot" data-home-specimen></div>
|
|
50
50
|
<div class="home-project-inventory" data-home-inventory aria-label="Project inventory"></div>
|
|
51
51
|
</div>
|
|
52
52
|
{%- else -%}
|
data/_layouts/home.html
CHANGED
|
@@ -234,32 +234,47 @@ layout: shell
|
|
|
234
234
|
eligible: function () { return true; }
|
|
235
235
|
});
|
|
236
236
|
var selectedSlug = (featuredPick && featuredPick.slug) || portfolio[0].slug;
|
|
237
|
-
var iconSlots = 6;
|
|
237
|
+
var iconSlots = Math.min(portfolio.length, 6);
|
|
238
238
|
|
|
239
239
|
function entryBySlug(slug) {
|
|
240
240
|
return portfolio.filter(function (entry) { return entry.slug === slug; })[0] || portfolio[0];
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
function positionOf(slug) {
|
|
244
|
+
for (var i = 0; i < portfolio.length; i += 1) {
|
|
245
|
+
if (portfolio[i].slug === slug) return i;
|
|
246
|
+
}
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* Specimen and inventory sizes are CSS-fixed (no ResizeObserver), so
|
|
251
|
+
swapping projects never moves the band. */
|
|
243
252
|
function paintSpecimen(entry) {
|
|
244
253
|
specimenSlot.innerHTML = '';
|
|
245
|
-
|
|
246
|
-
poster plate only, so they do not overlap the image. Meta strip
|
|
247
|
-
is suppressed; tags take that slot below the plate. Specimen and
|
|
248
|
-
inventory sizes are CSS-fixed (no ResizeObserver). */
|
|
249
|
-
specimenSlot.appendChild(runtime.renderSystemCard(asItem(entry), {
|
|
254
|
+
specimenSlot.appendChild(runtime.renderProjectSpecimen(asItem(entry), {
|
|
250
255
|
href: entry.url || runtime.projectUrl(entry.slug),
|
|
251
256
|
tagHref: systemTagHref,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
metaParts: []
|
|
257
|
+
folio: positionOf(entry.slug) + 1,
|
|
258
|
+
openLabel: 'Open project'
|
|
255
259
|
}));
|
|
256
260
|
}
|
|
257
261
|
|
|
262
|
+
function select(slug) {
|
|
263
|
+
if (selectedSlug === slug) return;
|
|
264
|
+
selectedSlug = slug;
|
|
265
|
+
paintSpecimen(entryBySlug(slug));
|
|
266
|
+
Array.prototype.forEach.call(inventorySlot.children, function (node) {
|
|
267
|
+
var isSelected = node.getAttribute('data-slug') === selectedSlug;
|
|
268
|
+
node.classList.toggle('is-selected', isSelected);
|
|
269
|
+
node.setAttribute('aria-pressed', isSelected ? 'true' : 'false');
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
258
273
|
function paintInventory() {
|
|
259
274
|
inventorySlot.hidden = false;
|
|
260
275
|
inventorySlot.innerHTML = '';
|
|
261
|
-
/*
|
|
262
|
-
|
|
276
|
+
/* One mobile column per project, up to six a row, so the strip
|
|
277
|
+
always spans the band. */
|
|
263
278
|
inventorySlot.style.setProperty('--home-project-icon-count', String(iconSlots));
|
|
264
279
|
portfolio.forEach(function (entry, i) {
|
|
265
280
|
var row = runtime.renderProjectInventoryRow(asItem(entry), {
|
|
@@ -267,21 +282,11 @@ layout: shell
|
|
|
267
282
|
selected: entry.slug === selectedSlug,
|
|
268
283
|
index: i + 1
|
|
269
284
|
});
|
|
270
|
-
function select()
|
|
271
|
-
if (selectedSlug === entry.slug) return;
|
|
272
|
-
selectedSlug = entry.slug;
|
|
273
|
-
paintSpecimen(entry);
|
|
274
|
-
Array.prototype.forEach.call(inventorySlot.children, function (node) {
|
|
275
|
-
var isSelected = node.getAttribute('data-slug') === selectedSlug;
|
|
276
|
-
node.classList.toggle('is-selected', isSelected);
|
|
277
|
-
node.setAttribute('aria-pressed', isSelected ? 'true' : 'false');
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
row.addEventListener('click', select);
|
|
285
|
+
row.addEventListener('click', function () { select(entry.slug); });
|
|
281
286
|
row.addEventListener('keydown', function (event) {
|
|
282
287
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
283
288
|
event.preventDefault();
|
|
284
|
-
select();
|
|
289
|
+
select(entry.slug);
|
|
285
290
|
}
|
|
286
291
|
});
|
|
287
292
|
inventorySlot.appendChild(row);
|