diff --git a/common/components.py b/common/components.py
index ce86c04..a8e2ec1 100644
--- a/common/components.py
+++ b/common/components.py
@@ -291,3 +291,26 @@ def PurchasePrice(purchase) -> str:
wrapped_content=f"{floatformat(purchase.converted_price)} {purchase.converted_currency}",
wrapped_classes="underline decoration-dotted",
)
+
+
+def Toast(
+ message: str = "",
+ type: str = "info",
+ attributes: list = [],
+):
+ valid_types = ["success", "error", "info"]
+ if type not in valid_types:
+ type = "info"
+
+ safe_message = message.replace("\\", "\\\\").replace("`", "\\`")
+ safe_type = type.replace("\\", "\\\\").replace("`", "\\`")
+ return Component(
+ tag_name="div",
+ attributes=[
+ ("class", "hidden"),
+ ("x-data", "toastStore"),
+ ("x-init", f"addToast(`{safe_message}`, `{safe_type}`)"),
+ ]
+ + attributes,
+ children=[message],
+ )
diff --git a/common/input.css b/common/input.css
index 2f2fc22..fe8c9da 100644
--- a/common/input.css
+++ b/common/input.css
@@ -225,3 +225,9 @@ textarea:disabled {
justify-content: space-between;
}
}
+
+@layer utilities {
+ .toast-container {
+ @apply fixed z-50 flex flex-col items-end bottom-0 right-0 p-4;
+ }
+}
diff --git a/games/api.py b/games/api.py
index 28f72c6..0e43764 100644
--- a/games/api.py
+++ b/games/api.py
@@ -1,6 +1,7 @@
from datetime import date, datetime
from typing import List
+from django.contrib import messages
from django.shortcuts import get_object_or_404
from django.utils.timezone import now as django_timezone_now
from ninja import Field, ModelSchema, NinjaAPI, Router, Schema
@@ -54,6 +55,7 @@ def partial_update_game(request, game_id: int, payload: GameStatusUpdate):
game = get_object_or_404(Game, id=game_id)
setattr(game, "status", payload.status)
game.save()
+ messages.success(request, "Status updated")
return 204, None
@@ -65,6 +67,7 @@ def list_playevents(request):
@playevent_router.post("/", response={201: PlayEventOut})
def create_playevent(request, payload: PlayEventIn):
playevent = PlayEvent.objects.create(**payload.dict())
+ messages.success(request, "Game played!")
return playevent
@@ -105,6 +108,7 @@ def partial_update_session_device(request, session_id: int, payload: SessionDevi
session = get_object_or_404(Session, id=session_id)
session.device_id = payload.device_id
session.save()
+ messages.success(request, "Device updated")
return 204, None
diff --git a/games/htmx_middleware.py b/games/htmx_middleware.py
new file mode 100644
index 0000000..b6a03d8
--- /dev/null
+++ b/games/htmx_middleware.py
@@ -0,0 +1,59 @@
+import json
+
+from django.contrib import messages as django_messages
+from django.contrib.messages import constants as message_constants
+
+MESSAGE_LEVEL_MAP = {
+ message_constants.DEBUG: "debug",
+ message_constants.INFO: "info",
+ message_constants.SUCCESS: "success",
+ message_constants.WARNING: "warning",
+ message_constants.ERROR: "error",
+}
+
+
+class HTMXMessagesMiddleware:
+ """
+ Converts Django messages into HX-Trigger headers so toasts display
+ automatically without changes to views.
+
+ Works for HTMX requests (processed natively by HTMX client),
+ vanilla fetch() calls using fetchWithHtmxTriggers(), and is harmless
+ for full-page loads (browsers ignore HX-Trigger).
+ """
+
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ response = self.get_response(request)
+
+ # Skip HX-Trigger and don't consume messages if there's an HX-Redirect
+ # so the message persists in the session for the redirect target page
+ if "HX-Redirect" in response:
+ return response
+
+ messages = list(django_messages.get_messages(request))
+ if not messages:
+ return response
+
+ triggers = []
+ for msg in messages:
+ toast_type = MESSAGE_LEVEL_MAP.get(msg.level, "info")
+ triggers.append(
+ {
+ "message": msg.message,
+ "type": toast_type,
+ }
+ )
+
+ if triggers:
+ # Use last message (most recent) as the primary toast
+ trigger = triggers[-1]
+ response["HX-Trigger"] = json.dumps(
+ {
+ "show-toast": trigger,
+ }
+ )
+
+ return response
diff --git a/games/static/base.css b/games/static/base.css
index 74f4fd9..ab291cb 100644
--- a/games/static/base.css
+++ b/games/static/base.css
@@ -30,6 +30,15 @@
--color-orange-800: oklch(47% 0.157 37.304);
--color-orange-900: oklch(40.8% 0.123 38.172);
--color-orange-950: oklch(26.6% 0.079 36.259);
+ --color-amber-50: oklch(98.7% 0.022 95.277);
+ --color-amber-200: oklch(92.4% 0.12 95.746);
+ --color-amber-300: oklch(87.9% 0.169 91.605);
+ --color-amber-400: oklch(82.8% 0.189 84.429);
+ --color-amber-500: oklch(76.9% 0.188 70.08);
+ --color-amber-600: oklch(66.6% 0.179 58.318);
+ --color-amber-700: oklch(55.5% 0.163 48.998);
+ --color-amber-800: oklch(47.3% 0.137 46.201);
+ --color-amber-900: oklch(41.4% 0.112 45.904);
--color-yellow-50: oklch(98.7% 0.026 102.212);
--color-yellow-100: oklch(97.3% 0.071 103.193);
--color-yellow-200: oklch(94.5% 0.129 101.54);
@@ -458,6 +467,9 @@
}
}
@layer utilities {
+ .pointer-events-auto {
+ pointer-events: auto;
+ }
.pointer-events-none {
pointer-events: none;
}
@@ -916,6 +928,9 @@
.mt-0 {
margin-top: calc(var(--spacing) * 0);
}
+ .mt-0\.5 {
+ margin-top: calc(var(--spacing) * 0.5);
+ }
.mt-1 {
margin-top: calc(var(--spacing) * 1);
}
@@ -1547,6 +1562,9 @@
.w-64 {
width: calc(var(--spacing) * 64);
}
+ .w-72 {
+ width: calc(var(--spacing) * 72);
+ }
.w-80 {
width: calc(var(--spacing) * 80);
}
@@ -1631,6 +1649,9 @@
.flex-shrink {
flex-shrink: 1;
}
+ .flex-shrink-0 {
+ flex-shrink: 0;
+ }
.-translate-x-full {
--tw-translate-x: -100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -1639,6 +1660,10 @@
--tw-translate-x: calc(var(--spacing) * 0);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
+ .translate-x-8 {
+ --tw-translate-x: calc(var(--spacing) * 8);
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
.translate-x-full {
--tw-translate-x: 100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -1941,6 +1966,12 @@
.border-accent {
border-color: var(--color-accent);
}
+ .border-amber-200 {
+ border-color: var(--color-amber-200);
+ }
+ .border-blue-200 {
+ border-color: var(--color-blue-200);
+ }
.border-brand {
border-color: var(--color-brand);
}
@@ -1959,9 +1990,15 @@
.border-gray-300 {
border-color: var(--color-gray-300);
}
+ .border-green-200 {
+ border-color: var(--color-green-200);
+ }
.border-purple-200 {
border-color: var(--color-purple-200);
}
+ .border-red-200 {
+ border-color: var(--color-red-200);
+ }
.border-transparent {
border-color: transparent;
}
@@ -1996,12 +2033,18 @@
background-color: var(--color-neutral-secondary-medium);
}
}
+ .bg-amber-50 {
+ background-color: var(--color-amber-50);
+ }
.bg-black\/70 {
background-color: color-mix(in srgb, #000 70%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-black) 70%, transparent);
}
}
+ .bg-blue-50 {
+ background-color: var(--color-blue-50);
+ }
.bg-blue-100 {
background-color: var(--color-blue-100);
}
@@ -2041,6 +2084,9 @@
background-color: color-mix(in oklab, var(--color-gray-900) 50%, transparent);
}
}
+ .bg-green-50 {
+ background-color: var(--color-green-50);
+ }
.bg-green-500 {
background-color: var(--color-green-500);
}
@@ -2071,6 +2117,9 @@
.bg-purple-500 {
background-color: var(--color-purple-500);
}
+ .bg-red-50 {
+ background-color: var(--color-red-50);
+ }
.bg-red-500 {
background-color: var(--color-red-500);
}
@@ -2454,9 +2503,24 @@
color: var(--color-heading);
}
}
+ .text-amber-400 {
+ color: var(--color-amber-400);
+ }
+ .text-amber-500 {
+ color: var(--color-amber-500);
+ }
+ .text-amber-800 {
+ color: var(--color-amber-800);
+ }
.text-black {
color: var(--color-black);
}
+ .text-blue-400 {
+ color: var(--color-blue-400);
+ }
+ .text-blue-500 {
+ color: var(--color-blue-500);
+ }
.text-blue-600 {
color: var(--color-blue-600);
}
@@ -2487,17 +2551,32 @@
.text-gray-700 {
color: var(--color-gray-700);
}
+ .text-gray-800 {
+ color: var(--color-gray-800);
+ }
.text-gray-900 {
color: var(--color-gray-900);
}
- .text-green-600 {
- color: var(--color-green-600);
+ .text-green-400 {
+ color: var(--color-green-400);
+ }
+ .text-green-500 {
+ color: var(--color-green-500);
+ }
+ .text-green-800 {
+ color: var(--color-green-800);
}
.text-heading {
color: var(--color-heading);
}
- .text-red-600 {
- color: var(--color-red-600);
+ .text-red-400 {
+ color: var(--color-red-400);
+ }
+ .text-red-500 {
+ color: var(--color-red-500);
+ }
+ .text-red-800 {
+ color: var(--color-red-800);
}
.text-slate-300 {
color: var(--color-slate-300);
@@ -2548,6 +2627,10 @@
--tw-shadow: 0 1px var(--tw-shadow-color, rgb(0 0 0 / 0.05));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
+ .shadow-lg {
+ --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
.shadow-md {
--tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -2712,6 +2795,11 @@
color: var(--color-body);
}
}
+ .last\:mb-0 {
+ &:last-child {
+ margin-bottom: calc(var(--spacing) * 0);
+ }
+ }
.odd\:bg-white {
&:nth-child(odd) {
background-color: var(--color-white);
@@ -2834,6 +2922,20 @@
}
}
}
+ .hover\:text-amber-600 {
+ &:hover {
+ @media (hover: hover) {
+ color: var(--color-amber-600);
+ }
+ }
+ }
+ .hover\:text-blue-600 {
+ &:hover {
+ @media (hover: hover) {
+ color: var(--color-blue-600);
+ }
+ }
+ }
.hover\:text-blue-700 {
&:hover {
@media (hover: hover) {
@@ -2848,6 +2950,13 @@
}
}
}
+ .hover\:text-gray-600 {
+ &:hover {
+ @media (hover: hover) {
+ color: var(--color-gray-600);
+ }
+ }
+ }
.hover\:text-gray-700 {
&:hover {
@media (hover: hover) {
@@ -2862,6 +2971,13 @@
}
}
}
+ .hover\:text-green-600 {
+ &:hover {
+ @media (hover: hover) {
+ color: var(--color-green-600);
+ }
+ }
+ }
.hover\:text-heading {
&:hover {
@media (hover: hover) {
@@ -2869,6 +2985,13 @@
}
}
}
+ .hover\:text-red-600 {
+ &:hover {
+ @media (hover: hover) {
+ color: var(--color-red-600);
+ }
+ }
+ }
.hover\:text-white {
&:hover {
@media (hover: hover) {
@@ -3220,6 +3343,16 @@
}
}
}
+ .dark\:border-amber-700 {
+ &:is(.dark *) {
+ border-color: var(--color-amber-700);
+ }
+ }
+ .dark\:border-blue-700 {
+ &:is(.dark *) {
+ border-color: var(--color-blue-700);
+ }
+ }
.dark\:border-gray-500 {
&:is(.dark *) {
border-color: var(--color-gray-500);
@@ -3235,11 +3368,26 @@
border-color: var(--color-gray-700);
}
}
+ .dark\:border-green-700 {
+ &:is(.dark *) {
+ border-color: var(--color-green-700);
+ }
+ }
.dark\:border-purple-600 {
&:is(.dark *) {
border-color: var(--color-purple-600);
}
}
+ .dark\:border-red-700 {
+ &:is(.dark *) {
+ border-color: var(--color-red-700);
+ }
+ }
+ .dark\:bg-amber-900 {
+ &:is(.dark *) {
+ background-color: var(--color-amber-900);
+ }
+ }
.dark\:bg-blue-200 {
&:is(.dark *) {
background-color: var(--color-blue-200);
@@ -3250,6 +3398,11 @@
background-color: var(--color-blue-600);
}
}
+ .dark\:bg-blue-900 {
+ &:is(.dark *) {
+ background-color: var(--color-blue-900);
+ }
+ }
.dark\:bg-gray-600 {
&:is(.dark *) {
background-color: var(--color-gray-600);
@@ -3299,6 +3452,11 @@
background-color: var(--color-green-600);
}
}
+ .dark\:bg-green-900 {
+ &:is(.dark *) {
+ background-color: var(--color-green-900);
+ }
+ }
.dark\:bg-purple-800 {
&:is(.dark *) {
background-color: var(--color-purple-800);
@@ -3309,6 +3467,31 @@
background-color: var(--color-red-600);
}
}
+ .dark\:bg-red-900 {
+ &:is(.dark *) {
+ background-color: var(--color-red-900);
+ }
+ }
+ .dark\:text-amber-200 {
+ &:is(.dark *) {
+ color: var(--color-amber-200);
+ }
+ }
+ .dark\:text-amber-500 {
+ &:is(.dark *) {
+ color: var(--color-amber-500);
+ }
+ }
+ .dark\:text-blue-200 {
+ &:is(.dark *) {
+ color: var(--color-blue-200);
+ }
+ }
+ .dark\:text-blue-500 {
+ &:is(.dark *) {
+ color: var(--color-blue-500);
+ }
+ }
.dark\:text-blue-800 {
&:is(.dark *) {
color: var(--color-blue-800);
@@ -3339,14 +3522,24 @@
color: var(--color-gray-600);
}
}
- .dark\:text-green-400 {
+ .dark\:text-green-200 {
&:is(.dark *) {
- color: var(--color-green-400);
+ color: var(--color-green-200);
}
}
- .dark\:text-red-400 {
+ .dark\:text-green-500 {
&:is(.dark *) {
- color: var(--color-red-400);
+ color: var(--color-green-500);
+ }
+ }
+ .dark\:text-red-200 {
+ &:is(.dark *) {
+ color: var(--color-red-200);
+ }
+ }
+ .dark\:text-red-500 {
+ &:is(.dark *) {
+ color: var(--color-red-500);
}
}
.dark\:text-slate-300 {
@@ -3471,6 +3664,51 @@
}
}
}
+ .dark\:hover\:text-amber-300 {
+ &:is(.dark *) {
+ &:hover {
+ @media (hover: hover) {
+ color: var(--color-amber-300);
+ }
+ }
+ }
+ }
+ .dark\:hover\:text-blue-300 {
+ &:is(.dark *) {
+ &:hover {
+ @media (hover: hover) {
+ color: var(--color-blue-300);
+ }
+ }
+ }
+ }
+ .dark\:hover\:text-gray-300 {
+ &:is(.dark *) {
+ &:hover {
+ @media (hover: hover) {
+ color: var(--color-gray-300);
+ }
+ }
+ }
+ }
+ .dark\:hover\:text-green-300 {
+ &:is(.dark *) {
+ &:hover {
+ @media (hover: hover) {
+ color: var(--color-green-300);
+ }
+ }
+ }
+ }
+ .dark\:hover\:text-red-300 {
+ &:is(.dark *) {
+ &:hover {
+ @media (hover: hover) {
+ color: var(--color-red-300);
+ }
+ }
+ }
+ }
.dark\:hover\:text-white {
&:is(.dark *) {
&:hover {
@@ -4042,6 +4280,18 @@ form input:disabled, select:disabled, textarea:disabled {
justify-content: space-between;
}
}
+@layer utilities {
+ .toast-container {
+ position: fixed;
+ right: calc(var(--spacing) * 0);
+ bottom: calc(var(--spacing) * 0);
+ z-index: 50;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ padding: calc(var(--spacing) * 4);
+ }
+}
@layer base {
input:where([type='text']),input:where(:not([type])),input:where([type='email']),input:where([type='url']),input:where([type='password']),input:where([type='number']),input:where([type='date']),input:where([type='datetime-local']),input:where([type='month']),input:where([type='search']),input:where([type='tel']),input:where([type='time']),input:where([type='week']),select:where([multiple]),textarea,select {
appearance: none;
diff --git a/games/static/js/htmx-redirect-toast.js b/games/static/js/htmx-redirect-toast.js
new file mode 100644
index 0000000..fb0eab1
--- /dev/null
+++ b/games/static/js/htmx-redirect-toast.js
@@ -0,0 +1,37 @@
+(function() {
+ htmx.defineExtension("hx-redirect-toast", {
+ isInlineSwap: function(swapStyle) {
+ return swapStyle === "hx-redirect-toast";
+ },
+ handleSwap: function(swapStyle, target, fragment, settleInfo, htmxConfig) {
+ var xhr = htmxConfig.xhr;
+ var hxRedirect = xhr.getResponseHeader("HX-Redirect");
+ var hxTrigger = xhr.getResponseHeader("HX-Trigger");
+
+ // Redirect immediately (toast will be shown on the new page)
+ if (hxRedirect) {
+ window.location.href = hxRedirect;
+ }
+
+ // Only dispatch HX-Trigger events for toasts when not redirecting
+ if (!hxRedirect && hxTrigger) {
+ var triggers = JSON.parse(hxTrigger);
+ var events = Array.isArray(triggers) ? triggers : [triggers];
+ events.forEach(function(triggerObj) {
+ Object.entries(triggerObj).forEach(function(entry) {
+ var name = entry[0];
+ var detail = entry[1];
+ try { detail = JSON.parse(detail); } catch(e) {}
+ target.dispatchEvent(new CustomEvent(name, {
+ detail: detail,
+ bubbles: true,
+ cancelable: true
+ }));
+ });
+ });
+ }
+ // Return null to prevent any DOM swap
+ return null;
+ }
+ });
+})();
diff --git a/games/static/js/htmx.min.js b/games/static/js/htmx.min.js
index 25ba2a2..37cd83c 100644
--- a/games/static/js/htmx.min.js
+++ b/games/static/js/htmx.min.js
@@ -1 +1 @@
-(function(e,t){if(typeof define==="function"&&define.amd){define([],t)}else if(typeof module==="object"&&module.exports){module.exports=t()}else{e.htmx=e.htmx||t()}})(typeof self!=="undefined"?self:this,function(){return function(){"use strict";var G={onLoad:t,process:Nt,on:le,off:ue,trigger:oe,ajax:xr,find:b,findAll:f,closest:d,values:function(e,t){var r=er(e,t||"post");return r.values},remove:U,addClass:B,removeClass:n,toggleClass:V,takeClass:j,defineExtension:Rr,removeExtension:Or,logAll:X,logNone:F,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",useTemplateFragments:false,scrollBehavior:"smooth",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get"],selfRequestsOnly:false},parseInterval:v,_:e,createEventSource:function(e){return new EventSource(e,{withCredentials:true})},createWebSocket:function(e){var t=new WebSocket(e,[]);t.binaryType=G.config.wsBinaryType;return t},version:"1.9.5"};var C={addTriggerHandler:bt,bodyContains:re,canAccessLocalStorage:M,findThisElement:he,filterValues:ar,hasAttribute:o,getAttributeValue:Z,getClosestAttributeValue:Y,getClosestMatch:c,getExpressionVars:gr,getHeaders:ir,getInputValues:er,getInternalData:ee,getSwapSpecification:sr,getTriggerSpecs:Ge,getTarget:de,makeFragment:l,mergeObjects:ne,makeSettleInfo:S,oobSwap:me,querySelectorExt:ie,selectAndSwap:De,settleImmediately:Wt,shouldCancel:Qe,triggerEvent:oe,triggerErrorEvent:ae,withExtensions:w};var R=["get","post","put","delete","patch"];var O=R.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function v(e){if(e==undefined){return undefined}if(e.slice(-2)=="ms"){return parseFloat(e.slice(0,-2))||undefined}if(e.slice(-1)=="s"){return parseFloat(e.slice(0,-1))*1e3||undefined}if(e.slice(-1)=="m"){return parseFloat(e.slice(0,-1))*1e3*60||undefined}return parseFloat(e)||undefined}function J(e,t){return e.getAttribute&&e.getAttribute(t)}function o(e,t){return e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function Z(e,t){return J(e,t)||J(e,"data-"+t)}function u(e){return e.parentElement}function K(){return document}function c(e,t){while(e&&!t(e)){e=u(e)}return e?e:null}function T(e,t,r){var n=Z(t,r);var i=Z(t,"hx-disinherit");if(e!==t&&i&&(i==="*"||i.split(" ").indexOf(r)>=0)){return"unset"}else{return n}}function Y(t,r){var n=null;c(t,function(e){return n=T(t,e,r)});if(n!=="unset"){return n}}function h(e,t){var r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector;return r&&r.call(e,t)}function q(e){var t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=t.exec(e);if(r){return r[1].toLowerCase()}else{return""}}function i(e,t){var r=new DOMParser;var n=r.parseFromString(e,"text/html");var i=n.body;while(t>0){t--;i=i.firstChild}if(i==null){i=K().createDocumentFragment()}return i}function H(e){return e.match(/
"+e+"",0);return r.querySelector("template").content}else{var n=q(e);switch(n){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return i("",1);case"col":return i("",2);case"tr":return i("",2);case"td":case"th":return i("",3);case"script":return i(""+e+"
",1);default:return i(e,0)}}}function Q(e){if(e){e()}}function L(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function A(e){return L(e,"Function")}function N(e){return L(e,"Object")}function ee(e){var t="htmx-internal-data";var r=e[t];if(!r){r=e[t]={}}return r}function I(e){var t=[];if(e){for(var r=0;r=0}function re(e){if(e.getRootNode&&e.getRootNode()instanceof window.ShadowRoot){return K().body.contains(e.getRootNode().host)}else{return K().body.contains(e)}}function k(e){return e.trim().split(/\s+/)}function ne(e,t){for(var r in t){if(t.hasOwnProperty(r)){e[r]=t[r]}}return e}function y(e){try{return JSON.parse(e)}catch(e){x(e);return null}}function M(){var e="htmx:localStorageTest";try{localStorage.setItem(e,e);localStorage.removeItem(e);return true}catch(e){return false}}function D(t){try{var e=new URL(t);if(e){t=e.pathname+e.search}if(!t.match("^/$")){t=t.replace(/\/+$/,"")}return t}catch(e){return t}}function e(e){return hr(K().body,function(){return eval(e)})}function t(t){var e=G.on("htmx:load",function(e){t(e.detail.elt)});return e}function X(){G.logger=function(e,t,r){if(console){console.log(t,e,r)}}}function F(){G.logger=null}function b(e,t){if(t){return e.querySelector(t)}else{return b(K(),e)}}function f(e,t){if(t){return e.querySelectorAll(t)}else{return f(K(),e)}}function U(e,t){e=s(e);if(t){setTimeout(function(){U(e);e=null},t)}else{e.parentElement.removeChild(e)}}function B(e,t,r){e=s(e);if(r){setTimeout(function(){B(e,t);e=null},r)}else{e.classList&&e.classList.add(t)}}function n(e,t,r){e=s(e);if(r){setTimeout(function(){n(e,t);e=null},r)}else{if(e.classList){e.classList.remove(t);if(e.classList.length===0){e.removeAttribute("class")}}}}function V(e,t){e=s(e);e.classList.toggle(t)}function j(e,t){e=s(e);te(e.parentElement.children,function(e){n(e,t)});B(e,t)}function d(e,t){e=s(e);if(e.closest){return e.closest(t)}else{do{if(e==null||h(e,t)){return e}}while(e=e&&u(e));return null}}function r(e){var t=e.trim();if(t.startsWith("<")&&t.endsWith("/>")){return t.substring(1,t.length-2)}else{return t}}function W(e,t){if(t.indexOf("closest ")===0){return[d(e,r(t.substr(8)))]}else if(t.indexOf("find ")===0){return[b(e,r(t.substr(5)))]}else if(t.indexOf("next ")===0){return[_(e,r(t.substr(5)))]}else if(t.indexOf("previous ")===0){return[z(e,r(t.substr(9)))]}else if(t==="document"){return[document]}else if(t==="window"){return[window]}else if(t==="body"){return[document.body]}else{return K().querySelectorAll(r(t))}}var _=function(e,t){var r=K().querySelectorAll(t);for(var n=0;n=0;n--){var i=r[n];if(i.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING){return i}}};function ie(e,t){if(t){return W(e,t)[0]}else{return W(K().body,e)[0]}}function s(e){if(L(e,"String")){return b(e)}else{return e}}function $(e,t,r){if(A(t)){return{target:K().body,event:e,listener:t}}else{return{target:s(e),event:t,listener:r}}}function le(t,r,n){Hr(function(){var e=$(t,r,n);e.target.addEventListener(e.event,e.listener)});var e=A(r);return e?r:n}function ue(t,r,n){Hr(function(){var e=$(t,r,n);e.target.removeEventListener(e.event,e.listener)});return A(r)?r:n}var fe=K().createElement("output");function ce(e,t){var r=Y(e,t);if(r){if(r==="this"){return[he(e,t)]}else{var n=W(e,r);if(n.length===0){x('The selector "'+r+'" on '+t+" returned no matches!");return[fe]}else{return n}}}}function he(e,t){return c(e,function(e){return Z(e,t)!=null})}function de(e){var t=Y(e,"hx-target");if(t){if(t==="this"){return he(e,"hx-target")}else{return ie(e,t)}}else{var r=ee(e);if(r.boosted){return K().body}else{return e}}}function ve(e){var t=G.config.attributesToSettle;for(var r=0;r0){o=e.substr(0,e.indexOf(":"));t=e.substr(e.indexOf(":")+1,e.length)}else{o=e}var r=K().querySelectorAll(t);if(r){te(r,function(e){var t;var r=i.cloneNode(true);t=K().createDocumentFragment();t.appendChild(r);if(!pe(o,e)){t=r}var n={shouldSwap:true,target:e,fragment:t};if(!oe(e,"htmx:oobBeforeSwap",n))return;e=n.target;if(n["shouldSwap"]){ke(o,e,e,t,a)}te(a.elts,function(e){oe(e,"htmx:oobAfterSwap",n)})});i.parentNode.removeChild(i)}else{i.parentNode.removeChild(i);ae(K().body,"htmx:oobErrorNoTarget",{content:i})}return e}function xe(e,t,r){var n=Y(e,"hx-select-oob");if(n){var i=n.split(",");for(let e=0;e0){var r=t.replace("'","\\'");var n=e.tagName.replace(":","\\:");var i=o.querySelector(n+"[id='"+r+"']");if(i&&i!==o){var a=e.cloneNode();ge(e,i);s.tasks.push(function(){ge(e,a)})}}})}function we(e){return function(){n(e,G.config.addedClass);Nt(e);St(e);Se(e);oe(e,"htmx:load")}}function Se(e){var t="[autofocus]";var r=h(e,t)?e:e.querySelector(t);if(r!=null){r.focus()}}function a(e,t,r,n){be(e,r,n);while(r.childNodes.length>0){var i=r.firstChild;B(i,G.config.addedClass);e.insertBefore(i,t);if(i.nodeType!==Node.TEXT_NODE&&i.nodeType!==Node.COMMENT_NODE){n.tasks.push(we(i))}}}function Ee(e,t){var r=0;while(r-1){var t=e.replace(/