pandorga 1.2.1 → 1.2.2

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: 966067aa7d06a13ff1a2058ec9b74559b4ddeae2768f405f5d6a76db552ecf52
4
- data.tar.gz: d1cf6e47e30fd5af662bf0bd7ae8e832b1c33416062c59732fc68c5ce095ada9
3
+ metadata.gz: 4788fb732dc3564b624c97cbba44c8a3377f89b636efcdec54a48f6873c95fb8
4
+ data.tar.gz: edd571d9c4cb6a9e1933130dda3e122175fed1f374349b80f8c8c419340c5125
5
5
  SHA512:
6
- metadata.gz: f85528387ad6ea2e6aea2b68a00da07df3aeddadfe82ae1c4cc84236d258b533a2ffef224f88a41c1971456ee7c251d42a934e4a380d66a064c0b671914ecb01
7
- data.tar.gz: f5c1a056cd9cbe678d5e082c8744f720aff0cef2418003800da7dee2ce58210765fa7fa613ef0f0962b8dea1cee1ba5180eb4a4a8f8ca4faa41baca2a582c326
6
+ metadata.gz: b22151e16f5933b3312fcb097716c607f0eea6b711c4cc441dbb4090645d905ac526438802fe7c1f3721f53604e915deeea99087e49a043e2b7fddd39d702885
7
+ data.tar.gz: 97fba0fdb7c83daf494d42b46f11b536fda6c7d84c8b99aa256e1d056b88326bdb970bafa174c4562ad31faeb8237192054758ffd401d08b205df71787cd08a0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.2] — 2026-10-05
4
+
5
+ ### Added
6
+
7
+ - Blog listing chrome follows `pages[].language` (`en` default; `pt-BR` for Portuguese UI strings)
8
+
9
+ ### Changed
10
+
11
+ - Projects index cards are denser (shorter posters, tighter type) on the uniform Nx3 grid
12
+ - Home draft cards are taller (~40%) so excerpts stay readable
13
+ - Full example blog page is `Rascunhos` with `language: pt-BR`
14
+
15
+ ### Removed
16
+
17
+ - Mobile nav no longer shows a page `badge` note (language belongs in `language`, not a sidebar label)
18
+
3
19
  ## [1.2.1] — 2026-10-05
4
20
 
5
21
  ### Added
@@ -83,3 +83,118 @@ cited_by:
83
83
  ptbr: "Citado por"
84
84
  frfr: "Cité par"
85
85
  eses: "Citado por"
