@cogenta/theme-docs 0.3.3 → 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 +2 -0
  2. package/dist/src/index.d.ts.map +1 -1
  3. package/dist/src/index.js +2 -0
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/render/blocks/accordion.d.ts +8 -0
  6. package/dist/src/render/blocks/accordion.d.ts.map +1 -1
  7. package/dist/src/render/blocks/accordion.js +10 -10
  8. package/dist/src/render/blocks/accordion.js.map +1 -1
  9. package/dist/src/render/blocks/collection-list.d.ts +3 -11
  10. package/dist/src/render/blocks/collection-list.d.ts.map +1 -1
  11. package/dist/src/render/blocks/collection-list.js +69 -68
  12. package/dist/src/render/blocks/collection-list.js.map +1 -1
  13. package/dist/src/render/blocks/cta.d.ts +6 -0
  14. package/dist/src/render/blocks/cta.d.ts.map +1 -1
  15. package/dist/src/render/blocks/cta.js +9 -4
  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 +14 -13
  19. package/dist/src/render/blocks/embed.js.map +1 -1
  20. package/dist/src/render/blocks/faq.d.ts +7 -0
  21. package/dist/src/render/blocks/faq.d.ts.map +1 -1
  22. package/dist/src/render/blocks/faq.js +10 -10
  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 +19 -19
  26. package/dist/src/render/blocks/feature-grid.js.map +1 -1
  27. package/dist/src/render/blocks/gallery.d.ts +8 -4
  28. package/dist/src/render/blocks/gallery.d.ts.map +1 -1
  29. package/dist/src/render/blocks/gallery.js +19 -13
  30. package/dist/src/render/blocks/gallery.js.map +1 -1
  31. package/dist/src/render/blocks/hero.d.ts +12 -9
  32. package/dist/src/render/blocks/hero.d.ts.map +1 -1
  33. package/dist/src/render/blocks/hero.js +22 -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 +6 -10
  38. package/dist/src/render/blocks/logo-strip.js.map +1 -1
  39. package/dist/src/render/blocks/logos.d.ts +10 -0
  40. package/dist/src/render/blocks/logos.d.ts.map +1 -1
  41. package/dist/src/render/blocks/logos.js +20 -17
  42. package/dist/src/render/blocks/logos.js.map +1 -1
  43. package/dist/src/render/blocks/media-figure.d.ts +0 -6
  44. package/dist/src/render/blocks/media-figure.d.ts.map +1 -1
  45. package/dist/src/render/blocks/media-figure.js +28 -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 +16 -20
  49. package/dist/src/render/blocks/pricing-table.js.map +1 -1
  50. package/dist/src/render/blocks/prose.d.ts +13 -1
  51. package/dist/src/render/blocks/prose.d.ts.map +1 -1
  52. package/dist/src/render/blocks/prose.js +12 -31
  53. package/dist/src/render/blocks/prose.js.map +1 -1
  54. package/dist/src/render/blocks/quote.d.ts +5 -3
  55. package/dist/src/render/blocks/quote.d.ts.map +1 -1
  56. package/dist/src/render/blocks/quote.js +10 -14
  57. package/dist/src/render/blocks/quote.js.map +1 -1
  58. package/dist/src/render/blocks/stat-counter.d.ts +5 -0
  59. package/dist/src/render/blocks/stat-counter.d.ts.map +1 -1
  60. package/dist/src/render/blocks/stat-counter.js +6 -10
  61. package/dist/src/render/blocks/stat-counter.js.map +1 -1
  62. package/dist/src/render/blocks/stats.d.ts +10 -0
  63. package/dist/src/render/blocks/stats.d.ts.map +1 -1
  64. package/dist/src/render/blocks/stats.js +13 -8
  65. package/dist/src/render/blocks/stats.js.map +1 -1
  66. package/dist/src/render/blocks/testimonial.d.ts +4 -5
  67. package/dist/src/render/blocks/testimonial.d.ts.map +1 -1
  68. package/dist/src/render/blocks/testimonial.js +9 -13
  69. package/dist/src/render/blocks/testimonial.js.map +1 -1
  70. package/dist/src/render/chrome.d.ts +36 -1
  71. package/dist/src/render/chrome.d.ts.map +1 -1
  72. package/dist/src/render/chrome.js +122 -80
  73. package/dist/src/render/chrome.js.map +1 -1
  74. package/dist/src/render/doc-index.d.ts +25 -0
  75. package/dist/src/render/doc-index.d.ts.map +1 -0
  76. package/dist/src/render/doc-index.js +49 -0
  77. package/dist/src/render/doc-index.js.map +1 -0
  78. package/dist/src/render/doc-page.d.ts +6 -0
  79. package/dist/src/render/doc-page.d.ts.map +1 -0
  80. package/dist/src/render/doc-page.js +127 -0
  81. package/dist/src/render/doc-page.js.map +1 -0
  82. package/dist/src/render/layout.d.ts +23 -0
  83. package/dist/src/render/layout.d.ts.map +1 -0
  84. package/dist/src/render/layout.js +40 -0
  85. package/dist/src/render/layout.js.map +1 -0
  86. package/dist/src/render/render-block.d.ts +9 -6
  87. package/dist/src/render/render-block.d.ts.map +1 -1
  88. package/dist/src/render/render-block.js +47 -100
  89. package/dist/src/render/render-block.js.map +1 -1
  90. package/dist/src/render/rich-text.d.ts +26 -0
  91. package/dist/src/render/rich-text.d.ts.map +1 -0
  92. package/dist/src/render/rich-text.js +325 -0
  93. package/dist/src/render/rich-text.js.map +1 -0
  94. package/dist/src/render/search.d.ts +15 -0
  95. package/dist/src/render/search.d.ts.map +1 -0
  96. package/dist/src/render/search.js +31 -0
  97. package/dist/src/render/search.js.map +1 -0
  98. package/dist/src/render/strings.d.ts +51 -0
  99. package/dist/src/render/strings.d.ts.map +1 -0
  100. package/dist/src/render/strings.js +60 -0
  101. package/dist/src/render/strings.js.map +1 -0
  102. package/dist/src/render/term-archive.d.ts +5 -5
  103. package/dist/src/render/term-archive.d.ts.map +1 -1
  104. package/dist/src/render/term-archive.js +27 -16
  105. package/dist/src/render/term-archive.js.map +1 -1
  106. package/dist/src/render/widget-areas.d.ts +22 -0
  107. package/dist/src/render/widget-areas.d.ts.map +1 -0
  108. package/dist/src/render/widget-areas.js +29 -0
  109. package/dist/src/render/widget-areas.js.map +1 -0
  110. package/package.json +4 -4
  111. package/src/index.ts +2 -0
  112. package/src/render/blocks/accordion.ts +31 -42
  113. package/src/render/blocks/collection-list.ts +132 -123
  114. package/src/render/blocks/cta.ts +23 -18
  115. package/src/render/blocks/embed.ts +37 -28
  116. package/src/render/blocks/faq.ts +26 -37
  117. package/src/render/blocks/feature-grid.ts +34 -42
  118. package/src/render/blocks/gallery.ts +33 -29
  119. package/src/render/blocks/hero.ts +41 -45
  120. package/src/render/blocks/logo-strip.ts +21 -21
  121. package/src/render/blocks/logos.ts +32 -42
  122. package/src/render/blocks/media-figure.ts +47 -25
  123. package/src/render/blocks/pricing-table.ts +34 -41
  124. package/src/render/blocks/prose.ts +25 -40
  125. package/src/render/blocks/quote.ts +25 -19
  126. package/src/render/blocks/stat-counter.ts +27 -29
  127. package/src/render/blocks/stats.ts +35 -32
  128. package/src/render/blocks/testimonial.ts +18 -19
  129. package/src/render/chrome.ts +170 -83
  130. package/src/render/doc-index.ts +65 -0
  131. package/src/render/doc-page.ts +303 -0
  132. package/src/render/layout.ts +73 -0
  133. package/src/render/render-block.ts +67 -191
  134. package/src/render/rich-text.ts +446 -0
  135. package/src/render/search.ts +41 -0
  136. package/src/render/strings.ts +67 -0
  137. package/src/render/term-archive.ts +62 -38
  138. package/src/render/widget-areas.ts +33 -0
  139. package/src/styles/archive.css +21 -27
  140. package/src/styles/base.css +438 -838
  141. package/src/styles/blocks.css +658 -916
  142. package/src/styles/chrome.css +439 -0
  143. package/src/styles/docs.css +404 -0
  144. package/src/styles/rich.css +337 -0
  145. package/src/styles/theme.css +28 -23
  146. package/src/styles/tokens.css +209 -215
  147. package/src/styles/widgets.css +890 -0
  148. package/tokens.json +15 -15
