maverick-wave 4.27.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/.claude/settings.local.json +29 -1
  2. package/.claude/skills/mw-maverick-wave/SKILL.md +42 -25
  3. package/.claude/skills/mw-maverick-wave/examples/angular-services.md +16 -4
  4. package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +13 -4
  5. package/.claude/skills/mw-maverick-wave/references/components.md +90 -5
  6. package/.claude/skills/mw-maverick-wave/references/forms.md +14 -2
  7. package/.claude/skills/mw-maverick-wave/references/javascript.md +18 -4
  8. package/.claude/skills/mw-maverick-wave/references/layout.md +53 -19
  9. package/.claude/skills/mw-maverick-wave/references/theming.md +38 -1
  10. package/.impeccable/config.local.json +5 -0
  11. package/.impeccable/hook.cache.json +1 -0
  12. package/CHANGELOG.md +17 -0
  13. package/CLAUDE.md +5 -5
  14. package/README.md +48 -4
  15. package/index.html +19 -38
  16. package/maverick-wave.min.css +14 -12
  17. package/maverick-wave.min.js +1 -1
  18. package/package.json +2 -2
  19. package/scripts/verify.js +27 -2
  20. package/src/js/main.js +266 -75
  21. package/src/partials/accordions-container.html +6 -10
  22. package/src/partials/alerts-container.html +6 -6
  23. package/src/partials/announcement-container.html +5 -5
  24. package/src/partials/avatars-container.html +85 -17
  25. package/src/partials/badges-container.html +6 -6
  26. package/src/partials/blog-posts-container.html +11 -12
  27. package/src/partials/buttons-container.html +3 -3
  28. package/src/partials/cards-container.html +26 -9
  29. package/src/partials/code-container.html +5 -5
  30. package/src/partials/content-slider-container.html +5 -4
  31. package/src/partials/divider-container.html +7 -7
  32. package/src/partials/dropdown-container.html +2 -2
  33. package/src/partials/empty-state-container.html +5 -5
  34. package/src/partials/footer-container.html +21 -18
  35. package/src/partials/form-elements-container.html +14 -14
  36. package/src/partials/gallery-container.html +16 -8
  37. package/src/partials/get-started-container.html +4 -8
  38. package/src/partials/header-container.html +18 -7
  39. package/src/partials/header-utilities-container.html +192 -5
  40. package/src/partials/home-container.html +10 -20
  41. package/src/partials/input-group-container.html +11 -2
  42. package/src/partials/kbd-container.html +1 -1
  43. package/src/partials/login-container.html +5 -1
  44. package/src/partials/media-container.html +2 -2
  45. package/src/partials/modals-container.html +22 -32
  46. package/src/partials/pricing-container.html +9 -9
  47. package/src/partials/skeleton-container.html +6 -6
  48. package/src/partials/stepper-container.html +2 -2
  49. package/src/partials/tables-container.html +12 -12
  50. package/src/partials/tabs-container.html +17 -27
  51. package/src/partials/tags-container.html +23 -17
  52. package/src/partials/techstack-bucket-container.html +13 -0
  53. package/src/partials/tiles-container.html +26 -9
  54. package/src/partials/typography-container.html +7 -6
  55. package/src/partials/utilities-container.html +35 -15
  56. package/src/scss/_layers.scss +10 -0
  57. package/src/scss/abstracts/_index.scss +6 -0
  58. package/src/scss/abstracts/_mixins.scss +61 -153
  59. package/src/scss/abstracts/_variables.scss +123 -319
  60. package/src/scss/base/_base.scss +170 -190
  61. package/src/scss/base/_reset.scss +106 -104
  62. package/src/scss/base/_typography.scss +153 -151
  63. package/src/scss/components/_accordions.scss +97 -97
  64. package/src/scss/components/_alerts.scss +71 -70
  65. package/src/scss/components/_announcement.scss +71 -70
  66. package/src/scss/components/_avatars.scss +130 -142
  67. package/src/scss/components/_badge.scss +131 -132
  68. package/src/scss/components/_blog-post.scss +223 -220
  69. package/src/scss/components/_breadcrumbs.scss +93 -92
  70. package/src/scss/components/_button-bar.scss +102 -110
  71. package/src/scss/components/_buttons.scss +319 -324
  72. package/src/scss/components/_calendar.scss +251 -252
  73. package/src/scss/components/_cards.scss +359 -386
  74. package/src/scss/components/_code.scss +190 -194
  75. package/src/scss/components/_coming-soon.scss +77 -75
  76. package/src/scss/components/_content-slider.scss +90 -89
  77. package/src/scss/components/_divider.scss +55 -53
  78. package/src/scss/components/_dropdown.scss +179 -185
  79. package/src/scss/components/_empty-state.scss +57 -57
  80. package/src/scss/components/_flag.scss +325 -0
  81. package/src/scss/components/_gallery.scss +131 -133
  82. package/src/scss/components/_index.scss +2 -0
  83. package/src/scss/components/_info.scss +151 -167
  84. package/src/scss/components/_kanban.scss +332 -341
  85. package/src/scss/components/_kbd.scss +40 -38
  86. package/src/scss/components/_lang-switch.scss +108 -0
  87. package/src/scss/components/_links.scss +21 -28
  88. package/src/scss/components/_lists.scss +510 -514
  89. package/src/scss/components/_localhost-indicator.scss +26 -24
  90. package/src/scss/components/_media.scss +22 -20
  91. package/src/scss/components/_meta-info.scss +35 -33
  92. package/src/scss/components/_modals.scss +277 -186
  93. package/src/scss/components/_pagination.scss +81 -87
  94. package/src/scss/components/_panels.scss +99 -97
  95. package/src/scss/components/_pricing.scss +183 -241
  96. package/src/scss/components/_progress.scss +112 -89
  97. package/src/scss/components/_prose.scss +35 -33
  98. package/src/scss/components/_ratings.scss +40 -38
  99. package/src/scss/components/_segmented.scss +82 -84
  100. package/src/scss/components/_skeleton.scss +66 -72
  101. package/src/scss/components/_spinners.scss +155 -159
  102. package/src/scss/components/_stepper.scss +116 -116
  103. package/src/scss/components/_tables.scss +155 -148
  104. package/src/scss/components/_tabs.scss +232 -141
  105. package/src/scss/components/_tags.scss +109 -107
  106. package/src/scss/components/_techstack-bucket.scss +123 -122
  107. package/src/scss/components/_testimonial.scss +72 -70
  108. package/src/scss/components/_theme-toggle.scss +53 -51
  109. package/src/scss/components/_tiles.scss +174 -174
  110. package/src/scss/components/_timelines.scss +282 -280
  111. package/src/scss/components/_toasts.scss +66 -72
  112. package/src/scss/form-elements/_checkbox.scss +117 -132
  113. package/src/scss/form-elements/_form.scss +149 -115
  114. package/src/scss/form-elements/_input-group.scss +99 -103
  115. package/src/scss/form-elements/_input.scss +74 -74
  116. package/src/scss/form-elements/_login.scss +48 -48
  117. package/src/scss/form-elements/_prefilled.scss +53 -51
  118. package/src/scss/form-elements/_radio.scss +95 -107
  119. package/src/scss/form-elements/_select.scss +42 -42
  120. package/src/scss/form-elements/_slider.scss +117 -132
  121. package/src/scss/form-elements/_textarea.scss +68 -54
  122. package/src/scss/form-elements/_toggle.scss +127 -140
  123. package/src/scss/layout/_footer.scss +172 -171
  124. package/src/scss/layout/_grid.scss +268 -302
  125. package/src/scss/layout/_header.scss +426 -427
  126. package/src/scss/layout/_home.scss +28 -27
  127. package/src/scss/layout/_main.scss +275 -238
  128. package/src/scss/layout/_page-header.scss +38 -36
  129. package/src/scss/layout/_section.scss +150 -124
  130. package/src/scss/main.scss +1 -1
  131. package/src/scss/utilities/_accessibility.scss +46 -45
  132. package/src/scss/utilities/_aspect.scss +22 -20
  133. package/src/scss/utilities/_corner.scss +23 -5
  134. package/src/scss/utilities/_display.scss +76 -70
  135. package/src/scss/utilities/_elevation.scss +13 -11
  136. package/src/scss/utilities/_flex.scss +83 -85
  137. package/src/scss/utilities/_reveal.scss +40 -63
  138. package/src/scss/utilities/_spacing.scss +63 -64
  139. package/src/scss/utilities/_text.scss +139 -141
  140. package/src/scss/utilities/_touch-targets.scss +130 -147