86
+ filter:
87
+ enus: "Filter"
88
+ ptbr: "Filtrar"
89
+ frfr: "Filtrer"
90
+ eses: "Filtrar"
91
+ keyword:
92
+ enus: "Keyword"
93
+ ptbr: "Palavra-chave"
94
+ frfr: "Mot-clé"
95
+ eses: "Palabra clave"
96
+ filter_by_keyword:
97
+ enus: "Filter entries by keyword"
98
+ ptbr: "Filtrar entradas por palavra-chave"
99
+ frfr: "Filtrer les entrées par mot-clé"
100
+ eses: "Filtrar entradas por palabra clave"
101
+ keyword_placeholder:
102
+ enus: "Title, summary, tag…"
103
+ ptbr: "Título, resumo, tag…"
104
+ frfr: "Titre, résumé, tag…"
105
+ eses: "Título, resumen, etiqueta…"
106
+ loading_entries:
107
+ enus: "Loading entries..."
108
+ ptbr: "Carregando entradas..."
109
+ frfr: "Chargement des entrées…"
110
+ eses: "Cargando entradas…"
111
+ nothing_matches_filters:
112
+ enus: "Nothing matches these filters yet."
113
+ ptbr: "Nada corresponde a estes filtros ainda."
114
+ frfr: "Rien ne correspond encore à ces filtres."
115
+ eses: "Nada coincide con estos filtros todavía."
116
+ order_date_written:
117
+ enus: "Order: date written"
118
+ ptbr: "Ordem: data de escrita"
119
+ frfr: "Ordre : date d'écriture"
120
+ eses: "Orden: fecha de escritura"
121
+ order_last_revised:
122
+ enus: "Order: last revised"
123
+ ptbr: "Ordem: última revisão"
124
+ frfr: "Ordre : dernière révision"
125
+ eses: "Orden: última revisión"
126
+ order_random:
127
+ enus: "Order: random"
128
+ ptbr: "Ordem: aleatória"
129
+ frfr: "Ordre : aléatoire"
130
+ eses: "Orden: aleatorio"
131
+ form_label:
132
+ enus: "Form"
133
+ ptbr: "Forma"
134
+ frfr: "Forme"
135
+ eses: "Forma"
136
+ order_by:
137
+ enus: "Order by"
138
+ ptbr: "Ordenar por"
139
+ frfr: "Trier par"
140
+ eses: "Ordenar por"
141
+ all_label:
142
+ enus: "All"
143
+ ptbr: "Todas"
144
+ frfr: "Toutes"
145
+ eses: "Todas"
146
+ date_written:
147
+ enus: "Date written"
148
+ ptbr: "Data de escrita"
149
+ frfr: "Date d'écriture"
150
+ eses: "Fecha de escritura"
151
+ last_revised:
152
+ enus: "Last revised"
153
+ ptbr: "Última revisão"
154
+ frfr: "Dernière révision"
155
+ eses: "Última revisión"
156
+ revised_prefix:
157
+ enus: "Revised"
158
+ ptbr: "Revisado"
159
+ frfr: "Révisé"
160
+ eses: "Revisado"
161
+ entry_singular:
162
+ enus: "entry"
163
+ ptbr: "entrada"
164
+ frfr: "entrée"
165
+ eses: "entrada"
166
+ entry_plural:
167
+ enus: "entries"
168
+ ptbr: "entradas"
169
+ frfr: "entrées"
170
+ eses: "entradas"
171
+ of_total:
172
+ enus: "of"
173
+ ptbr: "de"
174
+ frfr: "sur"
175
+ eses: "de"
176
+ licensing_reuse:
177
+ enus: "Licensing & reuse"
178
+ ptbr: "Licença e reutilização"
179
+ frfr: "Licence et réutilisation"
180
+ eses: "Licencia y reutilización"
181
+ entries_published_under:
182
+ enus: "Entries published under"
183
+ ptbr: "Entradas publicadas sob"
184
+ frfr: "Entrées publiées sous"
185
+ eses: "Entradas publicadas bajo"
186
+ attribution_to:
187
+ enus: "Attribution to"
188
+ ptbr: "Atribuição a"
189
+ frfr: "Attribution à"
190
+ eses: "Atribución a"
191
+ licence_terms:
192
+ enus: "Licence terms"
193
+ ptbr: "Termos da licença"
194
+ frfr: "Conditions de licence"
195
+ eses: "Términos de la licencia"
196
+ post_load_error:
197
+ enus: "This post could not be loaded right now."
198
+ ptbr: "Este rascunho não pôde ser carregado agora."
199
+ frfr: "Ce billet n'a pas pu être chargé pour le moment."
200
+ eses: "Esta nota no se pudo cargar ahora."
data/_layouts/blog.html CHANGED
@@ -3,6 +3,12 @@ layout: shell
3
3
  ---
4
4
  {%- assign blog_locale = page.lang | default: site.pandorga.identity.locale | default: "en" -%}
5
5
  {%- assign blog_title = page.title | default: "Blog" -%}
6
+ {%- assign blog_ui = "enus" -%}
7
+ {%- assign _blog_lang = blog_locale | downcase | replace: "_", "-" -%}
8
+ {%- if _blog_lang == "pt-br" or _blog_lang == "ptbr" or _blog_lang == "pt" -%}
9
+ {%- assign blog_ui = "ptbr" -%}
10
+ {%- endif -%}
11
+ {%- assign t = site.data.translations -%}
6
12
 
7
13
  <main class="relative max-w-7xl mx-auto px-4 sm:px-8 md:px-16 pt-10 pb-16 rascunhos-main{% if page.accent and page.accent != 'blue' %} page-accent--{{ page.accent }}{% endif %}"
