pandorga 1.4.8 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/DESIGN.md +7 -7
- data/_layouts/home.html +4 -6
- data/_layouts/shell.html +2 -1
- data/assets/css/article.css +5 -3
- data/assets/css/ledger/detail.css +10 -9
- data/assets/css/ledger/home.css +31 -18
- data/examples/full/_config.yml +2 -0
- data/lib/pandorga/jekyll/plugins/identity_name.rb +67 -0
- data/lib/pandorga/registry/schema.yml +9 -1
- data/lib/pandorga/version.rb +1 -1
- data/scripts/test/test-hero-name.rb +165 -0
- data/scripts/validate.sh +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eab1ea9696bdf99c28026f135762061c5a627ea398df5ddd8080f2d10dd9abc7
|
|
4
|
+
data.tar.gz: 89b13dcd397f666cf5660845d7ffece69478d6275b1d821d9500a4bf08046e5b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e83aceb1f79cdd9f3ab201315b19532f3182cad64ed34ac4ed285be49f4257e4038a672aada1e6b7ce6b2ce7fb837749fc3eeb4347b99687f07fc2a33a8a6367
|
|
7
|
+
data.tar.gz: 7d987a05926fa2d9653faf04fc66585219430e57cd12d5383421aa9a32fcb6b1a52831fa4576f321e5392e482d6128bf4755cb36e237eb6ae8a1788f086848e3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.5.0] — 2026-10-10
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Optional `pandorga.identity.first_name` and `pandorga.identity.last_name`.
|
|
8
|
+
The home hero uses them as the given name and the family name. The top
|
|
9
|
+
bar shows `first_name`. `last_name` is kept whole, spaces included.
|
|
10
|
+
Omit either key and that part still comes from splitting `identity.name`
|
|
11
|
+
on the first space, as in 1.4.9. `identity.name` stays required.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- The home hero subtitle uses `--type-body`, the same size as the summary
|
|
16
|
+
under the name.
|
|
17
|
+
- Below 768px the hero name is 3pt larger, and a wrap breaks only between
|
|
18
|
+
the given name and the family name.
|
|
19
|
+
|
|
20
|
+
## [1.4.9] — 2026-10-09
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Article `##` uses the card step (`--type-card-lg`). `###` sits halfway
|
|
25
|
+
between that step and the reading size. `####` matches the reading size
|
|
26
|
+
(`--type-prose`).
|
|
27
|
+
|
|
3
28
|
## [1.4.8] — 2026-10-08
|
|
4
29
|
|
|
5
30
|
### Changed
|
data/DESIGN.md
CHANGED
|
@@ -853,13 +853,13 @@ Its ink is `onSurface` — pure black in light, pure white in dark — so body
|
|
|
853
853
|
copy and titles share the full foreground. Meta, captions, chips and other
|
|
854
854
|
chrome stay on `onSurfaceVariant`.
|
|
855
855
|
|
|
856
|
-
* **`h2`** is the
|
|
857
|
-
above and `--space-2xl` of air, so a long article reads
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
856
|
+
* **`h2`** (`##`) is the content-title face at `--type-card-lg` / 400, with a
|
|
857
|
+
`gentleDivider` rule above and `--space-2xl` of air, so a long article reads
|
|
858
|
+
as a sequence of plates. The first `h2` in the column loses its rule.
|
|
859
|
+
* **`h3`** (`###`) is the same face, at the midpoint of `--type-card-lg` and
|
|
860
|
+
`--type-prose`.
|
|
861
|
+
* **`h4`** (`####`) is the body face at `--type-prose` / 400, the same size as
|
|
862
|
+
the paragraph.
|
|
863
863
|
* **`h1`** is untouched from the old build: body sans, 600, `1.4em`. It is
|
|
864
864
|
rare in prose and has no rule above it.
|
|
865
865
|
|
data/_layouts/home.html
CHANGED
|
@@ -5,7 +5,8 @@ layout: shell
|
|
|
5
5
|
{%- comment -%}
|
|
6
6
|
Home — composition from the page registry (PLT §5.4).
|
|
7
7
|
|
|
8
|
-
The hero reads identity
|
|
8
|
+
The hero reads identity (first_name and last_name, or a split of name)
|
|
9
|
+
plus content fragments. Decorative art is a slot
|
|
9
10
|
(`pandorga.home.hero_art`, default page/hero-default.html). Listing and
|
|
10
11
|
index bands come from site.data.pandorga_home_bands in registry order.
|
|
11
12
|
Band copy (kicker, title, note) arrives from content/pages/headers.yml
|
|
@@ -13,10 +14,7 @@ layout: shell
|
|
|
13
14
|
in this layout.
|
|
14
15
|
{%- endcomment -%}
|
|
15
16
|
|
|
16
|
-
{%- assign
|
|
17
|
-
{%- assign _name_parts = _identity_name | split: " " -%}
|
|
18
|
-
{%- assign _name_given = _name_parts | first -%}
|
|
19
|
-
{%- assign _name_family = _name_parts | slice: 1, 8 | join: " " -%}
|
|
17
|
+
{%- assign _hero_name = site.pandorga.identity | pandorga_hero_name: site.author, site.title -%}
|
|
20
18
|
{%- assign _cv_page = site.pages | where: "key", "cv" | first -%}
|
|
21
19
|
{%- assign _hero_art = site.pandorga.home.hero_art | default: "page/hero-default.html" -%}
|
|
22
20
|
{%- assign _home_bands = site.data.pandorga_home_bands -%}
|
|
@@ -33,7 +31,7 @@ layout: shell
|
|
|
33
31
|
<div id="home-subtitle-content" class="home-hero-tagline font-label"
|
|
34
32
|
data-content-fragment="home/subtitle"></div>
|
|
35
33
|
<h1 class="home-hero-title">
|
|
36
|
-
<span class="home-hero-given">{{
|
|
34
|
+
<span class="home-hero-given">{{ _hero_name.given }}</span>{%- if _hero_name.family != "" %} <span class="home-hero-family">{{ _hero_name.family }}</span>{%- endif %}
|
|
37
35
|
</h1>
|
|
38
36
|
<div id="home-cv-summary" class="home-hero-summary font-body"
|
|
39
37
|
data-content-fragment="cv/summary"></div>
|
data/_layouts/shell.html
CHANGED
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
|
|
85
85
|
<body class="bg-background text-on-surface">
|
|
86
86
|
|
|
87
|
+
{% assign _nav_name = site.pandorga.identity | pandorga_hero_name: site.author, site.title %}
|
|
87
88
|
{% assign nav_active = nil %}
|
|
88
89
|
{% if page.collection == "articles" %}{% assign nav_active = "articles" %}{% endif %}
|
|
89
90
|
{% if page.collection == "posts" %}{% assign nav_active = "blog" %}{% endif %}
|
|
@@ -99,7 +100,7 @@
|
|
|
99
100
|
<div class="flex items-center gap-3 sm:gap-4 min-w-0">
|
|
100
101
|
<a href="/" aria-label="{{ site.author }} – Home" class="shrink-0 inline-flex items-center gap-3 site-wordmark-link">
|
|
101
102
|
{% include theme/site-logo.html %}
|
|
102
|
-
<span class="site-wordmark">{{
|
|
103
|
+
<span class="site-wordmark">{{ _nav_name.wordmark }}</span>
|
|
103
104
|
</a>
|
|
104
105
|
</div>
|
|
105
106
|
|
data/assets/css/article.css
CHANGED
|
@@ -73,15 +73,17 @@
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
.article-content h2 {
|
|
76
|
-
|
|
76
|
+
/* Same step ### used to take. */
|
|
77
|
+
font-size: var(--type-card-lg);
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
.article-content h3 {
|
|
80
|
-
|
|
81
|
+
/* Halfway between ## and the reading size. */
|
|
82
|
+
font-size: calc((var(--type-card-lg) + var(--type-prose)) / 2);
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
.article-content h4 {
|
|
84
|
-
font-size: var(--type-
|
|
86
|
+
font-size: var(--type-prose);
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
/* The paragraph gap belongs to ledger/detail.css, which sets it at 1.15em;
|
|
@@ -690,10 +690,15 @@ aside:has(> .text-detail-sidebar) {
|
|
|
690
690
|
|
|
691
691
|
Raised from 1.6em: at that size a section head opening a plate sat too close
|
|
692
692
|
to the paragraph under it to stop the eye on its own, and the hairline was
|
|
693
|
-
doing most of the work. h3 and the sidebar h1 move with it below.
|
|
693
|
+
doing most of the work. h3 and the sidebar h1 move with it below.
|
|
694
|
+
|
|
695
|
+
## takes the card step (`--type-card-lg`), the size ### used to have.
|
|
696
|
+
### is the midpoint of that step and the reading size. #### is the reading
|
|
697
|
+
size itself (`--type-prose`), so it matches the paragraph and still reads
|
|
698
|
+
as a heading by weight. */
|
|
694
699
|
.article-content h2 {
|
|
695
700
|
font-family: var(--font-content-title);
|
|
696
|
-
font-size: var(--type-
|
|
701
|
+
font-size: var(--type-card-lg);
|
|
697
702
|
font-weight: var(--weight-title);
|
|
698
703
|
font-synthesis: none;
|
|
699
704
|
line-height: 1.25;
|
|
@@ -719,8 +724,8 @@ aside:has(> .text-detail-sidebar) {
|
|
|
719
724
|
over a sentence-shaped heading they shout. */
|
|
720
725
|
.article-content h3 {
|
|
721
726
|
font-family: var(--font-content-title);
|
|
722
|
-
/*
|
|
723
|
-
font-size: var(--type-card-lg);
|
|
727
|
+
/* Midpoint of ## (--type-card-lg) and #### (--type-prose). */
|
|
728
|
+
font-size: calc((var(--type-card-lg) + var(--type-prose)) / 2);
|
|
724
729
|
font-weight: var(--weight-title);
|
|
725
730
|
font-synthesis: none;
|
|
726
731
|
letter-spacing: normal;
|
|
@@ -733,7 +738,7 @@ aside:has(> .text-detail-sidebar) {
|
|
|
733
738
|
|
|
734
739
|
.article-content h4 {
|
|
735
740
|
font-family: var(--font-body);
|
|
736
|
-
font-size: var(--type-
|
|
741
|
+
font-size: var(--type-prose);
|
|
737
742
|
font-weight: var(--weight-title);
|
|
738
743
|
font-synthesis: none;
|
|
739
744
|
color: var(--color-on-surface);
|
|
@@ -1119,10 +1124,6 @@ nav.text-page-nav:not(:has(a)) {
|
|
|
1119
1124
|
padding-right: var(--space-md);
|
|
1120
1125
|
}
|
|
1121
1126
|
|
|
1122
|
-
.article-content h2 {
|
|
1123
|
-
font-size: var(--type-section-lg);
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
1127
|
/* A four-column benchmark table wanted 468px inside a 355px column at 390px
|
|
1127
1128
|
and pushed the whole document sideways — every page on the site got a
|
|
1128
1129
|
horizontal scrollbar because of one article. The ledger cell padding is
|
data/assets/css/ledger/home.css
CHANGED
|
@@ -219,19 +219,14 @@
|
|
|
219
219
|
margin: 0 0 var(--space-sm);
|
|
220
220
|
color: var(--color-on-surface);
|
|
221
221
|
font-family: var(--font-label);
|
|
222
|
-
|
|
222
|
+
/* Same size as the summary under the name, which inherits --type-body. */
|
|
223
|
+
font-size: var(--type-body);
|
|
223
224
|
font-weight: var(--weight-label);
|
|
224
225
|
letter-spacing: 0.03em;
|
|
225
226
|
line-height: 1.35;
|
|
226
227
|
max-width: 100%;
|
|
227
228
|
}
|
|
228
229
|
|
|
229
|
-
@media (min-width: 1024px) {
|
|
230
|
-
.home-hero-tagline {
|
|
231
|
-
font-size: calc(var(--type-label-lg) + 2pt);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
230
|
.home-hero-tagline::after {
|
|
236
231
|
content: "";
|
|
237
232
|
flex: 1 1 auto;
|
|
@@ -262,10 +257,13 @@
|
|
|
262
257
|
max-width: 100%;
|
|
263
258
|
}
|
|
264
259
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
260
|
+
.home-hero-given,
|
|
261
|
+
.home-hero-family {
|
|
262
|
+
display: inline;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.home-hero-family {
|
|
266
|
+
color: var(--color-primary);
|
|
269
267
|
}
|
|
270
268
|
|
|
271
269
|
@media (min-width: 640px) {
|
|
@@ -274,19 +272,34 @@
|
|
|
274
272
|
}
|
|
275
273
|
}
|
|
276
274
|
|
|
277
|
-
|
|
275
|
+
/* Below 768px the name is 3pt larger than the clamp above, and the only
|
|
276
|
+
wrap point is the gap between the given name and the family name. */
|
|
277
|
+
@media (max-width: 639px) {
|
|
278
278
|
.home-hero-title {
|
|
279
|
-
white-space:
|
|
279
|
+
white-space: normal;
|
|
280
|
+
font-size: calc(clamp(var(--type-section), calc(100cqi * 0.92 / 14.5), var(--type-page-lg)) + 3pt);
|
|
280
281
|
}
|
|
281
282
|
}
|
|
282
283
|
|
|
283
|
-
|
|
284
|
-
.home-hero-
|
|
285
|
-
|
|
284
|
+
@media (min-width: 640px) and (max-width: 767px) {
|
|
285
|
+
.home-hero-title {
|
|
286
|
+
white-space: normal;
|
|
287
|
+
font-size: calc(clamp(var(--type-section-lg), calc(100cqi * 0.92 / 14.5), var(--type-page-lg)) + 3pt);
|
|
288
|
+
}
|
|
286
289
|
}
|
|
287
290
|
|
|
288
|
-
|
|
289
|
-
|
|
291
|
+
@media (max-width: 767px) {
|
|
292
|
+
.home-hero-given,
|
|
293
|
+
.home-hero-family {
|
|
294
|
+
display: inline-block;
|
|
295
|
+
white-space: nowrap;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
@media (min-width: 768px) {
|
|
300
|
+
.home-hero-title {
|
|
301
|
+
white-space: nowrap;
|
|
302
|
+
}
|
|
290
303
|
}
|
|
291
304
|
|
|
292
305
|
.home-hero-summary {
|
data/examples/full/_config.yml
CHANGED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Spec: docs/features/hero-name.md
|
|
4
|
+
# Refs: PLT-AC-14
|
|
5
|
+
module Pandorga
|
|
6
|
+
# Given name, family name, and top-bar wordmark for the home hero.
|
|
7
|
+
module IdentityName
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# identity may use string or symbol keys. author and title are the
|
|
11
|
+
# Jekyll site.author and site.title fallbacks.
|
|
12
|
+
def resolve(identity, author: nil, title: nil)
|
|
13
|
+
fields = normalize(identity)
|
|
14
|
+
given_field = explicit(fields, "first_name")
|
|
15
|
+
family_field = explicit(fields, "last_name")
|
|
16
|
+
legacy = tokens(text(fields["name"]).empty? ? author : fields["name"])
|
|
17
|
+
|
|
18
|
+
given = present(given_field) ? given_field : legacy.first.to_s
|
|
19
|
+
family = present(family_field) ? family_field : legacy.drop(1).take(8).join(" ")
|
|
20
|
+
wordmark = if present(given_field)
|
|
21
|
+
given_field
|
|
22
|
+
else
|
|
23
|
+
source = [fields["name"], author, title].map { |value| text(value) }.find { |value| !value.empty? }
|
|
24
|
+
tokens(source).first.to_s
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
{ "given" => given, "family" => family, "wordmark" => wordmark }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def normalize(identity)
|
|
31
|
+
raw = case identity
|
|
32
|
+
when Hash then identity
|
|
33
|
+
else identity.respond_to?(:to_hash) ? identity.to_hash : {}
|
|
34
|
+
end
|
|
35
|
+
raw.each_with_object({}) { |(key, value), out| out[key.to_s] = value }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# :missing when the key is absent or null. Blank is handled by present.
|
|
39
|
+
def explicit(fields, key)
|
|
40
|
+
return :missing unless fields.key?(key)
|
|
41
|
+
return :missing if fields[key].nil?
|
|
42
|
+
|
|
43
|
+
fields[key].to_s.strip
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def present(value)
|
|
47
|
+
value.is_a?(String) && !value.empty?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def text(value)
|
|
51
|
+
value.to_s.strip
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def tokens(value)
|
|
55
|
+
text(value).split(" ")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Liquid filter used by the home hero and the top bar.
|
|
61
|
+
module PandorgaHeroNameFilter
|
|
62
|
+
def pandorga_hero_name(identity, author = nil, title = nil)
|
|
63
|
+
Pandorga::IdentityName.resolve(identity, author: author, title: title)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
Liquid::Template.register_filter(PandorgaHeroNameFilter) if defined?(Liquid::Template)
|
|
@@ -8,7 +8,15 @@ identity:
|
|
|
8
8
|
name:
|
|
9
9
|
type: string
|
|
10
10
|
required: true
|
|
11
|
-
notes: Title suffix, default author, BibTeX
|
|
11
|
+
notes: Title suffix, default author, BibTeX. Also the legacy hero name when `first_name` or `last_name` is omitted
|
|
12
|
+
first_name:
|
|
13
|
+
type: string
|
|
14
|
+
required: false
|
|
15
|
+
notes: Hero given name and top-bar wordmark. Omitted or blank, the first word of `name` is used
|
|
16
|
+
last_name:
|
|
17
|
+
type: string
|
|
18
|
+
required: false
|
|
19
|
+
notes: Hero family name, kept whole (spaces allowed). Omitted or blank, the remainder of `name` is used
|
|
12
20
|
short_name:
|
|
13
21
|
type: string
|
|
14
22
|
required: false
|
data/lib/pandorga/version.rb
CHANGED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Hero name fields and headline size (docs/features/hero-name.md).
|
|
5
|
+
#
|
|
6
|
+
# PLT-AC-13 subtitle uses the body size
|
|
7
|
+
# PLT-AC-14 first_name / last_name, with the 1.4.9 split as fallback
|
|
8
|
+
# PLT-AC-15 phone name is +3pt and wraps only between the two names
|
|
9
|
+
|
|
10
|
+
require "pathname"
|
|
11
|
+
|
|
12
|
+
ROOT = Pathname.new(__dir__).join("..", "..").expand_path
|
|
13
|
+
HOME = ROOT.join("_layouts/home.html")
|
|
14
|
+
SHELL = ROOT.join("_layouts/shell.html")
|
|
15
|
+
CSS = ROOT.join("assets/css/ledger/home.css")
|
|
16
|
+
SCHEMA = ROOT.join("lib/pandorga/registry/schema.yml")
|
|
17
|
+
|
|
18
|
+
def check!(condition, message)
|
|
19
|
+
raise "Assertion failed: #{message}" unless condition
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
puts "Testing hero name..."
|
|
23
|
+
|
|
24
|
+
# ── PLT-AC-13 ───────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
css = CSS.read
|
|
27
|
+
tagline = css[/\.home-hero-tagline \{.*?\n\}/m]
|
|
28
|
+
check!(tagline, "missing .home-hero-tagline rule")
|
|
29
|
+
check!(tagline.include?("font-size: var(--type-body);"),
|
|
30
|
+
"subtitle must use the body size, got:\n#{tagline}")
|
|
31
|
+
check!(!tagline.include?("type-label-lg"),
|
|
32
|
+
"subtitle must not keep the label size")
|
|
33
|
+
check!(!css.include?("calc(var(--type-label-lg) + 2pt)"),
|
|
34
|
+
"desktop subtitle must not add 2pt")
|
|
35
|
+
|
|
36
|
+
summary = css[/\.home-hero-summary \{.*?\n\}/m]
|
|
37
|
+
check!(summary, "missing .home-hero-summary rule")
|
|
38
|
+
check!(!summary.include?("font-size:"),
|
|
39
|
+
"summary inherits the page body size and must not set its own")
|
|
40
|
+
|
|
41
|
+
# ── PLT-AC-14 ───────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
schema = SCHEMA.read
|
|
44
|
+
check!(schema.include?("first_name:"), "schema missing first_name")
|
|
45
|
+
check!(schema.include?("last_name:"), "schema missing last_name")
|
|
46
|
+
check!(schema.include?("name:") && schema.include?("required: true"),
|
|
47
|
+
"identity.name stays required")
|
|
48
|
+
|
|
49
|
+
home = HOME.read
|
|
50
|
+
shell = SHELL.read
|
|
51
|
+
check!(home.include?("pandorga_hero_name"),
|
|
52
|
+
"home hero must resolve the name through pandorga_hero_name")
|
|
53
|
+
check!(home.include?("home-hero-given") && home.include?("home-hero-family"),
|
|
54
|
+
"hero keeps a given span and a family span")
|
|
55
|
+
check!(shell.include?("pandorga_hero_name"),
|
|
56
|
+
"top bar must resolve the wordmark through pandorga_hero_name")
|
|
57
|
+
check!(shell.include?(".wordmark"),
|
|
58
|
+
"top bar prints the wordmark from the resolver")
|
|
59
|
+
check!(!shell.include?('site.author | default: site.title | split: " " | first'),
|
|
60
|
+
"top bar must not split site.author on its own")
|
|
61
|
+
|
|
62
|
+
require "liquid"
|
|
63
|
+
require_relative "../../lib/pandorga/jekyll/plugins/identity_name"
|
|
64
|
+
|
|
65
|
+
template = Liquid::Template.parse(<<~LIQUID)
|
|
66
|
+
{% assign hero = identity | pandorga_hero_name: author, title %}
|
|
67
|
+
{{ hero.given }}|{{ hero.family }}|{{ hero.wordmark }}
|
|
68
|
+
LIQUID
|
|
69
|
+
rendered = template.render(
|
|
70
|
+
"identity" => { "first_name" => "Ada", "last_name" => "Rivera Santos" },
|
|
71
|
+
"author" => "Other Person",
|
|
72
|
+
"title" => "Site Title"
|
|
73
|
+
)
|
|
74
|
+
check!(rendered.strip == "Ada|Rivera Santos|Ada",
|
|
75
|
+
"Liquid filter must keep a multi-word last name, got #{rendered.inspect}")
|
|
76
|
+
|
|
77
|
+
both = Pandorga::IdentityName.resolve(
|
|
78
|
+
{ "name" => "Ada Rivera Santos", "first_name" => "Ada", "last_name" => "Rivera Santos" },
|
|
79
|
+
author: "Other Person",
|
|
80
|
+
title: "Site Title"
|
|
81
|
+
)
|
|
82
|
+
check!(both["given"] == "Ada", "given name, got #{both.inspect}")
|
|
83
|
+
check!(both["family"] == "Rivera Santos", "multi-word family name must stay whole, got #{both.inspect}")
|
|
84
|
+
check!(both["wordmark"] == "Ada", "top bar shows the first name, got #{both.inspect}")
|
|
85
|
+
|
|
86
|
+
spaced = Pandorga::IdentityName.resolve(
|
|
87
|
+
{ "first_name" => " Mary Ann ", "last_name" => " Rivera Santos " }
|
|
88
|
+
)
|
|
89
|
+
check!(spaced["given"] == "Mary Ann" && spaced["wordmark"] == "Mary Ann",
|
|
90
|
+
"first_name is kept whole, got #{spaced.inspect}")
|
|
91
|
+
check!(spaced["family"] == "Rivera Santos",
|
|
92
|
+
"last_name is trimmed but not split, got #{spaced.inspect}")
|
|
93
|
+
|
|
94
|
+
legacy = Pandorga::IdentityName.resolve(
|
|
95
|
+
{ "name" => "Ada Rivera Santos" },
|
|
96
|
+
author: "Other Person",
|
|
97
|
+
title: "Site Title"
|
|
98
|
+
)
|
|
99
|
+
check!(legacy["given"] == "Ada", "legacy given name, got #{legacy.inspect}")
|
|
100
|
+
check!(legacy["family"] == "Rivera Santos", "legacy family name, got #{legacy.inspect}")
|
|
101
|
+
check!(legacy["wordmark"] == "Ada", "legacy wordmark prefers identity.name, got #{legacy.inspect}")
|
|
102
|
+
|
|
103
|
+
partial_family = Pandorga::IdentityName.resolve(
|
|
104
|
+
{ "name" => "Someone Else", "last_name" => "Rivera Santos" }
|
|
105
|
+
)
|
|
106
|
+
check!(partial_family["given"] == "Someone", "missing first_name uses the first word of name")
|
|
107
|
+
check!(partial_family["family"] == "Rivera Santos", "explicit last_name replaces the remainder")
|
|
108
|
+
check!(partial_family["wordmark"] == "Someone", "missing first_name wordmark is the first word of name")
|
|
109
|
+
|
|
110
|
+
partial_given = Pandorga::IdentityName.resolve({ "name" => "Ada Example", "first_name" => "Ada" })
|
|
111
|
+
check!(partial_given["family"] == "Example", "missing last_name keeps the remainder of name")
|
|
112
|
+
check!(partial_given["wordmark"] == "Ada", "wordmark uses first_name")
|
|
113
|
+
|
|
114
|
+
blank_last = Pandorga::IdentityName.resolve({ "name" => "Ada Example", "last_name" => "" })
|
|
115
|
+
check!(blank_last["family"] == "Example", "blank last_name keeps the 1.4.9 remainder")
|
|
116
|
+
|
|
117
|
+
author_only = Pandorga::IdentityName.resolve({}, author: "Other Person", title: "Site Title")
|
|
118
|
+
check!(author_only["given"] == "Other" && author_only["family"] == "Person",
|
|
119
|
+
"name falls back to site.author, got #{author_only.inspect}")
|
|
120
|
+
check!(author_only["wordmark"] == "Other", "wordmark falls back to site.author")
|
|
121
|
+
|
|
122
|
+
title_only = Pandorga::IdentityName.resolve({}, title: "Site Title")
|
|
123
|
+
check!(title_only["given"] == "" && title_only["family"] == "",
|
|
124
|
+
"hero does not take site.title, got #{title_only.inspect}")
|
|
125
|
+
check!(title_only["wordmark"] == "Site", "wordmark falls back to site.title")
|
|
126
|
+
|
|
127
|
+
long_name = (["Ada"] + (1..9).map { |index| "Tok#{index}" }).join(" ")
|
|
128
|
+
long = Pandorga::IdentityName.resolve({ "name" => long_name })
|
|
129
|
+
check!(long["family"] == (1..8).map { |index| "Tok#{index}" }.join(" "),
|
|
130
|
+
"legacy remainder stays eight words, got #{long["family"].inspect}")
|
|
131
|
+
|
|
132
|
+
symbols = Pandorga::IdentityName.resolve({ first_name: "Ada", last_name: "Rivera Santos" })
|
|
133
|
+
check!(symbols["family"] == "Rivera Santos", "symbol keys work, got #{symbols.inspect}")
|
|
134
|
+
|
|
135
|
+
# ── PLT-AC-15 ───────────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
hero = css[/\.home-hero-title \{.*?(?=\.home-hero-summary)/m]
|
|
138
|
+
check!(hero, "missing hero title block")
|
|
139
|
+
check!(hero.include?("font-size: clamp(var(--type-section), calc(100cqi * 0.92 / 14.5), var(--type-page-lg));"),
|
|
140
|
+
"base name size stays the 1.4.9 clamp")
|
|
141
|
+
check!(hero.include?("@media (max-width: 639px)"), "missing phone size query")
|
|
142
|
+
check!(hero.include?("calc(clamp(var(--type-section), calc(100cqi * 0.92 / 14.5), var(--type-page-lg)) + 3pt)"),
|
|
143
|
+
"below 640px the name must be the current clamp plus 3pt")
|
|
144
|
+
check!(hero.include?("@media (min-width: 640px) and (max-width: 767px)"),
|
|
145
|
+
"missing 640-767 size query")
|
|
146
|
+
check!(hero.include?("calc(clamp(var(--type-section-lg), calc(100cqi * 0.92 / 14.5), var(--type-page-lg)) + 3pt)"),
|
|
147
|
+
"from 640px to 767px the name must be the section-lg clamp plus 3pt")
|
|
148
|
+
|
|
149
|
+
plain = hero.index("@media (min-width: 640px) {")
|
|
150
|
+
banded = hero.index("@media (min-width: 640px) and (max-width: 767px)")
|
|
151
|
+
check!(plain && banded && banded > plain,
|
|
152
|
+
"the +3pt band must follow the plain 640px clamp so it wins")
|
|
153
|
+
|
|
154
|
+
phone = hero[/@media \(max-width: 767px\) \{.*?\n\}/m]
|
|
155
|
+
check!(phone && phone.include?("inline-block") && phone.include?("white-space: nowrap"),
|
|
156
|
+
"on a phone each name is an unbreakable unit, got:\n#{phone}")
|
|
157
|
+
check!(phone.include?(".home-hero-given") && phone.include?(".home-hero-family"),
|
|
158
|
+
"both name parts are unbreakable")
|
|
159
|
+
|
|
160
|
+
desktop = hero[/@media \(min-width: 768px\) \{.*?\n\}/m]
|
|
161
|
+
check!(desktop && desktop.include?("white-space: nowrap"),
|
|
162
|
+
"from 768px the name stays on one line")
|
|
163
|
+
check!(!desktop.include?("3pt"), "the +3pt bump is phone-only")
|
|
164
|
+
|
|
165
|
+
puts "PASS test-hero-name"
|
data/scripts/validate.sh
CHANGED
|
@@ -51,6 +51,7 @@ TESTS=(
|
|
|
51
51
|
scripts/test/test-absent-field-absent-element.rb
|
|
52
52
|
scripts/test/test-ledger-blocks.rb
|
|
53
53
|
scripts/test/test-home-band-art.rb
|
|
54
|
+
scripts/test/test-hero-name.rb
|
|
54
55
|
scripts/test/test-r2-json-sync.rb
|
|
55
56
|
scripts/test/test-writing-slug-limits.rb
|
|
56
57
|
scripts/test/test-examples-build.rb
|
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.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- The Pandorga contributors
|
|
@@ -285,6 +285,7 @@ files:
|
|
|
285
285
|
- lib/pandorga/jekyll/plugins/force_utf8_encoding.rb
|
|
286
286
|
- lib/pandorga/jekyll/plugins/git_chronology.rb
|
|
287
287
|
- lib/pandorga/jekyll/plugins/git_metadata.rb
|
|
288
|
+
- lib/pandorga/jekyll/plugins/identity_name.rb
|
|
288
289
|
- lib/pandorga/jekyll/plugins/job_sections.rb
|
|
289
290
|
- lib/pandorga/jekyll/plugins/lib/content_validators.rb
|
|
290
291
|
- lib/pandorga/jekyll/plugins/lib/resource_validator.rb
|
|
@@ -391,6 +392,7 @@ files:
|
|
|
391
392
|
- scripts/test/test-exported-fields-reach-the-view.rb
|
|
392
393
|
- scripts/test/test-form-control-theming.rb
|
|
393
394
|
- scripts/test/test-git-chronology.rb
|
|
395
|
+
- scripts/test/test-hero-name.rb
|
|
394
396
|
- scripts/test/test-hero-portraits.mjs
|
|
395
397
|
- scripts/test/test-hero-portraits.rb
|
|
396
398
|
- scripts/test/test-home-band-art.rb
|