@cogenta/theme-canonical 0.1.0

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 (126) hide show
  1. package/dist/src/index.d.ts +21 -0
  2. package/dist/src/index.d.ts.map +1 -0
  3. package/dist/src/index.js +21 -0
  4. package/dist/src/index.js.map +1 -0
  5. package/dist/src/render/actions.d.ts +17 -0
  6. package/dist/src/render/actions.d.ts.map +1 -0
  7. package/dist/src/render/actions.js +43 -0
  8. package/dist/src/render/actions.js.map +1 -0
  9. package/dist/src/render/blocks/collection-list.d.ts +16 -0
  10. package/dist/src/render/blocks/collection-list.d.ts.map +1 -0
  11. package/dist/src/render/blocks/collection-list.js +54 -0
  12. package/dist/src/render/blocks/collection-list.js.map +1 -0
  13. package/dist/src/render/blocks/cta.d.ts +5 -0
  14. package/dist/src/render/blocks/cta.d.ts.map +1 -0
  15. package/dist/src/render/blocks/cta.js +10 -0
  16. package/dist/src/render/blocks/cta.js.map +1 -0
  17. package/dist/src/render/blocks/embed.d.ts +5 -0
  18. package/dist/src/render/blocks/embed.d.ts.map +1 -0
  19. package/dist/src/render/blocks/embed.js +88 -0
  20. package/dist/src/render/blocks/embed.js.map +1 -0
  21. package/dist/src/render/blocks/faq.d.ts +5 -0
  22. package/dist/src/render/blocks/faq.d.ts.map +1 -0
  23. package/dist/src/render/blocks/faq.js +21 -0
  24. package/dist/src/render/blocks/faq.js.map +1 -0
  25. package/dist/src/render/blocks/feature-grid.d.ts +5 -0
  26. package/dist/src/render/blocks/feature-grid.d.ts.map +1 -0
  27. package/dist/src/render/blocks/feature-grid.js +27 -0
  28. package/dist/src/render/blocks/feature-grid.js.map +1 -0
  29. package/dist/src/render/blocks/gallery.d.ts +15 -0
  30. package/dist/src/render/blocks/gallery.d.ts.map +1 -0
  31. package/dist/src/render/blocks/gallery.js +28 -0
  32. package/dist/src/render/blocks/gallery.js.map +1 -0
  33. package/dist/src/render/blocks/hero.d.ts +12 -0
  34. package/dist/src/render/blocks/hero.d.ts.map +1 -0
  35. package/dist/src/render/blocks/hero.js +25 -0
  36. package/dist/src/render/blocks/hero.js.map +1 -0
  37. package/dist/src/render/blocks/logos.d.ts +5 -0
  38. package/dist/src/render/blocks/logos.d.ts.map +1 -0
  39. package/dist/src/render/blocks/logos.js +25 -0
  40. package/dist/src/render/blocks/logos.js.map +1 -0
  41. package/dist/src/render/blocks/media-figure.d.ts +15 -0
  42. package/dist/src/render/blocks/media-figure.d.ts.map +1 -0
  43. package/dist/src/render/blocks/media-figure.js +30 -0
  44. package/dist/src/render/blocks/media-figure.js.map +1 -0
  45. package/dist/src/render/blocks/prose.d.ts +10 -0
  46. package/dist/src/render/blocks/prose.d.ts.map +1 -0
  47. package/dist/src/render/blocks/prose.js +11 -0
  48. package/dist/src/render/blocks/prose.js.map +1 -0
  49. package/dist/src/render/blocks/quote.d.ts +14 -0
  50. package/dist/src/render/blocks/quote.d.ts.map +1 -0
  51. package/dist/src/render/blocks/quote.js +25 -0
  52. package/dist/src/render/blocks/quote.js.map +1 -0
  53. package/dist/src/render/blocks/stats.d.ts +5 -0
  54. package/dist/src/render/blocks/stats.d.ts.map +1 -0
  55. package/dist/src/render/blocks/stats.js +23 -0
  56. package/dist/src/render/blocks/stats.js.map +1 -0
  57. package/dist/src/render/entry.d.ts +7 -0
  58. package/dist/src/render/entry.d.ts.map +1 -0
  59. package/dist/src/render/entry.js +35 -0
  60. package/dist/src/render/entry.js.map +1 -0
  61. package/dist/src/render/heading.d.ts +14 -0
  62. package/dist/src/render/heading.d.ts.map +1 -0
  63. package/dist/src/render/heading.js +34 -0
  64. package/dist/src/render/heading.js.map +1 -0
  65. package/dist/src/render/html.d.ts +44 -0
  66. package/dist/src/render/html.d.ts.map +1 -0
  67. package/dist/src/render/html.js +96 -0
  68. package/dist/src/render/html.js.map +1 -0
  69. package/dist/src/render/media.d.ts +32 -0
  70. package/dist/src/render/media.d.ts.map +1 -0
  71. package/dist/src/render/media.js +42 -0
  72. package/dist/src/render/media.js.map +1 -0
  73. package/dist/src/render/render-block.d.ts +26 -0
  74. package/dist/src/render/render-block.d.ts.map +1 -0
  75. package/dist/src/render/render-block.js +66 -0
  76. package/dist/src/render/render-block.js.map +1 -0
  77. package/dist/src/render/rich-text.d.ts +5 -0
  78. package/dist/src/render/rich-text.d.ts.map +1 -0
  79. package/dist/src/render/rich-text.js +105 -0
  80. package/dist/src/render/rich-text.js.map +1 -0
  81. package/dist/src/theme-contract.d.ts +165 -0
  82. package/dist/src/theme-contract.d.ts.map +1 -0
  83. package/dist/src/theme-contract.js +21 -0
  84. package/dist/src/theme-contract.js.map +1 -0
  85. package/dist/theme.config.d.ts +31 -0
  86. package/dist/theme.config.d.ts.map +1 -0
  87. package/dist/theme.config.js +42 -0
  88. package/dist/theme.config.js.map +1 -0
  89. package/package.json +57 -0
  90. package/src/blocks/CollectionList.astro +20 -0
  91. package/src/blocks/Cta.astro +11 -0
  92. package/src/blocks/Embed.astro +18 -0
  93. package/src/blocks/Faq.astro +16 -0
  94. package/src/blocks/FeatureGrid.astro +11 -0
  95. package/src/blocks/Gallery.astro +17 -0
  96. package/src/blocks/Hero.astro +25 -0
  97. package/src/blocks/Logos.astro +11 -0
  98. package/src/blocks/MediaFigure.astro +11 -0
  99. package/src/blocks/Prose.astro +11 -0
  100. package/src/blocks/Quote.astro +11 -0
  101. package/src/blocks/Stats.astro +11 -0
  102. package/src/components/BlockZone.astro +62 -0
  103. package/src/index.ts +20 -0
  104. package/src/layouts/Base.astro +63 -0
  105. package/src/render/actions.ts +57 -0
  106. package/src/render/blocks/collection-list.ts +98 -0
  107. package/src/render/blocks/cta.ts +17 -0
  108. package/src/render/blocks/embed.ts +111 -0
  109. package/src/render/blocks/faq.ts +42 -0
  110. package/src/render/blocks/feature-grid.ts +54 -0
  111. package/src/render/blocks/gallery.ts +49 -0
  112. package/src/render/blocks/hero.ts +42 -0
  113. package/src/render/blocks/logos.ts +45 -0
  114. package/src/render/blocks/media-figure.ts +42 -0
  115. package/src/render/blocks/prose.ts +17 -0
  116. package/src/render/blocks/quote.ts +39 -0
  117. package/src/render/blocks/stats.ts +41 -0
  118. package/src/render/entry.ts +40 -0
  119. package/src/render/heading.ts +44 -0
  120. package/src/render/html.ts +122 -0
  121. package/src/render/media.ts +62 -0
  122. package/src/render/render-block.ts +97 -0
  123. package/src/render/rich-text.ts +150 -0
  124. package/src/styles/theme.css +606 -0
  125. package/src/theme-contract.ts +159 -0
  126. package/tokens.json +36 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rich-text.d.ts","sourceRoot":"","sources":["../../../src/render/rich-text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,gBAAgB,EAAsB,MAAM,iBAAiB,CAAA;AAC3F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAE,KAAK,QAAQ,EAAW,MAAM,WAAW,CAAA;AA+GlD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,aAAa,EAClB,QAAQ,EAAE,gBAAgB,GACzB,SAAS,QAAQ,EAAE,CAiCrB"}
