pandorga 1.3.6 → 1.3.8

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: c6a4f2b9d6301de61dc8a70de9ea3e47838f8896ac2b87f0cd77e07665c0030d
4
- data.tar.gz: 41c8cefdadd4e2b58d2cf424f587bcb70c98531b870f04a8bfdef7ee0420a33c
3
+ metadata.gz: cc27aeecd257bc67bb04735b6f9d1069057abe7df27b9857bd1acf3a3eb6ea13
4
+ data.tar.gz: d7028bb2f9ed6c2c021564327a5a5cb3333e344cd7447ab6a088ec7bb03eba84
5
5
  SHA512:
6
- metadata.gz: 26e0bbd8c1f915e22a81e34f365e70e7302cd983701730ecdef31bb11a3892a78fad827651cd364276a7cd87b74566b5dd9f60ff208cd9ad9b90bb3df764f7c0
7
- data.tar.gz: 3a4c0e1a3641a3558c02867e832424e12f2bcbf20d8d9db0675387f314ed550d780cd870930cc03ecdc8fb5e75898de6d79698ac78fc636b4ab2e9c243e8827e
6
+ metadata.gz: 43003521e76f118bf9c12e720fc2c4132748ebdd03f8c9443b1591ef0283e81ee6d088e6bf5d96c1f4066f9ffabf408070b0c6b0326ebc314ecc09a67da7f822
7
+ data.tar.gz: 12319e52eaea1045ca920545e2b816d626cb85e4b8fb32f32d7ccf04e1159e5cbda0c0896851172c172c59594a5d8da09e950d78a02b22236ae551df91db3094
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.3.8] — 2026-10-06
4
+
5
+ ### Fixed
6
+
7
+ - Home specimen grows with its tagline and wrapping tag chips instead of
8
+ clipping under a fixed height; pills stay one line (row wraps whole chips)
9
+ - Specimen strip above the plate uses the projects label chrome at a larger
10
+ step so it matches the featured scale
11
+
12
+ ## [1.3.7] — 2026-10-06
13
+
14
+ ### Added
15
+
16
+ - Home projects band specimen bench: project specimen, rack card, and tape
17
+ blocks, with inventory rows and glyph/initial fallbacks when an icon is
18
+ missing
19
+
20
+ ### Fixed
21
+
22
+ - Specimen stays half-width and 16:9; title always sits below the plate with
23
+ roomier copy and links; drafting tape removed from the home layout
24
+
3
25
  ## [1.3.6] — 2026-10-05
4
26
 
5
27
  ### 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 + inventory bench from 900px —
939
- a 40% / 60% split: 16:9 specimen card beside a panel inventory rail whose
940
- height matches the full specimen card, with equal-flex rows (thumb 16:9
941
- left, meta right); list click updates the specimen, specimen click opens
942
- the project. Below 768px the hero portrait runs to the screen edge, pulled
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
- /* Compact inventory row for the home portfolio bench.
328
- * Desktop: index + thumb + title + project icon. Mobile strip uses the
329
- * thumb/initial only. Selectable rows update the specimen. */
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
- if (links.length) {
449
- html += '<div class="ledger-card-foot ledger-card-links">' + links.join('') + '</div>';
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 grows with its tagline and wrapping chips; the rack
251
+ stretches to the same row via the bench grid. */
243
252
  function paintSpecimen(entry) {
244
253
  specimenSlot.innerHTML = '';
245
- /* No card-level drafting corners / edge tick — markers live on the
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
- className: 'home-project-specimen',
253
- glyphFallback: true,
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
- /* Always reserve six mobile columns so one project does not stretch
262
- a single giant button across the band. */
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/base.css CHANGED
@@ -831,6 +831,8 @@ a.no-underline:active {
831
831
  }
832
832
 
833
833
  .ledger-chip {
834
+ flex: 0 0 auto;
835
+ white-space: nowrap;
834
836
  font-family: var(--font-label);
835
837
  font-size: var(--type-cta);
836
838
  font-weight: var(--weight-label);