├── .gitignore ├── LICENSE.md ├── README.md ├── dist ├── css │ ├── style.bundle.css │ └── style.bundle.css.map ├── favicon │ └── favicon.ico ├── fonts │ └── Roboto-Regular.ttf ├── img │ └── logo.svg ├── index.html ├── js │ ├── bundle.js │ ├── bundle.js.LICENSE.txt │ └── bundle.js.map ├── second.html └── uploads │ └── test.jpg ├── docs ├── article.md ├── article_new.md ├── featured-image.png └── img │ ├── dist.png │ └── visual_studio_code.png ├── img └── featured-image.png ├── package-lock.json ├── package.json ├── src ├── favicon │ └── favicon.ico ├── fonts │ └── Roboto-Regular.ttf ├── html │ ├── includes │ │ ├── footer.html │ │ └── header.html │ └── views │ │ ├── index.html │ │ └── second.html ├── img │ └── logo.svg ├── js │ └── index.js ├── scss │ └── style.scss └── uploads │ └── test.jpg └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | Copyright © 2018-present Sergienko Anton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # static-site-webpack-habr 2 | 3 | ![Webpack](https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/master/img/featured-image.png) 4 | 5 | Демонстрация сборки HTML страниц (или статического сайта) с помощью `Webpack`. 6 | 7 | Статья (в ней прописан устаревший [вариант](https://github.com/Harrix/static-site-webpack-habr/releases/tag/v1.0)) [https://habr.com/ru/articles/350886/](https://habr.com/post/350886/). 8 | 9 | Новая статьи: [article.md](https://github.com/Harrix/static-site-webpack-habr/blob/master/docs/article.md) (в процессе долгого написания). 10 | 11 | Лицензия: [MIT](https://github.com/Harrix/static-site-webpack-habr/blob/master/LICENSE.md). 12 | 13 | ## Использование 14 | 15 | 1. Установите программу [Node.js](https://nodejs.org/en/). 16 | 2. Создайте директорию (например, 'C:\projects\test'), в которой будете создавать своей проект. 17 | 3. В командной строке (например, 'cmd' или 'PowerShell') перейдите в эту папку. 18 | 4. Скопируйте в папку файлы из данного проекта. 19 | 20 | ## Автор 21 | 22 | Автор: Сергиенко Антон Борисович. 23 | -------------------------------------------------------------------------------- /dist/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/dist/favicon/favicon.ico -------------------------------------------------------------------------------- /dist/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/dist/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /dist/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Заголовок | Проект 9 | 10 | 11 |
12 |
Первая страница.
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /dist/js/bundle.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see bundle.js.LICENSE.txt */ 2 | (()=>{"use strict";var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}};(()=>{var e={};t.r(e),t.d(e,{afterMain:()=>w,afterRead:()=>b,afterWrite:()=>T,applyStyles:()=>D,arrow:()=>G,auto:()=>r,basePlacements:()=>a,beforeMain:()=>v,beforeRead:()=>g,beforeWrite:()=>A,bottom:()=>n,clippingParents:()=>h,computeStyles:()=>et,createPopper:()=>Dt,createPopperBase:()=>St,createPopperLite:()=>$t,detectOverflow:()=>_t,end:()=>c,eventListeners:()=>nt,flip:()=>bt,hide:()=>wt,left:()=>o,main:()=>y,modifierPhases:()=>C,offset:()=>At,placements:()=>m,popper:()=>d,popperGenerator:()=>Lt,popperOffsets:()=>Et,preventOverflow:()=>Tt,read:()=>_,reference:()=>f,right:()=>s,start:()=>l,top:()=>i,variationPlacements:()=>p,viewport:()=>u,write:()=>E});var i="top",n="bottom",s="right",o="left",r="auto",a=[i,n,s,o],l="start",c="end",h="clippingParents",u="viewport",d="popper",f="reference",p=a.reduce((function(t,e){return t.concat([e+"-"+l,e+"-"+c])}),[]),m=[].concat(a,[r]).reduce((function(t,e){return t.concat([e,e+"-"+l,e+"-"+c])}),[]),g="beforeRead",_="read",b="afterRead",v="beforeMain",y="main",w="afterMain",A="beforeWrite",E="write",T="afterWrite",C=[g,_,b,v,y,w,A,E,T];function O(t){return t?(t.nodeName||"").toLowerCase():null}function x(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function k(t){return t instanceof x(t).Element||t instanceof Element}function L(t){return t instanceof x(t).HTMLElement||t instanceof HTMLElement}function S(t){return"undefined"!=typeof ShadowRoot&&(t instanceof x(t).ShadowRoot||t instanceof ShadowRoot)}const D={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];L(s)&&O(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});L(n)&&O(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function $(t){return t.split("-")[0]}var I=Math.max,N=Math.min,P=Math.round;function j(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function M(){return!/^((?!chrome|android).)*safari/i.test(j())}function F(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&L(t)&&(s=t.offsetWidth>0&&P(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&P(n.height)/t.offsetHeight||1);var r=(k(t)?x(t):window).visualViewport,a=!M()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,u=n.height/o;return{width:h,height:u,top:c,right:l+h,bottom:c+u,left:l,x:l,y:c}}function H(t){var e=F(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function W(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&S(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function B(t){return x(t).getComputedStyle(t)}function z(t){return["table","td","th"].indexOf(O(t))>=0}function R(t){return((k(t)?t.ownerDocument:t.document)||window.document).documentElement}function q(t){return"html"===O(t)?t:t.assignedSlot||t.parentNode||(S(t)?t.host:null)||R(t)}function V(t){return L(t)&&"fixed"!==B(t).position?t.offsetParent:null}function K(t){for(var e=x(t),i=V(t);i&&z(i)&&"static"===B(i).position;)i=V(i);return i&&("html"===O(i)||"body"===O(i)&&"static"===B(i).position)?e:i||function(t){var e=/firefox/i.test(j());if(/Trident/i.test(j())&&L(t)&&"fixed"===B(t).position)return null;var i=q(t);for(S(i)&&(i=i.host);L(i)&&["html","body"].indexOf(O(i))<0;){var n=B(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Q(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function X(t,e,i){return I(t,N(e,i))}function Y(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function U(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const G={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,r=t.state,l=t.name,c=t.options,h=r.elements.arrow,u=r.modifiersData.popperOffsets,d=$(r.placement),f=Q(d),p=[o,s].indexOf(d)>=0?"height":"width";if(h&&u){var m=function(t,e){return Y("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:U(t,a))}(c.padding,r),g=H(h),_="y"===f?i:o,b="y"===f?n:s,v=r.rects.reference[p]+r.rects.reference[f]-u[f]-r.rects.popper[p],y=u[f]-r.rects.reference[f],w=K(h),A=w?"y"===f?w.clientHeight||0:w.clientWidth||0:0,E=v/2-y/2,T=m[_],C=A-g[p]-m[b],O=A/2-g[p]/2+E,x=X(T,O,C),k=f;r.modifiersData[l]=((e={})[k]=x,e.centerOffset=x-O,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&W(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function J(t){return t.split("-")[1]}var Z={top:"auto",right:"auto",bottom:"auto",left:"auto"};function tt(t){var e,r=t.popper,a=t.popperRect,l=t.placement,h=t.variation,u=t.offsets,d=t.position,f=t.gpuAcceleration,p=t.adaptive,m=t.roundOffsets,g=t.isFixed,_=u.x,b=void 0===_?0:_,v=u.y,y=void 0===v?0:v,w="function"==typeof m?m({x:b,y}):{x:b,y};b=w.x,y=w.y;var A=u.hasOwnProperty("x"),E=u.hasOwnProperty("y"),T=o,C=i,O=window;if(p){var k=K(r),L="clientHeight",S="clientWidth";if(k===x(r)&&"static"!==B(k=R(r)).position&&"absolute"===d&&(L="scrollHeight",S="scrollWidth"),l===i||(l===o||l===s)&&h===c)C=n,y-=(g&&k===O&&O.visualViewport?O.visualViewport.height:k[L])-a.height,y*=f?1:-1;if(l===o||(l===i||l===n)&&h===c)T=s,b-=(g&&k===O&&O.visualViewport?O.visualViewport.width:k[S])-a.width,b*=f?1:-1}var D,$=Object.assign({position:d},p&&Z),I=!0===m?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:P(i*s)/s||0,y:P(n*s)/s||0}}({x:b,y},x(r)):{x:b,y};return b=I.x,y=I.y,f?Object.assign({},$,((D={})[C]=E?"0":"",D[T]=A?"0":"",D.transform=(O.devicePixelRatio||1)<=1?"translate("+b+"px, "+y+"px)":"translate3d("+b+"px, "+y+"px, 0)",D)):Object.assign({},$,((e={})[C]=E?y+"px":"",e[T]=A?b+"px":"",e.transform="",e))}const et={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:$(e.placement),variation:J(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,tt(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,tt(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var it={passive:!0};const nt={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=x(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,it)})),a&&l.addEventListener("resize",i.update,it),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,it)})),a&&l.removeEventListener("resize",i.update,it)}},data:{}};var st={left:"right",right:"left",bottom:"top",top:"bottom"};function ot(t){return t.replace(/left|right|bottom|top/g,(function(t){return st[t]}))}var rt={start:"end",end:"start"};function at(t){return t.replace(/start|end/g,(function(t){return rt[t]}))}function lt(t){var e=x(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ct(t){return F(R(t)).left+lt(t).scrollLeft}function ht(t){var e=B(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function ut(t){return["html","body","#document"].indexOf(O(t))>=0?t.ownerDocument.body:L(t)&&ht(t)?t:ut(q(t))}function dt(t,e){var i;void 0===e&&(e=[]);var n=ut(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=x(n),r=s?[o].concat(o.visualViewport||[],ht(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(dt(q(r)))}function ft(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function pt(t,e,i){return e===u?ft(function(t,e){var i=x(t),n=R(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=M();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+ct(t),y:l}}(t,i)):k(e)?function(t,e){var i=F(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):ft(function(t){var e,i=R(t),n=lt(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=I(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=I(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ct(t),l=-n.scrollTop;return"rtl"===B(s||i).direction&&(a+=I(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(R(t)))}function mt(t,e,i,n){var s="clippingParents"===e?function(t){var e=dt(q(t)),i=["absolute","fixed"].indexOf(B(t).position)>=0&&L(t)?K(t):t;return k(i)?e.filter((function(t){return k(t)&&W(t,i)&&"body"!==O(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=pt(t,i,n);return e.top=I(s.top,e.top),e.right=N(s.right,e.right),e.bottom=N(s.bottom,e.bottom),e.left=I(s.left,e.left),e}),pt(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}function gt(t){var e,r=t.reference,a=t.element,h=t.placement,u=h?$(h):null,d=h?J(h):null,f=r.x+r.width/2-a.width/2,p=r.y+r.height/2-a.height/2;switch(u){case i:e={x:f,y:r.y-a.height};break;case n:e={x:f,y:r.y+r.height};break;case s:e={x:r.x+r.width,y:p};break;case o:e={x:r.x-a.width,y:p};break;default:e={x:r.x,y:r.y}}var m=u?Q(u):null;if(null!=m){var g="y"===m?"height":"width";switch(d){case l:e[m]=e[m]-(r[g]/2-a[g]/2);break;case c:e[m]=e[m]+(r[g]/2-a[g]/2)}}return e}function _t(t,e){void 0===e&&(e={});var o=e,r=o.placement,l=void 0===r?t.placement:r,c=o.strategy,p=void 0===c?t.strategy:c,m=o.boundary,g=void 0===m?h:m,_=o.rootBoundary,b=void 0===_?u:_,v=o.elementContext,y=void 0===v?d:v,w=o.altBoundary,A=void 0!==w&&w,E=o.padding,T=void 0===E?0:E,C=Y("number"!=typeof T?T:U(T,a)),O=y===d?f:d,x=t.rects.popper,L=t.elements[A?O:y],S=mt(k(L)?L:L.contextElement||R(t.elements.popper),g,b,p),D=F(t.elements.reference),$=gt({reference:D,element:x,strategy:"absolute",placement:l}),I=ft(Object.assign({},x,$)),N=y===d?I:D,P={top:S.top-N.top+C.top,bottom:N.bottom-S.bottom+C.bottom,left:S.left-N.left+C.left,right:N.right-S.right+C.right},j=t.modifiersData.offset;if(y===d&&j){var M=j[l];Object.keys(P).forEach((function(t){var e=[s,n].indexOf(t)>=0?1:-1,o=[i,n].indexOf(t)>=0?"y":"x";P[t]+=M[o]*e}))}return P}const bt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,c=t.options,h=t.name;if(!e.modifiersData[h]._skip){for(var u=c.mainAxis,d=void 0===u||u,f=c.altAxis,g=void 0===f||f,_=c.fallbackPlacements,b=c.padding,v=c.boundary,y=c.rootBoundary,w=c.altBoundary,A=c.flipVariations,E=void 0===A||A,T=c.allowedAutoPlacements,C=e.options.placement,O=$(C),x=_||(O===C||!E?[ot(C)]:function(t){if($(t)===r)return[];var e=ot(t);return[at(t),e,at(e)]}(C)),k=[C].concat(x).reduce((function(t,i){return t.concat($(i)===r?function(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,l=i.flipVariations,c=i.allowedAutoPlacements,h=void 0===c?m:c,u=J(n),d=u?l?p:p.filter((function(t){return J(t)===u})):a,f=d.filter((function(t){return h.indexOf(t)>=0}));0===f.length&&(f=d);var g=f.reduce((function(e,i){return e[i]=_t(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[$(i)],e}),{});return Object.keys(g).sort((function(t,e){return g[t]-g[e]}))}(e,{placement:i,boundary:v,rootBoundary:y,padding:b,flipVariations:E,allowedAutoPlacements:T}):i)}),[]),L=e.rects.reference,S=e.rects.popper,D=new Map,I=!0,N=k[0],P=0;P=0,W=H?"width":"height",B=_t(e,{placement:j,boundary:v,rootBoundary:y,altBoundary:w,padding:b}),z=H?F?s:o:F?n:i;L[W]>S[W]&&(z=ot(z));var R=ot(z),q=[];if(d&&q.push(B[M]<=0),g&&q.push(B[z]<=0,B[R]<=0),q.every((function(t){return t}))){N=j,I=!1;break}D.set(j,q)}if(I)for(var V=function(t){var e=k.find((function(e){var i=D.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return N=e,"break"},K=E?3:1;K>0;K--){if("break"===V(K))break}e.placement!==N&&(e.modifiersData[h]._skip=!0,e.placement=N,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function vt(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function yt(t){return[i,s,n,o].some((function(e){return t[e]>=0}))}const wt={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=_t(e,{elementContext:"reference"}),a=_t(e,{altBoundary:!0}),l=vt(r,n),c=vt(a,s,o),h=yt(l),u=yt(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:u},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":u})}};const At={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,n=t.options,r=t.name,a=n.offset,l=void 0===a?[0,0]:a,c=m.reduce((function(t,n){return t[n]=function(t,e,n){var r=$(t),a=[o,i].indexOf(r)>=0?-1:1,l="function"==typeof n?n(Object.assign({},e,{placement:t})):n,c=l[0],h=l[1];return c=c||0,h=(h||0)*a,[o,s].indexOf(r)>=0?{x:h,y:c}:{x:c,y:h}}(n,e.rects,l),t}),{}),h=c[e.placement],u=h.x,d=h.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=u,e.modifiersData.popperOffsets.y+=d),e.modifiersData[r]=c}};const Et={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=gt({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}};const Tt={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,r=t.options,a=t.name,c=r.mainAxis,h=void 0===c||c,u=r.altAxis,d=void 0!==u&&u,f=r.boundary,p=r.rootBoundary,m=r.altBoundary,g=r.padding,_=r.tether,b=void 0===_||_,v=r.tetherOffset,y=void 0===v?0:v,w=_t(e,{boundary:f,rootBoundary:p,padding:g,altBoundary:m}),A=$(e.placement),E=J(e.placement),T=!E,C=Q(A),O="x"===C?"y":"x",x=e.modifiersData.popperOffsets,k=e.rects.reference,L=e.rects.popper,S="function"==typeof y?y(Object.assign({},e.rects,{placement:e.placement})):y,D="number"==typeof S?{mainAxis:S,altAxis:S}:Object.assign({mainAxis:0,altAxis:0},S),P=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,j={x:0,y:0};if(x){if(h){var M,F="y"===C?i:o,W="y"===C?n:s,B="y"===C?"height":"width",z=x[C],R=z+w[F],q=z-w[W],V=b?-L[B]/2:0,Y=E===l?k[B]:L[B],U=E===l?-L[B]:-k[B],G=e.elements.arrow,Z=b&&G?H(G):{width:0,height:0},tt=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},et=tt[F],it=tt[W],nt=X(0,k[B],Z[B]),st=T?k[B]/2-V-nt-et-D.mainAxis:Y-nt-et-D.mainAxis,ot=T?-k[B]/2+V+nt+it+D.mainAxis:U+nt+it+D.mainAxis,rt=e.elements.arrow&&K(e.elements.arrow),at=rt?"y"===C?rt.clientTop||0:rt.clientLeft||0:0,lt=null!=(M=null==P?void 0:P[C])?M:0,ct=z+ot-lt,ht=X(b?N(R,z+st-lt-at):R,z,b?I(q,ct):q);x[C]=ht,j[C]=ht-z}if(d){var ut,dt="x"===C?i:o,ft="x"===C?n:s,pt=x[O],mt="y"===O?"height":"width",gt=pt+w[dt],bt=pt-w[ft],vt=-1!==[i,o].indexOf(A),yt=null!=(ut=null==P?void 0:P[O])?ut:0,wt=vt?gt:pt-k[mt]-L[mt]-yt+D.altAxis,At=vt?pt+k[mt]+L[mt]-yt-D.altAxis:bt,Et=b&&vt?function(t,e,i){var n=X(t,e,i);return n>i?i:n}(wt,pt,At):X(b?wt:gt,pt,b?At:bt);x[O]=Et,j[O]=Et-pt}e.modifiersData[a]=j}},requiresIfExists:["offset"]};function Ct(t,e,i){void 0===i&&(i=!1);var n,s,o=L(e),r=L(e)&&function(t){var e=t.getBoundingClientRect(),i=P(e.width)/t.offsetWidth||1,n=P(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=R(e),l=F(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==O(e)||ht(a))&&(c=(n=e)!==x(n)&&L(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:lt(n)),L(e)?((h=F(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=ct(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function Ot(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var xt={placement:"bottom",modifiers:[],strategy:"absolute"};function kt(){for(var t=arguments.length,e=new Array(t),i=0;iIt.has(t)&&It.get(t).get(e)||null,remove(t,e){if(!It.has(t))return;const i=It.get(t);i.delete(e),0===i.size&&It.delete(t)}},Pt="transitionend",jt=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),Mt=t=>{t.dispatchEvent(new Event(Pt))},Ft=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),Ht=t=>Ft(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(jt(t)):null,Wt=t=>{if(!Ft(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},Bt=t=>!t||t.nodeType!==Node.ELEMENT_NODE||(!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled"))),zt=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?zt(t.parentNode):null},Rt=()=>{},qt=t=>{t.offsetHeight},Vt=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,Kt=[],Qt=()=>"rtl"===document.documentElement.dir,Xt=t=>{var e;e=()=>{const e=Vt();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(Kt.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of Kt)t()})),Kt.push(e)):e()},Yt=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,Ut=(t,e,i=!0)=>{if(!i)return void Yt(t);const n=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let s=!1;const o=({target:i})=>{i===e&&(s=!0,e.removeEventListener(Pt,o),Yt(t))};e.addEventListener(Pt,o),setTimeout((()=>{s||Mt(e)}),n)},Gt=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},Jt=/[^.]*(?=\..*)\.|.*/,Zt=/\..*/,te=/::\d+$/,ee={};let ie=1;const ne={mouseenter:"mouseover",mouseleave:"mouseout"},se=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"]);function oe(t,e){return e&&`${e}::${ie++}`||t.uidEvent||ie++}function re(t){const e=oe(t);return t.uidEvent=e,ee[e]=ee[e]||{},ee[e]}function ae(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function le(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=de(t);return se.has(o)||(o=t),[n,s,o]}function ce(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=le(e,i,n);if(e in ne){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=re(t),c=l[a]||(l[a]={}),h=ae(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const u=oe(r,e.replace(Jt,"")),d=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return pe(s,{delegateTarget:r}),n.oneOff&&fe.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return pe(n,{delegateTarget:t}),i.oneOff&&fe.off(t,n.type,e),e.apply(t,[n])}}(t,r);d.delegationSelector=o?i:null,d.callable=r,d.oneOff=s,d.uidEvent=u,c[u]=d,t.addEventListener(a,d,o)}function he(t,e,i,n,s){const o=ae(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function ue(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&he(t,e,i,r.callable,r.delegationSelector)}function de(t){return t=t.replace(Zt,""),ne[t]||t}const fe={on(t,e,i,n){ce(t,e,i,n,!1)},one(t,e,i,n){ce(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=le(e,i,n),a=r!==e,l=re(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))ue(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(te,"");a&&!e.includes(s)||he(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;he(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=Vt();let s=null,o=!0,r=!0,a=!1;e!==de(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=pe(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function pe(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function me(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function ge(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const _e={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${ge(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${ge(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=me(t.dataset[n])}return e},getDataAttribute:(t,e)=>me(t.getAttribute(`data-bs-${ge(e)}`))};class be{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=Ft(e)?_e.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...Ft(e)?_e.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],o=Ft(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(o))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${o}" but expected type "${s}".`)}var i}}class ve extends be{constructor(t,e){super(),(t=Ht(t))&&(this._element=t,this._config=this._getConfig(e),Nt.set(this._element,this.constructor.DATA_KEY,this))}dispose(){Nt.remove(this._element,this.constructor.DATA_KEY),fe.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){Ut(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return Nt.get(Ht(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.3"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const ye=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e?e.split(",").map((t=>jt(t))).join(","):null},we={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!Bt(t)&&Wt(t)))},getSelectorFromElement(t){const e=ye(t);return e&&we.findOne(e)?e:null},getElementFromSelector(t){const e=ye(t);return e?we.findOne(e):null},getMultipleElementsFromSelector(t){const e=ye(t);return e?we.find(e):[]}},Ae=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;fe.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),Bt(this))return;const s=we.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},Ee=".bs.alert",Te=`close${Ee}`,Ce=`closed${Ee}`;class Oe extends ve{static get NAME(){return"alert"}close(){if(fe.trigger(this._element,Te).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),fe.trigger(this._element,Ce),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Oe.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}Ae(Oe,"close"),Xt(Oe);const xe='[data-bs-toggle="button"]';class ke extends ve{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=ke.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}fe.on(document,"click.bs.button.data-api",xe,(t=>{t.preventDefault();const e=t.target.closest(xe);ke.getOrCreateInstance(e).toggle()})),Xt(ke);const Le=".bs.swipe",Se=`touchstart${Le}`,De=`touchmove${Le}`,$e=`touchend${Le}`,Ie=`pointerdown${Le}`,Ne=`pointerup${Le}`,Pe={endCallback:null,leftCallback:null,rightCallback:null},je={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class Me extends be{constructor(t,e){super(),this._element=t,t&&Me.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return Pe}static get DefaultType(){return je}static get NAME(){return"swipe"}dispose(){fe.off(this._element,Le)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),Yt(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&Yt(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(fe.on(this._element,Ie,(t=>this._start(t))),fe.on(this._element,Ne,(t=>this._end(t))),this._element.classList.add("pointer-event")):(fe.on(this._element,Se,(t=>this._start(t))),fe.on(this._element,De,(t=>this._move(t))),fe.on(this._element,$e,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const Fe=".bs.carousel",He=".data-api",We="ArrowLeft",Be="ArrowRight",ze="next",Re="prev",qe="left",Ve="right",Ke=`slide${Fe}`,Qe=`slid${Fe}`,Xe=`keydown${Fe}`,Ye=`mouseenter${Fe}`,Ue=`mouseleave${Fe}`,Ge=`dragstart${Fe}`,Je=`load${Fe}${He}`,Ze=`click${Fe}${He}`,ti="carousel",ei="active",ii=".active",ni=".carousel-item",si=ii+ni,oi={[We]:Ve,[Be]:qe},ri={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},ai={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class li extends ve{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=we.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===ti&&this.cycle()}static get Default(){return ri}static get DefaultType(){return ai}static get NAME(){return"carousel"}next(){this._slide(ze)}nextWhenVisible(){!document.hidden&&Wt(this._element)&&this.next()}prev(){this._slide(Re)}pause(){this._isSliding&&Mt(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?fe.one(this._element,Qe,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void fe.one(this._element,Qe,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?ze:Re;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&fe.on(this._element,Xe,(t=>this._keydown(t))),"hover"===this._config.pause&&(fe.on(this._element,Ye,(()=>this.pause())),fe.on(this._element,Ue,(()=>this._maybeEnableCycle()))),this._config.touch&&Me.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of we.find(".carousel-item img",this._element))fe.on(t,Ge,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(qe)),rightCallback:()=>this._slide(this._directionToOrder(Ve)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new Me(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=oi[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=we.findOne(ii,this._indicatorsElement);e.classList.remove(ei),e.removeAttribute("aria-current");const i=we.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(ei),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===ze,s=e||Gt(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>fe.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(Ke).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),qt(s),i.classList.add(l),s.classList.add(l);this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(ei),i.classList.remove(ei,c,l),this._isSliding=!1,r(Qe)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return we.findOne(si,this._element)}_getItems(){return we.find(ni,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return Qt()?t===qe?Re:ze:t===qe?ze:Re}_orderToDirection(t){return Qt()?t===Re?qe:Ve:t===Re?Ve:qe}static jQueryInterface(t){return this.each((function(){const e=li.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}fe.on(document,Ze,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=we.getElementFromSelector(this);if(!e||!e.classList.contains(ti))return;t.preventDefault();const i=li.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===_e.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),fe.on(window,Je,(()=>{const t=we.find('[data-bs-ride="carousel"]');for(const e of t)li.getOrCreateInstance(e)})),Xt(li);const ci=".bs.collapse",hi=`show${ci}`,ui=`shown${ci}`,di=`hide${ci}`,fi=`hidden${ci}`,pi=`click${ci}.data-api`,mi="show",gi="collapse",_i="collapsing",bi=`:scope .${gi} .${gi}`,vi='[data-bs-toggle="collapse"]',yi={parent:null,toggle:!0},wi={parent:"(null|element)",toggle:"boolean"};class Ai extends ve{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=we.find(vi);for(const t of i){const e=we.getSelectorFromElement(t),i=we.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return yi}static get DefaultType(){return wi}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Ai.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(fe.trigger(this._element,hi).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(gi),this._element.classList.add(_i),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(_i),this._element.classList.add(gi,mi),this._element.style[e]="",fe.trigger(this._element,ui)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(fe.trigger(this._element,di).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,qt(this._element),this._element.classList.add(_i),this._element.classList.remove(gi,mi);for(const t of this._triggerArray){const e=we.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0;this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(_i),this._element.classList.add(gi),fe.trigger(this._element,fi)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(mi)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=Ht(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(vi);for(const e of t){const t=we.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=we.find(bi,this._config.parent);return we.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Ai.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}fe.on(document,pi,vi,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of we.getMultipleElementsFromSelector(this))Ai.getOrCreateInstance(t,{toggle:!1}).toggle()})),Xt(Ai);const Ei="dropdown",Ti=".bs.dropdown",Ci=".data-api",Oi="ArrowUp",xi="ArrowDown",ki=`hide${Ti}`,Li=`hidden${Ti}`,Si=`show${Ti}`,Di=`shown${Ti}`,$i=`click${Ti}${Ci}`,Ii=`keydown${Ti}${Ci}`,Ni=`keyup${Ti}${Ci}`,Pi="show",ji='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',Mi=`${ji}.${Pi}`,Fi=".dropdown-menu",Hi=Qt()?"top-end":"top-start",Wi=Qt()?"top-start":"top-end",Bi=Qt()?"bottom-end":"bottom-start",zi=Qt()?"bottom-start":"bottom-end",Ri=Qt()?"left-start":"right-start",qi=Qt()?"right-start":"left-start",Vi={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},Ki={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class Qi extends ve{constructor(t,e){super(t,e),this._popper=null,this._parent=this._element.parentNode,this._menu=we.next(this._element,Fi)[0]||we.prev(this._element,Fi)[0]||we.findOne(Fi,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return Vi}static get DefaultType(){return Ki}static get NAME(){return Ei}toggle(){return this._isShown()?this.hide():this.show()}show(){if(Bt(this._element)||this._isShown())return;const t={relatedTarget:this._element};if(!fe.trigger(this._element,Si,t).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const t of[].concat(...document.body.children))fe.on(t,"mouseover",Rt);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Pi),this._element.classList.add(Pi),fe.trigger(this._element,Di,t)}}hide(){if(Bt(this._element)||!this._isShown())return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){if(!fe.trigger(this._element,ki,t).defaultPrevented){if("ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.off(t,"mouseover",Rt);this._popper&&this._popper.destroy(),this._menu.classList.remove(Pi),this._element.classList.remove(Pi),this._element.setAttribute("aria-expanded","false"),_e.removeDataAttribute(this._menu,"popper"),fe.trigger(this._element,Li,t)}}_getConfig(t){if("object"==typeof(t=super._getConfig(t)).reference&&!Ft(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ei.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(){if(void 0===e)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let t=this._element;"parent"===this._config.reference?t=this._parent:Ft(this._config.reference)?t=Ht(this._config.reference):"object"==typeof this._config.reference&&(t=this._config.reference);const i=this._getPopperConfig();this._popper=Dt(t,this._menu,i)}_isShown(){return this._menu.classList.contains(Pi)}_getPlacement(){const t=this._parent;if(t.classList.contains("dropend"))return Ri;if(t.classList.contains("dropstart"))return qi;if(t.classList.contains("dropup-center"))return"top";if(t.classList.contains("dropdown-center"))return"bottom";const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?Wi:Hi:e?zi:Bi}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(_e.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...Yt(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=we.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>Wt(t)));i.length&&Gt(i,e,t===xi,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=Qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=we.find(Mi);for(const i of e){const e=Qi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Oi,xi].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(ji)?this:we.prev(this,ji)[0]||we.next(this,ji)[0]||we.findOne(ji,t.delegateTarget.parentNode),o=Qi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}fe.on(document,Ii,ji,Qi.dataApiKeydownHandler),fe.on(document,Ii,Fi,Qi.dataApiKeydownHandler),fe.on(document,$i,Qi.clearMenus),fe.on(document,Ni,Qi.clearMenus),fe.on(document,$i,ji,(function(t){t.preventDefault(),Qi.getOrCreateInstance(this).toggle()})),Xt(Qi);const Xi="backdrop",Yi="show",Ui=`mousedown.bs.${Xi}`,Gi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Ji={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Zi extends be{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Gi}static get DefaultType(){return Ji}static get NAME(){return Xi}show(t){if(!this._config.isVisible)return void Yt(t);this._append();const e=this._getElement();this._config.isAnimated&&qt(e),e.classList.add(Yi),this._emulateAnimation((()=>{Yt(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Yi),this._emulateAnimation((()=>{this.dispose(),Yt(t)}))):Yt(t)}dispose(){this._isAppended&&(fe.off(this._element,Ui),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=Ht(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),fe.on(t,Ui,(()=>{Yt(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){Ut(t,this._getElement(),this._config.isAnimated)}}const tn=".bs.focustrap",en=`focusin${tn}`,nn=`keydown.tab${tn}`,sn="backward",on={autofocus:!0,trapElement:null},rn={autofocus:"boolean",trapElement:"element"};class an extends be{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return on}static get DefaultType(){return rn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),fe.off(document,tn),fe.on(document,en,(t=>this._handleFocusin(t))),fe.on(document,nn,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,fe.off(document,tn))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=we.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===sn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?sn:"forward")}}const ln=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",cn=".sticky-top",hn="padding-right",un="margin-right";class dn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,hn,(e=>e+t)),this._setElementAttributes(ln,hn,(e=>e+t)),this._setElementAttributes(cn,un,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,hn),this._resetElementAttributes(ln,hn),this._resetElementAttributes(cn,un)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&_e.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=_e.getDataAttribute(t,e);null!==i?(_e.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(Ft(t))e(t);else for(const i of we.find(t,this._element))e(i)}}const fn=".bs.modal",pn=`hide${fn}`,mn=`hidePrevented${fn}`,gn=`hidden${fn}`,_n=`show${fn}`,bn=`shown${fn}`,vn=`resize${fn}`,yn=`click.dismiss${fn}`,wn=`mousedown.dismiss${fn}`,An=`keydown.dismiss${fn}`,En=`click${fn}.data-api`,Tn="modal-open",Cn="show",On="modal-static",xn={backdrop:!0,focus:!0,keyboard:!0},kn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ln extends ve{constructor(t,e){super(t,e),this._dialog=we.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new dn,this._addEventListeners()}static get Default(){return xn}static get DefaultType(){return kn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){if(this._isShown||this._isTransitioning)return;fe.trigger(this._element,_n,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Tn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;fe.trigger(this._element,pn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Cn),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated()))}dispose(){fe.off(window,fn),fe.off(this._dialog,fn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Zi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new an({trapElement:this._element})}_showElement(t){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;const e=we.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),qt(this._element),this._element.classList.add(Cn);this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,fe.trigger(this._element,bn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){fe.on(this._element,An,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),fe.on(window,vn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),fe.on(this._element,wn,(t=>{fe.one(this._element,yn,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_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(Tn),this._resetAdjustments(),this._scrollBar.reset(),fe.trigger(this._element,gn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(fe.trigger(this._element,mn).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(On)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(On),this._queueCallback((()=>{this._element.classList.remove(On),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=Qt()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=Qt()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Ln.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}fe.on(document,En,'[data-bs-toggle="modal"]',(function(t){const e=we.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),fe.one(e,_n,(t=>{t.defaultPrevented||fe.one(e,gn,(()=>{Wt(this)&&this.focus()}))}));const i=we.findOne(".modal.show");i&&Ln.getInstance(i).hide();Ln.getOrCreateInstance(e).toggle(this)})),Ae(Ln),Xt(Ln);const Sn=".bs.offcanvas",Dn=".data-api",$n=`load${Sn}${Dn}`,In="show",Nn="showing",Pn="hiding",jn=".offcanvas.show",Mn=`show${Sn}`,Fn=`shown${Sn}`,Hn=`hide${Sn}`,Wn=`hidePrevented${Sn}`,Bn=`hidden${Sn}`,zn=`resize${Sn}`,Rn=`click${Sn}${Dn}`,qn=`keydown.dismiss${Sn}`,Vn={backdrop:!0,keyboard:!0,scroll:!1},Kn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class Qn extends ve{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Vn}static get DefaultType(){return Kn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){if(this._isShown)return;if(fe.trigger(this._element,Mn,{relatedTarget:t}).defaultPrevented)return;this._isShown=!0,this._backdrop.show(),this._config.scroll||(new dn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Nn);this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(In),this._element.classList.remove(Nn),fe.trigger(this._element,Fn,{relatedTarget:t})}),this._element,!0)}hide(){if(!this._isShown)return;if(fe.trigger(this._element,Hn).defaultPrevented)return;this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Pn),this._backdrop.hide();this._queueCallback((()=>{this._element.classList.remove(In,Pn),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new dn).reset(),fe.trigger(this._element,Bn)}),this._element,!0)}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Zi({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():fe.trigger(this._element,Wn)}:null})}_initializeFocusTrap(){return new an({trapElement:this._element})}_addEventListeners(){fe.on(this._element,qn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():fe.trigger(this._element,Wn))}))}static jQueryInterface(t){return this.each((function(){const e=Qn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}fe.on(document,Rn,'[data-bs-toggle="offcanvas"]',(function(t){const e=we.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),Bt(this))return;fe.one(e,Bn,(()=>{Wt(this)&&this.focus()}));const i=we.findOne(jn);i&&i!==e&&Qn.getInstance(i).hide();Qn.getOrCreateInstance(e).toggle(this)})),fe.on(window,$n,(()=>{for(const t of we.find(jn))Qn.getOrCreateInstance(t).show()})),fe.on(window,zn,(()=>{for(const t of we.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&Qn.getOrCreateInstance(t).hide()})),Ae(Qn),Xt(Qn);const Xn={"*":["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:[]},Yn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Un=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Gn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Yn.has(i)||Boolean(Un.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))};const Jn={allowList:Xn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Zn={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},ts={entry:"(string|element|function|null)",selector:"(string|element)"};class es extends be{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Jn}static get DefaultType(){return Zn}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},ts)}_setContent(t,e,i){const n=we.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?Ft(e)?this._putElementInTemplate(Ht(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Gn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return Yt(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const is=new Set(["sanitize","allowList","sanitizeFn"]),ns="fade",ss="show",os=".tooltip-inner",rs=".modal",as="hide.bs.modal",ls="hover",cs="focus",hs={AUTO:"auto",TOP:"top",RIGHT:Qt()?"left":"right",BOTTOM:"bottom",LEFT:Qt()?"right":"left"},us={allowList:Xn,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"},ds={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 fs extends ve{constructor(t,i){if(void 0===e)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,i),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 us}static get DefaultType(){return ds}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),fe.off(this._element.closest(rs),as,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("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=fe.trigger(this._element,this.constructor.eventName("show")),e=(zt(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),fe.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(ss),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.on(t,"mouseover",Rt);this._queueCallback((()=>{fe.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(!this._isShown())return;if(fe.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented)return;if(this._getTipElement().classList.remove(ss),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.off(t,"mouseover",Rt);this._activeTrigger.click=!1,this._activeTrigger[cs]=!1,this._activeTrigger[ls]=!1,this._isHovered=null;this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),fe.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ns,ss),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ns),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new es({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{[os]:this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ns)}_isShown(){return this.tip&&this.tip.classList.contains(ss)}_createPopper(t){const e=Yt(this._config.placement,[this,t,this._element]),i=hs[e.toUpperCase()];return Dt(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return Yt(t,[this._element])}_getPopperConfig(t){const e={placement:t,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:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...Yt(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)fe.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===ls?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ls?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");fe.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?cs:ls]=!0,e._enter()})),fe.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?cs:ls]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},fe.on(this._element.closest(rs),as,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(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(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=_e.getDataAttributes(this._element);for(const t of Object.keys(e))is.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:Ht(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=fs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Xt(fs);const ps=".popover-header",ms=".popover-body",gs={...fs.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},_s={...fs.DefaultType,content:"(null|string|element|function)"};class bs extends fs{static get Default(){return gs}static get DefaultType(){return _s}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{[ps]:this._getTitle(),[ms]:this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=bs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Xt(bs);const vs=".bs.scrollspy",ys=`activate${vs}`,ws=`click${vs}`,As=`load${vs}.data-api`,Es="active",Ts="[href]",Cs=".nav-link",Os=`${Cs}, .nav-item > ${Cs}, .list-group-item`,xs={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},ks={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Ls extends ve{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return xs}static get DefaultType(){return ks}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=Ht(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(fe.off(this._config.target,ws),fe.on(this._config.target,ws,Ts,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=we.find(Ts,this._config.target);for(const e of t){if(!e.hash||Bt(e))continue;const t=we.findOne(decodeURI(e.hash),this._element);Wt(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(Es),this._activateParents(t),fe.trigger(this._element,ys,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))we.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(Es);else for(const e of we.parents(t,".nav, .list-group"))for(const t of we.prev(e,Os))t.classList.add(Es)}_clearActiveClass(t){t.classList.remove(Es);const e=we.find(`${Ts}.${Es}`,t);for(const t of e)t.classList.remove(Es)}static jQueryInterface(t){return this.each((function(){const e=Ls.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}fe.on(window,As,(()=>{for(const t of we.find('[data-bs-spy="scroll"]'))Ls.getOrCreateInstance(t)})),Xt(Ls);const Ss=".bs.tab",Ds=`hide${Ss}`,$s=`hidden${Ss}`,Is=`show${Ss}`,Ns=`shown${Ss}`,Ps=`click${Ss}`,js=`keydown${Ss}`,Ms=`load${Ss}`,Fs="ArrowLeft",Hs="ArrowRight",Ws="ArrowUp",Bs="ArrowDown",zs="Home",Rs="End",qs="active",Vs="fade",Ks="show",Qs=".dropdown-toggle",Xs=`:not(${Qs})`,Ys='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Us=`${`.nav-link${Xs}, .list-group-item${Xs}, [role="tab"]${Xs}`}, ${Ys}`,Gs=`.${qs}[data-bs-toggle="tab"], .${qs}[data-bs-toggle="pill"], .${qs}[data-bs-toggle="list"]`;class Js extends ve{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),fe.on(this._element,js,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?fe.trigger(e,Ds,{relatedTarget:t}):null;fe.trigger(t,Is,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){if(!t)return;t.classList.add(qs),this._activate(we.getElementFromSelector(t));this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),fe.trigger(t,Ns,{relatedTarget:e})):t.classList.add(Ks)}),t,t.classList.contains(Vs))}_deactivate(t,e){if(!t)return;t.classList.remove(qs),t.blur(),this._deactivate(we.getElementFromSelector(t));this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),fe.trigger(t,$s,{relatedTarget:e})):t.classList.remove(Ks)}),t,t.classList.contains(Vs))}_keydown(t){if(![Fs,Hs,Ws,Bs,zs,Rs].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!Bt(t)));let i;if([zs,Rs].includes(t.key))i=e[t.key===zs?0:e.length-1];else{const n=[Hs,Bs].includes(t.key);i=Gt(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Js.getOrCreateInstance(i).show())}_getChildren(){return we.find(Us,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=we.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=we.findOne(t,i);s&&s.classList.toggle(n,e)};n(Qs,qs),n(".dropdown-menu",Ks),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(qs)}_getInnerElement(t){return t.matches(Us)?t:we.findOne(Us,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Js.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}fe.on(document,Ps,Ys,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),Bt(this)||Js.getOrCreateInstance(this).show()})),fe.on(window,Ms,(()=>{for(const t of we.find(Gs))Js.getOrCreateInstance(t)})),Xt(Js);const Zs=".bs.toast",to=`mouseover${Zs}`,eo=`mouseout${Zs}`,io=`focusin${Zs}`,no=`focusout${Zs}`,so=`hide${Zs}`,oo=`hidden${Zs}`,ro=`show${Zs}`,ao=`shown${Zs}`,lo="hide",co="show",ho="showing",uo={animation:"boolean",autohide:"boolean",delay:"number"},fo={animation:!0,autohide:!0,delay:5e3};class po extends ve{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return fo}static get DefaultType(){return uo}static get NAME(){return"toast"}show(){if(fe.trigger(this._element,ro).defaultPrevented)return;this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");this._element.classList.remove(lo),qt(this._element),this._element.classList.add(co,ho),this._queueCallback((()=>{this._element.classList.remove(ho),fe.trigger(this._element,ao),this._maybeScheduleHide()}),this._element,this._config.animation)}hide(){if(!this.isShown())return;if(fe.trigger(this._element,so).defaultPrevented)return;this._element.classList.add(ho),this._queueCallback((()=>{this._element.classList.add(lo),this._element.classList.remove(ho,co),fe.trigger(this._element,oo)}),this._element,this._config.animation)}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(co),super.dispose()}isShown(){return this._element.classList.contains(co)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){fe.on(this._element,to,(t=>this._onInteraction(t,!0))),fe.on(this._element,eo,(t=>this._onInteraction(t,!1))),fe.on(this._element,io,(t=>this._onInteraction(t,!0))),fe.on(this._element,no,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=po.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}Ae(po),Xt(po),document.body.style.color="blue"})()})(); 3 | //# sourceMappingURL=bundle.js.map -------------------------------------------------------------------------------- /dist/js/bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v5.3.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | -------------------------------------------------------------------------------- /dist/second.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Второй заголовок | Проект 9 | 10 | 11 |
12 |
Вторая страница.
13 |
Harrix
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /dist/uploads/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/dist/uploads/test.jpg -------------------------------------------------------------------------------- /docs/article.md: -------------------------------------------------------------------------------- 1 | # Простой статический сайт на Webpack 4 2 | 3 | После прочтения ряда статей (например, [этой](https://habr.com/ru/company/mailru/blog/340922/)) решил перейти на современный подход с использованием Node.js при написании простых сайтов с подхода «динозавров». Ниже представлен разбор примера сборки простого статического сайта с помощью [Webpack 4](https://webpack.js.org/). Статья написана, так как инструкции с решением моей задачи не нашел: пришлось собирать всё по кусочкам. 4 | 5 | ## Постановка задачи 6 | 7 | Сайт представляет собой простой набор HTML-страниц со своим CSS стилями и файлом JavaScript. Необходимо написать проект, который бы собирал наш сайт из исходников: 8 | 9 | - из SASS (точнее SCSS) файлов формируется один CSS файл; 10 | - из различных JavaScript библиотек и пользовательского кода формируется один JavaScript файл; 11 | - HTML страницы собираются с помощью шаблона, где содержимое шапки и футера можно разнести по отдельным файлам. 12 | 13 | В собранном сайте не должны использоваться React, Vue.js. 14 | 15 | При выборе технологий выбираются по возможности наиболее популярные на данный момент. По этой причине отказался и от Grunt и Gulp в пользу Webpack, хотя, если честно, синтаксис Gulp мне понравился больше своим однообразием. 16 | 17 | Для примера будет сверстано несколько страничек на базе Bootstrap 4. Но это только для примера. 18 | 19 | Предполагается, что [Node.js](https://nodejs.org/) установлен (в Windows просто нужно скачать установщик и установить в стиле «далее, далее»), и вы умеете работать с командной строкой CLI. 20 | 21 | Нужно получить набор готовых HTML страниц, которые можно залить на хостинг без дополнительных настроек (например, на GitHub Pages) или открыть локально на компьютере. 22 | 23 | ## Структура проекта 24 | 25 | Общая структура проекта представлена ниже: 26 | 27 | ```text 28 | . 29 | ├── dist - папка, куда будет собираться сайт 30 | ├─┬ src - папка с исходниками сайта 31 | │ ├── favicon - папка с файлами иконок для сайта 32 | │ ├── fonts - папка со шрифтами 33 | │ ├─┬ html - папка заготовок HTML страниц 34 | │ │ ├── includes - папка с встраиваемыми шаблонами (header, footer) 35 | │ │ └── views - папка с самими HTML страницами 36 | │ ├── img - папка с общими изображениями (логотип, иконки и др.) 37 | │ ├── js - папка с JavaScript файлами 38 | │ ├── scss - папка с SСSS файлами 39 | │ └── uploads - папка с файлами статей (картинки, архивы и др.) 40 | ├── package.json - файл настроек Node.js 41 | └── webpack.config.js - файл настроек Webpack 42 | ``` 43 | 44 | Та же структура, но с показом файлов, которые присутствуют в примере: 45 | 46 | ```text 47 | . 48 | ├── dist 49 | ├─┬ src 50 | │ ├─┬ favicon 51 | │ │ └── favicon.ico 52 | │ ├─┬ fonts 53 | │ │ └── Roboto-Regular.ttf 54 | │ ├─┬ html 55 | │ │ ├─┬ includes 56 | │ │ │ ├── footer.html 57 | │ │ │ └── header.html 58 | │ │ └─┬ views 59 | │ │ ├── index.html 60 | │ │ └── second.html 61 | │ ├─┬ img 62 | │ │ └── logo.svg 63 | │ ├─┬ js 64 | │ │ └── index.js 65 | │ ├─┬ scss 66 | │ │ └── style.scss 67 | │ └─┬ uploads 68 | │ └── test.jpg 69 | ├── package.json 70 | └── webpack.config.js 71 | ``` 72 | 73 | Под favicon выделена целая папка, так как в современном web обычным одним ICO файлом не обойтись. Но для примера используется только этот один файл. 74 | 75 | Спорным решением может показаться разделение картинок на две папки: `img` и `uploads`. Но здесь использовал идеологию расположения файлов из Wordpress. На мой взгляд, кидать все изображения в одну папку - не очень хорошая идея. 76 | 77 | Для работы с проектом использую [Visual Studio Code](https://code.visualstudio.com/), которым очень доволен. Особенно мне нравится, что командная строка встроена в программу и вызывается через `Ctrl` + `` ` ``. 78 | 79 | ![Visual Studio Code](img/visual_studio_code.png) 80 | 81 | Сделаем болванку Node.js проекта. Для этого создадим папку нашего проекта с вышеописанной структурой и перейдем в неё в командной строке, где вызовем команду для создания файла `package.json`. 82 | 83 | ```shell 84 | npm init 85 | ``` 86 | 87 | На все вопросы можно просто отвечать, нажимая `Enter`, если заполнять подробную информацию не хочется. 88 | 89 | Установим три общих пакета, которые нам потребуются в любом случае: `webpack`, `webpack-cli` (работу с командной строкой в webpack вынесли в отдельный пакет) и `webpack-dev-server` (для запуска локального сервера, чтобы в браузере сразу отображались сохраненные изменения проекта). 90 | 91 | ```shell 92 | npm install webpack webpack-cli webpack-dev-server --save-dev 93 | ``` 94 | 95 | Файл package.json сейчас выглядит примерно так: 96 | 97 | ```json 98 | { 99 | "name": "static-site-webpack-habr", 100 | "version": "1.0.0", 101 | "description": "", 102 | "main": "index.js", 103 | "scripts": { 104 | "test": "echo \"Error: no test specified\" && exit 1" 105 | }, 106 | "license": "ISC", 107 | "devDependencies": { 108 | "webpack": "^4.1.1", 109 | "webpack-cli": "^2.0.11", 110 | "webpack-dev-server": "^3.1.1" 111 | } 112 | } 113 | ``` 114 | 115 | Также создастся файл `package-lock.json`, который вообще не трогаем. Но в git репозиторий добавлять этот файл нужно, в отличии от папки `node_modules`, которую нужно прописать в файле `.gitignore`, если пользуетесь git. 116 | 117 | ## Собираем JavaScript 118 | 119 | Так как Webpack создан в первую очередь для сборки JS файлов, то эта часть будем самой простой. Чтобы можно было писать JavaScript в современном виде ES2015, который не поддерживается браузерами, поставим пакеты `babel-core`, `babel-loader`, `babel-preset-env`. 120 | 121 | ```shell 122 | npm install babel-core babel-loader babel-preset-env --save-dev 123 | ``` 124 | 125 | После создаем файл настроек `webpack.config.js` с таким содержимым: 126 | 127 | ```javascript 128 | const path = require("path"); 129 | 130 | module.exports = { 131 | entry: ["./src/js/index.js"], 132 | output: { 133 | filename: "./js/bundle.js", 134 | }, 135 | devtool: "source-map", 136 | module: { 137 | rules: [ 138 | { 139 | test: /\.js$/, 140 | include: path.resolve(__dirname, "src/js"), 141 | use: { 142 | loader: "babel-loader", 143 | options: { 144 | presets: "env", 145 | }, 146 | }, 147 | }, 148 | ], 149 | }, 150 | plugins: [], 151 | }; 152 | ``` 153 | 154 | В разделе `entry` (точки входа) указываем, какой JS файл будем собирать, в разделе `output` указываем путь в папке `dist`, куда будем помещаться собранный файл. Обратите внимание, что в webpack 4 в пути `output` саму папку `dist` указывать не нужно! И да, как же мне не нравится, что в одном файле webpack в одних случаях нужно писать относительный путь, в других случаях относительный путь в специальной папке, в третьих случаях нужен уже абсолютный путь (например, его получаем этой командой `path.resolve(__dirname, 'src/js')`). 155 | 156 | Также указано значение параметра `devtool`, равное: `source-map`, что позволит создавать [карты исходников](https://habr.com/ru/post/178743/) для JS и CSS файлов. 157 | 158 | Для обработки конкретных файлов (по расширению, по месторасположению) в webpack создаются правила в разделе `rules`. Сейчас у нас там стоит правило, что все JS файлы пропускаем через транслятор Babel, который преобразует наш новомодный ES2015 в стандартный JavaScript вариант, понятный браузерам. 159 | 160 | В нашем тестовом примере мы верстаем наши странице на [Boostrap 4](https://getbootstrap.com/). Поэтому нам нужно будет установить три пакета: `bootstrap`, `jquery`, `popper.js`. Второй и третий пакет мы устанавливаем по требованию Bootstrap. 161 | 162 | ```shell 163 | npm install bootstrap jquery popper.js --save 164 | ``` 165 | 166 | Обратите внимание на то, что эти три пакета нам нужны именно для самого сайта, а не для его сборки. Поэтому эти пакеты мы устанавливаем с флагом `--save`, а не `--save-dev`. 167 | 168 | Теперь можно приступить к написанию нашего `index.js` файла: 169 | 170 | ```javascript 171 | import jQuery from "jquery"; 172 | import popper from "popper.js"; 173 | import bootstrap from "bootstrap"; 174 | 175 | jQuery(function () { 176 | jQuery("body").css("color", "blue"); 177 | }); 178 | ``` 179 | 180 | В качестве примера пользовательского кода JS просто перекрасили цвет текста на синий. 181 | 182 | Теперь можно перейти к сборке JS файла. Для этого в файле `package.json` в разделе `scripts` пропишем следующие npm скрипты: 183 | 184 | ```json 185 | "scripts": { 186 | "dev": "webpack --mode development", 187 | "build": "webpack --mode production", 188 | "watch": "webpack --mode development --watch", 189 | "start": "webpack --mode development serve" 190 | }, 191 | ``` 192 | 193 | Теперь при запуске в командной строке строчки **npm run dev** произойдет сборка проекта (CSS и HTML файлы потом также будут собираться этой командой), и в папке `/dist/js` появятся файлы `bundle.js` и `bundle.js.map`. 194 | 195 | При запуске команды **npm run build** также произойдет сборка проекта, но уже итоговая (с оптимизацией, максимальной минимизацией файла), которую можно выкладывать на хостинг. 196 | 197 | При запуске **npm run watch** запускается режим автоматического просмотра изменений файлов проекта с автоматическим сборкой измененных файлов. Да, чтобы в командной строке отключить этот режим (например, чтобы можно было написать другие команды) можно нажать `Ctrl` + `C` (как минимум в PowerShell). 198 | 199 | При запуске **npm run start** запустится локальный сервер, который запустит HTML страницу и также будет отслеживать изменения в файлах. Но пока этой командой не пользуемся, так как сборку HTML страниц не добавили. 200 | 201 | Режим построения проекта создает или переписывает файлы в папке `dist`. Но во время разработки проекта при разных сборках файлы могут переименовываться, удаляться. И Webpack не будет следить, чтобы уже ненужные файлы, оставшиеся после предыдущих сборок, удалялись из папки `dist`. ~~Поэтому добавим еще один пакет `clean-webpack-plugin`, который будет очищать папку `dist` перед каждой сборкой проекта.~~ 202 | 203 | Пришлось отказаться от `clean-webpack-plugin`. Почему? Когда запускаешь сервер через команду `npm run start` (`webpack serve`), то webpack компилирует файлы автоматом, не сохраняя их в папку `dist`. И это нормально. Но при этом папка `dist` очищается из-за наличия `clean-webpack-plugin`. В результате в режиме работы локального сервера папка `dist` пустует, что негативно сказывается на работе с git (только в случае, если вы в git репозиторий сохраняется сборку проекта, как и я): после каждого запуска сервера появляется куча изменений из-за удаленных файлов. Было бы хорошо, чтобы очистка папки `dist` происходила только при полноценной сборке, например, `npm run build-and-beautify` (об этой команде ниже). Плагин `clean-webpack-plugin` настроить нужным способом не смог. Поэтому использую другой плагин `del-cli`, который не связан с webpack и работает отдельно. 204 | 205 | ```shell 206 | npm install del-cli --save-dev 207 | ``` 208 | 209 | Внесем изменения в файл `package.json`. 210 | 211 | ```json 212 | { 213 | ... 214 | "scripts": { 215 | ... 216 | "clear": "del-cli dist" 217 | }, 218 | ... 219 | } 220 | ``` 221 | 222 | ## Сборка CSS файла 223 | 224 | CSS файл будем собирать из SCSS файлов, под которые у нас зарезервирована папка `src/scss`. В ней создадим файл `style.scss`, например, со следующим содержимым: 225 | 226 | ```scss 227 | $font-stack: -apple-system, BlinkMacSystemFont, Roboto, "Open Sans", "Helvetica Neue", sans-serif; 228 | 229 | @import "~bootstrap/scss/bootstrap"; 230 | 231 | @font-face { 232 | font-family: "Roboto"; 233 | font-style: normal; 234 | font-weight: 400; 235 | src: url(../fonts/Roboto-Regular.ttf); 236 | } 237 | 238 | body { 239 | font-family: $font-stack; 240 | #logo { 241 | width: 10rem; 242 | } 243 | .container { 244 | img { 245 | width: 20rem; 246 | } 247 | } 248 | } 249 | ``` 250 | 251 | Обратите внимание на то, что стили Bootstrap подключаем не через его CSS файл, а через SСSS (~~`@import "node_modules/bootstrap/scss/bootstrap"`~~ `@import "~bootstrap/scss/bootstrap";`), который позволит в случае надобности переписать те или иные свойства библиотеки, использовать его миксины и др. Но что печалит. Если при сборке JS файла при подключении JS файла Bootstrap библиотеки Webpack знает, где находятся нужные файлы, то при подключении стилей нужно указывать путь к папке в `node_modules`. 252 | 253 | Для обработки css файлов нам будут нужны следующие модули: `node-sass`, `sass-loader`, `css-loader` и `extract-text-webpack-plugin` (говорят, что в следующей версии Webpack в последнем плагине надобность отпадет). 254 | 255 | Важно! На момент написания статьи плагин `extract-text-webpack-plugin` в стабильной версии не умеет работать с Webpack 4. Поэтому нужно устанавливать его beta версию через `@next`: 256 | 257 | ```shell 258 | npm install node-sass sass-loader css-loader extract-text-webpack-plugin@next --save-dev 259 | ``` 260 | 261 | Надеюсь, что вскоре можно будет устанавливать все плагины по нормальному: 262 | 263 | ```shell 264 | npm install node-sass sass-loader css-loader extract-text-webpack-plugin --save-dev 265 | ``` 266 | 267 | В `webpack.config.js` добавим следующие изменения: 268 | 269 | ```javascript 270 | ... 271 | const ExtractTextPlugin = require("extract-text-webpack-plugin"); 272 | ... 273 | 274 | module.exports = { 275 | entry: [ 276 | ... 277 | './src/scss/style.scss' 278 | ], 279 | ... 280 | module: { 281 | rules: [{ 282 | ... 283 | { 284 | test: /\.(sass|scss)$/, 285 | include: path.resolve(__dirname, 'src/scss'), 286 | use: ExtractTextPlugin.extract({ 287 | use: [{ 288 | loader: "css-loader", 289 | options: { 290 | sourceMap: true, 291 | minimize: true, 292 | url: false 293 | } 294 | }, 295 | { 296 | loader: "sass-loader", 297 | options: { 298 | sourceMap: true 299 | } 300 | } 301 | ] 302 | }) 303 | }, 304 | ] 305 | }, 306 | plugins: [ 307 | new ExtractTextPlugin({ 308 | filename: './css/style.bundle.css', 309 | allChunks: true, 310 | }), 311 | ... 312 | ] 313 | }; 314 | ``` 315 | 316 | Обратите внимание на то, что в точках входа `entry` мы добавили новый входной файл `style.scss`, но выходной файл указали не в `output`, а в вызове плагина ExtractTextPlugin в разделе `plugins`. Включаем поддержку карт источников sourceMap для пакетов `sass-loader` и `css-loader`. 317 | 318 | Также можно заметить, что тут нет пакета `style-loader`, который чаще всего упоминается при работе с CSS в Webpack. Данный пакет встраивает CSS код в файл HTML, что может быть удобно для одностраничных приложений, но никак не для многостраничного. 319 | 320 | И самый спорный момент. Для пакета `css-loader` мы добавили параметр `url`, равный `false`. Зачем? По умолчанию `url=true`, и если Webpack при сборке CSS находит ссылки на внешние файлы: фоновые изображения, шрифты (например, в нашем случае есть ссылка на файл шрифта `url(../fonts/Roboto-Regular.ttf)`), то он эти файлы попросит как-то обработать. Для этого используют чаще всего пакеты `file-loader` (копирует файлы в папку сборки) или `url-loader` (маленькие файлы пытается встроить в HTML код). При этом прописанные относительные пути к файлам в собранном CSS могут быть изменены. 321 | 322 | Но с какой проблемой столкнулся на практике. Есть у меня папка `src/scss` с SСSS кодом. Есть папка `src/img` с картинками, на которые ссылаются в SСSS коде. Всё хорошо. Но, например, мне потребовалось подключить на сайт стороннюю библиотеку (например, lightgallery). SCSS код у неё располагается в папке `node_modules/lightgallery/src/sass`, который ссылается на картинки из папки `node_modules/lightgallery/src/img` через относительные пути. И если добавить стили библиотеки в наш `style.scss`, то `file-loader` будет искать картинки библиотеки `lightgallery` в моей папке `src/img`, а не там, где они находятся. И побороть я это не смог. 323 | 324 | **Update.** С последней проблемой можно справиться, как подсказал [Odrin](https://habr.com/ru/users/odrin/), с помощью пакета [resolve-url-loader](https://github.com/bholloway/resolve-url-loader) и file-loader. 325 | 326 | Пример решения: 327 | 328 | ```javascript 329 | ... 330 | 331 | module.exports = { 332 | ... 333 | module: { 334 | rules: [ 335 | ... 336 | { 337 | test: /\.(png|jpg|gif)$/, 338 | use: [ 339 | { 340 | loader: 'file-loader', 341 | options: {name: 'img/[name].[ext]'} 342 | } 343 | ] 344 | }, 345 | { 346 | test: /\.(sass|scss)$/, 347 | include: path.resolve(__dirname, 'src/scss'), 348 | use: ExtractTextPlugin.extract({ 349 | use: [{ 350 | loader: "css-loader", 351 | options: { 352 | sourceMap: true, 353 | minimize: true//, 354 | //url: false 355 | } 356 | }, 357 | { 358 | loader: "resolve-url-loader" 359 | }, 360 | { 361 | loader: "sass-loader", 362 | options: { 363 | sourceMap: true 364 | } 365 | } 366 | ] 367 | }) 368 | } 369 | ... 370 | ] 371 | }, 372 | ... 373 | }; 374 | ``` 375 | 376 | То есть пакет resolve-url-loader вместо относительных путей ставит пути, которые webpack поймет. А уже file-loader будет копировать нужные файлы. Проблема в свойстве name в file-loader. Если его указать как `name: '[path]/[name].[ext]'`, то в моей примере в папке `dist` появится папка `dist\node_modules\lightgallery\src\img`, в которой уже находятся изображения. Нет, в CSS будут прописанные верные пути до этой папки, но это будет не красиво. Поэтому лучше название файла указывать без пути (например `name: 'img/[name].[ext]'`). Правда, тогда все картинки пойдут в одну папку - не всегда это будет полезно. 377 | 378 | Поэтому установкой `url=false` говорим, что все ссылки на файлы в SCSS коде не трогаем, пути не меняем, никакие файлы не копируем и не встраиваем: с ними разберемся потом отдельно. Возможно, это решение плохое, и вы предложите более правильный подход. 379 | 380 | ## Сборка HTML страниц 381 | 382 | Перейдем к самому веселому: к сборке HTML страниц, где у меня возникли самые большие трудности. 383 | 384 | Для сборки HTML страниц будем использовать плагин `html-webpack-plugin`, который поддерживает различные виды шаблонизаторов. Также нам потребуются пакет `raw-loader`. 385 | 386 | ```shell 387 | npm install html-webpack-plugin raw-loader --save-dev 388 | ``` 389 | 390 | В качестве шаблонизатора HTML будем использовать шаблонизатор по умолчанию lodash. Вот так будет выглядеть типичная HTML страница до сборки: 391 | 392 | ```html 393 | <% var data = { title: "Заголовок | Проект", author: "Harrix" }; %> <%= 394 | _.template(require('./../includes/header.html'))(data) %> 395 | 396 |

text

397 | 398 | <%= _.template(require('./../includes/footer.html'))(data) %> 399 | ``` 400 | 401 | Вначале в переменной `data` прописываем все наши переменные страницы, которые хотим использовать на этой странице. Потом встраиваем шаблоны шапки и футера через `_.template(require())`. 402 | 403 | **Важное уточнение.** В статьях про сборку HTML страниц через `html-webpack-plugin` обычно подключают встраиваемые шаблоны просто через команду: 404 | 405 | ```javascript 406 | require("html-loader!./../includes/header.html"); 407 | ``` 408 | 409 | Но при этом в этих встраиваемых шаблонах синтаксис lodash работать не будет (так и не понял, почему так происходит). И данные из переменной `data` туда не передадутся. Поэтому принудительно говорим webpack, что мы встраиваем именно шаблон, который надо обработать как lodash шаблон. 410 | 411 | Теперь мы можем использовать полноценные lodash синтаксис в встраиваемых шаблонах. В коде файла `header.html` ниже через `<%=title%>` печатаем заголовок статьи. 412 | 413 | ```html 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | <%=title%> 424 | 425 | 426 |
427 | 428 | 429 | ``` 430 | 431 | В пакете html-webpack-plugin [есть возможность](https://github.com/jantimon/html-webpack-plugin#generating-multiple-html-files) генерировать несколько HTML страниц: 432 | 433 | ```javascript 434 | plugins: [ 435 | new HtmlWebpackPlugin(), // Generates default index.html 436 | new HtmlWebpackPlugin({ 437 | // Also generate a test.html 438 | filename: "test.html", 439 | template: "src/assets/test.html", 440 | }), 441 | ]; 442 | ``` 443 | 444 | Но прописывать для каждой страницы создание своего экземпляра плагина точно не есть хорошо. Поэтому автоматизируем этот процесс, найдя все HTML файлы в папке `src/html/views` и создадим для них свои версии `new HtmlWebpackPlugin()`. 445 | 446 | Для этого в файле `webpack.config.js` внесем следующие изменения: 447 | 448 | ```javascript 449 | ... 450 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 451 | const fs = require('fs') 452 | 453 | function generateHtmlPlugins(templateDir) { 454 | const templateFiles = fs.readdirSync(path.resolve(__dirname, templateDir)); 455 | return templateFiles.map(item => { 456 | const parts = item.split('.'); 457 | const name = parts[0]; 458 | const extension = parts[1]; 459 | return new HtmlWebpackPlugin({ 460 | filename: `${name}.html`, 461 | template: path.resolve(__dirname, `${templateDir}/${name}.${extension}`), 462 | inject: false, 463 | }) 464 | }) 465 | } 466 | 467 | const htmlPlugins = generateHtmlPlugins('./src/html/views') 468 | 469 | module.exports = { 470 | module: { 471 | ... 472 | { 473 | test: /\.html$/, 474 | include: path.resolve(__dirname, 'src/html/includes'), 475 | use: ['raw-loader'] 476 | }, 477 | ] 478 | }, 479 | plugins: [ 480 | ... 481 | ].concat(htmlPlugins) 482 | }; 483 | ``` 484 | 485 | Функция `generateHtmlPlugins` будет осуществлять поиск всех HTML страниц. Обратите внимание, что в коде функции есть настройка `inject: false`, которая говорит Webpack, что не нужно встраивать ссылки на JS и CSS файл в HTML код самостоятельно: мы сделаем всё сами вручную в шаблонах `header.html` и `footer.html`. 486 | 487 | Также нужно отметить, что встраиваемые шаблоны обрабатываются плагином `raw-loader` (содержимое файла просто загрузить как текст), а не `html-loader`, как чаще всего предлагают. И также, как в случае с CSS, не использую пакеты `file-loader` или `url-loader`. 488 | 489 | И остается последний необязательный момент для работы с HTML. JavaScript файл и CSS файл у нас будут минимизироваться. А вот HTML файлы хочу, наоборот, сделать красивыми и не минимизировать. Поэтому после сборки всех HTML файлов хочется пройтись по ним каким-то beautify плагином. И тут меня ждала подстава: не нашел способа как это сделать в Webpack. Проблема в том, что обработать файлы нужно после того, как будут вставлены встраиваемые шаблоны. 490 | 491 | Нашел пакет [html-cli](https://www.npmjs.com/package/html-cli), который может это сделать независимо от Webpack. Но у него 38 установок в месяц. То есть это означает два варианта: либо никому не нужно приводить к красивому внешнему виду HTML файлы, либо есть другое популярное решение, о котором я не знаю. А ради только одной этой функции Gulp прикручивать не хочется. 492 | 493 | Устанавливаем этот плагин: 494 | 495 | ```shell 496 | npm install html-cli --save-dev 497 | ``` 498 | 499 | И в файле `package.json` прописываем еще два скрипта, которые после работы Webpack будут приводить к красивому внешнему виду HTML файлы с установкой табуляции в два пробела. 500 | 501 | ```json 502 | "scripts": { 503 | "build-and-beautify": "del-cli dist && webpack --mode production && html dist/*.html --indent-size 2", 504 | "beautify": "html dist/*.html --indent-size 2" 505 | }, 506 | ``` 507 | 508 | Обратите внимание на то, что в команду `build-and-beautify` я добавил еще `del-cli dist`, который очищает папку `dist` перед сборкой. 509 | 510 | Поэтому для итоговой сборки рекомендую использовать не команду `npm run build`, а команду **npm run build-and-beautify**. 511 | 512 | ## Копирование оставшихся файлов 513 | 514 | Мы получили JS, CSS файлы, HTML страницы. Остались файлы изображений, шрифтов и др., которые мы не трогали и сознательно не копировали через `file-loader` или `url-loader`. Поэтому скопируем все оставшиеся папки через плагин `copy-webpack-plugin`: 515 | 516 | ```shell 517 | npm install copy-webpack-plugin --save-dev 518 | ``` 519 | 520 | В файле `webpack.config.js` внесем изменения: 521 | 522 | ```javascript 523 | ... 524 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 525 | ... 526 | 527 | module.exports = { 528 | ... 529 | plugins: [ 530 | ... 531 | new CopyWebpackPlugin([{ 532 | from: './src/fonts', 533 | to: './fonts' 534 | }, 535 | { 536 | from: './src/favicon', 537 | to: './favicon' 538 | }, 539 | { 540 | from: './src/img', 541 | to: './img' 542 | }, 543 | { 544 | from: './src/uploads', 545 | to: './uploads' 546 | } 547 | ]), 548 | ]... 549 | }; 550 | ``` 551 | 552 | Всё. Теперь командой **npm run build-and-beautify** собираем проект и в папке `dist` появится собранный статический сайт. 553 | 554 | ![Builded files](img/dist.png) 555 | 556 | ## Итоговые файлы 557 | 558 | Итоговые файлы смотрите в репозитории [https://github.com/Harrix/static-site-webpack-habr](https://github.com/Harrix/static-site-webpack-habr). Они могут частично отличаться от приведенных в статье, так как проект развивается. 559 | -------------------------------------------------------------------------------- /docs/article_new.md: -------------------------------------------------------------------------------- 1 | # Простой статический сайт на webpack 5 2 | 3 | Данная статья является документацией к проекту [static-site-webpack-habr](https://github.com/Harrix/static-site-webpack-habr). С помощью пакета webpack под Node.js и других пакетов, включая HTML шаблонизатор, создается набор HTML страниц, которые удобно использовать для тестирования HTML шаблонов или для генерации простого статического сайта. 4 | 5 | На Хабре находится устаревшая версия этой [статьи](https://habr.com/ru/post/350886/). За это время некоторые подходы изменились, некоторые пакеты обновились, а некоторые пакеты устарели. Так что в этой статье представлен сильно переработанный вариант того материала. 6 | 7 | ## Постановка задачи 8 | 9 | «Сайт» представляет собой простой набор HTML страниц со своими CSS стилями и файлом JavaScript. Необходимо написать проект, который бы собирал наш сайт из исходников: 10 | 11 | - из SASS (точнее [SCSS](https://sass-lang.com/documentation/syntax)) файлов формируется один CSS файл; 12 | - из различных JavaScript библиотек и пользовательского кода формируется один JavaScript файл; 13 | - HTML страницы собираются с помощью шаблонов, где содержимое шапки и футера можно разнести по отдельным файлам. 14 | 15 |
16 | Что не используем 17 | 18 | В собранном сайте не используются, например, [React](https://reactjs.org), [Vue.js](https://vuejs.org/), [Angular](https://angularjs.org/) или другие подобные фреймворки и библиотеки. Причина не в том, что не люблю эти фреймворки, а в том, что хочу в тестовом примере использовать универсальный подход без привязки к какому-то конкретному JavaScript фреймворку. 19 | 20 | При выборе технологий выбираются по возможности наиболее популярные на текущий момент. По этой причине отказался и от [Grunt](https://gruntjs.com/), и [Gulp](https://gulpjs.com/) в пользу [webpack](https://webpack.js.org), хотя, если честно, синтаксис Gulp мне понравился больше своим однообразием при составлении скриптов. 21 | 22 |
23 | 24 | Будем использовать для сборки [webpack](https://webpack.js.org/). 25 | 26 | Для примера будет сверстано несколько страниц на базе CSS фреймворка [Bootstrap 5](https://getbootstrap.com). Но это только для примера, и вы можете спокойно использовать любой другой CSS движок (например, [Bulma](https://bulma.io)) или не использовать вообще никакой, и всё писать самому. 27 | 28 | Предполагается, что [Node.js](https://nodejs.org) у вас уже установлен (в Windows просто нужно скачать файл-установщик и установить в стиле «далее, далее»), и вы умеете работать с командной строкой [CLI](https://ru.wikipedia.org/wiki/Интерфейс_командной_строки) (в Windows это `cmd` или `PowerShell`). 29 | 30 | Нужно получить в итоге набор готовых HTML страниц, которые можно залить на любой хостинг без дополнительных действий (например, на [GitHub Pages](https://pages.github.com/)) или открыть локально на компьютере без поднятия сервера. 31 | 32 | ## Структура проекта 33 | 34 | Общая структура проекта представлена ниже: 35 | 36 | ```text 37 | . 38 | ├── dist - папка, куда будет собираться итоговый сайт 39 | ├─┬ src - папка с исходниками сайта 40 | │ ├── favicon - папка с файлами иконок для сайта 41 | │ ├── fonts - папка со шрифтами 42 | │ ├─┬ html - папка шаблонов HTML страниц 43 | │ │ ├── includes - папка с встраиваемыми шаблонами (header, footer и др.) 44 | │ │ └── views - папка с самими HTML страницами (без header, footer и др.) 45 | │ ├── img - папка с общими изображениями (логотип, иконки и др.) 46 | │ ├── js - папка с JavaScript файлами 47 | │ ├── scss - папка с SСSS файлами стилей 48 | │ └── uploads - папка с файлами статей (картинки, архивы и др.) 49 | ├── package-lock.json - файл со списком версий пакетов (редактируется автоматически) 50 | ├── package.json - файл настроек Node.js 51 | └── webpack.config.js - файл настроек webpack 52 | ``` 53 | 54 | Ниже приведена та же структура проекта, но с отображением файлов, которые присутствуют в примере данной статьи: 55 | 56 | ```text 57 | . 58 | ├── dist 59 | ├─┬ src 60 | │ ├─┬ favicon 61 | │ │ └── favicon.ico 62 | │ ├─┬ fonts 63 | │ │ └── Roboto-Regular.ttf 64 | │ ├─┬ html 65 | │ │ ├─┬ includes 66 | │ │ │ ├── footer.html 67 | │ │ │ └── header.html 68 | │ │ └─┬ views 69 | │ │ ├── index.html 70 | │ │ └── second.html 71 | │ ├─┬ img 72 | │ │ └── logo.svg 73 | │ ├─┬ js 74 | │ │ └── index.js 75 | │ ├─┬ scss 76 | │ │ └── style.scss 77 | │ └─┬ uploads 78 | │ └── test.jpg 79 | ├── package-lock.json 80 | ├── package.json 81 | └── webpack.config.js 82 | ``` 83 | 84 | Спорным решением может показаться разделение файлов изображений на две папки: `img` и `uploads`. Так как на мой взгляд размещать все изображения в одной папке — это не очень хороший подход. 85 | 86 |
87 | Про дополнительные файлы 88 | 89 | В папке проекта [static-site-webpack-habr](https://github.com/Harrix/static-site-webpack-habr) находятся также дополнительные служебные файлы и папки (например, `docs` с документацией к проекту, файл `README.md` с описанием проекта в формате Markdown и др.). Но эти остальные файлы и папки несут служебную функцию, так что можно копировать в свой проект только вышеприведенные файлы. 90 | 91 |
92 | 93 | При установке пакетов появится папка `node_modules` с устанавливаемыми пакетами на основании информации из файлов `package.json` и `package-lock.json` (второй файл вы сами не трогаете, так как он генерируется самим Node.js на основании первого файла `package.json`). Эту папку не надо заливать, например, на GitHub через коммиты (через `.gitignore`). С ней вручную вообще лучше ничего не делать. 94 | 95 | Для работы с проектом использую текстовой редактор [Visual Studio Code](https://code.visualstudio.com/), которым крайне доволен. Особенно нравится, что командная строка встроена в саму программу и вызывается через комбинацию клавиш `Ctrl` + `` ` ``. 96 | 97 | ![Visual Studio Code с встроенной командной строкой](img/visual_studio_code.png) 98 | 99 | Сделаем шаблон Node.js нашего проекта. Для этого создадим папки проекта с вышеописанной структурой (`dist`, `src` и так далее), перейдем в неё в командной строке, где вызовем команду для создания файла `package.json`: 100 | 101 | ```shell 102 | npm init 103 | ``` 104 | 105 | На предложенные вопросы можно не отвечать, нажимая клавишу `Enter`, если не хочется заполнять подробную информацию о проекте (он у нас же тестовый проект). 106 | 107 | После этого установим пакеты общего назначения, которые потребуются в любом случае: `webpack`, `webpack-cli` (работу с командной строкой в webpack вынесли в отдельный пакет) и `webpack-dev-server` (для запуска локального сервера на вашем компьютере, чтобы изменения сразу отображались в браузере): 108 | 109 | ```shell 110 | npm install webpack webpack-cli webpack-dev-server --save-dev 111 | ``` 112 | 113 | Файл `package.json` на данном этапе выглядит примерно так (версии пакетов у вас будут другими, так как они регулярно обновляться): 114 | 115 | ```json 116 | { 117 | "name": "static-site-webpack-habr", 118 | "version": "2.0.0", 119 | "description": "", 120 | "main": "index.js", 121 | "scripts": { 122 | "test": "echo \"Error: no test specified\" && exit 1" 123 | }, 124 | "license": "ISC", 125 | "devDependencies": { 126 | "webpack": "^5.75.0", 127 | "webpack-cli": "^5.0.1", 128 | "webpack-dev-server": "^4.11.1" 129 | } 130 | } 131 | ``` 132 | 133 | ## Собираем JavaScript 134 | -------------------------------------------------------------------------------- /docs/featured-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/docs/featured-image.png -------------------------------------------------------------------------------- /docs/img/dist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/docs/img/dist.png -------------------------------------------------------------------------------- /docs/img/visual_studio_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/docs/img/visual_studio_code.png -------------------------------------------------------------------------------- /img/featured-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/img/featured-image.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-site-webpack-habr", 3 | "version": "2.0.0", 4 | "description": "HTML template", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "dev": "webpack --mode development", 8 | "watch": "webpack --mode development --watch", 9 | "start": "webpack serve --no-client-overlay-warnings", 10 | "build": "webpack --mode production && prettier --print-width=120 --parser html --write dist/*.html" 11 | }, 12 | "dependencies": { 13 | "@popperjs/core": "^2.11.8", 14 | "bootstrap": "^5.3.3" 15 | }, 16 | "devDependencies": { 17 | "clean-webpack-plugin": "^4.0.0", 18 | "copy-webpack-plugin": "^12.0.2", 19 | "css-loader": "^7.1.2", 20 | "css-minimizer-webpack-plugin": "^7.0.0", 21 | "html-webpack-plugin": "^5.6.0", 22 | "mini-css-extract-plugin": "^2.9.0", 23 | "prettier": "^3.3.2", 24 | "raw-loader": "^4.0.2", 25 | "sass": "^1.77.6", 26 | "sass-loader": "^14.2.1", 27 | "terser-webpack-plugin": "^5.3.10", 28 | "webpack": "^5.92.1", 29 | "webpack-cli": "^5.1.4", 30 | "webpack-dev-server": "^5.0.4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/src/favicon/favicon.ico -------------------------------------------------------------------------------- /src/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/src/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /src/html/includes/footer.html: -------------------------------------------------------------------------------- 1 |
<%=author%>
2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/html/includes/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%=title%> 11 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /src/html/views/index.html: -------------------------------------------------------------------------------- 1 | <% var data = { 2 | title: "Заголовок | Проект", 3 | author: "Harrix" 4 | }; %> 5 | <%= _.template(require('./../includes/header.html').default)(data) %> 6 | 7 |
Первая страница.
8 | 9 | <%= _.template(require('./../includes/footer.html').default)(data) %> -------------------------------------------------------------------------------- /src/html/views/second.html: -------------------------------------------------------------------------------- 1 | <% var data = { 2 | title: "Второй заголовок | Проект", 3 | author: "Harrix" 4 | }; %> 5 | <%= _.template(require('./../includes/header.html').default)(data) %> 6 | 7 |
Вторая страница.
8 | 9 | <%= _.template(require('./../includes/footer.html').default)(data) %> -------------------------------------------------------------------------------- /src/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import "bootstrap"; 2 | 3 | document.body.style.color = "blue"; 4 | -------------------------------------------------------------------------------- /src/scss/style.scss: -------------------------------------------------------------------------------- 1 | $font-stack: -apple-system, BlinkMacSystemFont, Roboto, "Open Sans", "Helvetica Neue", sans-serif; 2 | 3 | @import "~bootstrap/scss/bootstrap"; 4 | 5 | @font-face { 6 | font-family: "Roboto"; 7 | font-style: normal; 8 | font-weight: 400; 9 | src: url(../fonts/Roboto-Regular.ttf); 10 | } 11 | 12 | body { 13 | font-family: $font-stack; 14 | #logo { 15 | width: 10rem; 16 | } 17 | .container { 18 | img { 19 | width: 20rem; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/uploads/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harrix/static-site-webpack-habr/a9057df53f6c44d47b82f334887506d5956764bb/src/uploads/test.jpg -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | const { CleanWebpackPlugin } = require("clean-webpack-plugin"); 4 | const CopyPlugin = require("copy-webpack-plugin"); 5 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 6 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 7 | const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); 8 | const TerserPlugin = require("terser-webpack-plugin"); 9 | 10 | function generateHtmlPlugins(templateDir) { 11 | const templateFiles = fs.readdirSync(path.resolve(__dirname, templateDir)); 12 | return templateFiles.map((item) => { 13 | const parts = item.split("."); 14 | const name = parts[0]; 15 | const extension = parts[1]; 16 | return new HtmlWebpackPlugin({ 17 | filename: `${name}.html`, 18 | template: path.resolve(__dirname, `${templateDir}/${name}.${extension}`), 19 | inject: false, 20 | }); 21 | }); 22 | } 23 | 24 | const htmlPlugins = generateHtmlPlugins("src/html/views"); 25 | 26 | const config = { 27 | entry: ["./src/js/index.js", "./src/scss/style.scss"], 28 | output: { 29 | path: path.resolve(__dirname, "dist"), 30 | filename: "js/bundle.js", 31 | }, 32 | devtool: "source-map", 33 | mode: "production", 34 | optimization: { 35 | minimize: true, 36 | minimizer: [ 37 | new CssMinimizerPlugin({ 38 | minimizerOptions: { 39 | preset: [ 40 | "default", 41 | { 42 | discardComments: { removeAll: true }, 43 | }, 44 | ], 45 | }, 46 | }), 47 | new TerserPlugin({ 48 | extractComments: true, 49 | }), 50 | ], 51 | }, 52 | module: { 53 | rules: [ 54 | { 55 | test: /\.(sass|scss)$/, 56 | include: path.resolve(__dirname, "src/scss"), 57 | use: [ 58 | { 59 | loader: MiniCssExtractPlugin.loader, 60 | options: {}, 61 | }, 62 | { 63 | loader: "css-loader", 64 | options: { 65 | sourceMap: true, 66 | url: false, 67 | }, 68 | }, 69 | { 70 | loader: "sass-loader", 71 | options: { 72 | implementation: require("sass"), 73 | sourceMap: true, 74 | }, 75 | }, 76 | ], 77 | }, 78 | { 79 | test: /\.html$/, 80 | include: path.resolve(__dirname, "src/html/includes"), 81 | use: ["raw-loader"], 82 | }, 83 | ], 84 | }, 85 | plugins: [ 86 | new MiniCssExtractPlugin({ 87 | filename: "css/style.bundle.css", 88 | }), 89 | new CopyPlugin({ 90 | patterns: [ 91 | { 92 | from: "src/fonts", 93 | to: "fonts", 94 | }, 95 | { 96 | from: "src/favicon", 97 | to: "favicon", 98 | }, 99 | { 100 | from: "src/img", 101 | to: "img", 102 | }, 103 | { 104 | from: "src/uploads", 105 | to: "uploads", 106 | }, 107 | ], 108 | }), 109 | ].concat(htmlPlugins), 110 | }; 111 | 112 | module.exports = (env, argv) => { 113 | if (argv.mode === "production") { 114 | config.plugins.push(new CleanWebpackPlugin()); 115 | } 116 | return config; 117 | }; 118 | --------------------------------------------------------------------------------