@cogenta/theme-portfolio 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +373 -0
- package/dist/src/index.d.ts +17 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +20 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/render/blocks/accordion.d.ts +4 -0
- package/dist/src/render/blocks/accordion.d.ts.map +1 -0
- package/dist/src/render/blocks/accordion.js +24 -0
- package/dist/src/render/blocks/accordion.js.map +1 -0
- package/dist/src/render/blocks/collection-list.d.ts +25 -0
- package/dist/src/render/blocks/collection-list.d.ts.map +1 -0
- package/dist/src/render/blocks/collection-list.js +77 -0
- package/dist/src/render/blocks/collection-list.js.map +1 -0
- package/dist/src/render/blocks/cta.d.ts +10 -0
- package/dist/src/render/blocks/cta.d.ts.map +1 -0
- package/dist/src/render/blocks/cta.js +16 -0
- package/dist/src/render/blocks/cta.js.map +1 -0
- package/dist/src/render/blocks/embed.d.ts +4 -0
- package/dist/src/render/blocks/embed.d.ts.map +1 -0
- package/dist/src/render/blocks/embed.js +88 -0
- package/dist/src/render/blocks/embed.js.map +1 -0
- package/dist/src/render/blocks/faq.d.ts +4 -0
- package/dist/src/render/blocks/faq.d.ts.map +1 -0
- package/dist/src/render/blocks/faq.js +23 -0
- package/dist/src/render/blocks/faq.js.map +1 -0
- package/dist/src/render/blocks/feature-grid.d.ts +4 -0
- package/dist/src/render/blocks/feature-grid.d.ts.map +1 -0
- package/dist/src/render/blocks/feature-grid.js +30 -0
- package/dist/src/render/blocks/feature-grid.js.map +1 -0
- package/dist/src/render/blocks/gallery.d.ts +18 -0
- package/dist/src/render/blocks/gallery.d.ts.map +1 -0
- package/dist/src/render/blocks/gallery.js +34 -0
- package/dist/src/render/blocks/gallery.js.map +1 -0
- package/dist/src/render/blocks/hero.d.ts +15 -0
- package/dist/src/render/blocks/hero.d.ts.map +1 -0
- package/dist/src/render/blocks/hero.js +31 -0
- package/dist/src/render/blocks/hero.js.map +1 -0
- package/dist/src/render/blocks/logo-strip.d.ts +4 -0
- package/dist/src/render/blocks/logo-strip.d.ts.map +1 -0
- package/dist/src/render/blocks/logo-strip.js +23 -0
- package/dist/src/render/blocks/logo-strip.js.map +1 -0
- package/dist/src/render/blocks/logos.d.ts +4 -0
- package/dist/src/render/blocks/logos.d.ts.map +1 -0
- package/dist/src/render/blocks/logos.js +28 -0
- package/dist/src/render/blocks/logos.js.map +1 -0
- package/dist/src/render/blocks/media-figure.d.ts +19 -0
- package/dist/src/render/blocks/media-figure.d.ts.map +1 -0
- package/dist/src/render/blocks/media-figure.js +36 -0
- package/dist/src/render/blocks/media-figure.js.map +1 -0
- package/dist/src/render/blocks/pricing-table.d.ts +4 -0
- package/dist/src/render/blocks/pricing-table.d.ts.map +1 -0
- package/dist/src/render/blocks/pricing-table.js +29 -0
- package/dist/src/render/blocks/pricing-table.js.map +1 -0
- package/dist/src/render/blocks/prose.d.ts +16 -0
- package/dist/src/render/blocks/prose.d.ts.map +1 -0
- package/dist/src/render/blocks/prose.js +17 -0
- package/dist/src/render/blocks/prose.js.map +1 -0
- package/dist/src/render/blocks/quote.d.ts +18 -0
- package/dist/src/render/blocks/quote.d.ts.map +1 -0
- package/dist/src/render/blocks/quote.js +31 -0
- package/dist/src/render/blocks/quote.js.map +1 -0
- package/dist/src/render/blocks/stat-counter.d.ts +4 -0
- package/dist/src/render/blocks/stat-counter.d.ts.map +1 -0
- package/dist/src/render/blocks/stat-counter.js +18 -0
- package/dist/src/render/blocks/stat-counter.js.map +1 -0
- package/dist/src/render/blocks/stats.d.ts +4 -0
- package/dist/src/render/blocks/stats.d.ts.map +1 -0
- package/dist/src/render/blocks/stats.js +22 -0
- package/dist/src/render/blocks/stats.js.map +1 -0
- package/dist/src/render/blocks/testimonial.d.ts +20 -0
- package/dist/src/render/blocks/testimonial.d.ts.map +1 -0
- package/dist/src/render/blocks/testimonial.js +29 -0
- package/dist/src/render/blocks/testimonial.js.map +1 -0
- package/dist/src/render/chrome.d.ts +3 -0
- package/dist/src/render/chrome.d.ts.map +1 -0
- package/dist/src/render/chrome.js +114 -0
- package/dist/src/render/chrome.js.map +1 -0
- package/dist/src/render/render-block.d.ts +12 -0
- package/dist/src/render/render-block.d.ts.map +1 -0
- package/dist/src/render/render-block.js +111 -0
- package/dist/src/render/render-block.js.map +1 -0
- package/dist/src/render/term-archive.d.ts +14 -0
- package/dist/src/render/term-archive.d.ts.map +1 -0
- package/dist/src/render/term-archive.js +36 -0
- package/dist/src/render/term-archive.js.map +1 -0
- package/dist/theme.config.d.ts +26 -0
- package/dist/theme.config.d.ts.map +1 -0
- package/dist/theme.config.js +56 -0
- package/dist/theme.config.js.map +1 -0
- package/package.json +57 -0
- package/src/index.ts +19 -0
- package/src/render/blocks/accordion.ts +64 -0
- package/src/render/blocks/collection-list.ts +181 -0
- package/src/render/blocks/cta.ts +37 -0
- package/src/render/blocks/embed.ts +111 -0
- package/src/render/blocks/faq.ts +54 -0
- package/src/render/blocks/feature-grid.ts +68 -0
- package/src/render/blocks/gallery.ts +55 -0
- package/src/render/blocks/hero.ts +55 -0
- package/src/render/blocks/logo-strip.ts +38 -0
- package/src/render/blocks/logos.ts +63 -0
- package/src/render/blocks/media-figure.ts +51 -0
- package/src/render/blocks/pricing-table.ts +69 -0
- package/src/render/blocks/prose.ts +22 -0
- package/src/render/blocks/quote.ts +48 -0
- package/src/render/blocks/stat-counter.ts +49 -0
- package/src/render/blocks/stats.ts +49 -0
- package/src/render/blocks/testimonial.ts +45 -0
- package/src/render/chrome.ts +142 -0
- package/src/render/render-block.ts +155 -0
- package/src/render/term-archive.ts +88 -0
- package/src/styles/archive.css +40 -0
- package/src/styles/base.css +1056 -0
- package/src/styles/blocks.css +1468 -0
- package/src/styles/theme.css +20 -0
- package/src/styles/tokens.css +363 -0
- package/tokens.json +36 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { type BlockRegistry, VOCABULARY_NAMES, type VocabularyBlock } from '@cogenta/blocks'
|
|
2
|
+
import {
|
|
3
|
+
type FetchedEntries,
|
|
4
|
+
type HtmlElement,
|
|
5
|
+
h,
|
|
6
|
+
type PageContent,
|
|
7
|
+
pageHasOwnHeading,
|
|
8
|
+
type RenderContext,
|
|
9
|
+
renderEntryHeader,
|
|
10
|
+
resolveBlockForRender,
|
|
11
|
+
withBlockKey,
|
|
12
|
+
withBlockVariant,
|
|
13
|
+
} from '@cogenta/theme-kit'
|
|
14
|
+
import { renderAccordion } from './blocks/accordion.js'
|
|
15
|
+
import { renderCollectionList } from './blocks/collection-list.js'
|
|
16
|
+
import { renderCta } from './blocks/cta.js'
|
|
17
|
+
import { renderEmbed } from './blocks/embed.js'
|
|
18
|
+
import { renderFaq } from './blocks/faq.js'
|
|
19
|
+
import { renderFeatureGrid } from './blocks/feature-grid.js'
|
|
20
|
+
import { renderGallery } from './blocks/gallery.js'
|
|
21
|
+
import { renderHero } from './blocks/hero.js'
|
|
22
|
+
import { renderLogoStrip } from './blocks/logo-strip.js'
|
|
23
|
+
import { renderLogos } from './blocks/logos.js'
|
|
24
|
+
import { renderMediaFigure } from './blocks/media-figure.js'
|
|
25
|
+
import { renderPricingTable } from './blocks/pricing-table.js'
|
|
26
|
+
import { renderProse } from './blocks/prose.js'
|
|
27
|
+
import { renderQuote } from './blocks/quote.js'
|
|
28
|
+
import { renderStatCounter } from './blocks/stat-counter.js'
|
|
29
|
+
import { renderStats } from './blocks/stats.js'
|
|
30
|
+
import { renderTestimonial } from './blocks/testimonial.js'
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `PageContent`/`FetchedEntries`/`pageHasOwnHeading`/`withBlockKey` are
|
|
34
|
+
* `@cogenta/theme-kit`'s own — re-exported here so this package's public
|
|
35
|
+
* surface names them, the same shape every theme package shares them under.
|
|
36
|
+
*/
|
|
37
|
+
export type { FetchedEntries, PageContent }
|
|
38
|
+
export { pageHasOwnHeading, renderEntryHeader, withBlockKey }
|
|
39
|
+
|
|
40
|
+
export function renderBlock(
|
|
41
|
+
block: VocabularyBlock,
|
|
42
|
+
ctx: RenderContext,
|
|
43
|
+
entries: FetchedEntries = {},
|
|
44
|
+
registry?: BlockRegistry,
|
|
45
|
+
): HtmlElement | null {
|
|
46
|
+
// `block`'s type says `VocabularyBlock`, but the value crossing this
|
|
47
|
+
// boundary from stored content is not always literally one of the shared
|
|
48
|
+
// vocabulary — resolving here is what turns an unimplemented theme-private
|
|
49
|
+
// block into its declared fallback instead of a silently blank slot.
|
|
50
|
+
const resolved = resolveBlockForRender(block, VOCABULARY_NAMES, registry)
|
|
51
|
+
if (resolved === null) return null
|
|
52
|
+
const known = resolved as unknown as VocabularyBlock
|
|
53
|
+
// `withBlockVariant` (blocks@2.0, RFC 0002) is applied once here, after
|
|
54
|
+
// dispatch, rather than inside each of the seventeen block renderers —
|
|
55
|
+
// `variant` is envelope data every block carries identically, exactly the
|
|
56
|
+
// same reasoning `withBlockKey` in `renderPage` already follows for `_key`.
|
|
57
|
+
return withBlockVariant(renderKnownBlock(known, ctx, entries), known.variant)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function renderKnownBlock(
|
|
61
|
+
known: VocabularyBlock,
|
|
62
|
+
ctx: RenderContext,
|
|
63
|
+
entries: FetchedEntries,
|
|
64
|
+
): HtmlElement | null {
|
|
65
|
+
switch (known._type) {
|
|
66
|
+
case 'hero':
|
|
67
|
+
return renderHero(known, ctx)
|
|
68
|
+
case 'prose':
|
|
69
|
+
return renderProse(known, ctx)
|
|
70
|
+
case 'mediaFigure':
|
|
71
|
+
return renderMediaFigure(known, ctx)
|
|
72
|
+
case 'featureGrid':
|
|
73
|
+
return renderFeatureGrid(known, ctx)
|
|
74
|
+
case 'cta':
|
|
75
|
+
return renderCta(known, ctx)
|
|
76
|
+
case 'gallery':
|
|
77
|
+
return renderGallery(known, ctx)
|
|
78
|
+
case 'quote':
|
|
79
|
+
return renderQuote(known, ctx)
|
|
80
|
+
case 'faq':
|
|
81
|
+
return renderFaq(known, ctx)
|
|
82
|
+
case 'stats':
|
|
83
|
+
return renderStats(known, ctx)
|
|
84
|
+
case 'logos':
|
|
85
|
+
return renderLogos(known, ctx)
|
|
86
|
+
case 'collectionList':
|
|
87
|
+
return renderCollectionList(known, ctx, entries[known._key] ?? [])
|
|
88
|
+
case 'embed':
|
|
89
|
+
return renderEmbed(known, ctx)
|
|
90
|
+
case 'testimonial':
|
|
91
|
+
return renderTestimonial(known, ctx)
|
|
92
|
+
case 'pricingTable':
|
|
93
|
+
return renderPricingTable(known, ctx)
|
|
94
|
+
case 'accordion':
|
|
95
|
+
return renderAccordion(known, ctx)
|
|
96
|
+
case 'statCounter':
|
|
97
|
+
return renderStatCounter(known, ctx)
|
|
98
|
+
case 'logoStrip':
|
|
99
|
+
return renderLogoStrip(known, ctx)
|
|
100
|
+
default: {
|
|
101
|
+
// Exhaustive over contract B's vocabulary: `known` is `never` here, so a
|
|
102
|
+
// block this package does not implement stops it compiling until it is.
|
|
103
|
+
const unreachable: never = known
|
|
104
|
+
void unreachable
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* `renderEntryHeader` (`theme@1.4`) draws the eyebrow/title/excerpt/meta/
|
|
112
|
+
* cover furniture for a page backed by a real content entry — a project —
|
|
113
|
+
* and already returns `null` for a page with no `entry` meta *and* for one
|
|
114
|
+
* whose blocks draw their own heading (a `hero`), so the bare
|
|
115
|
+
* `<h1 class="cg-page__title">` fallback below is the right markup in both
|
|
116
|
+
* of those cases and only those — never a double heading, and never a page
|
|
117
|
+
* with none at all. A page rendered before `theme@1.4` (`page.entry`
|
|
118
|
+
* absent) gets exactly the pre-1.4 markup, byte for byte.
|
|
119
|
+
*/
|
|
120
|
+
/**
|
|
121
|
+
* `withBlockKey` stamps `data-block-key` (the L16 builder's own hook); this
|
|
122
|
+
* theme additionally stamps a real `id`, the block's own `_key`, on the same
|
|
123
|
+
* element — `_key` is already unique within a page (contract B), so it
|
|
124
|
+
* doubles as a genuine in-page anchor. The header/footer's own single-page
|
|
125
|
+
* links (the `portfolio` blueprint's "Work"/"Services" nav) resolve to a
|
|
126
|
+
* real target this way, rather than a `#fragment` nothing on the page
|
|
127
|
+
* answers to.
|
|
128
|
+
*/
|
|
129
|
+
function withBlockAnchor(element: HtmlElement | null, key: string): HtmlElement | null {
|
|
130
|
+
if (element === null) return null
|
|
131
|
+
return { ...element, attrs: { ...element.attrs, id: key } }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function renderPage(
|
|
135
|
+
page: PageContent,
|
|
136
|
+
ctx: RenderContext,
|
|
137
|
+
entries: FetchedEntries = {},
|
|
138
|
+
registry?: BlockRegistry,
|
|
139
|
+
): HtmlElement {
|
|
140
|
+
const entryHeader = renderEntryHeader(page, ctx)
|
|
141
|
+
return h(
|
|
142
|
+
'main',
|
|
143
|
+
{ class: 'cg-main', id: 'cg-main' },
|
|
144
|
+
entryHeader,
|
|
145
|
+
entryHeader === null && !pageHasOwnHeading(page.blocks)
|
|
146
|
+
? h('h1', { class: 'cg-page__title' }, page.title)
|
|
147
|
+
: null,
|
|
148
|
+
page.blocks.map((block) =>
|
|
149
|
+
withBlockAnchor(
|
|
150
|
+
withBlockKey(renderBlock(block, ctx, entries, registry), block._key),
|
|
151
|
+
block._key,
|
|
152
|
+
),
|
|
153
|
+
),
|
|
154
|
+
)
|
|
155
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type HtmlElement, h, type TermArchiveInput } from '@cogenta/theme-kit'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The taxonomy-term archive (contract D `theme@1.3`), in this theme's own
|
|
5
|
+
* brutalist-editorial voice: the term set as a display heading with the
|
|
6
|
+
* taxonomy name as a kicker above it, and the entries as the same numbered
|
|
7
|
+
* `cg-entry` rows `collectionList` already renders here — an index, not a
|
|
8
|
+
* card grid.
|
|
9
|
+
*
|
|
10
|
+
* Reusing the block's own classes rather than inventing archive-only ones is
|
|
11
|
+
* deliberate: it is what makes an archive of work look like this theme's
|
|
12
|
+
* lists without a second stylesheet to keep in step.
|
|
13
|
+
*/
|
|
14
|
+
export function renderTermArchive(input: TermArchiveInput): HtmlElement {
|
|
15
|
+
const items = input.entries.map((entry, index) =>
|
|
16
|
+
h(
|
|
17
|
+
'li',
|
|
18
|
+
{ class: 'cg-entry' },
|
|
19
|
+
h(
|
|
20
|
+
'div',
|
|
21
|
+
{ class: 'cg-entry__body' },
|
|
22
|
+
h(
|
|
23
|
+
'span',
|
|
24
|
+
{ class: 'cg-entry__index', 'aria-hidden': 'true' },
|
|
25
|
+
String(index + 1).padStart(2, '0'),
|
|
26
|
+
),
|
|
27
|
+
h(
|
|
28
|
+
'h2',
|
|
29
|
+
{ class: 'cg-entry__title' },
|
|
30
|
+
entry.href === null
|
|
31
|
+
? entry.title
|
|
32
|
+
: h('a', { class: 'cg-entry__link', href: entry.href }, entry.title),
|
|
33
|
+
),
|
|
34
|
+
entry.publishedAt === null
|
|
35
|
+
? null
|
|
36
|
+
: h(
|
|
37
|
+
'time',
|
|
38
|
+
{ class: 'cg-entry__date', datetime: entry.publishedAt },
|
|
39
|
+
entry.publishedAt.slice(0, 10),
|
|
40
|
+
),
|
|
41
|
+
entry.summary === null ? null : h('p', { class: 'cg-entry__excerpt' }, entry.summary),
|
|
42
|
+
),
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return h(
|
|
47
|
+
'main',
|
|
48
|
+
{ class: 'cg-main cg-archive', id: 'cg-main' },
|
|
49
|
+
input.ancestors.length === 0
|
|
50
|
+
? null
|
|
51
|
+
: h(
|
|
52
|
+
'nav',
|
|
53
|
+
{ class: 'cg-archive__breadcrumb', 'aria-label': input.labels.breadcrumb },
|
|
54
|
+
h(
|
|
55
|
+
'ol',
|
|
56
|
+
{},
|
|
57
|
+
...input.ancestors.map((l) => h('li', {}, h('a', { href: l.href }, l.label))),
|
|
58
|
+
),
|
|
59
|
+
),
|
|
60
|
+
h('p', { class: 'cg-collection__title' }, input.taxonomyName),
|
|
61
|
+
h('h1', { class: 'cg-page__title' }, input.term.label),
|
|
62
|
+
input.children.length === 0
|
|
63
|
+
? null
|
|
64
|
+
: h(
|
|
65
|
+
'ul',
|
|
66
|
+
{ class: 'cg-archive__children', 'aria-label': input.labels.subterms },
|
|
67
|
+
...input.children.map((c) => h('li', {}, h('a', { href: c.href }, c.label))),
|
|
68
|
+
),
|
|
69
|
+
items.length === 0
|
|
70
|
+
? h('p', { class: 'cg-collection__empty' }, input.labels.empty)
|
|
71
|
+
: h('ul', { class: 'cg-collection__items' }, ...items),
|
|
72
|
+
pager(input),
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function pager(input: TermArchiveInput): HtmlElement | null {
|
|
77
|
+
if (input.page.previousHref === null && input.page.nextHref === null) return null
|
|
78
|
+
return h(
|
|
79
|
+
'nav',
|
|
80
|
+
{ class: 'cg-archive__pager', 'aria-label': input.labels.pagination },
|
|
81
|
+
input.page.previousHref === null
|
|
82
|
+
? null
|
|
83
|
+
: h('a', { rel: 'prev', href: input.page.previousHref }, input.labels.previous),
|
|
84
|
+
input.page.nextHref === null
|
|
85
|
+
? null
|
|
86
|
+
: h('a', { rel: 'next', href: input.page.nextHref }, input.labels.next),
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The taxonomy-term archive (contract D `theme@1.3`).
|
|
3
|
+
*
|
|
4
|
+
* Only the three things an archive adds beyond what `collectionList` already
|
|
5
|
+
* styles — a breadcrumb, a sub-term row, a prev/next pager. The entry list
|
|
6
|
+
* itself reuses the `cg-entry`/`cg-collection__items` rules, on purpose: an
|
|
7
|
+
* archive of articles must look like this theme's list of articles, and a
|
|
8
|
+
* second set of card rules is exactly how those two drift apart.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
.cg-archive {
|
|
12
|
+
max-inline-size: var(--cg-page);
|
|
13
|
+
margin-inline: auto;
|
|
14
|
+
padding: var(--cg-s10) var(--cg-gutter);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.cg-archive__breadcrumb ol,
|
|
18
|
+
.cg-archive__children {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-wrap: wrap;
|
|
21
|
+
gap: var(--cg-s3);
|
|
22
|
+
margin: 0 0 var(--cg-s4);
|
|
23
|
+
padding: 0;
|
|
24
|
+
list-style: none;
|
|
25
|
+
font-size: var(--cg-text-sm);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.cg-archive__children {
|
|
29
|
+
margin-block: var(--cg-s4) var(--cg-s8);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.cg-archive__pager {
|
|
33
|
+
display: flex;
|
|
34
|
+
justify-content: space-between;
|
|
35
|
+
gap: var(--cg-s4);
|
|
36
|
+
margin-block-start: var(--cg-s10);
|
|
37
|
+
padding-block-start: var(--cg-s5);
|
|
38
|
+
border-block-start: 1px solid var(--cg-line-soft);
|
|
39
|
+
font-weight: var(--cg-weight-semibold);
|
|
40
|
+
}
|