pandorga 1.6.6 → 1.6.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +3 -2
  4. data/_includes/home/band-listing.html +1 -1
  5. data/_includes/page/content-runtime.html +3 -0
  6. data/_includes/page/network-graph.html +5 -2
  7. data/_includes/page/timeline-section.html +13 -1
  8. data/_includes/theme/favicons.html +3 -1
  9. data/_includes/theme/font-loader.html +5 -9
  10. data/_layouts/articles.html +5 -2
  11. data/_layouts/bibliography.html +5 -2
  12. data/_layouts/blog.html +5 -2
  13. data/_layouts/cv.html +5 -2
  14. data/_layouts/home.html +10 -3
  15. data/_layouts/projects.html +5 -2
  16. data/_layouts/resources.html +5 -2
  17. data/_layouts/shell.html +85 -13
  18. data/assets/css/ledger/home.css +20 -0
  19. data/assets/css/ledger/rascunhos.css +40 -2
  20. data/assets/icons/apple-touch-icon.png +0 -0
  21. data/assets/icons/favicon-32.png +0 -0
  22. data/assets/icons/favicon.ico +0 -0
  23. data/assets/icons/favicon.svg +23 -0
  24. data/assets/icons/icon-192.png +0 -0
  25. data/assets/icons/icon-32.png +0 -0
  26. data/assets/icons/icon-512.png +0 -0
  27. data/assets/icons/icon-maskable-192.png +0 -0
  28. data/assets/icons/icon-maskable-512.png +0 -0
  29. data/assets/icons/studio-apple-touch-icon.png +0 -0
  30. data/assets/icons/studio-icon-192.png +0 -0
  31. data/assets/icons/studio-icon-512.png +0 -0
  32. data/assets/icons/studio-logo-32.png +0 -0
  33. data/assets/icons/studio-logo.ico +0 -0
  34. data/assets/icons/studio-logo.svg +25 -5
  35. data/assets/icons/studio-maskable-192.png +0 -0
  36. data/assets/icons/studio-maskable-512.png +0 -0
  37. data/examples/full/content/pages/cv/summary.md +1 -1
  38. data/lib/pandorga/jekyll/plugins/web_manifest.rb +81 -0
  39. data/lib/pandorga/version.rb +1 -1
  40. data/scripts/check_home_lists.mjs +209 -0
  41. data/scripts/lighthouse-home.sh +9 -1
  42. data/scripts/render-pwa-icons.py +141 -0
  43. data/scripts/test/test-home-articles.rb +30 -0
  44. data/scripts/test/test-home-lists.rb +74 -0
  45. data/scripts/test/test-lighthouse-policy.rb +13 -6
  46. data/scripts/test/test-pwa-icons.rb +280 -0
  47. data/scripts/test/test-rascunhos-licence.rb +43 -0
  48. data/scripts/validate.sh +4 -0
  49. data/studio/index.html +4 -1
  50. data/studio-app/index.html +4 -1
  51. metadata +24 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea34cfd7c17d2af28fcbd6a14c791f47b62fcf3a0c1c27b453f2467e6e4e101e
4
- data.tar.gz: 7935fd040c610ffd52cab6587ecbcf31368a6097e21010f41b36ea92df013332
3
+ metadata.gz: 355770a826a6483d7fbc1e014c76fc0ef865d23bba44674cc2153d25ed840a9c
4
+ data.tar.gz: 6fd8451064d1a3bc58a8ff4ed53b90606183644756c28984018ff20ae485a543
5
5
  SHA512:
