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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fdc6dcec6dea0007093b40500ca2ec287d17170029f68b3f4284090fe14236a1
4
- data.tar.gz: be5ea215e27ebe6755ce5d09875c8ca175d16daf3d8328c94161255fba179531
3
+ metadata.gz: eab1ea9696bdf99c28026f135762061c5a627ea398df5ddd8080f2d10dd9abc7
4
+ data.tar.gz: 89b13dcd397f666cf5660845d7ffece69478d6275b1d821d9500a4bf08046e5b
5
5
  SHA512:
6
- metadata.gz: f2f5de4ed2c7737a20b33fe03018c49f8a2267a4903ed065c5af9f99bf1b36e708305186c3d022786152efc15ba39791c6f55c2cf9427d2bac4f97a61cda77a7
7
- data.tar.gz: 43e93a55bd09913445387e511741522588d2fb51d37589dc51abc4d0d4e4145fa3fef32c7087a7b4d59fa841fe25b2e5cc799bc1c88c2efa3883e7dfb5d546d4
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 display serif at `1.6em / 400`, with a `gentleDivider` rule
857
- above and `--space-2xl` of air, so a long article reads as a sequence of
858
- plates. It drops to `1.4em` below 768px, and the first `h2` in the column
859
- loses its rule.
860
- * **`h3`** is the label face at `0.95em`, uppercase, `0.1em` tracking, in
861
- `primary`.
862
- * **`h4`** is the body sans at `1em / 600`.
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 plus content fragments. Decorative art is a slot
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 _identity_name = site.pandorga.identity.name | default: site.author -%}
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">{{ _name_given }}</span>{%- if _name_family != "" %} <span class="home-hero-family">{{ _name_family }}</span>{%- endif %}
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">{{ site.author | default: site.title | split: " " | first }}</span>
103
+ <span class="site-wordmark">{{ _nav_name.wordmark }}</span>
103
104
  </a>
104
105
  </div>
105
106
 
@@ -73,15 +73,17 @@
73
73
  }
74
74
 
75
75
  .article-content h2 {
76
- font-size: var(--type-title);
76
+ /* Same step ### used to take. */
77
+ font-size: var(--type-card-lg);
77
78
  }
78
79
 
79
80
  .article-content h3 {
80
- font-size: var(--type-card-lg);
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-body);
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-title);
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
- /* Holds a step below h2. */
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-body);
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
@@ -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
- font-size: var(--type-label-lg);
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
- @media (max-width: 767px) {
266
- .home-hero-title {
267
- white-space: normal;
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
- @media (min-width: 768px) {
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: nowrap;
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
- .home-hero-given,
284
- .home-hero-family {
285
- display: inline;
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
- .home-hero-family {
289
- color: var(--color-primary);
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 {
@@ -8,6 +8,8 @@ plugins:
8
8
  pandorga:
9
9
  identity:
10
10
  name: "Ada Example"
11
+ first_name: "Ada"
12
+ last_name: "Example"
11
13
  short_name: "A. Example"
12
14
  url: "https://example.org"
13
15
  locale: en
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pandorga
4
- VERSION = "1.4.8"
4
+ VERSION = "1.5.0"
5
5
  end
@@ -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.8
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