pandorga 1.14.1 → 2.0.1

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/DESIGN.md +15 -30
  4. data/_includes/content-runtime/43-blocks.html +5 -4
  5. data/_includes/content-runtime/60-fragments.html +90 -149
  6. data/_includes/content-runtime/90-bootstrap.html +3 -0
  7. data/_includes/cv/cv-box.html +10 -4
  8. data/_includes/home/band-index.html +44 -7
  9. data/_includes/home/band-listing.html +7 -3
  10. data/_includes/page/timeline-section.html +8 -3
  11. data/_includes/theme/theme-vars-block.liquid +3 -0
  12. data/_layouts/articles.html +0 -2
  13. data/_layouts/bibliography.html +0 -2
  14. data/_layouts/blog.html +0 -5
  15. data/_layouts/cv.html +27 -8
  16. data/_layouts/home.html +3 -13
  17. data/_layouts/projects.html +17 -9
  18. data/_layouts/resources.html +0 -9
  19. data/_layouts/shell.html +1 -7
  20. data/assets/css/article.css +10 -2
  21. data/assets/css/base.css +65 -44
  22. data/assets/css/ledger/bibliography.css +0 -31
  23. data/assets/css/ledger/chrome.css +51 -2
  24. data/assets/css/ledger/cv.css +0 -40
  25. data/assets/css/ledger/detail.css +18 -4
  26. data/assets/css/ledger/home.css +269 -413
  27. data/assets/css/ledger/projects.css +17 -19
  28. data/assets/css/ledger/rascunhos.css +0 -27
  29. data/assets/css/ledger/sources.css +0 -57
  30. data/assets/css/ledger/writing.css +0 -62
  31. data/lib/pandorga/commands/new.rb +2 -2
  32. data/lib/pandorga/commands/thumbnails.rb +3 -2
  33. data/lib/pandorga/registry/schema.yml +0 -3
  34. data/lib/pandorga/thumbnail_variants.rb +29 -3
  35. data/lib/pandorga/version.rb +1 -1
  36. data/scripts/check_home_lists.mjs +33 -5
  37. data/scripts/content/hero_portraits.rb +59 -11
  38. data/scripts/test/test-css-structure.rb +0 -1
  39. data/scripts/test/test-hero-16x9.rb +379 -0
  40. data/scripts/test/test-hero-portraits.mjs +10 -20
  41. data/scripts/test/test-hero-portraits.rb +31 -25
  42. data/scripts/test/test-home-article-cards.rb +56 -0
  43. data/scripts/test/test-home-lists.rb +5 -0
  44. data/scripts/test/test-new.rb +6 -6
  45. data/scripts/test/test-shell-a11y.rb +2 -1
  46. data/scripts/test/test-studio-section-texts.rb +625 -0
  47. data/scripts/test/test-theme-contrast.rb +13 -2
  48. data/scripts/test/test-v3-layout.rb +559 -0
  49. data/scripts/validate.sh +4 -0
  50. data/studio/assets/{index-BJyN-kB3.js → index-B9h1WUYY.js} +1 -1
  51. data/studio/index.html +1 -1
  52. data/studio/schema.yml +126 -2
  53. data/studio-app/src/lib/yaml-doc.js +6 -0
  54. data/templates/network/studio.yml +10 -0
  55. metadata +6 -2
@@ -21,11 +21,11 @@
21
21
  }
22
22
 
23
23
  .home-band--hero {
24
- background-color: var(--color-background);
24
+ background-color: transparent;
25
25
  }
26
26
 
27
27
  .home-band--contrast {
28
- background-color: var(--color-panel-contrast);
28
+ background-color: transparent;
29
29
  background-image: none;
30
30
  }
31
31
 
@@ -104,40 +104,6 @@
104
104
  }
105
105
  }
106
106
 
107
- /* Ambient pool behind the hero.
108
- *
109
- * Same weight as the earlier primary-container bloom, but keyed to neutral
110
- * ink/outline so it stays grey paper depth instead of a blue spotlight. */
111
- .home-band--hero::before {
112
- content: "";
113
- position: absolute;
114
- z-index: -1;
115
- top: -18%;
116
- right: 8%;
117
- width: min(38rem, 60%);
118
- aspect-ratio: 1;
119
- border-radius: 50%;
120
- background: radial-gradient(circle,
121
- color-mix(in srgb, var(--color-outline) 22%, transparent) 0%,
122
- color-mix(in srgb, var(--color-on-surface) 8%, transparent) 42%,
123
- transparent 72%);
124
- filter: blur(52px);
125
- pointer-events: none;
126
- }
127
-
128
- [data-theme="dark"] .home-band--hero::before {
129
- background: radial-gradient(circle,
130
- color-mix(in srgb, var(--color-on-surface) 10%, transparent) 0%,
131
- color-mix(in srgb, var(--color-outline) 8%, transparent) 50%,
132
- transparent 70%);
133
- filter: blur(60px);
134
- }
135
-
136
- @media (prefers-reduced-motion: no-preference) {
137
- .home-band--hero::before {
138
- transition: opacity var(--motion-duration-slow, 400ms) var(--motion-ease-default);
139
- }
140
- }
141
107
 
