@port60/template-kit 0.5.0 → 0.7.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/package.json +1 -1
- package/src/vendor/contract/v1/context.json +722 -528
- package/src/vendor/contract/v1/sections.json +676 -577
- package/src/vendor/contract/v1.lock.json +446 -0
- package/src/vendor/validator/validate.mjs +49 -1
- package/starter/assets/theme.css +27 -0
- package/starter/manifest.json +48 -47
- package/starter/sections/people.liquid +26 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "FROZEN compatibility baseline for port60-liquid@1 (8.6). tests/contract-freeze.test.mjs enforces that everything here still exists — removals/narrowings fail. Regenerate ONLY deliberately via scripts/contract-lock.mjs; the diff is the review.",
|
|
3
|
+
"format": "port60-liquid@1",
|
|
4
|
+
"dialect": {
|
|
5
|
+
"tags": [
|
|
6
|
+
"assign",
|
|
7
|
+
"break",
|
|
8
|
+
"capture",
|
|
9
|
+
"case",
|
|
10
|
+
"comment",
|
|
11
|
+
"continue",
|
|
12
|
+
"echo",
|
|
13
|
+
"else",
|
|
14
|
+
"elsif",
|
|
15
|
+
"endcapture",
|
|
16
|
+
"endcase",
|
|
17
|
+
"endcomment",
|
|
18
|
+
"endfor",
|
|
19
|
+
"endif",
|
|
20
|
+
"endraw",
|
|
21
|
+
"endunless",
|
|
22
|
+
"for",
|
|
23
|
+
"if",
|
|
24
|
+
"liquid",
|
|
25
|
+
"raw",
|
|
26
|
+
"unless",
|
|
27
|
+
"when"
|
|
28
|
+
],
|
|
29
|
+
"filters": [
|
|
30
|
+
"abs",
|
|
31
|
+
"append",
|
|
32
|
+
"at_least",
|
|
33
|
+
"at_most",
|
|
34
|
+
"capitalize",
|
|
35
|
+
"ceil",
|
|
36
|
+
"compact",
|
|
37
|
+
"concat",
|
|
38
|
+
"date",
|
|
39
|
+
"default",
|
|
40
|
+
"divided_by",
|
|
41
|
+
"downcase",
|
|
42
|
+
"escape",
|
|
43
|
+
"escape_once",
|
|
44
|
+
"first",
|
|
45
|
+
"floor",
|
|
46
|
+
"join",
|
|
47
|
+
"last",
|
|
48
|
+
"lstrip",
|
|
49
|
+
"map",
|
|
50
|
+
"minus",
|
|
51
|
+
"modulo",
|
|
52
|
+
"newline_to_br",
|
|
53
|
+
"plus",
|
|
54
|
+
"prepend",
|
|
55
|
+
"raw",
|
|
56
|
+
"remove",
|
|
57
|
+
"remove_first",
|
|
58
|
+
"replace",
|
|
59
|
+
"replace_first",
|
|
60
|
+
"reverse",
|
|
61
|
+
"round",
|
|
62
|
+
"rstrip",
|
|
63
|
+
"size",
|
|
64
|
+
"slice",
|
|
65
|
+
"sort",
|
|
66
|
+
"sort_natural",
|
|
67
|
+
"split",
|
|
68
|
+
"strip",
|
|
69
|
+
"strip_html",
|
|
70
|
+
"strip_newlines",
|
|
71
|
+
"times",
|
|
72
|
+
"truncate",
|
|
73
|
+
"truncatewords",
|
|
74
|
+
"uniq",
|
|
75
|
+
"upcase",
|
|
76
|
+
"url_decode",
|
|
77
|
+
"url_encode",
|
|
78
|
+
"where"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"sectionTypes": [
|
|
82
|
+
"appealGrid",
|
|
83
|
+
"articles",
|
|
84
|
+
"cta",
|
|
85
|
+
"emergency",
|
|
86
|
+
"events",
|
|
87
|
+
"hero",
|
|
88
|
+
"homeHero",
|
|
89
|
+
"impact",
|
|
90
|
+
"people",
|
|
91
|
+
"stats",
|
|
92
|
+
"values",
|
|
93
|
+
"whatsOn"
|
|
94
|
+
],
|
|
95
|
+
"islands": {
|
|
96
|
+
"course_enrol": [
|
|
97
|
+
".course-enrol",
|
|
98
|
+
".course-pay-panel",
|
|
99
|
+
".course-soldout-note",
|
|
100
|
+
".course-subject-list",
|
|
101
|
+
".course-summary",
|
|
102
|
+
".course-variant-list"
|
|
103
|
+
],
|
|
104
|
+
"donation_widget": [
|
|
105
|
+
".amount-grid",
|
|
106
|
+
".amount-list",
|
|
107
|
+
".amount-option",
|
|
108
|
+
".amount-row",
|
|
109
|
+
".donate-card",
|
|
110
|
+
".donate-head",
|
|
111
|
+
".donate-note",
|
|
112
|
+
".donate-sub",
|
|
113
|
+
".donate-submit",
|
|
114
|
+
".donate-success",
|
|
115
|
+
".freq-tab",
|
|
116
|
+
".freq-tabs",
|
|
117
|
+
".giftaid-box"
|
|
118
|
+
],
|
|
119
|
+
"events_carousel": [
|
|
120
|
+
".carousel-caption",
|
|
121
|
+
".carousel-dot",
|
|
122
|
+
".carousel-dots",
|
|
123
|
+
".carousel-slide",
|
|
124
|
+
".events-carousel"
|
|
125
|
+
],
|
|
126
|
+
"hero_carousel": [
|
|
127
|
+
".hero-carousel",
|
|
128
|
+
".hero-dot",
|
|
129
|
+
".hero-dot--active",
|
|
130
|
+
".hero-dots",
|
|
131
|
+
".hero-slide",
|
|
132
|
+
".hero-slide-img",
|
|
133
|
+
".hero-slide-scrim",
|
|
134
|
+
".hero-slides"
|
|
135
|
+
],
|
|
136
|
+
"latest_articles": [
|
|
137
|
+
".article-card",
|
|
138
|
+
".article-card-body",
|
|
139
|
+
".article-grid",
|
|
140
|
+
".article-strip-cta"
|
|
141
|
+
],
|
|
142
|
+
"member_menu": [
|
|
143
|
+
".nav-account",
|
|
144
|
+
".nav-account-menu",
|
|
145
|
+
".nav-account-toggle",
|
|
146
|
+
".nav-p60-signin",
|
|
147
|
+
".p60-mark"
|
|
148
|
+
],
|
|
149
|
+
"whats_on_strip": [
|
|
150
|
+
".info-event-chip",
|
|
151
|
+
".info-events-bar",
|
|
152
|
+
".info-events-inner"
|
|
153
|
+
]
|
|
154
|
+
},
|
|
155
|
+
"contextVariables": {
|
|
156
|
+
"brand.footerLogoUrl": [
|
|
157
|
+
"layout"
|
|
158
|
+
],
|
|
159
|
+
"brand.logoType": [
|
|
160
|
+
"layout"
|
|
161
|
+
],
|
|
162
|
+
"brand.logoUrl": [
|
|
163
|
+
"layout"
|
|
164
|
+
],
|
|
165
|
+
"brand.name": [
|
|
166
|
+
"layout",
|
|
167
|
+
"section"
|
|
168
|
+
],
|
|
169
|
+
"brand.tagline": [
|
|
170
|
+
"layout",
|
|
171
|
+
"section"
|
|
172
|
+
],
|
|
173
|
+
"course": [
|
|
174
|
+
"page:course"
|
|
175
|
+
],
|
|
176
|
+
"course.aboutHtml": [
|
|
177
|
+
"page:course"
|
|
178
|
+
],
|
|
179
|
+
"course.audience": [
|
|
180
|
+
"page:course"
|
|
181
|
+
],
|
|
182
|
+
"course.backHref": [
|
|
183
|
+
"page:course"
|
|
184
|
+
],
|
|
185
|
+
"course.billingMode": [
|
|
186
|
+
"page:course"
|
|
187
|
+
],
|
|
188
|
+
"course.currency": [
|
|
189
|
+
"page:course"
|
|
190
|
+
],
|
|
191
|
+
"course.description": [
|
|
192
|
+
"page:course"
|
|
193
|
+
],
|
|
194
|
+
"course.id": [
|
|
195
|
+
"page:course"
|
|
196
|
+
],
|
|
197
|
+
"course.imageUrl": [
|
|
198
|
+
"page:course"
|
|
199
|
+
],
|
|
200
|
+
"course.priceFrom": [
|
|
201
|
+
"page:course"
|
|
202
|
+
],
|
|
203
|
+
"course.soldOut": [
|
|
204
|
+
"page:course"
|
|
205
|
+
],
|
|
206
|
+
"course.termEnd": [
|
|
207
|
+
"page:course"
|
|
208
|
+
],
|
|
209
|
+
"course.termStart": [
|
|
210
|
+
"page:course"
|
|
211
|
+
],
|
|
212
|
+
"course.title": [
|
|
213
|
+
"page:course"
|
|
214
|
+
],
|
|
215
|
+
"course.venue": [
|
|
216
|
+
"page:course"
|
|
217
|
+
],
|
|
218
|
+
"events": [
|
|
219
|
+
"page:events",
|
|
220
|
+
"section:events"
|
|
221
|
+
],
|
|
222
|
+
"events[].currency": [
|
|
223
|
+
"page:events"
|
|
224
|
+
],
|
|
225
|
+
"events[].description": [
|
|
226
|
+
"page:events"
|
|
227
|
+
],
|
|
228
|
+
"events[].detailHref": [
|
|
229
|
+
"page:events"
|
|
230
|
+
],
|
|
231
|
+
"events[].endsAt": [
|
|
232
|
+
"page:events"
|
|
233
|
+
],
|
|
234
|
+
"events[].id": [
|
|
235
|
+
"page:events"
|
|
236
|
+
],
|
|
237
|
+
"events[].imageUrl": [
|
|
238
|
+
"page:events"
|
|
239
|
+
],
|
|
240
|
+
"events[].name": [
|
|
241
|
+
"page:events"
|
|
242
|
+
],
|
|
243
|
+
"events[].online": [
|
|
244
|
+
"page:events"
|
|
245
|
+
],
|
|
246
|
+
"events[].onSale": [
|
|
247
|
+
"page:events"
|
|
248
|
+
],
|
|
249
|
+
"events[].priceFrom": [
|
|
250
|
+
"page:events"
|
|
251
|
+
],
|
|
252
|
+
"events[].registrationMode": [
|
|
253
|
+
"page:events"
|
|
254
|
+
],
|
|
255
|
+
"events[].soldOut": [
|
|
256
|
+
"page:events"
|
|
257
|
+
],
|
|
258
|
+
"events[].startsAt": [
|
|
259
|
+
"page:events"
|
|
260
|
+
],
|
|
261
|
+
"events[].venueName": [
|
|
262
|
+
"page:events"
|
|
263
|
+
],
|
|
264
|
+
"infoEvents": [
|
|
265
|
+
"section:whatsOn"
|
|
266
|
+
],
|
|
267
|
+
"latestArticles": [
|
|
268
|
+
"section:articles"
|
|
269
|
+
],
|
|
270
|
+
"latestArticles[].categories": [
|
|
271
|
+
"section:articles"
|
|
272
|
+
],
|
|
273
|
+
"latestArticles[].excerpt": [
|
|
274
|
+
"section:articles"
|
|
275
|
+
],
|
|
276
|
+
"latestArticles[].href": [
|
|
277
|
+
"section:articles"
|
|
278
|
+
],
|
|
279
|
+
"latestArticles[].imageUrl": [
|
|
280
|
+
"section:articles"
|
|
281
|
+
],
|
|
282
|
+
"latestArticles[].publishedAt": [
|
|
283
|
+
"section:articles"
|
|
284
|
+
],
|
|
285
|
+
"latestArticles[].readingMinutes": [
|
|
286
|
+
"section:articles"
|
|
287
|
+
],
|
|
288
|
+
"latestArticles[].title": [
|
|
289
|
+
"section:articles"
|
|
290
|
+
],
|
|
291
|
+
"nav.items": [
|
|
292
|
+
"layout"
|
|
293
|
+
],
|
|
294
|
+
"nav.items[].children": [
|
|
295
|
+
"layout"
|
|
296
|
+
],
|
|
297
|
+
"nav.items[].cta": [
|
|
298
|
+
"layout"
|
|
299
|
+
],
|
|
300
|
+
"nav.items[].href": [
|
|
301
|
+
"layout"
|
|
302
|
+
],
|
|
303
|
+
"nav.items[].label": [
|
|
304
|
+
"layout"
|
|
305
|
+
],
|
|
306
|
+
"section": [
|
|
307
|
+
"section"
|
|
308
|
+
],
|
|
309
|
+
"socials": [
|
|
310
|
+
"layout"
|
|
311
|
+
],
|
|
312
|
+
"socials[].iconSvg": [
|
|
313
|
+
"layout"
|
|
314
|
+
],
|
|
315
|
+
"socials[].platform": [
|
|
316
|
+
"layout"
|
|
317
|
+
],
|
|
318
|
+
"socials[].url": [
|
|
319
|
+
"layout"
|
|
320
|
+
],
|
|
321
|
+
"worship": [
|
|
322
|
+
"layout"
|
|
323
|
+
],
|
|
324
|
+
"worship.beginsLabel": [
|
|
325
|
+
"layout"
|
|
326
|
+
],
|
|
327
|
+
"worship.congregationLabel": [
|
|
328
|
+
"layout"
|
|
329
|
+
],
|
|
330
|
+
"worship.href": [
|
|
331
|
+
"layout"
|
|
332
|
+
],
|
|
333
|
+
"worship.note": [
|
|
334
|
+
"layout"
|
|
335
|
+
],
|
|
336
|
+
"worship.times": [
|
|
337
|
+
"layout"
|
|
338
|
+
],
|
|
339
|
+
"worship.today": [
|
|
340
|
+
"layout"
|
|
341
|
+
],
|
|
342
|
+
"worship.todayAlt": [
|
|
343
|
+
"layout"
|
|
344
|
+
]
|
|
345
|
+
},
|
|
346
|
+
"tokens": [
|
|
347
|
+
"--accent",
|
|
348
|
+
"--accent-strong",
|
|
349
|
+
"--bg",
|
|
350
|
+
"--bg-tint",
|
|
351
|
+
"--container",
|
|
352
|
+
"--cta-hover",
|
|
353
|
+
"--cta-hover-strong",
|
|
354
|
+
"--danger",
|
|
355
|
+
"--gold",
|
|
356
|
+
"--ink",
|
|
357
|
+
"--line",
|
|
358
|
+
"--line-strong",
|
|
359
|
+
"--muted",
|
|
360
|
+
"--primary",
|
|
361
|
+
"--primary-soft",
|
|
362
|
+
"--primary-strong",
|
|
363
|
+
"--radius",
|
|
364
|
+
"--radius-sm",
|
|
365
|
+
"--shadow",
|
|
366
|
+
"--shadow-sm",
|
|
367
|
+
"--surface"
|
|
368
|
+
],
|
|
369
|
+
"fonts": [
|
|
370
|
+
"Abril Fatface",
|
|
371
|
+
"Albert Sans",
|
|
372
|
+
"Alfa Slab One",
|
|
373
|
+
"Almarai",
|
|
374
|
+
"Amiri",
|
|
375
|
+
"Archivo",
|
|
376
|
+
"Arvo",
|
|
377
|
+
"Baloo 2",
|
|
378
|
+
"Barlow",
|
|
379
|
+
"Bebas Neue",
|
|
380
|
+
"Bitter",
|
|
381
|
+
"Cairo",
|
|
382
|
+
"Comfortaa",
|
|
383
|
+
"Cormorant Garamond",
|
|
384
|
+
"Crimson Pro",
|
|
385
|
+
"DM Sans",
|
|
386
|
+
"EB Garamond",
|
|
387
|
+
"Figtree",
|
|
388
|
+
"Fraunces",
|
|
389
|
+
"Fredoka",
|
|
390
|
+
"IBM Plex Sans",
|
|
391
|
+
"IBM Plex Sans Arabic",
|
|
392
|
+
"IBM Plex Serif",
|
|
393
|
+
"Inter",
|
|
394
|
+
"Josefin Sans",
|
|
395
|
+
"Jost",
|
|
396
|
+
"Karla",
|
|
397
|
+
"Lato",
|
|
398
|
+
"Libre Baskerville",
|
|
399
|
+
"Lora",
|
|
400
|
+
"Manrope",
|
|
401
|
+
"Merriweather",
|
|
402
|
+
"Montserrat",
|
|
403
|
+
"Newsreader",
|
|
404
|
+
"Noto Kufi Arabic",
|
|
405
|
+
"Noto Naskh Arabic",
|
|
406
|
+
"Nunito",
|
|
407
|
+
"Open Sans",
|
|
408
|
+
"Oswald",
|
|
409
|
+
"Outfit",
|
|
410
|
+
"Playfair Display",
|
|
411
|
+
"Plus Jakarta Sans",
|
|
412
|
+
"Poppins",
|
|
413
|
+
"Quicksand",
|
|
414
|
+
"Raleway",
|
|
415
|
+
"Roboto Slab",
|
|
416
|
+
"Rubik",
|
|
417
|
+
"Scheherazade New",
|
|
418
|
+
"Sora",
|
|
419
|
+
"Source Serif 4",
|
|
420
|
+
"Space Grotesk",
|
|
421
|
+
"Tajawal",
|
|
422
|
+
"Work Sans",
|
|
423
|
+
"Zilla Slab"
|
|
424
|
+
],
|
|
425
|
+
"manifest": {
|
|
426
|
+
"required": [
|
|
427
|
+
"format",
|
|
428
|
+
"name",
|
|
429
|
+
"supports",
|
|
430
|
+
"version"
|
|
431
|
+
],
|
|
432
|
+
"properties": [
|
|
433
|
+
"changelog",
|
|
434
|
+
"description",
|
|
435
|
+
"fonts",
|
|
436
|
+
"format",
|
|
437
|
+
"imagery",
|
|
438
|
+
"label",
|
|
439
|
+
"looks",
|
|
440
|
+
"name",
|
|
441
|
+
"settings",
|
|
442
|
+
"supports",
|
|
443
|
+
"version"
|
|
444
|
+
]
|
|
445
|
+
}
|
|
446
|
+
}
|
|
@@ -103,6 +103,17 @@ export async function validateArtifact(files) {
|
|
|
103
103
|
let homeHeroMultiShows = null; // sample (several photos): probe in html OR hero_carousel placed
|
|
104
104
|
let homeHeroSingleShows = null; // single-photo variant: probe rendered directly
|
|
105
105
|
|
|
106
|
+
// Author-renderable widget sections (the flip): each carries a curated data context; a template
|
|
107
|
+
// either places the section's DEFAULT ISLAND (which owns rendering + empty states) or renders
|
|
108
|
+
// the data itself — in which case both directions are proven behaviourally (the worship
|
|
109
|
+
// pattern): the populated fixture's sentinel must appear, and the EMPTY context must render it
|
|
110
|
+
// away (derive or omit — nothing invented, nothing dangling).
|
|
111
|
+
const WIDGET_SECTIONS = {
|
|
112
|
+
events: { island: 'events_carousel', dataKey: 'events', sentinel: 'Fixture Lantern Gala' },
|
|
113
|
+
whatsOn: { island: 'whats_on_strip', dataKey: 'infoEvents', sentinel: 'Fixture Open Morning' },
|
|
114
|
+
articles: { island: 'latest_articles', dataKey: 'latestArticles', sentinel: 'Fixture Winter Diary' }
|
|
115
|
+
};
|
|
116
|
+
|
|
106
117
|
// 2–4. Sections: catalogue membership, parse, fixture renders.
|
|
107
118
|
for (const type of manifest?.supports?.sections ?? []) {
|
|
108
119
|
const entry = catalogueByType.get(type);
|
|
@@ -122,12 +133,49 @@ export async function validateArtifact(files) {
|
|
|
122
133
|
errors.push(`section '${type}': does not parse under the dialect — ${e.message}`);
|
|
123
134
|
continue;
|
|
124
135
|
}
|
|
136
|
+
// Widget-section proof (independent of the minimal/sample loop): island placed → the island
|
|
137
|
+
// owns everything; hand-rendered → sentinel appears with data, vanishes without.
|
|
138
|
+
const widget = WIDGET_SECTIONS[type];
|
|
139
|
+
if (widget) {
|
|
140
|
+
const dataFixture = contextContract.fixtures.sections?.[type] ?? {};
|
|
141
|
+
try {
|
|
142
|
+
const populated = await liquid.render(parsed, {
|
|
143
|
+
section: {},
|
|
144
|
+
brand: contextContract.fixtures.brand,
|
|
145
|
+
...dataFixture
|
|
146
|
+
});
|
|
147
|
+
const placesIsland = splitIslandParts(populated).some((p) => p.island === widget.island);
|
|
148
|
+
if (!placesIsland) {
|
|
149
|
+
if (!populated.includes(widget.sentinel)) {
|
|
150
|
+
errors.push(
|
|
151
|
+
`section '${type}': neither places the ${widget.island} island nor renders the ${widget.dataKey} context — render the data (the fixture's "${widget.sentinel}" must appear) or place the island`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const empty = await liquid.render(parsed, {
|
|
155
|
+
section: {},
|
|
156
|
+
brand: contextContract.fixtures.brand,
|
|
157
|
+
[widget.dataKey]: []
|
|
158
|
+
});
|
|
159
|
+
if (empty.includes(widget.sentinel)) {
|
|
160
|
+
errors.push(`section '${type}': still shows fixture content with an empty ${widget.dataKey} — content must come from the context`);
|
|
161
|
+
}
|
|
162
|
+
if (/\bundefined\b|\bnull\b/.test(empty.replace(/data-[a-z-]+="[^"]*"/g, ''))) {
|
|
163
|
+
errors.push(`section '${type}': renders 'undefined'/'null' literals when ${widget.dataKey} is empty — guard the empty case (derive or omit)`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch (e) {
|
|
167
|
+
errors.push(`section '${type}': failed rendering the ${widget.dataKey} context fixtures — ${e.message}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
125
170
|
for (const fixtureName of ['minimal', 'sample']) {
|
|
126
171
|
const fixture = entry[fixtureName] ?? {};
|
|
127
172
|
try {
|
|
128
173
|
const html = await liquid.render(parsed, {
|
|
129
174
|
section: fixture,
|
|
130
|
-
brand: contextContract.fixtures.brand
|
|
175
|
+
brand: contextContract.fixtures.brand,
|
|
176
|
+
// Widget data rides the ordinary fixture renders too, so a hand-rendering section
|
|
177
|
+
// doesn't fail the generic pass for want of its context.
|
|
178
|
+
...(widget ? (contextContract.fixtures.sections?.[type] ?? {}) : {})
|
|
131
179
|
});
|
|
132
180
|
if (type === 'homeHero' && fixtureName === 'sample') {
|
|
133
181
|
const probe = 'Hero photo one';
|
package/starter/assets/theme.css
CHANGED
|
@@ -160,3 +160,30 @@ body { font-family: ' IBM Plex Sans Arabic', var(--p60s-siteFont, 'Inter'), ui-s
|
|
|
160
160
|
@media (max-width: 899px) {
|
|
161
161
|
.lq-vista-overlap { margin-top: 0; padding-top: 1.6rem; }
|
|
162
162
|
}
|
|
163
|
+
|
|
164
|
+
/* People / team cards — grid to single column on phones. */
|
|
165
|
+
.lq-people {
|
|
166
|
+
display: grid;
|
|
167
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
168
|
+
gap: 1.2rem;
|
|
169
|
+
text-align: center;
|
|
170
|
+
}
|
|
171
|
+
.lq-person {
|
|
172
|
+
background: var(--surface);
|
|
173
|
+
border: 1px solid var(--line);
|
|
174
|
+
border-radius: 12px;
|
|
175
|
+
padding: 1.4rem 1.1rem;
|
|
176
|
+
}
|
|
177
|
+
.lq-person-photo {
|
|
178
|
+
width: 88px;
|
|
179
|
+
height: 88px;
|
|
180
|
+
border-radius: 50%;
|
|
181
|
+
object-fit: cover;
|
|
182
|
+
display: block;
|
|
183
|
+
margin: 0 auto 0.8rem;
|
|
184
|
+
}
|
|
185
|
+
.lq-person-role { margin: 0.15rem 0 0; font-size: 0.9rem; color: var(--muted); }
|
|
186
|
+
.lq-person-statement { margin: 0.7rem 0 0; font-style: italic; color: var(--muted); }
|
|
187
|
+
@media (max-width: 640px) {
|
|
188
|
+
.lq-people { grid-template-columns: 1fr; }
|
|
189
|
+
}
|
package/starter/manifest.json
CHANGED
|
@@ -1,51 +1,52 @@
|
|
|
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
|
-
|
|
2
|
+
"name": "starter",
|
|
3
|
+
"version": "1.5.5",
|
|
4
|
+
"format": "port60-liquid@1",
|
|
5
|
+
"label": "Starter",
|
|
6
|
+
"description": "The reference template for the Port60 dialect — the developer docs' worked example and the base to copy when building your own. 1.1.0 adds the layout slot: the template owns the header, navigation and footer chrome.",
|
|
7
|
+
"supports": {
|
|
8
|
+
"pages": [
|
|
9
|
+
"home",
|
|
10
|
+
"about"
|
|
11
|
+
],
|
|
12
|
+
"sections": [
|
|
13
|
+
"homeHero",
|
|
14
|
+
"hero",
|
|
15
|
+
"values",
|
|
16
|
+
"cta",
|
|
17
|
+
"people"
|
|
18
|
+
],
|
|
19
|
+
"islands": [
|
|
20
|
+
"donation_widget",
|
|
21
|
+
"member_menu",
|
|
22
|
+
"hero_carousel"
|
|
23
|
+
],
|
|
24
|
+
"layout": true,
|
|
25
|
+
"heroImagery": true
|
|
26
|
+
},
|
|
27
|
+
"settings": {
|
|
28
|
+
"schema": [
|
|
29
|
+
{
|
|
30
|
+
"key": "siteFont",
|
|
31
|
+
"kind": "font",
|
|
32
|
+
"label": "Site font",
|
|
33
|
+
"default": "Inter",
|
|
34
|
+
"weights": [
|
|
35
|
+
400,
|
|
36
|
+
500,
|
|
37
|
+
600,
|
|
38
|
+
700,
|
|
39
|
+
800
|
|
40
|
+
]
|
|
41
|
+
}
|
|
39
42
|
]
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"note": "Landscape around 16:9 fills the hero cleanly; panoramas suit the whole-photo framing."
|
|
43
|
+
},
|
|
44
|
+
"changelog": "Versionless source workflow — no content changes.",
|
|
45
|
+
"imagery": {
|
|
46
|
+
"hero": {
|
|
47
|
+
"idealAspect": "16:9",
|
|
48
|
+
"minWidth": 1600,
|
|
49
|
+
"note": "Landscape around 16:9 fills the hero cleanly; panoramas suit the whole-photo framing."
|
|
50
|
+
}
|
|
49
51
|
}
|
|
50
|
-
}
|
|
51
52
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{% comment %}
|
|
2
|
+
People / team — the reference implementation: simple cards (name, role, optional photo and
|
|
3
|
+
one-line statement), a grid that collapses to one column on phones. No placeholder faces:
|
|
4
|
+
a person without a photo just renders text (derive or omit, never fabricate).
|
|
5
|
+
{% endcomment %}
|
|
6
|
+
<section class="lq-section">
|
|
7
|
+
<div class="container">
|
|
8
|
+
{% if section.eyebrow or section.heading or section.text %}
|
|
9
|
+
<div class="lq-section-head">
|
|
10
|
+
{% if section.eyebrow %}<p class="lq-kicker">{{ section.eyebrow }}</p>{% endif %}
|
|
11
|
+
{% if section.heading %}<h2>{{ section.heading }}</h2>{% endif %}
|
|
12
|
+
{% if section.text %}<p class="lq-muted">{{ section.text }}</p>{% endif %}
|
|
13
|
+
</div>
|
|
14
|
+
{% endif %}
|
|
15
|
+
<div class="lq-people">
|
|
16
|
+
{% for person in section.items %}
|
|
17
|
+
<article class="lq-person">
|
|
18
|
+
{% if person.imageUrl %}<img class="lq-person-photo" src="{{ person.imageUrl }}" alt="" loading="lazy">{% endif %}
|
|
19
|
+
<h3>{{ person.name }}</h3>
|
|
20
|
+
<p class="lq-person-role">{{ person.role }}</p>
|
|
21
|
+
{% if person.statement %}<p class="lq-person-statement">“{{ person.statement }}”</p>{% endif %}
|
|
22
|
+
</article>
|
|
23
|
+
{% endfor %}
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</section>
|