@port60/template-kit 0.7.0 → 0.9.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/commands/create.mjs +4 -1
- package/src/commands/dev.mjs +43 -5
- package/src/lib/agentsMd.mjs +32 -3
- package/src/vendor/contract/v1/behaviours.json +129 -0
- package/src/vendor/contract/v1/context.json +459 -7
- package/src/vendor/contract/v1/islands.json +125 -0
- package/src/vendor/contract/v1/layout.json +25 -0
- package/src/vendor/contract/v1/manifest.schema.json +51 -5
- package/src/vendor/contract/v1/sections.json +114 -1
- package/src/vendor/validator/behaviors-runtime.js +2 -0
- package/src/vendor/validator/platform-base.css +4229 -0
- package/src/vendor/validator/preview.mjs +399 -18
- package/src/vendor/validator/validate.mjs +149 -6
- package/starter/assets/theme.css +46 -0
- package/starter/layout.liquid +16 -6
- package/starter/manifest.json +10 -5
- package/starter/sections/campaigns.liquid +23 -0
- package/starter/sections/homeHero.liquid +16 -1
- package/starter/sections/values.liquid +6 -1
|
@@ -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
|
+
}
|
|
@@ -13,8 +13,8 @@
|
|
|
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,6 +23,43 @@
|
|
|
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,
|
|
@@ -38,7 +75,7 @@
|
|
|
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",
|
|
@@ -61,10 +98,10 @@
|
|
|
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",
|
|
@@ -75,6 +112,15 @@
|
|
|
75
112
|
"type": "boolean",
|
|
76
113
|
"default": false,
|
|
77
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
|
},
|
|
@@ -419,6 +419,20 @@
|
|
|
419
419
|
"latestArticles"
|
|
420
420
|
]
|
|
421
421
|
},
|
|
422
|
+
{
|
|
423
|
+
"type": "campaigns",
|
|
424
|
+
"label": "Campaigns",
|
|
425
|
+
"pages": ["home"],
|
|
426
|
+
"description": "The tenant's LIVE campaigns (appeals, petitions, surveys) as cards linking to each campaign's page. Render the `campaigns` context your way; render nothing when no campaign is live.",
|
|
427
|
+
"context": ["campaigns", "campaignsLabel"],
|
|
428
|
+
"fields": [
|
|
429
|
+
{ "name": "eyebrow", "kind": "text", "required": false, "description": "Short kicker." },
|
|
430
|
+
{ "name": "heading", "kind": "text", "required": false, "description": "The section heading; fall back to the campaignsLabel context when unset." },
|
|
431
|
+
{ "name": "text", "kind": "text", "required": false, "description": "Optional introduction." }
|
|
432
|
+
],
|
|
433
|
+
"minimal": {},
|
|
434
|
+
"sample": { "eyebrow": "Get involved", "heading": "Our campaigns", "text": "What we are asking for right now." }
|
|
435
|
+
},
|
|
422
436
|
{
|
|
423
437
|
"type": "emergency",
|
|
424
438
|
"label": "Emergency appeal strip",
|
|
@@ -426,6 +440,9 @@
|
|
|
426
440
|
"home"
|
|
427
441
|
],
|
|
428
442
|
"description": "A high-urgency full-width strip — one headline, an optional line, one action. For live emergencies and time-critical appeals.",
|
|
443
|
+
"context": [
|
|
444
|
+
"causes"
|
|
445
|
+
],
|
|
429
446
|
"fields": [
|
|
430
447
|
{
|
|
431
448
|
"name": "heading",
|
|
@@ -537,6 +554,9 @@
|
|
|
537
554
|
"home"
|
|
538
555
|
],
|
|
539
556
|
"description": "A grid of appeal/campaign cards — image, title, a line, and a per-card action.",
|
|
557
|
+
"context": [
|
|
558
|
+
"causes"
|
|
559
|
+
],
|
|
540
560
|
"fields": [
|
|
541
561
|
{
|
|
542
562
|
"name": "eyebrow",
|
|
@@ -616,6 +636,99 @@
|
|
|
616
636
|
]
|
|
617
637
|
}
|
|
618
638
|
},
|
|
639
|
+
{
|
|
640
|
+
"type": "programmes",
|
|
641
|
+
"label": "Services and programmes",
|
|
642
|
+
"pages": [
|
|
643
|
+
"home"
|
|
644
|
+
],
|
|
645
|
+
"description": "The tenant's published services and programmes as a dynamic card collection. Render the services context and omit the section when it is empty.",
|
|
646
|
+
"context": [
|
|
647
|
+
"services"
|
|
648
|
+
],
|
|
649
|
+
"fields": [
|
|
650
|
+
{
|
|
651
|
+
"name": "eyebrow",
|
|
652
|
+
"kind": "text",
|
|
653
|
+
"required": false,
|
|
654
|
+
"description": "Short kicker."
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
"name": "heading",
|
|
658
|
+
"kind": "text",
|
|
659
|
+
"required": false,
|
|
660
|
+
"description": "The section heading."
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
"name": "text",
|
|
664
|
+
"kind": "text",
|
|
665
|
+
"required": false,
|
|
666
|
+
"description": "Optional introduction."
|
|
667
|
+
}
|
|
668
|
+
],
|
|
669
|
+
"minimal": {},
|
|
670
|
+
"sample": {
|
|
671
|
+
"eyebrow": "What we do",
|
|
672
|
+
"heading": "Programmes for our community",
|
|
673
|
+
"text": "Explore practical support, learning and activities."
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
"type": "resources",
|
|
678
|
+
"label": "Resource library",
|
|
679
|
+
"pages": ["home"],
|
|
680
|
+
"description": "Public documents from the tenant's media library. The resources context contains PUBLIC documents only; private files and storage metadata never reach Liquid.",
|
|
681
|
+
"context": ["resources"],
|
|
682
|
+
"fields": [
|
|
683
|
+
{ "name": "eyebrow", "kind": "text", "required": false, "description": "Short kicker." },
|
|
684
|
+
{ "name": "heading", "kind": "text", "required": false, "description": "The section heading." },
|
|
685
|
+
{ "name": "text", "kind": "text", "required": false, "description": "Optional introduction." }
|
|
686
|
+
],
|
|
687
|
+
"minimal": {},
|
|
688
|
+
"sample": { "eyebrow": "Resources", "heading": "Useful documents", "text": "Policies, guides and downloads." }
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
"type": "locations",
|
|
692
|
+
"label": "Locations",
|
|
693
|
+
"pages": ["home"],
|
|
694
|
+
"description": "The tenant's public venue directory with safe directions links. Provider references and private venue notes are never exposed.",
|
|
695
|
+
"context": ["locations"],
|
|
696
|
+
"fields": [
|
|
697
|
+
{ "name": "eyebrow", "kind": "text", "required": false, "description": "Short kicker." },
|
|
698
|
+
{ "name": "heading", "kind": "text", "required": false, "description": "The section heading." },
|
|
699
|
+
{ "name": "text", "kind": "text", "required": false, "description": "Optional introduction." }
|
|
700
|
+
],
|
|
701
|
+
"minimal": {},
|
|
702
|
+
"sample": { "eyebrow": "Find us", "heading": "Our locations", "text": "Visit the place nearest to you." }
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
"type": "volunteering",
|
|
706
|
+
"label": "Volunteering opportunities",
|
|
707
|
+
"pages": ["home"],
|
|
708
|
+
"description": "Published opportunities from the volunteering module. Templates render the cards and place volunteer_signup for the platform-owned, privacy-recorded expression-of-interest flow.",
|
|
709
|
+
"context": ["opportunities"],
|
|
710
|
+
"fields": [
|
|
711
|
+
{ "name": "eyebrow", "kind": "text", "required": false, "description": "Short kicker." },
|
|
712
|
+
{ "name": "heading", "kind": "text", "required": false, "description": "The section heading." },
|
|
713
|
+
{ "name": "text", "kind": "text", "required": false, "description": "Optional introduction." }
|
|
714
|
+
],
|
|
715
|
+
"minimal": {},
|
|
716
|
+
"sample": { "eyebrow": "Get involved", "heading": "Volunteer with us", "text": "Find a role that suits your time and skills." }
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
"type": "media",
|
|
720
|
+
"label": "Audio, video and livestreams",
|
|
721
|
+
"pages": ["home"],
|
|
722
|
+
"description": "Structured public audio, video and livestream entries with titles, speakers, dates and safe media links.",
|
|
723
|
+
"context": ["media"],
|
|
724
|
+
"fields": [
|
|
725
|
+
{ "name": "eyebrow", "kind": "text", "required": false, "description": "Short kicker." },
|
|
726
|
+
{ "name": "heading", "kind": "text", "required": false, "description": "The section heading." },
|
|
727
|
+
{ "name": "text", "kind": "text", "required": false, "description": "Optional introduction." }
|
|
728
|
+
],
|
|
729
|
+
"minimal": {},
|
|
730
|
+
"sample": { "eyebrow": "Media", "heading": "Watch and listen", "text": "Talks, updates and live broadcasts." }
|
|
731
|
+
},
|
|
619
732
|
{
|
|
620
733
|
"type": "people",
|
|
621
734
|
"label": "People / team",
|
|
@@ -707,4 +820,4 @@
|
|
|
707
820
|
}
|
|
708
821
|
}
|
|
709
822
|
]
|
|
710
|
-
}
|
|
823
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// GENERATED by scripts/build-behaviors-runtime.mjs — do not edit by hand.
|
|
2
|
+
"use strict";var p60Behaviors=(()=>{var L=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var M=Object.prototype.hasOwnProperty;var k=(r,i)=>{for(var t in i)L(r,t,{get:i[t],enumerable:!0})},S=(r,i,t,n)=>{if(i&&typeof i=="object"||typeof i=="function")for(let e of x(i))!M.call(r,e)&&e!==t&&L(r,e,{get:()=>i[e],enumerable:!(n=w(i,e))||n.enumerable});return r};var T=r=>S(L({},"__esModule",{value:!0}),r);var R={};k(R,{initBehaviors:()=>K});var A=()=>window.matchMedia("(prefers-reduced-motion: reduce)").matches;function v(r,i){let t=`data-p60-bound-${i}`;return r.hasAttribute(t)?!0:(r.setAttribute(t,""),!1)}function E(r,i){if(r.length===0)return;if(A()||!("IntersectionObserver"in window)){r.forEach(i);return}let t=new IntersectionObserver(n=>{for(let e of n)e.isIntersecting&&(t.unobserve(e.target),i(e.target))},{rootMargin:"0px 0px -10% 0px",threshold:.15});r.forEach(n=>t.observe(n))}function H(r){let i=Array.from(r.querySelectorAll("[data-p60-reveal-group]")).filter(n=>!v(n,"reveal-group"));for(let n of i)Array.from(n.children).forEach((e,o)=>{e.style.setProperty("--p60-reveal-index",String(o)),e.classList.add("p60-reveal-child")});E(i,n=>n.classList.add("is-revealed"));let t=Array.from(r.querySelectorAll("[data-p60-reveal]")).filter(n=>!v(n,"reveal"));E(t,n=>n.classList.add("is-revealed"))}function q(r){let i=Array.from(r.querySelectorAll("[data-p60-count]")).filter(t=>!v(t,"count"));E(i,t=>{t.classList.add("is-revealed");let n=Number.parseInt(t.getAttribute("data-p60-count")??"",10);if(!Number.isFinite(n)||A())return;let e=Math.min(Number.parseInt(t.getAttribute("data-p60-count-duration")??"1200",10)||1200,4e3),o=new Intl.NumberFormat(document.documentElement.lang||void 0),d=performance.now(),s=a=>{let c=Math.min((a-d)/e,1),u=1-Math.pow(1-c,3);t.textContent=o.format(Math.round(n*u)),c<1&&requestAnimationFrame(s)};requestAnimationFrame(s)})}function N(r){let i=Array.from(r.querySelectorAll("[data-p60-progress]")).filter(t=>!v(t,"progress"));if(!A()){for(let t of i){let n=t.style.width||getComputedStyle(t).width;t.style.transition="none",t.style.width="0px",t.offsetWidth,t.style.transition="width 1.1s cubic-bezier(0.22, 1, 0.36, 1)",t.dataset.p60ProgressTarget=n}E(i,t=>{t.classList.add("is-revealed"),t.style.width=t.dataset.p60ProgressTarget??""})}}function P(r){let i=Array.from(r.querySelectorAll("[data-p60-countdown]")).filter(t=>!v(t,"countdown"));for(let t of i){let n=Date.parse(t.getAttribute("data-p60-until")??"");if(!Number.isFinite(n))continue;let e=t.getAttribute("data-p60-countdown")||"dhm",o=e==="dhms",d=()=>{let a=n-Date.now();if(a<=0){t.classList.add("is-elapsed");let l=t.getAttribute("data-p60-elapsed-text");l&&(t.textContent=l),window.clearInterval(s);return}let c=Math.floor(a/1e3),u=Math.floor(c/86400),f=Math.floor(c%86400/3600),g=Math.floor(c%3600/60),m=c%60,h=[];e.includes("d")&&u>0&&h.push(`${u}d`),h.push(`${f}h`,`${g}m`),o&&h.push(`${m}s`),t.textContent=h.join(" ")},s=window.setInterval(d,o?1e3:15e3);d()}}function C(r){let i=Array.from(r.querySelectorAll("[data-p60-accordion]")).filter(t=>!v(t,"accordion"));for(let t of i){let n=t.getAttribute("data-p60-accordion")!=="multi",e=Array.from(t.querySelectorAll("details"));for(let o of e){o.addEventListener("toggle",()=>{if(o.classList.toggle("is-open",o.open),o.open&&n)for(let a of e)a!==o&&a.open&&(a.open=!1)}),o.open&&o.classList.add("is-open");let s=o.querySelector("summary")?.nextElementSibling;s&&!A()&&o.addEventListener("toggle",()=>{if(!o.open)return;let a=s.scrollHeight;s.style.overflow="hidden",s.animate([{height:"0px",opacity:"0"},{height:`${a}px`,opacity:"1"}],{duration:260,easing:"ease-out"}).addEventListener("finish",()=>{s.style.overflow=""})})}}}function I(r){let i=Array.from(r.querySelectorAll("[data-p60-carousel]")).filter(t=>!v(t,"carousel"));for(let t of i){let n=Array.from(t.querySelectorAll("[data-p60-slide]"));if(n.length<2)continue;let e=Math.max(0,n.findIndex(l=>l.classList.contains("is-active"))),o=!1,d=[],s=document.createElement("nav");s.className="p60-carousel-dots",s.setAttribute("aria-label","Slides"),n.forEach((l,y)=>{let p=document.createElement("button");p.type="button",p.className="p60-carousel-dot",p.setAttribute("aria-label",`Go to slide ${y+1}`),p.addEventListener("click",()=>a(y,!0)),d.push(p),s.appendChild(p)}),t.appendChild(s),t.setAttribute("aria-roledescription","carousel"),t.hasAttribute("tabindex")||t.setAttribute("tabindex","0");let a=(l,y=!1)=>{e=(l+n.length)%n.length,n.forEach((p,b)=>{p.classList.toggle("is-active",b===e),b===e?(p.removeAttribute("aria-hidden"),p.removeAttribute("inert")):(p.setAttribute("aria-hidden","true"),p.setAttribute("inert",""))}),d.forEach((p,b)=>{p.classList.toggle("p60-carousel-dot--active",b===e),p.setAttribute("aria-current",b===e?"true":"false")}),t.style.setProperty("--p60-carousel-index",String(e)),y&&h()};t.querySelectorAll("[data-p60-carousel-prev]").forEach(l=>l.addEventListener("click",()=>a(e-1,!0))),t.querySelectorAll("[data-p60-carousel-next]").forEach(l=>l.addEventListener("click",()=>a(e+1,!0))),t.addEventListener("keydown",l=>{l.key==="ArrowLeft"&&a(e-1,!0),l.key==="ArrowRight"&&a(e+1,!0)});let c=0,u=0;t.addEventListener("pointerdown",l=>{c=l.clientX,u=l.clientY}),t.addEventListener("pointerup",l=>{let y=l.clientX-c,p=l.clientY-u;Math.abs(y)>40&&Math.abs(y)>Math.abs(p)&&a(e+(y<0?1:-1),!0)}),t.addEventListener("mouseenter",()=>{o=!0}),t.addEventListener("mouseleave",()=>{o=!1}),t.addEventListener("focusin",()=>{o=!0}),t.addEventListener("focusout",()=>{o=!1});let f,g=t.getAttribute("data-p60-carousel")==="auto"&&!A(),m=Math.max(Number.parseInt(t.getAttribute("data-p60-interval")??"6000",10)||6e3,3e3),h=()=>{f!==void 0&&window.clearInterval(f),g&&(f=window.setInterval(()=>{!o&&!document.hidden&&a(e+1)},m))};a(e),h()}}function F(r){let i=Array.from(r.querySelectorAll("[data-p60-sticky-header]")).filter(t=>!v(t,"sticky-header"));for(let t of i){let n=t.getAttribute("data-p60-sticky-header")==="auto-hide",e=window.scrollY,o=!1,d=()=>{let s=window.scrollY;t.classList.toggle("is-condensed",s>24),n&&(s>160&&s>e?t.classList.add("is-hidden"):t.classList.remove("is-hidden")),e=s,o=!1};window.addEventListener("scroll",()=>{o||(o=!0,requestAnimationFrame(d))},{passive:!0}),d()}}function D(r){let i=Array.from(r.querySelectorAll("[data-p60-sticky-cta]")).filter(t=>!v(t,"sticky-cta"));for(let t of i){let n=Number.parseInt(t.getAttribute("data-p60-sticky-cta")??"480",10)||480,e=!1,o=()=>{t.classList.toggle("is-stuck",window.scrollY>n),e=!1};window.addEventListener("scroll",()=>{e||(e=!0,requestAnimationFrame(o))},{passive:!0}),o()}}function Y(r){let i=Array.from(r.querySelectorAll("[data-p60-lightbox]")).filter(t=>!v(t,"lightbox"));for(let t of i){let n=Array.from(t.querySelectorAll("a[data-p60-lightbox-item]"));if(n.length===0)continue;let e=null,o=0,d=null,s=()=>{e?.remove(),e=null,t.classList.remove("is-open"),document.removeEventListener("keydown",c),d?.focus()},a=f=>{o=(f+n.length)%n.length;let g=n[o];if(!e)return;let m=e.querySelector(".p60-lightbox-img"),h=e.querySelector(".p60-lightbox-caption");m&&(m.src=g.href);let l=g.getAttribute("data-p60-caption")??g.querySelector("img")?.alt??"";h&&(h.textContent=l),m&&(m.alt=l)},c=f=>{if(f.key==="Escape"&&s(),f.key==="ArrowLeft"&&a(o-1),f.key==="ArrowRight"&&a(o+1),f.key==="Tab"&&e){let g=Array.from(e.querySelectorAll("button")),m=g.indexOf(document.activeElement),h=f.shiftKey?m<=0?g.length-1:m-1:m>=g.length-1?0:m+1;g[h]?.focus(),f.preventDefault()}},u=(f,g)=>{d=g,e=document.createElement("div"),e.className="p60-lightbox",e.setAttribute("role","dialog"),e.setAttribute("aria-modal","true"),A()&&(e.style.transition="none"),e.innerHTML='<figure><img class="p60-lightbox-img" alt=""><figcaption class="p60-lightbox-caption"></figcaption></figure>';let m=(l,y,p)=>{let b=document.createElement("button");return b.type="button",b.className=l,b.setAttribute("aria-label",y),b.addEventListener("click",p),e.appendChild(b),b};n.length>1&&(m("p60-lightbox-prev","Previous image",()=>a(o-1)),m("p60-lightbox-next","Next image",()=>a(o+1)));let h=m("p60-lightbox-close","Close",s);e.addEventListener("click",l=>{l.target===e&&s()}),document.body.appendChild(e),t.classList.add("is-open"),document.addEventListener("keydown",c),a(f),h.focus()};n.forEach((f,g)=>f.addEventListener("click",m=>{m.preventDefault(),u(g,f)}))}}function $(r){let i=Array.from(r.querySelectorAll("[data-p60-tabs]")).filter(t=>!v(t,"tabs"));for(let t of i){let n=Array.from(t.querySelectorAll("[data-p60-tab]")),e=Array.from(t.querySelectorAll("[data-p60-panel]"));if(n.length===0||e.length===0)continue;let o=s=>e.find(a=>a.getAttribute("data-p60-panel")===s.getAttribute("data-p60-tab"));n[0].closest("[data-p60-tabs] > *")?.setAttribute("role","tablist");let d=(s,a=!1)=>{for(let c of n){let u=c===s;c.classList.toggle("is-active",u),c.setAttribute("aria-selected",u?"true":"false"),c.setAttribute("tabindex",u?"0":"-1")}for(let c of e){let u=c===o(s);c.classList.toggle("is-active",u),c.hidden=!u}a&&s.focus()};n.forEach((s,a)=>{s.setAttribute("role","tab"),s.addEventListener("click",c=>{c.preventDefault(),d(s)}),s.addEventListener("keydown",c=>{let u=c.key;u==="ArrowLeft"&&d(n[(a-1+n.length)%n.length],!0),u==="ArrowRight"&&d(n[(a+1)%n.length],!0),u==="Home"&&d(n[0],!0),u==="End"&&d(n[n.length-1],!0)})}),e.forEach(s=>s.setAttribute("role","tabpanel")),d(n.find(s=>s.classList.contains("is-active"))??n[0])}}function K(r=document){document.documentElement.classList.add("p60-js"),H(r),q(r),N(r),P(r),C(r),I(r),F(r),D(r),Y(r),$(r)}return T(R);})();
|