6
- metadata.gz: 03e056b64de07a581a1b6293dd8a4db3aa40872219f170adfeaf25efe948317a62c04444db0cfe36cf67d8138ed003f9f832fc5c1150a0d84d174abc48232cdc
7
- data.tar.gz: cd7dec364ba9a21e18c0ec05e60bd7eed94ca99a217b99a5cd2a67498c9778e51a78267ba4cad58bab5098020e7c75f8be2e8a5df3b84b4d23c37b07313ed16f
6
+ metadata.gz: 331cf7a4687f241ee1b653e2435986bd511536ac3d1bfd21b65c91deef8969994999d8d65696e54a5725e7be41169d10ff9abac70475844a018480869cb19c1c
7
+ data.tar.gz: 88fc4c23d1cb5680b054c2e7028e374caab861977114ae235de52547b482de74b0c282957fc5bdf31764c9c3f11e39a65bbd4372ee7ee8fbf397b238b41f1c66
data/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.6.8] — 2026-10-10
4
+
5
+ ### Fixed
6
+
7
+ - Home bands and the other listing pages fill again. Their scripts sit in
8
+ the page and used to run before the runtime existed, then return. The
9
+ titles stayed in the HTML and the lists stayed empty, on the phone and
10
+ on the desktop. Lighthouse scored that empty document. Those scripts
11
+ now start when the runtime is assigned. The runtime stays after the
12
+ page. The static hero summary, the deferred text faces, the deferred
13
+ markdown library, and the named icon subset stay as they are.
14
+ - CI loads the built example home and fails when the projects, articles,
15
+ or posts band has no items after load, on a phone viewport and on a
16
+ desktop viewport.
17
+ - On a phone, the featured card in the home Articles band has more space
18
+ above its text. The other article cards keep their padding. Article
19
+ titles in that band are 2pt larger.
20
+ - On the Rascunhos page, the licence sits under the entries again below
21
+ 1024px. The per-page stylesheet split had stopped loading the sheet
22
+ that ordered it, and the page's own `display: block` put it back on
23
+ top. The order now lives on the sheet the blog layout always loads.
24
+
25
+ ### Changed
26
+
27
+ - The site and Studio launcher icons are the same stamp, on the same
28
+ plate, in the same pastel triad. The stamp is the same size on both,
29
+ with the same padding, at 512, 192, 180, and 32. Studio adds a small
30
+ badge inside that stamp. The build writes `/site.webmanifest` and
31
+ `/studio.webmanifest` with maskable icons, so Android masks both to
32
+ one shape and one visual size. The shell links the site manifest.
33
+ Studio links the studio manifest.
34
+
35
+ No `pandorga:` config keys were added or renamed.
36
+
37
+ ## [1.6.7] — 2026-10-10
38
+
39
+ ### Fixed
40
+
41
+ - Text faces are requested after the first paint and before the load
42
+ event. A blocking sheet held first paint on the font host. The load
43
+ event held the hero summary until images finished, so the live home
44
+ scored about 50 while the example, which has no summary, scored 98.
45
+ - The icon face names the icons it needs. The full variable file is about
46
+ a megabyte, and decoding it blocked the main thread on the live home.
47
+ - The project inventory region has a role to match its accessible name.
48
+ - Lighthouse measures `examples/full`, whose home includes a static hero
49
+ summary, the element a real site paints largest.
50
+
51
+ No `pandorga:` config keys were added or renamed.
52
+
3
53
  ## [1.6.6] — 2026-10-10
4
54
 
5
55
  ### Fixed
data/README.md CHANGED
@@ -85,8 +85,9 @@ and an urgent performance campaign must then bring it back to 90 or above.
85
85
  Below 75 is never acceptable for a release. A pull request or a merge may
86
86
  score lower when that is unavoidable to keep the work modular. The release
87
87
  build publishes at 90 or above. Notes that include `LIGHTHOUSE_BUGFIX=1`
88
- may publish a bug fix from 75 to 89. Pull requests and merges only warn.
89
- The checklist is in [CONTRIBUTING.md](CONTRIBUTING.md).
88
+ may publish a bug fix from 75 to 89. Pull requests and merges only warn
89
+ on the score. The same run fails when the loaded example home has no
90
+ project or article items. The checklist is in [CONTRIBUTING.md](CONTRIBUTING.md).
90
91
 
