@cogenta/theme-restaurant 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 (138) hide show
  1. package/dist/src/index.d.ts +3 -0
  2. package/dist/src/index.d.ts.map +1 -1
  3. package/dist/src/index.js +3 -0
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/render/blocks/accordion.d.ts +7 -0
  6. package/dist/src/render/blocks/accordion.d.ts.map +1 -1
  7. package/dist/src/render/blocks/accordion.js +10 -16
  8. package/dist/src/render/blocks/accordion.js.map +1 -1
  9. package/dist/src/render/blocks/collection-list.d.ts +4 -11
  10. package/dist/src/render/blocks/collection-list.d.ts.map +1 -1
  11. package/dist/src/render/blocks/collection-list.js +96 -96
  12. package/dist/src/render/blocks/collection-list.js.map +1 -1
  13. package/dist/src/render/blocks/cta.d.ts +7 -4
  14. package/dist/src/render/blocks/cta.d.ts.map +1 -1
  15. package/dist/src/render/blocks/cta.js +9 -7
  16. package/dist/src/render/blocks/cta.js.map +1 -1
  17. package/dist/src/render/blocks/embed.d.ts.map +1 -1
  18. package/dist/src/render/blocks/embed.js +30 -28
  19. package/dist/src/render/blocks/embed.js.map +1 -1
  20. package/dist/src/render/blocks/faq.d.ts +13 -1
  21. package/dist/src/render/blocks/faq.d.ts.map +1 -1
  22. package/dist/src/render/blocks/faq.js +15 -11
  23. package/dist/src/render/blocks/faq.js.map +1 -1
  24. package/dist/src/render/blocks/feature-grid.d.ts.map +1 -1
  25. package/dist/src/render/blocks/feature-grid.js +43 -16
  26. package/dist/src/render/blocks/feature-grid.js.map +1 -1
  27. package/dist/src/render/blocks/gallery.d.ts +0 -7
  28. package/dist/src/render/blocks/gallery.d.ts.map +1 -1
  29. package/dist/src/render/blocks/gallery.js +28 -10
  30. package/dist/src/render/blocks/gallery.js.map +1 -1
  31. package/dist/src/render/blocks/hero.d.ts +16 -9
  32. package/dist/src/render/blocks/hero.d.ts.map +1 -1
  33. package/dist/src/render/blocks/hero.js +27 -20
  34. package/dist/src/render/blocks/hero.js.map +1 -1
  35. package/dist/src/render/blocks/logo-strip.d.ts +6 -0
  36. package/dist/src/render/blocks/logo-strip.d.ts.map +1 -1
  37. package/dist/src/render/blocks/logo-strip.js +7 -9
  38. package/dist/src/render/blocks/logo-strip.js.map +1 -1
  39. package/dist/src/render/blocks/logos.d.ts +8 -0
  40. package/dist/src/render/blocks/logos.d.ts.map +1 -1
  41. package/dist/src/render/blocks/logos.js +18 -18
  42. package/dist/src/render/blocks/logos.js.map +1 -1
  43. package/dist/src/render/blocks/media-figure.d.ts +14 -3
  44. package/dist/src/render/blocks/media-figure.d.ts.map +1 -1
  45. package/dist/src/render/blocks/media-figure.js +34 -16
  46. package/dist/src/render/blocks/media-figure.js.map +1 -1
  47. package/dist/src/render/blocks/pricing-table.d.ts.map +1 -1
  48. package/dist/src/render/blocks/pricing-table.js +17 -22
  49. package/dist/src/render/blocks/pricing-table.js.map +1 -1
  50. package/dist/src/render/blocks/prose.d.ts +11 -6
  51. package/dist/src/render/blocks/prose.d.ts.map +1 -1
  52. package/dist/src/render/blocks/prose.js +13 -7
  53. package/dist/src/render/blocks/prose.js.map +1 -1
  54. package/dist/src/render/blocks/quote.d.ts +6 -2
  55. package/dist/src/render/blocks/quote.d.ts.map +1 -1
  56. package/dist/src/render/blocks/quote.js +14 -12
  57. package/dist/src/render/blocks/quote.js.map +1 -1
  58. package/dist/src/render/blocks/stat-counter.d.ts +6 -0
  59. package/dist/src/render/blocks/stat-counter.d.ts.map +1 -1
  60. package/dist/src/render/blocks/stat-counter.js +7 -10
  61. package/dist/src/render/blocks/stat-counter.js.map +1 -1
  62. package/dist/src/render/blocks/stats.d.ts +7 -0
  63. package/dist/src/render/blocks/stats.d.ts.map +1 -1
  64. package/dist/src/render/blocks/stats.js +8 -16
  65. package/dist/src/render/blocks/stats.js.map +1 -1
  66. package/dist/src/render/blocks/testimonial.d.ts +5 -11
  67. package/dist/src/render/blocks/testimonial.d.ts.map +1 -1
  68. package/dist/src/render/blocks/testimonial.js +9 -17
  69. package/dist/src/render/blocks/testimonial.js.map +1 -1
  70. package/dist/src/render/chrome.d.ts.map +1 -1
  71. package/dist/src/render/chrome.js +101 -84
  72. package/dist/src/render/chrome.js.map +1 -1
  73. package/dist/src/render/dish.d.ts +13 -0
  74. package/dist/src/render/dish.d.ts.map +1 -0
  75. package/dist/src/render/dish.js +84 -0
  76. package/dist/src/render/dish.js.map +1 -0
  77. package/dist/src/render/layout.d.ts +23 -0
  78. package/dist/src/render/layout.d.ts.map +1 -0
  79. package/dist/src/render/layout.js +33 -0
  80. package/dist/src/render/layout.js.map +1 -0
  81. package/dist/src/render/menu.d.ts +53 -0
  82. package/dist/src/render/menu.d.ts.map +1 -0
  83. package/dist/src/render/menu.js +99 -0
  84. package/dist/src/render/menu.js.map +1 -0
  85. package/dist/src/render/render-block.d.ts +19 -0
  86. package/dist/src/render/render-block.d.ts.map +1 -1
  87. package/dist/src/render/render-block.js +50 -39
  88. package/dist/src/render/render-block.js.map +1 -1
  89. package/dist/src/render/strings.d.ts +15 -0
  90. package/dist/src/render/strings.d.ts.map +1 -0
  91. package/dist/src/render/strings.js +23 -0
  92. package/dist/src/render/strings.js.map +1 -0
  93. package/dist/src/render/term-archive.d.ts +10 -4
  94. package/dist/src/render/term-archive.d.ts.map +1 -1
  95. package/dist/src/render/term-archive.js +25 -16
  96. package/dist/src/render/term-archive.js.map +1 -1
  97. package/dist/src/render/widget-areas.d.ts +13 -0
  98. package/dist/src/render/widget-areas.d.ts.map +1 -0
  99. package/dist/src/render/widget-areas.js +20 -0
  100. package/dist/src/render/widget-areas.js.map +1 -0
  101. package/dist/theme.config.d.ts +1 -1
  102. package/dist/theme.config.js +2 -2
  103. package/dist/theme.config.js.map +1 -1
  104. package/package.json +5 -5
  105. package/src/index.ts +3 -0
  106. package/src/render/blocks/accordion.ts +18 -47
  107. package/src/render/blocks/collection-list.ts +245 -133
  108. package/src/render/blocks/cta.ts +20 -11
  109. package/src/render/blocks/embed.ts +45 -36
  110. package/src/render/blocks/faq.ts +32 -25
  111. package/src/render/blocks/feature-grid.ts +83 -33
  112. package/src/render/blocks/gallery.ts +39 -21
  113. package/src/render/blocks/hero.ts +41 -34
  114. package/src/render/blocks/logo-strip.ts +27 -25
  115. package/src/render/blocks/logos.ts +34 -43
  116. package/src/render/blocks/media-figure.ts +49 -24
  117. package/src/render/blocks/pricing-table.ts +30 -42
  118. package/src/render/blocks/prose.ts +18 -9
  119. package/src/render/blocks/quote.ts +29 -19
  120. package/src/render/blocks/stat-counter.ts +24 -29
  121. package/src/render/blocks/stats.ts +30 -40
  122. package/src/render/blocks/testimonial.ts +17 -22
  123. package/src/render/chrome.ts +113 -95
  124. package/src/render/dish.ts +195 -0
  125. package/src/render/layout.ts +64 -0
  126. package/src/render/menu.ts +117 -0
  127. package/src/render/render-block.ts +51 -40
  128. package/src/render/strings.ts +44 -0
  129. package/src/render/term-archive.ts +78 -44
  130. package/src/render/widget-areas.ts +23 -0
  131. package/src/styles/archive.css +46 -56
  132. package/src/styles/base.css +807 -554
  133. package/src/styles/blocks.css +723 -641
  134. package/src/styles/menu.css +482 -0
  135. package/src/styles/theme.css +24 -25
  136. package/src/styles/tokens.css +239 -225
  137. package/src/styles/widgets.css +853 -0
  138. package/tokens.json +16 -16
