From 9f19c23ba8500d89bfccc680e662839be0c81d5d Mon Sep 17 00:00:00 2001
From: olaru
Date: Fri, 25 Sep 2026 17:55:14 +0300
Subject: [PATCH 1/7] contact form backend
---
composer.json | 1 +
config/autoload/mail.global.php | 39 +++
config/config.php | 1 +
public/css/contact.css | 2 +-
public/js/app.js | 2 +-
src/App/assets/js/components/_contact.js | 39 +--
src/App/assets/scss/components/_contact.scss | 99 ++----
src/App/src/ConfigProvider.php | 6 +
src/App/src/Factory/ContactServiceFactory.php | 28 ++
.../PostContactCreateHandlerFactory.php | 32 ++
.../src/Handler/PostContactCreateHandler.php | 50 ++++
src/App/src/RoutesDelegator.php | 3 +
src/App/src/Service/ContactService.php | 169 +++++++++++
src/Blog/templates/page/contact.html.twig | 52 ++--
src/Page/src/Handler/GetPageViewHandler.php | 8 +-
.../Handler/PostContactCreateHandlerTest.php | 168 +++++++++++
test/Unit/App/RoutesDelegatorTest.php | 9 +
test/Unit/App/Service/ContactServiceTest.php | 283 ++++++++++++++++++
.../Page/Handler/GetPageViewHandlerTest.php | 27 +-
19 files changed, 880 insertions(+), 138 deletions(-)
create mode 100644 config/autoload/mail.global.php
create mode 100644 src/App/src/Factory/ContactServiceFactory.php
create mode 100644 src/App/src/Factory/PostContactCreateHandlerFactory.php
create mode 100644 src/App/src/Handler/PostContactCreateHandler.php
create mode 100644 src/App/src/Service/ContactService.php
create mode 100644 test/Unit/App/Handler/PostContactCreateHandlerTest.php
create mode 100644 test/Unit/App/Service/ContactServiceTest.php
diff --git a/composer.json b/composer.json
index 2216e27e..ceb62d93 100644
--- a/composer.json
+++ b/composer.json
@@ -33,6 +33,7 @@
"doctrine/doctrine-fixtures-bundle": "^4.3",
"dotkernel/dot-cache": "^4.0",
"dotkernel/dot-errorhandler": "^5.0.0",
+ "dotkernel/dot-mail": "^5.4",
"dotkernel/dot-response-header": "^3.6",
"laminas/laminas-component-installer": "^3.5.0",
"laminas/laminas-config-aggregator": "^1.17.0",
diff --git a/config/autoload/mail.global.php b/config/autoload/mail.global.php
new file mode 100644
index 00000000..02ea4af4
--- /dev/null
+++ b/config/autoload/mail.global.php
@@ -0,0 +1,39 @@
+ [
+ 'contact' => [
+ 'message_options' => [
+ 'from' => $contactEmail,
+ 'from_name' => 'Dotkernel',
+ 'to' => [$contactEmail],
+ 'subject' => 'New contact form submission',
+ 'body' => [
+ 'content' => '',
+ 'charset' => 'utf-8',
+ ],
+ 'attachments' => [
+ 'files' => [],
+ 'dir' => [
+ 'iterate' => false,
+ 'path' => 'data/mail/attachments',
+ 'recursive' => false,
+ ],
+ ],
+ ],
+ 'transport' => 'sendmail',
+ ],
+ 'log' => [
+ 'sent' => getcwd() . '/log/mail-sent.log',
+ ],
+ ],
+ 'twig' => [
+ 'globals' => [
+ 'contact_email' => $contactEmail,
+ ],
+ ],
+];
diff --git a/config/config.php b/config/config.php
index 7120efe4..1fe12615 100644
--- a/config/config.php
+++ b/config/config.php
@@ -27,6 +27,7 @@
// Dotkernel packages
\Dot\ErrorHandler\ConfigProvider::class,
\Dot\Log\ConfigProvider::class,
+ \Dot\Mail\ConfigProvider::class,
\Dot\Cache\ConfigProvider::class,
\Dot\ResponseHeader\ConfigProvider::class,
diff --git a/public/css/contact.css b/public/css/contact.css
index a0ef2ec2..ba4a22a1 100644
--- a/public/css/contact.css
+++ b/public/css/contact.css
@@ -1 +1 @@
-html[data-theme=light] .contact-page{--field-border:#8a8478}html[data-theme=dark] .contact-page{--field-border:#7a746d}.contact-page code{font-family:var(--mono);background:var(--teal-dim);color:var(--teal);border-radius:4px;padding:1px 6px;font-size:.92em}.contact-page :focus-visible{outline:2px solid var(--amber);outline-offset:2px}.contact-label{font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--text-muted);font-size:11.5px}.contact-hero{padding:88px 0 64px}.contact-hero .wrap{flex-direction:column;gap:20px;display:flex}.contact-hero h1{letter-spacing:-.02em;font-size:44px;line-height:1.15}.contact-hero p{max-width:700px;font-size:18px;line-height:1.6}.contact-hero strong{color:var(--teal);font-weight:600}.contact-main{padding:0 0 88px}.contact-layout{grid-template-columns:minmax(0,1fr) minmax(0,1.45fr);align-items:start;gap:64px;display:grid}.contact-channels{flex-direction:column;gap:18px;display:flex}.contact-channel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);gap:16px;padding:22px 24px;display:flex}.contact-channel:hover{border-color:var(--teal)}.contact-channel>svg{fill:none;width:20px;height:20px;stroke:var(--teal);stroke-width:1.6px;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0;margin-top:2px}.contact-channel p{font-size:14.5px;line-height:1.65}.contact-channel-body{flex-direction:column;gap:4px;display:flex}.contact-channel-kicker{font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--text-muted);font-size:10.5px}.contact-channel-title{font-family:var(--mono);letter-spacing:-.01em;font-size:17px;font-weight:600}.contact-support{background:var(--surface-2);border:1px solid var(--border-soft);border-radius:var(--radius);flex-direction:column;padding:22px 24px;display:flex}.contact-support .contact-label{padding-bottom:12px}.contact-support a{border-top:1px solid var(--border-soft);min-height:44px;color:var(--teal);justify-content:space-between;align-items:center;font-size:14.5px;display:flex}.contact-support a:hover{color:var(--amber)}.contact-form-card{background:var(--surface);border:1px solid var(--border);border-top:3px solid var(--amber);border-radius:var(--radius);padding:34px 36px 36px}.contact-form{flex-direction:column;gap:26px;display:flex}.contact-form[hidden]{display:none}.contact-form-head{flex-direction:column;gap:6px;display:flex}.contact-form-head h2{font-size:20px;line-height:1.2}.contact-form-head p{font-size:14.5px;line-height:1.65}.contact-topics{border:0;margin:0;padding:0}.contact-topics legend{float:none;width:auto;margin-bottom:12px;padding:0}.contact-pills{flex-wrap:wrap;gap:8px;display:flex}.contact-pill{min-height:44px;font-family:var(--mono);color:var(--text-muted);background:var(--surface);border:1px solid var(--field-border);cursor:pointer;border-radius:999px;padding:0 16px;font-size:13.5px;font-weight:500}.contact-pill:hover{border-color:var(--teal)}.contact-pill[aria-pressed=true]{color:var(--teal);background:var(--teal-dim);border-color:var(--teal)}.contact-fields{grid-template-columns:repeat(2,minmax(0,1fr));gap:18px;display:grid}.contact-field{flex-direction:column;gap:7px;display:flex}.contact-field label{font-family:var(--mono);color:var(--text);font-size:13px;font-weight:500}.contact-field textarea{resize:vertical;min-height:160px}.contact-input{width:100%;font-family:var(--sans);color:var(--text);background:var(--surface);border:1px solid var(--field-border);border-radius:8px;padding:11px 14px;font-size:15px;line-height:1.5}.contact-input::placeholder{color:var(--text-dim)}.contact-input:hover{border-color:var(--text-muted)}.contact-input:focus-visible{border-color:var(--amber)}.contact-consent{color:var(--text-muted);cursor:pointer;align-items:flex-start;gap:10px;font-size:14px;line-height:1.55;display:flex}.contact-consent input{width:18px;height:18px;accent-color:var(--amber);flex-shrink:0;margin:2px 0 0}.contact-form-foot{border-top:1px solid var(--border-soft);justify-content:space-between;align-items:center;gap:18px;padding-top:22px;display:flex}.contact-form-foot p{max-width:330px;font-size:13.5px;line-height:1.6}.contact-form-foot .btn{flex-shrink:0;height:46px;padding:0 22px}.contact-sent{flex-direction:column;justify-content:center;gap:18px;min-height:560px;display:flex}.contact-sent[hidden]{display:none}.contact-sent h2{font-size:28px;line-height:1.2}.contact-sent p{max-width:460px;font-size:16.5px;line-height:1.7}.contact-sent strong{color:var(--text);font-weight:600}.contact-sent .btn{height:44px;color:var(--text-muted);padding:0 18px}.contact-sent .btn:hover{border-color:var(--amber);color:var(--text)}.contact-sent-icon{border-radius:var(--radius);background:var(--teal-dim);width:48px;height:48px;color:var(--teal);justify-content:center;align-items:center;display:flex}.contact-sent-icon svg{width:22px;height:22px}.contact-faq-layout{grid-template-columns:minmax(0,1fr) minmax(0,1.6fr);align-items:start;gap:64px;display:grid}.contact-faq-head{flex-direction:column;gap:14px;display:flex}.contact-faq-head h2{font-size:30px;line-height:1.2}.contact-faq-list{border-top:1px solid var(--border-soft);flex-direction:column;display:flex}.contact-faq-list details.acc{padding:20px 0}.contact-faq-list details.acc summary{letter-spacing:-.01em;gap:18px;padding:0;font-size:17px;font-weight:600}.contact-faq-list details.acc summary .chev{width:18px;height:18px;color:var(--text-muted);flex-shrink:0}.contact-faq-list details.acc[open] summary .chev{transform:rotate(180deg)}.contact-faq-list details.acc p{max-width:620px;margin-top:12px;font-size:15px;line-height:1.7}.contact-faq-list details.acc a{color:var(--teal)}.contact-faq-list details.acc a:hover{color:var(--amber)}@media (width<=991.98px){.contact-layout,.contact-faq-layout{grid-template-columns:minmax(0,1fr);gap:40px}.contact-layout .contact-form-card{order:-1}}@media (width<=700px){.contact-hero{padding:56px 0 40px}.contact-hero h1{font-size:32px}.contact-hero p{font-size:16.5px}.contact-hero .wrap,.contact-layout,.contact-faq-layout{padding:0 16px}.contact-form-card{padding:26px 20px 24px}.contact-fields{grid-template-columns:minmax(0,1fr)}.contact-form-foot{flex-direction:column;align-items:stretch}.contact-form-foot .btn{justify-content:center}.contact-sent{min-height:360px}}
+html[data-theme=light] .contact-page{--field-border:#8a8478}html[data-theme=dark] .contact-page{--field-border:#7a746d}.contact-page code{font-family:var(--mono);background:var(--teal-dim);color:var(--teal);border-radius:4px;padding:1px 6px;font-size:.92em}.contact-page :focus-visible{outline:2px solid var(--amber);outline-offset:2px}.contact-label{font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--text-muted);font-size:11.5px}.contact-hero{padding:88px 0 64px}.contact-hero .wrap{flex-direction:column;gap:20px;display:flex}.contact-hero h1{letter-spacing:-.02em;font-size:44px;line-height:1.15}.contact-hero p{max-width:700px;font-size:18px;line-height:1.6}.contact-hero strong{color:var(--teal);font-weight:600}.contact-main{padding:0 0 88px}.contact-layout{grid-template-columns:minmax(0,1fr) minmax(0,1.45fr);align-items:start;gap:64px;display:grid}.contact-channels{flex-direction:column;gap:18px;display:flex}.contact-channel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);gap:16px;padding:22px 24px;display:flex}.contact-channel:hover{border-color:var(--teal)}.contact-channel>svg{fill:none;width:20px;height:20px;stroke:var(--teal);stroke-width:1.6px;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0;margin-top:2px}.contact-channel p{font-size:14.5px;line-height:1.65}.contact-channel-body{flex-direction:column;gap:4px;display:flex}.contact-channel-kicker{font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--text-muted);font-size:10.5px}.contact-channel-title{font-family:var(--mono);letter-spacing:-.01em;font-size:17px;font-weight:600}.contact-support{background:var(--surface-2);border:1px solid var(--border-soft);border-radius:var(--radius);flex-direction:column;padding:22px 24px;display:flex}.contact-support .contact-label{padding-bottom:12px}.contact-support a{border-top:1px solid var(--border-soft);min-height:44px;color:var(--teal);justify-content:space-between;align-items:center;font-size:14.5px;display:flex}.contact-support a:hover{color:var(--amber)}.contact-form-card{background:var(--surface);border:1px solid var(--border);border-top:3px solid var(--amber);border-radius:var(--radius);padding:34px 36px 36px}.contact-form{flex-direction:column;gap:26px;display:flex}.contact-form[hidden]{display:none}.contact-form-head{flex-direction:column;gap:6px;display:flex}.contact-form-head h2{font-size:20px;line-height:1.2}.contact-form-head p{font-size:14.5px;line-height:1.65}.contact-topics{border:0;margin:0;padding:0}.contact-topics legend{float:none;width:auto;margin-bottom:12px;padding:0}.contact-pills{flex-wrap:wrap;gap:8px;display:flex}.contact-pill{min-height:44px;font-family:var(--mono);color:var(--text-muted);background:var(--surface);border:1px solid var(--field-border);cursor:pointer;border-radius:999px;padding:0 16px;font-size:13.5px;font-weight:500}.contact-pill:hover{border-color:var(--teal)}.contact-pill[aria-pressed=true]{color:var(--teal);background:var(--teal-dim);border-color:var(--teal)}.contact-fields{grid-template-columns:repeat(2,minmax(0,1fr));gap:18px;display:grid}.contact-field{flex-direction:column;gap:7px;display:flex}.contact-field label{font-family:var(--mono);color:var(--text);font-size:13px;font-weight:500}.contact-field textarea{resize:vertical;min-height:160px}.contact-input{width:100%;font-family:var(--sans);color:var(--text);background:var(--surface);border:1px solid var(--field-border);border-radius:8px;padding:11px 14px;font-size:15px;line-height:1.5}.contact-input::placeholder{color:var(--text-dim)}.contact-input:hover{border-color:var(--text-muted)}.contact-input:focus-visible{border-color:var(--amber)}.contact-error{color:var(--rose);margin:0;font-size:13px}.contact-alert{border:1px solid;border-radius:8px;margin:0;padding:14px 16px;font-size:14.5px;line-height:1.55}.contact-alert a{color:inherit;text-decoration:underline}.contact-alert-success{color:var(--teal);background:var(--teal-dim);border-color:var(--teal)}.contact-alert-error{color:var(--rose);background:var(--rose-dim);border-color:var(--rose)}.contact-form-foot{border-top:1px solid var(--border-soft);justify-content:space-between;align-items:center;gap:18px;padding-top:22px;display:flex}.contact-form-foot p{max-width:330px;font-size:13.5px;line-height:1.6}.contact-form-foot .btn{flex-shrink:0;height:46px;padding:0 22px}.contact-faq-layout{grid-template-columns:minmax(0,1fr) minmax(0,1.6fr);align-items:start;gap:64px;display:grid}.contact-faq-head{flex-direction:column;gap:14px;display:flex}.contact-faq-head h2{font-size:30px;line-height:1.2}.contact-faq-list{border-top:1px solid var(--border-soft);flex-direction:column;display:flex}.contact-faq-list details.acc{padding:20px 0}.contact-faq-list details.acc summary{letter-spacing:-.01em;gap:18px;padding:0;font-size:17px;font-weight:600}.contact-faq-list details.acc summary .chev{width:18px;height:18px;color:var(--text-muted);flex-shrink:0}.contact-faq-list details.acc[open] summary .chev{transform:rotate(180deg)}.contact-faq-list details.acc p{max-width:620px;margin-top:12px;font-size:15px;line-height:1.7}.contact-faq-list details.acc a{color:var(--teal)}.contact-faq-list details.acc a:hover{color:var(--amber)}@media (width<=991.98px){.contact-layout,.contact-faq-layout{grid-template-columns:minmax(0,1fr);gap:40px}.contact-layout .contact-form-card{order:-1}}@media (width<=700px){.contact-hero{padding:56px 0 40px}.contact-hero h1{font-size:32px}.contact-hero p{font-size:16.5px}.contact-hero .wrap,.contact-layout,.contact-faq-layout{padding:0 16px}.contact-form-card{padding:26px 20px 24px}.contact-fields{grid-template-columns:minmax(0,1fr)}.contact-form-foot{flex-direction:column;align-items:stretch}.contact-form-foot .btn{justify-content:center}}
diff --git a/public/js/app.js b/public/js/app.js
index 49a4f5f9..95f1ebb5 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -1 +1 @@
-/* empty css */((e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports))(((e,t)=>{(function(n,r){typeof e==`object`&&t!==void 0?t.exports=r():typeof define==`function`&&define.amd?define(r):(n=typeof globalThis<`u`?globalThis:n||self,n.bootstrap=r())})(e,(function(){let e=new Map,t={set(t,n,r){e.has(t)||e.set(t,new Map);let i=e.get(t);if(!i.has(n)&&i.size!==0){console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(i.keys())[0]}.`);return}i.set(n,r)},get(t,n){return e.has(t)&&e.get(t).get(n)||null},remove(t,n){if(!e.has(t))return;let r=e.get(t);r.delete(n),r.size===0&&e.delete(t)}},n=`transitionend`,r=e=>(e&&window.CSS&&window.CSS.escape&&(e=e.replace(/#([^\s"#']+)/g,(e,t)=>`#${CSS.escape(t)}`)),e),i=e=>e==null?`${e}`:Object.prototype.toString.call(e).match(/\s([a-z]+)/i)[1].toLowerCase(),a=e=>{do e+=Math.floor(Math.random()*1e6);while(document.getElementById(e));return e},o=e=>{if(!e)return 0;let{transitionDuration:t,transitionDelay:n}=window.getComputedStyle(e);return!Number.parseFloat(t)&&!Number.parseFloat(n)?0:(t=t.split(`,`)[0],n=n.split(`,`)[0],(Number.parseFloat(t)+Number.parseFloat(n))*1e3)},s=e=>{e.dispatchEvent(new Event(n))},c=e=>!e||typeof e!=`object`?!1:(e.jquery!==void 0&&(e=e[0]),e.nodeType!==void 0),l=e=>c(e)?e.jquery?e[0]:e:typeof e==`string`&&e.length>0?document.querySelector(r(e)):null,u=e=>{if(!c(e)||e.getClientRects().length===0)return!1;let t=getComputedStyle(e).getPropertyValue(`visibility`)===`visible`,n=e.closest(`details:not([open])`);if(!n)return t;if(n!==e){let t=e.closest(`summary`);if(t&&t.parentNode!==n||t===null)return!1}return t},d=e=>!e||e.nodeType!==Node.ELEMENT_NODE||e.classList.contains(`disabled`)?!0:e.disabled===void 0?e.hasAttribute(`disabled`)&&e.getAttribute(`disabled`)!==`false`:e.disabled,f=e=>{if(!document.documentElement.attachShadow)return null;if(typeof e.getRootNode==`function`){let t=e.getRootNode();return t instanceof ShadowRoot?t:null}return e instanceof ShadowRoot?e:e.parentNode?f(e.parentNode):null},p=()=>{},m=e=>{e.offsetHeight},h=()=>window.jQuery&&!document.body.hasAttribute(`data-bs-no-jquery`)?window.jQuery:null,g=[],_=e=>{document.readyState===`loading`?(g.length||document.addEventListener(`DOMContentLoaded`,()=>{for(let e of g)e()}),g.push(e)):e()},v=()=>document.documentElement.dir===`rtl`,y=e=>{_(()=>{let t=h();if(t){let n=e.NAME,r=t.fn[n];t.fn[n]=e.jQueryInterface,t.fn[n].Constructor=e,t.fn[n].noConflict=()=>(t.fn[n]=r,e.jQueryInterface)}})},b=(e,t=[],n=e)=>typeof e==`function`?e.call(...t):n,x=(e,t,r=!0)=>{if(!r){b(e);return}let i=o(t)+5,a=!1,c=({target:r})=>{r===t&&(a=!0,t.removeEventListener(n,c),b(e))};t.addEventListener(n,c),setTimeout(()=>{a||s(t)},i)},S=(e,t,n,r)=>{let i=e.length,a=e.indexOf(t);return a===-1?!n&&r?e[i-1]:e[0]:(a+=n?1:-1,r&&(a=(a+i)%i),e[Math.max(0,Math.min(a,i-1))])},C=/[^.]*(?=\..*)\.|.*/,w=/\..*/,T=/::\d+$/,E={},D=1,O={mouseenter:`mouseover`,mouseleave:`mouseout`},k=new Set(`click.dblclick.mouseup.mousedown.contextmenu.mousewheel.DOMMouseScroll.mouseover.mouseout.mousemove.selectstart.selectend.keydown.keypress.keyup.orientationchange.touchstart.touchmove.touchend.touchcancel.pointerdown.pointermove.pointerup.pointerleave.pointercancel.gesturestart.gesturechange.gestureend.focus.blur.change.reset.select.submit.focusin.focusout.load.unload.beforeunload.resize.move.DOMContentLoaded.readystatechange.error.abort.scroll`.split(`.`));function ee(e,t){return t&&`${t}::${D++}`||e.uidEvent||D++}function A(e){let t=ee(e);return e.uidEvent=t,E[t]=E[t]||{},E[t]}function j(e,t){return function n(r){return ce(r,{delegateTarget:e}),n.oneOff&&M.off(e,r.type,t),t.apply(e,[r])}}function te(e,t,n){return function r(i){let a=e.querySelectorAll(t);for(let{target:o}=i;o&&o!==this;o=o.parentNode)for(let s of a)if(s===o)return ce(i,{delegateTarget:o}),r.oneOff&&M.off(e,i.type,t,n),n.apply(o,[i])}}function ne(e,t,n=null){return Object.values(e).find(e=>e.callable===t&&e.delegationSelector===n)}function re(e,t,n){let r=typeof t==`string`,i=r?n:t||n,a=se(e);return k.has(a)||(a=e),[r,i,a]}function ie(e,t,n,r,i){if(typeof t!=`string`||!e)return;let[a,o,s]=re(t,n,r);t in O&&(o=(e=>function(t){if(!t.relatedTarget||t.relatedTarget!==t.delegateTarget&&!t.delegateTarget.contains(t.relatedTarget))return e.call(this,t)})(o));let c=A(e),l=c[s]||(c[s]={}),u=ne(l,o,a?n:null);if(u){u.oneOff=u.oneOff&&i;return}let d=ee(o,t.replace(C,``)),f=a?te(e,n,o):j(e,o);f.delegationSelector=a?n:null,f.callable=o,f.oneOff=i,f.uidEvent=d,l[d]=f,e.addEventListener(s,f,a)}function ae(e,t,n,r,i){let a=ne(t[n],r,i);a&&(e.removeEventListener(n,a,!!i),delete t[n][a.uidEvent])}function oe(e,t,n,r){let i=t[n]||{};for(let[a,o]of Object.entries(i))a.includes(r)&&ae(e,t,n,o.callable,o.delegationSelector)}function se(e){return e=e.replace(w,``),O[e]||e}let M={on(e,t,n,r){ie(e,t,n,r,!1)},one(e,t,n,r){ie(e,t,n,r,!0)},off(e,t,n,r){if(typeof t!=`string`||!e)return;let[i,a,o]=re(t,n,r),s=o!==t,c=A(e),l=c[o]||{},u=t.startsWith(`.`);if(a!==void 0){if(!Object.keys(l).length)return;ae(e,c,o,a,i?n:null);return}if(u)for(let n of Object.keys(c))oe(e,c,n,t.slice(1));for(let[n,r]of Object.entries(l)){let i=n.replace(T,``);(!s||t.includes(i))&&ae(e,c,o,r.callable,r.delegationSelector)}},trigger(e,t,n){if(typeof t!=`string`||!e)return null;let r=h(),i=t!==se(t),a=null,o=!0,s=!0,c=!1;i&&r&&(a=r.Event(t,n),r(e).trigger(a),o=!a.isPropagationStopped(),s=!a.isImmediatePropagationStopped(),c=a.isDefaultPrevented());let l=ce(new Event(t,{bubbles:o,cancelable:!0}),n);return c&&l.preventDefault(),s&&e.dispatchEvent(l),l.defaultPrevented&&a&&a.preventDefault(),l}};function ce(e,t={}){for(let[n,r]of Object.entries(t))try{e[n]=r}catch{Object.defineProperty(e,n,{configurable:!0,get(){return r}})}return e}function le(e){if(e===`true`)return!0;if(e===`false`)return!1;if(e===Number(e).toString())return Number(e);if(e===``||e===`null`)return null;if(typeof e!=`string`)return e;try{return JSON.parse(decodeURIComponent(e))}catch{return e}}function ue(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}let N={setDataAttribute(e,t,n){e.setAttribute(`data-bs-${ue(t)}`,n)},removeDataAttribute(e,t){e.removeAttribute(`data-bs-${ue(t)}`)},getDataAttributes(e){if(!e)return{};let t={},n=Object.keys(e.dataset).filter(e=>e.startsWith(`bs`)&&!e.startsWith(`bsConfig`));for(let r of n){let n=r.replace(/^bs/,``);n=n.charAt(0).toLowerCase()+n.slice(1),t[n]=le(e.dataset[r])}return t},getDataAttribute(e,t){return le(e.getAttribute(`data-bs-${ue(t)}`))}};class de{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw Error(`You have to implement the static method "NAME", for each component!`)}_getConfig(e){return e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e}_mergeConfigObj(e,t){let n=c(t)?N.getDataAttribute(t,`config`):{};return{...this.constructor.Default,...typeof n==`object`?n:{},...c(t)?N.getDataAttributes(t):{},...typeof e==`object`?e:{}}}_typeCheckConfig(e,t=this.constructor.DefaultType){for(let[n,r]of Object.entries(t)){let t=e[n],a=c(t)?`element`:i(t);if(!new RegExp(r).test(a))throw TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${r}".`)}}}class P extends de{constructor(e,n){super(),e=l(e),e&&(this._element=e,this._config=this._getConfig(n),t.set(this._element,this.constructor.DATA_KEY,this))}dispose(){t.remove(this._element,this.constructor.DATA_KEY),M.off(this._element,this.constructor.EVENT_KEY);for(let e of Object.getOwnPropertyNames(this))this[e]=null}_queueCallback(e,t,n=!0){x(e,t,n)}_getConfig(e){return e=this._mergeConfigObj(e,this._element),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}static getInstance(e){return t.get(l(e),this.DATA_KEY)}static getOrCreateInstance(e,t={}){return this.getInstance(e)||new this(e,typeof t==`object`?t:null)}static get VERSION(){return`5.3.8`}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(e){return`${e}${this.EVENT_KEY}`}}let fe=e=>{let t=e.getAttribute(`data-bs-target`);if(!t||t===`#`){let n=e.getAttribute(`href`);if(!n||!n.includes(`#`)&&!n.startsWith(`.`))return null;n.includes(`#`)&&!n.startsWith(`#`)&&(n=`#${n.split(`#`)[1]}`),t=n&&n!==`#`?n.trim():null}return t?t.split(`,`).map(e=>r(e)).join(`,`):null},F={find(e,t=document.documentElement){return[].concat(...Element.prototype.querySelectorAll.call(t,e))},findOne(e,t=document.documentElement){return Element.prototype.querySelector.call(t,e)},children(e,t){return[].concat(...e.children).filter(e=>e.matches(t))},parents(e,t){let n=[],r=e.parentNode.closest(t);for(;r;)n.push(r),r=r.parentNode.closest(t);return n},prev(e,t){let n=e.previousElementSibling;for(;n;){if(n.matches(t))return[n];n=n.previousElementSibling}return[]},next(e,t){let n=e.nextElementSibling;for(;n;){if(n.matches(t))return[n];n=n.nextElementSibling}return[]},focusableChildren(e){let t=[`a`,`button`,`input`,`textarea`,`select`,`details`,`[tabindex]`,`[contenteditable="true"]`].map(e=>`${e}:not([tabindex^="-"])`).join(`,`);return this.find(t,e).filter(e=>!d(e)&&u(e))},getSelectorFromElement(e){let t=fe(e);return t&&F.findOne(t)?t:null},getElementFromSelector(e){let t=fe(e);return t?F.findOne(t):null},getMultipleElementsFromSelector(e){let t=fe(e);return t?F.find(t):[]}},pe=(e,t=`hide`)=>{let n=`click.dismiss${e.EVENT_KEY}`,r=e.NAME;M.on(document,n,`[data-bs-dismiss="${r}"]`,function(n){if([`A`,`AREA`].includes(this.tagName)&&n.preventDefault(),d(this))return;let i=F.getElementFromSelector(this)||this.closest(`.${r}`);e.getOrCreateInstance(i)[t]()})},me=`.bs.alert`,he=`close${me}`,ge=`closed${me}`;class _e extends P{static get NAME(){return`alert`}close(){if(M.trigger(this._element,he).defaultPrevented)return;this._element.classList.remove(`show`);let e=this._element.classList.contains(`fade`);this._queueCallback(()=>this._destroyElement(),this._element,e)}_destroyElement(){this._element.remove(),M.trigger(this._element,ge),this.dispose()}static jQueryInterface(e){return this.each(function(){let t=_e.getOrCreateInstance(this);if(typeof e==`string`){if(t[e]===void 0||e.startsWith(`_`)||e===`constructor`)throw TypeError(`No method named "${e}"`);t[e](this)}})}}pe(_e,`close`),y(_e);let I=`[data-bs-toggle="button"]`;class L extends P{static get NAME(){return`button`}toggle(){this._element.setAttribute(`aria-pressed`,this._element.classList.toggle(`active`))}static jQueryInterface(e){return this.each(function(){let t=L.getOrCreateInstance(this);e===`toggle`&&t[e]()})}}M.on(document,`click.bs.button.data-api`,I,e=>{e.preventDefault();let t=e.target.closest(I);L.getOrCreateInstance(t).toggle()}),y(L);let R=`.bs.swipe`,ve=`touchstart${R}`,ye=`touchmove${R}`,be=`touchend${R}`,xe=`pointerdown${R}`,Se=`pointerup${R}`,Ce={endCallback:null,leftCallback:null,rightCallback:null},we={endCallback:`(function|null)`,leftCallback:`(function|null)`,rightCallback:`(function|null)`};class Te extends de{constructor(e,t){super(),this._element=e,!(!e||!Te.isSupported())&&(this._config=this._getConfig(t),this._deltaX=0,this._supportPointerEvents=!!window.PointerEvent,this._initEvents())}static get Default(){return Ce}static get DefaultType(){return we}static get NAME(){return`swipe`}dispose(){M.off(this._element,R)}_start(e){if(!this._supportPointerEvents){this._deltaX=e.touches[0].clientX;return}this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX)}_end(e){this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX-this._deltaX),this._handleSwipe(),b(this._config.endCallback)}_move(e){this._deltaX=e.touches&&e.touches.length>1?0:e.touches[0].clientX-this._deltaX}_handleSwipe(){let e=Math.abs(this._deltaX);if(e<=40)return;let t=e/this._deltaX;this._deltaX=0,t&&b(t>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(M.on(this._element,xe,e=>this._start(e)),M.on(this._element,Se,e=>this._end(e)),this._element.classList.add(`pointer-event`)):(M.on(this._element,ve,e=>this._start(e)),M.on(this._element,ye,e=>this._move(e)),M.on(this._element,be,e=>this._end(e)))}_eventIsPointerPenTouch(e){return this._supportPointerEvents&&(e.pointerType===`pen`||e.pointerType===`touch`)}static isSupported(){return`ontouchstart`in document.documentElement||navigator.maxTouchPoints>0}}let z=`.bs.carousel`,Ee=`.data-api`,De=`next`,Oe=`prev`,ke=`left`,Ae=`right`,je=`slide${z}`,Me=`slid${z}`,Ne=`keydown${z}`,Pe=`mouseenter${z}`,Fe=`mouseleave${z}`,Ie=`dragstart${z}`,Le=`load${z}${Ee}`,Re=`click${z}${Ee}`,ze=`carousel`,Be=`active`,Ve={ArrowLeft:Ae,ArrowRight:ke},He={interval:5e3,keyboard:!0,pause:`hover`,ride:!1,touch:!0,wrap:!0},Ue={interval:`(number|boolean)`,keyboard:`boolean`,pause:`(string|boolean)`,ride:`(boolean|string)`,touch:`boolean`,wrap:`boolean`};class We extends P{constructor(e,t){super(e,t),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=F.findOne(`.carousel-indicators`,this._element),this._addEventListeners(),this._config.ride===ze&&this.cycle()}static get Default(){return He}static get DefaultType(){return Ue}static get NAME(){return`carousel`}next(){this._slide(De)}nextWhenVisible(){!document.hidden&&u(this._element)&&this.next()}prev(){this._slide(Oe)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval(()=>this.nextWhenVisible(),this._config.interval)}_maybeEnableCycle(){if(this._config.ride){if(this._isSliding){M.one(this._element,Me,()=>this.cycle());return}this.cycle()}}to(e){let t=this._getItems();if(e>t.length-1||e<0)return;if(this._isSliding){M.one(this._element,Me,()=>this.to(e));return}let n=this._getItemIndex(this._getActive());if(n===e)return;let r=e>n?De:Oe;this._slide(r,t[e])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(e){return e.defaultInterval=e.interval,e}_addEventListeners(){this._config.keyboard&&M.on(this._element,Ne,e=>this._keydown(e)),this._config.pause===`hover`&&(M.on(this._element,Pe,()=>this.pause()),M.on(this._element,Fe,()=>this._maybeEnableCycle())),this._config.touch&&Te.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(let e of F.find(`.carousel-item img`,this._element))M.on(e,Ie,e=>e.preventDefault());let e={leftCallback:()=>this._slide(this._directionToOrder(ke)),rightCallback:()=>this._slide(this._directionToOrder(Ae)),endCallback:()=>{this._config.pause===`hover`&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(()=>this._maybeEnableCycle(),500+this._config.interval))}};this._swipeHelper=new Te(this._element,e)}_keydown(e){if(/input|textarea/i.test(e.target.tagName))return;let t=Ve[e.key];t&&(e.preventDefault(),this._slide(this._directionToOrder(t)))}_getItemIndex(e){return this._getItems().indexOf(e)}_setActiveIndicatorElement(e){if(!this._indicatorsElement)return;let t=F.findOne(`.active`,this._indicatorsElement);t.classList.remove(Be),t.removeAttribute(`aria-current`);let n=F.findOne(`[data-bs-slide-to="${e}"]`,this._indicatorsElement);n&&(n.classList.add(Be),n.setAttribute(`aria-current`,`true`))}_updateInterval(){let e=this._activeElement||this._getActive();if(!e)return;let t=Number.parseInt(e.getAttribute(`data-bs-interval`),10);this._config.interval=t||this._config.defaultInterval}_slide(e,t=null){if(this._isSliding)return;let n=this._getActive(),r=e===De,i=t||S(this._getItems(),n,r,this._config.wrap);if(i===n)return;let a=this._getItemIndex(i),o=t=>M.trigger(this._element,t,{relatedTarget:i,direction:this._orderToDirection(e),from:this._getItemIndex(n),to:a});if(o(je).defaultPrevented||!n||!i)return;let s=!!this._interval;this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(a),this._activeElement=i;let c=r?`carousel-item-start`:`carousel-item-end`,l=r?`carousel-item-next`:`carousel-item-prev`;i.classList.add(l),m(i),n.classList.add(c),i.classList.add(c),this._queueCallback(()=>{i.classList.remove(c,l),i.classList.add(Be),n.classList.remove(Be,l,c),this._isSliding=!1,o(Me)},n,this._isAnimated()),s&&this.cycle()}_isAnimated(){return this._element.classList.contains(`slide`)}_getActive(){return F.findOne(`.active.carousel-item`,this._element)}_getItems(){return F.find(`.carousel-item`,this._element)}_clearInterval(){this._interval&&=(clearInterval(this._interval),null)}_directionToOrder(e){return v()?e===ke?Oe:De:e===ke?De:Oe}_orderToDirection(e){return v()?e===Oe?ke:Ae:e===Oe?Ae:ke}static jQueryInterface(e){return this.each(function(){let t=We.getOrCreateInstance(this,e);if(typeof e==`number`){t.to(e);return}if(typeof e==`string`){if(t[e]===void 0||e.startsWith(`_`)||e===`constructor`)throw TypeError(`No method named "${e}"`);t[e]()}})}}M.on(document,Re,`[data-bs-slide], [data-bs-slide-to]`,function(e){let t=F.getElementFromSelector(this);if(!t||!t.classList.contains(ze))return;e.preventDefault();let n=We.getOrCreateInstance(t),r=this.getAttribute(`data-bs-slide-to`);if(r){n.to(r),n._maybeEnableCycle();return}if(N.getDataAttribute(this,`slide`)===`next`){n.next(),n._maybeEnableCycle();return}n.prev(),n._maybeEnableCycle()}),M.on(window,Le,()=>{let e=F.find(`[data-bs-ride="carousel"]`);for(let t of e)We.getOrCreateInstance(t)}),y(We);let Ge=`.bs.collapse`,Ke=`show${Ge}`,qe=`shown${Ge}`,Je=`hide${Ge}`,Ye=`hidden${Ge}`,Xe=`click${Ge}.data-api`,Ze=`show`,Qe=`collapse`,$e=`collapsing`,et=`:scope .${Qe} .${Qe}`,tt=`[data-bs-toggle="collapse"]`,nt={parent:null,toggle:!0},rt={parent:`(null|element)`,toggle:`boolean`};class it extends P{constructor(e,t){super(e,t),this._isTransitioning=!1,this._triggerArray=[];let n=F.find(tt);for(let e of n){let t=F.getSelectorFromElement(e),n=F.find(t).filter(e=>e===this._element);t!==null&&n.length&&this._triggerArray.push(e)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return nt}static get DefaultType(){return rt}static get NAME(){return`collapse`}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let e=[];if(this._config.parent&&(e=this._getFirstLevelChildren(`.collapse.show, .collapse.collapsing`).filter(e=>e!==this._element).map(e=>it.getOrCreateInstance(e,{toggle:!1}))),e.length&&e[0]._isTransitioning||M.trigger(this._element,Ke).defaultPrevented)return;for(let t of e)t.hide();let t=this._getDimension();this._element.classList.remove(Qe),this._element.classList.add($e),this._element.style[t]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;let n=()=>{this._isTransitioning=!1,this._element.classList.remove($e),this._element.classList.add(Qe,Ze),this._element.style[t]=``,M.trigger(this._element,qe)},r=`scroll${t[0].toUpperCase()+t.slice(1)}`;this._queueCallback(n,this._element,!0),this._element.style[t]=`${this._element[r]}px`}hide(){if(this._isTransitioning||!this._isShown()||M.trigger(this._element,Je).defaultPrevented)return;let e=this._getDimension();this._element.style[e]=`${this._element.getBoundingClientRect()[e]}px`,m(this._element),this._element.classList.add($e),this._element.classList.remove(Qe,Ze);for(let e of this._triggerArray){let t=F.getElementFromSelector(e);t&&!this._isShown(t)&&this._addAriaAndCollapsedClass([e],!1)}this._isTransitioning=!0;let t=()=>{this._isTransitioning=!1,this._element.classList.remove($e),this._element.classList.add(Qe),M.trigger(this._element,Ye)};this._element.style[e]=``,this._queueCallback(t,this._element,!0)}_isShown(e=this._element){return e.classList.contains(Ze)}_configAfterMerge(e){return e.toggle=!!e.toggle,e.parent=l(e.parent),e}_getDimension(){return this._element.classList.contains(`collapse-horizontal`)?`width`:`height`}_initializeChildren(){if(!this._config.parent)return;let e=this._getFirstLevelChildren(tt);for(let t of e){let e=F.getElementFromSelector(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}}_getFirstLevelChildren(e){let t=F.find(et,this._config.parent);return F.find(e,this._config.parent).filter(e=>!t.includes(e))}_addAriaAndCollapsedClass(e,t){if(e.length)for(let n of e)n.classList.toggle(`collapsed`,!t),n.setAttribute(`aria-expanded`,t)}static jQueryInterface(e){let t={};return typeof e==`string`&&/show|hide/.test(e)&&(t.toggle=!1),this.each(function(){let n=it.getOrCreateInstance(this,t);if(typeof e==`string`){if(n[e]===void 0)throw TypeError(`No method named "${e}"`);n[e]()}})}}M.on(document,Xe,tt,function(e){(e.target.tagName===`A`||e.delegateTarget&&e.delegateTarget.tagName===`A`)&&e.preventDefault();for(let e of F.getMultipleElementsFromSelector(this))it.getOrCreateInstance(e,{toggle:!1}).toggle()}),y(it);var B=`top`,V=`bottom`,H=`right`,U=`left`,at=`auto`,ot=[B,V,H,U],st=`start`,ct=`end`,lt=`clippingParents`,ut=`viewport`,dt=`popper`,ft=`reference`,pt=ot.reduce(function(e,t){return e.concat([t+`-`+st,t+`-`+ct])},[]),mt=[].concat(ot,[at]).reduce(function(e,t){return e.concat([t,t+`-`+st,t+`-`+ct])},[]),ht=`beforeRead`,gt=`read`,_t=`afterRead`,vt=`beforeMain`,yt=`main`,bt=`afterMain`,xt=`beforeWrite`,St=`write`,Ct=`afterWrite`,wt=[ht,gt,_t,vt,yt,bt,xt,St,Ct];function W(e){return e?(e.nodeName||``).toLowerCase():null}function G(e){if(e==null)return window;if(e.toString()!==`[object Window]`){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function Tt(e){return e instanceof G(e).Element||e instanceof Element}function K(e){return e instanceof G(e).HTMLElement||e instanceof HTMLElement}function Et(e){return typeof ShadowRoot>`u`?!1:e instanceof G(e).ShadowRoot||e instanceof ShadowRoot}function Dt(e){var t=e.state;Object.keys(t.elements).forEach(function(e){var n=t.styles[e]||{},r=t.attributes[e]||{},i=t.elements[e];!K(i)||!W(i)||(Object.assign(i.style,n),Object.keys(r).forEach(function(e){var t=r[e];t===!1?i.removeAttribute(e):i.setAttribute(e,t===!0?``:t)}))})}function Ot(e){var t=e.state,n={popper:{position:t.options.strategy,left:`0`,top:`0`,margin:`0`},arrow:{position:`absolute`},reference:{}};return Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow),function(){Object.keys(t.elements).forEach(function(e){var r=t.elements[e],i=t.attributes[e]||{},a=Object.keys(t.styles.hasOwnProperty(e)?t.styles[e]:n[e]).reduce(function(e,t){return e[t]=``,e},{});!K(r)||!W(r)||(Object.assign(r.style,a),Object.keys(i).forEach(function(e){r.removeAttribute(e)}))})}}let kt={name:`applyStyles`,enabled:!0,phase:`write`,fn:Dt,effect:Ot,requires:[`computeStyles`]};function q(e){return e.split(`-`)[0]}var At=Math.max,jt=Math.min,Mt=Math.round;function Nt(){var e=navigator.userAgentData;return e!=null&&e.brands&&Array.isArray(e.brands)?e.brands.map(function(e){return e.brand+`/`+e.version}).join(` `):navigator.userAgent}function Pt(){return!/^((?!chrome|android).)*safari/i.test(Nt())}function Ft(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!1);var r=e.getBoundingClientRect(),i=1,a=1;t&&K(e)&&(i=e.offsetWidth>0&&Mt(r.width)/e.offsetWidth||1,a=e.offsetHeight>0&&Mt(r.height)/e.offsetHeight||1);var o=(Tt(e)?G(e):window).visualViewport,s=!Pt()&&n,c=(r.left+(s&&o?o.offsetLeft:0))/i,l=(r.top+(s&&o?o.offsetTop:0))/a,u=r.width/i,d=r.height/a;return{width:u,height:d,top:l,right:c+u,bottom:l+d,left:c,x:c,y:l}}function It(e){var t=Ft(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function Lt(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&Et(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function J(e){return G(e).getComputedStyle(e)}function Rt(e){return[`table`,`td`,`th`].indexOf(W(e))>=0}function Y(e){return((Tt(e)?e.ownerDocument:e.document)||window.document).documentElement}function zt(e){return W(e)===`html`?e:e.assignedSlot||e.parentNode||(Et(e)?e.host:null)||Y(e)}function Bt(e){return!K(e)||J(e).position===`fixed`?null:e.offsetParent}function Vt(e){var t=/firefox/i.test(Nt());if(/Trident/i.test(Nt())&&K(e)&&J(e).position===`fixed`)return null;var n=zt(e);for(Et(n)&&(n=n.host);K(n)&&[`html`,`body`].indexOf(W(n))<0;){var r=J(n);if(r.transform!==`none`||r.perspective!==`none`||r.contain===`paint`||[`transform`,`perspective`].indexOf(r.willChange)!==-1||t&&r.willChange===`filter`||t&&r.filter&&r.filter!==`none`)return n;n=n.parentNode}return null}function Ht(e){for(var t=G(e),n=Bt(e);n&&Rt(n)&&J(n).position===`static`;)n=Bt(n);return n&&(W(n)===`html`||W(n)===`body`&&J(n).position===`static`)?t:n||Vt(e)||t}function Ut(e){return[`top`,`bottom`].indexOf(e)>=0?`x`:`y`}function Wt(e,t,n){return At(e,jt(t,n))}function Gt(e,t,n){var r=Wt(e,t,n);return r>n?n:r}function Kt(){return{top:0,right:0,bottom:0,left:0}}function qt(e){return Object.assign({},Kt(),e)}function Jt(e,t){return t.reduce(function(t,n){return t[n]=e,t},{})}var Yt=function(e,t){return e=typeof e==`function`?e(Object.assign({},t.rects,{placement:t.placement})):e,qt(typeof e==`number`?Jt(e,ot):e)};function Xt(e){var t,n=e.state,r=e.name,i=e.options,a=n.elements.arrow,o=n.modifiersData.popperOffsets,s=q(n.placement),c=Ut(s),l=[U,H].indexOf(s)>=0?`height`:`width`;if(!(!a||!o)){var u=Yt(i.padding,n),d=It(a),f=c===`y`?B:U,p=c===`y`?V:H,m=n.rects.reference[l]+n.rects.reference[c]-o[c]-n.rects.popper[l],h=o[c]-n.rects.reference[c],g=Ht(a),_=g?c===`y`?g.clientHeight||0:g.clientWidth||0:0,v=m/2-h/2,y=u[f],b=_-d[l]-u[p],x=_/2-d[l]/2+v,S=Wt(y,x,b),C=c;n.modifiersData[r]=(t={},t[C]=S,t.centerOffset=S-x,t)}}function Zt(e){var t=e.state,n=e.options.element,r=n===void 0?`[data-popper-arrow]`:n;r!=null&&(typeof r==`string`&&(r=t.elements.popper.querySelector(r),!r)||Lt(t.elements.popper,r)&&(t.elements.arrow=r))}let Qt={name:`arrow`,enabled:!0,phase:`main`,fn:Xt,effect:Zt,requires:[`popperOffsets`],requiresIfExists:[`preventOverflow`]};function $t(e){return e.split(`-`)[1]}var en={top:`auto`,right:`auto`,bottom:`auto`,left:`auto`};function tn(e,t){var n=e.x,r=e.y,i=t.devicePixelRatio||1;return{x:Mt(n*i)/i||0,y:Mt(r*i)/i||0}}function nn(e){var t,n=e.popper,r=e.popperRect,i=e.placement,a=e.variation,o=e.offsets,s=e.position,c=e.gpuAcceleration,l=e.adaptive,u=e.roundOffsets,d=e.isFixed,f=o.x,p=f===void 0?0:f,m=o.y,h=m===void 0?0:m,g=typeof u==`function`?u({x:p,y:h}):{x:p,y:h};p=g.x,h=g.y;var _=o.hasOwnProperty(`x`),v=o.hasOwnProperty(`y`),y=U,b=B,x=window;if(l){var S=Ht(n),C=`clientHeight`,w=`clientWidth`;if(S===G(n)&&(S=Y(n),J(S).position!==`static`&&s===`absolute`&&(C=`scrollHeight`,w=`scrollWidth`)),S=S,i===B||(i===U||i===H)&&a===ct){b=V;var T=d&&S===x&&x.visualViewport?x.visualViewport.height:S[C];h-=T-r.height,h*=c?1:-1}if(i===U||(i===B||i===V)&&a===ct){y=H;var E=d&&S===x&&x.visualViewport?x.visualViewport.width:S[w];p-=E-r.width,p*=c?1:-1}}var D=Object.assign({position:s},l&&en),O=u===!0?tn({x:p,y:h},G(n)):{x:p,y:h};if(p=O.x,h=O.y,c){var k;return Object.assign({},D,(k={},k[b]=v?`0`:``,k[y]=_?`0`:``,k.transform=(x.devicePixelRatio||1)<=1?`translate(`+p+`px, `+h+`px)`:`translate3d(`+p+`px, `+h+`px, 0)`,k))}return Object.assign({},D,(t={},t[b]=v?h+`px`:``,t[y]=_?p+`px`:``,t.transform=``,t))}function rn(e){var t=e.state,n=e.options,r=n.gpuAcceleration,i=r===void 0?!0:r,a=n.adaptive,o=a===void 0?!0:a,s=n.roundOffsets,c=s===void 0?!0:s,l={placement:q(t.placement),variation:$t(t.placement),popper:t.elements.popper,popperRect:t.rects.popper,gpuAcceleration:i,isFixed:t.options.strategy===`fixed`};t.modifiersData.popperOffsets!=null&&(t.styles.popper=Object.assign({},t.styles.popper,nn(Object.assign({},l,{offsets:t.modifiersData.popperOffsets,position:t.options.strategy,adaptive:o,roundOffsets:c})))),t.modifiersData.arrow!=null&&(t.styles.arrow=Object.assign({},t.styles.arrow,nn(Object.assign({},l,{offsets:t.modifiersData.arrow,position:`absolute`,adaptive:!1,roundOffsets:c})))),t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-placement":t.placement})}let an={name:`computeStyles`,enabled:!0,phase:`beforeWrite`,fn:rn,data:{}};var on={passive:!0};function sn(e){var t=e.state,n=e.instance,r=e.options,i=r.scroll,a=i===void 0?!0:i,o=r.resize,s=o===void 0?!0:o,c=G(t.elements.popper),l=[].concat(t.scrollParents.reference,t.scrollParents.popper);return a&&l.forEach(function(e){e.addEventListener(`scroll`,n.update,on)}),s&&c.addEventListener(`resize`,n.update,on),function(){a&&l.forEach(function(e){e.removeEventListener(`scroll`,n.update,on)}),s&&c.removeEventListener(`resize`,n.update,on)}}let cn={name:`eventListeners`,enabled:!0,phase:`write`,fn:function(){},effect:sn,data:{}};var ln={left:`right`,right:`left`,bottom:`top`,top:`bottom`};function un(e){return e.replace(/left|right|bottom|top/g,function(e){return ln[e]})}var dn={start:`end`,end:`start`};function fn(e){return e.replace(/start|end/g,function(e){return dn[e]})}function pn(e){var t=G(e);return{scrollLeft:t.pageXOffset,scrollTop:t.pageYOffset}}function mn(e){return Ft(Y(e)).left+pn(e).scrollLeft}function hn(e,t){var n=G(e),r=Y(e),i=n.visualViewport,a=r.clientWidth,o=r.clientHeight,s=0,c=0;if(i){a=i.width,o=i.height;var l=Pt();(l||!l&&t===`fixed`)&&(s=i.offsetLeft,c=i.offsetTop)}return{width:a,height:o,x:s+mn(e),y:c}}function gn(e){var t=Y(e),n=pn(e),r=e.ownerDocument?.body,i=At(t.scrollWidth,t.clientWidth,r?r.scrollWidth:0,r?r.clientWidth:0),a=At(t.scrollHeight,t.clientHeight,r?r.scrollHeight:0,r?r.clientHeight:0),o=-n.scrollLeft+mn(e),s=-n.scrollTop;return J(r||t).direction===`rtl`&&(o+=At(t.clientWidth,r?r.clientWidth:0)-i),{width:i,height:a,x:o,y:s}}function _n(e){var t=J(e),n=t.overflow,r=t.overflowX,i=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+i+r)}function vn(e){return[`html`,`body`,`#document`].indexOf(W(e))>=0?e.ownerDocument.body:K(e)&&_n(e)?e:vn(zt(e))}function yn(e,t){t===void 0&&(t=[]);var n=vn(e),r=n===e.ownerDocument?.body,i=G(n),a=r?[i].concat(i.visualViewport||[],_n(n)?n:[]):n,o=t.concat(a);return r?o:o.concat(yn(zt(a)))}function bn(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function xn(e,t){var n=Ft(e,!1,t===`fixed`);return n.top+=e.clientTop,n.left+=e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}function Sn(e,t,n){return t===ut?bn(hn(e,n)):Tt(t)?xn(t,n):bn(gn(Y(e)))}function Cn(e){var t=yn(zt(e)),n=[`absolute`,`fixed`].indexOf(J(e).position)>=0&&K(e)?Ht(e):e;return Tt(n)?t.filter(function(e){return Tt(e)&&Lt(e,n)&&W(e)!==`body`}):[]}function wn(e,t,n,r){var i=t===`clippingParents`?Cn(e):[].concat(t),a=[].concat(i,[n]),o=a[0],s=a.reduce(function(t,n){var i=Sn(e,n,r);return t.top=At(i.top,t.top),t.right=jt(i.right,t.right),t.bottom=jt(i.bottom,t.bottom),t.left=At(i.left,t.left),t},Sn(e,o,r));return s.width=s.right-s.left,s.height=s.bottom-s.top,s.x=s.left,s.y=s.top,s}function Tn(e){var t=e.reference,n=e.element,r=e.placement,i=r?q(r):null,a=r?$t(r):null,o=t.x+t.width/2-n.width/2,s=t.y+t.height/2-n.height/2,c;switch(i){case B:c={x:o,y:t.y-n.height};break;case V:c={x:o,y:t.y+t.height};break;case H:c={x:t.x+t.width,y:s};break;case U:c={x:t.x-n.width,y:s};break;default:c={x:t.x,y:t.y}}var l=i?Ut(i):null;if(l!=null){var u=l===`y`?`height`:`width`;switch(a){case st:c[l]=c[l]-(t[u]/2-n[u]/2);break;case ct:c[l]=c[l]+(t[u]/2-n[u]/2);break}}return c}function En(e,t){t===void 0&&(t={});var n=t,r=n.placement,i=r===void 0?e.placement:r,a=n.strategy,o=a===void 0?e.strategy:a,s=n.boundary,c=s===void 0?lt:s,l=n.rootBoundary,u=l===void 0?ut:l,d=n.elementContext,f=d===void 0?dt:d,p=n.altBoundary,m=p===void 0?!1:p,h=n.padding,g=h===void 0?0:h,_=qt(typeof g==`number`?Jt(g,ot):g),v=f===dt?ft:dt,y=e.rects.popper,b=e.elements[m?v:f],x=wn(Tt(b)?b:b.contextElement||Y(e.elements.popper),c,u,o),S=Ft(e.elements.reference),C=Tn({reference:S,element:y,placement:i}),w=bn(Object.assign({},y,C)),T=f===dt?w:S,E={top:x.top-T.top+_.top,bottom:T.bottom-x.bottom+_.bottom,left:x.left-T.left+_.left,right:T.right-x.right+_.right},D=e.modifiersData.offset;if(f===dt&&D){var O=D[i];Object.keys(E).forEach(function(e){var t=[H,V].indexOf(e)>=0?1:-1,n=[B,V].indexOf(e)>=0?`y`:`x`;E[e]+=O[n]*t})}return E}function Dn(e,t){t===void 0&&(t={});var n=t,r=n.placement,i=n.boundary,a=n.rootBoundary,o=n.padding,s=n.flipVariations,c=n.allowedAutoPlacements,l=c===void 0?mt:c,u=$t(r),d=u?s?pt:pt.filter(function(e){return $t(e)===u}):ot,f=d.filter(function(e){return l.indexOf(e)>=0});f.length===0&&(f=d);var p=f.reduce(function(t,n){return t[n]=En(e,{placement:n,boundary:i,rootBoundary:a,padding:o})[q(n)],t},{});return Object.keys(p).sort(function(e,t){return p[e]-p[t]})}function On(e){if(q(e)===at)return[];var t=un(e);return[fn(e),t,fn(t)]}function kn(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var i=n.mainAxis,a=i===void 0?!0:i,o=n.altAxis,s=o===void 0?!0:o,c=n.fallbackPlacements,l=n.padding,u=n.boundary,d=n.rootBoundary,f=n.altBoundary,p=n.flipVariations,m=p===void 0?!0:p,h=n.allowedAutoPlacements,g=t.options.placement,_=q(g)===g,v=c||(_||!m?[un(g)]:On(g)),y=[g].concat(v).reduce(function(e,n){return e.concat(q(n)===at?Dn(t,{placement:n,boundary:u,rootBoundary:d,padding:l,flipVariations:m,allowedAutoPlacements:h}):n)},[]),b=t.rects.reference,x=t.rects.popper,S=new Map,C=!0,w=y[0],T=0;T=0,ee=k?`width`:`height`,A=En(t,{placement:E,boundary:u,rootBoundary:d,altBoundary:f,padding:l}),j=k?O?H:U:O?V:B;b[ee]>x[ee]&&(j=un(j));var te=un(j),ne=[];if(a&&ne.push(A[D]<=0),s&&ne.push(A[j]<=0,A[te]<=0),ne.every(function(e){return e})){w=E,C=!1;break}S.set(E,ne)}if(C)for(var re=m?3:1,ie=function(e){var t=y.find(function(t){var n=S.get(t);if(n)return n.slice(0,e).every(function(e){return e})});if(t)return w=t,`break`},ae=re;ae>0&&ie(ae)!==`break`;ae--);t.placement!==w&&(t.modifiersData[r]._skip=!0,t.placement=w,t.reset=!0)}}let An={name:`flip`,enabled:!0,phase:`main`,fn:kn,requiresIfExists:[`offset`],data:{_skip:!1}};function jn(e,t,n){return n===void 0&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function Mn(e){return[B,H,V,U].some(function(t){return e[t]>=0})}function Nn(e){var t=e.state,n=e.name,r=t.rects.reference,i=t.rects.popper,a=t.modifiersData.preventOverflow,o=En(t,{elementContext:`reference`}),s=En(t,{altBoundary:!0}),c=jn(o,r),l=jn(s,i,a),u=Mn(c),d=Mn(l);t.modifiersData[n]={referenceClippingOffsets:c,popperEscapeOffsets:l,isReferenceHidden:u,hasPopperEscaped:d},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":u,"data-popper-escaped":d})}let Pn={name:`hide`,enabled:!0,phase:`main`,requiresIfExists:[`preventOverflow`],fn:Nn};function Fn(e,t,n){var r=q(e),i=[U,B].indexOf(r)>=0?-1:1,a=typeof n==`function`?n(Object.assign({},t,{placement:e})):n,o=a[0],s=a[1];return o||=0,s=(s||0)*i,[U,H].indexOf(r)>=0?{x:s,y:o}:{x:o,y:s}}function In(e){var t=e.state,n=e.options,r=e.name,i=n.offset,a=i===void 0?[0,0]:i,o=mt.reduce(function(e,n){return e[n]=Fn(n,t.rects,a),e},{}),s=o[t.placement],c=s.x,l=s.y;t.modifiersData.popperOffsets!=null&&(t.modifiersData.popperOffsets.x+=c,t.modifiersData.popperOffsets.y+=l),t.modifiersData[r]=o}let Ln={name:`offset`,enabled:!0,phase:`main`,requires:[`popperOffsets`],fn:In};function Rn(e){var t=e.state,n=e.name;t.modifiersData[n]=Tn({reference:t.rects.reference,element:t.rects.popper,placement:t.placement})}let zn={name:`popperOffsets`,enabled:!0,phase:`read`,fn:Rn,data:{}};function Bn(e){return e===`x`?`y`:`x`}function Vn(e){var t=e.state,n=e.options,r=e.name,i=n.mainAxis,a=i===void 0?!0:i,o=n.altAxis,s=o===void 0?!1:o,c=n.boundary,l=n.rootBoundary,u=n.altBoundary,d=n.padding,f=n.tether,p=f===void 0?!0:f,m=n.tetherOffset,h=m===void 0?0:m,g=En(t,{boundary:c,rootBoundary:l,padding:d,altBoundary:u}),_=q(t.placement),v=$t(t.placement),y=!v,b=Ut(_),x=Bn(b),S=t.modifiersData.popperOffsets,C=t.rects.reference,w=t.rects.popper,T=typeof h==`function`?h(Object.assign({},t.rects,{placement:t.placement})):h,E=typeof T==`number`?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),D=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,O={x:0,y:0};if(S){if(a){var k=b===`y`?B:U,ee=b===`y`?V:H,A=b===`y`?`height`:`width`,j=S[b],te=j+g[k],ne=j-g[ee],re=p?-w[A]/2:0,ie=v===st?C[A]:w[A],ae=v===st?-w[A]:-C[A],oe=t.elements.arrow,se=p&&oe?It(oe):{width:0,height:0},M=t.modifiersData[`arrow#persistent`]?t.modifiersData[`arrow#persistent`].padding:Kt(),ce=M[k],le=M[ee],ue=Wt(0,C[A],se[A]),N=y?C[A]/2-re-ue-ce-E.mainAxis:ie-ue-ce-E.mainAxis,de=y?-C[A]/2+re+ue+le+E.mainAxis:ae+ue+le+E.mainAxis,P=t.elements.arrow&&Ht(t.elements.arrow),fe=P?b===`y`?P.clientTop||0:P.clientLeft||0:0,F=D?.[b]??0,pe=j+N-F-fe,me=j+de-F,he=Wt(p?jt(te,pe):te,j,p?At(ne,me):ne);S[b]=he,O[b]=he-j}if(s){var ge=b===`x`?B:U,_e=b===`x`?V:H,I=S[x],L=x===`y`?`height`:`width`,R=I+g[ge],ve=I-g[_e],ye=[B,U].indexOf(_)!==-1,be=D?.[x]??0,xe=ye?R:I-C[L]-w[L]-be+E.altAxis,Se=ye?I+C[L]+w[L]-be-E.altAxis:ve,Ce=p&&ye?Gt(xe,I,Se):Wt(p?xe:R,I,p?Se:ve);S[x]=Ce,O[x]=Ce-I}t.modifiersData[r]=O}}let Hn={name:`preventOverflow`,enabled:!0,phase:`main`,fn:Vn,requiresIfExists:[`offset`]};function Un(e){return{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}}function Wn(e){return e===G(e)||!K(e)?pn(e):Un(e)}function Gn(e){var t=e.getBoundingClientRect(),n=Mt(t.width)/e.offsetWidth||1,r=Mt(t.height)/e.offsetHeight||1;return n!==1||r!==1}function Kn(e,t,n){n===void 0&&(n=!1);var r=K(t),i=K(t)&&Gn(t),a=Y(t),o=Ft(e,i,n),s={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(r||!r&&!n)&&((W(t)!==`body`||_n(a))&&(s=Wn(t)),K(t)?(c=Ft(t,!0),c.x+=t.clientLeft,c.y+=t.clientTop):a&&(c.x=mn(a))),{x:o.left+s.scrollLeft-c.x,y:o.top+s.scrollTop-c.y,width:o.width,height:o.height}}function qn(e){var t=new Map,n=new Set,r=[];e.forEach(function(e){t.set(e.name,e)});function i(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach(function(e){if(!n.has(e)){var r=t.get(e);r&&i(r)}}),r.push(e)}return e.forEach(function(e){n.has(e.name)||i(e)}),r}function Jn(e){var t=qn(e);return wt.reduce(function(e,n){return e.concat(t.filter(function(e){return e.phase===n}))},[])}function Yn(e){var t;return function(){return t||=new Promise(function(n){Promise.resolve().then(function(){t=void 0,n(e())})}),t}}function Xn(e){var t=e.reduce(function(e,t){var n=e[t.name];return e[t.name]=n?Object.assign({},n,t,{options:Object.assign({},n.options,t.options),data:Object.assign({},n.data,t.data)}):t,e},{});return Object.keys(t).map(function(e){return t[e]})}var Zn={placement:`bottom`,modifiers:[],strategy:`absolute`};function Qn(){return![...arguments].some(function(e){return!(e&&typeof e.getBoundingClientRect==`function`)})}function $n(e){e===void 0&&(e={});var t=e,n=t.defaultModifiers,r=n===void 0?[]:n,i=t.defaultOptions,a=i===void 0?Zn:i;return function(e,t,n){n===void 0&&(n=a);var i={placement:`bottom`,orderedModifiers:[],options:Object.assign({},Zn,a),modifiersData:{},elements:{reference:e,popper:t},attributes:{},styles:{}},o=[],s=!1,c={state:i,setOptions:function(n){var o=typeof n==`function`?n(i.options):n;u(),i.options=Object.assign({},a,i.options,o),i.scrollParents={reference:Tt(e)?yn(e):e.contextElement?yn(e.contextElement):[],popper:yn(t)};var s=Jn(Xn([].concat(r,i.options.modifiers)));return i.orderedModifiers=s.filter(function(e){return e.enabled}),l(),c.update()},forceUpdate:function(){if(!s){var e=i.elements,t=e.reference,n=e.popper;if(Qn(t,n)){i.rects={reference:Kn(t,Ht(n),i.options.strategy===`fixed`),popper:It(n)},i.reset=!1,i.placement=i.options.placement,i.orderedModifiers.forEach(function(e){return i.modifiersData[e.name]=Object.assign({},e.data)});for(var r=0;rNumber.parseInt(e,10)):typeof e==`function`?t=>e(t,this._element):e}_getPopperConfig(){let e={placement:this._getPlacement(),modifiers:[{name:`preventOverflow`,options:{boundary:this._config.boundary}},{name:`offset`,options:{offset:this._getOffset()}}]};return(this._inNavbar||this._config.display===`static`)&&(N.setDataAttribute(this._menu,`popper`,`static`),e.modifiers=[{name:`applyStyles`,enabled:!1}]),{...e,...b(this._config.popperConfig,[void 0,e])}}_selectMenuItem({key:e,target:t}){let n=F.find(`.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)`,this._menu).filter(e=>u(e));n.length&&S(n,t,e===or,!n.includes(t)).focus()}static jQueryInterface(e){return this.each(function(){let t=X.getOrCreateInstance(this,e);if(typeof e==`string`){if(t[e]===void 0)throw TypeError(`No method named "${e}"`);t[e]()}})}static clearMenus(e){if(e.button===2||e.type===`keyup`&&e.key!==`Tab`)return;let t=F.find(gr);for(let n of t){let t=X.getInstance(n);if(!t||t._config.autoClose===!1)continue;let r=e.composedPath(),i=r.includes(t._menu);if(r.includes(t._element)||t._config.autoClose===`inside`&&!i||t._config.autoClose===`outside`&&i||t._menu.contains(e.target)&&(e.type===`keyup`&&e.key===`Tab`||/input|select|option|textarea|form/i.test(e.target.tagName)))continue;let a={relatedTarget:t._element};e.type===`click`&&(a.clickEvent=e),t._completeHide(a)}}static dataApiKeydownHandler(e){let t=/input|textarea/i.test(e.target.tagName),n=e.key===`Escape`,r=[`ArrowUp`,or].includes(e.key);if(!r&&!n||t&&!n)return;e.preventDefault();let i=this.matches(hr)?this:F.prev(this,hr)[0]||F.next(this,hr)[0]||F.findOne(hr,e.delegateTarget.parentNode),a=X.getOrCreateInstance(i);if(r){e.stopPropagation(),a.show(),a._selectMenuItem(e);return}a._isShown()&&(e.stopPropagation(),a.hide(),i.focus())}}M.on(document,fr,hr,X.dataApiKeydownHandler),M.on(document,fr,_r,X.dataApiKeydownHandler),M.on(document,dr,X.clearMenus),M.on(document,pr,X.clearMenus),M.on(document,dr,hr,function(e){e.preventDefault(),X.getOrCreateInstance(this).toggle()}),y(X);let Er=`backdrop`,Dr=`show`,Or=`mousedown.bs.${Er}`,kr={className:`modal-backdrop`,clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:`body`},Ar={className:`string`,clickCallback:`(function|null)`,isAnimated:`boolean`,isVisible:`boolean`,rootElement:`(element|string)`};class jr extends de{constructor(e){super(),this._config=this._getConfig(e),this._isAppended=!1,this._element=null}static get Default(){return kr}static get DefaultType(){return Ar}static get NAME(){return Er}show(e){if(!this._config.isVisible){b(e);return}this._append();let t=this._getElement();this._config.isAnimated&&m(t),t.classList.add(Dr),this._emulateAnimation(()=>{b(e)})}hide(e){if(!this._config.isVisible){b(e);return}this._getElement().classList.remove(Dr),this._emulateAnimation(()=>{this.dispose(),b(e)})}dispose(){this._isAppended&&=(M.off(this._element,Or),this._element.remove(),!1)}_getElement(){if(!this._element){let e=document.createElement(`div`);e.className=this._config.className,this._config.isAnimated&&e.classList.add(`fade`),this._element=e}return this._element}_configAfterMerge(e){return e.rootElement=l(e.rootElement),e}_append(){if(this._isAppended)return;let e=this._getElement();this._config.rootElement.append(e),M.on(e,Or,()=>{b(this._config.clickCallback)}),this._isAppended=!0}_emulateAnimation(e){x(e,this._getElement(),this._config.isAnimated)}}let Mr=`.bs.focustrap`,Nr=`focusin${Mr}`,Pr=`keydown.tab${Mr}`,Fr=`backward`,Ir={autofocus:!0,trapElement:null},Lr={autofocus:`boolean`,trapElement:`element`};class Rr extends de{constructor(e){super(),this._config=this._getConfig(e),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return Ir}static get DefaultType(){return Lr}static get NAME(){return`focustrap`}activate(){this._isActive||=(this._config.autofocus&&this._config.trapElement.focus(),M.off(document,Mr),M.on(document,Nr,e=>this._handleFocusin(e)),M.on(document,Pr,e=>this._handleKeydown(e)),!0)}deactivate(){this._isActive&&(this._isActive=!1,M.off(document,Mr))}_handleFocusin(e){let{trapElement:t}=this._config;if(e.target===document||e.target===t||t.contains(e.target))return;let n=F.focusableChildren(t);n.length===0?t.focus():this._lastTabNavDirection===Fr?n[n.length-1].focus():n[0].focus()}_handleKeydown(e){e.key===`Tab`&&(this._lastTabNavDirection=e.shiftKey?Fr:`forward`)}}let zr=`.fixed-top, .fixed-bottom, .is-fixed, .sticky-top`,Br=`.sticky-top`,Vr=`padding-right`,Hr=`margin-right`;class Ur{constructor(){this._element=document.body}getWidth(){let e=document.documentElement.clientWidth;return Math.abs(window.innerWidth-e)}hide(){let e=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,Vr,t=>t+e),this._setElementAttributes(zr,Vr,t=>t+e),this._setElementAttributes(Br,Hr,t=>t-e)}reset(){this._resetElementAttributes(this._element,`overflow`),this._resetElementAttributes(this._element,Vr),this._resetElementAttributes(zr,Vr),this._resetElementAttributes(Br,Hr)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,`overflow`),this._element.style.overflow=`hidden`}_setElementAttributes(e,t,n){let r=this.getWidth();this._applyManipulationCallback(e,e=>{if(e!==this._element&&window.innerWidth>e.clientWidth+r)return;this._saveInitialAttribute(e,t);let i=window.getComputedStyle(e).getPropertyValue(t);e.style.setProperty(t,`${n(Number.parseFloat(i))}px`)})}_saveInitialAttribute(e,t){let n=e.style.getPropertyValue(t);n&&N.setDataAttribute(e,t,n)}_resetElementAttributes(e,t){this._applyManipulationCallback(e,e=>{let n=N.getDataAttribute(e,t);if(n===null){e.style.removeProperty(t);return}N.removeDataAttribute(e,t),e.style.setProperty(t,n)})}_applyManipulationCallback(e,t){if(c(e)){t(e);return}for(let n of F.find(e,this._element))t(n)}}let Z=`.bs.modal`,Wr=`hide${Z}`,Gr=`hidePrevented${Z}`,Kr=`hidden${Z}`,qr=`show${Z}`,Jr=`shown${Z}`,Yr=`resize${Z}`,Xr=`click.dismiss${Z}`,Zr=`mousedown.dismiss${Z}`,Qr=`keydown.dismiss${Z}`,$r=`click${Z}.data-api`,ei=`modal-open`,ti=`show`,ni=`modal-static`,ri={backdrop:!0,focus:!0,keyboard:!0},ii={backdrop:`(boolean|string)`,focus:`boolean`,keyboard:`boolean`};class ai extends P{constructor(e,t){super(e,t),this._dialog=F.findOne(`.modal-dialog`,this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new Ur,this._addEventListeners()}static get Default(){return ri}static get DefaultType(){return ii}static get NAME(){return`modal`}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){this._isShown||this._isTransitioning||M.trigger(this._element,qr,{relatedTarget:e}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(ei),this._adjustDialog(),this._backdrop.show(()=>this._showElement(e)))}hide(){!this._isShown||this._isTransitioning||M.trigger(this._element,Wr).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(ti),this._queueCallback(()=>this._hideModal(),this._element,this._isAnimated()))}dispose(){M.off(window,Z),M.off(this._dialog,Z),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new jr({isVisible:!!this._config.backdrop,isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Rr({trapElement:this._element})}_showElement(e){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display=`block`,this._element.removeAttribute(`aria-hidden`),this._element.setAttribute(`aria-modal`,!0),this._element.setAttribute(`role`,`dialog`),this._element.scrollTop=0;let t=F.findOne(`.modal-body`,this._dialog);t&&(t.scrollTop=0),m(this._element),this._element.classList.add(ti),this._queueCallback(()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,M.trigger(this._element,Jr,{relatedTarget:e})},this._dialog,this._isAnimated())}_addEventListeners(){M.on(this._element,Qr,e=>{if(e.key===`Escape`){if(this._config.keyboard){this.hide();return}this._triggerBackdropTransition()}}),M.on(window,Yr,()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()}),M.on(this._element,Zr,e=>{M.one(this._element,Xr,t=>{if(!(this._element!==e.target||this._element!==t.target)){if(this._config.backdrop===`static`){this._triggerBackdropTransition();return}this._config.backdrop&&this.hide()}})})}_hideModal(){this._element.style.display=`none`,this._element.setAttribute(`aria-hidden`,!0),this._element.removeAttribute(`aria-modal`),this._element.removeAttribute(`role`),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove(ei),this._resetAdjustments(),this._scrollBar.reset(),M.trigger(this._element,Kr)})}_isAnimated(){return this._element.classList.contains(`fade`)}_triggerBackdropTransition(){if(M.trigger(this._element,Gr).defaultPrevented)return;let e=this._element.scrollHeight>document.documentElement.clientHeight,t=this._element.style.overflowY;t===`hidden`||this._element.classList.contains(ni)||(e||(this._element.style.overflowY=`hidden`),this._element.classList.add(ni),this._queueCallback(()=>{this._element.classList.remove(ni),this._queueCallback(()=>{this._element.style.overflowY=t},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){let e=this._element.scrollHeight>document.documentElement.clientHeight,t=this._scrollBar.getWidth(),n=t>0;if(n&&!e){let e=v()?`paddingLeft`:`paddingRight`;this._element.style[e]=`${t}px`}if(!n&&e){let e=v()?`paddingRight`:`paddingLeft`;this._element.style[e]=`${t}px`}}_resetAdjustments(){this._element.style.paddingLeft=``,this._element.style.paddingRight=``}static jQueryInterface(e,t){return this.each(function(){let n=ai.getOrCreateInstance(this,e);if(typeof e==`string`){if(n[e]===void 0)throw TypeError(`No method named "${e}"`);n[e](t)}})}}M.on(document,$r,`[data-bs-toggle="modal"]`,function(e){let t=F.getElementFromSelector(this);[`A`,`AREA`].includes(this.tagName)&&e.preventDefault(),M.one(t,qr,e=>{e.defaultPrevented||M.one(t,Kr,()=>{u(this)&&this.focus()})});let n=F.findOne(`.modal.show`);n&&ai.getInstance(n).hide(),ai.getOrCreateInstance(t).toggle(this)}),pe(ai),y(ai);let Q=`.bs.offcanvas`,oi=`.data-api`,si=`load${Q}${oi}`,ci=`show`,li=`showing`,ui=`hiding`,di=`.offcanvas.show`,fi=`show${Q}`,pi=`shown${Q}`,mi=`hide${Q}`,hi=`hidePrevented${Q}`,gi=`hidden${Q}`,_i=`resize${Q}`,vi=`click${Q}${oi}`,yi=`keydown.dismiss${Q}`,bi={backdrop:!0,keyboard:!0,scroll:!1},xi={backdrop:`(boolean|string)`,keyboard:`boolean`,scroll:`boolean`};class Si extends P{constructor(e,t){super(e,t),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return bi}static get DefaultType(){return xi}static get NAME(){return`offcanvas`}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){this._isShown||M.trigger(this._element,fi,{relatedTarget:e}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||new Ur().hide(),this._element.setAttribute(`aria-modal`,!0),this._element.setAttribute(`role`,`dialog`),this._element.classList.add(li),this._queueCallback(()=>{(!this._config.scroll||this._config.backdrop)&&this._focustrap.activate(),this._element.classList.add(ci),this._element.classList.remove(li),M.trigger(this._element,pi,{relatedTarget:e})},this._element,!0))}hide(){!this._isShown||M.trigger(this._element,mi).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(ui),this._backdrop.hide(),this._queueCallback(()=>{this._element.classList.remove(ci,ui),this._element.removeAttribute(`aria-modal`),this._element.removeAttribute(`role`),this._config.scroll||new Ur().reset(),M.trigger(this._element,gi)},this._element,!0))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){let e=()=>{if(this._config.backdrop===`static`){M.trigger(this._element,hi);return}this.hide()},t=!!this._config.backdrop;return new jr({className:`offcanvas-backdrop`,isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?e:null})}_initializeFocusTrap(){return new Rr({trapElement:this._element})}_addEventListeners(){M.on(this._element,yi,e=>{if(e.key===`Escape`){if(this._config.keyboard){this.hide();return}M.trigger(this._element,hi)}})}static jQueryInterface(e){return this.each(function(){let t=Si.getOrCreateInstance(this,e);if(typeof e==`string`){if(t[e]===void 0||e.startsWith(`_`)||e===`constructor`)throw TypeError(`No method named "${e}"`);t[e](this)}})}}M.on(document,vi,`[data-bs-toggle="offcanvas"]`,function(e){let t=F.getElementFromSelector(this);if([`A`,`AREA`].includes(this.tagName)&&e.preventDefault(),d(this))return;M.one(t,gi,()=>{u(this)&&this.focus()});let n=F.findOne(di);n&&n!==t&&Si.getInstance(n).hide(),Si.getOrCreateInstance(t).toggle(this)}),M.on(window,si,()=>{for(let e of F.find(di))Si.getOrCreateInstance(e).show()}),M.on(window,_i,()=>{for(let e of F.find(`[aria-modal][class*=show][class*=offcanvas-]`))getComputedStyle(e).position!==`fixed`&&Si.getOrCreateInstance(e).hide()}),pe(Si),y(Si);let Ci={"*":[`class`,`dir`,`id`,`lang`,`role`,/^aria-[\w-]*$/i],a:[`target`,`href`,`title`,`rel`],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:[`src`,`srcset`,`alt`,`title`,`width`,`height`],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},wi=new Set([`background`,`cite`,`href`,`itemtype`,`longdesc`,`poster`,`src`,`xlink:href`]),Ti=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Ei=(e,t)=>{let n=e.nodeName.toLowerCase();return t.includes(n)?wi.has(n)?!!Ti.test(e.nodeValue):!0:t.filter(e=>e instanceof RegExp).some(e=>e.test(n))};function Di(e,t,n){if(!e.length)return e;if(n&&typeof n==`function`)return n(e);let r=new window.DOMParser().parseFromString(e,`text/html`),i=[].concat(...r.body.querySelectorAll(`*`));for(let e of i){let n=e.nodeName.toLowerCase();if(!Object.keys(t).includes(n)){e.remove();continue}let r=[].concat(...e.attributes),i=[].concat(t[`*`]||[],t[n]||[]);for(let t of r)Ei(t,i)||e.removeAttribute(t.nodeName)}return r.body.innerHTML}let Oi={allowList:Ci,content:{},extraClass:``,html:!1,sanitize:!0,sanitizeFn:null,template:`
`},ki={allowList:`object`,content:`object`,extraClass:`(string|function)`,html:`boolean`,sanitize:`boolean`,sanitizeFn:`(null|function)`,template:`string`},Ai={entry:`(string|element|function|null)`,selector:`(string|element)`};class ji extends de{constructor(e){super(),this._config=this._getConfig(e)}static get Default(){return Oi}static get DefaultType(){return ki}static get NAME(){return`TemplateFactory`}getContent(){return Object.values(this._config.content).map(e=>this._resolvePossibleFunction(e)).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(e){return this._checkContent(e),this._config.content={...this._config.content,...e},this}toHtml(){let e=document.createElement(`div`);e.innerHTML=this._maybeSanitize(this._config.template);for(let[t,n]of Object.entries(this._config.content))this._setContent(e,n,t);let t=e.children[0],n=this._resolvePossibleFunction(this._config.extraClass);return n&&t.classList.add(...n.split(` `)),t}_typeCheckConfig(e){super._typeCheckConfig(e),this._checkContent(e.content)}_checkContent(e){for(let[t,n]of Object.entries(e))super._typeCheckConfig({selector:t,entry:n},Ai)}_setContent(e,t,n){let r=F.findOne(n,e);if(r){if(t=this._resolvePossibleFunction(t),!t){r.remove();return}if(c(t)){this._putElementInTemplate(l(t),r);return}if(this._config.html){r.innerHTML=this._maybeSanitize(t);return}r.textContent=t}}_maybeSanitize(e){return this._config.sanitize?Di(e,this._config.allowList,this._config.sanitizeFn):e}_resolvePossibleFunction(e){return b(e,[void 0,this])}_putElementInTemplate(e,t){if(this._config.html){t.innerHTML=``,t.append(e);return}t.textContent=e.textContent}}let Mi=new Set([`sanitize`,`allowList`,`sanitizeFn`]),Ni=`fade`,Pi=`show`,Fi=`.modal`,Ii=`hide.bs.modal`,Li=`hover`,Ri=`focus`,zi=`click`,Bi={AUTO:`auto`,TOP:`top`,RIGHT:v()?`left`:`right`,BOTTOM:`bottom`,LEFT:v()?`right`:`left`},Vi={allowList:Ci,animation:!0,boundary:`clippingParents`,container:!1,customClass:``,delay:0,fallbackPlacements:[`top`,`right`,`bottom`,`left`],html:!1,offset:[0,6],placement:`top`,popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:``,title:``,trigger:`hover focus`},Hi={allowList:`object`,animation:`boolean`,boundary:`(string|element)`,container:`(string|element|boolean)`,customClass:`(string|function)`,delay:`(number|object)`,fallbackPlacements:`array`,html:`boolean`,offset:`(array|string|function)`,placement:`(string|function)`,popperConfig:`(null|object|function)`,sanitize:`boolean`,sanitizeFn:`(null|function)`,selector:`(string|boolean)`,template:`string`,title:`(string|element|function)`,trigger:`string`};class Ui extends P{constructor(e,t){if(rr===void 0)throw TypeError(`Bootstrap's tooltips require Popper (https://popper.js.org/docs/v2/)`);super(e,t),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return Vi}static get DefaultType(){return Hi}static get NAME(){return`tooltip`}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){if(this._isEnabled){if(this._isShown()){this._leave();return}this._enter()}}dispose(){clearTimeout(this._timeout),M.off(this._element.closest(Fi),Ii,this._hideModalHandler),this._element.getAttribute(`data-bs-original-title`)&&this._element.setAttribute(`title`,this._element.getAttribute(`data-bs-original-title`)),this._disposePopper(),super.dispose()}show(){if(this._element.style.display===`none`)throw Error(`Please use show on visible elements`);if(!(this._isWithContent()&&this._isEnabled))return;let e=M.trigger(this._element,this.constructor.eventName(`show`)),t=(f(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(e.defaultPrevented||!t)return;this._disposePopper();let n=this._getTipElement();this._element.setAttribute(`aria-describedby`,n.getAttribute(`id`));let{container:r}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(r.append(n),M.trigger(this._element,this.constructor.eventName(`inserted`))),this._popper=this._createPopper(n),n.classList.add(Pi),`ontouchstart`in document.documentElement)for(let e of[].concat(...document.body.children))M.on(e,`mouseover`,p);this._queueCallback(()=>{M.trigger(this._element,this.constructor.eventName(`shown`)),this._isHovered===!1&&this._leave(),this._isHovered=!1},this.tip,this._isAnimated())}hide(){if(!(!this._isShown()||M.trigger(this._element,this.constructor.eventName(`hide`)).defaultPrevented)){if(this._getTipElement().classList.remove(Pi),`ontouchstart`in document.documentElement)for(let e of[].concat(...document.body.children))M.off(e,`mouseover`,p);this._activeTrigger[zi]=!1,this._activeTrigger[Ri]=!1,this._activeTrigger[Li]=!1,this._isHovered=null,this._queueCallback(()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute(`aria-describedby`),M.trigger(this._element,this.constructor.eventName(`hidden`)))},this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return!!this._getTitle()}_getTipElement(){return this.tip||=this._createTipElement(this._newContent||this._getContentForTemplate()),this.tip}_createTipElement(e){let t=this._getTemplateFactory(e).toHtml();if(!t)return null;t.classList.remove(Ni,Pi),t.classList.add(`bs-${this.constructor.NAME}-auto`);let n=a(this.constructor.NAME).toString();return t.setAttribute(`id`,n),this._isAnimated()&&t.classList.add(Ni),t}setContent(e){this._newContent=e,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(e){return this._templateFactory?this._templateFactory.changeContent(e):this._templateFactory=new ji({...this._config,content:e,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute(`data-bs-original-title`)}_initializeOnDelegatedTarget(e){return this.constructor.getOrCreateInstance(e.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(Ni)}_isShown(){return this.tip&&this.tip.classList.contains(Pi)}_createPopper(e){let t=b(this._config.placement,[this,e,this._element]),n=Bi[t.toUpperCase()];return nr(this._element,e,this._getPopperConfig(n))}_getOffset(){let{offset:e}=this._config;return typeof e==`string`?e.split(`,`).map(e=>Number.parseInt(e,10)):typeof e==`function`?t=>e(t,this._element):e}_resolvePossibleFunction(e){return b(e,[this._element,this._element])}_getPopperConfig(e){let t={placement:e,modifiers:[{name:`flip`,options:{fallbackPlacements:this._config.fallbackPlacements}},{name:`offset`,options:{offset:this._getOffset()}},{name:`preventOverflow`,options:{boundary:this._config.boundary}},{name:`arrow`,options:{element:`.${this.constructor.NAME}-arrow`}},{name:`preSetPlacement`,enabled:!0,phase:`beforeMain`,fn:e=>{this._getTipElement().setAttribute(`data-popper-placement`,e.state.placement)}}]};return{...t,...b(this._config.popperConfig,[void 0,t])}}_setListeners(){let e=this._config.trigger.split(` `);for(let t of e)if(t===`click`)M.on(this._element,this.constructor.eventName(`click`),this._config.selector,e=>{let t=this._initializeOnDelegatedTarget(e);t._activeTrigger[zi]=!(t._isShown()&&t._activeTrigger[zi]),t.toggle()});else if(t!==`manual`){let e=t===Li?this.constructor.eventName(`mouseenter`):this.constructor.eventName(`focusin`),n=t===Li?this.constructor.eventName(`mouseleave`):this.constructor.eventName(`focusout`);M.on(this._element,e,this._config.selector,e=>{let t=this._initializeOnDelegatedTarget(e);t._activeTrigger[e.type===`focusin`?Ri:Li]=!0,t._enter()}),M.on(this._element,n,this._config.selector,e=>{let t=this._initializeOnDelegatedTarget(e);t._activeTrigger[e.type===`focusout`?Ri:Li]=t._element.contains(e.relatedTarget),t._leave()})}this._hideModalHandler=()=>{this._element&&this.hide()},M.on(this._element.closest(Fi),Ii,this._hideModalHandler)}_fixTitle(){let e=this._element.getAttribute(`title`);e&&(!this._element.getAttribute(`aria-label`)&&!this._element.textContent.trim()&&this._element.setAttribute(`aria-label`,e),this._element.setAttribute(`data-bs-original-title`,e),this._element.removeAttribute(`title`))}_enter(){if(this._isShown()||this._isHovered){this._isHovered=!0;return}this._isHovered=!0,this._setTimeout(()=>{this._isHovered&&this.show()},this._config.delay.show)}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout(()=>{this._isHovered||this.hide()},this._config.delay.hide))}_setTimeout(e,t){clearTimeout(this._timeout),this._timeout=setTimeout(e,t)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(e){let t=N.getDataAttributes(this._element);for(let e of Object.keys(t))Mi.has(e)&&delete t[e];return e={...t,...typeof e==`object`&&e?e:{}},e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e.container=e.container===!1?document.body:l(e.container),typeof e.delay==`number`&&(e.delay={show:e.delay,hide:e.delay}),typeof e.title==`number`&&(e.title=e.title.toString()),typeof e.content==`number`&&(e.content=e.content.toString()),e}_getDelegateConfig(){let e={};for(let[t,n]of Object.entries(this._config))this.constructor.Default[t]!==n&&(e[t]=n);return e.selector=!1,e.trigger=`manual`,e}_disposePopper(){this._popper&&=(this._popper.destroy(),null),this.tip&&=(this.tip.remove(),null)}static jQueryInterface(e){return this.each(function(){let t=Ui.getOrCreateInstance(this,e);if(typeof e==`string`){if(t[e]===void 0)throw TypeError(`No method named "${e}"`);t[e]()}})}}y(Ui);let Wi={...Ui.Default,content:``,offset:[0,8],placement:`right`,template:``,trigger:`click`},Gi={...Ui.DefaultType,content:`(null|string|element|function)`};class Ki extends Ui{static get Default(){return Wi}static get DefaultType(){return Gi}static get NAME(){return`popover`}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(e){return this.each(function(){let t=Ki.getOrCreateInstance(this,e);if(typeof e==`string`){if(t[e]===void 0)throw TypeError(`No method named "${e}"`);t[e]()}})}}y(Ki);let qi=`.bs.scrollspy`,Ji=`activate${qi}`,Yi=`click${qi}`,Xi=`load${qi}.data-api`,Zi=`active`,Qi=`[href]`,$i=`.nav-link`,ea=`${$i}, .nav-item > ${$i}, .list-group-item`,ta={offset:null,rootMargin:`0px 0px -25%`,smoothScroll:!1,target:null,threshold:[.1,.5,1]},na={offset:`(number|null)`,rootMargin:`string`,smoothScroll:`boolean`,target:`element`,threshold:`array`};class ra extends P{constructor(e,t){super(e,t),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement=getComputedStyle(this._element).overflowY===`visible`?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return ta}static get DefaultType(){return na}static get NAME(){return`scrollspy`}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(let e of this._observableSections.values())this._observer.observe(e)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(e){return e.target=l(e.target)||document.body,e.rootMargin=e.offset?`${e.offset}px 0px -30%`:e.rootMargin,typeof e.threshold==`string`&&(e.threshold=e.threshold.split(`,`).map(e=>Number.parseFloat(e))),e}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(M.off(this._config.target,Yi),M.on(this._config.target,Yi,Qi,e=>{let t=this._observableSections.get(e.target.hash);if(t){e.preventDefault();let n=this._rootElement||window,r=t.offsetTop-this._element.offsetTop;if(n.scrollTo){n.scrollTo({top:r,behavior:`smooth`});return}n.scrollTop=r}}))}_getNewObserver(){let e={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver(e=>this._observerCallback(e),e)}_observerCallback(e){let t=e=>this._targetLinks.get(`#${e.target.id}`),n=e=>{this._previousScrollData.visibleEntryTop=e.target.offsetTop,this._process(t(e))},r=(this._rootElement||document.documentElement).scrollTop,i=r>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=r;for(let a of e){if(!a.isIntersecting){this._activeTarget=null,this._clearActiveClass(t(a));continue}let e=a.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(i&&e){if(n(a),!r)return;continue}!i&&!e&&n(a)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;let e=F.find(Qi,this._config.target);for(let t of e){if(!t.hash||d(t))continue;let e=F.findOne(decodeURI(t.hash),this._element);u(e)&&(this._targetLinks.set(decodeURI(t.hash),t),this._observableSections.set(t.hash,e))}}_process(e){this._activeTarget!==e&&(this._clearActiveClass(this._config.target),this._activeTarget=e,e.classList.add(Zi),this._activateParents(e),M.trigger(this._element,Ji,{relatedTarget:e}))}_activateParents(e){if(e.classList.contains(`dropdown-item`)){F.findOne(`.dropdown-toggle`,e.closest(`.dropdown`)).classList.add(Zi);return}for(let t of F.parents(e,`.nav, .list-group`))for(let e of F.prev(t,ea))e.classList.add(Zi)}_clearActiveClass(e){e.classList.remove(Zi);let t=F.find(`${Qi}.${Zi}`,e);for(let e of t)e.classList.remove(Zi)}static jQueryInterface(e){return this.each(function(){let t=ra.getOrCreateInstance(this,e);if(typeof e==`string`){if(t[e]===void 0||e.startsWith(`_`)||e===`constructor`)throw TypeError(`No method named "${e}"`);t[e]()}})}}M.on(window,Xi,()=>{for(let e of F.find(`[data-bs-spy="scroll"]`))ra.getOrCreateInstance(e)}),y(ra);let ia=`.bs.tab`,aa=`hide${ia}`,oa=`hidden${ia}`,sa=`show${ia}`,ca=`shown${ia}`,la=`click${ia}`,ua=`keydown${ia}`,da=`load${ia}`,fa=`ArrowRight`,pa=`ArrowDown`,ma=`Home`,ha=`active`,ga=`fade`,_a=`show`,va=`.dropdown-toggle`,ya=`:not(${va})`,ba=`.nav-link${ya}, .list-group-item${ya}, [role="tab"]${ya}`,xa=`[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]`,Sa=`${ba}, ${xa}`,Ca=`.${ha}[data-bs-toggle="tab"], .${ha}[data-bs-toggle="pill"], .${ha}[data-bs-toggle="list"]`;class wa extends P{constructor(e){super(e),this._parent=this._element.closest(`.list-group, .nav, [role="tablist"]`),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),M.on(this._element,ua,e=>this._keydown(e)))}static get NAME(){return`tab`}show(){let e=this._element;if(this._elemIsActive(e))return;let t=this._getActiveElem(),n=t?M.trigger(t,aa,{relatedTarget:e}):null;M.trigger(e,sa,{relatedTarget:t}).defaultPrevented||n&&n.defaultPrevented||(this._deactivate(t,e),this._activate(e,t))}_activate(e,t){e&&(e.classList.add(ha),this._activate(F.getElementFromSelector(e)),this._queueCallback(()=>{if(e.getAttribute(`role`)!==`tab`){e.classList.add(_a);return}e.removeAttribute(`tabindex`),e.setAttribute(`aria-selected`,!0),this._toggleDropDown(e,!0),M.trigger(e,ca,{relatedTarget:t})},e,e.classList.contains(ga)))}_deactivate(e,t){e&&(e.classList.remove(ha),e.blur(),this._deactivate(F.getElementFromSelector(e)),this._queueCallback(()=>{if(e.getAttribute(`role`)!==`tab`){e.classList.remove(_a);return}e.setAttribute(`aria-selected`,!1),e.setAttribute(`tabindex`,`-1`),this._toggleDropDown(e,!1),M.trigger(e,oa,{relatedTarget:t})},e,e.classList.contains(ga)))}_keydown(e){if(![`ArrowLeft`,fa,`ArrowUp`,pa,ma,`End`].includes(e.key))return;e.stopPropagation(),e.preventDefault();let t=this._getChildren().filter(e=>!d(e)),n;if([ma,`End`].includes(e.key))n=t[e.key===ma?0:t.length-1];else{let r=[fa,pa].includes(e.key);n=S(t,e.target,r,!0)}n&&(n.focus({preventScroll:!0}),wa.getOrCreateInstance(n).show())}_getChildren(){return F.find(Sa,this._parent)}_getActiveElem(){return this._getChildren().find(e=>this._elemIsActive(e))||null}_setInitialAttributes(e,t){this._setAttributeIfNotExists(e,`role`,`tablist`);for(let e of t)this._setInitialAttributesOnChild(e)}_setInitialAttributesOnChild(e){e=this._getInnerElement(e);let t=this._elemIsActive(e),n=this._getOuterElement(e);e.setAttribute(`aria-selected`,t),n!==e&&this._setAttributeIfNotExists(n,`role`,`presentation`),t||e.setAttribute(`tabindex`,`-1`),this._setAttributeIfNotExists(e,`role`,`tab`),this._setInitialAttributesOnTargetPanel(e)}_setInitialAttributesOnTargetPanel(e){let t=F.getElementFromSelector(e);t&&(this._setAttributeIfNotExists(t,`role`,`tabpanel`),e.id&&this._setAttributeIfNotExists(t,`aria-labelledby`,`${e.id}`))}_toggleDropDown(e,t){let n=this._getOuterElement(e);if(!n.classList.contains(`dropdown`))return;let r=(e,r)=>{let i=F.findOne(e,n);i&&i.classList.toggle(r,t)};r(va,ha),r(`.dropdown-menu`,_a),n.setAttribute(`aria-expanded`,t)}_setAttributeIfNotExists(e,t,n){e.hasAttribute(t)||e.setAttribute(t,n)}_elemIsActive(e){return e.classList.contains(ha)}_getInnerElement(e){return e.matches(Sa)?e:F.findOne(Sa,e)}_getOuterElement(e){return e.closest(`.nav-item, .list-group-item`)||e}static jQueryInterface(e){return this.each(function(){let t=wa.getOrCreateInstance(this);if(typeof e==`string`){if(t[e]===void 0||e.startsWith(`_`)||e===`constructor`)throw TypeError(`No method named "${e}"`);t[e]()}})}}M.on(document,la,xa,function(e){[`A`,`AREA`].includes(this.tagName)&&e.preventDefault(),!d(this)&&wa.getOrCreateInstance(this).show()}),M.on(window,da,()=>{for(let e of F.find(Ca))wa.getOrCreateInstance(e)}),y(wa);let $=`.bs.toast`,Ta=`mouseover${$}`,Ea=`mouseout${$}`,Da=`focusin${$}`,Oa=`focusout${$}`,ka=`hide${$}`,Aa=`hidden${$}`,ja=`show${$}`,Ma=`shown${$}`,Na=`hide`,Pa=`show`,Fa=`showing`,Ia={animation:`boolean`,autohide:`boolean`,delay:`number`},La={animation:!0,autohide:!0,delay:5e3};class Ra extends P{constructor(e,t){super(e,t),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return La}static get DefaultType(){return Ia}static get NAME(){return`toast`}show(){M.trigger(this._element,ja).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add(`fade`),this._element.classList.remove(Na),m(this._element),this._element.classList.add(Pa,Fa),this._queueCallback(()=>{this._element.classList.remove(Fa),M.trigger(this._element,Ma),this._maybeScheduleHide()},this._element,this._config.animation))}hide(){!this.isShown()||M.trigger(this._element,ka).defaultPrevented||(this._element.classList.add(Fa),this._queueCallback(()=>{this._element.classList.add(Na),this._element.classList.remove(Fa,Pa),M.trigger(this._element,Aa)},this._element,this._config.animation))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(Pa),super.dispose()}isShown(){return this._element.classList.contains(Pa)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(e,t){switch(e.type){case`mouseover`:case`mouseout`:this._hasMouseInteraction=t;break;case`focusin`:case`focusout`:this._hasKeyboardInteraction=t;break}if(t){this._clearTimeout();return}let n=e.relatedTarget;this._element===n||this._element.contains(n)||this._maybeScheduleHide()}_setListeners(){M.on(this._element,Ta,e=>this._onInteraction(e,!0)),M.on(this._element,Ea,e=>this._onInteraction(e,!1)),M.on(this._element,Da,e=>this._onInteraction(e,!0)),M.on(this._element,Oa,e=>this._onInteraction(e,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(e){return this.each(function(){let t=Ra.getOrCreateInstance(this,e);if(typeof e==`string`){if(t[e]===void 0)throw TypeError(`No method named "${e}"`);t[e](this)}})}}return pe(Ra),y(Ra),{Alert:_e,Button:L,Carousel:We,Collapse:it,Dropdown:X,Modal:ai,Offcanvas:Si,Popover:Ki,ScrollSpy:ra,Tab:wa,Toast:Ra,Tooltip:Ui}}))}))();function e(e){document.documentElement.setAttribute(`data-theme`,e)}function t(){var t=document.documentElement.getAttribute(`data-theme`)===`light`?`dark`:`light`;e(t),localStorage.setItem(`theme`,t)}window.toggleTheme=t;function n(e){document.querySelectorAll(`.md-actions.open`).forEach(function(t){t!==e&&(t.classList.remove(`open`),t.querySelector(`.md-actions-toggle`).setAttribute(`aria-expanded`,`false`))})}function r(e){var t=e.closest(`.md-actions`),r=t.classList.contains(`open`);n(t),t.classList.toggle(`open`,!r),e.setAttribute(`aria-expanded`,String(!r))}window.toggleMdActionsMenu=r,document.addEventListener(`click`,function(e){e.target.closest(`.md-actions`)||n()}),document.addEventListener(`keydown`,function(e){e.key===`Escape`&&n()}),document.addEventListener(`click`,function(e){var t=e.target.closest(`[data-copy-url]`);if(t){e.preventDefault();var r=t.getAttribute(`data-copy-url`),i=t.querySelector(`.label`),a=i.textContent;fetch(r).then(function(e){return e.text()}).then(function(e){return navigator.clipboard.writeText(e)}).then(function(){i.textContent=`Copied!`,setTimeout(function(){i.textContent=a},1500)}).catch(function(){navigator.clipboard.writeText(r)}),n()}}),(function(){var e=document.querySelector(`[data-contact-form]`);if(!e)return;var t=e.querySelector(`[data-contact-topic]`),n=e.querySelectorAll(`[data-topic]`),r=document.querySelector(`[data-contact-sent]`),i=r.querySelector(`[data-contact-sent-topic]`),a=r.querySelector(`[data-contact-reset]`);function o(){return e.querySelector(`[data-topic][aria-pressed="true"]`)}function s(e){n.forEach(function(t){t.setAttribute(`aria-pressed`,String(t.getAttribute(`data-topic`)===e))}),t.value=e}n.forEach(function(e){e.addEventListener(`click`,function(){s(e.getAttribute(`data-topic`))})}),e.addEventListener(`submit`,function(t){t.preventDefault();var n=o();i.textContent=n?n.textContent:``,e.hidden=!0,r.hidden=!1,r.querySelector(`h2`).focus({preventScroll:!0})}),a.addEventListener(`click`,function(){e.reset(),s(n[0].getAttribute(`data-topic`)),r.hidden=!0,e.hidden=!1,e.querySelector(`input:not([type="hidden"])`).focus()})})();
\ No newline at end of file
+/* empty css */((e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports))(((e,t)=>{(function(n,r){typeof e==`object`&&t!==void 0?t.exports=r():typeof define==`function`&&define.amd?define(r):(n=typeof globalThis<`u`?globalThis:n||self,n.bootstrap=r())})(e,(function(){let e=new Map,t={set(t,n,r){e.has(t)||e.set(t,new Map);let i=e.get(t);if(!i.has(n)&&i.size!==0){console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(i.keys())[0]}.`);return}i.set(n,r)},get(t,n){return e.has(t)&&e.get(t).get(n)||null},remove(t,n){if(!e.has(t))return;let r=e.get(t);r.delete(n),r.size===0&&e.delete(t)}},n=`transitionend`,r=e=>(e&&window.CSS&&window.CSS.escape&&(e=e.replace(/#([^\s"#']+)/g,(e,t)=>`#${CSS.escape(t)}`)),e),i=e=>e==null?`${e}`:Object.prototype.toString.call(e).match(/\s([a-z]+)/i)[1].toLowerCase(),a=e=>{do e+=Math.floor(Math.random()*1e6);while(document.getElementById(e));return e},o=e=>{if(!e)return 0;let{transitionDuration:t,transitionDelay:n}=window.getComputedStyle(e);return!Number.parseFloat(t)&&!Number.parseFloat(n)?0:(t=t.split(`,`)[0],n=n.split(`,`)[0],(Number.parseFloat(t)+Number.parseFloat(n))*1e3)},s=e=>{e.dispatchEvent(new Event(n))},c=e=>!e||typeof e!=`object`?!1:(e.jquery!==void 0&&(e=e[0]),e.nodeType!==void 0),l=e=>c(e)?e.jquery?e[0]:e:typeof e==`string`&&e.length>0?document.querySelector(r(e)):null,u=e=>{if(!c(e)||e.getClientRects().length===0)return!1;let t=getComputedStyle(e).getPropertyValue(`visibility`)===`visible`,n=e.closest(`details:not([open])`);if(!n)return t;if(n!==e){let t=e.closest(`summary`);if(t&&t.parentNode!==n||t===null)return!1}return t},d=e=>!e||e.nodeType!==Node.ELEMENT_NODE||e.classList.contains(`disabled`)?!0:e.disabled===void 0?e.hasAttribute(`disabled`)&&e.getAttribute(`disabled`)!==`false`:e.disabled,f=e=>{if(!document.documentElement.attachShadow)return null;if(typeof e.getRootNode==`function`){let t=e.getRootNode();return t instanceof ShadowRoot?t:null}return e instanceof ShadowRoot?e:e.parentNode?f(e.parentNode):null},p=()=>{},m=e=>{e.offsetHeight},h=()=>window.jQuery&&!document.body.hasAttribute(`data-bs-no-jquery`)?window.jQuery:null,g=[],_=e=>{document.readyState===`loading`?(g.length||document.addEventListener(`DOMContentLoaded`,()=>{for(let e of g)e()}),g.push(e)):e()},v=()=>document.documentElement.dir===`rtl`,y=e=>{_(()=>{let t=h();if(t){let n=e.NAME,r=t.fn[n];t.fn[n]=e.jQueryInterface,t.fn[n].Constructor=e,t.fn[n].noConflict=()=>(t.fn[n]=r,e.jQueryInterface)}})},b=(e,t=[],n=e)=>typeof e==`function`?e.call(...t):n,x=(e,t,r=!0)=>{if(!r){b(e);return}let i=o(t)+5,a=!1,c=({target:r})=>{r===t&&(a=!0,t.removeEventListener(n,c),b(e))};t.addEventListener(n,c),setTimeout(()=>{a||s(t)},i)},S=(e,t,n,r)=>{let i=e.length,a=e.indexOf(t);return a===-1?!n&&r?e[i-1]:e[0]:(a+=n?1:-1,r&&(a=(a+i)%i),e[Math.max(0,Math.min(a,i-1))])},C=/[^.]*(?=\..*)\.|.*/,w=/\..*/,T=/::\d+$/,E={},D=1,O={mouseenter:`mouseover`,mouseleave:`mouseout`},k=new Set(`click.dblclick.mouseup.mousedown.contextmenu.mousewheel.DOMMouseScroll.mouseover.mouseout.mousemove.selectstart.selectend.keydown.keypress.keyup.orientationchange.touchstart.touchmove.touchend.touchcancel.pointerdown.pointermove.pointerup.pointerleave.pointercancel.gesturestart.gesturechange.gestureend.focus.blur.change.reset.select.submit.focusin.focusout.load.unload.beforeunload.resize.move.DOMContentLoaded.readystatechange.error.abort.scroll`.split(`.`));function ee(e,t){return t&&`${t}::${D++}`||e.uidEvent||D++}function A(e){let t=ee(e);return e.uidEvent=t,E[t]=E[t]||{},E[t]}function j(e,t){return function n(r){return ce(r,{delegateTarget:e}),n.oneOff&&M.off(e,r.type,t),t.apply(e,[r])}}function te(e,t,n){return function r(i){let a=e.querySelectorAll(t);for(let{target:o}=i;o&&o!==this;o=o.parentNode)for(let s of a)if(s===o)return ce(i,{delegateTarget:o}),r.oneOff&&M.off(e,i.type,t,n),n.apply(o,[i])}}function ne(e,t,n=null){return Object.values(e).find(e=>e.callable===t&&e.delegationSelector===n)}function re(e,t,n){let r=typeof t==`string`,i=r?n:t||n,a=se(e);return k.has(a)||(a=e),[r,i,a]}function ie(e,t,n,r,i){if(typeof t!=`string`||!e)return;let[a,o,s]=re(t,n,r);t in O&&(o=(e=>function(t){if(!t.relatedTarget||t.relatedTarget!==t.delegateTarget&&!t.delegateTarget.contains(t.relatedTarget))return e.call(this,t)})(o));let c=A(e),l=c[s]||(c[s]={}),u=ne(l,o,a?n:null);if(u){u.oneOff=u.oneOff&&i;return}let d=ee(o,t.replace(C,``)),f=a?te(e,n,o):j(e,o);f.delegationSelector=a?n:null,f.callable=o,f.oneOff=i,f.uidEvent=d,l[d]=f,e.addEventListener(s,f,a)}function ae(e,t,n,r,i){let a=ne(t[n],r,i);a&&(e.removeEventListener(n,a,!!i),delete t[n][a.uidEvent])}function oe(e,t,n,r){let i=t[n]||{};for(let[a,o]of Object.entries(i))a.includes(r)&&ae(e,t,n,o.callable,o.delegationSelector)}function se(e){return e=e.replace(w,``),O[e]||e}let M={on(e,t,n,r){ie(e,t,n,r,!1)},one(e,t,n,r){ie(e,t,n,r,!0)},off(e,t,n,r){if(typeof t!=`string`||!e)return;let[i,a,o]=re(t,n,r),s=o!==t,c=A(e),l=c[o]||{},u=t.startsWith(`.`);if(a!==void 0){if(!Object.keys(l).length)return;ae(e,c,o,a,i?n:null);return}if(u)for(let n of Object.keys(c))oe(e,c,n,t.slice(1));for(let[n,r]of Object.entries(l)){let i=n.replace(T,``);(!s||t.includes(i))&&ae(e,c,o,r.callable,r.delegationSelector)}},trigger(e,t,n){if(typeof t!=`string`||!e)return null;let r=h(),i=t!==se(t),a=null,o=!0,s=!0,c=!1;i&&r&&(a=r.Event(t,n),r(e).trigger(a),o=!a.isPropagationStopped(),s=!a.isImmediatePropagationStopped(),c=a.isDefaultPrevented());let l=ce(new Event(t,{bubbles:o,cancelable:!0}),n);return c&&l.preventDefault(),s&&e.dispatchEvent(l),l.defaultPrevented&&a&&a.preventDefault(),l}};function ce(e,t={}){for(let[n,r]of Object.entries(t))try{e[n]=r}catch{Object.defineProperty(e,n,{configurable:!0,get(){return r}})}return e}function le(e){if(e===`true`)return!0;if(e===`false`)return!1;if(e===Number(e).toString())return Number(e);if(e===``||e===`null`)return null;if(typeof e!=`string`)return e;try{return JSON.parse(decodeURIComponent(e))}catch{return e}}function ue(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}let N={setDataAttribute(e,t,n){e.setAttribute(`data-bs-${ue(t)}`,n)},removeDataAttribute(e,t){e.removeAttribute(`data-bs-${ue(t)}`)},getDataAttributes(e){if(!e)return{};let t={},n=Object.keys(e.dataset).filter(e=>e.startsWith(`bs`)&&!e.startsWith(`bsConfig`));for(let r of n){let n=r.replace(/^bs/,``);n=n.charAt(0).toLowerCase()+n.slice(1),t[n]=le(e.dataset[r])}return t},getDataAttribute(e,t){return le(e.getAttribute(`data-bs-${ue(t)}`))}};class de{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw Error(`You have to implement the static method "NAME", for each component!`)}_getConfig(e){return e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e}_mergeConfigObj(e,t){let n=c(t)?N.getDataAttribute(t,`config`):{};return{...this.constructor.Default,...typeof n==`object`?n:{},...c(t)?N.getDataAttributes(t):{},...typeof e==`object`?e:{}}}_typeCheckConfig(e,t=this.constructor.DefaultType){for(let[n,r]of Object.entries(t)){let t=e[n],a=c(t)?`element`:i(t);if(!new RegExp(r).test(a))throw TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${r}".`)}}}class P extends de{constructor(e,n){super(),e=l(e),e&&(this._element=e,this._config=this._getConfig(n),t.set(this._element,this.constructor.DATA_KEY,this))}dispose(){t.remove(this._element,this.constructor.DATA_KEY),M.off(this._element,this.constructor.EVENT_KEY);for(let e of Object.getOwnPropertyNames(this))this[e]=null}_queueCallback(e,t,n=!0){x(e,t,n)}_getConfig(e){return e=this._mergeConfigObj(e,this._element),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}static getInstance(e){return t.get(l(e),this.DATA_KEY)}static getOrCreateInstance(e,t={}){return this.getInstance(e)||new this(e,typeof t==`object`?t:null)}static get VERSION(){return`5.3.8`}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(e){return`${e}${this.EVENT_KEY}`}}let fe=e=>{let t=e.getAttribute(`data-bs-target`);if(!t||t===`#`){let n=e.getAttribute(`href`);if(!n||!n.includes(`#`)&&!n.startsWith(`.`))return null;n.includes(`#`)&&!n.startsWith(`#`)&&(n=`#${n.split(`#`)[1]}`),t=n&&n!==`#`?n.trim():null}return t?t.split(`,`).map(e=>r(e)).join(`,`):null},F={find(e,t=document.documentElement){return[].concat(...Element.prototype.querySelectorAll.call(t,e))},findOne(e,t=document.documentElement){return Element.prototype.querySelector.call(t,e)},children(e,t){return[].concat(...e.children).filter(e=>e.matches(t))},parents(e,t){let n=[],r=e.parentNode.closest(t);for(;r;)n.push(r),r=r.parentNode.closest(t);return n},prev(e,t){let n=e.previousElementSibling;for(;n;){if(n.matches(t))return[n];n=n.previousElementSibling}return[]},next(e,t){let n=e.nextElementSibling;for(;n;){if(n.matches(t))return[n];n=n.nextElementSibling}return[]},focusableChildren(e){let t=[`a`,`button`,`input`,`textarea`,`select`,`details`,`[tabindex]`,`[contenteditable="true"]`].map(e=>`${e}:not([tabindex^="-"])`).join(`,`);return this.find(t,e).filter(e=>!d(e)&&u(e))},getSelectorFromElement(e){let t=fe(e);return t&&F.findOne(t)?t:null},getElementFromSelector(e){let t=fe(e);return t?F.findOne(t):null},getMultipleElementsFromSelector(e){let t=fe(e);return t?F.find(t):[]}},pe=(e,t=`hide`)=>{let n=`click.dismiss${e.EVENT_KEY}`,r=e.NAME;M.on(document,n,`[data-bs-dismiss="${r}"]`,function(n){if([`A`,`AREA`].includes(this.tagName)&&n.preventDefault(),d(this))return;let i=F.getElementFromSelector(this)||this.closest(`.${r}`);e.getOrCreateInstance(i)[t]()})},me=`.bs.alert`,he=`close${me}`,ge=`closed${me}`;class _e extends P{static get NAME(){return`alert`}close(){if(M.trigger(this._element,he).defaultPrevented)return;this._element.classList.remove(`show`);let e=this._element.classList.contains(`fade`);this._queueCallback(()=>this._destroyElement(),this._element,e)}_destroyElement(){this._element.remove(),M.trigger(this._element,ge),this.dispose()}static jQueryInterface(e){return this.each(function(){let t=_e.getOrCreateInstance(this);if(typeof e==`string`){if(t[e]===void 0||e.startsWith(`_`)||e===`constructor`)throw TypeError(`No method named "${e}"`);t[e](this)}})}}pe(_e,`close`),y(_e);let I=`[data-bs-toggle="button"]`;class L extends P{static get NAME(){return`button`}toggle(){this._element.setAttribute(`aria-pressed`,this._element.classList.toggle(`active`))}static jQueryInterface(e){return this.each(function(){let t=L.getOrCreateInstance(this);e===`toggle`&&t[e]()})}}M.on(document,`click.bs.button.data-api`,I,e=>{e.preventDefault();let t=e.target.closest(I);L.getOrCreateInstance(t).toggle()}),y(L);let R=`.bs.swipe`,ve=`touchstart${R}`,ye=`touchmove${R}`,be=`touchend${R}`,xe=`pointerdown${R}`,Se=`pointerup${R}`,Ce={endCallback:null,leftCallback:null,rightCallback:null},we={endCallback:`(function|null)`,leftCallback:`(function|null)`,rightCallback:`(function|null)`};class Te extends de{constructor(e,t){super(),this._element=e,!(!e||!Te.isSupported())&&(this._config=this._getConfig(t),this._deltaX=0,this._supportPointerEvents=!!window.PointerEvent,this._initEvents())}static get Default(){return Ce}static get DefaultType(){return we}static get NAME(){return`swipe`}dispose(){M.off(this._element,R)}_start(e){if(!this._supportPointerEvents){this._deltaX=e.touches[0].clientX;return}this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX)}_end(e){this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX-this._deltaX),this._handleSwipe(),b(this._config.endCallback)}_move(e){this._deltaX=e.touches&&e.touches.length>1?0:e.touches[0].clientX-this._deltaX}_handleSwipe(){let e=Math.abs(this._deltaX);if(e<=40)return;let t=e/this._deltaX;this._deltaX=0,t&&b(t>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(M.on(this._element,xe,e=>this._start(e)),M.on(this._element,Se,e=>this._end(e)),this._element.classList.add(`pointer-event`)):(M.on(this._element,ve,e=>this._start(e)),M.on(this._element,ye,e=>this._move(e)),M.on(this._element,be,e=>this._end(e)))}_eventIsPointerPenTouch(e){return this._supportPointerEvents&&(e.pointerType===`pen`||e.pointerType===`touch`)}static isSupported(){return`ontouchstart`in document.documentElement||navigator.maxTouchPoints>0}}let z=`.bs.carousel`,Ee=`.data-api`,De=`next`,Oe=`prev`,ke=`left`,Ae=`right`,je=`slide${z}`,Me=`slid${z}`,Ne=`keydown${z}`,Pe=`mouseenter${z}`,Fe=`mouseleave${z}`,Ie=`dragstart${z}`,Le=`load${z}${Ee}`,Re=`click${z}${Ee}`,ze=`carousel`,Be=`active`,Ve={ArrowLeft:Ae,ArrowRight:ke},He={interval:5e3,keyboard:!0,pause:`hover`,ride:!1,touch:!0,wrap:!0},Ue={interval:`(number|boolean)`,keyboard:`boolean`,pause:`(string|boolean)`,ride:`(boolean|string)`,touch:`boolean`,wrap:`boolean`};class We extends P{constructor(e,t){super(e,t),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=F.findOne(`.carousel-indicators`,this._element),this._addEventListeners(),this._config.ride===ze&&this.cycle()}static get Default(){return He}static get DefaultType(){return Ue}static get NAME(){return`carousel`}next(){this._slide(De)}nextWhenVisible(){!document.hidden&&u(this._element)&&this.next()}prev(){this._slide(Oe)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval(()=>this.nextWhenVisible(),this._config.interval)}_maybeEnableCycle(){if(this._config.ride){if(this._isSliding){M.one(this._element,Me,()=>this.cycle());return}this.cycle()}}to(e){let t=this._getItems();if(e>t.length-1||e<0)return;if(this._isSliding){M.one(this._element,Me,()=>this.to(e));return}let n=this._getItemIndex(this._getActive());if(n===e)return;let r=e>n?De:Oe;this._slide(r,t[e])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(e){return e.defaultInterval=e.interval,e}_addEventListeners(){this._config.keyboard&&M.on(this._element,Ne,e=>this._keydown(e)),this._config.pause===`hover`&&(M.on(this._element,Pe,()=>this.pause()),M.on(this._element,Fe,()=>this._maybeEnableCycle())),this._config.touch&&Te.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(let e of F.find(`.carousel-item img`,this._element))M.on(e,Ie,e=>e.preventDefault());let e={leftCallback:()=>this._slide(this._directionToOrder(ke)),rightCallback:()=>this._slide(this._directionToOrder(Ae)),endCallback:()=>{this._config.pause===`hover`&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(()=>this._maybeEnableCycle(),500+this._config.interval))}};this._swipeHelper=new Te(this._element,e)}_keydown(e){if(/input|textarea/i.test(e.target.tagName))return;let t=Ve[e.key];t&&(e.preventDefault(),this._slide(this._directionToOrder(t)))}_getItemIndex(e){return this._getItems().indexOf(e)}_setActiveIndicatorElement(e){if(!this._indicatorsElement)return;let t=F.findOne(`.active`,this._indicatorsElement);t.classList.remove(Be),t.removeAttribute(`aria-current`);let n=F.findOne(`[data-bs-slide-to="${e}"]`,this._indicatorsElement);n&&(n.classList.add(Be),n.setAttribute(`aria-current`,`true`))}_updateInterval(){let e=this._activeElement||this._getActive();if(!e)return;let t=Number.parseInt(e.getAttribute(`data-bs-interval`),10);this._config.interval=t||this._config.defaultInterval}_slide(e,t=null){if(this._isSliding)return;let n=this._getActive(),r=e===De,i=t||S(this._getItems(),n,r,this._config.wrap);if(i===n)return;let a=this._getItemIndex(i),o=t=>M.trigger(this._element,t,{relatedTarget:i,direction:this._orderToDirection(e),from:this._getItemIndex(n),to:a});if(o(je).defaultPrevented||!n||!i)return;let s=!!this._interval;this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(a),this._activeElement=i;let c=r?`carousel-item-start`:`carousel-item-end`,l=r?`carousel-item-next`:`carousel-item-prev`;i.classList.add(l),m(i),n.classList.add(c),i.classList.add(c),this._queueCallback(()=>{i.classList.remove(c,l),i.classList.add(Be),n.classList.remove(Be,l,c),this._isSliding=!1,o(Me)},n,this._isAnimated()),s&&this.cycle()}_isAnimated(){return this._element.classList.contains(`slide`)}_getActive(){return F.findOne(`.active.carousel-item`,this._element)}_getItems(){return F.find(`.carousel-item`,this._element)}_clearInterval(){this._interval&&=(clearInterval(this._interval),null)}_directionToOrder(e){return v()?e===ke?Oe:De:e===ke?De:Oe}_orderToDirection(e){return v()?e===Oe?ke:Ae:e===Oe?Ae:ke}static jQueryInterface(e){return this.each(function(){let t=We.getOrCreateInstance(this,e);if(typeof e==`number`){t.to(e);return}if(typeof e==`string`){if(t[e]===void 0||e.startsWith(`_`)||e===`constructor`)throw TypeError(`No method named "${e}"`);t[e]()}})}}M.on(document,Re,`[data-bs-slide], [data-bs-slide-to]`,function(e){let t=F.getElementFromSelector(this);if(!t||!t.classList.contains(ze))return;e.preventDefault();let n=We.getOrCreateInstance(t),r=this.getAttribute(`data-bs-slide-to`);if(r){n.to(r),n._maybeEnableCycle();return}if(N.getDataAttribute(this,`slide`)===`next`){n.next(),n._maybeEnableCycle();return}n.prev(),n._maybeEnableCycle()}),M.on(window,Le,()=>{let e=F.find(`[data-bs-ride="carousel"]`);for(let t of e)We.getOrCreateInstance(t)}),y(We);let Ge=`.bs.collapse`,Ke=`show${Ge}`,qe=`shown${Ge}`,Je=`hide${Ge}`,Ye=`hidden${Ge}`,Xe=`click${Ge}.data-api`,Ze=`show`,Qe=`collapse`,$e=`collapsing`,et=`:scope .${Qe} .${Qe}`,tt=`[data-bs-toggle="collapse"]`,nt={parent:null,toggle:!0},rt={parent:`(null|element)`,toggle:`boolean`};class it extends P{constructor(e,t){super(e,t),this._isTransitioning=!1,this._triggerArray=[];let n=F.find(tt);for(let e of n){let t=F.getSelectorFromElement(e),n=F.find(t).filter(e=>e===this._element);t!==null&&n.length&&this._triggerArray.push(e)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return nt}static get DefaultType(){return rt}static get NAME(){return`collapse`}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let e=[];if(this._config.parent&&(e=this._getFirstLevelChildren(`.collapse.show, .collapse.collapsing`).filter(e=>e!==this._element).map(e=>it.getOrCreateInstance(e,{toggle:!1}))),e.length&&e[0]._isTransitioning||M.trigger(this._element,Ke).defaultPrevented)return;for(let t of e)t.hide();let t=this._getDimension();this._element.classList.remove(Qe),this._element.classList.add($e),this._element.style[t]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;let n=()=>{this._isTransitioning=!1,this._element.classList.remove($e),this._element.classList.add(Qe,Ze),this._element.style[t]=``,M.trigger(this._element,qe)},r=`scroll${t[0].toUpperCase()+t.slice(1)}`;this._queueCallback(n,this._element,!0),this._element.style[t]=`${this._element[r]}px`}hide(){if(this._isTransitioning||!this._isShown()||M.trigger(this._element,Je).defaultPrevented)return;let e=this._getDimension();this._element.style[e]=`${this._element.getBoundingClientRect()[e]}px`,m(this._element),this._element.classList.add($e),this._element.classList.remove(Qe,Ze);for(let e of this._triggerArray){let t=F.getElementFromSelector(e);t&&!this._isShown(t)&&this._addAriaAndCollapsedClass([e],!1)}this._isTransitioning=!0;let t=()=>{this._isTransitioning=!1,this._element.classList.remove($e),this._element.classList.add(Qe),M.trigger(this._element,Ye)};this._element.style[e]=``,this._queueCallback(t,this._element,!0)}_isShown(e=this._element){return e.classList.contains(Ze)}_configAfterMerge(e){return e.toggle=!!e.toggle,e.parent=l(e.parent),e}_getDimension(){return this._element.classList.contains(`collapse-horizontal`)?`width`:`height`}_initializeChildren(){if(!this._config.parent)return;let e=this._getFirstLevelChildren(tt);for(let t of e){let e=F.getElementFromSelector(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}}_getFirstLevelChildren(e){let t=F.find(et,this._config.parent);return F.find(e,this._config.parent).filter(e=>!t.includes(e))}_addAriaAndCollapsedClass(e,t){if(e.length)for(let n of e)n.classList.toggle(`collapsed`,!t),n.setAttribute(`aria-expanded`,t)}static jQueryInterface(e){let t={};return typeof e==`string`&&/show|hide/.test(e)&&(t.toggle=!1),this.each(function(){let n=it.getOrCreateInstance(this,t);if(typeof e==`string`){if(n[e]===void 0)throw TypeError(`No method named "${e}"`);n[e]()}})}}M.on(document,Xe,tt,function(e){(e.target.tagName===`A`||e.delegateTarget&&e.delegateTarget.tagName===`A`)&&e.preventDefault();for(let e of F.getMultipleElementsFromSelector(this))it.getOrCreateInstance(e,{toggle:!1}).toggle()}),y(it);var B=`top`,V=`bottom`,H=`right`,U=`left`,at=`auto`,ot=[B,V,H,U],st=`start`,ct=`end`,lt=`clippingParents`,ut=`viewport`,dt=`popper`,ft=`reference`,pt=ot.reduce(function(e,t){return e.concat([t+`-`+st,t+`-`+ct])},[]),mt=[].concat(ot,[at]).reduce(function(e,t){return e.concat([t,t+`-`+st,t+`-`+ct])},[]),ht=`beforeRead`,gt=`read`,_t=`afterRead`,vt=`beforeMain`,yt=`main`,bt=`afterMain`,xt=`beforeWrite`,St=`write`,Ct=`afterWrite`,wt=[ht,gt,_t,vt,yt,bt,xt,St,Ct];function W(e){return e?(e.nodeName||``).toLowerCase():null}function G(e){if(e==null)return window;if(e.toString()!==`[object Window]`){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function Tt(e){return e instanceof G(e).Element||e instanceof Element}function K(e){return e instanceof G(e).HTMLElement||e instanceof HTMLElement}function Et(e){return typeof ShadowRoot>`u`?!1:e instanceof G(e).ShadowRoot||e instanceof ShadowRoot}function Dt(e){var t=e.state;Object.keys(t.elements).forEach(function(e){var n=t.styles[e]||{},r=t.attributes[e]||{},i=t.elements[e];!K(i)||!W(i)||(Object.assign(i.style,n),Object.keys(r).forEach(function(e){var t=r[e];t===!1?i.removeAttribute(e):i.setAttribute(e,t===!0?``:t)}))})}function Ot(e){var t=e.state,n={popper:{position:t.options.strategy,left:`0`,top:`0`,margin:`0`},arrow:{position:`absolute`},reference:{}};return Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow),function(){Object.keys(t.elements).forEach(function(e){var r=t.elements[e],i=t.attributes[e]||{},a=Object.keys(t.styles.hasOwnProperty(e)?t.styles[e]:n[e]).reduce(function(e,t){return e[t]=``,e},{});!K(r)||!W(r)||(Object.assign(r.style,a),Object.keys(i).forEach(function(e){r.removeAttribute(e)}))})}}let kt={name:`applyStyles`,enabled:!0,phase:`write`,fn:Dt,effect:Ot,requires:[`computeStyles`]};function q(e){return e.split(`-`)[0]}var At=Math.max,jt=Math.min,Mt=Math.round;function Nt(){var e=navigator.userAgentData;return e!=null&&e.brands&&Array.isArray(e.brands)?e.brands.map(function(e){return e.brand+`/`+e.version}).join(` `):navigator.userAgent}function Pt(){return!/^((?!chrome|android).)*safari/i.test(Nt())}function Ft(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!1);var r=e.getBoundingClientRect(),i=1,a=1;t&&K(e)&&(i=e.offsetWidth>0&&Mt(r.width)/e.offsetWidth||1,a=e.offsetHeight>0&&Mt(r.height)/e.offsetHeight||1);var o=(Tt(e)?G(e):window).visualViewport,s=!Pt()&&n,c=(r.left+(s&&o?o.offsetLeft:0))/i,l=(r.top+(s&&o?o.offsetTop:0))/a,u=r.width/i,d=r.height/a;return{width:u,height:d,top:l,right:c+u,bottom:l+d,left:c,x:c,y:l}}function It(e){var t=Ft(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function Lt(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&Et(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function J(e){return G(e).getComputedStyle(e)}function Rt(e){return[`table`,`td`,`th`].indexOf(W(e))>=0}function Y(e){return((Tt(e)?e.ownerDocument:e.document)||window.document).documentElement}function zt(e){return W(e)===`html`?e:e.assignedSlot||e.parentNode||(Et(e)?e.host:null)||Y(e)}function Bt(e){return!K(e)||J(e).position===`fixed`?null:e.offsetParent}function Vt(e){var t=/firefox/i.test(Nt());if(/Trident/i.test(Nt())&&K(e)&&J(e).position===`fixed`)return null;var n=zt(e);for(Et(n)&&(n=n.host);K(n)&&[`html`,`body`].indexOf(W(n))<0;){var r=J(n);if(r.transform!==`none`||r.perspective!==`none`||r.contain===`paint`||[`transform`,`perspective`].indexOf(r.willChange)!==-1||t&&r.willChange===`filter`||t&&r.filter&&r.filter!==`none`)return n;n=n.parentNode}return null}function Ht(e){for(var t=G(e),n=Bt(e);n&&Rt(n)&&J(n).position===`static`;)n=Bt(n);return n&&(W(n)===`html`||W(n)===`body`&&J(n).position===`static`)?t:n||Vt(e)||t}function Ut(e){return[`top`,`bottom`].indexOf(e)>=0?`x`:`y`}function Wt(e,t,n){return At(e,jt(t,n))}function Gt(e,t,n){var r=Wt(e,t,n);return r>n?n:r}function Kt(){return{top:0,right:0,bottom:0,left:0}}function qt(e){return Object.assign({},Kt(),e)}function Jt(e,t){return t.reduce(function(t,n){return t[n]=e,t},{})}var Yt=function(e,t){return e=typeof e==`function`?e(Object.assign({},t.rects,{placement:t.placement})):e,qt(typeof e==`number`?Jt(e,ot):e)};function Xt(e){var t,n=e.state,r=e.name,i=e.options,a=n.elements.arrow,o=n.modifiersData.popperOffsets,s=q(n.placement),c=Ut(s),l=[U,H].indexOf(s)>=0?`height`:`width`;if(!(!a||!o)){var u=Yt(i.padding,n),d=It(a),f=c===`y`?B:U,p=c===`y`?V:H,m=n.rects.reference[l]+n.rects.reference[c]-o[c]-n.rects.popper[l],h=o[c]-n.rects.reference[c],g=Ht(a),_=g?c===`y`?g.clientHeight||0:g.clientWidth||0:0,v=m/2-h/2,y=u[f],b=_-d[l]-u[p],x=_/2-d[l]/2+v,S=Wt(y,x,b),C=c;n.modifiersData[r]=(t={},t[C]=S,t.centerOffset=S-x,t)}}function Zt(e){var t=e.state,n=e.options.element,r=n===void 0?`[data-popper-arrow]`:n;r!=null&&(typeof r==`string`&&(r=t.elements.popper.querySelector(r),!r)||Lt(t.elements.popper,r)&&(t.elements.arrow=r))}let Qt={name:`arrow`,enabled:!0,phase:`main`,fn:Xt,effect:Zt,requires:[`popperOffsets`],requiresIfExists:[`preventOverflow`]};function $t(e){return e.split(`-`)[1]}var en={top:`auto`,right:`auto`,bottom:`auto`,left:`auto`};function tn(e,t){var n=e.x,r=e.y,i=t.devicePixelRatio||1;return{x:Mt(n*i)/i||0,y:Mt(r*i)/i||0}}function nn(e){var t,n=e.popper,r=e.popperRect,i=e.placement,a=e.variation,o=e.offsets,s=e.position,c=e.gpuAcceleration,l=e.adaptive,u=e.roundOffsets,d=e.isFixed,f=o.x,p=f===void 0?0:f,m=o.y,h=m===void 0?0:m,g=typeof u==`function`?u({x:p,y:h}):{x:p,y:h};p=g.x,h=g.y;var _=o.hasOwnProperty(`x`),v=o.hasOwnProperty(`y`),y=U,b=B,x=window;if(l){var S=Ht(n),C=`clientHeight`,w=`clientWidth`;if(S===G(n)&&(S=Y(n),J(S).position!==`static`&&s===`absolute`&&(C=`scrollHeight`,w=`scrollWidth`)),S=S,i===B||(i===U||i===H)&&a===ct){b=V;var T=d&&S===x&&x.visualViewport?x.visualViewport.height:S[C];h-=T-r.height,h*=c?1:-1}if(i===U||(i===B||i===V)&&a===ct){y=H;var E=d&&S===x&&x.visualViewport?x.visualViewport.width:S[w];p-=E-r.width,p*=c?1:-1}}var D=Object.assign({position:s},l&&en),O=u===!0?tn({x:p,y:h},G(n)):{x:p,y:h};if(p=O.x,h=O.y,c){var k;return Object.assign({},D,(k={},k[b]=v?`0`:``,k[y]=_?`0`:``,k.transform=(x.devicePixelRatio||1)<=1?`translate(`+p+`px, `+h+`px)`:`translate3d(`+p+`px, `+h+`px, 0)`,k))}return Object.assign({},D,(t={},t[b]=v?h+`px`:``,t[y]=_?p+`px`:``,t.transform=``,t))}function rn(e){var t=e.state,n=e.options,r=n.gpuAcceleration,i=r===void 0?!0:r,a=n.adaptive,o=a===void 0?!0:a,s=n.roundOffsets,c=s===void 0?!0:s,l={placement:q(t.placement),variation:$t(t.placement),popper:t.elements.popper,popperRect:t.rects.popper,gpuAcceleration:i,isFixed:t.options.strategy===`fixed`};t.modifiersData.popperOffsets!=null&&(t.styles.popper=Object.assign({},t.styles.popper,nn(Object.assign({},l,{offsets:t.modifiersData.popperOffsets,position:t.options.strategy,adaptive:o,roundOffsets:c})))),t.modifiersData.arrow!=null&&(t.styles.arrow=Object.assign({},t.styles.arrow,nn(Object.assign({},l,{offsets:t.modifiersData.arrow,position:`absolute`,adaptive:!1,roundOffsets:c})))),t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-placement":t.placement})}let an={name:`computeStyles`,enabled:!0,phase:`beforeWrite`,fn:rn,data:{}};var on={passive:!0};function sn(e){var t=e.state,n=e.instance,r=e.options,i=r.scroll,a=i===void 0?!0:i,o=r.resize,s=o===void 0?!0:o,c=G(t.elements.popper),l=[].concat(t.scrollParents.reference,t.scrollParents.popper);return a&&l.forEach(function(e){e.addEventListener(`scroll`,n.update,on)}),s&&c.addEventListener(`resize`,n.update,on),function(){a&&l.forEach(function(e){e.removeEventListener(`scroll`,n.update,on)}),s&&c.removeEventListener(`resize`,n.update,on)}}let cn={name:`eventListeners`,enabled:!0,phase:`write`,fn:function(){},effect:sn,data:{}};var ln={left:`right`,right:`left`,bottom:`top`,top:`bottom`};function un(e){return e.replace(/left|right|bottom|top/g,function(e){return ln[e]})}var dn={start:`end`,end:`start`};function fn(e){return e.replace(/start|end/g,function(e){return dn[e]})}function pn(e){var t=G(e);return{scrollLeft:t.pageXOffset,scrollTop:t.pageYOffset}}function mn(e){return Ft(Y(e)).left+pn(e).scrollLeft}function hn(e,t){var n=G(e),r=Y(e),i=n.visualViewport,a=r.clientWidth,o=r.clientHeight,s=0,c=0;if(i){a=i.width,o=i.height;var l=Pt();(l||!l&&t===`fixed`)&&(s=i.offsetLeft,c=i.offsetTop)}return{width:a,height:o,x:s+mn(e),y:c}}function gn(e){var t=Y(e),n=pn(e),r=e.ownerDocument?.body,i=At(t.scrollWidth,t.clientWidth,r?r.scrollWidth:0,r?r.clientWidth:0),a=At(t.scrollHeight,t.clientHeight,r?r.scrollHeight:0,r?r.clientHeight:0),o=-n.scrollLeft+mn(e),s=-n.scrollTop;return J(r||t).direction===`rtl`&&(o+=At(t.clientWidth,r?r.clientWidth:0)-i),{width:i,height:a,x:o,y:s}}function _n(e){var t=J(e),n=t.overflow,r=t.overflowX,i=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+i+r)}function vn(e){return[`html`,`body`,`#document`].indexOf(W(e))>=0?e.ownerDocument.body:K(e)&&_n(e)?e:vn(zt(e))}function yn(e,t){t===void 0&&(t=[]);var n=vn(e),r=n===e.ownerDocument?.body,i=G(n),a=r?[i].concat(i.visualViewport||[],_n(n)?n:[]):n,o=t.concat(a);return r?o:o.concat(yn(zt(a)))}function bn(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function xn(e,t){var n=Ft(e,!1,t===`fixed`);return n.top+=e.clientTop,n.left+=e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}function Sn(e,t,n){return t===ut?bn(hn(e,n)):Tt(t)?xn(t,n):bn(gn(Y(e)))}function Cn(e){var t=yn(zt(e)),n=[`absolute`,`fixed`].indexOf(J(e).position)>=0&&K(e)?Ht(e):e;return Tt(n)?t.filter(function(e){return Tt(e)&&Lt(e,n)&&W(e)!==`body`}):[]}function wn(e,t,n,r){var i=t===`clippingParents`?Cn(e):[].concat(t),a=[].concat(i,[n]),o=a[0],s=a.reduce(function(t,n){var i=Sn(e,n,r);return t.top=At(i.top,t.top),t.right=jt(i.right,t.right),t.bottom=jt(i.bottom,t.bottom),t.left=At(i.left,t.left),t},Sn(e,o,r));return s.width=s.right-s.left,s.height=s.bottom-s.top,s.x=s.left,s.y=s.top,s}function Tn(e){var t=e.reference,n=e.element,r=e.placement,i=r?q(r):null,a=r?$t(r):null,o=t.x+t.width/2-n.width/2,s=t.y+t.height/2-n.height/2,c;switch(i){case B:c={x:o,y:t.y-n.height};break;case V:c={x:o,y:t.y+t.height};break;case H:c={x:t.x+t.width,y:s};break;case U:c={x:t.x-n.width,y:s};break;default:c={x:t.x,y:t.y}}var l=i?Ut(i):null;if(l!=null){var u=l===`y`?`height`:`width`;switch(a){case st:c[l]=c[l]-(t[u]/2-n[u]/2);break;case ct:c[l]=c[l]+(t[u]/2-n[u]/2);break}}return c}function En(e,t){t===void 0&&(t={});var n=t,r=n.placement,i=r===void 0?e.placement:r,a=n.strategy,o=a===void 0?e.strategy:a,s=n.boundary,c=s===void 0?lt:s,l=n.rootBoundary,u=l===void 0?ut:l,d=n.elementContext,f=d===void 0?dt:d,p=n.altBoundary,m=p===void 0?!1:p,h=n.padding,g=h===void 0?0:h,_=qt(typeof g==`number`?Jt(g,ot):g),v=f===dt?ft:dt,y=e.rects.popper,b=e.elements[m?v:f],x=wn(Tt(b)?b:b.contextElement||Y(e.elements.popper),c,u,o),S=Ft(e.elements.reference),C=Tn({reference:S,element:y,placement:i}),w=bn(Object.assign({},y,C)),T=f===dt?w:S,E={top:x.top-T.top+_.top,bottom:T.bottom-x.bottom+_.bottom,left:x.left-T.left+_.left,right:T.right-x.right+_.right},D=e.modifiersData.offset;if(f===dt&&D){var O=D[i];Object.keys(E).forEach(function(e){var t=[H,V].indexOf(e)>=0?1:-1,n=[B,V].indexOf(e)>=0?`y`:`x`;E[e]+=O[n]*t})}return E}function Dn(e,t){t===void 0&&(t={});var n=t,r=n.placement,i=n.boundary,a=n.rootBoundary,o=n.padding,s=n.flipVariations,c=n.allowedAutoPlacements,l=c===void 0?mt:c,u=$t(r),d=u?s?pt:pt.filter(function(e){return $t(e)===u}):ot,f=d.filter(function(e){return l.indexOf(e)>=0});f.length===0&&(f=d);var p=f.reduce(function(t,n){return t[n]=En(e,{placement:n,boundary:i,rootBoundary:a,padding:o})[q(n)],t},{});return Object.keys(p).sort(function(e,t){return p[e]-p[t]})}function On(e){if(q(e)===at)return[];var t=un(e);return[fn(e),t,fn(t)]}function kn(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var i=n.mainAxis,a=i===void 0?!0:i,o=n.altAxis,s=o===void 0?!0:o,c=n.fallbackPlacements,l=n.padding,u=n.boundary,d=n.rootBoundary,f=n.altBoundary,p=n.flipVariations,m=p===void 0?!0:p,h=n.allowedAutoPlacements,g=t.options.placement,_=q(g)===g,v=c||(_||!m?[un(g)]:On(g)),y=[g].concat(v).reduce(function(e,n){return e.concat(q(n)===at?Dn(t,{placement:n,boundary:u,rootBoundary:d,padding:l,flipVariations:m,allowedAutoPlacements:h}):n)},[]),b=t.rects.reference,x=t.rects.popper,S=new Map,C=!0,w=y[0],T=0;T=0,ee=k?`width`:`height`,A=En(t,{placement:E,boundary:u,rootBoundary:d,altBoundary:f,padding:l}),j=k?O?H:U:O?V:B;b[ee]>x[ee]&&(j=un(j));var te=un(j),ne=[];if(a&&ne.push(A[D]<=0),s&&ne.push(A[j]<=0,A[te]<=0),ne.every(function(e){return e})){w=E,C=!1;break}S.set(E,ne)}if(C)for(var re=m?3:1,ie=function(e){var t=y.find(function(t){var n=S.get(t);if(n)return n.slice(0,e).every(function(e){return e})});if(t)return w=t,`break`},ae=re;ae>0&&ie(ae)!==`break`;ae--);t.placement!==w&&(t.modifiersData[r]._skip=!0,t.placement=w,t.reset=!0)}}let An={name:`flip`,enabled:!0,phase:`main`,fn:kn,requiresIfExists:[`offset`],data:{_skip:!1}};function jn(e,t,n){return n===void 0&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function Mn(e){return[B,H,V,U].some(function(t){return e[t]>=0})}function Nn(e){var t=e.state,n=e.name,r=t.rects.reference,i=t.rects.popper,a=t.modifiersData.preventOverflow,o=En(t,{elementContext:`reference`}),s=En(t,{altBoundary:!0}),c=jn(o,r),l=jn(s,i,a),u=Mn(c),d=Mn(l);t.modifiersData[n]={referenceClippingOffsets:c,popperEscapeOffsets:l,isReferenceHidden:u,hasPopperEscaped:d},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":u,"data-popper-escaped":d})}let Pn={name:`hide`,enabled:!0,phase:`main`,requiresIfExists:[`preventOverflow`],fn:Nn};function Fn(e,t,n){var r=q(e),i=[U,B].indexOf(r)>=0?-1:1,a=typeof n==`function`?n(Object.assign({},t,{placement:e})):n,o=a[0],s=a[1];return o||=0,s=(s||0)*i,[U,H].indexOf(r)>=0?{x:s,y:o}:{x:o,y:s}}function In(e){var t=e.state,n=e.options,r=e.name,i=n.offset,a=i===void 0?[0,0]:i,o=mt.reduce(function(e,n){return e[n]=Fn(n,t.rects,a),e},{}),s=o[t.placement],c=s.x,l=s.y;t.modifiersData.popperOffsets!=null&&(t.modifiersData.popperOffsets.x+=c,t.modifiersData.popperOffsets.y+=l),t.modifiersData[r]=o}let Ln={name:`offset`,enabled:!0,phase:`main`,requires:[`popperOffsets`],fn:In};function Rn(e){var t=e.state,n=e.name;t.modifiersData[n]=Tn({reference:t.rects.reference,element:t.rects.popper,placement:t.placement})}let zn={name:`popperOffsets`,enabled:!0,phase:`read`,fn:Rn,data:{}};function Bn(e){return e===`x`?`y`:`x`}function Vn(e){var t=e.state,n=e.options,r=e.name,i=n.mainAxis,a=i===void 0?!0:i,o=n.altAxis,s=o===void 0?!1:o,c=n.boundary,l=n.rootBoundary,u=n.altBoundary,d=n.padding,f=n.tether,p=f===void 0?!0:f,m=n.tetherOffset,h=m===void 0?0:m,g=En(t,{boundary:c,rootBoundary:l,padding:d,altBoundary:u}),_=q(t.placement),v=$t(t.placement),y=!v,b=Ut(_),x=Bn(b),S=t.modifiersData.popperOffsets,C=t.rects.reference,w=t.rects.popper,T=typeof h==`function`?h(Object.assign({},t.rects,{placement:t.placement})):h,E=typeof T==`number`?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),D=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,O={x:0,y:0};if(S){if(a){var k=b===`y`?B:U,ee=b===`y`?V:H,A=b===`y`?`height`:`width`,j=S[b],te=j+g[k],ne=j-g[ee],re=p?-w[A]/2:0,ie=v===st?C[A]:w[A],ae=v===st?-w[A]:-C[A],oe=t.elements.arrow,se=p&&oe?It(oe):{width:0,height:0},M=t.modifiersData[`arrow#persistent`]?t.modifiersData[`arrow#persistent`].padding:Kt(),ce=M[k],le=M[ee],ue=Wt(0,C[A],se[A]),N=y?C[A]/2-re-ue-ce-E.mainAxis:ie-ue-ce-E.mainAxis,de=y?-C[A]/2+re+ue+le+E.mainAxis:ae+ue+le+E.mainAxis,P=t.elements.arrow&&Ht(t.elements.arrow),fe=P?b===`y`?P.clientTop||0:P.clientLeft||0:0,F=D?.[b]??0,pe=j+N-F-fe,me=j+de-F,he=Wt(p?jt(te,pe):te,j,p?At(ne,me):ne);S[b]=he,O[b]=he-j}if(s){var ge=b===`x`?B:U,_e=b===`x`?V:H,I=S[x],L=x===`y`?`height`:`width`,R=I+g[ge],ve=I-g[_e],ye=[B,U].indexOf(_)!==-1,be=D?.[x]??0,xe=ye?R:I-C[L]-w[L]-be+E.altAxis,Se=ye?I+C[L]+w[L]-be-E.altAxis:ve,Ce=p&&ye?Gt(xe,I,Se):Wt(p?xe:R,I,p?Se:ve);S[x]=Ce,O[x]=Ce-I}t.modifiersData[r]=O}}let Hn={name:`preventOverflow`,enabled:!0,phase:`main`,fn:Vn,requiresIfExists:[`offset`]};function Un(e){return{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}}function Wn(e){return e===G(e)||!K(e)?pn(e):Un(e)}function Gn(e){var t=e.getBoundingClientRect(),n=Mt(t.width)/e.offsetWidth||1,r=Mt(t.height)/e.offsetHeight||1;return n!==1||r!==1}function Kn(e,t,n){n===void 0&&(n=!1);var r=K(t),i=K(t)&&Gn(t),a=Y(t),o=Ft(e,i,n),s={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(r||!r&&!n)&&((W(t)!==`body`||_n(a))&&(s=Wn(t)),K(t)?(c=Ft(t,!0),c.x+=t.clientLeft,c.y+=t.clientTop):a&&(c.x=mn(a))),{x:o.left+s.scrollLeft-c.x,y:o.top+s.scrollTop-c.y,width:o.width,height:o.height}}function qn(e){var t=new Map,n=new Set,r=[];e.forEach(function(e){t.set(e.name,e)});function i(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach(function(e){if(!n.has(e)){var r=t.get(e);r&&i(r)}}),r.push(e)}return e.forEach(function(e){n.has(e.name)||i(e)}),r}function Jn(e){var t=qn(e);return wt.reduce(function(e,n){return e.concat(t.filter(function(e){return e.phase===n}))},[])}function Yn(e){var t;return function(){return t||=new Promise(function(n){Promise.resolve().then(function(){t=void 0,n(e())})}),t}}function Xn(e){var t=e.reduce(function(e,t){var n=e[t.name];return e[t.name]=n?Object.assign({},n,t,{options:Object.assign({},n.options,t.options),data:Object.assign({},n.data,t.data)}):t,e},{});return Object.keys(t).map(function(e){return t[e]})}var Zn={placement:`bottom`,modifiers:[],strategy:`absolute`};function Qn(){return![...arguments].some(function(e){return!(e&&typeof e.getBoundingClientRect==`function`)})}function $n(e){e===void 0&&(e={});var t=e,n=t.defaultModifiers,r=n===void 0?[]:n,i=t.defaultOptions,a=i===void 0?Zn:i;return function(e,t,n){n===void 0&&(n=a);var i={placement:`bottom`,orderedModifiers:[],options:Object.assign({},Zn,a),modifiersData:{},elements:{reference:e,popper:t},attributes:{},styles:{}},o=[],s=!1,c={state:i,setOptions:function(n){var o=typeof n==`function`?n(i.options):n;u(),i.options=Object.assign({},a,i.options,o),i.scrollParents={reference:Tt(e)?yn(e):e.contextElement?yn(e.contextElement):[],popper:yn(t)};var s=Jn(Xn([].concat(r,i.options.modifiers)));return i.orderedModifiers=s.filter(function(e){return e.enabled}),l(),c.update()},forceUpdate:function(){if(!s){var e=i.elements,t=e.reference,n=e.popper;if(Qn(t,n)){i.rects={reference:Kn(t,Ht(n),i.options.strategy===`fixed`),popper:It(n)},i.reset=!1,i.placement=i.options.placement,i.orderedModifiers.forEach(function(e){return i.modifiersData[e.name]=Object.assign({},e.data)});for(var r=0;rNumber.parseInt(e,10)):typeof e==`function`?t=>e(t,this._element):e}_getPopperConfig(){let e={placement:this._getPlacement(),modifiers:[{name:`preventOverflow`,options:{boundary:this._config.boundary}},{name:`offset`,options:{offset:this._getOffset()}}]};return(this._inNavbar||this._config.display===`static`)&&(N.setDataAttribute(this._menu,`popper`,`static`),e.modifiers=[{name:`applyStyles`,enabled:!1}]),{...e,...b(this._config.popperConfig,[void 0,e])}}_selectMenuItem({key:e,target:t}){let n=F.find(`.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)`,this._menu).filter(e=>u(e));n.length&&S(n,t,e===or,!n.includes(t)).focus()}static jQueryInterface(e){return this.each(function(){let t=X.getOrCreateInstance(this,e);if(typeof e==`string`){if(t[e]===void 0)throw TypeError(`No method named "${e}"`);t[e]()}})}static clearMenus(e){if(e.button===2||e.type===`keyup`&&e.key!==`Tab`)return;let t=F.find(gr);for(let n of t){let t=X.getInstance(n);if(!t||t._config.autoClose===!1)continue;let r=e.composedPath(),i=r.includes(t._menu);if(r.includes(t._element)||t._config.autoClose===`inside`&&!i||t._config.autoClose===`outside`&&i||t._menu.contains(e.target)&&(e.type===`keyup`&&e.key===`Tab`||/input|select|option|textarea|form/i.test(e.target.tagName)))continue;let a={relatedTarget:t._element};e.type===`click`&&(a.clickEvent=e),t._completeHide(a)}}static dataApiKeydownHandler(e){let t=/input|textarea/i.test(e.target.tagName),n=e.key===`Escape`,r=[`ArrowUp`,or].includes(e.key);if(!r&&!n||t&&!n)return;e.preventDefault();let i=this.matches(hr)?this:F.prev(this,hr)[0]||F.next(this,hr)[0]||F.findOne(hr,e.delegateTarget.parentNode),a=X.getOrCreateInstance(i);if(r){e.stopPropagation(),a.show(),a._selectMenuItem(e);return}a._isShown()&&(e.stopPropagation(),a.hide(),i.focus())}}M.on(document,fr,hr,X.dataApiKeydownHandler),M.on(document,fr,_r,X.dataApiKeydownHandler),M.on(document,dr,X.clearMenus),M.on(document,pr,X.clearMenus),M.on(document,dr,hr,function(e){e.preventDefault(),X.getOrCreateInstance(this).toggle()}),y(X);let Er=`backdrop`,Dr=`show`,Or=`mousedown.bs.${Er}`,kr={className:`modal-backdrop`,clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:`body`},Ar={className:`string`,clickCallback:`(function|null)`,isAnimated:`boolean`,isVisible:`boolean`,rootElement:`(element|string)`};class jr extends de{constructor(e){super(),this._config=this._getConfig(e),this._isAppended=!1,this._element=null}static get Default(){return kr}static get DefaultType(){return Ar}static get NAME(){return Er}show(e){if(!this._config.isVisible){b(e);return}this._append();let t=this._getElement();this._config.isAnimated&&m(t),t.classList.add(Dr),this._emulateAnimation(()=>{b(e)})}hide(e){if(!this._config.isVisible){b(e);return}this._getElement().classList.remove(Dr),this._emulateAnimation(()=>{this.dispose(),b(e)})}dispose(){this._isAppended&&=(M.off(this._element,Or),this._element.remove(),!1)}_getElement(){if(!this._element){let e=document.createElement(`div`);e.className=this._config.className,this._config.isAnimated&&e.classList.add(`fade`),this._element=e}return this._element}_configAfterMerge(e){return e.rootElement=l(e.rootElement),e}_append(){if(this._isAppended)return;let e=this._getElement();this._config.rootElement.append(e),M.on(e,Or,()=>{b(this._config.clickCallback)}),this._isAppended=!0}_emulateAnimation(e){x(e,this._getElement(),this._config.isAnimated)}}let Mr=`.bs.focustrap`,Nr=`focusin${Mr}`,Pr=`keydown.tab${Mr}`,Fr=`backward`,Ir={autofocus:!0,trapElement:null},Lr={autofocus:`boolean`,trapElement:`element`};class Rr extends de{constructor(e){super(),this._config=this._getConfig(e),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return Ir}static get DefaultType(){return Lr}static get NAME(){return`focustrap`}activate(){this._isActive||=(this._config.autofocus&&this._config.trapElement.focus(),M.off(document,Mr),M.on(document,Nr,e=>this._handleFocusin(e)),M.on(document,Pr,e=>this._handleKeydown(e)),!0)}deactivate(){this._isActive&&(this._isActive=!1,M.off(document,Mr))}_handleFocusin(e){let{trapElement:t}=this._config;if(e.target===document||e.target===t||t.contains(e.target))return;let n=F.focusableChildren(t);n.length===0?t.focus():this._lastTabNavDirection===Fr?n[n.length-1].focus():n[0].focus()}_handleKeydown(e){e.key===`Tab`&&(this._lastTabNavDirection=e.shiftKey?Fr:`forward`)}}let zr=`.fixed-top, .fixed-bottom, .is-fixed, .sticky-top`,Br=`.sticky-top`,Vr=`padding-right`,Hr=`margin-right`;class Ur{constructor(){this._element=document.body}getWidth(){let e=document.documentElement.clientWidth;return Math.abs(window.innerWidth-e)}hide(){let e=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,Vr,t=>t+e),this._setElementAttributes(zr,Vr,t=>t+e),this._setElementAttributes(Br,Hr,t=>t-e)}reset(){this._resetElementAttributes(this._element,`overflow`),this._resetElementAttributes(this._element,Vr),this._resetElementAttributes(zr,Vr),this._resetElementAttributes(Br,Hr)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,`overflow`),this._element.style.overflow=`hidden`}_setElementAttributes(e,t,n){let r=this.getWidth();this._applyManipulationCallback(e,e=>{if(e!==this._element&&window.innerWidth>e.clientWidth+r)return;this._saveInitialAttribute(e,t);let i=window.getComputedStyle(e).getPropertyValue(t);e.style.setProperty(t,`${n(Number.parseFloat(i))}px`)})}_saveInitialAttribute(e,t){let n=e.style.getPropertyValue(t);n&&N.setDataAttribute(e,t,n)}_resetElementAttributes(e,t){this._applyManipulationCallback(e,e=>{let n=N.getDataAttribute(e,t);if(n===null){e.style.removeProperty(t);return}N.removeDataAttribute(e,t),e.style.setProperty(t,n)})}_applyManipulationCallback(e,t){if(c(e)){t(e);return}for(let n of F.find(e,this._element))t(n)}}let Z=`.bs.modal`,Wr=`hide${Z}`,Gr=`hidePrevented${Z}`,Kr=`hidden${Z}`,qr=`show${Z}`,Jr=`shown${Z}`,Yr=`resize${Z}`,Xr=`click.dismiss${Z}`,Zr=`mousedown.dismiss${Z}`,Qr=`keydown.dismiss${Z}`,$r=`click${Z}.data-api`,ei=`modal-open`,ti=`show`,ni=`modal-static`,ri={backdrop:!0,focus:!0,keyboard:!0},ii={backdrop:`(boolean|string)`,focus:`boolean`,keyboard:`boolean`};class ai extends P{constructor(e,t){super(e,t),this._dialog=F.findOne(`.modal-dialog`,this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new Ur,this._addEventListeners()}static get Default(){return ri}static get DefaultType(){return ii}static get NAME(){return`modal`}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){this._isShown||this._isTransitioning||M.trigger(this._element,qr,{relatedTarget:e}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(ei),this._adjustDialog(),this._backdrop.show(()=>this._showElement(e)))}hide(){!this._isShown||this._isTransitioning||M.trigger(this._element,Wr).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(ti),this._queueCallback(()=>this._hideModal(),this._element,this._isAnimated()))}dispose(){M.off(window,Z),M.off(this._dialog,Z),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new jr({isVisible:!!this._config.backdrop,isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Rr({trapElement:this._element})}_showElement(e){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display=`block`,this._element.removeAttribute(`aria-hidden`),this._element.setAttribute(`aria-modal`,!0),this._element.setAttribute(`role`,`dialog`),this._element.scrollTop=0;let t=F.findOne(`.modal-body`,this._dialog);t&&(t.scrollTop=0),m(this._element),this._element.classList.add(ti),this._queueCallback(()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,M.trigger(this._element,Jr,{relatedTarget:e})},this._dialog,this._isAnimated())}_addEventListeners(){M.on(this._element,Qr,e=>{if(e.key===`Escape`){if(this._config.keyboard){this.hide();return}this._triggerBackdropTransition()}}),M.on(window,Yr,()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()}),M.on(this._element,Zr,e=>{M.one(this._element,Xr,t=>{if(!(this._element!==e.target||this._element!==t.target)){if(this._config.backdrop===`static`){this._triggerBackdropTransition();return}this._config.backdrop&&this.hide()}})})}_hideModal(){this._element.style.display=`none`,this._element.setAttribute(`aria-hidden`,!0),this._element.removeAttribute(`aria-modal`),this._element.removeAttribute(`role`),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove(ei),this._resetAdjustments(),this._scrollBar.reset(),M.trigger(this._element,Kr)})}_isAnimated(){return this._element.classList.contains(`fade`)}_triggerBackdropTransition(){if(M.trigger(this._element,Gr).defaultPrevented)return;let e=this._element.scrollHeight>document.documentElement.clientHeight,t=this._element.style.overflowY;t===`hidden`||this._element.classList.contains(ni)||(e||(this._element.style.overflowY=`hidden`),this._element.classList.add(ni),this._queueCallback(()=>{this._element.classList.remove(ni),this._queueCallback(()=>{this._element.style.overflowY=t},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){let e=this._element.scrollHeight>document.documentElement.clientHeight,t=this._scrollBar.getWidth(),n=t>0;if(n&&!e){let e=v()?`paddingLeft`:`paddingRight`;this._element.style[e]=`${t}px`}if(!n&&e){let e=v()?`paddingRight`:`paddingLeft`;this._element.style[e]=`${t}px`}}_resetAdjustments(){this._element.style.paddingLeft=``,this._element.style.paddingRight=``}static jQueryInterface(e,t){return this.each(function(){let n=ai.getOrCreateInstance(this,e);if(typeof e==`string`){if(n[e]===void 0)throw TypeError(`No method named "${e}"`);n[e](t)}})}}M.on(document,$r,`[data-bs-toggle="modal"]`,function(e){let t=F.getElementFromSelector(this);[`A`,`AREA`].includes(this.tagName)&&e.preventDefault(),M.one(t,qr,e=>{e.defaultPrevented||M.one(t,Kr,()=>{u(this)&&this.focus()})});let n=F.findOne(`.modal.show`);n&&ai.getInstance(n).hide(),ai.getOrCreateInstance(t).toggle(this)}),pe(ai),y(ai);let Q=`.bs.offcanvas`,oi=`.data-api`,si=`load${Q}${oi}`,ci=`show`,li=`showing`,ui=`hiding`,di=`.offcanvas.show`,fi=`show${Q}`,pi=`shown${Q}`,mi=`hide${Q}`,hi=`hidePrevented${Q}`,gi=`hidden${Q}`,_i=`resize${Q}`,vi=`click${Q}${oi}`,yi=`keydown.dismiss${Q}`,bi={backdrop:!0,keyboard:!0,scroll:!1},xi={backdrop:`(boolean|string)`,keyboard:`boolean`,scroll:`boolean`};class Si extends P{constructor(e,t){super(e,t),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return bi}static get DefaultType(){return xi}static get NAME(){return`offcanvas`}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){this._isShown||M.trigger(this._element,fi,{relatedTarget:e}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||new Ur().hide(),this._element.setAttribute(`aria-modal`,!0),this._element.setAttribute(`role`,`dialog`),this._element.classList.add(li),this._queueCallback(()=>{(!this._config.scroll||this._config.backdrop)&&this._focustrap.activate(),this._element.classList.add(ci),this._element.classList.remove(li),M.trigger(this._element,pi,{relatedTarget:e})},this._element,!0))}hide(){!this._isShown||M.trigger(this._element,mi).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(ui),this._backdrop.hide(),this._queueCallback(()=>{this._element.classList.remove(ci,ui),this._element.removeAttribute(`aria-modal`),this._element.removeAttribute(`role`),this._config.scroll||new Ur().reset(),M.trigger(this._element,gi)},this._element,!0))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){let e=()=>{if(this._config.backdrop===`static`){M.trigger(this._element,hi);return}this.hide()},t=!!this._config.backdrop;return new jr({className:`offcanvas-backdrop`,isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?e:null})}_initializeFocusTrap(){return new Rr({trapElement:this._element})}_addEventListeners(){M.on(this._element,yi,e=>{if(e.key===`Escape`){if(this._config.keyboard){this.hide();return}M.trigger(this._element,hi)}})}static jQueryInterface(e){return this.each(function(){let t=Si.getOrCreateInstance(this,e);if(typeof e==`string`){if(t[e]===void 0||e.startsWith(`_`)||e===`constructor`)throw TypeError(`No method named "${e}"`);t[e](this)}})}}M.on(document,vi,`[data-bs-toggle="offcanvas"]`,function(e){let t=F.getElementFromSelector(this);if([`A`,`AREA`].includes(this.tagName)&&e.preventDefault(),d(this))return;M.one(t,gi,()=>{u(this)&&this.focus()});let n=F.findOne(di);n&&n!==t&&Si.getInstance(n).hide(),Si.getOrCreateInstance(t).toggle(this)}),M.on(window,si,()=>{for(let e of F.find(di))Si.getOrCreateInstance(e).show()}),M.on(window,_i,()=>{for(let e of F.find(`[aria-modal][class*=show][class*=offcanvas-]`))getComputedStyle(e).position!==`fixed`&&Si.getOrCreateInstance(e).hide()}),pe(Si),y(Si);let Ci={"*":[`class`,`dir`,`id`,`lang`,`role`,/^aria-[\w-]*$/i],a:[`target`,`href`,`title`,`rel`],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:[`src`,`srcset`,`alt`,`title`,`width`,`height`],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},wi=new Set([`background`,`cite`,`href`,`itemtype`,`longdesc`,`poster`,`src`,`xlink:href`]),Ti=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Ei=(e,t)=>{let n=e.nodeName.toLowerCase();return t.includes(n)?wi.has(n)?!!Ti.test(e.nodeValue):!0:t.filter(e=>e instanceof RegExp).some(e=>e.test(n))};function Di(e,t,n){if(!e.length)return e;if(n&&typeof n==`function`)return n(e);let r=new window.DOMParser().parseFromString(e,`text/html`),i=[].concat(...r.body.querySelectorAll(`*`));for(let e of i){let n=e.nodeName.toLowerCase();if(!Object.keys(t).includes(n)){e.remove();continue}let r=[].concat(...e.attributes),i=[].concat(t[`*`]||[],t[n]||[]);for(let t of r)Ei(t,i)||e.removeAttribute(t.nodeName)}return r.body.innerHTML}let Oi={allowList:Ci,content:{},extraClass:``,html:!1,sanitize:!0,sanitizeFn:null,template:`
`},ki={allowList:`object`,content:`object`,extraClass:`(string|function)`,html:`boolean`,sanitize:`boolean`,sanitizeFn:`(null|function)`,template:`string`},Ai={entry:`(string|element|function|null)`,selector:`(string|element)`};class ji extends de{constructor(e){super(),this._config=this._getConfig(e)}static get Default(){return Oi}static get DefaultType(){return ki}static get NAME(){return`TemplateFactory`}getContent(){return Object.values(this._config.content).map(e=>this._resolvePossibleFunction(e)).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(e){return this._checkContent(e),this._config.content={...this._config.content,...e},this}toHtml(){let e=document.createElement(`div`);e.innerHTML=this._maybeSanitize(this._config.template);for(let[t,n]of Object.entries(this._config.content))this._setContent(e,n,t);let t=e.children[0],n=this._resolvePossibleFunction(this._config.extraClass);return n&&t.classList.add(...n.split(` `)),t}_typeCheckConfig(e){super._typeCheckConfig(e),this._checkContent(e.content)}_checkContent(e){for(let[t,n]of Object.entries(e))super._typeCheckConfig({selector:t,entry:n},Ai)}_setContent(e,t,n){let r=F.findOne(n,e);if(r){if(t=this._resolvePossibleFunction(t),!t){r.remove();return}if(c(t)){this._putElementInTemplate(l(t),r);return}if(this._config.html){r.innerHTML=this._maybeSanitize(t);return}r.textContent=t}}_maybeSanitize(e){return this._config.sanitize?Di(e,this._config.allowList,this._config.sanitizeFn):e}_resolvePossibleFunction(e){return b(e,[void 0,this])}_putElementInTemplate(e,t){if(this._config.html){t.innerHTML=``,t.append(e);return}t.textContent=e.textContent}}let Mi=new Set([`sanitize`,`allowList`,`sanitizeFn`]),Ni=`fade`,Pi=`show`,Fi=`.modal`,Ii=`hide.bs.modal`,Li=`hover`,Ri=`focus`,zi=`click`,Bi={AUTO:`auto`,TOP:`top`,RIGHT:v()?`left`:`right`,BOTTOM:`bottom`,LEFT:v()?`right`:`left`},Vi={allowList:Ci,animation:!0,boundary:`clippingParents`,container:!1,customClass:``,delay:0,fallbackPlacements:[`top`,`right`,`bottom`,`left`],html:!1,offset:[0,6],placement:`top`,popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:``,title:``,trigger:`hover focus`},Hi={allowList:`object`,animation:`boolean`,boundary:`(string|element)`,container:`(string|element|boolean)`,customClass:`(string|function)`,delay:`(number|object)`,fallbackPlacements:`array`,html:`boolean`,offset:`(array|string|function)`,placement:`(string|function)`,popperConfig:`(null|object|function)`,sanitize:`boolean`,sanitizeFn:`(null|function)`,selector:`(string|boolean)`,template:`string`,title:`(string|element|function)`,trigger:`string`};class Ui extends P{constructor(e,t){if(rr===void 0)throw TypeError(`Bootstrap's tooltips require Popper (https://popper.js.org/docs/v2/)`);super(e,t),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return Vi}static get DefaultType(){return Hi}static get NAME(){return`tooltip`}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){if(this._isEnabled){if(this._isShown()){this._leave();return}this._enter()}}dispose(){clearTimeout(this._timeout),M.off(this._element.closest(Fi),Ii,this._hideModalHandler),this._element.getAttribute(`data-bs-original-title`)&&this._element.setAttribute(`title`,this._element.getAttribute(`data-bs-original-title`)),this._disposePopper(),super.dispose()}show(){if(this._element.style.display===`none`)throw Error(`Please use show on visible elements`);if(!(this._isWithContent()&&this._isEnabled))return;let e=M.trigger(this._element,this.constructor.eventName(`show`)),t=(f(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(e.defaultPrevented||!t)return;this._disposePopper();let n=this._getTipElement();this._element.setAttribute(`aria-describedby`,n.getAttribute(`id`));let{container:r}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(r.append(n),M.trigger(this._element,this.constructor.eventName(`inserted`))),this._popper=this._createPopper(n),n.classList.add(Pi),`ontouchstart`in document.documentElement)for(let e of[].concat(...document.body.children))M.on(e,`mouseover`,p);this._queueCallback(()=>{M.trigger(this._element,this.constructor.eventName(`shown`)),this._isHovered===!1&&this._leave(),this._isHovered=!1},this.tip,this._isAnimated())}hide(){if(!(!this._isShown()||M.trigger(this._element,this.constructor.eventName(`hide`)).defaultPrevented)){if(this._getTipElement().classList.remove(Pi),`ontouchstart`in document.documentElement)for(let e of[].concat(...document.body.children))M.off(e,`mouseover`,p);this._activeTrigger[zi]=!1,this._activeTrigger[Ri]=!1,this._activeTrigger[Li]=!1,this._isHovered=null,this._queueCallback(()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute(`aria-describedby`),M.trigger(this._element,this.constructor.eventName(`hidden`)))},this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return!!this._getTitle()}_getTipElement(){return this.tip||=this._createTipElement(this._newContent||this._getContentForTemplate()),this.tip}_createTipElement(e){let t=this._getTemplateFactory(e).toHtml();if(!t)return null;t.classList.remove(Ni,Pi),t.classList.add(`bs-${this.constructor.NAME}-auto`);let n=a(this.constructor.NAME).toString();return t.setAttribute(`id`,n),this._isAnimated()&&t.classList.add(Ni),t}setContent(e){this._newContent=e,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(e){return this._templateFactory?this._templateFactory.changeContent(e):this._templateFactory=new ji({...this._config,content:e,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute(`data-bs-original-title`)}_initializeOnDelegatedTarget(e){return this.constructor.getOrCreateInstance(e.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(Ni)}_isShown(){return this.tip&&this.tip.classList.contains(Pi)}_createPopper(e){let t=b(this._config.placement,[this,e,this._element]),n=Bi[t.toUpperCase()];return nr(this._element,e,this._getPopperConfig(n))}_getOffset(){let{offset:e}=this._config;return typeof e==`string`?e.split(`,`).map(e=>Number.parseInt(e,10)):typeof e==`function`?t=>e(t,this._element):e}_resolvePossibleFunction(e){return b(e,[this._element,this._element])}_getPopperConfig(e){let t={placement:e,modifiers:[{name:`flip`,options:{fallbackPlacements:this._config.fallbackPlacements}},{name:`offset`,options:{offset:this._getOffset()}},{name:`preventOverflow`,options:{boundary:this._config.boundary}},{name:`arrow`,options:{element:`.${this.constructor.NAME}-arrow`}},{name:`preSetPlacement`,enabled:!0,phase:`beforeMain`,fn:e=>{this._getTipElement().setAttribute(`data-popper-placement`,e.state.placement)}}]};return{...t,...b(this._config.popperConfig,[void 0,t])}}_setListeners(){let e=this._config.trigger.split(` `);for(let t of e)if(t===`click`)M.on(this._element,this.constructor.eventName(`click`),this._config.selector,e=>{let t=this._initializeOnDelegatedTarget(e);t._activeTrigger[zi]=!(t._isShown()&&t._activeTrigger[zi]),t.toggle()});else if(t!==`manual`){let e=t===Li?this.constructor.eventName(`mouseenter`):this.constructor.eventName(`focusin`),n=t===Li?this.constructor.eventName(`mouseleave`):this.constructor.eventName(`focusout`);M.on(this._element,e,this._config.selector,e=>{let t=this._initializeOnDelegatedTarget(e);t._activeTrigger[e.type===`focusin`?Ri:Li]=!0,t._enter()}),M.on(this._element,n,this._config.selector,e=>{let t=this._initializeOnDelegatedTarget(e);t._activeTrigger[e.type===`focusout`?Ri:Li]=t._element.contains(e.relatedTarget),t._leave()})}this._hideModalHandler=()=>{this._element&&this.hide()},M.on(this._element.closest(Fi),Ii,this._hideModalHandler)}_fixTitle(){let e=this._element.getAttribute(`title`);e&&(!this._element.getAttribute(`aria-label`)&&!this._element.textContent.trim()&&this._element.setAttribute(`aria-label`,e),this._element.setAttribute(`data-bs-original-title`,e),this._element.removeAttribute(`title`))}_enter(){if(this._isShown()||this._isHovered){this._isHovered=!0;return}this._isHovered=!0,this._setTimeout(()=>{this._isHovered&&this.show()},this._config.delay.show)}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout(()=>{this._isHovered||this.hide()},this._config.delay.hide))}_setTimeout(e,t){clearTimeout(this._timeout),this._timeout=setTimeout(e,t)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(e){let t=N.getDataAttributes(this._element);for(let e of Object.keys(t))Mi.has(e)&&delete t[e];return e={...t,...typeof e==`object`&&e?e:{}},e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e.container=e.container===!1?document.body:l(e.container),typeof e.delay==`number`&&(e.delay={show:e.delay,hide:e.delay}),typeof e.title==`number`&&(e.title=e.title.toString()),typeof e.content==`number`&&(e.content=e.content.toString()),e}_getDelegateConfig(){let e={};for(let[t,n]of Object.entries(this._config))this.constructor.Default[t]!==n&&(e[t]=n);return e.selector=!1,e.trigger=`manual`,e}_disposePopper(){this._popper&&=(this._popper.destroy(),null),this.tip&&=(this.tip.remove(),null)}static jQueryInterface(e){return this.each(function(){let t=Ui.getOrCreateInstance(this,e);if(typeof e==`string`){if(t[e]===void 0)throw TypeError(`No method named "${e}"`);t[e]()}})}}y(Ui);let Wi={...Ui.Default,content:``,offset:[0,8],placement:`right`,template:``,trigger:`click`},Gi={...Ui.DefaultType,content:`(null|string|element|function)`};class Ki extends Ui{static get Default(){return Wi}static get DefaultType(){return Gi}static get NAME(){return`popover`}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(e){return this.each(function(){let t=Ki.getOrCreateInstance(this,e);if(typeof e==`string`){if(t[e]===void 0)throw TypeError(`No method named "${e}"`);t[e]()}})}}y(Ki);let qi=`.bs.scrollspy`,Ji=`activate${qi}`,Yi=`click${qi}`,Xi=`load${qi}.data-api`,Zi=`active`,Qi=`[href]`,$i=`.nav-link`,ea=`${$i}, .nav-item > ${$i}, .list-group-item`,ta={offset:null,rootMargin:`0px 0px -25%`,smoothScroll:!1,target:null,threshold:[.1,.5,1]},na={offset:`(number|null)`,rootMargin:`string`,smoothScroll:`boolean`,target:`element`,threshold:`array`};class ra extends P{constructor(e,t){super(e,t),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement=getComputedStyle(this._element).overflowY===`visible`?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return ta}static get DefaultType(){return na}static get NAME(){return`scrollspy`}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(let e of this._observableSections.values())this._observer.observe(e)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(e){return e.target=l(e.target)||document.body,e.rootMargin=e.offset?`${e.offset}px 0px -30%`:e.rootMargin,typeof e.threshold==`string`&&(e.threshold=e.threshold.split(`,`).map(e=>Number.parseFloat(e))),e}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(M.off(this._config.target,Yi),M.on(this._config.target,Yi,Qi,e=>{let t=this._observableSections.get(e.target.hash);if(t){e.preventDefault();let n=this._rootElement||window,r=t.offsetTop-this._element.offsetTop;if(n.scrollTo){n.scrollTo({top:r,behavior:`smooth`});return}n.scrollTop=r}}))}_getNewObserver(){let e={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver(e=>this._observerCallback(e),e)}_observerCallback(e){let t=e=>this._targetLinks.get(`#${e.target.id}`),n=e=>{this._previousScrollData.visibleEntryTop=e.target.offsetTop,this._process(t(e))},r=(this._rootElement||document.documentElement).scrollTop,i=r>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=r;for(let a of e){if(!a.isIntersecting){this._activeTarget=null,this._clearActiveClass(t(a));continue}let e=a.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(i&&e){if(n(a),!r)return;continue}!i&&!e&&n(a)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;let e=F.find(Qi,this._config.target);for(let t of e){if(!t.hash||d(t))continue;let e=F.findOne(decodeURI(t.hash),this._element);u(e)&&(this._targetLinks.set(decodeURI(t.hash),t),this._observableSections.set(t.hash,e))}}_process(e){this._activeTarget!==e&&(this._clearActiveClass(this._config.target),this._activeTarget=e,e.classList.add(Zi),this._activateParents(e),M.trigger(this._element,Ji,{relatedTarget:e}))}_activateParents(e){if(e.classList.contains(`dropdown-item`)){F.findOne(`.dropdown-toggle`,e.closest(`.dropdown`)).classList.add(Zi);return}for(let t of F.parents(e,`.nav, .list-group`))for(let e of F.prev(t,ea))e.classList.add(Zi)}_clearActiveClass(e){e.classList.remove(Zi);let t=F.find(`${Qi}.${Zi}`,e);for(let e of t)e.classList.remove(Zi)}static jQueryInterface(e){return this.each(function(){let t=ra.getOrCreateInstance(this,e);if(typeof e==`string`){if(t[e]===void 0||e.startsWith(`_`)||e===`constructor`)throw TypeError(`No method named "${e}"`);t[e]()}})}}M.on(window,Xi,()=>{for(let e of F.find(`[data-bs-spy="scroll"]`))ra.getOrCreateInstance(e)}),y(ra);let ia=`.bs.tab`,aa=`hide${ia}`,oa=`hidden${ia}`,sa=`show${ia}`,ca=`shown${ia}`,la=`click${ia}`,ua=`keydown${ia}`,da=`load${ia}`,fa=`ArrowRight`,pa=`ArrowDown`,ma=`Home`,ha=`active`,ga=`fade`,_a=`show`,va=`.dropdown-toggle`,ya=`:not(${va})`,ba=`.nav-link${ya}, .list-group-item${ya}, [role="tab"]${ya}`,xa=`[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]`,Sa=`${ba}, ${xa}`,Ca=`.${ha}[data-bs-toggle="tab"], .${ha}[data-bs-toggle="pill"], .${ha}[data-bs-toggle="list"]`;class wa extends P{constructor(e){super(e),this._parent=this._element.closest(`.list-group, .nav, [role="tablist"]`),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),M.on(this._element,ua,e=>this._keydown(e)))}static get NAME(){return`tab`}show(){let e=this._element;if(this._elemIsActive(e))return;let t=this._getActiveElem(),n=t?M.trigger(t,aa,{relatedTarget:e}):null;M.trigger(e,sa,{relatedTarget:t}).defaultPrevented||n&&n.defaultPrevented||(this._deactivate(t,e),this._activate(e,t))}_activate(e,t){e&&(e.classList.add(ha),this._activate(F.getElementFromSelector(e)),this._queueCallback(()=>{if(e.getAttribute(`role`)!==`tab`){e.classList.add(_a);return}e.removeAttribute(`tabindex`),e.setAttribute(`aria-selected`,!0),this._toggleDropDown(e,!0),M.trigger(e,ca,{relatedTarget:t})},e,e.classList.contains(ga)))}_deactivate(e,t){e&&(e.classList.remove(ha),e.blur(),this._deactivate(F.getElementFromSelector(e)),this._queueCallback(()=>{if(e.getAttribute(`role`)!==`tab`){e.classList.remove(_a);return}e.setAttribute(`aria-selected`,!1),e.setAttribute(`tabindex`,`-1`),this._toggleDropDown(e,!1),M.trigger(e,oa,{relatedTarget:t})},e,e.classList.contains(ga)))}_keydown(e){if(![`ArrowLeft`,fa,`ArrowUp`,pa,ma,`End`].includes(e.key))return;e.stopPropagation(),e.preventDefault();let t=this._getChildren().filter(e=>!d(e)),n;if([ma,`End`].includes(e.key))n=t[e.key===ma?0:t.length-1];else{let r=[fa,pa].includes(e.key);n=S(t,e.target,r,!0)}n&&(n.focus({preventScroll:!0}),wa.getOrCreateInstance(n).show())}_getChildren(){return F.find(Sa,this._parent)}_getActiveElem(){return this._getChildren().find(e=>this._elemIsActive(e))||null}_setInitialAttributes(e,t){this._setAttributeIfNotExists(e,`role`,`tablist`);for(let e of t)this._setInitialAttributesOnChild(e)}_setInitialAttributesOnChild(e){e=this._getInnerElement(e);let t=this._elemIsActive(e),n=this._getOuterElement(e);e.setAttribute(`aria-selected`,t),n!==e&&this._setAttributeIfNotExists(n,`role`,`presentation`),t||e.setAttribute(`tabindex`,`-1`),this._setAttributeIfNotExists(e,`role`,`tab`),this._setInitialAttributesOnTargetPanel(e)}_setInitialAttributesOnTargetPanel(e){let t=F.getElementFromSelector(e);t&&(this._setAttributeIfNotExists(t,`role`,`tabpanel`),e.id&&this._setAttributeIfNotExists(t,`aria-labelledby`,`${e.id}`))}_toggleDropDown(e,t){let n=this._getOuterElement(e);if(!n.classList.contains(`dropdown`))return;let r=(e,r)=>{let i=F.findOne(e,n);i&&i.classList.toggle(r,t)};r(va,ha),r(`.dropdown-menu`,_a),n.setAttribute(`aria-expanded`,t)}_setAttributeIfNotExists(e,t,n){e.hasAttribute(t)||e.setAttribute(t,n)}_elemIsActive(e){return e.classList.contains(ha)}_getInnerElement(e){return e.matches(Sa)?e:F.findOne(Sa,e)}_getOuterElement(e){return e.closest(`.nav-item, .list-group-item`)||e}static jQueryInterface(e){return this.each(function(){let t=wa.getOrCreateInstance(this);if(typeof e==`string`){if(t[e]===void 0||e.startsWith(`_`)||e===`constructor`)throw TypeError(`No method named "${e}"`);t[e]()}})}}M.on(document,la,xa,function(e){[`A`,`AREA`].includes(this.tagName)&&e.preventDefault(),!d(this)&&wa.getOrCreateInstance(this).show()}),M.on(window,da,()=>{for(let e of F.find(Ca))wa.getOrCreateInstance(e)}),y(wa);let $=`.bs.toast`,Ta=`mouseover${$}`,Ea=`mouseout${$}`,Da=`focusin${$}`,Oa=`focusout${$}`,ka=`hide${$}`,Aa=`hidden${$}`,ja=`show${$}`,Ma=`shown${$}`,Na=`hide`,Pa=`show`,Fa=`showing`,Ia={animation:`boolean`,autohide:`boolean`,delay:`number`},La={animation:!0,autohide:!0,delay:5e3};class Ra extends P{constructor(e,t){super(e,t),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return La}static get DefaultType(){return Ia}static get NAME(){return`toast`}show(){M.trigger(this._element,ja).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add(`fade`),this._element.classList.remove(Na),m(this._element),this._element.classList.add(Pa,Fa),this._queueCallback(()=>{this._element.classList.remove(Fa),M.trigger(this._element,Ma),this._maybeScheduleHide()},this._element,this._config.animation))}hide(){!this.isShown()||M.trigger(this._element,ka).defaultPrevented||(this._element.classList.add(Fa),this._queueCallback(()=>{this._element.classList.add(Na),this._element.classList.remove(Fa,Pa),M.trigger(this._element,Aa)},this._element,this._config.animation))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(Pa),super.dispose()}isShown(){return this._element.classList.contains(Pa)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(e,t){switch(e.type){case`mouseover`:case`mouseout`:this._hasMouseInteraction=t;break;case`focusin`:case`focusout`:this._hasKeyboardInteraction=t;break}if(t){this._clearTimeout();return}let n=e.relatedTarget;this._element===n||this._element.contains(n)||this._maybeScheduleHide()}_setListeners(){M.on(this._element,Ta,e=>this._onInteraction(e,!0)),M.on(this._element,Ea,e=>this._onInteraction(e,!1)),M.on(this._element,Da,e=>this._onInteraction(e,!0)),M.on(this._element,Oa,e=>this._onInteraction(e,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(e){return this.each(function(){let t=Ra.getOrCreateInstance(this,e);if(typeof e==`string`){if(t[e]===void 0)throw TypeError(`No method named "${e}"`);t[e](this)}})}}return pe(Ra),y(Ra),{Alert:_e,Button:L,Carousel:We,Collapse:it,Dropdown:X,Modal:ai,Offcanvas:Si,Popover:Ki,ScrollSpy:ra,Tab:wa,Toast:Ra,Tooltip:Ui}}))}))();function e(e){document.documentElement.setAttribute(`data-theme`,e)}function t(){var t=document.documentElement.getAttribute(`data-theme`)===`light`?`dark`:`light`;e(t),localStorage.setItem(`theme`,t)}window.toggleTheme=t;function n(e){document.querySelectorAll(`.md-actions.open`).forEach(function(t){t!==e&&(t.classList.remove(`open`),t.querySelector(`.md-actions-toggle`).setAttribute(`aria-expanded`,`false`))})}function r(e){var t=e.closest(`.md-actions`),r=t.classList.contains(`open`);n(t),t.classList.toggle(`open`,!r),e.setAttribute(`aria-expanded`,String(!r))}window.toggleMdActionsMenu=r,document.addEventListener(`click`,function(e){e.target.closest(`.md-actions`)||n()}),document.addEventListener(`keydown`,function(e){e.key===`Escape`&&n()}),document.addEventListener(`click`,function(e){var t=e.target.closest(`[data-copy-url]`);if(t){e.preventDefault();var r=t.getAttribute(`data-copy-url`),i=t.querySelector(`.label`),a=i.textContent;fetch(r).then(function(e){return e.text()}).then(function(e){return navigator.clipboard.writeText(e)}).then(function(){i.textContent=`Copied!`,setTimeout(function(){i.textContent=a},1500)}).catch(function(){navigator.clipboard.writeText(r)}),n()}}),(function(){var e=document.querySelector(`[data-contact-form]`);if(e){var t=e.querySelector(`[data-contact-topic]`),n=e.querySelectorAll(`[data-topic]`);n.forEach(function(e){e.addEventListener(`click`,function(){var r=e.getAttribute(`data-topic`);n.forEach(function(t){t.setAttribute(`aria-pressed`,String(t===e))}),t.value=r})})}})();
\ No newline at end of file
diff --git a/src/App/assets/js/components/_contact.js b/src/App/assets/js/components/_contact.js
index 3c2db3a8..d00b0b99 100644
--- a/src/App/assets/js/components/_contact.js
+++ b/src/App/assets/js/components/_contact.js
@@ -1,5 +1,3 @@
-// Contact page: topic pills and the client-side "message sent" state.
-// The form is not wired to a backend yet, so submit only swaps the panels.
(function () {
var form = document.querySelector('[data-contact-form]');
if (!form) {
@@ -8,41 +6,14 @@
var topicInput = form.querySelector('[data-contact-topic]');
var pills = form.querySelectorAll('[data-topic]');
- var sent = document.querySelector('[data-contact-sent]');
- var sentTopic = sent.querySelector('[data-contact-sent-topic]');
- var reset = sent.querySelector('[data-contact-reset]');
-
- function currentPill() {
- return form.querySelector('[data-topic][aria-pressed="true"]');
- }
-
- function selectTopic(id) {
- pills.forEach(function (pill) {
- pill.setAttribute('aria-pressed', String(pill.getAttribute('data-topic') === id));
- });
- topicInput.value = id;
- }
pills.forEach(function (pill) {
pill.addEventListener('click', function () {
- selectTopic(pill.getAttribute('data-topic'));
+ var id = pill.getAttribute('data-topic');
+ pills.forEach(function (other) {
+ other.setAttribute('aria-pressed', String(other === pill));
+ });
+ topicInput.value = id;
});
});
-
- form.addEventListener('submit', function (event) {
- event.preventDefault();
- var pill = currentPill();
- sentTopic.textContent = pill ? pill.textContent : '';
- form.hidden = true;
- sent.hidden = false;
- sent.querySelector('h2').focus({ preventScroll: true });
- });
-
- reset.addEventListener('click', function () {
- form.reset();
- selectTopic(pills[0].getAttribute('data-topic'));
- sent.hidden = true;
- form.hidden = false;
- form.querySelector('input:not([type="hidden"])').focus();
- });
})();
diff --git a/src/App/assets/scss/components/_contact.scss b/src/App/assets/scss/components/_contact.scss
index 30d7922b..f6d01523 100644
--- a/src/App/assets/scss/components/_contact.scss
+++ b/src/App/assets/scss/components/_contact.scss
@@ -284,24 +284,38 @@ html[data-theme="dark"] .contact-page {
}
}
-.contact-consent {
- display: flex;
- align-items: flex-start;
- gap: 10px;
- font-size: 14px;
+.contact-error {
+ margin: 0;
+ font-size: 13px;
+ color: var(--rose);
+}
+
+.contact-alert {
+ margin: 0;
+ padding: 14px 16px;
+ border-radius: 8px;
+ border: 1px solid;
+ font-size: 14.5px;
line-height: 1.55;
- color: var(--text-muted);
- cursor: pointer;
- input {
- width: 18px;
- height: 18px;
- margin: 2px 0 0;
- flex-shrink: 0;
- accent-color: var(--amber);
+ a {
+ color: inherit;
+ text-decoration: underline;
}
}
+.contact-alert-success {
+ color: var(--teal);
+ background: var(--teal-dim);
+ border-color: var(--teal);
+}
+
+.contact-alert-error {
+ color: var(--rose);
+ background: var(--rose-dim);
+ border-color: var(--rose);
+}
+
.contact-form-foot {
display: flex;
align-items: center;
@@ -323,61 +337,6 @@ html[data-theme="dark"] .contact-page {
}
}
-.contact-sent {
- min-height: 560px;
- display: flex;
- flex-direction: column;
- justify-content: center;
- gap: 18px;
-
- &[hidden] {
- display: none;
- }
-
- h2 {
- font-size: 28px;
- line-height: 1.2;
- }
-
- p {
- max-width: 460px;
- font-size: 16.5px;
- line-height: 1.7;
- }
-
- strong {
- color: var(--text);
- font-weight: 600;
- }
-
- .btn {
- height: 44px;
- padding: 0 18px;
- color: var(--text-muted);
-
- &:hover {
- border-color: var(--amber);
- color: var(--text);
- }
- }
-}
-
-.contact-sent-icon {
- width: 48px;
- height: 48px;
- border-radius: var(--radius);
- display: flex;
- align-items: center;
- justify-content: center;
- background: var(--teal-dim);
- color: var(--teal);
-
- svg {
- width: 22px;
- height: 22px;
- }
-}
-
/* FAQ */
.contact-faq-layout {
display: grid;
@@ -488,8 +447,4 @@ html[data-theme="dark"] .contact-page {
justify-content: center;
}
}
-
- .contact-sent {
- min-height: 360px;
- }
}
diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php
index 965448c3..43b1b2b3 100644
--- a/src/App/src/ConfigProvider.php
+++ b/src/App/src/ConfigProvider.php
@@ -11,6 +11,7 @@
use Dot\Cache\Adapter\ArrayAdapter;
use Dot\Cache\Adapter\FilesystemAdapter;
use Light\App\DBAL\Types\UuidType;
+use Light\App\Factory\ContactServiceFactory;
use Light\App\Factory\EntityListenerResolverFactory;
use Light\App\Factory\FeedGeneratorFactory;
use Light\App\Factory\GetFeedViewHandlerFactory;
@@ -24,13 +25,16 @@
use Light\App\Factory\MarkdownExtensionFactory;
use Light\App\Factory\MarkdownRuntimeLoaderFactory;
use Light\App\Factory\PackageGeneratorFactory;
+use Light\App\Factory\PostContactCreateHandlerFactory;
use Light\App\Factory\SitemapGeneratorFactory;
use Light\App\Handler\GetFeedViewHandler;
use Light\App\Handler\GetIndexViewHandler;
use Light\App\Handler\GetMarkdownArticleHandler;
use Light\App\Handler\GetPackagesViewHandler;
use Light\App\Handler\GetSitemapViewHandler;
+use Light\App\Handler\PostContactCreateHandler;
use Light\App\Resolver\EntityListenerResolver;
+use Light\App\Service\ContactService;
use Light\App\Service\FeedGenerator;
use Light\App\Service\GitHubClient;
use Light\App\Service\GitHubClientInterface;
@@ -142,6 +146,8 @@ public function getDependencies(): array
GetMarkdownArticleHandler::class => GetMarkdownArticleHandlerFactory::class,
GetSitemapViewHandler::class => GetSitemapViewHandlerFactory::class,
GetPackagesViewHandler::class => GetPackagesViewHandlerFactory::class,
+ PostContactCreateHandler::class => PostContactCreateHandlerFactory::class,
+ ContactService::class => ContactServiceFactory::class,
FeedGenerator::class => FeedGeneratorFactory::class,
SitemapGenerator::class => SitemapGeneratorFactory::class,
GitHubClient::class => GitHubClientFactory::class,
diff --git a/src/App/src/Factory/ContactServiceFactory.php b/src/App/src/Factory/ContactServiceFactory.php
new file mode 100644
index 00000000..25c45b68
--- /dev/null
+++ b/src/App/src/Factory/ContactServiceFactory.php
@@ -0,0 +1,28 @@
+get('dot-mail.service.contact');
+ assert($mailService instanceof MailServiceInterface);
+
+ return new ContactService($mailService);
+ }
+}
diff --git a/src/App/src/Factory/PostContactCreateHandlerFactory.php b/src/App/src/Factory/PostContactCreateHandlerFactory.php
new file mode 100644
index 00000000..40a54291
--- /dev/null
+++ b/src/App/src/Factory/PostContactCreateHandlerFactory.php
@@ -0,0 +1,32 @@
+get(TemplateRendererInterface::class);
+ assert($template instanceof TemplateRendererInterface);
+
+ $contactService = $container->get(ContactService::class);
+ assert($contactService instanceof ContactService);
+
+ return new PostContactCreateHandler($template, $contactService);
+ }
+}
diff --git a/src/App/src/Handler/PostContactCreateHandler.php b/src/App/src/Handler/PostContactCreateHandler.php
new file mode 100644
index 00000000..1bab4f3c
--- /dev/null
+++ b/src/App/src/Handler/PostContactCreateHandler.php
@@ -0,0 +1,50 @@
+contactService->normalize($request->getParsedBody());
+ $errors = $this->contactService->validate($data);
+ $mailFailed = false;
+
+ if ($errors === [] && ! $this->contactService->send($data)) {
+ $mailFailed = true;
+ }
+
+ if ($errors !== [] || $mailFailed) {
+ $rawBody = $request->getParsedBody();
+
+ return new HtmlResponse($this->template->render(self::TEMPLATE, [
+ 'contact_errors' => $errors,
+ 'contact_values' => $data,
+ 'contact_mail_failed' => $mailFailed,
+ 'query' => ContactService::extractQuery(is_array($rawBody) ? $rawBody : []),
+ ]));
+ }
+
+ return new RedirectResponse('/contact/?contact=sent#contact-form', 303);
+ }
+}
diff --git a/src/App/src/RoutesDelegator.php b/src/App/src/RoutesDelegator.php
index 26efd8e7..10289e27 100644
--- a/src/App/src/RoutesDelegator.php
+++ b/src/App/src/RoutesDelegator.php
@@ -9,6 +9,7 @@
use Light\App\Handler\GetMarkdownArticleHandler;
use Light\App\Handler\GetPackagesViewHandler;
use Light\App\Handler\GetSitemapViewHandler;
+use Light\App\Handler\PostContactCreateHandler;
use Mezzio\Application;
use Psr\Container\ContainerInterface;
@@ -25,6 +26,8 @@ public function __invoke(ContainerInterface $container, string $serviceName, cal
$app->get('/sitemap/', [GetSitemapViewHandler::class], 'app::sitemap');
$app->get('/{categorySlug}/{slug}.md', [GetMarkdownArticleHandler::class], 'app::markdown-article');
+ $app->post('/contact/', [PostContactCreateHandler::class], 'app::create-contact');
+
// Route name kept as `page::…` because `@layout/default.html.twig` links it by name.
// The matching entry must stay out of `routes.page` in local.php to avoid a duplicate.
$app->get(
diff --git a/src/App/src/Service/ContactService.php b/src/App/src/Service/ContactService.php
new file mode 100644
index 00000000..a2ea7085
--- /dev/null
+++ b/src/App/src/Service/ContactService.php
@@ -0,0 +1,169 @@
+ 'Migration',
+ 'project' => 'Project work',
+ 'oss' => 'Open source',
+ 'other' => 'Something else',
+ ];
+
+ private const array REQUIRED_FIELDS = [
+ 'name' => 'Name',
+ 'email' => 'Work email',
+ 'message' => 'Message',
+ ];
+
+ private const array RESERVED_FIELDS = ['topic', 'name', 'email', 'company', 'stack', 'message', 'contact'];
+
+ public function __construct(private readonly MailServiceInterface $mailService)
+ {
+ }
+
+ /**
+ * @param array $source
+ * @return array
+ */
+ public static function extractQuery(array $source): array
+ {
+ $params = [];
+ foreach ($source as $param => $value) {
+ if (is_string($param) && ! in_array($param, self::RESERVED_FIELDS, true) && is_scalar($value)) {
+ $params[$param] = (string) $value;
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * @return ContactData
+ */
+ public function normalize(mixed $parsedBody): array
+ {
+ $raw = is_array($parsedBody) ? $parsedBody : [];
+ $topic = $this->stringValue($raw, 'topic');
+
+ return [
+ 'topic' => isset(self::TOPICS[$topic]) ? $topic : '',
+ 'name' => $this->stringValue($raw, 'name'),
+ 'email' => $this->stringValue($raw, 'email'),
+ 'company' => $this->stringValue($raw, 'company'),
+ 'stack' => $this->stringValue($raw, 'stack'),
+ 'message' => $this->stringValue($raw, 'message'),
+ 'query' => http_build_query(self::extractQuery($raw)),
+ ];
+ }
+
+ /**
+ * @param ContactData $data
+ * @return array
+ */
+ public function validate(array $data): array
+ {
+ $errors = [];
+ $notEmpty = new NotEmpty();
+
+ foreach (self::REQUIRED_FIELDS as $field => $label) {
+ if (! $notEmpty->isValid($data[$field])) {
+ $errors[$field] = $label . ' is required.';
+ }
+ }
+
+ if ($data['email'] !== '' && ! (new EmailAddress())->isValid($data['email'])) {
+ $errors['email'] = 'Enter a valid email address.';
+ }
+
+ return $errors;
+ }
+
+ /**
+ * @param ContactData $data
+ */
+ public function send(array $data): bool
+ {
+ $this->mailService->getMessage()->setReplyTo($data['email'], $data['name']);
+ $this->mailService->setSubject('New contact form submission on dotkernel.com');
+ $this->mailService->setBody($this->buildBody($data));
+
+ try {
+ return $this->mailService->send()->isValid();
+ } catch (Throwable) {
+ return false;
+ }
+ }
+
+ /**
+ * @param array $raw
+ */
+ private function stringValue(array $raw, string $key): string
+ {
+ $value = $raw[$key] ?? '';
+
+ return is_string($value) ? trim($value) : '';
+ }
+
+ /**
+ * @param ContactData $data
+ */
+ private function buildBody(array $data): string
+ {
+ $rows = [
+ 'Topic' => self::TOPICS[$data['topic']] ?? '',
+ 'Name' => $data['name'],
+ 'Work email' => $data['email'],
+ 'Company' => $data['company'],
+ 'Current stack' => $data['stack'],
+ 'Message' => $data['message'],
+ 'Query params' => $data['query'],
+ ];
+
+ $html = '';
+ foreach ($rows as $label => $value) {
+ if ($value === '') {
+ continue;
+ }
+
+ $html .= sprintf(
+ '%s: %s
',
+ htmlspecialchars($label, ENT_QUOTES),
+ nl2br(htmlspecialchars($value, ENT_QUOTES))
+ );
+ }
+
+ return $html;
+ }
+}
diff --git a/src/Blog/templates/page/contact.html.twig b/src/Blog/templates/page/contact.html.twig
index a6ba2979..ee6f43d9 100644
--- a/src/Blog/templates/page/contact.html.twig
+++ b/src/Blog/templates/page/contact.html.twig
@@ -75,20 +75,30 @@
-
diff --git a/src/Page/src/Handler/GetPageViewHandler.php b/src/Page/src/Handler/GetPageViewHandler.php
index 3c531894..357a7107 100644
--- a/src/Page/src/Handler/GetPageViewHandler.php
+++ b/src/Page/src/Handler/GetPageViewHandler.php
@@ -7,6 +7,7 @@
use Fig\Http\Message\StatusCodeInterface;
use Laminas\Diactoros\Response\HtmlResponse;
use Laminas\Diactoros\Response\TextResponse;
+use Light\App\Service\ContactService;
use Light\Blog\Repository\CategoryRepository;
use Light\Blog\Repository\PostRepository;
use Light\Page\Service\PageServiceInterface;
@@ -46,10 +47,13 @@ public function handle(ServerRequestInterface $request): ResponseInterface
$posts = $this->postRepository->getRecentPosts(3);
$categories = $this->categoryRepository->getCategories();
+ $query = $request->getQueryParams();
return new HtmlResponse(
$this->template->render($template, [
- 'posts' => $posts,
- 'categories' => $categories,
+ 'posts' => $posts,
+ 'categories' => $categories,
+ 'contact_success' => ($query['contact'] ?? null) === 'sent',
+ 'query' => ContactService::extractQuery($query),
])
);
}
diff --git a/test/Unit/App/Handler/PostContactCreateHandlerTest.php b/test/Unit/App/Handler/PostContactCreateHandlerTest.php
new file mode 100644
index 00000000..ef430714
--- /dev/null
+++ b/test/Unit/App/Handler/PostContactCreateHandlerTest.php
@@ -0,0 +1,168 @@
+
+ */
+ private function validData(): array
+ {
+ return [
+ 'topic' => 'migration',
+ 'name' => 'Jane Doe',
+ 'email' => 'jane@example.com',
+ 'company' => 'Acme Inc',
+ 'stack' => 'ZF3, PHP 7.4',
+ 'message' => 'We would like to talk about migrating our platform.',
+ ];
+ }
+
+ private function service(MailResult|Throwable $sendResult): ContactService
+ {
+ $message = $this->createStub(Email::class);
+
+ $mailService = $this->createStub(MailServiceInterface::class);
+ $mailService->method('getMessage')->willReturn($message);
+ if ($sendResult instanceof Throwable) {
+ $mailService->method('send')->willThrowException($sendResult);
+ } else {
+ $mailService->method('send')->willReturn($sendResult);
+ }
+
+ return new ContactService($mailService);
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function testValidSubmissionRedirectsOnSuccess(): void
+ {
+ $request = $this->createStub(ServerRequestInterface::class);
+ $request->method('getParsedBody')->willReturn($this->validData());
+
+ $template = $this->createMock(TemplateRendererInterface::class);
+ $template->expects($this->never())->method('render');
+
+ $handler = new PostContactCreateHandler($template, $this->service(new MailResult(true)));
+ $response = $handler->handle($request);
+
+ $this->assertInstanceOf(RedirectResponse::class, $response);
+ $this->assertSame(303, $response->getStatusCode());
+ $this->assertSame('/contact/?contact=sent#contact-form', $response->getHeaderLine('Location'));
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function testMissingRequiredFieldsRerendersWithErrors(): void
+ {
+ $data = $this->validData();
+ $data['name'] = '';
+ $data['message'] = '';
+
+ $request = $this->createStub(ServerRequestInterface::class);
+ $request->method('getParsedBody')->willReturn($data);
+
+ $template = $this->createMock(TemplateRendererInterface::class);
+ $template
+ ->expects($this->once())
+ ->method('render')
+ ->with(
+ 'page::contact',
+ $this->callback(function (array $params): bool {
+ return $params['contact_mail_failed'] === false
+ && array_key_exists('name', $params['contact_errors'])
+ && array_key_exists('message', $params['contact_errors'])
+ && $params['contact_values']['email'] === 'jane@example.com';
+ })
+ )
+ ->willReturn('');
+
+ $handler = new PostContactCreateHandler($template, $this->service(new MailResult(true)));
+ $response = $handler->handle($request);
+
+ $this->assertInstanceOf(HtmlResponse::class, $response);
+ $this->assertSame(200, $response->getStatusCode());
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function testRerenderKeepsAnyExtraQueryParamsAsDynamicHiddenFields(): void
+ {
+ $data = $this->validData();
+ $data['name'] = '';
+ $data['utm_source'] = 'google';
+ $data['ref'] = 'partner';
+
+ $request = $this->createStub(ServerRequestInterface::class);
+ $request->method('getParsedBody')->willReturn($data);
+
+ $template = $this->createMock(TemplateRendererInterface::class);
+ $template
+ ->expects($this->once())
+ ->method('render')
+ ->with(
+ 'page::contact',
+ $this->callback(fn (array $params): bool => $params['query'] === [
+ 'utm_source' => 'google',
+ 'ref' => 'partner',
+ ])
+ )
+ ->willReturn('');
+
+ $handler = new PostContactCreateHandler($template, $this->service(new MailResult(true)));
+ $handler->handle($request);
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function testMailTransportFailureRerendersWithMailFailedFlag(): void
+ {
+ $request = $this->createStub(ServerRequestInterface::class);
+ $request->method('getParsedBody')->willReturn($this->validData());
+
+ $template = $this->createMock(TemplateRendererInterface::class);
+ $template
+ ->expects($this->once())
+ ->method('render')
+ ->with(
+ 'page::contact',
+ $this->callback(function (array $params): bool {
+ return $params['contact_errors'] === []
+ && $params['contact_mail_failed'] === true;
+ })
+ )
+ ->willReturn('');
+
+ $handler = new PostContactCreateHandler(
+ $template,
+ $this->service(new RuntimeException('SMTP connection refused'))
+ );
+ $response = $handler->handle($request);
+
+ $this->assertInstanceOf(HtmlResponse::class, $response);
+ $this->assertSame(200, $response->getStatusCode());
+ }
+}
diff --git a/test/Unit/App/RoutesDelegatorTest.php b/test/Unit/App/RoutesDelegatorTest.php
index 4781a2fc..7d169200 100644
--- a/test/Unit/App/RoutesDelegatorTest.php
+++ b/test/Unit/App/RoutesDelegatorTest.php
@@ -9,6 +9,7 @@
use Light\App\Handler\GetMarkdownArticleHandler;
use Light\App\Handler\GetPackagesViewHandler;
use Light\App\Handler\GetSitemapViewHandler;
+use Light\App\Handler\PostContactCreateHandler;
use Light\App\RoutesDelegator;
use LightTest\Unit\UnitTest;
use Mezzio\Application;
@@ -38,6 +39,11 @@ public function testWillRegisterAllRoutes(): void
return $this->createStub(Route::class);
});
+ $app->method('post')->willReturnCallback(function (string $uri, mixed $handler, ?string $name = null) {
+ $this->registeredRoutes['POST ' . $uri] = ['handler' => $handler, 'name' => $name];
+
+ return $this->createStub(Route::class);
+ });
$application = (new RoutesDelegator())($container, '', fn () => $app);
@@ -66,5 +72,8 @@ public function testWillRegisterAllRoutes(): void
GetPackagesViewHandler::TEMPLATE,
$this->registeredRoutes['/dotkernel-packages-oss-lifecycle/']['name']
);
+
+ $this->assertSame([PostContactCreateHandler::class], $this->registeredRoutes['POST /contact/']['handler']);
+ $this->assertSame('app::create-contact', $this->registeredRoutes['POST /contact/']['name']);
}
}
diff --git a/test/Unit/App/Service/ContactServiceTest.php b/test/Unit/App/Service/ContactServiceTest.php
new file mode 100644
index 00000000..39499891
--- /dev/null
+++ b/test/Unit/App/Service/ContactServiceTest.php
@@ -0,0 +1,283 @@
+ 'migration',
+ 'name' => 'Jane Doe',
+ 'email' => 'jane@example.com',
+ 'company' => 'Acme Inc',
+ 'stack' => 'ZF3, PHP 7.4',
+ 'message' => 'We would like to talk about migrating our platform.',
+ 'query' => '',
+ ];
+ }
+
+ private function service(): ContactService
+ {
+ return new ContactService($this->createStub(MailServiceInterface::class));
+ }
+
+ public function testNormalizeExtractsAndTrimsKnownFields(): void
+ {
+ $data = $this->service()->normalize([
+ 'name' => ' Jane Doe ',
+ 'email' => 'jane@example.com',
+ 'unknown' => 'ignored',
+ ]);
+
+ $this->assertSame('Jane Doe', $data['name']);
+ $this->assertSame('jane@example.com', $data['email']);
+ $this->assertSame('', $data['company']);
+ $this->assertArrayNotHasKey('unknown', $data);
+ }
+
+ public function testNormalizeDropsAnUnknownTopic(): void
+ {
+ $data = $this->service()->normalize(['topic' => 'not-a-topic']);
+
+ $this->assertSame('', $data['topic']);
+ }
+
+ public function testNormalizeKeepsAKnownTopic(): void
+ {
+ $data = $this->service()->normalize(['topic' => 'oss']);
+
+ $this->assertSame('oss', $data['topic']);
+ }
+
+ public function testNormalizeReturnsAllEmptyWhenNotAnArray(): void
+ {
+ $data = $this->service()->normalize('not-an-array');
+
+ $this->assertSame(
+ [
+ 'topic' => '',
+ 'name' => '',
+ 'email' => '',
+ 'company' => '',
+ 'stack' => '',
+ 'message' => '',
+ 'query' => '',
+ ],
+ $data
+ );
+ }
+
+ public function testNormalizeBuildsQueryStringFromAnyExtraField(): void
+ {
+ $data = $this->service()->normalize([
+ 'utm_source' => 'google',
+ 'ref' => 'partner',
+ ]);
+
+ $this->assertSame('utm_source=google&ref=partner', $data['query']);
+ }
+
+ public function testNormalizeExcludesKnownContactFieldsFromQueryString(): void
+ {
+ $data = $this->service()->normalize([
+ 'name' => 'Jane Doe',
+ 'email' => 'jane@example.com',
+ 'contact' => 'sent',
+ 'utm_source' => 'google',
+ ]);
+
+ $this->assertSame('utm_source=google', $data['query']);
+ }
+
+ public function testExtractQueryReturnsAnyNonReservedParam(): void
+ {
+ $query = ContactService::extractQuery([
+ 'utm_source' => 'newsletter',
+ 'ref' => 'partner site',
+ ]);
+
+ $this->assertSame(['utm_source' => 'newsletter', 'ref' => 'partner site'], $query);
+ }
+
+ public function testExtractQueryExcludesReservedFormFields(): void
+ {
+ $query = ContactService::extractQuery([
+ 'name' => 'Jane Doe',
+ 'contact' => 'sent',
+ 'utm_source' => 'google',
+ ]);
+
+ $this->assertSame(['utm_source' => 'google'], $query);
+ }
+
+ public function testExtractQueryReturnsEmptyArrayWhenNoParams(): void
+ {
+ $this->assertSame([], ContactService::extractQuery([]));
+ }
+
+ public function testNormalizeIgnoresNonStringValues(): void
+ {
+ $data = $this->service()->normalize(['name' => ['not', 'a', 'string']]);
+
+ $this->assertSame('', $data['name']);
+ }
+
+ public function testValidSubmissionHasNoErrors(): void
+ {
+ $errors = $this->service()->validate($this->validData());
+
+ $this->assertSame([], $errors);
+ }
+
+ public function testMissingRequiredFieldsProduceErrors(): void
+ {
+ $data = $this->validData();
+ $data['name'] = '';
+ $data['email'] = '';
+ $data['message'] = '';
+
+ $errors = $this->service()->validate($data);
+
+ $this->assertArrayHasKey('name', $errors);
+ $this->assertArrayHasKey('email', $errors);
+ $this->assertArrayHasKey('message', $errors);
+ $this->assertArrayNotHasKey('company', $errors);
+ $this->assertArrayNotHasKey('stack', $errors);
+ }
+
+ public function testInvalidEmailFormatProducesError(): void
+ {
+ $data = $this->validData();
+ $data['email'] = 'not-an-email';
+
+ $errors = $this->service()->validate($data);
+
+ $this->assertArrayHasKey('email', $errors);
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function testSendReturnsTrueOnSuccess(): void
+ {
+ $message = $this->createMock(Email::class);
+ $message->expects($this->once())->method('setReplyTo')->with('jane@example.com', 'Jane Doe');
+
+ $mailService = $this->createMock(MailServiceInterface::class);
+ $mailService->method('getMessage')->willReturn($message);
+ $mailService
+ ->expects($this->once())
+ ->method('setSubject')
+ ->with('New contact form submission on dotkernel.com');
+ $mailService->expects($this->once())->method('setBody');
+ $mailService->method('send')->willReturn(new MailResult(true));
+
+ $service = new ContactService($mailService);
+
+ $this->assertTrue($service->send($this->validData()));
+ }
+
+ public function testSendReturnsFalseWhenTransportThrows(): void
+ {
+ $message = $this->createStub(Email::class);
+
+ $mailService = $this->createStub(MailServiceInterface::class);
+ $mailService->method('getMessage')->willReturn($message);
+ $mailService->method('send')->willThrowException(new RuntimeException('SMTP connection refused'));
+
+ $service = new ContactService($mailService);
+
+ $this->assertFalse($service->send($this->validData()));
+ }
+
+ public function testSendReturnsFalseWhenResultIsInvalid(): void
+ {
+ $message = $this->createStub(Email::class);
+
+ $mailService = $this->createStub(MailServiceInterface::class);
+ $mailService->method('getMessage')->willReturn($message);
+ $mailService->method('send')->willReturn(new MailResult(false, 'Invalid message'));
+
+ $service = new ContactService($mailService);
+
+ $this->assertFalse($service->send($this->validData()));
+ }
+
+ public function testSendIncludesQueryParamsInBodyWhenPresent(): void
+ {
+ $message = $this->createStub(Email::class);
+
+ $mailService = $this->createMock(MailServiceInterface::class);
+ $mailService->method('getMessage')->willReturn($message);
+ $mailService
+ ->expects($this->once())
+ ->method('setBody')
+ ->with($this->callback(
+ fn (string $body): bool => str_contains($body, 'Query params')
+ && str_contains($body, 'utm_source=google&ref=partner')
+ ));
+ $mailService->method('send')->willReturn(new MailResult(true));
+
+ $service = new ContactService($mailService);
+
+ $data = $this->validData();
+ $data['query'] = 'utm_source=google&ref=partner';
+
+ $service->send($data);
+ }
+
+ public function testSendOmitsQueryRowWhenAbsent(): void
+ {
+ $message = $this->createStub(Email::class);
+
+ $mailService = $this->createMock(MailServiceInterface::class);
+ $mailService->method('getMessage')->willReturn($message);
+ $mailService
+ ->expects($this->once())
+ ->method('setBody')
+ ->with($this->callback(
+ fn (string $body): bool => ! str_contains($body, 'Query params')
+ ));
+ $mailService->method('send')->willReturn(new MailResult(true));
+
+ $service = new ContactService($mailService);
+
+ $service->send($this->validData());
+ }
+
+ public function testSendUsesTheTopicLabelInBody(): void
+ {
+ $message = $this->createStub(Email::class);
+
+ $mailService = $this->createMock(MailServiceInterface::class);
+ $mailService->method('getMessage')->willReturn($message);
+ $mailService
+ ->expects($this->once())
+ ->method('setBody')
+ ->with($this->callback(
+ fn (string $body): bool => str_contains($body, 'Topic: Migration')
+ ));
+ $mailService->method('send')->willReturn(new MailResult(true));
+
+ (new ContactService($mailService))->send($this->validData());
+ }
+}
diff --git a/test/Unit/Page/Handler/GetPageViewHandlerTest.php b/test/Unit/Page/Handler/GetPageViewHandlerTest.php
index cf91de01..d9915991 100644
--- a/test/Unit/Page/Handler/GetPageViewHandlerTest.php
+++ b/test/Unit/Page/Handler/GetPageViewHandlerTest.php
@@ -180,14 +180,39 @@ private function createHandler(
/**
* @throws Exception
*/
- private function createRequest(string $routeName, string $accept = ''): ServerRequestInterface
+ public function testHandlePassesTheContactStateToTheTemplate(): void
{
+ $template = $this->createMock(TemplateRendererInterface::class);
+ $template
+ ->expects($this->once())
+ ->method('render')
+ ->with('page::contact', $this->callback(
+ fn (array $params): bool => $params['contact_success'] === true
+ && $params['query'] === ['utm_source' => 'newsletter']
+ ))
+ ->willReturn('');
+
+ $this->createHandler($template)->handle(
+ $this->createRequest('page::contact', query: ['contact' => 'sent', 'utm_source' => 'newsletter'])
+ );
+ }
+
+ /**
+ * @param array $query
+ * @throws Exception
+ */
+ private function createRequest(
+ string $routeName,
+ string $accept = '',
+ array $query = [],
+ ): ServerRequestInterface {
$routeResult = $this->createStub(RouteResult::class);
$routeResult->method('getMatchedRouteName')->willReturn($routeName);
$request = $this->createStub(ServerRequestInterface::class);
$request->method('getAttribute')->willReturn($routeResult);
$request->method('getHeaderLine')->willReturn($accept);
+ $request->method('getQueryParams')->willReturn($query);
return $request;
}
From 2953fbec9bb7a4a3989a1b6bc1d419a8e7c9fe1c Mon Sep 17 00:00:00 2001
From: olaru
Date: Fri, 25 Sep 2026 18:16:55 +0300
Subject: [PATCH 2/7] php test: update query
---
test/Unit/Page/Handler/PageHandlerTest.php | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/test/Unit/Page/Handler/PageHandlerTest.php b/test/Unit/Page/Handler/PageHandlerTest.php
index c8d52929..906c6dad 100644
--- a/test/Unit/Page/Handler/PageHandlerTest.php
+++ b/test/Unit/Page/Handler/PageHandlerTest.php
@@ -60,6 +60,10 @@ public function testHandle(): void
->method('getHeaderLine')
->willReturn('');
+ $request
+ ->method('getQueryParams')
+ ->willReturn([]);
+
$template
->method('render')
->willReturn('' . $routeName . '
');
From 7c5358a0793bc7e09b61d0e7c7818bd6b20baba0 Mon Sep 17 00:00:00 2001
From: olaru
Date: Mon, 28 Sep 2026 17:32:18 +0300
Subject: [PATCH 3/7] Contact form inject method
---
composer.json | 1 +
config/config.php | 1 +
src/App/src/ConfigProvider.php | 7 ++--
src/App/src/Factory/ContactServiceFactory.php | 28 ----------------
.../PostContactCreateHandlerFactory.php | 32 -------------------
.../src/Handler/PostContactCreateHandler.php | 5 +++
src/App/src/Service/ContactService.php | 2 ++
7 files changed, 12 insertions(+), 64 deletions(-)
delete mode 100644 src/App/src/Factory/ContactServiceFactory.php
delete mode 100644 src/App/src/Factory/PostContactCreateHandlerFactory.php
diff --git a/composer.json b/composer.json
index ceb62d93..a6fe63dc 100644
--- a/composer.json
+++ b/composer.json
@@ -32,6 +32,7 @@
"doctrine/data-fixtures": "^2.2",
"doctrine/doctrine-fixtures-bundle": "^4.3",
"dotkernel/dot-cache": "^4.0",
+ "dotkernel/dot-dependency-injection": "^1.4",
"dotkernel/dot-errorhandler": "^5.0.0",
"dotkernel/dot-mail": "^5.4",
"dotkernel/dot-response-header": "^3.6",
diff --git a/config/config.php b/config/config.php
index 1fe12615..147edc17 100644
--- a/config/config.php
+++ b/config/config.php
@@ -25,6 +25,7 @@
\Mezzio\Twig\ConfigProvider::class,
// Dotkernel packages
+ \Dot\DependencyInjection\ConfigProvider::class,
\Dot\ErrorHandler\ConfigProvider::class,
\Dot\Log\ConfigProvider::class,
\Dot\Mail\ConfigProvider::class,
diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php
index 43b1b2b3..8646a556 100644
--- a/src/App/src/ConfigProvider.php
+++ b/src/App/src/ConfigProvider.php
@@ -10,8 +10,8 @@
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
use Dot\Cache\Adapter\ArrayAdapter;
use Dot\Cache\Adapter\FilesystemAdapter;
+use Dot\DependencyInjection\Factory\AttributedServiceFactory;
use Light\App\DBAL\Types\UuidType;
-use Light\App\Factory\ContactServiceFactory;
use Light\App\Factory\EntityListenerResolverFactory;
use Light\App\Factory\FeedGeneratorFactory;
use Light\App\Factory\GetFeedViewHandlerFactory;
@@ -25,7 +25,6 @@
use Light\App\Factory\MarkdownExtensionFactory;
use Light\App\Factory\MarkdownRuntimeLoaderFactory;
use Light\App\Factory\PackageGeneratorFactory;
-use Light\App\Factory\PostContactCreateHandlerFactory;
use Light\App\Factory\SitemapGeneratorFactory;
use Light\App\Handler\GetFeedViewHandler;
use Light\App\Handler\GetIndexViewHandler;
@@ -146,8 +145,8 @@ public function getDependencies(): array
GetMarkdownArticleHandler::class => GetMarkdownArticleHandlerFactory::class,
GetSitemapViewHandler::class => GetSitemapViewHandlerFactory::class,
GetPackagesViewHandler::class => GetPackagesViewHandlerFactory::class,
- PostContactCreateHandler::class => PostContactCreateHandlerFactory::class,
- ContactService::class => ContactServiceFactory::class,
+ PostContactCreateHandler::class => AttributedServiceFactory::class,
+ ContactService::class => AttributedServiceFactory::class,
FeedGenerator::class => FeedGeneratorFactory::class,
SitemapGenerator::class => SitemapGeneratorFactory::class,
GitHubClient::class => GitHubClientFactory::class,
diff --git a/src/App/src/Factory/ContactServiceFactory.php b/src/App/src/Factory/ContactServiceFactory.php
deleted file mode 100644
index 25c45b68..00000000
--- a/src/App/src/Factory/ContactServiceFactory.php
+++ /dev/null
@@ -1,28 +0,0 @@
-get('dot-mail.service.contact');
- assert($mailService instanceof MailServiceInterface);
-
- return new ContactService($mailService);
- }
-}
diff --git a/src/App/src/Factory/PostContactCreateHandlerFactory.php b/src/App/src/Factory/PostContactCreateHandlerFactory.php
deleted file mode 100644
index 40a54291..00000000
--- a/src/App/src/Factory/PostContactCreateHandlerFactory.php
+++ /dev/null
@@ -1,32 +0,0 @@
-get(TemplateRendererInterface::class);
- assert($template instanceof TemplateRendererInterface);
-
- $contactService = $container->get(ContactService::class);
- assert($contactService instanceof ContactService);
-
- return new PostContactCreateHandler($template, $contactService);
- }
-}
diff --git a/src/App/src/Handler/PostContactCreateHandler.php b/src/App/src/Handler/PostContactCreateHandler.php
index 1bab4f3c..3b298b6e 100644
--- a/src/App/src/Handler/PostContactCreateHandler.php
+++ b/src/App/src/Handler/PostContactCreateHandler.php
@@ -4,6 +4,7 @@
namespace Light\App\Handler;
+use Dot\DependencyInjection\Attribute\Inject;
use Laminas\Diactoros\Response\HtmlResponse;
use Laminas\Diactoros\Response\RedirectResponse;
use Light\App\Service\ContactService;
@@ -18,6 +19,10 @@ class PostContactCreateHandler implements RequestHandlerInterface
{
public const string TEMPLATE = 'page::contact';
+ #[Inject(
+ TemplateRendererInterface::class,
+ ContactService::class,
+ )]
public function __construct(
protected TemplateRendererInterface $template,
protected ContactService $contactService,
diff --git a/src/App/src/Service/ContactService.php b/src/App/src/Service/ContactService.php
index a2ea7085..becccc66 100644
--- a/src/App/src/Service/ContactService.php
+++ b/src/App/src/Service/ContactService.php
@@ -4,6 +4,7 @@
namespace Light\App\Service;
+use Dot\DependencyInjection\Attribute\Inject;
use Dot\Mail\Service\MailServiceInterface;
use Laminas\Validator\EmailAddress;
use Laminas\Validator\NotEmpty;
@@ -49,6 +50,7 @@ final class ContactService
private const array RESERVED_FIELDS = ['topic', 'name', 'email', 'company', 'stack', 'message', 'contact'];
+ #[Inject('dot-mail.service.contact')]
public function __construct(private readonly MailServiceInterface $mailService)
{
}
From b5dcd29e65dfa07062c008e57257bf7906855f38 Mon Sep 17 00:00:00 2001
From: olaru
Date: Tue, 29 Sep 2026 10:00:30 +0300
Subject: [PATCH 4/7] Added recommended contact form handling and template
rendering
---
src/App/src/ConfigProvider.php | 2 +
src/App/src/Enum/ContactTopicEnum.php | 23 +++++++++++
.../Handler/GetContactCreateFormHandler.php | 38 +++++++++++++++++++
.../src/Handler/PostContactCreateHandler.php | 20 ++++++++--
src/App/src/RoutesDelegator.php | 2 +
src/App/src/Service/ContactService.php | 15 ++------
src/App/src/Service/SitemapGenerator.php | 3 ++
src/Blog/templates/page/contact.html.twig | 13 ++-----
src/Page/src/Handler/GetPageViewHandler.php | 8 +---
.../Handler/PostContactCreateHandlerTest.php | 30 ++++++++++++---
test/Unit/App/RoutesDelegatorTest.php | 4 ++
.../Unit/App/Service/SitemapGeneratorTest.php | 7 ++--
.../Page/Handler/GetPageViewHandlerTest.php | 27 +------------
test/Unit/Page/Handler/PageHandlerTest.php | 4 --
14 files changed, 127 insertions(+), 69 deletions(-)
create mode 100644 src/App/src/Enum/ContactTopicEnum.php
create mode 100644 src/App/src/Handler/GetContactCreateFormHandler.php
diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php
index 8646a556..f2d12a85 100644
--- a/src/App/src/ConfigProvider.php
+++ b/src/App/src/ConfigProvider.php
@@ -26,6 +26,7 @@
use Light\App\Factory\MarkdownRuntimeLoaderFactory;
use Light\App\Factory\PackageGeneratorFactory;
use Light\App\Factory\SitemapGeneratorFactory;
+use Light\App\Handler\GetContactCreateFormHandler;
use Light\App\Handler\GetFeedViewHandler;
use Light\App\Handler\GetIndexViewHandler;
use Light\App\Handler\GetMarkdownArticleHandler;
@@ -145,6 +146,7 @@ public function getDependencies(): array
GetMarkdownArticleHandler::class => GetMarkdownArticleHandlerFactory::class,
GetSitemapViewHandler::class => GetSitemapViewHandlerFactory::class,
GetPackagesViewHandler::class => GetPackagesViewHandlerFactory::class,
+ GetContactCreateFormHandler::class => AttributedServiceFactory::class,
PostContactCreateHandler::class => AttributedServiceFactory::class,
ContactService::class => AttributedServiceFactory::class,
FeedGenerator::class => FeedGeneratorFactory::class,
diff --git a/src/App/src/Enum/ContactTopicEnum.php b/src/App/src/Enum/ContactTopicEnum.php
new file mode 100644
index 00000000..4e8857e7
--- /dev/null
+++ b/src/App/src/Enum/ContactTopicEnum.php
@@ -0,0 +1,23 @@
+ 'Migration',
+ self::Project => 'Project work',
+ self::Oss => 'Open source',
+ self::Other => 'Something else',
+ };
+ }
+}
diff --git a/src/App/src/Handler/GetContactCreateFormHandler.php b/src/App/src/Handler/GetContactCreateFormHandler.php
new file mode 100644
index 00000000..48b93fff
--- /dev/null
+++ b/src/App/src/Handler/GetContactCreateFormHandler.php
@@ -0,0 +1,38 @@
+getQueryParams();
+
+ return new HtmlResponse($this->template->render(self::TEMPLATE, [
+ 'topics' => ContactTopicEnum::cases(),
+ 'contact_success' => ($query['contact'] ?? null) === 'sent',
+ 'query' => ContactService::extractQuery($query),
+ ]));
+ }
+}
diff --git a/src/App/src/Handler/PostContactCreateHandler.php b/src/App/src/Handler/PostContactCreateHandler.php
index 3b298b6e..a7e87aee 100644
--- a/src/App/src/Handler/PostContactCreateHandler.php
+++ b/src/App/src/Handler/PostContactCreateHandler.php
@@ -5,9 +5,12 @@
namespace Light\App\Handler;
use Dot\DependencyInjection\Attribute\Inject;
+use Fig\Http\Message\StatusCodeInterface;
use Laminas\Diactoros\Response\HtmlResponse;
use Laminas\Diactoros\Response\RedirectResponse;
+use Light\App\Enum\ContactTopicEnum;
use Light\App\Service\ContactService;
+use Mezzio\Helper\UrlHelper;
use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
@@ -17,14 +20,14 @@
class PostContactCreateHandler implements RequestHandlerInterface
{
- public const string TEMPLATE = 'page::contact';
-
#[Inject(
TemplateRendererInterface::class,
+ UrlHelper::class,
ContactService::class,
)]
public function __construct(
protected TemplateRendererInterface $template,
+ protected UrlHelper $urlHelper,
protected ContactService $contactService,
) {
}
@@ -42,7 +45,8 @@ public function handle(ServerRequestInterface $request): ResponseInterface
if ($errors !== [] || $mailFailed) {
$rawBody = $request->getParsedBody();
- return new HtmlResponse($this->template->render(self::TEMPLATE, [
+ return new HtmlResponse($this->template->render(GetContactCreateFormHandler::TEMPLATE, [
+ 'topics' => ContactTopicEnum::cases(),
'contact_errors' => $errors,
'contact_values' => $data,
'contact_mail_failed' => $mailFailed,
@@ -50,6 +54,14 @@ public function handle(ServerRequestInterface $request): ResponseInterface
]));
}
- return new RedirectResponse('/contact/?contact=sent#contact-form', 303);
+ return new RedirectResponse(
+ $this->urlHelper->generate(
+ GetContactCreateFormHandler::TEMPLATE,
+ [],
+ ['contact' => 'sent'],
+ 'contact-form'
+ ),
+ StatusCodeInterface::STATUS_SEE_OTHER
+ );
}
}
diff --git a/src/App/src/RoutesDelegator.php b/src/App/src/RoutesDelegator.php
index 10289e27..d9b7659f 100644
--- a/src/App/src/RoutesDelegator.php
+++ b/src/App/src/RoutesDelegator.php
@@ -4,6 +4,7 @@
namespace Light\App;
+use Light\App\Handler\GetContactCreateFormHandler;
use Light\App\Handler\GetFeedViewHandler;
use Light\App\Handler\GetIndexViewHandler;
use Light\App\Handler\GetMarkdownArticleHandler;
@@ -26,6 +27,7 @@ public function __invoke(ContainerInterface $container, string $serviceName, cal
$app->get('/sitemap/', [GetSitemapViewHandler::class], 'app::sitemap');
$app->get('/{categorySlug}/{slug}.md', [GetMarkdownArticleHandler::class], 'app::markdown-article');
+ $app->get('/contact/', [GetContactCreateFormHandler::class], GetContactCreateFormHandler::TEMPLATE);
$app->post('/contact/', [PostContactCreateHandler::class], 'app::create-contact');
// Route name kept as `page::…` because `@layout/default.html.twig` links it by name.
diff --git a/src/App/src/Service/ContactService.php b/src/App/src/Service/ContactService.php
index becccc66..febe3881 100644
--- a/src/App/src/Service/ContactService.php
+++ b/src/App/src/Service/ContactService.php
@@ -8,6 +8,7 @@
use Dot\Mail\Service\MailServiceInterface;
use Laminas\Validator\EmailAddress;
use Laminas\Validator\NotEmpty;
+use Light\App\Enum\ContactTopicEnum;
use Throwable;
use function htmlspecialchars;
@@ -35,13 +36,6 @@
*/
final class ContactService
{
- public const array TOPICS = [
- 'migration' => 'Migration',
- 'project' => 'Project work',
- 'oss' => 'Open source',
- 'other' => 'Something else',
- ];
-
private const array REQUIRED_FIELDS = [
'name' => 'Name',
'email' => 'Work email',
@@ -76,11 +70,10 @@ public static function extractQuery(array $source): array
*/
public function normalize(mixed $parsedBody): array
{
- $raw = is_array($parsedBody) ? $parsedBody : [];
- $topic = $this->stringValue($raw, 'topic');
+ $raw = is_array($parsedBody) ? $parsedBody : [];
return [
- 'topic' => isset(self::TOPICS[$topic]) ? $topic : '',
+ 'topic' => ContactTopicEnum::tryFrom($this->stringValue($raw, 'topic'))->value ?? '',
'name' => $this->stringValue($raw, 'name'),
'email' => $this->stringValue($raw, 'email'),
'company' => $this->stringValue($raw, 'company'),
@@ -144,7 +137,7 @@ private function stringValue(array $raw, string $key): string
private function buildBody(array $data): string
{
$rows = [
- 'Topic' => self::TOPICS[$data['topic']] ?? '',
+ 'Topic' => ContactTopicEnum::tryFrom($data['topic'])?->label() ?? '',
'Name' => $data['name'],
'Work email' => $data['email'],
'Company' => $data['company'],
diff --git a/src/App/src/Service/SitemapGenerator.php b/src/App/src/Service/SitemapGenerator.php
index 8afc921e..5f898beb 100644
--- a/src/App/src/Service/SitemapGenerator.php
+++ b/src/App/src/Service/SitemapGenerator.php
@@ -71,6 +71,9 @@ public function write(): int
$this->appendUrl($dom, $urlset, $this->baseUrl . '/dotkernel-packages-oss-lifecycle/');
$count++;
+ $this->appendUrl($dom, $urlset, $this->baseUrl . '/contact/');
+ $count++;
+
foreach ($this->pageRoutes as $routeUri) {
$this->appendUrl($dom, $urlset, sprintf('%s/%s/', $this->baseUrl, $routeUri));
$count++;
diff --git a/src/Blog/templates/page/contact.html.twig b/src/Blog/templates/page/contact.html.twig
index ee6f43d9..4c39ada8 100644
--- a/src/Blog/templates/page/contact.html.twig
+++ b/src/Blog/templates/page/contact.html.twig
@@ -5,13 +5,6 @@
{% block stylesheets %} {% endblock %}
-{% set topics = [
- {id: 'migration', label: 'Migration'},
- {id: 'project', label: 'Project work'},
- {id: 'oss', label: 'Open source'},
- {id: 'other', label: 'Something else'},
-] %}
-
{% set support_links = [
{label: 'Support documentation', href: 'https://github.com/dotkernel/.github/blob/main/SUPPORT.md'},
{label: 'Security policy', href: 'https://github.com/dotkernel/.github/blob/main/SECURITY.md'},
@@ -78,7 +71,7 @@
@@ -109,7 +109,7 @@
Blog
Discussion
About
- Contact us
+ Contact us
diff --git a/src/Blog/templates/page/JSON-LD/contact.jsonld.twig b/src/Blog/templates/page/JSON-LD/contact.jsonld.twig
index dad51248..8b58c796 100644
--- a/src/Blog/templates/page/JSON-LD/contact.jsonld.twig
+++ b/src/Blog/templates/page/JSON-LD/contact.jsonld.twig
@@ -2,8 +2,8 @@
{
"@context": "https://schema.org",
"@type": "ContactPage",
- "@id": "{{ app.url ~ path('page::contact') }}#webpage",
- "url": "{{ app.url ~ path('page::contact') }}",
+ "@id": "{{ app.url ~ path('app::create-contact-form') }}#webpage",
+ "url": "{{ app.url ~ path('app::create-contact-form') }}",
"name": "Contact",
"isPartOf": { "@id": "{{ app.url }}#website" }
}
diff --git a/src/Page/templates/page/about.html.twig b/src/Page/templates/page/about.html.twig
index b597c1eb..61501216 100644
--- a/src/Page/templates/page/about.html.twig
+++ b/src/Page/templates/page/about.html.twig
@@ -39,7 +39,7 @@
on. Everything here is made by a team that has to live with its own decisions.
@@ -311,7 +311,7 @@
a platform, hiring a team, or just want to argue about middleware, we would rather have the
conversation than the brochure.
diff --git a/src/Page/templates/page/admin.html.twig b/src/Page/templates/page/admin.html.twig
index 4feadf4c..dcec8785 100644
--- a/src/Page/templates/page/admin.html.twig
+++ b/src/Page/templates/page/admin.html.twig
@@ -655,7 +655,7 @@
the interface before you install anything. Dotkernel Admin is developed and led by the dev team at
Apidemia, and released as open source for the community.
diff --git a/src/Page/templates/page/api.html.twig b/src/Page/templates/page/api.html.twig
index 2b73003f..7c195002 100644
--- a/src/Page/templates/page/api.html.twig
+++ b/src/Page/templates/page/api.html.twig
@@ -692,7 +692,7 @@
internal tool for handling complex architectures, released under MIT as our way of giving back to
the community.
diff --git a/src/Page/templates/page/architecture.html.twig b/src/Page/templates/page/architecture.html.twig
index f4962616..e2747dc6 100644
--- a/src/Page/templates/page/architecture.html.twig
+++ b/src/Page/templates/page/architecture.html.twig
@@ -618,7 +618,7 @@
internal tool for handling complex architectures, released under MIT as our way of giving back to
the community.
diff --git a/src/Page/templates/page/frontend.html.twig b/src/Page/templates/page/frontend.html.twig
index cf8ca336..3edda148 100644
--- a/src/Page/templates/page/frontend.html.twig
+++ b/src/Page/templates/page/frontend.html.twig
@@ -625,7 +625,7 @@
as open source for the community. Try the demo to see the account flows before you install
anything.
diff --git a/src/Page/templates/page/light.html.twig b/src/Page/templates/page/light.html.twig
index c2014e2a..8f21cb10 100644
--- a/src/Page/templates/page/light.html.twig
+++ b/src/Page/templates/page/light.html.twig
@@ -585,7 +585,7 @@
open source for the community. If you want to see the whole application before you commit to it,
that is the entire promise of this skeleton.
diff --git a/src/Page/templates/page/queue.html.twig b/src/Page/templates/page/queue.html.twig
index 62c7fecf..b7ef1389 100644
--- a/src/Page/templates/page/queue.html.twig
+++ b/src/Page/templates/page/queue.html.twig
@@ -636,7 +636,7 @@
Dotkernel Queue is developed and led by the dev team at Apidemia - built to keep
real platforms responsive under real load, and released as open source for the community.
diff --git a/src/Page/templates/page/wsl2.html.twig b/src/Page/templates/page/wsl2.html.twig
index 749525b1..3d87d94d 100644
--- a/src/Page/templates/page/wsl2.html.twig
+++ b/src/Page/templates/page/wsl2.html.twig
@@ -367,7 +367,7 @@
Docker approximation of it.
diff --git a/test/Unit/App/Handler/GetContactCreateFormHandlerTest.php b/test/Unit/App/Handler/GetCreateContactFormHandlerTest.php
similarity index 90%
rename from test/Unit/App/Handler/GetContactCreateFormHandlerTest.php
rename to test/Unit/App/Handler/GetCreateContactFormHandlerTest.php
index ade887fe..4acd2cea 100644
--- a/test/Unit/App/Handler/GetContactCreateFormHandlerTest.php
+++ b/test/Unit/App/Handler/GetCreateContactFormHandlerTest.php
@@ -7,13 +7,13 @@
use Fig\Http\Message\StatusCodeInterface;
use Laminas\Diactoros\Response\HtmlResponse;
use Light\App\Enum\ContactTopicEnum;
-use Light\App\Handler\GetContactCreateFormHandler;
+use Light\App\Handler\GetCreateContactFormHandler;
use LightTest\Unit\UnitTest;
use Mezzio\Template\TemplateRendererInterface;
use PHPUnit\Framework\MockObject\Exception;
use Psr\Http\Message\ServerRequestInterface;
-class GetContactCreateFormHandlerTest extends UnitTest
+class GetCreateContactFormHandlerTest extends UnitTest
{
/**
* @throws Exception
@@ -31,7 +31,7 @@ public function testHandleRendersTheContactFormWithTheTopics(): void
))
->willReturn(' ');
- $response = (new GetContactCreateFormHandler($template))->handle($this->createRequest([]));
+ $response = (new GetCreateContactFormHandler($template))->handle($this->createRequest([]));
$this->assertInstanceOf(HtmlResponse::class, $response);
$this->assertSame(StatusCodeInterface::STATUS_OK, $response->getStatusCode());
@@ -53,7 +53,7 @@ public function testHandleFlagsSuccessAndKeepsExtraQueryParams(): void
))
->willReturn('');
- (new GetContactCreateFormHandler($template))->handle(
+ (new GetCreateContactFormHandler($template))->handle(
$this->createRequest(['contact' => 'sent', 'utm_source' => 'newsletter'])
);
}
diff --git a/test/Unit/App/Handler/PostContactCreateHandlerTest.php b/test/Unit/App/Handler/PostCreateContactHandlerTest.php
similarity index 91%
rename from test/Unit/App/Handler/PostContactCreateHandlerTest.php
rename to test/Unit/App/Handler/PostCreateContactHandlerTest.php
index 7719d709..a866cb2c 100644
--- a/test/Unit/App/Handler/PostContactCreateHandlerTest.php
+++ b/test/Unit/App/Handler/PostCreateContactHandlerTest.php
@@ -11,19 +11,20 @@
use Laminas\Diactoros\Response\HtmlResponse;
use Laminas\Diactoros\Response\RedirectResponse;
use Light\App\Enum\ContactTopicEnum;
-use Light\App\Handler\PostContactCreateHandler;
+use Light\App\Handler\PostCreateContactHandler;
use Light\App\Service\ContactService;
use Mezzio\Helper\UrlHelper;
use Mezzio\Template\TemplateRendererInterface;
use PHPUnit\Framework\MockObject\Exception;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
+use Psr\Log\NullLogger;
use RuntimeException;
use Throwable;
use function array_key_exists;
-class PostContactCreateHandlerTest extends TestCase
+class PostCreateContactHandlerTest extends TestCase
{
/**
* @return array
@@ -52,7 +53,7 @@ private function service(MailResult|Throwable $sendResult): ContactService
$mailService->method('send')->willReturn($sendResult);
}
- return new ContactService($mailService);
+ return new ContactService($mailService, new NullLogger());
}
/**
@@ -70,14 +71,14 @@ public function testValidSubmissionRedirectsOnSuccess(): void
$urlHelper
->expects($this->once())
->method('generate')
- ->with('page::contact', [], ['contact' => 'sent'], 'contact-form')
+ ->with('app::create-contact-form', [], ['contact' => 'sent'], 'contact-form')
->willReturn('/contact/?contact=sent#contact-form');
- $handler = new PostContactCreateHandler($template, $urlHelper, $this->service(new MailResult(true)));
+ $handler = new PostCreateContactHandler($template, $urlHelper, $this->service(new MailResult(true)));
$response = $handler->handle($request);
$this->assertInstanceOf(RedirectResponse::class, $response);
- $this->assertSame(StatusCodeInterface::STATUS_SEE_OTHER, $response->getStatusCode());
+ $this->assertSame(StatusCodeInterface::STATUS_FOUND, $response->getStatusCode());
$this->assertSame('/contact/?contact=sent#contact-form', $response->getHeaderLine('Location'));
}
@@ -109,7 +110,7 @@ public function testMissingRequiredFieldsRerendersWithErrors(): void
)
->willReturn('');
- $handler = new PostContactCreateHandler(
+ $handler = new PostCreateContactHandler(
$template,
$this->createStub(UrlHelper::class),
$this->service(new MailResult(true))
@@ -146,7 +147,7 @@ public function testRerenderKeepsAnyExtraQueryParamsAsDynamicHiddenFields(): voi
)
->willReturn('');
- $handler = new PostContactCreateHandler(
+ $handler = new PostCreateContactHandler(
$template,
$this->createStub(UrlHelper::class),
$this->service(new MailResult(true))
@@ -175,7 +176,7 @@ public function testMailTransportFailureRerendersWithMailFailedFlag(): void
)
->willReturn('');
- $handler = new PostContactCreateHandler(
+ $handler = new PostCreateContactHandler(
$template,
$this->createStub(UrlHelper::class),
$this->service(new RuntimeException('SMTP connection refused'))
diff --git a/test/Unit/App/RoutesDelegatorTest.php b/test/Unit/App/RoutesDelegatorTest.php
index 50c59b5a..51b96bac 100644
--- a/test/Unit/App/RoutesDelegatorTest.php
+++ b/test/Unit/App/RoutesDelegatorTest.php
@@ -4,13 +4,13 @@
namespace LightTest\Unit\App;
-use Light\App\Handler\GetContactCreateFormHandler;
+use Light\App\Handler\GetCreateContactFormHandler;
use Light\App\Handler\GetFeedViewHandler;
use Light\App\Handler\GetIndexViewHandler;
use Light\App\Handler\GetMarkdownArticleHandler;
use Light\App\Handler\GetPackagesViewHandler;
use Light\App\Handler\GetSitemapViewHandler;
-use Light\App\Handler\PostContactCreateHandler;
+use Light\App\Handler\PostCreateContactHandler;
use Light\App\RoutesDelegator;
use LightTest\Unit\UnitTest;
use Mezzio\Application;
@@ -74,10 +74,10 @@ public function testWillRegisterAllRoutes(): void
$this->registeredRoutes['/dotkernel-packages-oss-lifecycle/']['name']
);
- $this->assertSame([GetContactCreateFormHandler::class], $this->registeredRoutes['/contact/']['handler']);
- $this->assertSame('page::contact', $this->registeredRoutes['/contact/']['name']);
+ $this->assertSame([GetCreateContactFormHandler::class], $this->registeredRoutes['/contact/']['handler']);
+ $this->assertSame('app::create-contact-form', $this->registeredRoutes['/contact/']['name']);
- $this->assertSame([PostContactCreateHandler::class], $this->registeredRoutes['POST /contact/']['handler']);
+ $this->assertSame([PostCreateContactHandler::class], $this->registeredRoutes['POST /contact/']['handler']);
$this->assertSame('app::create-contact', $this->registeredRoutes['POST /contact/']['name']);
}
}
diff --git a/test/Unit/App/Service/ContactServiceTest.php b/test/Unit/App/Service/ContactServiceTest.php
index 39499891..4a97ca78 100644
--- a/test/Unit/App/Service/ContactServiceTest.php
+++ b/test/Unit/App/Service/ContactServiceTest.php
@@ -10,6 +10,8 @@
use Light\App\Service\ContactService;
use PHPUnit\Framework\MockObject\Exception;
use PHPUnit\Framework\TestCase;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
use RuntimeException;
use function str_contains;
@@ -37,7 +39,7 @@ private function validData(): array
private function service(): ContactService
{
- return new ContactService($this->createStub(MailServiceInterface::class));
+ return new ContactService($this->createStub(MailServiceInterface::class), new NullLogger());
}
public function testNormalizeExtractsAndTrimsKnownFields(): void
@@ -191,7 +193,7 @@ public function testSendReturnsTrueOnSuccess(): void
$mailService->expects($this->once())->method('setBody');
$mailService->method('send')->willReturn(new MailResult(true));
- $service = new ContactService($mailService);
+ $service = new ContactService($mailService, new NullLogger());
$this->assertTrue($service->send($this->validData()));
}
@@ -204,7 +206,16 @@ public function testSendReturnsFalseWhenTransportThrows(): void
$mailService->method('getMessage')->willReturn($message);
$mailService->method('send')->willThrowException(new RuntimeException('SMTP connection refused'));
- $service = new ContactService($mailService);
+ $logger = $this->createMock(LoggerInterface::class);
+ $logger
+ ->expects($this->once())
+ ->method('error')
+ ->with($this->anything(), $this->callback(
+ fn (array $context): bool => $context['message'] === 'SMTP connection refused'
+ && $context['exception'] instanceof RuntimeException
+ ));
+
+ $service = new ContactService($mailService, $logger);
$this->assertFalse($service->send($this->validData()));
}
@@ -217,7 +228,13 @@ public function testSendReturnsFalseWhenResultIsInvalid(): void
$mailService->method('getMessage')->willReturn($message);
$mailService->method('send')->willReturn(new MailResult(false, 'Invalid message'));
- $service = new ContactService($mailService);
+ $logger = $this->createMock(LoggerInterface::class);
+ $logger
+ ->expects($this->once())
+ ->method('error')
+ ->with($this->anything(), ['message' => 'Invalid message']);
+
+ $service = new ContactService($mailService, $logger);
$this->assertFalse($service->send($this->validData()));
}
@@ -237,7 +254,7 @@ public function testSendIncludesQueryParamsInBodyWhenPresent(): void
));
$mailService->method('send')->willReturn(new MailResult(true));
- $service = new ContactService($mailService);
+ $service = new ContactService($mailService, new NullLogger());
$data = $this->validData();
$data['query'] = 'utm_source=google&ref=partner';
@@ -259,7 +276,7 @@ public function testSendOmitsQueryRowWhenAbsent(): void
));
$mailService->method('send')->willReturn(new MailResult(true));
- $service = new ContactService($mailService);
+ $service = new ContactService($mailService, new NullLogger());
$service->send($this->validData());
}
@@ -278,6 +295,6 @@ public function testSendUsesTheTopicLabelInBody(): void
));
$mailService->method('send')->willReturn(new MailResult(true));
- (new ContactService($mailService))->send($this->validData());
+ (new ContactService($mailService, new NullLogger()))->send($this->validData());
}
}
From c4c081bfe4e5647a06c269f58ce0716a33dd38d3 Mon Sep 17 00:00:00 2001
From: OStefan2001
Date: Tue, 29 Sep 2026 12:16:09 +0300
Subject: [PATCH 7/7] update handler names and routes
---
config/autoload/local.php.dist | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/config/autoload/local.php.dist b/config/autoload/local.php.dist
index 3cd2b2e1..d6b209b2 100644
--- a/config/autoload/local.php.dist
+++ b/config/autoload/local.php.dist
@@ -83,13 +83,12 @@ return [
'sitemap' => [
'path' => realpath(__DIR__ . '/../../public/sitemap.xml'),
],
- // `dotkernel-packages-oss-lifecycle` is intentionally absent: it has a dedicated handler
- // and is routed in Light\App\RoutesDelegator. Re-adding it here registers the path twice.
+ // `dotkernel-packages-oss-lifecycle` and `contact` are intentionally absent: they have dedicated
+ // handlers and are routed in Light\App\RoutesDelegator. Re-adding them here registers the path twice.
'routes' => [
'page' => [
// Linked from the footer on every page, so this route must stay registered.
'about' => 'about',
- 'contact' => 'contact',
'api' => 'api',
'admin' => 'admin',
'queue' => 'queue',