142
108
  .home-hero {
143
109
  display: grid;
@@ -146,64 +112,6 @@
146
112
  align-items: start;
147
113
  }
148
114
 
149
- @media (min-width: 1024px) {
150
- .home-hero {
151
- /* Copy content height sets the flex line. Portrait stretches to that
152
- height; aspect-ratio then sets W = 3/4 × H. Children are taken out of
153
- flow so the photo's intrinsic box cannot inflate the line or collapse
154
- the copy. */
155
- display: flex;
156
- flex-direction: row;
157
- align-items: stretch;
158
- gap: var(--space-2xl);
159
- }
160
-
161
- .home-hero-copy {
162
- flex: 1 1 auto;
163
- min-width: min(100%, 20rem);
164
- }
165
-
166
- .home-hero-portrait {
167
- position: relative;
168
- flex: 0 0 auto;
169
- align-self: stretch;
170
- width: auto;
171
- height: auto;
172
- aspect-ratio: 3 / 4;
173
- overflow: hidden;
174
- min-width: 0;
175
- min-height: 0;
176
- max-width: none;
177
- justify-self: unset;
178
- }
179
-
180
- .home-hero-plate-wrap {
181
- position: absolute;
182
- inset: 0;
183
- width: 100%;
184
- height: 100%;
185
- flex: none;
186
- }
187
-
188
- .home-hero-plate {
189
- position: absolute;
190
- inset: 0;
191
- width: 100%;
192
- height: 100%;
193
- aspect-ratio: auto;
194
- flex: none;
195
- }
196
-
197
- .home-hero-plate img {
198
- position: absolute;
199
- inset: 0;
200
- width: 100%;
201
- height: 100%;
202
- max-width: none;
203
- object-fit: cover;
204
- }
205
- }
206
-
207
115
  .home-hero-copy {
208
116
  display: flex;
209
117
  flex-direction: column;
@@ -327,8 +235,6 @@
327
235
  .home-hero-portrait {
328
236
  position: relative;
329
237
  width: 100%;
330
- max-width: 16rem;
331
- justify-self: end;
332
238
  display: flex;
333
239
  flex-direction: column;
334
240
  min-height: 0;
@@ -368,18 +274,16 @@
368
274
  transform: translateY(0);
369
275
  }
370
276
 
371
- /* ── Portrait plate ──
372
- Beside the copy on a wide page the plate is portrait (3:4): its height
373
- matches the left column and the width is W = 3/4 × H. On a phone it stays
374
- 3:4. A wider box would cover-crop that portrait a second time. When
375
- hero_landscape is set, or a portrait entry has its own landscape, the
376
- phone plate is 16:9 and the chosen 16:9 file is shown whole. */
277
+ /* ── Photograph plate ──
278
+ One 16:9 frame at every width, the shape of the hero photographs, so
279
+ below 1024px cover does not crop them. From 1024px the plate fills a box
280
+ as tall as the copy and cover trims the sides or the top (PLT-AC-51). */
377
281
  .home-hero-plate {
378
282
  position: relative;
379
283
  z-index: 1;
380
284
  margin: 0;
381
285
  overflow: hidden;
382
- aspect-ratio: 3 / 4;
286
+ aspect-ratio: 16 / 9;
383
287
  /* Black in both themes: the photograph fades in over this, so a theme
384
288
  canvas here would read as the wrong picture arriving late. */
385
289
  background: black;
@@ -401,64 +305,13 @@ html[data-theme="light"] .home-hero-plate {
401
305
  box-shadow: none;
402
306
  }
403
307
 
404
- /* Silk facet behind the portrait — restored; the Borders pass had hidden it. */
405
- .home-hero-shard {
406
- top: -2.25rem;
407
- right: -2rem;
408
- width: 15rem;
409
- height: 15rem;
410
- background: var(--color-primary);
411
- opacity: var(--cubist-shard-opacity-strong, 16%);
412
- z-index: 0;
413
- }
414
308
 
415
- @media (max-width: 1023px) {
416
- .home-hero-shard {
417
- width: 11rem;
418
- height: 11rem;
419
- top: -1.5rem;
420
- right: -1.25rem;
421
- }
422
- }
423
-
424
- @media (max-width: 1023px) {
425
- .home-hero-portrait::before {
426
- content: "";
427
- position: absolute;
428
- z-index: 0;
429
- top: 55%;
430
- left: 50%;
431
- transform: translate(-50%, -50%);
432
- width: 140%;
433
- height: 120%;
434
- border-radius: 50%;
435
- background: radial-gradient(circle,
436
- color-mix(in srgb, var(--color-outline) 18%, transparent) 0%,
437
- color-mix(in srgb, var(--color-on-surface) 7%, transparent) 45%,
438
- transparent 72%);
439
- filter: blur(40px);
440
- pointer-events: none;
441
- }
442
-
443
- [data-theme="dark"] .home-hero-portrait::before {
444
- background: radial-gradient(circle,
445
- color-mix(in srgb, var(--color-on-surface) 12%, transparent) 0%,
446
- transparent 70%);
447
- }
448
- }
449
-
450
- .home-hero-plate picture {
451
- display: block;
452
- width: 100%;
453
- height: 100%;
454
- margin: 0;
455
- }
456
309
 
457
310
  .home-hero-plate img {
458
311
  width: 100%;
459
312
  height: 100%;
460
313
  object-fit: cover;
461
- object-position: 52% 18%;
314
+ object-position: center;
462
315
  display: block;
463
316
  filter: saturate(1.05) contrast(0.98);
464
317
  opacity: 0;
@@ -472,27 +325,6 @@ html[data-theme="light"] .home-hero-plate {
472
325
  opacity: 1;
473
326
  }
474
327
 
475
- /* 16:9 file in a 16:9 plate. contain shows the whole frame. The image stays
476
- hidden until the chosen file has loaded, so a fallback source does not
477
- flash before a pool landscape replaces it. The plate is already 16:9, so
478
- the wait does not move the layout. transition none so the paint is not a fade. */
479
- @media (max-width: 767px) {
480
- .home-hero-plate--landscape {
481
- aspect-ratio: 16 / 9;
482
- }
483
-
484
- .home-hero-plate--landscape img {
485
- object-fit: contain;
486
- object-position: center center;
487
- opacity: 0;
488
- transition: none;
489
- }
490
-
491
- .home-hero-plate--landscape img.is-shown {
492
- opacity: 1;
493
- }
494
- }
495
-
496
328
  .home-hero-plate:hover img {
497
329
  transform: scale(1.02);
498
330
  }
@@ -506,10 +338,86 @@ html[data-theme="light"] .home-hero-plate {
506
338
  }
507
339
  }
508
340
 
341
+ /* ── Hero from 1024px ──
342
+ The copy takes 46% and the name sets on two lines, given name over family
343
+ name, so the photograph takes the rest of the row. The copy sets the band
344
+ height; the plate wrapper and the plate leave the flow and the photograph
345
+ covers that box, at least 15rem tall, like the featured article poster.
346
+ This block follows the base hero rules on purpose: before them, their
347
+ max-width, position and aspect-ratio won on source order (PLT-AC-51). */
348
+ @media (min-width: 1024px) {
349
+ .home-hero {
350
+ display: flex;
351
+ flex-direction: row;
352
+ align-items: stretch;
353
+ gap: var(--space-2xl);
354
+ }
355
+
356
+ .home-hero-copy {
357
+ flex: 0 0 46%;
358
+ min-width: 0;
359
+ }
360
+
361
+ .home-hero-portrait {
362
+ flex: 1 1 0;
363
+ align-self: stretch;
364
+ width: auto;
365
+ min-width: 0;
366
+ min-height: 15rem;
367
+ max-width: none;
368
+ aspect-ratio: auto;
369
+ overflow: hidden;
370
+ }
371
+
372
+ .home-hero-plate-wrap {
373
+ position: absolute;
374
+ inset: 0;
375
+ aspect-ratio: auto;
376
+ }
377
+
378
+ .home-hero-plate {
379
+ position: absolute;
380
+ inset: 0;
381
+ width: 100%;
382
+ height: 100%;
383
+ aspect-ratio: auto;
384
+ }
385
+
386
+ .home-hero-plate img {
387
+ position: absolute;
388
+ inset: 0;
389
+ width: 100%;
390
+ height: 100%;
391
+ max-width: none;
392
+ object-fit: cover;
393
+ object-position: center;
394
+ }
395
+
396
+ /* --hero-name-fit is the longer name part in em at the title's tracking;
397
+ a site with a longer word raises it. The selector outranks a site's
398
+ one-line size written as .home-ledger .home-hero-title. */
399
+ .home-ledger .home-hero-title {
400
+ white-space: normal;
401
+ font-size: min(calc(100cqi / var(--hero-name-fit, 8.4)), var(--hero-name-max, 4.5rem));
402
+ }
403
+
404
+ .home-hero-given,
405
+ .home-hero-family {
406
+ display: inline-block;
407
+ white-space: nowrap;
408
+ }
409
+ }
410
+
509
411
  /* ── Section banners ──
510
412
  Atelier plate edge: a denser hairline, a longer accent rule, and a short
511
- vertical tick so each band reads as a ruled plate rather than a soft fade. */
413
+ vertical tick so each band reads as a ruled plate rather than a soft fade.
414
+ The banner is a 2-column grid so the lead's children join the template. */
512
415
  .home-band:not(.home-band--hero) .ledger-section-banner {
416
+ display: grid;
417
+ grid-template-columns: minmax(0, 1fr) auto;
418
+ column-gap: var(--space-md);
419
+ row-gap: var(--space-xs);
420
+ align-items: baseline;
513
421
  padding-top: var(--space-lg);
514
422
  padding-bottom: var(--space-sm);
515
423
  border-top: 1px solid var(--border-gentle);
@@ -537,50 +445,58 @@ html[data-theme="light"] .home-hero-plate {
537
445
  background: color-mix(in srgb, var(--color-primary) 55%, var(--color-outline));
538
446
  }
539
447
 
540
- .home-section-aside {
541
- display: flex;
542
- flex-direction: column;
543
- align-items: flex-start;
544
- gap: var(--space-sm);
545
- max-width: 30rem;
448
+ .home-band .ledger-section-lead {
449
+ display: contents;
546
450
  }
547
451
 
548
- /* The aside sits at the end of the banner, so its contents align to that same
549
- edge — button and copy on one axis. Aligning the button left while the copy
550
- ran right made every band look like a zigzag. */
551
- @media (min-width: 768px) {
552
- .home-section-aside {
553
- align-items: flex-end;
554
- text-align: right;
555
- }
452
+ .home-band .ledger-section-banner .ledger-kicker {
453
+ grid-column: 1 / -1;
454
+ grid-row: 1;
455
+ }
556
456
 
557
- .home-section-aside .ledger-cta {
558
- align-self: flex-end;
559
- }
457
+ .home-band .ledger-kicker:empty {
458
+ display: none;
459
+ }
460
+
461
+ .home-band .ledger-section-banner .section-title {
462
+ grid-column: 1;
463
+ grid-row: 2;
464
+ margin: 0;
465
+ font-size: var(--type-section-sm);
560
466
  }
561
467
 
562
- /* The lead column of a banner holds the kicker, the title and the note, all
563
- three filled from content/pages/headers.yml. It has to be able to
564
- shrink: the banner is a flex row and the aside beside it is sized by its
565
- button, so without this the note pushes the button off the line. */
468
+ .home-band .home-section-aside {
469
+ grid-column: 2;
470
+ grid-row: 2;
471
+ align-self: baseline;
472
+ margin-top: 0;
473
+ }
474
+
475
+ /* Outside the home bands the banner is still a flex row, so the lead has to
476
+ be able to shrink or the note pushes the aside off the line. */
566
477
  .ledger-section-banner .ledger-section-lead {
567
478
  min-width: 0;
568
479
  }
569
480
 
570
- /* One line under the title saying what the band holds. It is prose rather than
571
- a chrome label, so it takes the body face and the reading measure instead of
572
- the uppercase treatment `.ledger-section-note` carries in base.css — that
573
- one is the right-hand tally at the end of a banner, which is a different
574
- thing standing in a different place.
575
-
576
- The element is removed outright when the field is empty (see
577
- hydrateHomeSections), so this margin never opens a gap of its own. */
578
- .home-section-note {
579
- margin: var(--space-sm) 0 0;
481
+ .home-band .home-section-note {
482
+ grid-column: 1 / -1;
483
+ grid-row: 3;
484
+ margin: 0;
485
+ font-size: var(--type-dek);
580
486
  max-width: var(--reading-measure, 45rem);
581
487
  color: var(--color-on-surface-variant);
582
488
  }
583
489
 
490
+ @media (min-width: 768px) {
491
+ .home-band .ledger-section-banner .section-title {
492
+ font-size: var(--type-section);
493
+ }
494
+
495
+ .home-band .home-section-note {
496
+ font-size: var(--type-body);
497
+ }
498
+ }
499
+
584
500
  /* ── Featured article band ── */
585
501
  /* The lead is a panel, not a row.
586
502
  It used to be three bare columns sitting on the band background, which is
@@ -611,7 +527,7 @@ html[data-theme="light"] .home-hero-plate {
611
527
  gap: var(--space-md);
612
528
  align-items: stretch;
613
529
  background: var(--color-surface-card);
614
- border: 1.5px solid var(--border-ink);
530
+ border: 1px solid var(--border-ink);
615
531
  border-radius: var(--radius-card);
616
532
  border-top: 2px solid var(--color-primary);
617
533
  padding: var(--space-md) var(--space-lg);
@@ -621,6 +537,28 @@ html[data-theme="light"] .home-hero-plate {
621
537
  margin-bottom: var(--space-lg);
622
538
  }
623
539
 
540
+ @media (max-width: 767px) {
541
+ /* Stacks image first edge to edge on narrow screens. */
542
+ .home-featured {
543
+ display: flex;
544
+ flex-direction: column;
545
+ padding: 0;
546
+ }
547
+
548
+ .home-featured > .ledger-poster {
549
+ order: -1;
550
+ width: 100%;
551
+ height: auto;
552
+ margin: 0;
553
+ aspect-ratio: 16 / 9;
554
+ border: 0;
555
+ }
556
+
557
+ .home-featured-lead {
558
+ padding: var(--space-md);
559
+ }
560
+ }
561
+
624
562
  /* The hover rung, same as `.ledger-card` takes: the panel is one link target
625
563
  end to end, so the whole surface answers. */
626
564
  .home-featured:hover {
@@ -652,65 +590,6 @@ html[data-theme="light"] .home-hero-plate {
652
590
  }
653
591
  }
654
592
 
655
- /* Shards on the featured panel, which carried none: the one card the home
656
- page leads with was also the only one on the band with no footing in the
657
- cubist half of the identity.
658
-
659
- The pair is placed around the poster rather than across the card, because
660
- the poster is opaque and a shard behind it is a shard nobody sees. The
661
- alpha takes the top-left, over the index column, where there is nothing but
662
- chrome and air. The beta takes the bottom-right and comes out from *under*
663
- the poster into the strip below it, which is the same move the index band
664
- makes with its own plates.
665
-
666
- Hues are the two the home band does not already spend: the index panels
667
- below run secondary and primary, so this pair runs tertiary and secondary,
668
- and neither repeats the corner its neighbor uses. Shards are absolutely
669
- positioned, so they are not grid items and the three-column template is
670
- untouched. */
671
- .home-featured-shard {
672
- left: -3rem;
673
- top: -3.5rem;
674
- width: 11rem;
675
- height: 11rem;
676
- background: var(--color-tertiary);
677
- }
678
-
679
- .home-featured-shard--beta {
680
- left: auto;
681
- right: -3rem;
682
- top: auto;
683
- bottom: -4rem;
684
- width: 13rem;
685
- height: 13rem;
686
- background: var(--color-secondary);
687
- }
688
-
689
- /* Below the two-column layout the panel is short and the columns stack, so a
690
- shard sized for the wide card reaches the title.
691
-
692
- The beta restates `top`/`left` as `auto`. It carries both class names, so
693
- the rule above also matches it, and an override that gives it a `bottom`
694
- without taking back the `top` leaves it with all four offsets set — at
695
- which point top and left win and the pair collapses into one corner. */
696
- @media (max-width: 1023px) {
697
- .home-featured-shard {
698
- width: 8rem;
699
- height: 8rem;
700
- top: -2.5rem;
701
- left: -2.5rem;
702
- }
703
-
704
- .home-featured-shard--beta {
705
- width: 9rem;
706
- height: 9rem;
707
- top: auto;
708
- left: auto;
709
- bottom: -3rem;
710
- right: -2.5rem;
711
- }
712
- }
713
-
714
593
  /* The columns sit clear of both. */
715
594
  .home-featured> :not(.shard-accent) {
716
595
  position: relative;
@@ -800,6 +679,18 @@ html[data-theme="light"] .home-hero-plate {
800
679
  font-weight: var(--weight-body);
801
680
  line-height: var(--home-featured-copy-line);
802
681
  color: var(--color-legend);
682
+ display: -webkit-box;
683
+ -webkit-box-orient: vertical;
684
+ -webkit-line-clamp: 3;
685
+ line-clamp: 3;
686
+ overflow: hidden;
687
+ }
688
+
689
+ @media (min-width: 768px) {
690
+ .home-featured-copy {
691
+ -webkit-line-clamp: 4;
692
+ line-clamp: 4;
693
+ }
803
694
  }
804
695
 
805
696
  .home-featured-foot {
@@ -872,26 +763,23 @@ html[data-theme="light"] .home-hero-plate {
872
763
  display: none;
873
764
  }
874
765
 
766
+ /* Tags and "Read" share the last row, as on the featured card (PLT-AC-52). */
875
767
  .home-card-grid .ledger-card-foot {
876
- justify-content: flex-end;
768
+ justify-content: space-between;
769
+ }
770
+
771
+ .home-card-grid .ledger-card-foot .ledger-cta {
772
+ margin-left: auto;
877
773
  }
878
774
 
879
- /* A top rule per card, rotating through the palette triad, is what gives the
880
- grid its ledger rhythm in the mockup. */
775
+ /* Every card's top rule is the band accent, which `page-accent--<accent>`
776
+ sets on the section (PLT-AC-52). */
881
777
  .home-card-grid .ledger-card {
882
778
  border-top-width: 2px;
883
779
  border-top-style: solid;
884
780
  border-top-color: var(--color-primary);
885
781
  }
886
782
 
887
- .home-card-grid .ledger-card:nth-child(2) {
888
- border-top-color: var(--color-secondary);
889
- }
890
-
891
- .home-card-grid .ledger-card:nth-child(3) {
892
- border-top-color: var(--color-tertiary);
893
- }
894
-
895
783
  /* ── Rascunhos: dropped sheets ──
896
784
  Four compact cards rather than four full-width text rows: the poster is
897
785
  visible at every breakpoint now (it used to hide below 640px), each card
@@ -918,7 +806,6 @@ html[data-theme="light"] .home-hero-plate {
918
806
  border-top-width: 2px;
919
807
  border-top-style: solid;
920
808
  border-top-color: var(--color-tertiary);
921
- transform: rotate(0deg);
922
809
  transition: transform var(--motion-duration-fast) var(--motion-ease-default),
923
810
  background-color var(--motion-duration-fast) var(--motion-ease-default),
924
811
  border-color var(--motion-duration-fast) var(--motion-ease-default);
@@ -931,25 +818,73 @@ html[data-theme="light"] .home-hero-plate {
931
818
 
932
819
  /* A handful of fixed degrees per grid position — not a JS random() — so the
933
820
  tilt is identical on every render and every screenshot. */
934
- .home-draft-list>*:nth-child(4n + 1) {
935
- transform: rotate(-1.2deg);
936
- }
821
+ @media (min-width: 768px) {
822
+ .home-draft-list>*:nth-child(4n + 1) {
823
+ transform: rotate(-1.2deg);
824
+ }
937
825
 
938
- .home-draft-list>*:nth-child(4n + 2) {
939
- transform: rotate(0.9deg);
940
- }
826
+ .home-draft-list>*:nth-child(4n + 2) {
827
+ transform: rotate(0.9deg);
828
+ }
941
829
 
942
- .home-draft-list>*:nth-child(4n + 3) {
943
- transform: rotate(-0.7deg);
944
- }
830
+ .home-draft-list>*:nth-child(4n + 3) {
831
+ transform: rotate(-0.7deg);
832
+ }
945
833
 
946
- .home-draft-list>*:nth-child(4n) {
947
- transform: rotate(1.1deg);
834
+ .home-draft-list>*:nth-child(4n) {
835
+ transform: rotate(1.1deg);
836
+ }
837
+
838
+ .home-draft-card:hover,
839
+ .home-draft-card:focus-within {
840
+ transform: rotate(0deg);
841
+ }
948
842
  }
949
843
 
950
- .home-draft-card:hover,
951
- .home-draft-card:focus-within {
952
- transform: rotate(0deg);
844
+ @media (max-width: 767px) {
845
+ .home-draft-list>.home-draft-card:nth-child(3) {
846
+ grid-column: 1 / -1;
847
+ flex-direction: row;
848
+ }
849
+
850
+ .home-draft-list>.home-draft-card:nth-child(3) .ledger-poster {
851
+ flex: 0 0 38%;
852
+ aspect-ratio: auto;
853
+ align-self: stretch;
854
+ }
855
+
856
+ .home-draft-list>.home-draft-card:nth-child(3) .ledger-poster img,
857
+ .home-draft-list>.home-draft-card:nth-child(3) .ledger-poster video {
858
+ position: absolute;
859
+ inset: 0;
860
+ }
861
+
862
+ .home-draft-list>.home-draft-card:nth-child(3) .ledger-card-body {
863
+ flex: 1 1 auto;
864
+ min-width: 0;
865
+ }
866
+
867
+ .home-draft-list>.home-draft-card:nth-child(n + 4) {
868
+ display: none;
869
+ }
870
+
871
+ .home-draft-card .ledger-poster {
872
+ aspect-ratio: 4 / 3;
873
+ }
874
+
875
+ .home-draft-card .ledger-meta {
876
+ flex-wrap: nowrap;
877
+ white-space: nowrap;
878
+ overflow: hidden;
879
+ }
880
+
881
+ .home-draft-card .ledger-card-title {
882
+ font-size: var(--type-dek);
883
+ }
884
+
885
+ .home-draft-card .ledger-card-copy {
886
+ font-size: var(--type-label);
887
+ }
953
888
  }
954
889
 
955
890
  .home-draft-card .ledger-card-body {
@@ -964,6 +899,11 @@ html[data-theme="light"] .home-hero-plate {
964
899
  .home-draft-card .ledger-card-title {
965
900
  font-size: var(--type-card);
966
901
  line-height: 1.3;
902
+ display: -webkit-box;
903
+ -webkit-line-clamp: 3;
904
+ line-clamp: 3;
905
+ -webkit-box-orient: vertical;
906
+ overflow: hidden;
967
907
  }
968
908
 
969
909
  .home-draft-card .ledger-meta {
@@ -978,30 +918,18 @@ html[data-theme="light"] .home-hero-plate {
978
918
  font-weight: var(--weight-body);
979
919
  line-height: var(--leading-body);
980
920
  color: var(--color-legend);
981
- /* Fill the surplus the equal-height grid hands the body. `height: 0` with
982
- `flex: 1` gives the paragraph a definite block size from the flex
983
- allocation, so line-clamp/overflow clip against that height instead of
984
- growing past the ellipsis (the old flex:1 + clamp:2 bug that left text
985
- after "…" and sliced mid-glyph at the card edge). */
986
921
  flex: 1 1 auto;
987
- /* With the 16:9 plate, this band keeps the whole card about 20% shorter
988
- than the old 16/12.6 poster and 8.4-line excerpt, on narrow and wide grids. */
989
- min-height: calc(var(--type-subtitle) * var(--leading-body) * 5.33);
990
- height: 0;
991
922
  display: -webkit-box;
992
- -webkit-line-clamp: 5;
993
- line-clamp: 5;
923
+ -webkit-line-clamp: 3;
924
+ line-clamp: 3;
994
925
  -webkit-box-orient: vertical;
995
926
  overflow: hidden;
996
927
  }
997
928
 
998
- /* Four-up posters were the tall part of the old card. Give the excerpt a
999
- little of that height back so the wide card is 20% shorter too, not more. */
1000
- @media (min-width: 1024px) {
929
+ @media (min-width: 768px) {
1001
930
  .home-draft-card .ledger-card-copy {
1002
- min-height: calc(var(--type-subtitle) * var(--leading-body) * 6.05);
1003
- -webkit-line-clamp: 6;
1004
- line-clamp: 6;
931
+ -webkit-line-clamp: 4;
932
+ line-clamp: 4;
1005
933
  }
1006
934
  }
1007
935
 
@@ -1073,14 +1001,6 @@ html[data-theme="light"] .home-hero-plate {
1073
1001
  border-top: 2px solid var(--color-primary);
1074
1002
  }
1075
1003
 
1076
- .home-band--contrast .home-project-specimen {
1077
- background: var(--color-surface-card-contrast);
1078
- }
1079
-
1080
- .home-band--contrast .home-project-specimen:hover {
1081
- background: var(--color-surface-high-contrast);
1082
- }
1083
-
1084
1004
  .home-specimen-strip,
1085
1005
  .home-project-inventory-code {
1086
1006
  display: flex;
@@ -1338,8 +1258,8 @@ button.home-project-inventory-row {
1338
1258
  min-width: 0;
1339
1259
  height: var(--home-icon-btn-size);
1340
1260
  padding: 0;
1341
- border: 1px solid var(--border-gentle-divider);
1342
- background: var(--color-panel-contrast);
1261
+ border: 1px solid var(--border-ink);
1262
+ background: var(--color-surface-card);
1343
1263
  cursor: pointer;
1344
1264
  transition: background-color var(--motion-duration-fast) var(--motion-ease-default),
1345
1265
  border-color var(--motion-duration-fast) var(--motion-ease-default);
@@ -1384,12 +1304,6 @@ button.home-project-inventory-row {
1384
1304
  color: var(--color-primary);
1385
1305
  }
1386
1306
 
1387
- .home-band--contrast .home-project-inventory-row:hover,
1388
- .home-band--contrast .home-project-inventory-row:focus-visible,
1389
- .home-band--contrast .home-project-inventory-row.is-selected {
1390
- background: var(--color-surface-high-contrast);
1391
- }
1392
-
1393
1307
  @media (min-width: 900px) {
1394
1308
 
1395
1309
  /* The specimen column is 35% of the band (at least 20rem), which keeps the
@@ -1440,17 +1354,12 @@ button.home-project-inventory-row {
1440
1354
  max-height: var(--home-inventory-row-max);
1441
1355
  box-sizing: border-box;
1442
1356
  padding: var(--space-sm) var(--space-md);
1443
- border: 1px solid var(--border-gentle-divider);
1357
+ border: 1px solid var(--border-ink);
1444
1358
  background: var(--color-surface-card);
1445
1359
  outline: none;
1446
1360
  }
1447
1361
 
1448
- .home-band--contrast .home-project-inventory-row {
1449
- background: var(--color-surface-card-contrast);
1450
- }
1451
-
1452
- .home-project-inventory-row.is-selected,
1453
- .home-band--contrast .home-project-inventory-row.is-selected {
1362
+ .home-project-inventory-row.is-selected {
1454
1363
  background: var(--color-surface-high);
1455
1364
  border-color: var(--border-gentle-divider);
1456
1365
  outline: none;
@@ -1567,23 +1476,6 @@ button.home-project-inventory-row {
1567
1476
  isolation: isolate;
1568
1477
  }
1569
1478
 
1570
- [data-theme="dark"] .home-band--index::before {
1571
- content: "";
1572
- position: absolute;
1573
- z-index: -1;
1574
- top: -8%;
1575
- left: 50%;
1576
- width: min(72rem, 100%);
1577
- height: 116%;
1578
- transform: translateX(-50%);
1579
- border-radius: 50%;
1580
- background: radial-gradient(ellipse,
1581
- color-mix(in srgb, var(--color-primary) 5%, transparent) 0%,
1582
- transparent 70%);
1583
- filter: blur(70px);
1584
- pointer-events: none;
1585
- }
1586
-
1587
1479
  .home-index-split {
1588
1480
  display: grid;
1589
1481
  grid-template-columns: minmax(0, 1fr);
@@ -1603,10 +1495,10 @@ button.home-project-inventory-row {
1603
1495
  display: flex;
1604
1496
  flex-direction: column;
1605
1497
  align-items: flex-start;
1606
- gap: var(--space-md);
1607
- padding: 0 0 var(--space-lg);
1608
- background: var(--color-surface-card-contrast);
1609
- border: 1.5px solid var(--border-ink);
1498
+ gap: 0;
1499
+ padding: 0;
1500
+ background: var(--color-surface-card);
1501
+ border: 1px solid var(--border-ink);
1610
1502
  border-radius: var(--radius-card);
1611
1503
  transition:
1612
1504
  background-color var(--motion-duration-fast) var(--motion-ease-default),
@@ -1614,59 +1506,31 @@ button.home-project-inventory-row {
1614
1506
  }
1615
1507
 
1616
1508
  .home-index-panel:hover {
1617
- background: var(--color-surface-high-contrast);
1509
+ background: var(--color-surface-high);
1618
1510
  border-color: var(--border-gentle-hover);
1619
1511
  }
1620
1512
 
1621
- /* The plates are line-art, so the cubist half of the identity had no footing
1622
- on this band at all. One shard per panel, the canonical polygons across
1623
- the set, rotating hue the way every other shard on the site does.
1624
- *
1625
- * They sit in the foot of the panel, under the title and the link: the plate
1626
- * above carries an opaque background, so a shard behind it would be a shard
1627
- * nobody sees. `overflow: hidden` on the panel above keeps the negative
1628
- * offsets out of the document scroll width, and the shared utility already
1629
- * unmounts both below 640px. */
1630
- .home-index-shard {
1631
- right: -3rem;
1632
- bottom: -3.5rem;
1633
- width: 11rem;
1634
- height: 11rem;
1635
- background: var(--color-secondary);
1636
- }
1637
-
1638
- .home-index-shard--beta {
1639
- right: auto;
1640
- left: -3rem;
1641
- bottom: -3.5rem;
1642
- width: 11rem;
1643
- height: 11rem;
1644
- background: var(--color-primary);
1645
- }
1646
-
1647
- /* Between the shard cut-off and the layout each panel is the full
1648
- band wide on small screens, so a shard sized for half of it stops being a corner accent. */
1649
- @media (max-width: 899px) {
1650
-
1651
- .home-index-shard,
1652
- .home-index-shard--beta {
1653
- width: 8rem;
1654
- height: 8rem;
1655
- bottom: -2.5rem;
1656
- }
1513
+ /* Header row for index panels: holds title and Open link. */
1514
+ .home-index-panel-head {
1515
+ display: flex;
1516
+ justify-content: space-between;
1517
+ align-items: center;
1518
+ gap: var(--space-md);
1519
+ padding: var(--space-sm) var(--space-md);
1520
+ width: 100%;
1657
1521
  }
1658
1522
 
1659
- .home-index-panel:not(:has(.home-index-plate)) {
1660
- padding-top: var(--space-xl);
1523
+ /* The Open link keeps the right end of the head when the title is empty. */
1524
+ .home-index-panel-head > .ledger-cta {
1525
+ margin-left: auto;
1661
1526
  }
1662
1527
 
1663
- /* The title and the link have to sit clear of the shard behind them. The
1664
- plate is excluded because it is the one child that must stay full-bleed,
1665
- and the shard because it is the thing being sat clear of. */
1666
- .home-index-panel> :not(.home-index-plate):not(.shard-accent) {
1667
- position: relative;
1668
- z-index: 1;
1669
- margin-inline: var(--space-lg);
1528
+ /* Panel description from the page's headers note (PLT-AC-50). */
1529
+ .home-index-note {
1530
+ margin: 0;
1531
+ padding: 0 var(--space-md) var(--space-sm);
1532
+ font-size: var(--type-dek);
1533
+ color: var(--color-on-surface-variant);
1670
1534
  }
1671
1535
 
1672
1536
  /* Card-scale title, not `.section-title`: that face is for band banners and
@@ -1693,6 +1557,7 @@ button.home-project-inventory-row {
1693
1557
  min-height: 0;
1694
1558
  margin: 0;
1695
1559
  background: var(--color-background);
1560
+ border-top: 1px solid var(--border-gentle-divider);
1696
1561
  border-bottom: 1px solid var(--border-gentle-subtle);
1697
1562
  }
1698
1563
 
@@ -1908,7 +1773,7 @@ html[data-theme="light"] .home-featured .ledger-poster {
1908
1773
  --frame-corner-size: 0.85rem;
1909
1774
  }
1910
1775
 
1911
- @media (max-width: 899px) {
1776
+ @media (min-width: 768px) and (max-width: 899px) {
1912
1777
  .home-featured-lead {
1913
1778
  order: 1;
1914
1779
  }
@@ -1927,9 +1792,8 @@ html[data-theme="light"] .home-featured .ledger-poster {
1927
1792
  ends flush with the viewport and adds no scrollable width; this must stay
1928
1793
  below 768px, which is where .home-band-inner swaps that token for --gutter.
1929
1794
  The side hairlines go with it: a border pinned to the screen edge reads as a
1930
- frame cut off rather than as the plate's edge. The plate stays 3:4, the
1931
- same frame as the portrait file, so cover does not crop it again. A
1932
- configured hero_landscape switches this query to 16:9 and contain. */
1795
+ frame cut off rather than as the plate's edge. The plate stays 16:9, the
1796
+ same frame as the photograph, so cover does not crop it. */
1933
1797
  @media (max-width: 767px) {
1934
1798
  .home-hero-portrait {
1935
1799
  max-width: none;
@@ -1944,14 +1808,6 @@ html[data-theme="light"] .home-featured .ledger-poster {
1944
1808
  }
1945
1809
  }
1946
1810
 
1947
- /* ── Hero portrait between phone and desktop ──
1948
- Keep the plate modest; the 3:4 crop applies from tablet up. */
1949
- @media (min-width: 768px) and (max-width: 1023px) {
1950
- .home-hero-portrait {
1951
- max-width: 14rem;
1952
- }
1953
- }
1954
-
1955
1811
  /* ── Bibliography stats plate ── */
1956
1812
  #home-bib-panel .home-index-plate {
1957
1813
  display: flex;