PageSourceSearch

https://st-stephens-guernsey.org/418/Contact-Us

html st-stephens-guernsey.org collected 2026-09-25 10:24:25 UTC 64,514 bytes, 1,116 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en-GB">
3<head>
4<!-- start of head-->
5    <title>Contact -  St Stephen's Guernsey</title>
6	<link rel="shortcut icon" href="/_data/site/117/templates/current/favicon.ico" type="image/x-icon" />
7    <meta charset="utf-8">
8    <meta name="dcterms.rightsHolder" content="Khoo Systems Limited">
9    <meta name="dcterms.dateCopyrighted" content="2014">
10    <meta name="robots" content="all">
11    <meta name="description" content="Contact St Stephen&apos;s Guernsey.">
12    <meta name="csrf_token" content="ststephens:0xB242C4CC847B11F19A5EF23C9435931F:::1790331874:be69cd8faad048b357aa141a8b5aa89f">
13    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
14
15<meta name="viewport" content="width=device-width, initial-scale=1.0">
16<meta property="og:type" content="website">
17<meta property="og:site_name" content="St Stephens Guernsey">
18<meta property="og:image" content="https://st-stephens-guernsey.org/cache/img/117/pg|418&sz500x500&cp&tn&ql&fm&bo&bc&sg515d8c2607&ft1707408783&contactBanner.jpg">
19<meta property="og:title" content="Contact -  St Stephen&#039;s Guernsey">
20<meta property="og:description" content="Contact St Stephen&#039;s Guernsey.">
21<meta property="og:url" content="https://st-stephens-guernsey.org/418/Contact-Us">
22<meta name="twitter:card" content="summary_large_image">
23<meta name="twitter:title" content="Contact -  St Stephen&#039;s Guernsey">
24<meta name="twitter:description" content="Contact St Stephen&#039;s Guernsey.">
25<meta name="twitter:image" content="https://st-stephens-guernsey.org/cache/img/117/pg|418&sz500x500&cp&tn&ql&fm&bo&bc&sg515d8c2607&ft1707408783&contactBanner.jpg">
26<script type='application/ld+json'>
27{"@type":"Organization","@context":"https://schema.org","@id":"https://st-stephens-guernsey.org/#organization","sameAs":["https://www.facebook.com/StStephensGuernsey/"],"name":"St Stephens Guernsey","url":"https://st-stephens-guernsey.org/","contactPoint":[{"@type":"ContactPoint","telephone":"+44-1481 720268","contactType":"customer support"}],"logo":"https://st-stephens-guernsey.org/_data/site/117/templates/current/mobile_logo.svg"}
28</script>
28
29
30<script type='application/ld+json'>
31{"@type":"WebSite","@context":"https://schema.org","@id":"https://st-stephens-guernsey.org/#website","url":"https://st-stephens-guernsey.org/","name":"St Stephens Guernsey","publisher":{"@id":"https://st-stephens-guernsey.org/#organization"}}
32</script>
32
33<!-- Start_Google_structured_data_breadcrumb -->
34<script type="application/ld+json">
35{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"https://st-stephens-guernsey.org/","name":"Home"}},{"@type":"ListItem","position":2,"item":{"@id":"https://st-stephens-guernsey.org/311/Contact-Us","name":"Contact"}},{"@type":"ListItem","position":3,"item":{"@id":"https://st-stephens-guernsey.org/418/Contact-Us","name":"Contact Us","image":"https://st-stephens-guernsey.org/cache/img/117/pg|418&sz200x200&cp&tn&ql&fm&bo&bc&sg133d703b53&ft1707408783&contactBanner.jpg"}}]}
36</script>
36
37<!-- End_Google_structured_data_breadcrumb -->
38<link rel='canonical' href='https://st-stephens-guernsey.org/418/Contact-Us'>
39
40
41
42<link rel='stylesheet' type='text/css' href='/js/plugins/cookieconsent/dist/cookieconsent.css?v=5'>
43<link rel='stylesheet' type='text/css' href='/js/plugins/swiper/swiper-bundle.min.css?v=5'>
44<link rel='stylesheet' type='text/css' href='/js/jquery-plugins/jquery-ui/jquery-ui.custom.css?v=5'>
45<link rel='stylesheet' type='text/css' href='https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.theme.css'>
46<link rel='stylesheet' type='text/css' href='https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css'>
47<link rel='stylesheet' type='text/css' href='https://cdn.jsdelivr.net/npm/shortcut-buttons-flatpickr/dist/themes/light.min.css'>
48<link rel='stylesheet' type='text/css' href='/css/default.css?v=5'>
49<link rel='stylesheet' type='text/css' href='/modules/folder/css/folder.css?v=5'>
50<link rel='stylesheet' type='text/css' href='/modules/contact/css/contact.css?v=5'>
51<link rel='stylesheet' type='text/css' href='/modules/news/css/news.css?v=5'>
52<link rel='stylesheet' type='text/css' href='/modules/pg/css/pg.css?v=5'>
53<link rel='stylesheet' type='text/css' href='/modules/search/css/search.css?v=5'>
54<link rel='stylesheet' type='text/css' href='/modules/user/css/user.css?v=5'>
55<link rel='stylesheet' type='text/css' href='/modules/attribute/css/attribute.css?v=5'>
56<link rel='stylesheet' type='text/css' href='/modules/calendar/css/calendar.css?v=5'>
57<link rel='stylesheet' type='text/css' href='/modules/directory/css/directory.css?v=5'>
58<link rel='stylesheet' type='text/css' href='/modules/crm/css/crm.css?v=5'>
59<link rel='stylesheet' type='text/css' href='/modules/sermon/css/sermon.css?v=5'>
60<link rel='stylesheet' type='text/css' href='/css/search-autocomplete.css?v=5'>
61
62<link rel='stylesheet' type='text/css' href='/cache/css/117/modern-form.css?v=5.1781178638'>
63<link rel='stylesheet' type='text/css' href='/cache/css/117/responsive.css?v=5.1781178638'>
64<link rel='stylesheet' type='text/css' href='/cache/css/117/menu.css?v=5.1781178638'>
65<link rel='stylesheet' type='text/css' href='/cache/css/117/ecampaign.css?v=5.1781178638'>
66<link rel='stylesheet' type='text/css' href='/cache/css/117/calevent.css?v=5.1781178638'>
67<link rel='stylesheet' type='text/css' href='/cache/css/117/template_fonts.css?v=5.1781178638'>
68<link rel='stylesheet' type='text/css' href='/cache/css/117/template.css?v=5.1781178638'>
69<link rel='stylesheet' type='text/css' href='/cache/css/117/template_custom.css?v=5.1781178638'>
70
71
72<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js'></script>
vendor: 1 bytes, line 72
72
73<script defer src='https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js'></script>
vendor: 1 bytes, line 73
73
74<script defer src='https://unpkg.com/[email protected]/dist/htmx.min.js'></script>
vendor: 1 bytes, line 74
74
75<script defer src='https://cdn.jsdelivr.net/npm/@alpinejs/[email protected]/dist/cdn.min.js'></script>
vendor: 1 bytes, line 75
75
76<script defer src='https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js'></script>
vendor: 1 bytes, line 76
76
77<script defer src='https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js'></script>
vendor: 1 bytes, line 77
77
78<script defer src='https://cdn.jsdelivr.net/npm/tippy.js@6/dist/tippy-bundle.umd.js'></script>
vendor: 1 bytes, line 78
78
79<script defer src='/js/plugins/swiper/swiper-bundle.min.js?v=5'></script>
vendor: 1 bytes, line 79
79
80<script defer src='/js/component/swiperLegacyAlias.js?v=5'></script>
80
81<!-- Viewport width for Google -->
82<meta name="viewport" content="width=device-width, initial-scale=1">
83<script src='https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js'></script>
vendor: 1 bytes, line 83
83
84<script defer src='/js/jquery-plugins/jquery.validate/jquery.validate.min.js?v=5'></script>
vendor: 1 bytes, line 84
84
85<script defer src='/js/jquery-plugins/jquery.validate/additional/creditcard.js?v=5'></script>
vendor: 1 bytes, line 85
85
86<script defer src='/js/component/matchHeight.js?v=5'></script>
vendor: 1 bytes, line 86
86
87<script defer src='/js/plugins/headroom/headroom.min.js?v=5'></script>
vendor: 1 bytes, line 87
87
88<script defer src='/js/plugins/lazyload/lazyload.js?v=5'></script>
vendor: 1 bytes, line 88
88
89<script>
90document.addEventListener("DOMContentLoaded",function(){lazyload();});
91</script>
vendor: 1 bytes, line 91
91
92<script defer src='https://cdn.jsdelivr.net/npm/flatpickr'></script>
vendor: 1 bytes, line 92
92
93<script defer src='https://cdn.jsdelivr.net/npm/shortcut-buttons-flatpickr/dist/shortcut-buttons-flatpickr.min.js'></script>
vendor: 1 bytes, line 93
93
94<script defer src='/js/plugins/base64.js?v=5'></script>
vendor: 1 bytes, line 94
94
95<script src='/js/default.js?v=5'></script>
vendor: 1 bytes, line 95
95
96<script defer src='/modules/menu/js/menu_h.js?v=5'></script>
vendor: 1 bytes, line 96
96
97<script defer src='/modules/menu/js/menu_hamb.js?v=5'></script>
vendor: 1 bytes, line 97
97
98<script defer src='/modules/menu/js/menu_mega.js?v=5'></script>
vendor: 1 bytes, line 98
98
99<script defer src='/js/component/searchAutocomplete.js?v=5'></script>
99
100
101
102
103
104
105<!--end of head -->
106</head>
107<body class="body-template body-311 body-418 _body-418 body-pg body-layout-preset-985 browser-chrome engine-webkit majorver-128 ver-128.0.0.0 is-desktop geo-CA  body-menu-hamb body-menu-hamb-left body-menu-hamb-slide   ">
108	
109		<div id="q-app">
110		<div id="shim"></div>
111		<div id='menu-hamb' class='left slide'><a id='menu-icon-close'>		<svg style="fill:#fff" version="1.1" id="hamb-icon" class="hamb-icon-svg" 
112			xmlns="http://www.w3.org/2000/svg" 
113			xmlns:xlink="http://www.w3.org/1999/xlink" 
114			x="0px" y="0px" width="45px" height="45px" 
115			viewBox="0 0 128 128" 
116			enable-background="new 0 0 128 128" xml:space="preserve">
117		<g>
118			<rect style="fill:#26B5E3;" id="hamb-icon-close-box" x="0" y="0" width="128" height="128"/>
119			<g transform="matrix(0.5, 0, 0, 0.5, 32,32)">
120				<g id="close" transform="translate(18.000000, 18.000000)">
121					<path d="M92,9.19938255 L82.8006174,0 L46,36.8006174 L9.19938255,0 L0,9.19938255 L36.8006174,46 L0,82.8006174 L9.19938255,92 L46,55.1993826 L82.8006174,92 L92,82.8006174 L55.1993826,46 L92,9.19938255 Z" id="Shape"></path>
122				</g>
123			</g>
124		</g>
125		</svg></a><ul class=' n0'><li class='n159 show-items folder' data-nav-id='159'><div><a class=' ajax-branch' href='/159/About' >About</a><a class='menu-branch ajax' href='/159/ajax/menu_branch.php' rel='nofollow'>&nbsp;</a></div></li><li class='n405 show-items folder' data-nav-id='405'><div><a class=' ajax-branch' href='/405/Services' >Services</a><a class='menu-branch ajax' href='/405/ajax/menu_branch.php' rel='nofollow'>&nbsp;</a></div></li><li class='n451 show-items folder' data-nav-id='451'><div><a class=' ajax-branch' href='/451/Whats-On' >What's On</a><a class='menu-branch ajax' href='/451/ajax/menu_branch.php' rel='nofollow'>&nbsp;</a></div></li><li class='n217 hide-items folder' data-nav-id='217'><div><a class='' href='/217/News' >News</a></div></li><li class='n350 show-items folder' data-nav-id='350'><div><a class=' ajax-branch' href='/350/Community' >Community</a><a class='menu-branch ajax' href='/350/ajax/menu_branch.php' rel='nofollow'>&nbsp;</a></div></li><li class='n9 show-items folder' data-nav-id='9'><div><a class=' ajax-branch' href='/9/Safeguarding' >Safeguarding</a><a class='menu-branch ajax' href='/9/ajax/menu_branch.php' rel='nofollow'>&nbsp;</a></div></li><li class='n311 hide-items folder' data-nav-id='311'><div><a class='' href='/311/Contact-Us' >Contact</a></div></li></ul></div>
126		<div id="header-fixed" class="header--fixed">
127				  	
127<script>
128	  		$(function(){
129				var header = document.querySelector("#header-fixed");
130				if (window.location.hash) {
131				  header.classList.add("slide--up");
132				}
133				var headroom = new Headroom(header, {
134					tolerance: {
135					  down : 10,
136					  up : 20
137					},
138					offset: 100
139				}).init();
140	  		})
141	  	</script>
141	  	<header id="header-mob">
142	  		<div id="header-mob-box">
143	  			<a id="header-mob-logo"  href="/"></a>
144				<div id="mobile-actions">
145	
146	
147	<svg id='mobile-search-icon' viewBox='0 0 100 100' x-data @click="$dispatch('toggle-mobile-search')"><path d='M94.5,88.6L76.8,70.9c6.6-7.4,10.7-17.2,10.7-27.9c0-23.1-18.8-41.9-41.9-41.9S3.7,19.9,3.7,43s18.8,41.9,41.9,41.9c8.2,0,15.8-2.4,22.2-6.4l18.4,18.4c1.1,1.1,2.6,1.7,4.1,1.7c1.5,0,3-0.6,4.1-1.7C96.8,94.6,96.8,90.9,94.5,88.6z M15.4,43c0-16.6,13.5-30.2,30.2-30.2S75.7,26.4,75.7,43S62.2,73.2,45.6,73.2S15.4,59.7,15.4,43z'/></svg>
148</div>
149<div id="mobile-search"
150     x-data="{ open: false }"
151     :class="{ 'is-open': open }"
152     @toggle-mobile-search.window="open = !open">
153	<form action="/search/index.htm" method="post">
154		<img id="icon-search" src="/media/icons/search.svg" alt="" width="18" height="18">
155		<input name="p_search"
156		       value=""
157		       type="text"
158		       placeholder="Search"
159		       autocomplete="off"
160		       data-autocomplete-url="/search/ajax/search_select.php"
161		       data-autocomplete-min="3"
162		       data-autocomplete-delay="100" />
163		<img id="close-search" src="/media/icons/close.svg" alt="Close" width="18" height="18" @click="open = false">
164	</form>
165</div>
166
167<div id='menu-icon-wrapper'><div id='menu-icon'>		<svg style="fill:#fff" version="1.1" id="hamb-icon" class="hamb-icon-svg" 
168			xmlns="http://www.w3.org/2000/svg" 
169			xmlns:xlink="http://www.w3.org/1999/xlink" 
170			x="0px" y="0px" width="45px" height="45px" 
171			viewBox="0 0 128 128" 
172			enable-background="new 0 0 128 128" xml:space="preserve">
173		<g>
174			<rect style="fill:#26B5E3;" id="hamb-icon-box" x="0" y="0" width="128" height="128"/>
175			<g transform="matrix(0.6, 0, 0, 0.6, 25.6,25.6)">
176				<rect x="24" y="60" width="80" height="8"/>
177				<rect x="24" y="36" width="80" height="8"/>
178				<rect x="24" y="84" width="80" height="8"/>
179			</g>
180		</g>
181		</svg></div></div>
182			</div>
183			<div id='mobile-trolley-notification'></div>
184		</header>
185		</div>
186
187		<div id="template-content" class="template template-box">
188			<a id="staging-box" href="/_default/staging.htm">You are viewing this site in staging mode. Click in this bar to return to normal site.</a>
189			<div id="svg-icons"><!-- --></div>
190			<div id="notify-container"><div id="basic-template"><a class="ui-notify-close ui-notify-cross" href="#">x</a>#{notify_img}<div class="notify-title">#{notify_title}</div><div class="notify-text">#{notify_text}</div></div></div>
191			<div class="modal-dlg" id="modal-dlg"><!-- --></div>
192			<div class="modal-dlg" id="modal-dlg1"><!-- --></div>
193			<div class="modal-dlg" id="modal-dlg2"><!-- --></div>
194
195			<div id="browser-box">
196				<div id="page-box">
197					<div id="header-nav">
198						  			<header id="header">
199				<div id="header-inner" class="section-inner"><!-- -->
200				<div id="header-box">
201					<div class="header-logo"><a href="/"><img src="/_data/site/117/_default/stStephensLogo2.svg" alt="St Stephen's Guernsey Logo" /></a></div>
202<div class="header-search"><form id="search" class="search" action="/search/index.htm" method="post">
203	<input id="p-search"
204	       name="p_search"
205	       value=""
206	       type="text"
207	       placeholder="Search"
208	       autocomplete="off"
209	       data-autocomplete-url="/search/ajax/search_select.php"
210	       data-autocomplete-min="3"
211	       data-autocomplete-delay="100" />
212</form>
213</div>
214<div class="header-give"><a class="button-cta" href="/359/Give">Give</a></div>
215				</div>
216				</div>
217			</header>
218										<nav id="h-nav">
219					<div id="h-nav-inner" class="section-inner">
220						<div id="h-nav-box">
221							<div id='menu-h'><ul class=' n0'><li class='n159 show-items folder' data-nav-id='159'><div><a class=' ajax-branch' href='/159/About' >About</a><a class='menu-branch ajax' href='/159/ajax/menu_branch.php' rel='nofollow'>&nbsp;</a></div></li><li class='n405 show-items folder' data-nav-id='405'><div><a class=' ajax-branch' href='/405/Services' >Services</a><a class='menu-branch ajax' href='/405/ajax/menu_branch.php' rel='nofollow'>&nbsp;</a></div></li><li class='n451 show-items folder' data-nav-id='451'><div><a class=' ajax-branch' href='/451/Whats-On' >What's On</a><a class='menu-branch ajax' href='/451/ajax/menu_branch.php' rel='nofollow'>&nbsp;</a></div></li><li class='n217 hide-items folder' data-nav-id='217'><div><a class='' href='/217/News' >News</a></div></li><li class='n350 show-items folder' data-nav-id='350'><div><a class=' ajax-branch' href='/350/Community' >Community</a><a class='menu-branch ajax' href='/350/ajax/menu_branch.php' rel='nofollow'>&nbsp;</a></div></li><li class='n9 show-items folder' data-nav-id='9'><div><a class=' ajax-branch' href='/9/Safeguarding' >Safeguarding</a><a class='menu-branch ajax' href='/9/ajax/menu_branch.php' rel='nofollow'>&nbsp;</a></div></li><li class='n311 hide-items folder' data-nav-id='311'><div><a class='' href='/311/Contact-Us' >Contact</a></div></li></ul></div>
222						</div>
223					</div>
224				</nav>
225				
226						
227					</div>
228								<section id="body-header"><!--  -->
229				<div id="body-header-inner"  class="section-inner" ><!--  -->
230					<div id="body-header-box">
231					<div><div class='pg-gallery-fw'><div id="gallery-fw-wrapper-418" class='gallery-fw-wrapper-preset-585 breakout'>
232	
233	<div id="gallery-fw-418" class='swiper slick-slider cycle-slideshow gallery-fw-preset-585'>
234		<div class='swiper-wrapper slick-list slick-track'>
235			<div class='swiper-slide slick-slide cycle-slide im-wrapper ' style='background-image:url(/cache/img/117/pg|418&sz3000x3000&cp&tn&ql&fm&bo&bc&sgfbcdd90aa5&ft1707408783&contactBanner.jpg)' data-title='' data-description='&lt;h1 class=&#039;pg-title&#039;&gt;Contact Us&lt;/h1&gt;' data-link=""></div>
236		</div>
237		
238		
239	</div>
240	
241</div>
242
243<style>
244	.gallery-fw-wrapper-preset-585 {min-height:500px}
245.gallery-fw-wrapper-preset-585 .im-wrapper {background-size:cover;background-position:center center;width:100%;height:500px}
246#gallery-fw-418 {width:100%;min-width:0px;max-width:100%;overflow:hidden}
247.gallery-fw-wrapper-preset-585 .swiper-button-prev {background:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='15 6 9 12 15 18'/></svg>") no-repeat scroll 20% 50% rgba(0, 0, 0, 0);background-size:30px 30px;left:0px;margin-top:0px}
248.gallery-fw-wrapper-preset-585 .swiper-button-next {background:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='9 6 15 12 9 18'/></svg>") no-repeat scroll 80% 50% rgba(0, 0, 0, 0);background-size:30px 30px;right:0px;margin-top:0px}
249.gallery-fw-wrapper-preset-585 .swiper-button-prev:hover, #gallery-fw-418 .swiper-button-next:hover {opacity:1}
250.gallery-fw-wrapper-preset-585 .swiper-button-prev, #gallery-fw-418 .swiper-button-next {cursor:pointer;position:absolute;height:100%;opacity:0;top:0px;width:20%;z-index:800}
251.gallery-fw-wrapper-preset-585 .swiper-pagination {text-align:center;position:absolute;overflow:hidden;z-index:500}
252.gallery-fw-wrapper-preset-585 .swiper-pagination-bullet {font-family:arial;font-size:50px;width:16px;height:16px;display:inline-block;color:#ddd;cursor:pointer}
253.gallery-fw-wrapper-preset-585 .swiper-pagination-bullet-active {color:#D69746}
254.gallery-fw-wrapper-preset-585 .gallery-fw-caption {pointer-events:none}
255.gallery-fw-wrapper-preset-585 .gallery-fw-caption .caption-description {pointer-events:none;background-color:rgba(78,81,102,0.75);color:#ffffff;height:100%}
256.gallery-fw-wrapper-preset-585 .im-wrapper.video {overflow:hidden}
257.gallery-fw-wrapper-preset-585 .im-wrapper.video video {width:100%;object-fit:cover;min-height:500px;max-height:500px}
258@media only screen and (max-width: 767px ) {
259#gallery-fw-wrapper-418 {min-height:500px}
260.gallery-fw-wrapper-preset-585 .im-wrapper {height:500px}
261.gallery-fw-wrapper-preset-585 .im-wrapper.video video {object-fit:cover;min-height:500px;max-height:500px}
262}
263
264</style>
265<script>
266(function(){
267	var navId = 418;
268	function init() {
269		var root = document.getElementById('gallery-fw-' + navId);
270		if (!root || typeof Swiper === 'undefined') return;
271
272		var swiper = new Swiper(root, {"speed":1000,"effect":"fade","direction":"horizontal","fadeEffect":{"crossFade":true},"autoplay":false});
273		if (typeof window.swiperLegacyAlias === 'function') window.swiperLegacyAlias(swiper);
274
275		/* Swiper auto-attaches click listeners to navigation buttons,
276		 * but on opacity:0 buttons inside our breakout/table-cell
277		 * layout those clicks were observed not firing. Bind
278		 * explicit handlers as a guarantee. Calling slideNext/Prev
279		 * while a transition is in flight is safe (Swiper's own
280		 * isLocked/animating guards handle the no-op). */
281		var prevBtn = root.querySelector('.swiper-button-prev');
282		var nextBtn = root.querySelector('.swiper-button-next');
283		if (prevBtn) prevBtn.addEventListener('click', function(e){ e.stopPropagation(); swiper.slidePrev(); });
284		if (nextBtn) nextBtn.addEventListener('click', function(e){ e.stopPropagation(); swiper.slideNext(); });
285
286		var captionEnabled = (0 === 1);
287		if (captionEnabled) {
288			var caption = root.parentNode.querySelector('.gallery-fw-caption .caption-description');
289			if (caption) {
290				/* The first slide's caption is rendered server-side (FwImage.php),
291				 * so interactive content in it (e.g. a lightbox CTA) is present at
292				 * parse time and bound by page-level init like any static markup —
293				 * no initial JS injection here. On slide change we swap in the new
294				 * slide's caption from data-description and fire a bubbling
295				 * 'gallery:caption-changed' event so any plugin that binds to
296				 * injected markup can rescan. The widget stays framework-agnostic
297				 * and never hard-codes a plugin selector. */
298				var setCaption = function(s) {
299					var slides = root.querySelectorAll('.swiper-slide:not(.swiper-slide-duplicate)');
300					var active = slides[s.realIndex];
301					caption.innerHTML = (active && active.dataset.description) ? active.dataset.description : '';
302					caption.dispatchEvent(new CustomEvent('gallery:caption-changed', { bubbles: true, detail: { slide: s.realIndex } }));
303				};
304				swiper.on('slideChangeTransitionStart', setCaption);
305			}
306		}
307
308		root.querySelectorAll('video.bgvid').forEach(function(v){
309			var p = v.play();
310			if (p && p.catch) p.catch(function(){});
311		});
312
313		root.addEventListener('click', function(e){
314			if (e.target.closest('.swiper-button-prev, .swiper-button-next, .swiper-pagination')) return;
315			var link = e.target.closest('.linkable');
316			if (!link) return;
317			var href = link.dataset.link;
318			if (href) window.location = href;
319		});
320	}
321
322	if (document.readyState === 'loading') {
323		document.addEventListener('DOMContentLoaded', init);
324	} else {
325		init();
326	}
327})();
328</script>
328
329</div></div>
330					</div>
331				</div>
332			</section>
333
334					<main id="main-content">
335						<div id="main-content-inner" class="section-inner">
336							<div id="main-content-box">
337								
338												<div id="c-col" class='no-lc no-rc '>
339					<div id="c-col-inner">
340					<div id="c-col-box">
341									<div id="content-header"><!--  -->
342				
343			</div>
344				
345						<div id="content-box" class="layout-0xDCE416AE847B11F19A5EF23C9435931F">
346							<!-- content -->
347<div class="item-contact ">
348	<div class="fg-row">
349		<div class="small-12 medium-6 large-6 columns">
350			<div class='pg-body'><h5>Contact information:</h5>
351<div class="fa">
352<ul>
353<li class="fa-map">St Stephen's Church, Les Grav&eacute;es, St Peter Port, Guernsey, GY1 1RN.</li>
354<li class="fa-phone"><a href="tel:01481720268">01481 720268</a></li>
355<li class="fa-mail"><a href='#' class='email-b64' data-e='am9lLnRob21wc29uQGRlYW5lcnkuZ2c='>email us for Church enquiries</a></li>
356</ul>
357</div>
358<p>Or send us a message using the form provided.</p>
359<p>You can also <a href="https://www.facebook.com/StStephensGuernsey/" target="_blank" rel="noopener noreferrer">follow us on facebook here</a></p></div> 
360		</div>
361		<div class="small-12 medium-6 large-6 columns">
362			<div class='fetch-load ajax-contact-form' data-url='/contact-115::::1790331874:bdb66d7c4676a410ec4bc66c510396f7/ajax/contact_form.htm?' data-fade=0></div>
363		</div>
364	</div>
365	  <div class='pg-gallery-av'></div> 
366</div>
367
368<!-- end of content -->
369
370						</div>
371				
372									<div id="content-footer"><!--  -->
373				
374			</div>
375					</div>
376					</div>
377				</div>
378								
379							</div>
380						</div>
381					</main>
382								<section id="body-footer">
383				<div id="body-footer-inner"  class="section-inner" ><!--  -->
384					<div id="body-footer-box">
385					
386					</div>
387				</div>
388			</section>
389								<footer id="footer">
390				<div id="footer-inner" class="section-inner">
391					<div id="footer-box">
392						<div class="block-fw footer-bg">
393<div class="section-inner">
394<div class="section-upper">
395<div class="footer-logo"><a href="/"><img src="/_data/site/117/_default/stStephensLogoWhite.svg" alt="St Stephen's Guernsey Logo" /></a></div>
396</div>
397<div class="connect-wrapper">
398	<ul>
399		<a title="St Stephen's Guernsey on Facebook" class="facebook" href="https://www.facebook.com/StStephensGuernsey/" target="_blank" rel="noopener noreferrer">
400			<li>Facebook</li>
401		</a>
402		<a title="Call St Stephen's Guernsey" class="phone" href="tel:01481720268" target="_blank" rel="noopener noreferrer">
403			<li>Phone</li>
404		</a> 
405	</ul>
406</div>
407<hr />
408<div class="section-lower"><h6>Copyright &copy; 
408<script type="text/javascript">// <![CDATA[
409document.write(new Date().getFullYear());
410// ]]></script>
410 St Stephen's Guernsey | <a href="/9/Safeguarding">Safeguarding</a> | <a href="/8/Sitemap">Sitemap</a> | <a title="Privacy Policy" href="/434/"> Privacy</a> | Designed &amp; Powered by the <a href="https://www.churchpages.co.uk" target="_blank" rel="noopener noreferrer">Church Pages Initiative</a></h6></div>
411</div>
412</div>
413						<span class="powered">Powered by 
414							<a href='https://www.khooseller.co.uk' target='_blank'>Khooweb e-commerce website specialists</a>
415						</span>
416					</div>
417				</div>
418			</footer>
419				</div>
420			</div>
421		</div>
422
423	</div>
424
425	
426	
426<script src='/js/plugins/cookieconsent/dist/cookieconsent.umd.js?v=5'></script>
vendor: 1 bytes, line 426
426
427<script src='/js/alpine-init.js?v=5'></script>
vendor: 1 bytes, line 427
427
428<script src='/js/component/fieldValidator.js?v=5'></script>
vendor: 1 bytes, line 428
428
429<script>
430$(function() {
431$("#h-nav").sticky({ topSpacing: 0 });
432});
433
434</script>
vendor: 1 bytes, line 434
434
435<script defer src='/js/plugins/popupwindow.js?v=5'></script>
vendor: 1 bytes, line 435
435
436<script src='/js/stores/modals.js?v=5'></script>
vendor: 1 bytes, line 436
436
437<script src='/js/stores/slideout.js?v=5'></script>
vendor: 1 bytes, line 437
437
438<script src='/js/component/slideoutForm.js?v=5'></script>
vendor: 1 bytes, line 438
438
439<script src='/js/component/suggestionBox.js?v=5'></script>
vendor: 1 bytes, line 439
439
440<script src='/js/component/progressBar.js?v=5'></script>
vendor: 1 bytes, line 440
440
441<script src='/js/component/successRedirect.js?v=5'></script>
vendor: 1 bytes, line 441
441
442<script src='/js/component/tinyMceModalForm.js?v=5'></script>
vendor: 1 bytes, line 442
442
443<script defer src='/js/default_body.js?v=5'></script>
443
444
445	
445<script src="/_default/js/cookie_consent.htm"></script>
445
446    <!-- ========================================================================= -->
447    <!-- Modal HTML. We wrap it in a simple x-data                               -->
448    <!-- to ensure it's part of Alpine's initialization process.                 -->
449    <!-- ========================================================================= -->
450    <div id="alpine-modal-root" class="modern-form" 
451        x-data
452        @htmx:before-request="$store.modals.clearTopModalErrors()"
453        @htmx:after-request="$store.modals.handleHtmxResponse(event)"
454        @htmx:after-swap="$store.modals.bindFormValidation(event)"
455        @close-top-modal="$store.modals.close()"
456        > 
457        <!-- The x-data makes this an official Alpine component -->
458
459        <!-- BEGIN: Global Alpine/HTMX Modal Stack -->
460        <template x-if="$store.modals.stack.length > 0"> <!-- THE CRITICAL FIX IS HERE -->
461            <div class="modal-stack-container" 
462                 @keydown.escape="$store.modals.close()"
463                 x-cloak>
464        
465                <!-- The blackout background. Closes the topmost modal when clicked.
466                     Tracks the topmost modal's z-base (minus 1) so that a modal
467                     elevated above an "-above-modal" slideout (z-base 2200) dims
468                     the slideout too; the default case stays at 1999 via the CSS. -->
469                <div class="blackout-bg" @click="$store.modals.close()"
470                     :style="$store.modals.top && $store.modals.top.zBase ? { zIndex: $store.modals.top.zBase - 1 } : {}"></div>
471
472                <!-- This loops through each open modal and "stacks" them visually.
473                     Each modal carries its own z-base (2000 by default, 2200 when
474                     opened from inside an elevated slideout — see modals.js open()),
475                     keeping the dialog above the modal blackout, the slideout panel
476                     (1100 / 2100 elevated), and the legacy \Core\Dialog stack (1001). -->
477                <template x-for="(modal, index) in $store.modals.stack" :key="index">
478                    <div class="dialog-wrapper" :style="{ zIndex: (modal.zBase || 2000) + index }">
479                        <div class="dialog" :style="{ maxWidth: modal.width || '600px' }">
480                            
481                            <!-- ======================================================= -->
482                            <!-- BEGIN: "HTML" Dynamic Content from HTMX PRESET          -->
483                            <!-- ======================================================= -->
484                            <template x-if="modal.type === 'html'">
485                                <div x-html="modal.content"></div>
486                            </template>
487    
488                            <!-- ======================================================= -->
489                            <!-- BEGIN: "FORM" PRESET                                    -->
490                            <!-- ======================================================= -->
491                            <template x-if="modal.type === 'form'">
492                                <div class="dialog-content-wrapper" x-data="{
493                                        title: modal.title || 'Dialog',
494                                        confirmText: modal.confirmText || 'Save Changes',
495                                        loadUrl: modal.loadUrl || '',
496                                        postUrl: modal.postUrl || '',
497                                        context: modal.context || '',
498                                        clearError(event) {
499                                            const fieldName = event.target.name;
500                                            // If there's a name and a corresponding error exists, delete it.
501                                            if (fieldName && $store.modals.top?.errors?.[fieldName]) {
502                                                delete $store.modals.top.errors[fieldName];
503                                                // If it was a field-specific error, the form is no longer fatal.
504                                                // A general fatal error will remain.
505                                                if ($store.modals.top.isFatal && !$store.modals.top.errors.general) {
506                                                    $store.modals.top.isFatal = false;
507                                                }
508                                                $store.modals._renderErrors();
509                                            }
510                                        }
511                                    }"
512                                   x-init="Alpine.nextTick(() => htmx.process($el))">
513                                   
514                                    <!-- 1. The Header (Now part of the shell) -->
515                                    <div class="dialog-header">
516                                        <div class="dialog-title" x-text="title"></div>
517                                        <button class="dialog-close" @click="$store.modals.close()">&times;</button>
518                                    </div>
519
520                                    <!-- 2. The Form -->
521                                    <!-- Bind fieldValidator once the body's htmx content lands so
522                                         .required / .digits / min / max are enforced client-side,
523                                         mirroring the slideout and legacy Core\Dialog. Without this
524                                         a `max` on a text/digits input is never checked (native
525                                         HTML5 max only applies to type=number). bind() self-guards
526                                         against the repeat afterSwaps of nested panel loads.
527
528                                         `context` (which can be a JSON array of every ticked row id)
529                                         travels as a hidden input, NOT hx-vals: hx-vals is inherited
530                                         by descendants, so the .dialog-body loader GET below would
531                                         carry the whole id list in its query string and 414 on a
532                                         large selection. htmx collects enclosing-form inputs on the
533                                         form's own POST only. -->
534                                    <form
535                                        :hx-post="postUrl"
536                                        hx-swap="none"
537                                        hx-disabled-elt="find .button-submit"
538                                        x-init="$el.addEventListener('htmx:afterSwap', () => window.fieldValidator && window.fieldValidator.bind($el))"
539                                        @input.debounce.250ms="clearError($event)"
540                                    >
541                                        <input type="hidden" name="context" :value="context" />
542                                        <div class="validation-error -general"
543                                            data-error-general
544                                            style="display:none">
545                                        </div>
546                                        <div class="dialog-body modern-form"
547                                            :hx-get="loadUrl"
548                                            hx-trigger="load"
549                                            hx-swap="innerHTML"
550                                            hx-indicator="#loading-spinner" 
551                                            >                                            
552                                            <div id="loading-spinner" class="htmx-indicator">
553                                                <div class="loader"></div>
554                                            </div>
555                                        </div>
556
557                                        <div class="dialog-buttons">
558                                            <button type="button" class="button-cancel" @click="$store.modals.close()">Cancel</button>
559                                            <button type="submit" class="button-submit" :disabled="$store.modals.top?.isFatal" x-text="confirmText"></button>
560                                        </div>
561                                    </form>
562                                </div>
563                            </template>
564
565                            <!-- ======================================================= -->
566                            <!-- BEGIN: "CONFIRM" PRESET                                 -->
567                            <!-- ======================================================= -->
568                            <template x-if="modal.type === 'confirm'">
569                                <div class="dialog-content-wrapper">
570                                    <div class="dialog-header">
571                                        <div class="dialog-title" x-text="modal.title || 'Confirm'"></div>
572                                        <button class="dialog-close" @click="$store.modals.close()">&times;</button>
573                                    </div>
574                                    <div class="dialog-body">
575                                        <template x-if="modal.bodyHtml"><div x-html="modal.bodyHtml"></div></template>
576                                        <template x-if="!modal.bodyHtml"><div class="info-box" x-text="modal.message || modal.description || 'Are you sure?'"></div></template>
577                                    </div>
578                                    <div class="dialog-buttons">
579                                        <button class="button-cancel" @click="$store.modals.close()">
580                                            <span x-text="modal.cancelText || 'Cancel'"></span>
581                                        </button>
582                                        <button :class="modal.destructive ? 'button-destructive' : 'button-submit'"
583                                                @click="
584                                                    if (modal.okUrl) { window.location.assign(modal.okUrl); return; }
585                                                    // Function-valued onOk (inline callers) or string method on targetId (Node::modalConfirm)
586                                                    if (typeof modal.onOk === 'function') {
587                                                        modal.onOk(...(modal.okArgs || []));
588                                                    } else if (modal.targetId && modal.onOk) {
589                                                        const targetEl = document.getElementById(modal.targetId);
590                                                        if (targetEl && Alpine.$data(targetEl) && typeof Alpine.$data(targetEl)[modal.onOk] === 'function') {
591                                                            Alpine.$data(targetEl)[modal.onOk](...(modal.okArgs || []));
592                                                        }
593                                                    }
594                                                    $store.modals.close();
595                                                ">
596                                            <span x-text="modal.okText || modal.confirmText || 'OK'"></span>
597                                        </button>
598                                    </div>
599                                </div>
600                            </template>
601
602                            <!-- ======================================================= -->
603                            <!-- BEGIN: "INPUT CONFIRM" PRESET                           -->
604                            <!-- ======================================================= -->
605                            <template x-if="modal.type === 'inputConfirm'">
606                                <!-- This component has its own internal state for the user's input -->
607                                <div class="dialog-content-wrapper" x-data="{ userInput: '' }">
608                                    <div class="dialog-header">
609                                        <div class="dialog-title" x-text="modal.title || 'Confirm Action'"></div>
610                                        <button class="dialog-close" @click="$store.modals.close()">&times;</button>
611                                    </div>
612                                    <div class="dialog-body modern-form">
613                                        <!-- The main instructional message -->
614                                        <div class="info-box" x-text="modal.message || 'Please confirm your action.'"></div>
615
616                                        <!-- The input field for validation -->
617                                        <div class="form-section">
618                                            <div class="form-group">
619                                                <input type="text"
620                                                    x-model="userInput"
621                                                    :placeholder="modal.requiredText"
622                                                    autocomplete="off"
623                                                    @keydown.enter.prevent="if (userInput === modal.requiredText) $refs.submitButton.click()"
624                                                />
625                                                <label x-text="modal.label || 'Please type the confirmation text'"></label>
626                                            </div>
627                                        </div>
628                                    </div>
629                                    <div class="dialog-buttons">
630                                        <button class="button-cancel" @click="$store.modals.close()">
631                                            <span x-text="modal.cancelText || 'Cancel'"></span>
632                                        </button>
633                                        <button class="button-submit"
634                                                x-ref="submitButton"
635                                                :disabled="userInput !== modal.requiredText"
636                                                @click="
637                                                    if (modal.targetId && modal.onOk) {
638                                                        const targetEl = document.getElementById(modal.targetId);
639                                                        if (targetEl) Alpine.$data(targetEl)[modal.onOk](...(modal.okArgs || []));
640                                                    }
641                                                    $store.modals.close();
642                                                ">
643                                            <span x-text="modal.okText || 'OK'"></span>
644                                        </button>
645                                    </div>
646                                </div>
647                            </template>
648
649                            <!-- ======================================================= -->
650                            <!-- BEGIN: "PROGRESS" PRESET (NEW!)                         -->
651                            <!-- ======================================================= -->
652                            <template x-if="modal.type === 'progress'">
653                                <!--
654                                This block is now pure structure. The logic comes from the progressBar component.
655                                It's initialized with the taskId passed in the modal data object.
656                                -->
657                                 <div class="dialog-content-wrapper" 
658                                    x-data="progressBar(modal.taskId, modal.stoppable)" 
659                                    x-cloak
660                                >
661                                    <!-- The rest of your template remains exactly the same! -->
662                                    <div class="dialog-header"><div class="dialog-title">Processing...</div></div>
663                                    <div class="dialog-body">
664                                        <p class="progress-status" x-text="statusText"
665                                        :class="hasFailed ? 'validation-error' : ''"></p>
666                                        <div style="background: #eee; border-radius: 5px; overflow: hidden;">
667                                            <div style="width: 0%; background: #3498db; height: 20px; text-align: center; color: white; transition: width 0.3s ease;"
668                                                :style="{ width: percent + '%' }"
669                                                x-text="percent > 0 ? percent + '%' : ''"
670                                                :class="hasFailed ? 'bg-fail' : ''"
671                                            >
672                                            </div>
673                                        </div>
674                                    </div>
675                                    <div class="dialog-buttons">
676                                        <!-- The new, optional Stop button -->
677                                        <button type="button" class="button-destructive" 
678                                                x-show="isStoppable && !isComplete && !hasFailed && !isStopping" 
679                                                @click="stopTask()">
680                                            Stop
681                                        </button>
682
683                                        <!-- A disabled version for better UX after clicking stop -->
684                                        <button type="button" class="button-destructive" disabled
685                                                x-show="isStopping">
686                                            Stopping...
687                                        </button>
688
689                                        <!-- Your existing close button for failures -->
690                                        <button type="button" class="button-cancel" 
691                                                x-show="hasFailed" 
692                                                @click="closeAndCleanup()">
693                                            Close
694                                        </button>
695                                    </div>
696                                </div>
697                            </template>
698
699                            <!-- ======================================================= -->
700                            <!-- BEGIN: "SUCCESS REDIRECT" PRESET (NEW!)                 -->
701                            <!-- ======================================================= -->
702                            <template x-if="modal.type === 'success'">
703                                <!--
704                                The component is initialized with the message and URL from the modal data.
705                                The @destroy directive ensures we clean up timers if the user hits Escape.
706                                -->
707                                <div class="dialog-content-wrapper" 
708                                    x-data="successRedirect(modal.message, modal.redirectUrl)" 
709                                    @destroy="destroy()"
710                                    x-cloak>
711                                    
712                                    <div class="dialog-header"><div class="dialog-title">Success</div></div>
713                                    <div class="dialog-body" style="text-align: center;">
714                                        <!-- You can add a success icon here if you like -->
715                                        <p style="margin: 1rem 0;" x-text="message"></p>
716                                    </div>
717                                    <div class="dialog-buttons">
718                                        <!-- The OK button calls redirectNow() and shows the countdown -->
719                                        <button type="button" class="button-submit" @click="redirectNow()">
720                                            OK <span x-text="'(' + countdown + ')'"></span>
721                                        </button>
722                                    </div>
723                                </div>
724                            </template>
725
726                        </div>
727                    </div>
728                </template>
729            </div>
730        </template>
731        <!-- END: Global Alpine/HTMX Modal Stack -->
732
733    </div> <!-- End of #alpine-modal-root -->
734
735    <!-- ======================================================= -->
736    <!-- SLIDE-OUT PANEL COMPONENT (Styled for .modern-form)   -->
737    <!-- ======================================================= -->
738    <div id="slideout-panel-root" class="modern-form"
739        x-data="slideoutForm()"
740    >
741        <!-- Overlay -->
742        <div 
743            class="slideout-overlay"
744            :class="{ '-below-topbar': $store.slideout.panelClass && $store.slideout.panelClass.indexOf('-below-topbar') !== -1, '-above-modal': $store.slideout.panelClass && $store.slideout.panelClass.indexOf('-above-modal') !== -1 }"
745            @click="handleOverlayClick()"
746            x-show="$store.slideout.isOpen && (!$store.slideout.panelClass || $store.slideout.panelClass.indexOf('-object-map-list') === -1)"
747            x-cloak
748            x-transition:enter="transition-opacity ease-in-out duration-300"
749            x-transition:enter-start="opacity-0"
750            x-transition:enter-end="opacity-100"
751            x-transition:leave="transition-opacity ease-in-out duration-300"
752            x-transition:leave-start="opacity-100"
753            x-transition:leave-end="opacity-0"
754        ></div>
755
756        <!-- Panel -->
757        <div 
758            class="slideout-panel"
759            :class="$store.slideout.panelClass.split(' ').filter(function (c) { return c; })"
760            x-show="$store.slideout.isOpen"
761            x-cloak
762            x-transition:enter="transition ease-in-out duration-300 transform"
763            x-transition:enter-start="translate-x-full"
764            x-transition:enter-end="translate-x-0"
765            x-transition:leave="transition ease-in-out duration-300 transform"
766            x-transition:leave-start="translate-x-0"
767            x-transition:leave-end="translate-x-full"
768            :style="$store.slideout.currentWidth ? { width: $store.slideout.currentWidth } : {}"
769        >
770            <!-- Header -->
771            <div class="slideout-header">
772                <div class="slideout-header-text">
773                    <h2 class="slideout-title" x-text="$store.slideout.title"></h2>
774                    <p class="slideout-subtitle" x-show="$store.slideout.subt
774itle" x-text="$store.slideout.subtitle" x-cloak></p>
775                </div>
776                <div id="slideout-head-slot" class="slideout-head-slot"></div>
777                <button @click="handleCloseClick()" class="slideout-close" aria-label="Close panel">&times;</button>
778            </div>
779
780            <!-- Content shell: teleports (e.g. object-map list) mount as direct children;
781                 htmx fragments load into #slideout-htmx-body so innerHTML swaps never
782                 wipe Alpine teleport anchors on this container. -->
783            <div id="slideout-content" class="slideout-body">
784                <div id="slideout-htmx-body"
785                     hx-trigger="load-slideout-content"
786                     hx-swap="innerHTML"></div>
787            </div>
788
789            <!-- Footer (form type only) -->
790            <div class="slideout-footer"
791                 x-show="$store.slideout.type === 'form' && !$store.slideout.hideFooter"
792                 x-cloak>
793                <div id="slideout-foot-left" class="slideout-foot-left"></div>
794                <div class="slideout-foot-actions">
795                    <button type="button" class="btn button-cancel" @click="cancelExplicit()">Cancel</button>
796                    <div id="slideout-foot-actions"></div>
797                </div>
798            </div>
799        </div>
800
801        <iframe id="slideout-save-frame" name="slideout-save-frame" class="slideout-save-frame"
802                title="slideout save target" tabindex="-1" aria-hidden="true"></iframe>
803    </div>
804
805    <!-- ======================================================= -->
806    <!-- GUIDED TUTORIAL OVERLAY (engine mount point)            -->
807    <!-- The player builds its dim-backdrop + popover in <body>; -->
808    <!-- this stable root just hosts the Alpine component.       -->
809    <!-- Only mounted where the tutorial JS bundle is registered  -->
810    <!-- (admin) — the slot is left empty otherwise; see _foot.php -->
811    <!-- ======================================================= -->
812
813    <!-- Hidden form – submits same data as original -->
814    <form id="advanced-search-hidden-form" x-data
815        hx-post="/index.php?url=/418/index.htm"
816        hx-target="#search-results-content"
817        hx-select="#search-results-content"
818        hx-swap="innerHTML"
819        style="display:none;"
820        @htmx:after-request="handleSearchSuccess(event, 'advanced-search-template')"
821        >
822        <div id="advanced-search-hidden-fields"></div>
823    </form>
824
825    <template id="show-loader-loader-template">
826        <div style="padding:80px 0; text-align:center; font-size:1.1em; color:#666;">
827            <div class="loader" style="display:inline-block; vertical-align:middle;"></div>
828        </div>
829    </template>
830<script>
831    document.body.addEventListener('htmx:beforeRequest', function(evt) {
832        // Get the element that will be updated (the swap target)
833        let target = evt.detail.target;
834
835        // If no explicit target, fall back to the requesting element
836        if (!target) {
837            const requestConfig = evt.detail.xhr; // not directly available, so use the elt
838            target = evt.target;
839        }
840
841        // Also check if the actual swap target (from hx-target on trigger) has the class
842        // HTMX sets evt.detail.target to the swap target element
843        if (target && target.classList && target.classList.contains('show-loader')) {
844            const loaderTemplate = document.getElementById('show-loader-loader-template');
845            
846            if (loaderTemplate && !target.querySelector('.htmx-loader-overlay')) { // prevent duplicates
847                const overlay = document.createElement('div');
848                overlay.className = 'htmx-loader-overlay';
849                
850                Object.assign(overlay.style, {
851                    position: 'absolute',
852                    top: '0',
853                    left: '0',
854                    width: '100%',
855                    height: '100%',
856                    zIndex: '100',
857                    display: 'flex',
858                    alignItems: 'center',
859                    justifyContent: 'center',
860                    backgroundColor: 'rgba(255,255,255,0.8)',
861                    backdropFilter: 'blur(2px)'
862                });
863
864                const loaderClone = loaderTemplate.content.cloneNode(true);
865                overlay.appendChild(loaderClone);
866
867                // Ensure relative positioning
868                const computedStyle = window.getComputedStyle(target);
869                if (computedStyle.position === 'static') {
870                    target.style.position = 'relative';
871                }
872
873                target.appendChild(overlay);
874            }
875        }
876    });
877</script>
877
878    
878<script>
879    /**
880     * Creates a reusable object with logic for managing a selection.
881     * This is a "mixin" that we will add to our main Alpine component.
882     * @param {string} initialId - The ID of the item that should be selected on page load.
883     */
884    function selectionManagerMixin(initialId) {
885        return {
886            // --- Properties ---
887            initialSelectedId: initialId,
888
889            // --- Methods ---
890            initializeSelection() {
891                // Explicit initial id (detail-context pages) — select unconditionally.
892                if (this.initialSelectedId && this.initialSelectedId !== '0') {
893                    setTimeout(() => {
894                        Alpine.store('selection').select(this.initialSelectedId);
895                    }, 0);
896                    return;
897                }
898                // Read the URL-persisted selection first: clear() strips ?selrow from the
899                // URL synchronously (history.replaceState), so reading it after is too late.
900                let selrow = '';
901                try { selrow = new URL(window.location.href).searchParams.get('selrow') || ''; } catch (e) {}
902
903                // No server-preselected row on this page. The selection store is a
904                // global singleton shared across pages, so a selection set on another
905                // screen (e.g. a product-variant idx picked on the custom-price page)
906                // survives an admin partial-swap navigation. Clear it so detail panes
907                // keyed on $store.selection.selectedId don't fetch with a foreign id.
908                Alpine.store('selection').clear();
909
910                // Then restore that selection so a refresh keeps the same row — but only
911                // if that row is actually present in the current list.
912                if (selrow) this._restorePersistedSelection(selrow);
913            },
914
915            // A single setTimeout(0) races row render: on list pages where htmx/Alpine
916            // reprocess the table after load, the row can appear a tick or two later, so
917            // the one-shot lookup missed it and the selection was silently dropped (looked
918            // like the selection "jumped" away on refresh). Retry across a few frames until
919            // the row exists, then select it; give up quietly if it never shows (e.g. it's
920            // on another paginated page).
921            _restorePersistedSelection(selrow, attempt = 0) {
922                const sel = (window.CSS && CSS.escape) ? CSS.escape(selrow) : selrow;
923                if (document.querySelector('tr[data-selected-id="' + sel + '"]')) {
924                    Alpine.store('selection').select(selrow);
925                    return;
926                }
927                if (attempt < 20) {
928                    setTimeout(() => this._restorePersistedSelection(selrow, attempt + 1), 30);
929                }
930            }
931        };
932    }
933
934    /**
935     * The primary handler for after ANY search request completes.
936     * It updates the search tags and resets the selection pane.
937     */
938    function handleSearchSuccess(event, templateId) {
939        if (event.detail.successful) {
940            
941            // Get the raw payload from the request
942            const payload = event.detail.requestConfig.parameters;
943
944
945            // Set the tags using the payload
946            Alpine.store('searchTags').setFromPayload(payload, templateId);
947            
948            Alpine.store('selection').clear();
949        } else {
950            console.error('[handleSearchSuccess] A search request was not successful.');
951        }
952    }
953
954
955    window.handleAdvancedSearchSuccess = (event, templateId) => {
956        if (event.detail.successful) {
957            Alpine.store('slideout').close();
958        }
959        handleSearchSuccess(event, templateId);
960    };
961
962    /**
963     * openBulkModal — click handler for Node::modalBulkForm bulk-action menus.
964     *
965     * Three scopes:
966     *   - 'selection' : collect .p-check:checked values from #list_form, bail
967     *                   out with a notify if none selected, open a form modal
968     *                   with the checked-id count in the loadUrl query string
969     *                   and the ids JSON-encoded in `context` for the action
970     *                   handler.
971     *   - 'selection-into' : like 'selection', but also needs the single
972     *                   highlighted row (`$store.selection.selectedId`) as a
973     *                   *target*. Bails with a notify if nothing is highlighted,
974     *                   substitutes the `__TARGET__` sentinel in loadUrl/postUrl
975     *                   with the target idx (so it lands in the URL path segment
976     *                   the action handler reads), and excludes the target from
977     *                   the checked-id sources. Used by the customer "Merge
978     *                   into selected" action.
979     *   - 'results'   : no id collection; the current URL's query string is
980     *                   appended to both loadUrl and postUrl so the action
981     *                   handler can re-run the current search. `context` is
982     *                   the literal string 'results'.
983     *
984     * Pairs with:
985     *   - Node::modalBulkForm() — renders the trigger @click handler.
986     *   - fetch/html/*_bulk.php loader — reads ?count= / query-string
987     *     params to render the confirmation message.
988     *   - fetch/action/*_bulk.php handler — reads $oREQUEST->context to
989     *     distinguish selection (JSON array of ids) vs results (literal
990     *     'results' plus query-string search params).
991     */
992    window.openBulkModal = function(title, loadUrl, postUrl, confirmText, scope, width) {
993        scope = scope || 'selection';
994
995        if (scope === 'selection' || scope === 'selection-into') {
996            // 'selection-into' also needs the highlighted row as a target: inject
997            // it into the URL path (__TARGET__ sentinel) up front, then fall
998            // through to the same checked-source collection as plain 'selection'.
999            let target = null;
1000            if (scope === 'selection-into') {
1001                target = Alpine.store('selection')?.selectedId;
1002                if (!target) {
1003                    notify('No target customer', 'Click a customer row to pick the merge target first.', 'info');
1004                    return;
1005                }
1006                loadUrl = loadUrl.replaceAll('__TARGET__', target);
1007                postUrl = postUrl.replaceAll('__TARGET__', target);
1008            }
1009
1010            const listForm = document.getElementById('list_form');
1011            const rootSel  = listForm ? listForm.querySelectorAll('input.p-check:checked')
1012                                      : document.querySelectorAll('#list_form input.p-check:checked');
1013            let checked = Array.from(rootSel).map(el => el.value);
1014            // the target can't be a source (can't merge a customer into itself)
1015            if (target) checked = checked.filter(id => id !== target);
1016            if (checked.length === 0) {
1017                notify('Nothing selected', 'Tick a checkbox on at least one row first.', 'info');
1018                return;
1019            }
1020            const sep = loadUrl.includes('?') ? '&' : '?';
1021            Alpine.store('modals').open({
1022                type:        'form',
1023                title:       title,
1024                loadUrl:     loadUrl + sep + 'count=' + checked.length,
1025                postUrl:     postUrl,
1026                confirmText: confirmText || 'Confirm',
1027                context:     JSON.stringify(checked),
1028                width:       width || ''
1029            });
1030            return;
1031        }
1032
1033        if (scope === 'results') {
1034            // The active filters live in the searchTags store — they're posted
1035            // to the list via htmx.ajax() without ever being written to the URL
1036            // (only chip *removal* prunes the URL). So window.location.search is
1037            // stale and would make the bulk action operate on a different set
1038            // than the one on screen. Prefer the store's authoritative filter
1039            // set; fall back to the URL for legacy pages that don't use tags.
1040            const tagStore = Alpine.store('searchTags');
1041            let extra;
1042            if (tagStore && Array.isArray(tagStore.tags) && tagStore.tags.length) {
1043                const params = new URLSearchParams();
1044                tagStore.tags.forEach(t => {
1045                    if (t.value !== '' && t.value != null) params.append(t.name, t.value);
1046                });
1047                extra = params.toString();
1048            } else {
1049                const qs = window.location.search || '';
1050                extra = qs ? qs.replace(/^\?/, '') : '';
1051            }
1052            const loadSep = loadUrl.includes('?') ? '&' : '?';
1053            const postSep = postUrl.includes('?') ? '&' : '?';
1054            Alpine.store('modals').open({
1055                type:        'form',
1056                title:       title,
1057                loadUrl:     loadUrl + (extra ? loadSep + extra : ''),
1058                postUrl:     postUrl + (extra ? postSep + extra : ''),
1059                confirmText: confirmText || 'Confirm',
1060                context:     'results',
1061                width:       width || ''
1062            });
1063            return;
1064        }
1065
1066        console.error('openBulkModal: unknown scope', scope);
1067    };
1068
1069    /**
1070     * Global submit_list bridge for legacy Jaxon list-action dialogs.
1071     *
1072     * Pages converted to the modern htmx + modern-form blueprint no longer
1073     * include the old hidden `<form id="unisearch-form">` that the legacy
1074     * `submit_list({list_action, list_dlg_data})` callback used to populate
1075     * and submit. This shim is invoked by the Jaxon dialog `cbfunction` and
1076     * builds a temporary POST form on the fly so existing PHP `list_action`
1077     * handlers (delete / merge / email / set_group / etc.) keep working
1078     * without per-page rewires.
1079     *
1080     * If a page defines its own `submit_list` (e.g. orders.htm, which has
1081     * its own hidden legacy form for backwards compat), that wins.
1082     */
1083    if (typeof window.submit_list !== 'function') {
1084        window.submit_list = function(data) {
1085            const checked = [];
1086            document.querySelectorAll('input.p-check:checked').forEach(el => {
1087                checked.push(el.value);
1088            });
1089
1090            const form = document.createElement('form');
1091            form.method = 'post';
1092            form.action = location.pathname + location.search;
1093            form.style.display = 'none';
1094
1095            const addField = (name, value) => {
1096                const input = document.createElement('input');
1097                input.type = 'hidden';
1098                input.name = name;
1099                input.value = value;
1100                form.appendChild(input);
1101            };
1102
1103            addField('list_action',  data.list_action || '');
1104            addField('list_dlg_data', data.list_dlg_data || '');
1105            addField('p_checked',    checked.join(','));
1106
1107            document.body.appendChild(form);
1108            form.submit();
1109        };
1110    }
1111    </script>
1111
1112    <!-- alpine-init.js is served via AssetCatalog ('alpine.init', registered in
1113         _default ModuleInterface::registerIncludes) so it gets the cache-buster
1114         version query. Do not re-add a hardcoded script tag here. -->
1115</body>
1116</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.