@cogenta/theme-association 0.3.2 → 0.5.3

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 (148) hide show
  1. package/dist/src/index.d.ts +6 -0
  2. package/dist/src/index.d.ts.map +1 -1
  3. package/dist/src/index.js +9 -0
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/render/blocks/accordion.d.ts.map +1 -1
  6. package/dist/src/render/blocks/accordion.js +13 -10
  7. package/dist/src/render/blocks/accordion.js.map +1 -1
  8. package/dist/src/render/blocks/breakdown.d.ts +14 -0
  9. package/dist/src/render/blocks/breakdown.d.ts.map +1 -0
  10. package/dist/src/render/blocks/breakdown.js +17 -0
  11. package/dist/src/render/blocks/breakdown.js.map +1 -0
  12. package/dist/src/render/blocks/collection-list.d.ts +4 -10
  13. package/dist/src/render/blocks/collection-list.d.ts.map +1 -1
  14. package/dist/src/render/blocks/collection-list.js +89 -58
  15. package/dist/src/render/blocks/collection-list.js.map +1 -1
  16. package/dist/src/render/blocks/cta.d.ts +0 -5
  17. package/dist/src/render/blocks/cta.d.ts.map +1 -1
  18. package/dist/src/render/blocks/cta.js +22 -10
  19. package/dist/src/render/blocks/cta.js.map +1 -1
  20. package/dist/src/render/blocks/embed.d.ts.map +1 -1
  21. package/dist/src/render/blocks/embed.js +30 -22
  22. package/dist/src/render/blocks/embed.js.map +1 -1
  23. package/dist/src/render/blocks/faq.d.ts.map +1 -1
  24. package/dist/src/render/blocks/faq.js +11 -15
  25. package/dist/src/render/blocks/faq.js.map +1 -1
  26. package/dist/src/render/blocks/feature-grid.d.ts +6 -0
  27. package/dist/src/render/blocks/feature-grid.d.ts.map +1 -1
  28. package/dist/src/render/blocks/feature-grid.js +42 -20
  29. package/dist/src/render/blocks/feature-grid.js.map +1 -1
  30. package/dist/src/render/blocks/gallery.d.ts +9 -7
  31. package/dist/src/render/blocks/gallery.d.ts.map +1 -1
  32. package/dist/src/render/blocks/gallery.js +22 -16
  33. package/dist/src/render/blocks/gallery.js.map +1 -1
  34. package/dist/src/render/blocks/hero.d.ts +23 -9
  35. package/dist/src/render/blocks/hero.d.ts.map +1 -1
  36. package/dist/src/render/blocks/hero.js +40 -23
  37. package/dist/src/render/blocks/hero.js.map +1 -1
  38. package/dist/src/render/blocks/logo-strip.d.ts +6 -0
  39. package/dist/src/render/blocks/logo-strip.d.ts.map +1 -1
  40. package/dist/src/render/blocks/logo-strip.js +7 -9
  41. package/dist/src/render/blocks/logo-strip.js.map +1 -1
  42. package/dist/src/render/blocks/logos.d.ts +14 -0
  43. package/dist/src/render/blocks/logos.d.ts.map +1 -1
  44. package/dist/src/render/blocks/logos.js +30 -16
  45. package/dist/src/render/blocks/logos.js.map +1 -1
  46. package/dist/src/render/blocks/media-figure.d.ts +12 -5
  47. package/dist/src/render/blocks/media-figure.d.ts.map +1 -1
  48. package/dist/src/render/blocks/media-figure.js +32 -18
  49. package/dist/src/render/blocks/media-figure.js.map +1 -1
  50. package/dist/src/render/blocks/pricing-table.d.ts.map +1 -1
  51. package/dist/src/render/blocks/pricing-table.js +18 -20
  52. package/dist/src/render/blocks/pricing-table.js.map +1 -1
  53. package/dist/src/render/blocks/prose.d.ts +9 -3
  54. package/dist/src/render/blocks/prose.d.ts.map +1 -1
  55. package/dist/src/render/blocks/prose.js +11 -4
  56. package/dist/src/render/blocks/prose.js.map +1 -1
  57. package/dist/src/render/blocks/quote.d.ts +4 -7
  58. package/dist/src/render/blocks/quote.d.ts.map +1 -1
  59. package/dist/src/render/blocks/quote.js +12 -17
  60. package/dist/src/render/blocks/quote.js.map +1 -1
  61. package/dist/src/render/blocks/stat-counter.d.ts +8 -1
  62. package/dist/src/render/blocks/stat-counter.d.ts.map +1 -1
  63. package/dist/src/render/blocks/stat-counter.js +18 -10
  64. package/dist/src/render/blocks/stat-counter.js.map +1 -1
  65. package/dist/src/render/blocks/stats.d.ts +12 -1
  66. package/dist/src/render/blocks/stats.d.ts.map +1 -1
  67. package/dist/src/render/blocks/stats.js +24 -14
  68. package/dist/src/render/blocks/stats.js.map +1 -1
  69. package/dist/src/render/blocks/testimonial.d.ts +6 -5
  70. package/dist/src/render/blocks/testimonial.d.ts.map +1 -1
  71. package/dist/src/render/blocks/testimonial.js +13 -11
  72. package/dist/src/render/blocks/testimonial.js.map +1 -1
  73. package/dist/src/render/chrome.d.ts +38 -1
  74. package/dist/src/render/chrome.d.ts.map +1 -1
  75. package/dist/src/render/chrome.js +117 -91
  76. package/dist/src/render/chrome.js.map +1 -1
  77. package/dist/src/render/details.d.ts +79 -0
  78. package/dist/src/render/details.d.ts.map +1 -0
  79. package/dist/src/render/details.js +221 -0
  80. package/dist/src/render/details.js.map +1 -0
  81. package/dist/src/render/figures.d.ts +29 -0
  82. package/dist/src/render/figures.d.ts.map +1 -0
  83. package/dist/src/render/figures.js +59 -0
  84. package/dist/src/render/figures.js.map +1 -0
  85. package/dist/src/render/layout.d.ts +31 -0
  86. package/dist/src/render/layout.d.ts.map +1 -0
  87. package/dist/src/render/layout.js +46 -0
  88. package/dist/src/render/layout.js.map +1 -0
  89. package/dist/src/render/page-head.d.ts +7 -0
  90. package/dist/src/render/page-head.d.ts.map +1 -0
  91. package/dist/src/render/page-head.js +95 -0
  92. package/dist/src/render/page-head.js.map +1 -0
  93. package/dist/src/render/render-block.d.ts +8 -12
  94. package/dist/src/render/render-block.d.ts.map +1 -1
  95. package/dist/src/render/render-block.js +40 -29
  96. package/dist/src/render/render-block.js.map +1 -1
  97. package/dist/src/render/strings.d.ts +15 -0
  98. package/dist/src/render/strings.d.ts.map +1 -0
  99. package/dist/src/render/strings.js +39 -0
  100. package/dist/src/render/strings.js.map +1 -0
  101. package/dist/src/render/term-archive.d.ts.map +1 -1
  102. package/dist/src/render/term-archive.js +33 -25
  103. package/dist/src/render/term-archive.js.map +1 -1
  104. package/dist/src/render/widget-areas.d.ts +14 -0
  105. package/dist/src/render/widget-areas.d.ts.map +1 -0
  106. package/dist/src/render/widget-areas.js +21 -0
  107. package/dist/src/render/widget-areas.js.map +1 -0
  108. package/dist/theme.config.d.ts +1 -1
  109. package/dist/theme.config.js +3 -3
  110. package/dist/theme.config.js.map +1 -1
  111. package/package.json +5 -5
  112. package/src/index.ts +9 -0
  113. package/src/render/blocks/accordion.ts +28 -22
  114. package/src/render/blocks/breakdown.ts +42 -0
  115. package/src/render/blocks/collection-list.ts +234 -103
  116. package/src/render/blocks/cta.ts +48 -19
  117. package/src/render/blocks/embed.ts +47 -29
  118. package/src/render/blocks/faq.ts +22 -31
  119. package/src/render/blocks/feature-grid.ts +73 -41
  120. package/src/render/blocks/gallery.ts +32 -26
  121. package/src/render/blocks/hero.ts +56 -43
  122. package/src/render/blocks/logo-strip.ts +23 -21
  123. package/src/render/blocks/logos.ts +45 -40
  124. package/src/render/blocks/media-figure.ts +49 -24
  125. package/src/render/blocks/pricing-table.ts +31 -40
  126. package/src/render/blocks/prose.ts +16 -6
  127. package/src/render/blocks/quote.ts +34 -27
  128. package/src/render/blocks/stat-counter.ts +35 -29
  129. package/src/render/blocks/stats.ts +46 -38
  130. package/src/render/blocks/testimonial.ts +29 -20
  131. package/src/render/chrome.ts +165 -91
  132. package/src/render/details.ts +276 -0
  133. package/src/render/figures.ts +68 -0
  134. package/src/render/layout.ts +78 -0
  135. package/src/render/page-head.ts +193 -0
  136. package/src/render/render-block.ts +45 -32
  137. package/src/render/strings.ts +74 -0
  138. package/src/render/term-archive.ts +94 -58
  139. package/src/render/widget-areas.ts +24 -0
  140. package/src/styles/archive.css +38 -102
  141. package/src/styles/base.css +503 -554
  142. package/src/styles/blocks.css +913 -666
  143. package/src/styles/chrome.css +506 -0
  144. package/src/styles/listings.css +591 -0
  145. package/src/styles/theme.css +25 -19
  146. package/src/styles/tokens.css +284 -234
  147. package/src/styles/widgets.css +1059 -0
  148. package/tokens.json +18 -18
