pandorga 1.3.6 → 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 +13 -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 +214 -163
- 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,18 @@
|
|
|
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
|
+
|
|
3
16
|
## [1.3.6] — 2026-10-05
|
|
4
17
|
|
|
5
18
|
### 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);
|
data/assets/css/ledger/home.css
CHANGED
|
@@ -943,10 +943,13 @@ html[data-theme="light"] .home-hero-plate {
|
|
|
943
943
|
}
|
|
944
944
|
}
|
|
945
945
|
|
|
946
|
-
/* ── Engineering artifacts: specimen
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
946
|
+
/* ── Engineering artifacts: specimen bench ──
|
|
947
|
+
A specimen card beside a filing rack of project cards. The specimen has a
|
|
948
|
+
fixed height, so swapping projects never resizes the band. Every plate is
|
|
949
|
+
16:9, the ratio the thumbnails are drawn at, with the title below it; the
|
|
950
|
+
tagline takes the height left. From 900px the specimen is half the band and the rack a
|
|
951
|
+
narrower column beside it, the rest left blank. Below 900px the rack is
|
|
952
|
+
an icon strip above the specimen, one column per project. */
|
|
950
953
|
.home-projects-bench {
|
|
951
954
|
--home-specimen-height: 32rem;
|
|
952
955
|
--home-icon-btn-size: 2.75rem;
|
|
@@ -957,55 +960,115 @@ html[data-theme="light"] .home-hero-plate {
|
|
|
957
960
|
gap: var(--space-md);
|
|
958
961
|
}
|
|
959
962
|
|
|
963
|
+
.home-project-inventory {
|
|
964
|
+
order: -1;
|
|
965
|
+
}
|
|
966
|
+
|
|
960
967
|
.home-project-specimen {
|
|
961
|
-
position: relative;
|
|
962
|
-
display: flex;
|
|
963
|
-
flex-direction: column;
|
|
964
|
-
gap: var(--space-md);
|
|
965
|
-
padding: var(--space-md);
|
|
966
|
-
padding-bottom: var(--space-lg);
|
|
967
|
-
max-width: none;
|
|
968
|
-
width: 100%;
|
|
969
968
|
height: var(--home-specimen-height);
|
|
970
969
|
overflow: hidden;
|
|
971
|
-
|
|
972
|
-
|
|
970
|
+
gap: var(--space-sm);
|
|
971
|
+
padding: var(--space-md);
|
|
973
972
|
box-sizing: border-box;
|
|
974
973
|
}
|
|
975
974
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
975
|
+
/* The triad top rule (`.ledger-card--edge-primary`), restated at a weight
|
|
976
|
+
that beats the light-paper hairline reset on `.ledger-system-card`. */
|
|
977
|
+
:root .home-projects-bench .home-project-specimen,
|
|
978
|
+
:root .home-projects-bench .home-project-specimen:hover {
|
|
979
|
+
border-top: 2px solid var(--color-primary);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
.home-band--contrast .home-project-specimen {
|
|
983
|
+
background: var(--color-surface-card-contrast);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
.home-band--contrast .home-project-specimen:hover {
|
|
987
|
+
background: var(--color-surface-high-contrast);
|
|
980
988
|
}
|
|
981
989
|
|
|
982
|
-
.home-
|
|
983
|
-
.home-project-
|
|
990
|
+
.home-specimen-strip,
|
|
991
|
+
.home-project-inventory-code {
|
|
992
|
+
display: flex;
|
|
984
993
|
flex: 0 0 auto;
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
994
|
+
align-items: center;
|
|
995
|
+
justify-content: space-between;
|
|
996
|
+
gap: var(--space-sm);
|
|
997
|
+
min-width: 0;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
.home-specimen-strip .ledger-meta,
|
|
1001
|
+
.home-project-inventory-code .ledger-meta {
|
|
1002
|
+
min-width: 0;
|
|
1003
|
+
flex-wrap: nowrap;
|
|
990
1004
|
overflow: hidden;
|
|
1005
|
+
white-space: nowrap;
|
|
991
1006
|
}
|
|
992
1007
|
|
|
993
|
-
.home-
|
|
994
|
-
.home-project-
|
|
1008
|
+
.home-specimen-folio,
|
|
1009
|
+
.home-project-inventory-index {
|
|
1010
|
+
color: var(--color-primary);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
.home-specimen-strip>.ledger-status,
|
|
1014
|
+
.home-project-inventory-code>.ledger-status {
|
|
1015
|
+
flex: 0 0 auto;
|
|
1016
|
+
font-family: var(--font-label);
|
|
1017
|
+
font-size: var(--type-micro);
|
|
1018
|
+
letter-spacing: 0.06em;
|
|
1019
|
+
text-transform: uppercase;
|
|
1020
|
+
line-height: 1.4;
|
|
1021
|
+
padding: 0.1rem 0.45rem;
|
|
1022
|
+
background: color-mix(in srgb, var(--color-secondary) 14%, transparent);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/* The figure holds the plate and, always below it, the title. The badge
|
|
1026
|
+
shares the poster's grid cell; when the image fails, renderPosterMedia
|
|
1027
|
+
removes it and the badge hides with it. */
|
|
1028
|
+
.home-specimen-figure {
|
|
1029
|
+
flex: 0 0 auto;
|
|
1030
|
+
display: grid;
|
|
1031
|
+
gap: var(--space-md);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
.home-specimen-figure>.ledger-poster,
|
|
1035
|
+
.home-specimen-figure>.projects-plate {
|
|
1036
|
+
grid-area: 1 / 1;
|
|
995
1037
|
width: 100%;
|
|
996
|
-
|
|
997
|
-
|
|
1038
|
+
aspect-ratio: 16 / 9;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
.home-specimen-figure>.projects-plate {
|
|
1042
|
+
position: relative;
|
|
1043
|
+
display: flex;
|
|
1044
|
+
align-items: center;
|
|
1045
|
+
justify-content: center;
|
|
1046
|
+
background: var(--color-surface-high);
|
|
1047
|
+
color: var(--color-primary);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
.home-specimen-figure .projects-plate-glyph {
|
|
1051
|
+
font-size: 3rem;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
.home-specimen-figure .is-initial {
|
|
1055
|
+
font-family: var(--font-content-title);
|
|
1056
|
+
font-weight: var(--weight-title);
|
|
1057
|
+
line-height: 1;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
.home-specimen-figure:has(> .ledger-poster)>.home-specimen-badge.is-initial {
|
|
1061
|
+
font-size: var(--type-subtitle);
|
|
998
1062
|
}
|
|
999
1063
|
|
|
1000
1064
|
/* Glyph plates have no .ledger-poster::after — mirror the image corners. */
|
|
1001
|
-
.home-
|
|
1065
|
+
.home-specimen-figure>.projects-plate::after {
|
|
1002
1066
|
content: "";
|
|
1003
1067
|
--frame-corner-size: 0.7rem;
|
|
1004
1068
|
--frame-corner-thickness: 1px;
|
|
1005
1069
|
position: absolute;
|
|
1006
1070
|
inset: 0.3rem;
|
|
1007
1071
|
pointer-events: none;
|
|
1008
|
-
z-index: 2;
|
|
1009
1072
|
background:
|
|
1010
1073
|
linear-gradient(var(--color-primary), var(--color-primary)) top left / var(--frame-corner-size) var(--frame-corner-thickness) no-repeat,
|
|
1011
1074
|
linear-gradient(var(--color-primary), var(--color-primary)) top left / var(--frame-corner-thickness) var(--frame-corner-size) no-repeat,
|
|
@@ -1017,82 +1080,111 @@ html[data-theme="light"] .home-project-specimen,
|
|
|
1017
1080
|
linear-gradient(var(--color-primary), var(--color-primary)) bottom right / var(--frame-corner-thickness) var(--frame-corner-size) no-repeat;
|
|
1018
1081
|
}
|
|
1019
1082
|
|
|
1020
|
-
.home-
|
|
1021
|
-
display:
|
|
1083
|
+
.home-specimen-figure>.home-specimen-badge {
|
|
1084
|
+
display: none;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
.home-specimen-figure:has(> .ledger-poster)>.home-specimen-badge {
|
|
1088
|
+
grid-area: 1 / 1;
|
|
1089
|
+
justify-self: end;
|
|
1090
|
+
align-self: start;
|
|
1091
|
+
z-index: 3;
|
|
1092
|
+
display: inline-flex;
|
|
1022
1093
|
align-items: center;
|
|
1023
1094
|
justify-content: center;
|
|
1024
|
-
|
|
1095
|
+
width: 2.25rem;
|
|
1096
|
+
height: 2.25rem;
|
|
1097
|
+
margin: var(--space-sm);
|
|
1098
|
+
font-size: 1.25rem;
|
|
1099
|
+
background: var(--color-surface-card);
|
|
1025
1100
|
color: var(--color-primary);
|
|
1026
1101
|
}
|
|
1027
1102
|
|
|
1028
|
-
.home-project-specimen .
|
|
1029
|
-
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
.home-project-specimen .ledger-meta {
|
|
1033
|
-
display: none;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
.home-project-specimen .ledger-card-title,
|
|
1037
|
-
.home-project-specimen .ledger-card-title a {
|
|
1038
|
-
flex: 0 0 auto;
|
|
1039
|
-
font-family: var(--font-content-title);
|
|
1103
|
+
.home-project-specimen .home-specimen-title {
|
|
1104
|
+
margin: 0;
|
|
1040
1105
|
font-size: var(--type-card-lg);
|
|
1041
|
-
font-weight: var(--weight-title);
|
|
1042
|
-
font-synthesis: none;
|
|
1043
1106
|
line-height: 1.2;
|
|
1044
|
-
color: var(--color-on-surface);
|
|
1045
|
-
min-height: calc(1.2em * 1);
|
|
1046
|
-
margin: var(--space-sm) 0 0;
|
|
1047
1107
|
overflow: hidden;
|
|
1048
1108
|
text-overflow: ellipsis;
|
|
1049
1109
|
white-space: nowrap;
|
|
1050
1110
|
}
|
|
1051
1111
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
color: var(--color-primary);
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1112
|
+
/* The tagline takes whatever height the plate leaves, clipped at a whole
|
|
1113
|
+
line by the clamp; the chips and the foot keep to the bottom edge. */
|
|
1057
1114
|
.home-project-specimen .ledger-card-copy {
|
|
1058
|
-
flex: 0
|
|
1059
|
-
|
|
1115
|
+
flex: 0 1 auto;
|
|
1116
|
+
min-height: 0;
|
|
1060
1117
|
font-size: var(--type-body);
|
|
1061
|
-
font-weight: var(--weight-body);
|
|
1062
1118
|
line-height: var(--leading-body);
|
|
1063
1119
|
color: var(--color-on-surface-variant);
|
|
1064
1120
|
display: -webkit-box;
|
|
1065
1121
|
-webkit-box-orient: vertical;
|
|
1066
|
-
-webkit-line-clamp:
|
|
1067
|
-
line-clamp:
|
|
1122
|
+
-webkit-line-clamp: var(--home-specimen-copy-lines, 4);
|
|
1123
|
+
line-clamp: var(--home-specimen-copy-lines, 4);
|
|
1068
1124
|
overflow: hidden;
|
|
1069
|
-
min-height: calc(var(--type-body) * var(--leading-body) * 3);
|
|
1070
|
-
margin: 0;
|
|
1071
1125
|
}
|
|
1072
1126
|
|
|
1073
1127
|
.home-project-specimen .ledger-chip-row {
|
|
1074
|
-
|
|
1075
|
-
flex-wrap:
|
|
1128
|
+
flex: 0 0 auto;
|
|
1129
|
+
flex-wrap: nowrap;
|
|
1130
|
+
overflow: hidden;
|
|
1076
1131
|
gap: var(--space-xs);
|
|
1077
|
-
margin: 0;
|
|
1132
|
+
margin: auto 0 0;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
.home-specimen-foot {
|
|
1136
|
+
flex: 0 0 auto;
|
|
1137
|
+
flex-wrap: nowrap;
|
|
1138
|
+
gap: var(--space-md);
|
|
1078
1139
|
margin-top: auto;
|
|
1140
|
+
padding-top: var(--space-md);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
.home-project-specimen .ledger-chip-row~.home-specimen-foot {
|
|
1144
|
+
margin-top: var(--space-xs);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
.home-specimen-foot .ledger-card-links {
|
|
1148
|
+
position: relative;
|
|
1149
|
+
z-index: 1;
|
|
1150
|
+
min-width: 0;
|
|
1151
|
+
display: flex;
|
|
1152
|
+
align-items: center;
|
|
1153
|
+
flex-wrap: wrap;
|
|
1154
|
+
gap: var(--space-sm) var(--space-lg);
|
|
1155
|
+
padding-top: 0;
|
|
1156
|
+
border-top: 0;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
.home-specimen-open {
|
|
1160
|
+
margin-left: auto;
|
|
1079
1161
|
flex: 0 0 auto;
|
|
1080
|
-
max-height: none;
|
|
1081
|
-
overflow: visible;
|
|
1082
1162
|
}
|
|
1083
1163
|
|
|
1084
|
-
.home-project-specimen .
|
|
1085
|
-
|
|
1164
|
+
.home-project-specimen:hover .home-specimen-open {
|
|
1165
|
+
color: var(--color-on-primary, var(--color-surface));
|
|
1166
|
+
border-color: var(--color-link-hover);
|
|
1167
|
+
background: var(--color-primary);
|
|
1086
1168
|
}
|
|
1087
1169
|
|
|
1088
|
-
|
|
1170
|
+
.home-project-specimen:hover .home-specimen-open .ledger-cta-arrow {
|
|
1171
|
+
transform: translateX(4px);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1175
|
+
.home-project-specimen:hover .home-specimen-open .ledger-cta-arrow {
|
|
1176
|
+
transform: none;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/* Mobile: one equal column per project (the script sets the count, up to
|
|
1181
|
+
six), so the strip spans the band; fixed button height. */
|
|
1089
1182
|
.home-project-inventory {
|
|
1090
1183
|
display: grid;
|
|
1091
1184
|
grid-template-columns: repeat(var(--home-project-icon-count, 6), minmax(0, 1fr));
|
|
1092
1185
|
gap: var(--space-xs);
|
|
1093
1186
|
min-width: 0;
|
|
1094
1187
|
min-height: 0;
|
|
1095
|
-
height: auto !important;
|
|
1096
1188
|
overflow: visible;
|
|
1097
1189
|
background: transparent;
|
|
1098
1190
|
border: 0;
|
|
@@ -1104,22 +1196,16 @@ html[data-theme="light"] .home-project-specimen,
|
|
|
1104
1196
|
display: flex;
|
|
1105
1197
|
align-items: center;
|
|
1106
1198
|
justify-content: center;
|
|
1107
|
-
flex: none;
|
|
1108
|
-
width: auto;
|
|
1109
1199
|
min-width: 0;
|
|
1110
1200
|
height: var(--home-icon-btn-size);
|
|
1111
|
-
aspect-ratio: auto;
|
|
1112
|
-
gap: 0;
|
|
1113
1201
|
padding: 0;
|
|
1114
1202
|
border: 1px solid var(--border-gentle-divider);
|
|
1115
1203
|
background: var(--color-panel-contrast);
|
|
1116
1204
|
cursor: pointer;
|
|
1117
|
-
box-shadow: none;
|
|
1118
1205
|
transition: background-color var(--motion-duration-fast) var(--motion-ease-default),
|
|
1119
1206
|
border-color var(--motion-duration-fast) var(--motion-ease-default);
|
|
1120
1207
|
}
|
|
1121
1208
|
|
|
1122
|
-
.home-project-inventory-index,
|
|
1123
1209
|
.home-project-inventory-media,
|
|
1124
1210
|
.home-project-inventory-body {
|
|
1125
1211
|
display: none;
|
|
@@ -1129,9 +1215,6 @@ html[data-theme="light"] .home-project-specimen,
|
|
|
1129
1215
|
display: inline-flex;
|
|
1130
1216
|
align-items: center;
|
|
1131
1217
|
justify-content: center;
|
|
1132
|
-
width: auto;
|
|
1133
|
-
height: auto;
|
|
1134
|
-
margin: 0;
|
|
1135
1218
|
font-size: 1.25rem;
|
|
1136
1219
|
line-height: 1;
|
|
1137
1220
|
color: var(--color-on-surface-variant);
|
|
@@ -1154,7 +1237,6 @@ html[data-theme="light"] .home-project-specimen,
|
|
|
1154
1237
|
outline: 1px solid var(--color-primary);
|
|
1155
1238
|
outline-offset: -1px;
|
|
1156
1239
|
background: var(--color-surface-high);
|
|
1157
|
-
box-shadow: none;
|
|
1158
1240
|
}
|
|
1159
1241
|
|
|
1160
1242
|
.home-project-inventory-row.is-selected .home-project-inventory-glyph,
|
|
@@ -1169,93 +1251,68 @@ html[data-theme="light"] .home-project-specimen,
|
|
|
1169
1251
|
background: var(--color-surface-high-contrast);
|
|
1170
1252
|
}
|
|
1171
1253
|
|
|
1172
|
-
.home-project-inventory-row .ledger-meta {
|
|
1173
|
-
display: none;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
1254
|
@media (min-width: 900px) {
|
|
1255
|
+
|
|
1256
|
+
/* The rack panel stretches to the specimen's fixed height. */
|
|
1177
1257
|
.home-projects-bench {
|
|
1178
1258
|
--home-specimen-height: 36rem;
|
|
1179
1259
|
display: grid;
|
|
1180
|
-
grid-template-columns: minmax(0,
|
|
1181
|
-
gap: var(--space-xl);
|
|
1260
|
+
grid-template-columns: minmax(0, 50%) minmax(18rem, 34%);
|
|
1261
|
+
column-gap: var(--space-xl);
|
|
1182
1262
|
align-items: stretch;
|
|
1183
1263
|
}
|
|
1184
1264
|
|
|
1185
|
-
.home-project-specimen {
|
|
1186
|
-
height: var(--home-specimen-height);
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
/* Panel container over the tiled band — not bare rows on the grid. */
|
|
1190
1265
|
.home-project-inventory {
|
|
1266
|
+
order: 0;
|
|
1191
1267
|
display: flex;
|
|
1192
1268
|
flex-direction: column;
|
|
1193
|
-
|
|
1269
|
+
gap: var(--space-sm);
|
|
1270
|
+
min-height: 0;
|
|
1194
1271
|
overflow: hidden;
|
|
1195
|
-
|
|
1196
|
-
padding: 0;
|
|
1272
|
+
padding: var(--space-md);
|
|
1197
1273
|
background: var(--color-panel-contrast);
|
|
1198
|
-
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
.home-project-specimen .home-specimen-title {
|
|
1277
|
+
font-size: var(--type-title);
|
|
1199
1278
|
}
|
|
1200
1279
|
|
|
1201
1280
|
.home-project-inventory-row {
|
|
1202
|
-
display: flex;
|
|
1203
|
-
flex-direction: row;
|
|
1204
|
-
align-items: center;
|
|
1205
|
-
justify-content: flex-start;
|
|
1206
1281
|
flex: 1 1 0;
|
|
1207
|
-
|
|
1282
|
+
justify-content: flex-start;
|
|
1283
|
+
gap: var(--space-md);
|
|
1208
1284
|
height: auto;
|
|
1209
1285
|
min-height: 0;
|
|
1210
|
-
aspect-ratio: auto;
|
|
1211
|
-
gap: var(--space-md);
|
|
1212
1286
|
padding: var(--space-sm) var(--space-md);
|
|
1213
|
-
border:
|
|
1214
|
-
|
|
1215
|
-
background: transparent;
|
|
1287
|
+
border: 1px solid var(--border-gentle-divider);
|
|
1288
|
+
background: var(--color-surface-card);
|
|
1216
1289
|
outline: none;
|
|
1217
1290
|
}
|
|
1218
1291
|
|
|
1219
|
-
.home-project-inventory-row
|
|
1220
|
-
|
|
1292
|
+
.home-band--contrast .home-project-inventory-row {
|
|
1293
|
+
background: var(--color-surface-card-contrast);
|
|
1221
1294
|
}
|
|
1222
1295
|
|
|
1223
|
-
.home-project-inventory-row.is-selected
|
|
1296
|
+
.home-project-inventory-row.is-selected,
|
|
1297
|
+
.home-band--contrast .home-project-inventory-row.is-selected {
|
|
1224
1298
|
background: var(--color-surface-high);
|
|
1225
|
-
border-color:
|
|
1299
|
+
border-color: var(--border-gentle-divider);
|
|
1226
1300
|
outline: none;
|
|
1227
1301
|
box-shadow: inset 3px 0 0 var(--color-primary);
|
|
1228
1302
|
}
|
|
1229
1303
|
|
|
1230
|
-
.home-project-inventory-index {
|
|
1231
|
-
display: block;
|
|
1232
|
-
flex: 0 0 1.75rem;
|
|
1233
|
-
font-family: var(--font-mono);
|
|
1234
|
-
font-size: var(--type-caption);
|
|
1235
|
-
letter-spacing: 0.04em;
|
|
1236
|
-
color: var(--color-on-surface-variant);
|
|
1237
|
-
line-height: 1;
|
|
1238
|
-
text-align: right;
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
.home-project-inventory-row.is-selected .home-project-inventory-index,
|
|
1242
|
-
.home-project-inventory-row:hover .home-project-inventory-index,
|
|
1243
|
-
.home-project-inventory-row:focus-visible .home-project-inventory-index {
|
|
1244
|
-
color: var(--color-primary);
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
1304
|
.home-project-inventory-media {
|
|
1248
1305
|
display: block;
|
|
1249
1306
|
flex: 0 0 auto;
|
|
1250
1307
|
width: 5.5rem;
|
|
1251
|
-
align-self: center;
|
|
1252
|
-
height: auto;
|
|
1253
|
-
min-width: 0;
|
|
1254
|
-
min-height: 0;
|
|
1255
1308
|
aspect-ratio: 16 / 9;
|
|
1256
1309
|
overflow: hidden;
|
|
1257
1310
|
background: var(--color-surface-high);
|
|
1258
|
-
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
.home-project-inventory-row.is-selected .home-project-inventory-media {
|
|
1314
|
+
outline: 1px solid var(--color-primary);
|
|
1315
|
+
outline-offset: 2px;
|
|
1259
1316
|
}
|
|
1260
1317
|
|
|
1261
1318
|
.home-project-inventory-media .ledger-poster,
|
|
@@ -1263,7 +1320,6 @@ html[data-theme="light"] .home-project-specimen,
|
|
|
1263
1320
|
width: 100%;
|
|
1264
1321
|
height: 100%;
|
|
1265
1322
|
aspect-ratio: auto;
|
|
1266
|
-
border: 0;
|
|
1267
1323
|
overflow: hidden;
|
|
1268
1324
|
background: var(--color-surface-high);
|
|
1269
1325
|
}
|
|
@@ -1272,13 +1328,6 @@ html[data-theme="light"] .home-project-specimen,
|
|
|
1272
1328
|
display: none;
|
|
1273
1329
|
}
|
|
1274
1330
|
|
|
1275
|
-
.home-project-inventory-media .ledger-poster img {
|
|
1276
|
-
width: 100%;
|
|
1277
|
-
height: 100%;
|
|
1278
|
-
object-fit: cover;
|
|
1279
|
-
display: block;
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
1331
|
.home-project-inventory-mark {
|
|
1283
1332
|
display: flex;
|
|
1284
1333
|
align-items: center;
|
|
@@ -1286,6 +1335,10 @@ html[data-theme="light"] .home-project-specimen,
|
|
|
1286
1335
|
color: var(--color-primary);
|
|
1287
1336
|
}
|
|
1288
1337
|
|
|
1338
|
+
.home-project-inventory-mark .material-symbols-outlined {
|
|
1339
|
+
font-size: 1.5rem;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1289
1342
|
.home-project-inventory-mark--initial {
|
|
1290
1343
|
font-family: var(--font-content-title);
|
|
1291
1344
|
font-size: var(--type-card);
|
|
@@ -1294,33 +1347,23 @@ html[data-theme="light"] .home-project-specimen,
|
|
|
1294
1347
|
color: var(--color-on-surface);
|
|
1295
1348
|
}
|
|
1296
1349
|
|
|
1297
|
-
.home-project-inventory-mark .material-symbols-outlined {
|
|
1298
|
-
font-size: 1.25rem;
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
1350
|
.home-project-inventory-body {
|
|
1302
1351
|
display: flex;
|
|
1303
1352
|
flex-direction: column;
|
|
1304
1353
|
justify-content: center;
|
|
1305
|
-
gap: 0;
|
|
1354
|
+
gap: 0.1rem;
|
|
1306
1355
|
flex: 1 1 0;
|
|
1307
1356
|
min-width: 0;
|
|
1308
|
-
padding: 0;
|
|
1309
1357
|
}
|
|
1310
1358
|
|
|
1311
1359
|
.home-project-inventory-title {
|
|
1312
1360
|
margin: 0;
|
|
1313
|
-
font-
|
|
1314
|
-
font-size: var(--type-card);
|
|
1315
|
-
font-weight: var(--weight-title);
|
|
1316
|
-
font-synthesis: none;
|
|
1361
|
+
font-size: var(--type-label-lg);
|
|
1317
1362
|
line-height: 1.25;
|
|
1318
1363
|
color: var(--color-on-surface);
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
color: inherit;
|
|
1323
|
-
text-decoration: none;
|
|
1364
|
+
overflow: hidden;
|
|
1365
|
+
text-overflow: ellipsis;
|
|
1366
|
+
white-space: nowrap;
|
|
1324
1367
|
}
|
|
1325
1368
|
|
|
1326
1369
|
.home-project-inventory-row:hover .home-project-inventory-title,
|
|
@@ -1329,15 +1372,23 @@ html[data-theme="light"] .home-project-specimen,
|
|
|
1329
1372
|
color: var(--color-primary);
|
|
1330
1373
|
}
|
|
1331
1374
|
|
|
1332
|
-
.home-project-inventory-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1375
|
+
.home-project-inventory-copy {
|
|
1376
|
+
margin: 0;
|
|
1377
|
+
font-size: var(--type-meta);
|
|
1378
|
+
font-weight: var(--weight-body);
|
|
1379
|
+
line-height: 1.4;
|
|
1380
|
+
color: var(--color-on-surface-variant);
|
|
1381
|
+
overflow: hidden;
|
|
1382
|
+
text-overflow: ellipsis;
|
|
1383
|
+
white-space: nowrap;
|
|
1338
1384
|
}
|
|
1339
|
-
}
|
|
1340
1385
|
|
|
1386
|
+
/* Two classes: the glyph also carries .material-symbols-outlined, whose
|
|
1387
|
+
font stylesheet loads after this one and sets display. */
|
|
1388
|
+
.home-project-inventory-row .home-project-inventory-glyph {
|
|
1389
|
+
display: none;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1341
1392
|
|
|
1342
1393
|
@media (min-width: 768px) {}
|
|
1343
1394
|
|
|
@@ -24,7 +24,7 @@ module Pandorga
|
|
|
24
24
|
|
|
25
25
|
gem "pandorga", "~> 1.3"
|
|
26
26
|
# Local checkout: gem "pandorga", path: "../pandorga"
|
|
27
|
-
# GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v1.3.
|
|
27
|
+
# GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v1.3.7"
|
|
28
28
|
gem "csv"
|
|
29
29
|
gem "base64"
|
|
30
30
|
RUBY
|
data/lib/pandorga/version.rb
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "project-inventory-row/icon mark without a thumbnail (PLT-AC-15)",
|
|
3
|
+
"block": "renderProjectInventoryRow",
|
|
4
|
+
"args": [
|
|
5
|
+
{
|
|
6
|
+
"slug": "bossa",
|
|
7
|
+
"front_matter": {
|
|
8
|
+
"project": "Bossa",
|
|
9
|
+
"icon": "music_note"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"selectable": true,
|
|
14
|
+
"selected": true
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"selectors": [
|
|
18
|
+
"div.home-project-inventory-mark",
|
|
19
|
+
"h3.home-project-inventory-title"
|
|
20
|
+
],
|
|
21
|
+
"forbidden": [
|
|
22
|
+
"ledger-poster",
|
|
23
|
+
"home-project-inventory-mark--initial"
|
|
24
|
+
],
|
|
25
|
+
"contains": [
|
|
26
|
+
"aria-pressed=\"true\"",
|
|
27
|
+
"is-selected"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "project-inventory-row/rack card (PLT-AC-15)",
|
|
3
|
+
"block": "renderProjectInventoryRow",
|
|
4
|
+
"args": [
|
|
5
|
+
{
|
|
6
|
+
"slug": "arco",
|
|
7
|
+
"front_matter": {
|
|
8
|
+
"project": "ARCO",
|
|
9
|
+
"thumbnail": "/media/projects/arco.png",
|
|
10
|
+
"icon": "route",
|
|
11
|
+
"label": "Simulation",
|
|
12
|
+
"status": "Ongoing",
|
|
13
|
+
"tagline": "A city-scale race replayed from telemetry."
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"selectable": true,
|
|
18
|
+
"selected": false,
|
|
19
|
+
"index": 2
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"selectors": [
|
|
23
|
+
"article.home-project-inventory-row",
|
|
24
|
+
"div.home-project-inventory-media",
|
|
25
|
+
"div.ledger-poster",
|
|
26
|
+
"div.home-project-inventory-code",
|
|
27
|
+
"span.home-project-inventory-index",
|
|
28
|
+
"span.ledger-status",
|
|
29
|
+
"h3.home-project-inventory-title",
|
|
30
|
+
"p.home-project-inventory-copy",
|
|
31
|
+
"span.home-project-inventory-glyph"
|
|
32
|
+
],
|
|
33
|
+
"forbidden": [
|
|
34
|
+
"ledger-card-stretch"
|
|
35
|
+
],
|
|
36
|
+
"contains": [
|
|
37
|
+
">02<",
|
|
38
|
+
">Simulation<",
|
|
39
|
+
">Ongoing<",
|
|
40
|
+
">ARCO<",
|
|
41
|
+
">A city-scale race replayed from telemetry.<",
|
|
42
|
+
">route<",
|
|
43
|
+
"aria-pressed=\"false\"",
|
|
44
|
+
"data-slug=\"arco\""
|
|
45
|
+
]
|
|
46
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "project-specimen/icon plate without a thumbnail (PLT-AC-13)",
|
|
3
|
+
"block": "renderProjectSpecimen",
|
|
4
|
+
"args": [
|
|
5
|
+
{
|
|
6
|
+
"slug": "bossa",
|
|
7
|
+
"front_matter": {
|
|
8
|
+
"project": "Bossa",
|
|
9
|
+
"icon": "music_note",
|
|
10
|
+
"tagline": "Edge nodes for street ensembles."
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"href": "/projects/bossa/"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"selectors": [
|
|
18
|
+
"div.home-specimen-figure",
|
|
19
|
+
"projects-plate--mark",
|
|
20
|
+
"h3.ledger-card-title"
|
|
21
|
+
],
|
|
22
|
+
"forbidden": [
|
|
23
|
+
"ledger-poster",
|
|
24
|
+
"home-specimen-badge",
|
|
25
|
+
"home-specimen-strip",
|
|
26
|
+
"home-specimen-open"
|
|
27
|
+
],
|
|
28
|
+
"contains": [
|
|
29
|
+
">music_note<",
|
|
30
|
+
">Bossa<"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "project-specimen/initial badge on a thumbnail without an icon (PLT-AC-18)",
|
|
3
|
+
"block": "renderProjectSpecimen",
|
|
4
|
+
"args": [
|
|
5
|
+
{
|
|
6
|
+
"slug": "arco",
|
|
7
|
+
"front_matter": {
|
|
8
|
+
"project": "ARCO",
|
|
9
|
+
"thumbnail": "/media/projects/arco.png"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"href": "/projects/arco/"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"selectors": [
|
|
17
|
+
"ledger-poster",
|
|
18
|
+
"span.home-specimen-badge",
|
|
19
|
+
"is-initial"
|
|
20
|
+
],
|
|
21
|
+
"forbidden": [
|
|
22
|
+
"projects-plate",
|
|
23
|
+
"material-symbols-outlined"
|
|
24
|
+
],
|
|
25
|
+
"contains": [
|
|
26
|
+
"aria-hidden=\"true\">A</span>"
|
|
27
|
+
],
|
|
28
|
+
"counts": {
|
|
29
|
+
"home-specimen-badge": 1
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "project-specimen/initial plate without a thumbnail or icon (PLT-AC-18)",
|
|
3
|
+
"block": "renderProjectSpecimen",
|
|
4
|
+
"args": [
|
|
5
|
+
{
|
|
6
|
+
"slug": "bossa",
|
|
7
|
+
"front_matter": {
|
|
8
|
+
"project": "bossa",
|
|
9
|
+
"tagline": "Edge nodes for street ensembles."
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"href": "/projects/bossa/"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"selectors": [
|
|
17
|
+
"div.home-specimen-figure",
|
|
18
|
+
"projects-plate--mark",
|
|
19
|
+
"span.is-initial",
|
|
20
|
+
"projects-plate-glyph",
|
|
21
|
+
"h3.ledger-card-title"
|
|
22
|
+
],
|
|
23
|
+
"forbidden": [
|
|
24
|
+
"ledger-poster",
|
|
25
|
+
"material-symbols-outlined",
|
|
26
|
+
"home-specimen-badge"
|
|
27
|
+
],
|
|
28
|
+
"contains": [
|
|
29
|
+
">B</span></div>",
|
|
30
|
+
">bossa<"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "project-specimen/full (PLT-AC-13)",
|
|
3
|
+
"block": "renderProjectSpecimen",
|
|
4
|
+
"args": [
|
|
5
|
+
{
|
|
6
|
+
"slug": "arco",
|
|
7
|
+
"front_matter": {
|
|
8
|
+
"project": "ARCO",
|
|
9
|
+
"thumbnail": "/media/projects/arco.png",
|
|
10
|
+
"icon": "route",
|
|
11
|
+
"label": "Simulation",
|
|
12
|
+
"status": "Ongoing",
|
|
13
|
+
"start_date": "2024-03-01",
|
|
14
|
+
"tagline": "A city-scale race replayed from telemetry.",
|
|
15
|
+
"tags": [
|
|
16
|
+
"Systems",
|
|
17
|
+
"Rust"
|
|
18
|
+
],
|
|
19
|
+
"github": "https://github.com/example/arco",
|
|
20
|
+
"article_url": "/articles/arco/",
|
|
21
|
+
"external_url": "https://arco.example"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"href": "/projects/arco/",
|
|
26
|
+
"folio": 1,
|
|
27
|
+
"openLabel": "Open project"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"selectors": [
|
|
31
|
+
"article.ledger-system-card",
|
|
32
|
+
"article.home-project-specimen",
|
|
33
|
+
"div.home-specimen-strip",
|
|
34
|
+
"div.ledger-meta",
|
|
35
|
+
"span.ledger-status",
|
|
36
|
+
"div.home-specimen-figure",
|
|
37
|
+
"div.ledger-poster",
|
|
38
|
+
"img",
|
|
39
|
+
"span.home-specimen-badge",
|
|
40
|
+
"h3.ledger-card-title",
|
|
41
|
+
"a.ledger-card-stretch",
|
|
42
|
+
"p.ledger-card-copy",
|
|
43
|
+
"div.ledger-chip-row",
|
|
44
|
+
"div.ledger-card-links",
|
|
45
|
+
"span.home-specimen-open"
|
|
46
|
+
],
|
|
47
|
+
"forbidden": [
|
|
48
|
+
"projects-plate"
|
|
49
|
+
],
|
|
50
|
+
"contains": [
|
|
51
|
+
">#01<",
|
|
52
|
+
">Simulation<",
|
|
53
|
+
">2024<",
|
|
54
|
+
">Ongoing<",
|
|
55
|
+
">ARCO<",
|
|
56
|
+
">Repository<",
|
|
57
|
+
">Article<",
|
|
58
|
+
">Visit<",
|
|
59
|
+
">Open project<",
|
|
60
|
+
"href=\"/projects/arco/\""
|
|
61
|
+
],
|
|
62
|
+
"counts": {
|
|
63
|
+
"ledger-card-stretch": 1,
|
|
64
|
+
"ledger-cta": 4,
|
|
65
|
+
"ledger-cta-icon": 3,
|
|
66
|
+
"ledger-chip": 2,
|
|
67
|
+
"ledger-meta-sep": 2
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -214,6 +214,46 @@ omits(
|
|
|
214
214
|
|
|
215
215
|
separatorCount('renderSystemCard / label without status drops the separator', ['Simulation', ''], 0);
|
|
216
216
|
|
|
217
|
+
// PLT-AC-14: the home specimen and its rack card carry only what the project
|
|
218
|
+
// declares. A project that is only a name is its initial plate (PLT-AC-18)
|
|
219
|
+
// and a title, nothing else.
|
|
220
|
+
omits(
|
|
221
|
+
'renderProjectSpecimen / project with no media, meta, tags, or links',
|
|
222
|
+
'renderProjectSpecimen',
|
|
223
|
+
[{ front_matter: { project: 'Bossa' } }, { href: '/projects/bossa/' }],
|
|
224
|
+
[
|
|
225
|
+
'ledger-poster',
|
|
226
|
+
'material-symbols-outlined',
|
|
227
|
+
'home-specimen-badge',
|
|
228
|
+
'home-specimen-strip',
|
|
229
|
+
'ledger-meta',
|
|
230
|
+
'ledger-status',
|
|
231
|
+
'ledger-card-copy',
|
|
232
|
+
'ledger-chip-row',
|
|
233
|
+
'ledger-card-foot'
|
|
234
|
+
]
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
omits(
|
|
238
|
+
'renderProjectSpecimen / a start_date with no year renders no year',
|
|
239
|
+
'renderProjectSpecimen',
|
|
240
|
+
[{ front_matter: { project: 'Bossa', start_date: 'someday' } }, { href: '/projects/bossa/' }],
|
|
241
|
+
['home-specimen-strip', 'ledger-meta']
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
omits(
|
|
245
|
+
'renderProjectInventoryRow / project with no index, label, status, or tagline',
|
|
246
|
+
'renderProjectInventoryRow',
|
|
247
|
+
[{ slug: 'bossa', front_matter: { project: 'Bossa' } }, { selectable: true }],
|
|
248
|
+
[
|
|
249
|
+
'home-project-inventory-code',
|
|
250
|
+
'home-project-inventory-index',
|
|
251
|
+
'ledger-status',
|
|
252
|
+
'home-project-inventory-copy',
|
|
253
|
+
'ledger-poster'
|
|
254
|
+
]
|
|
255
|
+
);
|
|
256
|
+
|
|
217
257
|
// The listing preview is derived from description, then excerpt, then body.
|
|
218
258
|
// With all three absent there is nothing to say, so there is no paragraph.
|
|
219
259
|
omits(
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "open3"
|
|
5
|
+
require "pathname"
|
|
6
|
+
|
|
7
|
+
ROOT = Pathname.new(__dir__).join("..", "..").expand_path
|
|
8
|
+
FIXTURE_DIR = Pathname.new(__dir__).join("render-parity", "blocks")
|
|
9
|
+
RUNNER = Pathname.new(__dir__).join("render-parity", "run-block-fixture.mjs")
|
|
10
|
+
|
|
11
|
+
# The block half of test-content-render-parity, without its Markdown half.
|
|
12
|
+
# That gate needs `marked` from scripts/node_modules, which CI never installs,
|
|
13
|
+
# so it stays off validate.sh; the ledger blocks fall back cleanly without it.
|
|
14
|
+
|
|
15
|
+
def check!(condition, message = "condition was false")
|
|
16
|
+
raise "Assertion failed: #{message}" unless condition
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
puts "Testing ledger block fixtures..."
|
|
20
|
+
|
|
21
|
+
paths = Dir.glob(FIXTURE_DIR.join("*.json")).sort
|
|
22
|
+
check! paths.any?, "no ledger block fixtures found"
|
|
23
|
+
|
|
24
|
+
paths.each do |fixture_path|
|
|
25
|
+
stdout, stderr, status = Open3.capture3(
|
|
26
|
+
"node", RUNNER.to_s, fixture_path, chdir: ROOT.join("scripts").to_s
|
|
27
|
+
)
|
|
28
|
+
check! status.success?, "#{File.basename(fixture_path)} failed:\n#{stderr}\n#{stdout}"
|
|
29
|
+
puts " #{stdout.strip}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
puts "All ledger block fixtures passed."
|
data/scripts/validate.sh
CHANGED
|
@@ -49,6 +49,7 @@ TESTS=(
|
|
|
49
49
|
scripts/test/test-math-row-breaks.rb
|
|
50
50
|
scripts/test/test-detail-render-parity.rb
|
|
51
51
|
scripts/test/test-absent-field-absent-element.rb
|
|
52
|
+
scripts/test/test-ledger-blocks.rb
|
|
52
53
|
scripts/test/test-home-band-art.rb
|
|
53
54
|
scripts/test/test-r2-json-sync.rb
|
|
54
55
|
scripts/test/test-writing-slug-limits.rb
|
|
@@ -32,6 +32,42 @@ Optional: `title`, `nav`, `home` (default limit 4).
|
|
|
32
32
|
home: { limit: 4 }
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
## Home band
|
|
36
|
+
|
|
37
|
+
The home band is a specimen bench: one featured project card beside a
|
|
38
|
+
filing rack of selectable project cards. Every element is backed by a
|
|
39
|
+
collection field; a missing field means a missing element.
|
|
40
|
+
|
|
41
|
+
- `PLT-AC-13` When the band renders, the specimen card shall carry a strip
|
|
42
|
+
with the project's folio, `label`, `start_date` year and `status`; a 16:9
|
|
43
|
+
plate, the `thumbnail` with the `icon` as a badge (or the `icon` plate),
|
|
44
|
+
with the title always below it; the `tagline`; the `tags`; and the
|
|
45
|
+
outbound `github`, `article_url` and `external_url` links. The title is
|
|
46
|
+
its only link to the project page.
|
|
47
|
+
- `PLT-AC-14` When a project lacks `thumbnail`, `label`, `status`,
|
|
48
|
+
`start_date`, `tagline`, `tags` or outbound links, the specimen card and its
|
|
49
|
+
rack card shall render no element for that field. A missing `icon` is
|
|
50
|
+
covered by `PLT-AC-18`.
|
|
51
|
+
- `PLT-AC-15` When the viewport is at least 900px wide, the specimen shall
|
|
52
|
+
take half the band and the rack a narrower column beside it, leaving the
|
|
53
|
+
rest blank. Each project shall appear in the rack as a card with its folio,
|
|
54
|
+
`label`, `status`, title, `tagline` and a 16:9 `thumbnail` (or `icon`, or
|
|
55
|
+
initial) mark. Selecting a card shall swap the specimen in place and mark
|
|
56
|
+
that card `aria-pressed="true"`.
|
|
57
|
+
- `PLT-AC-16` Withdrawn: the tape of previous and next arrows was removed;
|
|
58
|
+
the rack is the only way to change the specimen.
|
|
59
|
+
- `PLT-AC-17` When the viewport is narrower than 900px, the rack shall be a
|
|
60
|
+
strip of icon buttons above the specimen, one per project (up to six), that
|
|
61
|
+
together span the band's width.
|
|
62
|
+
- `PLT-AC-18` When a project lacks `icon`, the first letter of its name shall
|
|
63
|
+
stand in for it wherever the icon would appear: the specimen badge, the
|
|
64
|
+
specimen plate, the rack mark and the strip button. The elements and the
|
|
65
|
+
behavior shall be those of a project with an icon.
|
|
66
|
+
|
|
67
|
+
`PLT-AC-13`, `-14` and `-18` are covered by the block fixtures under
|
|
68
|
+
`scripts/test/render-parity/blocks/` and by the absent-field gate; `-15` and
|
|
69
|
+
`-17` are layout behavior verified in the browser.
|
|
70
|
+
|
|
35
71
|
## Package layout
|
|
36
72
|
|
|
37
73
|
- `template.yml` — slots, detail route, export fields
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pandorga
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- The Pandorga contributors
|
|
@@ -339,6 +339,12 @@ files:
|
|
|
339
339
|
- scripts/test/render-parity/blocks/meta-strip.json
|
|
340
340
|
- scripts/test/render-parity/blocks/poster-media-image.json
|
|
341
341
|
- scripts/test/render-parity/blocks/poster-media-video.json
|
|
342
|
+
- scripts/test/render-parity/blocks/project-inventory-row-icon.json
|
|
343
|
+
- scripts/test/render-parity/blocks/project-inventory-row.json
|
|
344
|
+
- scripts/test/render-parity/blocks/project-specimen-glyph.json
|
|
345
|
+
- scripts/test/render-parity/blocks/project-specimen-initial-badge.json
|
|
346
|
+
- scripts/test/render-parity/blocks/project-specimen-initial.json
|
|
347
|
+
- scripts/test/render-parity/blocks/project-specimen.json
|
|
342
348
|
- scripts/test/render-parity/blocks/section-banner.json
|
|
343
349
|
- scripts/test/render-parity/blocks/system-card-custom-meta.json
|
|
344
350
|
- scripts/test/render-parity/blocks/system-card.json
|
|
@@ -389,6 +395,7 @@ files:
|
|
|
389
395
|
- scripts/test/test-home-band-art.rb
|
|
390
396
|
- scripts/test/test-install-functions.rb
|
|
391
397
|
- scripts/test/test-language-export.rb
|
|
398
|
+
- scripts/test/test-ledger-blocks.rb
|
|
392
399
|
- scripts/test/test-listing-filter-contract.rb
|
|
393
400
|
- scripts/test/test-math-row-breaks.mjs
|
|
394
401
|
- scripts/test/test-math-row-breaks.rb
|