@@ -0,0 +1,105 @@
1
+ import { h, text } from './html.js';
2
+ import { image } from './media.js';
3
+ const KNOWN_MARKS = new Map([
4
+ ['strong', 'strong'],
5
+ ['em', 'em'],
6
+ ['code', 'code'],
7
+ ]);
8
+ function applyMark(ctx, mark, markDefs, child) {
9
+ const tag = KNOWN_MARKS.get(mark);
10
+ if (tag !== undefined)
11
+ return h(tag, {}, child);
12
+ const definition = markDefs.find((candidate) => candidate._key === mark);
13
+ if (definition === undefined) {
14
+ // An annotation this theme does not know stays text. Guessing markup for it
15
+ // is how a mark becomes an injection vector.
16
+ return child;
17
+ }
18
+ if (definition._type === 'link') {
19
+ return h('a', { href: ctx.link(definition.href), rel: definition.rel }, child);
20
+ }
21
+ return h('a', { href: ctx.link({ collection: definition.collection, id: definition.id }) }, child);
22
+ }
23
+ function renderSpan(ctx, span, markDefs) {
24
+ let node = text(span.text);
25
+ // Applied in reverse so the first mark ends up outermost, which keeps
26
+ // `['link', 'strong']` rendering as a link containing bold text.
27
+ for (const mark of [...span.marks].reverse()) {
28
+ node = applyMark(ctx, mark, markDefs, node);
29
+ }
30
+ return node;
31
+ }
32
+ function renderChildren(ctx, block) {
33
+ return block.children.map((span) => renderSpan(ctx, span, block.markDefs));
34
+ }
35
+ function isListItem(node) {
36
+ return node !== undefined && node._type === 'block' && node.listItem !== undefined;
37
+ }
38
+ function levelOf(node) {
39
+ return node.level ?? 1;
40
+ }
41
+ /**
42
+ * Builds one list, and every list nested inside it, from a run of consecutive
43
+ * list items. A nested list lives *inside* the preceding `<li>` — putting it
44
+ * beside one produces a list whose items do not match what is announced.
45
+ */
46
+ function buildList(ctx, nodes, start, level) {
47
+ const first = nodes[start];
48
+ if (!isListItem(first))
49
+ return { node: text(''), next: start + 1 };
50
+ const kind = first.listItem;
51
+ const items = [];
52
+ let index = start;
53
+ while (index < nodes.length) {
54
+ const node = nodes[index];
55
+ if (!isListItem(node))
56
+ break;
57
+ const nodeLevel = levelOf(node);
58
+ if (nodeLevel < level || node.listItem !== kind)
59
+ break;
60
+ if (nodeLevel > level) {
61
+ const nested = buildList(ctx, nodes, index, nodeLevel);
62
+ const last = items.pop();
63
+ items.push(last === undefined || last.kind !== 'element'
64
+ ? h('li', {}, nested.node)
65
+ : h('li', last.attrs, ...last.children, nested.node));
66
+ index = nested.next;
67
+ continue;
68
+ }
69
+ items.push(h('li', {}, ...renderChildren(ctx, node)));
70
+ index += 1;
71
+ }
72
+ return { node: h(kind === 'number' ? 'ol' : 'ul', {}, items), next: index };
73
+ }
74
+ function renderTextBlock(ctx, block) {
75
+ const children = renderChildren(ctx, block);
76
+ if (block.style === 'blockquote')
77
+ return h('blockquote', {}, h('p', {}, ...children));
78
+ if (block.style === 'normal')
79
+ return h('p', {}, ...children);
80
+ return h(block.style, {}, ...children);
81
+ }
82
+ export function renderRichText(ctx, document) {
83
+ const out = [];
84
+ let index = 0;
85
+ while (index < document.length) {
86
+ const node = document[index];
87
+ if (node === undefined)
88
+ break;
89
+ if (isListItem(node)) {
90
+ const list = buildList(ctx, document, index, levelOf(node));
91
+ out.push(list.node);
92
+ index = list.next;
93
+ continue;
94
+ }
95
+ if (node._type === 'media') {
96
+ out.push(h('figure', { class: 'cg-prose__figure' }, image(ctx, node.id, { sizes: '(min-width: 45rem) 40rem, 100vw' }), node.caption === undefined ? null : h('figcaption', {}, node.caption)));
97
+ index += 1;
98
+ continue;
99
+ }
100
+ out.push(renderTextBlock(ctx, node));
101
+ index += 1;
102
+ }
103
+ return out;
104
+ }
105
+ //# sourceMappingURL=rich-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rich-text.js","sourceRoot":"","sources":["../../../src/render/rich-text.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Contract D — Thème, frozen at `theme@1.0` on 2026-08-13.
3
+ *
4
+ * TEMPORARY HOME. These types and `defineTheme` belong to `@cogenta/render`,
5
+ * which is being written in parallel (tasks 1–3 of L3). They live here so the
6
+ * canonical theme can be written, typechecked and tested today, and must be
7
+ * replaced by an import from `@cogenta/render` at merge time — not duplicated.
8
+ *
9
+ * Nothing below adds to `ctx`. Two shapes the contract names but does not spell
10
+ * out — `MediaReference` and `ContentEntry` — are given the narrowest reading
11
+ * that matches what is already frozen elsewhere; both are flagged in the code.
12
+ */
13
+ /**
14
+ * Contract B stores a media field as the media library's identifier, never as
15
+ * an URL and never as a rendition (`f.media` in `@cogenta/blocks`). So a
16
+ * reference the theme can hold is that identifier.
17
+ */
18
+ export type MediaReference = string;
19
+ export interface ImageOptions {
20
+ readonly width?: number;
21
+ readonly height?: number;
22
+ readonly format?: 'avif' | 'webp' | 'jpeg' | 'png';
23
+ readonly fit?: 'cover' | 'contain';
24
+ }
25
+ export interface ImageSource {
26
+ readonly src: string;
27
+ readonly srcset: string;
28
+ readonly width: number;
29
+ readonly height: number;
30
+ /** Alt text and focal point come from the media entity, never invented here. */
31
+ readonly alt: string;
32
+ readonly focal: {
33
+ readonly x: number;
34
+ readonly y: number;
35
+ } | null;
36
+ }
37
+ /**
38
+ * Contract A, "Champs système". A theme only ever sees published entries — the
39
+ * read token carries the `public` role — so `status` is narrow in practice but
40
+ * kept whole here rather than guessed at.
41
+ *
42
+ * The schema-defined fields are open: which of them exist depends on the
43
+ * collection, and a theme must not assume one is present. `entryTitle` in
44
+ * `src/render/entry.ts` is the only place that reaches for one.
45
+ */
46
+ export interface ContentEntry {
47
+ readonly id: string;
48
+ readonly collection: string;
49
+ readonly locale: string;
50
+ readonly status: 'draft' | 'scheduled' | 'published' | 'archived';
51
+ readonly [field: string]: unknown;
52
+ }
53
+ export interface QueryRequest {
54
+ readonly collection: string;
55
+ readonly filter?: Readonly<Record<string, unknown>>;
56
+ readonly sort?: {
57
+ readonly field: string;
58
+ readonly direction: 'asc' | 'desc';
59
+ };
60
+ readonly limit?: number;
61
+ readonly cursor?: string;
62
+ }
63
+ export interface Page<T> {
64
+ readonly items: readonly T[];
65
+ readonly nextCursor: string | null;
66
+ }
67
+ export interface ContentClient {
68
+ entry(collection: string, id: string): Promise<ContentEntry | null>;
69
+ byPath(path: string): Promise<ContentEntry | null>;
70
+ list(request: QueryRequest): Promise<Page<ContentEntry>>;
71
+ }
72
+ export type LinkTargetInput = {
73
+ collection: string;
74
+ id: string;
75
+ } | {
76
+ path: string;
77
+ } | string;
78
+ export interface RenderContext {
79
+ readonly site: {
80
+ readonly name: string;
81
+ readonly url: string;
82
+ readonly locales: readonly string[];
83
+ readonly defaultLocale: string;
84
+ };
85
+ /** The locale being rendered. */
86
+ readonly locale: string;
87
+ /** The URL being rendered, already resolved. */
88
+ readonly url: URL;
89
+ /** Translation. An unknown key returns the key, never an empty string. */
90
+ t(key: string, values?: Readonly<Record<string, string | number>>): string;
91
+ /** Image variants. Returns what a responsive `<img>` needs, nothing more. */
92
+ image(media: MediaReference, options?: ImageOptions): ImageSource;
93
+ /** URL of an entry, of a path, or of an external target. Locale-aware. */
94
+ link(target: LinkTargetInput): string;
95
+ /** Read-only content access. The only door to data a theme has. */
96
+ readonly content: ContentClient;
97
+ }
98
+ /** Contract D, "Besoins runtime". */
99
+ export type ThemeRuntime = 'static' | 'server' | 'edge';
100
+ export interface ThemeManifest {
101
+ readonly name: string;
102
+ readonly version: string;
103
+ /** Version of the theme contract. */
104
+ readonly engine: string;
105
+ /** Version of the block vocabulary supported. */
106
+ readonly blocks: string;
107
+ /** Every vocabulary block the theme renders. A gap fails installation. */
108
+ readonly implements: readonly string[];
109
+ /** Content types expected, or `'*'`. */
110
+ readonly collections: readonly string[] | '*';
111
+ readonly runtime: ThemeRuntime;
112
+ /** Path to the default skin, relative to the manifest. */
113
+ readonly tokens: string;
114
+ readonly a11y: {
115
+ readonly verified: 'WCAG-2.2-AA' | 'WCAG-2.2-AAA' | 'none';
116
+ };
117
+ }
118
+ /**
119
+ * Identity function. It exists for the inference and for the single place a
120
+ * manifest is recognised, not to validate: validation happens at installation,
121
+ * on the theme's sources, where a lie can actually be caught.
122
+ */
123
+ export declare function defineTheme<const M extends ThemeManifest>(manifest: M): M;
124
+ /**
125
+ * Contract D, "Tokens de skin". The set is **closed and complete**: a skin that
126
+ * omits a token is refused. Rendered as `--cogenta-<group>-<name>` into a single
127
+ * stylesheet, so changing skin rewrites one file and needs no build.
128
+ */
129
+ export interface SkinTokens {
130
+ readonly color: {
131
+ readonly bg: string;
132
+ readonly fg: string;
133
+ readonly accent: string;
134
+ readonly accentFg: string;
135
+ readonly muted: string;
136
+ readonly mutedFg: string;
137
+ readonly border: string;
138
+ };
139
+ readonly font: {
140
+ readonly sans: string;
141
+ readonly serif: string;
142
+ readonly mono: string;
143
+ readonly scale: number;
144
+ readonly baseSize: string;
145
+ };
146
+ readonly space: {
147
+ readonly unit: string;
148
+ readonly density: 'compact' | 'comfortable' | 'spacious';
149
+ };
150
+ readonly radius: {
151
+ readonly sm: string;
152
+ readonly md: string;
153
+ readonly lg: string;
154
+ };
155
+ readonly motion: {
156
+ readonly duration: string;
157
+ readonly easing: string;
158
+ readonly reduced: boolean;
159
+ };
160
+ readonly shadow: {
161
+ readonly sm: string;
162
+ readonly md: string;
163
+ };
164
+ }
165
+ //# sourceMappingURL=theme-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-contract.d.ts","sourceRoot":"","sources":["../../src/theme-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAA;AAEnC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;IAClD,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,gFAAgF;IAChF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAClE;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,CAAA;IACjE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACnD,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,CAAA;IAC9E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,IAAI,CAAC,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CACnC;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAA;IACnE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAA;IAClD,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAA;CACzD;AAED,MAAM,MAAM,eAAe,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAA;AAE5F,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE;QACb,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;QACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;QACpB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;QACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;KAC/B,CAAA;IACD,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,gDAAgD;IAChD,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IAEjB,0EAA0E;IAC1E,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,GAAG,MAAM,CAAA;IAE1E,6EAA6E;IAC7E,KAAK,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,WAAW,CAAA;IAEjE,0EAA0E;IAC1E,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAAA;IAErC,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;CAChC;AAED,qCAAqC;AACrC,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;AAEvD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,qCAAqC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,wCAAwC;IACxC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,CAAA;IAC7C,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAC9B,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,aAAa,GAAG,cAAc,GAAG,MAAM,CAAA;KAAE,CAAA;CAC9E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,CAAC,CAAC,SAAS,aAAa,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAEzE;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;QACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KACxB,CAAA;IACD,QAAQ,CAAC,IAAI,EAAE;QACb,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;QACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;QACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAC1B,CAAA;IACD,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;QACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,GAAG,aAAa,GAAG,UAAU,CAAA;KACzD,CAAA;IACD,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;IAClF,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAA;IAClG,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9D"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Contract D — Thème, frozen at `theme@1.0` on 2026-08-13.
3
+ *
4
+ * TEMPORARY HOME. These types and `defineTheme` belong to `@cogenta/render`,
5
+ * which is being written in parallel (tasks 1–3 of L3). They live here so the
6
+ * canonical theme can be written, typechecked and tested today, and must be
7
+ * replaced by an import from `@cogenta/render` at merge time — not duplicated.
8
+ *
9
+ * Nothing below adds to `ctx`. Two shapes the contract names but does not spell
10
+ * out — `MediaReference` and `ContentEntry` — are given the narrowest reading
11
+ * that matches what is already frozen elsewhere; both are flagged in the code.
12
+ */
13
+ /**
14
+ * Identity function. It exists for the inference and for the single place a
15
+ * manifest is recognised, not to validate: validation happens at installation,
16
+ * on the theme's sources, where a lie can actually be caught.
17
+ */
18
+ export function defineTheme(manifest) {
19
+ return manifest;
20
+ }
21
+ //# sourceMappingURL=theme-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-contract.js","sourceRoot":"","sources":["../../src/theme-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA6GH;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAgC,QAAW;IACpE,OAAO,QAAQ,CAAA;AACjB,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Contract D — Thème, `theme@1.0`.
3
+ *
4
+ * `implements` lists the twelve blocks of contract B in the order the contract
5
+ * lists them. A theme that omits one fails installation, which is what
6
+ * guarantees that switching theme never erases content.
7
+ *
8
+ * `runtime: 'static'` describes what *this theme* needs, not what every block
9
+ * on a page needs: `collectionList` is `runtime: 'server'` in the vocabulary
10
+ * and it is the block, not the theme, that makes a static build refuse. Saying
11
+ * `'server'` here would force every site using the canonical theme onto a
12
+ * server even when no page lists anything.
13
+ *
14
+ * `a11y.verified` is a claim this package has to keep true: the tests assert
15
+ * the heading outline, the mandatory `alt`, and the closed token set.
16
+ */
17
+ declare const _default: {
18
+ readonly name: "canonical";
19
+ readonly version: "1.0.0";
20
+ readonly engine: "^1.0.0";
21
+ readonly blocks: "^1.0.0";
22
+ readonly implements: readonly ["hero", "prose", "mediaFigure", "featureGrid", "cta", "gallery", "quote", "faq", "stats", "logos", "collectionList", "embed"];
23
+ readonly collections: readonly ["article", "page"];
24
+ readonly runtime: "static";
25
+ readonly tokens: "./tokens.json";
26
+ readonly a11y: {
27
+ readonly verified: "WCAG-2.2-AA";
28
+ };
29
+ };
30
+ export default _default;
31
+ //# sourceMappingURL=theme.config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.config.d.ts","sourceRoot":"","sources":["../theme.config.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG"}
@@ -0,0 +1,42 @@
1
+ import { defineTheme } from './src/theme-contract.js';
2
+ /**
3
+ * Contract D — Thème, `theme@1.0`.
4
+ *
5
+ * `implements` lists the twelve blocks of contract B in the order the contract
6
+ * lists them. A theme that omits one fails installation, which is what
7
+ * guarantees that switching theme never erases content.
8
+ *
9
+ * `runtime: 'static'` describes what *this theme* needs, not what every block
10
+ * on a page needs: `collectionList` is `runtime: 'server'` in the vocabulary
11
+ * and it is the block, not the theme, that makes a static build refuse. Saying
12
+ * `'server'` here would force every site using the canonical theme onto a
13
+ * server even when no page lists anything.
14
+ *
15
+ * `a11y.verified` is a claim this package has to keep true: the tests assert
16
+ * the heading outline, the mandatory `alt`, and the closed token set.
17
+ */
18
+ export default defineTheme({
19
+ name: 'canonical',
20
+ version: '1.0.0',
21
+ engine: '^1.0.0',
22
+ blocks: '^1.0.0',
23
+ implements: [
24
+ 'hero',
25
+ 'prose',
26
+ 'mediaFigure',
27
+ 'featureGrid',
28
+ 'cta',
29
+ 'gallery',
30
+ 'quote',
31
+ 'faq',
32
+ 'stats',
33
+ 'logos',
34
+ 'collectionList',
35
+ 'embed',
36
+ ],
37
+ collections: ['article', 'page'],
38
+ runtime: 'static',
39
+ tokens: './tokens.json',
40
+ a11y: { verified: 'WCAG-2.2-AA' },
41
+ });
42
+ //# sourceMappingURL=theme.config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.config.js","sourceRoot":"","sources":["../theme.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAErD;;;;;;;;;;;;;;;GAeG;AACH,eAAe,WAAW,CAAC;IACzB,IAAI,EAAE,WAAW;IACjB,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE;QACV,MAAM;QACN,OAAO;QACP,aAAa;QACb,aAAa;QACb,KAAK;QACL,SAAS;QACT,OAAO;QACP,KAAK;QACL,OAAO;QACP,OAAO;QACP,gBAAgB;QAChB,OAAO;KACR;IACD,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC;IAChC,OAAO,EAAE,QAAQ;IACjB,MAAM,EAAE,eAAe;IACvB,IAAI,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE;CAClC,CAAC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@cogenta/theme-canonical",
3
+ "version": "0.1.0",
4
+ "description": "The reference Cogenta theme: the twelve vocabulary blocks, rendered accessibly, with zero client JavaScript.",
5
+ "license": "MPL-2.0",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/src/index.d.ts",
11
+ "default": "./dist/src/index.js"
12
+ },
13
+ "./theme.config": {
14
+ "types": "./dist/theme.config.d.ts",
15
+ "default": "./dist/theme.config.js"
16
+ },
17
+ "./tokens.json": "./tokens.json",
18
+ "./blocks/*.astro": "./src/blocks/*.astro",
19
+ "./layouts/*.astro": "./src/layouts/*.astro",
20
+ "./components/*.astro": "./src/components/*.astro",
21
+ "./styles/theme.css": "./src/styles/theme.css"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "src",
26
+ "tokens.json"
27
+ ],
28
+ "engines": {
29
+ "node": ">=22.11.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "provenance": true
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/cogenta-cms/cogenta.git",
38
+ "directory": "packages/theme-canonical"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.json",
42
+ "typecheck": "tsc -p tsconfig.typecheck.json",
43
+ "test": "vitest run --passWithNoTests",
44
+ "test:integration": "vitest run --config vitest.integration.config.ts --passWithNoTests"
45
+ },
46
+ "dependencies": {
47
+ "@cogenta/blocks": "workspace:*"
48
+ },
49
+ "peerDependencies": {
50
+ "astro": "catalog:"
51
+ },
52
+ "devDependencies": {
53
+ "astro": "catalog:",
54
+ "typescript": "catalog:",
55
+ "vitest": "catalog:"
56
+ }
57
+ }
@@ -0,0 +1,20 @@
1
+ ---
2
+ import type { CollectionListBlock } from '@cogenta/blocks'
3
+ import { query, renderCollectionList } from '../render/blocks/collection-list.js'
4
+ import { serialize } from '../render/html.js'
5
+ import type { RenderContext } from '../theme-contract.js'
6
+
7
+ /**
8
+ * See `Hero.astro` for why the markup lives in `src/render/`.
9
+ *
10
+ * The only block of the twelve that reads data at render time — contract B
11
+ * marks it `runtime: 'server'`. The read goes through `ctx.content`, the
12
+ * read-only HTTP client the theme is handed (ADR-0016); the theme has no
13
+ * database connection and no secrets, and this is its single door to data.
14
+ */
15
+ const { block, ctx } = Astro.props as { block: CollectionListBlock; ctx: RenderContext }
16
+
17
+ const page = await ctx.content.list(query(block))
18
+ ---
19
+
20
+ <Fragment set:html={serialize(renderCollectionList(block, ctx, page.items))} />
@@ -0,0 +1,11 @@
1
+ ---
2
+ import type { CtaBlock } from '@cogenta/blocks'
3
+ import { renderCta } from '../render/blocks/cta.js'
4
+ import { serialize } from '../render/html.js'
5
+ import type { RenderContext } from '../theme-contract.js'
6
+
7
+ /** See `Hero.astro` for why the markup lives in `src/render/`. */
8
+ const { block, ctx } = Astro.props as { block: CtaBlock; ctx: RenderContext }
9
+ ---
10
+
11
+ <Fragment set:html={serialize(renderCta(block, ctx))} />
@@ -0,0 +1,18 @@
1
+ ---
2
+ import type { EmbedBlock } from '@cogenta/blocks'
3
+ import { renderEmbed } from '../render/blocks/embed.js'
4
+ import { serialize } from '../render/html.js'
5
+ import type { RenderContext } from '../theme-contract.js'
6
+
7
+ /**
8
+ * See `Hero.astro` for why the markup lives in `src/render/`.
9
+ *
10
+ * When `consentRequired` is true nothing third-party is requested: no iframe,
11
+ * no script, no preconnect, no poster image. A thumbnail fetched from the
12
+ * provider has already handed over the visitor's IP address, which is exactly
13
+ * the transfer the consent flag exists to gate.
14
+ */
15
+ const { block, ctx } = Astro.props as { block: EmbedBlock; ctx: RenderContext }
16
+ ---
17
+
18
+ <Fragment set:html={serialize(renderEmbed(block, ctx))} />
@@ -0,0 +1,16 @@
1
+ ---
2
+ import type { FaqBlock } from '@cogenta/blocks'
3
+ import { renderFaq } from '../render/blocks/faq.js'
4
+ import { serialize } from '../render/html.js'
5
+ import type { RenderContext } from '../theme-contract.js'
6
+
7
+ /**
8
+ * See `Hero.astro` for why the markup lives in `src/render/`.
9
+ *
10
+ * The accordion is `<details>`/`<summary>`: expanding, keyboard operation, the
11
+ * announced state and in-page search all come from the browser, at zero bytes.
12
+ */
13
+ const { block, ctx } = Astro.props as { block: FaqBlock; ctx: RenderContext }
14
+ ---
15
+
16
+ <Fragment set:html={serialize(renderFaq(block, ctx))} />
@@ -0,0 +1,11 @@
1
+ ---
2
+ import type { FeatureGridBlock } from '@cogenta/blocks'
3
+ import { renderFeatureGrid } from '../render/blocks/feature-grid.js'
4
+ import { serialize } from '../render/html.js'
5
+ import type { RenderContext } from '../theme-contract.js'
6
+
7
+ /** See `Hero.astro` for why the markup lives in `src/render/`. */
8
+ const { block, ctx } = Astro.props as { block: FeatureGridBlock; ctx: RenderContext }
9
+ ---
10
+
11
+ <Fragment set:html={serialize(renderFeatureGrid(block, ctx))} />
@@ -0,0 +1,17 @@
1
+ ---
2
+ import type { GalleryBlock } from '@cogenta/blocks'
3
+ import { renderGallery } from '../render/blocks/gallery.js'
4
+ import { serialize } from '../render/html.js'
5
+ import type { RenderContext } from '../theme-contract.js'
6
+
7
+ /**
8
+ * See `Hero.astro` for why the markup lives in `src/render/`.
9
+ *
10
+ * This is the one block the brief allows to ask for client JavaScript, in
11
+ * carousel mode. It does not: the carousel is a scroll-snapping list. No
12
+ * `client:*` directive appears anywhere in this theme.
13
+ */
14
+ const { block, ctx } = Astro.props as { block: GalleryBlock; ctx: RenderContext }
15
+ ---
16
+
17
+ <Fragment set:html={serialize(renderGallery(block, ctx))} />
@@ -0,0 +1,25 @@
1
+ ---
2
+ import type { HeroBlock } from '@cogenta/blocks'
3
+ import { renderHero } from '../render/blocks/hero.js'
4
+ import { serialize } from '../render/html.js'
5
+ import type { RenderContext } from '../theme-contract.js'
6
+
7
+ /**
8
+ * Contract D, "Interface d'un composant de bloc": `{ block, ctx }`, nothing else.
9
+ *
10
+ * The markup itself lives in `src/render/blocks/*.ts`. Two reasons, and they
11
+ * apply to all twelve of these files:
12
+ *
13
+ * - it is testable without an Astro toolchain, so the twelve blocks have real
14
+ * snapshot tests and the heading outline and `alt` rules are asserted on the
15
+ * output rather than on a reading of the source;
16
+ * - that module has no way to emit raw HTML at all, so a string arriving in a
17
+ * block field cannot become markup — which is rule R3 enforced by
18
+ * construction rather than by review.
19
+ *
20
+ * These components stay one call long so the two can never drift.
21
+ */
22
+ const { block, ctx } = Astro.props as { block: HeroBlock; ctx: RenderContext }
23
+ ---
24
+
25
+ <Fragment set:html={serialize(renderHero(block, ctx))} />
@@ -0,0 +1,11 @@
1
+ ---
2
+ import type { LogosBlock } from '@cogenta/blocks'
3
+ import { renderLogos } from '../render/blocks/logos.js'
4
+ import { serialize } from '../render/html.js'
5
+ import type { RenderContext } from '../theme-contract.js'
6
+
7
+ /** See `Hero.astro` for why the markup lives in `src/render/`. */
8
+ const { block, ctx } = Astro.props as { block: LogosBlock; ctx: RenderContext }
9
+ ---
10
+
11
+ <Fragment set:html={serialize(renderLogos(block, ctx))} />
@@ -0,0 +1,11 @@
1
+ ---
2
+ import type { MediaFigureBlock } from '@cogenta/blocks'
3
+ import { renderMediaFigure } from '../render/blocks/media-figure.js'
4
+ import { serialize } from '../render/html.js'
5
+ import type { RenderContext } from '../theme-contract.js'
6
+
7
+ /** See `Hero.astro` for why the markup lives in `src/render/`. */
8
+ const { block, ctx } = Astro.props as { block: MediaFigureBlock; ctx: RenderContext }
9
+ ---
10
+
11
+ <Fragment set:html={serialize(renderMediaFigure(block, ctx))} />
@@ -0,0 +1,11 @@
1
+ ---
2
+ import type { ProseBlock } from '@cogenta/blocks'
3
+ import { renderProse } from '../render/blocks/prose.js'
4
+ import { serialize } from '../render/html.js'
5
+ import type { RenderContext } from '../theme-contract.js'
6
+
7
+ /** See `Hero.astro` for why the markup lives in `src/render/`. */
8
+ const { block, ctx } = Astro.props as { block: ProseBlock; ctx: RenderContext }
9
+ ---
10
+
11
+ <Fragment set:html={serialize(renderProse(block, ctx))} />
@@ -0,0 +1,11 @@
1
+ ---
2
+ import type { QuoteBlock } from '@cogenta/blocks'
3
+ import { renderQuote } from '../render/blocks/quote.js'
4
+ import { serialize } from '../render/html.js'
5
+ import type { RenderContext } from '../theme-contract.js'
6
+
7
+ /** See `Hero.astro` for why the markup lives in `src/render/`. */
8
+ const { block, ctx } = Astro.props as { block: QuoteBlock; ctx: RenderContext }
9
+ ---
10
+
11
+ <Fragment set:html={serialize(renderQuote(block, ctx))} />
@@ -0,0 +1,11 @@
1
+ ---
2
+ import type { StatsBlock } from '@cogenta/blocks'
3
+ import { renderStats } from '../render/blocks/stats.js'
4
+ import { serialize } from '../render/html.js'
5
+ import type { RenderContext } from '../theme-contract.js'
6
+
7
+ /** See `Hero.astro` for why the markup lives in `src/render/`. */
8
+ const { block, ctx } = Astro.props as { block: StatsBlock; ctx: RenderContext }
9
+ ---
10
+
11
+ <Fragment set:html={serialize(renderStats(block, ctx))} />