@@ -1,106 +1,132 @@
1
- import { escapeAttribute, escapeText, renderBrandMark, renderSocialLinks, renderThemeToggle, serialize, } from '@cogenta/theme-kit';
2
- /**
3
- * This theme's own header and footer — a warm, community-notice-board read
4
- * rather than a corporate one: a sticky header with a pill-shaped nav and a
5
- * rounded, filled action button, and a four-column footer (brand+tagline,
6
- * navigation, social, a short note) on a tinted band, closing with Cogenta's
7
- * own credit.
8
- *
9
- * The mobile nav is a `<details>`/`<summary>` disclosure — the same
10
- * zero-JavaScript mechanism `faq`/`accordion` already use — rather than a
11
- * scripted hamburger: expanding, keyboard operation and the open state
12
- * announced to assistive technology all come from the browser. The same
13
- * links are rendered twice (once inside the disclosure, once as a plain
14
- * inline row) and `src/styles/base.css` shows exactly one of the two at any
15
- * given width via `display:none` — which also removes the hidden copy from
16
- * the accessibility tree, so nothing is ever announced twice.
17
- *
18
- * `theme@1.4` (L25 D2) fields — `headerAction`, `tagline`, `social`,
19
- * `footerNote` — are each rendered only when present; a render with none of
20
- * them set still produces a complete header/footer, exactly the additive
21
- * guarantee the contract promises.
22
- *
23
- * The manual light/dark toggle (`renderThemeToggle`, `@cogenta/theme-kit`,
24
- * L26) is unconditional — it sits between the desktop nav and the header
25
- * action, outside the `<details>` mobile disclosure so it stays reachable at
26
- * every width rather than one tap deep inside the hamburger menu.
27
- */
28
- function navItems(links) {
29
- return links
30
- .filter((link) => link.href !== null || link.kind === 'submenu-placeholder')
31
- .map((link) => {
32
- const label = escapeText(link.label);
33
- const titleAttr = link.title === null ? '' : ` title="${escapeAttribute(link.title)}"`;
34
- if (link.href === null)
35
- return `<li><span${titleAttr}>${label}</span></li>`;
36
- const href = escapeAttribute(link.href);
37
- const target = link.openInNewTab ? ' target="_blank" rel="noopener"' : '';
38
- return `<li><a href="${href}"${target}${titleAttr}>${label}</a></li>`;
39
- })
40
- .join('');
1
+ import { escapeAttribute, escapeText, renderBrandMark, renderFooterWidgets, renderSocialLinks, renderThemeToggle, serialize, } from '@cogenta/theme-kit';
2
+ import { associationString } from './strings.js';
3
+ function usable(links) {
4
+ return links.filter((link) => link.href !== null || link.kind === 'submenu-placeholder');
41
5
  }
42
- function renderNav(links, className, label) {
43
- const items = navItems(links);
44
- if (items === '')
45
- return '';
46
- return `<nav class="${className}" aria-label="${escapeAttribute(label)}"><ul class="cg-nav__items">${items}</ul></nav>`;
6
+ function linkItem(link, className) {
7
+ const label = escapeText(link.label);
8
+ const titleAttr = link.title === null ? '' : ` title="${escapeAttribute(link.title)}"`;
9
+ if (link.href === null)
10
+ return `<li class="${className}"><span${titleAttr}>${label}</span></li>`;
11
+ const target = link.openInNewTab ? ' target="_blank" rel="noopener"' : '';
12
+ return `<li class="${className}"><a href="${escapeAttribute(link.href)}"${target}${titleAttr}>${label}</a></li>`;
13
+ }
14
+ /** The footer menu, split into columns at each unlinked heading. */
15
+ export function footerGroups(links) {
16
+ const groups = [];
17
+ for (const link of usable(links)) {
18
+ if (link.kind === 'submenu-placeholder' && link.href === null) {
19
+ groups.push({ heading: link.label, links: [] });
20
+ continue;
21
+ }
22
+ const current = groups[groups.length - 1];
23
+ if (current === undefined)
24
+ groups.push({ heading: null, links: [link] });
25
+ else
26
+ current.links.push(link);
27
+ }
28
+ return groups.filter((group) => group.links.length > 0);
47
29
  }
48
- /** The header's own call-to-action link (`theme@1.4`) — a filled, rounded button, the "Donate"/"Volunteer" treatment. */
49
- function renderHeaderAction(action) {
50
- if (action === undefined)
30
+ /** `footerNote` as paragraphs at blank lines, each keeping its own line breaks. */
31
+ function renderFooterNote(note) {
32
+ const paragraphs = note
33
+ .replace(/\r\n?/g, '\n')
34
+ .split(/\n\s*\n/)
35
+ .map((paragraph) => paragraph
36
+ .split('\n')
37
+ .map((line) => line.trim())
38
+ .filter((line) => line !== ''))
39
+ .filter((lines) => lines.length > 0);
40
+ if (paragraphs.length === 0)
51
41
  return '';
52
- return (`<a class="cg-action cg-site-header__action" data-emphasis="primary" ` +
53
- `href="${escapeAttribute(action.href)}">${escapeText(action.label)}</a>`);
42
+ return `<div class="ca-footer__note">${paragraphs
43
+ .map((lines) => `<p>${lines.map(noteLine).join('<br>')}</p>`)
44
+ .join('')}</div>`;
45
+ }
46
+ /**
47
+ * A line of the footer note that is nothing but an email address or a
48
+ * telephone number is what a visitor taps to reach the organisation, so it
49
+ * becomes a link; every other line stays text.
50
+ */
51
+ function noteLine(line) {
52
+ const text = escapeText(line);
53
+ if (/^[^\s@<>"']+@[^\s@<>"']+\.[a-z]{2,}$/i.test(line)) {
54
+ return `<a href="mailto:${escapeAttribute(line)}">${text}</a>`;
55
+ }
56
+ const digits = line.replace(/[\s().-]/g, '');
57
+ if (/^\+?\d{7,15}$/.test(digits))
58
+ return `<a href="tel:${digits}">${text}</a>`;
59
+ return text;
54
60
  }
55
- /** A plain hamburger glyph — three bars, drawn as `<path>`s so nothing here needs the icon set's closed vocabulary. */
56
- const MENU_GLYPH = '<svg class="cg-nav-toggle__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">' +
57
- '<path d="M4 7h16M4 12h16M4 17h16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>' +
58
- '</svg>';
59
61
  export function renderChrome(input) {
60
- const siteNameText = escapeText(input.site.name);
61
- const headerNavDesktop = renderNav(input.headerNav, 'cg-nav cg-nav--desktop', 'Primary');
62
- const headerNavMobile = navItems(input.headerNav);
63
- const footerNav = renderNav(input.footerNav, 'cg-site-footer__nav', 'Footer');
64
- const mark = renderBrandMark(input.brand, { className: 'cg-site-header__logo' }) ?? siteNameText;
65
- const headerAction = renderHeaderAction(input.headerAction);
66
- const themeToggle = serialize(renderThemeToggle(input.locale, { className: 'cg-theme-toggle' }));
67
- const mobileToggle = headerNavMobile === ''
62
+ const siteName = escapeText(input.site.name);
63
+ const home = escapeAttribute(input.homeHref);
64
+ const links = usable(input.headerNav)
65
+ .map((link) => linkItem(link, 'ca-header__item'))
66
+ .join('');
67
+ const action = input.headerAction === undefined
68
68
  ? ''
69
- : `<details class="cg-nav-toggle">` +
70
- `<summary class="cg-nav-toggle__button" aria-label="Menu">${MENU_GLYPH}</summary>` +
71
- `<nav class="cg-nav cg-nav--mobile" aria-label="Primary"><ul class="cg-nav__items">${headerNavMobile}</ul></nav>` +
72
- `</details>`;
73
- const header = `<header class="cg-site-header"><div class="cg-site-header__inner">` +
74
- `<a class="cg-site-header__home" href="${escapeAttribute(input.homeHref)}">${mark}</a>` +
75
- `${headerNavDesktop}` +
76
- `${themeToggle}` +
77
- `${headerAction}` +
78
- `${mobileToggle}` +
69
+ : `<a class="ca-header__action" href="${escapeAttribute(input.headerAction.href)}">${escapeText(input.headerAction.label)}</a>`;
70
+ const toggle = serialize(renderThemeToggle(input.locale, { className: 'cg-theme-toggle' }));
71
+ const logo = renderBrandMark(input.brand, { className: 'ca-header__logo' });
72
+ const mark = logo ?? `<span class="ca-header__wordmark">${siteName}</span>`;
73
+ const panelTagline = input.tagline === undefined
74
+ ? ''
75
+ : `<p class="ca-header__tagline">${escapeText(input.tagline)}</p>`;
76
+ const menu = links === ''
77
+ ? ''
78
+ : `<input type="checkbox" id="ca-nav-toggle" class="ca-nav-toggle-input" aria-label="${escapeAttribute(associationString(input.locale, 'menu'))}">` +
79
+ `<label for="ca-nav-toggle" class="ca-nav-toggle-label" aria-hidden="true">` +
80
+ `<span class="ca-nav-toggle-open">${escapeText(associationString(input.locale, 'menu'))}</span>` +
81
+ `<span class="ca-nav-toggle-close">${escapeText(associationString(input.locale, 'close'))}</span>` +
82
+ `</label>` +
83
+ `<nav class="ca-header__nav" id="ca-nav" aria-label="${escapeAttribute(associationString(input.locale, 'primaryNav'))}">` +
84
+ `<ul class="ca-header__links">${links}</ul>` +
85
+ `${panelTagline}` +
86
+ `</nav>`;
87
+ const header = `<header class="ca-header" data-nav="${links === '' ? 'none' : 'links'}" data-action="${action === '' ? 'none' : 'link'}">` +
88
+ `<div class="ca-header__inner">` +
89
+ `<a class="ca-header__brand" href="${home}">${mark}</a>` +
90
+ `${menu}` +
91
+ `${action}` +
92
+ `${toggle}` +
79
93
  `</div></header>`;
80
94
  const tagline = input.tagline === undefined
81
95
  ? ''
82
- : `<p class="cg-site-footer__tagline">${escapeText(input.tagline)}</p>`;
83
- const social = input.social === undefined
84
- ? ''
85
- : serialize(renderSocialLinks(input.social, {
86
- className: 'cg-site-footer__social',
87
- itemClassName: 'cg-site-footer__social-item',
88
- }) ?? { kind: 'text', value: '' });
89
- const footerNote = input.footerNote === undefined
96
+ : `<p class="ca-footer__tagline" data-field="tagline">${escapeText(input.tagline)}</p>`;
97
+ const note = input.footerNote === undefined ? '' : renderFooterNote(input.footerNote);
98
+ const social = renderSocialLinks(input.social, {
99
+ className: 'ca-footer__social',
100
+ itemClassName: 'ca-footer__social-item',
101
+ });
102
+ const groups = footerGroups(input.footerNav);
103
+ const nav = groups.length === 0
90
104
  ? ''
91
- : `<p class="cg-site-footer__note">${escapeText(input.footerNote)}</p>`;
92
- const footer = `<footer class="cg-site-footer">` +
93
- `<div class="cg-site-footer__grid">` +
94
- `<div class="cg-site-footer__brand">` +
95
- `<a class="cg-site-footer__brand-link" href="${escapeAttribute(input.homeHref)}">${siteNameText}</a>` +
96
- `${tagline}` +
105
+ : `<nav class="ca-footer__nav" aria-label="${escapeAttribute(associationString(input.locale, 'footerNav'))}" data-columns="${Math.min(groups.length, 3)}">${groups
106
+ .map((group) => `<div class="ca-footer__group">${group.heading === null
107
+ ? ''
108
+ : `<p class="ca-footer__heading">${escapeText(group.heading)}</p>`}<ul class="ca-footer__links">${group.links
109
+ .map((link) => linkItem(link, 'ca-footer__item'))
110
+ .join('')}</ul></div>`)
111
+ .join('')}</nav>`;
112
+ const year = new Date().getFullYear();
113
+ const widgets = renderFooterWidgets(input.widgets, {
114
+ className: 'ca-footer__widgets',
115
+ headingLevel: 'h2',
116
+ });
117
+ const footer = `<footer class="ca-footer"><div class="ca-footer__inner">` +
118
+ `<div class="ca-footer__about">` +
119
+ `<a class="ca-footer__name" href="${home}">${siteName}</a>` +
120
+ `${tagline}${note}` +
121
+ `${social === null ? '' : serialize(social)}` +
97
122
  `</div>` +
98
- `<div class="cg-site-footer__col">${footerNav}</div>` +
99
- `<div class="cg-site-footer__col">${social}</div>` +
100
- `<div class="cg-site-footer__col">${footerNote}</div>` +
123
+ `${nav}` +
124
+ `${widgets === null ? '' : serialize(widgets)}` +
125
+ `<div class="ca-footer__legal">` +
126
+ `<p class="ca-footer__copyright">© ${year} ${siteName}</p>` +
127
+ `${input.brandingHtml === '' ? '' : `<div class="ca-footer__branding">${input.brandingHtml}</div>`}` +
101
128
  `</div>` +
102
- `<div class="cg-site-footer__bottom"><span>${siteNameText}</span>${input.brandingHtml}</div>` +
103
- `</footer>`;
129
+ `</div></footer>`;
104
130
  return { header, footer };
105
131
  }
106
132
  //# sourceMappingURL=chrome.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chrome.js","sourceRoot":"","sources":["../../../src/render/chrome.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chrome.js","sourceRoot":"","sources":["../../../src/render/chrome.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import { type HtmlElement } from '@cogenta/theme-kit';
2
+ /**
3
+ * Reading an event's date and a programme's practical details out of plain
4
+ * content data.
5
+ *
6
+ * Contract A fixes the system fields and nothing else, so a theme can only
7
+ * recognise these by convention: an entry with a date in `startsAt`, `start`,
8
+ * `date` or `eventDate` is something that happens on a day, and its
9
+ * `location`, `address`, `cost` and `booking` fields say where and how to
10
+ * come. The same convention reads an event in a list (the raw entry) and on
11
+ * its own page (`theme@1.5`'s `PageEntryMeta.fields`), so the two can never
12
+ * disagree about a date.
13
+ *
14
+ * Dates are written in UTC. Contract D carries no site time zone, and a
15
+ * server's own zone is an accident of where it happens to run; a stored
16
+ * `2026-10-15T18:00:00Z` therefore reads "6pm" on every server, which is
17
+ * what an editor who typed six o'clock meant.
18
+ */
19
+ export declare const START_FIELDS: readonly ['startsAt', 'start', 'date', 'eventDate'];
20
+ export declare const END_FIELDS: readonly ['endsAt', 'end'];
21
+ type Source = Readonly<Record<string, unknown>>;
22
+ export interface Moment {
23
+ readonly iso: string;
24
+ readonly date: Date;
25
+ /** `false` for a bare `YYYY-MM-DD`: the day is known, the hour is not. */
26
+ readonly hasTime: boolean;
27
+ }
28
+ export interface EventTime {
29
+ readonly start: Moment;
30
+ readonly end: Moment | undefined;
31
+ }
32
+ /** A non-empty string, trimmed, or `undefined`. */
33
+ export declare function textOf(value: unknown): string | undefined;
34
+ /** The start (and end, when there is one after it) of whatever this entry says happens. */
35
+ export declare function eventTimeOf(source: Source | undefined): EventTime | undefined;
36
+ export declare function dayOf(moment: Moment, locale: string): string;
37
+ export declare function monthOf(moment: Moment, locale: string): string;
38
+ export declare function weekdayOf(moment: Moment, locale: string): string;
39
+ export declare function yearOf(moment: Moment, locale: string): string;
40
+ export declare function longDateOf(moment: Moment, locale: string): string;
41
+ export declare function fullDateOf(moment: Moment, locale: string): string;
42
+ /**
43
+ * A time of day the way a charity's own printed copy writes it. A locale
44
+ * whose clock is twelve-hour reads "6pm", "7.30pm", "10am": lowercase, no
45
+ * ":00", a dot before the minutes. A locale whose clock is twenty-four-hour
46
+ * keeps the platform's own form ("18:30"). Which clock a locale uses comes
47
+ * from `Intl`, never from a list of countries.
48
+ */
49
+ export declare function timeOf(moment: Moment, locale: string): string;
50
+ /** "6pm to 7.30pm", "6pm", or `undefined` for an event with no hour. */
51
+ export declare function hoursOf(time: EventTime, locale: string): string | undefined;
52
+ export declare function placeOf(source: Source): string | undefined;
53
+ export interface Fact {
54
+ readonly key: string;
55
+ readonly label: string;
56
+ readonly value: string;
57
+ /** A second line under the value: the street address under the name of a hall. */
58
+ readonly detail?: string;
59
+ readonly href?: string;
60
+ }
61
+ /**
62
+ * The practical details a visitor looks for before they come, in the order
63
+ * they look: when, where, who it is for, what it costs, whether to book, and
64
+ * who to ask. An event's "when" is its date; a programme's is its schedule.
65
+ * Only what the entry actually says is listed.
66
+ */
67
+ export declare function factsOf(source: Source, locale: string): readonly Fact[];
68
+ /** The details as a definition list, or `null` when the entry says none of them. */
69
+ export declare function renderFacts(facts: readonly Fact[], className: string): HtmlElement | null;
70
+ /**
71
+ * The typographic date block: the day large, the month in small capitals,
72
+ * the weekday under them. The whole block is one `<time>`, so a reader hears
73
+ * one date rather than three loose words.
74
+ */
75
+ export declare function renderDateBlock(time: EventTime, locale: string, className: string, options?: {
76
+ readonly year?: boolean;
77
+ }): HtmlElement;
78
+ export {};
79
+ //# sourceMappingURL=details.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"details.d.ts","sourceRoot":"","sources":["../../../src/render/details.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAK,MAAM,oBAAoB,CAAA;AAGxD;;;;;;;;;;;;;;;;GAgBG;AAEH,eAAO,MAAM,YAAY,YAAI,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAU,CAAA;AAC/E,eAAO,MAAM,UAAU,YAAI,QAAQ,EAAE,KAAK,CAAU,CAAA;AAKpD,KAAK,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAE/C,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CACjC;AAED,mDAAmD;AACnD,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAIzD;AAkBD,2FAA2F;AAC3F,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAc7E;AAWD,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOjE;AAED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAgB7D;AAED,wEAAwE;AACxE,wBAAgB,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ3E;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE1D;AAED,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AASD;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,IAAI,EAAE,CA4CvE;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAqBzF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,WAAW,CAcb"}
@@ -0,0 +1,221 @@
1
+ import { h } from '@cogenta/theme-kit';
2
+ import { associationString } from './strings.js';
3
+ /**
4
+ * Reading an event's date and a programme's practical details out of plain
5
+ * content data.
6
+ *
7
+ * Contract A fixes the system fields and nothing else, so a theme can only
8
+ * recognise these by convention: an entry with a date in `startsAt`, `start`,
9
+ * `date` or `eventDate` is something that happens on a day, and its
10
+ * `location`, `address`, `cost` and `booking` fields say where and how to
11
+ * come. The same convention reads an event in a list (the raw entry) and on
12
+ * its own page (`theme@1.5`'s `PageEntryMeta.fields`), so the two can never
13
+ * disagree about a date.
14
+ *
15
+ * Dates are written in UTC. Contract D carries no site time zone, and a
16
+ * server's own zone is an accident of where it happens to run; a stored
17
+ * `2026-10-15T18:00:00Z` therefore reads "6pm" on every server, which is
18
+ * what an editor who typed six o'clock meant.
19
+ */
20
+ export const START_FIELDS = ['startsAt', 'start', 'date', 'eventDate'];
21
+ export const END_FIELDS = ['endsAt', 'end'];
22
+ const PLACE_FIELDS = ['location', 'venue', 'place'];
23
+ const SCHEDULE_FIELDS = ['schedule', 'when', 'hours'];
24
+ /** A non-empty string, trimmed, or `undefined`. */
25
+ export function textOf(value) {
26
+ if (typeof value !== 'string')
27
+ return undefined;
28
+ const trimmed = value.trim();
29
+ return trimmed === '' ? undefined : trimmed;
30
+ }
31
+ function firstText(source, names) {
32
+ for (const name of names) {
33
+ const value = textOf(source[name]);
34
+ if (value !== undefined)
35
+ return value;
36
+ }
37
+ return undefined;
38
+ }
39
+ function momentOf(value) {
40
+ const text = textOf(value);
41
+ if (text === undefined || !/^\d{4}-\d{2}-\d{2}/.test(text))
42
+ return undefined;
43
+ const date = new Date(text);
44
+ if (Number.isNaN(date.getTime()))
45
+ return undefined;
46
+ return { iso: text, date, hasTime: /T\d{2}:\d{2}/.test(text) };
47
+ }
48
+ /** The start (and end, when there is one after it) of whatever this entry says happens. */
49
+ export function eventTimeOf(source) {
50
+ if (source === undefined)
51
+ return undefined;
52
+ let start;
53
+ for (const name of START_FIELDS) {
54
+ start = momentOf(source[name]);
55
+ if (start !== undefined)
56
+ break;
57
+ }
58
+ if (start === undefined)
59
+ return undefined;
60
+ let end;
61
+ for (const name of END_FIELDS) {
62
+ end = momentOf(source[name]);
63
+ if (end !== undefined)
64
+ break;
65
+ }
66
+ return { start, end: end !== undefined && end.date > start.date ? end : undefined };
67
+ }
68
+ function format(date, locale, options) {
69
+ const resolved = { ...options, timeZone: 'UTC' };
70
+ try {
71
+ return new Intl.DateTimeFormat(locale, resolved).format(date);
72
+ }
73
+ catch {
74
+ return new Intl.DateTimeFormat('en', resolved).format(date);
75
+ }
76
+ }
77
+ export function dayOf(moment, locale) {
78
+ return format(moment.date, locale, { day: 'numeric' });
79
+ }
80
+ export function monthOf(moment, locale) {
81
+ return format(moment.date, locale, { month: 'short' }).replace(/\.$/, '');
82
+ }
83
+ export function weekdayOf(moment, locale) {
84
+ return format(moment.date, locale, { weekday: 'short' }).replace(/\.$/, '');
85
+ }
86
+ export function yearOf(moment, locale) {
87
+ return format(moment.date, locale, { year: 'numeric' });
88
+ }
89
+ export function longDateOf(moment, locale) {
90
+ return format(moment.date, locale, { weekday: 'long', day: 'numeric', month: 'long' });
91
+ }
92
+ export function fullDateOf(moment, locale) {
93
+ return format(moment.date, locale, {
94
+ weekday: 'long',
95
+ day: 'numeric',
96
+ month: 'long',
97
+ year: 'numeric',
98
+ });
99
+ }
100
+ /**
101
+ * A time of day the way a charity's own printed copy writes it. A locale
102
+ * whose clock is twelve-hour reads "6pm", "7.30pm", "10am": lowercase, no
103
+ * ":00", a dot before the minutes. A locale whose clock is twenty-four-hour
104
+ * keeps the platform's own form ("18:30"). Which clock a locale uses comes
105
+ * from `Intl`, never from a list of countries.
106
+ */
107
+ export function timeOf(moment, locale) {
108
+ let resolved;
109
+ try {
110
+ resolved = new Intl.DateTimeFormat(locale, { hour: 'numeric', timeZone: 'UTC' });
111
+ }
112
+ catch {
113
+ resolved = new Intl.DateTimeFormat('en', { hour: 'numeric', timeZone: 'UTC' });
114
+ }
115
+ const cycle = resolved.resolvedOptions().hourCycle;
116
+ if (cycle !== 'h12' && cycle !== 'h11') {
117
+ return format(moment.date, locale, { hour: '2-digit', minute: '2-digit', hourCycle: 'h23' });
118
+ }
119
+ const hours = moment.date.getUTCHours();
120
+ const minutes = moment.date.getUTCMinutes();
121
+ const hour = hours % 12 === 0 ? 12 : hours % 12;
122
+ const suffix = hours < 12 ? 'am' : 'pm';
123
+ return minutes === 0 ? `${hour}${suffix}` : `${hour}.${String(minutes).padStart(2, '0')}${suffix}`;
124
+ }
125
+ /** "6pm to 7.30pm", "6pm", or `undefined` for an event with no hour. */
126
+ export function hoursOf(time, locale) {
127
+ if (!time.start.hasTime)
128
+ return undefined;
129
+ const start = timeOf(time.start, locale);
130
+ const end = time.end;
131
+ if (end === undefined || !end.hasTime)
132
+ return start;
133
+ const sameDay = end.date.toISOString().slice(0, 10) === time.start.date.toISOString().slice(0, 10);
134
+ if (!sameDay)
135
+ return start;
136
+ return associationString(locale, 'timeRange', { start, end: timeOf(end, locale) });
137
+ }
138
+ export function placeOf(source) {
139
+ return firstText(source, PLACE_FIELDS);
140
+ }
141
+ function contactHref(value) {
142
+ if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
143
+ return `mailto:${value}`;
144
+ const digits = value.replace(/[\s().-]/g, '');
145
+ if (/^\+?\d{7,15}$/.test(digits))
146
+ return `tel:${digits}`;
147
+ return undefined;
148
+ }
149
+ /**
150
+ * The practical details a visitor looks for before they come, in the order
151
+ * they look: when, where, who it is for, what it costs, whether to book, and
152
+ * who to ask. An event's "when" is its date; a programme's is its schedule.
153
+ * Only what the entry actually says is listed.
154
+ */
155
+ export function factsOf(source, locale) {
156
+ const facts = [];
157
+ const time = eventTimeOf(source);
158
+ if (time !== undefined) {
159
+ const hours = hoursOf(time, locale);
160
+ facts.push({
161
+ key: 'when',
162
+ label: associationString(locale, 'when'),
163
+ value: fullDateOf(time.start, locale),
164
+ ...(hours === undefined ? {} : { detail: hours }),
165
+ });
166
+ }
167
+ else {
168
+ const schedule = firstText(source, SCHEDULE_FIELDS);
169
+ if (schedule !== undefined) {
170
+ facts.push({ key: 'when', label: associationString(locale, 'when'), value: schedule });
171
+ }
172
+ }
173
+ const place = placeOf(source);
174
+ const address = textOf(source.address);
175
+ if (place !== undefined) {
176
+ facts.push({
177
+ key: 'where',
178
+ label: associationString(locale, 'where'),
179
+ value: place,
180
+ ...(address === undefined ? {} : { detail: address }),
181
+ });
182
+ }
183
+ else if (address !== undefined) {
184
+ facts.push({ key: 'where', label: associationString(locale, 'address'), value: address });
185
+ }
186
+ for (const key of ['audience', 'cost', 'booking']) {
187
+ const value = textOf(source[key]);
188
+ if (value !== undefined)
189
+ facts.push({ key, label: associationString(locale, key), value });
190
+ }
191
+ const contact = textOf(source.contact);
192
+ if (contact !== undefined) {
193
+ const href = contactHref(contact);
194
+ facts.push({
195
+ key: 'contact',
196
+ label: associationString(locale, 'contact'),
197
+ value: contact,
198
+ ...(href === undefined ? {} : { href }),
199
+ });
200
+ }
201
+ return facts;
202
+ }
203
+ /** The details as a definition list, or `null` when the entry says none of them. */
204
+ export function renderFacts(facts, className) {
205
+ if (facts.length === 0)
206
+ return null;
207
+ return h('dl', { class: className, 'data-count': String(Math.min(facts.length, 4)) }, facts.map((fact) => h('div', { class: `${className}-item`, 'data-fact': fact.key }, h('dt', { class: `${className}-label` }, fact.label), h('dd', { class: `${className}-value` }, fact.href === undefined ? fact.value : h('a', { href: fact.href }, fact.value), fact.detail === undefined
208
+ ? null
209
+ : h('span', { class: `${className}-detail` }, fact.detail)))));
210
+ }
211
+ /**
212
+ * The typographic date block: the day large, the month in small capitals,
213
+ * the weekday under them. The whole block is one `<time>`, so a reader hears
214
+ * one date rather than three loose words.
215
+ */
216
+ export function renderDateBlock(time, locale, className, options = {}) {
217
+ return h('time', { class: className, datetime: time.start.iso }, h('span', { class: `${className}-month` }, monthOf(time.start, locale)), h('span', { class: `${className}-day` }, dayOf(time.start, locale)), h('span', { class: `${className}-weekday` }, options.year === true
218
+ ? `${weekdayOf(time.start, locale)} ${yearOf(time.start, locale)}`
219
+ : weekdayOf(time.start, locale)));
220
+ }
221
+ //# sourceMappingURL=details.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"details.js","sourceRoot":"","sources":["../../../src/render/details.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Two readings of plain text a charity writes all the time, recognised from
3
+ * the words themselves rather than from a setting contract B does not have.
4
+ *
5
+ * - **A breakdown.** Figures that are all percentages and add up to a whole
6
+ * (95 to 105, for rounding) say where the money goes: they are drawn as
7
+ * shares of one total, with a bar each, instead of four unrelated numbers.
8
+ * - **What a gift pays for.** A call to action whose sentences start with an
9
+ * amount of money ("£12 buys a week of fresh vegetables for one family.")
10
+ * lists those amounts large beside what they pay for. Any other text stays
11
+ * the paragraph it was written as.
12
+ */
13
+ export declare function shareOf(value: string, unit: string | undefined): number | undefined;
14
+ export declare function isBreakdown(items: readonly {
15
+ readonly value: string;
16
+ readonly unit?: string | undefined;
17
+ }[]): boolean;
18
+ export interface Gift {
19
+ readonly amount: string;
20
+ readonly text: string;
21
+ }
22
+ export interface GiftList {
23
+ readonly lead: string | undefined;
24
+ readonly gifts: readonly Gift[];
25
+ readonly tail: string | undefined;
26
+ }
27
+ /** The sentences of `text` that start with an amount, or `undefined` when fewer than two do. */
28
+ export declare function giftsOf(text: string | undefined): GiftList | undefined;
29
+ //# sourceMappingURL=figures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figures.d.ts","sourceRoot":"","sources":["../../../src/render/figures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAMnF;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EAAE,GAC/E,OAAO,CAMT;AAED,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAA;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CAClC;AAID,gGAAgG;AAChG,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAsBtE"}