@port60/template-kit 0.21.0 → 1.0.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 (53) hide show
  1. package/README.md +42 -3
  2. package/bin/cli.mjs +10 -0
  3. package/package.json +4 -2
  4. package/src/commands/content.mjs +4 -3
  5. package/src/commands/create.mjs +11 -3
  6. package/src/commands/dev.mjs +9 -3
  7. package/src/commands/model.mjs +3 -3
  8. package/src/commands/packageCmd.mjs +1 -1
  9. package/src/commands/publish.mjs +1 -1
  10. package/src/commands/refresh.mjs +8 -3
  11. package/src/commands/release.mjs +22 -0
  12. package/src/commands/setupPreviews.mjs +19 -0
  13. package/src/commands/validate.mjs +1 -1
  14. package/src/lib/agentsMd.mjs +108 -162
  15. package/src/lib/designer-bridge.js +26 -0
  16. package/src/lib/designerPalette.mjs +62 -0
  17. package/src/lib/galleryPosters.mjs +105 -0
  18. package/src/lib/previewOptions.mjs +1 -1
  19. package/src/lib/releaseBundle.mjs +141 -0
  20. package/src/vendor/contract/v2/behaviours.json +346 -0
  21. package/src/vendor/contract/v2/content-bounds.json +117 -0
  22. package/src/vendor/contract/v2/content-model.json +766 -0
  23. package/src/vendor/contract/v2/context.json +1569 -0
  24. package/src/vendor/contract/v2/dialect.json +105 -0
  25. package/src/vendor/contract/v2/fonts.json +910 -0
  26. package/src/vendor/contract/v2/imagery.json +32 -0
  27. package/src/vendor/contract/v2/islands.json +263 -0
  28. package/src/vendor/contract/v2/layout.json +28 -0
  29. package/src/vendor/contract/v2/manifest.schema.json +388 -0
  30. package/src/vendor/contract/v2/sections.json +947 -0
  31. package/src/vendor/contract/v2/site.schema.json +1353 -0
  32. package/src/vendor/contract/v2/tokens.json +101 -0
  33. package/src/vendor/contract/v2.lock.json +6931 -0
  34. package/src/vendor/engine/content-footprint.mjs +76 -1
  35. package/src/vendor/engine/majors.mjs +42 -0
  36. package/src/vendor/validator/gift-aid-logo.svg +6 -0
  37. package/src/vendor/validator/model-reference-v2.mjs +95 -0
  38. package/src/vendor/validator/platform-base.css +310 -0
  39. package/src/vendor/validator/preview-v1.mjs +691 -0
  40. package/src/vendor/validator/preview-v2.mjs +654 -0
  41. package/src/vendor/validator/preview.mjs +8 -688
  42. package/src/vendor/validator/site-context-v2.mjs +113 -0
  43. package/src/vendor/validator/validate-v1.mjs +666 -0
  44. package/src/vendor/validator/validate-v2.mjs +642 -0
  45. package/src/vendor/validator/validate.mjs +21 -663
  46. package/starter/assets/theme.css +3 -0
  47. package/starter/layout.liquid +15 -18
  48. package/starter/manifest.json +5 -4
  49. package/starter/preview/config.json +4 -0
  50. package/starter/preview/media/README.md +9 -0
  51. package/starter/sections/campaigns.liquid +5 -4
  52. package/starter/sections/homeHero.liquid +25 -21
  53. package/starter/sections/values.liquid +1 -1