8
14
  {% include page/listing-runtime-attrs.html
@@ -23,23 +29,23 @@ layout: shell
23
29
  <div id="blog-listing" class="rascunhos-body">
24
30
  {%- comment -%}
25
31
  Same sidebar pattern as the articles listing: keyword box, facets, and
26
- licence. Platform UI strings are English defaults; dates follow
27
- site.pandorga.identity.locale (or page.lang).
32
+ licence. Chrome follows page.language (English default; pt-BR for
33
+ rascunhos). Dates use the same locale.
28
34
  {%- endcomment -%}
29
35
  <aside class="writing-rail listing-filter" data-listing-filter-root hidden>
30
36
  <button type="button" class="writing-rail-trigger listing-filter-trigger"
31
37
  data-listing-filter-trigger aria-expanded="false"
32
38
  aria-controls="rascunhos-filter-panel">
33
39
  <span class="material-symbols-outlined" aria-hidden="true">filter_list</span>
34
- <span class="font-label writing-rail-trigger-label">Filter</span>
40
+ <span class="font-label writing-rail-trigger-label">{{ t.filter[blog_ui] }}</span>
35
41
  </button>
36
42
 
37
43
  <div id="rascunhos-filter-panel" class="writing-rail-panel" data-listing-filter-panel hidden>
38
- <p class="listing-filter-group-label font-label text-on-surface-variant">Keyword</p>
44
+ <p class="listing-filter-group-label font-label text-on-surface-variant">{{ t.keyword[blog_ui] }}</p>
39
45
  <label class="writing-rail-search">
40
- <span class="sr-only">Filter entries by keyword</span>
46
+ <span class="sr-only">{{ t.filter_by_keyword[blog_ui] }}</span>
41
47
  <input type="search" class="listing-filter-search writing-rail-input font-body"
42
- data-listing-filter-q placeholder="Title, summary, tag…" autocomplete="off">
48
+ data-listing-filter-q placeholder="{{ t.keyword_placeholder[blog_ui] }}" autocomplete="off">
43
49
  <span class="material-symbols-outlined writing-rail-search-icon" aria-hidden="true">search</span>
44
50
  </label>
45
51
  <div data-listing-filter-chips></div>
@@ -65,10 +71,10 @@ layout: shell
65
71
 
66
72
  <div class="rascunhos-entries" id="posts-list">
67
73
  <section id="empty-state" class="rascunhos-state">
68
- <p class="font-body text-on-surface-variant italic">Loading entries...</p>
74
+ <p class="font-body text-on-surface-variant italic">{{ t.loading_entries[blog_ui] }}</p>
69
75
  </section>
70
76
  <section id="no-results" class="rascunhos-state hidden">
71
- <p class="font-body text-on-surface-variant italic">Nothing matches these filters yet.</p>
77
+ <p class="font-body text-on-surface-variant italic">{{ t.nothing_matches_filters[blog_ui] }}</p>
72
78
  </section>
73
79
  </div>
74
80
 
@@ -125,6 +131,11 @@ layout: shell
125
131
  var filterUi = null;
126
132
  var dateLocale = '{{ blog_locale }}';
127
133
  if (dateLocale === 'ptbr' || dateLocale === 'pt_BR') dateLocale = 'pt-BR';
134
+ var uiLang = '{{ blog_ui }}';
135
+
136
+ function t(key) {
137
+ return runtime.translate(key, uiLang) || key;
138
+ }
128
139
 
129
140
  /* Folio number by slug. Assigned once over the whole log, oldest first, so
130
141
  * an entry keeps the number it was written under no matter which filter,
@@ -133,9 +144,9 @@ layout: shell
133
144
  var folioBySlug = Object.create(null);
134
145
 
135
146
  var SORT_LABELS = {
136
- published: 'Order: date written',
137
- updated: 'Order: last revised',
138
- random: 'Order: random'
147
+ published: t('order_date_written'),
148
+ updated: t('order_last_revised'),
149
+ random: t('order_random')
139
150
  };
140
151
 
141
152
  var MONTH_FORMAT = new Intl.DateTimeFormat(dateLocale, {
@@ -201,18 +212,18 @@ layout: shell
201
212
  return [
202
213
  {
203
214
  key: 'tag',
204
- label: 'Form',
205
- options: [{ value: null, label: 'All (' + allPosts.length + ')' }].concat(tags.map(function (tag) {
215
+ label: t('form_label'),
216
+ options: [{ value: null, label: t('all_label') + ' (' + allPosts.length + ')' }].concat(tags.map(function (tag) {
206
217
  return { value: tag, label: tag + ' (' + tagCounts[tag] + ')' };
207
218
  }))
208
219
  },
209
220
  {
210
221
  key: 'sort',
211
- label: 'Order by',
222
+ label: t('order_by'),
212
223
  options: [
213
- { value: 'published', label: 'Date written' },
214
- { value: 'updated', label: 'Last revised' },
215
- { value: 'random', label: 'Random' }
224
+ { value: 'published', label: t('date_written') },
225
+ { value: 'updated', label: t('last_revised') },
226
+ { value: 'random', label: t('random_text') }
216
227
  ]
217
228
  }
218
229
  ];
@@ -226,10 +237,10 @@ layout: shell
226
237
  if (orderEl) orderEl.textContent = SORT_LABELS[state.sort] || SORT_LABELS.published;
227
238
  if (!tallyEl) return;
228
239
  var total = allPosts.length;
229
- var word = total === 1 ? 'entry' : 'entries';
240
+ var word = total === 1 ? t('entry_singular') : t('entry_plural');
230
241
  tallyEl.textContent = shown === total
231
242
  ? total + ' ' + word
232
- : shown + ' of ' + total + ' ' + word;
243
+ : shown + ' ' + t('of_total') + ' ' + total + ' ' + word;
233
244
  }
234
245
 
235
246
  /* ── Notebook furniture ──────────────────────────────────────────────
@@ -287,7 +298,7 @@ layout: shell
287
298
  var folio = folioBySlug[post.slug];
288
299
  var updated = postUpdated(post);
289
300
  var revised = updated && updated.slice(0, 10) !== String(fm.date || '').slice(0, 10)
290
- ? '<span class="rascunhos-revision">Revised ' +
301
+ ? '<span class="rascunhos-revision">' + runtime.escapeHtml(t('revised_prefix')) + ' ' +
291
302
  runtime.escapeHtml(runtime.formatDate(updated, dateLocale)) + '</span>'
292
303
  : '';
293
304
 
@@ -488,16 +499,19 @@ layout: shell
488
499
  var licence = manifest && manifest.content_license;
489
500
  if (!licence || !licence.name) return;
490
501
 
491
- var parts = ['<span class="writing-rail-licence-label font-label">Licensing &amp; reuse</span>'];
502
+ var parts = ['<span class="writing-rail-licence-label font-label">' +
503
+ runtime.escapeHtml(t('licensing_reuse')) + '</span>'];
492
504
  var named = licence.id ? licence.name + ' (' + licence.id + ')' : licence.name;
493
- parts.push('<p class="writing-rail-licence-copy font-body">Entries published under ' +
505
+ parts.push('<p class="writing-rail-licence-copy font-body">' +
506
+ runtime.escapeHtml(t('entries_published_under')) + ' ' +
494
507
  runtime.escapeHtml(named) + '.</p>');
495
508
  if (licence.rights_holder) {
496
- parts.push('<p class="writing-rail-licence-copy font-body">Attribution to ' +
509
+ parts.push('<p class="writing-rail-licence-copy font-body">' +
510
+ runtime.escapeHtml(t('attribution_to')) + ' ' +
497
511
  runtime.escapeHtml(licence.rights_holder) + '.</p>');
498
512
  }
499
513
  var href = licence.page_path || licence.url;
500
- if (href) parts.push(runtime.renderCtaLink(href, 'Licence terms'));
514
+ if (href) parts.push(runtime.renderCtaLink(href, t('licence_terms')));
501
515
 
502
516
  licenceEl.innerHTML = parts.join('');
503
517
  licenceEl.hidden = false;
@@ -526,7 +540,7 @@ layout: shell
526
540
  emptyState: emptyState,
527
541
  grid: grid,
528
542
  noResults: noResults,
529
- message: 'This post could not be loaded right now.'
543
+ message: t('post_load_error')
530
544
  });
531
545
  });
532
546
  }
data/_layouts/shell.html CHANGED
@@ -150,9 +150,7 @@
150
150
  <a href="{{ _p.url }}" role="menuitem"
151
151
  class="flex items-center gap-2.5 font-body {% if nav_active == _key %}text-primary font-semibold{% else %}text-on-surface-variant hover:text-primary{% endif %} transition-colors duration-300 py-2 px-1">
152
152
  {% if _item.icon %}<span class="material-symbols-outlined text-lg shrink-0 flex items-center justify-center" aria-hidden="true">{{ _item.icon }}</span>{% endif %}
153
- <span class="inline-flex items-center gap-1.5 leading-none">{{
154
- _p.title }}{% if _p.badge %}<span class="marginalia font-label text-tertiary">{{ _p.badge }}</span>{% endif
155
- %}</span>
153
+ <span class="leading-none">{{ _p.title }}</span>
156
154
  </a>
157
155
  {% endfor %}
158
156
  {% endfor %}
@@ -845,6 +845,12 @@ html[data-theme="light"] .home-hero-plate {
845
845
  border-color var(--motion-duration-fast) var(--motion-ease-default);
846
846
  }
847
847
 
848
+ /* ~40% taller poster than the shared 16/9 plate so the dropped-sheet body
849
+ keeps room for the excerpt (four-up columns were clipping copy to a sliver). */
850
+ .home-draft-card .ledger-poster {
851
+ aspect-ratio: 16 / 12.6;
852
+ }
853
+
848
854
  /* A handful of fixed degrees per grid position — not a JS random() — so the
849
855
  tilt is identical on every render and every screenshot. */
850
856
  .home-draft-list>*:nth-child(4n + 1) {
@@ -897,13 +903,15 @@ html[data-theme="light"] .home-hero-plate {
897
903
  `flex: 1` gives the paragraph a definite block size from the flex
898
904
  allocation, so line-clamp/overflow clip against that height instead of
899
905
  growing past the ellipsis (the old flex:1 + clamp:2 bug that left text
900
- after "…" and sliced mid-glyph at the card edge). */
906
+ after "…" and sliced mid-glyph at the card edge).
907
+ min-height ~40% above the old six-line band so short posters still leave
908
+ readable excerpt room. */
901
909
  flex: 1 1 auto;
902
- min-height: 0;
910
+ min-height: calc(var(--type-subtitle) * var(--leading-body) * 8.4);
903
911
  height: 0;
904
912
  display: -webkit-box;
905
- -webkit-line-clamp: 6;
906
- line-clamp: 6;
913
+ -webkit-line-clamp: 8;
914
+ line-clamp: 8;
907
915
  -webkit-box-orient: vertical;
908
916
  overflow: hidden;
909
917
  }
@@ -86,7 +86,8 @@
86
86
  }