@@ -1,107 +1,124 @@
1
- import { escapeAttribute, escapeText, renderBrandMark, renderSocialLinks, renderThemeToggle, serialize, } from '@cogenta/theme-kit';
1
+ import { escapeAttribute, escapeText, renderBrandMark, renderFooterWidgets, renderSocialLinks, renderThemeToggle, serialize, } from '@cogenta/theme-kit';
2
2
  /**
3
- * This theme's own header and footer, built for `theme@1.4` from the start
4
- * rather than retrofitted: `headerAction` renders as a button beside the
5
- * primary nav, `tagline`/`social`/`footerNote` fill the footer's brand
6
- * column, and — the one thing none of the four L23 themes had yet — a
7
- * genuinely CSS-only mobile menu.
3
+ * The restaurant's header and footer (`theme@1.4`).
8
4
  *
9
- * The mobile menu is a `<details>`/`<summary>` disclosure, not a checkbox
10
- * hack: expanding, keyboard operation (`Enter`/`Space` on the `<summary>`)
11
- * and the open/closed state announced to assistive technology all come from
12
- * the browser, at zero bytes of JavaScript. It carries the *same* links as
13
- * the desktop nav plus the header action — never a second, drifting list —
14
- * and is shown only below the breakpoint `base.css` sets for `.cg-nav`.
5
+ * Header: one quiet row on the grid, under a hairline. The restaurant's name
6
+ * on the left in the display serif (or its uploaded logo), the pages in words
7
+ * at the text size, then the `headerAction` ("Reserve") as the one underlined
8
+ * link after a hairline, and the light/dark control. The action sits outside
9
+ * the navigation list on purpose: on a phone it stays in the header row
10
+ * beside the menu toggle, because booking a table is what most visitors came
11
+ * to do.
15
12
  *
16
- * The manual light/dark toggle (L26) sits right after the desktop nav, before
17
- * the header action — `renderThemeToggle` (`@cogenta/theme-kit`) returns
18
- * markup only, this theme styles `.cg-theme-toggle` itself in `base.css`. It
19
- * is unconditional (rendered on every page, `theme@1.4` fields or not),
20
- * unlike the header/footer additions above it.
13
+ * Below the wide breakpoint the pages become a panel of large serif links
14
+ * under the header, opened by a CSS-only toggle: a visually hidden checkbox
15
+ * paired with a `<label>`, placed before the `<nav>` so the sibling
16
+ * combinator reaches it. One `<nav>` in the markup, retiled by CSS. Under
17
+ * the links the open panel repeats the `footerNote` (the address card), which
18
+ * the stylesheet shows in the panel only.
19
+ *
20
+ * Footer: a charcoal band. The name and the `tagline`; the `footerNote` set as
21
+ * the restaurant's address card (a blank line starts a new paragraph, a line
22
+ * break stays a line break, so an address and a set of opening hours keep
23
+ * their shape); the footer navigation; the social profiles with their names;
24
+ * the footer widget columns (`theme@1.6`), when a site has placed any, as a
25
+ * second tier under a hairline; then the legal line, with the copyright year and the name beside the host's
26
+ * `brandingHtml`, placed once, as received. Every `theme@1.4` field renders
27
+ * only when present.
21
28
  */