@@ -0,0 +1,32 @@
1
+ {
2
+ "description": "Platform imagery envelopes, ADVICE, never enforcement. Entity images (events, courses, articles) render on SEVERAL surfaces at once (cards, carousel slides, detail banners), including platform-owned components, so their guidance is platform-level: the envelope every surface can live with. Template-composed slots (the home hero) are different, their ideal is the TEMPLATE's own design call, declared per-template in manifest.imagery and surfaced beside the upload. Editors measure the actual upload against the relevant guidance and advise; the charity always decides.",
3
+ "entityImages": {
4
+ "eventPromo": {
5
+ "idealAspect": "3:2",
6
+ "aspectRange": [
7
+ "4:3",
8
+ "16:9"
9
+ ],
10
+ "minWidth": 1600,
11
+ "note": "One photo, several surfaces: the events card, the home carousel slide, and the event page banner, each crops differently, so keep the subject centred and avoid text baked into the image."
12
+ },
13
+ "courseImage": {
14
+ "idealAspect": "3:2",
15
+ "aspectRange": [
16
+ "4:3",
17
+ "16:9"
18
+ ],
19
+ "minWidth": 1600,
20
+ "note": "Renders as the course card and the course page opener, subject centred, no baked-in text."
21
+ },
22
+ "articleCover": {
23
+ "idealAspect": "16:9",
24
+ "aspectRange": [
25
+ "3:2",
26
+ "2:1"
27
+ ],
28
+ "minWidth": 1600,
29
+ "note": "The article card thumbnail and the article page cover, wide editorial shots sit best."
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,263 @@
1
+ {
2
+ "description": "The platform's functional islands, the app-block table. A template PLACES an island with {% island 'name' %}; the platform renders, hydrates, upgrades and patches it. The stylingApi lists the STABLE class names a template may target with CSS; anything not listed is internal and may change without notice.",
3
+ "islands": [
4
+ {
5
+ "name": "donation_widget",
6
+ "label": "Donation widget",
7
+ "status": "available",
8
+ "description": "The full donation flow for the tenant's quick-give cause: suggested amounts, custom amount, donor details, Gift Aid opt-in, payment. All payment/compliance behaviour is platform-owned.",
9
+ "params": [],
10
+ "stylingApi": [
11
+ ".action-card",
12
+ ".donate-card",
13
+ ".donate-head",
14
+ ".donate-sub",
15
+ ".freq-tabs",
16
+ ".freq-tab",
17
+ ".amount-grid",
18
+ ".amount-option",
19
+ ".amount-list",
20
+ ".amount-row",
21
+ ".giftaid-box",
22
+ ".donate-submit",
23
+ ".donate-note",
24
+ ".donate-success"
25
+ ]
26
+ },
27
+ {
28
+ "name": "member_menu",
29
+ "label": "Port60 ID sign-in / account menu",
30
+ "status": "available",
31
+ "description": "The branded Port60 ID entry point: the sign-in button (popup flow) for guests, the account pill (Members area / Sign out) for signed-in members. Renders NOTHING when the tenant doesn't allow member sign-ups. The identity brand and popup behaviour are platform-owned and identical across every template, place it in your header's nav; never rebuild it.",
32
+ "params": [],
33
+ "stylingApi": [
34
+ ".nav-p60-signin",
35
+ ".nav-account",
36
+ ".nav-account-toggle",
37
+ ".nav-account-menu",
38
+ ".p60-mark",
39
+ ".nav-p60-signin-label",
40
+ ".nav-account-name",
41
+ ".nav-account-caret",
42
+ ".nav-account-icon"
43
+ ]
44
+ },
45
+ {
46
+ "name": "course_enrol",
47
+ "label": "Course enrolment widget",
48
+ "status": "available",
49
+ "description": "The member-gated course enrolment journey (schedule options, household picker, payment, seat holds) for the course the page addresses. Only meaningful on the course page template (pages/course.liquid), place it where your layout wants the purchase column (left rail, right rail, or below the content); position in your source is position on the page. Everything inside is platform-owned; it renders a standard sold-out note when no places remain (branch on course.soldOut if you want your own tag elsewhere).",
50
+ "params": [],
51
+ "stylingApi": [
52
+ ".course-enrol",
53
+ ".course-variant-list",
54
+ ".course-subject-list",
55
+ ".course-summary",
56
+ ".course-pay-panel",
57
+ ".course-soldout-note"
58
+ ]
59
+ },
60
+ {
61
+ "name": "next_prayer",
62
+ "label": "Next worship time",
63
+ "status": "available",
64
+ "description": "A live countdown to worship.next for the tenant's schedule. Renders nothing when no next time is available. The platform owns the clock; the template owns presentation through the stable classes.",
65
+ "params": [],
66
+ "stylingApi": [
67
+ ".next-prayer",
68
+ ".next-prayer-label",
69
+ ".next-prayer-name",
70
+ ".next-prayer-time",
71
+ ".next-prayer-countdown"
72
+ ]
73
+ },
74
+ {
75
+ "name": "article_engagement",
76
+ "label": "Article engagement",
77
+ "status": "available",
78
+ "description": "The platform-owned article views, likes and sharing bar for the article page being rendered.",
79
+ "params": [],
80
+ "stylingApi": [
81
+ ".article-engage",
82
+ ".article-views",
83
+ ".article-share-group",
84
+ ".article-share-btn",
85
+ ".article-share-word",
86
+ ".article-share-copied"
87
+ ]
88
+ },
89
+ {
90
+ "name": "article_comments",
91
+ "label": "Article comments",
92
+ "status": "available",
93
+ "description": "The identity-aware article conversation for the article page being rendered. Renders the closed state when comments are disabled.",
94
+ "params": [],
95
+ "stylingApi": [
96
+ ".article-comments",
97
+ ".article-comment-list",
98
+ ".article-comment",
99
+ ".article-comment-head",
100
+ ".article-comment-form",
101
+ ".article-comments-gate",
102
+ ".article-comments-note"
103
+ ]
104
+ },
105
+ {
106
+ "name": "hero_carousel",
107
+ "label": "Hero image carousel",
108
+ "status": "available",
109
+ "description": "Rotation for the home hero's photographs (homeHero.images, 2+): auto-advance every ~6s, pauses on hover/focus, honours prefers-reduced-motion (no auto-advance), swipe on touch, dot navigation. The platform owns the motion; the TEMPLATE owns the look, including the tonal treatment: every slide ships an empty .hero-slide-scrim layer over its image, and the template colours it from its OWN palette variables (a colour wash, a blend, a gradient) so any uploaded photo sits inside the template's tones. Renders nothing with fewer than 2 images, single-image and no-image states are the template's own renderer, not this island's.",
110
+ "params": [],
111
+ "stylingApi": [
112
+ ".hero-carousel",
113
+ ".hero-slides",
114
+ ".hero-slide",
115
+ ".hero-slide--active",
116
+ ".hero-slide-img",
117
+ ".hero-slide-scrim",
118
+ ".hero-dots",
119
+ ".hero-dot",
120
+ ".hero-dot--active"
121
+ ]
122
+ },
123
+ {
124
+ "name": "newsletter_signup",
125
+ "label": "Newsletter sign-up",
126
+ "status": "available",
127
+ "description": "Explicit email opt-in over the platform's versioned consent and one-click unsubscribe rail. Templates own placement and presentation; wording, evidence and abuse checks remain platform-owned.",
128
+ "params": [],
129
+ "stylingApi": [
130
+ ".newsletter-signup",
131
+ ".newsletter-label",
132
+ ".newsletter-fields",
133
+ ".newsletter-email",
134
+ ".newsletter-submit",
135
+ ".newsletter-consent",
136
+ ".newsletter-message"
137
+ ]
138
+ },
139
+ {
140
+ "name": "language_switch",
141
+ "label": "Language switch",
142
+ "status": "available",
143
+ "description": "Switches between the platform's supported public languages while preserving the current route. The host supplies locale and text direction to Liquid and the document element.",
144
+ "params": [],
145
+ "stylingApi": [
146
+ ".language-switch",
147
+ ".language-switch-label",
148
+ ".language-switch-select"
149
+ ]
150
+ },
151
+ {
152
+ "name": "search",
153
+ "label": "Site search",
154
+ "status": "available",
155
+ "description": "The site's search box: a typeahead over the platform's search index (articles, events, services, campaigns, appeals, volunteering and public documents; published and live rows only, never member, payment, private media or admin data). Suggestions are the things themselves, grouped by kind, each a direct link, with a matched-text line when the hit was in the body; Enter or the last row goes to the platform's /search results page. An accessible combobox: the active suggestion carries is-active. Where the box sits is the template's decision.",
156
+ "params": [],
157
+ "stylingApi": [
158
+ ".site-search",
159
+ ".site-search-form",
160
+ ".site-search-label",
161
+ ".site-search-fields",
162
+ ".site-search-input",
163
+ ".site-search-submit",
164
+ ".site-search-results",
165
+ ".site-search-result",
166
+ ".site-search-kind",
167
+ ".site-search-link",
168
+ ".site-search-summary",
169
+ ".site-search-empty",
170
+ ".site-search-title",
171
+ ".site-search-context",
172
+ ".site-search-more",
173
+ ".site-search-corrected"
174
+ ]
175
+ },
176
+ {
177
+ "name": "form",
178
+ "label": "Public form",
179
+ "status": "available",
180
+ "description": "Renders the tenant's first published platform form and records its submission with explicit privacy acceptance. Templates cannot handle or redirect submissions themselves.",
181
+ "params": [],
182
+ "stylingApi": [
183
+ ".public-form",
184
+ ".public-form-head",
185
+ ".public-form-field",
186
+ ".public-form-check",
187
+ ".public-form-privacy",
188
+ ".public-form-submit",
189
+ ".public-form-message"
190
+ ]
191
+ },
192
+ {
193
+ "name": "map",
194
+ "label": "Impact map",
195
+ "status": "available",
196
+ "description": "The selected map instance, including its own points and resolved public parent content. The platform owns interaction, eligibility and tiles; the template owns placement and documented styling.",
197
+ "params": [],
198
+ "stylingApi": [
199
+ ".impact-map",
200
+ ".impact-map-head",
201
+ ".impact-map-intro",
202
+ ".impact-map-canvas",
203
+ ".impact-map-pin",
204
+ ".impact-map-pin--active",
205
+ ".impact-map-card",
206
+ ".impact-map-card-kind",
207
+ ".impact-map-card-title",
208
+ ".impact-map-card-summary",
209
+ ".impact-map-card-cta",
210
+ ".impact-map-empty"
211
+ ]
212
+ },
213
+ {
214
+ "name": "volunteer_signup",
215
+ "label": "Volunteer sign-up",
216
+ "status": "preview",
217
+ "description": "The volunteer sign-up flow: what to help with (general, then the events and campaigns that need people), when (only when the opportunity runs in shifts), a few details, then confirmation by a charity-branded email that also signs the volunteer in. Shares the donation widget's card vocabulary so a themed donate card themes this too; the vol-* classes cover what is specific to asking for help.",
218
+ "params": [],
219
+ "stylingApi": [
220
+ ".donate-card",
221
+ ".action-card",
222
+ ".vol-body",
223
+ ".volunteer-card",
224
+ ".vol-card",
225
+ ".vol-chosen",
226
+ ".vol-search",
227
+ ".vol-list",
228
+ ".vol-group",
229
+ ".vol-group-label",
230
+ ".vol-option",
231
+ ".vol-option-main",
232
+ ".vol-option-summary",
233
+ ".vol-option-meta",
234
+ ".vol-needed",
235
+ ".vol-shift-count",
236
+ ".vol-form",
237
+ ".vol-availability",
238
+ ".vol-chips",
239
+ ".vol-chip",
240
+ ".vol-confirm",
241
+ ".vol-optional",
242
+ ".vol-why",
243
+ ".vol-extra",
244
+ ".vol-summary",
245
+ ".vol-signed-in",
246
+ ".vol-summary-needed",
247
+ ".vol-field-error"
248
+ ]
249
+ },
250
+ {
251
+ "name": "primary_action_widget",
252
+ "label": "Primary action widget",
253
+ "status": "available",
254
+ "description": "The platform-owned action widget selected by site.actions.widget: donate, volunteer or none. Templates place this island and style its documented classes; they never reverse the organisation's action choice.",
255
+ "params": [],
256
+ "stylingApi": [
257
+ ".action-card",
258
+ ".donate-card",
259
+ ".volunteer-card"
260
+ ]
261
+ }
262
+ ]
263
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "description": "How platform pages are placed inside a template. Every platform page (donate, services, events, campaigns, the members area) renders through a fixed set of LAYOUT INTENTS, the content SEAM. A template STYLES these intents to place content its way; it must never build a parallel content container, or platform pages drift from the template's own sections. Enforced at publish by the conformance validator.",
3
+ "widthToken": "--container",
4
+ "intents": [
5
+ {
6
+ "class": "container",
7
+ "kind": "contained",
8
+ "note": "The contained content column, text, forms, cards. Style it (and override --container) to set your content width and gutter. Use it in your own layout and sections too, so everything shares one column."
9
+ },
10
+ {
11
+ "class": "full",
12
+ "kind": "full-bleed",
13
+ "note": "Edge-to-edge regardless of the container it sits in, a hero image, a colour band. Restyle it freely; the default breaks out to the viewport width."
14
+ }
15
+ ],
16
+ "rules": [
17
+ {
18
+ "id": "no-parallel-container",
19
+ "kind": "css-width-off-token",
20
+ "token": "--container",
21
+ "allow": [
22
+ "container",
23
+ "full"
24
+ ],
25
+ "message": "builds its own content container from var(--container). Platform pages render inside .container, so a parallel container drifts from your sections, style .container instead (override --container for width and add your gutter there), and use .full for edge-to-edge."
26
+ }
27
+ ]
28
+ }