@port60/template-kit 0.16.0 → 0.18.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.
@@ -2579,6 +2579,300 @@ textarea.p60-form-control {
2579
2579
  border-color: var(--primary-strong);
2580
2580
  }
2581
2581
 
2582
+ /* ---- Volunteer sign-up widget (components/VolunteerWidget.tsx) ----
2583
+ Reuses the donate card vocabulary (.donate-card/.donate-head/.field/.donate-submit/.donate-success)
2584
+ so a themed donate card themes this too; vol-* covers what is specific to asking for help. */
2585
+ .vol-search input,
2586
+ .vol-form input:not([type='checkbox']):not([type='radio']),
2587
+ .vol-form select,
2588
+ .vol-form textarea {
2589
+ width: 100%;
2590
+ border: 1.5px solid var(--line);
2591
+ border-radius: 12px;
2592
+ padding: 0.7rem 0.85rem;
2593
+ font: inherit;
2594
+ line-height: 1.45;
2595
+ color: var(--ink);
2596
+ background: var(--surface);
2597
+ }
2598
+
2599
+ .vol-search input:focus,
2600
+ .vol-form input:not([type='checkbox']):not([type='radio']):focus,
2601
+ .vol-form select:focus,
2602
+ .vol-form textarea:focus {
2603
+ outline: none;
2604
+ border-color: var(--primary);
2605
+ box-shadow: 0 0 0 3px var(--primary-soft);
2606
+ }
2607
+
2608
+ /* The steps live in a body of ONE height (owner rule: the hero widget must not grow and jump
2609
+ between steps). Lists scroll inside it; a step shorter than the body leaves quiet space. */
2610
+ .vol-body {
2611
+ display: flex;
2612
+ flex-direction: column;
2613
+ gap: 1rem;
2614
+ }
2615
+
2616
+ .vol-body--list {
2617
+ height: 32rem;
2618
+ overflow: hidden;
2619
+ }
2620
+
2621
+ .vol-body--form {
2622
+ min-height: 32rem;
2623
+ }
2624
+
2625
+ .vol-list {
2626
+ display: grid;
2627
+ gap: 0.6rem;
2628
+ align-content: start;
2629
+ flex: 1 1 auto;
2630
+ min-height: 0;
2631
+ overflow-y: auto;
2632
+ padding-right: 0.2rem;
2633
+ scrollbar-gutter: stable;
2634
+ }
2635
+
2636
+ .vol-body > .vol-form,
2637
+ .vol-body > .vol-chosen,
2638
+ .vol-body > .vol-search {
2639
+ flex: none;
2640
+ }
2641
+
2642
+ .vol-group {
2643
+ display: grid;
2644
+ gap: 0.5rem;
2645
+ }
2646
+
2647
+ .vol-group-label {
2648
+ margin: 0.2rem 0 0;
2649
+ font-size: 0.78rem;
2650
+ font-weight: 700;
2651
+ letter-spacing: 0.06em;
2652
+ text-transform: uppercase;
2653
+ color: var(--muted);
2654
+ }
2655
+
2656
+ .vol-option {
2657
+ display: flex;
2658
+ align-items: center;
2659
+ justify-content: space-between;
2660
+ gap: 0.8rem;
2661
+ width: 100%;
2662
+ text-align: start;
2663
+ border: 1.5px solid var(--line);
2664
+ background: var(--surface);
2665
+ color: var(--ink);
2666
+ border-radius: 12px;
2667
+ padding: 0.85rem 1rem;
2668
+ font: inherit;
2669
+ cursor: pointer;
2670
+ transition: border-color 0.16s ease, background 0.16s ease;
2671
+ }
2672
+
2673
+ .vol-option:hover,
2674
+ .vol-option:focus-visible {
2675
+ border-color: var(--primary);
2676
+ background: var(--primary-soft);
2677
+ outline: none;
2678
+ }
2679
+
2680
+ .vol-option-main {
2681
+ display: grid;
2682
+ gap: 0.15rem;
2683
+ min-width: 0;
2684
+ }
2685
+
2686
+ .vol-option-summary {
2687
+ font-size: 0.88rem;
2688
+ color: var(--muted);
2689
+ line-height: 1.35;
2690
+ }
2691
+
2692
+ .vol-option-meta {
2693
+ display: grid;
2694
+ gap: 0.2rem;
2695
+ justify-items: end;
2696
+ flex-shrink: 0;
2697
+ text-align: end;
2698
+ }
2699
+
2700
+ .vol-needed {
2701
+ font-size: 0.8rem;
2702
+ font-weight: 700;
2703
+ color: var(--primary-strong);
2704
+ white-space: nowrap;
2705
+ }
2706
+
2707
+ .vol-needed.is-full {
2708
+ color: var(--muted);
2709
+ }
2710
+
2711
+ .vol-shift-count {
2712
+ font-size: 0.78rem;
2713
+ color: var(--muted);
2714
+ white-space: nowrap;
2715
+ }
2716
+
2717
+ .vol-chosen {
2718
+ display: flex;
2719
+ align-items: center;
2720
+ justify-content: space-between;
2721
+ gap: 0.8rem;
2722
+ padding: 0.7rem 0.9rem;
2723
+ border-radius: 12px;
2724
+ background: var(--primary-soft);
2725
+ font-size: 0.92rem;
2726
+ }
2727
+
2728
+ .vol-chosen-shift {
2729
+ display: block;
2730
+ color: var(--muted);
2731
+ font-size: 0.85rem;
2732
+ }
2733
+
2734
+ .vol-form {
2735
+ display: grid;
2736
+ gap: 0.9rem;
2737
+ }
2738
+
2739
+ .vol-optional {
2740
+ font-style: normal;
2741
+ font-weight: 500;
2742
+ color: var(--muted);
2743
+ }
2744
+
2745
+ .vol-availability {
2746
+ border: 0;
2747
+ padding: 0;
2748
+ margin: 0;
2749
+ }
2750
+
2751
+ .vol-availability legend {
2752
+ font-weight: 600;
2753
+ font-size: 0.9rem;
2754
+ line-height: 1.4;
2755
+ padding: 0;
2756
+ margin-bottom: 0.4rem;
2757
+ }
2758
+
2759
+ /* The When row is a select or a chip row; both sit on the same box height so the panel never
2760
+ changes height when the choice above changes. */
2761
+ .vol-form select,
2762
+ .vol-chip {
2763
+ box-sizing: border-box;
2764
+ min-height: 3.1rem;
2765
+ }
2766
+
2767
+ .vol-form .field > span {
2768
+ line-height: 1.4;
2769
+ }
2770
+
2771
+ .vol-chips {
2772
+ display: flex;
2773
+ flex-wrap: wrap;
2774
+ gap: 0.4rem;
2775
+ }
2776
+
2777
+ .vol-summary-needed {
2778
+ color: var(--primary-strong);
2779
+ font-weight: 700;
2780
+ text-transform: capitalize;
2781
+ }
2782
+
2783
+ .vol-chip {
2784
+ flex: 1 1 0;
2785
+ border: 1.5px solid var(--line);
2786
+ background: var(--surface);
2787
+ color: var(--ink);
2788
+ border-radius: 999px;
2789
+ /* The same vertical rhythm as a select in this form, so the When row never changes height. */
2790
+ padding: 0.7rem 0.4rem;
2791
+ font: inherit;
2792
+ font-size: 0.86rem;
2793
+ line-height: 1.45;
2794
+ font-weight: 600;
2795
+ cursor: pointer;
2796
+ white-space: nowrap;
2797
+ }
2798
+
2799
+ .vol-chip.selected {
2800
+ border-color: var(--primary);
2801
+ background: var(--primary);
2802
+ color: #fff;
2803
+ }
2804
+
2805
+
2806
+ .vol-why {
2807
+ text-align: center;
2808
+ }
2809
+
2810
+ .vol-form select {
2811
+ appearance: none;
2812
+ -webkit-appearance: none;
2813
+ padding-right: 2.4rem;
2814
+ background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23555' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
2815
+ background-repeat: no-repeat;
2816
+ background-position: right 0.9rem center;
2817
+ }
2818
+
2819
+ .vol-form select:disabled {
2820
+ opacity: 0.6;
2821
+ }
2822
+
2823
+ .vol-summary {
2824
+ margin: -0.3rem 0 0;
2825
+ font-size: 0.86rem;
2826
+ line-height: 1.4;
2827
+ min-height: calc(0.86rem * 1.4 * 2);
2828
+ color: var(--muted);
2829
+ display: -webkit-box;
2830
+ -webkit-line-clamp: 2;
2831
+ -webkit-box-orient: vertical;
2832
+ overflow: hidden;
2833
+ }
2834
+
2835
+ .vol-field-error {
2836
+ display: block;
2837
+ font-style: normal;
2838
+ font-size: 0.8rem;
2839
+ line-height: 1.3;
2840
+ color: var(--danger, #b3261e);
2841
+ }
2842
+
2843
+ .vol-form .field.has-error input,
2844
+ .vol-form .field.has-error select {
2845
+ border-color: var(--danger, #b3261e);
2846
+ }
2847
+
2848
+ .vol-signed-in {
2849
+ margin: 0;
2850
+ }
2851
+
2852
+ .vol-extra {
2853
+ display: grid;
2854
+ gap: 0.9rem;
2855
+ padding: 0.9rem;
2856
+ border: 1px dashed var(--line);
2857
+ border-radius: 12px;
2858
+ }
2859
+
2860
+ .vol-extra .p60-form-heading {
2861
+ margin: 0;
2862
+ font-size: 1rem;
2863
+ }
2864
+
2865
+ @media (max-width: 560px) {
2866
+ .vol-option {
2867
+ flex-direction: column;
2868
+ align-items: stretch;
2869
+ }
2870
+ .vol-option-meta {
2871
+ justify-items: start;
2872
+ text-align: start;
2873
+ }
2874
+ }
2875
+
2582
2876
  /* ---- Members area (shell + sections; see components/members/) ----
2583
2877
  Desktop: left rail + content. Small screens: the rail becomes a horizontal, scrollable pill
2584
2878
  strip above the content — same registry, no separate mobile markup. */
@@ -17,6 +17,7 @@ import islandRegistry from '../contract/v1/islands.json' with { type: 'json' };
17
17
  import contextContract from '../contract/v1/context.json' with { type: 'json' };
18
18
  import { resolveFixtureArt } from './fixture-art.mjs';
19
19
  import { buildSiteFixture, applyPreviewContent } from './site-context.mjs';
20
+ import { normaliseFocus, previewActions, withResolvedActions, applyFocus } from './focus.mjs';
20
21
 
21
22
  // Fixture imagery resolved for the SEALED studio render (p60fixture: refs become inline-SVG data
22
23
  // URIs the network-dead CSP can show). The dev preview may instead resolve them to the platform
@@ -143,6 +144,25 @@ function islandSkeleton(name, ctx = {}, fx = STUDIO_FX) {
143
144
  <label class="newsletter-label">Email address</label><div class="newsletter-fields"><input class="newsletter-email" type="email" disabled><button class="newsletter-submit" type="button" disabled>Join our newsletter</button></div>
144
145
  <label class="newsletter-consent"><input type="checkbox" disabled><span>Email me about our work and appeals.</span></label>
145
146
  </form>`;
147
+ case 'primary_action_widget':
148
+ // What this island becomes follows the preview's focus (fixtures.focus, from ?focus= on the
149
+ // dev server): the volunteer sign-up, nothing, or the default, the donation widget.
150
+ if (fixtures.focus === 'volunteer') {
151
+ return islandSkeleton('volunteer_signup', ctx, fx).replace('data-p60-preview-island="volunteer_signup"', 'data-p60-preview-island="primary_action_widget"');
152
+ }
153
+ if (fixtures.focus === 'none') {
154
+ return `<div class="p60-preview-empty" data-p60-preview-island="primary_action_widget">${previewNote(name)}<p>No widget here: this charity leads with buttons only (site.focus is 'none').</p></div>`;
155
+ }
156
+ return islandSkeleton('donation_widget', ctx, fx).replace('data-p60-preview-island="donation_widget"', 'data-p60-preview-island="primary_action_widget"');
157
+ case 'volunteer_signup':
158
+ return `<section class="donate-card vol-card" data-p60-preview-island="volunteer_signup">
159
+ ${previewNote(name)}
160
+ <header class="donate-head"><h2>Volunteer with us</h2><p class="donate-sub">Where would you like to help?</p></header>
161
+ <div class="vol-list">
162
+ <div class="vol-group"><p class="vol-group-label">General</p><button class="vol-option" type="button" disabled><span class="vol-option-main"><strong>Help out where needed</strong><span class="vol-option-summary">A few hours a month, wherever the team needs a hand.</span></span><span class="vol-option-meta"><span class="vol-needed">Everyone welcome</span></span></button></div>
163
+ <div class="vol-group"><p class="vol-group-label">Events</p><button class="vol-option" type="button" disabled><span class="vol-option-main"><strong>Spring fair stewards</strong><span class="vol-option-summary">Welcome visitors and keep the day running.</span></span><span class="vol-option-meta"><span class="vol-needed">6 volunteers needed</span><span class="vol-shift-count">3 shifts</span></span></button></div>
164
+ </div>
165
+ </section>`;
146
166
  case 'language_switch':
147
167
  return `<label class="language-switch" data-p60-preview-island="language_switch">${previewNote(name)}<span class="language-switch-label">Language</span><select class="language-switch-select" disabled><option>English</option><option>Cymraeg</option><option>العربية</option></select></label>`;
148
168
  case 'search':
@@ -348,18 +368,27 @@ const SURFACES = {
348
368
  /** The routed dev preview's surface names (plus 'home'). */
349
369
  export const PREVIEW_SURFACES = ['home', ...Object.keys(SURFACES)];
350
370
 
351
- function surfaceBar(active) {
371
+ function surfaceBar(active, focus = 'donate') {
372
+ const withFocus = (href) => (focus === 'donate' ? href : `${href}${href.includes('?') ? '&' : '?'}focus=${focus}`);
352
373
  const link = (name, href) =>
353
- `<a href="${href}"${name === active ? ' style="font-weight:700;text-decoration:underline"' : ''}>${name}</a>`;
374
+ `<a href="${withFocus(href)}"${name === active ? ' style="font-weight:700;text-decoration:underline"' : ''}>${name}</a>`;
354
375
  const links = [
355
376
  link('home', '/'), link('events', '/events'), link('event', '/events?event=fixture'),
356
377
  link('services', '/services'), link('service', '/services?service=fixture'),
357
378
  link('donate', '/donate'), link('articles', '/articles'), link('article', '/articles/fixture'),
358
379
  link('campaigns', '/campaigns'), link('campaign', '/campaigns/fixture'), link('course', '/courses'),
359
- `<a href="/model" style="margin-left:auto;font-weight:700">site.content model →</a>`,
380
+ `<a href="${withFocus('/model')}" style="margin-left:auto;font-weight:700">site.content model →</a>`,
360
381
  ];
382
+ // What leads: the charity's switch, here as three links, so a developer sees the hero widget as
383
+ // the donation widget, as the volunteer sign-up, or absent, with the buttons following each time.
384
+ const focusLink = (value, label) =>
385
+ `<a href="${value === 'donate' ? '/' : `/?focus=${value}`}"${value === focus ? ' style="font-weight:700;text-decoration:underline"' : ''}>${label}</a>`;
386
+ const focusLinks = [focusLink('donate', 'giving'), focusLink('volunteer', 'volunteering'), focusLink('none', 'buttons only')];
361
387
  return `<nav class="p60-preview-surfaces" aria-label="Preview surfaces" style="position:sticky;top:0;z-index:99;display:flex;gap:12px;flex-wrap:wrap;padding:8px 14px;font:12px/1.4 system-ui,sans-serif;background:#0b1220;color:#e6e9f2;opacity:.94">
362
388
  <strong style="letter-spacing:.06em;text-transform:uppercase;font-size:10px">Surfaces</strong>${links.join('')}
389
+ <span style="flex-basis:100%;height:0"></span>
390
+ <strong style="letter-spacing:.06em;text-transform:uppercase;font-size:10px">Leads with</strong>${focusLinks.join('')}
391
+ <span style="opacity:.7">the hero widget becomes the donation widget, the volunteer sign-up, or nothing; the buttons follow</span>
363
392
  </nav>`;
364
393
  }
365
394
 
@@ -409,12 +438,17 @@ export async function renderStudioPreview(files, options = {}) {
409
438
  // imagery to the platform CDN instead of inline-SVG art (the dev-richer half of the split).
410
439
  const artOptions = options.fixtureImageBase ? { imageBase: options.fixtureImageBase } : null;
411
440
  let fx = artOptions ? resolveFixtureArt(contextContract.fixtures, artOptions) : STUDIO_FX;
441
+ // What leads (docs/volunteering.md 'Site focus'): the dev server passes ?focus=; the studio
442
+ // renders the platform default. The island skeletons read it from the fixtures they are handed.
443
+ const focus = normaliseFocus(options.focus);
444
+ const actions = previewActions(focus);
445
+ fx = { ...fx, focus };
412
446
  // The one content tree (content model v1): about composed from this manifest's declared
413
447
  // sections, dev preview-content overlaid when the kit passes it (validated there), imagery
414
448
  // resolved exactly like the rest of the fixtures.
415
- const site = resolveFixtureArt(
449
+ const site = applyFocus(resolveFixtureArt(
416
450
  applyPreviewContent(buildSiteFixture(manifest), options.previewContent ?? null),
417
- artOptions ?? {});
451
+ artOptions ?? {}), actions);
418
452
  const brand = site.brand ?? fx.brand;
419
453
  // With a content override, the TREE is the source of truth for every fixture view: the routed
420
454
  // platform skeletons and island skeletons re-derive their slices from the overridden site, so
@@ -470,8 +504,12 @@ export async function renderStudioPreview(files, options = {}) {
470
504
  const entry = catalogueByType.get(type);
471
505
  const source = files[`sections/${type}.liquid`];
472
506
  if (!entry || source == null) continue;
507
+ const sample = artOptions ? resolveFixtureArt(entry.sample ?? {}, artOptions) : resolveFixtureArt(entry.sample ?? {});
473
508
  const context = {
474
- section: artOptions ? resolveFixtureArt(entry.sample ?? {}, artOptions) : resolveFixtureArt(entry.sample ?? {}),
509
+ // The home hero carries the resolved actions exactly as the platform hands them over, so a
510
+ // developer sees the widget AND the button that pairs with it. The catalogue sample's
511
+ // givingStyle is dropped here: in the preview the focus decides the style.
512
+ section: type === 'homeHero' ? withResolvedActions({ ...sample, givingStyle: undefined, actionStyle: undefined }, actions) : sample,
475
513
  brand,
476
514
  site,
477
515
  ...(fx.sections?.[type] ?? {})
@@ -569,7 +607,7 @@ export async function renderStudioPreview(files, options = {}) {
569
607
  <style>${themeCss}</style>
570
608
  </head>
571
609
  <body ${attrs}>
572
- ${options.surface ? surfaceBar(surface) : ''}
610
+ ${options.surface ? surfaceBar(surface, focus) : ''}
573
611
  ${bodyHtml}
574
612
  ${runtime ? `<script>${runtime}\np60Behaviors.initBehaviors();</script>` : ''}
575
613
  </body>
@@ -99,7 +99,8 @@ export async function validateArtifact(files) {
99
99
  locations: () => sections.has('locations'),
100
100
  newsletter: () => islands.has('newsletter_signup'),
101
101
  i18n: () => islands.has('language_switch'),
102
- search: () => islands.has('search')
102
+ search: () => islands.has('search'),
103
+ volunteering: () => islands.has('volunteer_signup') || islands.has('primary_action_widget')
103
104
  };
104
105
  for (const capability of manifest?.requiresCapabilities ?? []) {
105
106
  if (!capabilitySurface[capability]?.()) {
@@ -108,6 +109,20 @@ export async function validateArtifact(files) {
108
109
  }
109
110
  }
110
111
 
112
+ // Site focus honesty (docs/volunteering.md): a template that claims it can lead with
113
+ // volunteering must give the volunteer sign-up a way into the hero, either the
114
+ // primary_action_widget island (which becomes the sign-up when volunteering leads) or the
115
+ // volunteer_signup island placed directly.
116
+ {
117
+ const focusKinds = new Set(manifest?.supports?.focus ?? []);
118
+ if (focusKinds.has('volunteer')) {
119
+ const liquidSource = Object.entries(files).filter(([path]) => path.endsWith('.liquid')).map(([, s]) => s).join('\n');
120
+ if (!/island\s+['"]primary_action_widget['"]/.test(liquidSource) && !/island\s+['"]volunteer_signup['"]/.test(liquidSource)) {
121
+ errors.push("manifest: supports.focus lists 'volunteer' but no section places {% island 'primary_action_widget' %} (or 'volunteer_signup'), so the site could never lead with volunteering");
122
+ }
123
+ }
124
+ }
125
+
111
126
  // Markup and attributes, NEVER code — the machine-enforced JavaScript ban over every liquid
112
127
  // source (docs/template-behaviours.md). Behaviour is engine-owned; a template wanting motion
113
128
  // declares supports.behaviors and uses the data-p60-* grammar.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starter",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "format": "port60-liquid@1",
5
5
  "label": "Starter",
6
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.",
@@ -22,13 +22,18 @@
22
22
  "donation_widget",
23
23
  "member_menu",
24
24
  "hero_carousel",
25
- "map"
25
+ "map",
26
+ "primary_action_widget"
26
27
  ],
27
28
  "layout": true,
28
29
  "heroImagery": true,
29
30
  "behaviors": [
30
31
  "reveal",
31
32
  "carousel"
33
+ ],
34
+ "focus": [
35
+ "donate",
36
+ "volunteer"
32
37
  ]
33
38
  },
34
39
  "settings": {
@@ -31,7 +31,7 @@
31
31
  {% endif %}
32
32
  {% assign heroFraming = section.photoFraming | default: 'fill' %}
33
33
  {% if heroCount > 0 and heroFraming == 'whole' %}
34
- {% if section.givingStyle == 'button' %}
34
+ {% if section.actionStyle == 'button' or section.givingStyle == 'button' %}
35
35
  <section class="lq-homehero lq-homehero--vista{% if heroCount > 1 %} lq-homehero--carousel{% endif %}">
36
36
  <div class="lq-vista">
37
37
  <img class="lq-vista-sizer" src="{{ section.images.first.imageUrl }}" alt="{{ section.images.first.alt }}" style="object-position: {{ heroFocus }}" fetchpriority="high" decoding="async">
@@ -63,12 +63,13 @@
63
63
  <h1>{{ section.title | default: site.brand.tagline | default: site.brand.name }}</h1>
64
64
  {% if section.lead %}<p class="lq-homehero-lead">{{ section.lead }}</p>{% endif %}
65
65
  <div class="lq-homehero-actions">
66
- <a class="button button-primary" href="#donate">{{ section.primaryLabel | default: 'Donate now' }}</a>
66
+ {% if section.action or section.primaryLabel %}<a class="button button-primary" href="{{ section.primaryHref | default: section.action.href | default: '#donate' }}">{{ section.primaryLabel | default: section.action.label | default: 'Donate now' }}</a>{% endif %}
67
67
  <a class="button" href="{{ section.secondaryHref | default: '/about' }}">{{ section.secondaryLabel | default: 'Our story' }}</a>
68
68
  </div>
69
69
  </div>
70
70
  <aside id="donate">
71
- {% island 'donation_widget' %}
71
+ {%- comment -%} The site focus decides which widget leads (section.primary); this island becomes it: the donation widget or the volunteer sign-up. {%- endcomment -%}
72
+ {% island 'primary_action_widget' %}
72
73
  </aside>
73
74
  </div>
74
75
  </section>
@@ -97,17 +98,17 @@
97
98
  <h1>{{ section.title | default: site.brand.tagline | default: site.brand.name }}</h1>
98
99
  {% if section.lead %}<p class="lq-homehero-lead">{{ section.lead }}</p>{% endif %}
99
100
  <div class="lq-homehero-actions">
100
- {% if section.givingStyle == 'button' %}
101
- <a class="button button-primary" href="{{ section.primaryHref | default: '/donate' }}">{{ section.primaryLabel | default: 'Donate now' }}</a>
101
+ {% if section.actionStyle == 'button' or section.givingStyle == 'button' %}
102
+ {% if section.action or section.primaryLabel %}<a class="button button-primary" href="{{ section.primaryHref | default: section.action.href | default: '/donate' }}">{{ section.primaryLabel | default: section.action.label | default: 'Donate now' }}</a>{% endif %}
102
103
  {% else %}
103
- <a class="button button-primary" href="#donate">{{ section.primaryLabel | default: 'Donate now' }}</a>
104
+ {% if section.action or section.primaryLabel %}<a class="button button-primary" href="{{ section.primaryHref | default: section.action.href | default: '#donate' }}">{{ section.primaryLabel | default: section.action.label | default: 'Donate now' }}</a>{% endif %}
104
105
  {% endif %}
105
106
  <a class="button" href="{{ section.secondaryHref | default: '/about' }}">{{ section.secondaryLabel | default: 'Our story' }}</a>
106
107
  </div>
107
108
  </div>
108
- {% unless section.givingStyle == 'button' %}
109
+ {% unless section.actionStyle == 'button' or section.givingStyle == 'button' %}
109
110
  <aside id="donate">
110
- {% island 'donation_widget' %}
111
+ {% island 'primary_action_widget' %}
111
112
  </aside>
112
113
  {% endunless %}
113
114
  </div>