1 2 3<!DOCTYPE html> 4 5<html lang="en-GB"> 6<head><!-- Powered by www.ukmsl.com - v8.90.2609.303 --><title> 7 Development & Democracy Officer Blog 8</title><meta charset="utf-8" /><link href="//static-uk.ukmsl.net/8.90/skins/print.css" rel="stylesheet" type="text/css" media="print" /> 9<link href="//static-uk.ukmsl.net/8.90/skins/system.css" rel="stylesheet" type="text/css" media="all" /> 10<link href="//static-uk.ukmsl.net/8.90/skins/icons.css" rel="stylesheet" type="text/css" media="all" /> 11<link href="//static-uk.ukmsl.net/8.90/skins/Froala.css" rel="stylesheet" type="text/css" media="all" /> 12 13<!-- start of su-meta snippet --> 14<!-- Basic Meta Tags --> 15<meta charset="utf-8"> 16<meta name="viewport" content="width=device-width, initial-scale=1.0"> 17<meta name="referrer" content="strict-origin-when-cross-origin"> 18<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin=""> 19<link rel="preload" href="/assets/site_resources/Lato-Light.woff2" as="font" type="font/woff2" crossorigin> 20<link rel="preload" href="/assets/site_resources/league-spartan-700.woff2" as="font" type="font/woff2" crossorigin> 21 22<!-- Branding --> 23<meta name="theme-color" content="#22004d"> 24<meta name="msapplication-TileColor" content="#22004d"> 25<!-- 26 Removed msapplication-navbutton-color: Windows Phone IE only, no modern 27 browser reads it (same dead-platform relic as the old UA-sniffing code). 28--> 29 30<!-- iOS / mobile web app --> 31<!-- apple-mobile-web-app-status-bar-style only accepts "default", 32 "black", or "black-translucent" â a hex color is invalid and is 33 silently ignored by iOS. Using "black-translucent" as the closest 34 supported equivalent for a dark theme-color; adjust if you want the 35 plain black bar instead. --> 36<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> 37<!-- apple-mobile-web-app-capable is required for standalone/home-screen 38 mode on iOS versions before 16.4; mobile-web-app-capable alone is not 39 enough for those older Safari versions. Keeping both. --> 40<meta name="apple-mobile-web-app-capable" content="yes"> 41<meta name="mobile-web-app-capable" content="yes"> 42<meta name="apple-itunes-app" content="app-id=1579307063"> 43 44<!-- App Titles --> 45<meta name="application-name" content="Keele SU"> 46<meta name="apple-mobile-web-app-title" content="Keele SU"> 47 48<!-- Icons --> 49<link rel="apple-touch-icon" sizes="180x180" href="https://keelesu.com/stylesheet/Brand/apple-touch-icon.png"> 50<link rel="icon" type="image/png" sizes="32x32" href="https://keelesu.com/stylesheet/Brand/favicon-32x32.png"> 51<link rel="icon" type="image/png" sizes="16x16" href="https://keelesu.com/stylesheet/Brand/favicon-16x16.png"> 52<link rel="manifest" href="https://keelesu.com/stylesheet/Brand/site.webmanifest"> 53<link rel="mask-icon" href="https://keelesu.com/stylesheet/Brand/safari-pinned-tab.svg" color="#2e1a47"> 54<link rel="shortcut icon" href="https://keelesu.com/stylesheet/Brand/favicon.ico"> 55 56<!-- Open Graph (Global defaults via MSL fields) --> 57<meta property="og:type" content="website"> 58<meta property="og:site_name" content="Keele SU"> 59<meta property="og:locale" content="en_GB"> 60<meta property="og:url" content="https://keelesu.com/yourunion/electedofficers/blogs/ddofficer/"> 61<meta property="og:title" content="Officer Blogs"> 62<meta property="og:description" content=""> 63<meta property="og:image" content="https://keelesu.com/pageassets/yourunion/electedofficers/blogs/Elected-Officers.jpeg"> 64 65<!-- Twitter --> 66<meta name="twitter:card" content="summary_large_image"> 67<meta name="twitter:site" content="@keelesu"> 68<meta name="twitter:url" content="https://keelesu.com/yourunion/electedofficers/blogs/ddofficer/"> 69<meta name="twitter:title" content="Officer Blogs"> 70<meta name="twitter:description" content=""> 71<meta name="twitter:image" content="https://keelesu.com/pageassets/yourunion/electedofficers/blogs/Elected-Officers.jpeg"> 72 73<!-- General --> 74<meta name="author" content="Keele SU"> 75<meta name="url" content="https://keelesu.com/yourunion/electedofficers/blogs/ddofficer/"> 76<meta name="title" content="Officer Blogs"> 77<meta name="description" content=""> 78<meta name="image" content="https://keelesu.com/pageassets/yourunion/electedofficers/blogs/Elected-Officers.jpeg"> 79<!-- 80 NOTE: <meta name="title"> is not a recognised SEO tag â it does nothing 81 for search engines or browsers. Confirm the CMS also emits a real 82 <title>...</title> element somewhere in this head; none is present in 83 this fragment. 84--> 85 86<!-- Facebook Verify --> 87<meta name="facebook-domain-verification" content="j8zqeuyega64aha524xuxeb1913wri" /> 88 89<!-- 90 Meta Pixel Code 91 NOTE: this fires unconditionally on every page load. As a UK site, 92 this is subject to UK GDPR/PECR, which requires consent before
93 non-essential tracking pixels/cookies load. If a consent-management 94 layer elsewhere gates this script (e.g. only injecting it after 95 opt-in, or holding it via a tag manager's consent mode), that's fine â 96 but as a standalone unconditional
96<script> it loads Facebook's 97 tracker before any opt-in choice is made. Verify a consent gate wraps 98 this before shipping. 99-->
100<script>
vendor: 408 bytes, lines 100-109
100 101!function(f,b,e,v,n,t,s) 102{if(f.fbq)return;n=f.fbq=function(){n.callMethod? 103n.callMethod.apply(n,arguments):n.queue.push(arguments)}; 104if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; 105n.queue=[];t=b.createElement(e);t.async=!0; 106t.src=v;s=b.getElementsByTagName(e)[0]; 107s.parentNode.insertBefore(t,s)}(window, document,'script', 108'https://connect.facebook.net/en_GB/fbevents.js'); 109fbq('init', '
1092123049768236773
vendor: 32 bytes, lines 109-110
109'); 110fbq('track', 'PageView');
111</script>
111 112<noscript><img height="1" width="1" style="display:none" 113src="https://www.facebook.com/tr?id=2123049768236773&ev=PageView&noscript=1" 114/></noscript> 115<!-- End Meta Pixel Code --> 116 117<!-- Structured Data (Unified Organization/EducationalOrganization) -->
118<script type="application/ld+json"> 119{ 120 "@context": "https://schema.org", 121 "@type": ["Organization", "EducationalOrganization"], 122 "@id": "https://keelesu.com/#organization", 123 "name": "Keele University Students' Union", 124 "alternateName": "Keele SU", 125 "url": "https://keelesu.com/", 126 "logo": "https://keelesu.com/stylesheet/Brand/keelesu_rich_blue_logo_long.png", 127 "sameAs": [ 128 "https://www.facebook.com/keelestudentsunion/", 129 "https://www.instagram.com/keelesu/", 130 "https://www.tiktok.com/@keelesu", 131 "https://www.youtube.com/@KeeleSU", 132 "https://www.linkedin.com/company/keele-students-union/", 133 "https://linktr.ee/keelesu" 134 ], 135 "address": { 136 "@type": "PostalAddress", 137 "streetAddress": "Keele University", 138 "addressLocality": "Keele", 139 "addressRegion": "Staffordshire", 140 "postalCode": "ST5 5BJ", 141 "addressCountry": "GB" 142 }, 143 "contactPoint": { 144 "@type": "ContactPoint", 145 "telephone": "+44 1782 733700", 146 "email": "[email protected]", 147 "contactType": "Customer Service", 148 "availableLanguage": "English" 149 }, 150 "parentOrganization": { 151 "@type": "CollegeOrUniversity", 152 "name": "Keele University", 153 "url": "https://www.keele.ac.uk/" 154 } 155} 156</script>
156 157 158<!-- ========================= --> 159<!-- Structured Data: Consistent Author --> 160<!-- ========================= --> 161<!-- 162 This now correctly resolves: the "@id" above matches the "@id" this 163 block references below, so search engines can actually connect the 164 two entities. Previously the first schema had no "@id" at all, so the 165 link was broken. 166-->
167<script type="application/ld+json"> 168{ 169 "@context": "https://schema.org", 170 "@type": "Organization", 171 "@id": "https://keelesu.com/#author", 172 "name": "Keele SU Team", 173 "url": "https://keelesu.com/yoursu/aboutus/", 174 "parentOrganization": { 175 "@id": "https://keelesu.com/#organization" 176 } 177} 178</script>
178 179 180<!-- 181 Canonical link. 182 IMPORTANT: this is still a client-side fallback, kept only for parity 183 with the original snippet. A canonical tag this important should 184 ideally be rendered server-side directly into the HTML <head> by the 185 CMS/MSL template, since crawlers that don't execute JavaScript (and 186 even Googlebot, under rendering-budget constraints) may never see a 187 tag injected this way. Treat this script as a safety net, not the 188 primary mechanism, and prefer wiring MSL's own canonical/URL field 189 into a real <link rel="canonical"> tag if one exists. 190 191 Fixes applied vs the original inline script: 192 - Skips entirely if a canonical <link> already exists in <head>, to 193 avoid emitting a duplicate (which causes Google to ignore or 194 arbitrarily choose between them). 195 - Does NOT force a trailing slash onto paths that already end in a 196 file extension (.aspx, .pdf, .jpg, etc.) â this site's markup 197 (ctl00_basket_lnk... control IDs) indicates ASP.NET/MSL, where many 198 real URLs end in .aspx and would 404 if forced to end in "/". 199 - Uses the built-in URL API instead of ad hoc string/regex surgery to 200 avoid double slashes or malformed protocols. 201 - Deliberately still drops the query string, matching the original 202 behavior. If any pages rely on query strings for distinct content 203 (pagination, filters, search results), those pages need their own 204 canonical logic â this generic script should not run on them. 205-->
206<script> 207(function () { 208 if (document.querySelector('link[rel="canonical"]')) return; 209 210 var url = new URL(window.location.href); 211 var path = url.pathname; 212 213 // Only add a trailing slash if the last path segment has no file 214 // extension (i.e. looks like a directory/route, not a file). 215 var lastSegment = path.split('/').pop(); 216 var looksLikeFile = lastSegment.indexOf('.') !== -1; 217 218 if (!looksLikeFile && !path.endsWith('/')) { 219 path += '/'; 220 } 221 222 var canonicalURL = url.origin + path; 223 224 var link = document.createElement('link'); 225 link.rel = 'canonical'; 226 link.href = canonicalURL; 227 228 document.head.appendChild(link); 229})(); 230</script>
230 231 232<!-- Google tag (gtag.js) -->
vendor: 65 bytes, lines 232-233
232 233<script async src="https://www.googletagmanager.com/gtag/js?id=
233AW-18398694871
vendor: 13 bytes, line 233
233"></script>
234<script> 235
vendor: 138 bytes, lines 235-239
235window.dataLayer = window.dataLayer || []; 236 function gtag(){dataLayer.push(arguments);} 237 gtag('js', new Date()); 238 239 gtag('config', '
239AW-18398694871
vendor: 5 bytes, line 239
239');
240</script>
240 241 242 <!-- Google Tag Manager -->
243<script>
vendor: 331 bytes, lines 243-247
243(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': 244new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], 245j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 246'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); 247})(window,document,'script','dataLayer','
247GTM-W8L44PJ6
vendor: 3 bytes, line 247
247');
247</script>
247 248<!-- End Google Tag Manager --> 249<!-- end of su-meta snippet --> 250 251 252<!-- start of novus-head snippet --> 253<!-- JavaScript Bundle with Popper -->
254<script defer src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.8/js/bootstrap.bundle.min.js" integrity="sha512-HvOjJrdwNpDbkGJIG2ZNqDlVqMo77qbs4Me4cah0HoDrfhrbA+8SBlZn1KrvAQw7cILLPFJvdwIgphzQmMm+Pw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
vendor: 2 bytes, line 254
254
255<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
255 256<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous" referrerpolicy="no-referrer" /> 257 258<!-- Selfâhosted fonts --> 259<style> 260/* Lato Light â Weight 300 */ 261@font-face { 262 font-family: "Lato"; 263 font-style: normal; 264 font-weight: 300; 265 font-display: swap; 266 src: url("https://keelesu.com/assets/site_resources/Lato-Light.woff2") format("woff2"); 267} 268 269/* League Spartan Bold â Weight 700 270@font-face { 271 font-family: "League Spartan"; 272 font-style: normal; 273 font-weight: 700; 274 font-display: swap; 275 src: url("https://keelesu.com/assets/site_resources/league-spartan-700.woff2") format("woff2"); 276} 277 278/* League Spartan Bold â Weight 700 */ 279@font-face { 280 font-family: "League Spartan"; 281 font-style: normal; 282 font-weight: 600; 283 font-display: swap; 284 src: url("https://keelesu.com/assets/site_resources/Spartan-Bold.ttf") format("ttf"); 285} 286</style> 287 288 289 290
291<script> 292 (function() { 293 const canonicalLink = document.createElement('link'); 294 canonicalLink.rel = 'canonical'; 295 canonicalLink.href = 'https://keelesu.com' + window.location.pathname; 296 document.head.appendChild(canonicalLink); 297 })(); 298</script>
298 299<!-- end of novus-head snippet --> 300 301<link href="//static-uk.ukmsl.net/8.90/skins/Controls/Breadcrumb.css" rel="stylesheet" type="text/css" media="all" /> 302<link href="//static-uk.ukmsl.net/8.90/skins/Controls/BasketLink.css" rel="stylesheet" type="text/css" media="all" /> 303<link href="//static-uk.ukmsl.net/8.90/skins/Modules/Blogs/Default.css" rel="stylesheet" type="text/css" media="all" /> 304<link href="/stylesheet/novus/core.css?v=1405" rel="stylesheet" type="text/css" /> 305<link href="/stylesheet/novus/navigation.css?v=380" rel="stylesheet" type="text/css" /> 306<link href="/stylesheet/novus/bootstrap.css?v=152" rel="stylesheet" type="text/css" /> 307<link href="/stylesheet/novus/core-2026.css?v=82" rel="stylesheet" type="text/css" /> 308<link href="/pagestylesheet/yourunion/electedofficers/blogs/ddofficer/styles.css?v=22" rel="stylesheet" type="text/css" media="all" /><link rel="alternate" type="application/rss+xml" title="DDOfficer" href="https://keelesu.com/blogs/rss/DDOfficer/" /><!-- Global site tag (gtag.js) - Google Analytics -->
vendor: 65 bytes, lines 308-309
308 309<script async src="https://www.googletagmanager.com/gtag/js?id=
309G-6L5N1C41XV
vendor: 13 bytes, line 309
309"></script>
310<script> 311
vendor: 133 bytes, lines 311-314
311window.dataLayer = window.dataLayer || []; 312 function gtag(){dataLayer.push(arguments);} 313 gtag('js', new Date()); 314 gtag('config', '
314G-6L5N1C41XV
vendor: 5 bytes, line 314
314');
315</script>
315 316<link href="https://ajax.aspnetcdn.com/ajax/act/16_1_1/Content/AjaxControlToolkit/Styles/Backgrounds.min.css" type="text/css" rel="stylesheet" /></head> 317<body id="msl"> 318 <form method="post" action="./" id="aspnetForm"> 319<div class="aspNetHidden"> 320<input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" /> 321<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" /> 322<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="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" /> 323</div> 324
325<script type="text/javascript"> 326//<![CDATA[ 327var theForm = document.forms['aspnetForm']; 328if (!theForm) { 329 theForm = document.aspnetForm; 330} 331function __doPostBack(eventTarget, eventArgument) { 332 if (!theForm.onsubmit || (theForm.onsubmit() != false)) { 333 theForm.__EVENTTARGET.value = eventTarget; 334 theForm.__EVENTARGUMENT.value = eventArgument; 335 theForm.submit(); 336 } 337} 338//]]> 339</script>
339 340 341
342<script src="https://ajax.aspnetcdn.com/ajax/4.6/1/WebForms.js" type="text/javascript"></script>
vendor: 2 bytes, line 342
342
343<script type="text/javascript"> 344//<![CDATA[ 345window.WebForm_PostBackOptions||document.write('
345<script type="text/javascript" src="/WebResource.axd?d=pynGkmcFUV13He1Qd6_TZA5Pm6HoQodZMTRqDRmkRQVgWn7tOgQlDkJJRuri60lphrOTNQ2&t=639190869332169432"><\/script>');//]]> 346</script>
346 347 348 349
350<script type="text/javascript"> 351//<![CDATA[ 352var __cultureInfo = {"name":"en-GB","numberFormat":{"CurrencyDecimalDigits":2,"CurrencyDecimalSeparator":".","IsReadOnly":true,"CurrencyGroupSizes":[3],"NumberGroupSizes":[3],"PercentGroupSizes":[3],"CurrencyGroupSeparator":",","CurrencySymbol":"£","NaNSymbol":"NaN","CurrencyNegativePattern":1,"NumberNegativePattern":1,"PercentPositivePattern":1,"PercentNegativePattern":1,"NegativeInfinitySymbol":"-â","NegativeSign":"-","NumberDecimalDigits":2,"NumberDecimalSeparator":".","NumberGroupSeparator":",","CurrencyPositivePattern":0,"PositiveInfinitySymbol":"â","PositiveSign":"+","PercentDecimalDigits":2,"PercentDecimalSeparator":".","PercentGroupSeparator":",","PercentSymbol":"%","PerMilleSymbol":"â°","NativeDigits":["0","1","2","3","4","5","6","7","8","9"],"DigitSubstitution":1},"dateTimeFormat":{"AMDesignator":"AM","Calendar":{"MinSupportedDateTime":"\/Date(-62135596800000)\/","MaxSupportedDateTime":"\/Date(253402300799999)\/","AlgorithmType":1,"CalendarType":1,"Eras":[1],"TwoDigitYearMax":2049,"IsReadOnly":true},"DateSeparator":"/","FirstDayOfWeek":1,"CalendarWeekRule":2,"FullDateTimePattern":"dd MMMM yyyy HH:mm:ss","LongDatePattern":"dd MMMM yyyy","LongTimePattern":"HH:mm:ss","MonthDayPattern":"d MMMM","PMDesignator":"PM","RFC1123Pattern":"ddd, dd MMM yyyy HH\u0027:\u0027mm\u0027:\u0027ss \u0027GMT\u0027","ShortDatePattern":"dd/MM/yyyy","ShortTimePattern":"HH:mm","SortableDateTimePattern":"yyyy\u0027-\u0027MM\u0027-\u0027dd\u0027T\u0027HH\u0027:\u0027mm\u0027:\u0027ss","TimeSeparator":":","UniversalSortableDateTimePattern":"yyyy\u0027-\u0027MM\u0027-\u0027dd HH\u0027:\u0027mm\u0027:\u0027ss\u0027Z\u0027","YearMonthPattern":"MMMM yyyy","AbbreviatedDayNames":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"ShortestDayNames":["Su","Mo","Tu","We","Th","Fr","Sa"],"DayNames":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"AbbreviatedMonthNames":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",""],"MonthNames":["January","February","March","April","May","June","July","August","September","October","November","December",""],"IsReadOnly":true,"NativeCalendarName":"Gregorian Calendar","AbbreviatedMonthGenitiveNames":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",""],"MonthGenitiveNames":["January","February","March","April","May","June","July","August","September","October","November","December",""]},"eras":[1,"A.D.",null,0]};//]]> 353</script>
353 354
355<script src="https://ajax.aspnetcdn.com/ajax/4.6/1/MicrosoftAjax.js" type="text/javascript"></script>
vendor: 2 bytes, line 355
355
356<script type="text/javascript"> 357//<![CDATA[ 358(window.Sys && Sys._Application && Sys.Observer)||document.write('
358<script type="text/javascript" src="/ScriptResource.axd?d=NJmAwtEo3Ipnlaxl6CMhvl6DVv6mN-f_T9GleXprYSdJat24ORSRp2MgHzKTv7RMDyjpmNp17kiLYmqNfTjOiH9ZwxJObGjuiB_78QzmIFo3Ld4bu-KqIXFacyxI7uv3b6pZYUkoIdYqrIdUk5FZxvfOUcU1&t=ffffffffbec1863d"><\/script>');//]]> 359</script>
359 360
361<script src="https://ajax.aspnetcdn.com/ajax/4.6/1/MicrosoftAjaxWebForms.js" type="text/javascript"></script>
vendor: 2 bytes, line 361
361
362<script type="text/javascript"> 363//<![CDATA[ 364(window.Sys && Sys.WebForms)||document.write('
364<script type="text/javascript" src="/ScriptResource.axd?d=dwY9oWetJoJoVpgL6Zq8OD0U5mUkqMO8C1rKJavZtjUNmyiUn5yn5Q5faVPjJjfK5g40WTCxLeZxWbrkdd5QwCtfh1f5-qqjMpEsCSjGTZF0ygktRjoYsDEZlkO91nN0JfonM1IFsswLnVxGcfk9jAVQXU01&t=ffffffffbec1863d"><\/script>');//]]> 365</script>
365 366 367<div class="aspNetHidden"> 368 369 <input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="CA0B0334" /> 370 <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="J6sNVfsC5YbNE0GLZ8ILK8Z8qBKg689vl7D887ybT+YiqVF3RZ51FytJis5MXSiyGjvjfmMaqsNxKgNRdf6KfAEfNJdyXPs2VOzkcO9uUwluOX2iAVZOhRn57O/jbbLt+LazHxiwrC6D9MUcSrrFPOSZgvo=" /> 371</div> 372 373 374
374<script id="ctl00_commonjs" type="text/javascript" src="//static-uk.ukmsl.net/8.90/scripts/common.js"></script>
374 375
375<script type="text/javascript" src="/pagestylesheet/yourunion/electedofficers/blogs/ddofficer/scripts.js?v=22"></script>
375 376 377 378 379 380 381 382 <div id="ctl00_pnlCatchEnter" class="CatchEnter" onkeypress="javascript:return WebForm_FireDefaultButton(event, 'ctl00_btnCatchEnter')"> 383 384 <input type="submit" name="ctl00$btnCatchEnter" value="" onclick="javascript:return false;" id="ctl00_btnCatchEnter" style="display:none;" /> 385 386</div> 387 388
388<script type="text/javascript"> 389//<![CDATA[ 390Sys.WebForms.PageRequestManager._initialize('ctl00$ctl04', 'aspnetForm', [], [], [], 90, 'ctl00'); 391//]]> 392</script>
392 393 394 395 <div id="skin_novus" class="page_yourunion page_electedofficers page_blogs"> 396 397<!-- start of novus-header-redesign snippet --> 398<header class="slim no-padding"> 399 <div class="container"> 400 <div class="row"> 401 <div class="col-sm-4 hidden-sm-down branding no-padding "> 402 <a href="/"><img src="https://keelesu.com/stylesheet/Brand/navbar-logo.png" alt="KeeleSU Logo" URL='/' width="120" height="85"></a> 403 <a class="toggle-left dsk-navigation-toggle" id="navMenu" href="https://keelesu.com/" aria-expanded="false" aria-label="Open menu"> 404 <i class="nav-menu-icon fa-solid fa-bars" aria-hidden="true"></i> 405 <span class="nav-menu-label">Menu</span> 406 </a> 407 408 </div> 409 <div class="controls col-12 col-md-8 no-padding"> 410 <div id="search-form" class="search-form d-none"> 411 412<div id="search"> 413 <div role="search" id="site-search" class="mslwidget"> 414 <div id="ctl00_search_pnlSearch" onkeypress="javascript:return WebForm_FireDefaultButton(event, 'ctl00_search_btnSubmit')"> 415 416 <label for="ctl00_search_txtSearchStr" id="ctl00_search_lblSearch" class="msl-visually-hidden">Search</label> 417 <input name="ctl00$search$txtSearchStr" type="text" id="ctl00_search_txtSearchStr" class="textbox" placeholder="Search text..." /> 418 419 <input type="submit" name="ctl00$search$btnSubmit" value="Search" id="ctl00_search_btnSubmit" class="button" /> 420 421</div> 422 </div> 423</div> 424</MSL:search> 425 </div> 426 <div id="login-controls"> 427 <a id="ctl00_ctl31_lnkLogin" class="msl-loginbutton msl-loggedout " href="/login/">Log in</a> 428</MSL:Loginbutton> 429 </div> 430 <div class="admin-links desktop-admin-links"> 431 <ul class="nav-icons icons"> 432 <li> 433 434 435<div id="msl-basket-summary"> 436 <a title="Review Basket" class="msl-basket-link" href="/shop/basket/">Basket</a> 437 <div class="msl-basket-summary-items"> 438 439 <div class="msl-basket-itemcount-container"> 440 441 <span class="msl-basket-noitems-text"></span> 442 443 <div class="msl-basket-itemcount">0</div> 444 445 </div> 446 447 448 449 450 451 </div> 452</div></MSL:BasketLink> 453 <div class="hidden-xl-down"> 454 </div> 455 </li> 456 <!-- CHANGED: was class="fa-solid fa-user"aria-hidden="true" - missing space 457 meant the class and aria-hidden attributes ran together. Harmless in 458 most browsers' lenient parsing, but invalid HTML; fixed the spacing. --> 459 <li class="profile"><a href="https://keelesu.com/profile/"><i class="fa-solid fa-user" aria-hidden="true"></i><span class="hidden-xl-down">My Account</span></a></li> 460 <li class="admin-container"> 461 <a href="https://keelesu.com/admin/" class="notext" id="admin-trigger"><i class="fas fa-cog" aria-hidden="true"></i><span class="hidden-xl-down">Admin Panel</span></a> 462 463 </li> 464 <li> <a class="searchbar" role="button" aria-label="Site Search" href="https://keelesu.com/search/"> <i class="fas fa-search fa-inverse" aria-hidden="true"></i><span class="hidden-xl-down">Site Search</span> </a> </li> 465 </ul> 466 <div class="dropdown-menu"> 467 </MSL:ControlPanel> 468 </MSL:Admin> 469 </div> 470 </div> 471 </div> 472 </div> 473 </div> 474 <div class="container mainnav dsk-nav-c"> 475 <div class="row"> 476 <div class="col-xl-12"> 477 </div> 478 <div class="mobile-admin-links"> 479 <div id="login-controls"> 480 <a id="ctl00_ctl37_lnkLogin" class="msl-loginbutton msl-loggedout " href="/login/">Log in</a> 481</MSL:Loginbutton>
481 482 </div> 483</div> 484 <div class="admin-links mobile-admin-links"> 485 <ul class="nav-icons icons"> 486 <li> 487 488 489<div id="msl-basket-summary"> 490 <a title="Review Basket" class="msl-basket-link" href="/shop/basket/">Basket</a> 491 <div class="msl-basket-summary-items"> 492 493 <div class="msl-basket-itemcount-container"> 494 495 <span class="msl-basket-noitems-text"></span> 496 497 <div class="msl-basket-itemcount">0</div> 498 499 </div> 500 501 502 503 504 505 </div> 506</div></MSL:BasketLink> 507 <div class="hidden-xl-down"> 508 </div> 509 </li> 510 <!-- CHANGED: was class="fa-solid fa-user"aria-hidden="true" - missing space 511 meant the class and aria-hidden attributes ran together. Harmless in 512 most browsers' lenient parsing, but invalid HTML; fixed the spacing. --> 513 <li class="profile"><a href="https://keelesu.com/profile/"><i class="fa-solid fa-user" aria-hidden="true"></i><span class="hidden-xl-down">My Account</span></a></li> 514 <li class="admin-container"> 515 <a href="https://keelesu.com/admin/" class="notext" id="admin-trigger"><i class="fas fa-cog" aria-hidden="true"></i><span class="hidden-xl-down">Admin Panel</span></a> 516 517 </li> 518 <li> <a class="searchbar" role="button" aria-label="Site Search" href="https://keelesu.com/search/"> <i class="fas fa-search fa-inverse" aria-hidden="true"></i><span class="hidden-xl-down">Site Search</span> </a> </li> 519 </ul> 520 <div class="dropdown-menu"> 521 </MSL:ControlPanel> 522 </MSL:Admin> 523 </div> 524 </div> 525 <div aria-label="Main Navigation" id="topnav" class="mslwidget dsk-nav"> 526<ul class="msl_navbar"> <li><a href="/whatson/" class="msl-itemhassubnav"><span class="nav-text">Events and Gigs</span></a> 527<ul class="level_1 subnav_nav_whatson_"> <li><a href="https://keelesu.com/gigs/" class="nav_whatson_gigs_">Public Events & Gigs - Open to Everyone</a></li> 528 <li><a href="/whatson/venueinfo/" class="nav_whatson_venueinfo_">Venue Info</a></li> 529</ul> 530</li> 531 <li><a href="/yourunion/" class="current msl-itemhassubnav"><span class="nav-text">Student Voice</span></a> 532<ul class="level_1 subnav_nav_yourunion_"> <li><a href="/yourunion/campaigns/" class="nav_yourunion_campaigns_">Campaigns</a></li> 533 <li><a href="/yourunion/elections/" class="nav_yourunion_elections_">Elections</a></li> 534 <li><a href="/yourunion/ideas/" class="nav_yourunion_ideas_">Have Your Say - Your Ideas</a></li> 535 <li><a href="/yourunion/networks/" class="nav_yourunion_networks_">Student Networks</a></li> 536 <li><a href="/yourunion/uniondelegates/" class="nav_yourunion_uniondelegates_">Union Delegates</a></li> 537 <li><a href="/yourunion/voicereps/" class="nav_yourunion_voicereps_">Voice Reps</a></li> 538 <li><a href="/yourunion/electedofficers/" class="nav_yourunion_electedofficers_ current">Your Current Elected Officers</a></li> 539</ul> 540</li> 541 <li><a href="/advice/" class="msl-itemhassubnav"><span class="nav-text">Advice and Support</span></a> 542<ul class="level_1 subnav_nav_advice_"> <li><a href="/advice/aboutask/" class="nav_advice_aboutask_">About ASK</a></li> 543 <li><a href="/advice/academic/" class="nav_advice_academic_">Academic</a></li> 544 <li><a href="/advice/complaints/" class="nav_advice_complaints_">Complaints</a></li> 545 <li><a href="/advice/discipline/" class="nav_advice_discipline_">Discipline</a></li> 546 <li><a href="/advice/employment/" class="nav_advice_employment_">Employment</a></li> 547 <li><a href="/advice/family/" class="nav_advice_family_">Family</a></li> 548 <li><a href="/advice/graduating/" class="nav_advice_graduating_">Graduate Guide</a></li> 549 <li><a href="/advice/accommodation/" class="nav_advice_accommodation_">Housing</a></li> 550 <li><a href="/advice/international/" class="nav_advice_international_">International Students</a></li> 551 <li><a href="/advice/legal/" class="nav_advice_legal_">Legal</a></li> 552 <li><a href="/advice/finance/" class="nav_advice_finance_">Money</a></li> 553 <li><a href="/advice/wellbeing/" class="nav_advice_wellbeing_">Wellbeing</a></li> 554</ul> 555</li> 556 <li><a href="/activities/" class="msl-itemhassubnav"><span class="nav-text">Get Involved</span></a> 557<ul class="level_1 subnav_nav_activities_"> <li><a href="/activities/clubs-societies/" class="nav_activities_clubs-societies_">Societies</a></li> 558 <li><a href="/activities/au/" class="nav_activities_au_">Sport Clubs</a></li> 559 <li><a href="/activities/volunteering/" class="nav_activities_volunteering_">Volunteering</a></li> 560</ul> 561</li> 562 <li><a href="/shopsandservices/" class="msl-itemhassubnav"><span class="nav-text">Shops and Services</span></a> 563<ul class="level_1 subnav_nav_shopsandservices_"> <li><a href="/shopsandservices/bookinghub/" class="nav_shopsandservices_bookinghub_">Booking Hub</a></li> 564 <li><a href="/shopsandservices/printshop/" class="nav_shopsandservices_printshop_">Keele Print House</a></li> 565 <li><a href="/shopsandservices/vehiclehire/" class="nav_shopsandservices_vehiclehire_">Minibus Bookings</a></li> 566 <li><a href="/shopsandservices/shop/" class="nav_shopsandservices_shop_">Online Shop</a></li> 567 <li><a href="/shopsandservices/student-discounts/" class="nav_shopsandservices_student-discounts_">Student Discounts</a></li> 568 <li><a href="/shopsandservices/venues/" class="nav_shopsandservices_venues_">Venues & Outlets</a></li> 569</ul> 570</li> 571 <li><a href="/yoursu/" class="msl-itemhassubnav"><span class="nav-text">Your Union</span></a> 572<ul class="level_1 subnav_nav_yoursu_"> <li><a href="/yoursu/aboutus/" class="nav_yoursu_aboutus_">About your SU</a></li> 573 <li><a href="/yoursu/contact/" class="nav_yoursu_contact_">Contact Us</a></li> 574 <li><a href="/yoursu/feedback/" class="nav_yoursu_feedback_">Feedback & Complaints</a></li> 575 <li><a href="/yoursu/sustainability/" class="nav_yoursu_sustainability_">Sustainability & Environmental Responsibility</a></li> 576 <li><a href="https://jobs.keelesu.com" class="nav_yoursu_workwithus_">Work with Us</a></li> 577 <li><a href="/yoursu/advertise/" class="nav_yoursu_advertise_">Advertise with us!</a></li> 578</ul> 579</li> 580 <li><a href="/freshers/" class="fresherstab"><span class="nav-text">
580Freshers 2026 🎉</span></a></li> 581</ul> 582 583</div></MSL:Navigation> 584 </div> 585 <!--end 12--> 586 </div> 587 <!--end row--> 588 </div> 589</header> 590<div class="adminmenu"> 591 <div class="dropdown-menu"> 592 </MSL:ControlPanel> 593 </MSL:Admin> 594 <!-- Mobile copy of admin links so MSL links appear inside the nav pop-up --> 595 <div class="admin-links mobile-admin-links"> 596 <ul class="nav-icons icons"> 597 <li> 598 599 600<div id="msl-basket-summary"> 601 <a title="Review Basket" class="msl-basket-link" href="/shop/basket/">Basket</a> 602 <div class="msl-basket-summary-items"> 603 604 <div class="msl-basket-itemcount-container"> 605 606 <span class="msl-basket-noitems-text"></span> 607 608 <div class="msl-basket-itemcount">0</div> 609 610 </div> 611 612 613 614 615 616 </div> 617</div></MSL:BasketLink> 618 </li> 619 <li class="profile"><a href="https://keelesu.com/profile/"><i class="fa-solid fa-user" aria-hidden="true"></i><span>My Account</span></a></li> 620 <li class="admin-container"><a href="https://keelesu.com/admin/" class="notext" id="admin-trigger-mobile"><i class="fas fa-cog" aria-hidden="true"></i><span>Admin Panel</span></a></li> 621 <li><a class="searchbar" role="button" aria-label="Site Search" href="https://keelesu.com/search/"><i class="fas fa-search fa-inverse" aria-hidden="true"></i><span>Site Search</span></a></li> 622 </ul> 623 </div> 624 </div> 625</div> 626<!-- feedback form and button --> 627<!-- 628<iframe id="feedbackForm" width="640px" height="480px" src="https://forms.office.com/Pages/ResponsePage.aspx?id=7vO7K2i2-U2wY1mJRcPUZnsNe7WejyZOv7GKwMl3GbdUMVVKMDNHNVpPSVJIQVVFT0Q5NjZHRjRLSS4u&embed=true" frameborder="0" marginwidth="0" marginheight="0" style="border: none; max-width:100%; max-height:100vh" allowfullscreen=""> </iframe> 629<div id="FBBS" class="row"> 630<div id="fbButton" onclick="formToggle()">How did we do?<br />Give your Feedback here!</div> 631<div id="closeFB" onclick="hideButton()">✖</div> 632</div>-->
633<script defer> 634 document.addEventListener("DOMContentLoaded", function () { 635 let searchVisible = false; 636 const searchForm = document.getElementById("search-form"); 637 const searchBars = document.querySelectorAll("a.searchbar"); 638 const navMenu = document.getElementById("navMenu"); 639 640 function toggleSearch() { 641 if (!searchForm) return; 642 if (!searchVisible) { 643 searchForm.classList.remove("d-none"); 644 searchForm.scrollIntoView({ behavior: "smooth" }); 645 searchVisible = true; 646 } else { 647 searchForm.classList.add("d-none"); 648 searchVisible = false; 649 } 650 } 651 652 function hideSearch() { 653 if (searchVisible && searchForm) { 654 searchForm.classList.add("d-none"); 655 searchVisible = false; 656 } 657 } 658 659 // Keep the mobile search links as real navigation to the search page. 660 // Desktop still opens the inline search form. 661 if (searchBars && searchBars.length) { 662 searchBars.forEach(function (searchBar) { 663 searchBar.addEventListener("click", function (e) { 664 if (window.innerWidth <= 767) return; 665 e.preventDefault(); 666 toggleSearch(); 667 }); 668 }); 669 } 670 671 if (navMenu) { 672 const navMenuIcon = navMenu.querySelector(".nav-menu-icon"); 673 const navMenuLabel = navMenu.querySelector(".nav-menu-label"); 674 const mainNav = document.querySelector(".mainnav"); 675 676 function setNavMenuState(isOpen) { 677 navMenu.classList.toggle("is-open", isOpen); 678 navMenu.setAttribute("aria-expanded", String(isOpen)); 679 navMenu.setAttribute("aria-label", isOpen ? "Close menu" : "Open menu"); 680 681 if (navMenuIcon) { 682 navMenuIcon.classList.remove("fa-bars", "fa-xmark"); 683 navMenuIcon.classList.add(isOpen ? "fa-xmark" : "fa-bars"); 684 } 685 686 if (navMenuLabel) { 687 navMenuLabel.textContent = isOpen ? "Close" : "Menu"; 688 } 689 690 if (mainNav) { 691 mainNav.classList.toggle("open", isOpen); 692 mainNav.style.display = isOpen ? "block" : "none"; 693 } 694 } 695 696 setNavMenuState(false); 697 698 navMenu.addEventListener("click", function (e) { 699 e.preventDefault(); 700 const isOpen = navMenu.classList.toggle("is-open"); 701 setNavMenuState(isOpen); 702 hideSearch(); 703 // also close admin menus when opening nav 704 closeAllAdmin(); 705 }); 706 } 707 708 // Admin menu handling (desktop and mobile) 709 function toggleAdminMenu(trigger) { 710 const container = trigger.closest(".admin-links") || document.querySelector('.adminmenu'); 711 if (!container) return; 712 const dropdown = container.querySelector(".dropdown-menu"); 713 if (!dropdown) return; 714 const isOpen = dropdown.classList.contains("show"); 715 closeAllAdmin(); 716 if (!isOpen) { 717 dropdown.classList.add("show"); 718 } 719 } 720 721 function closeAllAdmin() { 722 document.querySelectorAll(".dropdown-menu").forEach(function (d) { 723 d.classList.remove("show"); 724 }); 725 } 726 727 // Attach handlers to admin triggers (covers #admin-trigger, #admin-trigger-mobile and any .admin-container anchors) 728 const adminTriggers = Array.from(document.querySelectorAll("a#admin-trigger, a#admin-trigger-mobile, .admin-container > a")); 729 adminTriggers.forEach(function (t) { 730 t.addEventListener("click", function (e) { 731 e.preventDefault(); 732 hideSearch(); 733 toggleAdminMenu(t); 734 }); 735 }); 736 737 // Close menus/search when clicking outside 738 document.addEventListener("click", function (e) { 739 const clickedInsideAdmin = e.target.closest(".admin-links, .adminmenu, a#admin-trigger, a#admin-trigger-mobile"); 740 const clickedSearch = e.target.closest("#search-form, a.searchbar"); 741 if (!clickedInsideAdmin) { 742 closeAllAdmin(); 743 } 744 if (!clickedSearch) { 745 hideSearch(); 746 } 747 }); 748 749 // Close on Escape key 750 document.addEventListener("keydown", function (e) { 751 if (e.key === "Escape") { 752 hideSearch(); 753 closeAllAdmin(); 754 } 755 });
756 }); 757</script>
757 758
759<script> 760document.addEventListener("DOMContentLoaded", function () { 761 const triggers = document.querySelectorAll( 762 "#admin-trigger, #admin-trigger-mobile" 763 ); 764 765 const adminMenu = document.querySelector(".adminmenu"); 766 const dropdown = adminMenu?.querySelector(".dropdown-menu"); 767 768 if (!adminMenu || !dropdown) return; 769 770 triggers.forEach(trigger => { 771 trigger.addEventListener("click", function (e) { 772 if (window.innerWidth > 767) return; 773 774 e.preventDefault(); 775 776 const isHidden = 777 adminMenu.style.display === "none" || 778 adminMenu.style.display === ""; 779 780 if (isHidden) { 781 adminMenu.style.display = "block"; 782 dropdown.style.display = "contents"; 783 } else { 784 adminMenu.style.display = "none"; 785 dropdown.style.display = "none"; 786 } 787 }); 788 }); 789}); 790</script>
790 791<!-- end of novus-header-redesign snippet --> 792 793<div class="container-fluid" role="main"> 794 <div class="row"> 795 <div class="col-xl-12 no-padding"> 796 <section class="main"> 797 <div class="container no-padding"> 798 <div class="wht-container"> 799 <div class="row"> 800 <div class="col-xl-12"> 801 <div id="title-section"> 802 <div class="mslwidget"> 803Officer Blogs 804</div> 805 </div> 806 <div class="row breadcrumbs"><div class="span12"> 807<nav id="breadcrumb" class="msl-breadcrumb" aria-label="Breadcrumb"> 808 809 <ul class="breadcrumb"><li><a href="/yourunion/">Student Voice</a><span class="msl-breadcrumb-separator" aria-hidden="true">></span></li><li><a href="/yourunion/electedofficers/">Your Current Elected Officers</a><span class="msl-breadcrumb-separator" aria-hidden="true">></span></li><li class="current-page" aria-current="page"><span>Development &amp; Democracy Officer Blog</span></li></ul> 810 811</nav></div></div> 812 <div class="row"> 813 <div class="col-xl-12"> 814 815 <p><a href="https://keelesu.com/edit/page/widget/?page_name=/yourunion/electedofficers/blogs/&control_name=blog" title="Edit widget settings"><div class="mslwidget"> 816 817<div class="msl_blog msl_blog_posts"> 818 819 820 <h1><a href="/yourunion/electedofficers/blogs/DDOfficer/">Development & Democracy Officer Blog</a></h1> 821 822 <div class="msl_blog_tools"> 823 824 <ul> 825 826 827 <li id="ctl00_blog_liRss"><a id="ctl00_blog_lnkRSS" class="msl-icon msl_rss" href="/blogs/rss/DDOfficer/">RSS</a></li> 828 </ul> 829 830 </div> 831 832 <div class="msl_blog_image"> 833 834 <img src="/asset/Blog/1/Benjamin-Buckley-Wain-Keele-SU-Officer(1)%20(1).jpg?thumbnail_width=200&thumbnail_height=200" alt="" /> 835 836 </div> 837 838 <div class="msl_blog_description"> 839 <p>Hello!! I am Benjamin, and I am your elected Development and Democracy Officer. </p> 840 841<p>I started my Studies here at Keele in 2024. I am a second-year Law student with my full degree title hopefully being; Law with Professional Legal Practice with a Placement Year with an Understanding of British Sign Language... A mouthful I know. I have been a student ambassador for my academic area and a Voice Rep for my Course. I also volunteered in Keele University's Legal Clinic as a student legal advisor. </p> 842 843<p>Outside of my academics, I used to be a Dancer. I danced in a variety of styles, with a focus in competing across the country. In college I had stopped dancing due to work, but i was still interested in performing arts, here I volunteered in theatres and partook in my college’s reproduction of legally blonde the musical as a Technical Director and Lighting Designer. Quite possibly the best experience I have had in theatre. My interests are all over the place, every week I fixate on a niche subject and find myself learning everything about it. </p> 844 845<p>My favourite part about campus is Keele woods. There is no place in Keele woods that I do not know. And my favourite fact is that co-op memberships cost £1 which goes directly back to the student union and gets you member prices on products!!! </p> 846 847<p>You can talk to me about anything, however my role’s main focus are: </p> 848 849<ul role="list"> 850 <li aria-setsize="-1" data-aria-level="1" data-aria-posinset="1" data-font="Symbol" data-leveltext="?" data-list-defn-props="{"335552541":1,"335559685":720,"335559991":360,"469769226":"Symbol","469769242":[8226],"469777803":"left","469777804":"?","469777815":"hybridMultilevel"}" data-listid="1" role="listitem"> 851 <p>Campus Issues and getting to campus </p> 852 </li> 853</ul> 854 855<ul role="list"> 856 <li aria-setsize="-1" data-aria-level="1" data-aria-posinset="2" data-font="Symbol" data-leveltext="?" data-list-defn-props="{"335552541":1,"335559685":720,"335559991":360,"469769226":"Symbol","469769242":[8226],"469777803":"left","469777804":"?","469777815":"hybridMultilevel"}" data-listid="1" role="listitem"> 857 <p>Campaign Networks </p> 858 </li> 859</ul> 860 861<ul role="list"> 862 <li aria-setsize="-1" data-aria-level="1" data-aria-posinset="3" data-font="Symbol" data-leveltext="?" data-list-defn-props="{"335552541":1,"335559685":720,"335559991":360,"469769226":"Symbol","469769242":[8226],"469777803":"left","469777804":"?","469777815":"hybridMultilevel"}" data-listid="1" role="listitem"> 863 <p>Any suggestion/development regarding the SU and/or the University </p> 864 </li> 865</ul> 866 867<ul role="list"> 868 <li aria-setsize="-1" data-aria-level="1" data-aria-posinset="4" data-font="Symbol" data-leveltext="?" data-list-defn-props="{"335552541":1,"335559685":720,"335559991":360,"469769226":"Symbol","469769242":[8226],"469777803":"left","469777804":"?","469777815":"hybridMultilevel"}" data-listid="1" role="listitem"> 869 <p>Democracy, Voting, Elections and why we do what we do </p> 870 </li> 871</ul> 872 873<ul role="list"> 874 <li aria-setsize="-1" data-aria-level="1" data-aria-posinset="5" data-font="Symbol" data-leveltext="?" data-list-defn-props="{"335552541":1,"335559685":720,"335559991":360,"469769226":"Symbol","469769242":[8226],"469777803":"left","469777804":"?","469777815":"hybridMultilevel"}" data-listid="1" role="listitem"> 875 <p>Sustainability </p> 876 </li> 877</ul> 878 879<p>I love helping people, cliche I know, but everything I do is in the interest in all students. </p> 880 881<p>You can contact me at <a href="mailto:[email protected]" rel="noreferrer noopener" target="_blank">[email protected]</a> or you can find me in the chatterbox! </p> 882 883<p>I look forward to working with you. Have a lovely day!!! </p> 884 </div> 885 886
887 <div class="blog_day">Mon 02 Dec 2024</div> 888 <div class="mslwidget msl_blog_post"> 889 890 <h2 class="msl_blog_post_title"><a id="ctl00_blog_rptPosts_ctl00_BlogPost1_lnkSubject" href="/yourunion/electedofficers/blogs/DDOfficer/2024/12/02/December-AGM-2024/">December AGM 2024</a></h2> 891 <div class="msl_blog_post_date">Posted on Mon 02 Dec 2024 at 15:31 </div> 892 893 <div class="msl_blog_post_body"></div> 894 <div id="ctl00_blog_rptPosts_ctl00_BlogPost1_divCommentCount" class="msl_blog_post_comments"> 895 896 </div> 897 898 899 900</div> 901 902 903 904 905 906 907</div> 908 909</div></a></p> 910 911 912 913 914 915 </div> 916 </div> 917 </div> 918 </div> 919 </section> 920 </div> 921 </div> 922</div> 923 924 925<!-- start of novus-footer snippet --> 926<footer> 927 <div class="container"> 928 <!-- Advertisement Section --> 929 <!-- NOTE: this MSL:Advert control is the "Perks" banner I found on the live homepage 930 (asset/Advert/34/1.png) - it's the one image on the whole page NOT going through 931 MSL's resize pipeline (?thumbnail_width=...&resize_type=CropToFit) that every news/ 932 event/officer image uses. That's set by whatever image is uploaded in Adverts Admin 933 for this advert slot, not by this template - re-uploading it there through the same 934 resize pipeline is the actual fix, this file has no control over it. --> 935 <div class="row" style="padding-top: 15px;"> 936 <div class="col-12" style="text-align: center;"> 937 <div class="mslwidget"> 938<a href="/advertclick/34/" target="_blank"><img src="/asset/Advert/34/1.png" alt="International Student Identity Card. Student ID &a" /></a> 939</div> 940 </div> 941 </div> 942 943 <!-- Footer Links --> 944 <div class="row f-links"> 945 <!-- Useful Links --> 946 <div class="col-sm-4 col-xs-12 f-links-col"> 947 <div class="f-links-cont"> 948 <div class="f-head">Useful Links</div> 949 <div class="mslwidget footer-nav"> 950<ul class="msl_navbar"> <li><a href="/yoursu/workwithus/" target="_blank"><span class="nav-text">Jobs at Keele SU</span></a></li> 951 <li><a href="/yoursu/advertise/"><span class="nav-text">Advertise</span></a></li> 952 <li><a href="/yoursu/feedback/"><span class="nav-text">Make a Complaint</span></a></li> 953 <li><a href="/tac/"><span class="nav-text">Terms & Conditions</span></a></li> 954 <li><a href="/yoursu/aboutus/governance/"><span class="nav-text">Our Policies</span></a></li> 955 <li><a href="/accessibility/"><span class="nav-text">Accessibility</span></a></li> 956 <li><a href="/cookies/"><span class="nav-text">Cookies Policy</span></a></li> 957 <li><a href="/it/"><span class="nav-text">IT Support Links</span></a></li> 958 <li><a href="/accessable/" target="_blank"><span class="nav-text">Accessibility at Keele SU</span></a></li> 959 <li><a href="/system-status/"><span class="nav-text">System Status</span></a></li> 960 <li><a href="/activities/clubs-societies/committeeresources/"><span class="nav-text">Committee Resources</span></a></li> 961 <li><a href="/lostproperty/"><span class="nav-text">Lost Property</span></a></li> 962</ul> 963 964</div> 965 </div> 966 </div> 967 968 <!-- Vision, Mission, and Values --> 969 <div class="col-sm-5 col-xs-12 f-links-col"> 970 <div class="f-links-cont"> 971 <div class="f-head">Our Vision</div> 972 <p>Empower every student to be who they want to be and make a difference to the world.</p> 973 974 <div class="f-head">Our Mission</div> 975 <p>Through the delivery of our quality support, activity, representation, entertainment, and value-for-money services, we will ensure that our students feel part of a community which is inclusive, where they can grow in confidence and simply be students.</p> 976 977 <div class="f-head">Our Values</div> 978 <p>We are Global Citizens, We Challenge & Collaborate, We Unlock Potential.</p> 979 </div> 980 </div> 981 982 <!-- Contact Information and Social Media --> 983 <div class="col-sm-3 col-xs-12 f-links-col"> 984 <div class="f-links-cont"> 985 <div class="f-head">Contact Us</div> 986 <p> 987 Keele University Students' Union<br> 988 Keele University<br> 989 Newcastle-Under-Lyme<br> 990 Staffordshire<br> 991 ST5 5BJ<br> 992 United Kingdom 993 </p> 994 <p><i class="fas fa-phone"></i> 01782 733700</p> 995 <p><i class="fas fa-envelope"></i> <a href="mailto:[email protected]">[email protected]</a></p> 996 <p><i class="fas fa-map-marker-alt"></i> <a href="https://maps.app.goo.gl/U8Mw4fiQVAKoC1iNA/">Maps & Directions</a></p> 997 </div> 998 999 <!-- Social Media Links --> 1000 <br> 1001 <div class="f-head">Follow Us!</div> 1002 <div id="socialsFooter" class="clearfix"> 1003 <ul> 1004 <!-- Facebook --> 1005 <li> 1006 <a target="_blank" href="https://www.facebook.com/keelestudentsunion/" aria-label="Facebook" tabindex="0" role="button" aria-pressed="false"> 1007 <i class="fa-brands fa-facebook"></i>
1008 <span class="social-description">Facebook</span> 1009 </a> 1010 </li> 1011 <!-- Instagram --> 1012 <li> 1013 <a target="_blank" href="https://www.instagram.com/keelesu/" aria-label="Instagram" tabindex="0" role="button" aria-pressed="false"> 1014 <i class="fa-brands fa-instagram"></i> 1015 <span class="social-description">Instagram</span> 1016 </a> 1017 </li> 1018 <!-- YouTube --> 1019 <li> 1020 <a target="_blank" href="https://www.youtube.com/user/keelesu/" aria-label="YouTube" tabindex="0" role="button" aria-pressed="false"> 1021 <i class="fa-brands fa-youtube"></i> 1022 <span class="social-description">YouTube</span> 1023 </a> 1024 </li> 1025 <!-- LinkedIn --> 1026 <li> 1027 <a target="_blank" href="https://www.linkedin.com/company/keelesu/" aria-label="LinkedIn" tabindex="0" role="button" aria-pressed="false"> 1028 <i class="fa-brands fa-linkedin"></i> 1029 <span class="social-description">LinkedIn</span> 1030 </a> 1031 </li> 1032 <!-- TikTok --> 1033 <li> 1034 <a target="_blank" href="https://www.tiktok.com/@keele_su/" aria-label="TikTok" tabindex="0" role="button" aria-pressed="false"> 1035 <i class="fa-brands fa-tiktok"></i> 1036 <span class="social-description">TikTok</span> 1037 </a> 1038 </li> 1039 <!-- LinkTree --> 1040 <li> 1041 <a target="_blank" href="https://linktr.ee/keelesu/" aria-label="LinkTree" tabindex="0" role="button" aria-pressed="false"> 1042 <!-- CHANGED: added loading="lazy" - this was the one footer image without it --> 1043 <img width="32" height="32" src="https://keelesu.com/stylesheet/Brand/linktree-icon.png" alt="LinkTree" loading="lazy"/> 1044 <span class="social-description">LinkTree</span> 1045 </a> 1046 </li> 1047 </ul> 1048 </div> 1049 </div> 1050 </div> 1051 1052 <!-- Download App Section --> 1053 <div class="row"> 1054 <div class="col-12"> 1055 <div class="f-head text-center">Download Our App</div> 1056 <p class="text-center"> 1057 <a href="https://apps.apple.com/gb/app/studentlink/id1579307063/" rel="noopener noreferrer" target="_blank" aria-label="Keele SU App on the Appstore"> 1058 <i class="fa-brands fa-apple fa-2xl"></i> 1059 <span class="social-description">Keele SU iOS App</span> 1060 </a> 1061 <a href="https://play.google.com/store/apps/details?id=com.ukmsl.studentlinkplus.keele" rel="noopener noreferrer" target="_blank" aria-label="Keele SU App on the Playstore"> 1062 <i class="fa-brands fa-android fa-2xl"></i> 1063 <span class="social-description">Keele SU Android App</span> 1064 </a> 1065 </p> 1066 </div> 1067 </div> 1068 1069 <!-- Footer Credits --> 1070 <div class="row"> 1071 <div class="col-12"> 1072 <p class="msl-footer">Keele SU is a Charitable Incorporated Organisation No.1173328</p> 1073 <p class="msl-footer"> 1074 <a href="https://www.ukmsl.com/" id="poweredmsl">Powered by MSL</a> 1075 </p> 1076 <p class="msl-footer"> 1077 Last Updated: Thu, 03 Jul 2025 13:34:53 GMT 1078 </p> 1079 </div> 1080 </div> 1081 </div> 1082</footer> 1083 1084<!-- Popup for Voting --> 1085<!--class="hide"--> 1086<div id="popup"> 1087<MSL:VoterPopup 1088 ContainerTag="" 1089 ContainerID="" 1090 ContainerCssClass="" 1091 SuppressContainer="" 1092 ElectionId="1293" 1093 VotingPeriodNotStartedText="" 1094 VotingNotStartedText="<img src='https://keelesu.com/stylesheet/novus/electionsheaderfs.jpg' alt='Elections Banner' width='100%'> 1095 <br>You haven't voted in the current Elections 😭 1096 <br><a href='https://keelesu.com/elections/vote/1293/'>Vote Now!</a> 1097 <br>" 1098 VotingInProgressText="" 1099 VotingCompleteText="" 1100 VotingClosedText="" 1101 HideButtonText="Close" 1102/> 1103 1104 <div id="fbmllike"> 1105 <div style="clear:both"></div> 1106 </div> 1107</div> 1108 1109<!-- Microsoft Clarity Analytics -->
1110<script> 1111 (function(c, l, a, r, i, t, y) { 1112 c[a] = c[a] || function() { 1113 (c[a].q = c[a].q || []).push(arguments); 1114 }; 1115 t = l.createElement(r); 1116 t.async = true; 1117 t.src = "https://www.clarity.ms/tag/" + i; 1118 y = l.getElementsByTagName(r)[0]; 1119 y.parentNode.insertBefore(t, y); 1120 })(window, document, "clarity", "script", "lqptr9hbv4"); 1121</script>
1121 1122 1123<!-- Chatbot Styles --> 1124<!-- NOTE: no change needed here. Earlier I flagged this chatbot as a likely big contributor 1125 to page weight, going only off the fact that it existed. Having now seen the actual 1126 implementation: the iframe ships with loading="lazy" AND starts display:none, and the 1127 script below only flips it to display:block after a 60s timer or a manual click. A 1128 hidden, lazy iframe like this is very unlikely to be fetched during a single automated 1129 page-load measurement (the kind carbon calculators run), so this was probably already a 1130 non-issue - correcting my earlier read rather than "fixing" something that isn't broken. --> 1131<style> 1132 :root { 1133 --chat-width: 320px; 1134 --chat-height: 600px; 1135 --chat-margin: 20px; 1136 --chat-button-size: 60px; 1137 } 1138 1139 #chat-popup { 1140 position: fixed; 1141 bottom: var(--chat-margin); 1142 right: calc(var(--chat-margin) * 2); 1143 width: var(--chat-width); 1144 height: var(--chat-height); 1145 display: none; 1146 border: none; 1147 z-index: 1000; 1148 } 1149 1150 #chat-button { 1151 position: fixed; 1152 bottom: var(--chat-margin); 1153 right: var(--chat-margin); 1154 width: var(--chat-button-size); 1155 height: var(--chat-button-size); 1156 border: none; 1157 border-radius: 50%; 1158 background-color: var(--secondary-color, #65b22e); 1159 color: #fff; 1160 cursor: pointer; 1161 z-index: 1001; 1162 font-size: 1rem; 1163 } 1164 1165 .popup-container { 1166 display: none; 1167 position: fixed; 1168 top: 50%; 1169 left: 50%; 1170 transform: translate(-50%, -50%); 1171 background-color: #fff3cd; 1172 color: #856404; 1173 border: 1px solid #ffeeba; 1174 padding: 30px; 1175 border-radius: 10px; 1176 box-shadow: 0 6px 12px rgba(0,0,0,0.15); 1177 max-width: 500px; 1178 width: 90%; 1179 font-family: Arial, sans-serif; 1180 z-index: 1000; 1181 text-align: center; 1182 } 1183 1184 .popup-container h2 { 1185 margin-top: 0; 1186 } 1187 1188 .app-links a { 1189 color: #856404; 1190 text-decoration: underline; 1191 font-weight: bold; 1192 } 1193 1194 .popup-close { 1195 background-color: #856404; 1196 color: white; 1197 border: none; 1198 padding: 10px 16px; 1199 border-radius: 5px; 1200 cursor: pointer; 1201 margin-top: 15px; 1202 font-size: 1em; 1203 } 1204 1205 .popup-close:hover { 1206 background-color: #6c4f03; 1207 } 1208</style> 1209 1210<!-- Chatbot Iframe --> 1211<iframe 1212 id="chat-popup" 1213 src="https://www.chatbase.co/chatbot-iframe/KUL--w-zDPlx88ZgZl8Xh" 1214 title="Live Chat" 1215 loading="lazy" 1216></iframe> 1217 1218<!-- Chat Toggle Button --> 1219<button id="chat-button" type="button" aria-label="Open Chat">Chat</button> 1220 1221<!-- Chat Toggle Script (Vanilla JS) -->
1222<script> 1223 (function() { 1224 'use strict'; 1225 1226 function initChatToggle() { 1227 const chatButton = document.getElementById('chat-button'); 1228 const chatPopup = document.getElementById('chat-popup'); 1229 1230 if (!chatButton || !chatPopup) return; 1231 1232 chatButton.addEventListener('click', function(e) { 1233 e.preventDefault(); 1234 const isVisible = chatPopup.style.display === 'block'; 1235 chatPopup.style.display = isVisible ? 'none' : 'block'; 1236 }); 1237 } 1238 1239 // Initialize when DOM is ready 1240 if (document.readyState === 'loading') { 1241 document.addEventListener('DOMContentLoaded', initChatToggle); 1242 } else { 1243 initChatToggle(); 1244 } 1245 })(); 1246</script>
1246 1247
1248<script> 1249window.addEventListener("load", () => { 1250 const path = window.location.pathname; 1251 const isBackend = path.includes("/admin/") || path.includes("/edit/"); 1252 1253 const STORAGE_KEY = "chatPopupLastShown"; 1254 const COOLDOWN_DAYS = 3; 1255 1256 // Check if popup should show 1257 function canShowPopup() { 1258 const lastShown = localStorage.getItem(STORAGE_KEY); 1259 if (!lastShown) return true; 1260 1261 const now = Date.now(); 1262 const diff = now - parseInt(lastShown, 10); 1263 const cooldown = COOLDOWN_DAYS * 24 * 60 * 60 * 1000; 1264 1265 return diff > cooldown; 1266 } 1267 1268 // Save timestamp 1269 function markShown() { 1270 localStorage.setItem(STORAGE_KEY, Date.now().toString()); 1271 } 1272 1273 // If admin/edit page â stop 1274 if (isBackend) { 1275 const iframe = document.getElementById("chat-popup"); 1276 if (iframe) iframe.style.display = "none"; 1277 return; 1278 } 1279 1280 // ð´ If within 3 days â don't show 1281 if (!canShowPopup()) return; 1282 1283 // â Show popup after 60s 1284 setTimeout(() => { 1285 const interval = setInterval(() => { 1286 const iframe = document.getElementById("chat-popup"); 1287 if (iframe) { 1288 iframe.style.display = "block"; 1289 iframe.click(); 1290 1291 // Save that it has been shown 1292 markShown(); 1293 1294 clearInterval(interval); 1295 } 1296 }, 300); 1297 }, 60000); 1298}); 1299</script>
1299 1300 1301<!-- Changes Colour/Text on Chatbot Button --> 1302
1303<script> 1304document.addEventListener("DOMContentLoaded", function () { 1305 const chatPopup = document.getElementById("chat-popup"); 1306 const chatButton = document.getElementById("chat-button"); 1307 1308 if (!chatPopup || !chatButton) return; 1309 1310 // Store original button styles/text so we can restore them 1311 const originalText = chatButton.textContent; 1312 const originalBg = getComputedStyle(chatButton).backgroundColor; 1313 const originalColor = getComputedStyle(chatButton).color; 1314 1315 function updateChatButton() { 1316 const isVisible = getComputedStyle(chatPopup).display === "block"; 1317 1318 if (isVisible) { 1319 chatButton.textContent = "Close"; 1320 chatButton.style.backgroundColor = "red"; 1321 chatButton.style.color = "black"; 1322 chatButton.setAttribute("aria-label", "Close Chat"); 1323 } else { 1324 chatButton.textContent = originalText || "Chat"; 1325 chatButton.style.backgroundColor = originalBg; 1326 chatButton.style.color = originalColor; 1327 chatButton.setAttribute("aria-label", "Open Chat"); 1328 } 1329 } 1330 1331 // Watch for changes to the iframe's style attribute 1332 const observer = new MutationObserver(updateChatButton); 1333 observer.observe(chatPopup, { 1334 attributes: true, 1335 attributeFilter: ["style", "class"] 1336 }); 1337 1338 // Run once on load 1339 updateChatButton(); 1340}); 1341 1342</script>
1342 1343
1344<script> 1345// Removes empty location box from events 1346document.querySelectorAll('.location').forEach(location => { 1347 const text = location.textContent.trim(); 1348 1349 if (!text) { 1350 location.style.display = 'none'; 1351 } 1352}); 1353</script>
1353 1354 1355<!-- Cookie Popup --> 1356<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cookieconsent/3.1.1/cookieconsent.min.css" integrity="sha512-LQ97camar/lOliT/MqjcQs5kWgy6Qz/cCRzzRzUCfv0fotsCTC9ZHXaPQmJV8Xu/PVALfJZ7BDezl5lW3/qBxg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
1357<script defer src="https://cdnjs.cloudflare.com/ajax/libs/cookieconsent/3.1.1/cookieconsent.min.js" integrity="sha512-yXXqOFjdjHNH1GND+1EO0jbvvebABpzGKD66djnUfiKlYME5HGMUJHoCaeE4D5PTG2YsSJf6dwqyUUvQvS0vaA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
vendor: 2 bytes, line 1357
1357
1358<script> 1359 (function() { 1360 'use strict'; 1361 1362 function initCookieConsent() { 1363 if (typeof window.cookieconsent === 'undefined') return; 1364 1365 window.cookieconsent.initialise({ 1366 palette: { 1367 popup: { 1368 background: "#575952" 1369 }, 1370 button: { 1371 background: "#22004d" 1372 } 1373 }, 1374 theme: "edgeless", 1375 content: { 1376 message: "This site uses cookies", 1377 dismiss: "Dismiss", 1378 link: "More info", 1379 href: "https://keelesu.com/cookies/" 1380 }, 1381 position: "bottom-left" 1382 }); 1383 } 1384 1385 window.addEventListener("load", initCookieConsent); 1386 })(); 1387</script>
1387 1388 1389<!-- Accessibility Widget -->
1390<script> 1391 window.interdeal = { 1392 sitekey: "6771fa1a11958d581b731f95e0c44d43", 1393 Position: "right", 1394 domains: { 1395 js: "https://cdn.equalweb.com/", 1396 acc: "https://access.equalweb.com/" 1397 }, 1398 showTooltip: true, 1399 btnStyle: { 1400 vPosition: ["92%", "20%"], 1401 scale: ["0.5", "0.5"], 1402 color: { 1403 main: "#2b0e48", 1404 second: "#ffffff" 1405 }, 1406 icon: { 1407 type: 11, 1408 shape: "rounded" 1409 } 1410 } 1411 }; 1412 1413 (function(doc, head, body) { 1414 'use strict'; 1415 1416 const coreCall = doc.createElement('script'); 1417 coreCall.src = window.interdeal.domains.js + 'core/4.6.14/accessibility.js'; 1418 coreCall.defer = true; 1419 coreCall.integrity = 'sha512-DeZBWLOEHK1CkmU3vQTNSXeENfZmhTHFmn7zzhvZCPERahpp/DTouMc1ZHOu+RAo1FTKnuKEf1NQyB76z8mU1Q=='; 1420 coreCall.crossOrigin = 'anonymous'; 1421 coreCall.setAttribute('data-cfasync', true); 1422 1423 (body || head).appendChild(coreCall); 1424 })(document, document.head, document.body); 1425</script>
1425 1426 1427<!-- System Issue Popup (Vanilla JS) -->
1428<script> 1429 (function() { 1430 'use strict'; 1431 1432 function initPopup() { 1433 const popup = document.getElementById('system-issue-popup'); 1434 const closeBtn = document.querySelector('.popup-close'); 1435 1436 if (!popup) return; 1437 1438 // Check if popup was already dismissed in this session 1439 const dismissed = sessionStorage.getItem('popupDismissed'); 1440 1441 if (!dismissed) { 1442 setTimeout(function() { 1443 popup.style.display = 'block'; 1444 }, 1000); 1445 } 1446 1447 // Close button handler 1448 if (closeBtn) { 1449 closeBtn.addEventListener('click', function() { 1450 popup.style.display = 'none'; 1451 sessionStorage.setItem('popupDismissed', 'true'); 1452 }); 1453 } 1454 } 1455 1456 // Initialize when DOM is ready 1457 if (document.readyState === 'loading') { 1458 document.addEventListener('DOMContentLoaded', initPopup); 1459 } else { 1460 initPopup(); 1461 } 1462 })(); 1463</script>
1463 1464<!-- end of novus-footer snippet --> 1465
1466<script type="text/javascript"> 1467 $(document).ready(function () { 1468 1469<!-- start of novus-scripts snippet --> 1470// Enhanced Site Script - Optimized Vanilla JavaScript 1471// Performance optimized with caching and efficient DOM operations 1472// 1473// Fixes applied in this version: 1474// 1. Moved the "accessibility cleanup / admin controls / basket buttons / 1475// event location / referendum" block OUT of the newsItems.forEach loop. 1476// It previously only ran once per news-scroller item (and not at all 1477// when there were no news items) instead of once per page load. 1478// 2. Removed no-op `return false` inside a native addEventListener handler 1479// (that only works with jQuery); replaced with explicit preventDefault. 1480// 3. Added aria-expanded updates to the nav toggle and admin trigger buttons 1481// so assistive tech can announce expanded/collapsed state. 1482// 4. Added aria-hidden="true" to the accordion chevron icon, and aria-controls 1483// linking the accordion title to its content panel, for consistency with 1484// the slider's icons and proper ARIA relationships. 1485// 5. Guarded the popup auto-focus so it only steals focus when the popup is 1486// actually visible, instead of unconditionally on every page load. 1487// 6. Removed dead/commented-out code (legacy UA sniffing comment left as-is 1488// where still functionally relevant, but the disabled bxSlider/doubleTap 1489// leftovers were removed). 1490 1491(function() { 1492 'use strict'; 1493 1494 const $ = (selector, context = document) => context.querySelector(selector); 1495 1496 const $$ = (selector, context = document) => { 1497 return Array.from(context.querySelectorAll(selector)); 1498 }; 1499 1500 // Batch DOM operations 1501 const domBatch = { 1502 operations: [], 1503 add(fn) { 1504 this.operations.push(fn); 1505 }, 1506 execute() { 1507 requestAnimationFrame(() => { 1508 this.operations.forEach(fn => fn()); 1509 this.operations = []; 1510 }); 1511 } 1512 }; 1513 1514 // Double Tap To Go (optimized with event delegation where possible) 1515 function doubleTapToGo(selector) { 1516 if (!('ontouchstart' in window) && 1517 !navigator.msMaxTouchPoints && 1518 !navigator.userAgent.toLowerCase().match(/windows phone os 7/i)) { 1519 return; 1520 } 1521 1522 let lastTapped = null; 1523 const container = document.getElementById('topnav'); 1524 if (!container) return; 1525 1526 container.addEventListener('click', function(e) { 1527 const element = e.target.closest(selector); 1528 if (element && element !== lastTapped) { 1529 e.preventDefault(); 1530 lastTapped = element; 1531 } 1532 }); 1533 1534 document.addEventListener('click', function(e) { 1535 if (lastTapped && !lastTapped.contains(e.target)) { 1536 lastTapped = null; 1537 } 1538 }); 1539 1540 document.addEventListener('touchstart', function(e) { 1541 if (lastTapped && !lastTapped.contains(e.target)) { 1542 lastTapped = null; 1543 } 1544 }, { passive: true }); 1545 } 1546 1547 // Relative time formatting (cached results) 1548 const timeCache = new Map(); 1549 function relTime(dateString) { 1550 if (timeCache.has(dateString)) { 1551 return timeCache.get(dateString); 1552 } 1553 1554 const date = new Date(dateString); 1555 const now = new Date(); 1556 const diff = now - date; 1557 const seconds = Math.floor(diff / 1000); 1558 const minutes = Math.floor(seconds / 60); 1559 const hours = Math.floor(minutes / 60); 1560 const days = Math.floor(hours / 24); 1561 1562 let result; 1563 if (days > 7) result = date.toLocaleDateString(); 1564 else if (days > 0) result = `${days} day${days > 1 ? 's' : ''} ago`; 1565 else if (hours > 0) result = `${hours} hour${hours > 1 ? 's' : ''} ago`; 1566 else if (minutes > 0) result = `${minutes} minute${minutes > 1 ? 's' : ''} ago`; 1567 else result = 'Just now'; 1568 1569 timeCache.set(dateString, result); 1570 return result; 1571 } 1572 1573 // Main initialization (optimized with fewer DOM queries) 1574 function init() { 1575 const body = document.body; 1576 const mainNav = document.querySelector('.mainnav'); 1577 1578 // Remove admin elements for logged out users (single pass) 1579 if (document.querySelector('.msl-loggedout')) { 1580 const adminSelectors = '.admin-links ul li.admin-container, #admin-trigger, .admin-links ul li.profile';
1581 $$(adminSelectors).forEach(el => el.remove()); 1582 } 1583 1584 // Event delegation for navigation toggle 1585 body.addEventListener('click', function(e) { 1586 const toggle = e.target.closest('a[class*="toggle-left"]'); 1587 if (toggle) { 1588 if (!mainNav.classList.contains('show-nav')) { 1589 body.classList.remove('show-admin'); 1590 } 1591 mainNav.classList.toggle('show-nav'); 1592 toggle.setAttribute('aria-expanded', mainNav.classList.contains('show-nav')); 1593 return; 1594 } 1595 1596 // Admin trigger 1597 const adminTrigger = e.target.closest('#admin-trigger'); 1598 if (adminTrigger) { 1599 e.preventDefault(); 1600 if (!body.classList.contains('show-admin')) { 1601 mainNav.classList.remove('show-nav'); 1602 } 1603 body.classList.toggle('show-admin'); 1604 adminTrigger.setAttribute('aria-expanded', body.classList.contains('show-admin')); 1605 return; 1606 } 1607 1608 // Shop tile clicks 1609 const shopTile = e.target.closest('.shop-cat li'); 1610 if (shopTile) { 1611 const link = shopTile.querySelector('a'); 1612 if (link) { 1613 e.preventDefault(); 1614 window.location = link.href; 1615 return; 1616 } 1617 } 1618 1619 // Accordion clicks 1620 const accordionTitle = e.target.closest('.accordion-title'); 1621 if (accordionTitle) { 1622 handleAccordion(accordionTitle); 1623 } 1624 1625 // Submenu clicks 1626 const submenuLink = e.target.closest('#topnav ul.msl_navbar li:has(ul) a'); 1627 if (submenuLink) { 1628 const parent = submenuLink.parentElement; 1629 const siblings = Array.from(parent.parentElement.children).filter(el => el !== parent); 1630 1631 siblings.forEach(sib => { 1632 const openUl = sib.querySelector('ul.open'); 1633 if (openUl) openUl.classList.remove('open'); 1634 }); 1635 1636 const firstUl = parent.querySelector('ul'); 1637 if (firstUl) firstUl.classList.toggle('open'); 1638 } 1639 }); 1640 1641 // Enable double-tap navigation 1642 doubleTapToGo('#topnav ul.msl_navbar > li:has(ul) > a'); 1643 1644 // Batch DOM manipulations 1645 const exploreTiles = $$('.explore-tiles ul li'); 1646 const shopTiles = $$('.shop-cat li'); 1647 const tables = $$('#main table:not([class])'); 1648 const newsItems = $$('#scroller .news_item'); 1649 const mslNewsItems = $$('.msl-news .msl-item'); 1650 const bannerItems = $$('#banner .msl-news .msl-item'); 1651 1652 // Process explore tiles 1653 exploreTiles.forEach(tile => { 1654 const link = tile.querySelector('a'); 1655 if (!link) return; 1656 1657 if (!link.querySelector('.msl-imagenav-image')) { 1658 const img = document.createElement('img'); 1659 img.src = 'https://keelesu.com/stylesheet/Brand/image_nav_default.png'; 1660 img.alt = 'KeeleSU Branded Image Placeholder'; 1661 img.width = 570; 1662 img.height = 285; 1663 img.className = 'msl-imagenav-image'; 1664 link.appendChild(img); 1665 } 1666 1667 const desc = tile.querySelectorAll('.msl-imagenav-description, .msl-imagenav-title'); 1668 if (desc.length > 0) { 1669 const wrapper = document.createElement('div'); 1670 wrapper.className = 'explore_description'; 1671 desc[0].parentNode.insertBefore(wrapper, desc[0]); 1672 desc.forEach(el => wrapper.appendChild(el)); 1673 1674 const title = wrapper.querySelector('.msl-imagenav-title'); 1675 const description = wrapper.querySelector('.msl-imagenav-description'); 1676 if (title && description) { 1677 description.parentNode.insertBefore(title, description.nextSibling); 1678 } 1679 1680 const image = tile.querySelector('.msl-imagenav-image'); 1681 if (image && wrapper) { 1682 image.parentNode.insertBefore(wrapper, image.nextSibling); 1683 } 1684 } 1685 }); 1686 1687 // Process shop tiles 1688 shopTiles.forEach(tile => { 1689 const name = tile.querySelector('.msl-product-name'); 1690 const price = tile.querySelector('.msl-product-price'); 1691 1692 if (name && price && !name.parentElement.classList.contains('details')) { 1693 const wrapper = document.createElement('div'); 1694 wrapper.className = 'details'; 1695 name.parentNode.insertBefore(wrapper, name); 1696 wrapper.appendChild(name); 1697 wrapper.appendChild(price); 1698 } 1699 });
1700 1701 // Wrap tables 1702 tables.forEach(table => { 1703 table.className = 'table table-light table-bordered table-striped'; 1704 const wrapper = document.createElement('div'); 1705 wrapper.className = 'table-responsive'; 1706 table.parentNode.insertBefore(wrapper, table); 1707 wrapper.appendChild(table); 1708 }); 1709 1710 // Basket handling 1711 $$('.product_box > .btn').forEach(btn => btn.value = 'Add to Basket'); 1712 1713 const basket = document.getElementById('basket'); 1714 if (basket) { 1715 const basketItems = basket.querySelectorAll('dt'); 1716 const itemCount = basketItems.length; 1717 const basketContainer = document.querySelector('.basket-container'); 1718 1719 if (itemCount > 0 && basketContainer) { 1720 const basketCount = document.createElement('span'); 1721 basketCount.className = 'basket-count'; 1722 basketCount.textContent = itemCount; 1723 basketCount.setAttribute('aria-label', `${itemCount} item${itemCount > 1 ? 's' : ''} in basket`); 1724 basketContainer.style.display = ''; 1725 basketContainer.classList.add('bagged'); 1726 const link = basketContainer.querySelector('a'); 1727 if (link) link.appendChild(basketCount); 1728 } else if (basketContainer) { 1729 basketContainer.style.display = 'none'; 1730 } 1731 1732 let totalQty = 0; 1733 basket.querySelectorAll('.qty').forEach(qty => { 1734 const match = qty.textContent.match(/\d+/); 1735 if (match) { 1736 const num = parseInt(match[0], 10); 1737 if (!isNaN(num)) totalQty += num; 1738 } 1739 }); 1740 1741 if (totalQty > 0) { 1742 const basketIcon = document.querySelector('.basket-icon'); 1743 if (basketIcon) { 1744 const quantitySpan = document.createElement('span'); 1745 quantitySpan.className = 'basket-quantity'; 1746 quantitySpan.setAttribute('aria-label', `${totalQty} item${totalQty > 1 ? 's' : ''} in basket`); 1747 quantitySpan.innerHTML = `<div class="quantity-inner" aria-hidden="true">${totalQty}</div>`; 1748 basketIcon.appendChild(quantitySpan); 1749 } 1750 } 1751 1752 basketItems.forEach(dt => { 1753 const dd = dt.nextElementSibling?.nextElementSibling; 1754 if (dd) { 1755 const wrapper = document.createElement('div'); 1756 wrapper.className = 'basket-item'; 1757 dt.parentNode.insertBefore(wrapper, dt); 1758 wrapper.appendChild(dt); 1759 wrapper.appendChild(dd); 1760 } 1761 }); 1762 } 1763 1764 // Add rel="noopener" to target="_blank" links 1765 $$('a[target="_blank"]').forEach(link => { 1766 const rel = link.getAttribute('rel') || ''; 1767 if (!rel.includes('noopener')) { 1768 link.setAttribute('rel', (rel + ' noopener').trim()); 1769 } 1770 }); 1771 1772 // News scroller formatting 1773 newsItems.forEach(item => { 1774 const title = item.querySelector('h5'); 1775 if (title) { 1776 const newTitle = document.createElement('h2'); 1777 newTitle.innerHTML = title.innerHTML; 1778 title.replaceWith(newTitle); 1779 1780 const leader = item.querySelector('p.leader'); 1781 const newsDetail = document.createElement('div'); 1782 newsDetail.className = 'news-detail'; 1783 newsDetail.innerHTML = `<div class="news-detail-cell">${newTitle.outerHTML}</div>`; 1784 item.appendChild(newsDetail); 1785 1786 if (leader) { 1787 newsDetail.querySelector('.news-detail-cell').appendChild(leader); 1788 } 1789 } 1790 }); 1791 1792 // --- The block below previously lived (incorrectly) inside the 1793 // newsItems.forEach loop above, so it only ran once per news item 1794 // and not at all when #scroller had no news items. It now runs 1795 // exactly once per page load, as intended. --- 1796 1797 // Accessibility cleanup 1798 const popup = document.querySelector('.popup.vp_form'); 1799 if (popup) { 1800 popup.setAttribute('tabindex', '0'); 1801 popup.setAttribute('title', 'Modal form popup'); 1802 // Only steal focus if the popup is actually visible/open, so we don't 1803 // yank focus away from the page on every load when it's hidden. 1804 const isVisible = popup.offsetParent !== null && 1805 window.getComputedStyle(popup).display !== 'none'; 1806 if (isVisible) popup.focus(); 1807 } 1808 1809 if (typeof mslUserInfo === 'undefined') { 1810 $$('#skip-to-user, #skip-to-admin, #skip-to-basket').forEach(el => el.remove()); 1811 } else { 1812 const skipLogin = document.getElementById('skip-to-login');
1813 if (skipLogin) skipLogin.remove(); 1814 } 1815 1816 $$('[aria-label="Site navigation"] > ul > li > a > span').forEach(span => { 1817 const h2 = document.createElement('h2'); 1818 span.parentNode.insertBefore(h2, span); 1819 h2.appendChild(span); 1820 }); 1821 1822 const adminControls = document.querySelector('.controls-container'); 1823 if (adminControls) { 1824 if (adminControls.querySelectorAll('li').length > 0) { 1825 adminControls.style.display = 'block'; 1826 } else { 1827 const skipAdmin = document.getElementById('skip-to-admin'); 1828 if (skipAdmin) skipAdmin.remove(); 1829 } 1830 } 1831 1832 const eventLocation = document.querySelector('.event-location'); 1833 if (eventLocation && eventLocation.textContent.length === 1) { 1834 eventLocation.style.display = 'none'; 1835 } 1836 1837 $$('.qty > .remove').forEach(btn => btn.textContent = ''); 1838 1839 const reviewBtn = document.getElementById('ctl00_basket_lnkReviewBasket'); 1840 if (reviewBtn) { 1841 reviewBtn.classList.add('btn', 'btn-primary'); 1842 } 1843 1844 const checkoutBtn = document.getElementById('ctl00_basket_lnkCheckout'); 1845 if (checkoutBtn) { 1846 checkoutBtn.classList.add('btn', 'btn-secondary'); 1847 } 1848 1849 // Format publication metadata for news items 1850 mslNewsItems.forEach(item => { 1851 const title = item.querySelector('.msl-title'); 1852 const org = item.querySelector('.msl-organisation'); 1853 const byline = item.querySelector('.msl-byline'); 1854 const pubDateEl = item.querySelector('.msl-pubdate'); 1855 1856 if (pubDateEl) { 1857 const pubDate = pubDateEl.textContent; 1858 const isBanner = item.closest('.mslwidget')?.classList.contains('banner'); 1859 pubDateEl.textContent = isBanner ? 'Posted ' + relTime(pubDate) : relTime(pubDate); 1860 } 1861 1862 if (org && byline && title) { 1863 org.textContent += ' | ' + byline.textContent; 1864 title.parentNode.insertBefore(org, title); 1865 byline.remove(); 1866 } else if (org && title) { 1867 title.parentNode.insertBefore(org, title); 1868 } else if (byline && title) { 1869 byline.classList.add('msl-organisation'); 1870 title.parentNode.insertBefore(byline, title); 1871 } 1872 }); 1873 1874 // Banner formatting 1875 bannerItems.forEach(item => { 1876 const h = item.querySelector('h3'); 1877 const d = item.querySelector('.msl-pubdate'); 1878 const o = item.querySelector('.msl-organisation'); 1879 1880 if (h) { 1881 h.classList.remove('msl-title'); 1882 const wrapper = document.createElement('div'); 1883 wrapper.className = 'msl-title'; 1884 h.parentNode.insertBefore(wrapper, h); 1885 wrapper.appendChild(h); 1886 1887 if (d) h.parentNode.insertBefore(d, h.nextSibling); 1888 if (o) h.parentNode.insertBefore(o, h); 1889 } 1890 }); 1891 1892 // Hide "More" buttons if no content 1893 const latestEventP = document.querySelector('.banner-latest .latest-event .msl_eventlist > p'); 1894 if (latestEventP && latestEventP.textContent.length > 0) { 1895 const btnGroup = document.querySelector('.banner-latest .latest-event .btn-group'); 1896 if (btnGroup) btnGroup.style.display = 'none'; 1897 } 1898 1899 const noNewsMsg = document.querySelector('.banner-latest .latest-news .msl-no-news-message'); 1900 if (noNewsMsg && noNewsMsg.textContent.length > 0) { 1901 const btnGroup = document.querySelector('.banner-latest .latest-news .btn-group'); 1902 if (btnGroup) btnGroup.style.display = 'none'; 1903 } 1904 1905 // Referendum styling 1906 if (document.querySelector('.referendum')) { 1907 $$('.referendum .vote').forEach(el => el.classList.toggle('btn-group')); 1908 $$('.referendum .votenow').forEach(el => { 1909 el.classList.add('btn', 'btn-primary'); 1910 }); 1911 } 1912 } 1913 1914 function handleAccordion(title) { 1915 const tabId = title.dataset.tab; 1916 const content = document.getElementById(tabId); 1917 if (!content) return; 1918 1919 // Ensure the ARIA relationship between title and content is present. 1920 if (!title.hasAttribute('aria-controls')) { 1921 title.setAttribute('aria-controls', tabId); 1922 } 1923 1924 const parent = content.parentElement; 1925 const siblings = Array.from(parent.parentElement.children).filter(el => el !== parent); 1926 1927 const isVisible = window.getComputedStyle(content).display !== 'none'; 1928 content.style.display = isVisible ? 'none' : 'block'; 1929
1930 siblings.forEach(sib => { 1931 const sibContent = sib.querySelector('.accordion-content'); 1932 if (sibContent) sibContent.style.display = 'none'; 1933 }); 1934 1935 title.classList.toggle('active-title'); 1936 siblings.forEach(sib => { 1937 const sibTitle = sib.querySelector('.accordion-title'); 1938 if (sibTitle) sibTitle.classList.remove('active-title'); 1939 }); 1940 1941 title.setAttribute('aria-expanded', title.classList.contains('active-title')); 1942 1943 const icon = title.querySelector('i.fa-chevron-down'); 1944 if (icon) { 1945 icon.classList.toggle('chevron-top'); 1946 icon.setAttribute('aria-hidden', 'true'); 1947 } 1948 1949 siblings.forEach(sib => { 1950 const sibIcon = sib.querySelector('.accordion-title i.fa-chevron-down'); 1951 if (sibIcon) { 1952 sibIcon.classList.remove('chevron-top'); 1953 sibIcon.setAttribute('aria-hidden', 'true'); 1954 } 1955 }); 1956 } 1957 1958 // Initialize when DOM is ready 1959 if (document.readyState === 'loading') { 1960 document.addEventListener('DOMContentLoaded', init); 1961 } else { 1962 init(); 1963 } 1964})(); 1965 1966 1967(function() { 1968 function ready(fn) { 1969 if (document.readyState !== 'loading') fn(); 1970 else document.addEventListener('DOMContentLoaded', fn); 1971 } 1972 1973 ready(function initAllSliders() { 1974 // Support multiple instances: ID (hero/mini hook) and class (mini) 1975 var containers = Array.prototype.slice.call( 1976 document.querySelectorAll('#sliderControl, .sliderControls-mini') 1977 ); 1978 1979 // Remove duplicates if the same element is returned twice 1980 containers = containers.filter(function(el, i, arr){
1981 return arr.indexOf(el) === i; 1982 }); 1983 1984 containers.forEach(function(container){ 1985 initSliderContainer(container); 1986 }); 1987 }); 1988 1989 function initSliderContainer(container) { 1990 // Prevent double init 1991 if (container.dataset.sliderInit === '1') return; 1992 1993 // --- STEP 1: Collect slides (news first; else adapt events) --- 1994 var slides = Array.prototype.slice.call(container.querySelectorAll('.news_item')); 1995 1996 if (!slides.length) { 1997 // Try events markup and adapt to news structure 1998 var eventSlides = Array.prototype.slice.call(container.querySelectorAll('.event_item')); 1999 if (eventSlides.length) { 2000 slides = adaptEventsToNews(container, eventSlides); 2001 } 2002 } 2003 2004 if (!slides.length) return; // nothing to do 2005 2006 // --- STEP 2: Track setup --- 2007 var track = container.querySelector('.slider-track'); 2008 var createdTrack = false; 2009 2010 if (!track) { 2011 track = document.createElement('div'); 2012 track.className = 'slider-track'; 2013 createdTrack = true; 2014 } 2015 2016 // Ensure each slide has the expected inner structure 2017 slides.forEach(function(slide, i) { 2018 // If not already structured, build .slide-grid with .slide-image & .slide-text 2019 var hasGrid = slide.querySelector('.slide-grid'); 2020 if (!hasGrid) { 2021 var titleBlock = slide.querySelector('h2, h3, h4, h5'); 2022 var leader = slide.querySelector('.leader'); 2023 var imageWrap = slide.querySelector('.news_image'); 2024 2025 var grid = document.createElement('div'); 2026 grid.className = 'slide-grid'; 2027 2028 var textCol = document.createElement('div'); 2029 textCol.className = 'slide-text'; 2030 2031 var imageCol = document.createElement('div'); 2032 imageCol.className = 'slide-image'; 2033 2034 if (imageWrap) { 2035 var img = imageWrap.querySelector('img'); 2036 if (img) { 2037 img.removeAttribute('width'); 2038 img.removeAttribute('height'); 2039 img.loading = 'lazy'; 2040 } 2041 imageCol.appendChild(imageWrap); 2042 } 2043 2044 if (titleBlock) textCol.appendChild(titleBlock); 2045 if (leader) textCol.appendChild(leader); 2046 2047 while (slide.firstChild) slide.removeChild(slide.firstChild); 2048 slide.appendChild(grid); 2049 grid.appendChild(imageCol); 2050 grid.appendChild(textCol); 2051 } 2052 2053 // A11y 2054 slide.setAttribute('role', 'group'); 2055 slide.setAttribute('aria-roledescription', 'slide'); 2056 slide.setAttribute('aria-label', (i + 1) + ' of ' + slides.length); 2057 2058 // Move into track if we just created it 2059 if (createdTrack) track.appendChild(slide); 2060 }); 2061 2062 // Insert track if we created it 2063 if (createdTrack) { 2064 container.insertBefore(track, container.firstChild); 2065 } 2066 2067 // --- STEP 3: Controls & dots (create only if not present) --- 2068 var prevBtn = container.querySelector('.slider-edge.left'); 2069 var nextBtn = container.querySelector('.slider-edge.right'); 2070 2071 if (!prevBtn) { 2072 prevBtn = document.createElement('button'); 2073 prevBtn.type = 'button'; 2074 prevBtn.className = 'slider-edge left'; 2075 prevBtn.setAttribute('aria-label', 'Previous slide'); 2076 prevBtn.innerHTML = '<i class="fa-solid fa-chevron-left" aria-hidden="true"></i>'; 2077 container.appendChild(prevBtn); 2078 } 2079 2080 if (!nextBtn) { 2081 nextBtn = document.createElement('button'); 2082 nextBtn.type = 'button'; 2083 nextBtn.className = 'slider-edge right'; 2084 nextBtn.setAttribute('aria-label', 'Next slide'); 2085 nextBtn.innerHTML = '<i class="fa-solid fa-chevron-right" aria-hidden="true"></i>'; 2086 container.appendChild(nextBtn); 2087 } 2088 2089 var dotsWrap = container.querySelector('.slider-dots'); 2090 if (!dotsWrap) { 2091 dotsWrap = document.createElement('div'); 2092 dotsWrap.className = 'slider-dots'; 2093 dotsWrap.setAttribute('role', 'tablist'); 2094 dotsWrap.setAttribute('aria-label', 'Slide navigation'); 2095 container.appendChild(dotsWrap); 2096 } 2097 2098 // Pause/Play control so autoplay can be stopped by keyboard/AT users, 2099 // not just via mouseenter. 2100 var pauseBtn = container.querySelector('.slider-pause'); 2101 if (!pauseBtn) { 2102 pauseBtn = document.createElement('button'); 2103 pauseBtn.type = 'button'; 2104 pauseBtn.className = 'slider-pause'; 2105 container.appendChild(pauseBtn); 2106 } 2107 2108 container.classList.add('slider-initialized'); 2109 container.dataset.sliderInit = '1'; 2110 2111 // --- STEP 4: State & behavior --- 2112 var index = 0; 2113 var autoTimer = null; 2114 var AUTO_MS = 6000; 2115 var PAUSE_ON_HOVER = true; 2116 var prefersReducedMotion = window.matchMedia && 2117 window.matchMedia('(prefers-reduced-motion: reduce)').matches; 2118 var userPaused = prefersReducedMotion; // start paused if user prefers reduced motion 2119 2120 // Dots 2121 var dots = []; 2122 function buildDots() { 2123 while (dotsWrap.firstChild) dotsWrap.removeChild(dotsWrap.firstChild); 2124 dots = slides.map(function(_, i) { 2125 var b = document.createElement('button'); 2126 b.type = 'button'; 2127 b.setAttribute('role', 'tab'); 2128 b.setAttribute('aria-label', 'Go to slide ' + (i + 1)); 2129 b.addEventListener('click', function() { goTo(i); });
2130 dotsWrap.appendChild(b); 2131 return b; 2132 }); 2133 } 2134 2135 function updatePauseButton() { 2136 var isPaused = !autoTimer; 2137 pauseBtn.setAttribute('aria-label', isPaused ? 'Play slideshow' : 'Pause slideshow'); 2138 pauseBtn.setAttribute('aria-pressed', String(isPaused)); 2139 pauseBtn.innerHTML = isPaused 2140 ? '<i class="fa-solid fa-play" aria-hidden="true"></i>' 2141 : '<i class="fa-solid fa-pause" aria-hidden="true"></i>'; 2142 } 2143 2144 function updateUI() { 2145 var slideWidth = slides[0].getBoundingClientRect().width || container.getBoundingClientRect().width; 2146 if (!slideWidth) return; // container hidden? wait for next tick 2147 2148 var offset = index * slideWidth * -1; 2149 track.style.transform = 'translateX(' + offset + 'px)'; 2150 2151 dots.forEach(function(d, i) { 2152 d.setAttribute('aria-current', i === index ? 'true' : 'false'); 2153 }); 2154 } 2155 2156 function goTo(i) { 2157 index = (i + slides.length) % slides.length; 2158 updateUI(); 2159 resetAuto(); 2160 } 2161 function next() { goTo(index + 1); } 2162 function prev() { goTo(index - 1); } 2163 2164 // Autoplay 2165 function startAuto() { 2166 stopAuto(); 2167 if (userPaused) { updatePauseButton(); return; } 2168 autoTimer = setInterval(next, AUTO_MS); 2169 updatePauseButton(); 2170 } 2171 function stopAuto() { 2172 if (autoTimer) { clearInterval(autoTimer); autoTimer = null; } 2173 updatePauseButton(); 2174 } 2175 function resetAuto() { if (!userPaused) startAuto(); } 2176 2177 pauseBtn.addEventListener('click', function() { 2178 userPaused = !userPaused; 2179 if (userPaused) stopAuto(); else startAuto(); 2180 }); 2181 2182 // Keyboard 2183 container.setAttribute('tabindex', '0'); 2184 container.addEventListener('keydown', function(e) { 2185 var k = e.key; 2186 if (k === 'ArrowRight' || k === 'PageDown') { e.preventDefault(); next(); } 2187 else if (k === 'ArrowLeft' || k === 'PageUp') { e.preventDefault(); prev(); } 2188 else if (k === 'Home') { e.preventDefault(); goTo(0); } 2189 else if (k === 'End') { e.preventDefault(); goTo(slides.length - 1); } 2190 }); 2191 2192 // Buttons 2193 prevBtn.addEventListener('click', prev); 2194 nextBtn.addEventListener('click', next); 2195 2196 // Pause on hover (does not permanently set userPaused, just suspends the timer) 2197 if (PAUSE_ON_HOVER) { 2198 container.addEventListener('mouseenter', function() { 2199 if (autoTimer) { clearInterval(autoTimer); autoTimer = null; } 2200 }); 2201 container.addEventListener('mouseleave', function() { 2202 if (!userPaused) startAuto(); 2203 }); 2204 } 2205 2206 // Touch swipe 2207 var startX = null, startY = null, dragging = false; 2208 var SWIPE_THRESHOLD = 40; // px 2209 container.addEventListener('touchstart', function(e) { 2210 var t = e.touches ? e.touches[0] : e; 2211 startX = t.clientX; startY = t.clientY; dragging = true; 2212 if (autoTimer) { clearInterval(autoTimer); autoTimer = null; } 2213 }, { passive: true }); 2214 container.addEventListener('touchmove', function(e) { 2215 if (!dragging) return; 2216 var t = e.touches ? e.touches[0] : e; 2217 var dx = t.clientX - startX, dy = t.clientY - startY; 2218 if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > SWIPE_THRESHOLD) { 2219 dragging = false; (dx < 0 ? next() : prev()); 2220 } 2221 }, { passive: true }); 2222 container.addEventListener('touchend', function() { 2223 dragging = false; 2224 if (!userPaused) startAuto(); 2225 }); 2226 2227 // Resize & late image load sync 2228 var resizeTimer = null; 2229 window.addEventListener('resize', function() { 2230 if (resizeTimer) clearTimeout(resizeTimer); 2231 resizeTimer = setTimeout(updateUI, 150); 2232 }); 2233 window.addEventListener('load', updateUI); 2234 2235 // On hidden container at init (e.g., tabs), re-check. 2236 // Also re-check via ResizeObserver where available, so sliders revealed 2237 // later (e.g. inside a tab panel opened after page load) still sync. 2238 setTimeout(updateUI, 200); 2239 setTimeout(updateUI, 500); 2240 2241 if (typeof ResizeObserver !== 'undefined') { 2242 var ro = new ResizeObserver(function() { updateUI(); }); 2243 ro.observe(container); 2244 } 2245 2246 // Init 2247 buildDots(); 2248 goTo(0); 2249 if (!userPaused) startAuto(); 2250 updatePauseButton(); 2251 } 2252 2253 /** 2254 * Adapt events markup (.event_item) to the news structure the slider expects.
2255 * Returns the new slides NodeList (now with .news_item). 2256 */ 2257 function adaptEventsToNews(container, eventSlides) { 2258 eventSlides.forEach(function(slide) { 2259 // 1) Mark as news_item so existing logic sees it 2260 slide.classList.add('news_item'); 2261 2262 // 2) Extract typical events markup 2263 var dl = slide.querySelector('dl'); 2264 var dt = dl ? dl.querySelector('dt') : null; 2265 var nameLink = dt ? dt.querySelector('.msl_event_name') : null; 2266 var imgWrap = dt ? dt.querySelector('.msl_event_image') : null; 2267 var desc = slide.querySelector('.msl_event_description'); 2268 2269 // 3) Build grid columns 2270 var grid = document.createElement('div'); 2271 grid.className = 'slide-grid'; 2272 2273 var imgCol = document.createElement('div'); 2274 imgCol.className = 'slide-image'; 2275 2276 var textCol = document.createElement('div'); 2277 textCol.className = 'slide-text'; 2278 2279 // Image column: convert to .news_image for CSS compatibility 2280 if (imgWrap) { 2281 imgWrap.classList.add('news_image'); 2282 var img = imgWrap.querySelector('img'); 2283 if (img) { 2284 img.removeAttribute('width'); 2285 img.removeAttribute('height'); 2286 img.loading = 'lazy'; 2287 } 2288 imgCol.appendChild(imgWrap); 2289 } 2290 2291 // Text column: wrap name in h5, convert description to .leader 2292 if (nameLink) { 2293 var h2 = document.createElement('h2'); 2294 h2.appendChild(nameLink); 2295 textCol.appendChild(h2); 2296 } 2297 if (desc) { 2298 desc.classList.add('leader'); 2299 textCol.appendChild(desc); 2300 } 2301 2302 // Replace slide content 2303 while (slide.firstChild) slide.removeChild(slide.firstChild); 2304 slide.appendChild(grid); 2305 grid.appendChild(imgCol); 2306 grid.appendChild(textCol); 2307 }); 2308 2309 // Return as slides for the rest of the flow 2310 return Array.prototype.slice.call(container.querySelectorAll('.news_item')); 2311 } 2312})(); 2313<!-- end of novus-scripts snippet --> 2314 2315 }); 2316</script>
2316 2317 </div> 2318 2319 2320 2321 2322 2323 2324 2325 2326 </form> 2327 2328</body> 2329</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.