@port60/template-kit 0.8.0 → 0.10.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.
- package/README.md +10 -3
- package/bin/cli.mjs +26 -4
- package/package.json +1 -1
- package/src/commands/content.mjs +42 -0
- package/src/commands/create.mjs +10 -7
- package/src/commands/dev.mjs +134 -8
- package/src/commands/model.mjs +34 -0
- package/src/commands/packageCmd.mjs +3 -3
- package/src/commands/refresh.mjs +48 -0
- package/src/commands/upgrade.mjs +44 -0
- package/src/commands/validate.mjs +2 -3
- package/src/lib/agentsMd.mjs +78 -21
- package/src/vendor/contract/v1/behaviours.json +129 -0
- package/src/vendor/contract/v1/content-bounds.json +3 -3
- package/src/vendor/contract/v1/content-model.json +193 -0
- package/src/vendor/contract/v1/context.json +1985 -141
- package/src/vendor/contract/v1/dialect.json +3 -3
- package/src/vendor/contract/v1/fonts.json +1 -1
- package/src/vendor/contract/v1/imagery.json +4 -4
- package/src/vendor/contract/v1/islands.json +256 -110
- package/src/vendor/contract/v1/layout.json +25 -0
- package/src/vendor/contract/v1/manifest.schema.json +60 -14
- package/src/vendor/contract/v1/sections.json +330 -61
- package/src/vendor/contract/v1/tokens.json +1 -1
- package/src/vendor/contract/v1.lock.json +1 -1
- package/src/vendor/engine/content-footprint.mjs +79 -0
- package/src/vendor/validator/behaviors-runtime.js +2 -0
- package/src/vendor/validator/fixture-art.mjs +109 -0
- package/src/vendor/validator/model-reference.mjs +92 -0
- package/src/vendor/validator/platform-base.css +4250 -0
- package/src/vendor/validator/preview.mjs +497 -23
- package/src/vendor/validator/site-context.mjs +97 -0
- package/src/vendor/validator/validate.mjs +214 -14
- package/starter/assets/theme.css +54 -8
- package/starter/layout.liquid +15 -16
- package/starter/manifest.json +10 -5
- package/starter/sections/campaigns.liquid +23 -0
- package/starter/sections/homeHero.liquid +26 -11
- package/starter/sections/people.liquid +1 -1
- package/starter/sections/values.liquid +6 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "port60-liquid@1",
|
|
3
|
-
"description": "The Port60 template dialect
|
|
3
|
+
"description": "The Port60 template dialect, a strict, whitelisted subset of Liquid. Anything not listed here is rejected at parse time by the engine and at publish time by the conformance validator.",
|
|
4
4
|
"outputEscape": "escape",
|
|
5
5
|
"rawFilter": "raw",
|
|
6
6
|
"tags": [
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
{
|
|
17
17
|
"name": "island",
|
|
18
18
|
"usage": "{% island 'donation_widget' %}",
|
|
19
|
-
"description": "Places a platform functional island (a live, hydrated component) at this position. Templates place islands, never implement them
|
|
19
|
+
"description": "Places a platform functional island (a live, hydrated component) at this position. Templates place islands, never implement them, the island's markup, behaviour and data are platform-owned and upgraded platform-side. The island name must be declared in the manifest's supports.islands."
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"name": "content",
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
],
|
|
35
35
|
"excluded": {
|
|
36
36
|
"tags": ["include", "render", "layout", "tablerow", "cycle", "increment", "decrement"],
|
|
37
|
-
"reason": "File inclusion (no file system exists inside an artifact yet
|
|
37
|
+
"reason": "File inclusion (no file system exists inside an artifact yet, a snippets mechanism arrives with the Layout slot), table markup generation, and stateful counters are outside the v1 dialect."
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "The curated font catalogue behind `font` settings knobs. The tenant picks a FAMILY; the template's typographic system (weights, sizes, tracking) is author-owned
|
|
2
|
+
"description": "The curated font catalogue behind `font` settings knobs. The tenant picks a FAMILY; the template's typographic system (weights, sizes, tracking) is author-owned, the host requests the template's declared weights for the chosen family, clamped to the family's real weights below (nearest wins). Curated, not the raw Google catalogue: every family here is open-licence (OFL/Apache), quality-checked, and tagged so non-technical admins choose by vibe. Served from Bunny Fonts (a privacy-first, EU-hosted drop-in mirror of the Google Fonts catalogue, same families, same css2 API). Additive-only within a contract major; never remove a family (published sites reference them).",
|
|
3
3
|
"provider": {
|
|
4
4
|
"cssBase": "https://fonts.bunny.net/css2",
|
|
5
5
|
"preconnect": "https://fonts.bunny.net"
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "Platform imagery envelopes
|
|
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
3
|
"entityImages": {
|
|
4
4
|
"eventPromo": {
|
|
5
5
|
"idealAspect": "3:2",
|
|
6
6
|
"aspectRange": ["4:3", "16:9"],
|
|
7
7
|
"minWidth": 1600,
|
|
8
|
-
"note": "One photo, several surfaces: the events card, the home carousel slide, and the event page banner
|
|
8
|
+
"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."
|
|
9
9
|
},
|
|
10
10
|
"courseImage": {
|
|
11
11
|
"idealAspect": "3:2",
|
|
12
12
|
"aspectRange": ["4:3", "16:9"],
|
|
13
13
|
"minWidth": 1600,
|
|
14
|
-
"note": "Renders as the course card and the course page opener
|
|
14
|
+
"note": "Renders as the course card and the course page opener, subject centred, no baked-in text."
|
|
15
15
|
},
|
|
16
16
|
"articleCover": {
|
|
17
17
|
"idealAspect": "16:9",
|
|
18
18
|
"aspectRange": ["3:2", "2:1"],
|
|
19
19
|
"minWidth": 1600,
|
|
20
|
-
"note": "The article card thumbnail and the article page cover
|
|
20
|
+
"note": "The article card thumbnail and the article page cover, wide editorial shots sit best."
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
|
@@ -1,112 +1,258 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
+
".donate-card",
|
|
12
|
+
".donate-head",
|
|
13
|
+
".donate-sub",
|
|
14
|
+
".freq-tabs",
|
|
15
|
+
".freq-tab",
|
|
16
|
+
".amount-grid",
|
|
17
|
+
".amount-option",
|
|
18
|
+
".amount-list",
|
|
19
|
+
".amount-row",
|
|
20
|
+
".giftaid-box",
|
|
21
|
+
".donate-submit",
|
|
22
|
+
".donate-note",
|
|
23
|
+
".donate-success"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "member_menu",
|
|
28
|
+
"label": "Port60 ID sign-in / account menu",
|
|
29
|
+
"status": "available",
|
|
30
|
+
"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.",
|
|
31
|
+
"params": [],
|
|
32
|
+
"stylingApi": [
|
|
33
|
+
".nav-p60-signin",
|
|
34
|
+
".nav-account",
|
|
35
|
+
".nav-account-toggle",
|
|
36
|
+
".nav-account-menu",
|
|
37
|
+
".p60-mark"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "events_carousel",
|
|
42
|
+
"label": "Events carousel",
|
|
43
|
+
"status": "available",
|
|
44
|
+
"description": "The rotating showcase of published ticketed events. Renders nothing when the tenant has no matching published content.",
|
|
45
|
+
"params": [],
|
|
46
|
+
"stylingApi": [
|
|
47
|
+
".events-carousel",
|
|
48
|
+
".carousel-slide",
|
|
49
|
+
".carousel-caption",
|
|
50
|
+
".carousel-dots",
|
|
51
|
+
".carousel-dot"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "whats_on_strip",
|
|
56
|
+
"label": "What's-on strip",
|
|
57
|
+
"status": "available",
|
|
58
|
+
"description": "The slim banner of upcoming info events. Renders nothing when the tenant has no matching published content.",
|
|
59
|
+
"params": [],
|
|
60
|
+
"stylingApi": [
|
|
61
|
+
".info-events-bar",
|
|
62
|
+
".info-events-inner",
|
|
63
|
+
".info-event-chip"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "latest_articles",
|
|
68
|
+
"label": "Latest articles",
|
|
69
|
+
"status": "available",
|
|
70
|
+
"description": "A strip of the most recent published articles. Renders nothing when the tenant has no matching published content.",
|
|
71
|
+
"params": [],
|
|
72
|
+
"stylingApi": [
|
|
73
|
+
".article-grid",
|
|
74
|
+
".article-card",
|
|
75
|
+
".article-card-body",
|
|
76
|
+
".article-strip-cta"
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "course_enrol",
|
|
81
|
+
"label": "Course enrolment widget",
|
|
82
|
+
"status": "available",
|
|
83
|
+
"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).",
|
|
84
|
+
"params": [],
|
|
85
|
+
"stylingApi": [
|
|
86
|
+
".course-enrol",
|
|
87
|
+
".course-variant-list",
|
|
88
|
+
".course-subject-list",
|
|
89
|
+
".course-summary",
|
|
90
|
+
".course-pay-panel",
|
|
91
|
+
".course-soldout-note"
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"name": "next_prayer",
|
|
96
|
+
"label": "Next worship time",
|
|
97
|
+
"status": "available",
|
|
98
|
+
"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.",
|
|
99
|
+
"params": [],
|
|
100
|
+
"stylingApi": [
|
|
101
|
+
".next-prayer",
|
|
102
|
+
".next-prayer-label",
|
|
103
|
+
".next-prayer-name",
|
|
104
|
+
".next-prayer-time",
|
|
105
|
+
".next-prayer-countdown"
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "article_engagement",
|
|
110
|
+
"label": "Article engagement",
|
|
111
|
+
"status": "available",
|
|
112
|
+
"description": "The platform-owned article views, likes and sharing bar for the article page being rendered.",
|
|
113
|
+
"params": [],
|
|
114
|
+
"stylingApi": [
|
|
115
|
+
".article-engage",
|
|
116
|
+
".article-views",
|
|
117
|
+
".article-share-group",
|
|
118
|
+
".article-share-btn",
|
|
119
|
+
".article-share-word",
|
|
120
|
+
".article-share-copied"
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "article_comments",
|
|
125
|
+
"label": "Article comments",
|
|
126
|
+
"status": "available",
|
|
127
|
+
"description": "The identity-aware article conversation for the article page being rendered. Renders the closed state when comments are disabled.",
|
|
128
|
+
"params": [],
|
|
129
|
+
"stylingApi": [
|
|
130
|
+
".article-comments",
|
|
131
|
+
".article-comment-list",
|
|
132
|
+
".article-comment",
|
|
133
|
+
".article-comment-head",
|
|
134
|
+
".article-comment-form",
|
|
135
|
+
".article-comments-gate",
|
|
136
|
+
".article-comments-note"
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "hero_carousel",
|
|
141
|
+
"label": "Hero image carousel",
|
|
142
|
+
"status": "available",
|
|
143
|
+
"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.",
|
|
144
|
+
"params": [],
|
|
145
|
+
"stylingApi": [
|
|
146
|
+
".hero-carousel",
|
|
147
|
+
".hero-slides",
|
|
148
|
+
".hero-slide",
|
|
149
|
+
".hero-slide--active",
|
|
150
|
+
".hero-slide-img",
|
|
151
|
+
".hero-slide-scrim",
|
|
152
|
+
".hero-dots",
|
|
153
|
+
".hero-dot",
|
|
154
|
+
".hero-dot--active"
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"name": "newsletter_signup",
|
|
159
|
+
"label": "Newsletter sign-up",
|
|
160
|
+
"status": "available",
|
|
161
|
+
"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.",
|
|
162
|
+
"params": [],
|
|
163
|
+
"stylingApi": [
|
|
164
|
+
".newsletter-signup",
|
|
165
|
+
".newsletter-label",
|
|
166
|
+
".newsletter-fields",
|
|
167
|
+
".newsletter-email",
|
|
168
|
+
".newsletter-submit",
|
|
169
|
+
".newsletter-consent",
|
|
170
|
+
".newsletter-message"
|
|
171
|
+
]
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"name": "language_switch",
|
|
175
|
+
"label": "Language switch",
|
|
176
|
+
"status": "available",
|
|
177
|
+
"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.",
|
|
178
|
+
"params": [],
|
|
179
|
+
"stylingApi": [
|
|
180
|
+
".language-switch",
|
|
181
|
+
".language-switch-label",
|
|
182
|
+
".language-switch-select"
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "search",
|
|
187
|
+
"label": "Site search",
|
|
188
|
+
"status": "available",
|
|
189
|
+
"description": "Searches published articles, programmes, events and public resources. It reads public projections only and cannot search member, payment, private media or admin data.",
|
|
190
|
+
"params": [],
|
|
191
|
+
"stylingApi": [
|
|
192
|
+
".site-search",
|
|
193
|
+
".site-search-form",
|
|
194
|
+
".site-search-label",
|
|
195
|
+
".site-search-fields",
|
|
196
|
+
".site-search-input",
|
|
197
|
+
".site-search-submit",
|
|
198
|
+
".site-search-results",
|
|
199
|
+
".site-search-result",
|
|
200
|
+
".site-search-kind",
|
|
201
|
+
".site-search-link",
|
|
202
|
+
".site-search-summary",
|
|
203
|
+
".site-search-empty"
|
|
204
|
+
]
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"name": "volunteer_signup",
|
|
208
|
+
"label": "Volunteer expression of interest",
|
|
209
|
+
"status": "available",
|
|
210
|
+
"description": "Lists published opportunities and captures a privacy-recorded expression of interest. Templates own placement and styling; validation, abuse checks and submission storage remain platform-owned.",
|
|
211
|
+
"params": [],
|
|
212
|
+
"stylingApi": [
|
|
213
|
+
".volunteer-signup",
|
|
214
|
+
".volunteer-field",
|
|
215
|
+
".volunteer-privacy",
|
|
216
|
+
".volunteer-submit",
|
|
217
|
+
".volunteer-message"
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"name": "form",
|
|
222
|
+
"label": "Public form",
|
|
223
|
+
"status": "available",
|
|
224
|
+
"description": "Renders the tenant's first published platform form and records its submission with explicit privacy acceptance. Templates cannot handle or redirect submissions themselves.",
|
|
225
|
+
"params": [],
|
|
226
|
+
"stylingApi": [
|
|
227
|
+
".public-form",
|
|
228
|
+
".public-form-head",
|
|
229
|
+
".public-form-field",
|
|
230
|
+
".public-form-check",
|
|
231
|
+
".public-form-privacy",
|
|
232
|
+
".public-form-submit",
|
|
233
|
+
".public-form-message"
|
|
234
|
+
]
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"name": "map",
|
|
238
|
+
"label": "Impact map",
|
|
239
|
+
"status": "available",
|
|
240
|
+
"description": "The tenant's published impact map: a themed, privacy-clean map (platform-served tiles, never a third-party tile CDN) whose points open cards derived from the content they link. The island derives its colours from the template's tokens; v1 renders the tenant's first published map.",
|
|
241
|
+
"params": [],
|
|
242
|
+
"stylingApi": [
|
|
243
|
+
".impact-map",
|
|
244
|
+
".impact-map-head",
|
|
245
|
+
".impact-map-intro",
|
|
246
|
+
".impact-map-canvas",
|
|
247
|
+
".impact-map-pin",
|
|
248
|
+
".impact-map-pin--active",
|
|
249
|
+
".impact-map-card",
|
|
250
|
+
".impact-map-card-kind",
|
|
251
|
+
".impact-map-card-title",
|
|
252
|
+
".impact-map-card-summary",
|
|
253
|
+
".impact-map-card-cta",
|
|
254
|
+
".impact-map-empty"
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
]
|
|
112
258
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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": ["container", "full"],
|
|
22
|
+
"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."
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
"name": {
|
|
10
10
|
"type": "string",
|
|
11
11
|
"pattern": "^[a-z][a-z0-9-]{1,48}[a-z0-9]$",
|
|
12
|
-
"description": "The template's identity
|
|
12
|
+
"description": "The template's identity, lowercase, hyphenated, stable across versions."
|
|
13
13
|
},
|
|
14
14
|
"version": {
|
|
15
15
|
"type": "string",
|
|
16
|
-
"pattern": "^\\d+\\.\\d+\\.\\d
|
|
17
|
-
"description": "Semver.
|
|
16
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+(?:-dev\\.\\d+)?$",
|
|
17
|
+
"description": "Semver. Stable versions are immutable once submitted. Owner-only development versions use x.y.z-dev.N, remain non-promotable, and may be replaced before creating a stable release."
|
|
18
18
|
},
|
|
19
19
|
"format": {
|
|
20
20
|
"type": "string",
|
|
@@ -23,10 +23,47 @@
|
|
|
23
23
|
},
|
|
24
24
|
"label": { "type": "string", "maxLength": 60, "description": "Human-readable name shown in pickers." },
|
|
25
25
|
"description": { "type": "string", "maxLength": 300 },
|
|
26
|
+
"requiresCapabilities": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"enum": [
|
|
31
|
+
"giving",
|
|
32
|
+
"appeals",
|
|
33
|
+
"worship",
|
|
34
|
+
"courses",
|
|
35
|
+
"membership",
|
|
36
|
+
"events",
|
|
37
|
+
"articles",
|
|
38
|
+
"services",
|
|
39
|
+
"volunteering",
|
|
40
|
+
"forms",
|
|
41
|
+
"resources",
|
|
42
|
+
"locations",
|
|
43
|
+
"newsletter",
|
|
44
|
+
"i18n",
|
|
45
|
+
"search",
|
|
46
|
+
"media"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"uniqueItems": true,
|
|
50
|
+
"default": [],
|
|
51
|
+
"description": "Platform capabilities this design needs in order to work as intended. This is a catalogue matching signal, never an entitlement grant. The validator checks that each declared capability has a corresponding section, page template, island or context surface."
|
|
52
|
+
},
|
|
53
|
+
"suitsProfiles": {
|
|
54
|
+
"type": "array",
|
|
55
|
+
"items": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"enum": ["general", "mosque", "church", "appeal-charity", "pta", "community", "training"]
|
|
58
|
+
},
|
|
59
|
+
"uniqueItems": true,
|
|
60
|
+
"default": [],
|
|
61
|
+
"description": "Organisation profiles this design was composed for. An empty list means the template is general purpose. Profiles improve catalogue recommendations but do not hide or unlock features."
|
|
62
|
+
},
|
|
26
63
|
"changelog": {
|
|
27
64
|
"type": "string",
|
|
28
65
|
"maxLength": 300,
|
|
29
|
-
"description": "One human line describing what THIS version changed
|
|
66
|
+
"description": "One human line describing what THIS version changed, shown on the Appearance update nudge."
|
|
30
67
|
},
|
|
31
68
|
"supports": {
|
|
32
69
|
"type": "object",
|
|
@@ -38,14 +75,14 @@
|
|
|
38
75
|
"items": { "type": "string", "enum": ["home", "about"] },
|
|
39
76
|
"minItems": 1,
|
|
40
77
|
"uniqueItems": true,
|
|
41
|
-
"description": "
|
|
78
|
+
"description": "Section based page bodies this template renders from sections/{type}.liquid. Contract v1 supports home and about. This is independent of layout chrome and route-specific pageTemplates."
|
|
42
79
|
},
|
|
43
80
|
"sections": {
|
|
44
81
|
"type": "array",
|
|
45
82
|
"items": { "type": "string" },
|
|
46
83
|
"minItems": 1,
|
|
47
84
|
"uniqueItems": true,
|
|
48
|
-
"description": "Section types this template ships renderers for (sections/{type}.liquid). Unsupported types are omitted at render
|
|
85
|
+
"description": "Section types this template ships renderers for (sections/{type}.liquid). Unsupported types are omitted at render, never an error."
|
|
49
86
|
},
|
|
50
87
|
"islands": {
|
|
51
88
|
"type": "array",
|
|
@@ -57,14 +94,14 @@
|
|
|
57
94
|
"layout": {
|
|
58
95
|
"type": "boolean",
|
|
59
96
|
"default": false,
|
|
60
|
-
"description": "True when the template ships layout.liquid and owns the page chrome (header/nav/footer). The platform still owns <head>, consent, and identity
|
|
97
|
+
"description": "True when the template ships layout.liquid and owns the page chrome (header/nav/footer). The platform still owns <head>, consent, and identity, see the layouts guide."
|
|
61
98
|
},
|
|
62
99
|
"pageTemplates": {
|
|
63
100
|
"type": "array",
|
|
64
|
-
"items": { "type": "string", "enum": ["events", "course"] },
|
|
101
|
+
"items": { "type": "string", "enum": ["events", "course", "articles", "article"] },
|
|
65
102
|
"uniqueItems": true,
|
|
66
103
|
"default": [],
|
|
67
|
-
"description": "
|
|
104
|
+
"description": "Route-specific data views rendered from pages/{page}.liquid with documented context. Events and articles own listings; course and article own detail presentation. Event transactions, course enrolment, article engagement, comments, identity and consent stay platform-owned islands. Requires supports.layout; without an entry the platform body renders inside the template chrome."
|
|
68
105
|
},
|
|
69
106
|
"worship": {
|
|
70
107
|
"type": "boolean",
|
|
@@ -74,14 +111,23 @@
|
|
|
74
111
|
"heroImagery": {
|
|
75
112
|
"type": "boolean",
|
|
76
113
|
"default": false,
|
|
77
|
-
"description": "True when the homeHero renderer displays the tenant's hero photographs (homeHero.images, legacy imageUrl)
|
|
114
|
+
"description": "True when the homeHero renderer displays the tenant's hero photographs (homeHero.images, legacy imageUrl), always under a TREATMENT built from the template's own palette variables (scrim/tint/blend), never raw. Declared honesty, proven behaviourally at validation: the choosers badge photo-led tenants toward templates that show their images and warn on ones that won't. Requires the homeHero section among supports.sections."
|
|
115
|
+
},
|
|
116
|
+
"behaviors": {
|
|
117
|
+
"type": "array",
|
|
118
|
+
"items": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"enum": ["reveal", "counter", "progress", "countdown", "accordion", "carousel", "stickyHeader", "stickyCta", "lightbox", "tabs"]
|
|
121
|
+
},
|
|
122
|
+
"uniqueItems": true,
|
|
123
|
+
"description": "Engine-attached behaviours this template's markup opts into via data-p60-* attributes (see the behaviour catalogue). Templates never ship JavaScript; declaring here is what makes the engine wire the markup, and the validator proves declaration and usage agree in both directions."
|
|
78
124
|
}
|
|
79
125
|
}
|
|
80
126
|
},
|
|
81
127
|
"imagery": {
|
|
82
128
|
"type": "object",
|
|
83
129
|
"additionalProperties": false,
|
|
84
|
-
"description": "The template's OWN image advice, surfaced beside uploads in the charity's editor
|
|
130
|
+
"description": "The template's OWN image advice, surfaced beside uploads in the charity's editor, a design statement (what shape suits THIS composition), advisory only, never enforced. Entity images (events, courses, articles) are platform-scoped instead: see the contract's imagery.json envelopes.",
|
|
85
131
|
"properties": {
|
|
86
132
|
"hero": {
|
|
87
133
|
"type": "object",
|
|
@@ -127,7 +173,7 @@
|
|
|
127
173
|
"name": {
|
|
128
174
|
"type": "string",
|
|
129
175
|
"maxLength": 40,
|
|
130
|
-
"description": "The look's evocative, author-chosen name
|
|
176
|
+
"description": "The look's evocative, author-chosen name, shown as a one-click chip in Appearance."
|
|
131
177
|
},
|
|
132
178
|
"values": {
|
|
133
179
|
"type": "object",
|
|
@@ -160,7 +206,7 @@
|
|
|
160
206
|
"group": {
|
|
161
207
|
"type": "string",
|
|
162
208
|
"enum": ["typography", "looks"],
|
|
163
|
-
"description": "Where the admin surfaces this knob: 'typography' → the Typography section beside the font slots; 'looks' → the Looks section with the one-click bundles (colour schemes and accent dials belong there
|
|
209
|
+
"description": "Where the admin surfaces this knob: 'typography' → the Typography section beside the font slots; 'looks' → the Looks section with the one-click bundles (colour schemes and accent dials belong there, and ungrouped color knobs plus a knob keyed 'scheme' default there anyway); other ungrouped knobs render under Template options."
|
|
164
210
|
},
|
|
165
211
|
"weights": {
|
|
166
212
|
"type": "array",
|
|
@@ -170,7 +216,7 @@
|
|
|
170
216
|
}
|
|
171
217
|
}
|
|
172
218
|
},
|
|
173
|
-
"description": "The template's own theming knobs (colour schemes etc.)
|
|
219
|
+
"description": "The template's own theming knobs (colour schemes etc.), rendered as an editor in Appearance/get-started; values land in site-config config.theme."
|
|
174
220
|
}
|
|
175
221
|
}
|
|
176
222
|
}
|