@@ -1,46 +1,35 @@
1
- import type { FaqBlock, FaqItem } from '@cogenta/blocks'
2
- import {
3
- blockHeadingTag,
4
- type HtmlElement,
5
- h,
6
- heading,
7
- type RenderContext,
8
- renderRichText,
9
- } from '@cogenta/theme-kit'
1
+ import type { FaqBlock } from '@cogenta/blocks'
2
+ import { type HtmlElement, h, nestedHeadingTag, type RenderContext } from '@cogenta/theme-kit'
3
+ import { section, sectionHead } from '../layout.js'
4
+ import { renderDocsRichText } from '../rich-text.js'
10
5
 
11
6
  /**
12
- * `<details>`/`<summary>` rather than a scripted accordion: expanding,
13
- * keyboard operation, and the expanded state announced to assistive
14
- * technology all come from the browser, at zero bytes of JavaScript.
7
+ * Questions a reader asks before reading further ("Which databases are
8
+ * supported"), answered in the open: no disclosure to click. The title on
9
+ * the first four columns, the questions and answers on the last eight in two
10
+ * columns of ruled items on a wide screen, each question a real heading so a
11
+ * screen reader's heading list reaches it.
15
12
  */
16
- function renderItem(item: FaqItem, ctx: RenderContext): HtmlElement {
17
- return h(
18
- 'li',
19
- { class: 'cg-faq__item' },
20
- h(
21
- 'details',
22
- { class: 'cg-faq__details' },
23
- h('summary', { class: 'cg-faq__question' }, item.question),
24
- h('div', { class: 'cg-faq__answer' }, renderRichText(ctx, item.answer)),
25
- ),
26
- )
27
- }
28
-
29
13
  export function renderFaq(block: FaqBlock, ctx: RenderContext): HtmlElement {
30
- return h(
14
+ const tag = nestedHeadingTag('faq', block.title !== undefined)
15
+ return section(
31
16
  'section',
32
- { class: 'cg-block cg-faq', 'data-block': 'faq' },
33
- block.title === undefined
34
- ? null
35
- : heading(
36
- blockHeadingTag('faq') ?? 'h2',
37
- { class: 'cg-faq__title', 'data-field': 'title' },
38
- block.title,
39
- ),
17
+ 'faq',
18
+ 'cd-faq',
19
+ { 'data-titled': block.title === undefined ? 'false' : 'true' },
20
+ 'div',
21
+ sectionHead('faq', block.title),
40
22
  h(
41
- 'ul',
42
- { class: 'cg-faq__items' },
43
- block.items.map((item) => renderItem(item, ctx)),
23
+ 'div',
24
+ { class: 'cd-faq__items' },
25
+ block.items.map((item) =>
26
+ h(
27
+ 'div',
28
+ { class: 'cd-faq__item' },
29
+ h(tag, { class: 'cd-faq__question' }, item.question),
30
+ h('div', { class: 'cd-faq__answer cd-rich' }, renderDocsRichText(ctx, item.answer)),
31
+ ),
32
+ ),
44
33
  ),
45
34
  )
46
35
  }
@@ -1,67 +1,59 @@
1
1
  import type { FeatureGridBlock, FeatureItem } from '@cogenta/blocks'
2
2
  import {
3
- blockHeadingTag,
4
- type HeadingTag,
5
3
  type HtmlElement,
6
4
  h,
7
- heading,
8
5
  href,
9
6
  nestedHeadingTag,
10
7
  type RenderContext,
11
8
  renderIcon,
12
9
  } from '@cogenta/theme-kit'
10
+ import { optionalText, section, sectionHead } from '../layout.js'
13
11
 
14
12
  /**
15
- * The item's title is the link, so the link's accessible name is the
16
- * feature's own name (WCAG 2.4.4) — the block carries no separate label
17
- * field to write a "learn more" with anyway.
13
+ * Short entries in three columns: "Start here", "Get help". No card, no tile,
14
+ * no background: each entry hangs from a hairline, its title is the link (an
15
+ * underlined arrow link when there is one), and one or two lines under it say
16
+ * what the reader will find. An icon, when the item names one this theme
17
+ * knows, is a small stroke drawing inline before the title in the secondary
18
+ * ink, the size of the text it sits beside.
18
19
  *
19
- * `icon` names a symbol, never markup: when the name is one `renderIcon`
20
- * recognises it becomes a real inline glyph, `aria-hidden` since the title
21
- * already names the card. An unrecognised name keeps the bare, empty
22
- * `data-icon` span for a skin/plugin to key off.
20
+ * Three columns on a desk, two on a tablet, one on a phone; four items make
21
+ * a row of four on a wide screen, so no row is left with an empty cell.
23
22
  */
24
- function renderItem(item: FeatureItem, ctx: RenderContext, tag: HeadingTag): HtmlElement {
25
- const title =
26
- item.link === undefined
27
- ? heading(tag, { class: 'cg-feature__title' }, item.title)
28
- : heading(
29
- tag,
30
- { class: 'cg-feature__title' },
31
- h('a', { class: 'cg-feature__link', href: href(ctx, item.link) }, item.title),
32
- )
33
- return h(
34
- 'li',
35
- { class: 'cg-feature' },
23
+ function entry(item: FeatureItem, ctx: RenderContext, tag: string): HtmlElement {
24
+ const icon =
36
25
  item.icon === undefined
37
26
  ? null
38
- : h(
39
- 'span',
40
- { class: 'cg-feature__icon', 'data-icon': item.icon, 'aria-hidden': 'true' },
41
- renderIcon(item.icon),
42
- ),
43
- title,
44
- item.text === undefined ? null : h('p', { class: 'cg-feature__text' }, item.text),
27
+ : renderIcon(item.icon, { className: 'cd-features__icon', size: 18 })
28
+ return h(
29
+ 'li',
30
+ { class: 'cd-features__item' },
31
+ h(
32
+ tag,
33
+ { class: 'cd-features__title' },
34
+ icon,
35
+ item.link === undefined
36
+ ? h('span', {}, item.title)
37
+ : h('a', { class: 'cd-arrow-link', href: href(ctx, item.link) }, item.title),
38
+ ),
39
+ optionalText('p', 'cd-features__text', item.text),
45
40
  )
46
41
  }
47
42
 
48
43
  export function renderFeatureGrid(block: FeatureGridBlock, ctx: RenderContext): HtmlElement {
49
- const hasTitle = block.title !== undefined
50
- const itemTag = nestedHeadingTag('featureGrid', hasTitle)
51
- return h(
44
+ const tag = nestedHeadingTag('featureGrid', block.title !== undefined)
45
+ const count = block.items.length
46
+ return section(
52
47
  'section',
53
- { class: 'cg-block cg-features', 'data-block': 'featureGrid' },
54
- hasTitle
55
- ? heading(
56
- blockHeadingTag('featureGrid') ?? 'h2',
57
- { class: 'cg-features__title', 'data-field': 'title' },
58
- block.title ?? '',
59
- )
60
- : null,
48
+ 'featureGrid',
49
+ 'cd-features',
50
+ { 'data-count': String(count % 4 === 0 ? 4 : Math.min(count, 3)) },
51
+ 'div',
52
+ sectionHead('featureGrid', block.title),
61
53
  h(
62
54
  'ul',
63
- { class: 'cg-features__items' },
64
- block.items.map((item) => renderItem(item, ctx, itemTag)),
55
+ { class: 'cd-features__items' },
56
+ block.items.map((item) => entry(item, ctx, tag)),
65
57
  ),
66
58
  )
67
59
  }
@@ -1,39 +1,43 @@
1
1
  import type { GalleryBlock } from '@cogenta/blocks'
2
2
  import { type HtmlElement, h, image, type RenderContext } from '@cogenta/theme-kit'
3
+ import { section } from '../layout.js'
3
4
 
4
5
  /**
5
- * The carousel ships **no JavaScript**. It is a scroll-snapping list, which
6
- * gives touch swiping, trackpad scrolling and — because the list is
7
- * focusable and labelled — arrow-key scrolling, with none of the failure
8
- * modes of a scripted carousel.
6
+ * A set of screenshots or diagrams, each in the hairline frame.
7
+ *
8
+ * - `grid`: two or three columns, every frame cropped to the same 16:10 so
9
+ * the rows stay level.
10
+ * - `masonry`: three columns that keep each picture's own proportions.
11
+ * - `carousel`: one row that scrolls sideways inside the container, with
12
+ * scroll snapping and no script, named so a screen reader announces it as
13
+ * a scrollable group.
9
14
  */
10
15
  export function renderGallery(block: GalleryBlock, ctx: RenderContext): HtmlElement {
11
- const items = h(
12
- 'ul',
13
- { class: 'cg-gallery__items' },
14
- block.items.map((item) =>
15
- h(
16
- 'li',
17
- { class: 'cg-gallery__item' },
18
- image(ctx, item.media, { sizes: '(min-width: 45rem) 20rem, 60vw' }),
16
+ const carousel = block.layout === 'carousel'
17
+ return section(
18
+ 'div',
19
+ 'gallery',
20
+ 'cd-gallery',
21
+ { 'data-layout': block.layout, 'data-count': String(Math.min(block.items.length, 6)) },
22
+ 'div',
23
+ h(
24
+ 'ul',
25
+ {
26
+ class: 'cd-gallery__items',
27
+ ...(carousel
28
+ ? { role: 'region', 'aria-label': ctx.t('gallery.carousel'), tabindex: 0 }
29
+ : {}),
30
+ },
31
+ block.items.map((item) =>
32
+ h(
33
+ 'li',
34
+ { class: 'cd-gallery__item cd-frame' },
35
+ image(ctx, item.media, {
36
+ className: 'cd-gallery__image cd-frame__image',
37
+ sizes: '(min-width: 64rem) 25rem, (min-width: 40rem) 50vw, 100vw',
38
+ }),
39
+ ),
19
40
  ),
20
41
  ),
21
42
  )
22
-
23
- return h(
24
- 'section',
25
- { class: 'cg-block cg-gallery', 'data-block': 'gallery', 'data-layout': block.layout },
26
- block.layout === 'carousel'
27
- ? h(
28
- 'div',
29
- {
30
- class: 'cg-gallery__viewport',
31
- role: 'region',
32
- 'aria-label': ctx.t('gallery.carousel'),
33
- tabindex: '0',
34
- },
35
- items,
36
- )
37
- : items,
38
- )
39
43
  }
@@ -1,61 +1,57 @@
1
1
  import type { HeroBlock } from '@cogenta/blocks'
2
- import {
3
- actionList,
4
- blockHeadingTag,
5
- type HtmlElement,
6
- h,
7
- heading,
8
- image,
9
- type RenderContext,
10
- renderIcon,
11
- } from '@cogenta/theme-kit'
2
+ import { actionList, type HtmlElement, h, image, type RenderContext } from '@cogenta/theme-kit'
3
+ import { optionalText } from '../layout.js'
4
+ import { renderSearchForm } from '../search.js'
12
5
 
13
6
  /**
14
- * The hero carries the page's `h1` (contract B: `headingLevel: 'h1'`), so
15
- * `renderPage` relies on it to avoid emitting a second one.
7
+ * The opening of a documentation site: a clear statement and the search.
16
8
  *
17
- * A reference-docs hero: copy on the left, a decorative search-looking
18
- * prompt right under the actions (purely presentational — `aria-hidden`,
19
- * no `<input>`, no `<form>` — this theme ships zero client JavaScript and a
20
- * fake control that looked operable would be a worse failure than no
21
- * control at all), and an optional small illustration on the right rather
22
- * than a full-bleed image wash, matching the information-dense register the
23
- * aesthetic direction asks for.
9
+ * Left-aligned on the grid, never centred. An eyebrow, when there is one, is
10
+ * a quiet line above the title (a product and its version), never a badge.
11
+ * The title is short and set large on the first eight columns, the subtitle
12
+ * says in two sentences what the documentation covers, and under it the
13
+ * search field at its large size: a real `GET /search` form, the first thing
14
+ * a reader of documentation reaches for. The actions follow, the primary one
15
+ * as the small teal control and the rest as underlined arrow links.
16
+ *
17
+ * Media, when an editor adds one, is a diagram or a screenshot on the last
18
+ * five columns in the hairline frame, loaded eagerly: it is above the fold by
19
+ * construction. Nothing decorative stands in for it when there is none.
24
20
  */
25
21
  export function renderHero(block: HeroBlock, ctx: RenderContext): HtmlElement {
26
- const tag = blockHeadingTag('hero') ?? 'h1'
22
+ const actions = actionList(ctx, block.actions, ctx.t('hero.actions'))
23
+ const media = block.media !== undefined
24
+
27
25
  return h(
28
26
  'section',
29
- { class: 'cg-block cg-hero', 'data-block': 'hero' },
27
+ { class: 'cd-section cd-hero', 'data-block': 'hero', 'data-media': media ? 'true' : 'false' },
30
28
  h(
31
29
  'div',
32
- { class: 'cg-hero__copy' },
33
- block.eyebrow === undefined
34
- ? null
35
- : h('p', { class: 'cg-hero__eyebrow', 'data-field': 'eyebrow' }, block.eyebrow),
36
- heading(tag, { class: 'cg-hero__title', 'data-field': 'title' }, block.title),
37
- block.subtitle === undefined
38
- ? null
39
- : h('p', { class: 'cg-hero__subtitle', 'data-field': 'subtitle' }, block.subtitle),
40
- actionList(ctx, block.actions, ctx.t('hero.actions')),
30
+ { class: 'cd-container cd-hero__inner' },
41
31
  h(
42
32
  'div',
43
- { class: 'cg-hero__search', 'aria-hidden': 'true' },
44
- renderIcon('search', { className: 'cg-hero__search-icon' }),
45
- h('span', { class: 'cg-hero__search-text' }, 'Search the docs…'),
46
- h('kbd', { class: 'cg-hero__search-key' }, '/'),
47
- ),
48
- ),
49
- block.media === undefined
50
- ? null
51
- : h(
33
+ { class: 'cd-hero__copy' },
34
+ optionalText('p', 'cd-hero__eyebrow', block.eyebrow, { 'data-field': 'eyebrow' }),
35
+ h('h1', { class: 'cd-hero__title', 'data-field': 'title' }, block.title),
36
+ optionalText('p', 'cd-hero__subtitle', block.subtitle, { 'data-field': 'subtitle' }),
37
+ h(
52
38
  'div',
53
- { class: 'cg-hero__panel' },
54
- image(ctx, block.media, {
55
- className: 'cg-hero__media',
56
- loading: 'eager',
57
- sizes: '(min-width: 64rem) 32vw, 100vw',
58
- }),
39
+ { class: 'cd-hero__search' },
40
+ renderSearchForm(ctx.locale, 'cd-search-hero', 'large'),
59
41
  ),
42
+ actions === null ? null : h('div', { class: 'cd-hero__actions' }, actions),
43
+ ),
44
+ block.media === undefined
45
+ ? null
46
+ : h(
47
+ 'figure',
48
+ { class: 'cd-hero__media cd-frame' },
49
+ image(ctx, block.media, {
50
+ className: 'cd-hero__image cd-frame__image',
51
+ loading: 'eager',
52
+ sizes: '(min-width: 64rem) 30rem, 100vw',
53
+ }),
54
+ ),
55
+ ),
60
56
  )
61
57
  }
@@ -1,31 +1,31 @@
1
- import type { LogoStripBlock, LogoStripItem } from '@cogenta/blocks'
1
+ import type { LogoStripBlock } from '@cogenta/blocks'
2
2
  import { type HtmlElement, h, image, type RenderContext } from '@cogenta/theme-kit'
3
+ import { optionalText, section } from '../layout.js'
3
4
 
4
5
  /**
5
- * `blocks@2.0` (RFC 0001). The lighter-weight "as seen in" row next to
6
- * `logos`: no per-logo name or link, so no accessible-name field to
7
- * require — each image's own alt text (set once, in the media library)
8
- * names it.
6
+ * A strip of wordmarks ("Runs in production at"): the caption on its own
7
+ * line above, then one row of marks at one height in grey, wrapping to three
8
+ * and then two to a row on smaller screens. A hairline above separates the
9
+ * strip from what precedes it.
9
10
  */
10
- function renderItem(item: LogoStripItem, _ctx: RenderContext): HtmlElement {
11
- return h(
12
- 'li',
13
- { class: 'cg-logo-band__item' },
14
- image(_ctx, item.media, { className: 'cg-logo-band__image', variant: { fit: 'contain' } }),
15
- )
16
- }
17
-
18
11
  export function renderLogoStrip(block: LogoStripBlock, ctx: RenderContext): HtmlElement {
19
- return h(
20
- 'figure',
21
- { class: 'cg-block cg-logo-band', 'data-block': 'logoStrip' },
12
+ return section(
13
+ 'div',
14
+ 'logoStrip',
15
+ 'cd-strip',
16
+ { 'data-count': String(Math.min(block.logos.length, 6)) },
17
+ 'div',
18
+ optionalText('p', 'cd-strip__caption', block.caption, { 'data-field': 'caption' }),
22
19
  h(
23
20
  'ul',
24
- { class: 'cg-logo-band__items' },
25
- block.logos.map((item) => renderItem(item, ctx)),
21
+ { class: 'cd-strip__items' },
22
+ block.logos.map((logo) =>
23
+ h(
24
+ 'li',
25
+ { class: 'cd-strip__item' },
26
+ image(ctx, logo.media, { className: 'cd-strip__image cd-mark', sizes: '9rem' }),
27
+ ),
28
+ ),
26
29
  ),
27
- block.caption === undefined
28
- ? null
29
- : h('figcaption', { class: 'cg-logo-band__caption', 'data-field': 'caption' }, block.caption),
30
30
  )
31
31
  }
@@ -1,52 +1,42 @@
1
- import type { LogoItem, LogosBlock } from '@cogenta/blocks'
2
- import {
3
- blockHeadingTag,
4
- type HtmlElement,
5
- h,
6
- heading,
7
- image,
8
- type RenderContext,
9
- } from '@cogenta/theme-kit'
1
+ import type { LogosBlock } from '@cogenta/blocks'
2
+ import { type HtmlElement, h, image, type RenderContext } from '@cogenta/theme-kit'
3
+ import { section, sectionHead } from '../layout.js'
10
4
 
11
5
  /**
12
- * Contract B says the organisation's name **is** the accessible name of the
13
- * link. So the logo image carries it as alt text when the media entity has
14
- * none, and no visually hidden duplicate is added.
6
+ * Named organisations (the companies running the product, the platforms it
7
+ * integrates with) as a ruled grid: hairlines between cells, each wordmark in
8
+ * grey at one height, linked when the block gives an address. The
9
+ * organisation's name is the image's accessible name when the media library
10
+ * has no alt text.
11
+ *
12
+ * Wordmarks are shown in grey in light and inverted in dark, so a logo drawn
13
+ * in dark ink stays legible on the dark ground.
15
14
  */
16
- function renderItem(item: LogoItem, ctx: RenderContext): HtmlElement {
17
- const logo = image(ctx, item.media, {
18
- className: 'cg-logo__image',
19
- altFrom: item.name,
20
- variant: { fit: 'contain' },
21
- })
22
- return h(
23
- 'li',
24
- { class: 'cg-logo' },
25
- item.url === undefined
26
- ? logo
27
- : h(
28
- 'a',
29
- { class: 'cg-logo__link', href: ctx.link(item.url), rel: 'noopener noreferrer' },
30
- logo,
31
- ),
32
- )
33
- }
34
-
35
15
  export function renderLogos(block: LogosBlock, ctx: RenderContext): HtmlElement {
36
- return h(
16
+ return section(
37
17
  'section',
38
- { class: 'cg-block cg-logos', 'data-block': 'logos' },
39
- block.title === undefined
40
- ? null
41
- : heading(
42
- blockHeadingTag('logos') ?? 'h2',
43
- { class: 'cg-logos__title', 'data-field': 'title' },
44
- block.title,
45
- ),
18
+ 'logos',
19
+ 'cd-logos',
20
+ { 'data-count': String(Math.min(block.items.length, 6)) },
21
+ 'div',
22
+ sectionHead('logos', block.title),
46
23
  h(
47
24
  'ul',
48
- { class: 'cg-logos__items' },
49
- block.items.map((item) => renderItem(item, ctx)),
25
+ { class: 'cd-logos__items' },
26
+ block.items.map((item) => {
27
+ const mark = image(ctx, item.media, {
28
+ className: 'cd-logos__image cd-mark',
29
+ altFrom: item.name,
30
+ sizes: '10rem',
31
+ })
32
+ return h(
33
+ 'li',
34
+ { class: 'cd-logos__item' },
35
+ item.url === undefined
36
+ ? mark
37
+ : h('a', { class: 'cd-logos__link', href: ctx.link(item.url), rel: 'noopener' }, mark),
38
+ )
39
+ }),
50
40
  ),
51
41
  )
52
42
  }
@@ -1,36 +1,58 @@
1
1
  import type { MediaFigureBlock } from '@cogenta/blocks'
2
2
  import { aspectRatio, type HtmlElement, h, image, type RenderContext } from '@cogenta/theme-kit'
3
+ import { optionalText } from '../layout.js'
3
4
 
4
5
  /**
5
- * `<figure>`/`<figcaption>` rather than a div and a paragraph: the
6
- * association between the picture and its caption is then in the markup,
7
- * and a screen reader announces the caption as belonging to the image
8
- * instead of as loose text after it.
6
+ * A diagram, a screenshot or a photograph, in the hairline frame, with its
7
+ * caption and credit under it in the secondary ink.
8
+ *
9
+ * `align` (contract B) places the frame: `wide` takes the whole container
10
+ * (or the whole reading column on a documentation page), `center` the middle
11
+ * eight columns, `start` and `end` eight columns on either side, and `full`
12
+ * runs to the edges of the window, without a frame.
13
+ *
14
+ * `ratio` crops from the top edge, so an interface keeps its title bar; a
15
+ * focal point set in the media library still wins.
9
16
  */
17
+ const SIZES: Readonly<Record<string, string>> = {
18
+ full: '100vw',
19
+ wide: '(min-width: 80rem) 76rem, 100vw',
20
+ center: '(min-width: 64rem) 50rem, 100vw',
21
+ start: '(min-width: 64rem) 50rem, 100vw',
22
+ end: '(min-width: 64rem) 50rem, 100vw',
23
+ }
24
+
10
25
  export function renderMediaFigure(block: MediaFigureBlock, ctx: RenderContext): HtmlElement {
26
+ const align = block.align ?? 'wide'
11
27
  const ratio = aspectRatio(block.ratio)
12
28
  const hasCaption = block.caption !== undefined || block.credit !== undefined
29
+
13
30
  return h(
14
- 'figure',
15
- {
16
- class: 'cg-block cg-figure',
17
- 'data-block': 'mediaFigure',
18
- 'data-align': block.align ?? 'center',
19
- style: ratio === undefined ? undefined : `--cg-ratio:${ratio}`,
20
- },
21
- image(ctx, block.media, {
22
- className: 'cg-figure__media',
23
- sizes: '(min-width: 45rem) 40rem, 100vw',
24
- }),
25
- hasCaption
26
- ? h(
27
- 'figcaption',
28
- { class: 'cg-figure__caption' },
29
- block.caption,
30
- block.credit === undefined
31
- ? null
32
- : h('span', { class: 'cg-figure__credit', 'data-field': 'credit' }, block.credit),
33
- )
34
- : null,
31
+ 'div',
32
+ { class: 'cd-section cd-figure', 'data-block': 'mediaFigure', 'data-align': align },
33
+ h(
34
+ 'figure',
35
+ { class: 'cd-container cd-figure__inner' },
36
+ h(
37
+ 'div',
38
+ {
39
+ class: align === 'full' ? 'cd-figure__media' : 'cd-figure__media cd-frame',
40
+ style: ratio === undefined ? undefined : `aspect-ratio:${ratio}`,
41
+ 'data-ratio': ratio === undefined ? 'original' : 'fixed',
42
+ },
43
+ image(ctx, block.media, {
44
+ className: 'cd-figure__image cd-frame__image',
45
+ sizes: SIZES[align] ?? '100vw',
46
+ }),
47
+ ),
48
+ hasCaption
49
+ ? h(
50
+ 'figcaption',
51
+ { class: 'cd-figure__caption' },
52
+ optionalText('span', 'cd-figure__text', block.caption, { 'data-field': 'caption' }),
53
+ optionalText('span', 'cd-figure__credit', block.credit, { 'data-field': 'credit' }),
54
+ )
55
+ : null,
56
+ ),
35
57
  )
36
58
  }