87
87
 
88
88
  .projects-page .projects-plate--mark {
89
- aspect-ratio: 16 / 9;
89
+ /* Match the compact index poster — shorter than the shared 16/9 plate. */
90
+ aspect-ratio: 2 / 1;
90
91
  display: flex;
91
92
  align-items: center;
92
93
  justify-content: center;
@@ -290,7 +291,7 @@
290
291
  .projects-page .projects-grid {
291
292
  display: grid;
292
293
  grid-template-columns: minmax(0, 1fr);
293
- gap: var(--space-lg);
294
+ gap: var(--space-md);
294
295
  /* Cards stretch to the row. This used to be `align-items: start`, because a
295
296
  project with no poster left a hole between its summary and its links; now
296
297
  every project shows either its thumbnail or its glyph plate, so the row
@@ -310,31 +311,45 @@
310
311
  }
311
312
  }
312
313
 
314
+ /* Compact index cells: denser padding and a shorter poster than the shared
315
+ 16/9 system-card default, so three columns read as a catalogue rather than
316
+ a wall of panels. The lead plate above keeps its own scale. */
317
+ .projects-page .projects-grid .ledger-system-card {
318
+ gap: var(--space-xs);
319
+ padding: var(--space-md);
320
+ }
321
+
313
322
  /* The shared system card pads its own body, which insets the poster from the
314
323
  card edge and makes the picture look like an afterthought. Pulling it back
315
324
  out by the same padding is one rule and leaves every other spacing in the
316
325
  shared block alone. */
317
- .projects-page .ledger-system-card>.ledger-poster,
318
- .projects-page .ledger-system-card>.projects-plate {
326
+ .projects-page .projects-grid .ledger-system-card>.ledger-poster,
327
+ .projects-page .projects-grid .ledger-system-card>.projects-plate {
319
328
  width: auto;
320
- margin: calc(var(--space-lg) * -1) calc(var(--space-lg) * -1) 0;
329
+ margin: calc(var(--space-md) * -1) calc(var(--space-md) * -1) 0;
330
+ aspect-ratio: 2 / 1;
321
331
  }
322
332
 
323
333
  /* The glyph plate is this page's own shape, so it takes the same breathing
324
334
  room the unscoped poster rule above gives every system card. */
325
335
  .projects-page .projects-plate+.ledger-meta {
326
- margin-top: var(--space-md);
336
+ margin-top: var(--space-sm);
327
337
  }
328
338
 
329
- .projects-page .ledger-system-card .ledger-card-title {
330
- font-size: var(--type-card-lg);
331
- line-height: 1.15;
339
+ .projects-page .projects-grid .ledger-system-card .ledger-card-title {
340
+ font-size: var(--type-card);
341
+ line-height: 1.2;
332
342
  }
333
343
 
334
- .projects-page .ledger-system-card .ledger-card-copy {
344
+ .projects-page .projects-grid .ledger-system-card .ledger-card-copy {
335
345
  font-size: var(--type-subtitle);
336
346
  line-height: var(--leading-body);
337
347
  color: var(--color-legend);
348
+ display: -webkit-box;
349
+ -webkit-box-orient: vertical;
350
+ -webkit-line-clamp: 2;
351
+ line-clamp: 2;
352
+ overflow: hidden;
338
353
  }
339
354
 
340
355
  /* ══ Repositories ══
@@ -54,11 +54,11 @@ pandorga:
54
54
  home: { featured: true, limit: 3 }
55
55
  - key: blog
56
56
  template: blog
57
- title: Drafts
57
+ title: Rascunhos
58
58
  collection: posts
59
59
  path: /pages/blog/
60
60
  detail: /posts/:slug/
61
- language: en
61
+ language: pt-BR
62
62
  accent: ochre
63
63
  nav: { group: Writing, icon: stylus_note }
64
64
  home: { limit: 4 }
@@ -3,9 +3,9 @@ articles:
3
3
  kicker: Writing
4
4
  note: Long-form essays from the full example.
5
5
  blog:
6
- title: Drafts
7
- kicker: Notebook
8
- note: Shorter notes and drafts.
6
+ title: Rascunhos
7
+ kicker: Caderno
8
+ note: Notas curtas e rascunhos.
9
9
  projects:
10
10
  title: Projects
11
11
  kicker: Systems
@@ -24,7 +24,7 @@ module Pandorga
24
24
 
25
25
  gem "pandorga", "~> 1.2"
26
26
  # Local checkout: gem "pandorga", path: "../pandorga"
27
- # GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v1.2.1"
27
+ # GitHub tag: gem "pandorga", github: "alexandrelheinen/pandorga", tag: "v1.2.2"
28
28
  gem "csv"
29
29
  gem "base64"
30
30
  RUBY
@@ -97,7 +97,7 @@ pages:
97
97
  language:
98
98
  type: string
99
99
  required: false
100
- notes: Listing / home band language
100
+ notes: "Listing chrome + dates (`en` default). Use `pt-BR` for Portuguese UI (e.g. blog / rascunhos)"
101
101
  home_attributes:
102
102
  title: "`home` attributes"
103
103
  entries:
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pandorga
4
- VERSION = "1.2.1"
4
+ VERSION = "1.2.2"
5
5
  end
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.2.1
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - The Pandorga contributors