@port60/template-kit 0.1.1 → 0.3.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/lib/agentsMd.mjs +6 -0
- package/src/vendor/contract/v1/islands.json +18 -1
- package/src/vendor/contract/v1/manifest.schema.json +5 -0
- package/src/vendor/contract/v1/sections.json +45 -2
- package/src/vendor/validator/validate.mjs +43 -0
- package/starter/assets/theme.css +72 -0
- package/starter/manifest.json +9 -5
- package/starter/sections/homeHero.liquid +51 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@port60/template-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Build Port60 site templates locally: scaffold, live-preview, validate against the platform contract, and package for studio upload. AI-agent ready — every scaffold ships AGENTS.md and validate emits machine-readable JSON.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/lib/agentsMd.mjs
CHANGED
|
@@ -62,6 +62,12 @@ platform accepts it; if it fails here, the upload will fail identically.
|
|
|
62
62
|
- Theme via CSS custom properties and the settings knobs you declare in
|
|
63
63
|
\`manifest.settings.schema\` — they surface in the charity's Appearance editor as
|
|
64
64
|
\`--p60s-<key>\` variables and \`data-p60s-<key>\` body attributes.
|
|
65
|
+
- Hero photographs (\`homeHero.images\`, when you declare \`supports.heroImagery\`): render one
|
|
66
|
+
photo directly as a TREATED backdrop (a scrim/tint built from your own palette variables via
|
|
67
|
+
color-mix — never raw), place the \`hero_carousel\` island for two or more (style its
|
|
68
|
+
\`.hero-slide-scrim\`), and design the no-photo state as a gradient/colour — never a
|
|
69
|
+
placeholder. The starter's \`.lq-homehero\` is the reference; the validator checks all of this
|
|
70
|
+
behaviourally.
|
|
65
71
|
- "Looks" = named one-click bundles of knob values in \`manifest.looks\`.
|
|
66
72
|
- Fonts: only families from the platform font catalogue, declared with the weights you use.
|
|
67
73
|
|
|
@@ -90,6 +90,23 @@
|
|
|
90
90
|
".course-pay-panel",
|
|
91
91
|
".course-soldout-note"
|
|
92
92
|
]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"name": "hero_carousel",
|
|
96
|
+
"label": "Hero image carousel",
|
|
97
|
+
"status": "available",
|
|
98
|
+
"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.",
|
|
99
|
+
"params": [],
|
|
100
|
+
"stylingApi": [
|
|
101
|
+
".hero-carousel",
|
|
102
|
+
".hero-slides",
|
|
103
|
+
".hero-slide",
|
|
104
|
+
".hero-slide-img",
|
|
105
|
+
".hero-slide-scrim",
|
|
106
|
+
".hero-dots",
|
|
107
|
+
".hero-dot",
|
|
108
|
+
".hero-dot--active"
|
|
109
|
+
]
|
|
93
110
|
}
|
|
94
111
|
]
|
|
95
|
-
}
|
|
112
|
+
}
|
|
@@ -70,6 +70,11 @@
|
|
|
70
70
|
"type": "boolean",
|
|
71
71
|
"default": false,
|
|
72
72
|
"description": "True when the layout renders the `worship` context (the prayer/service-times rail). Declared honesty: the choosers badge worship-enabled tenants toward looks that show their times, and warn on looks that don't. Requires supports.layout; the layout must actually branch on `worship`."
|
|
73
|
+
},
|
|
74
|
+
"heroImagery": {
|
|
75
|
+
"type": "boolean",
|
|
76
|
+
"default": false,
|
|
77
|
+
"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."
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
80
|
},
|
|
@@ -221,13 +221,56 @@
|
|
|
221
221
|
"name": "imageUrl",
|
|
222
222
|
"kind": "image",
|
|
223
223
|
"required": false,
|
|
224
|
-
"description": "Background photograph for the hero (uploaded through the platform pipeline). The template
|
|
224
|
+
"description": "Background photograph for the hero (uploaded through the platform pipeline). Superseded by `images` when that carries any entry — render imageUrl only when images is absent/empty. The template must TREAT the photo (scrim/tint from its own palette variables), never place it raw, and omit for its designed no-photo state."
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"name": "images",
|
|
228
|
+
"kind": "items",
|
|
229
|
+
"required": false,
|
|
230
|
+
"description": "Hero photographs (0–6, uploaded through the platform pipeline). One = a main image; several = rotation material — a template with supports.heroImagery renders at least the first and MAY place the hero_carousel island (or a CSS scroll strip) for the rest. Like every field: absent means the template's designed no-photo state, never a placeholder.",
|
|
231
|
+
"itemFields": [
|
|
232
|
+
{
|
|
233
|
+
"name": "imageUrl",
|
|
234
|
+
"kind": "image",
|
|
235
|
+
"required": true,
|
|
236
|
+
"description": "The photograph."
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"name": "alt",
|
|
240
|
+
"kind": "text",
|
|
241
|
+
"required": false,
|
|
242
|
+
"description": "Accessible description; empty renders as decorative (alt=\"\")."
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"name": "focus",
|
|
246
|
+
"kind": "select",
|
|
247
|
+
"required": false,
|
|
248
|
+
"options": [
|
|
249
|
+
"centre",
|
|
250
|
+
"top",
|
|
251
|
+
"bottom",
|
|
252
|
+
"left",
|
|
253
|
+
"right"
|
|
254
|
+
],
|
|
255
|
+
"description": "Which part of the photograph survives the fill-crop (templates render photos cover-style, so an aspect mismatch crops — this picks the slice that stays). Maps to background-position/object-position; absent means centre."
|
|
256
|
+
}
|
|
257
|
+
]
|
|
225
258
|
}
|
|
226
259
|
],
|
|
227
260
|
"minimal": {},
|
|
228
261
|
"sample": {
|
|
229
262
|
"lead": "We turn everyday generosity into practical help for the families who need it most.",
|
|
230
|
-
"givingStyle": "button"
|
|
263
|
+
"givingStyle": "button",
|
|
264
|
+
"images": [
|
|
265
|
+
{
|
|
266
|
+
"imageUrl": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 90'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23355e4b'/%3E%3Cstop offset='1' stop-color='%23103528'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='160' height='90' fill='url(%23g)'/%3E%3Ccircle cx='118' cy='28' r='14' fill='%23baa769' opacity='.7'/%3E%3Ctext x='10' y='78' font-family='sans-serif' font-size='9' fill='%23ffffff' opacity='.75'%3EHero photo one%3C/text%3E%3C/svg%3E",
|
|
267
|
+
"alt": "Volunteers serving meals at dusk"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"imageUrl": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 90'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23445a78'/%3E%3Cstop offset='1' stop-color='%23141d2c'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='160' height='90' fill='url(%23g)'/%3E%3Ccircle cx='40' cy='30' r='12' fill='%23e8e2d4' opacity='.5'/%3E%3Ctext x='10' y='78' font-family='sans-serif' font-size='9' fill='%23ffffff' opacity='.75'%3EHero photo two%3C/text%3E%3C/svg%3E",
|
|
271
|
+
"alt": "The community hall on open day"
|
|
272
|
+
}
|
|
273
|
+
]
|
|
231
274
|
}
|
|
232
275
|
},
|
|
233
276
|
{
|
|
@@ -99,6 +99,9 @@ export async function validateArtifact(files) {
|
|
|
99
99
|
const catalogueByType = new Map(sectionCatalogue.sections.map((s) => [s.type, s]));
|
|
100
100
|
const declaredIslands = new Set(manifest?.supports?.islands ?? []);
|
|
101
101
|
const placedIslands = new Set();
|
|
102
|
+
// heroImagery proof state (filled by the homeHero renders below).
|
|
103
|
+
let homeHeroMultiShows = null; // sample (several photos): probe in html OR hero_carousel placed
|
|
104
|
+
let homeHeroSingleShows = null; // single-photo variant: probe rendered directly
|
|
102
105
|
|
|
103
106
|
// 2–4. Sections: catalogue membership, parse, fixture renders.
|
|
104
107
|
for (const type of manifest?.supports?.sections ?? []) {
|
|
@@ -126,6 +129,21 @@ export async function validateArtifact(files) {
|
|
|
126
129
|
section: fixture,
|
|
127
130
|
brand: contextContract.fixtures.brand
|
|
128
131
|
});
|
|
132
|
+
if (type === 'homeHero' && fixtureName === 'sample') {
|
|
133
|
+
const probe = 'Hero photo one';
|
|
134
|
+
homeHeroMultiShows = html.includes(probe)
|
|
135
|
+
|| splitIslandParts(html).some((p) => p.island === 'hero_carousel');
|
|
136
|
+
// The single-photo path proven separately: same fixture, first photo only.
|
|
137
|
+
try {
|
|
138
|
+
const single = await liquid.render(parsed, {
|
|
139
|
+
section: { ...fixture, images: (fixture.images ?? []).slice(0, 1) },
|
|
140
|
+
brand: contextContract.fixtures.brand
|
|
141
|
+
});
|
|
142
|
+
homeHeroSingleShows = single.includes(probe);
|
|
143
|
+
} catch {
|
|
144
|
+
homeHeroSingleShows = false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
129
147
|
for (const part of splitIslandParts(html)) {
|
|
130
148
|
if (part.island === CONTENT_SLOT) {
|
|
131
149
|
errors.push(`section '${type}': uses {% content %} — that tag is layout-only`);
|
|
@@ -139,6 +157,31 @@ export async function validateArtifact(files) {
|
|
|
139
157
|
}
|
|
140
158
|
}
|
|
141
159
|
|
|
160
|
+
// Hero-imagery honesty, checked BEHAVIOURALLY (the worship pattern). The homeHero sample
|
|
161
|
+
// fixture carries photographs whose data URIs embed a quote-free marker that survives HTML
|
|
162
|
+
// escaping, so "does the rendered hero display the tenant's photos?" is a substring check —
|
|
163
|
+
// and with several photos, placing the hero_carousel island IS displaying them (the island
|
|
164
|
+
// renders the slides at runtime). The single-photo path is proven separately: images[0] must
|
|
165
|
+
// appear directly. Declaration and behaviour must agree; the choosers badge photo-led tenants
|
|
166
|
+
// by supports.heroImagery. Legacy imageUrl-only renderers never match (the fixture's photos
|
|
167
|
+
// ride `images`), so they pass undeclared — they just don't earn the badge.
|
|
168
|
+
{
|
|
169
|
+
const declaresHero = manifest?.supports?.heroImagery === true;
|
|
170
|
+
if (declaresHero && !(manifest?.supports?.sections ?? []).includes('homeHero')) {
|
|
171
|
+
errors.push('manifest: supports.heroImagery requires the homeHero section — the photographs live on it');
|
|
172
|
+
} else if (homeHeroMultiShows !== null) {
|
|
173
|
+
if (declaresHero && !homeHeroMultiShows) {
|
|
174
|
+
errors.push('homeHero: manifest declares supports.heroImagery but the rendered section neither displays the images fixture nor places the hero_carousel island');
|
|
175
|
+
}
|
|
176
|
+
if (declaresHero && !homeHeroSingleShows) {
|
|
177
|
+
errors.push('homeHero: supports.heroImagery must render a SINGLE photograph directly (images[0], treated, never raw) — the carousel island only covers 2+');
|
|
178
|
+
}
|
|
179
|
+
if (!declaresHero && (homeHeroMultiShows || homeHeroSingleShows)) {
|
|
180
|
+
errors.push('homeHero: renders the hero photographs but the manifest does not declare supports.heroImagery — declare it so the choosers can badge it');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
142
185
|
// 7. Layout (when declared): parse + render the layout fixture + exactly one content slot.
|
|
143
186
|
if (manifest?.supports?.worship && !manifest?.supports?.layout) {
|
|
144
187
|
errors.push('manifest: supports.worship requires supports.layout — the worship rail is layout chrome');
|
package/starter/assets/theme.css
CHANGED
|
@@ -41,3 +41,75 @@ strong { color: var(--ink); }
|
|
|
41
41
|
|
|
42
42
|
/* The one font slot — headings above already follow it; body text too. */
|
|
43
43
|
body { font-family: ' IBM Plex Sans Arabic', var(--p60s-siteFont, 'Inter'), ui-sans-serif, system-ui, sans-serif; }
|
|
44
|
+
|
|
45
|
+
/* ── Home hero (supports.heroImagery — the reference treatment) ─────────────────────────────
|
|
46
|
+
The no-photo state is a DESIGNED gradient (rule: derive or omit, never a placeholder image).
|
|
47
|
+
Photos get a scrim built from the palette itself via color-mix, so uploads tone into the
|
|
48
|
+
template — and any palette change re-tones them for free. Never place a hero photo raw. */
|
|
49
|
+
.lq-homehero {
|
|
50
|
+
position: relative;
|
|
51
|
+
padding: 4.5rem 0;
|
|
52
|
+
background:
|
|
53
|
+
radial-gradient(720px 380px at 85% -10%, var(--primary-soft), transparent 60%),
|
|
54
|
+
linear-gradient(165deg, var(--bg-tint) 0%, var(--bg) 100%);
|
|
55
|
+
}
|
|
56
|
+
.lq-homehero--photo { background-size: cover; background-position: center; }
|
|
57
|
+
.lq-homehero--photo::before {
|
|
58
|
+
content: '';
|
|
59
|
+
position: absolute;
|
|
60
|
+
inset: 0;
|
|
61
|
+
background: linear-gradient(
|
|
62
|
+
180deg,
|
|
63
|
+
color-mix(in srgb, var(--bg) 68%, transparent) 0%,
|
|
64
|
+
color-mix(in srgb, var(--bg) 38%, transparent) 50%,
|
|
65
|
+
color-mix(in srgb, var(--bg-tint) 72%, transparent) 100%
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
.lq-homehero > .container { position: relative; z-index: 1; }
|
|
69
|
+
.lq-homehero-inner { display: grid; gap: 2.5rem; align-items: center; }
|
|
70
|
+
@media (min-width: 900px) {
|
|
71
|
+
.lq-homehero-inner { grid-template-columns: 1.2fr 1fr; }
|
|
72
|
+
}
|
|
73
|
+
.lq-homehero-lead { color: var(--muted); max-width: 46ch; margin: 0.9rem 0 1.4rem; }
|
|
74
|
+
.lq-homehero-actions { display: flex; gap: 0.8rem; flex-wrap: wrap; }
|
|
75
|
+
|
|
76
|
+
/* The platform hero_carousel island, styled through its class API — the island owns motion
|
|
77
|
+
(auto-advance, swipe, reduced-motion), this owns the look. Each slide's .hero-slide-scrim is
|
|
78
|
+
the same palette treatment as a single photo. */
|
|
79
|
+
.lq-homehero--carousel { overflow: hidden; }
|
|
80
|
+
.lq-homehero--carousel .hero-carousel { position: absolute; inset: 0; z-index: 0; }
|
|
81
|
+
.lq-homehero--carousel .hero-slides,
|
|
82
|
+
.lq-homehero--carousel .hero-slide { position: absolute; inset: 0; }
|
|
83
|
+
.lq-homehero--carousel .hero-slide { opacity: 0; transition: opacity 1s ease; }
|
|
84
|
+
.lq-homehero--carousel .hero-slide--active { opacity: 1; }
|
|
85
|
+
.lq-homehero--carousel .hero-slide-img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
86
|
+
.lq-homehero--carousel .hero-slide-scrim {
|
|
87
|
+
position: absolute;
|
|
88
|
+
inset: 0;
|
|
89
|
+
background: linear-gradient(
|
|
90
|
+
180deg,
|
|
91
|
+
color-mix(in srgb, var(--bg) 68%, transparent) 0%,
|
|
92
|
+
color-mix(in srgb, var(--bg) 38%, transparent) 50%,
|
|
93
|
+
color-mix(in srgb, var(--bg-tint) 72%, transparent) 100%
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
.lq-homehero--carousel .hero-dots {
|
|
97
|
+
position: absolute;
|
|
98
|
+
left: 0;
|
|
99
|
+
right: 0;
|
|
100
|
+
bottom: 0.8rem;
|
|
101
|
+
display: flex;
|
|
102
|
+
justify-content: center;
|
|
103
|
+
gap: 0.4rem;
|
|
104
|
+
z-index: 2;
|
|
105
|
+
}
|
|
106
|
+
.lq-homehero--carousel .hero-dot {
|
|
107
|
+
width: 9px;
|
|
108
|
+
height: 9px;
|
|
109
|
+
border-radius: 50%;
|
|
110
|
+
border: 1px solid var(--accent);
|
|
111
|
+
background: transparent;
|
|
112
|
+
padding: 0;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
}
|
|
115
|
+
.lq-homehero--carousel .hero-dot--active { background: var(--accent); }
|
package/starter/manifest.json
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"format": "port60-liquid@1",
|
|
5
5
|
"label": "Starter",
|
|
6
|
-
"description": "The reference template for the Port60 dialect
|
|
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
7
|
"supports": {
|
|
8
8
|
"pages": [
|
|
9
|
+
"home",
|
|
9
10
|
"about"
|
|
10
11
|
],
|
|
11
12
|
"sections": [
|
|
13
|
+
"homeHero",
|
|
12
14
|
"hero",
|
|
13
15
|
"values",
|
|
14
16
|
"cta"
|
|
15
17
|
],
|
|
16
18
|
"islands": [
|
|
17
19
|
"donation_widget",
|
|
18
|
-
"member_menu"
|
|
20
|
+
"member_menu",
|
|
21
|
+
"hero_carousel"
|
|
19
22
|
],
|
|
20
|
-
"layout": true
|
|
23
|
+
"layout": true,
|
|
24
|
+
"heroImagery": true
|
|
21
25
|
},
|
|
22
26
|
"settings": {
|
|
23
27
|
"schema": [
|
|
@@ -36,5 +40,5 @@
|
|
|
36
40
|
}
|
|
37
41
|
]
|
|
38
42
|
},
|
|
39
|
-
"changelog": "
|
|
43
|
+
"changelog": "Per-photo focus: choose which part of a hero photograph survives the fill-crop (centre/top/bottom/left/right) — panoramas and portraits stop cropping blind."
|
|
40
44
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{% comment %}
|
|
2
|
+
The home opener — and the REFERENCE IMPLEMENTATION of supports.heroImagery. Three rules worth
|
|
3
|
+
copying:
|
|
4
|
+
|
|
5
|
+
1. EVERY field is optional. Copy derives from brand (| default) or omits; no photos means the
|
|
6
|
+
designed gradient below — never a placeholder image.
|
|
7
|
+
2. Photos are NEVER placed raw. The .lq-homehero--photo treatment in theme.css lays a scrim
|
|
8
|
+
built from the palette variables (color-mix over var(--bg)/var(--primary)), so any uploaded
|
|
9
|
+
photograph is toned into the template. Tune the treatment, don't remove it.
|
|
10
|
+
3. Templates ship no JavaScript, so rotation is the PLATFORM's hero_carousel island: with two
|
|
11
|
+
or more photos, place it and style its documented classes (.hero-slide, .hero-slide-scrim,
|
|
12
|
+
.hero-dots) — the platform owns the motion, you own the look.
|
|
13
|
+
{% endcomment %}
|
|
14
|
+
{% assign heroCount = section.images | size %}
|
|
15
|
+
{% if heroCount == 1 %}
|
|
16
|
+
{% assign heroPhoto = section.images.first.imageUrl %}
|
|
17
|
+
{% elsif heroCount == 0 %}
|
|
18
|
+
{% assign heroPhoto = section.imageUrl %}
|
|
19
|
+
{% endif %}
|
|
20
|
+
{% assign heroFocus = 'center' %}
|
|
21
|
+
{% if heroCount > 0 %}
|
|
22
|
+
{% case section.images.first.focus %}
|
|
23
|
+
{% when 'top' %}{% assign heroFocus = 'center top' %}
|
|
24
|
+
{% when 'bottom' %}{% assign heroFocus = 'center bottom' %}
|
|
25
|
+
{% when 'left' %}{% assign heroFocus = 'left center' %}
|
|
26
|
+
{% when 'right' %}{% assign heroFocus = 'right center' %}
|
|
27
|
+
{% endcase %}
|
|
28
|
+
{% endif %}
|
|
29
|
+
<section class="lq-homehero{% if heroPhoto %} lq-homehero--photo{% endif %}{% if heroCount > 1 %} lq-homehero--carousel{% endif %}"{% if heroPhoto %} style="background-image: url('{{ heroPhoto }}'); background-position: {{ heroFocus }}"{% endif %}>
|
|
30
|
+
{% if heroCount > 1 %}{% island 'hero_carousel' %}{% endif %}
|
|
31
|
+
<div class="container lq-homehero-inner">
|
|
32
|
+
<div>
|
|
33
|
+
<p class="lq-kicker">{{ section.eyebrow | default: brand.name }}</p>
|
|
34
|
+
<h1>{{ section.title | default: brand.tagline | default: brand.name }}</h1>
|
|
35
|
+
{% if section.lead %}<p class="lq-homehero-lead">{{ section.lead }}</p>{% endif %}
|
|
36
|
+
<div class="lq-homehero-actions">
|
|
37
|
+
{% if section.givingStyle == 'button' %}
|
|
38
|
+
<a class="button button-primary" href="{{ section.primaryHref | default: '/donate' }}">{{ section.primaryLabel | default: 'Donate now' }}</a>
|
|
39
|
+
{% else %}
|
|
40
|
+
<a class="button button-primary" href="#donate">{{ section.primaryLabel | default: 'Donate now' }}</a>
|
|
41
|
+
{% endif %}
|
|
42
|
+
<a class="button" href="{{ section.secondaryHref | default: '/about' }}">{{ section.secondaryLabel | default: 'Our story' }}</a>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
{% unless section.givingStyle == 'button' %}
|
|
46
|
+
<aside id="donate">
|
|
47
|
+
{% island 'donation_widget' %}
|
|
48
|
+
</aside>
|
|
49
|
+
{% endunless %}
|
|
50
|
+
</div>
|
|
51
|
+
</section>
|