@@ -1 +1 @@
1
- !function(){"use strict";function e(e,t){e&&(e.classList.toggle("mw-active",t),t||e.classList.remove("active"))}function t(e){return!!e&&(e.classList.contains("mw-active")||e.classList.contains("active"))}function n(){document.querySelectorAll(".color-swatch").forEach(e=>{const t=function(e){if(!e||"transparent"===e)return"#000000";const t=e=>Math.max(0,Math.min(255,Math.round(e))).toString(16).padStart(2,"0"),n=e.match(/color\(srgb\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+))?\)/);if(n){const e="#"+t(255*n[1])+t(255*n[2])+t(255*n[3]),a=void 0===n[4]?1:parseFloat(n[4]);return(a<1?`${e} / ${a}`:e).toUpperCase()}const a=e.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(!a)return e;const c="#"+t(a[1])+t(a[2])+t(a[3]),o=void 0===a[4]?1:parseFloat(a[4]);return(o<1?`${c} / ${o}`:c).toUpperCase()}(window.getComputedStyle(e).backgroundColor),n=e.parentElement.querySelector(".mw-text-muted");n&&(n.textContent=t)})}function a(e){const t=Array.from(e.querySelectorAll(".mw-kanban-column")),n=e.querySelector(".mw-kanban-composer"),a=e.querySelector(".mw-kanban-card-template"),c=e.dataset.kanbanPrefix||"MW",o=n.querySelector(".mw-kanban-composer-title"),r=n.querySelector(".mw-kanban-composer-text"),s=n.querySelector(".mw-kanban-composer-priority"),l=n.querySelector(".mw-kanban-composer-assignee");let i=null;function d(){t.forEach(function(e,n){const a=e.querySelectorAll(".mw-kanban-card"),c=e.querySelector(".mw-kanban-count");c&&(c.textContent=a.length),e.querySelectorAll("[data-kanban-move]").forEach(function(e){const a=n+parseInt(e.dataset.kanbanMove,10);e.disabled=a<0||a>=t.length})})}function u(e,t){const n=e.querySelector(t);return n?n.textContent.replace(/\s+/g," ").trim():""}function m(e,t){f(),i=t||null,o.value=t?u(t,".mw-kanban-card-title"):"",r.value=t?u(t,".mw-kanban-card-text"):"",s.value=t?function(e){const t=e.querySelector(".mw-card-ribbon");if(!t)return"";const n=Array.from(t.classList).find(function(e){return 0===e.indexOf("mw-card-addon-")});return n?n.replace("mw-card-addon-",""):""}(t):"",l.value=t?function(e){const t=e.querySelector(".mw-avatar-initials");return t?t.textContent.trim():""}(t):"",t?(t.classList.add("mw-kanban-editing"),t.before(n)):e.querySelector(".mw-kanban-column-body").prepend(n),n.classList.add("mw-active"),o.focus()}function f(){n.classList.remove("mw-active"),i&&i.classList.remove("mw-kanban-editing"),i=null}function w(){const t=o.value.trim();if(!t)return void o.focus();const u=i||a.content.firstElementChild.cloneNode(!0);if(u.querySelector(".mw-kanban-card-title").textContent=t,u.querySelector(".mw-kanban-card-text").textContent=r.value.trim(),function(e,t){const n=e.querySelector(".mw-card-ribbon");if(n&&n.remove(),!t||!t.value)return;const a=document.createElement("div");a.className="mw-card-ribbon mw-card-addon-"+t.value,a.textContent=t.textContent,e.prepend(a)}(u,s.selectedOptions[0]),function(e,t){const n=e.querySelector(".mw-kanban-card-actions"),a=n.querySelector(".mw-avatar");if(!t)return void(a&&a.remove());const c=a||document.createElement("div");c.className="mw-avatar mw-avatar-xs mw-avatar-initials",c.textContent=t,a||n.prepend(c)}(u,l.value),!i){const t=function(){const t=Array.from(e.querySelectorAll(".mw-kanban-card-id")).map(function(e){return parseInt(e.textContent.split("-").pop(),10)||0});return c+"-"+(Math.max(0,...t)+1)}();u.classList.add("mw-kanban-card-in"),u.querySelector(".mw-kanban-card-id").textContent=t,u.querySelectorAll("[aria-label]").forEach(function(e){e.setAttribute("aria-label",e.getAttribute("aria-label").replace(/[A-Z]+-\d+/,t))}),n.before(u)}f(),d()}const h=["mw-kanban-card-moved-forward","mw-kanban-card-moved-back"];e.addEventListener("click",function(e){const n=e.target.closest("[data-kanban-move]");if(n){if(function(e,n){const a=e.closest(".mw-kanban-column"),c=t[t.indexOf(a)+n];c&&(c.querySelector(".mw-kanban-column-body").appendChild(e),e.classList.remove.apply(e.classList,h),e.offsetWidth,e.classList.add(n>0?h[0]:h[1]),d())}(n.closest(".mw-kanban-card"),parseInt(n.dataset.kanbanMove,10)),n.disabled){const e=n.parentElement.querySelector("[data-kanban-move]:not(:disabled)");e&&e.focus()}return}const a=e.target.closest(".mw-kanban-edit");if(a){const e=a.closest(".mw-kanban-card");return void m(e.closest(".mw-kanban-column"),e)}const c=e.target.closest(".mw-kanban-delete");if(c){const e=c.closest(".mw-kanban-card");return e===i&&f(),e.remove(),void d()}const o=e.target.closest(".mw-kanban-add");o?m(o.closest(".mw-kanban-column"),null):e.target.closest(".mw-kanban-composer-cancel")&&f()}),n.addEventListener("submit",function(e){e.preventDefault(),w()}),n.addEventListener("keydown",function(e){"Escape"===e.key&&f()}),d()}function c(e){const t=e.querySelector(".mw-calendar-grid"),n=e.querySelector(".mw-calendar-title");if(!t)return;const a="week"===e.dataset.calendar,c=function(e){if(!e)return{};try{return JSON.parse(e)}catch(e){return{}}}(e.dataset.calendarMarkers),o=e.dataset.calendarLocale||document.documentElement.lang||void 0,r=new Date;r.setHours(0,0,0,0);let s=e.dataset.calendarDate?new Date(e.dataset.calendarDate+"T00:00:00"):new Date(r),l=e.dataset.calendarSelected||"";function i(e){const t=new Date(e);return t.setDate(t.getDate()-(t.getDay()+6)%7),t}function d(e){return e.getFullYear()+"-"+String(e.getMonth()+1).padStart(2,"0")+"-"+String(e.getDate()).padStart(2,"0")}function u(){if(!a)return s.toLocaleDateString(o,{month:"long",year:"numeric"});const e=i(s),t=new Date(e);t.setDate(t.getDate()+6);const n=e.toLocaleDateString(o,{day:"numeric",month:e.getMonth()===t.getMonth()?void 0:"short"}),c=t.toLocaleDateString(o,{day:"numeric",month:"short",year:"numeric"});return"Week "+function(e){const t=i(e);t.setDate(t.getDate()+3);const n=i(new Date(t.getFullYear(),0,4));return n.setDate(n.getDate()+3),1+Math.round((t-n)/6048e5)}(e)+" · "+n+" - "+c}function m(e){const t=e.getDay();return 0===t||6===t}function f(e,t){const n=d(e),s=document.createElement("button");s.type="button",s.className="mw-calendar-day",s.tabIndex=-1,s.dataset.calendarDay=n,s.setAttribute("aria-pressed",n===l?"true":"false"),s.setAttribute("aria-label",e.toLocaleDateString(o,{weekday:"long",day:"numeric",month:"long",year:"numeric"})),m(e)&&s.classList.add("mw-calendar-weekend"),a||e.getMonth()===t||s.classList.add("mw-calendar-adjacent"),n===d(r)&&(s.classList.add("mw-calendar-today"),s.setAttribute("aria-current","date")),n===l&&s.classList.add("mw-selected");const i=document.createElement("span");i.className="mw-calendar-date",i.textContent=e.getDate(),s.appendChild(i);const u=[].concat(c[n]||[]);if(u.length){const e=document.createElement("span");e.className="mw-calendar-dots",u.forEach(function(t){const n=document.createElement("span");n.className=t?"mw-calendar-dot mw-calendar-dot-"+t:"mw-calendar-dot",e.appendChild(n)}),s.appendChild(e)}return s}function w(){const e=s.getMonth(),c=a?7:42,r=i(a?s:new Date(s.getFullYear(),e,1));t.innerHTML="",function(){const e=i(new Date);for(let n=0;n<7;n++){const n=document.createElement("div");n.className="mw-calendar-weekday",m(e)&&n.classList.add("mw-calendar-weekend"),n.setAttribute("aria-hidden","true"),n.textContent=e.toLocaleDateString(o,{weekday:"short"}),t.appendChild(n),e.setDate(e.getDate()+1)}}();for(let n=0;n<c;n++)t.appendChild(f(r,e)),r.setDate(r.getDate()+1);n&&(n.textContent=u()),function(){const e=t.querySelector(".mw-selected")||t.querySelector(".mw-calendar-today")||t.querySelector(".mw-calendar-day:not(.mw-calendar-adjacent)")||t.querySelector(".mw-calendar-day");e&&(e.tabIndex=0)}()}e.addEventListener("click",function(n){const c=n.target.closest("[data-calendar-nav]");if(c)return o=parseInt(c.dataset.calendarNav,10),a?s.setDate(s.getDate()+7*o):s=new Date(s.getFullYear(),s.getMonth()+o,1),void w();var o;const r=n.target.closest(".mw-calendar-day");var i;r&&!r.disabled&&(i=r.dataset.calendarDay,l=i===l?"":i,t.querySelectorAll(".mw-calendar-day").forEach(function(e){const t=e.dataset.calendarDay===l;e.classList.toggle("mw-selected",t),e.setAttribute("aria-pressed",t?"true":"false")}),e.dispatchEvent(new CustomEvent("mw-calendar-select",{bubbles:!0,detail:{date:l}})))}),t.addEventListener("keydown",function(e){const n={ArrowLeft:-1,ArrowRight:1,ArrowUp:-7,ArrowDown:7}[e.key];if(!n)return;const a=e.target.closest(".mw-calendar-day");if(!a)return;e.preventDefault();const c=new Date(a.dataset.calendarDay+"T00:00:00");c.setDate(c.getDate()+n),function(e){const n=d(e);let a=t.querySelector('[data-calendar-day="'+n+'"]');a||(s=new Date(e),w(),a=t.querySelector('[data-calendar-day="'+n+'"]')),a&&(t.querySelectorAll(".mw-calendar-day").forEach(function(e){e.tabIndex=-1}),a.tabIndex=0,a.focus())}(c)}),w()}document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll(".mw-gallery").forEach(e=>{const t=e.closest(".mw-gallery-container")||e.parentElement;t&&function(e,t){const n=t.querySelector(".mw-gallery-track");if(!n||!n.children.length)return;const a=n.children,c=e.querySelector(".mw-gallery-dots"),o=e.querySelector(".mw-gallery-desc"),r=t.querySelector(".mw-gallery-navi-prev"),s=t.querySelector(".mw-gallery-navi-next");let l=0;function i(e){const t=a.length;l=(e+t)%t,n.style.transform=`translateX(-${100*l}%)`,c&&c.querySelectorAll(".mw-gallery-dot").forEach((e,t)=>{e.classList.toggle("mw-active",t===l)}),o&&(o.textContent=a[l].dataset.desc||"")}if(c)for(let e=0;e<a.length;e++){const t=document.createElement("button");t.type="button",t.className="mw-gallery-dot"+(0===e?" mw-active":""),t.setAttribute("aria-label","Go to slide "+(e+1)),t.addEventListener("click",()=>i(e)),c.appendChild(t)}r&&r.addEventListener("click",()=>i(l-1)),s&&s.addEventListener("click",()=>i(l+1));let d=0;n.addEventListener("touchstart",e=>d=e.touches[0].clientX),n.addEventListener("touchend",e=>{const t=e.changedTouches[0].clientX-d;t>50&&i(l-1),t<-50&&i(l+1)}),i(0)}(t,e)}),function(){const t=document.querySelector(".mw-theme-toggle");if(!t)return;const a=document.body,c=t.querySelector(".mw-theme-toggle-slider i"),o=getComputedStyle(document.documentElement).getPropertyValue("--mw-internal-theme-mode").trim()||"switchable";if("switchable"!==o)return console.log(`Theme mode fixed to: ${o}. Disabling toggle.`),c&&(c.className="light"===o?"fas fa-sun":"fas fa-moon"),e(t,"light"===o),t.style.opacity="0.4",t.style.pointerEvents="none",t.style.cursor="default",t.setAttribute("aria-disabled","true"),localStorage.removeItem("mw-theme"),void("dark"===o&&a.classList.remove("mw-theme-light"));let r="light"===localStorage.getItem("mw-theme");const s=n=>{a.classList.toggle("mw-theme-light",n),e(t,n),c&&(c.className=n?"fas fa-sun":"fas fa-moon")};s(r),t.addEventListener("click",()=>{r=!r,s(r),localStorage.setItem("mw-theme",r?"light":"dark"),setTimeout(n,400)})}(),n(),document.querySelectorAll(".mw-accordion-header").forEach(n=>{const a="BUTTON"===n.tagName;a&&n.setAttribute("aria-expanded",String(t(n))),n.addEventListener("click",function(){const n=!t(this);e(this,n),e(this.nextElementSibling,n),a&&this.setAttribute("aria-expanded",String(n))})}),function(){const e=document.querySelector(".mw-menu-btn"),t=document.querySelector(".mw-navbar");if(!e||!t)return;function n(){e.classList.remove("open"),t.classList.remove("open")}function a(n){n.preventDefault(),n.stopPropagation(),e.classList.toggle("open"),t.classList.toggle("open")}e.addEventListener("click",a),e.addEventListener("touchstart",a,{passive:!1}),document.addEventListener("click",function(a){t.classList.contains("open")&&(t.contains(a.target)||e.contains(a.target)||n())}),document.addEventListener("keydown",function(e){"Escape"===e.key&&n()})}(),function(){const e=document.querySelectorAll(".mw-progress-fill");if(!e.length)return;const t=new IntersectionObserver((e,t)=>{e.forEach(e=>{if(!e.isIntersecting)return;const n=e.target,a=n.dataset.value||0;n.style.width=a+"%",t.unobserve(n)})},{root:null,threshold:.2});e.forEach(e=>t.observe(e))}(),document.querySelectorAll('a[href^="#"]').forEach(e=>{e.addEventListener("click",function(e){const t=this.getAttribute("href");if("#"===t)return;if(!document.querySelector(t))return;const n=document.querySelector(".mw-menu-btn"),a=document.querySelector(".mw-navbar");n&&a&&(n.classList.remove("open"),a.classList.remove("open"))})}),function(){const t=document.querySelectorAll("section[id]"),n=document.querySelectorAll(".mw-navbar-link");if(0===t.length||0===n.length)return;function a(){const e=parseFloat(getComputedStyle(document.documentElement).scrollPaddingTop);return(Number.isNaN(e)?0:e)+2}window.addEventListener("scroll",function(e,t){let n;return function(){const a=this,c=arguments;clearTimeout(n),n=setTimeout(()=>e.apply(a,c),t)}}(function(){let c="";const o=a();t.forEach(e=>{const t=e.offsetTop-o,n=e.clientHeight;scrollY>=t&&scrollY<t+n&&(c=e.getAttribute("id"))}),n.forEach(t=>{e(t,!1);const n=t.getAttribute("href");if(n){const a=n.indexOf("#");if(-1!==a){n.substring(a+1)===c&&e(t,!0)}}})},100))}(),document.querySelectorAll(".mw-tabs-nav-item").forEach(t=>{t.addEventListener("click",function(){const t=this.closest(".mw-tabs");if(!t)return;t.querySelectorAll(".mw-tabs-nav-item").forEach(t=>{e(t,!1)}),e(this,!0);const n=this.getAttribute("data-tab");n&&(t.querySelectorAll(".mw-tabs-panel").forEach(t=>{e(t,!1)}),e(document.getElementById(n),!0))})}),document.querySelectorAll(".mw-alert-close").forEach(e=>{e.addEventListener("click",function(){const e=this.closest(".mw-alert");e&&(e.classList.add("mw-alert-closing"),setTimeout(()=>{e.classList.add("mw-alert-closed")},300))})}),function(){const e=document.querySelector(".mw-localhost-indicator-activated"),t=document.querySelector(".mw-header");if(e){if("localhost"===window.location.hostname||"127.0.0.1"===window.location.hostname||window.location.hostname.includes("192.168.")){const e=document.createElement("div");e.className="mw-localhost-indicator-pulse",t.prepend(e)}}}(),document.querySelectorAll(".mw-slider-container").forEach(e=>{const t=e.querySelector(".mw-slider"),n=e.querySelector(".mw-slider-value");if(!t||!n)return;const a=()=>{const e=Number(t.value),a=Number(t.max)||100,c=Math.round(e/a*100);t.style.setProperty("--value",c+"%"),n.classList.contains("mw-slider-numeric")?n.setAttribute("data-value",e):n.setAttribute("data-value",c)};t.addEventListener("input",a),a()}),document.querySelectorAll(".mw-modal-close").forEach(e=>{e.addEventListener("click",function(){const e=this.closest(".mw-modal-overlay");e&&e.classList.remove("mw-modal-open")})}),function(){const e=document.getElementById("login-button");e&&e.addEventListener("click",function(){const e=this.querySelector("i");e.classList.contains("fa-lock")?(e.classList.remove("fa-lock"),e.classList.add("fa-lock-open")):(e.classList.remove("fa-lock-open"),e.classList.add("fa-lock"))})}(),document.querySelectorAll(".mw-image-slider").forEach(t=>{const n=t.querySelectorAll(".mw-image-slider-overlay-image"),a=t.querySelectorAll("button[data-index]");if(!n.length||!a.length)return;let c=0;function o(){n.forEach(t=>{e(t,parseInt(t.dataset.index)===c)}),a.forEach(t=>{e(t,parseInt(t.dataset.index)===c)})}function r(e){c=e,o()}a.forEach(e=>{e.addEventListener("click",()=>{r(parseInt(e.dataset.index))})}),o()}),document.querySelectorAll(".mw-item-list-checkbox, .mw-item-list-checkbox-scroll").forEach(e=>{e.querySelectorAll("li").forEach(e=>{const t=e.querySelector('input[type="checkbox"]'),n=e.querySelector(".mw-checkbox");t&&n&&(t.checked&&e.classList.add("mw-selected"),e.addEventListener("click",function(e){e.target!==t&&e.target.closest(".mw-checkbox")!==n&&toggleCheckbox(this)}),t.addEventListener("change",function(){const e=this.closest("li");this.checked?e.classList.add("mw-selected"):e.classList.remove("mw-selected"),e.dispatchEvent(new CustomEvent("checkboxToggle",{detail:{checked:this.checked,item:e}}))}),n.addEventListener("click",function(e){setTimeout(()=>{const e=this.closest("li");this.querySelector('input[type="checkbox"]').checked?e.classList.add("mw-selected"):e.classList.remove("mw-selected")},0)}))})}),document.addEventListener("checkboxToggle",function(e){console.log("Checkbox toggled:",e.detail.checked,e.detail.item)}),document.querySelectorAll(".mw-kanban").forEach(a),document.querySelectorAll("[data-calendar]").forEach(c),function(){function e(e){document.querySelectorAll(".mw-dropdown[open]").forEach(t=>{t!==e&&t.removeAttribute("open")})}document.addEventListener("click",function(t){const n=t.target.closest(".mw-dropdown");e(n),n&&t.target.closest(".mw-dropdown-item")&&n.removeAttribute("open")}),document.addEventListener("keydown",function(e){if("Escape"!==e.key)return;const t=document.querySelector(".mw-dropdown[open]");if(!t)return;t.removeAttribute("open");const n=t.querySelector("summary");n&&n.focus()})}()}),window.toggleCheckbox=function(e){const t=e.querySelector('input[type="checkbox"]');if(!t)return;const n=t.checked;t.checked=!n,t.checked?e.classList.add("mw-selected"):e.classList.remove("mw-selected"),e.dispatchEvent(new CustomEvent("checkboxToggle",{detail:{checked:t.checked,item:e}}))}}();
1
+ !function(){"use strict";function e(e,t){e&&(e.classList.toggle("mw-active",t),t||e.classList.remove("active"))}function t(e){return!!e&&(e.classList.contains("mw-active")||e.classList.contains("active"))}function n(){const e=Array.from(document.querySelectorAll(".color-swatch")),t=e.map(e=>function(e){if(!e||"transparent"===e)return"#000000";const t=e=>Math.max(0,Math.min(255,Math.round(e))).toString(16).padStart(2,"0"),n=e.match(/color\(srgb\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+))?\)/);if(n){const e="#"+t(255*n[1])+t(255*n[2])+t(255*n[3]),a=void 0===n[4]?1:parseFloat(n[4]);return(a<1?`${e} / ${a}`:e).toUpperCase()}const a=e.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(!a)return e;const o="#"+t(a[1])+t(a[2])+t(a[3]),r=void 0===a[4]?1:parseFloat(a[4]);return(r<1?`${o} / ${r}`:o).toUpperCase()}(window.getComputedStyle(e).backgroundColor));e.forEach((e,n)=>{const a=e.parentElement.querySelector(".mw-text-muted");a&&(a.textContent=t[n])})}function a(e){const t=e.querySelector(".mw-tabs-nav-item.mw-active, .mw-tabs-nav-item.active");t?(e.style.setProperty("--mw-tabs-marker-x",t.offsetLeft+"px"),e.style.setProperty("--mw-tabs-marker-w",t.offsetWidth),e.style.setProperty("--mw-tabs-marker-y",t.offsetTop+"px"),e.style.setProperty("--mw-tabs-marker-h",t.offsetHeight),e.setAttribute("data-mw-tabs-marker","")):e.removeAttribute("data-mw-tabs-marker")}function o(e){const t=e.getAttribute("data-tab");return t?document.getElementById(t):null}function r(e){const n=t(e);e.setAttribute("aria-selected",String(n)),e.tabIndex=n?0:-1}function c(t,n){t.forEach(t=>{const a=t===n;e(t,a),e(o(t),a),r(t)});const c=n.closest(".mw-tabs-nav");c&&a(c)}function s(e){e&&("DIALOG"===e.tagName?e.showModal():e.classList.add("mw-modal-open"))}function l(e){const t=Array.from(e.querySelectorAll(".mw-kanban-column")),n=e.querySelector(".mw-kanban-composer"),a=e.querySelector(".mw-kanban-card-template"),o=e.dataset.kanbanPrefix||"MW",r=n.querySelector(".mw-kanban-composer-title"),c=n.querySelector(".mw-kanban-composer-text"),s=n.querySelector(".mw-kanban-composer-priority"),l=n.querySelector(".mw-kanban-composer-assignee");let i=null;function d(){t.forEach(function(e,n){const a=e.querySelectorAll(".mw-kanban-card"),o=e.querySelector(".mw-kanban-count");o&&(o.textContent=a.length),e.querySelectorAll("[data-kanban-move]").forEach(function(e){const a=n+parseInt(e.dataset.kanbanMove,10);e.disabled=a<0||a>=t.length})})}function u(e,t){const n=e.querySelector(t);return n?n.textContent.replace(/\s+/g," ").trim():""}function m(e,t){w(),i=t||null,r.value=t?u(t,".mw-kanban-card-title"):"",c.value=t?u(t,".mw-kanban-card-text"):"",s.value=t?function(e){const t=e.querySelector(".mw-card-ribbon");if(!t)return"";const n=Array.from(t.classList).find(function(e){return 0===e.indexOf("mw-card-addon-")});return n?n.replace("mw-card-addon-",""):""}(t):"",l.value=t?function(e){const t=e.querySelector(".mw-avatar-initials");return t?t.textContent.trim():""}(t):"",t?(t.classList.add("mw-kanban-editing"),t.before(n)):e.querySelector(".mw-kanban-column-body").prepend(n),n.classList.add("mw-active"),r.focus()}function w(){n.classList.remove("mw-active"),i&&i.classList.remove("mw-kanban-editing"),i=null}function f(){const t=r.value.trim();if(!t)return void r.focus();const u=i||a.content.firstElementChild.cloneNode(!0);if(u.querySelector(".mw-kanban-card-title").textContent=t,u.querySelector(".mw-kanban-card-text").textContent=c.value.trim(),function(e,t){const n=e.querySelector(".mw-card-ribbon");if(n&&n.remove(),!t||!t.value)return;const a=document.createElement("div");a.className="mw-card-ribbon mw-card-addon-"+t.value,a.textContent=t.textContent,e.prepend(a)}(u,s.selectedOptions[0]),function(e,t){const n=e.querySelector(".mw-kanban-card-actions"),a=n.querySelector(".mw-avatar");if(!t)return void(a&&a.remove());const o=a||document.createElement("div");o.className="mw-avatar mw-avatar-xs mw-avatar-initials",o.textContent=t,a||n.prepend(o)}(u,l.value),!i){const t=function(){const t=Array.from(e.querySelectorAll(".mw-kanban-card-id")).map(function(e){return parseInt(e.textContent.split("-").pop(),10)||0});return o+"-"+(Math.max(0,...t)+1)}();u.classList.add("mw-kanban-card-in"),u.querySelector(".mw-kanban-card-id").textContent=t,u.querySelectorAll("[aria-label]").forEach(function(e){e.setAttribute("aria-label",e.getAttribute("aria-label").replace(/[A-Z]+-\d+/,t))}),n.before(u)}w(),d()}const g=["mw-kanban-card-moved-forward","mw-kanban-card-moved-back"];e.addEventListener("click",function(e){const n=e.target.closest("[data-kanban-move]");if(n){if(function(e,n){const a=e.closest(".mw-kanban-column"),o=t[t.indexOf(a)+n];o&&(o.querySelector(".mw-kanban-column-body").appendChild(e),e.classList.remove.apply(e.classList,g),e.offsetWidth,e.classList.add(n>0?g[0]:g[1]),d())}(n.closest(".mw-kanban-card"),parseInt(n.dataset.kanbanMove,10)),n.disabled){const e=n.parentElement.querySelector("[data-kanban-move]:not(:disabled)");e&&e.focus()}return}const a=e.target.closest(".mw-kanban-edit");if(a){const e=a.closest(".mw-kanban-card");return void m(e.closest(".mw-kanban-column"),e)}const o=e.target.closest(".mw-kanban-delete");if(o){const e=o.closest(".mw-kanban-card");return e===i&&w(),e.remove(),void d()}const r=e.target.closest(".mw-kanban-add");r?m(r.closest(".mw-kanban-column"),null):e.target.closest(".mw-kanban-composer-cancel")&&w()}),n.addEventListener("submit",function(e){e.preventDefault(),f()}),n.addEventListener("keydown",function(e){"Escape"===e.key&&w()}),d()}function i(e){const t=e.querySelector(".mw-calendar-grid"),n=e.querySelector(".mw-calendar-title");if(!t)return;const a="week"===e.dataset.calendar,o=function(e){if(!e)return{};try{return JSON.parse(e)}catch(e){return{}}}(e.dataset.calendarMarkers),r=e.dataset.calendarLocale||document.documentElement.lang||void 0,c=new Date;c.setHours(0,0,0,0);let s=e.dataset.calendarDate?new Date(e.dataset.calendarDate+"T00:00:00"):new Date(c),l=e.dataset.calendarSelected||"";function i(e){const t=new Date(e);return t.setDate(t.getDate()-(t.getDay()+6)%7),t}function d(e){return e.getFullYear()+"-"+String(e.getMonth()+1).padStart(2,"0")+"-"+String(e.getDate()).padStart(2,"0")}function u(){if(!a)return s.toLocaleDateString(r,{month:"long",year:"numeric"});const e=i(s),t=new Date(e);t.setDate(t.getDate()+6);const n=e.toLocaleDateString(r,{day:"numeric",month:e.getMonth()===t.getMonth()?void 0:"short"}),o=t.toLocaleDateString(r,{day:"numeric",month:"short",year:"numeric"});return"Week "+function(e){const t=i(e);t.setDate(t.getDate()+3);const n=i(new Date(t.getFullYear(),0,4));return n.setDate(n.getDate()+3),1+Math.round((t-n)/6048e5)}(e)+" · "+n+" - "+o}function m(e){const t=e.getDay();return 0===t||6===t}function w(e,t){const n=d(e),s=document.createElement("button");s.type="button",s.className="mw-calendar-day",s.tabIndex=-1,s.dataset.calendarDay=n,s.setAttribute("aria-pressed",n===l?"true":"false"),s.setAttribute("aria-label",e.toLocaleDateString(r,{weekday:"long",day:"numeric",month:"long",year:"numeric"})),m(e)&&s.classList.add("mw-calendar-weekend"),a||e.getMonth()===t||s.classList.add("mw-calendar-adjacent"),n===d(c)&&(s.classList.add("mw-calendar-today"),s.setAttribute("aria-current","date")),n===l&&s.classList.add("mw-selected");const i=document.createElement("span");i.className="mw-calendar-date",i.textContent=e.getDate(),s.appendChild(i);const u=[].concat(o[n]||[]);if(u.length){const e=document.createElement("span");e.className="mw-calendar-dots",u.forEach(function(t){const n=document.createElement("span");n.className=t?"mw-calendar-dot mw-calendar-dot-"+t:"mw-calendar-dot",e.appendChild(n)}),s.appendChild(e)}return s}function f(){const e=s.getMonth(),o=a?7:42,c=i(a?s:new Date(s.getFullYear(),e,1));t.innerHTML="",function(){const e=i(new Date);for(let n=0;n<7;n++){const n=document.createElement("div");n.className="mw-calendar-weekday",m(e)&&n.classList.add("mw-calendar-weekend"),n.setAttribute("aria-hidden","true"),n.textContent=e.toLocaleDateString(r,{weekday:"short"}),t.appendChild(n),e.setDate(e.getDate()+1)}}();for(let n=0;n<o;n++)t.appendChild(w(c,e)),c.setDate(c.getDate()+1);n&&(n.textContent=u()),function(){const e=t.querySelector(".mw-selected")||t.querySelector(".mw-calendar-today")||t.querySelector(".mw-calendar-day:not(.mw-calendar-adjacent)")||t.querySelector(".mw-calendar-day");e&&(e.tabIndex=0)}()}e.addEventListener("click",function(n){const o=n.target.closest("[data-calendar-nav]");if(o)return r=parseInt(o.dataset.calendarNav,10),a?s.setDate(s.getDate()+7*r):s=new Date(s.getFullYear(),s.getMonth()+r,1),void f();var r;const c=n.target.closest(".mw-calendar-day");var i;c&&!c.disabled&&(i=c.dataset.calendarDay,l=i===l?"":i,t.querySelectorAll(".mw-calendar-day").forEach(function(e){const t=e.dataset.calendarDay===l;e.classList.toggle("mw-selected",t),e.setAttribute("aria-pressed",t?"true":"false")}),e.dispatchEvent(new CustomEvent("mw-calendar-select",{bubbles:!0,detail:{date:l}})))}),t.addEventListener("keydown",function(e){const n={ArrowLeft:-1,ArrowRight:1,ArrowUp:-7,ArrowDown:7}[e.key];if(!n)return;const a=e.target.closest(".mw-calendar-day");if(!a)return;e.preventDefault();const o=new Date(a.dataset.calendarDay+"T00:00:00");o.setDate(o.getDate()+n),function(e){const n=d(e);let a=t.querySelector('[data-calendar-day="'+n+'"]');a||(s=new Date(e),f(),a=t.querySelector('[data-calendar-day="'+n+'"]')),a&&(t.querySelectorAll(".mw-calendar-day").forEach(function(e){e.tabIndex=-1}),a.tabIndex=0,a.focus())}(o)}),f()}document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll(".mw-gallery").forEach(e=>{const t=e.closest(".mw-gallery-container")||e.parentElement;t&&function(e,t){const n=t.querySelector(".mw-gallery-track");if(!n||!n.children.length)return;const a=n.children,o=e.querySelector(".mw-gallery-dots"),r=e.querySelector(".mw-gallery-desc"),c=t.querySelector(".mw-gallery-navi-prev"),s=t.querySelector(".mw-gallery-navi-next");let l=0;function i(e){const t=a.length;l=(e+t)%t,n.style.transform=`translateX(-${100*l}%)`,o&&o.querySelectorAll(".mw-gallery-dot").forEach((e,t)=>{e.classList.toggle("mw-active",t===l)}),r&&(r.textContent=a[l].dataset.desc||"")}if(o)for(let e=0;e<a.length;e++){const t=document.createElement("button");t.type="button",t.className="mw-gallery-dot"+(0===e?" mw-active":""),t.setAttribute("aria-label","Go to slide "+(e+1)),t.addEventListener("click",()=>i(e)),o.appendChild(t)}c&&c.addEventListener("click",()=>i(l-1)),s&&s.addEventListener("click",()=>i(l+1));let d=0;n.addEventListener("touchstart",e=>d=e.touches[0].clientX),n.addEventListener("touchend",e=>{const t=e.changedTouches[0].clientX-d;t>50&&i(l-1),t<-50&&i(l+1)}),i(0)}(t,e)}),function(){const t=document.querySelector(".mw-theme-toggle");if(!t)return;const a=document.body,o=document.documentElement,r=t.querySelector(".mw-theme-toggle-slider i"),c=getComputedStyle(document.documentElement).getPropertyValue("--mw-internal-theme-mode").trim()||"switchable";if("switchable"!==c)return console.log(`Theme mode fixed to: ${c}. Disabling toggle.`),r&&(r.className="light"===c?"fas fa-sun":"fas fa-moon"),e(t,"light"===c),t.style.opacity="0.4",t.style.pointerEvents="none",t.style.cursor="default",t.setAttribute("aria-disabled","true"),localStorage.removeItem("mw-theme"),void("dark"===c&&a.classList.remove("mw-theme-light"));let s="light"===localStorage.getItem("mw-theme");const l=n=>{o.classList.add("mw-theme-switching"),a.classList.toggle("mw-theme-light",n),o.offsetHeight,o.classList.remove("mw-theme-switching"),e(t,n),r&&(r.className=n?"fas fa-sun":"fas fa-moon")};l(s),t.addEventListener("click",()=>{s=!s,l(s),localStorage.setItem("mw-theme",s?"light":"dark"),n()})}(),n(),document.querySelectorAll(".mw-accordion-header").forEach(n=>{const a="BUTTON"===n.tagName;a&&n.setAttribute("aria-expanded",String(t(n))),n.addEventListener("click",function(){const n=!t(this);e(this,n),e(this.nextElementSibling,n),a&&this.setAttribute("aria-expanded",String(n))})}),function(){const e=document.querySelector(".mw-menu-btn"),t=document.querySelector(".mw-navbar");if(!e||!t)return;const n="BUTTON"===e.tagName;function a(a){e.classList.toggle("open",a),t.classList.toggle("open",a),n&&e.setAttribute("aria-expanded",String(a))}function o(e){e.preventDefault(),e.stopPropagation(),a(!t.classList.contains("open"))}e.addEventListener("click",o),e.addEventListener("touchstart",o,{passive:!1}),document.addEventListener("click",function(n){t.classList.contains("open")&&(t.contains(n.target)||e.contains(n.target)||a(!1))}),document.addEventListener("keydown",function(n){"Escape"===n.key&&t.classList.contains("open")&&(a(!1),e.focus())})}(),function(){const e=document.querySelectorAll(".mw-progress-fill");if(!e.length)return;const t=CSS.supports("animation-timeline","view()"),n=e=>e.dataset.value?e.dataset.value+"%":e.style.width,a=[];if(e.forEach(e=>{const o=n(e);o&&(e.style.setProperty("--mw-progress-value",o),t?e.classList.add("mw-progress-driven"):a.push(e))}),t)return;const o=new IntersectionObserver((e,t)=>{e.forEach(e=>{e.isIntersecting&&(e.target.style.width=n(e.target),t.unobserve(e.target))})},{root:null,threshold:.2});a.forEach(e=>o.observe(e))}(),document.querySelectorAll('a[href^="#"]').forEach(e=>{e.addEventListener("click",function(e){const t=this.getAttribute("href");if("#"===t)return;if(!document.querySelector(t))return;const n=document.querySelector(".mw-menu-btn"),a=document.querySelector(".mw-navbar");n&&a&&(n.classList.remove("open"),a.classList.remove("open"))})}),function(){const t=document.querySelectorAll("section[id]"),n=document.querySelectorAll(".mw-navbar-link");if(0===t.length||0===n.length)return;function a(){const e=parseFloat(getComputedStyle(document.documentElement).scrollPaddingTop);return(Number.isNaN(e)?0:e)+2}window.addEventListener("scroll",function(e,t){let n;return function(){const a=this,o=arguments;clearTimeout(n),n=setTimeout(()=>e.apply(a,o),t)}}(function(){let o="";const r=a();t.forEach(e=>{const t=e.offsetTop-r,n=e.clientHeight;scrollY>=t&&scrollY<t+n&&(o=e.getAttribute("id"))}),n.forEach(t=>{e(t,!1);const n=t.getAttribute("href");if(n){const a=n.indexOf("#");if(-1!==a){n.substring(a+1)===o&&e(t,!0)}}})},100))}(),document.querySelectorAll(".mw-tabs-nav").forEach(e=>{const t=Array.from(e.querySelectorAll(".mw-tabs-nav-item"));if(!t.length)return;const n=!!e.closest(".mw-tabs-vertical");e.setAttribute("role","tablist"),n&&e.setAttribute("aria-orientation","vertical"),t.forEach((e,a)=>{const s=o(e);e.setAttribute("role","tab"),e.id||(e.id=(s?s.id:"mw-tab-"+a)+"-tab"),s&&(e.setAttribute("aria-controls",s.id),s.setAttribute("role","tabpanel"),s.setAttribute("aria-labelledby",e.id)),r(e),e.addEventListener("click",()=>c(t,e)),e.addEventListener("keydown",e=>{const o=function(e,t,n,a){const o=t?"ArrowUp":"ArrowLeft",r=t?"ArrowDown":"ArrowRight";return e.key===o?(a-1+n)%n:e.key===r?(a+1)%n:"Home"===e.key?0:"End"===e.key?n-1:null}(e,n,t.length,a);null!==o&&(e.preventDefault(),c(t,t[o]),t[o].focus())})}),a(e);const s=new ResizeObserver(()=>a(e));s.observe(e),t.forEach(e=>s.observe(e))}),document.querySelectorAll(".mw-alert-close").forEach(e=>{e.addEventListener("click",function(){const e=this.closest(".mw-alert");e&&(e.classList.add("mw-alert-closing"),setTimeout(()=>{e.classList.add("mw-alert-closed")},300))})}),function(){const e=document.querySelector(".mw-localhost-indicator-activated"),t=document.querySelector(".mw-header");if(e){if("localhost"===window.location.hostname||"127.0.0.1"===window.location.hostname||window.location.hostname.includes("192.168.")){const e=document.createElement("div");e.className="mw-localhost-indicator-pulse",t.prepend(e)}}}(),document.querySelectorAll(".mw-slider-container").forEach(e=>{const t=e.querySelector(".mw-slider"),n=e.querySelector(".mw-slider-value");if(!t||!n)return;const a=()=>{const e=Number(t.value),a=Number(t.max)||100,o=Math.round(e/a*100);t.style.setProperty("--value",o+"%"),n.classList.contains("mw-slider-numeric")?n.setAttribute("data-value",e):n.setAttribute("data-value",o)};t.addEventListener("input",a),a()}),document.addEventListener("click",e=>{const t=e.target.closest(".mw-modal-close");if(t){const e=t.closest("dialog.mw-modal");if(e)return void e.close();const n=t.closest(".mw-modal-overlay");return void(n&&n.classList.remove("mw-modal-open"))}const n=e.target.closest("[data-mw-modal]");var a;n?s(document.getElementById(n.dataset.mwModal)):e.target.matches("dialog.mw-modal[open]")&&(!("closedBy"in(a=e.target))||null===a.getAttribute("closedby"))&&e.target.close()}),function(){const e=document.getElementById("login-button");e&&e.addEventListener("click",function(){const e=this.querySelector("i");e.classList.contains("fa-lock")?(e.classList.remove("fa-lock"),e.classList.add("fa-lock-open")):(e.classList.remove("fa-lock-open"),e.classList.add("fa-lock"))})}(),document.querySelectorAll(".mw-image-slider").forEach(t=>{const n=t.querySelectorAll(".mw-image-slider-overlay-image"),a=t.querySelectorAll("button[data-index]");if(!n.length||!a.length)return;let o=0;function r(){n.forEach(t=>{e(t,parseInt(t.dataset.index)===o)}),a.forEach(t=>{e(t,parseInt(t.dataset.index)===o)})}function c(e){o=e,r()}a.forEach(e=>{e.addEventListener("click",()=>{c(parseInt(e.dataset.index))})}),r()}),document.querySelectorAll(".mw-item-list-checkbox, .mw-item-list-checkbox-scroll").forEach(e=>{e.querySelectorAll("li").forEach(e=>{const t=e.querySelector('input[type="checkbox"]'),n=e.querySelector(".mw-checkbox");t&&n&&(t.checked&&e.classList.add("mw-selected"),e.addEventListener("click",function(e){e.target!==t&&e.target.closest(".mw-checkbox")!==n&&toggleCheckbox(this)}),t.addEventListener("change",function(){const e=this.closest("li");this.checked?e.classList.add("mw-selected"):e.classList.remove("mw-selected"),e.dispatchEvent(new CustomEvent("checkboxToggle",{detail:{checked:this.checked,item:e}}))}),n.addEventListener("click",function(e){setTimeout(()=>{const e=this.closest("li");this.querySelector('input[type="checkbox"]').checked?e.classList.add("mw-selected"):e.classList.remove("mw-selected")},0)}))})}),document.addEventListener("checkboxToggle",function(e){console.log("Checkbox toggled:",e.detail.checked,e.detail.item)}),document.querySelectorAll(".mw-kanban").forEach(l),document.querySelectorAll("[data-calendar]").forEach(i),function(){function e(e){document.querySelectorAll(".mw-dropdown[open]").forEach(t=>{t!==e&&t.removeAttribute("open")})}document.addEventListener("click",function(t){const n=t.target.closest(".mw-dropdown");e(n),n&&t.target.closest(".mw-dropdown-item")&&n.removeAttribute("open")}),document.addEventListener("keydown",function(e){if("Escape"!==e.key)return;const t=document.querySelector(".mw-dropdown[open]");if(!t)return;t.removeAttribute("open");const n=t.querySelector("summary");n&&n.focus()})}(),document.querySelectorAll(".mw-lang-switch").forEach(t=>{const n=t.querySelectorAll("[data-mw-lang]"),a=t.querySelector("summary"),o=t.querySelector("summary .mw-flag"),r=t.querySelector("summary .mw-lang-switch-code");n.forEach(c=>{c.addEventListener("click",function(){const s=c.dataset.mwLang,l=c.querySelector(".mw-lang-switch-name"),i=l?l.textContent.trim():s;n.forEach(t=>{e(t,t===c),t===c?t.setAttribute("aria-current","true"):t.removeAttribute("aria-current")}),r&&(r.textContent=s);const d=c.querySelector(".mw-flag");o&&d&&(o.className=d.className),a&&a.setAttribute("aria-label","Language: "+i),t.dispatchEvent(new CustomEvent("mw-language-change",{bubbles:!0,detail:{lang:s,name:i}}))})})})}),window.toggleCheckbox=function(e){const t=e.querySelector('input[type="checkbox"]');if(!t)return;const n=t.checked;t.checked=!n,t.checked?e.classList.add("mw-selected"):e.classList.remove("mw-selected"),e.dispatchEvent(new CustomEvent("checkboxToggle",{detail:{checked:t.checked,item:e}}))},window.mwOpenModal=e=>s(document.getElementById(e)),window.mwCloseModal=e=>{var t;(t=document.getElementById(e))&&("DIALOG"===t.tagName?t.close():t.classList.remove("mw-modal-open"))}}();
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "maverick-wave",
3
- "version": "4.27.0",
3
+ "version": "5.0.0",
4
4
  "config": {
5
- "version_short": "4.27"
5
+ "version_short": "5.0"
6
6
  },