91
92
  ## License
92
93
 
@@ -54,7 +54,7 @@
54
54
  data-tag-base="{{ _path }}"
55
55
  >
56
56
  <div class="home-project-specimen-slot" data-home-specimen></div>
57
- <div class="home-project-inventory" data-home-inventory aria-label="Project inventory"></div>
57
+ <div class="home-project-inventory" data-home-inventory role="region" aria-label="Project inventory"></div>
58
58
  </div>
59
59
  {%- else -%}
60
60
  <div
@@ -17,4 +17,7 @@
17
17
  {% include content-runtime/43-blocks.html %}
18
18
  {% include content-runtime/90-bootstrap.html %}
19
19
  })();
20
+ /* Band and listing scripts sit in the page, so they run before this
21
+ assignment. They listen for the event and start once the runtime exists. */
22
+ document.dispatchEvent(new CustomEvent('pandorga:runtime'));
20
23
  </script>
@@ -454,11 +454,14 @@
454
454
 
455
455
  <script src="https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js"></script>
456
456
  <script>
457
- (function () {
457
+ (function bootNetwork() {
458
458
  'use strict';
459
459
 
460
460
  var runtime = window.ContentRuntime;
461
- if (!runtime || !runtime.ListingFilter) return;
461
+ if (!runtime || typeof runtime.loadJson !== 'function') {
462
+ document.addEventListener('pandorga:runtime', bootNetwork, { once: true });
463
+ return;
464
+ }
462
465
 
463
466
  var params = new URLSearchParams(window.location.search);
464
467
  var filterRoot = document.querySelector('[data-listing-filter-root]');
@@ -852,14 +852,26 @@
852
852
  var runtime = window.ContentRuntime;
853
853
  /* The content base is empty when the JSON is served from the site root
854
854
  (local preview), which is a valid source, not a missing one — gate on
855
- the loader instead, and let the catch fall back to the inline stub. */
855
+ the loader instead, and let the catch fall back to the inline stub.
856
+ The runtime script follows the page, so the first call waits for it. */
856
857
  if (runtime && typeof runtime.loadJson === 'function') {
858
+ loadTimeline.started = true;
857
859
  runtime.loadJson('data/timeline.json').then(bootTimeline).catch(function (error) {
858
860
  console.error(error);
859
861
  if (el) bootTimeline(JSON.parse(el.textContent));
860
862
  });
861
863
  return;
862
864
  }
865
+ if (!loadTimeline.armed) {
866
+ loadTimeline.armed = true;
867
+ document.addEventListener('pandorga:runtime', loadTimeline, { once: true });
868
+ window.setTimeout(function () {
869
+ if (loadTimeline.started || !el) return;
870
+ loadTimeline.started = true;
871
+ bootTimeline(JSON.parse(el.textContent));
872
+ }, 0);
873
+ return;
874
+ }
863
875
  if (el) bootTimeline(JSON.parse(el.textContent));
864
876
  }
865
877
 
@@ -1,4 +1,6 @@
1
1
  <link rel="icon" href="/assets/icons/favicon.svg" type="image/svg+xml" sizes="any" />
2
2
  <link rel="icon" href="/assets/icons/favicon-32.png" type="image/png" sizes="32x32" />
3
- <link rel="apple-touch-icon" href="/assets/icons/apple-touch-icon.png" />
3
+ <link rel="apple-touch-icon" href="/assets/icons/apple-touch-icon.png" sizes="180x180" />
4
4
  <link rel="icon" href="/favicon.ico" sizes="16x16 32x32" />
5
+ <link rel="manifest" href="/site.webmanifest" />
6
+ <meta name="theme-color" content="#5e5d59" />
@@ -52,16 +52,12 @@
52
52
  {% endif %}
53
53
  {% assign fonts_href = fonts_href | append: "&display=swap" %}
54
54
  {% comment %}
55
- Not a head link. A preload or a print sheet is discovered immediately.
56
- On a fast connection the faces finish before the largest paint, and the
57
- simulator then waits on them. Add the sheet after load. display=swap stays.
55
+ Not a render-blocking head link, and not the load event. A blocking
56
+ sheet holds first paint on the font host. The load event holds the hero
57
+ summary until images finish. The shell inserts this sheet after the
58
+ first paint. display=swap stays.
58
59
  {% endcomment %}
59
60
  <script>
60
- window.addEventListener('load', function () {
61
- var link = document.createElement('link');
62
- link.rel = 'stylesheet';
63
- link.href = {{ fonts_href | jsonify }};
64
- document.head.appendChild(link);
65
- });
61
+ window.__pandorgaFontsHref = {{ fonts_href | jsonify }};
66
62
  </script>
67
63
  <noscript><link rel="stylesheet" href="{{ fonts_href }}" /></noscript>
@@ -129,11 +129,14 @@ layout: shell
129
129
  {% include mathjax.html %}
130
130
 
131
131
  <script>
132
- (function () {
132
+ (function bootArticles() {
133
133
  'use strict';
134
134
 
135
135
  var runtime = window.ContentRuntime;
136
- if (!runtime || !runtime.ListingFilter) return;
136
+ if (!runtime || typeof runtime.loadJson !== 'function') {
137
+ document.addEventListener('pandorga:runtime', bootArticles, { once: true });
138
+ return;
139
+ }
137
140
 
138
141
  var configRoot = document.querySelector('[data-listing-collection]');
139
142
  var collection = (configRoot && configRoot.getAttribute('data-listing-collection')) || 'articles';
@@ -24,11 +24,14 @@ layout: shell
24
24
  </main>
25
25
 
26
26
  <script>
27
- (function () {
27
+ (function bootBibliography() {
28
28
  'use strict';
29
29
 
30
30
  var runtime = window.ContentRuntime;
31
- if (!runtime || !runtime.ListingFilter) return;
31
+ if (!runtime || typeof runtime.loadJson !== 'function') {
32
+ document.addEventListener('pandorga:runtime', bootBibliography, { once: true });
33
+ return;
34
+ }
32
35
 
33
36
  var listing = runtime.Listing;
34
37
  var params = new URLSearchParams(window.location.search);
data/_layouts/blog.html CHANGED
@@ -93,11 +93,14 @@ layout: shell
93
93
  {% include mathjax.html %}
94
94
 
95
95
  <script>
96
- (function () {
96
+ (function bootBlog() {
97
97
  'use strict';
98
98
 
99
99
  var runtime = window.ContentRuntime;
100
- if (!runtime || !runtime.ListingFilter) return;
100
+ if (!runtime || typeof runtime.loadJson !== 'function') {
101
+ document.addEventListener('pandorga:runtime', bootBlog, { once: true });
102
+ return;
103
+ }
101
104
 
102
105
  var configRoot = document.querySelector('[data-listing-collection]');
103
106
  var collection = (configRoot && configRoot.getAttribute('data-listing-collection')) || 'posts';
data/_layouts/cv.html CHANGED
@@ -80,11 +80,14 @@ layout: shell
80
80
  </main>
81
81
 
82
82
  <script>
83
- (function () {
83
+ (function bootCv() {
84
84
  'use strict';
85
85
 
86
86
  var runtime = window.ContentRuntime;
87
- if (!runtime) return;
87
+ if (!runtime || typeof runtime.loadJson !== 'function') {
88
+ document.addEventListener('pandorga:runtime', bootCv, { once: true });
89
+ return;
90
+ }
88
91
 
89
92
  var experienceList = document.getElementById('cv-experience-list');
90
93
  var educationList = document.getElementById('cv-education-list');
data/_layouts/home.html CHANGED
@@ -63,8 +63,12 @@ layout: shell
63
63
  </div>
64
64
  </section>
65
65
  <script>
66
- (function () {
66
+ (function bootHomePaint() {
67
67
  var runtime = window.ContentRuntime;
68
+ if (!runtime || typeof runtime.renderMarkdown !== 'function') {
69
+ document.addEventListener('pandorga:runtime', bootHomePaint, { once: true });
70
+ return;
71
+ }
68
72
  var pre = window.__pandorgaPreload || {};
69
73
  function paint(id, path) {
70
74
  var el = document.getElementById(id);
@@ -95,11 +99,14 @@ layout: shell
95
99
 
96
100
  <script>
97
101
  {% raw %}
98
- (function () {
102
+ (function bootHomeBands() {
99
103
  'use strict';
100
104
 
101
105
  var runtime = window.ContentRuntime;
102
- if (!runtime) return;
106
+ if (!runtime || typeof runtime.loadJson !== 'function') {
107
+ document.addEventListener('pandorga:runtime', bootHomeBands, { once: true });
108
+ return;
109
+ }
103
110
 
104
111
  function asItem(entry) {
105
112
  return {
@@ -59,11 +59,14 @@ layout: shell
59
59
  </main>
60
60
 
61
61
  <script>
62
- (function () {
62
+ (function bootProjects() {
63
63
  'use strict';
64
64
 
65
65
  var runtime = window.ContentRuntime;
66
- if (!runtime) return;
66
+ if (!runtime || typeof runtime.loadJson !== 'function') {
67
+ document.addEventListener('pandorga:runtime', bootProjects, { once: true });
68
+ return;
69
+ }
67
70
 
68
71
  var esc = runtime.escapeHtml;
69
72
  var escAttr = runtime.escapeAttr;
@@ -34,11 +34,14 @@ layout: shell
34
34
  </main>
35
35
 
36
36
  <script>
37
- (function () {
37
+ (function bootResources() {
38
38
  'use strict';
39
39
 
40
40
  var runtime = window.ContentRuntime;
41
- if (!runtime || !runtime.ListingFilter) return;
41
+ if (!runtime || typeof runtime.loadJson !== 'function') {
42
+ document.addEventListener('pandorga:runtime', bootResources, { once: true });
43
+ return;
44
+ }
42
45
 
43
46
  var configRoot = document.querySelector('[data-listing-collection]');
44
47
  var collection = (configRoot && configRoot.getAttribute('data-listing-collection')) || 'resources';
data/_layouts/shell.html CHANGED
@@ -62,7 +62,7 @@
62
62
  Preconnect only. The preload script below is parser-blocking. A
63
63
  stylesheet before that script holds the parser until the font host
64
64
  answers, so the ledger sheets and the hero cannot paint. The text-face
65
- loader sits with the local sheets and adds its stylesheet after load.
65
+ loader sits with the local sheets.
66
66
  {%- endcomment -%}
67
67
  <link href="https://fonts.googleapis.com" rel="preconnect" />
68
68
  <link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect" />
@@ -85,7 +85,12 @@
85
85
  {%- assign _ledger = "writing|detail" | split: "|" -%}
86
86
  {%- assign _article_css = true -%}
87
87
  {%- when "blog" -%}
88
- {%- assign _ledger = "rascunhos|detail" | split: "|" -%}
88
+ {%- comment -%}
89
+ writing.css holds the rail, and it has to follow rascunhos.css. The
90
+ body rule in rascunhos.css is `display: block`, which would put the
91
+ licence back above the entries if it won the cascade.
92
+ {%- endcomment -%}
93
+ {%- assign _ledger = "rascunhos|writing|detail" | split: "|" -%}
89
94
  {%- assign _article_css = true -%}
90
95
  {%- when "cv" -%}
91
96
  {%- assign _ledger = "cv|detail" | split: "|" -%}
@@ -117,20 +122,65 @@
117
122
  {%- endfor -%}
118
123
  {% include theme/font-loader.html %}
119
124
  {%- comment -%}
120
- Icons are not first paint. The face is about a megabyte. A link in the
121
- head, even with print media, is discovered immediately and the simulator
122
- then waits on that face for the largest paint. Add the sheet after load.
125
+ Named icons only. The full variable face is about a megabyte, and a
126
+ late request for it becomes the largest paint on a real hero. Nav icons
127
+ from the registry join the shell set.
123
128
  {%- endcomment -%}
124
- {%- assign _symbols_href = "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" -%}
129
+ {%- assign _icon_names = "arrow_left_alt,arrow_right_alt,architecture,article,chat,chevron_left,chevron_right,close,code,dark_mode,expand_more,filter_list,format_quote,format_size,info,light_mode,menu,picture_as_pdf,public,routine,search,share,shuffle,sync" -%}
130
+ {%- for _group in site.data.nav -%}
131
+ {%- for _item in _group.pages -%}
132
+ {%- if _item.icon -%}
133
+ {%- assign _icon_hay = "," | append: _icon_names | append: "," -%}
134
+ {%- assign _icon_needle = "," | append: _item.icon | append: "," -%}
135
+ {%- unless _icon_hay contains _icon_needle -%}
136
+ {%- assign _icon_names = _icon_names | append: "," | append: _item.icon -%}
137
+ {%- endunless -%}
138
+ {%- endif -%}
139
+ {%- endfor -%}
140
+ {%- endfor -%}
141
+ {%- assign _symbols_href = "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&icon_names=" | append: _icon_names | append: "&display=swap" -%}
125
142
  <script>
126
- window.addEventListener('load', function () {
127
- var link = document.createElement('link');
128
- link.rel = 'stylesheet';
129
- link.href = {{ _symbols_href | jsonify }};
130
- document.head.appendChild(link);
131
- });
143
+ window.__pandorgaIconsHref = {{ _symbols_href | jsonify }};
144
+ </script>
145
+ <noscript><link id="pandorga-icons" rel="stylesheet" href="{{ _symbols_href }}" /></noscript>
146
+ <script>
147
+ (function () {
148
+ var known = {{ _icon_names | split: "," | jsonify }};
149
+ var seen = {};
150
+ known.forEach(function (name) { seen[name] = true; });
151
+ var dirty = false;
152
+ var timer = 0;
153
+ window.__pandorgaFlushIcons = function () {
154
+ var link = document.getElementById('pandorga-icons');
155
+ if (!link || !dirty) return;
156
+ dirty = false;
157
+ link.href = 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&icon_names=' + known.join(',') + '&display=swap';
158
+ };
159
+ function consider(root) {
160
+ if (!root || !root.querySelectorAll) return;
161
+ var nodes = root.querySelectorAll('.material-symbols-outlined');
162
+ var extra = false;
163
+ for (var i = 0; i < nodes.length; i++) {
164
+ var name = (nodes[i].textContent || '').trim();
165
+ if (!name || seen[name] || !/^[a-z0-9_]+$/.test(name)) continue;
166
+ seen[name] = true;
167
+ known.push(name);
168
+ extra = true;
169
+ }
170
+ if (!extra) return;
171
+ dirty = true;
172
+ clearTimeout(timer);
173
+ timer = setTimeout(window.__pandorgaFlushIcons, 50);
174
+ }
175
+ function start() {
176
+ consider(document);
177
+ var obs = new MutationObserver(function () { consider(document); });
178
+ obs.observe(document.body, { childList: true, subtree: true, characterData: true });
179
+ }
180
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
181
+ else start();
182
+ })();
132
183
  </script>
133
- <noscript><link rel="stylesheet" href="{{ _symbols_href }}" /></noscript>
134
184
  {%- assign _flags_href = "https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.2.3/css/flag-icons.min.css" -%}
135
185
  <script>
136
186
  window.addEventListener('load', function () {
@@ -265,6 +315,28 @@
265
315
  {% include page/site-footer.html nav_active=nav_active %}
266
316
 
267
317
  {% include page/content-runtime.html %}
318
+ <script>
319
+ (function () {
320
+ var fonts = window.__pandorgaFontsHref;
321
+ var icons = window.__pandorgaIconsHref;
322
+ if (!fonts && !icons) return;
323
+ requestAnimationFrame(function () {
324
+ requestAnimationFrame(function () {
325
+ function sheet(href, id) {
326
+ if (!href) return;
327
+ var link = document.createElement('link');
328
+ link.rel = 'stylesheet';
329
+ link.href = href;
330
+ if (id) link.id = id;
331
+ document.head.appendChild(link);
332
+ }
333
+ sheet(fonts);
334
+ sheet(icons, 'pandorga-icons');
335
+ if (window.__pandorgaFlushIcons) window.__pandorgaFlushIcons();
336
+ });
337
+ });
338
+ })();
339
+ </script>
268
340
 
269
341
  </body>
270
342
 
@@ -740,6 +740,26 @@ html[data-theme="light"] .home-hero-plate {
740
740
  line-height: var(--home-featured-title-line);
741
741
  }
742
742
 
743
+ /* Articles band only. Two points over the size the title already uses,
744
+ the same point step as the phone hero name. The section heading is
745
+ untouched. Listed cards in other bands are untouched. */
746
+ [data-home-kind="articles"] .home-featured-title {
747
+ font-size: calc(var(--home-featured-title-size) + 2pt);
748
+ }
749
+
750
+ [data-home-kind="articles"] .home-card-grid .card-title,
751
+ [data-home-kind="articles"] .home-card-grid .ledger-card-title {
752
+ font-size: calc(var(--type-card) + 2pt);
753
+ }
754
+
755
+ /* Phone only: the featured card's text sat against the top rule. One
756
+ step more than the shared top padding, and only on this card. */
757
+ @media (max-width: 767px) {
758
+ [data-home-kind="articles"] .home-featured {
759
+ padding-top: calc(var(--space-md) + 0.75rem);
760
+ }
761
+ }
762
+
743
763
  /* Home article / rascunho / project cards — content-title voice, not display. */
744
764
  .home-card-grid .ledger-card-title,
745
765
  .home-card-grid .card-title,
@@ -66,8 +66,46 @@
66
66
  display: block;
67
67
  }
68
68
 
69
- /* `.hidden` swaps the listing for the detail view; it has to beat the grid. */
70
- .rascunhos-body.hidden {
69
+ /* Below 1024px the rail is a disclosure above the ledger, and the licence
70
+ is a child of that rail, so source order paints it above the texts.
71
+ `writing.css` already sets this column, but the per-page split dropped
72
+ that sheet from the blog layout, and this `display: block` beats the
73
+ flex rule whenever rascunhos.css is the later file. The selector here is
74
+ more specific than that block display, so the licence stays under the
75
+ entries even if the sheet list changes again. */
76
+ @media (max-width: 1023px) {
77
+ .rascunhos-main .rascunhos-body {
78
+ display: flex;
79
+ flex-direction: column;
80
+ }
81
+
82
+ .rascunhos-main .rascunhos-body>.writing-rail:not([hidden]) {
83
+ display: contents;
84
+ }
85
+
86
+ .rascunhos-main .rascunhos-body>.rascunhos-ledger {
87
+ order: 1;
88
+ }
89
+
90
+ .rascunhos-main .writing-rail-licence {
91
+ order: 2;
92
+ margin-top: var(--space-xl);
93
+ padding-top: var(--space-lg);
94
+ border-top: 1px solid var(--border-gentle-subtle);
95
+ }
96
+
97
+ .rascunhos-main .writing-rail-panel {
98
+ margin-bottom: var(--space-xl);
99
+ }
100
+
101
+ .rascunhos-main .writing-rail:has(> .writing-rail-panel[hidden]) .writing-rail-trigger {
102
+ margin-bottom: var(--space-xl);
103
+ }
104
+ }
105
+
106
+ /* `.hidden` swaps the listing for the detail view; it has to beat the
107
+ mobile flex column above, which is why the page class is on the selector. */
108
+ .rascunhos-main .rascunhos-body.hidden {
71
109
  display: none;
72
110
  }
73
111
 
Binary file
Binary file
Binary file
@@ -0,0 +1,23 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Site">
2
+ <title>Site</title>
3
+ <style>
4
+ /* Light plate: pastel triad on the dark frame. Dark plate: production triad
5
+ on the light frame. The mark stays inside the maskable safe zone. */
6
+ .primary { fill: #a4c8da; }
7
+ .secondary { fill: #95c8a6; }
8
+ .tertiary { fill: #ddd8a8; }
9
+ .plate { fill: #5e5d59; }
10
+ @media (prefers-color-scheme: dark) {
11
+ .primary { fill: #1c5a7a; }
12
+ .secondary { fill: #287445; }
13
+ .tertiary { fill: #746f30; }
14
+ .plate { fill: #b8b7ae; }
15
+ }
16
+ </style>
17
+ <rect class="plate" width="32" height="32"/>
18
+ <g transform="translate(7.68 7.68) scale(0.52)">
19
+ <polygon class="primary" points="0,0 23,0 15,17 0,20"/>
20
+ <polygon class="secondary" points="23,0 32,0 32,32 17,21 15,17"/>
21
+ <polygon class="tertiary" points="0,20 15,17 17,21 32,32 0,32"/>
22
+ </g>
23
+ </svg>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,6 +1,26 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-hidden="true" focusable="false">
2
- <polygon fill="#a4c8da" points="16,1 24,5 13,14 2,17 2,9"/>
3
- <polygon fill="#95c8a6" points="24,5 30,9 30,23 22,28 19,18 13,14"/>
4
- <polygon fill="#ddd8a8" points="2,17 13,14 19,18 22,28 16,31 2,23"/>
5
- <polygon fill="none" stroke="#5e5d59" stroke-width="1" stroke-linejoin="miter" points="16,1 30,9 30,23 16,31 2,23 2,9"/>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Studio">
2
+ <title>Studio</title>
3
+ <style>
4
+ /* Same stamp and plate as the site icon. The small badge is the only
5
+ difference. It stays inside the maskable safe zone. */
6
+ .primary { fill: #a4c8da; }
7
+ .secondary { fill: #95c8a6; }
8
+ .tertiary { fill: #ddd8a8; }
9
+ .plate { fill: #5e5d59; }
10
+ .badge { fill: #b8b7ae; }
11
+ @media (prefers-color-scheme: dark) {
12
+ .primary { fill: #1c5a7a; }
13
+ .secondary { fill: #287445; }
14
+ .tertiary { fill: #746f30; }
15
+ .plate { fill: #b8b7ae; }
16
+ .badge { fill: #5e5d59; }
17
+ }
18
+ </style>
19
+ <rect class="plate" width="32" height="32"/>
20
+ <g transform="translate(7.68 7.68) scale(0.52)">
21
+ <polygon class="primary" points="0,0 23,0 15,17 0,20"/>
22
+ <polygon class="secondary" points="23,0 32,0 32,32 17,21 15,17"/>
23
+ <polygon class="tertiary" points="0,20 15,17 17,21 32,32 0,32"/>
24
+ </g>
25
+ <rect class="badge" x="20.32" y="20.32" width="2.88" height="2.88"/>
6
26
  </svg>
@@ -1 +1 @@
1
- Ada Example designs publishing frames. This summary is fictional.
1
+ Ada Example designs publishing frames. This summary is fictional. The home hero includes it as HTML, so the largest paint is this paragraph, the same element a real site measures.