22
- function navItems(links) {
29
+ function navItems(links, className) {
23
30
  return links
24
31
  .filter((link) => link.href !== null || link.kind === 'submenu-placeholder')
25
32
  .map((link) => {
26
33
  const label = escapeText(link.label);
27
34
  const titleAttr = link.title === null ? '' : ` title="${escapeAttribute(link.title)}"`;
28
- if (link.href === null)
29
- return `<li><span${titleAttr}>${label}</span></li>`;
30
- const href = escapeAttribute(link.href);
35
+ if (link.href === null) {
36
+ return `<li class="${className}"><span${titleAttr}>${label}</span></li>`;
37
+ }
31
38
  const target = link.openInNewTab ? ' target="_blank" rel="noopener"' : '';
32
- return `<li><a href="${href}"${target}${titleAttr}>${label}</a></li>`;
39
+ return `<li class="${className}"><a href="${escapeAttribute(link.href)}"${target}${titleAttr}>${label}</a></li>`;
33
40
  })
34
41
  .join('');
35
42
  }
36
- /** Three bars, drawn directly rather than pulled from an icon font (R9/R10) — the universal "menu" glyph. */
37
- const HAMBURGER_GLYPH = '<svg class="cg-mobile-nav__glyph" viewBox="0 0 24 24" fill="none" stroke="currentColor" ' +
38
- 'stroke-width="1.5" stroke-linecap="round" aria-hidden="true" focusable="false">' +
39
- '<path d="M4 7h16"/><path d="M4 12h16"/><path d="M4 17h16"/></svg>';
40
- /** The header's own call-to-action link (`theme@1.4`), styled like the block vocabulary's own primary action. */
41
- function renderHeaderAction(action) {
42
- if (action === undefined)
43
- return '';
44
- return (`<a class="cg-action cg-nav__cta" data-emphasis="primary" ` +
45
- `href="${escapeAttribute(action.href)}">${escapeText(action.label)}</a>`);
46
- }
47
- /**
48
- * The mobile drawer — the same nav links and header action as the desktop
49
- * header, in one `<details>` disclosure. Absent entirely when there is
50
- * nothing to show, exactly like the desktop `<nav>` above it.
51
- */
52
- function renderMobileNav(input) {
53
- const items = navItems(input.headerNav);
54
- const actionItem = input.headerAction === undefined ? '' : `<li>${renderHeaderAction(input.headerAction)}</li>`;
55
- if (items === '' && actionItem === '')
43
+ /** `footerNote` as paragraphs at blank lines, each keeping its own line breaks. */
44
+ function renderFooterNote(note, className) {
45
+ const paragraphs = note
46
+ .replace(/\r\n?/g, '\n')
47
+ .split(/\n\s*\n/)
48
+ .map((paragraph) => paragraph
49
+ .split('\n')
50
+ .map((line) => line.trim())
51
+ .filter((line) => line !== ''))
52
+ .filter((lines) => lines.length > 0);
53
+ if (paragraphs.length === 0)
56
54
  return '';
57
- return (`<details class="cg-mobile-nav">` +
58
- `<summary class="cg-mobile-nav__toggle" aria-label="Menu">${HAMBURGER_GLYPH}</summary>` +
59
- `<div class="cg-mobile-nav__panel"><ul class="cg-mobile-nav__items">${items}${actionItem}</ul></div>` +
60
- `</details>`);
55
+ return `<div class="${className}">${paragraphs
56
+ .map((lines) => `<p>${lines.map((line) => escapeText(line)).join('<br>')}</p>`)
57
+ .join('')}</div>`;
61
58
  }
62
59
  export function renderChrome(input) {
63
- const siteNameText = escapeText(input.site.name);
64
- const headerNav = navItems(input.headerNav);
65
- const footerNav = navItems(input.footerNav);
66
- // The uploaded logo replaces the wordmark, and only the wordmark: the
67
- // footer keeps the site's name in text, so a site whose logo fails to
68
- // load is still named somewhere on every page.
69
- const mark = renderBrandMark(input.brand, { className: 'cg-site-header__logo' }) ?? siteNameText;
70
- const themeToggle = serialize(renderThemeToggle(input.locale, { className: 'cg-theme-toggle' }));
71
- const headerAction = renderHeaderAction(input.headerAction);
72
- const mobileNav = renderMobileNav(input);
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
- `${headerNav === ''
76
- ? ''
77
- : `<nav class="cg-nav" aria-label="Primary"><ul class="cg-nav__items">${headerNav}</ul></nav>`}` +
78
- `${themeToggle}` +
79
- `${headerAction}` +
80
- `${mobileNav}` +
81
- `</div></header>`;
82
- const tagline = input.tagline === undefined
60
+ const siteName = escapeText(input.site.name);
61
+ const home = escapeAttribute(input.homeHref);
62
+ const links = navItems(input.headerNav, 'cr-header__item');
63
+ const action = input.headerAction === undefined
83
64
  ? ''
84
- : `<p class="cg-site-footer__tagline">${escapeText(input.tagline)}</p>`;
85
- const social = input.social === undefined
65
+ : `<a class="cr-header__action" href="${escapeAttribute(input.headerAction.href)}">${escapeText(input.headerAction.label)}</a>`;
66
+ const toggle = serialize(renderThemeToggle(input.locale, { className: 'cg-theme-toggle' }));
67
+ const logo = renderBrandMark(input.brand, { className: 'cr-header__logo' });
68
+ const mark = logo ?? `<span class="cr-header__wordmark">${siteName}</span>`;
69
+ // On a phone the open panel also carries the address card, because the
70
+ // address and the hours are what a guest on the street opens the menu for.
71
+ // It is the footer's own note, shown only in the panel.
72
+ const panelCard = input.footerNote === undefined ? '' : renderFooterNote(input.footerNote, 'cr-header__card');
73
+ const menu = links === ''
86
74
  ? ''
87
- : serialize(renderSocialLinks(input.social, {
88
- className: 'cg-site-footer__social',
89
- itemClassName: 'cg-site-footer__social-item',
90
- }) ?? { kind: 'text', value: '' });
91
- const footerNote = input.footerNote === undefined
75
+ : `<input type="checkbox" id="cr-nav-toggle" class="cr-nav-toggle-input" aria-label="Navigation">` +
76
+ `<label for="cr-nav-toggle" class="cr-nav-toggle-label" aria-hidden="true">` +
77
+ `<span class="cr-nav-toggle-bar"></span><span class="cr-nav-toggle-bar"></span>` +
78
+ `</label>` +
79
+ `<nav class="cr-header__nav" id="cr-nav" aria-label="Primary">` +
80
+ `<ul class="cr-header__links">${links}</ul>` +
81
+ `${panelCard}` +
82
+ `</nav>`;
83
+ const header = `<header class="cr-header" data-nav="${links === '' ? 'none' : 'links'}" data-action="${action === '' ? 'none' : 'link'}">` +
84
+ `<div class="cr-header__inner">` +
85
+ `<a class="cr-header__brand" href="${home}">${mark}</a>` +
86
+ `${menu}` +
87
+ `${action}` +
88
+ `${toggle}` +
89
+ `</div></header>`;
90
+ const tagline = input.tagline === undefined
92
91
  ? ''
93
- : `<p class="cg-site-footer__note">${escapeText(input.footerNote)}</p>`;
94
- const footer = `<footer class="cg-site-footer"><div class="cg-site-footer__grid">` +
95
- `<div class="cg-site-footer__brand">` +
96
- `<a class="cg-site-footer__brand-name" href="${escapeAttribute(input.homeHref)}">${siteNameText}</a>` +
97
- `${tagline}${social}</div>` +
98
- `${footerNav === ''
92
+ : `<p class="cr-footer__tagline" data-field="tagline">${escapeText(input.tagline)}</p>`;
93
+ const note = input.footerNote === undefined ? '' : renderFooterNote(input.footerNote, 'cr-footer__note');
94
+ const footerLinks = navItems(input.footerNav, 'cr-footer__item');
95
+ const social = renderSocialLinks(input.social, {
96
+ className: 'cr-footer__social',
97
+ itemClassName: 'cr-footer__social-item',
98
+ });
99
+ const year = new Date().getFullYear();
100
+ // The footer widget columns (`theme@1.6`): a row of their own under the
101
+ // name, the address card, the pages and the profiles, above the legal
102
+ // line, on the footer's own twelve columns.
103
+ const footerWidgets = renderFooterWidgets(input.widgets, {
104
+ className: 'cr-footer__widgets',
105
+ headingLevel: 'h2',
106
+ });
107
+ const footer = `<footer class="cr-footer"><div class="cr-footer__inner">` +
108
+ `<div class="cr-footer__about">` +
109
+ `<a class="cr-footer__name" href="${home}">${siteName}</a>` +
110
+ `${tagline}` +
111
+ `</div>` +
112
+ `${note}` +
113
+ `${footerLinks === ''
99
114
  ? ''
100
- : `<nav class="cg-site-footer__nav" aria-label="Footer"><ul class="cg-nav__items">${footerNav}</ul></nav>`}` +
101
- `<div class="cg-site-footer__about">${footerNote}</div>` +
102
- `</div><div class="cg-site-footer__bottom">` +
103
- `<span>${siteNameText}</span>` +
104
- `<div class="cg-site-footer__branding">${input.brandingHtml}</div>` +
115
+ : `<nav class="cr-footer__nav" aria-label="Footer"><ul class="cr-footer__links">${footerLinks}</ul></nav>`}` +
116
+ `${social === null ? '' : `<div class="cr-footer__follow">${serialize(social)}</div>`}` +
117
+ `${footerWidgets === null ? '' : serialize(footerWidgets)}` +
118
+ `<div class="cr-footer__legal">` +
119
+ `<p class="cr-footer__copyright">© ${year} ${siteName}</p>` +
120
+ `${input.brandingHtml === '' ? '' : `<div class="cr-footer__branding">${input.brandingHtml}</div>`}` +
121
+ `</div>` +
105
122
  `</div></footer>`;
106
123
  return { header, footer };
107
124
  }
@@ -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,13 @@
1
+ import { type HtmlElement, type PageContent, type PageEntryMeta, type RenderContext } from '@cogenta/theme-kit';
2
+ export declare function isDishPage(entry: PageEntryMeta | undefined): entry is PageEntryMeta;
3
+ export declare function renderDishHeader(page: PageContent, entry: PageEntryMeta, ctx: RenderContext): HtmlElement;
4
+ /**
5
+ * Any other page: its title large and light on the grid, the entry's summary
6
+ * under it in the serif italic when it has one. A dated entry (a note from
7
+ * the kitchen) also carries its date, author and reading time in one small
8
+ * line, and its cover photograph under the title. An undated entry with a
9
+ * picture keeps the picture for the lists that link to it: its own page
10
+ * opens on words.
11
+ */
12
+ export declare function renderPageHeader(page: PageContent, ctx: RenderContext): HtmlElement;
13
+ //# sourceMappingURL=dish.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dish.d.ts","sourceRoot":"","sources":["../../../src/render/dish.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,WAAW,EAEhB,KAAK,aAAa,EAClB,KAAK,aAAa,EAEnB,MAAM,oBAAoB,CAAA;AAmC3B,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,GAAG,KAAK,IAAI,aAAa,CAEnF;AAmCD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,aAAa,EACpB,GAAG,EAAE,aAAa,GACjB,WAAW,CAmDb;AA0BD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,aAAa,GAAG,WAAW,CAyBnF"}
@@ -0,0 +1,84 @@
1
+ import { h, renderImageSource, } from '@cogenta/theme-kit';
2
+ import { currencyOf, DETAIL_FIELDS, formatPrice, isVegetarian, priceOf, sectionOf, textOf, } from './menu.js';
3
+ import { menuString } from './strings.js';
4
+ export function isDishPage(entry) {
5
+ return entry !== undefined && priceOf(entry.fields?.price) !== undefined;
6
+ }
7
+ function details(fields, locale) {
8
+ const rows = DETAIL_FIELDS.flatMap((name) => {
9
+ const value = textOf(fields[name]);
10
+ if (value === undefined)
11
+ return [];
12
+ return [
13
+ h('div', { class: 'cr-dish__fact', 'data-fact': name }, h('dt', { class: 'cr-dish__fact-label' }, menuString(locale, name)), h('dd', { class: 'cr-dish__fact-value' }, value)),
14
+ ];
15
+ });
16
+ if (rows.length === 0)
17
+ return null;
18
+ return h('div', { class: 'cr-dish__details' }, h('h2', { class: 'cg-visually-hidden' }, menuString(locale, 'details')), h('dl', { class: 'cr-dish__facts' }, rows));
19
+ }
20
+ function terms(entry) {
21
+ if (entry.terms === undefined || entry.terms.length === 0)
22
+ return null;
23
+ return h('ul', { class: 'cr-dish__terms' }, entry.terms.map((term) => h('li', {}, term.href === null ? term.label : h('a', { href: term.href }, term.label))));
24
+ }
25
+ export function renderDishHeader(page, entry, ctx) {
26
+ const fields = entry.fields ?? {};
27
+ const price = priceOf(fields.price);
28
+ const section = sectionOf(fields);
29
+ const image = entry.image;
30
+ return h('article', { class: 'cr-dish', 'data-media': String(image !== undefined) }, h('div', { class: 'cr-container cr-dish__inner' }, image === undefined
31
+ ? null
32
+ : h('figure', { class: 'cr-dish__media' }, renderImageSource(image, {
33
+ className: 'cr-dish__image',
34
+ loading: 'eager',
35
+ sizes: '(min-width: 64rem) 48vw, 100vw',
36
+ })), h('div', { class: 'cr-dish__info' }, section === undefined ? null : h('p', { class: 'cr-dish__section' }, section), h('h1', { class: 'cr-dish__title' }, page.title), entry.excerpt === undefined ? null : h('p', { class: 'cr-dish__summary' }, entry.excerpt), h('p', { class: 'cr-dish__price-line' }, h('span', { class: 'cr-dish__price' }, h('span', { class: 'cg-visually-hidden' }, `${menuString(ctx.locale, 'price')} `), h('data', { value: String(price) }, formatPrice(price, currencyOf(fields.currency), ctx.locale))), isVegetarian(fields)
37
+ ? h('span', { class: 'cr-dish__diet' }, menuString(ctx.locale, 'vegetarian'))
38
+ : null), details(fields, ctx.locale), terms(entry))));
39
+ }
40
+ function formatDate(iso, locale) {
41
+ try {
42
+ return new Intl.DateTimeFormat(locale, { dateStyle: 'long' }).format(new Date(iso));
43
+ }
44
+ catch {
45
+ return iso;
46
+ }
47
+ }
48
+ /**
49
+ * Date, author and reading time, for a dated entry only: an opening-hours
50
+ * page is not something a guest reads against the clock.
51
+ */
52
+ function pageMeta(entry, ctx) {
53
+ if (entry.publishedAt === undefined)
54
+ return null;
55
+ const parts = [
56
+ h('time', { datetime: entry.publishedAt }, formatDate(entry.publishedAt, ctx.locale)),
57
+ ];
58
+ if (entry.author !== undefined)
59
+ parts.push(h('span', {}, entry.author.name));
60
+ if (entry.readingMinutes !== undefined) {
61
+ parts.push(h('span', {}, ctx.t('entry.readingTime', { minutes: entry.readingMinutes })));
62
+ }
63
+ return h('p', { class: 'cr-page-head__meta' }, parts);
64
+ }
65
+ /**
66
+ * Any other page: its title large and light on the grid, the entry's summary
67
+ * under it in the serif italic when it has one. A dated entry (a note from
68
+ * the kitchen) also carries its date, author and reading time in one small
69
+ * line, and its cover photograph under the title. An undated entry with a
70
+ * picture keeps the picture for the lists that link to it: its own page
71
+ * opens on words.
72
+ */
73
+ export function renderPageHeader(page, ctx) {
74
+ const entry = page.entry;
75
+ const cover = entry?.publishedAt === undefined ? undefined : entry.image;
76
+ return h('header', { class: 'cr-page-head', 'data-cover': String(cover !== undefined) }, h('div', { class: 'cr-container cr-page-head__inner' }, h('h1', { class: 'cr-page-head__title' }, page.title), entry?.excerpt === undefined ? null : h('p', { class: 'cr-page-head__lead' }, entry.excerpt), entry === undefined ? null : pageMeta(entry, ctx), cover === undefined
77
+ ? null
78
+ : h('figure', { class: 'cr-page-head__cover' }, renderImageSource(cover, {
79
+ className: 'cr-page-head__image',
80
+ loading: 'eager',
81
+ sizes: '(min-width: 90rem) 86rem, 100vw',
82
+ }))));
83
+ }
84
+ //# sourceMappingURL=dish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dish.js","sourceRoot":"","sources":["../../../src/render/dish.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { type Attributes, type Child, type HtmlElement } from '@cogenta/theme-kit';
2
+ /**
3
+ * The frame every block of this theme renders into.
4
+ *
5
+ * The outer element carries the one section rhythm (`--cr-section`, the same
6
+ * for every block) and paints edge to edge when a variant asks for a band.
7
+ * The inner element is the twelve-column container, so a section title, a
8
+ * dish name, a caption and a price all start on the same line.
9
+ *
10
+ * `inner` is `figure` for a block whose semantics are a figure (a quotation
11
+ * and its attribution, an image and its caption): `<figcaption>` must be a
12
+ * direct child of its `<figure>`.
13
+ */
14
+ export declare function section(outer: 'section' | 'div', block: string, className: string, attrs: Attributes, inner: 'div' | 'figure', ...children: readonly Child[]): HtmlElement;
15
+ /**
16
+ * A titled block's heading: set in the display serif at the section size, on
17
+ * the first column of the grid. A dining room lets the food and the room
18
+ * carry the page, so a section names itself in a few words and nothing else.
19
+ */
20
+ export declare function sectionHead(blockName: string, title: string | undefined): HtmlElement | null;
21
+ /** A paragraph that renders only when the text is there: no empty `<p>` on the page. */
22
+ export declare function optionalText(tag: string, className: string, value: string | undefined, attrs?: Attributes): HtmlElement | null;
23
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../src/render/layout.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EAEf,KAAK,KAAK,EACV,KAAK,WAAW,EAGjB,MAAM,oBAAoB,CAAA;AAE3B;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CACrB,KAAK,EAAE,SAAS,GAAG,KAAK,EACxB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,KAAK,GAAG,QAAQ,EACvB,GAAG,QAAQ,EAAE,SAAS,KAAK,EAAE,GAC5B,WAAW,CAMb;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,IAAI,CAW5F;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,KAAK,GAAE,UAAe,GACrB,WAAW,GAAG,IAAI,CAGpB"}
@@ -0,0 +1,33 @@
1
+ import { blockHeadingTag, h, heading, } from '@cogenta/theme-kit';
2
+ /**
3
+ * The frame every block of this theme renders into.
4
+ *
5
+ * The outer element carries the one section rhythm (`--cr-section`, the same
6
+ * for every block) and paints edge to edge when a variant asks for a band.
7
+ * The inner element is the twelve-column container, so a section title, a
8
+ * dish name, a caption and a price all start on the same line.
9
+ *
10
+ * `inner` is `figure` for a block whose semantics are a figure (a quotation
11
+ * and its attribution, an image and its caption): `<figcaption>` must be a
12
+ * direct child of its `<figure>`.
13
+ */
14
+ export function section(outer, block, className, attrs, inner, ...children) {
15
+ return h(outer, { class: `cr-section ${className}`, 'data-block': block, ...attrs }, h(inner, { class: `cr-container ${className}__inner` }, ...children));
16
+ }
17
+ /**
18
+ * A titled block's heading: set in the display serif at the section size, on
19
+ * the first column of the grid. A dining room lets the food and the room
20
+ * carry the page, so a section names itself in a few words and nothing else.
21
+ */
22
+ export function sectionHead(blockName, title) {
23
+ if (title === undefined)
24
+ return null;
25
+ return h('div', { class: 'cr-head' }, heading(blockHeadingTag(blockName) ?? 'h2', { class: 'cr-head__title', 'data-field': 'title' }, title));
26
+ }
27
+ /** A paragraph that renders only when the text is there: no empty `<p>` on the page. */
28
+ export function optionalText(tag, className, value, attrs = {}) {
29
+ if (value === undefined || value.trim() === '')
30
+ return null;
31
+ return h(tag, { class: className, ...attrs }, value);
32
+ }
33
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../../src/render/layout.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EAGf,CAAC,EACD,OAAO,GACR,MAAM,oBAAoB,CAAA;AAE3B;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,OAAO,CACrB,KAAwB,EACxB,KAAa,EACb,SAAiB,EACjB,KAAiB,EACjB,KAAuB,EACvB,GAAG,QAA0B;IAE7B,OAAO,CAAC,CACN,KAAK,EACL,EAAE,KAAK,EAAE,cAAc,SAAS,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,EACnE,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,gBAAgB,SAAS,SAAS,EAAE,EAAE,GAAG,QAAQ,CAAC,CACrE,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,SAAiB,EAAE,KAAyB;IACtE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACpC,OAAO,CAAC,CACN,KAAK,EACL,EAAE,KAAK,EAAE,SAAS,EAAE,EACpB,OAAO,CACL,eAAe,CAAC,SAAS,CAAC,IAAI,IAAI,EAClC,EAAE,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,OAAO,EAAE,EAClD,KAAK,CACN,CACF,CAAA;AACH,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,YAAY,CAC1B,GAAW,EACX,SAAiB,EACjB,KAAyB,EACzB,KAAK,GAAe,EAAE;IAEtB,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IAC3D,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,CAAA;AACtD,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Reading a dish out of plain content data.
3
+ *
4
+ * Contract A fixes the system fields and nothing else, so a theme can only
5
+ * recognise a dish by convention: an entry with a numeric `price` is
6
+ * something on the menu, whatever its collection is called, and its
7
+ * `category` (or `section`, or `course`) is the part of the menu it belongs
8
+ * to. The same convention reads a dish in a menu list (the raw entry) and on
9
+ * its own page (`theme@1.5`'s `PageEntryMeta.fields`), so the two can never
10
+ * disagree about a price.
11
+ *
12
+ * Money is shown, never computed: no rounding, no service charge, no
13
+ * conversion. The currency comes from the entry's own `currency` field (an
14
+ * ISO 4217 code); contract B has no currency, so an entry that does not say
15
+ * is shown in euros, the currency every dish this theme's blueprint seeds is
16
+ * priced in. A restaurant that charges in another currency sets the field.
17
+ */
18
+ export declare const DEFAULT_CURRENCY = "EUR";
19
+ /** A finite, non-negative number, or `undefined` for anything else. */
20
+ export declare function priceOf(value: unknown): number | undefined;
21
+ /** An ISO 4217 code the platform accepts, or the default. */
22
+ export declare function currencyOf(value: unknown): string;
23
+ /**
24
+ * `€16` in English, `16 €` in French: the platform's own currency formatting
25
+ * in the page's locale. A whole amount shows no decimals, a price with cents
26
+ * shows two, so a menu of round prices does not read as a column of `.00`.
27
+ */
28
+ export declare function formatPrice(amount: number, currency: string, locale: string): string;
29
+ /** A non-empty string, trimmed, or `undefined`. */
30
+ export declare function textOf(value: unknown): string | undefined;
31
+ /** The first field among `names` that holds text. */
32
+ export declare function firstText(source: Readonly<Record<string, unknown>>, names: readonly string[]): string | undefined;
33
+ /** The part of the menu a dish belongs to, under whichever of the usual names a collection gave it. */
34
+ export declare const SECTION_FIELDS: readonly ['category', 'section', 'course'];
35
+ /** The plain details a dish page lists, in the order a guest reads them. */
36
+ export declare const DETAIL_FIELDS: readonly ['sourcing', 'pairing', 'allergens'];
37
+ export type DetailField = (typeof DETAIL_FIELDS)[number];
38
+ export declare function sectionOf(source: Readonly<Record<string, unknown>>): string | undefined;
39
+ /** `vegetarian: true` says so; anything else says nothing. */
40
+ export declare function isVegetarian(source: Readonly<Record<string, unknown>>): boolean;
41
+ export interface MenuSection<T> {
42
+ /** `undefined` for dishes that name no section: one untitled group. */
43
+ readonly title: string | undefined;
44
+ readonly items: readonly T[];
45
+ }
46
+ /**
47
+ * Groups dishes by section, keeping the order the list arrived in: a section
48
+ * appears where its first dish does, and its dishes keep their own order
49
+ * inside it. The editor orders the menu by ordering the entries; the theme
50
+ * never sorts it alphabetically behind their back.
51
+ */
52
+ export declare function groupBySection<T extends Readonly<Record<string, unknown>>>(entries: readonly T[]): readonly MenuSection<T>[];
53
+ //# sourceMappingURL=menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../src/render/menu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,eAAO,MAAM,gBAAgB,QAAQ,CAAA;AAErC,uEAAuE;AACvE,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE1D;AAED,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIjD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAapF;AAED,mDAAmD;AACnD,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAIzD;AAED,qDAAqD;AACrD,wBAAgB,SAAS,CACvB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,MAAM,GAAG,SAAS,CAMpB;AAED,uGAAuG;AACvG,eAAO,MAAM,cAAc,YAAI,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAU,CAAA;AAExE,4EAA4E;AAC5E,eAAO,MAAM,aAAa,YAAI,UAAU,EAAE,SAAS,EAAE,WAAW,CAAU,CAAA;AAE1E,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAExD,wBAAgB,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,GAAG,SAAS,CAEvF;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,CAE/E;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAA;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxE,OAAO,EAAE,SAAS,CAAC,EAAE,GACpB,SAAS,WAAW,CAAC,CAAC,CAAC,EAAE,CAc3B"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Reading a dish out of plain content data.
3
+ *
4
+ * Contract A fixes the system fields and nothing else, so a theme can only
5
+ * recognise a dish by convention: an entry with a numeric `price` is
6
+ * something on the menu, whatever its collection is called, and its
7
+ * `category` (or `section`, or `course`) is the part of the menu it belongs
8
+ * to. The same convention reads a dish in a menu list (the raw entry) and on
9
+ * its own page (`theme@1.5`'s `PageEntryMeta.fields`), so the two can never
10
+ * disagree about a price.
11
+ *
12
+ * Money is shown, never computed: no rounding, no service charge, no
13
+ * conversion. The currency comes from the entry's own `currency` field (an
14
+ * ISO 4217 code); contract B has no currency, so an entry that does not say
15
+ * is shown in euros, the currency every dish this theme's blueprint seeds is
16
+ * priced in. A restaurant that charges in another currency sets the field.
17
+ */
18
+ export const DEFAULT_CURRENCY = 'EUR';
19
+ /** A finite, non-negative number, or `undefined` for anything else. */
20
+ export function priceOf(value) {
21
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
22
+ }
23
+ /** An ISO 4217 code the platform accepts, or the default. */
24
+ export function currencyOf(value) {
25
+ if (typeof value !== 'string' || !/^[A-Za-z]{3}$/.test(value.trim()))
26
+ return DEFAULT_CURRENCY;
27
+ const code = value.trim().toUpperCase();
28
+ return Intl.supportedValuesOf('currency').includes(code) ? code : DEFAULT_CURRENCY;
29
+ }
30
+ /**
31
+ * `€16` in English, `16 €` in French: the platform's own currency formatting
32
+ * in the page's locale. A whole amount shows no decimals, a price with cents
33
+ * shows two, so a menu of round prices does not read as a column of `.00`.
34
+ */
35
+ export function formatPrice(amount, currency, locale) {
36
+ const whole = Number.isInteger(amount);
37
+ const options = {
38
+ style: 'currency',
39
+ currency,
40
+ minimumFractionDigits: whole ? 0 : 2,
41
+ maximumFractionDigits: whole ? 0 : 2,
42
+ };
43
+ try {
44
+ return new Intl.NumberFormat(locale, options).format(amount);
45
+ }
46
+ catch {
47
+ return new Intl.NumberFormat('en', options).format(amount);
48
+ }
49
+ }
50
+ /** A non-empty string, trimmed, or `undefined`. */
51
+ export function textOf(value) {
52
+ if (typeof value !== 'string')
53
+ return undefined;
54
+ const trimmed = value.trim();
55
+ return trimmed === '' ? undefined : trimmed;
56
+ }
57
+ /** The first field among `names` that holds text. */
58
+ export function firstText(source, names) {
59
+ for (const name of names) {
60
+ const value = textOf(source[name]);
61
+ if (value !== undefined)
62
+ return value;
63
+ }
64
+ return undefined;
65
+ }
66
+ /** The part of the menu a dish belongs to, under whichever of the usual names a collection gave it. */
67
+ export const SECTION_FIELDS = ['category', 'section', 'course'];
68
+ /** The plain details a dish page lists, in the order a guest reads them. */
69
+ export const DETAIL_FIELDS = ['sourcing', 'pairing', 'allergens'];
70
+ export function sectionOf(source) {
71
+ return firstText(source, SECTION_FIELDS);
72
+ }
73
+ /** `vegetarian: true` says so; anything else says nothing. */
74
+ export function isVegetarian(source) {
75
+ return source.vegetarian === true;
76
+ }
77
+ /**
78
+ * Groups dishes by section, keeping the order the list arrived in: a section
79
+ * appears where its first dish does, and its dishes keep their own order
80
+ * inside it. The editor orders the menu by ordering the entries; the theme
81
+ * never sorts it alphabetically behind their back.
82
+ */
83
+ export function groupBySection(entries) {
84
+ const order = [];
85
+ const groups = new Map();
86
+ for (const entry of entries) {
87
+ const title = sectionOf(entry);
88
+ const group = groups.get(title);
89
+ if (group === undefined) {
90
+ order.push(title);
91
+ groups.set(title, [entry]);
92
+ }
93
+ else {
94
+ group.push(entry);
95
+ }
96
+ }
97
+ return order.map((title) => ({ title, items: groups.get(title) ?? [] }));
98
+ }
99
+ //# sourceMappingURL=menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu.js","sourceRoot":"","sources":["../../../src/render/menu.ts"],"names":[],"mappings":"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"}
@@ -2,5 +2,24 @@ import { type BlockRegistry, type VocabularyBlock } from '@cogenta/blocks';
2
2
  import { type FetchedEntries, type HtmlElement, type PageContent, type RenderContext } from '@cogenta/theme-kit';
3
3
  export type { FetchedEntries, PageContent };
4
4
  export declare function renderBlock(block: VocabularyBlock, ctx: RenderContext, entries?: FetchedEntries, registry?: BlockRegistry): HtmlElement | null;
5
+ /**
6
+ * `<main id="cg-main">` is mandatory: it is the skip link's target, written
7
+ * by the host outside any theme's control.
8
+ *
9
+ * Three openings, and exactly one `h1` in each:
10
+ *
11
+ * - a page whose blocks include a `hero` lets the hero carry the title;
12
+ * - a dish (`isDishPage`: its `theme@1.5` fields carry a price) gets the dish
13
+ * sheet: photograph, section, name, description, price, details;
14
+ * - anything else gets its title set large on the grid.
15
+ *
16
+ * A host older than `theme@1.5` sends no fields, so its dish pages open like
17
+ * any other page: title and description, never a price read from somewhere
18
+ * it was not given. A host older than `theme@1.4` sends no entry at all, and
19
+ * the page opens on its bare title.
20
+ *
21
+ * `withBlockKey` stamps every rendered block with its contract-B `_key`, so
22
+ * the visual page builder (L16) can map a clicked element back to its block.
23
+ */
5
24
  export declare function renderPage(page: PageContent, ctx: RenderContext, entries?: FetchedEntries, registry?: BlockRegistry): HtmlElement;
6
25
  //# sourceMappingURL=render-block.d.ts.map