7
7
  "description": "A lightweight, modern CSS framework for building responsive websites with elegance and speed.",
8
8
  "main": "src/js/main.js",
package/scripts/verify.js CHANGED
@@ -7,6 +7,7 @@
7
7
  * 2. every --mw-* definition is used somewhere
8
8
  * 3. every mw-* class in the markup exists in the built CSS
9
9
  * 4. the committed root release files exist and are not empty
10
+ * 5. the CDN version pinned in the docs matches package.json
10
11
  *
11
12
  * Sources are scanned rather than hard-coded so a new partial is covered
12
13
  * automatically. Exits 1 on any error; warnings never fail the build.
@@ -72,9 +73,12 @@ const docs = DOCS.map(read).filter(Boolean).map(stripFrontmatter).join('\n');
72
73
  // --- 1 + 2: custom properties ---------------------------------------------
73
74
 
74
75
  const tokensDefined = new Set(all(css, /(--mw-[a-z0-9-]+)\s*:/g));
76
+ // `\s*` after the paren: a long declaration wrapped by Prettier keeps its
77
+ // newline inside var() all the way through the minifier, and a token used only
78
+ // there would otherwise read as unused.
75
79
  const tokensUsed = new Set([
76
- ...all(css, /var\((--mw-[a-z0-9-]+)/g),
77
- ...all(html, /var\((--mw-[a-z0-9-]+)/g),
80
+ ...all(css, /var\(\s*(--mw-[a-z0-9-]+)/g),
81
+ ...all(html, /var\(\s*(--mw-[a-z0-9-]+)/g),
78
82
  ...all(js, /(--mw-[a-z0-9-]+)/g),
79
83
  ]);
80
84
 
@@ -136,6 +140,27 @@ for (const f of ['maverick-wave.min.css', 'maverick-wave.min.js']) {
136
140
  }
137
141
  }
138
142
 
143
+ // --- 5: pinned CDN version -------------------------------------------------
144
+ // Every jsDelivr snippet in the docs pins a version, which is the right advice
145
+ // and the reason it goes stale: 5.0.0 shipped with six snippets still on 4.28.0.
146
+
147
+ const { version } = require(path.join(ROOT, 'package.json'));
148
+
149
+ for (const p of [path.join(ROOT, 'README.md'), ...DOCS]) {
150
+ const md = read(p);
151
+ if (!md) continue;
152
+ const stale = new Set(
153
+ all(md, /maverick-wave@([0-9]+\.[0-9]+\.[0-9]+)/g).filter(
154
+ (v) => v !== version
155
+ )
156
+ );
157
+ for (const v of stale) {
158
+ errors.push(
159
+ `stale CDN pin in ${path.relative(ROOT, p)}: maverick-wave@${v} - package.json is ${version}`
160
+ );
161
+ }
162
+ }
163
+
139
164
  // Informational: framework classes the showcase never demonstrates
140
165
  const undocumented = [...classesInCss].filter(
141
166
  (c) => !classesInMarkup.has(c) && !classesInJs.has(c)
package/src/js/main.js CHANGED
@@ -1,10 +1,8 @@
1
1
  (function () {
2
2
  'use strict';
3
3
 
4
- // State classes: `mw-active` is the documented spelling, plain `active` is
5
- // the one older markup uses. Both are styled, so turning a state *off* has
6
- // to clear both - otherwise a stale `active` from the HTML would keep a
7
- // second tab lit.
4
+ // Both spellings are styled, so turning a state off has to clear both or a
5
+ // stale `active` in the HTML keeps a second tab lit
8
6
  function setActive(el, on) {
9
7
  if (!el) return;
10
8
  el.classList.toggle('mw-active', on);
@@ -40,17 +38,11 @@
40
38
  initKanbanBoards();
41
39
  initCalendars();
42
40
  initDropdowns();
41
+ initLangSwitches();
43
42
  });
44
43
 
45
- // ===== Dropdowns =====
46
- //
47
- // The menu is a <details>, so opening, closing, the keyboard and the state a
48
- // screen reader reads out are all the browser's job already. Two things it
49
- // does not do, because no markup can express them: close when the click lands
50
- // somewhere else, and close on Escape.
51
- //
52
- // Delegated to the document rather than bound per dropdown, so a menu added
53
- // to the page later works without being initialised.
44
+ // The menu is a <details>, so only two things are left: close on an outside
45
+ // click and close on Escape. Delegated, so a menu added later works too.
54
46
  function initDropdowns() {
55
47
  function closeAll(except) {
56
48
  document.querySelectorAll('.mw-dropdown[open]').forEach((dropdown) => {
@@ -88,6 +80,50 @@
88
80
  });
89
81
  }
90
82
 
83
+ // Keeps the trigger in step with the choice and announces it - which language
84
+ // actually loads is the application's business, usually the URL or the server:
85
+ // document.addEventListener('mw-language-change', (e) => e.detail.lang)
86
+ function initLangSwitches() {
87
+ document.querySelectorAll('.mw-lang-switch').forEach((languageSwitch) => {
88
+ const items = languageSwitch.querySelectorAll('[data-mw-lang]');
89
+ const summary = languageSwitch.querySelector('summary');
90
+ const triggerFlag = languageSwitch.querySelector('summary .mw-flag');
91
+ const triggerCode = languageSwitch.querySelector(
92
+ 'summary .mw-lang-switch-code'
93
+ );
94
+
95
+ items.forEach((item) => {
96
+ item.addEventListener('click', function () {
97
+ const lang = item.dataset.mwLang;
98
+ const nameElement = item.querySelector('.mw-lang-switch-name');
99
+ const label = nameElement ? nameElement.textContent.trim() : lang;
100
+
101
+ items.forEach((other) => {
102
+ setActive(other, other === item);
103
+ if (other === item) other.setAttribute('aria-current', 'true');
104
+ else other.removeAttribute('aria-current');
105
+ });
106
+
107
+ if (triggerCode) triggerCode.textContent = lang;
108
+
109
+ const itemFlag = item.querySelector('.mw-flag');
110
+ if (triggerFlag && itemFlag) {
111
+ triggerFlag.className = itemFlag.className;
112
+ }
113
+
114
+ if (summary) summary.setAttribute('aria-label', 'Language: ' + label);
115
+
116
+ languageSwitch.dispatchEvent(
117
+ new CustomEvent('mw-language-change', {
118
+ bubbles: true,
119
+ detail: { lang, name: label },
120
+ })
121
+ );
122
+ });
123
+ });
124
+ });
125
+ }
126
+
91
127
  // ===== Checkbox Lists =====
92
128
  function initCheckboxLists() {
93
129
  const checkboxLists = document.querySelectorAll(
@@ -269,6 +305,7 @@
269
305
  if (!themeToggle) return;
270
306
 
271
307
  const body = document.body;
308
+ const root = document.documentElement;
272
309
  const icon = themeToggle.querySelector('.mw-theme-toggle-slider i');
273
310
 
274
311
  const computedStyle = getComputedStyle(document.documentElement);
@@ -307,8 +344,12 @@
307
344
 
308
345
  // Function to apply theme styles and icon
309
346
  const applyTheme = (lightMode) => {
310
- // Use toggle's second argument for cleaner class switching
347
+ // The forced reflow is the point: the new colours land while transitions are off
348
+ root.classList.add('mw-theme-switching');
311
349
  body.classList.toggle('mw-theme-light', lightMode);
350
+ void root.offsetHeight;
351
+ root.classList.remove('mw-theme-switching');
352
+
312
353
  setActive(themeToggle, lightMode);
313
354
  if (icon) {
314
355
  icon.className = lightMode ? 'fas fa-sun' : 'fas fa-moon';
@@ -324,8 +365,8 @@
324
365
  applyTheme(isLight);
325
366
  localStorage.setItem('mw-theme', isLight ? 'light' : 'dark'); // Save
326
367
 
327
- // update color swatches
328
- setTimeout(updateColorSwatchHexValues, 400);
368
+ // No transition left to wait out, so the swatches can read the new values now
369
+ updateColorSwatchHexValues();
329
370
  });
330
371
  }
331
372
 
@@ -335,14 +376,20 @@
335
376
  }
336
377
 
337
378
  function updateColorSwatchHexValues() {
338
- const colorSwatches = document.querySelectorAll('.color-swatch');
339
- colorSwatches.forEach((swatch) => {
340
- const bgColor = window.getComputedStyle(swatch).backgroundColor;
341
- const hex = rgbToHex(bgColor);
379
+ const colorSwatches = Array.from(
380
+ document.querySelectorAll('.color-swatch')
381
+ );
382
+
383
+ // All reads before the first write - a write between two reads forces a recalc
384
+ const hexValues = colorSwatches.map((swatch) =>
385
+ rgbToHex(window.getComputedStyle(swatch).backgroundColor)
386
+ );
387
+
388
+ colorSwatches.forEach((swatch, index) => {
342
389
  const hexTextElement =
343
390
  swatch.parentElement.querySelector('.mw-text-muted');
344
391
  if (hexTextElement) {
345
- hexTextElement.textContent = hex;
392
+ hexTextElement.textContent = hexValues[index];
346
393
  }
347
394
  });
348
395
  }
@@ -406,16 +453,19 @@
406
453
 
407
454
  if (!menuBtn || !navbar) return;
408
455
 
409
- function closeMenu() {
410
- menuBtn.classList.remove('open');
411
- navbar.classList.remove('open');
456
+ // aria-expanded only on a real control - on a <div> it claims a state for nothing
457
+ const announces = menuBtn.tagName === 'BUTTON';
458
+
459
+ function setOpen(open) {
460
+ menuBtn.classList.toggle('open', open);
461
+ navbar.classList.toggle('open', open);
462
+ if (announces) menuBtn.setAttribute('aria-expanded', String(open));
412
463
  }
413
464
 
414
465
  function toggleMenu(e) {
415
466
  e.preventDefault();
416
467
  e.stopPropagation();
417
- menuBtn.classList.toggle('open');
418
- navbar.classList.toggle('open');
468
+ setOpen(!navbar.classList.contains('open'));
419
469
  }
420
470
 
421
471
  // Add multiple event listeners for better iOS compatibility
@@ -426,38 +476,58 @@
426
476
  document.addEventListener('click', function (e) {
427
477
  if (!navbar.classList.contains('open')) return;
428
478
  if (navbar.contains(e.target) || menuBtn.contains(e.target)) return;
429
- closeMenu();
479
+ setOpen(false);
430
480
  });
431
481
 
482
+ // Or Escape strands the keyboard user in a drawer that is no longer there
432
483
  document.addEventListener('keydown', function (e) {
433
- if (e.key === 'Escape') closeMenu();
484
+ if (e.key !== 'Escape') return;
485
+ if (!navbar.classList.contains('open')) return;
486
+ setOpen(false);
487
+ menuBtn.focus();
434
488
  });
435
489
  }
436
490
 
437
491
  // ===== Progress Bars =====
492
+
493
+ // The fill is driven from CSS where the browser can do it (_progress.scss), so
494
+ // this only hands the target width over and switches the animation on
438
495
  function initProgressBars() {
439
496
  const fills = document.querySelectorAll('.mw-progress-fill');
440
497
  if (!fills.length) return;
441
498
 
499
+ const scrollDriven = CSS.supports('animation-timeline', 'view()');
500
+
501
+ // Two spellings in the wild: data-value="75" and style="width: 75%".
502
+ const targetWidth = (bar) =>
503
+ bar.dataset.value ? bar.dataset.value + '%' : bar.style.width;
504
+
505
+ const driven = [];
506
+ fills.forEach((bar) => {
507
+ const target = targetWidth(bar);
508
+ if (!target) return;
509
+ bar.style.setProperty('--mw-progress-value', target);
510
+ if (scrollDriven) bar.classList.add('mw-progress-driven');
511
+ else driven.push(bar);
512
+ });
513
+
514
+ if (scrollDriven) return;
515
+
516
+ // Fallback: set the width once, when the bar comes into view, and let the
517
+ // transition in _progress.scss animate it. Only bars whose target is known -
518
+ // a bar already carrying its width in the markup is left alone.
442
519
  const obs = new IntersectionObserver(
443
520
  (entries, observer) => {
444
521
  entries.forEach((entry) => {
445
522
  if (!entry.isIntersecting) return;
446
- const bar = entry.target;
447
- const pct = bar.dataset.value || 0;
448
- // trigger the CSS transition
449
- bar.style.width = pct + '%';
450
- // stop observing this one
451
- observer.unobserve(bar);
523
+ entry.target.style.width = targetWidth(entry.target);
524
+ observer.unobserve(entry.target);
452
525
  });
453
526
  },
454
- {
455
- root: null,
456
- threshold: 0.2,
457
- }
527
+ { root: null, threshold: 0.2 }
458
528
  );
459
529
 
460
- fills.forEach((bar) => obs.observe(bar));
530
+ driven.forEach((bar) => obs.observe(bar));
461
531
  }
462
532
 
463
533
  // ===== Smooth Scrolling =====
@@ -487,10 +557,8 @@
487
557
 
488
558
  if (sections.length === 0 || navLinks.length === 0) return;
489
559
 
490
- // An anchor jump lands on scroll-padding-top, so the spy has to measure
491
- // against the same value - a hardcoded one marks the section before the
492
- // target as soon as an announcement ribbon raises the padding. Read per
493
- // run: the ribbon grows on small screens and can be dismissed at runtime.
560
+ // Measured against scroll-padding-top, the same value an anchor jump lands on,
561
+ // and read per run - the ribbon grows on small screens and can be dismissed
494
562
  function scrollOffset() {
495
563
  const padding = parseFloat(
496
564
  getComputedStyle(document.documentElement).scrollPaddingTop
@@ -532,35 +600,109 @@
532
600
  );
533
601
  }
534
602
 
535
- // ===== Tabs =====
603
+ // The strip draws the marker, this only hands over position and length as custom
604
+ // properties. Both axes every time - which pair the bar uses is the stylesheet's
605
+ // decision, at a breakpoint this file knows nothing of. The attribute switches
606
+ // the bar on and is set after the first measurement, so a page without script
607
+ // keeps the plain border and still looks finished.
608
+ function measureTabsMarker(nav) {
609
+ const active = nav.querySelector(
610
+ '.mw-tabs-nav-item.mw-active, .mw-tabs-nav-item.active'
611
+ );
612
+ if (!active) {
613
+ nav.removeAttribute('data-mw-tabs-marker');
614
+ return;
615
+ }
616
+
617
+ // Unitless for the scale factors: the bar is 1px long and scaled up.
618
+ nav.style.setProperty('--mw-tabs-marker-x', active.offsetLeft + 'px');
619
+ nav.style.setProperty('--mw-tabs-marker-w', active.offsetWidth);
620
+ nav.style.setProperty('--mw-tabs-marker-y', active.offsetTop + 'px');
621
+ nav.style.setProperty('--mw-tabs-marker-h', active.offsetHeight);
622
+ nav.setAttribute('data-mw-tabs-marker', '');
623
+ }
624
+
625
+ // Set here and not in the markup, so a page that already ships .mw-tabs gets the
626
+ // tablist semantics by loading the script
536
627
  function initTabs() {
537
- const tabNavItems = document.querySelectorAll('.mw-tabs-nav-item');
628
+ document.querySelectorAll('.mw-tabs-nav').forEach((nav) => {
629
+ const items = Array.from(nav.querySelectorAll('.mw-tabs-nav-item'));
630
+ if (!items.length) return;
538
631
 
539
- tabNavItems.forEach((item) => {
540
- item.addEventListener('click', function () {
541
- const tabsContainer = this.closest('.mw-tabs');
542
- if (!tabsContainer) return;
632
+ const vertical = !!nav.closest('.mw-tabs-vertical');
543
633
 
544
- tabsContainer
545
- .querySelectorAll('.mw-tabs-nav-item')
546
- .forEach((navItem) => {
547
- setActive(navItem, false);
548
- });
634
+ nav.setAttribute('role', 'tablist');
635
+ if (vertical) nav.setAttribute('aria-orientation', 'vertical');
549
636
 
550
- setActive(this, true);
637
+ items.forEach((item, index) => {
638
+ const panel = tabPanelOf(item);
551
639
 
552
- const tabId = this.getAttribute('data-tab');
553
- if (!tabId) return;
640
+ item.setAttribute('role', 'tab');
641
+ if (!item.id) item.id = (panel ? panel.id : 'mw-tab-' + index) + '-tab';
554
642
 
555
- tabsContainer.querySelectorAll('.mw-tabs-panel').forEach((panel) => {
556
- setActive(panel, false);
557
- });
643
+ if (panel) {
644
+ item.setAttribute('aria-controls', panel.id);
645
+ panel.setAttribute('role', 'tabpanel');
646
+ panel.setAttribute('aria-labelledby', item.id);
647
+ }
648
+
649
+ syncTabState(item);
650
+
651
+ item.addEventListener('click', () => selectTab(items, item));
558
652
 
559
- setActive(document.getElementById(tabId), true);
653
+ item.addEventListener('keydown', (event) => {
654
+ const target = tabKeyTarget(event, vertical, items.length, index);
655
+ if (target === null) return;
656
+ event.preventDefault();
657
+ selectTab(items, items[target]);
658
+ items[target].focus();
659
+ });
560
660
  });
661
+
662
+ measureTabsMarker(nav);
663
+
664
+ // The items are watched too: a web font arriving moves the label the bar is
665
+ // parked under without the strip itself changing size
666
+ const observer = new ResizeObserver(() => measureTabsMarker(nav));
667
+ observer.observe(nav);
668
+ items.forEach((item) => observer.observe(item));
561
669
  });
562
670
  }
563
671
 
672
+ function tabPanelOf(item) {
673
+ const id = item.getAttribute('data-tab');
674
+ return id ? document.getElementById(id) : null;
675
+ }
676
+
677
+ function syncTabState(item) {
678
+ const on = isActive(item);
679
+ item.setAttribute('aria-selected', String(on));
680
+ item.tabIndex = on ? 0 : -1;
681
+ }
682
+
683
+ function selectTab(items, item) {
684
+ items.forEach((other) => {
685
+ const on = other === item;
686
+ setActive(other, on);
687
+ setActive(tabPanelOf(other), on);
688
+ syncTabState(other);
689
+ });
690
+
691
+ const nav = item.closest('.mw-tabs-nav');
692
+ if (nav) measureTabsMarker(nav);
693
+ }
694
+
695
+ function tabKeyTarget(event, vertical, count, index) {
696
+ const back = vertical ? 'ArrowUp' : 'ArrowLeft';
697
+ const forward = vertical ? 'ArrowDown' : 'ArrowRight';
698
+
699
+ if (event.key === back) return (index - 1 + count) % count;
700
+ if (event.key === forward) return (index + 1) % count;
701
+ if (event.key === 'Home') return 0;
702
+ if (event.key === 'End') return count - 1;
703
+ return null;
704
+ }
705
+
564
706
  // ===== Alerts =====
565
707
  function initAlerts() {
566
708
  const alertCloseButtons = document.querySelectorAll('.mw-alert-close');
@@ -640,17 +782,71 @@
640
782
  }
641
783
 
642
784
  // ===== Modals =====
785
+
786
+ // Two shapes: the older `mw-modal-overlay` div toggled by class, and `<dialog>`,
787
+ // where focus trap, Escape and the inert background are the browser's job.
788
+ // Delegated, so a modal added later works too.
643
789
  function initModals() {
644
- document.querySelectorAll('.mw-modal-close').forEach((button) => {
645
- button.addEventListener('click', function () {
646
- const modal = this.closest('.mw-modal-overlay');
647
- if (modal) {
648
- modal.classList.remove('mw-modal-open');
790
+ document.addEventListener('click', (e) => {
791
+ const closer = e.target.closest('.mw-modal-close');
792
+ if (closer) {
793
+ const dialog = closer.closest('dialog.mw-modal');
794
+ if (dialog) {
795
+ dialog.close();
796
+ return;
649
797
  }
650
- });
798
+ const overlay = closer.closest('.mw-modal-overlay');
799
+ if (overlay) overlay.classList.remove('mw-modal-open');
800
+ return;
801
+ }
802
+
803
+ const opener = e.target.closest('[data-mw-modal]');
804
+ if (opener) {
805
+ openModal(document.getElementById(opener.dataset.mwModal));
806
+ return;
807
+ }
808
+
809
+ // Light dismiss for a dialog the browser does not dismiss itself. A click
810
+ // that lands on the dialog element is a click on the backdrop - the
811
+ // content sits in .mw-modal-header/body/footer inside it.
812
+ if (
813
+ e.target.matches('dialog.mw-modal[open]') &&
814
+ !dismissesItself(e.target)
815
+ ) {
816
+ e.target.close();
817
+ }
651
818
  });
652
819
  }
653
820
 
821
+ // True when `closedby` is on the element *and* understood, which is the only
822
+ // case where the browser closes the dialog on a backdrop click by itself.
823
+ function dismissesItself(dialog) {
824
+ return 'closedBy' in dialog && dialog.getAttribute('closedby') !== null;
825
+ }
826
+
827
+ // Opens either shape. Exposed, because which modal opens when is the
828
+ // application's decision and not something markup can express on its own.
829
+ function openModal(modal) {
830
+ if (!modal) return;
831
+ if (modal.tagName === 'DIALOG') {
832
+ modal.showModal();
833
+ } else {
834
+ modal.classList.add('mw-modal-open');
835
+ }
836
+ }
837
+
838
+ function closeModal(modal) {
839
+ if (!modal) return;
840
+ if (modal.tagName === 'DIALOG') {
841
+ modal.close();
842
+ } else {
843
+ modal.classList.remove('mw-modal-open');
844
+ }
845
+ }
846
+
847
+ window.mwOpenModal = (id) => openModal(document.getElementById(id));
848
+ window.mwCloseModal = (id) => closeModal(document.getElementById(id));
849
+
654
850
  // ===== Login Button =====
655
851
  function initHeaderLoginButton() {
656
852
  const loginButton = document.getElementById('login-button');
@@ -875,14 +1071,9 @@
875
1071
 
876
1072
  target.querySelector('.mw-kanban-column-body').appendChild(card);
877
1073
 
878
- // The card lands in a lane somewhere else on the board, so it says so on
879
- // arrival and comes in from the side it was pushed from. Re-parenting it
880
- // alone made it blink into the other column with no motion at all.
881
- //
882
- // Both classes come off first and the reflow is forced in between: an
883
- // element that already carries the class it is being given again keeps
884
- // the finished animation and plays nothing, so a second push in the same
885
- // direction would be the silent one.
1074
+ // Re-parenting alone makes the card blink into the other column, so it comes
1075
+ // in from the side it was pushed from. Both classes come off first with a
1076
+ // forced reflow between, or a second push in the same direction is silent.
886
1077
  card.classList.remove.apply(card.classList, MOVE_CLASSES);
887
1078
  void card.offsetWidth;
888
1079
  card.classList.add(offset > 0 ? MOVE_CLASSES[0] : MOVE_CLASSES[1]);