A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
72
fork

Configure Feed

Select the types of activity you want to include in your feed.

impeccable fixes, scanner fixes

+644 -264
+1
.gitignore
··· 36 36 .DS_Store 37 37 Thumbs.db 38 38 node_modules 39 + .impeccable.md
+128 -6
pkg/appview/db/queries.go
··· 5 5 "encoding/json" 6 6 "fmt" 7 7 "net/url" 8 + "regexp" 8 9 "strings" 9 10 "time" 10 11 ) ··· 192 193 193 194 var total int 194 195 if err := db.QueryRow(countQuery, currentUserDID, currentUserDID, searchPattern, query, searchPattern, searchPattern).Scan(&total); err != nil { 196 + return nil, 0, err 197 + } 198 + 199 + if err := PopulateRepoCardTags(db, cards); err != nil { 195 200 return nil, 0, err 196 201 } 197 202 ··· 2143 2148 return nil, err 2144 2149 } 2145 2150 2151 + if err := PopulateRepoCardTags(db, cards); err != nil { 2152 + return nil, err 2153 + } 2154 + 2146 2155 return cards, nil 2147 2156 } 2148 2157 ··· 2213 2222 } 2214 2223 2215 2224 if err := rows.Err(); err != nil { 2225 + return nil, err 2226 + } 2227 + 2228 + if err := PopulateRepoCardTags(db, cards); err != nil { 2216 2229 return nil, err 2217 2230 } 2218 2231 ··· 2561 2574 return holds, rows.Err() 2562 2575 } 2563 2576 2577 + // TagNameDigest is a lightweight (tag, digest) pair used for dropdown 2578 + // population and default-selection heuristics. 2579 + type TagNameDigest struct { 2580 + Name string 2581 + Digest string 2582 + } 2583 + 2584 + // shaTagPattern matches CI-style git-sha tags like "sha-937fa4c". 2585 + var shaTagPattern = regexp.MustCompile(`^sha-[0-9a-f]{6,40}$`) 2586 + 2587 + // PickDefaultTag chooses the best display tag from a list of (name, digest) 2588 + // pairs ordered most-recent first. 2589 + // 2590 + // 1. Start with the newest tag. 2591 + // 2. If that newest tag looks like a git-sha tag, look for a sibling with 2592 + // the same digest that doesn't — happyview-style repos push both 2593 + // "sha-937fa4c" and "2.0.0-dev.45" pointing at the same image; we'd 2594 + // rather show the semver name. 2595 + // 3. If "latest" exists AND points to the same digest as the chosen tag, 2596 + // prefer "latest" as the friendliest label. A stale "latest" pointing 2597 + // at an old digest is bypassed. 2598 + func PickDefaultTag(tags []TagNameDigest) string { 2599 + if len(tags) == 0 { 2600 + return "" 2601 + } 2602 + chosen := tags[0] 2603 + if shaTagPattern.MatchString(chosen.Name) { 2604 + for _, t := range tags[1:] { 2605 + if t.Digest == chosen.Digest && !shaTagPattern.MatchString(t.Name) { 2606 + chosen = t 2607 + break 2608 + } 2609 + } 2610 + } 2611 + if chosen.Name != "latest" { 2612 + for _, t := range tags { 2613 + if t.Name == "latest" && t.Digest == chosen.Digest { 2614 + return "latest" 2615 + } 2616 + } 2617 + } 2618 + return chosen.Name 2619 + } 2620 + 2621 + // PopulateRepoCardTags overrides each card's Tag field with the best display 2622 + // tag chosen by PickDefaultTag. Issues one batch query for all (handle, repository) 2623 + // pairs in the slice. No-op for an empty slice. 2624 + // 2625 + // RepoCardData doesn't carry the owner DID, so we join through users.handle. 2626 + // This is fine because (handle, repository) is unique within the appview. 2627 + func PopulateRepoCardTags(db DBTX, cards []RepoCardData) error { 2628 + if len(cards) == 0 { 2629 + return nil 2630 + } 2631 + type key struct{ handle, repo string } 2632 + placeholders := make([]string, 0, len(cards)) 2633 + args := make([]any, 0, len(cards)*2) 2634 + for _, c := range cards { 2635 + placeholders = append(placeholders, "(?, ?)") 2636 + args = append(args, c.OwnerHandle, c.Repository) 2637 + } 2638 + q := ` 2639 + SELECT u.handle, t.repository, t.tag, t.digest 2640 + FROM tags t 2641 + JOIN users u ON t.did = u.did 2642 + WHERE (u.handle, t.repository) IN (VALUES ` + strings.Join(placeholders, ",") + `) 2643 + ORDER BY t.repository, t.created_at DESC 2644 + ` 2645 + rows, err := db.Query(q, args...) 2646 + if err != nil { 2647 + return err 2648 + } 2649 + defer rows.Close() 2650 + groups := make(map[key][]TagNameDigest) 2651 + for rows.Next() { 2652 + var handle, repo, tag, digest string 2653 + if err := rows.Scan(&handle, &repo, &tag, &digest); err != nil { 2654 + return err 2655 + } 2656 + k := key{handle, repo} 2657 + groups[k] = append(groups[k], TagNameDigest{Name: tag, Digest: digest}) 2658 + } 2659 + if err := rows.Err(); err != nil { 2660 + return err 2661 + } 2662 + for i := range cards { 2663 + k := key{cards[i].OwnerHandle, cards[i].Repository} 2664 + if g, ok := groups[k]; ok && len(g) > 0 { 2665 + cards[i].Tag = PickDefaultTag(g) 2666 + } 2667 + } 2668 + return nil 2669 + } 2670 + 2564 2671 // GetAllTagNames returns all tag names for a repository, ordered by most recent first. 2565 2672 // Filters out tags whose manifests live on holds the viewer can't access. 2566 2673 func GetAllTagNames(db DBTX, did, repository string, viewerDID string) ([]string, error) { 2674 + pairs, err := GetAllTagsWithDigests(db, did, repository, viewerDID) 2675 + if err != nil { 2676 + return nil, err 2677 + } 2678 + names := make([]string, len(pairs)) 2679 + for i, p := range pairs { 2680 + names[i] = p.Name 2681 + } 2682 + return names, nil 2683 + } 2684 + 2685 + // GetAllTagsWithDigests returns all tags for a repository with their manifest 2686 + // digests, ordered by most recent first. Filters out tags whose manifests live 2687 + // on holds the viewer can't access. 2688 + func GetAllTagsWithDigests(db DBTX, did, repository string, viewerDID string) ([]TagNameDigest, error) { 2567 2689 rows, err := db.Query(` 2568 - SELECT t.tag FROM tags t 2690 + SELECT t.tag, t.digest FROM tags t 2569 2691 JOIN manifests m ON t.did = m.did AND t.repository = m.repository AND t.digest = m.digest 2570 2692 WHERE t.did = ? AND t.repository = ? 2571 2693 AND m.hold_endpoint IN `+accessibleHoldsSubquery+` ··· 2576 2698 } 2577 2699 defer rows.Close() 2578 2700 2579 - var names []string 2701 + var out []TagNameDigest 2580 2702 for rows.Next() { 2581 - var name string 2582 - if err := rows.Scan(&name); err != nil { 2703 + var p TagNameDigest 2704 + if err := rows.Scan(&p.Name, &p.Digest); err != nil { 2583 2705 return nil, err 2584 2706 } 2585 - names = append(names, name) 2707 + out = append(out, p) 2586 2708 } 2587 - return names, rows.Err() 2709 + return out, rows.Err() 2588 2710 } 2589 2711 2590 2712 // GetLayerCountForManifest returns the number of layers for a manifest identified by digest.
+8 -13
pkg/appview/handlers/repository.go
··· 78 78 viewerDID = vu.DID 79 79 } 80 80 81 - // Fetch all tag names for the selector dropdown 82 - allTags, err := db.GetAllTagNames(h.ReadOnlyDB, owner.DID, repository, viewerDID) 81 + // Fetch all tags (with digests) for the dropdown and default-selection heuristics. 82 + tagPairs, err := db.GetAllTagsWithDigests(h.ReadOnlyDB, owner.DID, repository, viewerDID) 83 83 if err != nil { 84 84 slog.Warn("Failed to fetch tag names", "error", err) 85 85 } 86 + allTags := make([]string, len(tagPairs)) 87 + for i, p := range tagPairs { 88 + allTags[i] = p.Name 89 + } 86 90 87 91 // Determine which tag to show 88 92 selectedTagName := r.URL.Query().Get("tag") 89 - if selectedTagName == "" { 90 - // Default: "latest" if it exists, otherwise most recent 91 - for _, t := range allTags { 92 - if t == "latest" { 93 - selectedTagName = "latest" 94 - break 95 - } 96 - } 97 - if selectedTagName == "" && len(allTags) > 0 { 98 - selectedTagName = allTags[0] // most recent (already sorted DESC) 99 - } 93 + if selectedTagName == "" && len(tagPairs) > 0 { 94 + selectedTagName = db.PickDefaultTag(tagPairs) 100 95 } 101 96 102 97 // Fetch the selected tag's full data
+11 -7
pkg/appview/handlers/sbom_details.go
··· 36 36 37 37 // sbomDetailsData is the template data for the sbom-details partial. 38 38 type sbomDetailsData struct { 39 - Packages []sbomPackage 40 - Total int 41 - Error string 42 - ScannedAt string 39 + Packages []sbomPackage 40 + Total int 41 + Error string 42 + ScannedAt string 43 + Digest string // image digest (for download URLs) 44 + HoldEndpoint string // hold DID (for download URLs) 43 45 } 44 46 45 47 type sbomPackage struct { ··· 199 201 }) 200 202 201 203 return sbomDetailsData{ 202 - Packages: packages, 203 - Total: len(packages), 204 - ScannedAt: scanRecord.ScannedAt, 204 + Packages: packages, 205 + Total: len(packages), 206 + ScannedAt: scanRecord.ScannedAt, 207 + Digest: digest, 208 + HoldEndpoint: holdEndpoint, 205 209 } 206 210 } 207 211
+155
pkg/appview/handlers/scan_download.go
··· 1 + package handlers 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + "io" 8 + "log/slog" 9 + "net/http" 10 + "net/url" 11 + "strings" 12 + "time" 13 + 14 + "atcr.io/pkg/atproto" 15 + ) 16 + 17 + // ScanDownloadHandler serves raw scan blobs (SPDX JSON or Grype JSON) as file downloads. 18 + // GET /api/scan-download?digest=sha256:...&holdEndpoint=did:web:...&type=vuln|sbom 19 + type ScanDownloadHandler struct { 20 + BaseUIHandler 21 + } 22 + 23 + func (h *ScanDownloadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 24 + digest := r.URL.Query().Get("digest") 25 + holdEndpoint := r.URL.Query().Get("holdEndpoint") 26 + blobType := r.URL.Query().Get("type") // "vuln" or "sbom" 27 + 28 + if digest == "" || holdEndpoint == "" || (blobType != "vuln" && blobType != "sbom") { 29 + http.Error(w, "missing or invalid parameters", http.StatusBadRequest) 30 + return 31 + } 32 + 33 + hold, err := ResolveHold(r.Context(), h.ReadOnlyDB, holdEndpoint) 34 + if err != nil { 35 + slog.Debug("Failed to resolve hold for download", "holdEndpoint", holdEndpoint, "error", err) 36 + http.Error(w, "could not resolve hold", http.StatusBadGateway) 37 + return 38 + } 39 + 40 + data, err := fetchScanBlob(r.Context(), hold.DID, hold.URL, digest, blobType) 41 + if err != nil { 42 + slog.Debug("Failed to fetch scan blob", "type", blobType, "error", err) 43 + http.Error(w, err.Error(), http.StatusNotFound) 44 + return 45 + } 46 + 47 + shortDigest := strings.TrimPrefix(digest, "sha256:") 48 + if len(shortDigest) > 12 { 49 + shortDigest = shortDigest[:12] 50 + } 51 + 52 + var filename, contentType string 53 + switch blobType { 54 + case "vuln": 55 + filename = fmt.Sprintf("vulnerabilities-%s.json", shortDigest) 56 + contentType = "application/json" 57 + case "sbom": 58 + filename = fmt.Sprintf("sbom-spdx-%s.json", shortDigest) 59 + contentType = "application/spdx+json" 60 + } 61 + 62 + w.Header().Set("Content-Type", contentType) 63 + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename)) 64 + if _, err := w.Write(data); err != nil { 65 + slog.Debug("Failed to write scan download response", "error", err) 66 + } 67 + } 68 + 69 + // fetchScanBlob fetches the raw scan blob bytes from a hold. 70 + // blobType is "vuln" or "sbom". 71 + func fetchScanBlob(ctx context.Context, holdDID, holdURL, digest, blobType string) ([]byte, error) { 72 + rkey := strings.TrimPrefix(digest, "sha256:") 73 + 74 + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) 75 + defer cancel() 76 + 77 + // Fetch the scan record 78 + scanURL := fmt.Sprintf("%s/xrpc/com.atproto.repo.getRecord?repo=%s&collection=%s&rkey=%s", 79 + holdURL, 80 + url.QueryEscape(holdDID), 81 + url.QueryEscape(atproto.ScanCollection), 82 + url.QueryEscape(rkey), 83 + ) 84 + 85 + req, err := http.NewRequestWithContext(ctx, "GET", scanURL, nil) 86 + if err != nil { 87 + return nil, fmt.Errorf("build request: %w", err) 88 + } 89 + 90 + resp, err := http.DefaultClient.Do(req) 91 + if err != nil { 92 + return nil, fmt.Errorf("hold unreachable: %w", err) 93 + } 94 + defer resp.Body.Close() 95 + 96 + if resp.StatusCode != http.StatusOK { 97 + return nil, fmt.Errorf("no scan record found") 98 + } 99 + 100 + var envelope struct { 101 + Value json.RawMessage `json:"value"` 102 + } 103 + if err := json.NewDecoder(resp.Body).Decode(&envelope); err != nil { 104 + return nil, fmt.Errorf("parse scan record: %w", err) 105 + } 106 + 107 + var scanRecord atproto.ScanRecord 108 + if err := json.Unmarshal(envelope.Value, &scanRecord); err != nil { 109 + return nil, fmt.Errorf("parse scan record: %w", err) 110 + } 111 + 112 + // Select the appropriate blob CID 113 + var blobCID string 114 + switch blobType { 115 + case "vuln": 116 + if scanRecord.VulnReportBlob == nil || scanRecord.VulnReportBlob.Ref.String() == "" { 117 + return nil, fmt.Errorf("no vulnerability report available") 118 + } 119 + blobCID = scanRecord.VulnReportBlob.Ref.String() 120 + case "sbom": 121 + if scanRecord.SbomBlob == nil || scanRecord.SbomBlob.Ref.String() == "" { 122 + return nil, fmt.Errorf("no SBOM available") 123 + } 124 + blobCID = scanRecord.SbomBlob.Ref.String() 125 + } 126 + 127 + // Fetch the blob 128 + blobURL := fmt.Sprintf("%s/xrpc/com.atproto.sync.getBlob?did=%s&cid=%s", 129 + holdURL, 130 + url.QueryEscape(holdDID), 131 + url.QueryEscape(blobCID), 132 + ) 133 + 134 + blobReq, err := http.NewRequestWithContext(ctx, "GET", blobURL, nil) 135 + if err != nil { 136 + return nil, fmt.Errorf("build blob request: %w", err) 137 + } 138 + 139 + blobResp, err := http.DefaultClient.Do(blobReq) 140 + if err != nil { 141 + return nil, fmt.Errorf("fetch blob: %w", err) 142 + } 143 + defer blobResp.Body.Close() 144 + 145 + if blobResp.StatusCode != http.StatusOK { 146 + return nil, fmt.Errorf("blob not accessible") 147 + } 148 + 149 + data, err := io.ReadAll(blobResp.Body) 150 + if err != nil { 151 + return nil, fmt.Errorf("read blob: %w", err) 152 + } 153 + 154 + return data, nil 155 + }
+11 -7
pkg/appview/handlers/vuln_details.go
··· 54 54 55 55 // vulnDetailsData is the template data for the vuln-details partial. 56 56 type vulnDetailsData struct { 57 - Matches []vulnMatch 58 - Summary vulnSummary 59 - Error string // non-empty if something went wrong 60 - ScannedAt string 57 + Matches []vulnMatch 58 + Summary vulnSummary 59 + Error string // non-empty if something went wrong 60 + ScannedAt string 61 + Digest string // image digest (for download URLs) 62 + HoldEndpoint string // hold DID (for download URLs) 61 63 } 62 64 63 65 type vulnMatch struct { ··· 384 386 }) 385 387 386 388 return vulnDetailsData{ 387 - Matches: matches, 388 - Summary: summary, 389 - ScannedAt: scanRecord.ScannedAt, 389 + Matches: matches, 390 + Summary: summary, 391 + ScannedAt: scanRecord.ScannedAt, 392 + Digest: digest, 393 + HoldEndpoint: holdEndpoint, 390 394 } 391 395 }
+2 -1
pkg/appview/public/js/bundle.min.js
··· 1 - var ce=(function(){"use strict";let htmx={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){return getInputValues(e,t||"post").values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:!0,historyCacheSize:10,refreshOnHistoryMiss:!1,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:!0,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:!0,allowScriptTags:!0,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:!1,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:!1,getCacheBusterParam:!1,globalViewTransitions:!1,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:!0,ignoreTitle:!1,scrollIntoViewOnBoost:!0,triggerSpecsCache:null,disableInheritance:!1,responseHandling:[{code:"204",swap:!1},{code:"[23]..",swap:!0},{code:"[45]..",swap:!1,error:!0}],allowNestedOobSwaps:!0,historyRestoreAsHxRequest:!0,reportValidityOfForms:!1},parseInterval:null,location,_:null,version:"2.0.8"};htmx.onLoad=onLoadHelper,htmx.process=processNode,htmx.on=addEventListenerImpl,htmx.off=removeEventListenerImpl,htmx.trigger=triggerEvent,htmx.ajax=ajaxHelper,htmx.find=find,htmx.findAll=findAll,htmx.closest=closest,htmx.remove=removeElement,htmx.addClass=addClassToElement,htmx.removeClass=removeClassFromElement,htmx.toggleClass=toggleClassOnElement,htmx.takeClass=takeClassForElement,htmx.swap=swap,htmx.defineExtension=defineExtension,htmx.removeExtension=removeExtension,htmx.logAll=logAll,htmx.logNone=logNone,htmx.parseInterval=parseInterval,htmx._=internalEval;let internalAPI={addTriggerHandler,bodyContains,canAccessLocalStorage,findThisElement,filterValues,swap,hasAttribute,getAttributeValue,getClosestAttributeValue,getClosestMatch,getExpressionVars,getHeaders,getInputValues,getInternalData,getSwapSpecification,getTriggerSpecs,getTarget,makeFragment,mergeObjects,makeSettleInfo,oobSwap,querySelectorExt,settleImmediately,shouldCancel,triggerEvent,triggerErrorEvent,withExtensions},VERBS=["get","post","put","delete","patch"],VERB_SELECTOR=VERBS.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function parseInterval(e){if(e==null)return;let t=NaN;return e.slice(-2)=="ms"?t=parseFloat(e.slice(0,-2)):e.slice(-1)=="s"?t=parseFloat(e.slice(0,-1))*1e3:e.slice(-1)=="m"?t=parseFloat(e.slice(0,-1))*1e3*60:t=parseFloat(e),isNaN(t)?void 0:t}function getRawAttribute(e,t){return e instanceof Element&&e.getAttribute(t)}function hasAttribute(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function getAttributeValue(e,t){return getRawAttribute(e,t)||getRawAttribute(e,"data-"+t)}function parentElt(e){let t=e.parentElement;return!t&&e.parentNode instanceof ShadowRoot?e.parentNode:t}function getDocument(){return document}function getRootNode(e,t){return e.getRootNode?e.getRootNode({composed:t}):getDocument()}function getClosestMatch(e,t){for(;e&&!t(e);)e=parentElt(e);return e||null}function getAttributeValueWithDisinheritance(e,t,n){let r=getAttributeValue(t,n),o=getAttributeValue(t,"hx-disinherit");var s=getAttributeValue(t,"hx-inherit");if(e!==t){if(htmx.config.disableInheritance)return s&&(s==="*"||s.split(" ").indexOf(n)>=0)?r:null;if(o&&(o==="*"||o.split(" ").indexOf(n)>=0))return"unset"}return r}function getClosestAttributeValue(e,t){let n=null;if(getClosestMatch(e,function(r){return!!(n=getAttributeValueWithDisinheritance(e,asElement(r),t))}),n!=="unset")return n}function matches(e,t){return e instanceof Element&&e.matches(t)}function getStartTag(e){let n=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i.exec(e);return n?n[1].toLowerCase():""}function parseHTML(e){return"parseHTMLUnsafe"in Document?Document.parseHTMLUnsafe(e):new DOMParser().parseFromString(e,"text/html")}function takeChildrenFor(e,t){for(;t.childNodes.length>0;)e.append(t.childNodes[0])}function duplicateScript(e){let t=getDocument().createElement("script");return forEach(e.attributes,function(n){t.setAttribute(n.name,n.value)}),t.textContent=e.textContent,t.async=!1,htmx.config.inlineScriptNonce&&(t.nonce=htmx.config.inlineScriptNonce),t}function isJavaScriptScriptNode(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function normalizeScriptTags(e){Array.from(e.querySelectorAll("script")).forEach(t=>{if(isJavaScriptScriptNode(t)){let n=duplicateScript(t),r=t.parentNode;try{r.insertBefore(n,t)}catch(o){logError(o)}finally{t.remove()}}})}function makeFragment(e){let t=e.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i,""),n=getStartTag(t),r;if(n==="html"){r=new DocumentFragment;let s=parseHTML(e);takeChildrenFor(r,s.body),r.title=s.title}else if(n==="body"){r=new DocumentFragment;let s=parseHTML(t);takeChildrenFor(r,s.body),r.title=s.title}else{let s=parseHTML('<body><template class="internal-htmx-wrapper">'+t+"</template></body>");r=s.querySelector("template").content,r.title=s.title;var o=r.querySelector("title");o&&o.parentNode===r&&(o.remove(),r.title=o.innerText)}return r&&(htmx.config.allowScriptTags?normalizeScriptTags(r):r.querySelectorAll("script").forEach(s=>s.remove())),r}function maybeCall(e){e&&e()}function isType(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function isFunction(e){return typeof e=="function"}function isRawObject(e){return isType(e,"Object")}function getInternalData(e){let t="htmx-internal-data",n=e[t];return n||(n=e[t]={}),n}function toArray(e){let t=[];if(e)for(let n=0;n<e.length;n++)t.push(e[n]);return t}function forEach(e,t){if(e)for(let n=0;n<e.length;n++)t(e[n])}function isScrolledIntoView(e){let t=e.getBoundingClientRect(),n=t.top,r=t.bottom;return n<window.innerHeight&&r>=0}function bodyContains(e){return e.getRootNode({composed:!0})===document}function splitOnWhitespace(e){return e.trim().split(/\s+/)}function mergeObjects(e,t){for(let n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}function parseJSON(e){try{return JSON.parse(e)}catch(t){return logError(t),null}}function canAccessLocalStorage(){let e="htmx:sessionStorageTest";try{return sessionStorage.setItem(e,e),sessionStorage.removeItem(e),!0}catch{return!1}}function normalizePath(e){let t=new URL(e,"http://x");return t&&(e=t.pathname+t.search),e!="/"&&(e=e.replace(/\/+$/,"")),e}function internalEval(str){return maybeEval(getDocument().body,function(){return eval(str)})}function onLoadHelper(e){return htmx.on("htmx:load",function(n){e(n.detail.elt)})}function logAll(){htmx.logger=function(e,t,n){console&&console.log(t,e,n)}}function logNone(){htmx.logger=null}function find(e,t){return typeof e!="string"?e.querySelector(t):find(getDocument(),e)}function findAll(e,t){return typeof e!="string"?e.querySelectorAll(t):findAll(getDocument(),e)}function getWindow(){return window}function removeElement(e,t){e=resolveTarget(e),t?getWindow().setTimeout(function(){removeElement(e),e=null},t):parentElt(e).removeChild(e)}function asElement(e){return e instanceof Element?e:null}function asHtmlElement(e){return e instanceof HTMLElement?e:null}function asString(e){return typeof e=="string"?e:null}function asParentNode(e){return e instanceof Element||e instanceof Document||e instanceof DocumentFragment?e:null}function addClassToElement(e,t,n){e=asElement(resolveTarget(e)),e&&(n?getWindow().setTimeout(function(){addClassToElement(e,t),e=null},n):e.classList&&e.classList.add(t))}function removeClassFromElement(e,t,n){let r=asElement(resolveTarget(e));r&&(n?getWindow().setTimeout(function(){removeClassFromElement(r,t),r=null},n):r.classList&&(r.classList.remove(t),r.classList.length===0&&r.removeAttribute("class")))}function toggleClassOnElement(e,t){e=resolveTarget(e),e.classList.toggle(t)}function takeClassForElement(e,t){e=resolveTarget(e),forEach(e.parentElement.children,function(n){removeClassFromElement(n,t)}),addClassToElement(asElement(e),t)}function closest(e,t){return e=asElement(resolveTarget(e)),e?e.closest(t):null}function startsWith(e,t){return e.substring(0,t.length)===t}function endsWith(e,t){return e.substring(e.length-t.length)===t}function normalizeSelector(e){let t=e.trim();return startsWith(t,"<")&&endsWith(t,"/>")?t.substring(1,t.length-2):t}function querySelectorAllExt(e,t,n){if(t.indexOf("global ")===0)return querySelectorAllExt(e,t.slice(7),!0);e=resolveTarget(e);let r=[];{let i=0,a=0;for(let l=0;l<t.length;l++){let c=t[l];if(c===","&&i===0){r.push(t.substring(a,l)),a=l+1;continue}c==="<"?i++:c==="/"&&l<t.length-1&&t[l+1]===">"&&i--}a<t.length&&r.push(t.substring(a))}let o=[],s=[];for(;r.length>0;){let i=normalizeSelector(r.shift()),a;i.indexOf("closest ")===0?a=closest(asElement(e),normalizeSelector(i.slice(8))):i.indexOf("find ")===0?a=find(asParentNode(e),normalizeSelector(i.slice(5))):i==="next"||i==="nextElementSibling"?a=asElement(e).nextElementSibling:i.indexOf("next ")===0?a=scanForwardQuery(e,normalizeSelector(i.slice(5)),!!n):i==="previous"||i==="previousElementSibling"?a=asElement(e).previousElementSibling:i.indexOf("previous ")===0?a=scanBackwardsQuery(e,normalizeSelector(i.slice(9)),!!n):i==="document"?a=document:i==="window"?a=window:i==="body"?a=document.body:i==="root"?a=getRootNode(e,!!n):i==="host"?a=e.getRootNode().host:s.push(i),a&&o.push(a)}if(s.length>0){let i=s.join(","),a=asParentNode(getRootNode(e,!!n));o.push(...toArray(a.querySelectorAll(i)))}return o}var scanForwardQuery=function(e,t,n){let r=asParentNode(getRootNode(e,n)).querySelectorAll(t);for(let o=0;o<r.length;o++){let s=r[o];if(s.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_PRECEDING)return s}},scanBackwardsQuery=function(e,t,n){let r=asParentNode(getRootNode(e,n)).querySelectorAll(t);for(let o=r.length-1;o>=0;o--){let s=r[o];if(s.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING)return s}};function querySelectorExt(e,t){return typeof e!="string"?querySelectorAllExt(e,t)[0]:querySelectorAllExt(getDocument().body,e)[0]}function resolveTarget(e,t){return typeof e=="string"?find(asParentNode(t)||document,e):e}function processEventArgs(e,t,n,r){return isFunction(t)?{target:getDocument().body,event:asString(e),listener:t,options:n}:{target:resolveTarget(e),event:asString(t),listener:n,options:r}}function addEventListenerImpl(e,t,n,r){return ready(function(){let s=processEventArgs(e,t,n,r);s.target.addEventListener(s.event,s.listener,s.options)}),isFunction(t)?t:n}function removeEventListenerImpl(e,t,n){return ready(function(){let r=processEventArgs(e,t,n);r.target.removeEventListener(r.event,r.listener)}),isFunction(t)?t:n}let DUMMY_ELT=getDocument().createElement("output");function findAttributeTargets(e,t){let n=getClosestAttributeValue(e,t);if(n){if(n==="this")return[findThisElement(e,t)];{let r=querySelectorAllExt(e,n);if(/(^|,)(\s*)inherit(\s*)($|,)/.test(n)){let s=asElement(getClosestMatch(e,function(i){return i!==e&&hasAttribute(asElement(i),t)}));s&&r.push(...findAttributeTargets(s,t))}return r.length===0?(logError('The selector "'+n+'" on '+t+" returned no matches!"),[DUMMY_ELT]):r}}}function findThisElement(e,t){return asElement(getClosestMatch(e,function(n){return getAttributeValue(asElement(n),t)!=null}))}function getTarget(e){let t=getClosestAttributeValue(e,"hx-target");return t?t==="this"?findThisElement(e,"hx-target"):querySelectorExt(e,t):getInternalData(e).boosted?getDocument().body:e}function shouldSettleAttribute(e){return htmx.config.attributesToSettle.includes(e)}function cloneAttributes(e,t){forEach(Array.from(e.attributes),function(n){!t.hasAttribute(n.name)&&shouldSettleAttribute(n.name)&&e.removeAttribute(n.name)}),forEach(t.attributes,function(n){shouldSettleAttribute(n.name)&&e.setAttribute(n.name,n.value)})}function isInlineSwap(e,t){let n=getExtensions(t);for(let r=0;r<n.length;r++){let o=n[r];try{if(o.isInlineSwap(e))return!0}catch(s){logError(s)}}return e==="outerHTML"}function oobSwap(e,t,n,r){r=r||getDocument();let o="#"+CSS.escape(getRawAttribute(t,"id")),s="outerHTML";e==="true"||(e.indexOf(":")>0?(s=e.substring(0,e.indexOf(":")),o=e.substring(e.indexOf(":")+1)):s=e),t.removeAttribute("hx-swap-oob"),t.removeAttribute("data-hx-swap-oob");let i=querySelectorAllExt(r,o,!1);return i.length?(forEach(i,function(a){let l,c=t.cloneNode(!0);l=getDocument().createDocumentFragment(),l.appendChild(c),isInlineSwap(s,a)||(l=asParentNode(c));let h={shouldSwap:!0,target:a,fragment:l};triggerEvent(a,"htmx:oobBeforeSwap",h)&&(a=h.target,h.shouldSwap&&(handlePreservedElements(l),swapWithStyle(s,a,a,l,n),restorePreservedElements()),forEach(n.elts,function(u){triggerEvent(u,"htmx:oobAfterSwap",h)}))}),t.parentNode.removeChild(t)):(t.parentNode.removeChild(t),triggerErrorEvent(getDocument().body,"htmx:oobErrorNoTarget",{content:t})),e}function restorePreservedElements(){let e=find("#--htmx-preserve-pantry--");if(e){for(let t of[...e.children]){let n=find("#"+t.id);n.parentNode.moveBefore(t,n),n.remove()}e.remove()}}function handlePreservedElements(e){forEach(findAll(e,"[hx-preserve], [data-hx-preserve]"),function(t){let n=getAttributeValue(t,"id"),r=getDocument().getElementById(n);if(r!=null)if(t.moveBefore){let o=find("#--htmx-preserve-pantry--");o==null&&(getDocument().body.insertAdjacentHTML("afterend","<div id='--htmx-preserve-pantry--'></div>"),o=find("#--htmx-preserve-pantry--")),o.moveBefore(r,null)}else t.parentNode.replaceChild(r,t)})}function handleAttributes(e,t,n){forEach(t.querySelectorAll("[id]"),function(r){let o=getRawAttribute(r,"id");if(o&&o.length>0){let s=o.replace("'","\\'"),i=r.tagName.replace(":","\\:"),a=asParentNode(e),l=a&&a.querySelector(i+"[id='"+s+"']");if(l&&l!==a){let c=r.cloneNode();cloneAttributes(r,l),n.tasks.push(function(){cloneAttributes(r,c)})}}})}function makeAjaxLoadTask(e){return function(){removeClassFromElement(e,htmx.config.addedClass),processNode(asElement(e)),processFocus(asParentNode(e)),triggerEvent(e,"htmx:load")}}function processFocus(e){let t="[autofocus]",n=asHtmlElement(matches(e,t)?e:e.querySelector(t));n?.focus()}function insertNodesBefore(e,t,n,r){for(handleAttributes(e,n,r);n.childNodes.length>0;){let o=n.firstChild;addClassToElement(asElement(o),htmx.config.addedClass),e.insertBefore(o,t),o.nodeType!==Node.TEXT_NODE&&o.nodeType!==Node.COMMENT_NODE&&r.tasks.push(makeAjaxLoadTask(o))}}function stringHash(e,t){let n=0;for(;n<e.length;)t=(t<<5)-t+e.charCodeAt(n++)|0;return t}function attributeHash(e){let t=0;for(let n=0;n<e.attributes.length;n++){let r=e.attributes[n];r.value&&(t=stringHash(r.name,t),t=stringHash(r.value,t))}return t}function deInitOnHandlers(e){let t=getInternalData(e);if(t.onHandlers){for(let n=0;n<t.onHandlers.length;n++){let r=t.onHandlers[n];removeEventListenerImpl(e,r.event,r.listener)}delete t.onHandlers}}function deInitNode(e){let t=getInternalData(e);t.timeout&&clearTimeout(t.timeout),t.listenerInfos&&forEach(t.listenerInfos,function(n){n.on&&removeEventListenerImpl(n.on,n.trigger,n.listener)}),deInitOnHandlers(e),forEach(Object.keys(t),function(n){n!=="firstInitCompleted"&&delete t[n]})}function cleanUpElement(e){triggerEvent(e,"htmx:beforeCleanupElement"),deInitNode(e),forEach(e.children,function(t){cleanUpElement(t)})}function swapOuterHTML(e,t,n){if(e.tagName==="BODY")return swapInnerHTML(e,t,n);let r,o=e.previousSibling,s=parentElt(e);if(s){for(insertNodesBefore(s,e,t,n),o==null?r=s.firstChild:r=o.nextSibling,n.elts=n.elts.filter(function(i){return i!==e});r&&r!==e;)r instanceof Element&&n.elts.push(r),r=r.nextSibling;cleanUpElement(e),e.remove()}}function swapAfterBegin(e,t,n){return insertNodesBefore(e,e.firstChild,t,n)}function swapBeforeBegin(e,t,n){return insertNodesBefore(parentElt(e),e,t,n)}function swapBeforeEnd(e,t,n){return insertNodesBefore(e,null,t,n)}function swapAfterEnd(e,t,n){return insertNodesBefore(parentElt(e),e.nextSibling,t,n)}function swapDelete(e){cleanUpElement(e);let t=parentElt(e);if(t)return t.removeChild(e)}function swapInnerHTML(e,t,n){let r=e.firstChild;if(insertNodesBefore(e,r,t,n),r){for(;r.nextSibling;)cleanUpElement(r.nextSibling),e.removeChild(r.nextSibling);cleanUpElement(r),e.removeChild(r)}}function swapWithStyle(e,t,n,r,o){switch(e){case"none":return;case"outerHTML":swapOuterHTML(n,r,o);return;case"afterbegin":swapAfterBegin(n,r,o);return;case"beforebegin":swapBeforeBegin(n,r,o);return;case"beforeend":swapBeforeEnd(n,r,o);return;case"afterend":swapAfterEnd(n,r,o);return;case"delete":swapDelete(n);return;default:var s=getExtensions(t);for(let i=0;i<s.length;i++){let a=s[i];try{let l=a.handleSwap(e,n,r,o);if(l){if(Array.isArray(l))for(let c=0;c<l.length;c++){let h=l[c];h.nodeType!==Node.TEXT_NODE&&h.nodeType!==Node.COMMENT_NODE&&o.tasks.push(makeAjaxLoadTask(h))}return}}catch(l){logError(l)}}e==="innerHTML"?swapInnerHTML(n,r,o):swapWithStyle(htmx.config.defaultSwapStyle,t,n,r,o)}}function findAndSwapOobElements(e,t,n){var r=findAll(e,"[hx-swap-oob], [data-hx-swap-oob]");return forEach(r,function(o){if(htmx.config.allowNestedOobSwaps||o.parentElement===null){let s=getAttributeValue(o,"hx-swap-oob");s!=null&&oobSwap(s,o,t,n)}else o.removeAttribute("hx-swap-oob"),o.removeAttribute("data-hx-swap-oob")}),r.length>0}function swap(e,t,n,r){r||(r={});let o=null,s=null,i=function(){maybeCall(r.beforeSwapCallback),e=resolveTarget(e);let c=r.contextElement?getRootNode(r.contextElement,!1):getDocument(),h=document.activeElement,u={};u={elt:h,start:h?h.selectionStart:null,end:h?h.selectionEnd:null};let f=makeSettleInfo(e);if(n.swapStyle==="textContent")e.textContent=t;else{let d=makeFragment(t);if(f.title=r.title||d.title,r.historyRequest&&(d=d.querySelector("[hx-history-elt],[data-hx-history-elt]")||d),r.selectOOB){let y=r.selectOOB.split(",");for(let m=0;m<y.length;m++){let b=y[m].split(":",2),v=b[0].trim();v.indexOf("#")===0&&(v=v.substring(1));let E=b[1]||"true",g=d.querySelector("#"+v);g&&oobSwap(E,g,f,c)}}if(findAndSwapOobElements(d,f,c),forEach(findAll(d,"template"),function(y){y.content&&findAndSwapOobElements(y.content,f,c)&&y.remove()}),r.select){let y=getDocument().createDocumentFragment();forEach(d.querySelectorAll(r.select),function(m){y.appendChild(m)}),d=y}handlePreservedElements(d),swapWithStyle(n.swapStyle,r.contextElement,e,d,f),restorePreservedElements()}if(u.elt&&!bodyContains(u.elt)&&getRawAttribute(u.elt,"id")){let d=document.getElementById(getRawAttribute(u.elt,"id")),y={preventScroll:n.focusScroll!==void 0?!n.focusScroll:!htmx.config.defaultFocusScroll};if(d){if(u.start&&d.setSelectionRange)try{d.setSelectionRange(u.start,u.end)}catch{}d.focus(y)}}e.classList.remove(htmx.config.swappingClass),forEach(f.elts,function(d){d.classList&&d.classList.add(htmx.config.settlingClass),triggerEvent(d,"htmx:afterSwap",r.eventInfo)}),maybeCall(r.afterSwapCallback),n.ignoreTitle||handleTitle(f.title);let x=function(){if(forEach(f.tasks,function(d){d.call()}),forEach(f.elts,function(d){d.classList&&d.classList.remove(htmx.config.settlingClass),triggerEvent(d,"htmx:afterSettle",r.eventInfo)}),r.anchor){let d=asElement(resolveTarget("#"+r.anchor));d&&d.scrollIntoView({block:"start",behavior:"auto"})}updateScrollState(f.elts,n),maybeCall(r.afterSettleCallback),maybeCall(o)};n.settleDelay>0?getWindow().setTimeout(x,n.settleDelay):x()},a=htmx.config.globalViewTransitions;n.hasOwnProperty("transition")&&(a=n.transition);let l=r.contextElement||getDocument();if(a&&triggerEvent(l,"htmx:beforeTransition",r.eventInfo)&&typeof Promise<"u"&&document.startViewTransition){let c=new Promise(function(u,f){o=u,s=f}),h=i;i=function(){document.startViewTransition(function(){return h(),c})}}try{n?.swapDelay&&n.swapDelay>0?getWindow().setTimeout(i,n.swapDelay):i()}catch(c){throw triggerErrorEvent(l,"htmx:swapError",r.eventInfo),maybeCall(s),c}}function handleTriggerHeader(e,t,n){let r=e.getResponseHeader(t);if(r.indexOf("{")===0){let o=parseJSON(r);for(let s in o)if(o.hasOwnProperty(s)){let i=o[s];isRawObject(i)?n=i.target!==void 0?i.target:n:i={value:i},triggerEvent(n,s,i)}}else{let o=r.split(",");for(let s=0;s<o.length;s++)triggerEvent(n,o[s].trim(),[])}}let WHITESPACE=/\s/,WHITESPACE_OR_COMMA=/[\s,]/,SYMBOL_START=/[_$a-zA-Z]/,SYMBOL_CONT=/[_$a-zA-Z0-9]/,STRINGISH_START=['"',"'","/"],NOT_WHITESPACE=/[^\s]/,COMBINED_SELECTOR_START=/[{(]/,COMBINED_SELECTOR_END=/[})]/;function tokenizeString(e){let t=[],n=0;for(;n<e.length;){if(SYMBOL_START.exec(e.charAt(n))){for(var r=n;SYMBOL_CONT.exec(e.charAt(n+1));)n++;t.push(e.substring(r,n+1))}else if(STRINGISH_START.indexOf(e.charAt(n))!==-1){let o=e.charAt(n);var r=n;for(n++;n<e.length&&e.charAt(n)!==o;)e.charAt(n)==="\\"&&n++,n++;t.push(e.substring(r,n+1))}else{let o=e.charAt(n);t.push(o)}n++}return t}function isPossibleRelativeReference(e,t,n){return SYMBOL_START.exec(e.charAt(0))&&e!=="true"&&e!=="false"&&e!=="this"&&e!==n&&t!=="."}function maybeGenerateConditional(e,t,n){if(t[0]==="["){t.shift();let r=1,o=" return (function("+n+"){ return (",s=null;for(;t.length>0;){let i=t[0];if(i==="]"){if(r--,r===0){s===null&&(o=o+"true"),t.shift(),o+=")})";try{let a=maybeEval(e,function(){return Function(o)()},function(){return!0});return a.source=o,a}catch(a){return triggerErrorEvent(getDocument().body,"htmx:syntax:error",{error:a,source:o}),null}}}else i==="["&&r++;isPossibleRelativeReference(i,s,n)?o+="(("+n+"."+i+") ? ("+n+"."+i+") : (window."+i+"))":o=o+i,s=t.shift()}}}function consumeUntil(e,t){let n="";for(;e.length>0&&!t.test(e[0]);)n+=e.shift();return n}function consumeCSSSelector(e){let t;return e.length>0&&COMBINED_SELECTOR_START.test(e[0])?(e.shift(),t=consumeUntil(e,COMBINED_SELECTOR_END).trim(),e.shift()):t=consumeUntil(e,WHITESPACE_OR_COMMA),t}let INPUT_SELECTOR="input, textarea, select";function parseAndCacheTrigger(e,t,n){let r=[],o=tokenizeString(t);do{consumeUntil(o,NOT_WHITESPACE);let a=o.length,l=consumeUntil(o,/[,\[\s]/);if(l!=="")if(l==="every"){let c={trigger:"every"};consumeUntil(o,NOT_WHITESPACE),c.pollInterval=parseInterval(consumeUntil(o,/[,\[\s]/)),consumeUntil(o,NOT_WHITESPACE);var s=maybeGenerateConditional(e,o,"event");s&&(c.eventFilter=s),r.push(c)}else{let c={trigger:l};var s=maybeGenerateConditional(e,o,"event");for(s&&(c.eventFilter=s),consumeUntil(o,NOT_WHITESPACE);o.length>0&&o[0]!==",";){let u=o.shift();if(u==="changed")c.changed=!0;else if(u==="once")c.once=!0;else if(u==="consume")c.consume=!0;else if(u==="delay"&&o[0]===":")o.shift(),c.delay=parseInterval(consumeUntil(o,WHITESPACE_OR_COMMA));else if(u==="from"&&o[0]===":"){if(o.shift(),COMBINED_SELECTOR_START.test(o[0]))var i=consumeCSSSelector(o);else{var i=consumeUntil(o,WHITESPACE_OR_COMMA);if(i==="closest"||i==="find"||i==="next"||i==="previous"){o.shift();let x=consumeCSSSelector(o);x.length>0&&(i+=" "+x)}}c.from=i}else u==="target"&&o[0]===":"?(o.shift(),c.target=consumeCSSSelector(o)):u==="throttle"&&o[0]===":"?(o.shift(),c.throttle=parseInterval(consumeUntil(o,WHITESPACE_OR_COMMA))):u==="queue"&&o[0]===":"?(o.shift(),c.queue=consumeUntil(o,WHITESPACE_OR_COMMA)):u==="root"&&o[0]===":"?(o.shift(),c[u]=consumeCSSSelector(o)):u==="threshold"&&o[0]===":"?(o.shift(),c[u]=consumeUntil(o,WHITESPACE_OR_COMMA)):triggerErrorEvent(e,"htmx:syntax:error",{token:o.shift()});consumeUntil(o,NOT_WHITESPACE)}r.push(c)}o.length===a&&triggerErrorEvent(e,"htmx:syntax:error",{token:o.shift()}),consumeUntil(o,NOT_WHITESPACE)}while(o[0]===","&&o.shift());return n&&(n[t]=r),r}function getTriggerSpecs(e){let t=getAttributeValue(e,"hx-trigger"),n=[];if(t){let r=htmx.config.triggerSpecsCache;n=r&&r[t]||parseAndCacheTrigger(e,t,r)}return n.length>0?n:matches(e,"form")?[{trigger:"submit"}]:matches(e,'input[type="button"], input[type="submit"]')?[{trigger:"click"}]:matches(e,INPUT_SELECTOR)?[{trigger:"change"}]:[{trigger:"click"}]}function cancelPolling(e){getInternalData(e).cancelled=!0}function processPolling(e,t,n){let r=getInternalData(e);r.timeout=getWindow().setTimeout(function(){bodyContains(e)&&r.cancelled!==!0&&(maybeFilterEvent(n,e,makeEvent("hx:poll:trigger",{triggerSpec:n,target:e}))||t(e),processPolling(e,t,n))},n.pollInterval)}function isLocalLink(e){return location.hostname===e.hostname&&getRawAttribute(e,"href")&&getRawAttribute(e,"href").indexOf("#")!==0}function eltIsDisabled(e){return closest(e,htmx.config.disableSelector)}function boostElement(e,t,n){if(e instanceof HTMLAnchorElement&&isLocalLink(e)&&(e.target===""||e.target==="_self")||e.tagName==="FORM"&&String(getRawAttribute(e,"method")).toLowerCase()!=="dialog"){t.boosted=!0;let r,o;if(e.tagName==="A")r="get",o=getRawAttribute(e,"href");else{let s=getRawAttribute(e,"method");r=s?s.toLowerCase():"get",o=getRawAttribute(e,"action"),(o==null||o==="")&&(o=location.href),r==="get"&&o.includes("?")&&(o=o.replace(/\?[^#]+/,""))}n.forEach(function(s){addEventListener(e,function(i,a){let l=asElement(i);if(eltIsDisabled(l)){cleanUpElement(l);return}issueAjaxRequest(r,o,l,a)},t,s,!0)})}}function shouldCancel(e,t){if(e.type==="submit"&&t.tagName==="FORM")return!0;if(e.type==="click"){let n=t.closest('input[type="submit"], button');if(n&&n.form&&n.type==="submit")return!0;let r=t.closest("a"),o=/^#.+/;if(r&&r.href&&!o.test(r.getAttribute("href")))return!0}return!1}function ignoreBoostedAnchorCtrlClick(e,t){return getInternalData(e).boosted&&e instanceof HTMLAnchorElement&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function maybeFilterEvent(e,t,n){let r=e.eventFilter;if(r)try{return r.call(t,n)!==!0}catch(o){let s=r.source;return triggerErrorEvent(getDocument().body,"htmx:eventFilter:error",{error:o,source:s}),!0}return!1}function addEventListener(e,t,n,r,o){let s=getInternalData(e),i;r.from?i=querySelectorAllExt(e,r.from):i=[e],r.changed&&("lastValue"in s||(s.lastValue=new WeakMap),i.forEach(function(a){s.lastValue.has(r)||s.lastValue.set(r,new WeakMap),s.lastValue.get(r).set(a,a.value)})),forEach(i,function(a){let l=function(c){if(!bodyContains(e)){a.removeEventListener(r.trigger,l);return}if(ignoreBoostedAnchorCtrlClick(e,c)||((o||shouldCancel(c,a))&&c.preventDefault(),maybeFilterEvent(r,e,c)))return;let h=getInternalData(c);if(h.triggerSpec=r,h.handledFor==null&&(h.handledFor=[]),h.handledFor.indexOf(e)<0){if(h.handledFor.push(e),r.consume&&c.stopPropagation(),r.target&&c.target&&!matches(asElement(c.target),r.target))return;if(r.once){if(s.triggeredOnce)return;s.triggeredOnce=!0}if(r.changed){let u=c.target,f=u.value,x=s.lastValue.get(r);if(x.has(u)&&x.get(u)===f)return;x.set(u,f)}if(s.delayed&&clearTimeout(s.delayed),s.throttle)return;r.throttle>0?s.throttle||(triggerEvent(e,"htmx:trigger"),t(e,c),s.throttle=getWindow().setTimeout(function(){s.throttle=null},r.throttle)):r.delay>0?s.delayed=getWindow().setTimeout(function(){triggerEvent(e,"htmx:trigger"),t(e,c)},r.delay):(triggerEvent(e,"htmx:trigger"),t(e,c))}};n.listenerInfos==null&&(n.listenerInfos=[]),n.listenerInfos.push({trigger:r.trigger,listener:l,on:a}),a.addEventListener(r.trigger,l)})}let windowIsScrolling=!1,scrollHandler=null;function initScrollHandler(){scrollHandler||(scrollHandler=function(){windowIsScrolling=!0},window.addEventListener("scroll",scrollHandler),window.addEventListener("resize",scrollHandler),setInterval(function(){windowIsScrolling&&(windowIsScrolling=!1,forEach(getDocument().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"),function(e){maybeReveal(e)}))},200))}function maybeReveal(e){!hasAttribute(e,"data-hx-revealed")&&isScrolledIntoView(e)&&(e.setAttribute("data-hx-revealed","true"),getInternalData(e).initHash?triggerEvent(e,"revealed"):e.addEventListener("htmx:afterProcessNode",function(){triggerEvent(e,"revealed")},{once:!0}))}function loadImmediately(e,t,n,r){let o=function(){n.loaded||(n.loaded=!0,triggerEvent(e,"htmx:trigger"),t(e))};r>0?getWindow().setTimeout(o,r):o()}function processVerbs(e,t,n){let r=!1;return forEach(VERBS,function(o){if(hasAttribute(e,"hx-"+o)){let s=getAttributeValue(e,"hx-"+o);r=!0,t.path=s,t.verb=o,n.forEach(function(i){addTriggerHandler(e,i,t,function(a,l){let c=asElement(a);if(eltIsDisabled(c)){cleanUpElement(c);return}issueAjaxRequest(o,s,c,l)})})}}),r}function addTriggerHandler(e,t,n,r){if(t.trigger==="revealed")initScrollHandler(),addEventListener(e,r,n,t),maybeReveal(asElement(e));else if(t.trigger==="intersect"){let o={};t.root&&(o.root=querySelectorExt(e,t.root)),t.threshold&&(o.threshold=parseFloat(t.threshold)),new IntersectionObserver(function(i){for(let a=0;a<i.length;a++)if(i[a].isIntersecting){triggerEvent(e,"intersect");break}},o).observe(asElement(e)),addEventListener(asElement(e),r,n,t)}else!n.firstInitCompleted&&t.trigger==="load"?maybeFilterEvent(t,e,makeEvent("load",{elt:e}))||loadImmediately(asElement(e),r,n,t.delay):t.pollInterval>0?(n.polling=!0,processPolling(asElement(e),r,t)):addEventListener(e,r,n,t)}function shouldProcessHxOn(e){let t=asElement(e);if(!t)return!1;let n=t.attributes;for(let r=0;r<n.length;r++){let o=n[r].name;if(startsWith(o,"hx-on:")||startsWith(o,"data-hx-on:")||startsWith(o,"hx-on-")||startsWith(o,"data-hx-on-"))return!0}return!1}let HX_ON_QUERY=new XPathEvaluator().createExpression('.//*[@*[ starts-with(name(), "hx-on:") or starts-with(name(), "data-hx-on:") or starts-with(name(), "hx-on-") or starts-with(name(), "data-hx-on-") ]]');function processHXOnRoot(e,t){shouldProcessHxOn(e)&&t.push(asElement(e));let n=HX_ON_QUERY.evaluate(e),r=null;for(;r=n.iterateNext();)t.push(asElement(r))}function findHxOnWildcardElements(e){let t=[];if(e instanceof DocumentFragment)for(let n of e.childNodes)processHXOnRoot(n,t);else processHXOnRoot(e,t);return t}function findElementsToProcess(e){if(e.querySelectorAll){let n=", [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]",r=[];for(let s in extensions){let i=extensions[s];if(i.getSelectors){var t=i.getSelectors();t&&r.push(t)}}return e.querySelectorAll(VERB_SELECTOR+n+", form, [type='submit'], [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]"+r.flat().map(s=>", "+s).join(""))}else return[]}function maybeSetLastButtonClicked(e){let t=getTargetButton(e.target),n=getRelatedFormData(e);n&&(n.lastButtonClicked=t)}function maybeUnsetLastButtonClicked(e){let t=getRelatedFormData(e);t&&(t.lastButtonClicked=null)}function getTargetButton(e){return closest(asElement(e),"button, input[type='submit']")}function getRelatedForm(e){return e.form||closest(e,"form")}function getRelatedFormData(e){let t=getTargetButton(e.target);if(!t)return;let n=getRelatedForm(t);if(n)return getInternalData(n)}function initButtonTracking(e){e.addEventListener("click",maybeSetLastButtonClicked),e.addEventListener("focusin",maybeSetLastButtonClicked),e.addEventListener("focusout",maybeUnsetLastButtonClicked)}function addHxOnEventHandler(e,t,n){let r=getInternalData(e);Array.isArray(r.onHandlers)||(r.onHandlers=[]);let o,s=function(i){maybeEval(e,function(){eltIsDisabled(e)||(o||(o=new Function("event",n)),o.call(e,i))})};e.addEventListener(t,s),r.onHandlers.push({event:t,listener:s})}function processHxOnWildcard(e){deInitOnHandlers(e);for(let t=0;t<e.attributes.length;t++){let n=e.attributes[t].name,r=e.attributes[t].value;if(startsWith(n,"hx-on")||startsWith(n,"data-hx-on")){let o=n.indexOf("-on")+3,s=n.slice(o,o+1);if(s==="-"||s===":"){let i=n.slice(o+1);startsWith(i,":")?i="htmx"+i:startsWith(i,"-")?i="htmx:"+i.slice(1):startsWith(i,"htmx-")&&(i="htmx:"+i.slice(5)),addHxOnEventHandler(e,i,r)}}}}function initNode(e){triggerEvent(e,"htmx:beforeProcessNode");let t=getInternalData(e),n=getTriggerSpecs(e);processVerbs(e,t,n)||(getClosestAttributeValue(e,"hx-boost")==="true"?boostElement(e,t,n):hasAttribute(e,"hx-trigger")&&n.forEach(function(o){addTriggerHandler(e,o,t,function(){})})),(e.tagName==="FORM"||getRawAttribute(e,"type")==="submit"&&hasAttribute(e,"form"))&&initButtonTracking(e),t.firstInitCompleted=!0,triggerEvent(e,"htmx:afterProcessNode")}function maybeDeInitAndHash(e){if(!(e instanceof Element))return!1;let t=getInternalData(e),n=attributeHash(e);return t.initHash!==n?(deInitNode(e),t.initHash=n,!0):!1}function processNode(e){if(e=resolveTarget(e),eltIsDisabled(e)){cleanUpElement(e);return}let t=[];maybeDeInitAndHash(e)&&t.push(e),forEach(findElementsToProcess(e),function(n){if(eltIsDisabled(n)){cleanUpElement(n);return}maybeDeInitAndHash(n)&&t.push(n)}),forEach(findHxOnWildcardElements(e),processHxOnWildcard),forEach(t,initNode)}function kebabEventName(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}function makeEvent(e,t){return new CustomEvent(e,{bubbles:!0,cancelable:!0,composed:!0,detail:t})}function triggerErrorEvent(e,t,n){triggerEvent(e,t,mergeObjects({error:t},n))}function ignoreEventForLogging(e){return e==="htmx:afterProcessNode"}function withExtensions(e,t,n){forEach(getExtensions(e,[],n),function(r){try{t(r)}catch(o){logError(o)}})}function logError(e){console.error(e)}function triggerEvent(e,t,n){e=resolveTarget(e),n==null&&(n={}),n.elt=e;let r=makeEvent(t,n);htmx.logger&&!ignoreEventForLogging(t)&&htmx.logger(e,t,n),n.error&&(logError(n.error),triggerEvent(e,"htmx:error",{errorInfo:n}));let o=e.dispatchEvent(r),s=kebabEventName(t);if(o&&s!==t){let i=makeEvent(s,r.detail);o=o&&e.dispatchEvent(i)}return withExtensions(asElement(e),function(i){o=o&&i.onEvent(t,r)!==!1&&!r.defaultPrevented}),o}let currentPathForHistory;function setCurrentPathForHistory(e){currentPathForHistory=e,canAccessLocalStorage()&&sessionStorage.setItem("htmx-current-path-for-history",e)}setCurrentPathForHistory(location.pathname+location.search);function getHistoryElement(){return getDocument().querySelector("[hx-history-elt],[data-hx-history-elt]")||getDocument().body}function saveToHistoryCache(e,t){if(!canAccessLocalStorage())return;let n=cleanInnerHtmlForHistory(t),r=getDocument().title,o=window.scrollY;if(htmx.config.historyCacheSize<=0){sessionStorage.removeItem("htmx-history-cache");return}e=normalizePath(e);let s=parseJSON(sessionStorage.getItem("htmx-history-cache"))||[];for(let a=0;a<s.length;a++)if(s[a].url===e){s.splice(a,1);break}let i={url:e,content:n,title:r,scroll:o};for(triggerEvent(getDocument().body,"htmx:historyItemCreated",{item:i,cache:s}),s.push(i);s.length>htmx.config.historyCacheSize;)s.shift();for(;s.length>0;)try{sessionStorage.setItem("htmx-history-cache",JSON.stringify(s));break}catch(a){triggerErrorEvent(getDocument().body,"htmx:historyCacheError",{cause:a,cache:s}),s.shift()}}function getCachedHistory(e){if(!canAccessLocalStorage())return null;e=normalizePath(e);let t=parseJSON(sessionStorage.getItem("htmx-history-cache"))||[];for(let n=0;n<t.length;n++)if(t[n].url===e)return t[n];return null}function cleanInnerHtmlForHistory(e){let t=htmx.config.requestClass,n=e.cloneNode(!0);return forEach(findAll(n,"."+t),function(r){removeClassFromElement(r,t)}),forEach(findAll(n,"[data-disabled-by-htmx]"),function(r){r.removeAttribute("disabled")}),n.innerHTML}function saveCurrentPageToHistory(){let e=getHistoryElement(),t=currentPathForHistory;canAccessLocalStorage()&&(t=sessionStorage.getItem("htmx-current-path-for-history")),t=t||location.pathname+location.search,getDocument().querySelector('[hx-history="false" i],[data-hx-history="false" i]')||(triggerEvent(getDocument().body,"htmx:beforeHistorySave",{path:t,historyElt:e}),saveToHistoryCache(t,e)),htmx.config.historyEnabled&&history.replaceState({htmx:!0},getDocument().title,location.href)}function pushUrlIntoHistory(e){htmx.config.getCacheBusterParam&&(e=e.replace(/org\.htmx\.cache-buster=[^&]*&?/,""),(endsWith(e,"&")||endsWith(e,"?"))&&(e=e.slice(0,-1))),htmx.config.historyEnabled&&history.pushState({htmx:!0},"",e),setCurrentPathForHistory(e)}function replaceUrlInHistory(e){htmx.config.historyEnabled&&history.replaceState({htmx:!0},"",e),setCurrentPathForHistory(e)}function settleImmediately(e){forEach(e,function(t){t.call(void 0)})}function loadHistoryFromServer(e){let t=new XMLHttpRequest,n={swapStyle:"innerHTML",swapDelay:0,settleDelay:0},r={path:e,xhr:t,historyElt:getHistoryElement(),swapSpec:n};t.open("GET",e,!0),htmx.config.historyRestoreAsHxRequest&&t.setRequestHeader("HX-Request","true"),t.setRequestHeader("HX-History-Restore-Request","true"),t.setRequestHeader("HX-Current-URL",location.href),t.onload=function(){this.status>=200&&this.status<400?(r.response=this.response,triggerEvent(getDocument().body,"htmx:historyCacheMissLoad",r),swap(r.historyElt,r.response,n,{contextElement:r.historyElt,historyRequest:!0}),setCurrentPathForHistory(r.path),triggerEvent(getDocument().body,"htmx:historyRestore",{path:e,cacheMiss:!0,serverResponse:r.response})):triggerErrorEvent(getDocument().body,"htmx:historyCacheMissLoadError",r)},triggerEvent(getDocument().body,"htmx:historyCacheMiss",r)&&t.send()}function restoreHistory(e){saveCurrentPageToHistory(),e=e||location.pathname+location.search;let t=getCachedHistory(e);if(t){let n={swapStyle:"innerHTML",swapDelay:0,settleDelay:0,scroll:t.scroll},r={path:e,item:t,historyElt:getHistoryElement(),swapSpec:n};triggerEvent(getDocument().body,"htmx:historyCacheHit",r)&&(swap(r.historyElt,t.content,n,{contextElement:r.historyElt,title:t.title}),setCurrentPathForHistory(r.path),triggerEvent(getDocument().body,"htmx:historyRestore",r))}else htmx.config.refreshOnHistoryMiss?htmx.location.reload(!0):loadHistoryFromServer(e)}function addRequestIndicatorClasses(e){let t=findAttributeTargets(e,"hx-indicator");return t==null&&(t=[e]),forEach(t,function(n){let r=getInternalData(n);r.requestCount=(r.requestCount||0)+1,n.classList.add.call(n.classList,htmx.config.requestClass)}),t}function disableElements(e){let t=findAttributeTargets(e,"hx-disabled-elt");return t==null&&(t=[]),forEach(t,function(n){let r=getInternalData(n);r.requestCount=(r.requestCount||0)+1,n.setAttribute("disabled",""),n.setAttribute("data-disabled-by-htmx","")}),t}function removeRequestIndicators(e,t){forEach(e.concat(t),function(n){let r=getInternalData(n);r.requestCount=(r.requestCount||1)-1}),forEach(e,function(n){getInternalData(n).requestCount===0&&n.classList.remove.call(n.classList,htmx.config.requestClass)}),forEach(t,function(n){getInternalData(n).requestCount===0&&(n.removeAttribute("disabled"),n.removeAttribute("data-disabled-by-htmx"))})}function haveSeenNode(e,t){for(let n=0;n<e.length;n++)if(e[n].isSameNode(t))return!0;return!1}function shouldInclude(e){let t=e;return t.name===""||t.name==null||t.disabled||closest(t,"fieldset[disabled]")||t.type==="button"||t.type==="submit"||t.tagName==="image"||t.tagName==="reset"||t.tagName==="file"?!1:t.type==="checkbox"||t.type==="radio"?t.checked:!0}function addValueToFormData(e,t,n){e!=null&&t!=null&&(Array.isArray(t)?t.forEach(function(r){n.append(e,r)}):n.append(e,t))}function removeValueFromFormData(e,t,n){if(e!=null&&t!=null){let r=n.getAll(e);Array.isArray(t)?r=r.filter(o=>t.indexOf(o)<0):r=r.filter(o=>o!==t),n.delete(e),forEach(r,o=>n.append(e,o))}}function getValueFromInput(e){return e instanceof HTMLSelectElement&&e.multiple?toArray(e.querySelectorAll("option:checked")).map(function(t){return t.value}):e instanceof HTMLInputElement&&e.files?toArray(e.files):e.value}function processInputValue(e,t,n,r,o){if(!(r==null||haveSeenNode(e,r))){if(e.push(r),shouldInclude(r)){let s=getRawAttribute(r,"name");addValueToFormData(s,getValueFromInput(r),t),o&&validateElement(r,n)}r instanceof HTMLFormElement&&(forEach(r.elements,function(s){e.indexOf(s)>=0?removeValueFromFormData(s.name,getValueFromInput(s),t):e.push(s),o&&validateElement(s,n)}),new FormData(r).forEach(function(s,i){s instanceof File&&s.name===""||addValueToFormData(i,s,t)}))}}function validateElement(e,t){let n=e;n.willValidate&&(triggerEvent(n,"htmx:validation:validate"),n.checkValidity()||(triggerEvent(n,"htmx:validation:failed",{message:n.validationMessage,validity:n.validity})&&!t.length&&htmx.config.reportValidityOfForms&&n.reportValidity(),t.push({elt:n,message:n.validationMessage,validity:n.validity})))}function overrideFormData(e,t){for(let n of t.keys())e.delete(n);return t.forEach(function(n,r){e.append(r,n)}),e}function getInputValues(e,t){let n=[],r=new FormData,o=new FormData,s=[],i=getInternalData(e);i.lastButtonClicked&&!bodyContains(i.lastButtonClicked)&&(i.lastButtonClicked=null);let a=e instanceof HTMLFormElement&&e.noValidate!==!0||getAttributeValue(e,"hx-validate")==="true";if(i.lastButtonClicked&&(a=a&&i.lastButtonClicked.formNoValidate!==!0),t!=="get"&&processInputValue(n,o,s,getRelatedForm(e),a),processInputValue(n,r,s,e,a),i.lastButtonClicked||e.tagName==="BUTTON"||e.tagName==="INPUT"&&getRawAttribute(e,"type")==="submit"){let c=i.lastButtonClicked||e,h=getRawAttribute(c,"name");addValueToFormData(h,c.value,o)}let l=findAttributeTargets(e,"hx-include");return forEach(l,function(c){processInputValue(n,r,s,asElement(c),a),matches(c,"form")||forEach(asParentNode(c).querySelectorAll(INPUT_SELECTOR),function(h){processInputValue(n,r,s,h,a)})}),overrideFormData(r,o),{errors:s,formData:r,values:formDataProxy(r)}}function appendParam(e,t,n){e!==""&&(e+="&"),String(n)==="[object Object]"&&(n=JSON.stringify(n));let r=encodeURIComponent(n);return e+=encodeURIComponent(t)+"="+r,e}function urlEncode(e){e=formDataFromObject(e);let t="";return e.forEach(function(n,r){t=appendParam(t,r,n)}),t}function getHeaders(e,t,n){let r={"HX-Request":"true","HX-Trigger":getRawAttribute(e,"id"),"HX-Trigger-Name":getRawAttribute(e,"name"),"HX-Target":getAttributeValue(t,"id"),"HX-Current-URL":location.href};return getValuesForElement(e,"hx-headers",!1,r),n!==void 0&&(r["HX-Prompt"]=n),getInternalData(e).boosted&&(r["HX-Boosted"]="true"),r}function filterValues(e,t){let n=getClosestAttributeValue(t,"hx-params");if(n){if(n==="none")return new FormData;if(n==="*")return e;if(n.indexOf("not ")===0)return forEach(n.slice(4).split(","),function(r){r=r.trim(),e.delete(r)}),e;{let r=new FormData;return forEach(n.split(","),function(o){o=o.trim(),e.has(o)&&e.getAll(o).forEach(function(s){r.append(o,s)})}),r}}else return e}function isAnchorLink(e){return!!getRawAttribute(e,"href")&&getRawAttribute(e,"href").indexOf("#")>=0}function getSwapSpecification(e,t){let n=t||getClosestAttributeValue(e,"hx-swap"),r={swapStyle:getInternalData(e).boosted?"innerHTML":htmx.config.defaultSwapStyle,swapDelay:htmx.config.defaultSwapDelay,settleDelay:htmx.config.defaultSettleDelay};if(htmx.config.scrollIntoViewOnBoost&&getInternalData(e).boosted&&!isAnchorLink(e)&&(r.show="top"),n){let i=splitOnWhitespace(n);if(i.length>0)for(let a=0;a<i.length;a++){let l=i[a];if(l.indexOf("swap:")===0)r.swapDelay=parseInterval(l.slice(5));else if(l.indexOf("settle:")===0)r.settleDelay=parseInterval(l.slice(7));else if(l.indexOf("transition:")===0)r.transition=l.slice(11)==="true";else if(l.indexOf("ignoreTitle:")===0)r.ignoreTitle=l.slice(12)==="true";else if(l.indexOf("scroll:")===0){var o=l.slice(7).split(":");let h=o.pop();var s=o.length>0?o.join(":"):null;r.scroll=h,r.scrollTarget=s}else if(l.indexOf("show:")===0){var o=l.slice(5).split(":");let u=o.pop();var s=o.length>0?o.join(":"):null;r.show=u,r.showTarget=s}else if(l.indexOf("focus-scroll:")===0){let c=l.slice(13);r.focusScroll=c=="true"}else a==0?r.swapStyle=l:logError("Unknown modifier in hx-swap: "+l)}}return r}function usesFormData(e){return getClosestAttributeValue(e,"hx-encoding")==="multipart/form-data"||matches(e,"form")&&getRawAttribute(e,"enctype")==="multipart/form-data"}function encodeParamsForBody(e,t,n){let r=null;return withExtensions(t,function(o){r==null&&(r=o.encodeParameters(e,n,t))}),r??(usesFormData(t)?overrideFormData(new FormData,formDataFromObject(n)):urlEncode(n))}function makeSettleInfo(e){return{tasks:[],elts:[e]}}function updateScrollState(e,t){let n=e[0],r=e[e.length-1];if(t.scroll){var o=null;t.scrollTarget&&(o=asElement(querySelectorExt(n,t.scrollTarget))),t.scroll==="top"&&(n||o)&&(o=o||n,o.scrollTop=0),t.scroll==="bottom"&&(r||o)&&(o=o||r,o.scrollTop=o.scrollHeight),typeof t.scroll=="number"&&getWindow().setTimeout(function(){window.scrollTo(0,t.scroll)},0)}if(t.show){var o=null;if(t.showTarget){let i=t.showTarget;t.showTarget==="window"&&(i="body"),o=asElement(querySelectorExt(n,i))}t.show==="top"&&(n||o)&&(o=o||n,o.scrollIntoView({block:"start",behavior:htmx.config.scrollBehavior})),t.show==="bottom"&&(r||o)&&(o=o||r,o.scrollIntoView({block:"end",behavior:htmx.config.scrollBehavior}))}}function getValuesForElement(e,t,n,r,o){if(r==null&&(r={}),e==null)return r;let s=getAttributeValue(e,t);if(s){let i=s.trim(),a=n;if(i==="unset")return null;i.indexOf("javascript:")===0?(i=i.slice(11),a=!0):i.indexOf("js:")===0&&(i=i.slice(3),a=!0),i.indexOf("{")!==0&&(i="{"+i+"}");let l;a?l=maybeEval(e,function(){return o?Function("event","return ("+i+")").call(e,o):Function("return ("+i+")").call(e)},{}):l=parseJSON(i);for(let c in l)l.hasOwnProperty(c)&&r[c]==null&&(r[c]=l[c])}return getValuesForElement(asElement(parentElt(e)),t,n,r,o)}function maybeEval(e,t,n){return htmx.config.allowEval?t():(triggerErrorEvent(e,"htmx:evalDisallowedError"),n)}function getHXVarsForElement(e,t,n){return getValuesForElement(e,"hx-vars",!0,n,t)}function getHXValsForElement(e,t,n){return getValuesForElement(e,"hx-vals",!1,n,t)}function getExpressionVars(e,t){return mergeObjects(getHXVarsForElement(e,t),getHXValsForElement(e,t))}function safelySetHeaderValue(e,t,n){if(n!==null)try{e.setRequestHeader(t,n)}catch{e.setRequestHeader(t,encodeURIComponent(n)),e.setRequestHeader(t+"-URI-AutoEncoded","true")}}function getPathFromResponse(e){if(e.responseURL)try{let t=new URL(e.responseURL);return t.pathname+t.search}catch{triggerErrorEvent(getDocument().body,"htmx:badResponseUrl",{url:e.responseURL})}}function hasHeader(e,t){return t.test(e.getAllResponseHeaders())}function ajaxHelper(e,t,n){if(e=e.toLowerCase(),n){if(n instanceof Element||typeof n=="string")return issueAjaxRequest(e,t,null,null,{targetOverride:resolveTarget(n)||DUMMY_ELT,returnPromise:!0});{let r=resolveTarget(n.target);return(n.target&&!r||n.source&&!r&&!resolveTarget(n.source))&&(r=DUMMY_ELT),issueAjaxRequest(e,t,resolveTarget(n.source),n.event,{handler:n.handler,headers:n.headers,values:n.values,targetOverride:r,swapOverride:n.swap,select:n.select,returnPromise:!0,push:n.push,replace:n.replace,selectOOB:n.selectOOB})}}else return issueAjaxRequest(e,t,null,null,{returnPromise:!0})}function hierarchyForElt(e){let t=[];for(;e;)t.push(e),e=e.parentElement;return t}function verifyPath(e,t,n){let r=new URL(t,location.protocol!=="about:"?location.href:window.origin),s=(location.protocol!=="about:"?location.origin:window.origin)===r.origin;return htmx.config.selfRequestsOnly&&!s?!1:triggerEvent(e,"htmx:validateUrl",mergeObjects({url:r,sameHost:s},n))}function formDataFromObject(e){if(e instanceof FormData)return e;let t=new FormData;for(let n in e)e.hasOwnProperty(n)&&(e[n]&&typeof e[n].forEach=="function"?e[n].forEach(function(r){t.append(n,r)}):typeof e[n]=="object"&&!(e[n]instanceof Blob)?t.append(n,JSON.stringify(e[n])):t.append(n,e[n]));return t}function formDataArrayProxy(e,t,n){return new Proxy(n,{get:function(r,o){return typeof o=="number"?r[o]:o==="length"?r.length:o==="push"?function(s){r.push(s),e.append(t,s)}:typeof r[o]=="function"?function(){r[o].apply(r,arguments),e.delete(t),r.forEach(function(s){e.append(t,s)})}:r[o]&&r[o].length===1?r[o][0]:r[o]},set:function(r,o,s){return r[o]=s,e.delete(t),r.forEach(function(i){e.append(t,i)}),!0}})}function formDataProxy(e){return new Proxy(e,{get:function(t,n){if(typeof n=="symbol"){let o=Reflect.get(t,n);return typeof o=="function"?function(){return o.apply(e,arguments)}:o}if(n==="toJSON")return()=>Object.fromEntries(e);if(n in t&&typeof t[n]=="function")return function(){return e[n].apply(e,arguments)};let r=e.getAll(n);if(r.length!==0)return r.length===1?r[0]:formDataArrayProxy(t,n,r)},set:function(t,n,r){return typeof n!="string"?!1:(t.delete(n),r&&typeof r.forEach=="function"?r.forEach(function(o){t.append(n,o)}):typeof r=="object"&&!(r instanceof Blob)?t.append(n,JSON.stringify(r)):t.append(n,r),!0)},deleteProperty:function(t,n){return typeof n=="string"&&t.delete(n),!0},ownKeys:function(t){return Reflect.ownKeys(Object.fromEntries(t))},getOwnPropertyDescriptor:function(t,n){return Reflect.getOwnPropertyDescriptor(Object.fromEntries(t),n)}})}function issueAjaxRequest(e,t,n,r,o,s){let i=null,a=null;if(o=o??{},o.returnPromise&&typeof Promise<"u")var l=new Promise(function(p,w){i=p,a=w});n==null&&(n=getDocument().body);let c=o.handler||handleAjaxResponse,h=o.select||null;if(!bodyContains(n))return maybeCall(i),l;let u=o.targetOverride||asElement(getTarget(n));if(u==null||u==DUMMY_ELT)return triggerErrorEvent(n,"htmx:targetError",{target:getClosestAttributeValue(n,"hx-target")}),maybeCall(a),l;let f=getInternalData(n),x=f.lastButtonClicked;if(x){let p=getRawAttribute(x,"formaction");p!=null&&(t=p);let w=getRawAttribute(x,"formmethod");if(w!=null)if(VERBS.includes(w.toLowerCase()))e=w;else return maybeCall(i),l}let d=getClosestAttributeValue(n,"hx-confirm");if(s===void 0&&triggerEvent(n,"htmx:confirm",{target:u,elt:n,path:t,verb:e,triggeringEvent:r,etc:o,issueRequest:function(A){return issueAjaxRequest(e,t,n,r,o,!!A)},question:d})===!1)return maybeCall(i),l;let y=n,m=getClosestAttributeValue(n,"hx-sync"),b=null,v=!1;if(m){let p=m.split(":"),w=p[0].trim();if(w==="this"?y=findThisElement(n,"hx-sync"):y=asElement(querySelectorExt(n,w)),m=(p[1]||"drop").trim(),f=getInternalData(y),m==="drop"&&f.xhr&&f.abortable!==!0)return maybeCall(i),l;if(m==="abort"){if(f.xhr)return maybeCall(i),l;v=!0}else m==="replace"?triggerEvent(y,"htmx:abort"):m.indexOf("queue")===0&&(b=(m.split(" ")[1]||"last").trim())}if(f.xhr)if(f.abortable)triggerEvent(y,"htmx:abort");else{if(b==null){if(r){let p=getInternalData(r);p&&p.triggerSpec&&p.triggerSpec.queue&&(b=p.triggerSpec.queue)}b==null&&(b="last")}return f.queuedRequests==null&&(f.queuedRequests=[]),b==="first"&&f.queuedRequests.length===0?f.queuedRequests.push(function(){issueAjaxRequest(e,t,n,r,o)}):b==="all"?f.queuedRequests.push(function(){issueAjaxRequest(e,t,n,r,o)}):b==="last"&&(f.queuedRequests=[],f.queuedRequests.push(function(){issueAjaxRequest(e,t,n,r,o)})),maybeCall(i),l}let E=new XMLHttpRequest;f.xhr=E,f.abortable=v;let g=function(){f.xhr=null,f.abortable=!1,f.queuedRequests!=null&&f.queuedRequests.length>0&&f.queuedRequests.shift()()},$=getClosestAttributeValue(n,"hx-prompt");if($){var F=prompt($);if(F===null||!triggerEvent(n,"htmx:prompt",{prompt:F,target:u}))return maybeCall(i),g(),l}if(d&&!s&&!confirm(d))return maybeCall(i),g(),l;let H=getHeaders(n,u,F);e!=="get"&&!usesFormData(n)&&(H["Content-Type"]="application/x-www-form-urlencoded"),o.headers&&(H=mergeObjects(H,o.headers));let J=getInputValues(n,e),O=J.errors,z=J.formData;o.values&&overrideFormData(z,formDataFromObject(o.values));let ie=formDataFromObject(getExpressionVars(n,r)),q=overrideFormData(z,ie),I=filterValues(q,n);htmx.config.getCacheBusterParam&&e==="get"&&I.set("org.htmx.cache-buster",getRawAttribute(u,"id")||"true"),(t==null||t==="")&&(t=location.href);let k=getValuesForElement(n,"hx-request"),Y=getInternalData(n).boosted,L=htmx.config.methodsThatUseUrlParams.indexOf(e)>=0,S={boosted:Y,useUrlParams:L,formData:I,parameters:formDataProxy(I),unfilteredFormData:q,unfilteredParameters:formDataProxy(q),headers:H,elt:n,target:u,verb:e,errors:O,withCredentials:o.credentials||k.credentials||htmx.config.withCredentials,timeout:o.timeout||k.timeout||htmx.config.timeout,path:t,triggeringEvent:r};if(!triggerEvent(n,"htmx:configRequest",S))return maybeCall(i),g(),l;if(t=S.path,e=S.verb,H=S.headers,I=formDataFromObject(S.parameters),O=S.errors,L=S.useUrlParams,O&&O.length>0)return triggerEvent(n,"htmx:validation:halted",S),maybeCall(i),g(),l;let K=t.split("#"),ae=K[0],_=K[1],T=t;if(L&&(T=ae,!I.keys().next().done&&(T.indexOf("?")<0?T+="?":T+="&",T+=urlEncode(I),_&&(T+="#"+_))),!verifyPath(n,T,S))return triggerErrorEvent(n,"htmx:invalidPath",S),maybeCall(a),g(),l;if(E.open(e.toUpperCase(),T,!0),E.overrideMimeType("text/html"),E.withCredentials=S.withCredentials,E.timeout=S.timeout,!k.noHeaders){for(let p in H)if(H.hasOwnProperty(p)){let w=H[p];safelySetHeaderValue(E,p,w)}}let C={xhr:E,target:u,requestConfig:S,etc:o,boosted:Y,select:h,pathInfo:{requestPath:t,finalRequestPath:T,responsePath:null,anchor:_}};if(E.onload=function(){try{let p=hierarchyForElt(n);if(C.pathInfo.responsePath=getPathFromResponse(E),c(n,C),C.keepIndicators!==!0&&removeRequestIndicators(P,N),triggerEvent(n,"htmx:afterRequest",C),triggerEvent(n,"htmx:afterOnLoad",C),!bodyContains(n)){let w=null;for(;p.length>0&&w==null;){let A=p.shift();bodyContains(A)&&(w=A)}w&&(triggerEvent(w,"htmx:afterRequest",C),triggerEvent(w,"htmx:afterOnLoad",C))}maybeCall(i)}catch(p){throw triggerErrorEvent(n,"htmx:onLoadError",mergeObjects({error:p},C)),p}finally{g()}},E.onerror=function(){removeRequestIndicators(P,N),triggerErrorEvent(n,"htmx:afterRequest",C),triggerErrorEvent(n,"htmx:sendError",C),maybeCall(a),g()},E.onabort=function(){removeRequestIndicators(P,N),triggerErrorEvent(n,"htmx:afterRequest",C),triggerErrorEvent(n,"htmx:sendAbort",C),maybeCall(a),g()},E.ontimeout=function(){removeRequestIndicators(P,N),triggerErrorEvent(n,"htmx:afterRequest",C),triggerErrorEvent(n,"htmx:timeout",C),maybeCall(a),g()},!triggerEvent(n,"htmx:beforeRequest",C))return maybeCall(i),g(),l;var P=addRequestIndicatorClasses(n),N=disableElements(n);forEach(["loadstart","loadend","progress","abort"],function(p){forEach([E,E.upload],function(w){w.addEventListener(p,function(A){triggerEvent(n,"htmx:xhr:"+p,{lengthComputable:A.lengthComputable,loaded:A.loaded,total:A.total})})})}),triggerEvent(n,"htmx:beforeSend",C);let le=L?null:encodeParamsForBody(E,n,I);return E.send(le),l}function determineHistoryUpdates(e,t){let n=t.xhr,r=null,o=null;if(hasHeader(n,/HX-Push:/i)?(r=n.getResponseHeader("HX-Push"),o="push"):hasHeader(n,/HX-Push-Url:/i)?(r=n.getResponseHeader("HX-Push-Url"),o="push"):hasHeader(n,/HX-Replace-Url:/i)&&(r=n.getResponseHeader("HX-Replace-Url"),o="replace"),r)return r==="false"?{}:{type:o,path:r};let s=t.pathInfo.finalRequestPath,i=t.pathInfo.responsePath,a=t.etc.push||getClosestAttributeValue(e,"hx-push-url"),l=t.etc.replace||getClosestAttributeValue(e,"hx-replace-url"),c=getInternalData(e).boosted,h=null,u=null;return a?(h="push",u=a):l?(h="replace",u=l):c&&(h="push",u=i||s),u?u==="false"?{}:(u==="true"&&(u=i||s),t.pathInfo.anchor&&u.indexOf("#")===-1&&(u=u+"#"+t.pathInfo.anchor),{type:h,path:u}):{}}function codeMatches(e,t){var n=new RegExp(e.code);return n.test(t.toString(10))}function resolveResponseHandling(e){for(var t=0;t<htmx.config.responseHandling.length;t++){var n=htmx.config.responseHandling[t];if(codeMatches(n,e.status))return n}return{swap:!1}}function handleTitle(e){if(e){let t=find("title");t?t.textContent=e:window.document.title=e}}function resolveRetarget(e,t){if(t==="this")return e;let n=asElement(querySelectorExt(e,t));if(n==null)throw triggerErrorEvent(e,"htmx:targetError",{target:t}),new Error(`Invalid re-target ${t}`);return n}function handleAjaxResponse(e,t){let n=t.xhr,r=t.target,o=t.etc,s=t.select;if(!triggerEvent(e,"htmx:beforeOnLoad",t))return;if(hasHeader(n,/HX-Trigger:/i)&&handleTriggerHeader(n,"HX-Trigger",e),hasHeader(n,/HX-Location:/i)){let v=n.getResponseHeader("HX-Location");var i={};v.indexOf("{")===0&&(i=parseJSON(v),v=i.path,delete i.path),i.push=i.push||"true",ajaxHelper("get",v,i);return}let a=hasHeader(n,/HX-Refresh:/i)&&n.getResponseHeader("HX-Refresh")==="true";if(hasHeader(n,/HX-Redirect:/i)){t.keepIndicators=!0,htmx.location.href=n.getResponseHeader("HX-Redirect"),a&&htmx.location.reload();return}if(a){t.keepIndicators=!0,htmx.location.reload();return}let l=determineHistoryUpdates(e,t),c=resolveResponseHandling(n),h=c.swap,u=!!c.error,f=htmx.config.ignoreTitle||c.ignoreTitle,x=c.select;c.target&&(t.target=resolveRetarget(e,c.target));var d=o.swapOverride;d==null&&c.swapOverride&&(d=c.swapOverride),hasHeader(n,/HX-Retarget:/i)&&(t.target=resolveRetarget(e,n.getResponseHeader("HX-Retarget"))),hasHeader(n,/HX-Reswap:/i)&&(d=n.getResponseHeader("HX-Reswap"));var y=n.response,m=mergeObjects({shouldSwap:h,serverResponse:y,isError:u,ignoreTitle:f,selectOverride:x,swapOverride:d},t);if(!(c.event&&!triggerEvent(r,c.event,m))&&triggerEvent(r,"htmx:beforeSwap",m)){if(r=m.target,y=m.serverResponse,u=m.isError,f=m.ignoreTitle,x=m.selectOverride,d=m.swapOverride,t.target=r,t.failed=u,t.successful=!u,m.shouldSwap){n.status===286&&cancelPolling(e),withExtensions(e,function(g){y=g.transformResponse(y,n,e)}),l.type&&saveCurrentPageToHistory();var b=getSwapSpecification(e,d);b.hasOwnProperty("ignoreTitle")||(b.ignoreTitle=f),r.classList.add(htmx.config.swappingClass),s&&(x=s),hasHeader(n,/HX-Reselect:/i)&&(x=n.getResponseHeader("HX-Reselect"));let v=o.selectOOB||getClosestAttributeValue(e,"hx-select-oob"),E=getClosestAttributeValue(e,"hx-select");swap(r,y,b,{select:x==="unset"?null:x||E,selectOOB:v,eventInfo:t,anchor:t.pathInfo.anchor,contextElement:e,afterSwapCallback:function(){if(hasHeader(n,/HX-Trigger-After-Swap:/i)){let g=e;bodyContains(e)||(g=getDocument().body),handleTriggerHeader(n,"HX-Trigger-After-Swap",g)}},afterSettleCallback:function(){if(hasHeader(n,/HX-Trigger-After-Settle:/i)){let g=e;bodyContains(e)||(g=getDocument().body),handleTriggerHeader(n,"HX-Trigger-After-Settle",g)}},beforeSwapCallback:function(){l.type&&(triggerEvent(getDocument().body,"htmx:beforeHistoryUpdate",mergeObjects({history:l},t)),l.type==="push"?(pushUrlIntoHistory(l.path),triggerEvent(getDocument().body,"htmx:pushedIntoHistory",{path:l.path})):(replaceUrlInHistory(l.path),triggerEvent(getDocument().body,"htmx:replacedInHistory",{path:l.path})))}})}u&&triggerErrorEvent(e,"htmx:responseError",mergeObjects({error:"Response Status Error Code "+n.status+" from "+t.pathInfo.requestPath},t))}}let extensions={};function extensionBase(){return{init:function(e){return null},getSelectors:function(){return null},onEvent:function(e,t){return!0},transformResponse:function(e,t,n){return e},isInlineSwap:function(e){return!1},handleSwap:function(e,t,n,r){return!1},encodeParameters:function(e,t,n){return null}}}function defineExtension(e,t){t.init&&t.init(internalAPI),extensions[e]=mergeObjects(extensionBase(),t)}function removeExtension(e){delete extensions[e]}function getExtensions(e,t,n){if(t==null&&(t=[]),e==null)return t;n==null&&(n=[]);let r=getAttributeValue(e,"hx-ext");return r&&forEach(r.split(","),function(o){if(o=o.replace(/ /g,""),o.slice(0,7)=="ignore:"){n.push(o.slice(7));return}if(n.indexOf(o)<0){let s=extensions[o];s&&t.indexOf(s)<0&&t.push(s)}}),getExtensions(asElement(parentElt(e)),t,n)}var isReady=!1;getDocument().addEventListener("DOMContentLoaded",function(){isReady=!0});function ready(e){isReady||getDocument().readyState==="complete"?e():getDocument().addEventListener("DOMContentLoaded",e)}function insertIndicatorStyles(){if(htmx.config.includeIndicatorStyles!==!1){let e=htmx.config.inlineStyleNonce?` nonce="${htmx.config.inlineStyleNonce}"`:"",t=htmx.config.indicatorClass,n=htmx.config.requestClass;getDocument().head.insertAdjacentHTML("beforeend",`<style${e}>.${t}{opacity:0;visibility: hidden} .${n} .${t}, .${n}.${t}{opacity:1;visibility: visible;transition: opacity 200ms ease-in}</style>`)}}function getMetaConfig(){let e=getDocument().querySelector('meta[name="htmx-config"]');return e?parseJSON(e.content):null}function mergeMetaConfig(){let e=getMetaConfig();e&&(htmx.config=mergeObjects(htmx.config,e))}return ready(function(){mergeMetaConfig(),insertIndicatorStyles();let e=getDocument().body;processNode(e);let t=getDocument().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(r){let o=r.detail.elt||r.target,s=getInternalData(o);s&&s.xhr&&s.xhr.abort()});let n=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(r){r.state&&r.state.htmx?(restoreHistory(),forEach(t,function(o){triggerEvent(o,"htmx:restored",{document:getDocument(),triggerEvent})})):n&&n(r)},getWindow().setTimeout(function(){triggerEvent(e,"htmx:load",{}),e=null},0)}),htmx})(),R=ce;(function(){let e;R.defineExtension("json-enc",{init:function(t){e=t},onEvent:function(t,n){t==="htmx:configRequest"&&(n.detail.headers["Content-Type"]="application/json")},encodeParameters:function(t,n,r){t.overrideMimeType("text/json");let o={};n.forEach(function(i,a){Object.hasOwn(o,a)?(Array.isArray(o[a])||(o[a]=[o[a]]),o[a].push(i)):o[a]=i});let s=e.getExpressionVars(r);return Object.keys(o).forEach(function(i){o[i]=Object.hasOwn(s,i)?s[i]:o[i]}),JSON.stringify(o)}})})();var G="https://typeahead.waow.tech",Z="https://public.api.bsky.app",ue="/xrpc/app.bsky.actor.searchActorsTypeahead",fe="/xrpc/app.bsky.actor.getProfiles";var de="atcr_recent_handles",ee="atcr_recent_profile_cache";var B=class{constructor(t){this.input=t,this.container=t.closest(".sailor-typeahead")||t.parentElement,this.dropdown=null,this.selectedCard=null,this.actors=[],this.currentItems=[],this.mode="hidden",this.focusIndex=-1,this.debounceTimer=null,this.requestSeq=0,this.primaryUnhealthyUntil=0,this.lastPrefetchPrefix="",this.lastPrefetchAt=0,this.createDropdown(),this.bindEvents(),this.input.value.trim().length===0&&this.showRecent()}createDropdown(){this.dropdown=document.createElement("div"),this.dropdown.className="sailor-typeahead-dropdown",this.dropdown.setAttribute("role","listbox"),this.dropdown.style.display="none",this.input.insertAdjacentElement("afterend",this.dropdown)}bindEvents(){this.input.addEventListener("focus",()=>this.handleFocus()),this.input.addEventListener("input",()=>this.handleInput()),this.input.addEventListener("keydown",t=>this.handleKeydown(t)),document.addEventListener("click",t=>{!this.input.contains(t.target)&&!this.dropdown.contains(t.target)&&this.hide()}),document.addEventListener("keydown",t=>{t.key==="Escape"&&this.selectedCard&&this.clearSelection()})}handleFocus(){this.input.value.trim().length===0&&this.showRecent()}handleInput(){let t=this.input.value.trim();if(t.length===0){this.showRecent();return}if(t.length>=2&&t.length<4){this.hide(),this.schedulePrefetch(t);return}if(t.length>=4){this.scheduleSearch(t);return}this.hide()}schedulePrefetch(t){clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>this.runPrefetch(t),150)}scheduleSearch(t){clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>this.runSearch(t),150)}async runPrefetch(t){let n=Date.now();if(!(t===this.lastPrefetchPrefix&&n-this.lastPrefetchAt<1e4)&&!(n<this.primaryUnhealthyUntil)){this.lastPrefetchPrefix=t,this.lastPrefetchAt=n;try{await U(G,t,400)}catch{this.primaryUnhealthyUntil=Date.now()+6e4}}}async runSearch(t){let n=++this.requestSeq,r=null;if(Date.now()>=this.primaryUnhealthyUntil)try{r=await U(G,t,1500)}catch{this.primaryUnhealthyUntil=Date.now()+6e4}if(r===null)try{r=await U(Z,t,1500)}catch{r=[]}n===this.requestSeq&&(this.actors=r||[],this.focusIndex=-1,this.renderResults())}renderResults(){if(this.mode="results",this.dropdown.innerHTML="",this.currentItems=[],this.actors.length===0){this.hide();return}this.actors.forEach((t,n)=>{this.currentItems.push(t),this.dropdown.appendChild(this.buildActorRow(t,n))}),this.dropdown.style.display="block"}buildActorRow(t,n){let r=document.createElement("div");r.className="sailor-typeahead-item",r.setAttribute("role","option"),r.dataset.index=String(n),r.dataset.handle=t.handle;let o=document.createElement("div");if(o.className="sailor-typeahead-avatar",t.avatar){let l=document.createElement("img");l.src=t.avatar,l.alt="",l.loading="lazy",o.appendChild(l)}let s=document.createElement("div");s.className="sailor-typeahead-text";let i=t.displayName&&t.displayName!==t.handle;if(i){let l=document.createElement("div");l.className="sailor-typeahead-name",l.textContent=t.displayName,s.appendChild(l)}let a=document.createElement("div");return a.className=i?"sailor-typeahead-handle":"sailor-typeahead-name",a.textContent="@"+t.handle,s.appendChild(a),r.append(o,s),r.addEventListener("mousedown",l=>{l.preventDefault(),this.select(t)}),r}showRecent(){let t=me();if(t.length===0){this.hide();return}this.mode="recent",this.focusIndex=-1,this.renderRecent(t),this.enrichRecent(t)}renderRecent(t){let n=M();this.dropdown.innerHTML="",this.currentItems=[];let r=document.createElement("div");r.className="sailor-typeahead-header",r.textContent="Recent accounts",this.dropdown.appendChild(r),t.forEach((o,s)=>{let i=n[o]?.profile||{handle:o};this.currentItems.push(i),this.dropdown.appendChild(this.buildActorRow(i,s))}),this.dropdown.style.display="block"}async enrichRecent(t){let n=M(),r=Date.now(),o=t.filter(a=>{let l=n[a];return!l||r-l.ts>864e5});if(o.length===0)return;let s=await he(o);if(s.length===0)return;let i=M();s.forEach(a=>{i[a.handle]={ts:r,profile:{handle:a.handle,displayName:a.displayName,avatar:a.avatar}}}),Q(i),this.mode==="recent"&&this.renderRecent(t)}hide(){this.mode="hidden",this.focusIndex=-1,this.dropdown.style.display="none"}select(t){if(typeof t=="string"&&(t={handle:t}),this.input.value=t.handle,this.hide(),this.showSelectedCard(t),t.handle){let n=M();n[t.handle]={ts:Date.now(),profile:{handle:t.handle,displayName:t.displayName,avatar:t.avatar}},Q(n)}}showSelectedCard(t){this.clearSelectedCard();let n=document.createElement("div");n.className="sailor-typeahead-selected";let r=document.createElement("div");if(r.className="sailor-typeahead-avatar",t.avatar){let l=document.createElement("img");l.src=t.avatar,l.alt="",r.appendChild(l)}let o=document.createElement("div");o.className="sailor-typeahead-text";let s=t.displayName&&t.displayName!==t.handle;if(s){let l=document.createElement("div");l.className="sailor-typeahead-name",l.textContent=t.displayName,o.appendChild(l)}let i=document.createElement("div");i.className=s?"sailor-typeahead-handle":"sailor-typeahead-name",i.textContent="@"+t.handle,o.appendChild(i);let a=document.createElement("button");a.type="button",a.className="sailor-typeahead-clear",a.tabIndex=-1,a.setAttribute("aria-label","Change account"),a.innerHTML="&times;",a.addEventListener("click",()=>this.clearSelection()),n.append(r,o,a),this.input.style.display="none",this.input.insertAdjacentElement("beforebegin",n),this.selectedCard=n}clearSelectedCard(){this.selectedCard&&(this.selectedCard.remove(),this.selectedCard=null)}clearSelection(){this.clearSelectedCard(),this.input.style.display="",this.input.value="",this.input.focus(),this.showRecent()}handleKeydown(t){if(this.mode==="hidden")return;let n=this.dropdown.querySelectorAll(".sailor-typeahead-item");n.length!==0&&(t.key==="ArrowDown"?(t.preventDefault(),this.focusIndex=(this.focusIndex+1)%n.length,this.updateFocus(n)):t.key==="ArrowUp"?(t.preventDefault(),this.focusIndex=this.focusIndex<=0?n.length-1:this.focusIndex-1,this.updateFocus(n)):t.key==="Enter"?this.focusIndex>=0&&this.currentItems[this.focusIndex]&&(t.preventDefault(),this.select(this.currentItems[this.focusIndex])):t.key==="Escape"?this.hide():t.key==="Tab"&&this.focusIndex===-1&&n.length>0&&(t.preventDefault(),this.focusIndex=0,this.updateFocus(n)))}updateFocus(t){t.forEach((n,r)=>{n.classList.toggle("focused",r===this.focusIndex),r===this.focusIndex&&n.scrollIntoView({block:"nearest"})})}};async function U(e,t,n){let r=new URL(ue,e);r.searchParams.set("q",t),r.searchParams.set("limit",String(8));let o=new AbortController,s=setTimeout(()=>o.abort(),n);try{let i=await fetch(r,{signal:o.signal});if(!i.ok)throw new Error("HTTP "+i.status);let a=await i.json();return Array.isArray(a.actors)?a.actors:[]}finally{clearTimeout(s)}}async function he(e){if(e.length===0)return[];let t=new URL(fe,Z);e.forEach(o=>t.searchParams.append("actors",o));let n=new AbortController,r=setTimeout(()=>n.abort(),3e3);try{let o=await fetch(t,{signal:n.signal});if(!o.ok)return[];let s=await o.json();return Array.isArray(s.profiles)?s.profiles:[]}catch{return[]}finally{clearTimeout(r)}}function M(){try{return JSON.parse(localStorage.getItem(ee)||"{}")}catch{return{}}}function Q(e){try{localStorage.setItem(ee,JSON.stringify(e))}catch{}}function me(){try{let e=localStorage.getItem(de);return e?JSON.parse(e):[]}catch{return[]}}document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("handle");e&&new B(e)});function ne(){return localStorage.getItem("theme")||"system"}function ge(e){return e==="dark"||e==="light"?e:window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function W(){let e=ne(),n=ge(e)==="dark";document.documentElement.classList.toggle("dark",n),document.documentElement.setAttribute("data-theme",n?"dark":"light"),pe(e)}function re(e){localStorage.setItem("theme",e),W(),Ee()}function pe(e){let t={system:"sun-moon",light:"sun",dark:"moon"};document.querySelectorAll("[data-theme-icon] use").forEach(n=>{n.setAttribute("href",`/icons.svg#${t[e]||"sun-moon"}`)}),document.querySelectorAll(".theme-option").forEach(n=>{let r=n.dataset.value===e,o=n.querySelector(".theme-check");o&&(o.style.visibility=r?"visible":"hidden")})}function Ee(){document.querySelectorAll("[data-theme-toggle]").forEach(e=>{let t=e.closest("details");t&&t.removeAttribute("open")})}window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{ne()==="system"&&W()});function ye(){let e=document.querySelector(".nav-search-wrapper"),t=document.getElementById("nav-search-input");!e||!t||(e.classList.toggle("expanded"),e.classList.contains("expanded")&&t.focus())}function V(){let e=document.querySelector(".nav-search-wrapper");e&&e.classList.remove("expanded")}document.addEventListener("DOMContentLoaded",()=>{let e=document.querySelector(".nav-search-wrapper"),t=document.getElementById("nav-search-input");!e||!t||(document.addEventListener("keydown",n=>{if(n.key==="Escape"&&e.classList.contains("expanded")&&V(),n.key==="/"&&!e.classList.contains("expanded")){let r=n.target.tagName;if(r==="INPUT"||r==="TEXTAREA"||n.target.isContentEditable)return;n.preventDefault(),e.classList.add("expanded"),t.focus()}}),document.addEventListener("click",n=>{e.classList.contains("expanded")&&!e.contains(n.target)&&V()}))});function oe(e,t){!t&&typeof event<"u"&&(t=event.target.closest("button")),navigator.clipboard.writeText(e).then(()=>{if(!t)return;let n=t.innerHTML;t.innerHTML='<svg class="icon size-4" aria-hidden="true"><use href="/icons.svg#check"></use></svg> Copied!',setTimeout(()=>{t.innerHTML=n},2e3)}).catch(n=>{console.error("Failed to copy:",n)})}document.addEventListener("DOMContentLoaded",()=>{document.addEventListener("click",e=>{let t=e.target.closest("button[data-cmd]");if(t){oe(t.getAttribute("data-cmd"),t);return}if(e.target.closest("a, button, input, .cmd"))return;let n=e.target.closest("[data-href]");n&&(window.location=n.getAttribute("data-href"))})});function xe(e){let t=Math.floor((new Date-new Date(e))/1e3),n={year:31536e3,month:2592e3,week:604800,day:86400,hour:3600,minute:60,second:1};for(let[r,o]of Object.entries(n)){let s=Math.floor(t/o);if(s>=1)return s===1?`1 ${r} ago`:`${s} ${r}s ago`}return"just now"}function j(){document.querySelectorAll("time[datetime]").forEach(e=>{let t=e.getAttribute("datetime");if(t&&!e.dataset.noUpdate){let n=xe(t);e.textContent!==n&&(e.textContent=n)}})}document.addEventListener("DOMContentLoaded",()=>{j(),W(),document.querySelectorAll("[data-theme-menu]").forEach(e=>{e.querySelectorAll(".theme-option").forEach(t=>{t.addEventListener("click",()=>{re(t.dataset.value)})})}),document.addEventListener("click",e=>{let t=e.target.closest("details.dropdown");document.querySelectorAll("details.dropdown[open]").forEach(n=>{n!==t&&n.removeAttribute("open")})})});document.addEventListener("htmx:afterSwap",j);setInterval(j,6e4);function ve(){let e=document.getElementById("show-offline-toggle"),t=document.querySelector(".manifests-list");!e||!t||(localStorage.setItem("showOfflineManifests",e.checked),e.checked?t.classList.add("show-offline"):t.classList.remove("show-offline"))}document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("show-offline-toggle");if(!e)return;let t=localStorage.getItem("showOfflineManifests")==="true";e.checked=t;let n=document.querySelector(".manifests-list");n&&(t?n.classList.add("show-offline"):n.classList.remove("show-offline"))});async function be(e,t,n){try{let r=await fetch("/api/manifests",{method:"DELETE",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({repo:e,digest:t,confirm:!1})});if(r.status===409){let o=await r.json();we(e,t,n,o.tags)}else if(r.ok)se(n);else{let o=await r.text();alert(`Failed to delete manifest: ${o}`)}}catch(r){console.error("Error deleting manifest:",r),alert(`Error deleting manifest: ${r.message}`)}}function we(e,t,n,r){let o=document.getElementById("manifest-delete-modal"),s=document.getElementById("manifest-delete-tags"),i=document.getElementById("confirm-manifest-delete-btn");s.innerHTML="",r.forEach(a=>{let l=document.createElement("li");l.textContent=a,s.appendChild(l)}),i.onclick=()=>Ce(e,t,n),o.style.display="flex"}function X(){let e=document.getElementById("manifest-delete-modal");e.style.display="none"}async function Ce(e,t,n){let r=document.getElementById("confirm-manifest-delete-btn"),o=r.textContent;try{r.disabled=!0,r.textContent="Deleting...";let s=await fetch("/api/manifests",{method:"DELETE",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({repo:e,digest:t,confirm:!0})});if(s.ok)X(),se(n),location.reload();else{let i=await s.text();alert(`Failed to delete manifest: ${i}`),r.disabled=!1,r.textContent=o}}catch(s){console.error("Error deleting manifest:",s),alert(`Error deleting manifest: ${s.message}`),r.disabled=!1,r.textContent=o}}async function Se(e){let t=document.getElementById("confirm-untagged-delete-btn"),n=t.textContent;try{t.disabled=!0,t.textContent="Deleting...";let r=await fetch("/api/manifests/untagged",{method:"DELETE",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({repo:e})}),o=await r.json();r.ok?(document.getElementById("untagged-delete-modal").close(),D(`Deleted ${o.deleted} untagged manifest(s)`,"success"),o.deleted>0&&location.reload(),t.disabled=!1,t.textContent=n):(alert(`Failed to delete untagged manifests: ${o.error||"Unknown error"}`),t.disabled=!1,t.textContent=n)}catch(r){console.error("Error deleting untagged manifests:",r),alert(`Error: ${r.message}`),t.disabled=!1,t.textContent=n}}function se(e){let t=document.getElementById(`manifest-${e}`);t&&t.remove()}document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("manifest-delete-modal");e&&e.addEventListener("click",t=>{t.target===e&&X()})});async function Te(e,t){let n=document.getElementById("vuln-detail-modal"),r=document.getElementById("vuln-modal-body");if(!(!n||!r)){r.innerHTML='<div class="flex justify-center py-8"><span class="loading loading-spinner loading-lg"></span></div>',n.showModal();try{let o=await fetch(`/api/vuln-details?digest=${encodeURIComponent(e)}&holdEndpoint=${encodeURIComponent(t)}`);r.innerHTML=await o.text()}catch{r.innerHTML='<p class="text-error">Failed to load vulnerability details</p>'}}}document.addEventListener("DOMContentLoaded",()=>{let e=document.cookie.split("; ").find(n=>n.startsWith("atcr_login_handle="));if(!e)return;let t=decodeURIComponent(e.split("=")[1]);if(t){try{let n="atcr_recent_handles",r=JSON.parse(localStorage.getItem(n)||"[]");r=r.filter(o=>o!==t),r.unshift(t),r=r.slice(0,5),localStorage.setItem(n,JSON.stringify(r))}catch(n){console.error("Failed to save recent account:",n)}document.cookie="atcr_login_handle=; path=/; max-age=0"}});function te(){let e=document.getElementById("featured-carousel"),t=document.getElementById("carousel-prev"),n=document.getElementById("carousel-next");if(!e)return;let r=Array.from(e.querySelectorAll(".carousel-item"));if(r.length===0)return;let o=null,s=5e3,i=0,a=0,l=0;function c(){let v=r[0];if(!v)return;let E=getComputedStyle(e),g=parseFloat(E.gap)||24;i=v.offsetWidth+g,a=e.offsetWidth,l=e.scrollWidth}requestAnimationFrame(()=>{requestAnimationFrame(()=>{c(),m()})});let h;window.addEventListener("resize",()=>{clearTimeout(h),h=setTimeout(c,150)});function u(){return i||c(),i}function f(){return(!a||!i)&&c(),Math.round(a/i)||1}function x(){return(!l||!a)&&c(),l-a}function d(){let v=u(),E=x(),g=e.scrollLeft;g>=E-10?e.scrollTo({left:0,behavior:"smooth"}):e.scrollTo({left:g+v,behavior:"smooth"})}function y(){let v=u(),E=x(),g=e.scrollLeft;g<=10?e.scrollTo({left:E,behavior:"smooth"}):e.scrollTo({left:g-v,behavior:"smooth"})}t&&t.addEventListener("click",()=>{b(),y(),m()}),n&&n.addEventListener("click",()=>{b(),d(),m()});function m(){o||r.length<=f()||(o=setInterval(d,s))}function b(){o&&(clearInterval(o),o=null)}e.addEventListener("mouseenter",b),e.addEventListener("mouseleave",m)}document.addEventListener("DOMContentLoaded",()=>{"requestIdleCallback"in window?requestIdleCallback(te,{timeout:2e3}):setTimeout(te,100)});function D(e,t){let n=document.getElementById("toast-container");n||(n=document.createElement("div"),n.id="toast-container",n.className="toast toast-end toast-bottom z-50",document.body.appendChild(n));let r=t==="error"?"alert-error":"alert-success",o=document.createElement("div");o.className=`alert ${r} shadow-lg transition-opacity duration-300`,o.innerHTML=`<span>${e}</span>`,n.appendChild(o),setTimeout(()=>{o.style.opacity="0",setTimeout(()=>o.remove(),300)},3e3)}async function Ae(e){try{let t=await fetch(`/api/webhooks/${e}/test`,{method:"POST",credentials:"include"}),n=await t.text();n.includes('class="success"')||t.ok&&!n.includes('class="error"')?D("Test webhook delivered successfully!","success"):D("Test delivery failed \u2014 check the webhook URL","error")}catch{D("Failed to reach server","error")}}window.setTheme=re;window.toggleSearch=ye;window.closeSearch=V;window.copyToClipboard=oe;window.toggleOfflineManifests=ve;window.deleteManifest=be;window.deleteUntaggedManifests=Se;window.closeManifestDeleteModal=X;window.openVulnDetails=Te;window.showToast=D;window.testWebhook=Ae;window.htmx=R;R.config.methodsThatUseUrlParams=["get"]; 1 + var ce=(function(){"use strict";let htmx={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){return getInputValues(e,t||"post").values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:!0,historyCacheSize:10,refreshOnHistoryMiss:!1,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:!0,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:!0,allowScriptTags:!0,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:!1,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:!1,getCacheBusterParam:!1,globalViewTransitions:!1,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:!0,ignoreTitle:!1,scrollIntoViewOnBoost:!0,triggerSpecsCache:null,disableInheritance:!1,responseHandling:[{code:"204",swap:!1},{code:"[23]..",swap:!0},{code:"[45]..",swap:!1,error:!0}],allowNestedOobSwaps:!0,historyRestoreAsHxRequest:!0,reportValidityOfForms:!1},parseInterval:null,location,_:null,version:"2.0.8"};htmx.onLoad=onLoadHelper,htmx.process=processNode,htmx.on=addEventListenerImpl,htmx.off=removeEventListenerImpl,htmx.trigger=triggerEvent,htmx.ajax=ajaxHelper,htmx.find=find,htmx.findAll=findAll,htmx.closest=closest,htmx.remove=removeElement,htmx.addClass=addClassToElement,htmx.removeClass=removeClassFromElement,htmx.toggleClass=toggleClassOnElement,htmx.takeClass=takeClassForElement,htmx.swap=swap,htmx.defineExtension=defineExtension,htmx.removeExtension=removeExtension,htmx.logAll=logAll,htmx.logNone=logNone,htmx.parseInterval=parseInterval,htmx._=internalEval;let internalAPI={addTriggerHandler,bodyContains,canAccessLocalStorage,findThisElement,filterValues,swap,hasAttribute,getAttributeValue,getClosestAttributeValue,getClosestMatch,getExpressionVars,getHeaders,getInputValues,getInternalData,getSwapSpecification,getTriggerSpecs,getTarget,makeFragment,mergeObjects,makeSettleInfo,oobSwap,querySelectorExt,settleImmediately,shouldCancel,triggerEvent,triggerErrorEvent,withExtensions},VERBS=["get","post","put","delete","patch"],VERB_SELECTOR=VERBS.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function parseInterval(e){if(e==null)return;let t=NaN;return e.slice(-2)=="ms"?t=parseFloat(e.slice(0,-2)):e.slice(-1)=="s"?t=parseFloat(e.slice(0,-1))*1e3:e.slice(-1)=="m"?t=parseFloat(e.slice(0,-1))*1e3*60:t=parseFloat(e),isNaN(t)?void 0:t}function getRawAttribute(e,t){return e instanceof Element&&e.getAttribute(t)}function hasAttribute(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function getAttributeValue(e,t){return getRawAttribute(e,t)||getRawAttribute(e,"data-"+t)}function parentElt(e){let t=e.parentElement;return!t&&e.parentNode instanceof ShadowRoot?e.parentNode:t}function getDocument(){return document}function getRootNode(e,t){return e.getRootNode?e.getRootNode({composed:t}):getDocument()}function getClosestMatch(e,t){for(;e&&!t(e);)e=parentElt(e);return e||null}function getAttributeValueWithDisinheritance(e,t,n){let r=getAttributeValue(t,n),o=getAttributeValue(t,"hx-disinherit");var s=getAttributeValue(t,"hx-inherit");if(e!==t){if(htmx.config.disableInheritance)return s&&(s==="*"||s.split(" ").indexOf(n)>=0)?r:null;if(o&&(o==="*"||o.split(" ").indexOf(n)>=0))return"unset"}return r}function getClosestAttributeValue(e,t){let n=null;if(getClosestMatch(e,function(r){return!!(n=getAttributeValueWithDisinheritance(e,asElement(r),t))}),n!=="unset")return n}function matches(e,t){return e instanceof Element&&e.matches(t)}function getStartTag(e){let n=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i.exec(e);return n?n[1].toLowerCase():""}function parseHTML(e){return"parseHTMLUnsafe"in Document?Document.parseHTMLUnsafe(e):new DOMParser().parseFromString(e,"text/html")}function takeChildrenFor(e,t){for(;t.childNodes.length>0;)e.append(t.childNodes[0])}function duplicateScript(e){let t=getDocument().createElement("script");return forEach(e.attributes,function(n){t.setAttribute(n.name,n.value)}),t.textContent=e.textContent,t.async=!1,htmx.config.inlineScriptNonce&&(t.nonce=htmx.config.inlineScriptNonce),t}function isJavaScriptScriptNode(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function normalizeScriptTags(e){Array.from(e.querySelectorAll("script")).forEach(t=>{if(isJavaScriptScriptNode(t)){let n=duplicateScript(t),r=t.parentNode;try{r.insertBefore(n,t)}catch(o){logError(o)}finally{t.remove()}}})}function makeFragment(e){let t=e.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i,""),n=getStartTag(t),r;if(n==="html"){r=new DocumentFragment;let s=parseHTML(e);takeChildrenFor(r,s.body),r.title=s.title}else if(n==="body"){r=new DocumentFragment;let s=parseHTML(t);takeChildrenFor(r,s.body),r.title=s.title}else{let s=parseHTML('<body><template class="internal-htmx-wrapper">'+t+"</template></body>");r=s.querySelector("template").content,r.title=s.title;var o=r.querySelector("title");o&&o.parentNode===r&&(o.remove(),r.title=o.innerText)}return r&&(htmx.config.allowScriptTags?normalizeScriptTags(r):r.querySelectorAll("script").forEach(s=>s.remove())),r}function maybeCall(e){e&&e()}function isType(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function isFunction(e){return typeof e=="function"}function isRawObject(e){return isType(e,"Object")}function getInternalData(e){let t="htmx-internal-data",n=e[t];return n||(n=e[t]={}),n}function toArray(e){let t=[];if(e)for(let n=0;n<e.length;n++)t.push(e[n]);return t}function forEach(e,t){if(e)for(let n=0;n<e.length;n++)t(e[n])}function isScrolledIntoView(e){let t=e.getBoundingClientRect(),n=t.top,r=t.bottom;return n<window.innerHeight&&r>=0}function bodyContains(e){return e.getRootNode({composed:!0})===document}function splitOnWhitespace(e){return e.trim().split(/\s+/)}function mergeObjects(e,t){for(let n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}function parseJSON(e){try{return JSON.parse(e)}catch(t){return logError(t),null}}function canAccessLocalStorage(){let e="htmx:sessionStorageTest";try{return sessionStorage.setItem(e,e),sessionStorage.removeItem(e),!0}catch{return!1}}function normalizePath(e){let t=new URL(e,"http://x");return t&&(e=t.pathname+t.search),e!="/"&&(e=e.replace(/\/+$/,"")),e}function internalEval(str){return maybeEval(getDocument().body,function(){return eval(str)})}function onLoadHelper(e){return htmx.on("htmx:load",function(n){e(n.detail.elt)})}function logAll(){htmx.logger=function(e,t,n){console&&console.log(t,e,n)}}function logNone(){htmx.logger=null}function find(e,t){return typeof e!="string"?e.querySelector(t):find(getDocument(),e)}function findAll(e,t){return typeof e!="string"?e.querySelectorAll(t):findAll(getDocument(),e)}function getWindow(){return window}function removeElement(e,t){e=resolveTarget(e),t?getWindow().setTimeout(function(){removeElement(e),e=null},t):parentElt(e).removeChild(e)}function asElement(e){return e instanceof Element?e:null}function asHtmlElement(e){return e instanceof HTMLElement?e:null}function asString(e){return typeof e=="string"?e:null}function asParentNode(e){return e instanceof Element||e instanceof Document||e instanceof DocumentFragment?e:null}function addClassToElement(e,t,n){e=asElement(resolveTarget(e)),e&&(n?getWindow().setTimeout(function(){addClassToElement(e,t),e=null},n):e.classList&&e.classList.add(t))}function removeClassFromElement(e,t,n){let r=asElement(resolveTarget(e));r&&(n?getWindow().setTimeout(function(){removeClassFromElement(r,t),r=null},n):r.classList&&(r.classList.remove(t),r.classList.length===0&&r.removeAttribute("class")))}function toggleClassOnElement(e,t){e=resolveTarget(e),e.classList.toggle(t)}function takeClassForElement(e,t){e=resolveTarget(e),forEach(e.parentElement.children,function(n){removeClassFromElement(n,t)}),addClassToElement(asElement(e),t)}function closest(e,t){return e=asElement(resolveTarget(e)),e?e.closest(t):null}function startsWith(e,t){return e.substring(0,t.length)===t}function endsWith(e,t){return e.substring(e.length-t.length)===t}function normalizeSelector(e){let t=e.trim();return startsWith(t,"<")&&endsWith(t,"/>")?t.substring(1,t.length-2):t}function querySelectorAllExt(e,t,n){if(t.indexOf("global ")===0)return querySelectorAllExt(e,t.slice(7),!0);e=resolveTarget(e);let r=[];{let i=0,a=0;for(let l=0;l<t.length;l++){let c=t[l];if(c===","&&i===0){r.push(t.substring(a,l)),a=l+1;continue}c==="<"?i++:c==="/"&&l<t.length-1&&t[l+1]===">"&&i--}a<t.length&&r.push(t.substring(a))}let o=[],s=[];for(;r.length>0;){let i=normalizeSelector(r.shift()),a;i.indexOf("closest ")===0?a=closest(asElement(e),normalizeSelector(i.slice(8))):i.indexOf("find ")===0?a=find(asParentNode(e),normalizeSelector(i.slice(5))):i==="next"||i==="nextElementSibling"?a=asElement(e).nextElementSibling:i.indexOf("next ")===0?a=scanForwardQuery(e,normalizeSelector(i.slice(5)),!!n):i==="previous"||i==="previousElementSibling"?a=asElement(e).previousElementSibling:i.indexOf("previous ")===0?a=scanBackwardsQuery(e,normalizeSelector(i.slice(9)),!!n):i==="document"?a=document:i==="window"?a=window:i==="body"?a=document.body:i==="root"?a=getRootNode(e,!!n):i==="host"?a=e.getRootNode().host:s.push(i),a&&o.push(a)}if(s.length>0){let i=s.join(","),a=asParentNode(getRootNode(e,!!n));o.push(...toArray(a.querySelectorAll(i)))}return o}var scanForwardQuery=function(e,t,n){let r=asParentNode(getRootNode(e,n)).querySelectorAll(t);for(let o=0;o<r.length;o++){let s=r[o];if(s.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_PRECEDING)return s}},scanBackwardsQuery=function(e,t,n){let r=asParentNode(getRootNode(e,n)).querySelectorAll(t);for(let o=r.length-1;o>=0;o--){let s=r[o];if(s.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING)return s}};function querySelectorExt(e,t){return typeof e!="string"?querySelectorAllExt(e,t)[0]:querySelectorAllExt(getDocument().body,e)[0]}function resolveTarget(e,t){return typeof e=="string"?find(asParentNode(t)||document,e):e}function processEventArgs(e,t,n,r){return isFunction(t)?{target:getDocument().body,event:asString(e),listener:t,options:n}:{target:resolveTarget(e),event:asString(t),listener:n,options:r}}function addEventListenerImpl(e,t,n,r){return ready(function(){let s=processEventArgs(e,t,n,r);s.target.addEventListener(s.event,s.listener,s.options)}),isFunction(t)?t:n}function removeEventListenerImpl(e,t,n){return ready(function(){let r=processEventArgs(e,t,n);r.target.removeEventListener(r.event,r.listener)}),isFunction(t)?t:n}let DUMMY_ELT=getDocument().createElement("output");function findAttributeTargets(e,t){let n=getClosestAttributeValue(e,t);if(n){if(n==="this")return[findThisElement(e,t)];{let r=querySelectorAllExt(e,n);if(/(^|,)(\s*)inherit(\s*)($|,)/.test(n)){let s=asElement(getClosestMatch(e,function(i){return i!==e&&hasAttribute(asElement(i),t)}));s&&r.push(...findAttributeTargets(s,t))}return r.length===0?(logError('The selector "'+n+'" on '+t+" returned no matches!"),[DUMMY_ELT]):r}}}function findThisElement(e,t){return asElement(getClosestMatch(e,function(n){return getAttributeValue(asElement(n),t)!=null}))}function getTarget(e){let t=getClosestAttributeValue(e,"hx-target");return t?t==="this"?findThisElement(e,"hx-target"):querySelectorExt(e,t):getInternalData(e).boosted?getDocument().body:e}function shouldSettleAttribute(e){return htmx.config.attributesToSettle.includes(e)}function cloneAttributes(e,t){forEach(Array.from(e.attributes),function(n){!t.hasAttribute(n.name)&&shouldSettleAttribute(n.name)&&e.removeAttribute(n.name)}),forEach(t.attributes,function(n){shouldSettleAttribute(n.name)&&e.setAttribute(n.name,n.value)})}function isInlineSwap(e,t){let n=getExtensions(t);for(let r=0;r<n.length;r++){let o=n[r];try{if(o.isInlineSwap(e))return!0}catch(s){logError(s)}}return e==="outerHTML"}function oobSwap(e,t,n,r){r=r||getDocument();let o="#"+CSS.escape(getRawAttribute(t,"id")),s="outerHTML";e==="true"||(e.indexOf(":")>0?(s=e.substring(0,e.indexOf(":")),o=e.substring(e.indexOf(":")+1)):s=e),t.removeAttribute("hx-swap-oob"),t.removeAttribute("data-hx-swap-oob");let i=querySelectorAllExt(r,o,!1);return i.length?(forEach(i,function(a){let l,c=t.cloneNode(!0);l=getDocument().createDocumentFragment(),l.appendChild(c),isInlineSwap(s,a)||(l=asParentNode(c));let d={shouldSwap:!0,target:a,fragment:l};triggerEvent(a,"htmx:oobBeforeSwap",d)&&(a=d.target,d.shouldSwap&&(handlePreservedElements(l),swapWithStyle(s,a,a,l,n),restorePreservedElements()),forEach(n.elts,function(u){triggerEvent(u,"htmx:oobAfterSwap",d)}))}),t.parentNode.removeChild(t)):(t.parentNode.removeChild(t),triggerErrorEvent(getDocument().body,"htmx:oobErrorNoTarget",{content:t})),e}function restorePreservedElements(){let e=find("#--htmx-preserve-pantry--");if(e){for(let t of[...e.children]){let n=find("#"+t.id);n.parentNode.moveBefore(t,n),n.remove()}e.remove()}}function handlePreservedElements(e){forEach(findAll(e,"[hx-preserve], [data-hx-preserve]"),function(t){let n=getAttributeValue(t,"id"),r=getDocument().getElementById(n);if(r!=null)if(t.moveBefore){let o=find("#--htmx-preserve-pantry--");o==null&&(getDocument().body.insertAdjacentHTML("afterend","<div id='--htmx-preserve-pantry--'></div>"),o=find("#--htmx-preserve-pantry--")),o.moveBefore(r,null)}else t.parentNode.replaceChild(r,t)})}function handleAttributes(e,t,n){forEach(t.querySelectorAll("[id]"),function(r){let o=getRawAttribute(r,"id");if(o&&o.length>0){let s=o.replace("'","\\'"),i=r.tagName.replace(":","\\:"),a=asParentNode(e),l=a&&a.querySelector(i+"[id='"+s+"']");if(l&&l!==a){let c=r.cloneNode();cloneAttributes(r,l),n.tasks.push(function(){cloneAttributes(r,c)})}}})}function makeAjaxLoadTask(e){return function(){removeClassFromElement(e,htmx.config.addedClass),processNode(asElement(e)),processFocus(asParentNode(e)),triggerEvent(e,"htmx:load")}}function processFocus(e){let t="[autofocus]",n=asHtmlElement(matches(e,t)?e:e.querySelector(t));n?.focus()}function insertNodesBefore(e,t,n,r){for(handleAttributes(e,n,r);n.childNodes.length>0;){let o=n.firstChild;addClassToElement(asElement(o),htmx.config.addedClass),e.insertBefore(o,t),o.nodeType!==Node.TEXT_NODE&&o.nodeType!==Node.COMMENT_NODE&&r.tasks.push(makeAjaxLoadTask(o))}}function stringHash(e,t){let n=0;for(;n<e.length;)t=(t<<5)-t+e.charCodeAt(n++)|0;return t}function attributeHash(e){let t=0;for(let n=0;n<e.attributes.length;n++){let r=e.attributes[n];r.value&&(t=stringHash(r.name,t),t=stringHash(r.value,t))}return t}function deInitOnHandlers(e){let t=getInternalData(e);if(t.onHandlers){for(let n=0;n<t.onHandlers.length;n++){let r=t.onHandlers[n];removeEventListenerImpl(e,r.event,r.listener)}delete t.onHandlers}}function deInitNode(e){let t=getInternalData(e);t.timeout&&clearTimeout(t.timeout),t.listenerInfos&&forEach(t.listenerInfos,function(n){n.on&&removeEventListenerImpl(n.on,n.trigger,n.listener)}),deInitOnHandlers(e),forEach(Object.keys(t),function(n){n!=="firstInitCompleted"&&delete t[n]})}function cleanUpElement(e){triggerEvent(e,"htmx:beforeCleanupElement"),deInitNode(e),forEach(e.children,function(t){cleanUpElement(t)})}function swapOuterHTML(e,t,n){if(e.tagName==="BODY")return swapInnerHTML(e,t,n);let r,o=e.previousSibling,s=parentElt(e);if(s){for(insertNodesBefore(s,e,t,n),o==null?r=s.firstChild:r=o.nextSibling,n.elts=n.elts.filter(function(i){return i!==e});r&&r!==e;)r instanceof Element&&n.elts.push(r),r=r.nextSibling;cleanUpElement(e),e.remove()}}function swapAfterBegin(e,t,n){return insertNodesBefore(e,e.firstChild,t,n)}function swapBeforeBegin(e,t,n){return insertNodesBefore(parentElt(e),e,t,n)}function swapBeforeEnd(e,t,n){return insertNodesBefore(e,null,t,n)}function swapAfterEnd(e,t,n){return insertNodesBefore(parentElt(e),e.nextSibling,t,n)}function swapDelete(e){cleanUpElement(e);let t=parentElt(e);if(t)return t.removeChild(e)}function swapInnerHTML(e,t,n){let r=e.firstChild;if(insertNodesBefore(e,r,t,n),r){for(;r.nextSibling;)cleanUpElement(r.nextSibling),e.removeChild(r.nextSibling);cleanUpElement(r),e.removeChild(r)}}function swapWithStyle(e,t,n,r,o){switch(e){case"none":return;case"outerHTML":swapOuterHTML(n,r,o);return;case"afterbegin":swapAfterBegin(n,r,o);return;case"beforebegin":swapBeforeBegin(n,r,o);return;case"beforeend":swapBeforeEnd(n,r,o);return;case"afterend":swapAfterEnd(n,r,o);return;case"delete":swapDelete(n);return;default:var s=getExtensions(t);for(let i=0;i<s.length;i++){let a=s[i];try{let l=a.handleSwap(e,n,r,o);if(l){if(Array.isArray(l))for(let c=0;c<l.length;c++){let d=l[c];d.nodeType!==Node.TEXT_NODE&&d.nodeType!==Node.COMMENT_NODE&&o.tasks.push(makeAjaxLoadTask(d))}return}}catch(l){logError(l)}}e==="innerHTML"?swapInnerHTML(n,r,o):swapWithStyle(htmx.config.defaultSwapStyle,t,n,r,o)}}function findAndSwapOobElements(e,t,n){var r=findAll(e,"[hx-swap-oob], [data-hx-swap-oob]");return forEach(r,function(o){if(htmx.config.allowNestedOobSwaps||o.parentElement===null){let s=getAttributeValue(o,"hx-swap-oob");s!=null&&oobSwap(s,o,t,n)}else o.removeAttribute("hx-swap-oob"),o.removeAttribute("data-hx-swap-oob")}),r.length>0}function swap(e,t,n,r){r||(r={});let o=null,s=null,i=function(){maybeCall(r.beforeSwapCallback),e=resolveTarget(e);let c=r.contextElement?getRootNode(r.contextElement,!1):getDocument(),d=document.activeElement,u={};u={elt:d,start:d?d.selectionStart:null,end:d?d.selectionEnd:null};let f=makeSettleInfo(e);if(n.swapStyle==="textContent")e.textContent=t;else{let h=makeFragment(t);if(f.title=r.title||h.title,r.historyRequest&&(h=h.querySelector("[hx-history-elt],[data-hx-history-elt]")||h),r.selectOOB){let p=r.selectOOB.split(",");for(let g=0;g<p.length;g++){let w=p[g].split(":",2),C=w[0].trim();C.indexOf("#")===0&&(C=C.substring(1));let x=w[1]||"true",E=h.querySelector("#"+C);E&&oobSwap(x,E,f,c)}}if(findAndSwapOobElements(h,f,c),forEach(findAll(h,"template"),function(p){p.content&&findAndSwapOobElements(p.content,f,c)&&p.remove()}),r.select){let p=getDocument().createDocumentFragment();forEach(h.querySelectorAll(r.select),function(g){p.appendChild(g)}),h=p}handlePreservedElements(h),swapWithStyle(n.swapStyle,r.contextElement,e,h,f),restorePreservedElements()}if(u.elt&&!bodyContains(u.elt)&&getRawAttribute(u.elt,"id")){let h=document.getElementById(getRawAttribute(u.elt,"id")),p={preventScroll:n.focusScroll!==void 0?!n.focusScroll:!htmx.config.defaultFocusScroll};if(h){if(u.start&&h.setSelectionRange)try{h.setSelectionRange(u.start,u.end)}catch{}h.focus(p)}}e.classList.remove(htmx.config.swappingClass),forEach(f.elts,function(h){h.classList&&h.classList.add(htmx.config.settlingClass),triggerEvent(h,"htmx:afterSwap",r.eventInfo)}),maybeCall(r.afterSwapCallback),n.ignoreTitle||handleTitle(f.title);let y=function(){if(forEach(f.tasks,function(h){h.call()}),forEach(f.elts,function(h){h.classList&&h.classList.remove(htmx.config.settlingClass),triggerEvent(h,"htmx:afterSettle",r.eventInfo)}),r.anchor){let h=asElement(resolveTarget("#"+r.anchor));h&&h.scrollIntoView({block:"start",behavior:"auto"})}updateScrollState(f.elts,n),maybeCall(r.afterSettleCallback),maybeCall(o)};n.settleDelay>0?getWindow().setTimeout(y,n.settleDelay):y()},a=htmx.config.globalViewTransitions;n.hasOwnProperty("transition")&&(a=n.transition);let l=r.contextElement||getDocument();if(a&&triggerEvent(l,"htmx:beforeTransition",r.eventInfo)&&typeof Promise<"u"&&document.startViewTransition){let c=new Promise(function(u,f){o=u,s=f}),d=i;i=function(){document.startViewTransition(function(){return d(),c})}}try{n?.swapDelay&&n.swapDelay>0?getWindow().setTimeout(i,n.swapDelay):i()}catch(c){throw triggerErrorEvent(l,"htmx:swapError",r.eventInfo),maybeCall(s),c}}function handleTriggerHeader(e,t,n){let r=e.getResponseHeader(t);if(r.indexOf("{")===0){let o=parseJSON(r);for(let s in o)if(o.hasOwnProperty(s)){let i=o[s];isRawObject(i)?n=i.target!==void 0?i.target:n:i={value:i},triggerEvent(n,s,i)}}else{let o=r.split(",");for(let s=0;s<o.length;s++)triggerEvent(n,o[s].trim(),[])}}let WHITESPACE=/\s/,WHITESPACE_OR_COMMA=/[\s,]/,SYMBOL_START=/[_$a-zA-Z]/,SYMBOL_CONT=/[_$a-zA-Z0-9]/,STRINGISH_START=['"',"'","/"],NOT_WHITESPACE=/[^\s]/,COMBINED_SELECTOR_START=/[{(]/,COMBINED_SELECTOR_END=/[})]/;function tokenizeString(e){let t=[],n=0;for(;n<e.length;){if(SYMBOL_START.exec(e.charAt(n))){for(var r=n;SYMBOL_CONT.exec(e.charAt(n+1));)n++;t.push(e.substring(r,n+1))}else if(STRINGISH_START.indexOf(e.charAt(n))!==-1){let o=e.charAt(n);var r=n;for(n++;n<e.length&&e.charAt(n)!==o;)e.charAt(n)==="\\"&&n++,n++;t.push(e.substring(r,n+1))}else{let o=e.charAt(n);t.push(o)}n++}return t}function isPossibleRelativeReference(e,t,n){return SYMBOL_START.exec(e.charAt(0))&&e!=="true"&&e!=="false"&&e!=="this"&&e!==n&&t!=="."}function maybeGenerateConditional(e,t,n){if(t[0]==="["){t.shift();let r=1,o=" return (function("+n+"){ return (",s=null;for(;t.length>0;){let i=t[0];if(i==="]"){if(r--,r===0){s===null&&(o=o+"true"),t.shift(),o+=")})";try{let a=maybeEval(e,function(){return Function(o)()},function(){return!0});return a.source=o,a}catch(a){return triggerErrorEvent(getDocument().body,"htmx:syntax:error",{error:a,source:o}),null}}}else i==="["&&r++;isPossibleRelativeReference(i,s,n)?o+="(("+n+"."+i+") ? ("+n+"."+i+") : (window."+i+"))":o=o+i,s=t.shift()}}}function consumeUntil(e,t){let n="";for(;e.length>0&&!t.test(e[0]);)n+=e.shift();return n}function consumeCSSSelector(e){let t;return e.length>0&&COMBINED_SELECTOR_START.test(e[0])?(e.shift(),t=consumeUntil(e,COMBINED_SELECTOR_END).trim(),e.shift()):t=consumeUntil(e,WHITESPACE_OR_COMMA),t}let INPUT_SELECTOR="input, textarea, select";function parseAndCacheTrigger(e,t,n){let r=[],o=tokenizeString(t);do{consumeUntil(o,NOT_WHITESPACE);let a=o.length,l=consumeUntil(o,/[,\[\s]/);if(l!=="")if(l==="every"){let c={trigger:"every"};consumeUntil(o,NOT_WHITESPACE),c.pollInterval=parseInterval(consumeUntil(o,/[,\[\s]/)),consumeUntil(o,NOT_WHITESPACE);var s=maybeGenerateConditional(e,o,"event");s&&(c.eventFilter=s),r.push(c)}else{let c={trigger:l};var s=maybeGenerateConditional(e,o,"event");for(s&&(c.eventFilter=s),consumeUntil(o,NOT_WHITESPACE);o.length>0&&o[0]!==",";){let u=o.shift();if(u==="changed")c.changed=!0;else if(u==="once")c.once=!0;else if(u==="consume")c.consume=!0;else if(u==="delay"&&o[0]===":")o.shift(),c.delay=parseInterval(consumeUntil(o,WHITESPACE_OR_COMMA));else if(u==="from"&&o[0]===":"){if(o.shift(),COMBINED_SELECTOR_START.test(o[0]))var i=consumeCSSSelector(o);else{var i=consumeUntil(o,WHITESPACE_OR_COMMA);if(i==="closest"||i==="find"||i==="next"||i==="previous"){o.shift();let y=consumeCSSSelector(o);y.length>0&&(i+=" "+y)}}c.from=i}else u==="target"&&o[0]===":"?(o.shift(),c.target=consumeCSSSelector(o)):u==="throttle"&&o[0]===":"?(o.shift(),c.throttle=parseInterval(consumeUntil(o,WHITESPACE_OR_COMMA))):u==="queue"&&o[0]===":"?(o.shift(),c.queue=consumeUntil(o,WHITESPACE_OR_COMMA)):u==="root"&&o[0]===":"?(o.shift(),c[u]=consumeCSSSelector(o)):u==="threshold"&&o[0]===":"?(o.shift(),c[u]=consumeUntil(o,WHITESPACE_OR_COMMA)):triggerErrorEvent(e,"htmx:syntax:error",{token:o.shift()});consumeUntil(o,NOT_WHITESPACE)}r.push(c)}o.length===a&&triggerErrorEvent(e,"htmx:syntax:error",{token:o.shift()}),consumeUntil(o,NOT_WHITESPACE)}while(o[0]===","&&o.shift());return n&&(n[t]=r),r}function getTriggerSpecs(e){let t=getAttributeValue(e,"hx-trigger"),n=[];if(t){let r=htmx.config.triggerSpecsCache;n=r&&r[t]||parseAndCacheTrigger(e,t,r)}return n.length>0?n:matches(e,"form")?[{trigger:"submit"}]:matches(e,'input[type="button"], input[type="submit"]')?[{trigger:"click"}]:matches(e,INPUT_SELECTOR)?[{trigger:"change"}]:[{trigger:"click"}]}function cancelPolling(e){getInternalData(e).cancelled=!0}function processPolling(e,t,n){let r=getInternalData(e);r.timeout=getWindow().setTimeout(function(){bodyContains(e)&&r.cancelled!==!0&&(maybeFilterEvent(n,e,makeEvent("hx:poll:trigger",{triggerSpec:n,target:e}))||t(e),processPolling(e,t,n))},n.pollInterval)}function isLocalLink(e){return location.hostname===e.hostname&&getRawAttribute(e,"href")&&getRawAttribute(e,"href").indexOf("#")!==0}function eltIsDisabled(e){return closest(e,htmx.config.disableSelector)}function boostElement(e,t,n){if(e instanceof HTMLAnchorElement&&isLocalLink(e)&&(e.target===""||e.target==="_self")||e.tagName==="FORM"&&String(getRawAttribute(e,"method")).toLowerCase()!=="dialog"){t.boosted=!0;let r,o;if(e.tagName==="A")r="get",o=getRawAttribute(e,"href");else{let s=getRawAttribute(e,"method");r=s?s.toLowerCase():"get",o=getRawAttribute(e,"action"),(o==null||o==="")&&(o=location.href),r==="get"&&o.includes("?")&&(o=o.replace(/\?[^#]+/,""))}n.forEach(function(s){addEventListener(e,function(i,a){let l=asElement(i);if(eltIsDisabled(l)){cleanUpElement(l);return}issueAjaxRequest(r,o,l,a)},t,s,!0)})}}function shouldCancel(e,t){if(e.type==="submit"&&t.tagName==="FORM")return!0;if(e.type==="click"){let n=t.closest('input[type="submit"], button');if(n&&n.form&&n.type==="submit")return!0;let r=t.closest("a"),o=/^#.+/;if(r&&r.href&&!o.test(r.getAttribute("href")))return!0}return!1}function ignoreBoostedAnchorCtrlClick(e,t){return getInternalData(e).boosted&&e instanceof HTMLAnchorElement&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function maybeFilterEvent(e,t,n){let r=e.eventFilter;if(r)try{return r.call(t,n)!==!0}catch(o){let s=r.source;return triggerErrorEvent(getDocument().body,"htmx:eventFilter:error",{error:o,source:s}),!0}return!1}function addEventListener(e,t,n,r,o){let s=getInternalData(e),i;r.from?i=querySelectorAllExt(e,r.from):i=[e],r.changed&&("lastValue"in s||(s.lastValue=new WeakMap),i.forEach(function(a){s.lastValue.has(r)||s.lastValue.set(r,new WeakMap),s.lastValue.get(r).set(a,a.value)})),forEach(i,function(a){let l=function(c){if(!bodyContains(e)){a.removeEventListener(r.trigger,l);return}if(ignoreBoostedAnchorCtrlClick(e,c)||((o||shouldCancel(c,a))&&c.preventDefault(),maybeFilterEvent(r,e,c)))return;let d=getInternalData(c);if(d.triggerSpec=r,d.handledFor==null&&(d.handledFor=[]),d.handledFor.indexOf(e)<0){if(d.handledFor.push(e),r.consume&&c.stopPropagation(),r.target&&c.target&&!matches(asElement(c.target),r.target))return;if(r.once){if(s.triggeredOnce)return;s.triggeredOnce=!0}if(r.changed){let u=c.target,f=u.value,y=s.lastValue.get(r);if(y.has(u)&&y.get(u)===f)return;y.set(u,f)}if(s.delayed&&clearTimeout(s.delayed),s.throttle)return;r.throttle>0?s.throttle||(triggerEvent(e,"htmx:trigger"),t(e,c),s.throttle=getWindow().setTimeout(function(){s.throttle=null},r.throttle)):r.delay>0?s.delayed=getWindow().setTimeout(function(){triggerEvent(e,"htmx:trigger"),t(e,c)},r.delay):(triggerEvent(e,"htmx:trigger"),t(e,c))}};n.listenerInfos==null&&(n.listenerInfos=[]),n.listenerInfos.push({trigger:r.trigger,listener:l,on:a}),a.addEventListener(r.trigger,l)})}let windowIsScrolling=!1,scrollHandler=null;function initScrollHandler(){scrollHandler||(scrollHandler=function(){windowIsScrolling=!0},window.addEventListener("scroll",scrollHandler),window.addEventListener("resize",scrollHandler),setInterval(function(){windowIsScrolling&&(windowIsScrolling=!1,forEach(getDocument().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"),function(e){maybeReveal(e)}))},200))}function maybeReveal(e){!hasAttribute(e,"data-hx-revealed")&&isScrolledIntoView(e)&&(e.setAttribute("data-hx-revealed","true"),getInternalData(e).initHash?triggerEvent(e,"revealed"):e.addEventListener("htmx:afterProcessNode",function(){triggerEvent(e,"revealed")},{once:!0}))}function loadImmediately(e,t,n,r){let o=function(){n.loaded||(n.loaded=!0,triggerEvent(e,"htmx:trigger"),t(e))};r>0?getWindow().setTimeout(o,r):o()}function processVerbs(e,t,n){let r=!1;return forEach(VERBS,function(o){if(hasAttribute(e,"hx-"+o)){let s=getAttributeValue(e,"hx-"+o);r=!0,t.path=s,t.verb=o,n.forEach(function(i){addTriggerHandler(e,i,t,function(a,l){let c=asElement(a);if(eltIsDisabled(c)){cleanUpElement(c);return}issueAjaxRequest(o,s,c,l)})})}}),r}function addTriggerHandler(e,t,n,r){if(t.trigger==="revealed")initScrollHandler(),addEventListener(e,r,n,t),maybeReveal(asElement(e));else if(t.trigger==="intersect"){let o={};t.root&&(o.root=querySelectorExt(e,t.root)),t.threshold&&(o.threshold=parseFloat(t.threshold)),new IntersectionObserver(function(i){for(let a=0;a<i.length;a++)if(i[a].isIntersecting){triggerEvent(e,"intersect");break}},o).observe(asElement(e)),addEventListener(asElement(e),r,n,t)}else!n.firstInitCompleted&&t.trigger==="load"?maybeFilterEvent(t,e,makeEvent("load",{elt:e}))||loadImmediately(asElement(e),r,n,t.delay):t.pollInterval>0?(n.polling=!0,processPolling(asElement(e),r,t)):addEventListener(e,r,n,t)}function shouldProcessHxOn(e){let t=asElement(e);if(!t)return!1;let n=t.attributes;for(let r=0;r<n.length;r++){let o=n[r].name;if(startsWith(o,"hx-on:")||startsWith(o,"data-hx-on:")||startsWith(o,"hx-on-")||startsWith(o,"data-hx-on-"))return!0}return!1}let HX_ON_QUERY=new XPathEvaluator().createExpression('.//*[@*[ starts-with(name(), "hx-on:") or starts-with(name(), "data-hx-on:") or starts-with(name(), "hx-on-") or starts-with(name(), "data-hx-on-") ]]');function processHXOnRoot(e,t){shouldProcessHxOn(e)&&t.push(asElement(e));let n=HX_ON_QUERY.evaluate(e),r=null;for(;r=n.iterateNext();)t.push(asElement(r))}function findHxOnWildcardElements(e){let t=[];if(e instanceof DocumentFragment)for(let n of e.childNodes)processHXOnRoot(n,t);else processHXOnRoot(e,t);return t}function findElementsToProcess(e){if(e.querySelectorAll){let n=", [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]",r=[];for(let s in extensions){let i=extensions[s];if(i.getSelectors){var t=i.getSelectors();t&&r.push(t)}}return e.querySelectorAll(VERB_SELECTOR+n+", form, [type='submit'], [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]"+r.flat().map(s=>", "+s).join(""))}else return[]}function maybeSetLastButtonClicked(e){let t=getTargetButton(e.target),n=getRelatedFormData(e);n&&(n.lastButtonClicked=t)}function maybeUnsetLastButtonClicked(e){let t=getRelatedFormData(e);t&&(t.lastButtonClicked=null)}function getTargetButton(e){return closest(asElement(e),"button, input[type='submit']")}function getRelatedForm(e){return e.form||closest(e,"form")}function getRelatedFormData(e){let t=getTargetButton(e.target);if(!t)return;let n=getRelatedForm(t);if(n)return getInternalData(n)}function initButtonTracking(e){e.addEventListener("click",maybeSetLastButtonClicked),e.addEventListener("focusin",maybeSetLastButtonClicked),e.addEventListener("focusout",maybeUnsetLastButtonClicked)}function addHxOnEventHandler(e,t,n){let r=getInternalData(e);Array.isArray(r.onHandlers)||(r.onHandlers=[]);let o,s=function(i){maybeEval(e,function(){eltIsDisabled(e)||(o||(o=new Function("event",n)),o.call(e,i))})};e.addEventListener(t,s),r.onHandlers.push({event:t,listener:s})}function processHxOnWildcard(e){deInitOnHandlers(e);for(let t=0;t<e.attributes.length;t++){let n=e.attributes[t].name,r=e.attributes[t].value;if(startsWith(n,"hx-on")||startsWith(n,"data-hx-on")){let o=n.indexOf("-on")+3,s=n.slice(o,o+1);if(s==="-"||s===":"){let i=n.slice(o+1);startsWith(i,":")?i="htmx"+i:startsWith(i,"-")?i="htmx:"+i.slice(1):startsWith(i,"htmx-")&&(i="htmx:"+i.slice(5)),addHxOnEventHandler(e,i,r)}}}}function initNode(e){triggerEvent(e,"htmx:beforeProcessNode");let t=getInternalData(e),n=getTriggerSpecs(e);processVerbs(e,t,n)||(getClosestAttributeValue(e,"hx-boost")==="true"?boostElement(e,t,n):hasAttribute(e,"hx-trigger")&&n.forEach(function(o){addTriggerHandler(e,o,t,function(){})})),(e.tagName==="FORM"||getRawAttribute(e,"type")==="submit"&&hasAttribute(e,"form"))&&initButtonTracking(e),t.firstInitCompleted=!0,triggerEvent(e,"htmx:afterProcessNode")}function maybeDeInitAndHash(e){if(!(e instanceof Element))return!1;let t=getInternalData(e),n=attributeHash(e);return t.initHash!==n?(deInitNode(e),t.initHash=n,!0):!1}function processNode(e){if(e=resolveTarget(e),eltIsDisabled(e)){cleanUpElement(e);return}let t=[];maybeDeInitAndHash(e)&&t.push(e),forEach(findElementsToProcess(e),function(n){if(eltIsDisabled(n)){cleanUpElement(n);return}maybeDeInitAndHash(n)&&t.push(n)}),forEach(findHxOnWildcardElements(e),processHxOnWildcard),forEach(t,initNode)}function kebabEventName(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}function makeEvent(e,t){return new CustomEvent(e,{bubbles:!0,cancelable:!0,composed:!0,detail:t})}function triggerErrorEvent(e,t,n){triggerEvent(e,t,mergeObjects({error:t},n))}function ignoreEventForLogging(e){return e==="htmx:afterProcessNode"}function withExtensions(e,t,n){forEach(getExtensions(e,[],n),function(r){try{t(r)}catch(o){logError(o)}})}function logError(e){console.error(e)}function triggerEvent(e,t,n){e=resolveTarget(e),n==null&&(n={}),n.elt=e;let r=makeEvent(t,n);htmx.logger&&!ignoreEventForLogging(t)&&htmx.logger(e,t,n),n.error&&(logError(n.error),triggerEvent(e,"htmx:error",{errorInfo:n}));let o=e.dispatchEvent(r),s=kebabEventName(t);if(o&&s!==t){let i=makeEvent(s,r.detail);o=o&&e.dispatchEvent(i)}return withExtensions(asElement(e),function(i){o=o&&i.onEvent(t,r)!==!1&&!r.defaultPrevented}),o}let currentPathForHistory;function setCurrentPathForHistory(e){currentPathForHistory=e,canAccessLocalStorage()&&sessionStorage.setItem("htmx-current-path-for-history",e)}setCurrentPathForHistory(location.pathname+location.search);function getHistoryElement(){return getDocument().querySelector("[hx-history-elt],[data-hx-history-elt]")||getDocument().body}function saveToHistoryCache(e,t){if(!canAccessLocalStorage())return;let n=cleanInnerHtmlForHistory(t),r=getDocument().title,o=window.scrollY;if(htmx.config.historyCacheSize<=0){sessionStorage.removeItem("htmx-history-cache");return}e=normalizePath(e);let s=parseJSON(sessionStorage.getItem("htmx-history-cache"))||[];for(let a=0;a<s.length;a++)if(s[a].url===e){s.splice(a,1);break}let i={url:e,content:n,title:r,scroll:o};for(triggerEvent(getDocument().body,"htmx:historyItemCreated",{item:i,cache:s}),s.push(i);s.length>htmx.config.historyCacheSize;)s.shift();for(;s.length>0;)try{sessionStorage.setItem("htmx-history-cache",JSON.stringify(s));break}catch(a){triggerErrorEvent(getDocument().body,"htmx:historyCacheError",{cause:a,cache:s}),s.shift()}}function getCachedHistory(e){if(!canAccessLocalStorage())return null;e=normalizePath(e);let t=parseJSON(sessionStorage.getItem("htmx-history-cache"))||[];for(let n=0;n<t.length;n++)if(t[n].url===e)return t[n];return null}function cleanInnerHtmlForHistory(e){let t=htmx.config.requestClass,n=e.cloneNode(!0);return forEach(findAll(n,"."+t),function(r){removeClassFromElement(r,t)}),forEach(findAll(n,"[data-disabled-by-htmx]"),function(r){r.removeAttribute("disabled")}),n.innerHTML}function saveCurrentPageToHistory(){let e=getHistoryElement(),t=currentPathForHistory;canAccessLocalStorage()&&(t=sessionStorage.getItem("htmx-current-path-for-history")),t=t||location.pathname+location.search,getDocument().querySelector('[hx-history="false" i],[data-hx-history="false" i]')||(triggerEvent(getDocument().body,"htmx:beforeHistorySave",{path:t,historyElt:e}),saveToHistoryCache(t,e)),htmx.config.historyEnabled&&history.replaceState({htmx:!0},getDocument().title,location.href)}function pushUrlIntoHistory(e){htmx.config.getCacheBusterParam&&(e=e.replace(/org\.htmx\.cache-buster=[^&]*&?/,""),(endsWith(e,"&")||endsWith(e,"?"))&&(e=e.slice(0,-1))),htmx.config.historyEnabled&&history.pushState({htmx:!0},"",e),setCurrentPathForHistory(e)}function replaceUrlInHistory(e){htmx.config.historyEnabled&&history.replaceState({htmx:!0},"",e),setCurrentPathForHistory(e)}function settleImmediately(e){forEach(e,function(t){t.call(void 0)})}function loadHistoryFromServer(e){let t=new XMLHttpRequest,n={swapStyle:"innerHTML",swapDelay:0,settleDelay:0},r={path:e,xhr:t,historyElt:getHistoryElement(),swapSpec:n};t.open("GET",e,!0),htmx.config.historyRestoreAsHxRequest&&t.setRequestHeader("HX-Request","true"),t.setRequestHeader("HX-History-Restore-Request","true"),t.setRequestHeader("HX-Current-URL",location.href),t.onload=function(){this.status>=200&&this.status<400?(r.response=this.response,triggerEvent(getDocument().body,"htmx:historyCacheMissLoad",r),swap(r.historyElt,r.response,n,{contextElement:r.historyElt,historyRequest:!0}),setCurrentPathForHistory(r.path),triggerEvent(getDocument().body,"htmx:historyRestore",{path:e,cacheMiss:!0,serverResponse:r.response})):triggerErrorEvent(getDocument().body,"htmx:historyCacheMissLoadError",r)},triggerEvent(getDocument().body,"htmx:historyCacheMiss",r)&&t.send()}function restoreHistory(e){saveCurrentPageToHistory(),e=e||location.pathname+location.search;let t=getCachedHistory(e);if(t){let n={swapStyle:"innerHTML",swapDelay:0,settleDelay:0,scroll:t.scroll},r={path:e,item:t,historyElt:getHistoryElement(),swapSpec:n};triggerEvent(getDocument().body,"htmx:historyCacheHit",r)&&(swap(r.historyElt,t.content,n,{contextElement:r.historyElt,title:t.title}),setCurrentPathForHistory(r.path),triggerEvent(getDocument().body,"htmx:historyRestore",r))}else htmx.config.refreshOnHistoryMiss?htmx.location.reload(!0):loadHistoryFromServer(e)}function addRequestIndicatorClasses(e){let t=findAttributeTargets(e,"hx-indicator");return t==null&&(t=[e]),forEach(t,function(n){let r=getInternalData(n);r.requestCount=(r.requestCount||0)+1,n.classList.add.call(n.classList,htmx.config.requestClass)}),t}function disableElements(e){let t=findAttributeTargets(e,"hx-disabled-elt");return t==null&&(t=[]),forEach(t,function(n){let r=getInternalData(n);r.requestCount=(r.requestCount||0)+1,n.setAttribute("disabled",""),n.setAttribute("data-disabled-by-htmx","")}),t}function removeRequestIndicators(e,t){forEach(e.concat(t),function(n){let r=getInternalData(n);r.requestCount=(r.requestCount||1)-1}),forEach(e,function(n){getInternalData(n).requestCount===0&&n.classList.remove.call(n.classList,htmx.config.requestClass)}),forEach(t,function(n){getInternalData(n).requestCount===0&&(n.removeAttribute("disabled"),n.removeAttribute("data-disabled-by-htmx"))})}function haveSeenNode(e,t){for(let n=0;n<e.length;n++)if(e[n].isSameNode(t))return!0;return!1}function shouldInclude(e){let t=e;return t.name===""||t.name==null||t.disabled||closest(t,"fieldset[disabled]")||t.type==="button"||t.type==="submit"||t.tagName==="image"||t.tagName==="reset"||t.tagName==="file"?!1:t.type==="checkbox"||t.type==="radio"?t.checked:!0}function addValueToFormData(e,t,n){e!=null&&t!=null&&(Array.isArray(t)?t.forEach(function(r){n.append(e,r)}):n.append(e,t))}function removeValueFromFormData(e,t,n){if(e!=null&&t!=null){let r=n.getAll(e);Array.isArray(t)?r=r.filter(o=>t.indexOf(o)<0):r=r.filter(o=>o!==t),n.delete(e),forEach(r,o=>n.append(e,o))}}function getValueFromInput(e){return e instanceof HTMLSelectElement&&e.multiple?toArray(e.querySelectorAll("option:checked")).map(function(t){return t.value}):e instanceof HTMLInputElement&&e.files?toArray(e.files):e.value}function processInputValue(e,t,n,r,o){if(!(r==null||haveSeenNode(e,r))){if(e.push(r),shouldInclude(r)){let s=getRawAttribute(r,"name");addValueToFormData(s,getValueFromInput(r),t),o&&validateElement(r,n)}r instanceof HTMLFormElement&&(forEach(r.elements,function(s){e.indexOf(s)>=0?removeValueFromFormData(s.name,getValueFromInput(s),t):e.push(s),o&&validateElement(s,n)}),new FormData(r).forEach(function(s,i){s instanceof File&&s.name===""||addValueToFormData(i,s,t)}))}}function validateElement(e,t){let n=e;n.willValidate&&(triggerEvent(n,"htmx:validation:validate"),n.checkValidity()||(triggerEvent(n,"htmx:validation:failed",{message:n.validationMessage,validity:n.validity})&&!t.length&&htmx.config.reportValidityOfForms&&n.reportValidity(),t.push({elt:n,message:n.validationMessage,validity:n.validity})))}function overrideFormData(e,t){for(let n of t.keys())e.delete(n);return t.forEach(function(n,r){e.append(r,n)}),e}function getInputValues(e,t){let n=[],r=new FormData,o=new FormData,s=[],i=getInternalData(e);i.lastButtonClicked&&!bodyContains(i.lastButtonClicked)&&(i.lastButtonClicked=null);let a=e instanceof HTMLFormElement&&e.noValidate!==!0||getAttributeValue(e,"hx-validate")==="true";if(i.lastButtonClicked&&(a=a&&i.lastButtonClicked.formNoValidate!==!0),t!=="get"&&processInputValue(n,o,s,getRelatedForm(e),a),processInputValue(n,r,s,e,a),i.lastButtonClicked||e.tagName==="BUTTON"||e.tagName==="INPUT"&&getRawAttribute(e,"type")==="submit"){let c=i.lastButtonClicked||e,d=getRawAttribute(c,"name");addValueToFormData(d,c.value,o)}let l=findAttributeTargets(e,"hx-include");return forEach(l,function(c){processInputValue(n,r,s,asElement(c),a),matches(c,"form")||forEach(asParentNode(c).querySelectorAll(INPUT_SELECTOR),function(d){processInputValue(n,r,s,d,a)})}),overrideFormData(r,o),{errors:s,formData:r,values:formDataProxy(r)}}function appendParam(e,t,n){e!==""&&(e+="&"),String(n)==="[object Object]"&&(n=JSON.stringify(n));let r=encodeURIComponent(n);return e+=encodeURIComponent(t)+"="+r,e}function urlEncode(e){e=formDataFromObject(e);let t="";return e.forEach(function(n,r){t=appendParam(t,r,n)}),t}function getHeaders(e,t,n){let r={"HX-Request":"true","HX-Trigger":getRawAttribute(e,"id"),"HX-Trigger-Name":getRawAttribute(e,"name"),"HX-Target":getAttributeValue(t,"id"),"HX-Current-URL":location.href};return getValuesForElement(e,"hx-headers",!1,r),n!==void 0&&(r["HX-Prompt"]=n),getInternalData(e).boosted&&(r["HX-Boosted"]="true"),r}function filterValues(e,t){let n=getClosestAttributeValue(t,"hx-params");if(n){if(n==="none")return new FormData;if(n==="*")return e;if(n.indexOf("not ")===0)return forEach(n.slice(4).split(","),function(r){r=r.trim(),e.delete(r)}),e;{let r=new FormData;return forEach(n.split(","),function(o){o=o.trim(),e.has(o)&&e.getAll(o).forEach(function(s){r.append(o,s)})}),r}}else return e}function isAnchorLink(e){return!!getRawAttribute(e,"href")&&getRawAttribute(e,"href").indexOf("#")>=0}function getSwapSpecification(e,t){let n=t||getClosestAttributeValue(e,"hx-swap"),r={swapStyle:getInternalData(e).boosted?"innerHTML":htmx.config.defaultSwapStyle,swapDelay:htmx.config.defaultSwapDelay,settleDelay:htmx.config.defaultSettleDelay};if(htmx.config.scrollIntoViewOnBoost&&getInternalData(e).boosted&&!isAnchorLink(e)&&(r.show="top"),n){let i=splitOnWhitespace(n);if(i.length>0)for(let a=0;a<i.length;a++){let l=i[a];if(l.indexOf("swap:")===0)r.swapDelay=parseInterval(l.slice(5));else if(l.indexOf("settle:")===0)r.settleDelay=parseInterval(l.slice(7));else if(l.indexOf("transition:")===0)r.transition=l.slice(11)==="true";else if(l.indexOf("ignoreTitle:")===0)r.ignoreTitle=l.slice(12)==="true";else if(l.indexOf("scroll:")===0){var o=l.slice(7).split(":");let d=o.pop();var s=o.length>0?o.join(":"):null;r.scroll=d,r.scrollTarget=s}else if(l.indexOf("show:")===0){var o=l.slice(5).split(":");let u=o.pop();var s=o.length>0?o.join(":"):null;r.show=u,r.showTarget=s}else if(l.indexOf("focus-scroll:")===0){let c=l.slice(13);r.focusScroll=c=="true"}else a==0?r.swapStyle=l:logError("Unknown modifier in hx-swap: "+l)}}return r}function usesFormData(e){return getClosestAttributeValue(e,"hx-encoding")==="multipart/form-data"||matches(e,"form")&&getRawAttribute(e,"enctype")==="multipart/form-data"}function encodeParamsForBody(e,t,n){let r=null;return withExtensions(t,function(o){r==null&&(r=o.encodeParameters(e,n,t))}),r??(usesFormData(t)?overrideFormData(new FormData,formDataFromObject(n)):urlEncode(n))}function makeSettleInfo(e){return{tasks:[],elts:[e]}}function updateScrollState(e,t){let n=e[0],r=e[e.length-1];if(t.scroll){var o=null;t.scrollTarget&&(o=asElement(querySelectorExt(n,t.scrollTarget))),t.scroll==="top"&&(n||o)&&(o=o||n,o.scrollTop=0),t.scroll==="bottom"&&(r||o)&&(o=o||r,o.scrollTop=o.scrollHeight),typeof t.scroll=="number"&&getWindow().setTimeout(function(){window.scrollTo(0,t.scroll)},0)}if(t.show){var o=null;if(t.showTarget){let i=t.showTarget;t.showTarget==="window"&&(i="body"),o=asElement(querySelectorExt(n,i))}t.show==="top"&&(n||o)&&(o=o||n,o.scrollIntoView({block:"start",behavior:htmx.config.scrollBehavior})),t.show==="bottom"&&(r||o)&&(o=o||r,o.scrollIntoView({block:"end",behavior:htmx.config.scrollBehavior}))}}function getValuesForElement(e,t,n,r,o){if(r==null&&(r={}),e==null)return r;let s=getAttributeValue(e,t);if(s){let i=s.trim(),a=n;if(i==="unset")return null;i.indexOf("javascript:")===0?(i=i.slice(11),a=!0):i.indexOf("js:")===0&&(i=i.slice(3),a=!0),i.indexOf("{")!==0&&(i="{"+i+"}");let l;a?l=maybeEval(e,function(){return o?Function("event","return ("+i+")").call(e,o):Function("return ("+i+")").call(e)},{}):l=parseJSON(i);for(let c in l)l.hasOwnProperty(c)&&r[c]==null&&(r[c]=l[c])}return getValuesForElement(asElement(parentElt(e)),t,n,r,o)}function maybeEval(e,t,n){return htmx.config.allowEval?t():(triggerErrorEvent(e,"htmx:evalDisallowedError"),n)}function getHXVarsForElement(e,t,n){return getValuesForElement(e,"hx-vars",!0,n,t)}function getHXValsForElement(e,t,n){return getValuesForElement(e,"hx-vals",!1,n,t)}function getExpressionVars(e,t){return mergeObjects(getHXVarsForElement(e,t),getHXValsForElement(e,t))}function safelySetHeaderValue(e,t,n){if(n!==null)try{e.setRequestHeader(t,n)}catch{e.setRequestHeader(t,encodeURIComponent(n)),e.setRequestHeader(t+"-URI-AutoEncoded","true")}}function getPathFromResponse(e){if(e.responseURL)try{let t=new URL(e.responseURL);return t.pathname+t.search}catch{triggerErrorEvent(getDocument().body,"htmx:badResponseUrl",{url:e.responseURL})}}function hasHeader(e,t){return t.test(e.getAllResponseHeaders())}function ajaxHelper(e,t,n){if(e=e.toLowerCase(),n){if(n instanceof Element||typeof n=="string")return issueAjaxRequest(e,t,null,null,{targetOverride:resolveTarget(n)||DUMMY_ELT,returnPromise:!0});{let r=resolveTarget(n.target);return(n.target&&!r||n.source&&!r&&!resolveTarget(n.source))&&(r=DUMMY_ELT),issueAjaxRequest(e,t,resolveTarget(n.source),n.event,{handler:n.handler,headers:n.headers,values:n.values,targetOverride:r,swapOverride:n.swap,select:n.select,returnPromise:!0,push:n.push,replace:n.replace,selectOOB:n.selectOOB})}}else return issueAjaxRequest(e,t,null,null,{returnPromise:!0})}function hierarchyForElt(e){let t=[];for(;e;)t.push(e),e=e.parentElement;return t}function verifyPath(e,t,n){let r=new URL(t,location.protocol!=="about:"?location.href:window.origin),s=(location.protocol!=="about:"?location.origin:window.origin)===r.origin;return htmx.config.selfRequestsOnly&&!s?!1:triggerEvent(e,"htmx:validateUrl",mergeObjects({url:r,sameHost:s},n))}function formDataFromObject(e){if(e instanceof FormData)return e;let t=new FormData;for(let n in e)e.hasOwnProperty(n)&&(e[n]&&typeof e[n].forEach=="function"?e[n].forEach(function(r){t.append(n,r)}):typeof e[n]=="object"&&!(e[n]instanceof Blob)?t.append(n,JSON.stringify(e[n])):t.append(n,e[n]));return t}function formDataArrayProxy(e,t,n){return new Proxy(n,{get:function(r,o){return typeof o=="number"?r[o]:o==="length"?r.length:o==="push"?function(s){r.push(s),e.append(t,s)}:typeof r[o]=="function"?function(){r[o].apply(r,arguments),e.delete(t),r.forEach(function(s){e.append(t,s)})}:r[o]&&r[o].length===1?r[o][0]:r[o]},set:function(r,o,s){return r[o]=s,e.delete(t),r.forEach(function(i){e.append(t,i)}),!0}})}function formDataProxy(e){return new Proxy(e,{get:function(t,n){if(typeof n=="symbol"){let o=Reflect.get(t,n);return typeof o=="function"?function(){return o.apply(e,arguments)}:o}if(n==="toJSON")return()=>Object.fromEntries(e);if(n in t&&typeof t[n]=="function")return function(){return e[n].apply(e,arguments)};let r=e.getAll(n);if(r.length!==0)return r.length===1?r[0]:formDataArrayProxy(t,n,r)},set:function(t,n,r){return typeof n!="string"?!1:(t.delete(n),r&&typeof r.forEach=="function"?r.forEach(function(o){t.append(n,o)}):typeof r=="object"&&!(r instanceof Blob)?t.append(n,JSON.stringify(r)):t.append(n,r),!0)},deleteProperty:function(t,n){return typeof n=="string"&&t.delete(n),!0},ownKeys:function(t){return Reflect.ownKeys(Object.fromEntries(t))},getOwnPropertyDescriptor:function(t,n){return Reflect.getOwnPropertyDescriptor(Object.fromEntries(t),n)}})}function issueAjaxRequest(e,t,n,r,o,s){let i=null,a=null;if(o=o??{},o.returnPromise&&typeof Promise<"u")var l=new Promise(function(m,v){i=m,a=v});n==null&&(n=getDocument().body);let c=o.handler||handleAjaxResponse,d=o.select||null;if(!bodyContains(n))return maybeCall(i),l;let u=o.targetOverride||asElement(getTarget(n));if(u==null||u==DUMMY_ELT)return triggerErrorEvent(n,"htmx:targetError",{target:getClosestAttributeValue(n,"hx-target")}),maybeCall(a),l;let f=getInternalData(n),y=f.lastButtonClicked;if(y){let m=getRawAttribute(y,"formaction");m!=null&&(t=m);let v=getRawAttribute(y,"formmethod");if(v!=null)if(VERBS.includes(v.toLowerCase()))e=v;else return maybeCall(i),l}let h=getClosestAttributeValue(n,"hx-confirm");if(s===void 0&&triggerEvent(n,"htmx:confirm",{target:u,elt:n,path:t,verb:e,triggeringEvent:r,etc:o,issueRequest:function(A){return issueAjaxRequest(e,t,n,r,o,!!A)},question:h})===!1)return maybeCall(i),l;let p=n,g=getClosestAttributeValue(n,"hx-sync"),w=null,C=!1;if(g){let m=g.split(":"),v=m[0].trim();if(v==="this"?p=findThisElement(n,"hx-sync"):p=asElement(querySelectorExt(n,v)),g=(m[1]||"drop").trim(),f=getInternalData(p),g==="drop"&&f.xhr&&f.abortable!==!0)return maybeCall(i),l;if(g==="abort"){if(f.xhr)return maybeCall(i),l;C=!0}else g==="replace"?triggerEvent(p,"htmx:abort"):g.indexOf("queue")===0&&(w=(g.split(" ")[1]||"last").trim())}if(f.xhr)if(f.abortable)triggerEvent(p,"htmx:abort");else{if(w==null){if(r){let m=getInternalData(r);m&&m.triggerSpec&&m.triggerSpec.queue&&(w=m.triggerSpec.queue)}w==null&&(w="last")}return f.queuedRequests==null&&(f.queuedRequests=[]),w==="first"&&f.queuedRequests.length===0?f.queuedRequests.push(function(){issueAjaxRequest(e,t,n,r,o)}):w==="all"?f.queuedRequests.push(function(){issueAjaxRequest(e,t,n,r,o)}):w==="last"&&(f.queuedRequests=[],f.queuedRequests.push(function(){issueAjaxRequest(e,t,n,r,o)})),maybeCall(i),l}let x=new XMLHttpRequest;f.xhr=x,f.abortable=C;let E=function(){f.xhr=null,f.abortable=!1,f.queuedRequests!=null&&f.queuedRequests.length>0&&f.queuedRequests.shift()()},J=getClosestAttributeValue(n,"hx-prompt");if(J){var F=prompt(J);if(F===null||!triggerEvent(n,"htmx:prompt",{prompt:F,target:u}))return maybeCall(i),E(),l}if(h&&!s&&!confirm(h))return maybeCall(i),E(),l;let H=getHeaders(n,u,F);e!=="get"&&!usesFormData(n)&&(H["Content-Type"]="application/x-www-form-urlencoded"),o.headers&&(H=mergeObjects(H,o.headers));let Y=getInputValues(n,e),O=Y.errors,z=Y.formData;o.values&&overrideFormData(z,formDataFromObject(o.values));let ie=formDataFromObject(getExpressionVars(n,r)),q=overrideFormData(z,ie),I=filterValues(q,n);htmx.config.getCacheBusterParam&&e==="get"&&I.set("org.htmx.cache-buster",getRawAttribute(u,"id")||"true"),(t==null||t==="")&&(t=location.href);let k=getValuesForElement(n,"hx-request"),K=getInternalData(n).boosted,L=htmx.config.methodsThatUseUrlParams.indexOf(e)>=0,S={boosted:K,useUrlParams:L,formData:I,parameters:formDataProxy(I),unfilteredFormData:q,unfilteredParameters:formDataProxy(q),headers:H,elt:n,target:u,verb:e,errors:O,withCredentials:o.credentials||k.credentials||htmx.config.withCredentials,timeout:o.timeout||k.timeout||htmx.config.timeout,path:t,triggeringEvent:r};if(!triggerEvent(n,"htmx:configRequest",S))return maybeCall(i),E(),l;if(t=S.path,e=S.verb,H=S.headers,I=formDataFromObject(S.parameters),O=S.errors,L=S.useUrlParams,O&&O.length>0)return triggerEvent(n,"htmx:validation:halted",S),maybeCall(i),E(),l;let G=t.split("#"),ae=G[0],_=G[1],T=t;if(L&&(T=ae,!I.keys().next().done&&(T.indexOf("?")<0?T+="?":T+="&",T+=urlEncode(I),_&&(T+="#"+_))),!verifyPath(n,T,S))return triggerErrorEvent(n,"htmx:invalidPath",S),maybeCall(a),E(),l;if(x.open(e.toUpperCase(),T,!0),x.overrideMimeType("text/html"),x.withCredentials=S.withCredentials,x.timeout=S.timeout,!k.noHeaders){for(let m in H)if(H.hasOwnProperty(m)){let v=H[m];safelySetHeaderValue(x,m,v)}}let b={xhr:x,target:u,requestConfig:S,etc:o,boosted:K,select:d,pathInfo:{requestPath:t,finalRequestPath:T,responsePath:null,anchor:_}};if(x.onload=function(){try{let m=hierarchyForElt(n);if(b.pathInfo.responsePath=getPathFromResponse(x),c(n,b),b.keepIndicators!==!0&&removeRequestIndicators(P,N),triggerEvent(n,"htmx:afterRequest",b),triggerEvent(n,"htmx:afterOnLoad",b),!bodyContains(n)){let v=null;for(;m.length>0&&v==null;){let A=m.shift();bodyContains(A)&&(v=A)}v&&(triggerEvent(v,"htmx:afterRequest",b),triggerEvent(v,"htmx:afterOnLoad",b))}maybeCall(i)}catch(m){throw triggerErrorEvent(n,"htmx:onLoadError",mergeObjects({error:m},b)),m}finally{E()}},x.onerror=function(){removeRequestIndicators(P,N),triggerErrorEvent(n,"htmx:afterRequest",b),triggerErrorEvent(n,"htmx:sendError",b),maybeCall(a),E()},x.onabort=function(){removeRequestIndicators(P,N),triggerErrorEvent(n,"htmx:afterRequest",b),triggerErrorEvent(n,"htmx:sendAbort",b),maybeCall(a),E()},x.ontimeout=function(){removeRequestIndicators(P,N),triggerErrorEvent(n,"htmx:afterRequest",b),triggerErrorEvent(n,"htmx:timeout",b),maybeCall(a),E()},!triggerEvent(n,"htmx:beforeRequest",b))return maybeCall(i),E(),l;var P=addRequestIndicatorClasses(n),N=disableElements(n);forEach(["loadstart","loadend","progress","abort"],function(m){forEach([x,x.upload],function(v){v.addEventListener(m,function(A){triggerEvent(n,"htmx:xhr:"+m,{lengthComputable:A.lengthComputable,loaded:A.loaded,total:A.total})})})}),triggerEvent(n,"htmx:beforeSend",b);let le=L?null:encodeParamsForBody(x,n,I);return x.send(le),l}function determineHistoryUpdates(e,t){let n=t.xhr,r=null,o=null;if(hasHeader(n,/HX-Push:/i)?(r=n.getResponseHeader("HX-Push"),o="push"):hasHeader(n,/HX-Push-Url:/i)?(r=n.getResponseHeader("HX-Push-Url"),o="push"):hasHeader(n,/HX-Replace-Url:/i)&&(r=n.getResponseHeader("HX-Replace-Url"),o="replace"),r)return r==="false"?{}:{type:o,path:r};let s=t.pathInfo.finalRequestPath,i=t.pathInfo.responsePath,a=t.etc.push||getClosestAttributeValue(e,"hx-push-url"),l=t.etc.replace||getClosestAttributeValue(e,"hx-replace-url"),c=getInternalData(e).boosted,d=null,u=null;return a?(d="push",u=a):l?(d="replace",u=l):c&&(d="push",u=i||s),u?u==="false"?{}:(u==="true"&&(u=i||s),t.pathInfo.anchor&&u.indexOf("#")===-1&&(u=u+"#"+t.pathInfo.anchor),{type:d,path:u}):{}}function codeMatches(e,t){var n=new RegExp(e.code);return n.test(t.toString(10))}function resolveResponseHandling(e){for(var t=0;t<htmx.config.responseHandling.length;t++){var n=htmx.config.responseHandling[t];if(codeMatches(n,e.status))return n}return{swap:!1}}function handleTitle(e){if(e){let t=find("title");t?t.textContent=e:window.document.title=e}}function resolveRetarget(e,t){if(t==="this")return e;let n=asElement(querySelectorExt(e,t));if(n==null)throw triggerErrorEvent(e,"htmx:targetError",{target:t}),new Error(`Invalid re-target ${t}`);return n}function handleAjaxResponse(e,t){let n=t.xhr,r=t.target,o=t.etc,s=t.select;if(!triggerEvent(e,"htmx:beforeOnLoad",t))return;if(hasHeader(n,/HX-Trigger:/i)&&handleTriggerHeader(n,"HX-Trigger",e),hasHeader(n,/HX-Location:/i)){let C=n.getResponseHeader("HX-Location");var i={};C.indexOf("{")===0&&(i=parseJSON(C),C=i.path,delete i.path),i.push=i.push||"true",ajaxHelper("get",C,i);return}let a=hasHeader(n,/HX-Refresh:/i)&&n.getResponseHeader("HX-Refresh")==="true";if(hasHeader(n,/HX-Redirect:/i)){t.keepIndicators=!0,htmx.location.href=n.getResponseHeader("HX-Redirect"),a&&htmx.location.reload();return}if(a){t.keepIndicators=!0,htmx.location.reload();return}let l=determineHistoryUpdates(e,t),c=resolveResponseHandling(n),d=c.swap,u=!!c.error,f=htmx.config.ignoreTitle||c.ignoreTitle,y=c.select;c.target&&(t.target=resolveRetarget(e,c.target));var h=o.swapOverride;h==null&&c.swapOverride&&(h=c.swapOverride),hasHeader(n,/HX-Retarget:/i)&&(t.target=resolveRetarget(e,n.getResponseHeader("HX-Retarget"))),hasHeader(n,/HX-Reswap:/i)&&(h=n.getResponseHeader("HX-Reswap"));var p=n.response,g=mergeObjects({shouldSwap:d,serverResponse:p,isError:u,ignoreTitle:f,selectOverride:y,swapOverride:h},t);if(!(c.event&&!triggerEvent(r,c.event,g))&&triggerEvent(r,"htmx:beforeSwap",g)){if(r=g.target,p=g.serverResponse,u=g.isError,f=g.ignoreTitle,y=g.selectOverride,h=g.swapOverride,t.target=r,t.failed=u,t.successful=!u,g.shouldSwap){n.status===286&&cancelPolling(e),withExtensions(e,function(E){p=E.transformResponse(p,n,e)}),l.type&&saveCurrentPageToHistory();var w=getSwapSpecification(e,h);w.hasOwnProperty("ignoreTitle")||(w.ignoreTitle=f),r.classList.add(htmx.config.swappingClass),s&&(y=s),hasHeader(n,/HX-Reselect:/i)&&(y=n.getResponseHeader("HX-Reselect"));let C=o.selectOOB||getClosestAttributeValue(e,"hx-select-oob"),x=getClosestAttributeValue(e,"hx-select");swap(r,p,w,{select:y==="unset"?null:y||x,selectOOB:C,eventInfo:t,anchor:t.pathInfo.anchor,contextElement:e,afterSwapCallback:function(){if(hasHeader(n,/HX-Trigger-After-Swap:/i)){let E=e;bodyContains(e)||(E=getDocument().body),handleTriggerHeader(n,"HX-Trigger-After-Swap",E)}},afterSettleCallback:function(){if(hasHeader(n,/HX-Trigger-After-Settle:/i)){let E=e;bodyContains(e)||(E=getDocument().body),handleTriggerHeader(n,"HX-Trigger-After-Settle",E)}},beforeSwapCallback:function(){l.type&&(triggerEvent(getDocument().body,"htmx:beforeHistoryUpdate",mergeObjects({history:l},t)),l.type==="push"?(pushUrlIntoHistory(l.path),triggerEvent(getDocument().body,"htmx:pushedIntoHistory",{path:l.path})):(replaceUrlInHistory(l.path),triggerEvent(getDocument().body,"htmx:replacedInHistory",{path:l.path})))}})}u&&triggerErrorEvent(e,"htmx:responseError",mergeObjects({error:"Response Status Error Code "+n.status+" from "+t.pathInfo.requestPath},t))}}let extensions={};function extensionBase(){return{init:function(e){return null},getSelectors:function(){return null},onEvent:function(e,t){return!0},transformResponse:function(e,t,n){return e},isInlineSwap:function(e){return!1},handleSwap:function(e,t,n,r){return!1},encodeParameters:function(e,t,n){return null}}}function defineExtension(e,t){t.init&&t.init(internalAPI),extensions[e]=mergeObjects(extensionBase(),t)}function removeExtension(e){delete extensions[e]}function getExtensions(e,t,n){if(t==null&&(t=[]),e==null)return t;n==null&&(n=[]);let r=getAttributeValue(e,"hx-ext");return r&&forEach(r.split(","),function(o){if(o=o.replace(/ /g,""),o.slice(0,7)=="ignore:"){n.push(o.slice(7));return}if(n.indexOf(o)<0){let s=extensions[o];s&&t.indexOf(s)<0&&t.push(s)}}),getExtensions(asElement(parentElt(e)),t,n)}var isReady=!1;getDocument().addEventListener("DOMContentLoaded",function(){isReady=!0});function ready(e){isReady||getDocument().readyState==="complete"?e():getDocument().addEventListener("DOMContentLoaded",e)}function insertIndicatorStyles(){if(htmx.config.includeIndicatorStyles!==!1){let e=htmx.config.inlineStyleNonce?` nonce="${htmx.config.inlineStyleNonce}"`:"",t=htmx.config.indicatorClass,n=htmx.config.requestClass;getDocument().head.insertAdjacentHTML("beforeend",`<style${e}>.${t}{opacity:0;visibility: hidden} .${n} .${t}, .${n}.${t}{opacity:1;visibility: visible;transition: opacity 200ms ease-in}</style>`)}}function getMetaConfig(){let e=getDocument().querySelector('meta[name="htmx-config"]');return e?parseJSON(e.content):null}function mergeMetaConfig(){let e=getMetaConfig();e&&(htmx.config=mergeObjects(htmx.config,e))}return ready(function(){mergeMetaConfig(),insertIndicatorStyles();let e=getDocument().body;processNode(e);let t=getDocument().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(r){let o=r.detail.elt||r.target,s=getInternalData(o);s&&s.xhr&&s.xhr.abort()});let n=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(r){r.state&&r.state.htmx?(restoreHistory(),forEach(t,function(o){triggerEvent(o,"htmx:restored",{document:getDocument(),triggerEvent})})):n&&n(r)},getWindow().setTimeout(function(){triggerEvent(e,"htmx:load",{}),e=null},0)}),htmx})(),R=ce;(function(){let e;R.defineExtension("json-enc",{init:function(t){e=t},onEvent:function(t,n){t==="htmx:configRequest"&&(n.detail.headers["Content-Type"]="application/json")},encodeParameters:function(t,n,r){t.overrideMimeType("text/json");let o={};n.forEach(function(i,a){Object.hasOwn(o,a)?(Array.isArray(o[a])||(o[a]=[o[a]]),o[a].push(i)):o[a]=i});let s=e.getExpressionVars(r);return Object.keys(o).forEach(function(i){o[i]=Object.hasOwn(s,i)?s[i]:o[i]}),JSON.stringify(o)}})})();var Q="https://typeahead.waow.tech",ee="https://public.api.bsky.app",ue="/xrpc/app.bsky.actor.searchActorsTypeahead",fe="/xrpc/app.bsky.actor.getProfiles";var de="atcr_recent_handles",te="atcr_recent_profile_cache";var U=class{constructor(t){this.input=t,this.container=t.closest(".sailor-typeahead")||t.parentElement,this.dropdown=null,this.selectedCard=null,this.actors=[],this.currentItems=[],this.mode="hidden",this.focusIndex=-1,this.debounceTimer=null,this.requestSeq=0,this.primaryUnhealthyUntil=0,this.lastPrefetchPrefix="",this.lastPrefetchAt=0,this.createDropdown(),this.bindEvents(),this.input.value.trim().length===0&&this.showRecent()}createDropdown(){this.dropdown=document.createElement("div"),this.dropdown.className="sailor-typeahead-dropdown",this.dropdown.setAttribute("role","listbox"),this.dropdown.style.display="none",this.input.insertAdjacentElement("afterend",this.dropdown)}bindEvents(){this.input.addEventListener("focus",()=>this.handleFocus()),this.input.addEventListener("input",()=>this.handleInput()),this.input.addEventListener("keydown",t=>this.handleKeydown(t)),document.addEventListener("click",t=>{!this.input.contains(t.target)&&!this.dropdown.contains(t.target)&&this.hide()}),document.addEventListener("keydown",t=>{t.key==="Escape"&&this.selectedCard&&this.clearSelection()})}handleFocus(){this.input.value.trim().length===0&&this.showRecent()}handleInput(){let t=this.input.value.trim();if(t.length===0){this.showRecent();return}if(t.length>=2&&t.length<4){this.hide(),this.schedulePrefetch(t);return}if(t.length>=4){this.scheduleSearch(t);return}this.hide()}schedulePrefetch(t){clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>this.runPrefetch(t),150)}scheduleSearch(t){clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>this.runSearch(t),150)}async runPrefetch(t){let n=Date.now();if(!(t===this.lastPrefetchPrefix&&n-this.lastPrefetchAt<1e4)&&!(n<this.primaryUnhealthyUntil)){this.lastPrefetchPrefix=t,this.lastPrefetchAt=n;try{await B(Q,t,400)}catch{this.primaryUnhealthyUntil=Date.now()+6e4}}}async runSearch(t){let n=++this.requestSeq,r=null;if(Date.now()>=this.primaryUnhealthyUntil)try{r=await B(Q,t,1500)}catch{this.primaryUnhealthyUntil=Date.now()+6e4}if(r===null)try{r=await B(ee,t,1500)}catch{r=[]}n===this.requestSeq&&(this.actors=r||[],this.focusIndex=-1,this.renderResults())}renderResults(){if(this.mode="results",this.dropdown.innerHTML="",this.currentItems=[],this.actors.length===0){this.hide();return}this.actors.forEach((t,n)=>{this.currentItems.push(t),this.dropdown.appendChild(this.buildActorRow(t,n))}),this.dropdown.style.display="block"}buildActorRow(t,n){let r=document.createElement("div");r.className="sailor-typeahead-item",r.setAttribute("role","option"),r.dataset.index=String(n),r.dataset.handle=t.handle;let o=document.createElement("div");if(o.className="sailor-typeahead-avatar",t.avatar){let l=document.createElement("img");l.src=t.avatar,l.alt="",l.loading="lazy",o.appendChild(l)}let s=document.createElement("div");s.className="sailor-typeahead-text";let i=t.displayName&&t.displayName!==t.handle;if(i){let l=document.createElement("div");l.className="sailor-typeahead-name",l.textContent=t.displayName,s.appendChild(l)}let a=document.createElement("div");return a.className=i?"sailor-typeahead-handle":"sailor-typeahead-name",a.textContent="@"+t.handle,s.appendChild(a),r.append(o,s),r.addEventListener("mousedown",l=>{l.preventDefault(),this.select(t)}),r}showRecent(){let t=me();if(t.length===0){this.hide();return}this.mode="recent",this.focusIndex=-1,this.renderRecent(t),this.enrichRecent(t)}renderRecent(t){let n=M();this.dropdown.innerHTML="",this.currentItems=[];let r=document.createElement("div");r.className="sailor-typeahead-header",r.textContent="Recent accounts",this.dropdown.appendChild(r),t.forEach((o,s)=>{let i=n[o]?.profile||{handle:o};this.currentItems.push(i),this.dropdown.appendChild(this.buildActorRow(i,s))}),this.dropdown.style.display="block"}async enrichRecent(t){let n=M(),r=Date.now(),o=t.filter(a=>{let l=n[a];return!l||r-l.ts>864e5});if(o.length===0)return;let s=await he(o);if(s.length===0)return;let i=M();s.forEach(a=>{i[a.handle]={ts:r,profile:{handle:a.handle,displayName:a.displayName,avatar:a.avatar}}}),Z(i),this.mode==="recent"&&this.renderRecent(t)}hide(){this.mode="hidden",this.focusIndex=-1,this.dropdown.style.display="none"}select(t){if(typeof t=="string"&&(t={handle:t}),this.input.value=t.handle,this.hide(),this.showSelectedCard(t),t.handle){let n=M();n[t.handle]={ts:Date.now(),profile:{handle:t.handle,displayName:t.displayName,avatar:t.avatar}},Z(n)}}showSelectedCard(t){this.clearSelectedCard();let n=document.createElement("div");n.className="sailor-typeahead-selected";let r=document.createElement("div");if(r.className="sailor-typeahead-avatar",t.avatar){let l=document.createElement("img");l.src=t.avatar,l.alt="",r.appendChild(l)}let o=document.createElement("div");o.className="sailor-typeahead-text";let s=t.displayName&&t.displayName!==t.handle;if(s){let l=document.createElement("div");l.className="sailor-typeahead-name",l.textContent=t.displayName,o.appendChild(l)}let i=document.createElement("div");i.className=s?"sailor-typeahead-handle":"sailor-typeahead-name",i.textContent="@"+t.handle,o.appendChild(i);let a=document.createElement("button");a.type="button",a.className="sailor-typeahead-clear",a.tabIndex=-1,a.setAttribute("aria-label","Change account"),a.innerHTML="&times;",a.addEventListener("click",()=>this.clearSelection()),n.append(r,o,a),this.input.style.display="none",this.input.insertAdjacentElement("beforebegin",n),this.selectedCard=n}clearSelectedCard(){this.selectedCard&&(this.selectedCard.remove(),this.selectedCard=null)}clearSelection(){this.clearSelectedCard(),this.input.style.display="",this.input.value="",this.input.focus(),this.showRecent()}handleKeydown(t){if(this.mode==="hidden")return;let n=this.dropdown.querySelectorAll(".sailor-typeahead-item");n.length!==0&&(t.key==="ArrowDown"?(t.preventDefault(),this.focusIndex=(this.focusIndex+1)%n.length,this.updateFocus(n)):t.key==="ArrowUp"?(t.preventDefault(),this.focusIndex=this.focusIndex<=0?n.length-1:this.focusIndex-1,this.updateFocus(n)):t.key==="Enter"?this.focusIndex>=0&&this.currentItems[this.focusIndex]&&(t.preventDefault(),this.select(this.currentItems[this.focusIndex])):t.key==="Escape"?this.hide():t.key==="Tab"&&this.focusIndex===-1&&n.length>0&&(t.preventDefault(),this.focusIndex=0,this.updateFocus(n)))}updateFocus(t){t.forEach((n,r)=>{n.classList.toggle("focused",r===this.focusIndex),r===this.focusIndex&&n.scrollIntoView({block:"nearest"})})}};async function B(e,t,n){let r=new URL(ue,e);r.searchParams.set("q",t),r.searchParams.set("limit",String(8));let o=new AbortController,s=setTimeout(()=>o.abort(),n);try{let i=await fetch(r,{signal:o.signal});if(!i.ok)throw new Error("HTTP "+i.status);let a=await i.json();return Array.isArray(a.actors)?a.actors:[]}finally{clearTimeout(s)}}async function he(e){if(e.length===0)return[];let t=new URL(fe,ee);e.forEach(o=>t.searchParams.append("actors",o));let n=new AbortController,r=setTimeout(()=>n.abort(),3e3);try{let o=await fetch(t,{signal:n.signal});if(!o.ok)return[];let s=await o.json();return Array.isArray(s.profiles)?s.profiles:[]}catch{return[]}finally{clearTimeout(r)}}function M(){try{return JSON.parse(localStorage.getItem(te)||"{}")}catch{return{}}}function Z(e){try{localStorage.setItem(te,JSON.stringify(e))}catch{}}function me(){try{let e=localStorage.getItem(de);return e?JSON.parse(e):[]}catch{return[]}}document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("handle");e&&new U(e)});function re(){return localStorage.getItem("theme")||"system"}function ge(e){return e==="dark"||e==="light"?e:window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function W(){let e=re(),n=ge(e)==="dark";document.documentElement.classList.toggle("dark",n),document.documentElement.setAttribute("data-theme",n?"dark":"light"),pe(e)}function oe(e){localStorage.setItem("theme",e),W(),Ee()}function pe(e){let t={system:"sun-moon",light:"sun",dark:"moon"};document.querySelectorAll("[data-theme-icon] use").forEach(n=>{n.setAttribute("href",`/icons.svg#${t[e]||"sun-moon"}`)}),document.querySelectorAll(".theme-option").forEach(n=>{let r=n.dataset.value===e,o=n.querySelector(".theme-check");o&&(o.style.visibility=r?"visible":"hidden")})}function Ee(){document.querySelectorAll("[data-theme-toggle]").forEach(e=>{let t=e.closest("details");t&&t.removeAttribute("open")})}window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{re()==="system"&&W()});function ye(){let e=document.querySelector(".nav-search-wrapper"),t=document.getElementById("nav-search-input");!e||!t||(e.classList.toggle("expanded"),e.classList.contains("expanded")&&t.focus())}function V(){let e=document.querySelector(".nav-search-wrapper");e&&e.classList.remove("expanded")}document.addEventListener("DOMContentLoaded",()=>{let e=document.querySelector(".nav-search-wrapper"),t=document.getElementById("nav-search-input");!e||!t||(document.addEventListener("keydown",n=>{if(n.key==="Escape"&&e.classList.contains("expanded")&&V(),n.key==="/"&&!e.classList.contains("expanded")){let r=n.target.tagName;if(r==="INPUT"||r==="TEXTAREA"||n.target.isContentEditable)return;n.preventDefault(),e.classList.add("expanded"),t.focus()}}),document.addEventListener("click",n=>{e.classList.contains("expanded")&&!e.contains(n.target)&&V()}))});function j(e,t){!t&&typeof event<"u"&&(t=event.target.closest("button")),navigator.clipboard.writeText(e).then(()=>{if(!t)return;let n=t.innerHTML;t.innerHTML='<svg class="icon size-4" aria-hidden="true"><use href="/icons.svg#check"></use></svg> Copied!',setTimeout(()=>{t.innerHTML=n},2e3)}).catch(n=>{console.error("Failed to copy:",n)})}function xe(e){let t=s=>{let i=(s==null?"":String(s)).replace(/\s+/g," ").trim();return/[",\n\r]/.test(i)?'"'+i.replace(/"/g,'""')+'"':i},n=s=>Array.from(s).map(i=>t(i.textContent)).join(","),r=[],o=e.querySelector("thead tr");return o&&r.push(n(o.querySelectorAll("th,td"))),e.querySelectorAll("tbody tr").forEach(s=>{r.push(n(s.querySelectorAll("td,th")))}),r.join(` 2 + `)}document.addEventListener("DOMContentLoaded",()=>{document.addEventListener("click",e=>{let t=e.target.closest("button[data-copy-csv]");if(t){let o=t.closest("[data-csv-section]"),s=o&&o.querySelector("table");s&&j(xe(s),t);return}let n=e.target.closest("button[data-cmd]");if(n){j(n.getAttribute("data-cmd"),n);return}if(e.target.closest("a, button, input, .cmd"))return;let r=e.target.closest("[data-href]");r&&(window.location=r.getAttribute("data-href"))})});function ve(e){let t=Math.floor((new Date-new Date(e))/1e3),n={year:31536e3,month:2592e3,week:604800,day:86400,hour:3600,minute:60,second:1};for(let[r,o]of Object.entries(n)){let s=Math.floor(t/o);if(s>=1)return s===1?`1 ${r} ago`:`${s} ${r}s ago`}return"just now"}function X(){document.querySelectorAll("time[datetime]").forEach(e=>{let t=e.getAttribute("datetime");if(t&&!e.dataset.noUpdate){let n=ve(t);e.textContent!==n&&(e.textContent=n)}})}document.addEventListener("DOMContentLoaded",()=>{X(),W(),document.querySelectorAll("[data-theme-menu]").forEach(e=>{e.querySelectorAll(".theme-option").forEach(t=>{t.addEventListener("click",()=>{oe(t.dataset.value)})})}),document.addEventListener("click",e=>{let t=e.target.closest("details.dropdown");document.querySelectorAll("details.dropdown[open]").forEach(n=>{n!==t&&n.removeAttribute("open")})})});document.addEventListener("htmx:afterSwap",X);setInterval(X,6e4);function be(){let e=document.getElementById("show-offline-toggle"),t=document.querySelector(".manifests-list");!e||!t||(localStorage.setItem("showOfflineManifests",e.checked),e.checked?t.classList.add("show-offline"):t.classList.remove("show-offline"))}document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("show-offline-toggle");if(!e)return;let t=localStorage.getItem("showOfflineManifests")==="true";e.checked=t;let n=document.querySelector(".manifests-list");n&&(t?n.classList.add("show-offline"):n.classList.remove("show-offline"))});async function we(e,t,n){try{let r=await fetch("/api/manifests",{method:"DELETE",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({repo:e,digest:t,confirm:!1})});if(r.status===409){let o=await r.json();Ce(e,t,n,o.tags)}else if(r.ok)se(n);else{let o=await r.text();alert(`Failed to delete manifest: ${o}`)}}catch(r){console.error("Error deleting manifest:",r),alert(`Error deleting manifest: ${r.message}`)}}function Ce(e,t,n,r){let o=document.getElementById("manifest-delete-modal"),s=document.getElementById("manifest-delete-tags"),i=document.getElementById("confirm-manifest-delete-btn");s.innerHTML="",r.forEach(a=>{let l=document.createElement("li");l.textContent=a,s.appendChild(l)}),i.onclick=()=>Se(e,t,n),o.style.display="flex"}function $(){let e=document.getElementById("manifest-delete-modal");e.style.display="none"}async function Se(e,t,n){let r=document.getElementById("confirm-manifest-delete-btn"),o=r.textContent;try{r.disabled=!0,r.textContent="Deleting...";let s=await fetch("/api/manifests",{method:"DELETE",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({repo:e,digest:t,confirm:!0})});if(s.ok)$(),se(n),location.reload();else{let i=await s.text();alert(`Failed to delete manifest: ${i}`),r.disabled=!1,r.textContent=o}}catch(s){console.error("Error deleting manifest:",s),alert(`Error deleting manifest: ${s.message}`),r.disabled=!1,r.textContent=o}}async function Te(e){let t=document.getElementById("confirm-untagged-delete-btn"),n=t.textContent;try{t.disabled=!0,t.textContent="Deleting...";let r=await fetch("/api/manifests/untagged",{method:"DELETE",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({repo:e})}),o=await r.json();r.ok?(document.getElementById("untagged-delete-modal").close(),D(`Deleted ${o.deleted} untagged manifest(s)`,"success"),o.deleted>0&&location.reload(),t.disabled=!1,t.textContent=n):(alert(`Failed to delete untagged manifests: ${o.error||"Unknown error"}`),t.disabled=!1,t.textContent=n)}catch(r){console.error("Error deleting untagged manifests:",r),alert(`Error: ${r.message}`),t.disabled=!1,t.textContent=n}}function se(e){let t=document.getElementById(`manifest-${e}`);t&&t.remove()}document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("manifest-delete-modal");e&&e.addEventListener("click",t=>{t.target===e&&$()})});async function Ae(e,t){let n=document.getElementById("vuln-detail-modal"),r=document.getElementById("vuln-modal-body");if(!(!n||!r)){r.innerHTML='<div class="flex justify-center py-8"><span class="loading loading-spinner loading-lg"></span></div>',n.showModal();try{let o=await fetch(`/api/vuln-details?digest=${encodeURIComponent(e)}&holdEndpoint=${encodeURIComponent(t)}`);r.innerHTML=await o.text()}catch{r.innerHTML='<p class="text-error">Failed to load vulnerability details</p>'}}}document.addEventListener("DOMContentLoaded",()=>{let e=document.cookie.split("; ").find(n=>n.startsWith("atcr_login_handle="));if(!e)return;let t=decodeURIComponent(e.split("=")[1]);if(t){try{let n="atcr_recent_handles",r=JSON.parse(localStorage.getItem(n)||"[]");r=r.filter(o=>o!==t),r.unshift(t),r=r.slice(0,5),localStorage.setItem(n,JSON.stringify(r))}catch(n){console.error("Failed to save recent account:",n)}document.cookie="atcr_login_handle=; path=/; max-age=0"}});function ne(){let e=document.getElementById("featured-carousel"),t=document.getElementById("carousel-prev"),n=document.getElementById("carousel-next");if(!e)return;let r=e.querySelectorAll(".carousel-item");if(r.length===0)return;let o=null,s=5e3;function i(){let u=r[0],f=parseFloat(getComputedStyle(e).gap)||24;return u.offsetWidth+f}function a(){let u=e.scrollWidth-e.clientWidth;e.scrollLeft>=u-10?e.scrollTo({left:0,behavior:"smooth"}):e.scrollBy({left:i(),behavior:"smooth"})}function l(){e.scrollLeft<=10?e.scrollTo({left:e.scrollWidth,behavior:"smooth"}):e.scrollBy({left:-i(),behavior:"smooth"})}function c(){o||e.scrollWidth<=e.clientWidth+10||(o=setInterval(a,s))}function d(){o&&(clearInterval(o),o=null)}t&&t.addEventListener("click",()=>{d(),l(),c()}),n&&n.addEventListener("click",()=>{d(),a(),c()}),e.addEventListener("mouseenter",d),e.addEventListener("mouseleave",c),c()}document.addEventListener("DOMContentLoaded",()=>{"requestIdleCallback"in window?requestIdleCallback(ne,{timeout:2e3}):setTimeout(ne,100)});function D(e,t){let n=document.getElementById("toast-container");n||(n=document.createElement("div"),n.id="toast-container",n.className="toast toast-end toast-bottom z-50",document.body.appendChild(n));let r=t==="error"?"alert-error":"alert-success",o=document.createElement("div");o.className=`alert ${r} shadow-lg transition-opacity duration-300`,o.innerHTML=`<span>${e}</span>`,n.appendChild(o),setTimeout(()=>{o.style.opacity="0",setTimeout(()=>o.remove(),300)},3e3)}async function He(e){try{let t=await fetch(`/api/webhooks/${e}/test`,{method:"POST",credentials:"include"}),n=await t.text();n.includes('class="success"')||t.ok&&!n.includes('class="error"')?D("Test webhook delivered successfully!","success"):D("Test delivery failed \u2014 check the webhook URL","error")}catch{D("Failed to reach server","error")}}window.setTheme=oe;window.toggleSearch=ye;window.closeSearch=V;window.copyToClipboard=j;window.toggleOfflineManifests=be;window.deleteManifest=we;window.deleteUntaggedManifests=Te;window.closeManifestDeleteModal=$;window.openVulnDetails=Ae;window.showToast=D;window.testWebhook=He;window.htmx=R;R.config.methodsThatUseUrlParams=["get"];
+1
pkg/appview/routes/routes.go
··· 135 135 router.Get("/api/scan-results", (&uihandlers.BatchScanResultHandler{BaseUIHandler: base}).ServeHTTP) 136 136 router.Get("/api/vuln-details", (&uihandlers.VulnDetailsHandler{BaseUIHandler: base}).ServeHTTP) 137 137 router.Get("/api/sbom-details", (&uihandlers.SbomDetailsHandler{BaseUIHandler: base}).ServeHTTP) 138 + router.Get("/api/scan-download", (&uihandlers.ScanDownloadHandler{BaseUIHandler: base}).ServeHTTP) 138 139 139 140 // Attestation details API endpoint (HTMX modal content) 140 141 router.Get("/api/attestation-details", middleware.OptionalAuth(deps.SessionStore, deps.Database)(
+63 -2
pkg/appview/src/css/main.css
··· 97 97 :root { 98 98 --shadow-card-hover: 99 99 0 8px 25px oklch(67.1% 0.05 145 / 0.25), 0 4px 12px oklch(0% 0 0 / 0.1); 100 + /* Dedicated star/favorite color — semantically distinct from `warning` 101 + even if values currently coincide. Used by .text-star/.fill-star/etc. */ 102 + --color-star: oklch(82% 0.189 84.429); 103 + } 104 + 105 + /* ======================================== 106 + STAR / FAVORITE UTILITIES 107 + Tailwind 4 @utility so the `!` important modifier and 108 + variants (hover:, group-hover:) still work on these classes. 109 + ======================================== */ 110 + @utility text-star { 111 + color: var(--color-star); 112 + } 113 + @utility stroke-star { 114 + stroke: var(--color-star); 115 + } 116 + @utility fill-star { 117 + fill: var(--color-star); 118 + } 119 + @utility border-star { 120 + border-color: var(--color-star); 100 121 } 101 122 102 123 [data-theme="dark"] { ··· 198 219 .icon.size-5 { width: 1.25rem; height: 1.25rem; } 199 220 .icon.size-6 { width: 1.5rem; height: 1.5rem; } 200 221 .icon.size-8 { width: 2rem; height: 2rem; } 222 + .icon.size-10 { width: 2.5rem; height: 2.5rem; } 223 + .icon.size-12 { width: 3rem; height: 3rem; } 224 + .icon.size-16 { width: 4rem; height: 4rem; } 225 + .icon.size-20 { width: 5rem; height: 5rem; } 201 226 202 227 /* Special size for slightly larger than 1rem (used in star/pull count) */ 203 228 .icon.size-\[1\.1rem\] { width: 1.1rem; height: 1.1rem; } ··· 431 456 } 432 457 433 458 /* ---------------------------------------- 459 + RESPONSIVE TABLE — STACK ON MOBILE 460 + Below md, each row becomes a labeled block. 461 + Add data-label="..." to each <td>. 462 + ---------------------------------------- */ 463 + @media (max-width: 47.999rem) { 464 + .table-stack-mobile thead { 465 + @apply sr-only; 466 + } 467 + .table-stack-mobile, 468 + .table-stack-mobile tbody, 469 + .table-stack-mobile tr, 470 + .table-stack-mobile td { 471 + display: block; 472 + width: 100%; 473 + } 474 + .table-stack-mobile tr { 475 + @apply border border-base-300 rounded-md mb-3 p-3 bg-base-100; 476 + } 477 + .table-stack-mobile tr:nth-child(even) { 478 + @apply bg-base-200; 479 + } 480 + .table-stack-mobile td { 481 + @apply py-1; 482 + border: 0; 483 + } 484 + .table-stack-mobile td::before { 485 + content: attr(data-label); 486 + @apply block text-xs font-semibold uppercase tracking-wide text-base-content/60 mb-0.5; 487 + } 488 + .table-stack-mobile td[data-label=""]::before, 489 + .table-stack-mobile td:not([data-label])::before { 490 + content: none; 491 + } 492 + } 493 + 494 + /* ---------------------------------------- 434 495 VULNERABILITY SEVERITY BOX STRIP 435 496 Docker Hub-style connected severity boxes 436 497 ---------------------------------------- */ ··· 442 503 } 443 504 .vuln-strip > span:first-child { @apply rounded-l-sm; } 444 505 .vuln-strip > span:last-child { @apply rounded-r-sm; } 445 - .vuln-box-critical { background-color: oklch(45% 0.16 20); color: oklch(97% 0.01 20); } 446 - .vuln-box-high { background-color: oklch(58% 0.18 35); color: oklch(97% 0.01 35); } 506 + .vuln-box-critical { background-color: oklch(45% 0.19 25); color: oklch(97% 0.01 25); } 507 + .vuln-box-high { background-color: oklch(56% 0.19 50); color: oklch(97% 0.01 50); } 447 508 .vuln-box-medium { background-color: oklch(72% 0.15 70); color: oklch(25% 0.05 70); } 448 509 .vuln-box-low { background-color: oklch(80% 0.1 85); color: oklch(25% 0.05 85); } 449 510 }
+48 -66
pkg/appview/src/js/app.js
··· 130 130 }); 131 131 } 132 132 133 + // Serialize a <table> (thead + tbody) as CSV (RFC 4180 quoting) 134 + function tableToCSV(table) { 135 + const escape = (s) => { 136 + const v = (s == null ? '' : String(s)).replace(/\s+/g, ' ').trim(); 137 + return /[",\n\r]/.test(v) ? '"' + v.replace(/"/g, '""') + '"' : v; 138 + }; 139 + const rowToCsv = (cells) => Array.from(cells).map((c) => escape(c.textContent)).join(','); 140 + const lines = []; 141 + const headRow = table.querySelector('thead tr'); 142 + if (headRow) lines.push(rowToCsv(headRow.querySelectorAll('th,td'))); 143 + table.querySelectorAll('tbody tr').forEach((tr) => { 144 + lines.push(rowToCsv(tr.querySelectorAll('td,th'))); 145 + }); 146 + return lines.join('\n'); 147 + } 148 + 133 149 // Initialize copy buttons with data-cmd attribute and clickable cards with data-href 134 150 document.addEventListener('DOMContentLoaded', () => { 135 151 document.addEventListener('click', (e) => { 152 + // Handle copy-as-CSV buttons (for vulnerability / SBOM tables) 153 + const csvBtn = e.target.closest('button[data-copy-csv]'); 154 + if (csvBtn) { 155 + const section = csvBtn.closest('[data-csv-section]'); 156 + const table = section && section.querySelector('table'); 157 + if (table) copyToClipboard(tableToCSV(table), csvBtn); 158 + return; 159 + } 160 + 136 161 // Handle copy buttons 137 162 const btn = e.target.closest('button[data-cmd]'); 138 163 if (btn) { ··· 453 478 } 454 479 }); 455 480 456 - // Featured carousel - scroll-based with proper wrap-around 457 - // Deferred initialization to avoid blocking main thread during page load 481 + // Featured carousel - relies on DaisyUI's native scroll-snap (.carousel + 482 + // .carousel-item already set scroll-snap-type: x mandatory and snap-start). 483 + // JS only handles next/prev buttons, auto-advance, and wrap-around. The 484 + // browser handles all snapping precision after scrollBy(). 458 485 function initFeaturedCarousel() { 459 486 const carousel = document.getElementById('featured-carousel'); 460 487 const prevBtn = document.getElementById('carousel-prev'); 461 488 const nextBtn = document.getElementById('carousel-next'); 462 489 if (!carousel) return; 463 490 464 - const items = Array.from(carousel.querySelectorAll('.carousel-item')); 491 + const items = carousel.querySelectorAll('.carousel-item'); 465 492 if (items.length === 0) return; 466 493 467 494 let intervalId = null; 468 495 const intervalMs = 5000; 469 496 470 - // Cache dimensions to avoid forced reflow on every navigation 471 - let cachedItemWidth = 0; 472 - let cachedContainerWidth = 0; 473 - let cachedScrollWidth = 0; 474 - 475 - function updateCachedDimensions() { 476 - const item = items[0]; 477 - if (!item) return; 478 - // Batch all geometric reads together to minimize reflow 479 - const style = getComputedStyle(carousel); 480 - const gap = parseFloat(style.gap) || 24; 481 - cachedItemWidth = item.offsetWidth + gap; 482 - cachedContainerWidth = carousel.offsetWidth; 483 - cachedScrollWidth = carousel.scrollWidth; 484 - } 485 - 486 - // Initial measurement after layout is stable (double-rAF) 487 - requestAnimationFrame(() => { 488 - requestAnimationFrame(() => { 489 - updateCachedDimensions(); 490 - startInterval(); 491 - }); 492 - }); 493 - 494 - // Update on resize (debounced to avoid excessive recalculation) 495 - let resizeTimeout; 496 - window.addEventListener('resize', () => { 497 - clearTimeout(resizeTimeout); 498 - resizeTimeout = setTimeout(updateCachedDimensions, 150); 499 - }); 500 - 501 - function getItemWidth() { 502 - if (!cachedItemWidth) updateCachedDimensions(); 503 - return cachedItemWidth; 504 - } 505 - 506 - function getVisibleCount() { 507 - if (!cachedContainerWidth || !cachedItemWidth) updateCachedDimensions(); 508 - return Math.round(cachedContainerWidth / cachedItemWidth) || 1; 509 - } 510 - 511 - function getMaxScroll() { 512 - if (!cachedScrollWidth || !cachedContainerWidth) updateCachedDimensions(); 513 - return cachedScrollWidth - cachedContainerWidth; 497 + // Read on demand — at most once per click or per 5s autoplay tick. 498 + function step() { 499 + const first = items[0]; 500 + const gap = parseFloat(getComputedStyle(carousel).gap) || 24; 501 + return first.offsetWidth + gap; 514 502 } 515 503 516 504 function advance() { 517 - const itemWidth = getItemWidth(); 518 - const maxScroll = getMaxScroll(); 519 - const currentScroll = carousel.scrollLeft; 520 - 521 - // If we're at or near the end, wrap to start 522 - if (currentScroll >= maxScroll - 10) { 505 + const max = carousel.scrollWidth - carousel.clientWidth; 506 + if (carousel.scrollLeft >= max - 10) { 523 507 carousel.scrollTo({ left: 0, behavior: 'smooth' }); 524 508 } else { 525 - carousel.scrollTo({ left: currentScroll + itemWidth, behavior: 'smooth' }); 509 + carousel.scrollBy({ left: step(), behavior: 'smooth' }); 526 510 } 527 511 } 528 512 529 513 function retreat() { 530 - const itemWidth = getItemWidth(); 531 - const maxScroll = getMaxScroll(); 532 - const currentScroll = carousel.scrollLeft; 533 - 534 - // If we're at or near the start, wrap to end 535 - if (currentScroll <= 10) { 536 - carousel.scrollTo({ left: maxScroll, behavior: 'smooth' }); 514 + if (carousel.scrollLeft <= 10) { 515 + carousel.scrollTo({ left: carousel.scrollWidth, behavior: 'smooth' }); 537 516 } else { 538 - carousel.scrollTo({ left: currentScroll - itemWidth, behavior: 'smooth' }); 517 + carousel.scrollBy({ left: -step(), behavior: 'smooth' }); 539 518 } 540 519 } 541 520 542 - if (prevBtn) prevBtn.addEventListener('click', () => { stopInterval(); retreat(); startInterval(); }); 543 - if (nextBtn) nextBtn.addEventListener('click', () => { stopInterval(); advance(); startInterval(); }); 544 - 545 521 function startInterval() { 546 - if (intervalId || items.length <= getVisibleCount()) return; 522 + if (intervalId) return; 523 + if (carousel.scrollWidth <= carousel.clientWidth + 10) return; 547 524 intervalId = setInterval(advance, intervalMs); 548 525 } 549 526 ··· 551 528 if (intervalId) { clearInterval(intervalId); intervalId = null; } 552 529 } 553 530 531 + if (prevBtn) prevBtn.addEventListener('click', () => { stopInterval(); retreat(); startInterval(); }); 532 + if (nextBtn) nextBtn.addEventListener('click', () => { stopInterval(); advance(); startInterval(); }); 533 + 554 534 carousel.addEventListener('mouseenter', stopInterval); 555 535 carousel.addEventListener('mouseleave', startInterval); 536 + 537 + startInterval(); 556 538 } 557 539 558 540 // Defer carousel setup to after critical path
+1 -1
pkg/appview/templates/components/nav-user.html
··· 5 5 <div class="avatar{{ if not .User.Avatar }} avatar-placeholder{{ end }}"> 6 6 {{ if .User.Avatar }} 7 7 <div class="w-7 rounded-full"> 8 - <img src="{{ resizeImage .User.Avatar 96 }}" alt="{{ .User.Handle }}" /> 8 + <img src="{{ resizeImage .User.Avatar 96 }}" alt="{{ .User.Handle }}" loading="lazy" /> 9 9 </div> 10 10 {{ else }} 11 11 <div class="bg-secondary text-secondary-content w-7 rounded-full">
+2 -2
pkg/appview/templates/components/repo-avatar.html
··· 6 6 */}} 7 7 <div id="repo-avatar" class="relative shrink-0"> 8 8 {{ if .IconURL }} 9 - <img src="{{ resizeImage .IconURL 96 }}" alt="{{ .RepositoryName }}" class="w-20 rounded-lg object-cover"> 9 + <img src="{{ resizeImage .IconURL 96 }}" alt="{{ .RepositoryName }}" loading="lazy" class="w-20 rounded-lg object-cover"> 10 10 {{ else }} 11 11 <div class="avatar avatar-placeholder"> 12 12 <div class="bg-neutral text-neutral-content w-20 rounded-lg shadow-sm uppercase"> ··· 16 16 {{ end }} 17 17 {{ if .IsOwner }} 18 18 <label class="absolute inset-0 flex items-center justify-center bg-black/50 opacity-0 hover:opacity-100 transition-opacity cursor-pointer rounded-lg" for="avatar-upload" aria-label="Upload repository icon"> 19 - {{ icon "plus" "size-8 text-white" }} 19 + {{ icon "plus" "size-8 text-neutral-content" }} 20 20 </label> 21 21 <input type="hidden" id="avatar-repo" name="repo" value="{{ .RepositoryName }}"> 22 22 <input type="file" id="avatar-upload" name="avatar"
+3 -3
pkg/appview/templates/components/star.html
··· 8 8 Display mode: renders as span (default) 9 9 */}} 10 10 {{ if .Interactive }} 11 - <button class="btn btn-sm gap-2 btn-ghost group border border-transparent hover:border-primary{{ if .IsStarred }} border-amber-400!{{ end }}" 11 + <button class="btn btn-sm gap-2 btn-ghost group border border-transparent hover:border-primary{{ if .IsStarred }} border-star!{{ end }}" 12 12 id="star-btn" 13 13 hx-ext="json-enc" 14 14 {{ if .IsStarred }} ··· 21 21 hx-on::before-request="this.disabled=true" 22 22 hx-on::after-request="if(event.detail.xhr.status===401) window.location='/auth/oauth/login'" 23 23 aria-label="{{ if .IsStarred }}Unstar{{ else }}Star{{ end }} {{ .Handle }}/{{ .Repository }}"> 24 - <svg class="icon size-4 text-amber-400 stroke-amber-400 transition-transform group-hover:scale-110{{ if .IsStarred }} fill-amber-400!{{ end }}" id="star-icon" aria-hidden="true"><use href="/icons.svg#star"></use></svg> 24 + <svg class="icon size-4 text-star stroke-star transition-transform group-hover:scale-110{{ if .IsStarred }} fill-star!{{ end }}" id="star-icon" aria-hidden="true"><use href="/icons.svg#star"></use></svg> 25 25 <span id="star-count">{{ .StarCount }}</span> 26 26 </button> 27 27 {{ else }} 28 28 <span class="flex items-center gap-2 text-base-content/60"> 29 - <svg class="icon size-[1.1rem] text-amber-400 stroke-amber-400{{ if .IsStarred }} fill-amber-400!{{ end }}" aria-hidden="true"><use href="/icons.svg#star"></use></svg> 29 + <svg class="icon size-[1.1rem] text-star stroke-star{{ if .IsStarred }} fill-star!{{ end }}" aria-hidden="true"><use href="/icons.svg#star"></use></svg> 30 30 <span class="font-semibold text-base-content">{{ .StarCount }}</span> 31 31 </span> 32 32 {{ end }}
+1 -1
pkg/appview/templates/pages/install.html
··· 64 64 65 65 <div class="alert alert-info"> 66 66 {{ icon "info" "size-5" }} 67 - <span>If you have an existing <code class="bg-black/10 dark:bg-white/10 px-1 py-0.5 rounded text-sm font-mono">config.json</code>, add the <code class="bg-black/10 dark:bg-white/10 px-1 py-0.5 rounded text-sm font-mono">credHelpers</code> entry to your existing configuration.</span> 67 + <span>If you have an existing <code class="bg-base-300 px-1 py-0.5 rounded text-sm font-mono">config.json</code>, add the <code class="bg-base-300 px-1 py-0.5 rounded text-sm font-mono">credHelpers</code> entry to your existing configuration.</span> 68 68 </div> 69 69 </section> 70 70
+72 -76
pkg/appview/templates/pages/privacy.html
··· 176 176 <p>We will not discriminate against you for exercising your CCPA rights.</p> 177 177 178 178 <h3 class="text-lg font-medium mt-6">Categories of Personal Information Collected</h3> 179 - <div class="overflow-x-auto mt-4"> 180 - <table class="table table-zebra w-full"> 181 - <thead> 182 - <tr> 183 - <th>Category</th> 184 - <th>Examples</th> 185 - <th>Collected</th> 186 - </tr> 187 - </thead> 188 - <tbody> 189 - <tr> 190 - <td>Identifiers</td> 191 - <td>DID, handle, IP address, device name</td> 192 - <td>Yes</td> 193 - </tr> 194 - <tr> 195 - <td>Internet activity</td> 196 - <td>Access logs, usage data, actions performed</td> 197 - <td>Yes</td> 198 - </tr> 199 - <tr> 200 - <td>Geolocation</td> 201 - <td>Approximate location via IP</td> 202 - <td>Yes</td> 203 - </tr> 204 - </tbody> 205 - </table> 206 - </div> 179 + <table class="table table-zebra w-full mt-4 table-stack-mobile"> 180 + <thead> 181 + <tr> 182 + <th>Category</th> 183 + <th>Examples</th> 184 + <th>Collected</th> 185 + </tr> 186 + </thead> 187 + <tbody> 188 + <tr> 189 + <td data-label="Category">Identifiers</td> 190 + <td data-label="Examples">DID, handle, IP address, device name</td> 191 + <td data-label="Collected">Yes</td> 192 + </tr> 193 + <tr> 194 + <td data-label="Category">Internet activity</td> 195 + <td data-label="Examples">Access logs, usage data, actions performed</td> 196 + <td data-label="Collected">Yes</td> 197 + </tr> 198 + <tr> 199 + <td data-label="Category">Geolocation</td> 200 + <td data-label="Examples">Approximate location via IP</td> 201 + <td data-label="Collected">Yes</td> 202 + </tr> 203 + </tbody> 204 + </table> 207 205 208 206 <p class="mt-4">We do not sell or share your personal information for cross-context behavioral advertising.</p> 209 207 </section> ··· 211 209 <section> 212 210 <h2 class="text-xl font-semibold text-primary">Data Retention</h2> 213 211 214 - <div class="overflow-x-auto mt-4"> 215 - <table class="table table-zebra w-full"> 216 - <thead> 217 - <tr> 218 - <th>Data Type</th> 219 - <th>Service</th> 220 - <th>Retention Period</th> 221 - </tr> 222 - </thead> 223 - <tbody> 224 - <tr> 225 - <td>OAuth tokens</td> 226 - <td>AppView</td> 227 - <td>Until revoked or logout</td> 228 - </tr> 229 - <tr> 230 - <td>Web UI session tokens</td> 231 - <td>AppView</td> 232 - <td>Until logout or expiration</td> 233 - </tr> 234 - <tr> 235 - <td>Device tokens (credential helper)</td> 236 - <td>AppView</td> 237 - <td>Until revoked by user</td> 238 - </tr> 239 - <tr> 240 - <td>Cached PDS data</td> 241 - <td>AppView</td> 242 - <td>Refreshed periodically; deleted on account deletion</td> 243 - </tr> 244 - <tr> 245 - <td>Server logs</td> 246 - <td>AppView</td> 247 - <td>Currently ephemeral; this policy will be updated if log retention is implemented</td> 248 - </tr> 249 - <tr> 250 - <td>Layer records</td> 251 - <td>Hold PDS</td> 252 - <td>Until you request deletion</td> 253 - </tr> 254 - <tr> 255 - <td>OCI blobs</td> 256 - <td>Hold Storage</td> 257 - <td>Until no longer referenced (pruned within 30 days)</td> 258 - </tr> 259 - </tbody> 260 - </table> 261 - </div> 212 + <table class="table table-zebra w-full mt-4 table-stack-mobile"> 213 + <thead> 214 + <tr> 215 + <th>Data Type</th> 216 + <th>Service</th> 217 + <th>Retention Period</th> 218 + </tr> 219 + </thead> 220 + <tbody> 221 + <tr> 222 + <td data-label="Data Type">OAuth tokens</td> 223 + <td data-label="Service">AppView</td> 224 + <td data-label="Retention">Until revoked or logout</td> 225 + </tr> 226 + <tr> 227 + <td data-label="Data Type">Web UI session tokens</td> 228 + <td data-label="Service">AppView</td> 229 + <td data-label="Retention">Until logout or expiration</td> 230 + </tr> 231 + <tr> 232 + <td data-label="Data Type">Device tokens (credential helper)</td> 233 + <td data-label="Service">AppView</td> 234 + <td data-label="Retention">Until revoked by user</td> 235 + </tr> 236 + <tr> 237 + <td data-label="Data Type">Cached PDS data</td> 238 + <td data-label="Service">AppView</td> 239 + <td data-label="Retention">Refreshed periodically; deleted on account deletion</td> 240 + </tr> 241 + <tr> 242 + <td data-label="Data Type">Server logs</td> 243 + <td data-label="Service">AppView</td> 244 + <td data-label="Retention">Currently ephemeral; this policy will be updated if log retention is implemented</td> 245 + </tr> 246 + <tr> 247 + <td data-label="Data Type">Layer records</td> 248 + <td data-label="Service">Hold PDS</td> 249 + <td data-label="Retention">Until you request deletion</td> 250 + </tr> 251 + <tr> 252 + <td data-label="Data Type">OCI blobs</td> 253 + <td data-label="Service">Hold Storage</td> 254 + <td data-label="Retention">Until no longer referenced (pruned within 30 days)</td> 255 + </tr> 256 + </tbody> 257 + </table> 262 258 </section> 263 259 264 260 <section>
+34 -9
pkg/appview/templates/pages/repository.html
··· 82 82 {{ if .SelectedTag }} 83 83 <div class="flex flex-wrap items-center gap-3"> 84 84 <div class="flex items-center gap-2"> 85 - {{ icon "tag" "size-4 text-base-content/60" }} 85 + {{ icon "tag" "size-6 text-base-content/60" }} 86 86 <select id="tag-selector" class="select select-sm select-bordered font-mono" 87 87 hx-get="/r/{{ .Owner.Handle }}/{{ .Repository.Name }}" 88 88 hx-target="#tag-content" ··· 94 94 <option value="{{ . }}"{{ if eq . $.SelectedTag.Info.Tag.Tag }} selected{{ end }}>{{ . }}</option> 95 95 {{ end }} 96 96 </select> 97 + {{ if gt (len .AllTags) 1 }} 98 + <div class="dropdown dropdown-end" id="diff-dropdown"> 99 + <label tabindex="0" class="btn btn-ghost btn-sm gap-1" title="Compare with another tag"> 100 + {{ icon "git-compare" "size-4" }} Diff 101 + </label> 102 + <ul tabindex="0" class="dropdown-content menu bg-base-200 rounded-box z-10 w-56 p-2 shadow max-h-60 overflow-y-auto"> 103 + {{ range .AllTags }} 104 + <li><a href="#" data-diff-to="{{ . }}" onclick="diffToTag(event, this)">{{ . }}</a></li> 105 + {{ end }} 106 + </ul> 107 + </div> 108 + {{ end }} 97 109 </div> 98 110 {{ if .SelectedTag.Info.IsMultiArch }} 99 111 <div id="platform-badges" class="flex flex-wrap items-center gap-1"> ··· 145 157 <div id="editor-write" class="editor-panel"> 146 158 <!-- Toolbar --> 147 159 <div class="flex flex-wrap gap-1 mb-2 p-1 bg-base-200 rounded-lg"> 148 - <button type="button" class="btn btn-ghost btn-xs" onclick="insertMd('heading')" title="Heading"> 160 + <button type="button" class="btn btn-ghost btn-sm md:btn-xs" onclick="insertMd('heading')" title="Heading"> 149 161 {{ icon "heading" "size-4" }} 150 162 </button> 151 - <button type="button" class="btn btn-ghost btn-xs" onclick="insertMd('bold')" title="Bold"> 163 + <button type="button" class="btn btn-ghost btn-sm md:btn-xs" onclick="insertMd('bold')" title="Bold"> 152 164 {{ icon "bold" "size-4" }} 153 165 </button> 154 - <button type="button" class="btn btn-ghost btn-xs" onclick="insertMd('italic')" title="Italic"> 166 + <button type="button" class="btn btn-ghost btn-sm md:btn-xs" onclick="insertMd('italic')" title="Italic"> 155 167 {{ icon "italic" "size-4" }} 156 168 </button> 157 169 <div class="divider divider-horizontal mx-0"></div> 158 - <button type="button" class="btn btn-ghost btn-xs" onclick="insertMd('link')" title="Link"> 170 + <button type="button" class="btn btn-ghost btn-sm md:btn-xs" onclick="insertMd('link')" title="Link"> 159 171 {{ icon "link" "size-4" }} 160 172 </button> 161 - <button type="button" class="btn btn-ghost btn-xs" onclick="insertMd('image')" title="Image"> 173 + <button type="button" class="btn btn-ghost btn-sm md:btn-xs" onclick="insertMd('image')" title="Image"> 162 174 {{ icon "image" "size-4" }} 163 175 </button> 164 176 <div class="divider divider-horizontal mx-0"></div> 165 - <button type="button" class="btn btn-ghost btn-xs" onclick="insertMd('ul')" title="Bulleted list"> 177 + <button type="button" class="btn btn-ghost btn-sm md:btn-xs" onclick="insertMd('ul')" title="Bulleted list"> 166 178 {{ icon "list" "size-4" }} 167 179 </button> 168 - <button type="button" class="btn btn-ghost btn-xs" onclick="insertMd('ol')" title="Numbered list"> 180 + <button type="button" class="btn btn-ghost btn-sm md:btn-xs" onclick="insertMd('ol')" title="Numbered list"> 169 181 {{ icon "list-ordered" "size-4" }} 170 182 </button> 171 - <button type="button" class="btn btn-ghost btn-xs" onclick="insertMd('code')" title="Code"> 183 + <button type="button" class="btn btn-ghost btn-sm md:btn-xs" onclick="insertMd('code')" title="Code"> 172 184 {{ icon "code" "size-4" }} 173 185 </button> 174 186 </div> ··· 461 473 if (!el) return null; 462 474 return '/api/repo-tags/' + el.dataset.owner + '/' + el.dataset.repo; 463 475 } 476 + 477 + window.diffToTag = function(e, link) { 478 + e.preventDefault(); 479 + var to = link.dataset.diffTo; 480 + var content = document.getElementById('tag-content'); 481 + var selector = document.getElementById('tag-selector'); 482 + if (!content || !selector || !to) return; 483 + var fromDigest = content.dataset.digest; 484 + var currentTag = selector.value; 485 + if (!fromDigest || to === currentTag) return; 486 + window.location.href = '/diff/' + content.dataset.owner + '/' + content.dataset.repo + 487 + '?from=' + encodeURIComponent(fromDigest) + '&to=' + encodeURIComponent(to); 488 + }; 464 489 465 490 window.switchRepoTab = function(tabId) { 466 491 window._activeRepoTab = tabId;
+64 -39
pkg/appview/templates/pages/settings.html
··· 18 18 </div> 19 19 20 20 <!-- Mobile tab bar (below lg) --> 21 - <div class="flex gap-2 overflow-x-auto pb-2 lg:hidden mb-6"> 22 - <button class="btn btn-sm btn-ghost settings-tab-mobile" data-tab="user"> 21 + <div class="flex gap-2 overflow-x-auto pb-2 lg:hidden mb-6" role="tablist" aria-label="Settings sections" aria-orientation="horizontal"> 22 + <button class="btn btn-sm btn-ghost settings-tab-mobile" data-tab="user" role="tab" id="tab-mobile-user" aria-controls="tab-user" aria-selected="false" tabindex="-1"> 23 23 {{ icon "user" "size-4" }} User 24 24 </button> 25 - <button class="btn btn-sm btn-ghost settings-tab-mobile" data-tab="billing"> 25 + <button class="btn btn-sm btn-ghost settings-tab-mobile" data-tab="billing" role="tab" id="tab-mobile-billing" aria-controls="tab-billing" aria-selected="false" tabindex="-1"> 26 26 {{ icon "credit-card" "size-4" }} Billing 27 27 </button> 28 - <button class="btn btn-sm btn-ghost settings-tab-mobile" data-tab="storage"> 28 + <button class="btn btn-sm btn-ghost settings-tab-mobile" data-tab="storage" role="tab" id="tab-mobile-storage" aria-controls="tab-storage" aria-selected="false" tabindex="-1"> 29 29 {{ icon "hard-drive" "size-4" }} Storage 30 30 </button> 31 - <button class="btn btn-sm btn-ghost settings-tab-mobile" data-tab="devices"> 31 + <button class="btn btn-sm btn-ghost settings-tab-mobile" data-tab="devices" role="tab" id="tab-mobile-devices" aria-controls="tab-devices" aria-selected="false" tabindex="-1"> 32 32 {{ icon "terminal" "size-4" }} Devices 33 33 </button> 34 - <button class="btn btn-sm btn-ghost settings-tab-mobile" data-tab="webhooks"> 34 + <button class="btn btn-sm btn-ghost settings-tab-mobile" data-tab="webhooks" role="tab" id="tab-mobile-webhooks" aria-controls="tab-webhooks" aria-selected="false" tabindex="-1"> 35 35 {{ icon "webhook" "size-4" }} Webhooks 36 36 </button> 37 - <button class="btn btn-sm btn-ghost settings-tab-mobile" data-tab="advanced"> 37 + <button class="btn btn-sm btn-ghost settings-tab-mobile" data-tab="advanced" role="tab" id="tab-mobile-advanced" aria-controls="tab-advanced" aria-selected="false" tabindex="-1"> 38 38 {{ icon "shield-check" "size-4" }} Advanced 39 39 </button> 40 40 </div> ··· 42 42 <div class="flex gap-8"> 43 43 <!-- Sidebar (lg and above) --> 44 44 <aside class="hidden lg:block w-56 shrink-0"> 45 - <ul class="menu bg-base-200 rounded-box w-full"> 46 - <li data-tab="user"><a href="#user">{{ icon "user" "size-4" }} User</a></li> 47 - <li data-tab="billing"><a href="#billing">{{ icon "credit-card" "size-4" }} Billing</a></li> 48 - <li data-tab="storage"><a href="#storage">{{ icon "hard-drive" "size-4" }} Storage</a></li> 49 - <li data-tab="devices"><a href="#devices">{{ icon "terminal" "size-4" }} Devices</a></li> 50 - <li data-tab="webhooks"><a href="#webhooks">{{ icon "webhook" "size-4" }} Webhooks</a></li> 51 - <li data-tab="advanced"><a href="#advanced">{{ icon "shield-check" "size-4" }} Advanced</a></li> 45 + <ul class="menu bg-base-200 rounded-box w-full" role="tablist" aria-label="Settings sections" aria-orientation="vertical"> 46 + <li data-tab="user" role="none"><a href="#user" role="tab" id="tab-desktop-user" aria-controls="tab-user" aria-selected="false" tabindex="-1">{{ icon "user" "size-4" }} User</a></li> 47 + <li data-tab="billing" role="none"><a href="#billing" role="tab" id="tab-desktop-billing" aria-controls="tab-billing" aria-selected="false" tabindex="-1">{{ icon "credit-card" "size-4" }} Billing</a></li> 48 + <li data-tab="storage" role="none"><a href="#storage" role="tab" id="tab-desktop-storage" aria-controls="tab-storage" aria-selected="false" tabindex="-1">{{ icon "hard-drive" "size-4" }} Storage</a></li> 49 + <li data-tab="devices" role="none"><a href="#devices" role="tab" id="tab-desktop-devices" aria-controls="tab-devices" aria-selected="false" tabindex="-1">{{ icon "terminal" "size-4" }} Devices</a></li> 50 + <li data-tab="webhooks" role="none"><a href="#webhooks" role="tab" id="tab-desktop-webhooks" aria-controls="tab-webhooks" aria-selected="false" tabindex="-1">{{ icon "webhook" "size-4" }} Webhooks</a></li> 51 + <li data-tab="advanced" role="none"><a href="#advanced" role="tab" id="tab-desktop-advanced" aria-controls="tab-advanced" aria-selected="false" tabindex="-1">{{ icon "shield-check" "size-4" }} Advanced</a></li> 52 52 </ul> 53 53 <div class="mt-4 px-2 space-y-1 text-xs text-base-content/50"> 54 54 <div class="break-all"><code>{{ .Profile.DID }}</code></div> ··· 60 60 <div class="flex-1 min-w-0"> 61 61 62 62 <!-- USER TAB --> 63 - <div id="tab-user" class="settings-panel hidden space-y-6"> 63 + <div id="tab-user" class="settings-panel hidden space-y-6" role="tabpanel" aria-labelledby="tab-desktop-user" tabindex="0"> 64 64 <section class="card bg-base-100 shadow-sm p-6 space-y-6"> 65 65 <div> 66 66 <h2 class="text-xl font-semibold">Preferences</h2> ··· 115 115 </div> 116 116 117 117 <!-- STORAGE TAB --> 118 - <div id="tab-storage" class="settings-panel hidden space-y-4"> 118 + <div id="tab-storage" class="settings-panel hidden space-y-4" role="tabpanel" aria-labelledby="tab-desktop-storage" tabindex="0"> 119 119 <!-- Holds --> 120 120 {{ if .AllHolds }} 121 121 <div class="grid grid-cols-1 lg:grid-cols-2 gap-4"> ··· 162 162 </div> 163 163 164 164 <!-- BILLING TAB --> 165 - <div id="tab-billing" class="settings-panel hidden space-y-4"> 165 + <div id="tab-billing" class="settings-panel hidden space-y-4" role="tabpanel" aria-labelledby="tab-desktop-billing" tabindex="0"> 166 166 {{ template "subscription_plans" .Subscription }} 167 167 </div> 168 168 169 169 <!-- DEVICES TAB --> 170 - <div id="tab-devices" class="settings-panel hidden space-y-6"> 170 + <div id="tab-devices" class="settings-panel hidden space-y-6" role="tabpanel" aria-labelledby="tab-desktop-devices" tabindex="0"> 171 171 <section class="card bg-base-100 shadow-sm p-6 space-y-6"> 172 172 <div> 173 173 <h2 class="text-xl font-semibold">Authorized Devices</h2> ··· 227 227 </div> 228 228 229 229 <!-- WEBHOOKS TAB --> 230 - <div id="tab-webhooks" class="settings-panel hidden space-y-6"> 230 + <div id="tab-webhooks" class="settings-panel hidden space-y-6" role="tabpanel" aria-labelledby="tab-desktop-webhooks" tabindex="0"> 231 231 <section class="card bg-base-100 shadow-sm p-6 space-y-4"> 232 232 <div> 233 233 <h2 class="text-xl font-semibold">Webhooks</h2> ··· 240 240 </div> 241 241 242 242 <!-- ADVANCED TAB --> 243 - <div id="tab-advanced" class="settings-panel hidden space-y-6"> 243 + <div id="tab-advanced" class="settings-panel hidden space-y-6" role="tabpanel" aria-labelledby="tab-desktop-advanced" tabindex="0"> 244 244 <!-- Data Privacy Section --> 245 245 <section class="card bg-base-100 shadow-sm p-6 space-y-4"> 246 246 <h2 class="text-xl font-semibold">Data Privacy</h2> ··· 305 305 // OCI client dropdown 306 306 // Tab switching 307 307 (function() { 308 - var validTabs = ['user', 'storage', 'devices', 'webhooks', 'advanced']; 308 + var validTabs = ['user', 'billing', 'storage', 'devices', 'webhooks', 'advanced']; 309 309 310 310 function switchSettingsTab(tabId) { 311 311 // Hide all panels ··· 316 316 var panel = document.getElementById('tab-' + tabId); 317 317 if (panel) panel.classList.remove('hidden'); 318 318 319 - // Sidebar: toggle menu-active on <li> elements 319 + // Sidebar: toggle menu-active + aria-selected + tabindex 320 320 document.querySelectorAll('.menu li[data-tab]').forEach(function(li) { 321 - if (li.dataset.tab === tabId) { 322 - li.classList.add('menu-active'); 323 - } else { 324 - li.classList.remove('menu-active'); 321 + var active = li.dataset.tab === tabId; 322 + li.classList.toggle('menu-active', active); 323 + var a = li.querySelector('a[role="tab"]'); 324 + if (a) { 325 + a.setAttribute('aria-selected', active ? 'true' : 'false'); 326 + a.setAttribute('tabindex', active ? '0' : '-1'); 325 327 } 326 328 }); 327 329 328 - // Mobile: toggle btn-secondary/btn-ghost on buttons 330 + // Mobile: toggle btn styles + aria-selected + tabindex 329 331 document.querySelectorAll('.settings-tab-mobile').forEach(function(btn) { 330 - if (btn.dataset.tab === tabId) { 331 - btn.classList.remove('btn-ghost'); 332 - btn.classList.add('btn-secondary'); 333 - } else { 334 - btn.classList.remove('btn-secondary'); 335 - btn.classList.add('btn-ghost'); 336 - } 332 + var active = btn.dataset.tab === tabId; 333 + btn.classList.toggle('btn-ghost', !active); 334 + btn.classList.toggle('btn-secondary', active); 335 + btn.setAttribute('aria-selected', active ? 'true' : 'false'); 336 + btn.setAttribute('tabindex', active ? '0' : '-1'); 337 337 }); 338 338 339 339 // Update URL hash without adding history entry ··· 348 348 return panel && !panel.classList.contains('hidden'); 349 349 }; 350 350 351 + // Roving tab navigation (arrow keys, Home, End) per WAI-ARIA tabs pattern 352 + function handleTabKeydown(tabs, orientation) { 353 + return function(e) { 354 + var prevKey = orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'; 355 + var nextKey = orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight'; 356 + var idx = tabs.indexOf(e.currentTarget); 357 + if (idx === -1) return; 358 + var target = null; 359 + if (e.key === prevKey) target = tabs[(idx - 1 + tabs.length) % tabs.length]; 360 + else if (e.key === nextKey) target = tabs[(idx + 1) % tabs.length]; 361 + else if (e.key === 'Home') target = tabs[0]; 362 + else if (e.key === 'End') target = tabs[tabs.length - 1]; 363 + if (!target) return; 364 + e.preventDefault(); 365 + switchSettingsTab(target.dataset.tab || target.parentElement.dataset.tab); 366 + target.focus(); 367 + }; 368 + } 369 + 351 370 document.addEventListener('DOMContentLoaded', function() { 352 371 // Read initial tab from hash 353 372 var hash = window.location.hash.replace('#', '') || 'user'; 354 - if (validTabs.indexOf(hash) === -1) hash = 'storage'; 373 + if (validTabs.indexOf(hash) === -1) hash = 'user'; 355 374 356 - // Mobile tab click handlers 357 - document.querySelectorAll('.settings-tab-mobile').forEach(function(btn) { 375 + // Mobile tab click + keyboard handlers 376 + var mobileTabs = Array.prototype.slice.call(document.querySelectorAll('.settings-tab-mobile')); 377 + var mobileKeydown = handleTabKeydown(mobileTabs, 'horizontal'); 378 + mobileTabs.forEach(function(btn) { 358 379 btn.addEventListener('click', function(e) { 359 380 e.preventDefault(); 360 381 switchSettingsTab(this.dataset.tab); 361 382 }); 383 + btn.addEventListener('keydown', mobileKeydown); 362 384 }); 363 385 364 - // Sidebar tab click handlers 365 - document.querySelectorAll('.menu li[data-tab] a').forEach(function(link) { 386 + // Sidebar tab click + keyboard handlers 387 + var sidebarTabs = Array.prototype.slice.call(document.querySelectorAll('.menu li[data-tab] a[role="tab"]')); 388 + var sidebarKeydown = handleTabKeydown(sidebarTabs, 'vertical'); 389 + sidebarTabs.forEach(function(link) { 366 390 link.addEventListener('click', function(e) { 367 391 e.preventDefault(); 368 392 switchSettingsTab(this.parentElement.dataset.tab); 369 393 }); 394 + link.addEventListener('keydown', sidebarKeydown); 370 395 }); 371 396 372 397 // Activate initial tab
+1 -1
pkg/appview/templates/pages/user.html
··· 15 15 {{ if .ViewedUser.Avatar }} 16 16 <div class="avatar"> 17 17 <div class="w-20 rounded-full shadow"> 18 - <img src="{{ .ViewedUser.Avatar }}" alt="{{ .ViewedUser.Handle }}" /> 18 + <img src="{{ .ViewedUser.Avatar }}" alt="{{ .ViewedUser.Handle }}" loading="lazy" /> 19 19 </div> 20 20 </div> 21 21 {{ else if .HasProfile }}
+1 -1
pkg/appview/templates/partials/other_holds_table.html
··· 26 26 <td class="text-center"> 27 27 {{ if eq .Status "online" }}<span class="text-success" title="Online">&#9679;</span> 28 28 {{ else if eq .Status "offline" }}<span class="text-error" title="Offline">&#9679;</span> 29 - {{ else }}<span class="text-base-content/30" title="Unknown">&#9679;</span> 29 + {{ else }}<span class="text-base-content/50" title="Unknown" aria-label="Unknown status">&#9679;</span> 30 30 {{ end }} 31 31 </td> 32 32 <td class="text-right">
+8 -24
pkg/appview/templates/partials/repo-tag-section.html
··· 73 73 74 74 <!-- Tab Navigation --> 75 75 <div class="border-b border-base-300 mt-6"> 76 - <nav class="flex gap-0" role="tablist"> 77 - <button class="repo-tab px-6 py-3 text-sm font-medium border-b-2 border-transparent text-base-content/60 transition-colors cursor-pointer" 76 + <nav class="flex gap-0 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden" role="tablist"> 77 + <button class="repo-tab shrink-0 whitespace-nowrap px-4 sm:px-6 py-3 text-sm font-medium border-b-2 border-transparent text-base-content/60 transition-colors cursor-pointer" 78 78 data-tab="overview" 79 79 role="tab" 80 80 onclick="switchRepoTab('overview')"> 81 81 Overview 82 82 </button> 83 83 {{ if .SelectedTag }} 84 - <button class="repo-tab px-6 py-3 text-sm font-medium border-b-2 border-transparent text-base-content/60 transition-colors cursor-pointer" 84 + <button class="repo-tab shrink-0 whitespace-nowrap px-4 sm:px-6 py-3 text-sm font-medium border-b-2 border-transparent text-base-content/60 transition-colors cursor-pointer" 85 85 data-tab="layers" 86 86 role="tab" 87 87 id="layers-tab-btn" 88 88 onclick="switchRepoTab('layers')"> 89 89 Layers 90 90 </button> 91 - <button class="repo-tab px-6 py-3 text-sm font-medium border-b-2 border-transparent text-base-content/60 transition-colors cursor-pointer" 91 + <button class="repo-tab shrink-0 whitespace-nowrap px-4 sm:px-6 py-3 text-sm font-medium border-b-2 border-transparent text-base-content/60 transition-colors cursor-pointer" 92 92 data-tab="vulns" 93 93 role="tab" 94 94 id="vulns-tab-btn" 95 95 onclick="switchRepoTab('vulns')"> 96 96 Vulnerabilities 97 97 </button> 98 - <button class="repo-tab px-6 py-3 text-sm font-medium border-b-2 border-transparent text-base-content/60 transition-colors cursor-pointer" 98 + <button class="repo-tab shrink-0 whitespace-nowrap px-4 sm:px-6 py-3 text-sm font-medium border-b-2 border-transparent text-base-content/60 transition-colors cursor-pointer" 99 99 data-tab="sbom" 100 100 role="tab" 101 101 id="sbom-tab-btn" ··· 103 103 SBOM 104 104 </button> 105 105 {{ end }} 106 - <button class="repo-tab px-6 py-3 text-sm font-medium border-b-2 border-transparent text-base-content/60 transition-colors cursor-pointer" 106 + <button class="repo-tab shrink-0 whitespace-nowrap px-4 sm:px-6 py-3 text-sm font-medium border-b-2 border-transparent text-base-content/60 transition-colors cursor-pointer" 107 107 data-tab="artifacts" 108 108 role="tab" 109 109 id="artifacts-tab-btn" 110 110 onclick="switchRepoTab('artifacts')"> 111 111 Artifacts 112 112 </button> 113 - {{ if and .SelectedTag (gt (len .AllTags) 1) }} 114 - <div class="ml-auto flex items-center"> 115 - <div class="dropdown dropdown-end"> 116 - <label tabindex="0" class="btn btn-ghost btn-sm gap-1"> 117 - {{ icon "git-compare" "size-4" }} Diff 118 - </label> 119 - <ul tabindex="0" class="dropdown-content menu bg-base-200 rounded-box z-10 w-56 p-2 shadow max-h-60 overflow-y-auto"> 120 - {{ range .AllTags }} 121 - {{ if ne . $.SelectedTag.Info.Tag.Tag }} 122 - <li><a href="/diff/{{ $.Owner.Handle }}/{{ $.Repository.Name }}?from={{ $.SelectedTag.Info.Digest }}&to={{ . }}">{{ . }}</a></li> 123 - {{ end }} 124 - {{ end }} 125 - </ul> 126 - </div> 127 - </div> 128 - {{ end }} 129 113 </nav> 130 114 </div> 131 115 ··· 165 149 {{ else }} 166 150 {{ if .IsOwner }} 167 151 <div class="text-center py-12"> 168 - {{ icon "file-text" "size-12 text-base-content/20 mx-auto" }} 152 + {{ icon "file-text" "size-12 text-base-content opacity-40 mx-auto" }} 169 153 <p class="text-base-content/60 mt-4">No README provided</p> 170 154 <p class="text-base-content/40 text-sm mt-1">Add a README to help users understand this image.</p> 171 155 <button class="btn btn-primary btn-sm mt-4" onclick="toggleOverviewEditor(true)"> ··· 174 158 </div> 175 159 {{ else }} 176 160 <div class="text-center py-12"> 177 - {{ icon "file-text" "size-12 text-base-content/20 mx-auto" }} 161 + {{ icon "file-text" "size-12 text-base-content opacity-40 mx-auto" }} 178 162 <p class="text-base-content/60 mt-4">No README provided</p> 179 163 <p class="text-base-content/40 text-sm mt-1">Image metadata is shown above.</p> 180 164 </div>
+13 -1
pkg/appview/templates/partials/sbom-details.html
··· 3 3 <p>{{ .Error }}</p> 4 4 {{ if .ScannedAt }}<p class="text-xs opacity-60">Scanned: {{ .ScannedAt }}</p>{{ end }} 5 5 {{ else }} 6 - <div class="space-y-4"> 6 + <div class="space-y-4" data-csv-section data-csv-filename="sbom.csv"> 7 7 <div class="flex flex-wrap items-center gap-3"> 8 8 <span class="font-semibold text-sm">{{ .Total }} packages</span> 9 + {{ if .Packages }} 10 + <details class="dropdown dropdown-end ml-auto"> 11 + <summary class="btn btn-ghost btn-xs gap-1 list-none" aria-label="Export SBOM"> 12 + {{ icon "download" "size-3.5" }} 13 + {{ icon "chevron-down" "size-3" }} 14 + </summary> 15 + <ul class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-lg border border-base-300"> 16 + <li><button type="button" data-copy-csv>{{ icon "copy" "size-4" }} Copy as CSV</button></li> 17 + <li><a href="/api/scan-download?type=sbom&digest={{ .Digest | urlquery }}&holdEndpoint={{ .HoldEndpoint | urlquery }}" download>{{ icon "download" "size-4" }} SPDX JSON</a></li> 18 + </ul> 19 + </details> 20 + {{ end }} 9 21 </div> 10 22 11 23 {{ if .ScannedAt }}<p class="text-xs opacity-60">Scanned: {{ .ScannedAt }}</p>{{ end }}
+13 -1
pkg/appview/templates/partials/vuln-details.html
··· 16 16 <p>{{ .Error }}</p> 17 17 {{ end }} 18 18 {{ else }} 19 - <div class="space-y-4"> 19 + <div class="space-y-4" data-csv-section data-csv-filename="vulnerabilities.csv"> 20 20 <!-- Summary badges --> 21 21 <div class="flex flex-wrap items-center gap-3"> 22 22 <span class="font-semibold text-sm">{{ .Summary.Total }} vulnerabilities</span> ··· 26 26 <span class="tooltip vuln-box-medium" data-tip="Medium">{{ .Summary.Medium }}</span> 27 27 <span class="tooltip vuln-box-low" data-tip="Low">{{ .Summary.Low }}</span> 28 28 </span> 29 + {{ if .Matches }} 30 + <details class="dropdown dropdown-end ml-auto"> 31 + <summary class="btn btn-ghost btn-xs gap-1 list-none" aria-label="Export vulnerabilities"> 32 + {{ icon "download" "size-3.5" }} 33 + {{ icon "chevron-down" "size-3" }} 34 + </summary> 35 + <ul class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-lg border border-base-300"> 36 + <li><button type="button" data-copy-csv>{{ icon "copy" "size-4" }} Copy as CSV</button></li> 37 + <li><a href="/api/scan-download?type=vuln&digest={{ .Digest | urlquery }}&holdEndpoint={{ .HoldEndpoint | urlquery }}" download>{{ icon "download" "size-4" }} Grype Report</a></li> 38 + </ul> 39 + </details> 40 + {{ end }} 29 41 </div> 30 42 31 43 {{ if .ScannedAt }}<p class="text-xs opacity-60">Scanned: {{ .ScannedAt }}</p>{{ end }}
+3 -3
scanner/internal/scan/grype.go
··· 41 41 42 42 // vulnDBRefreshAge is how long a cached DB is considered fresh. 43 43 // Set 1 day before the 5-day MaxAllowedBuiltAge so we refresh proactively. 44 - const vulnDBRefreshAge = 4 * 24 * time.Hour 44 + const vulnDBRefreshAge = 7 * 24 * time.Hour 45 45 46 46 // scanVulnerabilities scans an SBOM for vulnerabilities using Grype 47 47 func scanVulnerabilities(ctx context.Context, s *sbom.SBOM, vulnDBPath string) ([]byte, string, scanner.VulnerabilitySummary, error) { ··· 166 166 DBRootDir: vulnDBPath, 167 167 ValidateAge: true, 168 168 ValidateChecksum: true, 169 - MaxAllowedBuiltAge: 5 * 24 * time.Hour, // 5 days 169 + MaxAllowedBuiltAge: 14 * 24 * time.Hour, // 2 weeks — tolerates upstream publish gaps 170 170 } 171 171 172 172 // Try loading existing DB first (no network) ··· 233 233 DBRootDir: vulnDBPath, 234 234 ValidateAge: true, 235 235 ValidateChecksum: true, 236 - MaxAllowedBuiltAge: 5 * 24 * time.Hour, 236 + MaxAllowedBuiltAge: 14 * 24 * time.Hour, 237 237 } 238 238 239 239 downloader, err := distribution.NewClient(distConfig)