From 715e26a9eb219808d5c899d418e1d596f9318f61 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Mon, 22 Jun 2026 16:59:19 +0000
Subject: [PATCH] =Debugging and implementing the Checkout.js logic. Will be uploading to test integration with square next.

---
 assets/js/min/navigation.min.js          |    2 
 build/menu/style-index-rtl.css           |    2 
 assets/js/min/dragHandler.min.js         |    2 
 assets/js/min/modal.min.js               |    2 
 inc/helpers/ui.php                       |   30 
 inc/registrar/Terms.php                  |    1 
 assets/js/min/square.min.js              |    2 
 inc/meta/Form.php                        |   61 +
 assets/js/min/form.min.js                |    2 
 inc/managers/DashboardManager.php        |    1 
 assets/js/min/referral.min.js            |    2 
 assets/js/min/queue.min.js               |    2 
 assets/js/min/tabs.min.js                |    2 
 inc/blocks/CustomBlocks.php              |    2 
 assets/js/concise/SimpleCache.js         |   38 
 assets/js/concise/Checkout.js            |  422 +++++++++---
 assets/js/min/favourites.min.js          |    2 
 assets/js/min/utility.min.js             |    2 
 inc/ui/Checkout.php                      |   50 +
 assets/js/min/notifications.min.js       |    2 
 assets/js/min/schema.min.js              |    2 
 inc/managers/LoginManager.php            |    2 
 inc/integrations/Square.php              |   83 +-
 assets/js/min/cache.min.js               |    2 
 assets/js/min/gallery.min.js             |    2 
 inc/ui/CRUDSkeleton.php                  |    4 
 assets/js/concise/Popup.js               |    2 
 assets/js/concise/Tabs.js                |   14 
 inc/helpers/time.php                     |   43 
 assets/js/min/selector.min.js            |    2 
 assets/js/min/page-nav.min.js            |    2 
 assets/js/concise/FormController.js      |  197 +++++
 assets/js/min/handleSelection.min.js     |    2 
 inc/blocks/FeedBlock.php                 |  698 ++++++++++----------
 cleanup.php                              |    8 
 inc/registrar/Registrar.php              |   30 
 assets/js/min/crud.min.js                |    2 
 inc/managers/queue/Processor.php         |    2 
 package-lock.json                        |   16 
 assets/js/min/news.min.js                |    2 
 assets/css/forms.min.css                 |    2 
 assets/js/min/helcim.min.js              |    2 
 inc/registrar/Posts.php                  |   37 +
 assets/js/min/popup.min.js               |    2 
 assets/js/min/integrations.min.js        |    2 
 assets/js/min/quill.min.js               |    2 
 assets/js/min/settings.min.js            |    2 
 src/menu/style.scss                      |   21 
 assets/js/min/hours.min.js               |    2 
 assets/js/min/populate.min.js            |    2 
 inc/managers/queue/Queue.php             |    2 
 assets/js/min/auth.min.js                |    2 
 build/menu/style-index.css               |    2 
 inc/blocks/MenuBlock.php                 |   92 +-
 assets/js/min/error.min.js               |    2 
 checks.php                               |    5 
 assets/css/nav.min.css                   |    2 
 assets/js/concise/CopyHours.js           |   35 
 assets/js/min/maps.min.js                |    2 
 assets/js/min/uploader.min.js            |    2 
 inc/managers/ScriptLoader.php            |    2 
 assets/js/min/referralAdmin.min.js       |    2 
 assets/js/min/view.min.js                |    2 
 assets/js/min/creator.min.js             |    2 
 build/feed/view.asset.php                |    2 
 assets/js/min/ContentManager.min.js      |    2 
 assets/js/min/favouritesManager.min.js   |    2 
 base/options.php                         |   10 
 assets/js/min/notificationManager.min.js |    2 
 assets/js/min/checkout.min.js            |    2 
 assets/js/min/dataStore.min.js           |    2 
 build/feed/view.js                       |    2 
 inc/blocks/FormBlock.php                 |    9 
 assets/js/min/a11y.min.js                |    2 
 74 files changed, 1,297 insertions(+), 710 deletions(-)

diff --git a/assets/css/forms.min.css b/assets/css/forms.min.css
index af24019..6c816f4 100644
--- a/assets/css/forms.min.css
+++ b/assets/css/forms.min.css
@@ -1 +1 @@
-.field{width:100%;margin:.5em 0;padding:.5em 0}.field+.field{border-top:1px solid rgb(var(--base-200))}.field .wrapper{width:100%;position:relative}.field .validation{flex-shrink:0;max-width:0;transition:var(--trans-size)}.field.has-error .validation.error,.field.has-success .validation.success{max-width:var(--btn)}.field.has-error .error{color:rgb(var(--error))}.field.has-error input,.field.has-error select,.field.has-error textarea{border-color:rgb(var(--error));background-color:var(--errorBack)}.field.has-error input:focus,.field.has-error select:focus,.field.has-error textarea:focus{outline-color:rgb(var(--error));box-shadow:rgba(var(--error),.2) var(--shdw)}.field.has-success .success{color:var(--success)}.validation-message{color:var(--errorText);font-size:var(--txt-small);margin-top:.25rem;display:block}.field[data-field=post_status] .wrapper{--justify:flex-start}.field[data-field=post_status] .btn+label{width:var(--chipchip);min-height:var(--chipchip);padding:0}.field[data-field=post_status] .btn+label:hover,.field[data-field=post_status] .btn:focus+label{color:rgb(var(--action-contrast))}.date-wrapper{position:relative;display:inline-block}input[type=date]{padding:8px 36px 8px 8px;border-radius:4px}input[type=date]::-webkit-calendar-picker-indicator{opacity:0;width:100%;height:100%;position:absolute;top:0;left:0;cursor:pointer}input[type=date]+.icon{--w:20px;position:absolute;right:10px;top:50%;transform:translateY(-50%);pointer-events:none}input:is([type=time],[type=datetime-local],[type=date]){padding:.5rem;border:1px solid rgb(var(--contrast-200));border-radius:4px;font-size:14px;min-width:180px;background:rgb(var(--base));color:rgb(var(--contrast));cursor:pointer}.date-wrapper input[type=date]:focus,.datetime-wrapper input[type=datetime-local]:focus,.time-wrapper input[type=time]:focus,.wrapper input:is([type=time],[type=datetime-local],[type=date]):focus{border-color:rgb(var(--action-0));box-shadow:0 0 0 2px rgba(var(--action-0),.1)}.date-wrapper .icon,.datetime-wrapper .icon,.time-wrapper .icon,.wrapper .icon{width:18px;height:18px;background-color:rgb(var(--contrast));opacity:.7}.quantity{margin:0;display:inline-flex;width:fit-content;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;position:relative}.quantity:focus-within{border-color:rgb(var(--action-0))}.quantity label{margin:0;font-size:var(--txt-small)}.quantity button{background:rgb(var(--base));padding:0;width:var(--chip_);height:var(--chip_);min-height:0;z-index:0;position:relative;border:1px solid rgb(var(--base-200));color:rgb(var(--contrast-200))}.quantity button:hover:not(:disabled){color:rgb(var(--action-0));border-color:rgb(var(--action-0));background-color:rgb(var(--base))}.quantity button:active:not(:disabled){background-color:rgb(var(--action-0));color:rgb(var(--light-0));transform:scale(.95)}.quantity button:disabled{opacity:.5;cursor:not-allowed}.quantity input[type=number]{z-index:1;border:1px solid rgb(var(--base-200));background:rgb(var(--base));text-align:center;font-size:1.1rem;width:60px;height:48px;margin:0;padding:0!important;appearance:textfield}.quantity input[type=number]::-webkit-inner-spin-button,.quantity input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.quantity input[type=number]:focus{background-color:rgb(var(--base-50))}.quantity button.increase{left:-2px;border-radius:0 4px 4px 0}.quantity button.decrease{right:-2px;border-radius:4px 0 0 4px}details.uploader .file-upload-container{margin:1rem 0;max-width:100%}.field.upload{position:relative}.field.upload .progress{display:none}.field.upload.uploading .progress{display:block}.field.upload .actions{position:absolute;top:0;right:0}.empty-group,.file-upload-wrapper,.preview-wrap .item-grid{border:2px dashed rgb(var(--action-0));border-radius:4px;padding:2rem;text-align:center;transition:all .3s ease;background:rgba(var(--action-0),var(--op-1));position:relative;cursor:pointer;user-select:none}.file-upload-wrapper{max-width:var(--content);margin:1rem auto}.file-upload-wrapper h2{margin:0;font-size:var(--txt-large)}.dragover,.empty-group:hover,.file-upload-wrapper:hover,.preview-wrap .item-grid:hover{background:rgba(var(--action-0),var(--op-2));border-color:rgb(var(--action-0))}.preview-wrap:has(.item-grid:empty) .selection-controls{display:none}.preview-wrap .item-grid{min-height:20vh}.preview-wrap .item-grid:empty::before{content:'Unsorted images become their own posts.';display:block}.file-upload-wrapper input[type=file]{position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;cursor:pointer}.empty-group p,.file-upload-text{margin:0}.empty-group p strong,.file-upload-text strong{color:rgb(var(--action-0));text-decoration:underline}.item-grid.groups{grid-template-columns:repeat(1,1fr)}.item-grid.group{margin-bottom:0}.item-grid:is(.restore,.group,.preview) .item{display:block;--w:1.1em}.item-grid:is(.restore,.group,.preview) button{padding:.25rem .5rem}.item-grid:is(.restore,.group,.preview) .preview>input[type=checkbox]:not(.label-button)+label{padding-left:0;margin:0}.item-grid:is(.restore,.group,.preview) .item .item-actions{position:absolute;top:0;right:0;left:var(--chipchip)}.item-grid:is(.restore,.group,.preview) summary{padding:.5rem}.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked){padding:.5rem;background-color:rgba(var(--action-0),var(--op-4));opacity:1}.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked) img{filter:none}.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked) .item img{filter:var(--filter)}.item-grid.preview summary span,.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked) details{display:none}[type=radio].featured:checked+label .icon-star,[type=radio].featured:not(:checked)+label .icon-star-fi{display:none}[type=radio].featured:checked+label .icon-star-fi,[type=radio].featured:not(:checked)+label .icon-star{display:inline-block}.item:is(.restore,.upload){border-radius:var(--radius);aspect-ratio:unset;overflow:hidden;background:rgb(var(--base));border:1px solid rgb(var(--base-200))}.item:is(.restore,.upload) [for=select-item]{aspect-ratio:1}.item.upload:has(details[open]){grid-column:1/-1;padding:.5rem 10%;margin:1rem 0;background-color:transparent;border:2px dashed rgb(var(--action-200))}.item.upload:has(details[open]) details[open]{background-color:transparent}.item:is(.restore,.upload) img{transform:scale(1);transition:transform var(--trans-base)}.item:is(.upload,.restore):hover img{transform:scale(1.02)}.upload-group{padding:5px;border-radius:var(--radius);background-color:rgba(var(--action-0),var(--op-1))}.upload-group .selected .field{margin:0}.upload-group .selection-actions button{aspect-ratio:unset}.submit-uploads{position:fixed;bottom:0;left:var(--btn_);z-index:var(--z-6);height:var(--btn);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);border-radius:var(--radius);animation:pulse-color 5s infinite;animation-delay:1s;background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.submit-uploads:hover{background-color:rgb(var(--base-200));color:rgb(var(--contrast-200))}.empty-group{order:-1;grid-column:1/-1;padding:20px;border-radius:var(--radius);margin:10px 0;cursor:pointer;transition:all var(--trans-base);text-align:center;background-color:rgba(var(--action-0),var(--op-1))}.group-display:not([hidden])~.file-upload-container{display:none}.dragging,.upload.item.dragging{opacity:.7;transform:scale(.95) rotate(3deg);z-index:var(--z-7);box-shadow:0 8px 25px rgba(var(--contrast),var(--op-2))}.dragover{background:rgba(var(--action-0),var(--op-3))!important;border-color:rgb(var(--action-0))!important;transform:scale(1.05);animation:drop-pulse .8s infinite ease-in-out}.drag-preview{position:fixed;z-index:var(--z-9);width:fit-content;overflow:visible;pointer-events:none;opacity:.9;transform:scale(1.05);transition:transform .2s ease}.drag-preview .drag-items{width:max-content;height:max-content;position:relative}.drag-preview .drag-items .dragi-item{width:120px;height:120px;position:absolute;top:0;left:0;background:rgb(var(--base));border-radius:var(--radius-outer);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.drag-preview .drag-items .drag-item:nth-child(1){transform:rotate(-3deg);z-index:3}.drag-preview .drag-items .drag-item:nth-child(2){left:8px;top:-4px;transform:rotate(4deg);z-index:2;transition-delay:30ms}.drag-preview .drag-items .drag-item:nth-child(3){left:-6px;top:-8px;transform:rotate(-5deg);z-index:1;transition-delay:60ms}.drag-preview .drag-items .drag-item:nth-child(4){left:12px;top:-12px;transform:rotate(3deg);z-index:0;transition-delay:90ms}.drag-preview .drag-items .drag-item:nth-child(n+5){left:-10px;top:-16px;transform:rotate(-4deg);z-index:0;opacity:.8}.drag-preview .drag-items img,.drag-preview .drag-items video{width:100%;height:100%;object-fit:cover;display:block}.drag-preview .drag-count{position:absolute;top:-8px;right:-8px;background:rgb(var(--base-200));color:rgb(var(--contrast));border-radius:50%;width:24px;height:24px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-3)}.item.dragging{opacity:.5;transform:scale(.95);filter:grayscale(50%);transition:opacity .2s ease,transform .2s ease,filter .2s ease}@keyframes drop-pulse{0%,100%{background-color:rgba(var(--action-0),var(--op-3));transform:scale(1.02)}50%{background-color:var(rgba(var(--action-0),var(--op-4)));transform:scale(1.04)}}.selection-actions{display:flex;gap:.25rem}@media (max-width:767px){body:not(.uploading):has(.group-display:not([hidden])){overflow:hidden}body:not(.uploading):has(.group-display:not([hidden])) .qtoggle{z-index:var(--z-1)}.group-display.group-display{position:fixed;top:var(--btn);bottom:var(--btn);left:0;right:0;max-height:var(--maxHeight);overflow:hidden;z-index:var(--z-6);width:calc(100% - 1rem);height:calc(100% - 1rem);padding:0 0 3rem;--justify:flex-start;--align:flex-start;--gap:0}.group-display::before{content:'';display:block;z-index:-1;top:-.5rem;bottom:-.5rem;left:-.5rem;right:-.5rem;position:absolute;background-color:rgba(var(--base),var(--op-6));filter:blur(5px)}.group-display .preview-wrap,.group-display .sidebar{--wrap:nowrap;height:50%;overflow:hidden auto;position:relative;padding:.5rem}.group-display .preview-wrap{top:0}.group-display .preview-wrap .selected{display:flex;justify-content:space-between;align-items:center}.group-display .sidebar{bottom:0;flex-wrap:nowrap;overflow:hidden auto;background-color:rgb(var(--contrast-200));color:rgb(var(--base))}.group-display .sidebar>.hint{color:rgb(var(--contrast))}.group-display .sidebar .header{display:none}.group-display .preview-actions{top:0;flex-shrink:0}.group-display .preview-wrap>.hint,.group-display .sidebar>.hint{bottom:0;margin:0;text-align:center}.group-display .preview-actions,.group-display .preview-wrap>.hint,.group-display .sidebar>.hint{position:absolute;left:0;right:0;background-color:rgba(var(--base),var(--op-6));z-index:var(--z-3);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.group-display .item-grid{height:100%;overflow:hidden auto;grid-template-columns:repeat(3,1fr);padding:2rem 0}.group-display .sidebar>.item-grid{grid-template-columns:repeat(1,1fr);gap:1rem;padding:0}.group-display .sidebar .empty-group{order:0;position:sticky;height:fit-content;top:0;z-index:var(--z-3);background-color:rgba(var(--action-0),var(--op-6))}.group-display .sidebar .upload-group{order:1}.group-display .sidebar .empty-group p{margin:0}.group-display .field,.group-display .field label{margin:0;padding:0}.group-display .sidebar h4{margin:.25rem}.group-display .item{width:100%;height:max-content}.submit-uploads{bottom:var(--btn);left:0;right:0;width:100%;height:3rem}body.uploading .group-display.group-display{position:relative;top:unset;bottom:unset;right:unset;left:unset}}@media (min-width:768px){.group-display.group-display{--wrap:nowrap;--dir:row;--gap:1rem;--align:flex-start}.group-display .preview-wrap,.group-display .sidebar{--justify:flex-start;--wrap:nowrap;max-height:calc(100vh - var(--btnbtn));overflow:hidden auto}.group-display .preview-wrap,.group-display .sidebar{width:50%}.preview-actions,.preview-wrap .hint{position:sticky;z-index:var(--z-3);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);background-color:rgb(var(--base));width:100%}.preview-actions{top:0;left:0;right:0}.preview-actions .field{margin:0}.preview-wrap .hint,.sidebar>.hint{bottom:-1rem;padding-bottom:1rem;margin:0;left:0;right:0;text-align:center}}.item-grid.restore{grid-template-columns:repeat(1,1fr)}.editor-container .ql-toolbar{display:flex;background-color:rgb(var(--base-50));justify-content:flex-start;flex-wrap:wrap;padding:.25rem;gap:.5rem 1rem;border-top-left-radius:var(--radius);border-top-right-radius:var(--radius);border-bottom:4px solid rgb(var(--base-50))}.ql-toolbar button{min-height:0;padding:.5rem}.ql-toolbar .ql-formats{display:flex;gap:.25rem}.editor-container .ql-container{--padding:1rem;background-color:rgb(var(--base));border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius);height:fit-content;padding:2px;border:1px solid rgb(var(--base-200))}.editor-container .ql-container .ql-editor{padding:var(--padding);width:100%;height:100%;max-width:90vw}.ql-editor img{max-width:50%;height:auto}.ql-clipboard{left:-100000px;height:1px;overflow-y:hidden;position:absolute;top:50%}.ql-hidden{display:none}.ql-tooltip{position:absolute;transform:translateY(10px);background-color:rgb(var(--base-100));border:1px solid rgb(var(--base));box-shadow:0 0 5px rgba(var(--base),var(--op-6));color:rgb(var(--contrast));padding:5px 12px;white-space:nowrap}[data-type=single] .item-grid{display:flex}.repeater-row details summary::after{margin-left:0}.repeater-row details summary button{margin-left:auto}.repeater .field-input-wrapper{flex-direction:column}.repeater .repeater-items{width:100%}.add-repeater-row,.remove-row{margin-left:auto;min-height:0;height:var(--chipchip);background-color:rgb(var(--action-0))}.field.tag-list .row{margin-bottom:1rem}.field.tag-list .row .field{flex:1;min-width:150px;margin:0}.field.tag-list .tag .add-tag-item{flex-shrink:0;white-space:nowrap;margin-top:calc(var(--txt-medium) + 1rem)}.field.tag-list .tag-items{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:1rem;min-height:2rem}.field.tag-list .tag-item{background:rgb(var(--base-200));padding:.4rem .75rem;border-radius:4px;display:inline-flex;align-items:center;gap:.5rem;font-size:.9rem;line-height:1.2}.field.tag-list .tag-item:hover{background:rgb(var(--base-100))}.field.tag-list .tag-label{max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field.tag-list .remove-tag{min-height:0;padding:.25rem;color:rgb(var(--contrast));transition:transform .2s;box-shadow:none}.field.tag-list .remove-tag:hover{transform:scale(1.2)}@media (max-width:768px){.field.tag-list .tag{flex-direction:column;align-items:stretch}.field.tag-list .tag .field{min-width:100%}}.form-progress{padding:0 1rem}.form-progress .progress{background:rgb(var(--base-100));border-radius:var(--radius);padding:1rem}.form-progress .bar{height:6px;background:rgb(var(--base-200));border-radius:3px;overflow:hidden;margin-bottom:.5rem}.form-progress .fill{height:100%;background:linear-gradient(90deg,rgb(var(--action-0)),rgb(var(--action-200)));width:0%;transition:width .4s ease;border-radius:3px}.form-progress .step-text{font-size:var(--txt-small);font-weight:600;color:rgb(var(--contrast-200))}form nav.tabs{position:relative;top:0;left:0;right:0;padding:1rem 0;gap:0;z-index:0}form nav.tabs button{position:relative;background:0 0;border:none;padding:.5rem 1rem .5rem 3rem;z-index:1}form nav.tabs .step-number{width:2.5rem;height:100%;border-radius:50% 0 0 50%;position:absolute;left:0;top:0;background:rgb(var(--base-200));color:rgb(var(--contrast-50));display:flex;align-items:center;justify-content:center;font-weight:700;font-size:var(--txt-small);border:3px solid rgb(var(--base))}form nav.tabs button.pending .step-number{background:rgb(var(--base-100));color:rgb(var(--contrast-200))}form nav.tabs button.active .step-number,form nav.tabs button.current .step-number{background:rgb(var(--action-0));color:rgb(var(--action-contrast));border-color:rgb(var(--action-200))}form nav.tabs button.completed .step-number{background:var(--successBack);color:var(--successBack);border-color:var(--successText)}form nav.tabs button.completed .step-number::before{content:'✓';font-size:1.2rem;color:var(--successText);position:absolute}form nav.tabs button.completed h2{color:rgb(var(--contrast-200))}.step-navigation{margin-top:2rem;padding-top:2rem;border-top:1px solid rgb(var(--base-200));gap:1rem}.step-navigation .prev-step{background:rgb(var(--base-100))}.step-navigation .next-step,.step-navigation button[type=submit]{margin-left:auto}@media (max-width:768px){form nav.tabs button{min-width:80px;font-size:var(--txt-small)}form nav.tabs button h2{font-size:var(--txt-small)}form{--step-size:2rem}}.field input.error,.field select.error,.field textarea.error{border-color:var(--errorBack)}.error-message{color:var(--errorText);font-size:var(--txt-small);margin-top:.25rem;display:block}.form-summary{padding:2rem;border-radius:8px;margin-top:2rem;border:2px dashed rgb(var(--contrast-200))}.form-summary .message{margin-bottom:2rem}.form-summary .result+.result{position:relative;margin-top:1.5rem;padding-top:1.5rem}.form-summary .result+.result::before{position:absolute;top:0;left:16.5%;content:'';width:67%;height:1px;border-bottom:1px solid rgb(var(--base-200))}.form-summary h2{margin:1rem 0}.form-summary h4{background-color:rgb(var(--base-100));padding:.5rem 2rem;position:relative;left:-2rem;color:rgb(var(--contrast-200));font-size:.875rem;text-transform:uppercase;letter-spacing:.05em;margin-bottom:.75rem}.form-summary p{color:var(--text);margin:0}.group-summary,.repeater-summary{background:rgb(var(--base-100));padding:1rem;border-radius:4px;margin-top:.5rem}.repeater-row{margin-bottom:1rem}.repeater-row:last-child{margin-bottom:0}.selected-item{border:1px solid rgb(var(--base-200));border-radius:var(--radius);font-size:var(--txt-x-small);background-color:rgb(var(--base));padding:.25rem .5rem}.selected-item button{--w:.5em;min-height:1em;width:1em;padding:0}.selector .auto-wrapper,.selector .selected-items{flex:1;width:100%}.fstatus{z-index:var(--z-5);background-color:rgba(var(--base),var(--op-6));border-radius:var(--radius);padding:0 .5rem;position:fixed;right:.5rem;bottom:0;--w:1em;box-shadow:rgba(var(--base),var(--op-6)) var(--shdw);--wrap:nowrap;--gap:1rem}body:has(nav.fixed.bottom) .fstatus{bottom:var(--btn);z-index:var(--z-8)}.fstatus .spinner{display:none}.fstatus.loading .spinner{display:inline-block}.fstatus p{margin:0;padding:.25rem}.restore-uploads .item-grid.group .field.group,.restore-uploads .upload-group .selection-actions{display:none}.upload-group .item-grid.group{grid-template-columns:repeat(2,1fr)}.restore-uploads .item-grid.group{grid-template-columns:repeat(3,1fr)}fieldset{width:100%;border-color:rgb(var(--base-200))}.restore-form.restore-form[hidden]{display:block!important;position:fixed;bottom:var(--offScreen);right:var(--btnbtn);transition:bottom var(--trans-base);transition-duration:2s}.restore-form.restore-form:not([hidden]){width:min(500px,90vw);padding:1rem;z-index:var(--z-7);background-color:rgba(var(--base),var(--op-6));border-radius:var(--radius);box-shadow:rgba(var(--action-0),var(--op-6)) var(--shdw);position:fixed;left:0;bottom:0;transition:bottom var(--trans-base)}.restore-form h3{font-size:var(--txt-medium)}body:has(nav.fixed.bottom) .restore-form.restore-form:not([hidden]){bottom:var(--btn)}.restore-form .actions{display:flex;width:100%}.restore-form .actions button{min-height:var(--chip);font-size:var(--txt-x-small);width:100%}
\ No newline at end of file
+.field{width:100%;margin:.5em 0;padding:.5em 0}.field+.field{border-top:1px solid rgb(var(--base-200))}.field .wrapper{width:100%;position:relative}.field .validation{flex-shrink:0;max-width:0;transition:var(--trans-size)}.field.has-error .validation.error,.field.has-success .validation.success{max-width:var(--btn)}.field.has-error .error{color:rgb(var(--error))}.field.has-error input,.field.has-error select,.field.has-error textarea{border-color:rgb(var(--error));background-color:var(--errorBack)}.field.has-error input:focus,.field.has-error select:focus,.field.has-error textarea:focus{outline-color:rgb(var(--error));box-shadow:rgba(var(--error),.2) var(--shdw)}.field.has-success .success{color:var(--success)}.validation-message{color:var(--errorText);font-size:var(--txt-small);margin-top:.25rem;display:block}.field[data-field=post_status] .wrapper{--justify:flex-start}.field[data-field=post_status] .btn+label{width:var(--chipchip);min-height:var(--chipchip);padding:0}.field[data-field=post_status] .btn+label:hover,.field[data-field=post_status] .btn:focus+label{color:rgb(var(--action-contrast))}.date-wrapper{position:relative;display:inline-block}input[type=date]{padding:8px 36px 8px 8px;border-radius:4px}input[type=date]::-webkit-calendar-picker-indicator{opacity:0;width:100%;height:100%;position:absolute;top:0;left:0;cursor:pointer}input[type=date]+.icon{--w:20px;position:absolute;right:10px;top:50%;transform:translateY(-50%);pointer-events:none}input:is([type=time],[type=datetime-local],[type=date]){padding:.5rem;border:1px solid rgb(var(--contrast-200));border-radius:4px;font-size:14px;min-width:180px;background:rgb(var(--base));color:rgb(var(--contrast));cursor:pointer}.date-wrapper input[type=date]:focus,.datetime-wrapper input[type=datetime-local]:focus,.time-wrapper input[type=time]:focus,.wrapper input:is([type=time],[type=datetime-local],[type=date]):focus{border-color:rgb(var(--action-0));box-shadow:0 0 0 2px rgba(var(--action-0),.1)}.date-wrapper .icon,.datetime-wrapper .icon,.time-wrapper .icon,.wrapper .icon{width:18px;height:18px;background-color:rgb(var(--contrast));opacity:.7}.field.quantity.quantity{margin:0;padding:0;display:inline-flex;width:max-content;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;position:relative;--gap:0!important}.quantity:focus-within{border-color:rgb(var(--action-0))}.field.quantity .wrapper .row{--gap:0!important}.field.quantity>label{margin:0;background-color:transparent;font-size:var(--txt-medium);white-space:nowrap;top:0;left:0}.quantity button{background:rgb(var(--base));padding:0;width:var(--chip_)!important;height:var(--chip_)!important;min-height:0;z-index:0;position:relative;border:1px solid rgb(var(--base-200));color:rgb(var(--contrast-200))}.quantity button:hover:not(:disabled){color:rgb(var(--action-0));border-color:rgb(var(--action-0));background-color:rgb(var(--base))}.quantity button:active:not(:disabled){background-color:rgb(var(--action-0));color:rgb(var(--light-0));transform:scale(.95)}.quantity button:disabled{opacity:.5;cursor:not-allowed}.quantity input[type=number]{z-index:1;border:1px solid rgb(var(--base-200));background:rgb(var(--base));text-align:center;font-size:1.1rem;width:60px;height:48px;margin:0;padding:0!important;appearance:textfield}.quantity input[type=number]::-webkit-inner-spin-button,.quantity input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.quantity input[type=number]:focus{background-color:rgb(var(--base-50))}.quantity button.increase{left:-2px;border-radius:0 4px 4px 0}.quantity button.decrease{right:-2px;border-radius:4px 0 0 4px}.quantity .wrapper{width:max-content}details.uploader .file-upload-container{margin:1rem 0;max-width:100%}.field.upload{position:relative}.field.upload .progress{display:none}.field.upload.uploading .progress{display:block}.field.upload .actions{position:absolute;top:0;right:0}.empty-group,.file-upload-wrapper,.preview-wrap .item-grid{border:2px dashed rgb(var(--action-0));border-radius:4px;padding:2rem;text-align:center;transition:all .3s ease;background:rgba(var(--action-0),var(--op-1));position:relative;cursor:pointer;user-select:none}.file-upload-wrapper{max-width:var(--content);margin:1rem auto}.file-upload-wrapper h2{margin:0;font-size:var(--txt-large)}.dragover,.empty-group:hover,.file-upload-wrapper:hover,.preview-wrap .item-grid:hover{background:rgba(var(--action-0),var(--op-2));border-color:rgb(var(--action-0))}.preview-wrap:has(.item-grid:empty) .selection-controls{display:none}.preview-wrap .item-grid{min-height:20vh}.preview-wrap .item-grid:empty::before{content:'Unsorted images become their own posts.';display:block}.file-upload-wrapper input[type=file]{position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;cursor:pointer}.empty-group p,.file-upload-text{margin:0}.empty-group p strong,.file-upload-text strong{color:rgb(var(--action-0));text-decoration:underline}.item-grid.groups{grid-template-columns:repeat(1,1fr)}.item-grid.group{margin-bottom:0}.item-grid:is(.restore,.group,.preview) .item{display:block;--w:1.1em}.item-grid:is(.restore,.group,.preview) button{padding:.25rem .5rem}.item-grid:is(.restore,.group,.preview) .preview>input[type=checkbox]:not(.label-button)+label{padding-left:0;margin:0}.item-grid:is(.restore,.group,.preview) .item .item-actions{position:absolute;top:0;right:0;left:var(--chipchip)}.item-grid:is(.restore,.group,.preview) summary{padding:.5rem}.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked){padding:.5rem;background-color:rgba(var(--action-0),var(--op-4));opacity:1}.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked) img{filter:none}.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked) .item img{filter:var(--filter)}.item-grid.preview summary span,.item-grid:is(.restore,.group,.preview):has([type=checkbox]:checked) details{display:none}[type=radio].featured:checked+label .icon-star,[type=radio].featured:not(:checked)+label .icon-star-fi{display:none}[type=radio].featured:checked+label .icon-star-fi,[type=radio].featured:not(:checked)+label .icon-star{display:inline-block}.item:is(.restore,.upload){border-radius:var(--radius);aspect-ratio:unset;overflow:hidden;background:rgb(var(--base));border:1px solid rgb(var(--base-200))}.item:is(.restore,.upload) [for=select-item]{aspect-ratio:1}.item.upload:has(details[open]){grid-column:1/-1;padding:.5rem 10%;margin:1rem 0;background-color:transparent;border:2px dashed rgb(var(--action-200))}.item.upload:has(details[open]) details[open]{background-color:transparent}.item:is(.restore,.upload) img{transform:scale(1);transition:transform var(--trans-base)}.item:is(.upload,.restore):hover img{transform:scale(1.02)}.upload-group{padding:5px;border-radius:var(--radius);background-color:rgba(var(--action-0),var(--op-1))}.upload-group .selected .field{margin:0}.upload-group .selection-actions button{aspect-ratio:unset}.submit-uploads{position:fixed;bottom:0;left:var(--btn_);z-index:var(--z-6);height:var(--btn);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);border-radius:var(--radius);animation:pulse-color 5s infinite;animation-delay:1s;background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.submit-uploads:hover{background-color:rgb(var(--base-200));color:rgb(var(--contrast-200))}.empty-group{order:-1;grid-column:1/-1;padding:20px;border-radius:var(--radius);margin:10px 0;cursor:pointer;transition:all var(--trans-base);text-align:center;background-color:rgba(var(--action-0),var(--op-1))}.group-display:not([hidden])~.file-upload-container{display:none}.dragging,.upload.item.dragging{opacity:.7;transform:scale(.95) rotate(3deg);z-index:var(--z-7);box-shadow:0 8px 25px rgba(var(--contrast),var(--op-2))}.dragover{background:rgba(var(--action-0),var(--op-3))!important;border-color:rgb(var(--action-0))!important;transform:scale(1.05);animation:drop-pulse .8s infinite ease-in-out}.drag-preview{position:fixed;z-index:var(--z-9);width:fit-content;overflow:visible;pointer-events:none;opacity:.9;transform:scale(1.05);transition:transform .2s ease}.drag-preview .drag-items{width:max-content;height:max-content;position:relative}.drag-preview .drag-items .dragi-item{width:120px;height:120px;position:absolute;top:0;left:0;background:rgb(var(--base));border-radius:var(--radius-outer);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.drag-preview .drag-items .drag-item:nth-child(1){transform:rotate(-3deg);z-index:3}.drag-preview .drag-items .drag-item:nth-child(2){left:8px;top:-4px;transform:rotate(4deg);z-index:2;transition-delay:30ms}.drag-preview .drag-items .drag-item:nth-child(3){left:-6px;top:-8px;transform:rotate(-5deg);z-index:1;transition-delay:60ms}.drag-preview .drag-items .drag-item:nth-child(4){left:12px;top:-12px;transform:rotate(3deg);z-index:0;transition-delay:90ms}.drag-preview .drag-items .drag-item:nth-child(n+5){left:-10px;top:-16px;transform:rotate(-4deg);z-index:0;opacity:.8}.drag-preview .drag-items img,.drag-preview .drag-items video{width:100%;height:100%;object-fit:cover;display:block}.drag-preview .drag-count{position:absolute;top:-8px;right:-8px;background:rgb(var(--base-200));color:rgb(var(--contrast));border-radius:50%;width:24px;height:24px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-3)}.item.dragging{opacity:.5;transform:scale(.95);filter:grayscale(50%);transition:opacity .2s ease,transform .2s ease,filter .2s ease}@keyframes drop-pulse{0%,100%{background-color:rgba(var(--action-0),var(--op-3));transform:scale(1.02)}50%{background-color:var(rgba(var(--action-0),var(--op-4)));transform:scale(1.04)}}.selection-actions{display:flex;gap:.25rem}@media (max-width:767px){body:not(.uploading):has(.group-display:not([hidden])){overflow:hidden}body:not(.uploading):has(.group-display:not([hidden])) .qtoggle{z-index:var(--z-1)}.group-display.group-display{position:fixed;top:var(--btn);bottom:var(--btn);left:0;right:0;max-height:var(--maxHeight);overflow:hidden;z-index:var(--z-6);width:calc(100% - 1rem);height:calc(100% - 1rem);padding:0 0 3rem;--justify:flex-start;--align:flex-start;--gap:0}.group-display::before{content:'';display:block;z-index:-1;top:-.5rem;bottom:-.5rem;left:-.5rem;right:-.5rem;position:absolute;background-color:rgba(var(--base),var(--op-6));filter:blur(5px)}.group-display .preview-wrap,.group-display .sidebar{--wrap:nowrap;height:50%;overflow:hidden auto;position:relative;padding:.5rem}.group-display .preview-wrap{top:0}.group-display .preview-wrap .selected{display:flex;justify-content:space-between;align-items:center}.group-display .sidebar{bottom:0;flex-wrap:nowrap;overflow:hidden auto;background-color:rgb(var(--contrast-200));color:rgb(var(--base))}.group-display .sidebar>.hint{color:rgb(var(--contrast))}.group-display .sidebar .header{display:none}.group-display .preview-actions{top:0;flex-shrink:0}.group-display .preview-wrap>.hint,.group-display .sidebar>.hint{bottom:0;margin:0;text-align:center}.group-display .preview-actions,.group-display .preview-wrap>.hint,.group-display .sidebar>.hint{position:absolute;left:0;right:0;background-color:rgba(var(--base),var(--op-6));z-index:var(--z-3);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.group-display .item-grid{height:100%;overflow:hidden auto;grid-template-columns:repeat(3,1fr);padding:2rem 0}.group-display .sidebar>.item-grid{grid-template-columns:repeat(1,1fr);gap:1rem;padding:0}.group-display .sidebar .empty-group{order:0;position:sticky;height:fit-content;top:0;z-index:var(--z-3);background-color:rgba(var(--action-0),var(--op-6))}.group-display .sidebar .upload-group{order:1}.group-display .sidebar .empty-group p{margin:0}.group-display .field,.group-display .field label{margin:0;padding:0}.group-display .sidebar h4{margin:.25rem}.group-display .item{width:100%;height:max-content}.submit-uploads{bottom:var(--btn);left:0;right:0;width:100%;height:3rem}body.uploading .group-display.group-display{position:relative;top:unset;bottom:unset;right:unset;left:unset}}@media (min-width:768px){.group-display.group-display{--wrap:nowrap;--dir:row;--gap:1rem;--align:flex-start}.group-display .preview-wrap,.group-display .sidebar{--justify:flex-start;--wrap:nowrap;max-height:calc(100vh - var(--btnbtn));overflow:hidden auto}.group-display .preview-wrap,.group-display .sidebar{width:50%}.preview-actions,.preview-wrap .hint{position:sticky;z-index:var(--z-3);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);background-color:rgb(var(--base));width:100%}.preview-actions{top:0;left:0;right:0}.preview-actions .field{margin:0}.preview-wrap .hint,.sidebar>.hint{bottom:-1rem;padding-bottom:1rem;margin:0;left:0;right:0;text-align:center}}.item-grid.restore{grid-template-columns:repeat(1,1fr)}.editor-container .ql-toolbar{display:flex;background-color:rgb(var(--base-50));justify-content:flex-start;flex-wrap:wrap;padding:.25rem;gap:.5rem 1rem;border-top-left-radius:var(--radius);border-top-right-radius:var(--radius);border-bottom:4px solid rgb(var(--base-50))}.ql-toolbar button{min-height:0;padding:.5rem}.ql-toolbar .ql-formats{display:flex;gap:.25rem}.editor-container .ql-container{--padding:1rem;background-color:rgb(var(--base));border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius);height:fit-content;padding:2px;border:1px solid rgb(var(--base-200))}.editor-container .ql-container .ql-editor{padding:var(--padding);width:100%;height:100%;max-width:90vw}.ql-editor img{max-width:50%;height:auto}.ql-clipboard{left:-100000px;height:1px;overflow-y:hidden;position:absolute;top:50%}.ql-hidden{display:none}.ql-tooltip{position:absolute;transform:translateY(10px);background-color:rgb(var(--base-100));border:1px solid rgb(var(--base));box-shadow:0 0 5px rgba(var(--base),var(--op-6));color:rgb(var(--contrast));padding:5px 12px;white-space:nowrap}[data-type=single] .item-grid{display:flex}.repeater-row{--gap:0!important;--align:flex-start!important}.repeater-row details{width:100%}.repeater-row .drag-handle,.repeater-row button{width:var(--chipchip);height:var(--chipchip)}.drag-handle{cursor:grab;background-color:rgb(var(--base-100));user-select:none}.repeater-row details summary::after{margin-left:0}.repeater-row details summary button{margin-left:auto}.repeater .field-input-wrapper{flex-direction:column}.repeater .repeater-items{width:100%}.add-repeater-row,.remove-row{margin-left:auto;min-height:0;height:var(--chipchip);background-color:rgb(var(--action-0))}.repeater-row-ghost{opacity:.3;background:var(--gap-bg,#f0f0f0);border:2px dashed var(--gap-border,#ccc)}.repeater-row-chosen{opacity:.6}.field.tag-list .row{margin-bottom:1rem}.field.tag-list .row .field{flex:1;min-width:150px;margin:0}.field.tag-list .tag .add-tag-item{flex-shrink:0;white-space:nowrap;margin-top:calc(var(--txt-medium) + 1rem)}.field.tag-list .tag-items{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:1rem;min-height:2rem}.field.tag-list .tag-item{background:rgb(var(--base-200));padding:.4rem .75rem;border-radius:4px;display:inline-flex;align-items:center;gap:.5rem;font-size:.9rem;line-height:1.2}.field.tag-list .tag-item:hover{background:rgb(var(--base-100))}.field.tag-list .tag-label{max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field.tag-list .remove-tag{min-height:0;padding:.25rem;color:rgb(var(--contrast));transition:transform .2s;box-shadow:none}.field.tag-list .remove-tag:hover{transform:scale(1.2)}@media (max-width:768px){.field.tag-list .tag{flex-direction:column;align-items:stretch}.field.tag-list .tag .field{min-width:100%}}.form-progress{padding:0 1rem}.form-progress .progress{background:rgb(var(--base-100));border-radius:var(--radius);padding:1rem}.form-progress .bar{height:6px;background:rgb(var(--base-200));border-radius:3px;overflow:hidden;margin-bottom:.5rem}.form-progress .fill{height:100%;background:linear-gradient(90deg,rgb(var(--action-0)),rgb(var(--action-200)));width:0%;transition:width .4s ease;border-radius:3px}.form-progress .step-text{font-size:var(--txt-small);font-weight:600;color:rgb(var(--contrast-200))}form nav.tabs{position:relative;top:0;left:0;right:0;padding:1rem 0;gap:0;z-index:0}form nav.tabs button{position:relative;background:0 0;border:none;padding:.5rem 1rem .5rem 3rem;z-index:1}form nav.tabs .step-number{width:2.5rem;height:100%;position:absolute;left:0;top:0;background:rgb(var(--base-200));color:rgb(var(--contrast-50));display:flex;align-items:center;justify-content:center;font-weight:700;font-size:var(--txt-small)}form nav.tabs button.pending .step-number{background:rgb(var(--base-100));color:rgb(var(--contrast-200))}form nav.tabs button.active .step-number,form nav.tabs button.current .step-number{background:rgb(var(--action-200));color:rgb(var(--action-contrast))}form nav.tabs button.completed .step-number{background:var(--successBack);color:var(--successBack);border-color:var(--successText)}form nav.tabs button.completed .step-number::before{content:'✓';font-size:1.2rem;color:var(--successText);position:absolute}form nav.tabs button.completed h2{color:rgb(var(--contrast-200))}.step-navigation{margin-top:2rem;padding-top:2rem;border-top:1px solid rgb(var(--base-200));gap:1rem}.step-navigation .prev-step{background:rgb(var(--base-100))}.step-navigation .next-step,.step-navigation button[type=submit]{margin-left:auto}@media (max-width:768px){form nav.tabs button{min-width:80px;font-size:var(--txt-small)}form nav.tabs button h2{font-size:var(--txt-small)}form{--step-size:2rem}}.field input.error,.field select.error,.field textarea.error{border-color:var(--errorBack)}.error-message{color:var(--errorText);font-size:var(--txt-small);margin-top:.25rem;display:block}.form-summary{padding:2rem;border-radius:8px;margin-top:2rem;border:2px dashed rgb(var(--contrast-200))}.form-summary .message{margin-bottom:2rem}.form-summary .result+.result{position:relative;margin-top:1.5rem;padding-top:1.5rem}.form-summary .result+.result::before{position:absolute;top:0;left:16.5%;content:'';width:67%;height:1px;border-bottom:1px solid rgb(var(--base-200))}.form-summary h2{margin:1rem 0}.form-summary h4{background-color:rgb(var(--base-100));padding:.5rem 2rem;position:relative;left:-2rem;color:rgb(var(--contrast-200));font-size:.875rem;text-transform:uppercase;letter-spacing:.05em;margin-bottom:.75rem}.form-summary p{color:var(--text);margin:0}.group-summary,.repeater-summary{background:rgb(var(--base-100));padding:1rem;border-radius:4px;margin-top:.5rem}.repeater-row{margin-bottom:1rem}.repeater-row:last-child{margin-bottom:0}.selected-item{border:1px solid rgb(var(--base-200));border-radius:var(--radius);font-size:var(--txt-x-small);background-color:rgb(var(--base));padding:.25rem .5rem}.selected-item button{--w:.5em;min-height:1em;width:1em;padding:0}.selector .auto-wrapper,.selector .selected-items{flex:1;width:100%}.fstatus{z-index:var(--z-5);background-color:rgba(var(--base),var(--op-6));border-radius:var(--radius);padding:0 .5rem;position:fixed;right:.5rem;bottom:0;--w:1em;box-shadow:rgba(var(--base),var(--op-6)) var(--shdw);--wrap:nowrap;--gap:1rem}body:has(nav.fixed.bottom) .fstatus{bottom:var(--btn);z-index:var(--z-8)}.fstatus .spinner{display:none}.fstatus.loading .spinner{display:inline-block}.fstatus p{margin:0;padding:.25rem}.restore-uploads .item-grid.group .field.group,.restore-uploads .upload-group .selection-actions{display:none}.upload-group .item-grid.group{grid-template-columns:repeat(2,1fr)}.restore-uploads .item-grid.group{grid-template-columns:repeat(3,1fr)}fieldset{width:100%;border-color:rgb(var(--base-200))}.restore-form.restore-form[hidden]{display:block!important;position:fixed;bottom:var(--offScreen);right:var(--btnbtn);transition:bottom var(--trans-base);transition-duration:2s}.restore-form.restore-form:not([hidden]){width:min(500px,90vw);padding:1rem;z-index:var(--z-7);background-color:rgba(var(--base),var(--op-6));border-radius:var(--radius);box-shadow:rgba(var(--action-0),var(--op-6)) var(--shdw);position:fixed;left:0;bottom:0;transition:bottom var(--trans-base)}.restore-form h3{font-size:var(--txt-medium)}body:has(nav.fixed.bottom) .restore-form.restore-form:not([hidden]){bottom:var(--btn)}.restore-form .actions{display:flex;width:100%}.restore-form .actions button{min-height:var(--chip);font-size:var(--txt-x-small);width:100%}[data-field=openingHours] fieldset fieldset{display:flex;align-items:flex-end}[data-field=openingHours] .field+.field{border:none}[data-field=openingHours] .true-false{width:max-content}[data-field=openingHours] label.switch{width:max-content;padding:0 1rem 0 0}[data-field=openingHours] fieldset>.group>.row{max-width:calc(100% - var(--btn_))}[data-field=openingHours] fieldset .field{padding:0;margin:0}[data-field=openingHours] fieldset .group{display:flex}.feedback{position:fixed;top:2rem;right:2rem;background-color:var(--action-50);color:var(--action-contrast);padding:1rem 1.5rem;border-radius:var(--innerRadius);box-shadow:var(--shadow);z-index:10000;opacity:0;transform:translateX(100px);transition:all var(--transition-base);display:flex;align-items:center;gap:.5rem;--w:1.25rem}.feedback.show{opacity:1;transform:translateX(0)}
\ No newline at end of file
diff --git a/assets/css/nav.min.css b/assets/css/nav.min.css
index ef27a65..ec250b2 100644
--- a/assets/css/nav.min.css
+++ b/assets/css/nav.min.css
@@ -1 +1 @@
-nav,nav ol,nav ul{--padding:0 1rem;--wrap:nowrap;font-family:var(--heading)}nav,nav a,nav li,nav ol,nav ul,ul.socials{display:flex;flex-direction:var(--dir,row);justify-content:var(--justify,flex-start);align-items:var(--align,center);gap:var(--gap,0);flex-wrap:var(--wrap,nowrap);height:var(--btn,3rem);max-width:100%;padding:0;margin:0}nav.col,nav.col ul{height:max-content}nav>ul{width:100%;overflow:auto hidden}nav li{width:max-content;--justify:center;max-inline-size:none;padding:0;list-style:none}nav.fill li{width:100%}nav a,nav button{--justify:center;width:100%;white-space:nowrap;text-transform:uppercase;border-radius:0;background-color:transparent;text-decoration:none}nav a{padding:var(--padding)}nav .toggle{aspect-ratio:1;border:1px solid rgb(var(--base));color:rgb(var(--contrast))}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.toggle .icon-caret-down{transform:rotate(0);transition:transform var(--trans-base)}.open>.row>.toggle .icon-caret-down,.open>.toggle .icon-caret-down{transform:rotate(900deg)}.has-submenu{position:relative}ul.submenu{--dir:column;height:max-content;position:absolute;top:100%;right:0;max-height:0;transform:scaleY(0);transform-origin:top;width:100%;min-width:max-content;background-color:rgba(var(--base),var(--op-3));border:2px solid rgba(var(--base),var(--op-3));transition:max-height var(--trans-base),transform var(--trans-base);box-shadow:var(--shdw-none);overflow:hidden}.submenu li{background-color:rgba(var(--base),var(--op-6));border:1px solid rgb(var(--base-50))}.submenu a{height:var(--chipchip)}.open>ul.submenu{transform:scaleY(1);max-height:1000%;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.screen-reader-text{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}nav a:focus:not(:focus-visible){outline:0}nav a:focus-visible{outline:1px solid rgb(var(--action-0));outline-offset:1px}nav.always{overflow:visible;transition:width var(--trans-base);width:max-content}nav.always.fixed{z-index:var(--z-10)}nav.always>ul{--dir:column;--align:center;--justify:flex-start;--gap:0;height:100%;max-height:100vh;position:fixed;right:-300vw;bottom:0;width:100vw;padding:var(--btn) 0;overflow:hidden auto;transition:right var(--trans-base)}nav.always.open>ul{right:0}nav.always li{width:100%;height:max-content}nav.always.fixed.open{width:100vw;height:100vh;inset:0;z-index:var(--z-10)}nav.always a{height:max-content;min-height:var(--chipchip)}nav.always li:not(.has-submenu)>a{padding-right:calc(var(--btn) + 1rem)}nav.always .has-submenu{--dir:row;--wrap:wrap;height:max-content}nav.always .has-submenu a{max-width:calc(100% - var(--btn))}nav.always .has-submenu .toggle{max-width:var(--btn)}.has-submenu.open>a{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.has-submenu.open>ul{border:1px solid rgb(var(--action-0));background-color:rgb(var(--action-0));--align:flex-start}nav.always ul.submenu{position:relative;top:0}nav.always .submenu a{padding-left:var(--btn)}nav.always.fixed{width:var(--btn);height:var(--btn);bottom:0;right:0;overflow:hidden}nav.always.fixed .toggle.main{background-color:rgb(var(--base))}nav.always.fixed .toggle.main:focus,nav.always.fixed .toggle.main:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav.mobile .toggle.main{width:var(--btn);transition:width var(--trans-base)}nav.mobile .icon-list,nav.mobile .icon-x{--w:32px}nav.mobile .icon-x,nav.mobile.open .icon-list{display:none}nav.mobile .icon-list,nav.mobile.open .icon-x{display:block}nav.mobile.open>ul{--dir:column;z-index:var(--z-9);background-color:rgba(var(--base),var(--op-6));width:100vw;height:100vh;overflow:hidden auto;right:0;bottom:0;position:fixed;padding:var(--btn) 0}nav.always>ul::before,nav.mobile.open>ul::before{content:'';z-index:-1;position:absolute;inset:0;filter:blur(5px)}nav.always.open .main.toggle,nav.mobile.open .main.toggle{position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-10);aspect-ratio:unset}nav.always>ul,nav.always>ul:before,nav.mobile.open>ul,nav.mobile.open>ul::before{background-color:rgba(var(--base),var(--op-6))}@media (max-width:767px){nav.col{height:var(--btn)}nav.mobile>ul{--dir:column;--align:center;--justify:flex-end;--gap:0;height:100%;max-height:none;position:relative;right:-300vw;width:100vw;padding:var(--btn) 0 0;overflow:hidden auto}nav.mobile.open>ul{right:0}}@media (min-width:768px){nav.mobile:not(.always) .toggle.main{display:none}}nav#breadcrumbs{height:max-content;--wrap:wrap;--gap:0;width:max-content;max-width:var(--full);position:absolute;background-color:rgba(var(--base),var(--op-4));font-size:var(--txt-x-small);padding:.125em;z-index:var(--z-5)}nav#breadcrumbs ol{height:max-content;--wrap:wrap;--justify:flex-start}nav#breadcrumbs li{width:max-content;height:var(--chip);--wrap:nowrap}nav#breadcrumbs li::after{content:'/';color:rgb(var(--contrast-200));padding:0 .25rem}nav#breadcrumbs li:last-of-type::after{display:none}nav#breadcrumbs a{height:var(--chip)}nav#breadcrumbs a,nav#breadcrumbs span{padding:0 .125rem;color:rgb(var(--contrast));text-transform:none}nav#breadcrumbs a:focus{background-color:transparent;color:rgb(var(--action-0))}nav.fixed{position:fixed;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-6)}nav.fixed.bottom{left:0;bottom:0;width:calc(100vw - var(--btn))}nav.fixed:not(.always) ul{--justify:space-between;width:100%;background-color:rgb(var(--base));padding:0 .25rem}nav.fixed:not(.always) li{flex:1}nav.fixed a{--align:center;--gap:1rem;--w:var(--chip_);color:rgb(var(--contrast));font-size:var(--txt-x-small)}@media (min-width:768px){nav.fixed a{font-size:var(--txt-medium)}}nav.on-this-page.on-this-page{--justify:space-between;position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-5);background-color:rgba(var(--base),var(--op-45));max-width:none;margin:0!important}body:has(nav.fixed) nav.on-this-page{bottom:var(--btn)}nav.on-this-page button{order:3;padding:0 1rem;width:max-content;aspect-ratio:unset}nav.on-this-page.open button{order:0}nav.on-this-page ul{width:100%;--gap:0}nav.on-this-page a{padding:0}nav.on-this-page .active a{background-color:rgba(var(--base),var(--op-6));color:rgb(var(--action-contrast))}nav.on-this-page #back-to-top span{display:none}nav.on-this-page .active a{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav.letters,nav.letters a,nav.letters li,nav.letters ul{min-height:var(--chip);height:max-content}nav.letters li{width:calc(7.69% - 2px)}nav.letters ul{--wrap:wrap}@media (min-width:768px){nav.letters,nav.letters ul{height:var(--chip)}nav.letters ul{--wrap:nowrap}nav.letters li{width:inherit}nav.letters a{padding:.25rem .66rem}}nav.index{--justify:space-between;--padding:0;background-color:rgba(var(--base),var(--op-6))}nav.index,nav.index a,nav.index button,nav.index li,nav.index ul{min-height:var(--chipchip);height:var(--chipchip)}nav.index ul{width:100%}nav.index li{flex-shrink:0;transform:scaleX(0);max-width:0;overflow:hidden}nav.index li.active,nav.index li.adj{transform:scaleX(1);width:100%;flex-shrink:1;max-width:none}@media (max-width:767px){.index li.adj{transform:scaleX(0);max-width:0}}nav.index.open{--dir:column-reverse;height:var(--maxHeight);width:100%;--align:flex-end;background-color:rgba(var(--base),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}nav.index.open ul{min-height:max-content;--dir:column}nav.index.open li{width:100%;max-width:100%!important;transform:scaleX(1);overflow:visible}nav.index.open a{--justify:center;padding:0 2rem 0 0}nav.condensed,nav.condensed a,nav.condensed li,nav.condensed ul{height:max-content;width:max-content;--wrap:wrap;min-height:var(--chip)}.condensed ul{--justify:center;--dir:row}nav.condensed{--gap:0 .25rem;width:100%;--justify:center}nav.condensed li+li::before{content:'·';padding:0 .25em}nav.condensed a{font-size:var(--txt-x-small);padding:0 .25rem;text-transform:none;border-bottom:2px solid transparent}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:transparent;color:rgb(var(--contrast));border-color:rgb(var(--action-0))}ul.socials{--dir:row;height:max-content;--gap:.5rem;--justify:flex-end;--wrap:nowrap;overflow:auto hidden;touch-action:pan-x;width:100%}ul.socials li{list-style:none}.always ul.socials{width:100vw;--justify:stretch;--gap:0}.always ul.socials li{flex:1;--justify:center;--align:center}.always ul.socials a{display:inline-flex}ul.socials li{flex:1}ul.socials a{display:inline-flex;font-size:var(--txt-x-small);padding:.25rem .5rem;max-width:none;justify-content:center;width:100%}ul.socials .icon{margin:0}ul.socials .icon+span:not(.screen-reader-text){margin-left:.5rem}nav.tabs{padding-bottom:2px;touch-action:pan-x pan-y;--wrap:nowrap;overflow:auto hidden}nav.tabs button.active{cursor:default}nav.tabs button{font-family:var(--heading);font-size:var(--txt-x-small);border-bottom:4px solid transparent}nav.tabs button.active,nav.tabs button.active:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast));border-color:rgb(var(--base))}.tab-content nav.tabs button{height:var(--chip_);padding:.25rem .75rem;min-height:0}.tab-content h2{margin:0 0 .5rem}.tab-content nav.tabs{height:max-content;background-color:rgb(var(--base));--gap:0}.tab-content .tab-content nav.tabs{background-color:rgb(var(--base-100))}.tab-content .tab-content .tab-content nav.tabs{background-color:rgb(var(--base-200))}.tab-content nav.tabs button.active h2{color:rgb(var(--action-0))}nav.menu a{padding:.5rem .66rem}nav.share{height:max-content;margin:1rem 0}nav.share ul{overflow:visible}nav.share h4{display:inline-block;width:max-content;margin:.25rem .5rem .25rem 0;font-size:var(--txt-x-small)}.wp-site-blocks>header,body>header{--dir:row;--justify:space-between;position:sticky;top:0;left:0;right:0;height:var(--btn);width:100vw;display:flex;align-items:var(--align,center);justify-content:var(--justify,space-between);padding:0 .5rem;background-color:rgb(var(--base));box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-9)}.wp-site-blocks>header img{width:var(--btn)}.dashboard-nav{width:100%}nav.filters{--dir:row;overflow:auto hidden}nav.filters .filter{width:auto;padding:.25rem .75rem}nav.term-navigation:has([hidden]){display:none}nav.pagination{width:100%}nav.pagination>a{min-width:var(--chipchip)}nav.pagination>ul{margin:0 auto}nav.pagination:not(:has(a+ul)){margin-left:var(--chipchip)}nav.pagination:not(:has(ul+a)){margin-right:var(--chipchip)}.pagination.condensed li+li::before{display:none}.pagination li.current{width:var(--chip_);height:var(--chip_);background-color:rgb(var(--action-0));border-radius:var(--radius);line-height:1}.pagination.condensed a{font-size:var(--txt-medium);width:var(--chip_);height:var(--chip_)}
\ No newline at end of file
+nav,nav ol,nav ul{--padding:0 1rem;--wrap:nowrap;font-family:var(--heading)}nav,nav a,nav li,nav ol,nav ul,ul.socials{display:flex;flex-direction:var(--dir,row);justify-content:var(--justify,flex-start);align-items:var(--align,center);gap:var(--gap,0);flex-wrap:var(--wrap,nowrap);height:var(--btn,3rem);max-width:100%;padding:0;margin:0}nav.col,nav.col ul{height:max-content}nav>ul{width:100%;overflow:auto hidden}nav li{width:max-content;--justify:center;max-inline-size:none;padding:0;list-style:none}nav.fill li{width:100%}nav a,nav button{--justify:center;width:100%;white-space:nowrap;text-transform:uppercase;border-radius:0;background-color:transparent;text-decoration:none}nav a{padding:var(--padding)}nav .toggle{aspect-ratio:1;border:1px solid rgb(var(--base));color:rgb(var(--contrast))}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.toggle .icon-caret-down{transform:rotate(0);transition:transform var(--trans-base)}.open>.row>.toggle .icon-caret-down,.open>.toggle .icon-caret-down{transform:rotate(900deg)}.has-submenu{position:relative}ul.submenu{--dir:column;height:max-content;position:absolute;top:100%;right:0;max-height:0;transform:scaleY(0);transform-origin:top;width:100%;min-width:max-content;background-color:rgba(var(--base),var(--op-3));border:2px solid rgba(var(--base),var(--op-3));transition:max-height var(--trans-base),transform var(--trans-base);box-shadow:var(--shdw-none);overflow:hidden}.submenu li{background-color:rgba(var(--base),var(--op-6));border:1px solid rgb(var(--base-50))}.submenu a{height:var(--chipchip)}.open>ul.submenu{transform:scaleY(1);max-height:1000%;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.screen-reader-text{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}nav a:focus:not(:focus-visible){outline:0}nav a:focus-visible{outline:1px solid rgb(var(--action-0));outline-offset:1px}nav.always{overflow:visible;transition:width var(--trans-base);width:max-content}nav.always.fixed{z-index:var(--z-10)}nav.always>ul{--dir:column;--align:center;--justify:flex-start;--gap:0;height:100%;max-height:100vh;position:fixed;right:-300vw;bottom:0;width:100vw;padding:var(--btn) 0;overflow:hidden auto;transition:right var(--trans-base)}nav.always.open>ul{right:0}nav.always li{width:100%;height:max-content}nav.always.fixed.open{width:100vw;height:100vh;inset:0;z-index:var(--z-10)}nav.always a{height:max-content;min-height:var(--chipchip)}nav.always li:not(.has-submenu)>a{padding-right:calc(var(--btn) + 1rem)}nav.always .has-submenu{--dir:row;--wrap:wrap;height:max-content}nav.always .has-submenu a{max-width:calc(100% - var(--btn))}nav.always .has-submenu .toggle{max-width:var(--btn)}.has-submenu.open>a{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.has-submenu.open>ul{border:1px solid rgb(var(--action-0));background-color:rgb(var(--action-0));--align:flex-start}nav.always ul.submenu{position:relative;top:0}nav.always .submenu a{padding-left:var(--btn)}nav.always.fixed{width:var(--btn);height:var(--btn);bottom:0;right:0;overflow:hidden}nav.always.fixed .toggle.main{background-color:rgb(var(--base))}nav.always.fixed .toggle.main:focus,nav.always.fixed .toggle.main:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav.mobile .toggle.main{width:var(--btn);transition:width var(--trans-base)}nav.mobile .icon-list,nav.mobile .icon-x{--w:32px}nav.mobile .icon-x,nav.mobile.open .icon-list{display:none}nav.mobile .icon-list,nav.mobile.open .icon-x{display:block}nav.mobile.open>ul{--dir:column;z-index:var(--z-9);background-color:rgba(var(--base),var(--op-6));width:100vw;height:100vh;overflow:hidden auto;right:0;bottom:0;position:fixed;padding:var(--btn) 0}nav.always>ul::before,nav.mobile.open>ul::before{content:'';z-index:-1;position:absolute;inset:0;filter:blur(5px)}nav.always.open .main.toggle,nav.mobile.open .main.toggle{position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-10);aspect-ratio:unset}nav.always>ul,nav.always>ul:before,nav.mobile.open>ul,nav.mobile.open>ul::before{background-color:rgba(var(--base),var(--op-6))}@media (max-width:767px){nav.col{height:var(--btn)}nav.mobile>ul{--dir:column;--align:center;--justify:flex-end;--gap:0;height:100%;max-height:none;position:relative;right:-300vw;width:100vw;padding:var(--btn) 0 0;overflow:hidden auto}nav.mobile.open>ul{right:0}}@media (min-width:768px){nav.mobile:not(.always) .toggle.main{display:none}}nav#breadcrumbs{height:max-content;--wrap:wrap;--gap:0;width:max-content;max-width:var(--full);position:absolute;background-color:rgba(var(--base),var(--op-4));font-size:var(--txt-x-small);padding:.125em;z-index:var(--z-5)}nav#breadcrumbs ol{height:max-content;--wrap:wrap;--justify:flex-start}nav#breadcrumbs li{width:max-content;height:var(--chip);--wrap:nowrap}nav#breadcrumbs li::after{content:'/';color:rgb(var(--contrast-200));padding:0 .25rem}nav#breadcrumbs li:last-of-type::after{display:none}nav#breadcrumbs a{height:var(--chip)}nav#breadcrumbs a,nav#breadcrumbs span{padding:0 .125rem;color:rgb(var(--contrast));text-transform:none}nav#breadcrumbs a:focus{background-color:transparent;color:rgb(var(--action-0))}nav.fixed{position:fixed;box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-6)}nav.fixed.bottom{left:0;bottom:0;width:calc(100vw - var(--btn))}nav.fixed:not(.always) ul{--justify:space-between;width:100%;background-color:rgb(var(--base));padding:0 .25rem}nav.fixed:not(.always) li{flex:1}nav.fixed a{--align:center;--gap:1rem;--w:var(--chip_);color:rgb(var(--contrast));font-size:var(--txt-x-small)}@media (min-width:768px){nav.fixed a{font-size:var(--txt-medium)}}nav.on-this-page.on-this-page{--justify:space-between;position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-5);background-color:rgba(var(--base),var(--op-45));max-width:none;margin:0!important}body:has(nav.fixed) nav.on-this-page{bottom:var(--btn)}nav.on-this-page button{order:3;padding:0 1rem;width:max-content;aspect-ratio:unset}nav.on-this-page.open button{order:0}nav.on-this-page ul{width:100%;--gap:0}nav.on-this-page a{padding:0}nav.on-this-page .active a{background-color:rgba(var(--base),var(--op-6));color:rgb(var(--action-contrast))}nav.on-this-page #back-to-top span{display:none}nav.on-this-page .active a{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}nav.letters,nav.letters a,nav.letters li,nav.letters ul{min-height:var(--chip);height:max-content}nav.letters li{width:calc(7.69% - 2px)}nav.letters ul{--wrap:wrap}@media (min-width:768px){nav.letters,nav.letters ul{height:var(--chip)}nav.letters ul{--wrap:nowrap}nav.letters li{width:inherit}nav.letters a{padding:.25rem .66rem}}nav.index{--justify:space-between;--padding:0;background-color:rgba(var(--base),var(--op-6))}nav.index,nav.index a,nav.index button,nav.index li,nav.index ul{min-height:var(--chipchip);height:var(--chipchip)}nav.index ul{width:100%}nav.index li{flex-shrink:0;transform:scaleX(0);max-width:0;overflow:hidden}nav.index li.active,nav.index li.adj{transform:scaleX(1);width:100%;flex-shrink:1;max-width:none}@media (max-width:767px){.index li.adj{transform:scaleX(0);max-width:0}}nav.index.open{--dir:column-reverse;height:var(--maxHeight);width:100%;--align:flex-end;background-color:rgba(var(--base),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}nav.index.open ul{min-height:max-content;--dir:column}nav.index.open li{width:100%;max-width:100%!important;transform:scaleX(1);overflow:visible}nav.index.open a{--justify:center;padding:0 2rem 0 0}nav.condensed,nav.condensed a,nav.condensed li,nav.condensed ul{height:max-content;width:max-content;--wrap:wrap;min-height:var(--chip)}.condensed ul{--justify:center;--dir:row}nav.condensed{--gap:0 .25rem;width:100%;--justify:center}nav.condensed li+li::before{content:'·';padding:0 .25em}nav.condensed a{font-size:var(--txt-x-small);padding:0 .25rem;text-transform:none;border-bottom:2px solid transparent}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:transparent;color:rgb(var(--contrast));border-color:rgb(var(--action-0))}ul.socials{--dir:row;height:max-content;--gap:.5rem;--justify:flex-end;--wrap:nowrap;overflow:auto hidden;touch-action:pan-x;width:100%}ul.socials li{list-style:none}.always ul.socials{width:100vw;--justify:stretch;--gap:0}.always ul.socials li{flex:1;--justify:center;--align:center}.always ul.socials a{display:inline-flex}ul.socials li{flex:1}ul.socials a{display:inline-flex;font-size:var(--txt-x-small);padding:.25rem .5rem;max-width:none;justify-content:center;width:100%}ul.socials .icon{margin:0}ul.socials .icon+span:not(.screen-reader-text){margin-left:.5rem}nav.tabs.tabs.row{padding-bottom:2px;touch-action:pan-x pan-y;--wrap:nowrap;overflow:auto hidden;--dir:row}nav.tabs button.active{cursor:default}nav.tabs button{font-family:var(--heading);font-size:var(--txt-x-small);border-bottom:4px solid transparent}nav.tabs button.active,nav.tabs button.active:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast));border-color:rgb(var(--base))}.tab-content nav.tabs button{height:var(--chip_);padding:.25rem .75rem;min-height:0}.tab-content h2{margin:0 0 .5rem}.tab-content nav.tabs{height:max-content;background-color:rgb(var(--base));--gap:0}.tab-content .tab-content nav.tabs{background-color:rgb(var(--base-100))}.tab-content .tab-content .tab-content nav.tabs{background-color:rgb(var(--base-200))}.tab-content nav.tabs button.active h2{color:rgb(var(--action-0))}nav.menu a{padding:.5rem .66rem}nav.share{height:max-content;margin:1rem 0}nav.share ul{overflow:visible}nav.share h4{display:inline-block;width:max-content;margin:.25rem .5rem .25rem 0;font-size:var(--txt-x-small)}.wp-site-blocks>header,body>header{--dir:row;--justify:space-between;position:sticky;top:0;left:0;right:0;height:var(--btn);width:100vw;display:flex;align-items:var(--align,center);justify-content:var(--justify,space-between);padding:0 .5rem;background-color:rgb(var(--base));box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);z-index:var(--z-9)}.wp-site-blocks>header img{width:var(--btn)}.dashboard-nav{width:100%}nav.filters{--dir:row;overflow:auto hidden}nav.filters .filter{width:auto;padding:.25rem .75rem}nav.term-navigation:has([hidden]){display:none}nav.pagination{width:100%}nav.pagination>a{min-width:var(--chipchip)}nav.pagination>ul{margin:0 auto}nav.pagination:not(:has(a+ul)){margin-left:var(--chipchip)}nav.pagination:not(:has(ul+a)){margin-right:var(--chipchip)}.pagination.condensed li+li::before{display:none}.pagination li.current{width:var(--chip_);height:var(--chip_);background-color:rgb(var(--action-0));border-radius:var(--radius);line-height:1}.pagination.condensed a{font-size:var(--txt-medium);width:var(--chip_);height:var(--chip_)}
\ No newline at end of file
diff --git a/assets/js/concise/Checkout.js b/assets/js/concise/Checkout.js
index e0990f0..594914e 100644
--- a/assets/js/concise/Checkout.js
+++ b/assets/js/concise/Checkout.js
@@ -21,7 +21,9 @@
 		this.checkout      = document.querySelector('aside#cart');
 		this.provider      = this.checkout?.querySelector('form')?.dataset.provider || '';
 
-		this.isOpen        = this.config.isOpen !== '1' || false;
+		this.isOpen        = this.config.isOpen === '1' || false;
+		this.isOpen = true;
+
 		this.isLoggedIn    = this.config.is_logged_in || false;
 		this.userEmail     = this.config.user_email || '';
 		this.savedCards    = [];
@@ -31,6 +33,7 @@
 
 		this.cache = new window.jvbCache('cart', { TTL: 8.64e+7 });
 		this.a11y  = window.jvbA11y;
+		this.T = window.jvbTemplates;
 
 		this.initCart();
 
@@ -38,20 +41,38 @@
 			this.initElements();
 			this.init();        // provider-specific
 			this.initListeners();
+			this.defineTemplates();
 
 			if (this.isLoggedIn) {
 				this.loadSavedCards();
 			}
 		}
 
+		if (!this.isOpen)
+		{
+			this.closeShop();
+		}
+
 		this.popup = window.jvbPopup.registerPopup({
 			popup:  this.checkout,
-			toggle: this.toggle,
+			toggle: this.ui.toggle.btn,
 			name:   'Cart',
 			onOpen: this.maybeAddEmptyState.bind(this),
+			onClose: this.toggleButton.bind(this),
 		});
 	}
 
+	closeShop() {
+		document.querySelectorAll('[data-add-to-cart]').forEach(item => {
+			item.querySelectorAll('button, input').forEach(i => {
+				i.disabled = true;
+				i.title = 'Sorry, we are currently closed.';
+			});
+
+		});
+	}
+
+
 	/*****************************************************************
 	 * ABSTRACT — subclasses must implement
 	 *****************************************************************/
@@ -81,20 +102,30 @@
 	 *****************************************************************/
 
 	async initCart() {
-		this.cartItems = await this.cache.get('cart') ?? new Map();
-		if (this.cartItems.size > 0) {
+		let stored = await this.cache.get('cart') ?? new Map();
+		if (stored.size > 0) {
+			this.cartItems = stored;
+			this.updateToggle();
 			this.notifyRestoredCart();
 		}
 	}
 
 	saveCart() {
 		this.updateTotal();
+		this.updateToggle();
 		this.cache.set('cart', this.cartItems);
 	}
 
 	clearCart() {
+		for (let [id, item] of this.cartItems.entries()) {
+			this.removeFromCart(id);
+		}
 		this.cartItems.clear();
-		window.removeChildren(this.table);
+		Array.from(this.ui.cart.table.children).forEach(child => {
+			if (child.tagName === 'TBODY') {
+				child.remove();
+			}
+		});
 		this.saveCart();
 	}
 
@@ -109,22 +140,87 @@
 	/*****************************************************************
 	 * ELEMENT SETUP
 	 *****************************************************************/
+	defineTemplates() {
+		// const checkout = this;
+		this.T.define('mainCartItem', {
+			refs: {
+				header: '.header th'
+			},
+			setup({el, refs, manyRefs, data}) {
+				refs.header.textContent = data.label;
+				el.dataset.label = data.label;
+				el.dataset.id = data.parentId;
+			}
+		});
 
+		this.T.define('childCartItem', {
+			refs: {
+				quantity: '.quantity',
+				label: 'label',
+				price: 'span.price',
+				button: '[data-remove-from-cart]'
+			},
+			manyRefs: {},
+			setup({el, refs, manyRefs, data}) {
+				[
+					refs.quantity.dataset.id,
+					refs.quantity.dataset.price,
+					refs.quantity.dataset.catalogId,
+					refs.label.textContent,
+					refs.price.textContent,
+					refs.button.dataset.id
+				] = [
+					data.id,
+					data.price,
+					data.catalogId || '',
+					data.name,
+					window.formatPrice(data.price),
+					data.id
+				];
+			}
+		});
+		this.T.define('emptyCart');
+	}
 	initElements() {
-		this.toggle       = document.querySelector('.toggle-cart');
+		this.restoreElement = false;
+		this.selectors = {
+			toggle: {
+				btn: '.toggle-cart',
+				count: '.toggle-cart .count',
+				icon: '.toggle-cart .icon',
+			},
+			panels: {
+				checkout: {
+					toggle: 'button[data-tab="checkout"]',
+					form: '#cart form'
+				}
+			},
+			cart: {
+				wrap: '#cart .cart-items',
+				empty: '#cart .cart-items .empty',
+				table: '#cart .cart-items table',
+				body: '#cart .cart-items tbody',
+				total: '#cart .cart-total',
+				foot: {
+					foot: '#cart tfoot',
+					total: '#cart tfoot .total',
+					tax: '#cart tfoot .tax',
+				},
+				grandTotal: '#cart .total span',
+				tax: '#cart .cart-total .tax span',
+			}
+		};
+		this.ui = window.uiFromSelectors(this.selectors);
+		this.ui.cart.items = new Map();
+
 		if (!this.isOpen) {
-			this.toggle.disabled = true;
-			this.toggle.title    = 'Currently closed for online ordering';
+			this.ui.toggle.btn.disabled = true;
+			this.ui.toggle.btn.title    = 'Currently closed for online ordering';
 		}
-		this.checkoutPanel = this.checkout.querySelector('button[data-tab="checkout"]');
-		this.itemsList     = this.checkout.querySelector('.cart-items');
-		this.table         = this.checkout.querySelector('.cart-items tbody');
-		this.total         = this.checkout.querySelector('.cart-total');
-		this.totalTax      = this.total.querySelector('.tax span');
-		this.grandTotal    = this.total.querySelector('.total span');
+
 		this.checkoutForm  = this.checkout.querySelector('form');
 
-		this.tabs = new window.jvbTabs(this.checkoutForm, {
+		this.tabs = window.jvbTabs.registerTab(this.checkoutForm, {
 			updateURL: false
 		});
 	}
@@ -137,6 +233,7 @@
 		this.checkoutForm.addEventListener('submit', (e) => this.handleFormSubmit(e));
 		document.addEventListener('click', this.clickHandler);
 		document.addEventListener('change', this.changeHandler);
+		// document.addEventListener('keydown', this.keyHandler);
 	}
 
 	/*****************************************************************
@@ -147,14 +244,15 @@
 		if (window.targetCheck(e, 'button') && window.targetCheck(e, 'div.quantity')) {
 			let quantity = window.targetCheck(e, 'div.quantity');
 			this.handleNumberClick(e, quantity);
-		} else if (window.targetCheck(e, '[data-add-to-cart]')) {
+		} else if (window.targetCheck(e, '[data-add-to-cart]') && window.targetCheck(e, 'button')) {
 			let add = window.targetCheck(e, '[data-add-to-cart]');
 			this.handleAddToCart(add);
-		} else if (window.targetCheck(e, '[data-remove-from-cart]')) {
+		} else if (window.targetCheck(e, '[data-remove-from-cart]') ) {
 			let remove = window.targetCheck(e, '[data-remove-from-cart]');
 			this.handleRemoveFromCart(remove);
 		} else if (window.targetCheck(e, '[data-clear-cart]')) {
 			this.clearCart();
+			document.querySelector('.restored')?.remove();
 		} else if (window.targetCheck(e, '[data-dismiss]')) {
 			window.targetCheck(e, '[data-dismiss]').closest('.restored')?.remove();
 		}
@@ -164,12 +262,7 @@
 		let input = window.targetCheck(e, '.quantity-input');
 		if (input) {
 			let item  = e.target.closest('.quantity');
-			let value = input.value;
-
-			if (window.targetCheck(e, '.cart-items')) {
-				let main = document.querySelector(`.menu-section [data-id="${item.dataset.id}"] input`);
-				if (main) main.value = input.value;
-			}
+			let value = e.target.value;
 
 			if (value > 0) {
 				this.handleAddToCart(item);
@@ -181,6 +274,10 @@
 
 	handleEscape(e) {
 		if (e.key === 'Escape') {
+			if (this.restoreElement) {
+				this.restoreElement.classList.remove('show');
+				this.restoreElement = false;
+			}
 			this.stepMultiplier = 1;
 		} else if (e.ctrlKey && e.shiftKey) {
 			this.stepMultiplier = Math.max(parseInt(this.stepMultiplier) * 100, 1000);
@@ -196,38 +293,62 @@
 	handleAddToCart(item) {
 		let id       = item.dataset.id;
 		let price    = parseFloat(item.dataset.price);
-		let quantity = parseInt(item.querySelector('.quantity-input')?.value) ?? 1;
+		let quantity = parseInt(item.querySelector('.quantity input')?.value) ?? 1;
 		let total    = parseFloat(price * quantity);
 
 		this.createItemElement(item);
 
-		this.cartItems.set(id, {
+		let parent = this.determineParent(item);
+
+		let stored = {};
+		if (this.cartItems.has(id)) {
+			stored = this.cartItems.get(id);
+		}
+		let newValue = {
 			post_id:    id,
-			name:       item.dataset.name,
+			name:       stored.name ?? item.dataset.name,
+			label:		stored.label ?? parent.dataset.label ??'',
+			parentId: 	stored.parentId ?? parent.dataset.id ??'',
 			price:      price,
 			quantity:   quantity,
 			total:      total,
-			catalog_id: item.dataset.catalogId || '',
-		});
+			catalog_id: stored.catalogId ?? item.dataset.catalogId ?? '',
+		};
+		this.cartItems.set(id, newValue);
 		this.saveCart();
 	}
 
 	handleRemoveFromCart(item) {
+		console.log(item);
 		if (confirm('This will remove this item from the cart. Continue?')) {
-			if (!item.querySelector('[data-id]')) {
-				item = item.closest('.item')?.querySelector('.quantity.field');
+			let id = item.dataset.id??item.id??false;
+			if (!id) {
+				console.error('No ID found..');
 			}
-			let id = item.dataset.id;
-			this.cartItems.delete(id);
-			this.table.querySelector(`[data-id="${id}"]`)?.closest('tr').remove();
-
-			let input = document.querySelector(`[data-id="${id}"] input`);
-			if (input) input.value = 0;
+			this.removeFromCart(id);
 
 			this.maybeAddEmptyState();
 			this.saveCart();
 		}
 	}
+		removeFromCart(id) {
+			let parent = this.findParentItem(id);
+			if (parent) {
+				let el = parent.items.get(id);
+				el.el.remove();
+				parent.items.delete(id);
+				if (el.reference){
+					el.reference.value = 0;
+				}
+				if (parent.items.size === 0) {
+					console.log('Removing parent item');
+					parent.el.remove();
+					this.ui.cart.items.delete(parent.id);
+				}
+			}
+
+			this.cartItems.delete(id);
+		}
 
 	handleNumberClick(e, container) {
 		e.preventDefault();
@@ -236,11 +357,15 @@
 		else if (e.target.closest('.decrease'))  change -= 1;
 
 		if (change !== 0) {
-			let step  = parseInt(container.dataset.step);
 			let input = container.querySelector('input');
+			let step  = parseInt(input.step);
 			let value = (input.value === '') ? 0 : parseInt(input.value);
+			if (value === 0 && change < 0) return;
 
-			input.value = value + (step * change * this.stepMultiplier);
+			let increaseBy = step * change * this.stepMultiplier;
+
+			let newValue = value + increaseBy;
+			input.value = Math.max(newValue, 0);
 			input.dispatchEvent(new Event('change', { bubbles: true }));
 			this.handleNumberLimits(container);
 		}
@@ -254,10 +379,12 @@
 		let decrease = container.querySelector('.decrease');
 		let value    = parseInt(input.value);
 
-		if (value <= min) {
+		if (min !== undefined && value <= min) {
+			console.log('Resetting to minimum value');
 			input.value      = min;
 			decrease.disabled = true;
-		} else if (value >= max) {
+		} else if (max !== undefined && value >= max) {
+			console.log('Resetting to maximum value');
 			input.value      = max;
 			increase.disabled = true;
 		} else {
@@ -271,100 +398,163 @@
 	 *****************************************************************/
 
 	createItemElement(item) {
-		let element = this.itemsList.querySelector(`[data-id="${item.dataset.id}"]`);
-		let add     = false;
-		let price   = item.dataset.price;
-		let quantity = item.querySelector('[name="quantity"]')?.value ?? 1;
+		let element = this.maybeCreateCartItem(item);
 
-		if (!element) {
-			add     = true;
-			element = window.getTemplate('cartItem');
-			let field = element.querySelector('.quantity');
-			[
-				field.dataset.id,
-				element.querySelector('label').textContent,
-				element.querySelector('.price').textContent,
-				field.dataset.price,
-				field.dataset.catalogId,
-			] = [
-				item.dataset.id,
-				item.dataset.name,
-				window.formatPrice(price),
-				price,
-				item.dataset.catalogId || '',
-			];
-		} else {
-			element = element.closest('tr');
-		}
+		let quantity = item.tagName === undefined ?
+			item.quantity :
+			item.querySelector('input').value ?? 1;
+		let price = item.tagName === undefined ?
+			item.price :
+			item.dataset.price;
 
-		[
-			element.querySelector('[name="quantity"]').value,
-			element.querySelector('.total').textContent,
-		] = [
-			quantity,
-			window.formatPrice(quantity * price),
-		];
-
-		if (add) {
-			element.classList.add('adding');
-			this.table.append(element);
-			setTimeout(() => element.classList.remove('adding'), 500);
+		element.quantity.value = quantity;
+		element.total.textContent = window.formatPrice(quantity * price);
+		if (element.reference) {
+			element.reference.value = quantity;
 		}
 	}
+		maybeCreateCartItem(item) {
+			let normalized = {};
+			if (item.tagName === undefined) {
+				//We're getting it from the cached cart
+				normalized = {
+					id: String(item.post_id),
+					name: item.name,
+					price: item.price,
+					catalogId: item.catalog_id || '',
+					quantity: item.quantity,
+					label: item.label,
+					parentId: String(item.parentId)
+				};
+			}  else if (item.closest('#cart')) {
+				// quantity field clicked from inside the cart itself — item already exists
+				let id = item.dataset.id;
+				let parent = this.findParentItem(id);
+				if (parent) {
+					return parent.items.get(id);
+				}
+				// fallback: shouldn't normally happen, but avoid crashing
+				console.warn('Cart quantity changed but no existing row found for id:', id);
+			}else {
+				//We're getting it direct from the quantity field
+				normalized = {
+					id: item.dataset.id,
+					name: item.dataset.name,
+					price: item.dataset.price,
+					catalogId: item.dataset.catalogId || '',
+				}
+				let parent = this.determineParent(item);
+				normalized.parentId = parent.dataset.id??parent.id;
+				normalized.label = parent.dataset.label??parent.label;
+			}
+
+			if (!this.ui.cart.items.has(normalized.parentId)) {
+				let parentEl = this.T.create('mainCartItem', normalized);
+				this.ui.cart.items.set(normalized.parentId, {
+					el: parentEl,
+					id: normalized.parentId,
+					label: normalized.label,
+					items: new Map()
+				});
+				this.ui.cart.table.insertBefore(parentEl, this.ui.cart.foot.foot);
+			}
+
+			let parent = this.ui.cart.items.get(normalized.parentId);
+			if (!parent.items.has(normalized.id)) {
+				let el = this.T.create('childCartItem', normalized);
+				parent.items.set(normalized.id, {
+					el: el,
+					quantity: el.querySelector('[name="quantity"]'),
+					total: el.querySelector('.total'),
+					reference: document.querySelector(`.menu-items [data-id="${normalized.id}"] input`),
+				});
+				parent.el.append(el);
+			}
+
+			return parent.items.get(normalized.id);
+		}
+			determineParent(item) {
+				if (item.tagName !== undefined) {
+					return this.parentFromHTML(item);
+				}
+			}
+				parentFromHTML(item) {
+					let parent = item.closest('.menu-item');
+					if (parent) return parent;
+					let id = item.dataset.id;
+					return this.findParentItem(id).el??false;
+				}
+			findParentItem(searchId) {
+				let parentItem = false;
+				for (let [id, data] of this.ui.cart.items.entries()) {
+					if (data.items.has(searchId)) {
+						parentItem = this.ui.cart.items.get(id);
+					}
+				}
+				return parentItem;
+			}
+
 
 	/*****************************************************************
 	 * CART STATE
 	 *****************************************************************/
-
+	toggleButton(open = false)
+	{
+		this.ui.toggle.icon.hidden = open;
+	}
 	maybeAddEmptyState() {
-		let empty = this.itemsList.querySelector('.empty');
-		if (empty) empty.remove();
+		this.toggleButton(true);
+		this.findEmpty();
 
 		if (this.cartItems.size === 0) {
-			this.checkoutPanel.disabled = true;
-			this.checkoutPanel.title    = 'Add some things to your cart first!';
-			let emptyEl = window.getTemplate('emptyCart');
-			this.itemsList.append(emptyEl);
-			this.table.closest('table').hidden = true;
-			this.total.hidden = true;
+			this.ui.panels.checkout.toggle.disabled = true;
+			this.ui.panels.checkout.toggle.title    = 'Add some things to your cart first!';
+			this.ui.cart.empty.hidden = false;
+			this.ui.cart.table.hidden = true;
+			this.ui.cart.total.hidden = true;
 			this.a11y.announce('Nothing in Cart');
 		} else {
-			this.checkoutPanel.disabled       = false;
-			this.table.closest('table').hidden = false;
-			this.total.hidden                  = false;
-			this.checkoutPanel.title           = 'Checkout';
+			this.ui.cart.empty.hidden = true;
+			this.ui.panels.checkout.toggle.disabled       = false;
+			this.ui.cart.table.hidden = false;
+			this.ui.cart.total.hidden                  = false;
+			this.ui.panels.checkout.toggle.title           = 'Checkout';
 		}
 	}
+		findEmpty() {
+			if (!this.ui.cart.empty) {
+				this.ui.cart.empty = this.T.create('emptyCart');
+				this.ui.cart.wrap.append(this.ui.cart.empty);
+			}
+		}
 
 	notifyRestoredCart() {
 		let restored = window.getTemplate('restoredCart');
-		this.checkout.querySelector('.tab-content[data-tab=cartItems]').insertBefore(restored, this.itemsList);
+		document.body.append(restored);
+		restored.classList.toggle('show');
+		this.restoreElement = restored;
 
 		this.cartItems.forEach(item => {
-			let element = window.getTemplate('cartItem');
-			let field   = element.querySelector('.quantity');
-			[
-				field.dataset.id,
-				element.querySelector('label').textContent,
-				element.querySelector('.price').textContent,
-				field.dataset.price,
-				field.dataset.catalogId,
-				element.querySelector('[name="quantity"]').value,
-				element.querySelector('.total').textContent,
-			] = [
-				item.post_id,
-				item.name,
-				window.formatPrice(item.price),
-				item.price,
-				item.catalog_id || '',
-				item.quantity,
-				window.formatPrice(item.quantity * item.price),
-			];
-			this.table.append(element);
+			this.createItemElement(item);
 		});
 		this.updateTotal();
 	}
 
+	updateToggle() {
+		if (this.cartItems.size > 0 && this.ui.toggle.btn.classList.contains('empty')) {
+			this.ui.toggle.btn.classList.remove('empty');
+		}
+		if (this.cartItems.size === 0 && !this.ui.toggle.btn.classList.contains('empty')) {
+			this.ui.toggle.btn.classList.add('empty');
+		}
+		let total = 0;
+		for (let [id, item] of this.cartItems.entries()) {
+			total += item.quantity;
+		}
+
+		this.ui.toggle.count.textContent = this.cartItems.size === 0 ? '' : total;
+	}
+
 	/*****************************************************************
 	 * TOTALS
 	 *****************************************************************/
@@ -373,11 +563,13 @@
 		let total = 0;
 		this.cartItems.forEach(item => total += item.total);
 		let tax = total * 0.05;
-		window.eraseText(this.totalTax);
-		window.eraseText(this.grandTotal);
-		window.typeText(this.totalTax, window.formatPrice(tax));
-		window.typeText(this.grandTotal, window.formatPrice(total + tax));
-		this.totalTax.classList.remove('typeText');
+		window.eraseText(this.ui.cart.tax);
+		window.eraseText(this.ui.cart.foot.tax);
+		window.eraseText(this.ui.cart.foot.total);
+		window.typeText(this.ui.cart.foot.tax, window.formatPrice(tax));
+		window.typeText(this.ui.cart.grandTotal, window.formatPrice(total + tax));
+		window.typeText(this.ui.cart.foot.total, window.formatPrice(total + tax));
+		this.ui.cart.tax.classList.remove('typeText');
 	}
 
 	/*****************************************************************
diff --git a/assets/js/concise/CopyHours.js b/assets/js/concise/CopyHours.js
index 250952e..cf1cd5a 100644
--- a/assets/js/concise/CopyHours.js
+++ b/assets/js/concise/CopyHours.js
@@ -42,14 +42,11 @@
 				case 'modal-open':
 					let e = data.event;
 					let modal = data.modal;
-					const dayGroup = e.target.closest('.group-fields:not(.hours)');
-					console.log(dayGroup);
+					const dayGroup = e.target.closest('.group:not([data-field="openingHours"])');
 					const dayName = this.getDayNameFromGroup(dayGroup);
-					console.log(dayName);
 					if (!dayName) return;
 
 					this.currentSourceDay = dayName;
-					console.log(dayName);
 
 					// Get source day hours
 					const sourceData = this.getSourceDayData(dayGroup);
@@ -118,15 +115,13 @@
 	}
 
 	getDayNameFromGroup(group) {
-		const classList = Array.from(group.classList);
-		return this.days.find(day => classList.includes(day));
+		return group.dataset.field.replace('openingHours|','');
 	}
 
 	getSourceDayData(dayGroup) {
-		console.log(dayGroup);
-		const openCheckbox = dayGroup.querySelector('input[type="checkbox"][name$=":open"]');
-		const opensInput = dayGroup.querySelector('input[name$=":time_opens"]');
-		const closesInput = dayGroup.querySelector('input[name$=":time_closes"]');
+		const openCheckbox = dayGroup.querySelector(`input[type="checkbox"][name="openingHours|${this.currentSourceDay}|isOpen"]`);
+		const opensInput = dayGroup.querySelector(`input[name="openingHours|${this.currentSourceDay}|opens"]`);
+		const closesInput = dayGroup.querySelector(`input[name="openingHours|${this.currentSourceDay}|closes"]`);
 
 		return {
 			isOpen: openCheckbox ? openCheckbox.checked : false,
@@ -138,7 +133,7 @@
 	applyCopyHours() {
 		if (!this.currentSourceDay) return;
 
-		const sourceGroup = document.querySelector(`.group-fields.${this.currentSourceDay}`);
+		const sourceGroup = document.querySelector(`.group[data-field="openingHours|${this.currentSourceDay}"]`);
 		if (!sourceGroup) return;
 
 		const sourceData = this.getSourceDayData(sourceGroup);
@@ -164,12 +159,12 @@
 	}
 
 	applyHoursToDay(dayName, sourceData) {
-		const targetGroup = document.querySelector(`.group-fields.${dayName}`);
+		const targetGroup = document.querySelector(`.group[data-field="openingHours|${dayName}`);
 		if (!targetGroup) return;
 
-		const openCheckbox = targetGroup.querySelector('input[type="checkbox"][name$=":open"]');
-		const opensInput = targetGroup.querySelector('input[name$=":time_opens"]');
-		const closesInput = targetGroup.querySelector('input[name$=":time_closes"]');
+		const openCheckbox = targetGroup.querySelector(`input[type="checkbox"][name="openingHours|${dayName}|isOpen"]`);
+		const opensInput = targetGroup.querySelector(`input[name="openingHours|${dayName}|opens"]`);
+		const closesInput = targetGroup.querySelector(`input[name="openingHours|${dayName}|closes"]`);
 		console.log('applying hours to days: ', {
 			openCheckbox,
 			opensInput,
@@ -185,10 +180,14 @@
 		// Set time values if open
 		if (sourceData.isOpen && opensInput && closesInput) {
 			opensInput.value = sourceData.opens;
+			opensInput.dispatchEvent(new Event('change', {bubbles: true}));
 			closesInput.value = sourceData.closes;
+			closesInput.dispatchEvent(new Event('change', {bubbles: true}));
 		} else if (opensInput && closesInput) {
 			opensInput.value = '';
+			opensInput.dispatchEvent(new Event('change', {bubbles: true}));
 			closesInput.value = '';
+			closesInput.dispatchEvent(new Event('change', {bubbles: true}));
 		}
 
 	}
@@ -196,12 +195,6 @@
 	showSuccessFeedback(count) {
 		// Create or update feedback element
 		let feedback = document.getElementById('copy-success-feedback');
-		if (!feedback) {
-			feedback = document.createElement('div');
-			feedback.id = 'copy-success-feedback';
-			feedback.className = 'copy-success-feedback';
-			document.body.appendChild(feedback);
-		}
 
 		const message = count === 1 ?
 			'Hours copied to 1 day successfully!' :
diff --git a/assets/js/concise/FormController.js b/assets/js/concise/FormController.js
index 617c3a6..d5e5835 100644
--- a/assets/js/concise/FormController.js
+++ b/assets/js/concise/FormController.js
@@ -51,7 +51,7 @@
 		this.selectors = {
 			tabs: {
 				nav: 'nav.tabs',
-				sections: '.tab.content',			//querySelectorAll
+				sections: '.tab-content',			//querySelectorAll
 				progress: {
 					progress: '.progress',
 					fill: '.progress .fill',
@@ -84,6 +84,8 @@
 			},
 			repeater: {
 				repeater: '.repeater',				//querySelectorAll
+				row: '.repeater-row',
+				handle: '.drag-handle',
 				header: '.repeater-row-header',
 				remove: '.remove-row',
 				add: '.add-repeater-row',
@@ -356,6 +358,28 @@
 				case 'dismiss-restore':
 					form.ui.status.status.hidden = true;
 					break;
+				case 'next-step':
+					e.preventDefault();
+					form.tabs.ui.buttons.forEach((btn, index) => {
+						if (btn.dataset.tab === form.tabs.activeTab) {
+							let next = form.tabs.ui.buttons[index + 1]??false;
+							if (next) {
+								window.jvbTabs.switchTab(next.dataset.tab, window.jvbTabs.getConfig(next));
+							}
+						}
+					});
+					break;
+				case 'prev-step':
+					e.preventDefault();
+					form.tabs.ui.buttons.forEach((btn, index) => {
+						if (btn.dataset.tab === form.tabs.activeTab) {
+							let prev = form.tabs.ui.buttons[index - 1]??false;
+							if (prev) {
+								window.jvbTabs.switchTab(prev.dataset.tab, window.jvbTabs.getConfig(prev));
+							}
+						}
+					});
+					break;
 			}
 		}
 	}
@@ -376,6 +400,7 @@
 				});
 			}
 			const collectionName = collectionField.dataset.field;
+			this.maybeUpdateCollectionDisplay(field);
 			window.debouncer.schedule(
 				`collection:${collectionName}`,
 				() => this.updateCollectionField(collectionField),
@@ -460,7 +485,7 @@
 					data: this.changes.get(form.id)?.changes??{},
 				});
 			}
-			if (form.options.endpoint) {
+			if (form.options.endpoint && !form.options.handled) {
 				console.log('Submitting form...');
 				await this.handleServerSave(form, this.collectFormData(form.form));
 			}
@@ -500,15 +525,79 @@
 			changes.changes[name] = value;
 		}
 
+		if (form.tabs) {
+			console.log('Updating step progress on change');
+			this.updateStepProgress(form);
+		}
+
 		this.changes.set(form.id, changes);
 		if (form.options.cache) {
 			this.scheduleBackup();
 		}
 
-		if (form.options.endpoint) {
+		if (form.options.endpoint && !form.options.handled) {
 			this.scheduleServerSave(form, changes);
 		}
 	}
+		updateStepProgress(form) {
+			console.log('First check in updateStepProgress');
+			if (!form.tabs) return;
+			let currentStep = this.getCurrentStep(form);
+			console.log('Current step: ', currentStep);
+			if (!currentStep) return;
+
+			//The progress root will be 1 less than whatever the current step is
+			currentStep = currentStep - 1;
+
+			let section = this.getCurrentSection(form);
+			console.log('Current section: ', section);
+			if (!section) return;
+
+			let total = 0;
+			let filled = 0;
+			section.childNodes.forEach(f => {
+				console.log('Child node: ', f);
+				if (f.classList.contains('field')) {
+					total++;
+					let value = this.getFieldValue(f.querySelector(this.inputSelectors));
+					console.log('Value', value);
+					if (value.length > 0) {
+						filled++;
+					}
+				}
+			});
+			//Shouldn't happen, but simplifies things if it does
+			if (total === 0) return;
+			let current = currentStep + (filled / total);
+
+			let totalSteps = form.ui.tabs.sections.length;
+			window.showProgress(
+				form.ui.tabs.progress,
+				current,
+				totalSteps,
+				`Step ${currentStep +1} of ${totalSteps}`
+			);
+		}
+			getCurrentStep(form) {
+				let step = null;
+				form.tabs.ui.buttons.forEach(btn => {
+					if (btn.dataset.tab === form.tabs.activeTab) {
+						step = btn.dataset.step;
+					}
+				});
+				return step;
+			}
+
+			getCurrentSection(form) {
+				console.log('Getting current section: ', form.tabs.ui.sections);
+				let section = null;
+				form.tabs.ui.sections.forEach(s => {
+					if (s.dataset.tab === form.tabs.activeTab) {
+						section = s;
+					}
+				});
+				return section;
+			}
 
 	scheduleBackup() {
 		window.debouncer.schedule(
@@ -608,6 +697,7 @@
 			endpoint: Object.hasOwn(form.dataset, 'save') ? form.dataset.save: '',
 			showStatus: true,
 			showSummary: false,
+			handled: Object.hasOwn(form.dataset, 'handled'),
 			cache: true,
 			ignore: [],
 			... options
@@ -914,9 +1004,7 @@
 		}
 	}
 	checkForRepeaters(form) {
-
 		if (!form.querySelector(this.selectors.repeater.repeater)) return;
-
 		form.querySelectorAll(this.selectors.repeater.repeater).forEach(repeater => {
 			let config = {
 				id: repeater.querySelector('template').className??window.generateID('repeater'),
@@ -949,11 +1037,15 @@
 			);
 
 			if (window.Sortable) {
-				config.sortable = new Sortable(repeater, {
-					handle: this.selectors.repeater.header,
+				config.sortable = new Sortable(config.ui.items, {
 					animation: 150,
+					draggable: this.selectors.repeater.row,
+					handle: this.selectors.repeater.handle,
+					ghostClass: 'repeater-row-ghost',
+					chosenClass: 'repeater-row-chosen',
+					dragClass: 'dragging',
 					onEnd: () => {
-						this.reindexList(repeater);
+						this.reindexList(config.ui.items);
 					}
 				});
 			}
@@ -992,12 +1084,17 @@
 
 		let form = this.getForm(repeater);
 		this.initializeFields(repeater, form);
+		this.reindexList(config.ui.items);
 		this.a11y.announce('Row added');
 	}
 	removeRepeaterRow(row) {
 		let repeater = row.closest('[data-repeater-id]');
 		row.remove();
-		this.reindexList(repeater);
+		let items = this.repeaters.get(repeater);
+		if (items) {
+			this.reindexList(items.ui.items);
+		}
+
 		this.a11y.announce('Row removed');
 	}
 	checkForTagLists(form) {
@@ -1320,16 +1417,17 @@
 
 	checkForTabs(form, config) {
 		if (window.jvbTabs && form.querySelector('nav.tabs')) {
+
 			config.tabs = window.jvbTabs.registerTab(form, {
-				preCheck: (section, tabConfig) => {
-					return this.validateStep(section, config);
+				preCheck: (activeSection, switchTo, tabConfig) => {
+					return this.validateStep(activeSection, switchTo, tabConfig);
 				}
 			});
 			config.ui.tabs = window.uiFromSelectors(this.selectors.tabs, form);
 			config.ui.tabs.sections = Array.from(form.querySelectorAll(this.selectors.tabs.sections));
 			config.ui.tabs.inputs = {};
 			config.ui.tabs.sections.forEach(section => {
-				config.ui.tabs.inputs[section.dataset.tab] = Array.from(section.querySelectorAll(this.inputs));
+				config.ui.tabs.inputs[section.dataset.tab] = Array.from(section.querySelectorAll(this.inputSelectors));
 			});
 			config.ui.tabs.buttons = Array.from(form.querySelectorAll(this.selectors.tabs.buttons));
 
@@ -1338,13 +1436,14 @@
 					if (config.ui.tabs.progress) {
 						const section = config.ui.tabs.sections.filter(section => section.dataset.tab === data.current)[0]??false;
 						if (!section) return;
-						const step = section.dataset.step;
-						const total = config.ui.sections.length;
+						const step = section.dataset.step - 1;
+						const total = config.ui.tabs.sections.length;
 
 						window.showProgress(
 							config.ui.tabs.progress,
 							step,
-							total
+							total,
+							`Step ${step + 1} of ${total}`
 						);
 					}
 				}
@@ -1352,8 +1451,24 @@
 			this.forms.set(config.id, config);
 		}
 	}
-	validateStep(section, config) {
-		const formId = section.closest('[data-form-id]')?.dataset.formId;
+	validateStep(currentSection, switchTo, config) {
+		let doit = false;
+		if (config.ui.sections) {
+			config.ui.sections.forEach((section, index) => {
+				if (section === currentSection) {
+					let prev = index -1;
+					if (prev >= 0) {
+						let prevSection = config.ui.sections[prev];
+						if (prevSection && prevSection.dataset.tab === switchTo) {
+							doit = true;
+						}
+					}
+				}
+			});
+		}
+		if (doit) return true;
+
+		const formId = currentSection.closest('[data-form-id]')?.dataset.formId;
 		if (!formId) return true;
 
 		const form = this.forms.get(formId);
@@ -1363,7 +1478,7 @@
 			.filter(item =>
 				item &&
 				item.form === formId &&
-				item.section === section.dataset.tab &&
+				item.section === currentSection.dataset.tab &&
 				!item.element.closest('[hidden]')
 			);
 
@@ -1377,11 +1492,21 @@
 	 * @param {HTMLElement} container
 	 */
 	reindexList(container) {
-		const fieldName = container.dataset.field || container.dataset.repeaterId || container.dataset.tagListId;
+		let fieldName = container.dataset.field || container.dataset.repeaterId || container.dataset.tagListId;
+		if (!fieldName) {
+			let parent = container.closest('[data-field]');
+			fieldName = parent.dataset.field || parent.dataset.repeaterId || parent.dataset.tagListId;
+		}
 
 		Array.from(container.children).forEach((item, index) => {
 			item.dataset.index = `${index}`;
 
+			let rowNum = item.querySelector('.row-number');
+			if (rowNum) {
+				rowNum.textContent = `#${index + 1}`;
+			}
+			let rowTitle = item.querySelector('.row-title');
+			let rowLabel = rowTitle ? rowTitle.dataset.label : null;
 			// Find ALL inputs within this item, not just direct children
 			const inputs = item.querySelectorAll('input, select, textarea');
 
@@ -1390,14 +1515,17 @@
 				if (input.type === 'file') return;
 
 				// Get the field name from the input's data-field or name
-				const inputField = input.dataset.field || input.name.split(':').pop();
+				const inputField = Object.hasOwn(input.dataset, 'field') ? input.dataset.field : input.name.split(':').pop();
+				if (inputField === rowLabel) {
+					rowTitle.textContent = window.escapeHtml(input.value);
+				}
 
 				// Re-prefix with the new index, passing item as wrapper
 				window.prefixInput(
 					input,
-					`${fieldName}:${index}:`,
+					`${fieldName}:${index}:${inputField}`,
 					item,
-					false,
+					true,
 					true
 				);
 			});
@@ -1425,6 +1553,27 @@
 		const value = this.getFieldValue(field);
 		this.updateItem(field.dataset.field, value, form);
 	}
+
+	//TODO: Store these selectors in the repeater object maybe?
+	maybeUpdateCollectionDisplay(field) {
+		let parent = field.closest('[data-repeater-id],[data-tag-list-id]');
+		if (!parent) {
+			return;
+		}
+		if (parent.dataset.fieldType === 'repeater') {
+			let row = field.closest('.repeater-row');
+			if (!row) return;
+
+			let title = row.querySelector('.row-title');
+			if (!title || !title.dataset.label) return;
+			console.log(field.dataset.field.split(':').pop());
+			console.log(title.dataset.label);
+			if (field.dataset.field.split(':').pop() === title.dataset.label) {
+				title.textContent = this.getFieldValue(field.querySelector(this.inputSelectors));
+			}
+		}
+
+	}
 	/**********************************************************************
 	 VALIDATION
 	 **********************************************************************/
@@ -1767,6 +1916,10 @@
 		let value = [];
 		Array.from(items.children).forEach(row => {
 			let rowData = {};
+			row.querySelectorAll('input[type="hidden"]').forEach(hidden => {
+				rowData[hidden.name] = hidden.value;
+			});
+
 			row.querySelectorAll('[data-field]').forEach(field => {
 				if (!ignore.includes(field.dataset.field)) {
 					const input = this.getFieldInput(field);
diff --git a/assets/js/concise/Popup.js b/assets/js/concise/Popup.js
index b22d317..3194d63 100644
--- a/assets/js/concise/Popup.js
+++ b/assets/js/concise/Popup.js
@@ -62,7 +62,7 @@
 	handleClick(e) {
 		// Check if click is on any registered toggle
 		for (const [id, entry] of this.popups) {
-			if (e.target === entry.toggle || e.target.closest(id)) {
+			if (e.target === entry.toggle || window.targetCheck(e, id) || window.targetCheck(e, entry.toggle.tagName + '.' + [...entry.toggle.classList].join('.'))) {
 				this.togglePopup(id);
 				return;
 			}
diff --git a/assets/js/concise/SimpleCache.js b/assets/js/concise/SimpleCache.js
index e33fd14..afd488d 100644
--- a/assets/js/concise/SimpleCache.js
+++ b/assets/js/concise/SimpleCache.js
@@ -45,6 +45,9 @@
 	/**
 	 * Get a setting value
 	 */
+	/**
+	 * Get a setting value
+	 */
 	get(key) {
 		//Check memory cache first
 		if (this._cache.has(key)) {
@@ -54,11 +57,11 @@
 		let cacheKey = `${this.base}_${key}`;
 		let item;
 		try {
-			item =  localStorage.getItem(cacheKey);
+			item = localStorage.getItem(cacheKey);
 			if (!item) {
 				return null;
 			}
-			item = JSON.parse(item);
+			item = this._revive(JSON.parse(item));
 		} catch (error) {
 			console.warn('Error getting from localStorage:', error);
 			return null;
@@ -76,15 +79,16 @@
 	set(key, value) {
 		this._cache.set(key, value);
 		let cacheKey = `${this.base}_${key}`;
+		let serialized = JSON.stringify(this._serialize(value));
 
 		try {
-			localStorage.setItem(cacheKey, JSON.stringify(value));
+			localStorage.setItem(cacheKey, serialized);
 		} catch (error) {
 			// Handle quota exceeded
 			if (error instanceof DOMException && error.code === 22) {
 				this.clearOldestLocalStorageItems();
 				try {
-					localStorage.setItem(key, JSON.stringify(item));
+					localStorage.setItem(cacheKey, serialized);
 				} catch (retryError) {
 					console.warn('Still failed to set localStorage item after cleanup:', retryError);
 				}
@@ -97,6 +101,30 @@
 		this.notify('cache-saved', { key, value });
 	}
 
+	/**
+	 * Tag Maps/Sets so they survive JSON round-tripping
+	 */
+	_serialize(value) {
+		if (value instanceof Map) {
+			return { __type: 'Map', value: Array.from(value.entries()) };
+		}
+		if (value instanceof Set) {
+			return { __type: 'Set', value: Array.from(value.values()) };
+		}
+		return value;
+	}
+
+	/**
+	 * Revive a parsed value, restoring Maps/Sets if tagged
+	 */
+	_revive(parsed) {
+		if (parsed && typeof parsed === 'object') {
+			if (parsed.__type === 'Map') return new Map(parsed.value);
+			if (parsed.__type === 'Set') return new Set(parsed.value);
+		}
+		return parsed;
+	}
+
 	remove(key) {
 		let cacheKey = `${this.base}_${key}`;
 		try {
@@ -150,7 +178,7 @@
 				let cleanKey = key.replace(`${this.base}_`, '');
 				try {
 					// Parse the JSON value before caching
-					const value = JSON.parse(localStorage.getItem(key));
+					const value = this._revive(JSON.parse(localStorage.getItem(key)));
 					this._cache.set(cleanKey, value);
 				} catch (error) {
 					console.warn(`Failed to parse cached value for ${key}:`, error);
diff --git a/assets/js/concise/Tabs.js b/assets/js/concise/Tabs.js
index b93c083..3828671 100644
--- a/assets/js/concise/Tabs.js
+++ b/assets/js/concise/Tabs.js
@@ -61,6 +61,9 @@
 			console.error('No tab navigation or section found');
 			return false;
 		}
+		if (ui.nav.previousElementSibling && ui.nav.previousElementSibling.classList.contains('form-progress')) {
+
+		}
 		let tabsId = window.generateID('tab');
 
 		container.dataset.tabsId = tabsId;
@@ -82,6 +85,9 @@
 			ui: ui,
 			updateURL: options.updateURL ?? true
 		};
+		if (Object.hasOwn(options, 'preCheck')) {
+			config.preCheck = options.preCheck;
+		}
 
 		//Add listeners
 		ui.nav.addEventListener('click', this.clickHandler);
@@ -148,11 +154,15 @@
 		if (!config) return;
 		let activeTab = config.ui.sections.filter(section => section.classList.contains('active'));
 
-		if (Object.hasOwn(config, 'preCheck') && !config.preCheck(activeTab[0], config)) return;
+		if (Object.hasOwn(config, 'preCheck') && !config.preCheck(activeTab[0], tab, config)) return;
+
 		if (document.activeElement && this.isInTabs(document.activeElement, config)) document.activeElement.blur();
 
-		config.ui.buttons.forEach((btn, index) => {
+		config.ui.buttons.forEach(btn => {
 			btn.classList.remove('active', 'previous', 'next');
+		});
+
+		config.ui.buttons.forEach((btn, index) => {
 			let isActive = btn.dataset.tab === tab;
 			btn.setAttribute('aria-selected', isActive);
 			if (isActive) {
diff --git a/assets/js/min/ContentManager.min.js b/assets/js/min/ContentManager.min.js
index 722b224..3bfc76f 100644
--- a/assets/js/min/ContentManager.min.js
+++ b/assets/js/min/ContentManager.min.js
@@ -1 +1 @@
-window.contentManager=class{constructor(e){this.config={content:"",plural:"",taxonomies:{},selectors:{container:".items-list",grid:".item-grid:not(.preview)",uploadZone:".file-upload-wrapper",statusFilters:".status-filters",dateFilters:".date-filters",taxonomyFilters:".taxonomy-filters",viewControls:".view-controls",bulkControls:".bulk-controls",scrollSentinel:".scroll-sentinel",editModal:".edit-modal",bulkEditModal:".bulk-edit-modal",clearButton:".clear-filters"},createPostPerFile:!0,uploadConfig:{mode:"direct",allowMultiple:!0,createPostPerFile:!0,maxSize:5242880,allowedTypes:["image/jpeg","image/png","image/gif","image/webp"]},...e},this.resetCache=!1,this.queueManager=window.jvbQueue,this.loadingManager=window.jvbLoading,this.cache=window.jvbCache,this.error=window.jvbError,this.state={selected:new Set,filters:{status:"all",taxonomies:{},date:null},view:localStorage.getItem(`${this.config.content}_view`)||"grid",loading:!1},this.queue={all:{items:new Map,page:1,hasMore:!0,totalPages:0},draft:{items:new Map,page:1,hasMore:!0,totalPages:0},publish:{items:new Map,page:1,hasMore:!0,totalPages:0},trash:{items:new Map,page:1,hasMore:!0,totalPages:0}},this.init()}async init(){this.elements={},Object.entries(this.config.selectors).forEach((([e,t])=>{this.elements[e]=document.querySelector(t)})),this.config.uploadConfig&&(this.fileUploader=new window.jvbFileUploader({...this.config.uploadConfig,content:this.config.content,fieldName:null})),this.initStatusFilters(),this.initDateFilters(),this.initTaxonomyFilters(),this.initClearFilters(),this.initViewControls(),this.initBulkControls(),this.initInfiniteScroll(),this.initModals(),await this.loadContent()}queueContentUpdate(e,t){const s={type:"content_update",data:{posts:{[e]:{content:this.config.content,...t}},content:this.config.content}};this.queueManager.addToQueue(s),this.updateLocalState(e,t)}queueBulkUpdate(e,t){const s={};e.forEach((e=>{s[e]={content:this.config.content,...t}}));const i={user:window.auth.getUser(),type:"content_update",data:{posts:s}};this.queueManager.addToQueue(i),e.forEach((e=>this.updateLocalState(e,t)))}updateLocalState(e,t){const s=this.queue[this.state.filters.status].items.get(e);if(s){Object.assign(s,t),this.queue[this.state.filters.status].items.set(e,s);const i=this.elements.grid.querySelector(`[data-id="${e}"]`);i&&this.updateItemElement(i,s)}}processFormData(e){const t={};for(const[s,i]of e.entries())if("status"===s)t.status=i;else if(s.startsWith("taxonomy_")){const e=s.replace("taxonomy_","");t.taxonomies||(t.taxonomies={}),t.taxonomies[e]=Array.isArray(i)?i:[i]}else t[s]=i;return t}updateItemElement(e,t){e.classList.remove("draft","publish","trash"),e.classList.add(t.status);const s=e.querySelector(".action-status");s&&(removeChildren(s),s.append(getIcon(t.status))),t.taxonomies&&e.querySelectorAll(".label-group").forEach((e=>{const s=e.dataset.taxonomy;if(s&&t.taxonomies[s]){const i=t.taxonomies[s].terms;e.querySelector(".terms").innerHTML=this.renderTerms(i)}}))}handleItemAction(e,t){const s=t.dataset.id;switch(e){case"edit":this.editModal.handleOpen(),this.openEditModal(t),this.editModal.form&&new FormFields(this.editModal.form,{onSave:this.editModal.onSave(),itemID:t.dataset.id});break;case"restore":this.queueContentUpdate(s,{status:"draft"}),t.remove();break;case"trash":this.queueContentUpdate(s,{status:"trash"}),t.remove();break;case"delete":confirm(`Hold up! Are you sure you want to permanently delete this ${this.config.content}?\n\nThis is a forever kind of deal - no taking it back.`)&&(this.queueContentUpdate(s,{status:"delete"}),t.remove());break;case"toggle-status":const e="publish"===t.dataset.status?"draft":"publish";this.queueContentUpdate(s,{status:e}),t.dataset.status=e,removeChildren(t.querySelector(".action-status")),t.querySelector(".action-status").append(getIcon(e))}}async handleBulkOperation(e,t){window.jvbLoading.show("Processing bulk changes...");try{const s={};t.forEach((t=>{s[t]={content:this.config.content,status:e},["delete","trash","restore"].includes(e)&&document.querySelector('[data-id="'+t+'"]').remove()})),this.queueManager.addToQueue({type:"content_update",data:{posts:s}}),this.clearSelection(),this.showNotification("Bulk changes queued for processing")}catch(e){console.error("Bulk operation failed:",e),this.showNotification("Failed to queue bulk operation","error")}finally{window.jvbLoading.hide()}}getQueryKey(){return JSON.stringify({status:this.state.filters.status,page:this.state.page,filters:this.state.filters})}toggleItemSelection(e,t){const s=e.dataset.id;t?(this.state.selected.add(s),e.classList.add("selected"),e.querySelector("input[type=checkbox]").checked=!0):(this.state.selected.delete(s),e.classList.remove("selected"),e.querySelector("input[type=checkbox]").checked=!1)}async loadContent(e=!0){if(!this.state.loading)try{this.state.loading=!0,this.loadingManager.show();const t=this.state.filters.status;console.log("Loading Page: "),console.log(this.queue[t].page);const s=new URLSearchParams;s.set("type",this.config.content),s.set("page",this.queue[t].page),s.set("filters",JSON.stringify(this.state.filters)),s.set("user",window.auth.getUser()),e&&(this.queue[t].page=1,this.queue[t].items.clear(),removeChildren(this.elements.grid),this.elements.grid.classList.remove("empty"));const i=await this.cache.fetchWithCache(`${jvbSettings.api}content?`+s,{method:"GET",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash")}},{context:window.auth.getUser()+"-"+this.config.content,forceRefresh:!1});i.total>0?(this.elements.grid.classList.remove("empty"),i.items.forEach((e=>{this.queue[t].items.set(e.id,e)})),this.queue[t].page++,this.queue[t].totalPages=i.total_pages,this.queue[t].hasMore=this.queue[t].page<i.total_pages):(this.elements.grid.classList.add("empty"),this.elements.grid.innerHTML=`<div class="empty-state"><h3>${jvbSettings.icons[this.config.content]}Nothing here${jvbSettings.icons[this.config.content]}</h3><p>It doesn't look like you have any ${this.config.plural} yet.</p><p><small><i>Add some by uploading images above.</i></small></p></div>`,this.queue[t].page=1,this.queue[t].hasMore=!1),this.renderContent()}catch(e){console.error("Error loading content:",e),this.loadingManager.showError("Failed to load content")}finally{this.state.loading=!1,this.loadingManager.hide()}}renderContent(){const e=this.state.filters.status,t=this.queue[e].items;t.size>0&&(this.elements.grid.classList.remove("empty"),this.elements.grid.querySelector(".empty-state")&&removeChildren(this.elements.grid));const s=document.createDocumentFragment();t.forEach((t=>{const i=this.elements.grid.querySelector(`[data-id="${t.id}"]`);if(i){if(t.view!==this.state.view){const e=this.createItemElement(t);t.view=this.state.view,i.replaceWith(e)}}else{const e=this.createItemElement(t);t.view=this.state.view,s.appendChild(e)}this.queue[e].items.set(t.id,t)})),s.children.length>0&&this.elements.grid.appendChild(s)}createItemElement(e){let t=window.getTemplate(this.state.view+"View");t.classList.add(e.status),t.dataset.id=e.id,t.dataset.fields=JSON.stringify(e.fields),t.dataset.status=e.status,t.dataset.img=e.thumbnail;let s=t.querySelector(".gallery");if(e.images){t.dataset.images=e.images;let o=s.querySelector("img");for(var i of e.images){let e=o.cloneNode(!0);e.src=i.src,i.alt&&(e.alt=i.alt),s.appendChild(e)}o.remove()}else s.remove();let o=[],a=t.querySelector(".taxonomies"),n=a.querySelector(".label-group"),l=n.querySelector(".tax"),r=!1;for(let s in e.taxonomies){if(Object.keys(e.taxonomies[s].terms).length>0){r=!0,t.dataset[s]=JSON.stringify(e.taxonomies[s].terms);let i=n.cloneNode(!0),o=jvbSettings.icons[s];for(var c in i.innerHTML=o+i.innerHTML,i.querySelector(".screen-reader-text").textContent=e.taxonomies[s].name,e.taxonomies[s].terms){let e=l.cloneNode(!0);e.textContent=c.name,i.appendChild(e)}}else t.dataset[s]=JSON.stringify({});o.push(s)}r?(n.remove(),l.remove()):a.remove(),0===Object.keys(this.config.taxonomies).length&&(this.config.taxonomies=o);let d=t.querySelector("img");d.src=e.thumbnail,e.alt&&(d.alt=e.alt),t.querySelector(".date").textContent=formatDate(e.date);let u="Hide "+e.icon;"draft"===e.status&&(u="Show "+e.icon);let h=t.querySelector('button[data-action="toggle-status"]');return h.prepend(getIcon(e.status)),h.title=u,this.initItemEventListeners(t),t}initItemEventListeners(e){e.addEventListener("click",(t=>t.target.closest(".item-select")?(t.preventDefault(),this.toggleItemSelection(e,!e.classList.contains("selected")),void this.updateBulkControls()):t.target.closest(".action")?(t.preventDefault(),void this.handleItemAction(t.target.closest(".action").dataset.action,e)):void 0))}initInfiniteScroll(){this.elements.scrollSentinel&&new IntersectionObserver((e=>{e.forEach((e=>{e.isIntersecting&&this.queue[this.state.filters.status].hasMore&&this.loadContent(!1)}))})).observe(this.elements.scrollSentinel)}initStatusFilters(){const e=this.elements.container.querySelector(".controls");e&&e.addEventListener("change",(e=>{if("radio"===e.target.type&&"status-filters"===e.target.name){const t=e.target.id;t!==this.state.filters.status&&(this.state.filters.status=t,this.updateBulkActionOptions(),0===this.queue[t].items.size?this.loadContent(!0):this.renderContent())}}))}initDateFilters(){const e=this.elements.container.querySelector("select.date-filter"),t=this.elements.container.querySelector(".date-range");let s;if(e&&(this.hasFilters=!0,e.addEventListener("change",(e=>{const i=e.target.value;if(s=i,"custom"===i)return void t.showModal();t.close();const o=t.querySelector(".month-select");o&&(o.value=""),this.setDateFilter(i)})),e.addEventListener("click",(i=>{"custom"===s&&"custom"===e.value&&t.showModal()}))),t){const e=t.querySelector(".date-start"),s=t.querySelector(".date-end"),i=t.querySelector(".month-select");i&&i.addEventListener("change",(e=>{const[s,i]=e.target.value.split("-");if(s&&i){const e=new Date(s,i-1,1),o=new Date(s,i,0);o.setHours(23,59,59,999),this.setDateFilter("custom",e,o),t.close()}}));const o=()=>{const i=e.value,o=s.value;if(i&&o){const e=new Date(i),s=new Date(o);s.setHours(23,59,59,999),this.setDateFilter("custom",e,s),t.close()}};e.addEventListener("change",o),s.addEventListener("change",o)}}setDateFilter(e,t=null,s=null){const i=new Date;i.setHours(23,59,59,999);let o=t,a=s||i;if(!t&&""!==e)switch(o=new Date,e){case"today":o.setHours(0,0,0,0);break;case"week":o.setDate(i.getDate()-7);break;case"month":o.setMonth(i.getMonth()-1);break;case"year":o.setFullYear(i.getFullYear()-1)}this.state.filters.date=e?{range:{after:o.toISOString(),before:a.toISOString()},custom:"custom"===e}:{range:null,custom:!1},this.updateClearFiltersButton(),this.state.page=1,this.loadContent()}initTaxonomyFilters(){const e=this.elements.container.querySelectorAll(".filter[data-taxonomy]");e.length&&(this.hasFilters=!0,e.forEach((e=>{e.addEventListener("change",(e=>{const t=e.target.dataset.taxonomy,s=e.target.value;s?this.state.filters.taxonomies[t]=[parseInt(s)]:delete this.state.filters.taxonomies[t],this.updateClearFiltersButton(),this.state.page=1,this.loadContent(!0)}))})))}updateClearFiltersButton(){const e=document.querySelector(this.config.selectors.clearButton);if(!e)return;const t=Object.keys(this.state.filters.taxonomies).length>0||null!==this.state.filters.date.range;e.hidden=!t}clearAllFilters(){this.elements.container.querySelectorAll(".filter[data-taxonomy]").forEach((e=>e.value=""));const e=this.elements.container.querySelector("select.date-filter");e&&(e.value=""),this.state.filters={date:{range:null,custom:!1},taxonomies:{}},this.updateClearFiltersButton(),this.state.page=1,this.loadContent(!0)}initClearFilters(){this.config.selectors.clearButton&&document.querySelector(this.config.selectors.clearButton).addEventListener("click",(()=>this.clearAllFilters()))}initViewControls(){const e=this.elements.container.querySelector(".view-controls");if(!e)return;e.addEventListener("change",(e=>{const t=e.target;"radio"===t.type&&(this.setView(t.value),this.loadContent(!0))}));const t=localStorage.getItem(`${this.config.content}_view`)||"grid",s=e.querySelector(`input[value="${t}"]`);s&&(s.checked=!0,this.setView(t))}setView(e){this.state.view=e;const t=new Set(this.state.selected);this.elements.grid.classList.remove("grid-view","list-view"),this.elements.grid.classList.add(`${e}-view`),localStorage.setItem(`${this.config.content}_view`,e),this.loadContent(!0),t.forEach((e=>{const t=this.elements.grid.querySelector(`[data-id="${e}"]`);if(t){const e=t.querySelector('input[type="checkbox"]');e&&(e.checked=!0,t.classList.add("selected"))}})),this.updateBulkControls()}initBulkControls(){if(!this.elements.bulkControls)return;this.selectAll=this.elements.bulkControls.querySelector(".select-all"),this.selectAll&&this.selectAll.addEventListener("change",(()=>{this.getVisibleItems().forEach((e=>{this.toggleItemSelection(e,this.selectAll.checked)})),this.updateBulkControls()}));const e=this.elements.bulkControls.querySelector(".bulk-action-select"),t=this.elements.bulkControls.querySelector(".apply-bulk");t&&e&&(this.updateBulkActionOptions(),this.elements.container.querySelector(".status-filters"),t.addEventListener("click",(()=>{const t=e.value;if(!t)return;const s=Array.from(this.state.selected);switch(t){case"restore":this.handleBulkOperation("restore",s);break;case"delete":confirm(`Hold up! Are you sure you want to permanently delete these ${this.config.plural}?\n\nThis is a forever kind of deal - no taking it back.`)&&this.handleBulkOperation("delete",s);break;case"trash":this.handleBulkOperation("trash",s);break;case"edit":this.openBulkEditModal();const e=document.querySelector(".bulk-edit-modal");if(e){const t=e.querySelector(".selected-count");t&&(t.textContent=`( ${s.length} items )`);const i=e.querySelector(".selected");if(i){let e="";s.forEach((t=>{let s=this.elements.grid.querySelector('[data-id="'+t+'"]');e+='<input type="checkbox" id="selected-'+t+'" name="posts" value="'+t+'" checked><label for="selected-'+t+'"><img width="100%" height="auto" src="'+s.dataset.img+'"></label>'})),i.innerHTML=e}}break;case"publish":case"draft":this.handleBulkOperation(t,s)}e.value=""})));const s=this.elements.bulkControls.querySelector(".cancel-bulk");s&&s.addEventListener("click",(()=>{this.clearSelection()}))}updateBulkActionOptions(){const e=this.elements.bulkControls.querySelector(".bulk-action-select");e&&("trash"===this.state.filters.status?e.innerHTML='\n            <option value="">Bulk Actions...</option>\n            <option value="restore">Restore</option>\n            <option value="delete">Permanently Delete</option>\n        ':e.innerHTML='\n            <option value="">Bulk Actions...</option>\n            <option value="edit">Edit</option>\n            <option value="publish">Show</option>\n            <option value="draft">Hide</option>\n            <option value="trash">Scrap</option>\n        ')}initModals(){this.elements.editModal&&(this.editModal=new window.jvbModal(this.elements.editModal,{open:!1,close:this.elements.editModal.querySelector(".cancel"),save:this.elements.editModal.querySelector(".save"),onSave:()=>{const e=new FormData(this.elements.editModal.querySelector("form"));let t={};const s=this.elements.editModal.querySelectorAll(".taxonomies .jvb-selector");let i=Object.fromEntries(e);s.forEach((e=>{const s=e.dataset.taxonomy.replace(jvbSettings.base||"jvb_","");if(delete i["edit-"+s],e.__instance){const i=e.__instance.selectedItems;i&&Object.keys(i).length>0&&(t[s]=Object.keys(i).join(","))}})),i.taxonomies=t;for(let[e,t]of Object.entries(i))""!==t&&0!==Object.keys(t).length||delete i[e];this.queueContentUpdate(this.elements.editModal.dataset.id,i)}}));const e=this.elements.bulkEditModal;if(e){let t=!1;const s=e.querySelector("form");s?.addEventListener("change",(()=>{t=!0})),e.addEventListener("keydown",(s=>{"Escape"===s.key&&(s.preventDefault(),this.handleModalClose(e,t))})),e.addEventListener("click",(s=>{s.target===e&&this.handleModalClose(e,t)})),e.querySelector(".cancel")?.addEventListener("click",(()=>{this.handleModalClose(e,t),this.clearSelection()})),e.querySelector(".save")?.addEventListener("click",(()=>{const i=new FormData(s),o=Array.from(i.getAll("posts")),a={};""===i.get("term_name")&&(i.delete("term_name"),i.delete("select_parent"));let n={};e.querySelectorAll(".taxonomies .jvb-selector").forEach((e=>{const t=e.dataset.taxonomy.replace(jvbSettings.base||"jvb_","");if(e.__instance){const s=e.__instance.selectedItems;s&&Object.keys(s).length>0&&(n[t]=Object.keys(s).join(","))}})),o.forEach((e=>{a[e]={append:!0,content:this.config.content,status:i.get("bulk_status"),taxonomies:n}})),this.queueManager.addToQueue({type:"content_update",data:{posts:a}}),t=!1,e.close(),this.clearSelection()})),e.addEventListener("submit",(i=>{const o=new FormData(s),a=Array.from(o.getAll("posts")),n={};""===o.get("term_name")&&(o.delete("term_name"),o.delete("select_parent"));let l={};for(const e of this.config.taxonomies)l[e]=o.getAll(e),o.delete(e);a.forEach((e=>{n[e]={append:!0,content:this.config.content,status:o.get("bulk_status"),taxonomies:l}})),this.queueManager.addToQueue({type:"content_update",data:{posts:n}}),t=!1,e.close(),this.clearSelection()}))}this.openEditModal=e=>{console.log("Openening whatsit");const t=this.editModal.modal;if(!t)return;console.log("continuing");let s=e.dataset.id;t.dataset.id=s;let i=JSON.parse(e.dataset.fields),o=e.dataset.status;t.querySelector("input#set-"+o).checked=!0;for(let s in i){let o=i[s];o&&(t.querySelector("[name="+s+"]").value=o,"featured_image"===s&&(console.log(e),t.querySelector("[data-field=featured_image] .image-display").classList.add("has-image"),t.querySelector("[data-field=featured_image] .image-display img").src=e.dataset.img))}t.querySelector(".image")&&document.querySelectorAll(".image").forEach((e=>{const t=e.dataset.field,i=e.querySelector(".file-upload-container"),o=(new window.jvbFileUploader(e,{mode:"direct",content:this.config.content,postID:s,fieldName:t,type:"image_upload",selectors:{dropZone:i,uploader:e},onSuccess:t=>this.handleImageUploadSuccess(t,e),onError:t=>this.handleImageUploadError(t,e)}),e.querySelector(".remove-image"));o&&o.addEventListener("click",(()=>{this.handleImageRemove(e)}));const a=e.querySelector(".replace-image");a&&a.addEventListener("click",(()=>{e.querySelector('input[type="file"]').click()}))})),t.querySelector(".gallery")&&document.querySelectorAll(".gallery").forEach((t=>{const i=t.dataset.field,o=t.querySelector(".gallery-preview");e.dataset.images&&e.dataset.images.split(",").forEach((e=>{this.addToGalleryPreview(e,o)})),new window.jvbFileUploader(t,{mode:"gallery",selectors:{dropZone:t.querySelector(".file-upload-container"),previewGrid:o,uploader:t},type:"image_upload",content:this.config.content,postID:s,fieldName:i,onUploadComplete:e=>{const s=t.querySelector('input[type="hidden"]'),i=s.value?s.value.split(","):[],a=e.data.map((e=>e.attachment_id));s.value=[...i,...a].join(","),e.data.forEach((e=>{const t=document.createElement("div");t.className="preview-item",t.dataset.id=e.attachment_id,t.draggable=!0,t.innerHTML=`\n                        <img src="${e.url}" alt="Upload preview">\n                        <button type="button" class="remove-preview">\n                            ${jvbSettings.icons.delete}\n                        </button>\n                        <button type="button" class="move-image">\n                            ${jvbSettings.icons.grab}\n                        </button>\n                    `,o.appendChild(t)})),s.dispatchEvent(new Event("change",{bubbles:!0}))}}),new Sortable(o,{animation:150,handle:".move-image",onEnd:()=>{const e=t.querySelector('input[type="hidden"]'),s=[...o.querySelectorAll(".preview-item")].map((e=>e.dataset.id));e.value=s.join(","),e.dispatchEvent(new Event("change",{bubbles:!0}))}})})),t.querySelector(".taxonomies")&&t.querySelectorAll(".taxonomies .jvb-selector").forEach((t=>{let s=t.dataset.taxonomy,i=(t.classList.contains("hierarchical"),JSON.parse(t.dataset.config)),o=e.dataset[s]?JSON.parse(e.dataset[s]):{},a=i.common;t.__instance=new window.jvbSelector(t,{title:"Select "+s+"(s)",selected:o,common:a,allowMultiple:i.multiple,createNew:!0})})),t.showModal()},this.openBulkEditModal=()=>{const t=this.elements.bulkEditModal;if(!t)return;const s=this.state.selected,i=t.querySelector(".selected-count");i&&(i.textContent=`(${s.length} items)`),e.querySelectorAll(".taxonomies .jvb-selector").forEach((e=>{const t=e.dataset.taxonomy,s=(e.classList.contains("hierarchical"),JSON.parse(e.dataset.config));e.__instance=new window.jvbSelector(e,{title:`Select ${t}(s)`,values:{},allowMultiple:s.multiple,appendMode:!0,createNew:!0})})),t.showModal()}}handleModalClose(e,t){return t?!!confirm("You have unsaved changes. Are you sure you want to close this window?")&&(e.querySelectorAll(".gallery").forEach((e=>{e.__uploader&&(e.__uploader.cleanup(),delete e.__uploader)})),e.close(),!0):(e.close(),!0)}addToGalleryPreview(e,t){const s=document.createElement("div");return s.className="preview-item",s.draggable=!0,s.innerHTML=`\n        <img src="${e}" alt="Upload preview">\n        <div class="upload-status">\n            <div class="upload-progress"></div>\n        </div>\n        <button type="button" class="remove-preview" title="Remove Image">\n            ${jvbSettings.icons.delete}\n        </button>\n        <button type="button" class="move-image" title="Reorder Image">\n            ${jvbSettings.icons.grab}\n        </button>\n    `,t.appendChild(s),s}handleImageUploadSuccess(e,t){if(!e.data||!e.data.length)return;const s=t.querySelector(".image-display");removeChildren(s),s.classList.add("has-image");let i=[];e.data.forEach((e=>{let t=new Image;t.src=e.url,i.push(e.attachment_id),s.appendChild(t)})),t.querySelector('input[type="hidden"]').value=i.join(","),t.querySelector(".file-upload-container").hidden=!0,this.showNotification("Image updated successfully")}handleImageUploadError(e,t){console.error("Upload error:",e),this.showNotification("Failed to upload image","error"),t.querySelector(".file-upload-container").hidden=!1;const s=t.querySelector(".file-error");s&&(s.textContent="")}handleImageRemove(e){const t=e.querySelector(".image-display"),s=t.querySelector("img"),i=e.querySelector('input[type="hidden"]'),o=e.querySelector(".file-upload-container");i.value="",s.src="",t.classList.remove("has-image"),o.hidden=!1,this.showNotification("Image removed")}clearSelection(){this.getVisibleItems().forEach((e=>this.toggleItemSelection(e,!1))),this.state.selected.clear(),this.selectAll.checked=!1,this.updateBulkControls()}updateBulkControls(){const e=this.state.selected.size>0;this.elements.grid.classList.toggle("selecting",e),this.elements.bulkControls.classList.toggle("has-selection",e),this.elements.bulkControls.querySelector(".bulk-actions").hidden=!e,e&&document.addEventListener("keydown",(e=>{"Escape"===e.key&&this.state.selected.size>0&&(this.clearSelection(),this.showNotification("Selection cleared"))}));const t=this.elements.bulkControls.querySelector(".selected-count");t&&(t.textContent=e?`( ${this.state.selected.size} selected )`:"")}getVisibleItems(){return Array.from(this.elements.grid.querySelectorAll(".item:not([hidden])"))}showNotification(e,t="success"){window.jvbNotifications?window.jvbNotifications.showPopupNotification({message:e,type:t,priority:"medium",duration:3e3}):alert(e)}};
\ No newline at end of file
+window.contentManager=class{constructor(e){this.config={content:"",plural:"",taxonomies:{},selectors:{container:".items-list",grid:".item-grid:not(.preview)",uploadZone:".file-upload-wrapper",statusFilters:".status-filters",dateFilters:".date-filters",taxonomyFilters:".taxonomy-filters",viewControls:".view-controls",bulkControls:".bulk-controls",scrollSentinel:".scroll-sentinel",editModal:".edit-modal",bulkEditModal:".bulk-edit-modal",clearButton:".clear-filters"},createPostPerFile:!0,uploadConfig:{mode:"direct",allowMultiple:!0,createPostPerFile:!0,maxSize:5242880,allowedTypes:["image/jpeg","image/png","image/gif","image/webp"]},...e},this.resetCache=!1,this.queueManager=window.jvbQueue,this.loadingManager=window.jvbLoading,this.cache=window.jvbCache,this.error=window.jvbError,this.state={selected:new Set,filters:{status:"all",taxonomies:{},date:null},view:localStorage.getItem(`${this.config.content}_view`)||"grid",loading:!1},this.queue={all:{items:new Map,page:1,hasMore:!0,totalPages:0},draft:{items:new Map,page:1,hasMore:!0,totalPages:0},publish:{items:new Map,page:1,hasMore:!0,totalPages:0},trash:{items:new Map,page:1,hasMore:!0,totalPages:0}},this.init()}async init(){this.elements={},Object.entries(this.config.selectors).forEach(([e,t])=>{this.elements[e]=document.querySelector(t)}),this.config.uploadConfig&&(this.fileUploader=new window.jvbFileUploader({...this.config.uploadConfig,content:this.config.content,fieldName:null})),this.initStatusFilters(),this.initDateFilters(),this.initTaxonomyFilters(),this.initClearFilters(),this.initViewControls(),this.initBulkControls(),this.initInfiniteScroll(),this.initModals(),await this.loadContent()}queueContentUpdate(e,t){const s={type:"content_update",data:{posts:{[e]:{content:this.config.content,...t}},content:this.config.content}};this.queueManager.addToQueue(s),this.updateLocalState(e,t)}queueBulkUpdate(e,t){const s={};e.forEach(e=>{s[e]={content:this.config.content,...t}});const i={user:window.auth.getUser(),type:"content_update",data:{posts:s}};this.queueManager.addToQueue(i),e.forEach(e=>this.updateLocalState(e,t))}updateLocalState(e,t){const s=this.queue[this.state.filters.status].items.get(e);if(s){Object.assign(s,t),this.queue[this.state.filters.status].items.set(e,s);const i=this.elements.grid.querySelector(`[data-id="${e}"]`);i&&this.updateItemElement(i,s)}}processFormData(e){const t={};for(const[s,i]of e.entries())if("status"===s)t.status=i;else if(s.startsWith("taxonomy_")){const e=s.replace("taxonomy_","");t.taxonomies||(t.taxonomies={}),t.taxonomies[e]=Array.isArray(i)?i:[i]}else t[s]=i;return t}updateItemElement(e,t){e.classList.remove("draft","publish","trash"),e.classList.add(t.status);const s=e.querySelector(".action-status");s&&(removeChildren(s),s.append(getIcon(t.status))),t.taxonomies&&e.querySelectorAll(".label-group").forEach(e=>{const s=e.dataset.taxonomy;if(s&&t.taxonomies[s]){const i=t.taxonomies[s].terms;e.querySelector(".terms").innerHTML=this.renderTerms(i)}})}handleItemAction(e,t){const s=t.dataset.id;switch(e){case"edit":this.editModal.handleOpen(),this.openEditModal(t),this.editModal.form&&new FormFields(this.editModal.form,{onSave:this.editModal.onSave(),itemID:t.dataset.id});break;case"restore":this.queueContentUpdate(s,{status:"draft"}),t.remove();break;case"trash":this.queueContentUpdate(s,{status:"trash"}),t.remove();break;case"delete":confirm(`Hold up! Are you sure you want to permanently delete this ${this.config.content}?\n\nThis is a forever kind of deal - no taking it back.`)&&(this.queueContentUpdate(s,{status:"delete"}),t.remove());break;case"toggle-status":const e="publish"===t.dataset.status?"draft":"publish";this.queueContentUpdate(s,{status:e}),t.dataset.status=e,removeChildren(t.querySelector(".action-status")),t.querySelector(".action-status").append(getIcon(e))}}async handleBulkOperation(e,t){window.jvbLoading.show("Processing bulk changes...");try{const s={};t.forEach(t=>{s[t]={content:this.config.content,status:e},["delete","trash","restore"].includes(e)&&document.querySelector('[data-id="'+t+'"]').remove()}),this.queueManager.addToQueue({type:"content_update",data:{posts:s}}),this.clearSelection(),this.showNotification("Bulk changes queued for processing")}catch(e){console.error("Bulk operation failed:",e),this.showNotification("Failed to queue bulk operation","error")}finally{window.jvbLoading.hide()}}getQueryKey(){return JSON.stringify({status:this.state.filters.status,page:this.state.page,filters:this.state.filters})}toggleItemSelection(e,t){const s=e.dataset.id;t?(this.state.selected.add(s),e.classList.add("selected"),e.querySelector("input[type=checkbox]").checked=!0):(this.state.selected.delete(s),e.classList.remove("selected"),e.querySelector("input[type=checkbox]").checked=!1)}async loadContent(e=!0){if(!this.state.loading)try{this.state.loading=!0,this.loadingManager.show();const t=this.state.filters.status;console.log("Loading Page: "),console.log(this.queue[t].page);const s=new URLSearchParams;s.set("type",this.config.content),s.set("page",this.queue[t].page),s.set("filters",JSON.stringify(this.state.filters)),s.set("user",window.auth.getUser()),e&&(this.queue[t].page=1,this.queue[t].items.clear(),removeChildren(this.elements.grid),this.elements.grid.classList.remove("empty"));const i=await this.cache.fetchWithCache(`${jvbSettings.api}content?`+s,{method:"GET",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash")}},{context:window.auth.getUser()+"-"+this.config.content,forceRefresh:!1});i.total>0?(this.elements.grid.classList.remove("empty"),i.items.forEach(e=>{this.queue[t].items.set(e.id,e)}),this.queue[t].page++,this.queue[t].totalPages=i.total_pages,this.queue[t].hasMore=this.queue[t].page<i.total_pages):(this.elements.grid.classList.add("empty"),this.elements.grid.innerHTML=`<div class="empty-state"><h3>${jvbSettings.icons[this.config.content]}Nothing here${jvbSettings.icons[this.config.content]}</h3><p>It doesn't look like you have any ${this.config.plural} yet.</p><p><small><i>Add some by uploading images above.</i></small></p></div>`,this.queue[t].page=1,this.queue[t].hasMore=!1),this.renderContent()}catch(e){console.error("Error loading content:",e),this.loadingManager.showError("Failed to load content")}finally{this.state.loading=!1,this.loadingManager.hide()}}renderContent(){const e=this.state.filters.status,t=this.queue[e].items;t.size>0&&(this.elements.grid.classList.remove("empty"),this.elements.grid.querySelector(".empty-state")&&removeChildren(this.elements.grid));const s=document.createDocumentFragment();t.forEach(t=>{const i=this.elements.grid.querySelector(`[data-id="${t.id}"]`);if(i){if(t.view!==this.state.view){const e=this.createItemElement(t);t.view=this.state.view,i.replaceWith(e)}}else{const e=this.createItemElement(t);t.view=this.state.view,s.appendChild(e)}this.queue[e].items.set(t.id,t)}),s.children.length>0&&this.elements.grid.appendChild(s)}createItemElement(e){let t=window.getTemplate(this.state.view+"View");t.classList.add(e.status),t.dataset.id=e.id,t.dataset.fields=JSON.stringify(e.fields),t.dataset.status=e.status,t.dataset.img=e.thumbnail;let s=t.querySelector(".gallery");if(e.images){t.dataset.images=e.images;let o=s.querySelector("img");for(var i of e.images){let e=o.cloneNode(!0);e.src=i.src,i.alt&&(e.alt=i.alt),s.appendChild(e)}o.remove()}else s.remove();let o=[],a=t.querySelector(".taxonomies"),n=a.querySelector(".label-group"),l=n.querySelector(".tax"),r=!1;for(let s in e.taxonomies){if(Object.keys(e.taxonomies[s].terms).length>0){r=!0,t.dataset[s]=JSON.stringify(e.taxonomies[s].terms);let i=n.cloneNode(!0),o=jvbSettings.icons[s];for(var c in i.innerHTML=o+i.innerHTML,i.querySelector(".screen-reader-text").textContent=e.taxonomies[s].name,e.taxonomies[s].terms){let e=l.cloneNode(!0);e.textContent=c.name,i.appendChild(e)}}else t.dataset[s]=JSON.stringify({});o.push(s)}r?(n.remove(),l.remove()):a.remove(),0===Object.keys(this.config.taxonomies).length&&(this.config.taxonomies=o);let d=t.querySelector("img");d.src=e.thumbnail,e.alt&&(d.alt=e.alt),t.querySelector(".date").textContent=formatDate(e.date);let u="Hide "+e.icon;"draft"===e.status&&(u="Show "+e.icon);let h=t.querySelector('button[data-action="toggle-status"]');return h.prepend(getIcon(e.status)),h.title=u,this.initItemEventListeners(t),t}initItemEventListeners(e){e.addEventListener("click",t=>t.target.closest(".item-select")?(t.preventDefault(),this.toggleItemSelection(e,!e.classList.contains("selected")),void this.updateBulkControls()):t.target.closest(".action")?(t.preventDefault(),void this.handleItemAction(t.target.closest(".action").dataset.action,e)):void 0)}initInfiniteScroll(){this.elements.scrollSentinel&&new IntersectionObserver(e=>{e.forEach(e=>{e.isIntersecting&&this.queue[this.state.filters.status].hasMore&&this.loadContent(!1)})}).observe(this.elements.scrollSentinel)}initStatusFilters(){const e=this.elements.container.querySelector(".controls");e&&e.addEventListener("change",e=>{if("radio"===e.target.type&&"status-filters"===e.target.name){const t=e.target.id;t!==this.state.filters.status&&(this.state.filters.status=t,this.updateBulkActionOptions(),0===this.queue[t].items.size?this.loadContent(!0):this.renderContent())}})}initDateFilters(){const e=this.elements.container.querySelector("select.date-filter"),t=this.elements.container.querySelector(".date-range");let s;if(e&&(this.hasFilters=!0,e.addEventListener("change",e=>{const i=e.target.value;if(s=i,"custom"===i)return void t.showModal();t.close();const o=t.querySelector(".month-select");o&&(o.value=""),this.setDateFilter(i)}),e.addEventListener("click",i=>{"custom"===s&&"custom"===e.value&&t.showModal()})),t){const e=t.querySelector(".date-start"),s=t.querySelector(".date-end"),i=t.querySelector(".month-select");i&&i.addEventListener("change",e=>{const[s,i]=e.target.value.split("-");if(s&&i){const e=new Date(s,i-1,1),o=new Date(s,i,0);o.setHours(23,59,59,999),this.setDateFilter("custom",e,o),t.close()}});const o=()=>{const i=e.value,o=s.value;if(i&&o){const e=new Date(i),s=new Date(o);s.setHours(23,59,59,999),this.setDateFilter("custom",e,s),t.close()}};e.addEventListener("change",o),s.addEventListener("change",o)}}setDateFilter(e,t=null,s=null){const i=new Date;i.setHours(23,59,59,999);let o=t,a=s||i;if(!t&&""!==e)switch(o=new Date,e){case"today":o.setHours(0,0,0,0);break;case"week":o.setDate(i.getDate()-7);break;case"month":o.setMonth(i.getMonth()-1);break;case"year":o.setFullYear(i.getFullYear()-1)}this.state.filters.date=e?{range:{after:o.toISOString(),before:a.toISOString()},custom:"custom"===e}:{range:null,custom:!1},this.updateClearFiltersButton(),this.state.page=1,this.loadContent()}initTaxonomyFilters(){const e=this.elements.container.querySelectorAll(".filter[data-taxonomy]");e.length&&(this.hasFilters=!0,e.forEach(e=>{e.addEventListener("change",e=>{const t=e.target.dataset.taxonomy,s=e.target.value;s?this.state.filters.taxonomies[t]=[parseInt(s)]:delete this.state.filters.taxonomies[t],this.updateClearFiltersButton(),this.state.page=1,this.loadContent(!0)})}))}updateClearFiltersButton(){const e=document.querySelector(this.config.selectors.clearButton);if(!e)return;const t=Object.keys(this.state.filters.taxonomies).length>0||null!==this.state.filters.date.range;e.hidden=!t}clearAllFilters(){this.elements.container.querySelectorAll(".filter[data-taxonomy]").forEach(e=>e.value="");const e=this.elements.container.querySelector("select.date-filter");e&&(e.value=""),this.state.filters={date:{range:null,custom:!1},taxonomies:{}},this.updateClearFiltersButton(),this.state.page=1,this.loadContent(!0)}initClearFilters(){this.config.selectors.clearButton&&document.querySelector(this.config.selectors.clearButton).addEventListener("click",()=>this.clearAllFilters())}initViewControls(){const e=this.elements.container.querySelector(".view-controls");if(!e)return;e.addEventListener("change",e=>{const t=e.target;"radio"===t.type&&(this.setView(t.value),this.loadContent(!0))});const t=localStorage.getItem(`${this.config.content}_view`)||"grid",s=e.querySelector(`input[value="${t}"]`);s&&(s.checked=!0,this.setView(t))}setView(e){this.state.view=e;const t=new Set(this.state.selected);this.elements.grid.classList.remove("grid-view","list-view"),this.elements.grid.classList.add(`${e}-view`),localStorage.setItem(`${this.config.content}_view`,e),this.loadContent(!0),t.forEach(e=>{const t=this.elements.grid.querySelector(`[data-id="${e}"]`);if(t){const e=t.querySelector('input[type="checkbox"]');e&&(e.checked=!0,t.classList.add("selected"))}}),this.updateBulkControls()}initBulkControls(){if(!this.elements.bulkControls)return;this.selectAll=this.elements.bulkControls.querySelector(".select-all"),this.selectAll&&this.selectAll.addEventListener("change",()=>{this.getVisibleItems().forEach(e=>{this.toggleItemSelection(e,this.selectAll.checked)}),this.updateBulkControls()});const e=this.elements.bulkControls.querySelector(".bulk-action-select"),t=this.elements.bulkControls.querySelector(".apply-bulk");t&&e&&(this.updateBulkActionOptions(),this.elements.container.querySelector(".status-filters"),t.addEventListener("click",()=>{const t=e.value;if(!t)return;const s=Array.from(this.state.selected);switch(t){case"restore":this.handleBulkOperation("restore",s);break;case"delete":confirm(`Hold up! Are you sure you want to permanently delete these ${this.config.plural}?\n\nThis is a forever kind of deal - no taking it back.`)&&this.handleBulkOperation("delete",s);break;case"trash":this.handleBulkOperation("trash",s);break;case"edit":this.openBulkEditModal();const e=document.querySelector(".bulk-edit-modal");if(e){const t=e.querySelector(".selected-count");t&&(t.textContent=`( ${s.length} items )`);const i=e.querySelector(".selected");if(i){let e="";s.forEach(t=>{let s=this.elements.grid.querySelector('[data-id="'+t+'"]');e+='<input type="checkbox" id="selected-'+t+'" name="posts" value="'+t+'" checked><label for="selected-'+t+'"><img width="100%" height="auto" src="'+s.dataset.img+'"></label>'}),i.innerHTML=e}}break;case"publish":case"draft":this.handleBulkOperation(t,s)}e.value=""}));const s=this.elements.bulkControls.querySelector(".cancel-bulk");s&&s.addEventListener("click",()=>{this.clearSelection()})}updateBulkActionOptions(){const e=this.elements.bulkControls.querySelector(".bulk-action-select");e&&("trash"===this.state.filters.status?e.innerHTML='\n            <option value="">Bulk Actions...</option>\n            <option value="restore">Restore</option>\n            <option value="delete">Permanently Delete</option>\n        ':e.innerHTML='\n            <option value="">Bulk Actions...</option>\n            <option value="edit">Edit</option>\n            <option value="publish">Show</option>\n            <option value="draft">Hide</option>\n            <option value="trash">Scrap</option>\n        ')}initModals(){this.elements.editModal&&(this.editModal=new window.jvbModal(this.elements.editModal,{open:!1,close:this.elements.editModal.querySelector(".cancel"),save:this.elements.editModal.querySelector(".save"),onSave:()=>{const e=new FormData(this.elements.editModal.querySelector("form"));let t={};const s=this.elements.editModal.querySelectorAll(".taxonomies .jvb-selector");let i=Object.fromEntries(e);s.forEach(e=>{const s=e.dataset.taxonomy.replace(jvbSettings.base||"jvb_","");if(delete i["edit-"+s],e.__instance){const i=e.__instance.selectedItems;i&&Object.keys(i).length>0&&(t[s]=Object.keys(i).join(","))}}),i.taxonomies=t;for(let[e,t]of Object.entries(i))""!==t&&0!==Object.keys(t).length||delete i[e];this.queueContentUpdate(this.elements.editModal.dataset.id,i)}}));const e=this.elements.bulkEditModal;if(e){let t=!1;const s=e.querySelector("form");s?.addEventListener("change",()=>{t=!0}),e.addEventListener("keydown",s=>{"Escape"===s.key&&(s.preventDefault(),this.handleModalClose(e,t))}),e.addEventListener("click",s=>{s.target===e&&this.handleModalClose(e,t)}),e.querySelector(".cancel")?.addEventListener("click",()=>{this.handleModalClose(e,t),this.clearSelection()}),e.querySelector(".save")?.addEventListener("click",()=>{const i=new FormData(s),o=Array.from(i.getAll("posts")),a={};""===i.get("term_name")&&(i.delete("term_name"),i.delete("select_parent"));let n={};e.querySelectorAll(".taxonomies .jvb-selector").forEach(e=>{const t=e.dataset.taxonomy.replace(jvbSettings.base||"jvb_","");if(e.__instance){const s=e.__instance.selectedItems;s&&Object.keys(s).length>0&&(n[t]=Object.keys(s).join(","))}}),o.forEach(e=>{a[e]={append:!0,content:this.config.content,status:i.get("bulk_status"),taxonomies:n}}),this.queueManager.addToQueue({type:"content_update",data:{posts:a}}),t=!1,e.close(),this.clearSelection()}),e.addEventListener("submit",i=>{const o=new FormData(s),a=Array.from(o.getAll("posts")),n={};""===o.get("term_name")&&(o.delete("term_name"),o.delete("select_parent"));let l={};for(const e of this.config.taxonomies)l[e]=o.getAll(e),o.delete(e);a.forEach(e=>{n[e]={append:!0,content:this.config.content,status:o.get("bulk_status"),taxonomies:l}}),this.queueManager.addToQueue({type:"content_update",data:{posts:n}}),t=!1,e.close(),this.clearSelection()})}this.openEditModal=e=>{console.log("Openening whatsit");const t=this.editModal.modal;if(!t)return;console.log("continuing");let s=e.dataset.id;t.dataset.id=s;let i=JSON.parse(e.dataset.fields),o=e.dataset.status;t.querySelector("input#set-"+o).checked=!0;for(let s in i){let o=i[s];o&&(t.querySelector("[name="+s+"]").value=o,"featured_image"===s&&(console.log(e),t.querySelector("[data-field=featured_image] .image-display").classList.add("has-image"),t.querySelector("[data-field=featured_image] .image-display img").src=e.dataset.img))}t.querySelector(".image")&&document.querySelectorAll(".image").forEach(e=>{const t=e.dataset.field,i=e.querySelector(".file-upload-container"),o=(new window.jvbFileUploader(e,{mode:"direct",content:this.config.content,postID:s,fieldName:t,type:"image_upload",selectors:{dropZone:i,uploader:e},onSuccess:t=>this.handleImageUploadSuccess(t,e),onError:t=>this.handleImageUploadError(t,e)}),e.querySelector(".remove-image"));o&&o.addEventListener("click",()=>{this.handleImageRemove(e)});const a=e.querySelector(".replace-image");a&&a.addEventListener("click",()=>{e.querySelector('input[type="file"]').click()})}),t.querySelector(".gallery")&&document.querySelectorAll(".gallery").forEach(t=>{const i=t.dataset.field,o=t.querySelector(".gallery-preview");e.dataset.images&&e.dataset.images.split(",").forEach(e=>{this.addToGalleryPreview(e,o)}),new window.jvbFileUploader(t,{mode:"gallery",selectors:{dropZone:t.querySelector(".file-upload-container"),previewGrid:o,uploader:t},type:"image_upload",content:this.config.content,postID:s,fieldName:i,onUploadComplete:e=>{const s=t.querySelector('input[type="hidden"]'),i=s.value?s.value.split(","):[],a=e.data.map(e=>e.attachment_id);s.value=[...i,...a].join(","),e.data.forEach(e=>{const t=document.createElement("div");t.className="preview-item",t.dataset.id=e.attachment_id,t.draggable=!0,t.innerHTML=`\n                        <img src="${e.url}" alt="Upload preview">\n                        <button type="button" class="remove-preview">\n                            ${jvbSettings.icons.delete}\n                        </button>\n                        <button type="button" class="move-image">\n                            ${jvbSettings.icons.grab}\n                        </button>\n                    `,o.appendChild(t)}),s.dispatchEvent(new Event("change",{bubbles:!0}))}}),new Sortable(o,{animation:150,handle:".move-image",onEnd:()=>{const e=t.querySelector('input[type="hidden"]'),s=[...o.querySelectorAll(".preview-item")].map(e=>e.dataset.id);e.value=s.join(","),e.dispatchEvent(new Event("change",{bubbles:!0}))}})}),t.querySelector(".taxonomies")&&t.querySelectorAll(".taxonomies .jvb-selector").forEach(t=>{let s=t.dataset.taxonomy,i=(t.classList.contains("hierarchical"),JSON.parse(t.dataset.config)),o=e.dataset[s]?JSON.parse(e.dataset[s]):{},a=i.common;t.__instance=new window.jvbSelector(t,{title:"Select "+s+"(s)",selected:o,common:a,allowMultiple:i.multiple,createNew:!0})}),t.showModal()},this.openBulkEditModal=()=>{const t=this.elements.bulkEditModal;if(!t)return;const s=this.state.selected,i=t.querySelector(".selected-count");i&&(i.textContent=`(${s.length} items)`),e.querySelectorAll(".taxonomies .jvb-selector").forEach(e=>{const t=e.dataset.taxonomy,s=(e.classList.contains("hierarchical"),JSON.parse(e.dataset.config));e.__instance=new window.jvbSelector(e,{title:`Select ${t}(s)`,values:{},allowMultiple:s.multiple,appendMode:!0,createNew:!0})}),t.showModal()}}handleModalClose(e,t){return t?!!confirm("You have unsaved changes. Are you sure you want to close this window?")&&(e.querySelectorAll(".gallery").forEach(e=>{e.__uploader&&(e.__uploader.cleanup(),delete e.__uploader)}),e.close(),!0):(e.close(),!0)}addToGalleryPreview(e,t){const s=document.createElement("div");return s.className="preview-item",s.draggable=!0,s.innerHTML=`\n        <img src="${e}" alt="Upload preview">\n        <div class="upload-status">\n            <div class="upload-progress"></div>\n        </div>\n        <button type="button" class="remove-preview" title="Remove Image">\n            ${jvbSettings.icons.delete}\n        </button>\n        <button type="button" class="move-image" title="Reorder Image">\n            ${jvbSettings.icons.grab}\n        </button>\n    `,t.appendChild(s),s}handleImageUploadSuccess(e,t){if(!e.data||!e.data.length)return;const s=t.querySelector(".image-display");removeChildren(s),s.classList.add("has-image");let i=[];e.data.forEach(e=>{let t=new Image;t.src=e.url,i.push(e.attachment_id),s.appendChild(t)}),t.querySelector('input[type="hidden"]').value=i.join(","),t.querySelector(".file-upload-container").hidden=!0,this.showNotification("Image updated successfully")}handleImageUploadError(e,t){console.error("Upload error:",e),this.showNotification("Failed to upload image","error"),t.querySelector(".file-upload-container").hidden=!1;const s=t.querySelector(".file-error");s&&(s.textContent="")}handleImageRemove(e){const t=e.querySelector(".image-display"),s=t.querySelector("img"),i=e.querySelector('input[type="hidden"]'),o=e.querySelector(".file-upload-container");i.value="",s.src="",t.classList.remove("has-image"),o.hidden=!1,this.showNotification("Image removed")}clearSelection(){this.getVisibleItems().forEach(e=>this.toggleItemSelection(e,!1)),this.state.selected.clear(),this.selectAll.checked=!1,this.updateBulkControls()}updateBulkControls(){const e=this.state.selected.size>0;this.elements.grid.classList.toggle("selecting",e),this.elements.bulkControls.classList.toggle("has-selection",e),this.elements.bulkControls.querySelector(".bulk-actions").hidden=!e,e&&document.addEventListener("keydown",e=>{"Escape"===e.key&&this.state.selected.size>0&&(this.clearSelection(),this.showNotification("Selection cleared"))});const t=this.elements.bulkControls.querySelector(".selected-count");t&&(t.textContent=e?`( ${this.state.selected.size} selected )`:"")}getVisibleItems(){return Array.from(this.elements.grid.querySelectorAll(".item:not([hidden])"))}showNotification(e,t="success"){window.jvbNotifications?window.jvbNotifications.showPopupNotification({message:e,type:t,priority:"medium",duration:3e3}):alert(e)}};
\ No newline at end of file
diff --git a/assets/js/min/a11y.min.js b/assets/js/min/a11y.min.js
index e4c0032..23bdd6c 100644
--- a/assets/js/min/a11y.min.js
+++ b/assets/js/min/a11y.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.liveRegion=document.querySelector(".screen-reader-text.live-region"),this.liveRegion||console.log("No accessibility region.")}announce(e,t="polite"){this.liveRegion&&(this.liveRegion.setAttribute("aria-live",t),this.liveRegion.textContent="",setTimeout((()=>{this.liveRegion.textContent=e}),50))}announceItems(e,t=!1,n=!0){const o=t?"Added":"Loaded",i=n?". More available to load.":". No more available to load.";this.announce(`${o} ${e} new items${i}`)}announceEmpty(e=!1){const t=e?"No favourites found. Try adding some items to your collection.":"No items found matching your current filters.";this.announce(t)}announceNavigation(e,t,n=!1,o=!1){n?this.announce("At first image"):o?this.announce("At last image"):this.announce(`Image ${e} of ${t}`)}trapFocus(e,t=null){if(!e)return()=>{};const n=e.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if(!n.length)return()=>{};const o=n[0],i=n[n.length-1],a=t||document.activeElement;o.focus();const r=e=>{"Tab"===e.key&&(e.shiftKey&&document.activeElement===o?(i.focus(),e.preventDefault()):e.shiftKey||document.activeElement!==i||(o.focus(),e.preventDefault()))};return e.addEventListener("keydown",r),()=>{e.removeEventListener("keydown",r),a&&"function"==typeof a.focus&&a.focus()}}makeNavigable(e,t){e&&e.length&&Array.from(e).forEach((e=>{e.getAttribute("data-keyboard-nav")||(e.setAttribute("data-keyboard-nav","true"),e.setAttribute("tabindex","0"),e.addEventListener("keydown",(n=>{if("Enter"===n.key||" "===n.key)if(n.preventDefault(),t)t(e);else{const t=e.querySelector("a");t&&t.click()}})))}))}}document.addEventListener("DOMContentLoaded",(function(){window.jvbA11y=new e}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.liveRegion=document.querySelector(".screen-reader-text.live-region"),this.liveRegion||console.log("No accessibility region.")}announce(e,t="polite"){this.liveRegion&&(this.liveRegion.setAttribute("aria-live",t),this.liveRegion.textContent="",setTimeout(()=>{this.liveRegion.textContent=e},50))}announceItems(e,t=!1,n=!0){const o=t?"Added":"Loaded",i=n?". More available to load.":". No more available to load.";this.announce(`${o} ${e} new items${i}`)}announceEmpty(e=!1){const t=e?"No favourites found. Try adding some items to your collection.":"No items found matching your current filters.";this.announce(t)}announceNavigation(e,t,n=!1,o=!1){n?this.announce("At first image"):o?this.announce("At last image"):this.announce(`Image ${e} of ${t}`)}trapFocus(e,t=null){if(!e)return()=>{};const n=e.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if(!n.length)return()=>{};const o=n[0],i=n[n.length-1],a=t||document.activeElement;o.focus();const r=e=>{"Tab"===e.key&&(e.shiftKey&&document.activeElement===o?(i.focus(),e.preventDefault()):e.shiftKey||document.activeElement!==i||(o.focus(),e.preventDefault()))};return e.addEventListener("keydown",r),()=>{e.removeEventListener("keydown",r),a&&"function"==typeof a.focus&&a.focus()}}makeNavigable(e,t){e&&e.length&&Array.from(e).forEach(e=>{e.getAttribute("data-keyboard-nav")||(e.setAttribute("data-keyboard-nav","true"),e.setAttribute("tabindex","0"),e.addEventListener("keydown",n=>{if("Enter"===n.key||" "===n.key)if(n.preventDefault(),t)t(e);else{const t=e.querySelector("a");t&&t.click()}}))})}}document.addEventListener("DOMContentLoaded",function(){window.jvbA11y=new e})})();
\ No newline at end of file
diff --git a/assets/js/min/auth.min.js b/assets/js/min/auth.min.js
index daad9f4..6666f3a 100644
--- a/assets/js/min/auth.min.js
+++ b/assets/js/min/auth.min.js
@@ -1 +1 @@
-window.auth=new class{constructor(){this.initialized=!1,this.isAuthenticating=!1,this.authenticated=!1,this.user=!1,this.nonces={},this.subscribers=new Set,this.cacheExpiry=3e5,this.init()}async init(){if(!this.isAuthenticating){this.isAuthenticating=!0;try{if("undefined"!=typeof jvbAuth)return this.setAuthData(jvbAuth),this.initialized=!0,this.isAuthenticating=!1,void this.notify("auth-loaded",{fromCache:!1});await this.fetchAuth()}catch(t){this.clearAuthData(),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-error",{error:t})}}}async refreshNonce(t="wp_rest"){try{return await this.fetchAuth(),this.getNonce(t)}catch(t){return console.error("Failed to refresh nonce:",t),null}}async fetch(t,i={}){const e=async(s=0)=>{const n={...!(i.body instanceof FormData)&&{"Content-Type":"application/json"},...i.headers,"X-WP-Nonce":this.getNonce()},h=await fetch(t,{...i,credentials:"same-origin",headers:n});if((403===h.status||401===h.status)&&0===s){const t=await h.clone().json();if("rest_cookie_invalid_nonce"===t.code||t.message?.includes("Cookie check"))return console.log("Nonce invalid, refreshing auth..."),await this.refresh(),e(s+1)}return h};return e()}async fetchAuth(){const t=await fetch(`${jvbSettings.api}auth/status`,{method:"GET",credentials:"same-origin",headers:{"Content-Type":"application/json"}});if(!t.ok)throw new Error("Auth check failed");const i=await t.json();this.setAuthData(i),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-loaded",{fromCache:!1})}setAuthData(t){const i=this.initialized&&this.authenticated;this.authenticated=t.authenticated||!1,this.user=t.user||!1,this.nonces=t.nonces||{},console.log(this.nonces),i&&!this.authenticated&&(window.location.href=`/login?redirect_to=${encodeURIComponent(window.location.href)}`)}clearAuthData(){this.authenticated=!1,this.user=null,this.nonces={}}async refresh(){this.isAuthenticating=!0,this.initialized=!1;try{await this.fetchAuth(),this.notify("auth-refreshed",{})}catch(t){console.error("Failed to refresh auth:",t),this.clearAuthData(),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-error",{error:t})}}getNonce(t="wp_rest"){return this.nonces[t]||""}getUser(){return this.user}isAuthenticated(){return this.authenticated}async handleLogin(t=null){if(t)return this.setAuthData(t),this.initialized=!0,this.isAuthenticating=!1,void this.notify("auth-loaded",{fromCache:!1,fromLogin:!0});await this.refresh()}handleLogout(){this.clearAuthData(),this.notify("logged-out",{})}subscribe(t){return this.subscribers.add(t),this.initialized&&t("auth-loaded",{fromCache:!1,immediate:!0}),()=>this.subscribers.delete(t)}notify(t,i){this.subscribers.forEach((e=>{try{e(t,i)}catch(t){console.error("Subscriber error:",t)}}))}};
\ No newline at end of file
+window.auth=new class{constructor(){this.initialized=!1,this.isAuthenticating=!1,this.authenticated=!1,this.user=!1,this.nonces={},this.subscribers=new Set,this.cacheExpiry=3e5,this.init()}async init(){if(!this.isAuthenticating){this.isAuthenticating=!0;try{if("undefined"!=typeof jvbAuth)return this.setAuthData(jvbAuth),this.initialized=!0,this.isAuthenticating=!1,void this.notify("auth-loaded",{fromCache:!1});await this.fetchAuth()}catch(t){this.clearAuthData(),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-error",{error:t})}}}async refreshNonce(t="wp_rest"){try{return await this.fetchAuth(),this.getNonce(t)}catch(t){return console.error("Failed to refresh nonce:",t),null}}async fetch(t,i={}){const e=async(s=0)=>{const n={...!(i.body instanceof FormData)&&{"Content-Type":"application/json"},...i.headers,"X-WP-Nonce":this.getNonce()},h=await fetch(t,{...i,credentials:"same-origin",headers:n});if((403===h.status||401===h.status)&&0===s){const t=await h.clone().json();if("rest_cookie_invalid_nonce"===t.code||t.message?.includes("Cookie check"))return console.log("Nonce invalid, refreshing auth..."),await this.refresh(),e(s+1)}return h};return e()}async fetchAuth(){const t=await fetch(`${jvbSettings.api}auth/status`,{method:"GET",credentials:"same-origin",headers:{"Content-Type":"application/json"}});if(!t.ok)throw new Error("Auth check failed");const i=await t.json();this.setAuthData(i),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-loaded",{fromCache:!1})}setAuthData(t){const i=this.initialized&&this.authenticated;this.authenticated=t.authenticated||!1,this.user=t.user||!1,this.nonces=t.nonces||{},console.log(this.nonces),i&&!this.authenticated&&(window.location.href=`/login?redirect_to=${encodeURIComponent(window.location.href)}`)}clearAuthData(){this.authenticated=!1,this.user=null,this.nonces={}}async refresh(){this.isAuthenticating=!0,this.initialized=!1;try{await this.fetchAuth(),this.notify("auth-refreshed",{})}catch(t){console.error("Failed to refresh auth:",t),this.clearAuthData(),this.initialized=!0,this.isAuthenticating=!1,this.notify("auth-error",{error:t})}}getNonce(t="wp_rest"){return this.nonces[t]||""}getUser(){return this.user}isAuthenticated(){return this.authenticated}async handleLogin(t=null){if(t)return this.setAuthData(t),this.initialized=!0,this.isAuthenticating=!1,void this.notify("auth-loaded",{fromCache:!1,fromLogin:!0});await this.refresh()}handleLogout(){this.clearAuthData(),this.notify("logged-out",{})}subscribe(t){return this.subscribers.add(t),this.initialized&&t("auth-loaded",{fromCache:!1,immediate:!0}),()=>this.subscribers.delete(t)}notify(t,i){this.subscribers.forEach(e=>{try{e(t,i)}catch(t){console.error("Subscriber error:",t)}})}};
\ No newline at end of file
diff --git a/assets/js/min/cache.min.js b/assets/js/min/cache.min.js
index b75ead6..ef060d4 100644
--- a/assets/js/min/cache.min.js
+++ b/assets/js/min/cache.min.js
@@ -1 +1 @@
-window.jvbCache=class{constructor(e,t={}){this.base=e,this.config={namespace:`${jvbBase.base}cache`,TTL:36e5,maxSize:100,...t},this._cache=new Map,this.subscribers=new Set}clearMemoryCache(){const e=this._cache.size;return this._cache.clear(),console.log(`Cleared ${e} items from memory cache`),e}get(e){if(this._cache.has(e))return this._cache.get(e);let t,a=`${this.base}_${e}`;try{if(t=localStorage.getItem(a),!t)return null;t=JSON.parse(t)}catch(e){return console.warn("Error getting from localStorage:",e),null}return t&&this._cache.set(e,t),t}set(e,t){this._cache.set(e,t);let a=`${this.base}_${e}`;try{localStorage.setItem(a,JSON.stringify(t))}catch(t){if(t instanceof DOMException&&22===t.code){this.clearOldestLocalStorageItems();try{localStorage.setItem(e,JSON.stringify(item))}catch(e){console.warn("Still failed to set localStorage item after cleanup:",e)}}else console.warn("Error setting localStorage item:",t)}this.notify("cache-saved",{key:e,value:t})}remove(e){let t=`${this.base}_${e}`;try{localStorage.removeItem(t)}catch(e){console.warn("Error removing localStorage item:",e)}}clearOldestLocalStorageItems(){try{const e=[];for(let t=0;t<localStorage.length;t++){const a=localStorage.key(t);if(a.startsWith(this.config.namespace))try{const t=JSON.parse(localStorage.getItem(a));e.push({key:a,timestamp:t.timestamp||0})}catch(t){e.push({key:a,timestamp:0})}}e.sort(((e,t)=>e.timestamp-t.timestamp));const t=Math.max(1,Math.ceil(.2*e.length));for(let a=0;a<t;a++)e[a]&&localStorage.removeItem(e[a].key)}catch(e){console.warn("Error cleaning up localStorage:",e)}}async loadFromCache(){for(let e=0;e<localStorage.length;e++){const t=localStorage.key(e);if(t.startsWith(`${this.base}_`)){let e=t.replace(`${this.base}_`,"");try{const a=JSON.parse(localStorage.getItem(t));this._cache.set(e,a)}catch(e){console.warn(`Failed to parse cached value for ${t}:`,e)}}}}async clear(){this._cache.clear();try{for(let e=localStorage.length-1;e>=0;e--){const t=localStorage.key(e);t&&t.startsWith(this.config.namespace)&&localStorage.removeItem(t)}}catch(e){console.warn("Error clearing localStorage cache:",e)}}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach((a=>a(e,t)))}};
\ No newline at end of file
+window.jvbCache=class{constructor(e,t={}){this.base=e,this.config={namespace:`${jvbBase.base}cache`,TTL:36e5,maxSize:100,...t},this._cache=new Map,this.subscribers=new Set}clearMemoryCache(){const e=this._cache.size;return this._cache.clear(),console.log(`Cleared ${e} items from memory cache`),e}get(e){if(this._cache.has(e))return this._cache.get(e);let t,a=`${this.base}_${e}`;try{if(t=localStorage.getItem(a),!t)return null;t=this._revive(JSON.parse(t))}catch(e){return console.warn("Error getting from localStorage:",e),null}return t&&this._cache.set(e,t),t}set(e,t){this._cache.set(e,t);let a=`${this.base}_${e}`,r=JSON.stringify(this._serialize(t));try{localStorage.setItem(a,r)}catch(e){if(e instanceof DOMException&&22===e.code){this.clearOldestLocalStorageItems();try{localStorage.setItem(a,r)}catch(e){console.warn("Still failed to set localStorage item after cleanup:",e)}}else console.warn("Error setting localStorage item:",e)}this.notify("cache-saved",{key:e,value:t})}_serialize(e){return e instanceof Map?{__type:"Map",value:Array.from(e.entries())}:e instanceof Set?{__type:"Set",value:Array.from(e.values())}:e}_revive(e){if(e&&"object"==typeof e){if("Map"===e.__type)return new Map(e.value);if("Set"===e.__type)return new Set(e.value)}return e}remove(e){let t=`${this.base}_${e}`;try{localStorage.removeItem(t)}catch(e){console.warn("Error removing localStorage item:",e)}}clearOldestLocalStorageItems(){try{const e=[];for(let t=0;t<localStorage.length;t++){const a=localStorage.key(t);if(a.startsWith(this.config.namespace))try{const t=JSON.parse(localStorage.getItem(a));e.push({key:a,timestamp:t.timestamp||0})}catch(t){e.push({key:a,timestamp:0})}}e.sort((e,t)=>e.timestamp-t.timestamp);const t=Math.max(1,Math.ceil(.2*e.length));for(let a=0;a<t;a++)e[a]&&localStorage.removeItem(e[a].key)}catch(e){console.warn("Error cleaning up localStorage:",e)}}async loadFromCache(){for(let e=0;e<localStorage.length;e++){const t=localStorage.key(e);if(t.startsWith(`${this.base}_`)){let e=t.replace(`${this.base}_`,"");try{const a=this._revive(JSON.parse(localStorage.getItem(t)));this._cache.set(e,a)}catch(e){console.warn(`Failed to parse cached value for ${t}:`,e)}}}}async clear(){this._cache.clear();try{for(let e=localStorage.length-1;e>=0;e--){const t=localStorage.key(e);t&&t.startsWith(this.config.namespace)&&localStorage.removeItem(t)}}catch(e){console.warn("Error clearing localStorage cache:",e)}}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(a=>a(e,t))}};
\ No newline at end of file
diff --git a/assets/js/min/checkout.min.js b/assets/js/min/checkout.min.js
index aa852b2..26bc18b 100644
--- a/assets/js/min/checkout.min.js
+++ b/assets/js/min/checkout.min.js
@@ -1 +1 @@
-window.jvbCheckout=class{constructor(t={}){this.config=t,this.isInitialized=!1,this.cartItems=new Map,this.checkout=document.querySelector("aside#cart"),this.provider=this.checkout?.querySelector("form")?.dataset.provider||"",this.isOpen="1"!==this.config.isOpen||!1,this.isLoggedIn=this.config.is_logged_in||!1,this.userEmail=this.config.user_email||"",this.savedCards=[],this.selectedCardId=null,this.cartId=null,this.stepMultiplier=1,this.cache=new window.jvbCache("cart",{TTL:864e5}),this.a11y=window.jvbA11y,this.initCart(),this.checkout&&(this.initElements(),this.init(),this.initListeners(),this.isLoggedIn&&this.loadSavedCards()),this.popup=window.jvbPopup.registerPopup({popup:this.checkout,toggle:this.toggle,name:"Cart",onOpen:this.maybeAddEmptyState.bind(this)})}async init(){throw new Error("init() must be implemented by subclass")}async processPayment(t){throw new Error("processPayment() must be implemented by subclass")}async submitToServer(t,e){throw new Error("submitToServer() must be implemented by subclass")}async loadSavedCards(){}async initCart(){this.cartItems=await this.cache.get("cart")??new Map,this.cartItems.size>0&&this.notifyRestoredCart()}saveCart(){this.updateTotal(),this.cache.set("cart",this.cartItems)}clearCart(){this.cartItems.clear(),window.removeChildren(this.table),this.saveCart()}getCartId(){return this.cartId||(this.cartId=crypto.randomUUID(),this.cache.set("cart_id",this.cartId)),this.cartId}initElements(){this.toggle=document.querySelector(".toggle-cart"),this.isOpen||(this.toggle.disabled=!0,this.toggle.title="Currently closed for online ordering"),this.checkoutPanel=this.checkout.querySelector('button[data-tab="checkout"]'),this.itemsList=this.checkout.querySelector(".cart-items"),this.table=this.checkout.querySelector(".cart-items tbody"),this.total=this.checkout.querySelector(".cart-total"),this.totalTax=this.total.querySelector(".tax span"),this.grandTotal=this.total.querySelector(".total span"),this.checkoutForm=this.checkout.querySelector("form"),this.tabs=new window.jvbTabs(this.checkoutForm,{updateURL:!1})}initListeners(){this.clickHandler=this.handleClick.bind(this),this.keyHandler=this.handleEscape.bind(this),this.changeHandler=this.handleChange.bind(this),this.checkoutForm.addEventListener("submit",(t=>this.handleFormSubmit(t))),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler)}handleClick(t){if(window.targetCheck(t,"button")&&window.targetCheck(t,"div.quantity")){let e=window.targetCheck(t,"div.quantity");this.handleNumberClick(t,e)}else if(window.targetCheck(t,"[data-add-to-cart]")){let e=window.targetCheck(t,"[data-add-to-cart]");this.handleAddToCart(e)}else if(window.targetCheck(t,"[data-remove-from-cart]")){let e=window.targetCheck(t,"[data-remove-from-cart]");this.handleRemoveFromCart(e)}else window.targetCheck(t,"[data-clear-cart]")?this.clearCart():window.targetCheck(t,"[data-dismiss]")&&window.targetCheck(t,"[data-dismiss]").closest(".restored")?.remove()}handleChange(t){let e=window.targetCheck(t,".quantity-input");if(e){let a=t.target.closest(".quantity"),i=e.value;if(window.targetCheck(t,".cart-items")){let t=document.querySelector(`.menu-section [data-id="${a.dataset.id}"] input`);t&&(t.value=e.value)}i>0?this.handleAddToCart(a):this.handleRemoveFromCart(a)}}handleEscape(t){"Escape"===t.key?this.stepMultiplier=1:t.ctrlKey&&t.shiftKey?this.stepMultiplier=Math.max(100*parseInt(this.stepMultiplier),1e3):t.shiftKey&&(this.stepMultiplier=Math.max(10*parseInt(this.stepMultiplier),1e3))}handleAddToCart(t){let e=t.dataset.id,a=parseFloat(t.dataset.price),i=parseInt(t.querySelector(".quantity-input")?.value)??1,s=parseFloat(a*i);this.createItemElement(t),this.cartItems.set(e,{post_id:e,name:t.dataset.name,price:a,quantity:i,total:s,catalog_id:t.dataset.catalogId||""}),this.saveCart()}handleRemoveFromCart(t){if(confirm("This will remove this item from the cart. Continue?")){t.querySelector("[data-id]")||(t=t.closest(".item")?.querySelector(".quantity.field"));let e=t.dataset.id;this.cartItems.delete(e),this.table.querySelector(`[data-id="${e}"]`)?.closest("tr").remove();let a=document.querySelector(`[data-id="${e}"] input`);a&&(a.value=0),this.maybeAddEmptyState(),this.saveCart()}}handleNumberClick(t,e){t.preventDefault();let a=0;if(t.target.closest(".increase")?a+=1:t.target.closest(".decrease")&&(a-=1),0!==a){let t=parseInt(e.dataset.step),i=e.querySelector("input"),s=""===i.value?0:parseInt(i.value);i.value=s+t*a*this.stepMultiplier,i.dispatchEvent(new Event("change",{bubbles:!0})),this.handleNumberLimits(e)}}handleNumberLimits(t){let e=t.dataset.min,a=t.dataset.max,i=t.querySelector("input"),s=t.querySelector(".increase"),r=t.querySelector(".decrease"),n=parseInt(i.value);n<=e?(i.value=e,r.disabled=!0):n>=a?(i.value=a,s.disabled=!0):(s.disabled=!1,r.disabled=!1)}createItemElement(t){let e=this.itemsList.querySelector(`[data-id="${t.dataset.id}"]`),a=!1,i=t.dataset.price,s=t.querySelector('[name="quantity"]')?.value??1;if(e)e=e.closest("tr");else{a=!0,e=window.getTemplate("cartItem");let s=e.querySelector(".quantity");[s.dataset.id,e.querySelector("label").textContent,e.querySelector(".price").textContent,s.dataset.price,s.dataset.catalogId]=[t.dataset.id,t.dataset.name,window.formatPrice(i),i,t.dataset.catalogId||""]}[e.querySelector('[name="quantity"]').value,e.querySelector(".total").textContent]=[s,window.formatPrice(s*i)],a&&(e.classList.add("adding"),this.table.append(e),setTimeout((()=>e.classList.remove("adding")),500))}maybeAddEmptyState(){let t=this.itemsList.querySelector(".empty");if(t&&t.remove(),0===this.cartItems.size){this.checkoutPanel.disabled=!0,this.checkoutPanel.title="Add some things to your cart first!";let t=window.getTemplate("emptyCart");this.itemsList.append(t),this.table.closest("table").hidden=!0,this.total.hidden=!0,this.a11y.announce("Nothing in Cart")}else this.checkoutPanel.disabled=!1,this.table.closest("table").hidden=!1,this.total.hidden=!1,this.checkoutPanel.title="Checkout"}notifyRestoredCart(){let t=window.getTemplate("restoredCart");this.checkout.querySelector(".tab-content[data-tab=cartItems]").insertBefore(t,this.itemsList),this.cartItems.forEach((t=>{let e=window.getTemplate("cartItem"),a=e.querySelector(".quantity");[a.dataset.id,e.querySelector("label").textContent,e.querySelector(".price").textContent,a.dataset.price,a.dataset.catalogId,e.querySelector('[name="quantity"]').value,e.querySelector(".total").textContent]=[t.post_id,t.name,window.formatPrice(t.price),t.price,t.catalog_id||"",t.quantity,window.formatPrice(t.quantity*t.price)],this.table.append(e)})),this.updateTotal()}updateTotal(){let t=0;this.cartItems.forEach((e=>t+=e.total));let e=.05*t;window.eraseText(this.totalTax),window.eraseText(this.grandTotal),window.typeText(this.totalTax,window.formatPrice(e)),window.typeText(this.grandTotal,window.formatPrice(t+e)),this.totalTax.classList.remove("typeText")}extractOrderData(t){const e=Array.from(this.cartItems.values()).map((t=>({catalog_id:t.catalog_id,quantity:String(t.quantity),price:t.price,note:t.note||""}))),a=e.reduce(((t,e)=>t+e.price*e.quantity),0);return{total:Math.round(100*a),items:e,customer:{email:this.isLoggedIn?this.userEmail:t.querySelector('[name="cart_email"]')?.value||"",name:t.querySelector('[name="cart_name"]')?.value||"",phone:t.querySelector('[name="cart_phone"]')?.value||""},note:t.querySelector('[name="special_instructions"]')?.value||"",pickup_time:t.querySelector('[name="pickup_time"]')?.value||""}}async handleFormSubmit(t){if(!this.isOpen)return;if(t.preventDefault(),!this.isInitialized)return void this.handleError("Checkout not initialized");const e=t.target,a=this.extractOrderData(e);try{window.jvbLoading?.showLoading?.("Processing payment...");const t=await this.processPayment(a);this.handleSuccess(t,e)}catch(t){this.handleError(t)}finally{window.jvbLoading?.hideLoading?.()}}trackOrder(t){this.orderId=t,this.scheduleOrderCheck(),this.checkout.querySelector("button[data-tab=order]").hidden=!1}scheduleOrderCheck(){window.debouncer.schedule("order",(()=>this.checkOrderStatus()),3e4)}async checkOrderStatus(){const t=await fetch(`${this.config.api_url}order-status/${this.orderId}`,{headers:{"X-WP-Nonce":this.config.nonce}}),e=await t.json();"ready"!==e.status&&this.scheduleOrderCheck(),this.updateOrderStatus(e)}updateOrderStatus(t){this.checkout.querySelectorAll(".status-item").forEach((e=>{e.dataset.status===t.status&&e.classList.add("active")})),this.checkout.querySelector("#eta").textContent=t.eta||"In progress"}renderSavedCards(){const t=document.getElementById("saved-cards");if(!t||0===this.savedCards.length)return;const e=`\n\t\t\t<div class="saved-cards-section">\n\t\t\t\t<h4>Saved Payment Methods</h4>\n\t\t\t\t${this.savedCards.map((t=>`\n\t\t\t\t\t<label class="saved-card">\n\t\t\t\t\t\t<input type="radio" name="payment-method" value="saved" data-card-id="${t.id}">\n\t\t\t\t\t\t<span class="card-info">\n\t\t\t\t\t\t\t<strong>${t.card_brand}</strong> ending in ${t.last_4}\n\t\t\t\t\t\t\t<small>Exp: ${t.exp_month}/${t.exp_year}</small>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t`)).join("")}\n\t\t\t\t<label class="saved-card">\n\t\t\t\t\t<input type="radio" name="payment-method" value="new" checked>\n\t\t\t\t\t<span>Use a new card</span>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t`;t.innerHTML=e,t.querySelectorAll('input[name="payment-method"]').forEach((t=>{t.addEventListener("change",(t=>{const e="new"===t.target.value,a=document.getElementById("payment-container");a&&(a.style.display=e?"block":"none"),this.selectedCardId=e?null:t.target.dataset.cardId}))}))}handleSuccess(t,e){document.dispatchEvent(new CustomEvent("checkoutSuccess",{detail:{result:t,form:e,provider:this.provider}}));const a=e.dataset.successUrl||`/order-confirmation/?order=${t.order_id||t.wp_order_id}`;window.location.href=a}handleError(t){console.error(`${this.provider} checkout error:`,t),document.dispatchEvent(new CustomEvent("checkoutError",{detail:{error:t,provider:this.provider}})),window.jvbNotifications?.show?.(t.message||t||"Payment failed","error")}};
\ No newline at end of file
+window.jvbCheckout=class{constructor(t={}){this.config=t,this.isInitialized=!1,this.cartItems=new Map,this.checkout=document.querySelector("aside#cart"),this.provider=this.checkout?.querySelector("form")?.dataset.provider||"",this.isOpen="1"===this.config.isOpen||!1,this.isOpen=!0,this.isLoggedIn=this.config.is_logged_in||!1,this.userEmail=this.config.user_email||"",this.savedCards=[],this.selectedCardId=null,this.cartId=null,this.stepMultiplier=1,this.cache=new window.jvbCache("cart",{TTL:864e5}),this.a11y=window.jvbA11y,this.T=window.jvbTemplates,this.initCart(),this.checkout&&(this.initElements(),this.init(),this.initListeners(),this.defineTemplates(),this.isLoggedIn&&this.loadSavedCards()),this.isOpen||this.closeShop(),this.popup=window.jvbPopup.registerPopup({popup:this.checkout,toggle:this.ui.toggle.btn,name:"Cart",onOpen:this.maybeAddEmptyState.bind(this),onClose:this.toggleButton.bind(this)})}closeShop(){document.querySelectorAll("[data-add-to-cart]").forEach(t=>{t.querySelectorAll("button, input").forEach(t=>{t.disabled=!0,t.title="Sorry, we are currently closed."})})}async init(){throw new Error("init() must be implemented by subclass")}async processPayment(t){throw new Error("processPayment() must be implemented by subclass")}async submitToServer(t,e){throw new Error("submitToServer() must be implemented by subclass")}async loadSavedCards(){}async initCart(){let t=await this.cache.get("cart")??new Map;t.size>0&&(this.cartItems=t,this.updateToggle(),this.notifyRestoredCart())}saveCart(){this.updateTotal(),this.updateToggle(),this.cache.set("cart",this.cartItems)}clearCart(){for(let[t,e]of this.cartItems.entries())this.removeFromCart(t);this.cartItems.clear(),Array.from(this.ui.cart.table.children).forEach(t=>{"TBODY"===t.tagName&&t.remove()}),this.saveCart()}getCartId(){return this.cartId||(this.cartId=crypto.randomUUID(),this.cache.set("cart_id",this.cartId)),this.cartId}defineTemplates(){this.T.define("mainCartItem",{refs:{header:".header th"},setup({el:t,refs:e,manyRefs:a,data:i}){e.header.textContent=i.label,t.dataset.label=i.label,t.dataset.id=i.parentId}}),this.T.define("childCartItem",{refs:{quantity:".quantity",label:"label",price:"span.price",button:"[data-remove-from-cart]"},manyRefs:{},setup({el:t,refs:e,manyRefs:a,data:i}){[e.quantity.dataset.id,e.quantity.dataset.price,e.quantity.dataset.catalogId,e.label.textContent,e.price.textContent,e.button.dataset.id]=[i.id,i.price,i.catalogId||"",i.name,window.formatPrice(i.price),i.id]}}),this.T.define("emptyCart")}initElements(){this.restoreElement=!1,this.selectors={toggle:{btn:".toggle-cart",count:".toggle-cart .count",icon:".toggle-cart .icon"},panels:{checkout:{toggle:'button[data-tab="checkout"]',form:"#cart form"}},cart:{wrap:"#cart .cart-items",empty:"#cart .cart-items .empty",table:"#cart .cart-items table",body:"#cart .cart-items tbody",total:"#cart .cart-total",foot:{foot:"#cart tfoot",total:"#cart tfoot .total",tax:"#cart tfoot .tax"},grandTotal:"#cart .total span",tax:"#cart .cart-total .tax span"}},this.ui=window.uiFromSelectors(this.selectors),this.ui.cart.items=new Map,this.isOpen||(this.ui.toggle.btn.disabled=!0,this.ui.toggle.btn.title="Currently closed for online ordering"),this.checkoutForm=this.checkout.querySelector("form"),this.tabs=window.jvbTabs.registerTab(this.checkoutForm,{updateURL:!1})}initListeners(){this.clickHandler=this.handleClick.bind(this),this.keyHandler=this.handleEscape.bind(this),this.changeHandler=this.handleChange.bind(this),this.checkoutForm.addEventListener("submit",t=>this.handleFormSubmit(t)),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler)}handleClick(t){if(window.targetCheck(t,"button")&&window.targetCheck(t,"div.quantity")){let e=window.targetCheck(t,"div.quantity");this.handleNumberClick(t,e)}else if(window.targetCheck(t,"[data-add-to-cart]")&&window.targetCheck(t,"button")){let e=window.targetCheck(t,"[data-add-to-cart]");this.handleAddToCart(e)}else if(window.targetCheck(t,"[data-remove-from-cart]")){let e=window.targetCheck(t,"[data-remove-from-cart]");this.handleRemoveFromCart(e)}else window.targetCheck(t,"[data-clear-cart]")?(this.clearCart(),document.querySelector(".restored")?.remove()):window.targetCheck(t,"[data-dismiss]")&&window.targetCheck(t,"[data-dismiss]").closest(".restored")?.remove()}handleChange(t){if(window.targetCheck(t,".quantity-input")){let e=t.target.closest(".quantity");t.target.value>0?this.handleAddToCart(e):this.handleRemoveFromCart(e)}}handleEscape(t){"Escape"===t.key?(this.restoreElement&&(this.restoreElement.classList.remove("show"),this.restoreElement=!1),this.stepMultiplier=1):t.ctrlKey&&t.shiftKey?this.stepMultiplier=Math.max(100*parseInt(this.stepMultiplier),1e3):t.shiftKey&&(this.stepMultiplier=Math.max(10*parseInt(this.stepMultiplier),1e3))}handleAddToCart(t){let e=t.dataset.id,a=parseFloat(t.dataset.price),i=parseInt(t.querySelector(".quantity input")?.value)??1,r=parseFloat(a*i);this.createItemElement(t);let s=this.determineParent(t),n={};this.cartItems.has(e)&&(n=this.cartItems.get(e));let o={post_id:e,name:n.name??t.dataset.name,label:n.label??s.dataset.label??"",parentId:n.parentId??s.dataset.id??"",price:a,quantity:i,total:r,catalog_id:n.catalogId??t.dataset.catalogId??""};this.cartItems.set(e,o),this.saveCart()}handleRemoveFromCart(t){if(console.log(t),confirm("This will remove this item from the cart. Continue?")){let e=t.dataset.id??t.id??!1;e||console.error("No ID found.."),this.removeFromCart(e),this.maybeAddEmptyState(),this.saveCart()}}removeFromCart(t){let e=this.findParentItem(t);if(e){let a=e.items.get(t);a.el.remove(),e.items.delete(t),a.reference&&(a.reference.value=0),0===e.items.size&&(console.log("Removing parent item"),e.el.remove(),this.ui.cart.items.delete(e.id))}this.cartItems.delete(t)}handleNumberClick(t,e){t.preventDefault();let a=0;if(t.target.closest(".increase")?a+=1:t.target.closest(".decrease")&&(a-=1),0!==a){let t=e.querySelector("input"),i=parseInt(t.step),r=""===t.value?0:parseInt(t.value);if(0===r&&a<0)return;let s=r+i*a*this.stepMultiplier;t.value=Math.max(s,0),t.dispatchEvent(new Event("change",{bubbles:!0})),this.handleNumberLimits(e)}}handleNumberLimits(t){let e=t.dataset.min,a=t.dataset.max,i=t.querySelector("input"),r=t.querySelector(".increase"),s=t.querySelector(".decrease"),n=parseInt(i.value);void 0!==e&&n<=e?(console.log("Resetting to minimum value"),i.value=e,s.disabled=!0):void 0!==a&&n>=a?(console.log("Resetting to maximum value"),i.value=a,r.disabled=!0):(r.disabled=!1,s.disabled=!1)}createItemElement(t){let e=this.maybeCreateCartItem(t),a=void 0===t.tagName?t.quantity:t.querySelector("input").value??1,i=void 0===t.tagName?t.price:t.dataset.price;e.quantity.value=a,e.total.textContent=window.formatPrice(a*i),e.reference&&(e.reference.value=a)}maybeCreateCartItem(t){let e={};if(void 0===t.tagName)e={id:String(t.post_id),name:t.name,price:t.price,catalogId:t.catalog_id||"",quantity:t.quantity,label:t.label,parentId:String(t.parentId)};else if(t.closest("#cart")){let e=t.dataset.id,a=this.findParentItem(e);if(a)return a.items.get(e);console.warn("Cart quantity changed but no existing row found for id:",e)}else{e={id:t.dataset.id,name:t.dataset.name,price:t.dataset.price,catalogId:t.dataset.catalogId||""};let a=this.determineParent(t);e.parentId=a.dataset.id??a.id,e.label=a.dataset.label??a.label}if(!this.ui.cart.items.has(e.parentId)){let t=this.T.create("mainCartItem",e);this.ui.cart.items.set(e.parentId,{el:t,id:e.parentId,label:e.label,items:new Map}),this.ui.cart.table.insertBefore(t,this.ui.cart.foot.foot)}let a=this.ui.cart.items.get(e.parentId);if(!a.items.has(e.id)){let t=this.T.create("childCartItem",e);a.items.set(e.id,{el:t,quantity:t.querySelector('[name="quantity"]'),total:t.querySelector(".total"),reference:document.querySelector(`.menu-items [data-id="${e.id}"] input`)}),a.el.append(t)}return a.items.get(e.id)}determineParent(t){if(void 0!==t.tagName)return this.parentFromHTML(t)}parentFromHTML(t){let e=t.closest(".menu-item");if(e)return e;let a=t.dataset.id;return this.findParentItem(a).el??!1}findParentItem(t){let e=!1;for(let[a,i]of this.ui.cart.items.entries())i.items.has(t)&&(e=this.ui.cart.items.get(a));return e}toggleButton(t=!1){this.ui.toggle.icon.hidden=t}maybeAddEmptyState(){this.toggleButton(!0),this.findEmpty(),0===this.cartItems.size?(this.ui.panels.checkout.toggle.disabled=!0,this.ui.panels.checkout.toggle.title="Add some things to your cart first!",this.ui.cart.empty.hidden=!1,this.ui.cart.table.hidden=!0,this.ui.cart.total.hidden=!0,this.a11y.announce("Nothing in Cart")):(this.ui.cart.empty.hidden=!0,this.ui.panels.checkout.toggle.disabled=!1,this.ui.cart.table.hidden=!1,this.ui.cart.total.hidden=!1,this.ui.panels.checkout.toggle.title="Checkout")}findEmpty(){this.ui.cart.empty||(this.ui.cart.empty=this.T.create("emptyCart"),this.ui.cart.wrap.append(this.ui.cart.empty))}notifyRestoredCart(){let t=window.getTemplate("restoredCart");document.body.append(t),t.classList.toggle("show"),this.restoreElement=t,this.cartItems.forEach(t=>{this.createItemElement(t)}),this.updateTotal()}updateToggle(){this.cartItems.size>0&&this.ui.toggle.btn.classList.contains("empty")&&this.ui.toggle.btn.classList.remove("empty"),0!==this.cartItems.size||this.ui.toggle.btn.classList.contains("empty")||this.ui.toggle.btn.classList.add("empty");let t=0;for(let[e,a]of this.cartItems.entries())t+=a.quantity;this.ui.toggle.count.textContent=0===this.cartItems.size?"":t}updateTotal(){let t=0;this.cartItems.forEach(e=>t+=e.total);let e=.05*t;window.eraseText(this.ui.cart.tax),window.eraseText(this.ui.cart.foot.tax),window.eraseText(this.ui.cart.foot.total),window.typeText(this.ui.cart.foot.tax,window.formatPrice(e)),window.typeText(this.ui.cart.grandTotal,window.formatPrice(t+e)),window.typeText(this.ui.cart.foot.total,window.formatPrice(t+e)),this.ui.cart.tax.classList.remove("typeText")}extractOrderData(t){const e=Array.from(this.cartItems.values()).map(t=>({catalog_id:t.catalog_id,quantity:String(t.quantity),price:t.price,note:t.note||""})),a=e.reduce((t,e)=>t+e.price*e.quantity,0);return{total:Math.round(100*a),items:e,customer:{email:this.isLoggedIn?this.userEmail:t.querySelector('[name="cart_email"]')?.value||"",name:t.querySelector('[name="cart_name"]')?.value||"",phone:t.querySelector('[name="cart_phone"]')?.value||""},note:t.querySelector('[name="special_instructions"]')?.value||"",pickup_time:t.querySelector('[name="pickup_time"]')?.value||""}}async handleFormSubmit(t){if(!this.isOpen)return;if(t.preventDefault(),!this.isInitialized)return void this.handleError("Checkout not initialized");const e=t.target,a=this.extractOrderData(e);try{window.jvbLoading?.showLoading?.("Processing payment...");const t=await this.processPayment(a);this.handleSuccess(t,e)}catch(t){this.handleError(t)}finally{window.jvbLoading?.hideLoading?.()}}trackOrder(t){this.orderId=t,this.scheduleOrderCheck(),this.checkout.querySelector("button[data-tab=order]").hidden=!1}scheduleOrderCheck(){window.debouncer.schedule("order",()=>this.checkOrderStatus(),3e4)}async checkOrderStatus(){const t=await fetch(`${this.config.api_url}order-status/${this.orderId}`,{headers:{"X-WP-Nonce":this.config.nonce}}),e=await t.json();"ready"!==e.status&&this.scheduleOrderCheck(),this.updateOrderStatus(e)}updateOrderStatus(t){this.checkout.querySelectorAll(".status-item").forEach(e=>{e.dataset.status===t.status&&e.classList.add("active")}),this.checkout.querySelector("#eta").textContent=t.eta||"In progress"}renderSavedCards(){const t=document.getElementById("saved-cards");if(!t||0===this.savedCards.length)return;const e=`\n\t\t\t<div class="saved-cards-section">\n\t\t\t\t<h4>Saved Payment Methods</h4>\n\t\t\t\t${this.savedCards.map(t=>`\n\t\t\t\t\t<label class="saved-card">\n\t\t\t\t\t\t<input type="radio" name="payment-method" value="saved" data-card-id="${t.id}">\n\t\t\t\t\t\t<span class="card-info">\n\t\t\t\t\t\t\t<strong>${t.card_brand}</strong> ending in ${t.last_4}\n\t\t\t\t\t\t\t<small>Exp: ${t.exp_month}/${t.exp_year}</small>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t`).join("")}\n\t\t\t\t<label class="saved-card">\n\t\t\t\t\t<input type="radio" name="payment-method" value="new" checked>\n\t\t\t\t\t<span>Use a new card</span>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t`;t.innerHTML=e,t.querySelectorAll('input[name="payment-method"]').forEach(t=>{t.addEventListener("change",t=>{const e="new"===t.target.value,a=document.getElementById("payment-container");a&&(a.style.display=e?"block":"none"),this.selectedCardId=e?null:t.target.dataset.cardId})})}handleSuccess(t,e){document.dispatchEvent(new CustomEvent("checkoutSuccess",{detail:{result:t,form:e,provider:this.provider}}));const a=e.dataset.successUrl||`/order-confirmation/?order=${t.order_id||t.wp_order_id}`;window.location.href=a}handleError(t){console.error(`${this.provider} checkout error:`,t),document.dispatchEvent(new CustomEvent("checkoutError",{detail:{error:t,provider:this.provider}})),window.jvbNotifications?.show?.(t.message||t||"Payment failed","error")}};
\ No newline at end of file
diff --git a/assets/js/min/creator.min.js b/assets/js/min/creator.min.js
index 794be84..0962976 100644
--- a/assets/js/min/creator.min.js
+++ b/assets/js/min/creator.min.js
@@ -1 +1 @@
-window.jvbTaxCreator=class{constructor(e){this.selector=e,this.queue=window.jvbQueue,this.initElements(),this.initListeners()}initElements(){this.selectors={details:"details.create-term",parent:"#select_parent",summary:".create-term summary",suggestion:".term-suggestions",name:"#term_name",button:".submit-term",form:"form.create-term",label:{name:'[for="term_name"]',parent:'[for="select_parent"]'},loading:".loading-message.create-term"},this.ui=window.uiFromSelectors(this.selectors,this.selector.container)}handleOpen(e){this.field=e,this.ui.details&&(this.ui.details.hidden=!e.canCreate,this.ui.summary&&(this.ui.summary.textContent=`Add new ${e.singular}`),this.ui.label.name&&(this.ui.label.name.textContent=`Name this ${e.singular}`),this.ui.label.parent&&(this.ui.label.parent.textContent="Nest it under"))}initListeners(){this.clickHandler=this.handleClick.bind(this),document.addEventListener("click",this.clickHandler),this.ui.form&&this.ui.form.addEventListener("change",(e=>{e.preventDefault(),e.stopPropagation()}))}handleClick(e){if(window.targetCheck(e,this.selectors.summary))return this.ui.details.open&&this.ui.name?.focus(),void this.resetParentOptions()}async handleTermCreation(e){if(!e.name||e.name.length<2)return!1;try{const t=await this.createTerm(e);let i=this.selector.currentField();return t.success?t.term?.pending?(this.selector.setMessage(i,!0,`"${e.name}" submitted for approval`,!1),!1):(t.success&&t.term&&(await this.handleSuccessfulCreation(t.term,e),this.clearForm()),t.term):t.term&&t.term.id?(this.selector.setMessage(i,!0,`Using existing "${t.term.name}"`),t.term):(this.selector.setMessage(i,!0,t.message||"Creation failed",!1),!1)}catch(e){return console.error("Error creating term:",e),!1}}async handleSuccessfulCreation(e,t){const i={id:e.id,name:e.name,path:e.path||e.name,slug:e.slug||e.name.toLowerCase().replace(/\s+/g,"-"),parent:t.parent||0,taxonomy:t.taxonomy,count:0,hasChildren:!1};this.selector.store.data.set(e.id,i),this.ui.details&&(this.ui.details.open=!1),this.selector.store.clearCache(),this.selector.store.fetch().catch((e=>{console.warn("Background fetch after term creation failed:",e)}))}resetParentOptions(){const e=this.selector.currentField();if(!e)return;const t=e.taxonomy;if(!t)return;if(!this.ui.parent)return;let i=this.ui.parent.querySelector("option");if(!i)return;window.removeChildren(this.ui.parent),this.ui.parent.append(i.cloneNode(!0));const r=this.selector.store.filters.parent||0;if(0!==r){const e=this.selector.store.get(r);if(e){let t=i.cloneNode(!0);t.value=e.id,t.textContent=e.name,this.ui.parent.append(t)}}const s=[];this.selector.store.getFiltered().forEach((e=>{e.taxonomy===t&&e.parent===r&&s.push(e)})),s.sort(((e,t)=>e.name.localeCompare(t.name))),s.forEach((e=>{let t=i.cloneNode(!0);t.id=`select-parent-${e.id}`,t.value=e.id,t.textContent="  — "+e.name,this.ui.parent.append(t)}))}async createTerm(e){if(e.name&&void 0!==e.parent&&e.taxonomy)try{const t=await fetch(`${jvbSettings.api}terms`,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce()},body:JSON.stringify(e)});if(!t.ok)throw new Error(`Server error: ${t.status}`);return await t.json()}catch(e){throw console.error("Error creating term:",e),e}}clearForm(){this.ui.name&&(this.ui.name.value=""),this.selector.ui.search.input&&(this.selector.ui.search.input.value="")}destroy(){this.clickHandler&&document.removeEventListener("click",this.clickHandler),this.ui.loading&&(this.ui.loading.hidden=!0)}};
\ No newline at end of file
+window.jvbTaxCreator=class{constructor(e){this.selector=e,this.queue=window.jvbQueue,this.initElements(),this.initListeners()}initElements(){this.selectors={details:"details.create-term",parent:"#select_parent",summary:".create-term summary",suggestion:".term-suggestions",name:"#term_name",button:".submit-term",form:"form.create-term",label:{name:'[for="term_name"]',parent:'[for="select_parent"]'},loading:".loading-message.create-term"},this.ui=window.uiFromSelectors(this.selectors,this.selector.container)}handleOpen(e){this.field=e,this.ui.details&&(this.ui.details.hidden=!e.canCreate,this.ui.summary&&(this.ui.summary.textContent=`Add new ${e.singular}`),this.ui.label.name&&(this.ui.label.name.textContent=`Name this ${e.singular}`),this.ui.label.parent&&(this.ui.label.parent.textContent="Nest it under"))}initListeners(){this.clickHandler=this.handleClick.bind(this),document.addEventListener("click",this.clickHandler),this.ui.form&&this.ui.form.addEventListener("change",e=>{e.preventDefault(),e.stopPropagation()})}handleClick(e){if(window.targetCheck(e,this.selectors.summary))return this.ui.details.open&&this.ui.name?.focus(),void this.resetParentOptions()}async handleTermCreation(e){if(!e.name||e.name.length<2)return!1;try{const t=await this.createTerm(e);let i=this.selector.currentField();return t.success?t.term?.pending?(this.selector.setMessage(i,!0,`"${e.name}" submitted for approval`,!1),!1):(t.success&&t.term&&(await this.handleSuccessfulCreation(t.term,e),this.clearForm()),t.term):t.term&&t.term.id?(this.selector.setMessage(i,!0,`Using existing "${t.term.name}"`),t.term):(this.selector.setMessage(i,!0,t.message||"Creation failed",!1),!1)}catch(e){return console.error("Error creating term:",e),!1}}async handleSuccessfulCreation(e,t){const i={id:e.id,name:e.name,path:e.path||e.name,slug:e.slug||e.name.toLowerCase().replace(/\s+/g,"-"),parent:t.parent||0,taxonomy:t.taxonomy,count:0,hasChildren:!1};this.selector.store.data.set(e.id,i),this.ui.details&&(this.ui.details.open=!1),this.selector.store.clearCache(),this.selector.store.fetch().catch(e=>{console.warn("Background fetch after term creation failed:",e)})}resetParentOptions(){const e=this.selector.currentField();if(!e)return;const t=e.taxonomy;if(!t)return;if(!this.ui.parent)return;let i=this.ui.parent.querySelector("option");if(!i)return;window.removeChildren(this.ui.parent),this.ui.parent.append(i.cloneNode(!0));const r=this.selector.store.filters.parent||0;if(0!==r){const e=this.selector.store.get(r);if(e){let t=i.cloneNode(!0);t.value=e.id,t.textContent=e.name,this.ui.parent.append(t)}}const s=[];this.selector.store.getFiltered().forEach(e=>{e.taxonomy===t&&e.parent===r&&s.push(e)}),s.sort((e,t)=>e.name.localeCompare(t.name)),s.forEach(e=>{let t=i.cloneNode(!0);t.id=`select-parent-${e.id}`,t.value=e.id,t.textContent="  — "+e.name,this.ui.parent.append(t)})}async createTerm(e){if(e.name&&void 0!==e.parent&&e.taxonomy)try{const t=await fetch(`${jvbSettings.api}terms`,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce()},body:JSON.stringify(e)});if(!t.ok)throw new Error(`Server error: ${t.status}`);return await t.json()}catch(e){throw console.error("Error creating term:",e),e}}clearForm(){this.ui.name&&(this.ui.name.value=""),this.selector.ui.search.input&&(this.selector.ui.search.input.value="")}destroy(){this.clickHandler&&document.removeEventListener("click",this.clickHandler),this.ui.loading&&(this.ui.loading.hidden=!0)}};
\ No newline at end of file
diff --git a/assets/js/min/crud.min.js b/assets/js/min/crud.min.js
index 9462502..5076e61 100644
--- a/assets/js/min/crud.min.js
+++ b/assets/js/min/crud.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.container=document.querySelector(".crud[data-content]:not([data-ignore])"),this.container&&(this.content=this.container.dataset.content,this.endpoint=this.container.dataset.endpoint??"content",this.singular=this.container.dataset.singular,this.plural=this.container.dataset.plural,this.queue=window.jvbQueue,this.a11y=window.jvbA11y,this.error=window.jvbError,this.populate=window.jvbPopulate,this.cache=new window.jvbCache(this.content),this.activeItem=null,this.isTimeline=!1,this.isPopulating=!1,this.changes=new Map,this.items=new Map,this.init())}init(){this.initElements(),this.initListeners(),this.defineTemplates();let e=this.initSettings();this.initStore(e),this.checkHideFilters(),this.initIntegrations(),this.initUploader(),this.initModals()}defineTemplates(){const e=window.jvbTemplates,t=this,s=(e,s,i)=>{e.dataset.itemId=i.id;let a=s.checkbox.closest(".preview");window.prefixInput(s.checkbox,`select-${i.id}`,a,!0),s.checkbox.value=i.id,s.checkbox.checked=t.selected.has(parseInt(i.id)),s.selectLabel&&(s.selectLabel.htmlFor=`select-${i.id}`),s.edit&&(s.edit.dataset.id=i.id),s.trash&&(s.trash.dataset.id=i.id)},i=function(e,t,s){let i=s?.fields?.post_thumbnail||s?.fields?.thumbnail;if(i){const e=s.images[i]??{};t.img.src=e.medium??"",t.img.alt=e.alt??s.fields.post_title??""}};e.define("gridView",{refs:{img:"img",checkbox:".select-item",selectLabel:"label.select-item-label",edit:'[data-action="edit"]',trash:'[data-action="trash"]'},setup({el:e,refs:t,manyRefs:a,data:l}){s(e,t,l),i(0,t,l)}}),e.define("listView",{refs:{img:"img",checkbox:".select-item",selectLabel:"label.select-item-label",edit:'[data-action="edit"]',trash:'[data-action="trash"]'},manyRefs:{attrs:"[data-attr]",fields:"[data-field]"},setup({el:e,refs:t,manyRefs:a,data:l}){s(e,t,l),i(0,t,l),a?.attrs?.forEach((e=>{const t=l[e.dataset.attr];t&&""!==t?e.textContent=t:e.remove()})),a?.fields?.forEach((e=>{const t=l.fields?.[e.dataset.field];t&&""!==t?"DIV"===e.tagName?e.innerHTML=t:e.textContent=t:e.remove()}))}});let a={};this.isTimeline&&(a.sharedRow="tr.shared",a.point="tr.timeline-point"),e.define("tableView",{refs:{checkbox:".select-item",selectLabel:"label.select-item-label",...a},manyRefs:{inputs:"input,select,textarea",status:'input[name="post_status"]',selectors:'[data-type="selector"]',fields:"[data-field]"},setup({el:e,refs:i,manyRefs:a,data:l}){if(s(e,i,l),a?.inputs?.forEach((e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)})),a?.status?.forEach((e=>{e.value===l.status&&(e.checked=!0)})),t.isTimeline)i.sharedRow&&(i.sharedRow.querySelectorAll("input,select,textarea").forEach((e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)})),t.populate.populate(i.sharedRow,l),i.sharedRow.querySelectorAll('input[name="post_status"]').forEach((e=>{e.value===l.status&&(e.checked=!0)}))),i.point&&l.fields?.timeline&&(Object.entries(l.fields.timeline).forEach((([s,a],n)=>{const o=i.point.cloneNode(!0);o.dataset.index=`${n}`,o.dataset.itemId=a.id,o.querySelectorAll("input,select,textarea").forEach((e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${a.id}-`,t)})),t.populate.populate(o,{fields:a,images:l.images,taxonomies:l.taxonomies});const r=l.images?.[a.post_thumbnail];r&&o.querySelector(".field.upload")?.setAttribute("title",r["image-title"]??""),e.insertBefore(o,i.point)})),i.point.remove());else if(void 0!==t.ui.table.form?.dataset.edit)a?.inputs?.forEach((e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)})),a?.status?.forEach((e=>{e.value===l.status&&(e.checked=!0)})),t.populate.populate(e,l);else{const e=Object.hasOwn(l,"fields")?l.fields:l;a?.fields?.forEach((t=>{if(Object.hasOwn(e,t.dataset.field)&&""!==e[t.dataset.field]){let s=e[t.dataset.field],i=e.children[0];i&&(i.textContent="date"===t.dataset.field?window.formatTimeAgo(s):s)}}))}a?.selectors?.forEach((e=>e.setAttribute("data-lazy","")))}}),e.define("emptyState"),e.define("bulkItem",{refs:{checkbox:"input",img:"img",label:"label"},setup({el:e,refs:t,manyRefs:s,data:i}){t.checkbox&&(t.checkbox.id=`bulk_${i.id}`,t.checkbox.value=i.id,t.checkbox.checked=!0,t.checkbox.name="selected[]");let a=i?.images[i?.fields?.post_thumnbail]??{};t.img&&Object.keys(a).length>0&&(t.img.src=a.medium??"",t.img.alt=a.alt??""),t.label&&(t.label.title=item.fields.post_title)}}),e.define("trashOptions"),e.define("notTrashOptions"),e.define("contentTable")}initElements(){this.allowedFilters=["status","orderby","order","search","date-filter","dateFrom","dateTo"],this.selectors={buttons:{create:".create-item",clearFilters:'[data-action="clear-filters"]'},views:{grid:'input[data-view="grid"]',list:'input[data-view="list"]',table:'input[data-view="table"]'},modals:{create:{modal:"dialog.create",form:"dialog.create form",h2:"dialog.create h2"},edit:{modal:"dialog.edit",form:"dialog.edit form",h2:"dialog.edit h2"},bulkEdit:{modal:"dialog.bulkEdit",selected:"dialog.bulkEdit .selected",h2:"dialog.bulkEdit h2 span",form:"dialog.bulkEdit form"},date:{modal:"dialog.date-range",start:"dialog.date-range .date-start",end:"dialog.date-range .date-end",month:"dialog.date-range .month-select"}},grid:`.${this.content}.item-grid`,table:{nav:"#vertical",form:"form.table",table:"form.table table",body:"form.table body",head:"form.table thead",foot:"form.table tfoot",selectedColumns:".all-filters .multi-select",columns:"thead th"},bulk:{action:".bulk-action-select",count:".bulk-controls .selected-count",control:".bulk-controls .bulk-actions",select:".bulk-controls select",selectAll:".select-all"},filters:{container:"details.all-filters",search:'.all-filters input[type="search"]',status:{all:'[name="status"]#all',publish:'[name="status"]#publish',draft:'[name="status"]#draft',trash:'[name="status"]#trash'},orderby:{date:'[name="orderby"]#date',alphabetical:'[name="orderby"]#alphabetical'},order:{asc:'[name="order"][value="asc"]',desc:'[name="order"][value="desc"]'},date:'[data-filter="date"]'},uploader:{details:"details.uploader",form:"details.uploader form",uploader:'details.uploader [data-field-type="upload"]'}},this.ui=window.uiFromSelectors(this.selectors);const e=document.querySelectorAll('[data-filter="taxonomies"]');e.length>0&&(this.ui.filters.taxonomies={},e.forEach((e=>{const t=e.dataset.taxonomy;this.ui.filters.taxonomies[t]=e,this.allowedFilters.push(`tax_${t}`)}))),this.isTimeline=!!document.querySelector("[data-timeline]")}initUploader(){this.ui.uploader.form&&(this.uploadForm=this.forms.registerForm(this.ui.uploader.form).id??!1,window.jvbUploads.subscribe(((e,t)=>{if("sent-to-queue"===e&&t.field.id===this.ui.uploader.uploader.dataset.uploader&&(this.uploadForm&&this.forms.store.delete(this.uploadForm),window.debouncer.schedule("crud-complete",(()=>{this.store.clearCache()}))),"sent-to-queue"===e&&t.field){const e=t.field.config.name,s=t.field.config.itemID;s&&e&&this.changes.has(s)&&delete this.changes.get(s)[e]}})))}initModals(){this.modals={};for(let[e,t]of Object.entries(this.ui.modals))t.modal&&(this.modals[e]=new window.jvbModal(t.modal),this.modals[e].subscribe(((t,s)=>{if("modal-close"===t){const t=this.ui.modals[e].form.dataset.formId;t&&this.forms.clearForm(t),this.resetForm(this.ui.modals[e].form),"date"===e&&this.handleCustomDateSelection(),["edit","bulkEdit","create"].includes(e)&&window.debouncer.timeouts.has(`save-${this.content}`)&&this.scheduleSave(0)}})))}initStore(e){let t={...this.defaults,...e};const s=window.jvbStore.register(this.content,[{storeName:this.content,keyPath:"id",endpoint:this.endpoint??"content",headers:{"X-Action-Nonce":window.auth.getNonce("dash")},indexes:[{name:"id",keyPath:"id"},{name:"status",keyPath:"status"},{name:"date",keyPath:"date"},{name:"modified",keyPath:"modified"},{name:"title",keyPath:"title"}],isAuth:!0,filters:t,ignore:["content","user"],TTL:36e5,showLoading:!0},{storeName:"changes",keyPath:"id"}]);this.changesStore=s.changes,this.store=s[this.content],this.store.subscribe(((e,t)=>{if("data-loaded"===e)this.render(),this.selectionHandler.collectItems()})),this.changesStore.subscribe(((e,t)=>{if("data-ready"===e){let e=this.changesStore.getAll();e.length>0&&(e.forEach((e=>{this.changes.set(e.id,e)})),this.savePosts("",!1).then((()=>{})))}}))}initIntegrations(){this.selected=new Set,this.selectionHandler=new window.jvbHandleSelection(this.container,{selectAll:{checkbox:"#select-all",label:".bulk-select label",span:".bulk-select label span"},wrapper:{wrapper:".wrap"},item:{idAttribute:"itemId"}}),this.selectionHandler.subscribe(((e,t)=>{this.selected=new Set([...t.selectedItems].map((e=>parseInt(e)))),this.ui.bulk.control.hidden=0===this.selected.size,this.ui.bulk.count.hidden=0===this.selected.size,this.ui.bulk.count.textContent=`${this.selected.size} ${this.plural} selected`})),this.forms=window.jvbForm,window.jvbUploads&&window.jvbUploads.subscribe(((e,t)=>{"groups_uploaded"===e&&t.content===this.content&&this.handleGroupsUploaded(t)})),this.queue.subscribe(((e,t)=>{if(["image_upload","video_upload","document_upload"].includes(t.type)&&"operation-status"===e&&"completed"===t.status&&this.store.clearCache(),"operation-status"===e&&"completed"===t.status&&"uploads/groups"===t.endpoint&&(t.result&&t.result.group_mappings&&(console.log("Handling group mapping from queue response"),this.handleGroupMappings(t.result.group_mappings)),this.store.clearCache()),"operation-status"===e&&"completed"===t.status&&"content_update"===t.type){if(this.store.clearCache(),!t.result||!t.result.success||!t.result.errors)return void console.warn("Content update completed but no results",t);if(Object.keys(t.result.success).length>0&&this.checkCompletedChanges(Object.entries(t.result.success)),Object.keys(t.result.errors).length>0)return void this.checkFailedChanges(Object.entries(t.result.errors));0===Object.keys(t.result.success).length&&(console.log(t.result.success),t.result.success.forEach((e=>this.changesStore.delete(e))),this.store.clearCache())}if("sent-to-server"===e&&"content_update"===t.type){if(t instanceof FormData)return;for(let[e,s]of Object.entries(t.posts))this.compareStored(e,s)}}))}checkCompletedChanges(e){for(let[t,s]of e)this.compareStored(t,s)}compareStored(e,t){let s=this.changesStore.get(e);if(!s)return;for(let[e,i]of Object.entries(t))if(Object.hasOwn(s,e)){let t=window.getDifferences.map(s[e],i);t?s[e]=t:delete s[e]}let i=Object.hasOwn(s,"id"),a=Object.hasOwn(s,"content");i&&a&&2===Object.keys(s).length||(i||a)&&1===Object.keys(s).length||0===Object.keys(s).length?(this.changesStore.delete(e),this.store.clearCache()):this.changesStore.save(s)}checkFailedChanges(e){}initSettings(){this.defaults={content:this.content,user:window.auth.getUser(),page:1,status:"all",orderby:"date",order:"desc",search:""};let e={},t=this.container.dataset.view??"grid";this.view=this.cache.get("view")??t,this.view!==t&&(this.ui.views[this.view].checked=!0),this.status=this.cache.get("status")??this.defaults.status,this.status!==this.defaults.status&&(this.ui.filters.status[this.status].checked=!0,e.status=this.status),this.orderby=this.cache.get("orderby")??this.defaults.orderby,this.orderby!==this.defaults.orderby&&(this.ui.filters.orderby[this.orderby].checked=!0,e.orderBy=this.orderby),this.order=this.cache.get("order")??this.defaults.order,this.order!==this.defaults.order&&(this.ui.filters.order[this.order].checked=!0,e.order=this.order),this.ui.filters.taxonomies&&Object.entries(this.ui.filters.taxonomies).forEach((([t,s])=>{const i=`tax_${t}`,a=this.cache.get(i);a&&(s.value=a,e[i]=a)}));let s=this.cache.get("tabNav")??"horizontal";this.ui.table.nav&&"vertical"===s&&(this.ui.table.nav.checked=!0);let i={showFilters:{element:this.ui.filters.container,default:"closed"},showUploader:{element:this.ui.uploader.details,default:"open"}};for(let[e,t]of Object.entries(i))if(t.element){let s=this.cache.get(e)??t.default;t.element.open="open"===s,t.element.addEventListener("toggle",(()=>{this.cache.set(e,t.element.open?"open":"closed")}))}return e}initListeners(){this.changeHandler=this.handleChange.bind(this),this.clickHandler=this.handleClick.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleModalSubmit.bind(this),document.addEventListener("change",this.changeHandler),document.addEventListener("click",this.clickHandler),this.ui.filters.search&&this.ui.filters.search.addEventListener("input",this.inputHandler);for(let[e,t]of Object.entries(this.ui.modals))t.form&&t.form.addEventListener("submit",this.submitHandler)}handleModalSubmit(e){e.preventDefault();const t=e.target.closest("dialog");if(!t)return;if(t.classList.contains("create"))return void this.handleCreateSubmit(t);this.plural;this.scheduleSave(0),this.modals.edit.handleClose()}async handleCreateSubmit(e){e.dataset.itemId;this.changes.size>0&&(this.cancelBackup(),await this.handleBackup());const t=await this.changesStore.getAll();if(0===t.length)return;let s={};t.forEach((e=>{const{id:t,...i}=e;s[t]=i}));let i=this.queue.addToQueue({endpoint:this.endpoint,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},data:{posts:s},popup:`Creating your new ${this.singular}`,title:`Creating your new ${this.singular}`});if(!i)return;const a=e.querySelectorAll("[data-upload-field]");for(const e of a){const t=e.dataset.uploader;if(!t)continue;0!==window.jvbUploads.stores.uploads.filterByIndex({field:t}).length&&await window.jvbUploads.queueUploads("uploads",t,i)}}handleChange(e){const t=e.target.closest("[data-item-id]"),s=e.target.matches("[data-filter]"),i=e.target.matches(".bulk-action-select"),a=e.target.matches("[data-view]");if(t||s||i||a)if(this.isPopulating||!t||e.target.closest("[data-ignore], .select-item")){if(a)return this.items.clear(),void this.handleViewChange(e.target);if(i)this.handleBulkAction(e.target);else if(s)this.handleFilterChange(e.target);else if("table"===this.view){if(e.target.matches("details.multi-select"))return void this.toggleColumn(e.target.id,e.target.checked);e.target.matches(this.selectors.table.nav)&&(this.tabNav=e.target.checked,this.cache.set("tabNav",e.target.checked?"vertical":"horizontal"))}}else this.handleItemUpdate(e)}handleBulkAction(e){if(e.value.startsWith("tax-")){const t=e.options[e.selectedIndex],s=t.dataset.taxonomy,i=t.dataset.single,a=t.dataset.plural;return window.jvbSelector.openEmpty(s,i,a,(e=>this.handleBulkTaxonomy(e))),void(e.value="")}switch(e.value){case"edit":this.openBulkEditModal();break;case"publish":case"trash":case"delete":this.setBulkStatus(e.value);break;case"draft":case"restore":this.setBulkStatus("draft")}}handleBulkTaxonomy(e){e.termIds.length&&this.selected.size&&(this.selected.forEach((t=>{const s=this.store.get(t);if(!s)return;const i=(s.taxonomies?.[e.taxonomy]||[]).map((e=>e.id)),a=[...new Set([...i,...e.termIds])];this.updateItem(t,e.taxonomy,a)})),this.savePosts(`Adding ${e.terms.length} ${e.taxonomy} to ${this.selected.size} ${this.plural}...`).then((()=>{})),this.selectionHandler.clearSelection())}handleItemUpdate(e){let t=window.targetCheck(e,"[data-item-id]");if(!t)return;const s=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');let i,a;if(s)i=s.dataset.field,a=this.forms.getFieldValue(s);else{let t=e.target.closest("[data-field]");i=t.dataset.field,a=this.forms.getFieldValue(e.target)}t.dataset.itemId.split(",").forEach((e=>{this.updateItem(e,i,a)}))}updateItem(e,t,s){if(this.isPopulating)return;t.replace(`[${e}]`,"");const i=this.store.get(e);if(i){const a=i.fields?.[t]??i[t];if(null===window.getDifferences.map(a,s)){if(this.changes.has(e)){delete this.changes.get(e)[t];0===Object.keys(this.changes.get(e)).filter((e=>"id"!==e&&"content"!==e)).length&&(this.changes.delete(e),this.changesStore.delete(e))}return}}this.changes.has(e)||this.changes.set(e,{id:e,content:this.content}),this.changes.get(e)[t]=s,this.scheduleBackup(),"number"!=typeof e&&String(e).includes("group")||this.scheduleSave()}scheduleBackup(){window.debouncer.schedule(`changes-${this.content}`,(async()=>{this.changes.size>0&&await this.handleBackup()}),2e3)}cancelBackup(){window.debouncer.cancel(`changes-${this.content}`)}async handleBackup(){const e=Array.from(this.changes.values());this.changes.clear();const t=e.map((e=>e.id)),s=await Promise.all(t.map((e=>this.changesStore.get(e)))),i=e.map(((e,t)=>s[t]?window.deepMerge(s[t],e):e));await this.changesStore.saveMany(i)}scheduleSave(e=1e4){window.debouncer.schedule(`save-${this.content}`,(async()=>{this.changes.size>0&&(this.cancelBackup(),await this.handleBackup()),await this.savePosts("",!1)}),e)}handleFilterChange(e){let t=e.dataset.filter;return"date"===t&&"custom"===e.value?(e.value="",void this.modals.date.handleOpen()):"date"===t&&""!==e.value?(this.setFilter("date-filter",e.value),this.deleteFilter("dateFrom"),this.deleteFilter("dateTo"),void this.checkHideFilters()):("taxonomies"===t&&(t=`tax_${e.dataset.taxonomy}`),void this.setFilter(t,e.value))}checkHideFilters(){const e=this.store.filters,t=Object.entries(e).some((([e,t])=>!["content","user","page"].includes(e)&&(this.defaults[e]!==t&&""!==t&&null!==t)));this.ui.buttons.clearFilters.hidden=!t}clearAllFilters(){let e=this.store.filters;this.store.clearFilters();for(let[t,s]of Object.entries(e))this.cache.remove(t),this.deleteFilter(t,s);this.a11y.announce("All filters cleared")}handleCustomDateSelection(){if(this.ui.modals.date.month&&this.ui.modals.date.month.value){const[e,t]=this.ui.modals.date.month.value.split("-"),s=`${e}-${t}-01`,i=new Date(e,parseInt(t),0).getDate(),a=`${e}-${t}-${String(i).padStart(2,"0")}`;this.setFilter("dateFrom",s),this.setFilter("dateTo",a),this.deleteFilter("date-filter"),this.ui.modals.date.month.value=""}else this.ui.modals.date.start&&this.ui.modals.date.start.value&&this.ui.modals.date.end&&this.ui.modals.date.end.value&&(this.setFilter("dateFrom",this.ui.modals.date.start.value),this.setFilter("dateTo",this.ui.modals.date.end.value),this.deleteFilter("date-filter"),this.ui.modals.date.start.value="",this.ui.modals.date.end.value="");this.checkHideFilters()}handleViewChange(e){this.view=e.dataset.view,this.cache.set("view",this.view),this.render()}handleClick(e){if(e.target.matches(".clear-search"))return void this.deleteFilter("search","");const t=e.target.closest("[data-action]");return t?(e.preventDefault(),void this.handleActionButton(t)):e.target.matches(".apply-date-filter")?(this.handleCustomDateSelection(),void this.modals.date.handleClose()):void((e.target.matches(this.selectors.buttons.create)||e.target.closest(this.selectors.buttons.create))&&this.openCreateModal())}openCreateModal(){this.forms.registerForm(this.ui.modals.create.form,{cache:!1}),this.ui.modals.create.modal.dataset.itemId=window.generateID("new"),this.modals.create.handleOpen()}handleActionButton(e){const t=e.dataset.id;switch(e.dataset.action){case"edit":this.openEditModal(t);break;case"delete":confirm("Delete this item? This cannot be undone")&&(this.updateItem(t,"post_status","delete"),window.fade(e.closest(".item"),!1),this.savePosts(`Permanently deleting ${this.singular}...`).then((()=>{})),this.store.delete(t));break;case"trash":"trash"===this.status?confirm("Delete this item? This cannot be undone")&&(this.updateItem(t,"post_status","delete"),window.fade(e.closest(".item"),!1),this.savePosts(`Permanently deleting ${this.singular}...`).then((()=>{})),this.store.delete(t)):(this.updateItem(t,"post_status","trash"),window.fade(e.closest(".item"),!1),this.savePosts(`Sending ${this.singular} to trash...`).then((()=>{})));break;case"bulk-edit":this.selected.size>0&&this.openBulkEditModal();break;case"bulk-delete":this.handleBulkDelete();break;case"refresh":this.store.clearCache(),this.store.fetch();break;case"clear-filters":this.clearAllFilters()}}handleBulkDelete(){let e="trash"===this.status;if(this.selected.size>0&&confirm(`${e?"Permanently delete":"Send"} ${this.selected.size} ${1===this.selected.size?this.singular:this.plural}${e?"":"to trash"}?`)){this.selected.forEach((t=>{this.store.delete(t),this.updateItem(t,"post_status",e?"delete":"trash")}));let t=e?`Permanently deleting ${this.selected.size} ${1===this.selected.size?this.singular:this.plural}`:`Sending ${this.selected.size} ${1===this.selected.size?this.singular:this.plural} to trash`;this.savePosts(t).then((()=>{})),this.selectionHandler.clearSelection()}}handleInput(e){e.preventDefault(),e.stopPropagation();let t=e.target.value.trim(),s=`${this.content}-search`;0!==t.length?window.debouncer.schedule(s,(()=>{this.a11y.announce(`Searching for "${t}"...`),this.store.setFilters({search:t,page:1})}),300):this.deleteFilter("search","")}handleKeys(e){if(this.tabNav&&"Tab"===e.key){e.preventDefault();const t=e.target.closest("[data-field]"),s=e.target.closest("tr");if(!t||!s)return;const i=t.dataset.field,a=e.shiftKey;let l=this.findNextEditableRow(s,a);l||(l=this.wrapToRow(s,a)),l&&this.focusFieldInRow(l,i,a)}}findNextEditableRow(e,t=!1){let s=t?e.previousElementSibling:e.nextElementSibling;for(;s&&!this.isEditableRow(s);)s=t?s.previousElementSibling:s.nextElementSibling;return s}wrapToRow(e,t=!1){if(this.isTimeline){const s=e.closest("tbody");if(!s)return null;const i=Array.from(s.querySelectorAll("tr")).filter((e=>this.isEditableRow(e)));return t?i[i.length-1]:i[0]}{if(!this.ui.table.body)return null;const e=Array.from(this.ui.table.body.querySelectorAll("tr")).filter((e=>this.isEditableRow(e)));return t?e[e.length-1]:e[0]}}isEditableRow(e){return!e.closest("thead")&&!e.closest("tfoot")&&(this.isTimeline?e.classList.contains("shared")||e.classList.contains("timeline-point"):!!e.dataset.itemId)}focusFieldInRow(e,t,s=!1){const i=e.querySelector(`[data-field="${t}"]`);if(!i)return;const a=this.findFocusableInput(i);if(a){a.focus(),a.select&&"text"===a.type&&a.select();const e=s?"next":"previous";this.a11y?.announce(`Moved to ${t} in ${e} row`)}}findFocusableInput(e){const t=['input:not([type="hidden"]):not([disabled])',"textarea:not([disabled])","select:not([disabled])","button:not([disabled])"];for(const s of t){const t=e.querySelector(s);if(t)return t}return null}openEditModal(e){let t,s=this.store.get(parseInt(e));s&&(this.activeItem=s.id,this.ui.modals.edit.modal.dataset.itemId=e,this.ui.modals.edit.modal.dataset.content=this.content,Object.hasOwn(s.fields,"post_title")?t=s.fields.post_title:Object.hasOwn(s.fields,"name")&&(t=s.fields.name),this.ui.modals.edit.h2.textContent=`Editing ${""===t?this.singular:t}`,this.ui.modals.edit.form.dataset.formId=`edit-${e}`,this.isPopulating=!0,this.modals.edit.handleOpen(),this.forms.registerForm(this.ui.modals.edit.form,{cache:!1,autoUpload:!0}),this.populate.populate(this.ui.modals.edit.form,s),requestAnimationFrame((()=>{requestAnimationFrame((()=>{setTimeout((()=>{this.isPopulating=!1}),50)}))})))}openBulkEditModal(){window.removeChildren(this.ui.modals.bulkEdit.selected),this.ui.modals.edit.form.reset(),window.chunkIt(this.selected,(t=>{let s=this.store.get(parseInt(t));if(s)return e.push(s.id),window.jvbTemplates.create("bulkItem",s)}),(e=>this.ui.modals.bulkEdit.selected.append(e))).then((()=>{}));let e=Array.from(this.selected).map((e=>this.store.get(parseInt(e)))).filter(Boolean);this.ui.modals.bulkEdit.modal.dataset.itemId=e.join(","),this.ui.modals.bulkEdit.h2&&(this.ui.modals.bulkEdit.h2.textContent=this.selected.size),this.modals.bulkEdit.handleOpen(),this.forms.registerForm(this.ui.modals.bulkEdit.form,{cache:!1}),this.isPopulating=!0,this.populate.populate(this.ui.modals.edit.form,item),requestAnimationFrame((()=>{requestAnimationFrame((()=>{this.isPopulating=!1}))}))}async savePosts(e="",t=!1){this.changes.size>0&&(this.cancelBackup(),await this.handleBackup());let s=await this.changesStore.getAll();if(0===s.length)return;if(s=this.validateChanges(s),0===s.length)return;""===e&&(e=`Saving ${s.length} ${1===s.length?this.singular:this.plural}`);let i={},a=[];s.forEach((e=>{let t=e.id;const{id:s,...l}=e;i[t]=l,e.post_status&&this.shouldRemoveItemUI(e.post_status)&&a.push(t)})),a.length>0&&this.removeItems(a);let l={endpoint:this.endpoint,headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash")},data:{posts:i},delay:t,popup:"Saving changes",title:e};this.queue.addToQueue(l)}validateChanges(e){return e.reduce(((e,t)=>{const{id:s,content:i,...a}=t,l=this.store.get(s);if(!l)return e.push(t),e;const n={id:s,content:i};let o=!1;for(const[e,t]of Object.entries(a)){const s=l.fields?.[e]??l[e];null!==window.getDifferences.map(s,t)&&(n[e]=t,o=!0)}return o?e.push(n):(this.changes.delete(s),this.changesStore.delete(s)),e}),[])}setBulkStatus(e){if(!["publish","draft","trash","delete"].includes(e))return;let t,s=[];if(this.selected.forEach((t=>{s.push(t),this.updateItem(t,"post_status",e)})),"delete"===e)t="Deleting";else t=window.uppercaseFirst(e)+"ing";this.shouldRemoveItemUI(e)&&this.removeItems(s),this.selectionHandler.clearSelection(),this.savePosts(`${t} ${s.length} ${1===s.length?this.singular:this.plural}...`).then((()=>{}))}render(){const e=this.store.getFiltered();if(0!==e.length){switch(this.view){case"grid":this.renderGrid(e);break;case"table":this.renderTable(e).then((()=>{}));break;case"list":this.renderList(e)}this.updateUI()}else this.renderEmpty()}updateUI(){if(this.ui.bulk.action){let e=!1,t=this.ui.bulk.action.querySelector('[value="edit"]'),s=this.status;"trash"===s&&t?(window.removeChildren(this.ui.bulk.action),e=window.jvbTemplates.create("trashOptions")):"trash"===s||t||(window.removeChildren(this.ui.bulk.action),e=window.jvbTemplates.create("notTrashOptions")),e&&e.querySelectorAll("option").forEach(((e,t)=>{0===t&&(e.checked=!0),this.ui.bulk.action.append(e)})),this.ui.bulk.action.value=""}this.selected.size>0&&this.selectionHandler.updateSelectionUI()}renderEmpty(){this.toggleTable(!1),window.removeChildren(this.ui.grid);const e=window.jvbTemplates.create("emptyState");e&&(this.ui.grid.append(e),this.a11y.announceItems(0,!1,!1))}toggleTable(e=!0){if(this.ui.table.selectedColumns&&(this.ui.table.selectedColumns.hidden=!e),e&&!this.ui.table.form){let e=window.jvbTemplates.create("contentTable");this.container.append(e),this.ui.table=window.uiFromSelectors(this.selectors.table),this.ui.table.columns=this.container.querySelectorAll(this.selectors.table.columns)}this.ui.table.form&&(this.ui.table.form.hidden=!e,e||this.forms.clearForm(this.ui.table.form.dataset.formId),this.ui.table.body&&window.removeChildren(this.ui.table.body)),this.keyHandler=this.handleKeys.bind(this),e?document.addEventListener("keydown",this.keyHandler):document.removeEventListener("keydown",this.keyHandler)}renderGrid(e){window.removeChildren(this.ui.grid),this.toggleTable(!1),this.ui.grid.classList.remove("list-view"),this.ui.grid.classList.add("grid-view"),window.chunkIt(e,(e=>this.renderGridItem(e)),(e=>this.ui.grid.append(e))).then((()=>{}))}renderList(e){window.removeChildren(this.ui.grid),this.toggleTable(!1),this.ui.grid.classList.remove("grid-view"),this.ui.grid.classList.add("list-view"),window.chunkIt(e,(e=>this.renderListItem(e)),(e=>this.ui.grid.append(e))).then((()=>{}))}async renderTable(e){this.toggleTable(),window.removeChildren(this.ui.grid),await window.chunkIt(e,(e=>this.renderTableItem(e)),(e=>{this.ui.table.body?this.ui.table.body.append(e):this.ui.table.table.insertBefore(e,this.ui.table.foot)}),5),requestAnimationFrame((()=>{window.jvbSelector?.scanExistingFields(this.ui.table.table)}))}renderGridItem(e){let t=window.jvbTemplates.create("gridView",e);return this.items.set(e.id,t),t}renderListItem(e){let t=window.jvbTemplates.create("listView",e);return this.items.set(e.id,t),t}renderTableItem(e){let t=window.jvbTemplates.create("tableView",e);return this.items.set(e.id,t),t}toggleColumn(e,t){this.ui.table.table.querySelectorAll(`.${e}`).forEach((e=>{e.hidden=!t}))}handleGroupsUploaded(e){const{posts:t,fieldId:s}=e;let i=window.jvbUploads,a=(i.fields.get(s),[]);t.forEach((e=>{const t={id:e.groupId,title:e.fields.post_title||`New ${this.singular}`,status:"draft",date:(new Date).toISOString(),modified:(new Date).toISOString(),thumbnail:null,icon:this.content,taxonomies:{},fields:e.fields,images:{}};e.images.forEach(((e,s)=>{let a=e.upload_id;0===s&&(t.fields.post_thumbnail=e);let l=i.stores.uploads.get(a);l&&(t.images[a]={"image-alt-text":"","image-caption":"","image-title":l.fields.originalName,medium:i.createPreviewUrl(i.formatFile(l))})})),a.push(t)})),this.store.saveMany(a).then((()=>this.render())),this.a11y.announce(`${t.length} ${1===t.length?this.singular:this.plural} created. Waiting for server confirmation...`)}handleGroupMappings(e){for(const[t,s]of Object.entries(e)){let e={};this.changes.has(t)&&(e=this.changes.get(t),this.changes.delete(t));let i=this.changesStore.get(t)??{};(e.size>0||i.size>0)&&(e=window.deepMerge(i,e),this.changes.set(s,e),this.scheduleBackup())}}shouldRemoveItemUI(e){return"all"===this.status&&!["publish","draft"].includes(e)||e!==this.store.filters.status}removeItems(e){e.forEach((e=>{if(this.items.has(e)){let t=this.items.get(e);t&&window.fade(t,!1)}}))}setFilters(e){for(let[t,s]of Object.entries(e)){if(!this.allowedFilters.includes(t)){delete e[t];continue}this.cache.set(t,s);let i=this.findFilterEl(t);this.setElValue(i,s)}this.store.setFilters(e)}setFilter(e,t){if(!this.allowedFilters.includes(e))return;this.cache.set(e,t),"status"===e&&(this.status=t),"orderby"===e&&(this.orderby=t),"order"===e&&(this.order=t);let s=this.findFilterEl(e,t);this.setElValue(s,t),this.store.setFilter(e,t)}deleteFilter(e,t){if(!this.allowedFilters.includes(e))return;if(Object.hasOwn(this.defaults,e))return void this.setFilter(e,this.defaults[e]);let s=this.findFilterEl(e,t);this.setElValue(s,!1),this.cache.remove(e),this.setFilter(e,"")}setElValue(e,t){if(e){if(!t)return["SELECT","TEXTAREA"].includes(e.tagName)&&(e.value=""),["text","search"].includes(e.type)&&(e.value=""),void("radio"===e.type&&(e.checked=!1));["SELECT","TEXTAREA"].includes(e.tagName)&&(e.value=t),["text","search"].includes(e.type)&&(e.value=t),"radio"===e.type&&(e.checked=!0)}}findFilterEl(e,t){if(["date-filter","dateFrom","dateTo"].includes(e)){switch(e){case"date-filter":e="month";break;case"dateFrom":e="start";break;case"dateTo":e="end"}return this.ui.modals.date[e]}if(e.includes("tax_")){const t=e.replace("tax_",""),s=this.ui.filters.taxonomies?.[t];return s||(console.warn("Taxonomy filter element not found:",t),null)}if(!Object.hasOwn(this.ui.filters,e))return console.warn("Filter el not found: ",e),!1;let s=this.ui.filters[e];if("object"==typeof s){if(!Object.hasOwn(this.ui.filters[e],t))return!1;s=this.ui.filters[e][t]}return s}resetForm(e){e.querySelectorAll('input[type="hidden"], input[type="text"], input[type="number"], input[type="email"], input[type="url"], textarea').forEach((e=>{e.value=""})),e.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach((e=>{e.checked=!1})),e.querySelectorAll("select").forEach((e=>{e.selectedIndex=0})),e.querySelectorAll(".selected-items").forEach((e=>{window.removeChildren(e)})),e.querySelectorAll(".item-grid.preview").forEach((e=>{window.removeChildren(e)}))}destroy(){window.debouncer.cancel(`changes-${this.content}`),this.changes.size>0&&(this.changesStore.saveMany(this.changes).then((()=>{})),this.changes.clear()),this.timelineSortables&&(this.timelineSortables.forEach((e=>e.destroy())),this.timelineSortables=[]);for(let[e,t]of Object.entries(this.ui.modals))t.form&&t.form.removeEventListener("submit",this.submitHandler);document.removeEventListener("click",this.clickHandler),document.removeEventListener("change",this.changeHandler),this.ui.filters.search&&this.ui.filters.search.removeEventListener("input",this.handleInput)}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{if("auth-loaded"===t){let t=document.querySelector("[data-content]");t&&!Object.hasOwn(t.dataset,"ignore")&&(window.crudManager=new e({content:t.dataset.content}))}}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.container=document.querySelector(".crud[data-content]:not([data-ignore])"),this.container&&(this.content=this.container.dataset.content,this.endpoint=this.container.dataset.endpoint??"content",this.singular=this.container.dataset.singular,this.plural=this.container.dataset.plural,this.queue=window.jvbQueue,this.a11y=window.jvbA11y,this.error=window.jvbError,this.populate=window.jvbPopulate,this.cache=new window.jvbCache(this.content),this.activeItem=null,this.isTimeline=!1,this.isPopulating=!1,this.changes=new Map,this.items=new Map,this.init())}init(){this.initElements(),this.initListeners(),this.defineTemplates();let e=this.initSettings();this.initStore(e),this.checkHideFilters(),this.initIntegrations(),this.initUploader(),this.initModals()}defineTemplates(){const e=window.jvbTemplates,t=this,s=(e,s,i)=>{e.dataset.itemId=i.id;let a=s.checkbox.closest(".preview");window.prefixInput(s.checkbox,`select-${i.id}`,a,!0),s.checkbox.value=i.id,s.checkbox.checked=t.selected.has(parseInt(i.id)),s.selectLabel&&(s.selectLabel.htmlFor=`select-${i.id}`),s.edit&&(s.edit.dataset.id=i.id),s.trash&&(s.trash.dataset.id=i.id)},i=function(e,t,s){let i=s?.fields?.post_thumbnail||s?.fields?.thumbnail;if(i){const e=s.images[i]??{};t.img.src=e.medium??"",t.img.alt=e.alt??s.fields.post_title??""}};e.define("gridView",{refs:{img:"img",checkbox:".select-item",selectLabel:"label.select-item-label",edit:'[data-action="edit"]',trash:'[data-action="trash"]'},setup({el:e,refs:t,manyRefs:a,data:l}){s(e,t,l),i(0,t,l)}}),e.define("listView",{refs:{img:"img",checkbox:".select-item",selectLabel:"label.select-item-label",edit:'[data-action="edit"]',trash:'[data-action="trash"]'},manyRefs:{attrs:"[data-attr]",fields:"[data-field]"},setup({el:e,refs:t,manyRefs:a,data:l}){s(e,t,l),i(0,t,l),a?.attrs?.forEach(e=>{const t=l[e.dataset.attr];t&&""!==t?e.textContent=t:e.remove()}),a?.fields?.forEach(e=>{const t=l.fields?.[e.dataset.field];t&&""!==t?"DIV"===e.tagName?e.innerHTML=t:e.textContent=t:e.remove()})}});let a={};this.isTimeline&&(a.sharedRow="tr.shared",a.point="tr.timeline-point"),e.define("tableView",{refs:{checkbox:".select-item",selectLabel:"label.select-item-label",...a},manyRefs:{inputs:"input,select,textarea",status:'input[name="post_status"]',selectors:'[data-type="selector"]',fields:"[data-field]"},setup({el:e,refs:i,manyRefs:a,data:l}){if(s(e,i,l),a?.inputs?.forEach(e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)}),a?.status?.forEach(e=>{e.value===l.status&&(e.checked=!0)}),t.isTimeline)i.sharedRow&&(i.sharedRow.querySelectorAll("input,select,textarea").forEach(e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)}),t.populate.populate(i.sharedRow,l),i.sharedRow.querySelectorAll('input[name="post_status"]').forEach(e=>{e.value===l.status&&(e.checked=!0)})),i.point&&l.fields?.timeline&&(Object.entries(l.fields.timeline).forEach(([s,a],n)=>{const o=i.point.cloneNode(!0);o.dataset.index=`${n}`,o.dataset.itemId=a.id,o.querySelectorAll("input,select,textarea").forEach(e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${a.id}-`,t)}),t.populate.populate(o,{fields:a,images:l.images,taxonomies:l.taxonomies});const r=l.images?.[a.post_thumbnail];r&&o.querySelector(".field.upload")?.setAttribute("title",r["image-title"]??""),e.insertBefore(o,i.point)}),i.point.remove());else if(void 0!==t.ui.table.form?.dataset.edit)a?.inputs?.forEach(e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)}),a?.status?.forEach(e=>{e.value===l.status&&(e.checked=!0)}),t.populate.populate(e,l);else{const e=Object.hasOwn(l,"fields")?l.fields:l;a?.fields?.forEach(t=>{if(Object.hasOwn(e,t.dataset.field)&&""!==e[t.dataset.field]){let s=e[t.dataset.field],i=e.children[0];i&&(i.textContent="date"===t.dataset.field?window.formatTimeAgo(s):s)}})}a?.selectors?.forEach(e=>e.setAttribute("data-lazy",""))}}),e.define("emptyState"),e.define("bulkItem",{refs:{checkbox:"input",img:"img",label:"label"},setup({el:e,refs:t,manyRefs:s,data:i}){t.checkbox&&(t.checkbox.id=`bulk_${i.id}`,t.checkbox.value=i.id,t.checkbox.checked=!0,t.checkbox.name="selected[]");let a=i?.images[i?.fields?.post_thumnbail]??{};t.img&&Object.keys(a).length>0&&(t.img.src=a.medium??"",t.img.alt=a.alt??""),t.label&&(t.label.title=item.fields.post_title)}}),e.define("trashOptions"),e.define("notTrashOptions"),e.define("contentTable")}initElements(){this.allowedFilters=["status","orderby","order","search","date-filter","dateFrom","dateTo"],this.selectors={buttons:{create:".create-item",clearFilters:'[data-action="clear-filters"]'},views:{grid:'input[data-view="grid"]',list:'input[data-view="list"]',table:'input[data-view="table"]'},modals:{create:{modal:"dialog.create",form:"dialog.create form",h2:"dialog.create h2"},edit:{modal:"dialog.edit",form:"dialog.edit form",h2:"dialog.edit h2"},bulkEdit:{modal:"dialog.bulkEdit",selected:"dialog.bulkEdit .selected",h2:"dialog.bulkEdit h2 span",form:"dialog.bulkEdit form"},date:{modal:"dialog.date-range",start:"dialog.date-range .date-start",end:"dialog.date-range .date-end",month:"dialog.date-range .month-select"}},grid:`.${this.content}.item-grid`,table:{nav:"#vertical",form:"form.table",table:"form.table table",body:"form.table body",head:"form.table thead",foot:"form.table tfoot",selectedColumns:".all-filters .multi-select",columns:"thead th"},bulk:{action:".bulk-action-select",count:".bulk-controls .selected-count",control:".bulk-controls .bulk-actions",select:".bulk-controls select",selectAll:".select-all"},filters:{container:"details.all-filters",search:'.all-filters input[type="search"]',status:{all:'[name="status"]#all',publish:'[name="status"]#publish',draft:'[name="status"]#draft',trash:'[name="status"]#trash'},orderby:{date:'[name="orderby"]#date',alphabetical:'[name="orderby"]#alphabetical'},order:{asc:'[name="order"][value="asc"]',desc:'[name="order"][value="desc"]'},date:'[data-filter="date"]'},uploader:{details:"details.uploader",form:"details.uploader form",uploader:'details.uploader [data-field-type="upload"]'}},this.ui=window.uiFromSelectors(this.selectors);const e=document.querySelectorAll('[data-filter="taxonomies"]');e.length>0&&(this.ui.filters.taxonomies={},e.forEach(e=>{const t=e.dataset.taxonomy;this.ui.filters.taxonomies[t]=e,this.allowedFilters.push(`tax_${t}`)})),this.isTimeline=!!document.querySelector("[data-timeline]")}initUploader(){this.ui.uploader.form&&(this.uploadForm=this.forms.registerForm(this.ui.uploader.form).id??!1,window.jvbUploads.subscribe((e,t)=>{if("sent-to-queue"===e&&t.field.id===this.ui.uploader.uploader.dataset.uploader&&(this.uploadForm&&this.forms.store.delete(this.uploadForm),window.debouncer.schedule("crud-complete",()=>{this.store.clearCache()})),"sent-to-queue"===e&&t.field){const e=t.field.config.name,s=t.field.config.itemID;s&&e&&this.changes.has(s)&&delete this.changes.get(s)[e]}}))}initModals(){this.modals={};for(let[e,t]of Object.entries(this.ui.modals))t.modal&&(this.modals[e]=new window.jvbModal(t.modal),this.modals[e].subscribe((t,s)=>{if("modal-close"===t){const t=this.ui.modals[e].form.dataset.formId;t&&this.forms.clearForm(t),this.resetForm(this.ui.modals[e].form),"date"===e&&this.handleCustomDateSelection(),["edit","bulkEdit","create"].includes(e)&&window.debouncer.timeouts.has(`save-${this.content}`)&&this.scheduleSave(0)}}))}initStore(e){let t={...this.defaults,...e};const s=window.jvbStore.register(this.content,[{storeName:this.content,keyPath:"id",endpoint:this.endpoint??"content",headers:{"X-Action-Nonce":window.auth.getNonce("dash")},indexes:[{name:"id",keyPath:"id"},{name:"status",keyPath:"status"},{name:"date",keyPath:"date"},{name:"modified",keyPath:"modified"},{name:"title",keyPath:"title"}],isAuth:!0,filters:t,ignore:["content","user"],TTL:36e5,showLoading:!0},{storeName:"changes",keyPath:"id"}]);this.changesStore=s.changes,this.store=s[this.content],this.store.subscribe((e,t)=>{if("data-loaded"===e)this.render(),this.selectionHandler.collectItems()}),this.changesStore.subscribe((e,t)=>{if("data-ready"===e){let e=this.changesStore.getAll();e.length>0&&(e.forEach(e=>{this.changes.set(e.id,e)}),this.savePosts("",!1).then(()=>{}))}})}initIntegrations(){this.selected=new Set,this.selectionHandler=new window.jvbHandleSelection(this.container,{selectAll:{checkbox:"#select-all",label:".bulk-select label",span:".bulk-select label span"},wrapper:{wrapper:".wrap"},item:{idAttribute:"itemId"}}),this.selectionHandler.subscribe((e,t)=>{this.selected=new Set([...t.selectedItems].map(e=>parseInt(e))),this.ui.bulk.control.hidden=0===this.selected.size,this.ui.bulk.count.hidden=0===this.selected.size,this.ui.bulk.count.textContent=`${this.selected.size} ${this.plural} selected`}),this.forms=window.jvbForm,window.jvbUploads&&window.jvbUploads.subscribe((e,t)=>{"groups_uploaded"===e&&t.content===this.content&&this.handleGroupsUploaded(t)}),this.queue.subscribe((e,t)=>{if(["image_upload","video_upload","document_upload"].includes(t.type)&&"operation-status"===e&&"completed"===t.status&&this.store.clearCache(),"operation-status"===e&&"completed"===t.status&&"uploads/groups"===t.endpoint&&(t.result&&t.result.group_mappings&&(console.log("Handling group mapping from queue response"),this.handleGroupMappings(t.result.group_mappings)),this.store.clearCache()),"operation-status"===e&&"completed"===t.status&&"content_update"===t.type){if(this.store.clearCache(),!t.result||!t.result.success||!t.result.errors)return void console.warn("Content update completed but no results",t);if(Object.keys(t.result.success).length>0&&this.checkCompletedChanges(Object.entries(t.result.success)),Object.keys(t.result.errors).length>0)return void this.checkFailedChanges(Object.entries(t.result.errors));0===Object.keys(t.result.success).length&&(console.log(t.result.success),t.result.success.forEach(e=>this.changesStore.delete(e)),this.store.clearCache())}if("sent-to-server"===e&&"content_update"===t.type){if(t instanceof FormData)return;for(let[e,s]of Object.entries(t.posts))this.compareStored(e,s)}})}checkCompletedChanges(e){for(let[t,s]of e)this.compareStored(t,s)}compareStored(e,t){let s=this.changesStore.get(e);if(!s)return;for(let[e,i]of Object.entries(t))if(Object.hasOwn(s,e)){let t=window.getDifferences.map(s[e],i);t?s[e]=t:delete s[e]}let i=Object.hasOwn(s,"id"),a=Object.hasOwn(s,"content");i&&a&&2===Object.keys(s).length||(i||a)&&1===Object.keys(s).length||0===Object.keys(s).length?(this.changesStore.delete(e),this.store.clearCache()):this.changesStore.save(s)}checkFailedChanges(e){}initSettings(){this.defaults={content:this.content,user:window.auth.getUser(),page:1,status:"all",orderby:"date",order:"desc",search:""};let e={},t=this.container.dataset.view??"grid";this.view=this.cache.get("view")??t,this.view!==t&&(this.ui.views[this.view].checked=!0),this.status=this.cache.get("status")??this.defaults.status,this.status!==this.defaults.status&&(this.ui.filters.status[this.status].checked=!0,e.status=this.status),this.orderby=this.cache.get("orderby")??this.defaults.orderby,this.orderby!==this.defaults.orderby&&(this.ui.filters.orderby[this.orderby].checked=!0,e.orderBy=this.orderby),this.order=this.cache.get("order")??this.defaults.order,this.order!==this.defaults.order&&(this.ui.filters.order[this.order].checked=!0,e.order=this.order),this.ui.filters.taxonomies&&Object.entries(this.ui.filters.taxonomies).forEach(([t,s])=>{const i=`tax_${t}`,a=this.cache.get(i);a&&(s.value=a,e[i]=a)});let s=this.cache.get("tabNav")??"horizontal";this.ui.table.nav&&"vertical"===s&&(this.ui.table.nav.checked=!0);let i={showFilters:{element:this.ui.filters.container,default:"closed"},showUploader:{element:this.ui.uploader.details,default:"open"}};for(let[e,t]of Object.entries(i))if(t.element){let s=this.cache.get(e)??t.default;t.element.open="open"===s,t.element.addEventListener("toggle",()=>{this.cache.set(e,t.element.open?"open":"closed")})}return e}initListeners(){this.changeHandler=this.handleChange.bind(this),this.clickHandler=this.handleClick.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleModalSubmit.bind(this),document.addEventListener("change",this.changeHandler),document.addEventListener("click",this.clickHandler),this.ui.filters.search&&this.ui.filters.search.addEventListener("input",this.inputHandler);for(let[e,t]of Object.entries(this.ui.modals))t.form&&t.form.addEventListener("submit",this.submitHandler)}handleModalSubmit(e){e.preventDefault();const t=e.target.closest("dialog");if(!t)return;if(t.classList.contains("create"))return void this.handleCreateSubmit(t);this.plural;this.scheduleSave(0),this.modals.edit.handleClose()}async handleCreateSubmit(e){e.dataset.itemId;this.changes.size>0&&(this.cancelBackup(),await this.handleBackup());const t=await this.changesStore.getAll();if(0===t.length)return;let s={};t.forEach(e=>{const{id:t,...i}=e;s[t]=i});let i=this.queue.addToQueue({endpoint:this.endpoint,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},data:{posts:s},popup:`Creating your new ${this.singular}`,title:`Creating your new ${this.singular}`});if(!i)return;const a=e.querySelectorAll("[data-upload-field]");for(const e of a){const t=e.dataset.uploader;if(!t)continue;0!==window.jvbUploads.stores.uploads.filterByIndex({field:t}).length&&await window.jvbUploads.queueUploads("uploads",t,i)}}handleChange(e){const t=e.target.closest("[data-item-id]"),s=e.target.matches("[data-filter]"),i=e.target.matches(".bulk-action-select"),a=e.target.matches("[data-view]");if(t||s||i||a)if(this.isPopulating||!t||e.target.closest("[data-ignore], .select-item")){if(a)return this.items.clear(),void this.handleViewChange(e.target);if(i)this.handleBulkAction(e.target);else if(s)this.handleFilterChange(e.target);else if("table"===this.view){if(e.target.matches("details.multi-select"))return void this.toggleColumn(e.target.id,e.target.checked);e.target.matches(this.selectors.table.nav)&&(this.tabNav=e.target.checked,this.cache.set("tabNav",e.target.checked?"vertical":"horizontal"))}}else this.handleItemUpdate(e)}handleBulkAction(e){if(e.value.startsWith("tax-")){const t=e.options[e.selectedIndex],s=t.dataset.taxonomy,i=t.dataset.single,a=t.dataset.plural;return window.jvbSelector.openEmpty(s,i,a,e=>this.handleBulkTaxonomy(e)),void(e.value="")}switch(e.value){case"edit":this.openBulkEditModal();break;case"publish":case"trash":case"delete":this.setBulkStatus(e.value);break;case"draft":case"restore":this.setBulkStatus("draft")}}handleBulkTaxonomy(e){e.termIds.length&&this.selected.size&&(this.selected.forEach(t=>{const s=this.store.get(t);if(!s)return;const i=(s.taxonomies?.[e.taxonomy]||[]).map(e=>e.id),a=[...new Set([...i,...e.termIds])];this.updateItem(t,e.taxonomy,a)}),this.savePosts(`Adding ${e.terms.length} ${e.taxonomy} to ${this.selected.size} ${this.plural}...`).then(()=>{}),this.selectionHandler.clearSelection())}handleItemUpdate(e){let t=window.targetCheck(e,"[data-item-id]");if(!t)return;const s=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');let i,a;if(s)i=s.dataset.field,a=this.forms.getFieldValue(s);else{let t=e.target.closest("[data-field]");i=t.dataset.field,a=this.forms.getFieldValue(e.target)}t.dataset.itemId.split(",").forEach(e=>{this.updateItem(e,i,a)})}updateItem(e,t,s){if(this.isPopulating)return;t.replace(`[${e}]`,"");const i=this.store.get(e);if(i){const a=i.fields?.[t]??i[t];if(null===window.getDifferences.map(a,s)){if(this.changes.has(e)){delete this.changes.get(e)[t];0===Object.keys(this.changes.get(e)).filter(e=>"id"!==e&&"content"!==e).length&&(this.changes.delete(e),this.changesStore.delete(e))}return}}this.changes.has(e)||this.changes.set(e,{id:e,content:this.content}),this.changes.get(e)[t]=s,this.scheduleBackup(),"number"!=typeof e&&String(e).includes("group")||this.scheduleSave()}scheduleBackup(){window.debouncer.schedule(`changes-${this.content}`,async()=>{this.changes.size>0&&await this.handleBackup()},2e3)}cancelBackup(){window.debouncer.cancel(`changes-${this.content}`)}async handleBackup(){const e=Array.from(this.changes.values());this.changes.clear();const t=e.map(e=>e.id),s=await Promise.all(t.map(e=>this.changesStore.get(e))),i=e.map((e,t)=>s[t]?window.deepMerge(s[t],e):e);await this.changesStore.saveMany(i)}scheduleSave(e=1e4){window.debouncer.schedule(`save-${this.content}`,async()=>{this.changes.size>0&&(this.cancelBackup(),await this.handleBackup()),await this.savePosts("",!1)},e)}handleFilterChange(e){let t=e.dataset.filter;return"date"===t&&"custom"===e.value?(e.value="",void this.modals.date.handleOpen()):"date"===t&&""!==e.value?(this.setFilter("date-filter",e.value),this.deleteFilter("dateFrom"),this.deleteFilter("dateTo"),void this.checkHideFilters()):("taxonomies"===t&&(t=`tax_${e.dataset.taxonomy}`),void this.setFilter(t,e.value))}checkHideFilters(){const e=this.store.filters,t=Object.entries(e).some(([e,t])=>!["content","user","page"].includes(e)&&(this.defaults[e]!==t&&""!==t&&null!==t));this.ui.buttons.clearFilters.hidden=!t}clearAllFilters(){let e=this.store.filters;this.store.clearFilters();for(let[t,s]of Object.entries(e))this.cache.remove(t),this.deleteFilter(t,s);this.a11y.announce("All filters cleared")}handleCustomDateSelection(){if(this.ui.modals.date.month&&this.ui.modals.date.month.value){const[e,t]=this.ui.modals.date.month.value.split("-"),s=`${e}-${t}-01`,i=new Date(e,parseInt(t),0).getDate(),a=`${e}-${t}-${String(i).padStart(2,"0")}`;this.setFilter("dateFrom",s),this.setFilter("dateTo",a),this.deleteFilter("date-filter"),this.ui.modals.date.month.value=""}else this.ui.modals.date.start&&this.ui.modals.date.start.value&&this.ui.modals.date.end&&this.ui.modals.date.end.value&&(this.setFilter("dateFrom",this.ui.modals.date.start.value),this.setFilter("dateTo",this.ui.modals.date.end.value),this.deleteFilter("date-filter"),this.ui.modals.date.start.value="",this.ui.modals.date.end.value="");this.checkHideFilters()}handleViewChange(e){this.view=e.dataset.view,this.cache.set("view",this.view),this.render()}handleClick(e){if(e.target.matches(".clear-search"))return void this.deleteFilter("search","");const t=e.target.closest("[data-action]");return t?(e.preventDefault(),void this.handleActionButton(t)):e.target.matches(".apply-date-filter")?(this.handleCustomDateSelection(),void this.modals.date.handleClose()):void((e.target.matches(this.selectors.buttons.create)||e.target.closest(this.selectors.buttons.create))&&this.openCreateModal())}openCreateModal(){this.forms.registerForm(this.ui.modals.create.form,{cache:!1}),this.ui.modals.create.modal.dataset.itemId=window.generateID("new"),this.modals.create.handleOpen()}handleActionButton(e){const t=e.dataset.id;switch(e.dataset.action){case"edit":this.openEditModal(t);break;case"delete":confirm("Delete this item? This cannot be undone")&&(this.updateItem(t,"post_status","delete"),window.fade(e.closest(".item"),!1),this.savePosts(`Permanently deleting ${this.singular}...`).then(()=>{}),this.store.delete(t));break;case"trash":"trash"===this.status?confirm("Delete this item? This cannot be undone")&&(this.updateItem(t,"post_status","delete"),window.fade(e.closest(".item"),!1),this.savePosts(`Permanently deleting ${this.singular}...`).then(()=>{}),this.store.delete(t)):(this.updateItem(t,"post_status","trash"),window.fade(e.closest(".item"),!1),this.savePosts(`Sending ${this.singular} to trash...`).then(()=>{}));break;case"bulk-edit":this.selected.size>0&&this.openBulkEditModal();break;case"bulk-delete":this.handleBulkDelete();break;case"refresh":this.store.clearCache(),this.store.fetch();break;case"clear-filters":this.clearAllFilters()}}handleBulkDelete(){let e="trash"===this.status;if(this.selected.size>0&&confirm(`${e?"Permanently delete":"Send"} ${this.selected.size} ${1===this.selected.size?this.singular:this.plural}${e?"":"to trash"}?`)){this.selected.forEach(t=>{this.store.delete(t),this.updateItem(t,"post_status",e?"delete":"trash")});let t=e?`Permanently deleting ${this.selected.size} ${1===this.selected.size?this.singular:this.plural}`:`Sending ${this.selected.size} ${1===this.selected.size?this.singular:this.plural} to trash`;this.savePosts(t).then(()=>{}),this.selectionHandler.clearSelection()}}handleInput(e){e.preventDefault(),e.stopPropagation();let t=e.target.value.trim(),s=`${this.content}-search`;0!==t.length?window.debouncer.schedule(s,()=>{this.a11y.announce(`Searching for "${t}"...`),this.store.setFilters({search:t,page:1})},300):this.deleteFilter("search","")}handleKeys(e){if(this.tabNav&&"Tab"===e.key){e.preventDefault();const t=e.target.closest("[data-field]"),s=e.target.closest("tr");if(!t||!s)return;const i=t.dataset.field,a=e.shiftKey;let l=this.findNextEditableRow(s,a);l||(l=this.wrapToRow(s,a)),l&&this.focusFieldInRow(l,i,a)}}findNextEditableRow(e,t=!1){let s=t?e.previousElementSibling:e.nextElementSibling;for(;s&&!this.isEditableRow(s);)s=t?s.previousElementSibling:s.nextElementSibling;return s}wrapToRow(e,t=!1){if(this.isTimeline){const s=e.closest("tbody");if(!s)return null;const i=Array.from(s.querySelectorAll("tr")).filter(e=>this.isEditableRow(e));return t?i[i.length-1]:i[0]}{if(!this.ui.table.body)return null;const e=Array.from(this.ui.table.body.querySelectorAll("tr")).filter(e=>this.isEditableRow(e));return t?e[e.length-1]:e[0]}}isEditableRow(e){return!e.closest("thead")&&!e.closest("tfoot")&&(this.isTimeline?e.classList.contains("shared")||e.classList.contains("timeline-point"):!!e.dataset.itemId)}focusFieldInRow(e,t,s=!1){const i=e.querySelector(`[data-field="${t}"]`);if(!i)return;const a=this.findFocusableInput(i);if(a){a.focus(),a.select&&"text"===a.type&&a.select();const e=s?"next":"previous";this.a11y?.announce(`Moved to ${t} in ${e} row`)}}findFocusableInput(e){const t=['input:not([type="hidden"]):not([disabled])',"textarea:not([disabled])","select:not([disabled])","button:not([disabled])"];for(const s of t){const t=e.querySelector(s);if(t)return t}return null}openEditModal(e){let t,s=this.store.get(parseInt(e));s&&(this.activeItem=s.id,this.ui.modals.edit.modal.dataset.itemId=e,this.ui.modals.edit.modal.dataset.content=this.content,Object.hasOwn(s.fields,"post_title")?t=s.fields.post_title:Object.hasOwn(s.fields,"name")&&(t=s.fields.name),this.ui.modals.edit.h2.textContent=`Editing ${""===t?this.singular:t}`,this.ui.modals.edit.form.dataset.formId=`edit-${e}`,this.isPopulating=!0,this.modals.edit.handleOpen(),this.forms.registerForm(this.ui.modals.edit.form,{cache:!1,autoUpload:!0}),this.populate.populate(this.ui.modals.edit.form,s),requestAnimationFrame(()=>{requestAnimationFrame(()=>{setTimeout(()=>{this.isPopulating=!1},50)})}))}openBulkEditModal(){window.removeChildren(this.ui.modals.bulkEdit.selected),this.ui.modals.edit.form.reset(),window.chunkIt(this.selected,t=>{let s=this.store.get(parseInt(t));if(s)return e.push(s.id),window.jvbTemplates.create("bulkItem",s)},e=>this.ui.modals.bulkEdit.selected.append(e)).then(()=>{});let e=Array.from(this.selected).map(e=>this.store.get(parseInt(e))).filter(Boolean);this.ui.modals.bulkEdit.modal.dataset.itemId=e.join(","),this.ui.modals.bulkEdit.h2&&(this.ui.modals.bulkEdit.h2.textContent=this.selected.size),this.modals.bulkEdit.handleOpen(),this.forms.registerForm(this.ui.modals.bulkEdit.form,{cache:!1}),this.isPopulating=!0,this.populate.populate(this.ui.modals.edit.form,item),requestAnimationFrame(()=>{requestAnimationFrame(()=>{this.isPopulating=!1})})}async savePosts(e="",t=!1){this.changes.size>0&&(this.cancelBackup(),await this.handleBackup());let s=await this.changesStore.getAll();if(0===s.length)return;if(s=this.validateChanges(s),0===s.length)return;""===e&&(e=`Saving ${s.length} ${1===s.length?this.singular:this.plural}`);let i={},a=[];s.forEach(e=>{let t=e.id;const{id:s,...l}=e;i[t]=l,e.post_status&&this.shouldRemoveItemUI(e.post_status)&&a.push(t)}),a.length>0&&this.removeItems(a);let l={endpoint:this.endpoint,headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash")},data:{posts:i},delay:t,popup:"Saving changes",title:e};this.queue.addToQueue(l)}validateChanges(e){return e.reduce((e,t)=>{const{id:s,content:i,...a}=t,l=this.store.get(s);if(!l)return e.push(t),e;const n={id:s,content:i};let o=!1;for(const[e,t]of Object.entries(a)){const s=l.fields?.[e]??l[e];null!==window.getDifferences.map(s,t)&&(n[e]=t,o=!0)}return o?e.push(n):(this.changes.delete(s),this.changesStore.delete(s)),e},[])}setBulkStatus(e){if(!["publish","draft","trash","delete"].includes(e))return;let t,s=[];if(this.selected.forEach(t=>{s.push(t),this.updateItem(t,"post_status",e)}),"delete"===e)t="Deleting";else t=window.uppercaseFirst(e)+"ing";this.shouldRemoveItemUI(e)&&this.removeItems(s),this.selectionHandler.clearSelection(),this.savePosts(`${t} ${s.length} ${1===s.length?this.singular:this.plural}...`).then(()=>{})}render(){const e=this.store.getFiltered();if(0!==e.length){switch(this.view){case"grid":this.renderGrid(e);break;case"table":this.renderTable(e).then(()=>{});break;case"list":this.renderList(e)}this.updateUI()}else this.renderEmpty()}updateUI(){if(this.ui.bulk.action){let e=!1,t=this.ui.bulk.action.querySelector('[value="edit"]'),s=this.status;"trash"===s&&t?(window.removeChildren(this.ui.bulk.action),e=window.jvbTemplates.create("trashOptions")):"trash"===s||t||(window.removeChildren(this.ui.bulk.action),e=window.jvbTemplates.create("notTrashOptions")),e&&e.querySelectorAll("option").forEach((e,t)=>{0===t&&(e.checked=!0),this.ui.bulk.action.append(e)}),this.ui.bulk.action.value=""}this.selected.size>0&&this.selectionHandler.updateSelectionUI()}renderEmpty(){this.toggleTable(!1),window.removeChildren(this.ui.grid);const e=window.jvbTemplates.create("emptyState");e&&(this.ui.grid.append(e),this.a11y.announceItems(0,!1,!1))}toggleTable(e=!0){if(this.ui.table.selectedColumns&&(this.ui.table.selectedColumns.hidden=!e),e&&!this.ui.table.form){let e=window.jvbTemplates.create("contentTable");this.container.append(e),this.ui.table=window.uiFromSelectors(this.selectors.table),this.ui.table.columns=this.container.querySelectorAll(this.selectors.table.columns)}this.ui.table.form&&(this.ui.table.form.hidden=!e,e||this.forms.clearForm(this.ui.table.form.dataset.formId),this.ui.table.body&&window.removeChildren(this.ui.table.body)),this.keyHandler=this.handleKeys.bind(this),e?document.addEventListener("keydown",this.keyHandler):document.removeEventListener("keydown",this.keyHandler)}renderGrid(e){window.removeChildren(this.ui.grid),this.toggleTable(!1),this.ui.grid.classList.remove("list-view"),this.ui.grid.classList.add("grid-view"),window.chunkIt(e,e=>this.renderGridItem(e),e=>this.ui.grid.append(e)).then(()=>{})}renderList(e){window.removeChildren(this.ui.grid),this.toggleTable(!1),this.ui.grid.classList.remove("grid-view"),this.ui.grid.classList.add("list-view"),window.chunkIt(e,e=>this.renderListItem(e),e=>this.ui.grid.append(e)).then(()=>{})}async renderTable(e){this.toggleTable(),window.removeChildren(this.ui.grid),await window.chunkIt(e,e=>this.renderTableItem(e),e=>{this.ui.table.body?this.ui.table.body.append(e):this.ui.table.table.insertBefore(e,this.ui.table.foot)},5),requestAnimationFrame(()=>{window.jvbSelector?.scanExistingFields(this.ui.table.table)})}renderGridItem(e){let t=window.jvbTemplates.create("gridView",e);return this.items.set(e.id,t),t}renderListItem(e){let t=window.jvbTemplates.create("listView",e);return this.items.set(e.id,t),t}renderTableItem(e){let t=window.jvbTemplates.create("tableView",e);return this.items.set(e.id,t),t}toggleColumn(e,t){this.ui.table.table.querySelectorAll(`.${e}`).forEach(e=>{e.hidden=!t})}handleGroupsUploaded(e){const{posts:t,fieldId:s}=e;let i=window.jvbUploads,a=(i.fields.get(s),[]);t.forEach(e=>{const t={id:e.groupId,title:e.fields.post_title||`New ${this.singular}`,status:"draft",date:(new Date).toISOString(),modified:(new Date).toISOString(),thumbnail:null,icon:this.content,taxonomies:{},fields:e.fields,images:{}};e.images.forEach((e,s)=>{let a=e.upload_id;0===s&&(t.fields.post_thumbnail=e);let l=i.stores.uploads.get(a);l&&(t.images[a]={"image-alt-text":"","image-caption":"","image-title":l.fields.originalName,medium:i.createPreviewUrl(i.formatFile(l))})}),a.push(t)}),this.store.saveMany(a).then(()=>this.render()),this.a11y.announce(`${t.length} ${1===t.length?this.singular:this.plural} created. Waiting for server confirmation...`)}handleGroupMappings(e){for(const[t,s]of Object.entries(e)){let e={};this.changes.has(t)&&(e=this.changes.get(t),this.changes.delete(t));let i=this.changesStore.get(t)??{};(e.size>0||i.size>0)&&(e=window.deepMerge(i,e),this.changes.set(s,e),this.scheduleBackup())}}shouldRemoveItemUI(e){return"all"===this.status&&!["publish","draft"].includes(e)||e!==this.store.filters.status}removeItems(e){e.forEach(e=>{if(this.items.has(e)){let t=this.items.get(e);t&&window.fade(t,!1)}})}setFilters(e){for(let[t,s]of Object.entries(e)){if(!this.allowedFilters.includes(t)){delete e[t];continue}this.cache.set(t,s);let i=this.findFilterEl(t);this.setElValue(i,s)}this.store.setFilters(e)}setFilter(e,t){if(!this.allowedFilters.includes(e))return;this.cache.set(e,t),"status"===e&&(this.status=t),"orderby"===e&&(this.orderby=t),"order"===e&&(this.order=t);let s=this.findFilterEl(e,t);this.setElValue(s,t),this.store.setFilter(e,t)}deleteFilter(e,t){if(!this.allowedFilters.includes(e))return;if(Object.hasOwn(this.defaults,e))return void this.setFilter(e,this.defaults[e]);let s=this.findFilterEl(e,t);this.setElValue(s,!1),this.cache.remove(e),this.setFilter(e,"")}setElValue(e,t){if(e){if(!t)return["SELECT","TEXTAREA"].includes(e.tagName)&&(e.value=""),["text","search"].includes(e.type)&&(e.value=""),void("radio"===e.type&&(e.checked=!1));["SELECT","TEXTAREA"].includes(e.tagName)&&(e.value=t),["text","search"].includes(e.type)&&(e.value=t),"radio"===e.type&&(e.checked=!0)}}findFilterEl(e,t){if(["date-filter","dateFrom","dateTo"].includes(e)){switch(e){case"date-filter":e="month";break;case"dateFrom":e="start";break;case"dateTo":e="end"}return this.ui.modals.date[e]}if(e.includes("tax_")){const t=e.replace("tax_",""),s=this.ui.filters.taxonomies?.[t];return s||(console.warn("Taxonomy filter element not found:",t),null)}if(!Object.hasOwn(this.ui.filters,e))return console.warn("Filter el not found: ",e),!1;let s=this.ui.filters[e];if("object"==typeof s){if(!Object.hasOwn(this.ui.filters[e],t))return!1;s=this.ui.filters[e][t]}return s}resetForm(e){e.querySelectorAll('input[type="hidden"], input[type="text"], input[type="number"], input[type="email"], input[type="url"], textarea').forEach(e=>{e.value=""}),e.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(e=>{e.checked=!1}),e.querySelectorAll("select").forEach(e=>{e.selectedIndex=0}),e.querySelectorAll(".selected-items").forEach(e=>{window.removeChildren(e)}),e.querySelectorAll(".item-grid.preview").forEach(e=>{window.removeChildren(e)})}destroy(){window.debouncer.cancel(`changes-${this.content}`),this.changes.size>0&&(this.changesStore.saveMany(this.changes).then(()=>{}),this.changes.clear()),this.timelineSortables&&(this.timelineSortables.forEach(e=>e.destroy()),this.timelineSortables=[]);for(let[e,t]of Object.entries(this.ui.modals))t.form&&t.form.removeEventListener("submit",this.submitHandler);document.removeEventListener("click",this.clickHandler),document.removeEventListener("change",this.changeHandler),this.ui.filters.search&&this.ui.filters.search.removeEventListener("input",this.handleInput)}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{if("auth-loaded"===t){let t=document.querySelector("[data-content]");t&&!Object.hasOwn(t.dataset,"ignore")&&(window.crudManager=new e({content:t.dataset.content}))}})})})();
\ No newline at end of file
diff --git a/assets/js/min/dataStore.min.js b/assets/js/min/dataStore.min.js
index b02cb94..073b3a6 100644
--- a/assets/js/min/dataStore.min.js
+++ b/assets/js/min/dataStore.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){if(e.instance)return e.instance;e.instance=this,this.dbConfig=new Map,this.databases=new Map,this.stores=new Map,this.subscribers=new Map,this.pendingInits=new Map,this.fetchQueue=[],this._initialized=!1,this.body=document.body,this.loading=document.querySelector("dialog.loading"),this.init()}async init(){this._initialized||(this._initialized=!0,"indexedDB"in window||console.warn("IndexedDB not supported"))}register(e,t=[],s=1.25){if(Array.isArray(t)||(t=[t]),0===t.length)return;this.dbConfig.has(e)||this.dbConfig.set(e,{dbName:`${jvbBase.base}${e}`,version:s,stores:{},_initialized:!1});let r=this.dbConfig.get(e);t.forEach((t=>{if(!t.storeName)throw new Error(`Store config for "${e}" missing storeName`);if(!t.keyPath)throw new Error(`Store "${t.storeName}" requires keyPath`);const s=`${e}_${t.storeName}`,i={config:{dbName:r.dbName,storeName:"items",keyPath:"id",indexes:[],endpoint:null,apiBase:jvbSettings.api,filters:{},ignore:[],required:null,isAuth:!1,TTL:36e5,useHttpCaching:!0,showLoading:!1,delayFetch:!0,validateData:!0,...t},dbKey:e,storeKey:s,data:new Map,cache:new Map,filters:{...t.filters||{}},isFetching:!1,currentRequest:null,lastResponse:null,_initialized:!1};i.ignoreFilters=new Set(["search","page","per_page","orderby","order","context","source",...i.config.ignore]),i.config.headers={"X-WP-Nonce":window.auth.getNonce(),...i.config.headers},r.stores[t.storeName]=s,this.stores.set(s,i),this.subscribers.has(s)||this.subscribers.set(s,new Set)})),this.initDB(e).catch((t=>{console.error(`Failed to initialize store "${e}":`,t)}));const i={};for(const[e,t]of Object.entries(r.stores))i[e]=this.getStoreAPI(t);return i}getStoreAPI(e){const t={fetch:()=>this.fetch(e),save:t=>this.save(e,t),saveMany:t=>this.saveMany(e,t),delete:t=>this.delete(e,t),deleteMany:t=>this.deleteMany(e,t),get:t=>this.get(e,t),getMany:t=>this.getMany(e,t),getAll:()=>this.getAll(e),getAllByIndex:(t,s)=>this.getAllByIndex(e,t,s),filterByIndex:t=>this.filterByIndex(e,t),getFiltered:()=>this.getFiltered(e),clear:()=>this.clear(e),setFilter:(t,s)=>this.setFilter(e,t,s),setFilters:t=>this.setFilters(e,t),removeFilter:t=>this.removeFilter(e,t),clearFilters:()=>this.clearFilters(e),clearCache:()=>this.clearCache(e),subscribe:t=>this.subscribe(e,t),ensureInitialized:()=>this.ensureStoreInitialized(e),get filters(){return{...t.getStore().filters}},get lastResponse(){return t.getStore().lastResponse},get data(){return t.getStore().data},getStore:()=>this.stores.get(e)};return t}formDataToObject(e){const t={_isFormData:!0,entries:{}};for(const[s,r]of e.entries())r instanceof File||r instanceof Blob||(t.entries[s]?(Array.isArray(t.entries[s])||(t.entries[s]=[t.entries[s]]),t.entries[s].push(r)):t.entries[s]=r);return t}async objectToFormData(e){if(!e._isFormData)return e;const t=new FormData;for(const[s,r]of Object.entries(e.entries))Array.isArray(r)?r.forEach((e=>t.append(s,e))):t.append(s,r);if(window.jvbUploads&&e.entries.upload_ids){const s=JSON.parse(e.entries.upload_ids);for(const e of s){const s=await window.jvbUploads.getBlobData(e);s&&t.append("files[]",s)}}return t}async initDB(e){const t=this.dbConfig.get(e);if(!t||t._initialized)return;if(this.pendingInits.has(e))return this.pendingInits.get(e);const s=this._performDBInit(e);this.pendingInits.set(e,s);try{await s,t._initialized=!0}finally{this.pendingInits.delete(e)}}async _performDBInit(e){const t=this.dbConfig.get(e),{dbName:s,version:r}=t,i=Object.values(t.stores);try{if(!this.databases.has(s)){const e=await this.openDatabase(s,r,(e=>{i.forEach((t=>{let s=this.stores.get(t);s&&this.setupStores(e,s.config)}))}));this.databases.set(s,e)}i.forEach((e=>{let t=this.stores.get(e);t&&(t.db=this.databases.get(s),t._initialized=!0,this.loadStoreDataInBackground(e),this.notify(e,"db-init"))}))}catch(t){throw console.error(`Failed to initialize database for store "${e}":`,t),t}}openDatabase(e,t,s){return new Promise(((r,i)=>{const a=indexedDB.open(e,t);a.onupgradeneeded=e=>{s&&s(e.target.result,e.oldVersion,e.newVersion)},a.onsuccess=e=>r(e.target.result),a.onerror=e=>i(e.target.error),a.onblocked=()=>{console.warn(`Database ${e} blocked. Close other tabs.`)}}))}setupStores(e,t){if(!e.objectStoreNames.contains(t.storeName)){const s=e.createObjectStore(t.storeName,{keyPath:t.keyPath});t.indexes.forEach((e=>{s.createIndex(e.name,e.keyPath||e.name,{unique:e.unique||!1})}))}if(t.endpoint&&!e.objectStoreNames.contains("cache")){e.createObjectStore("cache",{keyPath:"key"}).createIndex("timestamp","timestamp",{unique:!1})}}async loadFromObjectStore(e,t,s){const r=this.stores.get(e);return r?.db&&r.db.objectStoreNames.contains(t)?new Promise((e=>{const i=r.db.transaction([t],"readonly").objectStore(t).getAll();i.onsuccess=t=>{const r=t.target.result||[];r.forEach(s),e(r)},i.onerror=()=>e([])})):[]}loadStoreDataInBackground(e){const t=this.stores.get(e);t?.db&&Promise.all([this.loadFromObjectStore(e,t.config.storeName,(e=>{const s=this.getItemKey(e,t.config.keyPath);t.data.set(s,e)})),this.loadFromObjectStore(e,"cache",(e=>{this.isCacheValid(e,t.config.TTL)&&t.cache.set(e.key,e)}))]).then((()=>{this.notify(e,"data-ready"),t.config.endpoint&&t.config.delayFetch?(this.fetchQueue.push(e),1===this.fetchQueue.length&&this.processFetchQueue()):t.config.endpoint&&!t.config.delayFetch&&("requestIdleCallback"in window?requestIdleCallback((()=>this.fetch(e)),{timeout:2e3}):setTimeout((()=>this.fetch(e)),100))})).catch((t=>{console.error(`Background load error for store "${e}":`,t)}))}async processFetchQueue(){if(0===this.fetchQueue.length)return;const e=this.fetchQueue.shift();if(!this.stores.get(e))return this.processFetchQueue();try{await this.fetch(e)}catch(t){console.error(`Queue fetch error for "${e}":`,t)}this.fetchQueue.length>0&&("requestIdleCallback"in window?requestIdleCallback((()=>this.processFetchQueue()),{timeout:2e3}):setTimeout((()=>this.processFetchQueue()),50))}async ensureStoreInitialized(e){const t=this.stores.get(e);if(!t)throw new Error(`Store "${e}" not registered`);t._initialized||await this.initDB(t.dbKey)}async withTransaction(e,t,s,r){const i=this.stores.get(e);return i?.db?("string"==typeof t&&(t=[t]),new Promise(((e,a)=>{const o=i.db.transaction(t,s),n=t.map((e=>o.objectStore(e))),c=1===n.length?n[0]:n;let h;o.oncomplete=()=>e(h),o.onerror=()=>{const e=o.error||new Error("Transaction failed with unknown error");a(e)};try{h=r(c,o)}catch(e){a(e||new Error("Callback failed with unknown error"))}}))):null}async fetch(e){await this.ensureStoreInitialized(e);const t=this.stores.get(e);if(!t.isFetching){if(t.config.required){if((Array.isArray(t.config.required)?t.config.required:[t.config.required]).some((e=>!t.filters[e]||""===t.filters[e])))return}t.isFetching=!0;try{const s=this.generateCacheKey(t.filters),r=t.cache.get(s);if(r&&this.isCacheValid(r,t.config.TTL)){let t=r.items.map((t=>this.get(e,t)));return this.notify(e,"data-loaded",{cached:!0,items:t??[]}),r}t.config.showLoading&&this.setLoading(!0);const i=this.buildFetchUrl(e),a={...t.config.headers};t.config.useHttpCaching&&r&&(r.etag&&(a["If-None-Match"]=r.etag),r.lastModified&&(a["If-Modified-Since"]=r.lastModified));const o=new AbortController;let n;if(t.currentRequest=o,n=t.isAuth?await window.auth.fetch(i,{method:"GET",headers:a,signal:o.signal}):await fetch(i,{method:"GET",headers:a,signal:o.signal}),!n.ok){const e=await n.text();throw new Error(`HTTP error! status: ${n.status}, message: ${e}`)}if(304===n.status)return r?(this.notify(e,"data-loaded",{cached:!0,notModified:!0,items:r.items||[]}),r):(this.notify(e,"data-loaded",{cached:!1,notModified:!0,items:[]}),t.lastResponse={has_more:!1,total:0,pages:1,queue_stats:{}},{items:[]});if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);const c=await n.json();return await this.processFetchedData(e,c,s,n),this.notify(e,"data-loaded",{cached:!1,items:c.items||[]}),c}catch(t){if(!("AbortError"===t?.name))throw console.error(`Fetch error for store "${e}":`,t.message),console.dir(t),this.notify(e,"fetch-error",{error:t}),t}finally{t.isFetching=!1,t.currentRequest=null,t.config.showLoading&&this.setLoading(!1)}}}buildFetchUrl(e){const t=this.stores.get(e),s=new URLSearchParams;Object.entries(t.filters).forEach((([e,t])=>{null!=t&&""!==t&&("object"==typeof t?s.set(e,JSON.stringify(t)):s.set(e,t))}));const r=t.config.apiBase+t.config.endpoint;return s.toString()?`${r}?${s}`:r}async processFetchedData(e,t,s,r){const i=this.stores.get(e),a=(t.items||[]).filter((e=>e&&"object"==typeof e)),o=[];i.db&&a.length>0&&await this.withTransaction(e,i.config.storeName,"readwrite",(t=>{a.forEach((s=>{try{const r=this._saveItem(e,s);o.push(r),t.put(r.processed)}catch(e){console.error("Error processing item:",e)}}))}));const n={key:s,items:a.map((e=>this.getItemKey(e,i.config.keyPath))),timestamp:Date.now(),endpoint:i.config.endpoint,filters:{...i.filters},etag:r.headers.get("ETag"),lastModified:r.headers.get("Last-Modified"),has_more:t.has_more||!1};i.cache.set(s,n),i.db?.objectStoreNames.contains("cache")&&await this.withTransaction(e,"cache","readwrite",(e=>{e.put(n)})),i.lastResponse={...t,has_more:t.has_more||!1,total:t.total||a.length,pages:t.pages||1,queue_stats:t.queue_stats||{}};for(let[t,s]of Object.entries(i.filters))"string"==typeof s&&s.includes(",")&&this.createSplitCacheEntries(e,a,t,i.filters,r);o.forEach((t=>{t.statusChanged&&this.notify(e,"item-saved",{item:t.item,key:t.key,previousItem:t.previousItem})}))}createSplitCacheEntries(e,t,s,r,i){const a=this.stores.get(e);r[s].split(",").map((e=>e.trim())).forEach((t=>{let o={};o[s]=t;const n={...r,[s]:t},c=this.generateCacheKey(n);if(a.cache.has(c))return;let h=this.filterByIndex(e,o).map((e=>this.getItemKey(e,a.config.keyPath)));const l={key:c,items:h,timestamp:Date.now(),endpoint:a.config.endpoint,filters:n,etag:i.headers.get("Etag"),lastModified:i.headers.get("Last-Modified"),has_more:20===h.length};a.cache.set(c,l),a.db?.objectStoreNames.contains("cache")&&this.withTransaction(e,"cache","readwrite",(e=>{e.put(l)}))}))}_saveItem(e,t){const s=this.stores.get(e),r=this.processForStorage(t,s.config.validateData);if(!r.valid)throw new Error(`Non-serializable data: ${r.error}`);const i=r.data,a=this.getItemKey(i,s.config.keyPath),o=s.data.get(a);return s.data.set(a,t),{item:t,previousItem:o,key:a,processed:i,statusChanged:o&&o.status!==t.status}}async save(e,t){const s=this.stores.get(e),r=this._saveItem(e,t);return await this.withTransaction(e,s.config.storeName,"readwrite",(e=>{e.put(r.processed)})),this.notify(e,"item-saved",{item:r.item,key:r.key,previousItem:r.previousItem}),r.key}async saveMany(e,t){const s=this.stores.get(e);if(!s)return[];const r=t instanceof Map?Array.from(t.values()):Array.isArray(t)?t:Object.values(t);if(0===r.length)return[];const i=[];return r.forEach((t=>{const s=this._saveItem(e,t);i.push(s)})),await this.withTransaction(e,s.config.storeName,"readwrite",(e=>{i.forEach((t=>{e.put(t.processed)}))})),this.notify(e,"items-saved",{count:i.length,keys:i.map((e=>e.key))}),i.map((e=>e.key))}processForStorage(e,t=!0,s="root"){if(null===e)return{valid:!0,data:null};if(void 0===e)return t?{valid:!1,error:`Undefined value at ${s}`}:{valid:!0,data:void 0};const r=typeof e;if(["string","number","boolean"].includes(r))return{valid:!0,data:e};if("function"===r)return t?{valid:!1,error:`Function at ${s}`}:{valid:!0,data:void 0};if(e instanceof HTMLElement||void 0!==e.nodeType)return t?{valid:!1,error:`DOM element at ${s}`}:{valid:!0,data:void 0};if(e instanceof FormData)return{valid:!0,data:this.formDataToObject(e)};if(e instanceof Date||e instanceof ArrayBuffer||ArrayBuffer.isView(e)||e instanceof Blob)return{valid:!0,data:e};if(e instanceof Set)return this.processForStorage(Array.from(e),t,s);if(e instanceof Map&&(e=Object.fromEntries(e)),Array.isArray(e)){const r=[];for(let i=0;i<e.length;i++){const a=this.processForStorage(e[i],t,`${s}[${i}]`);if(!a.valid)return a;void 0!==a.data&&r.push(a.data)}return{valid:!0,data:r}}if("object"===r){const r={};for(const[i,a]of Object.entries(e)){if(void 0===a)continue;const e=this.processForStorage(a,t,`${s}.${i}`);if(!e.valid)return e;void 0===e.data&&null!==a||(r[i]=e.data)}return{valid:!0,data:r}}return t?{valid:!1,error:`Unknown type at ${s}`}:{valid:!0,data:void 0}}async delete(e,t){const s=this.stores.get(e);s.data.delete(t),await this.withTransaction(e,s.config.storeName,"readwrite",(e=>{e.delete(t)})),this.notify(e,"item-deleted",{id:t})}async deleteMany(e,t){const s=this.stores.get(e);if(!s)return[];const r=t instanceof Set?Array.from(t):Array.isArray(t)?t:Object.keys(t);return 0===r.length?[]:(r.forEach((e=>{s.data.delete(e)})),await this.withTransaction(e,s.config.storeName,"readwrite",(e=>{r.forEach((t=>{e.delete(t)}))})),this.notify(e,"items-deleted",{count:r.length,ids:r}),r)}get(e,t){return this.stores.get(e).data.get(t)}getMany(e,t,s=!0){const r=this.stores.get(e);if(!r)return[];const i=t instanceof Set?Array.from(t):Array.isArray(t)?t:Object.keys(t);return 0===i.length?[]:s?i.reduce(((e,t)=>{const s=r.data.get(t);return s&&e.push(s),e}),[]):i.map((e=>r.data.get(e)??null))}getAll(e){const t=this.stores.get(e);return Array.from(t.data.values())}filterByIndex(e,t){const s=this.stores.get(e);return s?Array.from(s.data.values()).filter((e=>!(!e||"object"!=typeof e)&&Object.entries(t).every((([t,s])=>(Array.isArray(s)?s:[s]).includes(e[t]))))):[]}async getAllByIndex(e,t,s){const r=this.stores.get(e),i=Array.isArray(s)?s:[s];if(r.db&&r.db.objectStoreNames.contains(r.config.storeName))try{const e=r.db.transaction([r.config.storeName],"readonly").objectStore(r.config.storeName);if(e.indexNames.contains(t)){const s=e.index(t);return(await Promise.all(i.map((e=>new Promise(((t,r)=>{const i=s.getAll(e);i.onsuccess=()=>t(i.result||[]),i.onerror=()=>r(i.error)})))))).flat()}}catch(e){console.warn(`Index query failed for "${t}", falling back to filter:`,e)}return Array.from(r.data.values()).filter((e=>i.includes(e[t])))}getFiltered(e){const t=this.stores.get(e),s=this.generateCacheKey(t.filters),r=t.cache.get(s);if(r?.items){const e=r.items.reduce(((e,s)=>{const r=t.data.get(s);return r&&e.push(r),e}),[]);return this.applyOrdering(e,t)}const i=Array.from(t.data.values()),a=t.filters.search?.toLowerCase().trim()||"",o=[];t.filters.taxonomy&&"object"==typeof t.filters.taxonomy&&Object.entries(t.filters.taxonomy).forEach((([e,t])=>{const s=Array.isArray(t)?t:[t];o.push((t=>{if(!t.taxonomies||!t.taxonomies[e])return!1;const r=Object.keys(t.taxonomies[e]).map((e=>parseInt(e)));return s.some((e=>r.includes(parseInt(e))))}))}));for(const[e,s]of Object.entries(t.filters))if("taxonomy"!==e){if(!t.ignoreFilters.has(e)&&null!=s&&""!==s&&"all"!==s)if("string"==typeof s&&s.includes(",")){const t=s.split(",").map((e=>e.trim()));o.push((s=>t.includes(String(s[e]))))}else o.push((t=>String(t[e])===String(s)))}else"string"!=typeof s||s.includes(",")||o.push((e=>e.taxonomy===s));const n=i.filter((e=>{for(const t of o)if(!t(e))return!1;return!(a&&!this.searchObject(e,a))}));return this.applyOrdering(n,t)}applyOrdering(e,t){if(Array.isArray(e)||(e=Array.from(e)),0===e.length)return e;const s=t.filters.orderby||"date",r=(t.filters.order||"desc").toLowerCase();return["random","rand"].includes(s)||["random","rand"].includes(r)?this.shuffle(e):(e.sort(((e,t)=>{let i,a;switch(s){case"alphabetical":case"title":i=(e.title||e.name||"").toLowerCase(),a=(t.title||t.name||"").toLowerCase();break;case"modified":i=new Date(e.modified||e.date||0),a=new Date(t.modified||t.date||0);break;default:i=new Date(e.date||e.modified||0),a=new Date(t.date||t.modified||0)}return i<a?"asc"===r?-1:1:i>a?"asc"===r?1:-1:0})),e)}shuffle(e){const t=e.slice();for(let e=t.length-1;e>0;e--){const s=Math.floor(Math.random()*(e+1));[t[e],t[s]]=[t[s],t[e]]}return t}searchObject(e,t){if(!e||"object"!=typeof e)return"string"==typeof e&&e.toLowerCase().includes(t);for(const s of Object.values(e))if(null!=s)if("object"!=typeof s){if("string"==typeof s&&s.toLowerCase().includes(t))return!0}else if(this.searchObject(s,t))return!0;return!1}async clear(e){const t=this.stores.get(e);t.data.clear(),t.cache.clear(),await this.withTransaction(e,t.config.storeName,"readwrite",(e=>{e.clear()})),this.notify(e,"data-cleared")}async updateFilters(e,t,s=!1){const r=this.stores.get(e),i={...r.filters};s&&(r.filters={...r.config.filters}),Object.entries(t).forEach((([e,t])=>{null==t||""===t?delete r.filters[e]:r.filters[e]=t})),this.notify(e,"filters-changed",{oldFilters:i,filters:r.filters,updates:t});const a=await this.shouldFetchWithFilters(e,t,i);if(r.config.endpoint&&a)await this.fetch(e);else{const t=this.getFiltered(e);this.notify(e,"data-loaded",{cached:!0,items:t})}}async shouldFetchWithFilters(e,t,s){const r=this.stores.get(e);if(!r.config.endpoint||!r.lastResponse)return!0;if(!1===r.lastResponse.has_more&&this.hasCompleteData(r,r.filters))return!1;if("page"in t){const e=t.page,i=s.page||1;if(e>i&&!r.lastResponse.has_more)return r.filters.page=i,!1}if("search"in t){const e=t.search?.trim()||"",i=s.search?.trim()||"";if(!e&&i){const e={...r.filters};if(delete e.search,e.page=1,this.hasCompleteData(r,e))return!1}if(e&&e!==i){const e={...r.filters};if(delete e.search,e.page=1,this.hasCompleteData(r,e))return!1}}return!0}hasCompleteData(e,t){const s=this.generateCacheKey(t),r=e.cache.get(s);return!!r&&(!1===r.has_more||!1===e.lastResponse?.has_more)}setFilter(e,t,s){return this.updateFilters(e,{[t]:s})}async setFilters(e,t){const s=this.stores.get(e);if(Object.keys(t).some((e=>s.filters[e]!==t[e]))||Object.keys(s.filters).some((e=>!(e in t)&&t!==s.config.filters)))return this.updateFilters(e,t)}removeFilter(e,t){return this.updateFilters(e,{[t]:null})}clearFilters(e){return this.updateFilters(e,{},!0)}clearCache(e){const t=this.stores.get(e);t.cache.clear(),t.db?.objectStoreNames.contains("cache")&&this.withTransaction(e,"cache","readwrite",(e=>{e.clear()})),this.notify(e,"cache-cleared")}generateCacheKey(e){const t=Object.keys(e).sort().reduce(((t,s)=>(t[s]=e[s],t)),{});return JSON.stringify(t)}isCacheValid(e,t){if(!e||!e.timestamp)return!1;return Date.now()-e.timestamp<t}subscribe(e,t){this.subscribers.has(e)||this.subscribers.set(e,new Set);const s=this.subscribers.get(e);return s.add(t),()=>s.delete(t)}notify(e,t,s={}){const r=this.subscribers.get(e);r&&r.forEach((r=>{try{r(t,s)}catch(t){console.error(`Subscriber error for store "${e}":`,t)}}))}getItemKey(e,t){if("function"==typeof t)return t(e);const s=t.split(".");let r=e;for(const e of s)r=r?.[e];return r}setLoading(e){this.body.classList.toggle("loading",e),e?this.loading?.showModal():this.loading?.close()}destroy(){this.stores.forEach((e=>{e.currentRequest&&e.currentRequest.abort()})),this.databases.forEach((e=>e.close())),this.stores.clear(),this.subscribers.clear(),this.databases.clear(),this.pendingInits.clear()}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbStore=new e)}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){if(e.instance)return e.instance;e.instance=this,this.dbConfig=new Map,this.databases=new Map,this.stores=new Map,this.subscribers=new Map,this.pendingInits=new Map,this.fetchQueue=[],this._initialized=!1,this.body=document.body,this.loading=document.querySelector("dialog.loading"),this.init()}async init(){this._initialized||(this._initialized=!0,"indexedDB"in window||console.warn("IndexedDB not supported"))}register(e,t=[],s=1.25){if(Array.isArray(t)||(t=[t]),0===t.length)return;this.dbConfig.has(e)||this.dbConfig.set(e,{dbName:`${jvbBase.base}${e}`,version:s,stores:{},_initialized:!1});let r=this.dbConfig.get(e);t.forEach(t=>{if(!t.storeName)throw new Error(`Store config for "${e}" missing storeName`);if(!t.keyPath)throw new Error(`Store "${t.storeName}" requires keyPath`);const s=`${e}_${t.storeName}`,i={config:{dbName:r.dbName,storeName:"items",keyPath:"id",indexes:[],endpoint:null,apiBase:jvbSettings.api,filters:{},ignore:[],required:null,isAuth:!1,TTL:36e5,useHttpCaching:!0,showLoading:!1,delayFetch:!0,validateData:!0,...t},dbKey:e,storeKey:s,data:new Map,cache:new Map,filters:{...t.filters||{}},isFetching:!1,currentRequest:null,lastResponse:null,_initialized:!1};i.ignoreFilters=new Set(["search","page","per_page","orderby","order","context","source",...i.config.ignore]),i.config.headers={"X-WP-Nonce":window.auth.getNonce(),...i.config.headers},r.stores[t.storeName]=s,this.stores.set(s,i),this.subscribers.has(s)||this.subscribers.set(s,new Set)}),this.initDB(e).catch(t=>{console.error(`Failed to initialize store "${e}":`,t)});const i={};for(const[e,t]of Object.entries(r.stores))i[e]=this.getStoreAPI(t);return i}getStoreAPI(e){const t={fetch:()=>this.fetch(e),save:t=>this.save(e,t),saveMany:t=>this.saveMany(e,t),delete:t=>this.delete(e,t),deleteMany:t=>this.deleteMany(e,t),get:t=>this.get(e,t),getMany:t=>this.getMany(e,t),getAll:()=>this.getAll(e),getAllByIndex:(t,s)=>this.getAllByIndex(e,t,s),filterByIndex:t=>this.filterByIndex(e,t),getFiltered:()=>this.getFiltered(e),clear:()=>this.clear(e),setFilter:(t,s)=>this.setFilter(e,t,s),setFilters:t=>this.setFilters(e,t),removeFilter:t=>this.removeFilter(e,t),clearFilters:()=>this.clearFilters(e),clearCache:()=>this.clearCache(e),subscribe:t=>this.subscribe(e,t),ensureInitialized:()=>this.ensureStoreInitialized(e),get filters(){return{...t.getStore().filters}},get lastResponse(){return t.getStore().lastResponse},get data(){return t.getStore().data},getStore:()=>this.stores.get(e)};return t}formDataToObject(e){const t={_isFormData:!0,entries:{}};for(const[s,r]of e.entries())r instanceof File||r instanceof Blob||(t.entries[s]?(Array.isArray(t.entries[s])||(t.entries[s]=[t.entries[s]]),t.entries[s].push(r)):t.entries[s]=r);return t}async objectToFormData(e){if(!e._isFormData)return e;const t=new FormData;for(const[s,r]of Object.entries(e.entries))Array.isArray(r)?r.forEach(e=>t.append(s,e)):t.append(s,r);if(window.jvbUploads&&e.entries.upload_ids){const s=JSON.parse(e.entries.upload_ids);for(const e of s){const s=await window.jvbUploads.getBlobData(e);s&&t.append("files[]",s)}}return t}async initDB(e){const t=this.dbConfig.get(e);if(!t||t._initialized)return;if(this.pendingInits.has(e))return this.pendingInits.get(e);const s=this._performDBInit(e);this.pendingInits.set(e,s);try{await s,t._initialized=!0}finally{this.pendingInits.delete(e)}}async _performDBInit(e){const t=this.dbConfig.get(e),{dbName:s,version:r}=t,i=Object.values(t.stores);try{if(!this.databases.has(s)){const e=await this.openDatabase(s,r,e=>{i.forEach(t=>{let s=this.stores.get(t);s&&this.setupStores(e,s.config)})});this.databases.set(s,e)}i.forEach(e=>{let t=this.stores.get(e);t&&(t.db=this.databases.get(s),t._initialized=!0,this.loadStoreDataInBackground(e),this.notify(e,"db-init"))})}catch(t){throw console.error(`Failed to initialize database for store "${e}":`,t),t}}openDatabase(e,t,s){return new Promise((r,i)=>{const a=indexedDB.open(e,t);a.onupgradeneeded=e=>{s&&s(e.target.result,e.oldVersion,e.newVersion)},a.onsuccess=e=>r(e.target.result),a.onerror=e=>i(e.target.error),a.onblocked=()=>{console.warn(`Database ${e} blocked. Close other tabs.`)}})}setupStores(e,t){if(!e.objectStoreNames.contains(t.storeName)){const s=e.createObjectStore(t.storeName,{keyPath:t.keyPath});t.indexes.forEach(e=>{s.createIndex(e.name,e.keyPath||e.name,{unique:e.unique||!1})})}if(t.endpoint&&!e.objectStoreNames.contains("cache")){e.createObjectStore("cache",{keyPath:"key"}).createIndex("timestamp","timestamp",{unique:!1})}}async loadFromObjectStore(e,t,s){const r=this.stores.get(e);return r?.db&&r.db.objectStoreNames.contains(t)?new Promise(e=>{const i=r.db.transaction([t],"readonly").objectStore(t).getAll();i.onsuccess=t=>{const r=t.target.result||[];r.forEach(s),e(r)},i.onerror=()=>e([])}):[]}loadStoreDataInBackground(e){const t=this.stores.get(e);t?.db&&Promise.all([this.loadFromObjectStore(e,t.config.storeName,e=>{const s=this.getItemKey(e,t.config.keyPath);t.data.set(s,e)}),this.loadFromObjectStore(e,"cache",e=>{this.isCacheValid(e,t.config.TTL)&&t.cache.set(e.key,e)})]).then(()=>{this.notify(e,"data-ready"),t.config.endpoint&&t.config.delayFetch?(this.fetchQueue.push(e),1===this.fetchQueue.length&&this.processFetchQueue()):t.config.endpoint&&!t.config.delayFetch&&("requestIdleCallback"in window?requestIdleCallback(()=>this.fetch(e),{timeout:2e3}):setTimeout(()=>this.fetch(e),100))}).catch(t=>{console.error(`Background load error for store "${e}":`,t)})}async processFetchQueue(){if(0===this.fetchQueue.length)return;const e=this.fetchQueue.shift();if(!this.stores.get(e))return this.processFetchQueue();try{await this.fetch(e)}catch(t){console.error(`Queue fetch error for "${e}":`,t)}this.fetchQueue.length>0&&("requestIdleCallback"in window?requestIdleCallback(()=>this.processFetchQueue(),{timeout:2e3}):setTimeout(()=>this.processFetchQueue(),50))}async ensureStoreInitialized(e){const t=this.stores.get(e);if(!t)throw new Error(`Store "${e}" not registered`);t._initialized||await this.initDB(t.dbKey)}async withTransaction(e,t,s,r){const i=this.stores.get(e);return i?.db?("string"==typeof t&&(t=[t]),new Promise((e,a)=>{const o=i.db.transaction(t,s),n=t.map(e=>o.objectStore(e)),c=1===n.length?n[0]:n;let h;o.oncomplete=()=>e(h),o.onerror=()=>{const e=o.error||new Error("Transaction failed with unknown error");a(e)};try{h=r(c,o)}catch(e){a(e||new Error("Callback failed with unknown error"))}})):null}async fetch(e){await this.ensureStoreInitialized(e);const t=this.stores.get(e);if(!t.isFetching){if(t.config.required){if((Array.isArray(t.config.required)?t.config.required:[t.config.required]).some(e=>!t.filters[e]||""===t.filters[e]))return}t.isFetching=!0;try{const s=this.generateCacheKey(t.filters),r=t.cache.get(s);if(r&&this.isCacheValid(r,t.config.TTL)){let t=r.items.map(t=>this.get(e,t));return this.notify(e,"data-loaded",{cached:!0,items:t??[]}),r}t.config.showLoading&&this.setLoading(!0);const i=this.buildFetchUrl(e),a={...t.config.headers};t.config.useHttpCaching&&r&&(r.etag&&(a["If-None-Match"]=r.etag),r.lastModified&&(a["If-Modified-Since"]=r.lastModified));const o=new AbortController;let n;if(t.currentRequest=o,n=t.isAuth?await window.auth.fetch(i,{method:"GET",headers:a,signal:o.signal}):await fetch(i,{method:"GET",headers:a,signal:o.signal}),!n.ok){const e=await n.text();throw new Error(`HTTP error! status: ${n.status}, message: ${e}`)}if(304===n.status)return r?(this.notify(e,"data-loaded",{cached:!0,notModified:!0,items:r.items||[]}),r):(this.notify(e,"data-loaded",{cached:!1,notModified:!0,items:[]}),t.lastResponse={has_more:!1,total:0,pages:1,queue_stats:{}},{items:[]});if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);const c=await n.json();return await this.processFetchedData(e,c,s,n),this.notify(e,"data-loaded",{cached:!1,items:c.items||[]}),c}catch(t){if(!("AbortError"===t?.name))throw console.error(`Fetch error for store "${e}":`,t.message),console.dir(t),this.notify(e,"fetch-error",{error:t}),t}finally{t.isFetching=!1,t.currentRequest=null,t.config.showLoading&&this.setLoading(!1)}}}buildFetchUrl(e){const t=this.stores.get(e),s=new URLSearchParams;Object.entries(t.filters).forEach(([e,t])=>{null!=t&&""!==t&&("object"==typeof t?s.set(e,JSON.stringify(t)):s.set(e,t))});const r=t.config.apiBase+t.config.endpoint;return s.toString()?`${r}?${s}`:r}async processFetchedData(e,t,s,r){const i=this.stores.get(e),a=(t.items||[]).filter(e=>e&&"object"==typeof e),o=[];i.db&&a.length>0&&await this.withTransaction(e,i.config.storeName,"readwrite",t=>{a.forEach(s=>{try{const r=this._saveItem(e,s);o.push(r),t.put(r.processed)}catch(e){console.error("Error processing item:",e)}})});const n={key:s,items:a.map(e=>this.getItemKey(e,i.config.keyPath)),timestamp:Date.now(),endpoint:i.config.endpoint,filters:{...i.filters},etag:r.headers.get("ETag"),lastModified:r.headers.get("Last-Modified"),has_more:t.has_more||!1};i.cache.set(s,n),i.db?.objectStoreNames.contains("cache")&&await this.withTransaction(e,"cache","readwrite",e=>{e.put(n)}),i.lastResponse={...t,has_more:t.has_more||!1,total:t.total||a.length,pages:t.pages||1,queue_stats:t.queue_stats||{}};for(let[t,s]of Object.entries(i.filters))"string"==typeof s&&s.includes(",")&&this.createSplitCacheEntries(e,a,t,i.filters,r);o.forEach(t=>{t.statusChanged&&this.notify(e,"item-saved",{item:t.item,key:t.key,previousItem:t.previousItem})})}createSplitCacheEntries(e,t,s,r,i){const a=this.stores.get(e);r[s].split(",").map(e=>e.trim()).forEach(t=>{let o={};o[s]=t;const n={...r,[s]:t},c=this.generateCacheKey(n);if(a.cache.has(c))return;let h=this.filterByIndex(e,o).map(e=>this.getItemKey(e,a.config.keyPath));const l={key:c,items:h,timestamp:Date.now(),endpoint:a.config.endpoint,filters:n,etag:i.headers.get("Etag"),lastModified:i.headers.get("Last-Modified"),has_more:20===h.length};a.cache.set(c,l),a.db?.objectStoreNames.contains("cache")&&this.withTransaction(e,"cache","readwrite",e=>{e.put(l)})})}_saveItem(e,t){const s=this.stores.get(e),r=this.processForStorage(t,s.config.validateData);if(!r.valid)throw new Error(`Non-serializable data: ${r.error}`);const i=r.data,a=this.getItemKey(i,s.config.keyPath),o=s.data.get(a);return s.data.set(a,t),{item:t,previousItem:o,key:a,processed:i,statusChanged:o&&o.status!==t.status}}async save(e,t){const s=this.stores.get(e),r=this._saveItem(e,t);return await this.withTransaction(e,s.config.storeName,"readwrite",e=>{e.put(r.processed)}),this.notify(e,"item-saved",{item:r.item,key:r.key,previousItem:r.previousItem}),r.key}async saveMany(e,t){const s=this.stores.get(e);if(!s)return[];const r=t instanceof Map?Array.from(t.values()):Array.isArray(t)?t:Object.values(t);if(0===r.length)return[];const i=[];return r.forEach(t=>{const s=this._saveItem(e,t);i.push(s)}),await this.withTransaction(e,s.config.storeName,"readwrite",e=>{i.forEach(t=>{e.put(t.processed)})}),this.notify(e,"items-saved",{count:i.length,keys:i.map(e=>e.key)}),i.map(e=>e.key)}processForStorage(e,t=!0,s="root"){if(null===e)return{valid:!0,data:null};if(void 0===e)return t?{valid:!1,error:`Undefined value at ${s}`}:{valid:!0,data:void 0};const r=typeof e;if(["string","number","boolean"].includes(r))return{valid:!0,data:e};if("function"===r)return t?{valid:!1,error:`Function at ${s}`}:{valid:!0,data:void 0};if(e instanceof HTMLElement||void 0!==e.nodeType)return t?{valid:!1,error:`DOM element at ${s}`}:{valid:!0,data:void 0};if(e instanceof FormData)return{valid:!0,data:this.formDataToObject(e)};if(e instanceof Date||e instanceof ArrayBuffer||ArrayBuffer.isView(e)||e instanceof Blob)return{valid:!0,data:e};if(e instanceof Set)return this.processForStorage(Array.from(e),t,s);if(e instanceof Map&&(e=Object.fromEntries(e)),Array.isArray(e)){const r=[];for(let i=0;i<e.length;i++){const a=this.processForStorage(e[i],t,`${s}[${i}]`);if(!a.valid)return a;void 0!==a.data&&r.push(a.data)}return{valid:!0,data:r}}if("object"===r){const r={};for(const[i,a]of Object.entries(e)){if(void 0===a)continue;const e=this.processForStorage(a,t,`${s}.${i}`);if(!e.valid)return e;void 0===e.data&&null!==a||(r[i]=e.data)}return{valid:!0,data:r}}return t?{valid:!1,error:`Unknown type at ${s}`}:{valid:!0,data:void 0}}async delete(e,t){const s=this.stores.get(e);s.data.delete(t),await this.withTransaction(e,s.config.storeName,"readwrite",e=>{e.delete(t)}),this.notify(e,"item-deleted",{id:t})}async deleteMany(e,t){const s=this.stores.get(e);if(!s)return[];const r=t instanceof Set?Array.from(t):Array.isArray(t)?t:Object.keys(t);return 0===r.length?[]:(r.forEach(e=>{s.data.delete(e)}),await this.withTransaction(e,s.config.storeName,"readwrite",e=>{r.forEach(t=>{e.delete(t)})}),this.notify(e,"items-deleted",{count:r.length,ids:r}),r)}get(e,t){return this.stores.get(e).data.get(t)}getMany(e,t,s=!0){const r=this.stores.get(e);if(!r)return[];const i=t instanceof Set?Array.from(t):Array.isArray(t)?t:Object.keys(t);return 0===i.length?[]:s?i.reduce((e,t)=>{const s=r.data.get(t);return s&&e.push(s),e},[]):i.map(e=>r.data.get(e)??null)}getAll(e){const t=this.stores.get(e);return Array.from(t.data.values())}filterByIndex(e,t){const s=this.stores.get(e);return s?Array.from(s.data.values()).filter(e=>!(!e||"object"!=typeof e)&&Object.entries(t).every(([t,s])=>(Array.isArray(s)?s:[s]).includes(e[t]))):[]}async getAllByIndex(e,t,s){const r=this.stores.get(e),i=Array.isArray(s)?s:[s];if(r.db&&r.db.objectStoreNames.contains(r.config.storeName))try{const e=r.db.transaction([r.config.storeName],"readonly").objectStore(r.config.storeName);if(e.indexNames.contains(t)){const s=e.index(t);return(await Promise.all(i.map(e=>new Promise((t,r)=>{const i=s.getAll(e);i.onsuccess=()=>t(i.result||[]),i.onerror=()=>r(i.error)})))).flat()}}catch(e){console.warn(`Index query failed for "${t}", falling back to filter:`,e)}return Array.from(r.data.values()).filter(e=>i.includes(e[t]))}getFiltered(e){const t=this.stores.get(e),s=this.generateCacheKey(t.filters),r=t.cache.get(s);if(r?.items){const e=r.items.reduce((e,s)=>{const r=t.data.get(s);return r&&e.push(r),e},[]);return this.applyOrdering(e,t)}const i=Array.from(t.data.values()),a=t.filters.search?.toLowerCase().trim()||"",o=[];t.filters.taxonomy&&"object"==typeof t.filters.taxonomy&&Object.entries(t.filters.taxonomy).forEach(([e,t])=>{const s=Array.isArray(t)?t:[t];o.push(t=>{if(!t.taxonomies||!t.taxonomies[e])return!1;const r=Object.keys(t.taxonomies[e]).map(e=>parseInt(e));return s.some(e=>r.includes(parseInt(e)))})});for(const[e,s]of Object.entries(t.filters))if("taxonomy"!==e){if(!t.ignoreFilters.has(e)&&null!=s&&""!==s&&"all"!==s)if("string"==typeof s&&s.includes(",")){const t=s.split(",").map(e=>e.trim());o.push(s=>t.includes(String(s[e])))}else o.push(t=>String(t[e])===String(s))}else"string"!=typeof s||s.includes(",")||o.push(e=>e.taxonomy===s);const n=i.filter(e=>{for(const t of o)if(!t(e))return!1;return!(a&&!this.searchObject(e,a))});return this.applyOrdering(n,t)}applyOrdering(e,t){if(Array.isArray(e)||(e=Array.from(e)),0===e.length)return e;const s=t.filters.orderby||"date",r=(t.filters.order||"desc").toLowerCase();return["random","rand"].includes(s)||["random","rand"].includes(r)?this.shuffle(e):(e.sort((e,t)=>{let i,a;switch(s){case"alphabetical":case"title":i=(e.title||e.name||"").toLowerCase(),a=(t.title||t.name||"").toLowerCase();break;case"modified":i=new Date(e.modified||e.date||0),a=new Date(t.modified||t.date||0);break;default:i=new Date(e.date||e.modified||0),a=new Date(t.date||t.modified||0)}return i<a?"asc"===r?-1:1:i>a?"asc"===r?1:-1:0}),e)}shuffle(e){const t=e.slice();for(let e=t.length-1;e>0;e--){const s=Math.floor(Math.random()*(e+1));[t[e],t[s]]=[t[s],t[e]]}return t}searchObject(e,t){if(!e||"object"!=typeof e)return"string"==typeof e&&e.toLowerCase().includes(t);for(const s of Object.values(e))if(null!=s)if("object"!=typeof s){if("string"==typeof s&&s.toLowerCase().includes(t))return!0}else if(this.searchObject(s,t))return!0;return!1}async clear(e){const t=this.stores.get(e);t.data.clear(),t.cache.clear(),await this.withTransaction(e,t.config.storeName,"readwrite",e=>{e.clear()}),this.notify(e,"data-cleared")}async updateFilters(e,t,s=!1){const r=this.stores.get(e),i={...r.filters};s&&(r.filters={...r.config.filters}),Object.entries(t).forEach(([e,t])=>{null==t||""===t?delete r.filters[e]:r.filters[e]=t}),this.notify(e,"filters-changed",{oldFilters:i,filters:r.filters,updates:t});const a=await this.shouldFetchWithFilters(e,t,i);if(r.config.endpoint&&a)await this.fetch(e);else{const t=this.getFiltered(e);this.notify(e,"data-loaded",{cached:!0,items:t})}}async shouldFetchWithFilters(e,t,s){const r=this.stores.get(e);if(!r.config.endpoint||!r.lastResponse)return!0;if(!1===r.lastResponse.has_more&&this.hasCompleteData(r,r.filters))return!1;if("page"in t){const e=t.page,i=s.page||1;if(e>i&&!r.lastResponse.has_more)return r.filters.page=i,!1}if("search"in t){const e=t.search?.trim()||"",i=s.search?.trim()||"";if(!e&&i){const e={...r.filters};if(delete e.search,e.page=1,this.hasCompleteData(r,e))return!1}if(e&&e!==i){const e={...r.filters};if(delete e.search,e.page=1,this.hasCompleteData(r,e))return!1}}return!0}hasCompleteData(e,t){const s=this.generateCacheKey(t),r=e.cache.get(s);return!!r&&(!1===r.has_more||!1===e.lastResponse?.has_more)}setFilter(e,t,s){return this.updateFilters(e,{[t]:s})}async setFilters(e,t){const s=this.stores.get(e);if(Object.keys(t).some(e=>s.filters[e]!==t[e])||Object.keys(s.filters).some(e=>!(e in t)&&t!==s.config.filters))return this.updateFilters(e,t)}removeFilter(e,t){return this.updateFilters(e,{[t]:null})}clearFilters(e){return this.updateFilters(e,{},!0)}clearCache(e){const t=this.stores.get(e);t.cache.clear(),t.db?.objectStoreNames.contains("cache")&&this.withTransaction(e,"cache","readwrite",e=>{e.clear()}),this.notify(e,"cache-cleared")}generateCacheKey(e){const t=Object.keys(e).sort().reduce((t,s)=>(t[s]=e[s],t),{});return JSON.stringify(t)}isCacheValid(e,t){if(!e||!e.timestamp)return!1;return Date.now()-e.timestamp<t}subscribe(e,t){this.subscribers.has(e)||this.subscribers.set(e,new Set);const s=this.subscribers.get(e);return s.add(t),()=>s.delete(t)}notify(e,t,s={}){const r=this.subscribers.get(e);r&&r.forEach(r=>{try{r(t,s)}catch(t){console.error(`Subscriber error for store "${e}":`,t)}})}getItemKey(e,t){if("function"==typeof t)return t(e);const s=t.split(".");let r=e;for(const e of s)r=r?.[e];return r}setLoading(e){this.body.classList.toggle("loading",e),e?this.loading?.showModal():this.loading?.close()}destroy(){this.stores.forEach(e=>{e.currentRequest&&e.currentRequest.abort()}),this.databases.forEach(e=>e.close()),this.stores.clear(),this.subscribers.clear(),this.databases.clear(),this.pendingInits.clear()}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbStore=new e)})})})();
\ No newline at end of file
diff --git a/assets/js/min/dragHandler.min.js b/assets/js/min/dragHandler.min.js
index 505555b..6699868 100644
--- a/assets/js/min/dragHandler.min.js
+++ b/assets/js/min/dragHandler.min.js
@@ -1 +1 @@
-window.jvbDragHandler=class{constructor(t){this.draggableSelector=t.draggableSelector,this.dropTargetSelector=t.dropTargetSelector,this.getItemId=t.getItemId,this.getSelectedItems=t.getSelectedItems,this.validateDrop=t.validateDrop,this.onDrop=t.onDrop,this.handleSelector=t.handleSelector||null,this.ignoreSelector=t.ignoreSelector||"input, button, label, select, textarea, a",this.onDragStart=t.onDragStart||null,this.onDragEnd=t.onDragEnd||null,this.previewElement=t.previewElement||"img, video, .icon",this.previewOptions={offset:{x:-30,y:-40},showCount:!0,...t.previewOptions},this.dragThreshold={time:90,distance:5},this.state=this.getInitialState(),this.onPointerDown=this.handlePointerDown.bind(this),this.onPointerMove=this.handlePointerMove.bind(this),this.onPointerUp=this.handlePointerUp.bind(this),this.onPointerCancel=this.handlePointerCancel.bind(this),this.init()}getInitialState(){return{active:!1,itemIds:[],startPos:null,currentPos:null,targetElement:null,previewElement:null,startTime:null,holdTimer:null,pointerId:null,pointerTarget:null}}init(){document.addEventListener("pointerdown",this.onPointerDown)}handlePointerDown(t){if(this.shouldIgnoreElement(t.target))return;const e=t.target.closest(this.draggableSelector);if(!e)return;if(this.handleSelector){const i=t.target.closest(this.handleSelector);if(!i||!e.contains(i))return}const i=this.getItemId(e);if(!i)return;const n=this.getSelectedItems(e),r=n.includes(i)?n:[i];t.preventDefault(),this.state={active:!1,itemIds:r,startPos:{x:t.clientX,y:t.clientY},currentPos:{x:t.clientX,y:t.clientY},targetElement:null,previewElement:null,startTime:Date.now(),holdTimer:null,draggableElement:e,pointerId:t.pointerId,pointerTarget:e},document.addEventListener("pointermove",this.onPointerMove),document.addEventListener("pointerup",this.onPointerUp),document.addEventListener("pointercancel",this.onPointerCancel),this.state.holdTimer=setTimeout((()=>{this.state&&!this.state.active&&this.startDrag()}),this.dragThreshold.time)}startDrag(){if(this.state&&!this.state.active){if(this.state.holdTimer=null,this.state.active=!0,this.state.pointerTarget&&null!==this.state.pointerId)try{this.state.pointerTarget.setPointerCapture(this.state.pointerId)}catch(t){console.warn("Could not capture pointer:",t)}if(this.state.previewElement=this.createPreview(this.state.draggableElement,this.state.itemIds),this.applyDraggingState(!0),this.updatePreview(),this.onDragStart&&this.onDragStart(this.state.itemIds,this.state.draggableElement),window.jvbA11y){const t=this.state.itemIds.length>1?`Started dragging ${this.state.itemIds.length} items`:"Started dragging item";window.jvbA11y.announce(t)}}}handlePointerMove(t){if(!this.state)return;if(this.state.currentPos={x:t.clientX,y:t.clientY},!this.state.active&&this.state.holdTimer){const e=t.clientX-this.state.startPos.x,i=t.clientY-this.state.startPos.y;Math.sqrt(e*e+i*i)>2*this.dragThreshold.distance&&(clearTimeout(this.state.holdTimer),this.startDrag())}if(!this.state.active)return;t.preventDefault(),this.updatePreview();const e=document.elementFromPoint(t.clientX,t.clientY),i=this.findDropTarget(e);i!==this.state.targetElement&&(this.clearTargetHighlight(),this.state.targetElement=i,i&&this.validateDrop(this.state.itemIds,i)&&this.highlightTarget(i))}handlePointerUp(t){if(!this.state)return;if(this.state.holdTimer&&!this.state.active)return void this.cancelDragStart();if(!this.state.active)return void this.cleanup();document.removeEventListener("pointermove",this.onPointerMove),document.removeEventListener("pointerup",this.onPointerUp),document.removeEventListener("pointercancel",this.onPointerCancel);const{itemIds:e,targetElement:i}=this.state;let n=!1;if(i&&this.validateDrop(e,i)&&(this.onDrop(e,i),n=!0),this.cleanup(),this.onDragEnd&&this.onDragEnd(e,n),window.jvbA11y){const t=n?e.length>1?`Moved ${e.length} items`:"Item moved":"Drag cancelled";window.jvbA11y.announce(t)}}handlePointerCancel(t){this.state&&(document.removeEventListener("pointermove",this.onPointerMove),document.removeEventListener("pointerup",this.onPointerUp),document.removeEventListener("pointercancel",this.onPointerCancel),this.state.holdTimer&&(clearTimeout(this.state.holdTimer),this.state.holdTimer=null),this.cleanup(),window.jvbA11y&&window.jvbA11y.announce("Drag cancelled"))}cancelDragStart(){this.state&&this.state.holdTimer&&(clearTimeout(this.state.holdTimer),this.state.holdTimer=null),document.removeEventListener("pointermove",this.onPointerMove),document.removeEventListener("pointerup",this.onPointerUp),document.removeEventListener("pointercancel",this.onPointerCancel),this.cleanup()}createPreview(t,e){let i=window.getTemplate("dragPreview");i||(i=this.createPreviewElement());let n=i.querySelector(".drag-items"),r=n.querySelector(".drag-item");e.forEach(((t,e)=>{let i=this.findElementByItemId(t);if(i){let t=i.querySelector(this.previewElement)?.cloneNode(!0);if(t){let e=r.cloneNode(!0);e.appendChild(t),n.append(e)}}})),r.remove();let s=i.querySelector(".drag-count");return s&&(s.textContent="{"+e.length+"}",s.hidden=e.length<=1),document.body.appendChild(i),i}createPreviewElement(){const t=document.createElement("div");t.className="drag-preview";let e=t.cloneNode(!0),i=t.cloneNode(!0),n=t.cloneNode(!0);return n.className="drag-item",e.className="drag-items",e.append(n),i.className="drag-count",t.append(e),t.append(i),t}updatePreview(){this.state.previewElement&&this.state.currentPos&&(this.state.previewElement.style.left=`${this.state.currentPos.x+this.previewOptions.offset.x}px`,this.state.previewElement.style.top=`${this.state.currentPos.y+this.previewOptions.offset.y}px`)}findDropTarget(t){return t?t.closest(this.dropTargetSelector):null}highlightTarget(t){if(t&&(t.classList.add("dragover"),this.state.isMultiDrag&&(t.classList.add("multi-drop"),t.setAttribute("data-item-count",this.state.itemIds.length)),navigator.vibrate)){const t=this.state.isMultiDrag?[25,10,25]:[25];navigator.vibrate(t)}}clearTargetHighlight(){document.querySelectorAll(".dragover").forEach((t=>{t.classList.remove("dragover","multi-drop"),t.removeAttribute("data-item-count")}))}applyDraggingState(t){this.state.itemIds.forEach((e=>{const i=this.findElementByItemId(e);i&&i.classList.toggle("dragging",t)}))}findElementByItemId(t){const e=document.querySelectorAll(this.draggableSelector);for(const i of e)if(this.getItemId(i)===t)return i;return null}shouldIgnoreElement(t){return t.matches(this.ignoreSelector)}cleanup(){if(this.state&&this.state.holdTimer&&clearTimeout(this.state.holdTimer),this.state&&this.state.pointerTarget&&null!==this.state.pointerId)try{this.state.pointerTarget.releasePointerCapture(this.state.pointerId)}catch(t){}this.clearTargetHighlight(),this.applyDraggingState(!1),this.state&&this.state.previewElement&&this.state.previewElement.remove(),this.state=this.getInitialState()}destroy(){this.cleanup(),document.removeEventListener("pointerdown",this.onPointerDown),document.removeEventListener("pointermove",this.onPointerMove),document.removeEventListener("pointerup",this.onPointerUp),document.removeEventListener("pointercancel",this.onPointerCancel)}};
\ No newline at end of file
+window.jvbDragHandler=class{constructor(t){this.draggableSelector=t.draggableSelector,this.dropTargetSelector=t.dropTargetSelector,this.getItemId=t.getItemId,this.getSelectedItems=t.getSelectedItems,this.validateDrop=t.validateDrop,this.onDrop=t.onDrop,this.handleSelector=t.handleSelector||null,this.ignoreSelector=t.ignoreSelector||"input, button, label, select, textarea, a",this.onDragStart=t.onDragStart||null,this.onDragEnd=t.onDragEnd||null,this.previewElement=t.previewElement||"img, video, .icon",this.previewOptions={offset:{x:-30,y:-40},showCount:!0,...t.previewOptions},this.dragThreshold={time:90,distance:5},this.state=this.getInitialState(),this.onPointerDown=this.handlePointerDown.bind(this),this.onPointerMove=this.handlePointerMove.bind(this),this.onPointerUp=this.handlePointerUp.bind(this),this.onPointerCancel=this.handlePointerCancel.bind(this),this.init()}getInitialState(){return{active:!1,itemIds:[],startPos:null,currentPos:null,targetElement:null,previewElement:null,startTime:null,holdTimer:null,pointerId:null,pointerTarget:null}}init(){document.addEventListener("pointerdown",this.onPointerDown)}handlePointerDown(t){if(this.shouldIgnoreElement(t.target))return;const e=t.target.closest(this.draggableSelector);if(!e)return;if(this.handleSelector){const i=t.target.closest(this.handleSelector);if(!i||!e.contains(i))return}const i=this.getItemId(e);if(!i)return;const n=this.getSelectedItems(e),r=n.includes(i)?n:[i];t.preventDefault(),this.state={active:!1,itemIds:r,startPos:{x:t.clientX,y:t.clientY},currentPos:{x:t.clientX,y:t.clientY},targetElement:null,previewElement:null,startTime:Date.now(),holdTimer:null,draggableElement:e,pointerId:t.pointerId,pointerTarget:e},document.addEventListener("pointermove",this.onPointerMove),document.addEventListener("pointerup",this.onPointerUp),document.addEventListener("pointercancel",this.onPointerCancel),this.state.holdTimer=setTimeout(()=>{this.state&&!this.state.active&&this.startDrag()},this.dragThreshold.time)}startDrag(){if(this.state&&!this.state.active){if(this.state.holdTimer=null,this.state.active=!0,this.state.pointerTarget&&null!==this.state.pointerId)try{this.state.pointerTarget.setPointerCapture(this.state.pointerId)}catch(t){console.warn("Could not capture pointer:",t)}if(this.state.previewElement=this.createPreview(this.state.draggableElement,this.state.itemIds),this.applyDraggingState(!0),this.updatePreview(),this.onDragStart&&this.onDragStart(this.state.itemIds,this.state.draggableElement),window.jvbA11y){const t=this.state.itemIds.length>1?`Started dragging ${this.state.itemIds.length} items`:"Started dragging item";window.jvbA11y.announce(t)}}}handlePointerMove(t){if(!this.state)return;if(this.state.currentPos={x:t.clientX,y:t.clientY},!this.state.active&&this.state.holdTimer){const e=t.clientX-this.state.startPos.x,i=t.clientY-this.state.startPos.y;Math.sqrt(e*e+i*i)>2*this.dragThreshold.distance&&(clearTimeout(this.state.holdTimer),this.startDrag())}if(!this.state.active)return;t.preventDefault(),this.updatePreview();const e=document.elementFromPoint(t.clientX,t.clientY),i=this.findDropTarget(e);i!==this.state.targetElement&&(this.clearTargetHighlight(),this.state.targetElement=i,i&&this.validateDrop(this.state.itemIds,i)&&this.highlightTarget(i))}handlePointerUp(t){if(!this.state)return;if(this.state.holdTimer&&!this.state.active)return void this.cancelDragStart();if(!this.state.active)return void this.cleanup();document.removeEventListener("pointermove",this.onPointerMove),document.removeEventListener("pointerup",this.onPointerUp),document.removeEventListener("pointercancel",this.onPointerCancel);const{itemIds:e,targetElement:i}=this.state;let n=!1;if(i&&this.validateDrop(e,i)&&(this.onDrop(e,i),n=!0),this.cleanup(),this.onDragEnd&&this.onDragEnd(e,n),window.jvbA11y){const t=n?e.length>1?`Moved ${e.length} items`:"Item moved":"Drag cancelled";window.jvbA11y.announce(t)}}handlePointerCancel(t){this.state&&(document.removeEventListener("pointermove",this.onPointerMove),document.removeEventListener("pointerup",this.onPointerUp),document.removeEventListener("pointercancel",this.onPointerCancel),this.state.holdTimer&&(clearTimeout(this.state.holdTimer),this.state.holdTimer=null),this.cleanup(),window.jvbA11y&&window.jvbA11y.announce("Drag cancelled"))}cancelDragStart(){this.state&&this.state.holdTimer&&(clearTimeout(this.state.holdTimer),this.state.holdTimer=null),document.removeEventListener("pointermove",this.onPointerMove),document.removeEventListener("pointerup",this.onPointerUp),document.removeEventListener("pointercancel",this.onPointerCancel),this.cleanup()}createPreview(t,e){let i=window.getTemplate("dragPreview");i||(i=this.createPreviewElement());let n=i.querySelector(".drag-items"),r=n.querySelector(".drag-item");e.forEach((t,e)=>{let i=this.findElementByItemId(t);if(i){let t=i.querySelector(this.previewElement)?.cloneNode(!0);if(t){let e=r.cloneNode(!0);e.appendChild(t),n.append(e)}}}),r.remove();let s=i.querySelector(".drag-count");return s&&(s.textContent="{"+e.length+"}",s.hidden=e.length<=1),document.body.appendChild(i),i}createPreviewElement(){const t=document.createElement("div");t.className="drag-preview";let e=t.cloneNode(!0),i=t.cloneNode(!0),n=t.cloneNode(!0);return n.className="drag-item",e.className="drag-items",e.append(n),i.className="drag-count",t.append(e),t.append(i),t}updatePreview(){this.state.previewElement&&this.state.currentPos&&(this.state.previewElement.style.left=`${this.state.currentPos.x+this.previewOptions.offset.x}px`,this.state.previewElement.style.top=`${this.state.currentPos.y+this.previewOptions.offset.y}px`)}findDropTarget(t){return t?t.closest(this.dropTargetSelector):null}highlightTarget(t){if(t&&(t.classList.add("dragover"),this.state.isMultiDrag&&(t.classList.add("multi-drop"),t.setAttribute("data-item-count",this.state.itemIds.length)),navigator.vibrate)){const t=this.state.isMultiDrag?[25,10,25]:[25];navigator.vibrate(t)}}clearTargetHighlight(){document.querySelectorAll(".dragover").forEach(t=>{t.classList.remove("dragover","multi-drop"),t.removeAttribute("data-item-count")})}applyDraggingState(t){this.state.itemIds.forEach(e=>{const i=this.findElementByItemId(e);i&&i.classList.toggle("dragging",t)})}findElementByItemId(t){const e=document.querySelectorAll(this.draggableSelector);for(const i of e)if(this.getItemId(i)===t)return i;return null}shouldIgnoreElement(t){return t.matches(this.ignoreSelector)}cleanup(){if(this.state&&this.state.holdTimer&&clearTimeout(this.state.holdTimer),this.state&&this.state.pointerTarget&&null!==this.state.pointerId)try{this.state.pointerTarget.releasePointerCapture(this.state.pointerId)}catch(t){}this.clearTargetHighlight(),this.applyDraggingState(!1),this.state&&this.state.previewElement&&this.state.previewElement.remove(),this.state=this.getInitialState()}destroy(){this.cleanup(),document.removeEventListener("pointerdown",this.onPointerDown),document.removeEventListener("pointermove",this.onPointerMove),document.removeEventListener("pointerup",this.onPointerUp),document.removeEventListener("pointercancel",this.onPointerCancel)}};
\ No newline at end of file
diff --git a/assets/js/min/error.min.js b/assets/js/min/error.min.js
index 26f65e8..c1a4d7d 100644
--- a/assets/js/min/error.min.js
+++ b/assets/js/min/error.min.js
@@ -1 +1 @@
-(()=>{class t{constructor(t={}){this.options={apiUrl:"",logToServer:!0,displayNotifications:!0,notificationDuration:5e3,retryEnabled:!0,maxRetries:3,...t},this.retryCount=0}async log(t,e={},r=null){console.error("API Error:",t,e);const o=this.getErrorType(t),n=this.getErrorMessage(t,o);switch(this.options.logToServer&&await this.logErrorToServer(o,n,e),o){case"network":case"server":if(this.options.retryEnabled&&this.retryCount<this.options.maxRetries&&r)return this.retryCount++,this.retryWithBackoff(r);break;case"auth":this.handleAuthError();break;case"rate_limit":return this.handleRateLimitError(r)}return this.options.displayNotifications&&this.displayErrorNotification(n,o,r),r&&this.options.retryEnabled||(this.retryCount=0),{success:!1,error:o,message:n,context:e}}getErrorType(t){if("AbortError"===t.name)return"timeout";if(!navigator.onLine)return"offline";if(t.response){const e=t.response.status;if(e>=400&&e<500)return 401===e||403===e?"auth":429===e?"rate_limit":"client";if(e>=500)return"server"}return"network"}getErrorMessage(t,e){const r={network:"We couldn't connect to the server. Please check your connection and try again.",timeout:"The request took too long to complete. Please try again.",offline:"You appear to be offline. Please check your internet connection.",auth:"Your session may have expired. Please log in again.",rate_limit:"You've made too many requests. Please wait a moment and try again.",server:"We're experiencing technical difficulties. Please try again later.",client:"Something went wrong with your request. Please try again.",unknown:"An unexpected error occurred. Please try again."};return t.response&&t.response.data&&t.response.data.message?t.response.data.message:t.message?t.message:r[e]||r.unknown}async logErrorToServer(t,e,r){try{if(!this.options.apiUrl)return;const o={...r,url:window.location.href,pathname:window.location.pathname,userAgent:navigator.userAgent,timestamp:(new Date).toISOString(),viewport:`${window.innerWidth}x${window.innerHeight}`,component:r.component||this.extractComponentFromStack(r.stack),method:r.method||this.extractMethodFromStack(r.stack),stack:r.stack||r.error?.stack,isLoggedIn:window.auth.isAuthenticated(),source:"frontend"},n=new FormData;n.append("error_type",t),n.append("message",e),n.append("context",JSON.stringify(o)),await fetch(`${this.options.apiUrl}errors/log`,{method:"POST",headers:{"X-WP-Nonce":window.auth.getNonce()},body:n})}catch(t){console.warn("Failed to log error to server",t)}}extractComponentFromStack(t){if(!t)return"Unknown";const e=t.match(/at\s+(\w+)\./);return e?e[1]:"Unknown"}extractMethodFromStack(t){if(!t)return null;const e=t.match(/at\s+\w+\.(\w+)\s+/);return e?e[1]:null}displayErrorNotification(t,e,r){if(window.jvbNotifications){const e=[];return r&&e.push({label:"Try Again",icon:"refresh",action:r}),void window.jvbNotifications.queuePopupNotification({type:"error",message:t,icon:"alert",priority:"high",displayDuration:this.options.notificationDuration,actions:e})}alert(t)}handleAuthError(){window.jvbSettings&&window.jvbSettings.loginUrl?window.location.href=window.jvbSettings.loginUrl:window.location.reload()}async handleRateLimitError(t){const e=2e3*(this.retryCount+1);if(await new Promise((t=>setTimeout(t,e))),t)return this.retryCount++,t()}async retryWithBackoff(t){const e=Math.min(1e3*Math.pow(2,this.retryCount),1e4);return await new Promise((t=>setTimeout(t,e))),t()}resetRetryCount(){this.retryCount=0}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((e=>{"auth-loaded"===e&&(window.jvbError=new t({api:jvbSettings.api,logToServer:!0,displayNotifications:!0,notificationDuration:5e3,retryEnabled:!0,maxRetries:3}))}))}))})();
\ No newline at end of file
+(()=>{class t{constructor(t={}){this.options={apiUrl:"",logToServer:!0,displayNotifications:!0,notificationDuration:5e3,retryEnabled:!0,maxRetries:3,...t},this.retryCount=0}async log(t,e={},r=null){console.error("API Error:",t,e);const o=this.getErrorType(t),n=this.getErrorMessage(t,o);switch(this.options.logToServer&&await this.logErrorToServer(o,n,e),o){case"network":case"server":if(this.options.retryEnabled&&this.retryCount<this.options.maxRetries&&r)return this.retryCount++,this.retryWithBackoff(r);break;case"auth":this.handleAuthError();break;case"rate_limit":return this.handleRateLimitError(r)}return this.options.displayNotifications&&this.displayErrorNotification(n,o,r),r&&this.options.retryEnabled||(this.retryCount=0),{success:!1,error:o,message:n,context:e}}getErrorType(t){if("AbortError"===t.name)return"timeout";if(!navigator.onLine)return"offline";if(t.response){const e=t.response.status;if(e>=400&&e<500)return 401===e||403===e?"auth":429===e?"rate_limit":"client";if(e>=500)return"server"}return"network"}getErrorMessage(t,e){const r={network:"We couldn't connect to the server. Please check your connection and try again.",timeout:"The request took too long to complete. Please try again.",offline:"You appear to be offline. Please check your internet connection.",auth:"Your session may have expired. Please log in again.",rate_limit:"You've made too many requests. Please wait a moment and try again.",server:"We're experiencing technical difficulties. Please try again later.",client:"Something went wrong with your request. Please try again.",unknown:"An unexpected error occurred. Please try again."};return t.response&&t.response.data&&t.response.data.message?t.response.data.message:t.message?t.message:r[e]||r.unknown}async logErrorToServer(t,e,r){try{if(!this.options.apiUrl)return;const o={...r,url:window.location.href,pathname:window.location.pathname,userAgent:navigator.userAgent,timestamp:(new Date).toISOString(),viewport:`${window.innerWidth}x${window.innerHeight}`,component:r.component||this.extractComponentFromStack(r.stack),method:r.method||this.extractMethodFromStack(r.stack),stack:r.stack||r.error?.stack,isLoggedIn:window.auth.isAuthenticated(),source:"frontend"},n=new FormData;n.append("error_type",t),n.append("message",e),n.append("context",JSON.stringify(o)),await fetch(`${this.options.apiUrl}errors/log`,{method:"POST",headers:{"X-WP-Nonce":window.auth.getNonce()},body:n})}catch(t){console.warn("Failed to log error to server",t)}}extractComponentFromStack(t){if(!t)return"Unknown";const e=t.match(/at\s+(\w+)\./);return e?e[1]:"Unknown"}extractMethodFromStack(t){if(!t)return null;const e=t.match(/at\s+\w+\.(\w+)\s+/);return e?e[1]:null}displayErrorNotification(t,e,r){if(window.jvbNotifications){const e=[];return r&&e.push({label:"Try Again",icon:"refresh",action:r}),void window.jvbNotifications.queuePopupNotification({type:"error",message:t,icon:"alert",priority:"high",displayDuration:this.options.notificationDuration,actions:e})}alert(t)}handleAuthError(){window.jvbSettings&&window.jvbSettings.loginUrl?window.location.href=window.jvbSettings.loginUrl:window.location.reload()}async handleRateLimitError(t){const e=2e3*(this.retryCount+1);if(await new Promise(t=>setTimeout(t,e)),t)return this.retryCount++,t()}async retryWithBackoff(t){const e=Math.min(1e3*Math.pow(2,this.retryCount),1e4);return await new Promise(t=>setTimeout(t,e)),t()}resetRetryCount(){this.retryCount=0}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(e=>{"auth-loaded"===e&&(window.jvbError=new t({api:jvbSettings.api,logToServer:!0,displayNotifications:!0,notificationDuration:5e3,retryEnabled:!0,maxRetries:3}))})})})();
\ No newline at end of file
diff --git a/assets/js/min/favourites.min.js b/assets/js/min/favourites.min.js
index c5b1e5a..50aa9fd 100644
--- a/assets/js/min/favourites.min.js
+++ b/assets/js/min/favourites.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){let e=window.jvbStore.register("favourites",{storeName:"favourites",keyPath:"id",endpoint:"favourites",headers:{"X-Action-Nonce":window.auth.getNonce("favourites")},indexes:[{name:"content",keyPath:"content"},{name:"listId",keyPath:"listId"}],TTL:36e4,showLoading:!1,filters:{user:window.auth.getUser(),content:"all",order:"desc",orderby:"date",page:1,all:!0}});this.queue={add:new Map,remove:new Map},this.store=e.favourites,this.store.subscribe(((e,t)=>{e}))}toggleFavourite(e){if(!window.auth.getUser())return void(window.location.href=jvbSettings.redirect+"&action=register&type=favourites");e.classList.toggle("favourited");const t=e.classList.contains("favourited")?"add":"remove",o=e.classList.contains("favourited")?`Added ${e.dataset.type} to favourites.`:`Removed ${e.dataset.type} from favourites.`;window.jvbA11y.announce(o),e.innerHTML=jvbSettings.icons[e.classList.contains("favourited")?"heart-filled":"heart"],this.checkQueue(t,{target_id:e.dataset.id,action:t,type:e.dataset.type}),window.debouncer.schedule("favourites",this.postFavourites.bind(this),200)}checkQueue(e,t){switch(e){case"add":this.queue.remove.has(t.target_id)&&this.queue.remove.delete(t.target_id),this.queue.add.set(t.target_id,t);break;case"remove":this.queue.add.has(t.target_id)&&this.queue.add.delete(t.target_id),this.queue.remove.set(t.target_id,t);break;default:return}}async postFavourites(){console.log(this.queue,"Posting favourites");const e=await window.auth.fetch(`${jvbSettings.api}favourites`,{method:"POST",headers:{"X-Action-Nonce":window.auth.getNonce("favourites")},body:{user:window.auth.getUser(),...this.queue}});if(e.ok){console.log("Posted favourites - clearing queue");for(let e of this.queue.add.entries())await this.store.setItem(e.target_id,e);for(let e of this.queue.remove.entries())await this.store.delete(e.target_id);this.queue.add.clear(),this.queue.remove.clear()}else console.log(await e.json(),"Something went wrong"),window.debouncer.schedule("favourites",this.postFavourites.bind(this),200)}isFavourited(e,t){const o=`${this.userId}_${e}_${t}`;return void 0!==this.store.get(o)}}document.addEventListener("DOMContentLoaded",(function(){window.jvbFavourites=!1,window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbFavourites=new e)}))})),window.toggleFavourite=function(e){window.jvbFavourites()?window.jvbFavourites.toggleFavourite(e):console.log("No Favourites Loaded")},window.isFavourited=function(e,t){if(window.jvbFavourites())return window.jvbFavourites.isFavourited(e,t);console.log("No Favourites Loaded")}})();
\ No newline at end of file
+(()=>{class e{constructor(){let e=window.jvbStore.register("favourites",{storeName:"favourites",keyPath:"id",endpoint:"favourites",headers:{"X-Action-Nonce":window.auth.getNonce("favourites")},indexes:[{name:"content",keyPath:"content"},{name:"listId",keyPath:"listId"}],TTL:36e4,showLoading:!1,filters:{user:window.auth.getUser(),content:"all",order:"desc",orderby:"date",page:1,all:!0}});this.queue={add:new Map,remove:new Map},this.store=e.favourites,this.store.subscribe((e,t)=>{e})}toggleFavourite(e){if(!window.auth.getUser())return void(window.location.href=jvbSettings.redirect+"&action=register&type=favourites");e.classList.toggle("favourited");const t=e.classList.contains("favourited")?"add":"remove",o=e.classList.contains("favourited")?`Added ${e.dataset.type} to favourites.`:`Removed ${e.dataset.type} from favourites.`;window.jvbA11y.announce(o),e.innerHTML=jvbSettings.icons[e.classList.contains("favourited")?"heart-filled":"heart"],this.checkQueue(t,{target_id:e.dataset.id,action:t,type:e.dataset.type}),window.debouncer.schedule("favourites",this.postFavourites.bind(this),200)}checkQueue(e,t){switch(e){case"add":this.queue.remove.has(t.target_id)&&this.queue.remove.delete(t.target_id),this.queue.add.set(t.target_id,t);break;case"remove":this.queue.add.has(t.target_id)&&this.queue.add.delete(t.target_id),this.queue.remove.set(t.target_id,t);break;default:return}}async postFavourites(){console.log(this.queue,"Posting favourites");const e=await window.auth.fetch(`${jvbSettings.api}favourites`,{method:"POST",headers:{"X-Action-Nonce":window.auth.getNonce("favourites")},body:{user:window.auth.getUser(),...this.queue}});if(e.ok){console.log("Posted favourites - clearing queue");for(let e of this.queue.add.entries())await this.store.setItem(e.target_id,e);for(let e of this.queue.remove.entries())await this.store.delete(e.target_id);this.queue.add.clear(),this.queue.remove.clear()}else console.log(await e.json(),"Something went wrong"),window.debouncer.schedule("favourites",this.postFavourites.bind(this),200)}isFavourited(e,t){const o=`${this.userId}_${e}_${t}`;return void 0!==this.store.get(o)}}document.addEventListener("DOMContentLoaded",function(){window.jvbFavourites=!1,window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbFavourites=new e)})}),window.toggleFavourite=function(e){window.jvbFavourites()?window.jvbFavourites.toggleFavourite(e):console.log("No Favourites Loaded")},window.isFavourited=function(e,t){if(window.jvbFavourites())return window.jvbFavourites.isFavourited(e,t);console.log("No Favourites Loaded")}})();
\ No newline at end of file
diff --git a/assets/js/min/favouritesManager.min.js b/assets/js/min/favouritesManager.min.js
index 93d1ad5..ddf9442 100644
--- a/assets/js/min/favouritesManager.min.js
+++ b/assets/js/min/favouritesManager.min.js
@@ -1 +1 @@
-window.favouritesManager=class{constructor(){this.queue=window.jvbQueue,this.loadingManager=window.jvbLoading,this.cache=window.jvbCache,this.a11y=window.jvbA11y,this.error=window.jvbError,this.tabs=new window.jvbTabs(document.querySelector(".replace")),this.config={endpoints:{favourites:"favourites",lists:"favourites/lists",shares:"favourites/lists/shares"},selectors:{container:".favourites.container",itemsTab:'.tab-content[data-tab="items"]',listsTab:'.tab-content[data-tab="lists"]',grid:".item-grid",typeFilters:".type-filters",viewControls:".view-controls",bulkControls:".bulk-controls",selectAll:"#select-all",createListModal:".create-list-modal",addToListModal:".add-to-list-modal",shareListModal:".share-list-modal",noItems:".no-favourites",listContainer:".lists-container",listDetails:".list-details",loader:".favourites-loader"},defaultPage:1,defaultPerPage:24,defaultViewMode:"grid",refreshInterval:6e4,toastDuration:3e3},document.addEventListener("keydown",this.handleKeyDown.bind(this)),this.state={selectedItems:new Set,page:this.config.defaultPage,filter:{type:"all",order:"desc",orderBy:"date_added"},view:{mode:localStorage.getItem("favourites_view")||this.config.defaultViewMode,activeTab:"items"},pagination:{hasMore:!1,totalItems:0,totalPages:0},currentListId:null,loading:!1,initialized:!1},this.initDom(),this.initEvents(),this.loadInitialData(),this.state.initialized=!0}initDom(){this.container=document.querySelector(this.config.selectors.container),this.container?(this.grid=this.container.querySelector(this.config.selectors.grid),this.typeFilters=this.container.querySelector(this.config.selectors.typeFilters),this.viewControls=this.container.querySelector(this.config.selectors.viewControls),this.bulkControls=this.container.querySelector(this.config.selectors.bulkControls),this.listContainer=this.container.querySelector(this.config.selectors.listContainer),this.listDetails=this.container.querySelector(this.config.selectors.listDetails),this.loader=this.container.querySelector(this.config.selectors.loader),this.createListModal=document.querySelector(this.config.selectors.createListModal),this.addToListModal=document.querySelector(this.config.selectors.addToListModal),this.shareListModal=document.querySelector(this.config.selectors.shareListModal),this.grid&&this.state.view.mode&&this.grid.classList.add(`${this.state.view.mode}-view`)):console.warn("Favourites container not found")}initEvents(){if(this.typeFilters&&this.typeFilters.addEventListener("click",(t=>{const e=t.target.closest(".type-filter");e&&this.setFilterType(e.dataset.type)})),this.viewControls&&this.viewControls.addEventListener("click",(t=>{const e=t.target.closest(".view-toggle");e&&this.setView(e.dataset.view)})),this.container){const t=this.container.querySelector(this.config.selectors.selectAll);t&&t.addEventListener("change",(()=>{this.toggleSelectAll(t.checked)})),this.container.addEventListener("change",(t=>{t.target.matches(".item-select input[type=checkbox]")&&this.handleItemSelection(t.target)}));const e=this.container.querySelector(".bulk-action-select"),s=this.container.querySelector(".apply-bulk");e&&s&&s.addEventListener("click",(()=>{this.applyBulkAction(e.value)}));const i=this.container.querySelector(".cancel-bulk");i&&i.addEventListener("click",(()=>{this.clearSelection()}))}this.initModalEvents(),this.container.addEventListener("click",this.handleItemActions.bind(this)),this.grid&&this.setupInfiniteScroll()}initModalEvents(){if(this.createListModal){const t=this.createListModal.querySelector("form");t&&t.addEventListener("submit",(e=>{e.preventDefault(),this.handleCreateList(new FormData(t))}));const e=this.createListModal.querySelector(".cancel");e&&e.addEventListener("click",(()=>{this.createListModal.close()}))}if(this.addToListModal){const t=this.addToListModal.querySelector("form");t&&t.addEventListener("submit",(e=>{e.preventDefault(),this.handleAddToList(new FormData(t))}));const e=this.addToListModal.querySelector(".cancel");e&&e.addEventListener("click",(()=>{this.addToListModal.close()}))}if(this.shareListModal){const t=this.shareListModal.querySelector("form");t&&t.addEventListener("submit",(e=>{e.preventDefault(),this.handleShareList(new FormData(t))}));const e=this.shareListModal.querySelector(".cancel");e&&e.addEventListener("click",(()=>{this.shareListModal.close()}));const s=this.shareListModal.querySelector(".add-email");s&&s.addEventListener("click",(()=>{const e=this.shareListModal.querySelector("#share-email");e&&e.value&&this.handleShareList(new FormData(t))}))}}setupInfiniteScroll(){let t=this.container.querySelector(".scroll-sentinel");t||(t=document.createElement("div"),t.className="scroll-sentinel",t.setAttribute("aria-hidden","true"),this.grid.parentNode.appendChild(t)),new IntersectionObserver((t=>{t.forEach((t=>{t.isIntersecting&&this.state.pagination.hasMore&&!this.state.loading&&(this.state.page++,this.loadFavourites())}))}),{rootMargin:"200px"}).observe(t)}async loadInitialData(){this.loadingManager.show();try{await this.loadFavourites(),this.loadLists().catch((t=>{console.error("Error loading lists:",t)}))}catch(t){this.handleError(t,"loading initial data")}finally{this.loadingManager.hide()}}async loadFavourites(t=!0){if(!this.state.loading)try{this.state.loading=!0,this.loadingManager.show();const e=new URLSearchParams({page:this.state.page,per_page:this.config.defaultPerPage,type:"all"!==this.state.filter.type?this.state.filter.type:"",order:this.state.filter.order,orderby:this.state.filter.orderBy});t&&(this.state.page=1,removeChildren(this.grid),this.grid.classList.remove("empty"));const s=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.favourites}?${e}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"favouritesManager",forceRefresh:!0});return this.renderFavourites(s.favourites||[],this.state.page>1),s.counts&&this.updateTypeFilters(s.counts),s.pagination&&(this.state.pagination={hasMore:s.pagination.has_more,totalItems:s.pagination.total_items,totalPages:s.pagination.total_pages}),s}catch(t){throw this.handleError(t,"loading favourites"),t}finally{this.state.loading=!1,this.loadingManager.hide()}}renderFavourites(t,e=!1){this.grid&&(0!==t.length||e?(this.hideEmptyState(),e||removeChildren(this.grid),t.forEach((t=>{const e=this.createItemElement(t);this.grid.appendChild(e),this.initItemFunctionality(e,t)})),window.jvbA11y&&window.jvbA11y.announce(`${e?"Added":"Loaded"} ${t.length} favourites`)):this.showEmptyState())}createItemElement(t){const e=document.createElement("div");e.className=`item ${t.type} favourited`,e.dataset.id=t.target_id,e.dataset.type=t.type;const s=sanitizeHtml(t.title||!1),i=sanitizeHtml(t.notes||"");let a="";return t.thumbnail&&(a=`\n                <div class="item-thumbnail">\n                    <a href="${t.url}">${t.thumbnail}</a>\n                </div>\n            `),e.innerHTML=`\n            <div class="item-select">\n                <input type="checkbox"\n                   class="favourite-checkbox"\n                    id="select-${t.target_id}"\n                    value="${t.target_id}">\n                <label for="select-${t.target_id}"><span class="screen-reader-text">Select this ${t.type}</span</label>\n            </div>\n\n            <button type="button" class="favourite-button favourited"\n                onclick="toggleFavourite(this)"\n                data-id="${t.target_id}"\n                data-type="${t.type}"\n                title="Remove from favourites">\n                ${jvbSettings.icons["heart-filled"]}\n            </button>\n\n            ${a}\n\n            <div class="item-info">\n                ${s?`<h3><a href="${t.url}">${s}</a></h3>`:`<a href="${t.url}">View Item</a>`}\n\n                ${t.author?`\n                <div class="item-artist">\n                    <span>By ${t.author.name}</span>\n                </div>`:""}\n\n                ${t.taxonomies?.length?`\n                <div class="taxonomy-lists">\n                    ${t.taxonomies.map((t=>`\n                        <div class="taxonomy-group">\n                            ${jvbSettings.icons[t.icon]}\n                            <ul>\n                                ${t.terms.slice(0,3).map((t=>`\n                                    <li>\n                                        <a href="${t.url}" ${t.umami_click}>\n                                            ${t.title}\n                                        </a>\n                                    </li>\n                                `)).join("")}\n                            </ul>\n                        </div>\n                    `)).join("")}\n                </div>\n            `:""}\n\n                <div class="notes-section">\n                    <button type="button" class="toggle-notes" aria-expanded="false">\n                        ${jvbSettings.icons.note||"Notes"}\n                        <span>Notes</span>\n                    </button>\n\n                    <div class="notes-content" hidden>\n                        <textarea class="notes-input"\n                            placeholder="Add notes about this item..."\n                            data-id="${t.target_id}"\n                            data-type="${t.type}">${i}</textarea>\n                        <button type="button" class="save-notes">Save Notes</button>\n                    </div>\n                </div>\n            </div>\n        `,e}initItemFunctionality(t,e){const s=t.querySelector(".toggle-notes"),i=t.querySelector(".notes-content");s&&i&&s.addEventListener("click",(()=>{const t="true"===s.ariaExpanded;s.ariaExpanded=!t.toString(),i.hidden=t,t||i.querySelector("textarea")?.focus()}));const a=t.querySelector(".save-notes"),n=t.querySelector(".notes-input");a&&n&&(a.addEventListener("click",(()=>{this.saveNotes(n)})),n.addEventListener("keydown",(t=>{"Enter"===t.key&&(t.ctrlKey||t.metaKey)&&(t.preventDefault(),this.saveNotes(n))})))}saveNotes(t){if(!t)return;const e=t.value.trim(),s=t.dataset.id,i=t.dataset.type;s&&i&&(this.queue.addToQueue({type:"favourite_notes",data:{type:i,target_id:parseInt(s),notes:e}}),showToast("Notes saved"),this.a11y.announce("Notes saved"))}showEmptyState(t=!1){const e=this.container.querySelector(this.config.selectors.noItems)??this.createEmptyElement;e&&(e.hidden=!1),this.grid&&this.grid.classList.add("empty"),this.a11y.announce("No favourites to show!")}hideEmptyState(){const t=this.container.querySelector(".no-favourites");t&&t.remove(),this.grid&&this.grid.classList.remove("empty")}createEmptyElement(t=!1){const e=document.createElement("div");e.className="no-favourites",e.innerHTML="\n            <h3>♡ BLANK CANVAS ♡</h3>\n            <p>You haven't fallen in love with any pieces... yet!</p>\n            <p>Hit that heart icon when something stops your scroll.</p>\n            <p>Your dream collection is waiting to start.</p>\n        ",this.grid.after(e)}showEmptyListState(t=!1){const e=document.createElement("div");e.className="no-favourites",e.innerHTML="\n            <h3>♡ FULL OF POSSIBILITY ♡</h3>\n            <p>There's nothing in this list yet.</p>\n            <p>Add some gap fillers from the main favourites tab.</p>\n        ",this.grid.after(e),this.grid.classList.add("empty"),this.a11y.announce("No favourites to show!")}async loadMoreItems(){!this.state.loading&&this.state.pagination.hasMore&&(this.state.page+=1,await this.loadFavourites())}updateTypeFilters(t){this.typeFilters&&this.typeFilters.querySelectorAll(".type-filter").forEach((e=>{const s=e.querySelector(".count");if(!s)return;const i=e.dataset.type;if("all"===i){const e=Object.values(t).reduce(((t,e)=>t+(parseInt(e)||0)),0);s.textContent=`(${e})`}else s.textContent=`(${t[i]||0})`}))}setFilterType(t){t!==this.state.filter.type&&(this.typeFilters&&this.typeFilters.querySelectorAll(".type-filter").forEach((e=>{e.classList.toggle("active",e.dataset.type===t),e.setAttribute("aria-selected",e.dataset.type===t)})),this.state.filter.type=t,this.state.page=1,this.loadFavourites(),window.jvbA11y&&window.jvbA11y.announce(`Filtered to show ${"all"===t?"all":t} items`))}setView(t){t!==this.state.view.mode&&(this.viewControls&&this.viewControls.querySelectorAll(".view-toggle").forEach((e=>{const s=e.dataset.view===t;e.setAttribute("aria-pressed",s)})),this.grid&&(this.grid.classList.remove("grid-view","list-view"),this.grid.classList.add(`${t}-view`)),this.state.view.mode=t,localStorage.setItem("favourites_view",t),window.jvbA11y&&window.jvbA11y.announce(`Changed to ${t} view`))}toggleSelectAll(t){const e=this.getVisibleItems();e.forEach((e=>{const s=e.querySelector('.item-select input[type="checkbox"]');s&&(s.checked=t,this.toggleItemSelection(s.value,t))})),this.updateBulkControls(),window.jvbA11y&&window.jvbA11y.announce(t?`Selected all ${e.length} items`:"Deselected all items")}getVisibleItems(){return this.grid?Array.from(this.grid.querySelectorAll(".item:not([hidden])")):[]}toggleItemSelection(t,e){e?this.state.selectedItems.add(t):this.state.selectedItems.delete(t);const s=this.grid.querySelector(`.item[data-id="${t}"]`);s&&s.classList.toggle("selected",e)}handleItemSelection(t){const e=t.checked,s=t.value;if(this.toggleItemSelection(s,e),this.updateBulkControls(),this.updateSelectAllState(),window.jvbA11y){const s=t.closest(".item"),i=s&&s.querySelector("h3")?.textContent||"item";window.jvbA11y.announce(e?`Selected ${i}`:`Deselected ${i}`)}}updateSelectAllState(){const t=this.container.querySelector(this.config.selectors.selectAll);if(!t)return;const e=this.getVisibleItems();if(0===e.length)return t.checked=!1,void(t.indeterminate=!1);const s=e.filter((t=>{const e=t.querySelector('.item-select input[type="checkbox"]');return e&&e.checked})).length;0===s?(t.checked=!1,t.indeterminate=!1):s===e.length?(t.checked=!0,t.indeterminate=!1):(t.checked=!1,t.indeterminate=!0)}updateBulkControls(){if(!this.bulkControls)return;const t=this.bulkControls.querySelector(".bulk-actions");if(!t)return;const e=this.state.selectedItems.size>0;t.hidden=!e;const s=this.bulkControls.querySelector(".selected-count");s&&(s.textContent=e?`${this.state.selectedItems.size} selected`:"")}handleKeyDown(t){"Escape"===t.key&&this.state.selectedItems.size>0&&(t.preventDefault(),this.clearSelection(),window.jvbA11y&&window.jvbA11y.announce("Selection cleared using Escape key"))}clearSelection(){this.state.selectedItems.clear(),this.getVisibleItems().forEach((t=>{const e=t.querySelector('.item-select input[type="checkbox"]');e&&(e.checked=!1),t.classList.remove("selected")}));const t=this.container.querySelector(this.config.selectors.selectAll);t&&(t.checked=!1,t.indeterminate=!1),this.updateBulkControls(),window.jvbA11y&&window.jvbA11y.announce("Selection cleared")}applyBulkAction(t){if(!t||0===this.state.selectedItems.size)return;switch(t){case"unfavourite":this.bulkUnfavourite();break;case"add-to-list":this.showAddToListModal();break;case"create-list":this.showCreateListModal();break;case"add-notes":this.showBulkNotesModal()}const e=this.container.querySelector(".bulk-action-select");e&&(e.value="")}handleItemActions(t){if(t.target.closest(".toggle-notes")){const e=t.target.closest(".toggle-notes"),s="true"===e.getAttribute("aria-expanded"),i=e.closest(".notes-section").querySelector(".notes-content");return e.setAttribute("aria-expanded",!s),i.hidden=s,!s&&i&&i.querySelector("textarea")?.focus(),void t.preventDefault()}if(t.target.closest(".save-notes")){const e=t.target.closest(".save-notes").closest(".notes-content").querySelector("textarea");return e&&this.saveNotes(e),void t.preventDefault()}if(t.target.closest(".view-list")){const e=t.target.closest(".view-list").closest(".list-card");return e&&e.dataset.id&&this.viewList(e.dataset.id),void t.preventDefault()}if(t.target.closest(".share-list")){const e=t.target.closest(".share-list").closest(".list-card");return e&&e.dataset.id&&this.showShareModal(e.dataset.id),void t.preventDefault()}if(t.target.closest(".delete-list")){const e=t.target.closest(".delete-list").closest(".list-card");return e&&e.dataset.id&&this.deleteList(e.dataset.id),void t.preventDefault()}if(t.target.closest(".back-to-lists"))return this.exitListView(),void t.preventDefault()}async loadLists(t=!0){try{this.state.loading=!0,this.loadingManager.show("Loading lists...");const t=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.lists}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"favourite-lists",forceRefresh:!1});return t.lists&&this.renderLists(t.lists),t}catch(t){throw this.handleError(t,"loading lists"),t}finally{this.state.loading=!1,this.loadingManager.hide()}}renderLists(t){if(!this.listContainer)return;if(removeChildren(this.listContainer),!t||0===t.length)return void(this.listContainer.innerHTML='\n                <div class="no-lists">\n                    <h3>No Lists Yet</h3>\n                    <p>Select favourites from the main tab to organize into lists.</p>\n                </div>\n            ');const e=t.owned,s=t.shared;if(e.length>0){const t=document.createElement("details");t.className="lists-section owned-lists",t.open=!0,t.innerHTML="<summary>Your Lists:</summary>",e.forEach((e=>{const s=this.createListCard(e);t.appendChild(s)})),this.listContainer.appendChild(t)}if(s.length>0){const t=document.createElement("details");t.className="lists-section shared-lists",t.innerHTML="<summary>Lists Shared with You:</summary>",s.forEach((e=>{const s=this.createListCard(e);t.appendChild(s)})),this.listContainer.appendChild(t)}}createListCard(t){const e=document.createElement("div");e.className="list-card",e.dataset.id=t.id;const s="1"===t.is_shared;s&&e.classList.add("shared"),t.is_temp&&e.classList.add("temp"),t.is_owner;const i=sanitizeHtml(t.name||"Untitled List"),a=sanitizeHtml(t.description||"");return e.innerHTML=`\n            <div class="list-header">\n                <h3>${i}</h3>\n                <div class="list-actions">\n                    <button type="button" class="view-list" title="View List">\n                        ${jvbSettings.icons?.show||"View"}\n                    </button>\n                    ${s?"":`\n                        <button type="button" class="share-list" title="Share List">\n                            ${jvbSettings.icons?.share||"Share"}\n                        </button>\n                        <button type="button" class="delete-list" title="Delete List">\n                            ${jvbSettings.icons?.delete||"Delete"}\n                        </button>\n                    `}\n                </div>\n            </div>\n\n            ${a?`<p class="list-description">${a}</p>`:""}\n\n            <div class="list-meta">\n                <div class="meta-stats">\n                    <span class="item-count">${t.item_count||0} items</span>\n                    <span class="date">${formatDate(t.created_at)}</span>\n                </div>\n\n\n                ${s?`\n                    <div class="owner-info">\n                        Shared by ${t.owner_name||"another user"}\n                    </div>\n                `:t.share_count>0?`\n                    <div class="share-info">\n                        Shared with ${t.share_count} ${1===t.share_count?"person":"people"}\n                    </div>\n                `:""}\n            </div>\n        `,e}async viewList(t){try{this.state.loading=!0,this.loadingManager.show("Loading list..."),this.state.currentListId=t;const e=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.lists}?id=${t}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"list-item",forceRefresh:!1});if(!e.list)throw new Error("List not found");this.showListDetails(e.list)}catch(t){this.handleError(t,"viewing list")}finally{this.state.loading=!1,this.loadingManager.hide()}}showListDetails(t){this.listDetails&&this.listContainer&&(console.log(t),this.listDetails.querySelector(".list-title").value=t.name||"Untitled List",this.listDetails.querySelector(".list-description").value=t.description||"",t.is_owner?this.listDetails.querySelector(".list-actions")||this.createListActions():this.listDetails.querySelector(".list-actions")?.remove(),removeChildren(this.grid),this.renderFavourites(t.items||[],!1),0===t.items.length&&this.showEmptyListState(),window.jvbA11y&&window.jvbA11y.announce(`Viewing list: ${t.name} with ${t.items?.length||0} items`))}createListActions(){const t=document.createElement("div");t.className="list-actions",t.innerHTML='\n            <button type="button" class="share-list" title="Share List">\n            <i class="icon icon-share-fat"></i>\n            <span>Share</span>\n        </button>\n        <button type="button" class="duplicate-list" title="Duplicate List">\n            <i class="icon icon-copy"></i>\n            <span>Duplicate</span>\n        </button>\n        <button type="button" class="delete-list" title="Delete List">\n            <i class="icon icon-trash"></i>\n            <span>Delete</span>\n        </button>\n        ',this.listDetails.insertBefore(t,this.listDetails.querySelector(".bulk-controls"))}exitListView(){this.listDetails&&this.listContainer&&(this.listDetails.hidden=!0,this.listContainer.hidden=!1,this.container.classList.remove("viewing-list"),this.state.currentListId=null,window.jvbA11y&&window.jvbA11y.announce("Returned to lists view"))}showCreateListModal(){this.createListModal&&(this.createListModal.querySelector("form")?.reset(),this.createListModal.showModal(),setTimeout((()=>{this.createListModal.querySelector("#list-name")?.focus()}),100),window.jvbA11y&&window.jvbA11y.announce("Create list dialog opened"))}showAddToListModal(){this.addToListModal&&(this.populateAddToListModal(),this.addToListModal.showModal(),window.jvbA11y&&window.jvbA11y.announce("Add to list dialog opened"))}async populateAddToListModal(){if(!this.addToListModal)return;const t=this.addToListModal.querySelector(".lists-options");if(t){removeChildren(t);try{const e=(await this.loadLists()).lists.owned;if(0===e.length)return t.innerHTML='\n                    <div class="no-lists">\n                        <p>You don\'t have any lists yet.</p>\n                        <button type="button" class="create-list-button">Create a list</button>\n                    </div>\n                ',void t.querySelector(".create-list-button")?.addEventListener("click",(()=>{this.addToListModal.close(),this.showCreateListModal()}));e.forEach((e=>{const s=document.createElement("div");s.className="list-option",s.innerHTML=`\n                    <input type="checkbox" id="${e.id}" name="list_ids[]" value="${e.id}">\n                    <label for="${e.id}">\n\n                        <span class="list-name">${sanitizeHtml(e.name)}</span>\n                        <span class="item-count">( ${e.item_count||0} items )</span>\n                    </label>\n                `,t.appendChild(s)}))}catch(e){t.innerHTML='\n                <div class="error-message">\n                    <p>Error loading lists. Please try again.</p>\n                </div>\n            ',console.error("Error loading lists for modal:",e)}}}async handleCreateList(t){const e=t.get("list_name"),s=t.get("list_description");if(e)try{this.showLoader("Creating list...");const t=[];this.state.selectedItems.forEach((e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);s&&t.push({type:s.dataset.type,target_id:parseInt(e)})})),this.queue.addToQueue({type:"favourite_list_create",data:{name:e,description:s,items:t}}),showToast(`List "${e}" created`),this.a11y.announce(`List ${e} created with ${t.length} items`),this.createListModal.close(),this.clearSelection(),this.switchTab("lists")}catch(t){this.handleError(t,"creating list")}finally{this.hideLoader()}else showToast("Please enter a list name","error")}async handleAddToList(t){const e=t.getAll("list_ids[]");if(e.length)try{this.showLoader("Adding to list...");const t=[];this.state.selectedItems.forEach((e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);s&&t.push({type:s.dataset.type,target_id:parseInt(e)})})),this.queue.addToQueue({type:"favourite_list_add",data:{list_id:e.join(","),items:t}}),showToast(`Added to ${e.length} ${1===e.length?"list":"lists"}`),this.a11y.announce(`Added ${t.length} items to ${e.length} ${1===e.length?"list":"lists"}`),this.addToListModal.close(),this.clearSelection()}catch(t){this.handleError(t,"adding to list")}finally{this.hideLoader()}else showToast("Please select at least one list","error")}async handleRemoveFromList(t){const e=t.getAll("list_ids[]");if(e.length)try{this.showLoader("Removing from list...");const t=[];this.state.selectedItems.forEach((e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);s&&t.push({type:s.dataset.type,target_id:parseInt(e)})})),this.queue.addToQueue({type:"favourite_list_remove",data:{list_id:e.join(","),items:t}}),showToast(`Removed from ${e.length} ${1===e.length?"list":"lists"}`),this.a11y.announce(`Removed ${t.length} items to ${e.length} ${1===e.length?"list":"lists"}`),this.addToListModal.close(),this.clearSelection()}catch(t){this.handleError(t,"remove from list")}finally{this.hideLoader()}else showToast("Please select at least one list","error")}showShareModal(t){this.shareListModal&&(this.state.currentListId=t,this.shareListModal.querySelector("form")?.reset(),this.loadSharedUsers(t),this.shareListModal.showModal(),setTimeout((()=>{this.shareListModal.querySelector("#share-email")?.focus()}),100),window.jvbA11y&&window.jvbA11y.announce("Share list dialog opened"))}async loadSharedUsers(t){try{const e=this.shareListModal.querySelector(".shared-users");if(!e)return;e.innerHTML='<div class="loading">Loading shared users...</div>';const s=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.lists}?id=${t}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"list-item",forceRefresh:!1});removeChildren(e),s.list&&s.list.shared_users&&s.list.shared_users.length>0?(s.list.shared_users.forEach((t=>{const s=document.createElement("div");s.className=`shared-user ${t.status}`,s.innerHTML=`\n                        <span class="user-email">${t.email}</span>\n                        ${"pending"===t.status?'<span class="pending-badge">Invitation sent</span>':`<span class="permission-badge">${t.permission_type||"view"}</span>`}\n                        <button type="button" class="remove-share" data-email="${t.email}">\n                            ${jvbSettings.icons?.delete||"Remove"}\n                        </button>\n                    `,e.appendChild(s)})),e.querySelectorAll(".remove-share").forEach((t=>{t.addEventListener("click",(()=>{this.unshareList(t.dataset.email)}))}))):e.innerHTML='<div class="no-shares">This list is not shared with anyone yet.</div>'}catch(t){console.error("Error loading shared users:",t)}}async unshareList(t){if(confirm(`Remove ${t}'s access to this list?`))if(this.state.currentListId)try{this.showLoader("Removing access..."),this.queue.addToQueue({type:"favourite_list_unshare",data:{list_id:parseInt(this.state.currentListId),email:t}});const e=Array.from(this.shareListModal.querySelectorAll(".shared-user")).find((e=>e.querySelector(".user-email")?.textContent===t));e&&(e.classList.add("removing"),setTimeout((()=>{if(e.remove(),0===this.shareListModal.querySelectorAll(".shared-user").length){const t=this.shareListModal.querySelector(".shared-users");t&&(t.innerHTML='<div class="no-shares">This list is not shared with anyone yet.</div>')}}),300)),showToast(`Removed ${t}'s access`),this.a11y.announce(`Removed ${t}'s access to list`)}catch(t){this.handleError(t,"removing share access")}finally{this.hideLoader()}else showToast("No list selected","error")}async deleteList(t){if(confirm("Are you sure you want to delete this list? This cannot be undone."))try{this.showLoader("Deleting list..."),this.queue.addToQueue({type:"favourite_list_delete",data:{list_id:parseInt(t)}});const e=this.container.querySelector(`.list-card[data-id="${t}"]`);e&&(e.classList.add("removing"),setTimeout((()=>{e.remove(),0===this.container.querySelectorAll(".list-card").length&&(this.listContainer.innerHTML='\n                                <div class="no-lists">\n                                    <h3>No Lists Yet</h3>\n                                    <p>Create your first list to organize your favourites!</p>\n                                </div>\n                            ')}),300)),showToast("List deleted"),this.a11y.announce("List deleted")}catch(t){this.handleError(t,"deleting list")}finally{this.hideLoader()}}showBulkNotesModal(){let t=document.querySelector(".bulk-notes-modal");t||(t=document.createElement("dialog"),t.className="bulk-notes-modal",t.innerHTML='\n                <form method="dialog" data-save="favourites">\n                    <h2>Add Notes to Selected Items</h2>\n\n                    <div class="field">\n                        <label for="bulk-notes">Notes (will be applied to all selected items)</label>\n                        <textarea id="bulk-notes" name="bulk_notes" rows="5"></textarea>\n                    </div>\n\n                    <div class="actions">\n                        <button type="button" class="cancel">Cancel</button>\n                        <button type="submit" class="save">Save Notes</button>\n                    </div>\n                </form>\n            ',document.body.appendChild(t),t.querySelector("form").addEventListener("submit",(e=>{e.preventDefault();const s=t.querySelector("#bulk-notes").value;this.saveBulkNotes(s),t.close()})),t.querySelector(".cancel").addEventListener("click",(()=>{t.close()}))),t.querySelector("form")?.reset(),t.showModal(),setTimeout((()=>{t.querySelector("#bulk-notes")?.focus()}),100),window.jvbA11y&&window.jvbA11y.announce("Add notes dialog opened")}saveBulkNotes(t){if(t)try{this.showLoader("Saving notes...");let e=Array.from(this.state.selectedItems.values());this.queue.addToQueue({type:"favourite_notes",data:{target_id:e.join(","),notes:t}}),showToast(`Notes saved for ${e.length} items`),this.a11y.announce(`Notes saved for ${e.length} items`),this.clearSelection()}catch(t){this.handleError(t,"saving bulk notes")}finally{this.hideLoader()}}async bulkUnfavourite(){if(confirm("Are you sure you want to remove these items from your favourites?"))try{this.showLoader("Removing from favourites...");const t=[];this.state.selectedItems.forEach((e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);if(!s)return;const i=s.dataset.type;t.push({target_id:parseInt(e),type:i,action:"remove"})})),this.queue.addToQueue({type:"favourite_toggle",data:t});const e=[];this.state.selectedItems.forEach((t=>{const s=this.grid.querySelector(`.item[data-id="${t}"]`);if(!s)return;s.style.opacity="0",s.style.transform="scale(0.9)",s.style.transition="opacity 0.3s ease, transform 0.3s ease";const i=new Promise((t=>{setTimeout((()=>{s.remove(),t()}),300)}));e.push(i)})),await Promise.all(e),0===this.grid.children.length&&this.showEmptyState(),this.clearSelection(),showToast(`Removed ${t.length} items from favourites`),this.a11y.announce(`Removed ${t.length} items from favourites`)}catch(t){this.handleError(t,"removing favourites")}finally{this.hideLoader()}}async handleShareList(t){const e=t.get("share_email");if(e)if(this.validateEmail(e))try{this.showLoader("Sharing list..."),this.queue.addToQueue({type:"favourite_list_share",data:{list_id:parseInt(this.state.currentListId),email:e,permission_type:"view"}}),this.shareListModal.querySelector("#share-email").value="",this.loadSharedUsers(this.state.currentListId),showToast(`Invitation sent to ${e}`),this.a11y.announce(`Invitation sent to ${e}`)}catch(t){this.handleError(t,"sharing list")}finally{this.hideLoader()}else showToast("Please enter a valid email address","error");else showToast("Please enter an email address","error")}showLoader(t="Loading..."){if(!this.loader)return;const e=this.loader.querySelector(".loader-message");e&&(e.textContent=t),this.loader.hidden=!1}hideLoader(){this.loader&&(this.loader.hidden=!0)}showToast(t,e){window.jvbNotifications.showToast(t,e)}handleError(t,e){console.error(`Favourites error (${e}):`,t),showToast(`Error ${e}: ${t.message||"Something went wrong"}`,"error"),window.jvbError&&window.jvbError.log(t,{component:"FavouritesManager",action:e}),window.jvbA11y&&window.jvbA11y.announce(`Error ${e}. ${t.message||"Please try again."}`)}validateEmail(t){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)}};
\ No newline at end of file
+window.favouritesManager=class{constructor(){this.queue=window.jvbQueue,this.loadingManager=window.jvbLoading,this.cache=window.jvbCache,this.a11y=window.jvbA11y,this.error=window.jvbError,this.tabs=new window.jvbTabs(document.querySelector(".replace")),this.config={endpoints:{favourites:"favourites",lists:"favourites/lists",shares:"favourites/lists/shares"},selectors:{container:".favourites.container",itemsTab:'.tab-content[data-tab="items"]',listsTab:'.tab-content[data-tab="lists"]',grid:".item-grid",typeFilters:".type-filters",viewControls:".view-controls",bulkControls:".bulk-controls",selectAll:"#select-all",createListModal:".create-list-modal",addToListModal:".add-to-list-modal",shareListModal:".share-list-modal",noItems:".no-favourites",listContainer:".lists-container",listDetails:".list-details",loader:".favourites-loader"},defaultPage:1,defaultPerPage:24,defaultViewMode:"grid",refreshInterval:6e4,toastDuration:3e3},document.addEventListener("keydown",this.handleKeyDown.bind(this)),this.state={selectedItems:new Set,page:this.config.defaultPage,filter:{type:"all",order:"desc",orderBy:"date_added"},view:{mode:localStorage.getItem("favourites_view")||this.config.defaultViewMode,activeTab:"items"},pagination:{hasMore:!1,totalItems:0,totalPages:0},currentListId:null,loading:!1,initialized:!1},this.initDom(),this.initEvents(),this.loadInitialData(),this.state.initialized=!0}initDom(){this.container=document.querySelector(this.config.selectors.container),this.container?(this.grid=this.container.querySelector(this.config.selectors.grid),this.typeFilters=this.container.querySelector(this.config.selectors.typeFilters),this.viewControls=this.container.querySelector(this.config.selectors.viewControls),this.bulkControls=this.container.querySelector(this.config.selectors.bulkControls),this.listContainer=this.container.querySelector(this.config.selectors.listContainer),this.listDetails=this.container.querySelector(this.config.selectors.listDetails),this.loader=this.container.querySelector(this.config.selectors.loader),this.createListModal=document.querySelector(this.config.selectors.createListModal),this.addToListModal=document.querySelector(this.config.selectors.addToListModal),this.shareListModal=document.querySelector(this.config.selectors.shareListModal),this.grid&&this.state.view.mode&&this.grid.classList.add(`${this.state.view.mode}-view`)):console.warn("Favourites container not found")}initEvents(){if(this.typeFilters&&this.typeFilters.addEventListener("click",t=>{const e=t.target.closest(".type-filter");e&&this.setFilterType(e.dataset.type)}),this.viewControls&&this.viewControls.addEventListener("click",t=>{const e=t.target.closest(".view-toggle");e&&this.setView(e.dataset.view)}),this.container){const t=this.container.querySelector(this.config.selectors.selectAll);t&&t.addEventListener("change",()=>{this.toggleSelectAll(t.checked)}),this.container.addEventListener("change",t=>{t.target.matches(".item-select input[type=checkbox]")&&this.handleItemSelection(t.target)});const e=this.container.querySelector(".bulk-action-select"),s=this.container.querySelector(".apply-bulk");e&&s&&s.addEventListener("click",()=>{this.applyBulkAction(e.value)});const i=this.container.querySelector(".cancel-bulk");i&&i.addEventListener("click",()=>{this.clearSelection()})}this.initModalEvents(),this.container.addEventListener("click",this.handleItemActions.bind(this)),this.grid&&this.setupInfiniteScroll()}initModalEvents(){if(this.createListModal){const t=this.createListModal.querySelector("form");t&&t.addEventListener("submit",e=>{e.preventDefault(),this.handleCreateList(new FormData(t))});const e=this.createListModal.querySelector(".cancel");e&&e.addEventListener("click",()=>{this.createListModal.close()})}if(this.addToListModal){const t=this.addToListModal.querySelector("form");t&&t.addEventListener("submit",e=>{e.preventDefault(),this.handleAddToList(new FormData(t))});const e=this.addToListModal.querySelector(".cancel");e&&e.addEventListener("click",()=>{this.addToListModal.close()})}if(this.shareListModal){const t=this.shareListModal.querySelector("form");t&&t.addEventListener("submit",e=>{e.preventDefault(),this.handleShareList(new FormData(t))});const e=this.shareListModal.querySelector(".cancel");e&&e.addEventListener("click",()=>{this.shareListModal.close()});const s=this.shareListModal.querySelector(".add-email");s&&s.addEventListener("click",()=>{const e=this.shareListModal.querySelector("#share-email");e&&e.value&&this.handleShareList(new FormData(t))})}}setupInfiniteScroll(){let t=this.container.querySelector(".scroll-sentinel");t||(t=document.createElement("div"),t.className="scroll-sentinel",t.setAttribute("aria-hidden","true"),this.grid.parentNode.appendChild(t)),new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting&&this.state.pagination.hasMore&&!this.state.loading&&(this.state.page++,this.loadFavourites())})},{rootMargin:"200px"}).observe(t)}async loadInitialData(){this.loadingManager.show();try{await this.loadFavourites(),this.loadLists().catch(t=>{console.error("Error loading lists:",t)})}catch(t){this.handleError(t,"loading initial data")}finally{this.loadingManager.hide()}}async loadFavourites(t=!0){if(!this.state.loading)try{this.state.loading=!0,this.loadingManager.show();const e=new URLSearchParams({page:this.state.page,per_page:this.config.defaultPerPage,type:"all"!==this.state.filter.type?this.state.filter.type:"",order:this.state.filter.order,orderby:this.state.filter.orderBy});t&&(this.state.page=1,removeChildren(this.grid),this.grid.classList.remove("empty"));const s=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.favourites}?${e}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"favouritesManager",forceRefresh:!0});return this.renderFavourites(s.favourites||[],this.state.page>1),s.counts&&this.updateTypeFilters(s.counts),s.pagination&&(this.state.pagination={hasMore:s.pagination.has_more,totalItems:s.pagination.total_items,totalPages:s.pagination.total_pages}),s}catch(t){throw this.handleError(t,"loading favourites"),t}finally{this.state.loading=!1,this.loadingManager.hide()}}renderFavourites(t,e=!1){this.grid&&(0!==t.length||e?(this.hideEmptyState(),e||removeChildren(this.grid),t.forEach(t=>{const e=this.createItemElement(t);this.grid.appendChild(e),this.initItemFunctionality(e,t)}),window.jvbA11y&&window.jvbA11y.announce(`${e?"Added":"Loaded"} ${t.length} favourites`)):this.showEmptyState())}createItemElement(t){const e=document.createElement("div");e.className=`item ${t.type} favourited`,e.dataset.id=t.target_id,e.dataset.type=t.type;const s=sanitizeHtml(t.title||!1),i=sanitizeHtml(t.notes||"");let a="";return t.thumbnail&&(a=`\n                <div class="item-thumbnail">\n                    <a href="${t.url}">${t.thumbnail}</a>\n                </div>\n            `),e.innerHTML=`\n            <div class="item-select">\n                <input type="checkbox"\n                   class="favourite-checkbox"\n                    id="select-${t.target_id}"\n                    value="${t.target_id}">\n                <label for="select-${t.target_id}"><span class="screen-reader-text">Select this ${t.type}</span</label>\n            </div>\n\n            <button type="button" class="favourite-button favourited"\n                onclick="toggleFavourite(this)"\n                data-id="${t.target_id}"\n                data-type="${t.type}"\n                title="Remove from favourites">\n                ${jvbSettings.icons["heart-filled"]}\n            </button>\n\n            ${a}\n\n            <div class="item-info">\n                ${s?`<h3><a href="${t.url}">${s}</a></h3>`:`<a href="${t.url}">View Item</a>`}\n\n                ${t.author?`\n                <div class="item-artist">\n                    <span>By ${t.author.name}</span>\n                </div>`:""}\n\n                ${t.taxonomies?.length?`\n                <div class="taxonomy-lists">\n                    ${t.taxonomies.map(t=>`\n                        <div class="taxonomy-group">\n                            ${jvbSettings.icons[t.icon]}\n                            <ul>\n                                ${t.terms.slice(0,3).map(t=>`\n                                    <li>\n                                        <a href="${t.url}" ${t.umami_click}>\n                                            ${t.title}\n                                        </a>\n                                    </li>\n                                `).join("")}\n                            </ul>\n                        </div>\n                    `).join("")}\n                </div>\n            `:""}\n\n                <div class="notes-section">\n                    <button type="button" class="toggle-notes" aria-expanded="false">\n                        ${jvbSettings.icons.note||"Notes"}\n                        <span>Notes</span>\n                    </button>\n\n                    <div class="notes-content" hidden>\n                        <textarea class="notes-input"\n                            placeholder="Add notes about this item..."\n                            data-id="${t.target_id}"\n                            data-type="${t.type}">${i}</textarea>\n                        <button type="button" class="save-notes">Save Notes</button>\n                    </div>\n                </div>\n            </div>\n        `,e}initItemFunctionality(t,e){const s=t.querySelector(".toggle-notes"),i=t.querySelector(".notes-content");s&&i&&s.addEventListener("click",()=>{const t="true"===s.ariaExpanded;s.ariaExpanded=!t.toString(),i.hidden=t,t||i.querySelector("textarea")?.focus()});const a=t.querySelector(".save-notes"),n=t.querySelector(".notes-input");a&&n&&(a.addEventListener("click",()=>{this.saveNotes(n)}),n.addEventListener("keydown",t=>{"Enter"===t.key&&(t.ctrlKey||t.metaKey)&&(t.preventDefault(),this.saveNotes(n))}))}saveNotes(t){if(!t)return;const e=t.value.trim(),s=t.dataset.id,i=t.dataset.type;s&&i&&(this.queue.addToQueue({type:"favourite_notes",data:{type:i,target_id:parseInt(s),notes:e}}),showToast("Notes saved"),this.a11y.announce("Notes saved"))}showEmptyState(t=!1){const e=this.container.querySelector(this.config.selectors.noItems)??this.createEmptyElement;e&&(e.hidden=!1),this.grid&&this.grid.classList.add("empty"),this.a11y.announce("No favourites to show!")}hideEmptyState(){const t=this.container.querySelector(".no-favourites");t&&t.remove(),this.grid&&this.grid.classList.remove("empty")}createEmptyElement(t=!1){const e=document.createElement("div");e.className="no-favourites",e.innerHTML="\n            <h3>♡ BLANK CANVAS ♡</h3>\n            <p>You haven't fallen in love with any pieces... yet!</p>\n            <p>Hit that heart icon when something stops your scroll.</p>\n            <p>Your dream collection is waiting to start.</p>\n        ",this.grid.after(e)}showEmptyListState(t=!1){const e=document.createElement("div");e.className="no-favourites",e.innerHTML="\n            <h3>♡ FULL OF POSSIBILITY ♡</h3>\n            <p>There's nothing in this list yet.</p>\n            <p>Add some gap fillers from the main favourites tab.</p>\n        ",this.grid.after(e),this.grid.classList.add("empty"),this.a11y.announce("No favourites to show!")}async loadMoreItems(){!this.state.loading&&this.state.pagination.hasMore&&(this.state.page+=1,await this.loadFavourites())}updateTypeFilters(t){this.typeFilters&&this.typeFilters.querySelectorAll(".type-filter").forEach(e=>{const s=e.querySelector(".count");if(!s)return;const i=e.dataset.type;if("all"===i){const e=Object.values(t).reduce((t,e)=>t+(parseInt(e)||0),0);s.textContent=`(${e})`}else s.textContent=`(${t[i]||0})`})}setFilterType(t){t!==this.state.filter.type&&(this.typeFilters&&this.typeFilters.querySelectorAll(".type-filter").forEach(e=>{e.classList.toggle("active",e.dataset.type===t),e.setAttribute("aria-selected",e.dataset.type===t)}),this.state.filter.type=t,this.state.page=1,this.loadFavourites(),window.jvbA11y&&window.jvbA11y.announce(`Filtered to show ${"all"===t?"all":t} items`))}setView(t){t!==this.state.view.mode&&(this.viewControls&&this.viewControls.querySelectorAll(".view-toggle").forEach(e=>{const s=e.dataset.view===t;e.setAttribute("aria-pressed",s)}),this.grid&&(this.grid.classList.remove("grid-view","list-view"),this.grid.classList.add(`${t}-view`)),this.state.view.mode=t,localStorage.setItem("favourites_view",t),window.jvbA11y&&window.jvbA11y.announce(`Changed to ${t} view`))}toggleSelectAll(t){const e=this.getVisibleItems();e.forEach(e=>{const s=e.querySelector('.item-select input[type="checkbox"]');s&&(s.checked=t,this.toggleItemSelection(s.value,t))}),this.updateBulkControls(),window.jvbA11y&&window.jvbA11y.announce(t?`Selected all ${e.length} items`:"Deselected all items")}getVisibleItems(){return this.grid?Array.from(this.grid.querySelectorAll(".item:not([hidden])")):[]}toggleItemSelection(t,e){e?this.state.selectedItems.add(t):this.state.selectedItems.delete(t);const s=this.grid.querySelector(`.item[data-id="${t}"]`);s&&s.classList.toggle("selected",e)}handleItemSelection(t){const e=t.checked,s=t.value;if(this.toggleItemSelection(s,e),this.updateBulkControls(),this.updateSelectAllState(),window.jvbA11y){const s=t.closest(".item"),i=s&&s.querySelector("h3")?.textContent||"item";window.jvbA11y.announce(e?`Selected ${i}`:`Deselected ${i}`)}}updateSelectAllState(){const t=this.container.querySelector(this.config.selectors.selectAll);if(!t)return;const e=this.getVisibleItems();if(0===e.length)return t.checked=!1,void(t.indeterminate=!1);const s=e.filter(t=>{const e=t.querySelector('.item-select input[type="checkbox"]');return e&&e.checked}).length;0===s?(t.checked=!1,t.indeterminate=!1):s===e.length?(t.checked=!0,t.indeterminate=!1):(t.checked=!1,t.indeterminate=!0)}updateBulkControls(){if(!this.bulkControls)return;const t=this.bulkControls.querySelector(".bulk-actions");if(!t)return;const e=this.state.selectedItems.size>0;t.hidden=!e;const s=this.bulkControls.querySelector(".selected-count");s&&(s.textContent=e?`${this.state.selectedItems.size} selected`:"")}handleKeyDown(t){"Escape"===t.key&&this.state.selectedItems.size>0&&(t.preventDefault(),this.clearSelection(),window.jvbA11y&&window.jvbA11y.announce("Selection cleared using Escape key"))}clearSelection(){this.state.selectedItems.clear(),this.getVisibleItems().forEach(t=>{const e=t.querySelector('.item-select input[type="checkbox"]');e&&(e.checked=!1),t.classList.remove("selected")});const t=this.container.querySelector(this.config.selectors.selectAll);t&&(t.checked=!1,t.indeterminate=!1),this.updateBulkControls(),window.jvbA11y&&window.jvbA11y.announce("Selection cleared")}applyBulkAction(t){if(!t||0===this.state.selectedItems.size)return;switch(t){case"unfavourite":this.bulkUnfavourite();break;case"add-to-list":this.showAddToListModal();break;case"create-list":this.showCreateListModal();break;case"add-notes":this.showBulkNotesModal()}const e=this.container.querySelector(".bulk-action-select");e&&(e.value="")}handleItemActions(t){if(t.target.closest(".toggle-notes")){const e=t.target.closest(".toggle-notes"),s="true"===e.getAttribute("aria-expanded"),i=e.closest(".notes-section").querySelector(".notes-content");return e.setAttribute("aria-expanded",!s),i.hidden=s,!s&&i&&i.querySelector("textarea")?.focus(),void t.preventDefault()}if(t.target.closest(".save-notes")){const e=t.target.closest(".save-notes").closest(".notes-content").querySelector("textarea");return e&&this.saveNotes(e),void t.preventDefault()}if(t.target.closest(".view-list")){const e=t.target.closest(".view-list").closest(".list-card");return e&&e.dataset.id&&this.viewList(e.dataset.id),void t.preventDefault()}if(t.target.closest(".share-list")){const e=t.target.closest(".share-list").closest(".list-card");return e&&e.dataset.id&&this.showShareModal(e.dataset.id),void t.preventDefault()}if(t.target.closest(".delete-list")){const e=t.target.closest(".delete-list").closest(".list-card");return e&&e.dataset.id&&this.deleteList(e.dataset.id),void t.preventDefault()}if(t.target.closest(".back-to-lists"))return this.exitListView(),void t.preventDefault()}async loadLists(t=!0){try{this.state.loading=!0,this.loadingManager.show("Loading lists...");const t=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.lists}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"favourite-lists",forceRefresh:!1});return t.lists&&this.renderLists(t.lists),t}catch(t){throw this.handleError(t,"loading lists"),t}finally{this.state.loading=!1,this.loadingManager.hide()}}renderLists(t){if(!this.listContainer)return;if(removeChildren(this.listContainer),!t||0===t.length)return void(this.listContainer.innerHTML='\n                <div class="no-lists">\n                    <h3>No Lists Yet</h3>\n                    <p>Select favourites from the main tab to organize into lists.</p>\n                </div>\n            ');const e=t.owned,s=t.shared;if(e.length>0){const t=document.createElement("details");t.className="lists-section owned-lists",t.open=!0,t.innerHTML="<summary>Your Lists:</summary>",e.forEach(e=>{const s=this.createListCard(e);t.appendChild(s)}),this.listContainer.appendChild(t)}if(s.length>0){const t=document.createElement("details");t.className="lists-section shared-lists",t.innerHTML="<summary>Lists Shared with You:</summary>",s.forEach(e=>{const s=this.createListCard(e);t.appendChild(s)}),this.listContainer.appendChild(t)}}createListCard(t){const e=document.createElement("div");e.className="list-card",e.dataset.id=t.id;const s="1"===t.is_shared;s&&e.classList.add("shared"),t.is_temp&&e.classList.add("temp"),t.is_owner;const i=sanitizeHtml(t.name||"Untitled List"),a=sanitizeHtml(t.description||"");return e.innerHTML=`\n            <div class="list-header">\n                <h3>${i}</h3>\n                <div class="list-actions">\n                    <button type="button" class="view-list" title="View List">\n                        ${jvbSettings.icons?.show||"View"}\n                    </button>\n                    ${s?"":`\n                        <button type="button" class="share-list" title="Share List">\n                            ${jvbSettings.icons?.share||"Share"}\n                        </button>\n                        <button type="button" class="delete-list" title="Delete List">\n                            ${jvbSettings.icons?.delete||"Delete"}\n                        </button>\n                    `}\n                </div>\n            </div>\n\n            ${a?`<p class="list-description">${a}</p>`:""}\n\n            <div class="list-meta">\n                <div class="meta-stats">\n                    <span class="item-count">${t.item_count||0} items</span>\n                    <span class="date">${formatDate(t.created_at)}</span>\n                </div>\n\n\n                ${s?`\n                    <div class="owner-info">\n                        Shared by ${t.owner_name||"another user"}\n                    </div>\n                `:t.share_count>0?`\n                    <div class="share-info">\n                        Shared with ${t.share_count} ${1===t.share_count?"person":"people"}\n                    </div>\n                `:""}\n            </div>\n        `,e}async viewList(t){try{this.state.loading=!0,this.loadingManager.show("Loading list..."),this.state.currentListId=t;const e=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.lists}?id=${t}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"list-item",forceRefresh:!1});if(!e.list)throw new Error("List not found");this.showListDetails(e.list)}catch(t){this.handleError(t,"viewing list")}finally{this.state.loading=!1,this.loadingManager.hide()}}showListDetails(t){this.listDetails&&this.listContainer&&(console.log(t),this.listDetails.querySelector(".list-title").value=t.name||"Untitled List",this.listDetails.querySelector(".list-description").value=t.description||"",t.is_owner?this.listDetails.querySelector(".list-actions")||this.createListActions():this.listDetails.querySelector(".list-actions")?.remove(),removeChildren(this.grid),this.renderFavourites(t.items||[],!1),0===t.items.length&&this.showEmptyListState(),window.jvbA11y&&window.jvbA11y.announce(`Viewing list: ${t.name} with ${t.items?.length||0} items`))}createListActions(){const t=document.createElement("div");t.className="list-actions",t.innerHTML='\n            <button type="button" class="share-list" title="Share List">\n            <i class="icon icon-share-fat"></i>\n            <span>Share</span>\n        </button>\n        <button type="button" class="duplicate-list" title="Duplicate List">\n            <i class="icon icon-copy"></i>\n            <span>Duplicate</span>\n        </button>\n        <button type="button" class="delete-list" title="Delete List">\n            <i class="icon icon-trash"></i>\n            <span>Delete</span>\n        </button>\n        ',this.listDetails.insertBefore(t,this.listDetails.querySelector(".bulk-controls"))}exitListView(){this.listDetails&&this.listContainer&&(this.listDetails.hidden=!0,this.listContainer.hidden=!1,this.container.classList.remove("viewing-list"),this.state.currentListId=null,window.jvbA11y&&window.jvbA11y.announce("Returned to lists view"))}showCreateListModal(){this.createListModal&&(this.createListModal.querySelector("form")?.reset(),this.createListModal.showModal(),setTimeout(()=>{this.createListModal.querySelector("#list-name")?.focus()},100),window.jvbA11y&&window.jvbA11y.announce("Create list dialog opened"))}showAddToListModal(){this.addToListModal&&(this.populateAddToListModal(),this.addToListModal.showModal(),window.jvbA11y&&window.jvbA11y.announce("Add to list dialog opened"))}async populateAddToListModal(){if(!this.addToListModal)return;const t=this.addToListModal.querySelector(".lists-options");if(t){removeChildren(t);try{const e=(await this.loadLists()).lists.owned;if(0===e.length)return t.innerHTML='\n                    <div class="no-lists">\n                        <p>You don\'t have any lists yet.</p>\n                        <button type="button" class="create-list-button">Create a list</button>\n                    </div>\n                ',void t.querySelector(".create-list-button")?.addEventListener("click",()=>{this.addToListModal.close(),this.showCreateListModal()});e.forEach(e=>{const s=document.createElement("div");s.className="list-option",s.innerHTML=`\n                    <input type="checkbox" id="${e.id}" name="list_ids[]" value="${e.id}">\n                    <label for="${e.id}">\n\n                        <span class="list-name">${sanitizeHtml(e.name)}</span>\n                        <span class="item-count">( ${e.item_count||0} items )</span>\n                    </label>\n                `,t.appendChild(s)})}catch(e){t.innerHTML='\n                <div class="error-message">\n                    <p>Error loading lists. Please try again.</p>\n                </div>\n            ',console.error("Error loading lists for modal:",e)}}}async handleCreateList(t){const e=t.get("list_name"),s=t.get("list_description");if(e)try{this.showLoader("Creating list...");const t=[];this.state.selectedItems.forEach(e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);s&&t.push({type:s.dataset.type,target_id:parseInt(e)})}),this.queue.addToQueue({type:"favourite_list_create",data:{name:e,description:s,items:t}}),showToast(`List "${e}" created`),this.a11y.announce(`List ${e} created with ${t.length} items`),this.createListModal.close(),this.clearSelection(),this.switchTab("lists")}catch(t){this.handleError(t,"creating list")}finally{this.hideLoader()}else showToast("Please enter a list name","error")}async handleAddToList(t){const e=t.getAll("list_ids[]");if(e.length)try{this.showLoader("Adding to list...");const t=[];this.state.selectedItems.forEach(e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);s&&t.push({type:s.dataset.type,target_id:parseInt(e)})}),this.queue.addToQueue({type:"favourite_list_add",data:{list_id:e.join(","),items:t}}),showToast(`Added to ${e.length} ${1===e.length?"list":"lists"}`),this.a11y.announce(`Added ${t.length} items to ${e.length} ${1===e.length?"list":"lists"}`),this.addToListModal.close(),this.clearSelection()}catch(t){this.handleError(t,"adding to list")}finally{this.hideLoader()}else showToast("Please select at least one list","error")}async handleRemoveFromList(t){const e=t.getAll("list_ids[]");if(e.length)try{this.showLoader("Removing from list...");const t=[];this.state.selectedItems.forEach(e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);s&&t.push({type:s.dataset.type,target_id:parseInt(e)})}),this.queue.addToQueue({type:"favourite_list_remove",data:{list_id:e.join(","),items:t}}),showToast(`Removed from ${e.length} ${1===e.length?"list":"lists"}`),this.a11y.announce(`Removed ${t.length} items to ${e.length} ${1===e.length?"list":"lists"}`),this.addToListModal.close(),this.clearSelection()}catch(t){this.handleError(t,"remove from list")}finally{this.hideLoader()}else showToast("Please select at least one list","error")}showShareModal(t){this.shareListModal&&(this.state.currentListId=t,this.shareListModal.querySelector("form")?.reset(),this.loadSharedUsers(t),this.shareListModal.showModal(),setTimeout(()=>{this.shareListModal.querySelector("#share-email")?.focus()},100),window.jvbA11y&&window.jvbA11y.announce("Share list dialog opened"))}async loadSharedUsers(t){try{const e=this.shareListModal.querySelector(".shared-users");if(!e)return;e.innerHTML='<div class="loading">Loading shared users...</div>';const s=await this.cache.fetchWithCache(`${jvbSettings.api}${this.config.endpoints.lists}?id=${t}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("favourites")}},{context:"list-item",forceRefresh:!1});removeChildren(e),s.list&&s.list.shared_users&&s.list.shared_users.length>0?(s.list.shared_users.forEach(t=>{const s=document.createElement("div");s.className=`shared-user ${t.status}`,s.innerHTML=`\n                        <span class="user-email">${t.email}</span>\n                        ${"pending"===t.status?'<span class="pending-badge">Invitation sent</span>':`<span class="permission-badge">${t.permission_type||"view"}</span>`}\n                        <button type="button" class="remove-share" data-email="${t.email}">\n                            ${jvbSettings.icons?.delete||"Remove"}\n                        </button>\n                    `,e.appendChild(s)}),e.querySelectorAll(".remove-share").forEach(t=>{t.addEventListener("click",()=>{this.unshareList(t.dataset.email)})})):e.innerHTML='<div class="no-shares">This list is not shared with anyone yet.</div>'}catch(t){console.error("Error loading shared users:",t)}}async unshareList(t){if(confirm(`Remove ${t}'s access to this list?`))if(this.state.currentListId)try{this.showLoader("Removing access..."),this.queue.addToQueue({type:"favourite_list_unshare",data:{list_id:parseInt(this.state.currentListId),email:t}});const e=Array.from(this.shareListModal.querySelectorAll(".shared-user")).find(e=>e.querySelector(".user-email")?.textContent===t);e&&(e.classList.add("removing"),setTimeout(()=>{if(e.remove(),0===this.shareListModal.querySelectorAll(".shared-user").length){const t=this.shareListModal.querySelector(".shared-users");t&&(t.innerHTML='<div class="no-shares">This list is not shared with anyone yet.</div>')}},300)),showToast(`Removed ${t}'s access`),this.a11y.announce(`Removed ${t}'s access to list`)}catch(t){this.handleError(t,"removing share access")}finally{this.hideLoader()}else showToast("No list selected","error")}async deleteList(t){if(confirm("Are you sure you want to delete this list? This cannot be undone."))try{this.showLoader("Deleting list..."),this.queue.addToQueue({type:"favourite_list_delete",data:{list_id:parseInt(t)}});const e=this.container.querySelector(`.list-card[data-id="${t}"]`);e&&(e.classList.add("removing"),setTimeout(()=>{e.remove(),0===this.container.querySelectorAll(".list-card").length&&(this.listContainer.innerHTML='\n                                <div class="no-lists">\n                                    <h3>No Lists Yet</h3>\n                                    <p>Create your first list to organize your favourites!</p>\n                                </div>\n                            ')},300)),showToast("List deleted"),this.a11y.announce("List deleted")}catch(t){this.handleError(t,"deleting list")}finally{this.hideLoader()}}showBulkNotesModal(){let t=document.querySelector(".bulk-notes-modal");t||(t=document.createElement("dialog"),t.className="bulk-notes-modal",t.innerHTML='\n                <form method="dialog" data-save="favourites">\n                    <h2>Add Notes to Selected Items</h2>\n\n                    <div class="field">\n                        <label for="bulk-notes">Notes (will be applied to all selected items)</label>\n                        <textarea id="bulk-notes" name="bulk_notes" rows="5"></textarea>\n                    </div>\n\n                    <div class="actions">\n                        <button type="button" class="cancel">Cancel</button>\n                        <button type="submit" class="save">Save Notes</button>\n                    </div>\n                </form>\n            ',document.body.appendChild(t),t.querySelector("form").addEventListener("submit",e=>{e.preventDefault();const s=t.querySelector("#bulk-notes").value;this.saveBulkNotes(s),t.close()}),t.querySelector(".cancel").addEventListener("click",()=>{t.close()})),t.querySelector("form")?.reset(),t.showModal(),setTimeout(()=>{t.querySelector("#bulk-notes")?.focus()},100),window.jvbA11y&&window.jvbA11y.announce("Add notes dialog opened")}saveBulkNotes(t){if(t)try{this.showLoader("Saving notes...");let e=Array.from(this.state.selectedItems.values());this.queue.addToQueue({type:"favourite_notes",data:{target_id:e.join(","),notes:t}}),showToast(`Notes saved for ${e.length} items`),this.a11y.announce(`Notes saved for ${e.length} items`),this.clearSelection()}catch(t){this.handleError(t,"saving bulk notes")}finally{this.hideLoader()}}async bulkUnfavourite(){if(confirm("Are you sure you want to remove these items from your favourites?"))try{this.showLoader("Removing from favourites...");const t=[];this.state.selectedItems.forEach(e=>{const s=this.grid.querySelector(`.item[data-id="${e}"]`);if(!s)return;const i=s.dataset.type;t.push({target_id:parseInt(e),type:i,action:"remove"})}),this.queue.addToQueue({type:"favourite_toggle",data:t});const e=[];this.state.selectedItems.forEach(t=>{const s=this.grid.querySelector(`.item[data-id="${t}"]`);if(!s)return;s.style.opacity="0",s.style.transform="scale(0.9)",s.style.transition="opacity 0.3s ease, transform 0.3s ease";const i=new Promise(t=>{setTimeout(()=>{s.remove(),t()},300)});e.push(i)}),await Promise.all(e),0===this.grid.children.length&&this.showEmptyState(),this.clearSelection(),showToast(`Removed ${t.length} items from favourites`),this.a11y.announce(`Removed ${t.length} items from favourites`)}catch(t){this.handleError(t,"removing favourites")}finally{this.hideLoader()}}async handleShareList(t){const e=t.get("share_email");if(e)if(this.validateEmail(e))try{this.showLoader("Sharing list..."),this.queue.addToQueue({type:"favourite_list_share",data:{list_id:parseInt(this.state.currentListId),email:e,permission_type:"view"}}),this.shareListModal.querySelector("#share-email").value="",this.loadSharedUsers(this.state.currentListId),showToast(`Invitation sent to ${e}`),this.a11y.announce(`Invitation sent to ${e}`)}catch(t){this.handleError(t,"sharing list")}finally{this.hideLoader()}else showToast("Please enter a valid email address","error");else showToast("Please enter an email address","error")}showLoader(t="Loading..."){if(!this.loader)return;const e=this.loader.querySelector(".loader-message");e&&(e.textContent=t),this.loader.hidden=!1}hideLoader(){this.loader&&(this.loader.hidden=!0)}showToast(t,e){window.jvbNotifications.showToast(t,e)}handleError(t,e){console.error(`Favourites error (${e}):`,t),showToast(`Error ${e}: ${t.message||"Something went wrong"}`,"error"),window.jvbError&&window.jvbError.log(t,{component:"FavouritesManager",action:e}),window.jvbA11y&&window.jvbA11y.announce(`Error ${e}. ${t.message||"Please try again."}`)}validateEmail(t){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)}};
\ No newline at end of file
diff --git a/assets/js/min/form.min.js b/assets/js/min/form.min.js
index 4b1f090..a8457da 100644
--- a/assets/js/min/form.min.js
+++ b/assets/js/min/form.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.queue=window.jvbQueue,this.populate=window.jvbPopulate,this.changes=new Map,this.forms=new Map,this.inputs=new Map,this.repeaters=new Map,this.tagLists=new Map,this.charLimits=new Map,this.quantityFields=new Map,this.quillInstances=new Map,this.dependencies=new Map,this.subscribers=new Set,this.isRestoring=!1,this.hasListeners=!1,this.hasUploads=!1,this.summaryTemplate=!1,this.init()}init(){this.templates=window.jvbTemplates,this.defineSummaryTemplate(),this.initElements(),this.initListeners(),this.initStore(),this.initValidators(),this.initUploadSubscription()}initUploadSubscription(){window.jvbUploads.subscribe(((e,t)=>{if(this.hasUploads&&"upload-received"===e){let e=this.getForm(t.field);e&&this.updateItem(`${t.field.dataset.field}_tempUpload`,t.id,e)}}))}initElements(){this.inputSelectors="input, textarea, select",this.selectors={tabs:{nav:"nav.tabs",sections:".tab.content",progress:{progress:".progress",fill:".progress .fill",details:".progress .details",icon:".progress .icon"},buttons:"nav.tabs button"},dependsOn:"[data-depends-on]",forms:{status:{status:".fstatus",message:".fstatus .message",icon:".fstatus .icon",actions:".fstatus .actions"},restore:{container:".restore-form",restore:'[data-action="restore"]',clear:'[data-action="clear"]'}},inputs:this.inputSelectors,fields:{field:".field",label:"label",success:".success",error:".error",message:".validation-message"},repeater:{repeater:".repeater",header:".repeater-row-header",remove:".remove-row",add:".add-repeater-row",template:"template",items:".repeater-items",inputs:this.inputSelectors},tagList:{tagList:".field.tag-list",input:".row",add:".add-tag",remove:".remove-tag",label:".tag-label",items:".tag-items",item:".tag-item",inputs:this.inputSelectors,value:'input[type="hidden"]'},tag:{label:".tag-label"},number:{number:".field div.quantity",increase:"button.increase",decrease:"button.decrease",input:'input[type="number"]'},limits:{hasLimit:"[data-maxlength]",limit:".limit",current:".current"}}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.blurHandler=this.handleBlur.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleSubmit.bind(this),this.quantityClick=this.handleQuantityClick.bind(this),this.repeaterClick=this.handleRepeaterClick.bind(this),this.tagListClick=this.handleTagListClick.bind(this),this.tagListInput=this.handleTagListInput.bind(this)}addFormListeners(e){e.addEventListener("click",this.clickHandler),e.addEventListener("change",this.changeHandler),e.addEventListener("input",this.inputHandler),e.addEventListener("blur",this.blurHandler),e.addEventListener("submit",this.submitHandler)}removeFormListeners(e){e.removeEventListener("click",this.clickHandler),e.removeEventListener("change",this.changeHandler),e.removeEventListener("input",this.inputHandler),e.removeEventListener("blur",this.blurHandler),e.removeEventListener("submit",this.submitHandler)}initStore(){const e=window.jvbStore.register("forms",{storeName:"forms",keyPath:"id",indexes:[{name:"src",keyPath:"src"},{name:"timestamp",keyPath:"timestamp"},{name:"formType",keyPath:"type"}],TTL:1008e4});this.store=e.forms,this.store.subscribe(((e,t)=>{if("data-ready"===e){let e=this.store.getFiltered().filter((e=>e.src===window.location.pathname));for(let t of e)this.showPendingNotification(t.id,t.changes)}else"operation-status"===e&&"completed"===t.status&&t.config&&this.store.delete(t.config.id)}))}showPendingNotification(e,t){let s=this.forms.get(e);if(!s)return;let i=s.element;if(!i)return void console.warn(`Form element not found for: ${e}`);s.ui.restore.container.hidden=!1;const a=async(e,t)=>{this.isRestoring=!0;let i={fields:e};await this.checkStoredUploads(e,t),this.populate.populate(t,i),this.a11y.announce("Previous changes restored"),this.isRestoring=!1,s.ui.restore.container.remove()},r=async e=>{await this.checkStoredUploads(t,i,!1),await this.store.delete(e),this.a11y.announce("Previous changes discarded"),s.ui.restore.container.remove()};s.ui.restore.restore.addEventListener("click",(()=>a(t,i))),s.ui.restore.clear.addEventListener("click",(async()=>r(e)))}async checkStoredUploads(e,t,s=!0){let i=this.forms.get(t.dataset.formId);if(!i)return;let a=[];for(let[t,s]of Object.entries(e))if(t.includes("_tempUpload")){let e=t.replace("_tempUpload","");Object.hasOwn(i.ui.uploads,e)&&(a=[...a,...s])}a.length>0&&(s?await window.jvbUploads.restoreUploads(a):await window.jvbUploads.clearUploads(a))}initValidators(){this.validators={email:{pattern:/^[^\s@]+@[^\s@]+\.[^\s@]+$/,message:"Please enter a valid email address"},url:{pattern:/^https?:\/\/.+\..+/,message:"Please enter a valid URL starting with https://"},phone:{pattern:/^[\d\s\-+().]+$/,message:"Please enter a valid phone number"},number:{test:(e,t)=>{const s=parseFloat(e);if(isNaN(s))return"Please enter a valid number";const i=t.dataset.min,a=t.dataset.max;return void 0!==i&&s<parseFloat(i)?`Value must be at least ${i}`:!(void 0!==a&&s>parseFloat(a))||`Value must be at most ${a}`}},text:{test:(e,t)=>{const s=t.dataset.minlength,i=t.dataset.maxlength;return s&&e.length<parseInt(s)?`Must be at least ${s} characters`:!(i&&e.length>parseInt(i))||`Must be no more than ${i} characters`}}}}validateField(e){const t=this.performValidation(e);return this.updateValidationUI(e,t),t.isValid}performValidation(e){const t=e.closest(".field"),s=this.getFieldCheckedValue(e);if(!s&&!e.required)return{isValid:!0,message:""};if(e.required)if("checkbox"===e.type){if(!e.checked)return{isValid:!1,message:"This field is required"}}else if("radio"===e.type){const t=document.querySelectorAll(`input[name="${e.name}"]`);if(!Array.from(t).some((e=>e.checked)))return{isValid:!1,message:"Please select an option"}}else if(!s)return{isValid:!1,message:"This field is required"};if(e.checkValidity&&!e.checkValidity())return{isValid:!1,message:e.validationMessage};if(s&&Object.hasOwn(t.dataset,"pattern")){if(!new RegExp(t.dataset.pattern).test(s))return{isValid:!1,message:t.dataset.validationMessage||"Invalid format"}}if(Object.hasOwn(t.dataset,"validate")||e.type){const i=this.validators[t.dataset.validate||e.type];if(i&&i.pattern&&!i.pattern.test(s))return{isValid:!1,message:i.message};if(i&&i.test){const e=i.test(s,t);if(!0!==e)return{isValid:!1,message:e}}}return{isValid:!0,message:""}}updateValidationUI(e,t){t.isValid?this.showSuccess(e,t.message):this.showError(e,t.message)}handleClick(e){let t=this.getForm(e.target);if(!t)return;const s=window.targetCheck(e,"[data-action]");if(s){switch(s.dataset.action){case"clear-form":this.store.delete(t.id),t.element.reset(),t.ui.status.status.hidden=!0,this.a11y.announce("Form cleared, starting fresh");break;case"dismiss-restore":t.ui.status.status.hidden=!0}}}handleChange(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getField(e.target);const s=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');if(s){if(this.dependencies.has(t.dataset.field)){this.dependencies.get(t.dataset.field).forEach((e=>{this.checkFieldDependency(e,t.dataset.field)}))}const e=s.dataset.field;return void window.debouncer.schedule(`collection:${e}`,(()=>this.updateCollectionField(s)),150)}if(this.dependencies.has(t.dataset.field)){this.dependencies.get(t.dataset.field).forEach((e=>{this.checkFieldDependency(e,t.dataset.field)}))}let i=this.getForm(e.target);this.updateItem(t.dataset.field,this.getFieldValue(e.target),i)}handleBlur(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getForm(e.target);if(!t)return;let s=this.getField(e.target).dataset.field;window.debouncer.cancel(`form:${t.id}:validate:${s}`),this.validateField(e.target);const i=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');i?this.updateCollectionField(i):this.updateItem(s,this.getFieldValue(e.target),t)}handleInput(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getForm(e.target);if(!t)return;let s=this.getField(e.target);if(!s)return;const i=e.target,a=s.dataset.field;this.showFormStatus(t.id,"pending"),window.debouncer.schedule(`form:${t.id}:validate:${a}`,(()=>this.validateField(i)),500)}async handleSubmit(e){let t=this.getForm(e.target);if(t){if(this.subscribers.size>0){if(e.preventDefault(),t.options.cache){this.cancelBackup(),await this.backup();const e=await this.store.get(t.id);this.notify("form-submit",{config:t,data:e.changes})}else this.notify("form-submit",{config:t,data:this.changes.get(t.id)?.changes??{}});t.options.endpoint&&(console.log("Submitting form..."),await this.handleServerSave(t,this.collectFormData(t.form)))}if(t.options.showSummary){const e=await this.store.get(t.id);this.showSummary({config:t,changes:e?.changes})}}}updateItem(e,t,s){if(void 0===t)return;this.changes.has(s.id)||this.changes.set(s.id,{id:s.id,timestamp:Date.now(),src:window.location.pathname,changes:{}});let i=this.changes.get(s.id);e.includes("_tempUpload")?(Object.hasOwn(i.changes,e)||(i.changes[e]=[]),i.changes[e].push(t)):i.changes[e]=t,this.changes.set(s.id,i),s.options.cache&&this.scheduleBackup(),s.options.endpoint&&this.scheduleServerSave(s,i)}scheduleBackup(){window.debouncer.schedule("form_changes",(async()=>{this.changes.size>0&&await this.backup()}),2e3)}cancelBackup(){window.debouncer.cancel("form_changes")}async backup(){const e=new Map;for(let[t,s]of this.changes.entries()){const i=await this.store.get(t);i?e.set(t,{...i,...s,changes:{...i.changes,...s.changes},timestamp:Date.now()}):e.set(t,s)}await this.store.saveMany(e);for(let e of this.changes.keys())this.showFormStatus(e,"autosaved");this.changes.clear()}saveCache(e){if(!this.changes.has(e))return;let t=this.changes.get(e);0!==t.size&&(this.store.save(t).then((()=>{})),this.changes.delete(e))}scheduleServerSave(e,t){window.debouncer.schedule(`form_${e.id}_server_save`,(async()=>{await this.handleServerSave(e,t)}),1500)}cancelServerSave(e){window.debouncer.cancel(`form_${e}_server_save`)}async handleServerSave(e,t){this.cancelServerSave(e.id),window.jvbQueue?(t.user=window.auth.getUser(),window.jvbQueue.addToQueue({endpoint:e.options.endpoint,data:t,headers:e.options.headers??{}})):await window.auth.fetch(jvbBase.api+e.options.endpoint,{body:t,headers:e.options.headers??{}})}registerForm(e,t){if(t={autoUpload:!1,imageMeta:!0,delay:1500,endpoint:Object.hasOwn(e.dataset,"save")?e.dataset.save:"",showStatus:!0,showSummary:!1,cache:!0,ignore:[],...t},Object.hasOwn(e.dataset,"formId")&&this.forms.has(e.dataset.formId))return;Object.hasOwn(e.dataset,"formId")||(e.dataset.formId=window.generateID("form_"));const s=e.dataset.formId;this.addFormListeners(e);const i={element:e,id:s,status:"",options:t,ui:window.uiFromSelectors(this.selectors.forms,e)};return i.ui.fields={},e.querySelectorAll("[data-field]").forEach((e=>{i.ui.fields[e.dataset.field]=e})),this.initializeFields(e,i),this.forms.set(s,i),i}clearForm(e){const t=this.forms.get(e);if(!t)return;t.unsubscribeTabs&&t.unsubscribeTabs(),t.tabs&&window.jvbTabs.removeTab(t.element),t.cache&&this.changes.has(e)&&this.saveCache(e);for(let[t,s]of this.inputs.entries())s.form===e&&this.inputs.delete(t);if(this.dependencies.forEach(((t,s)=>{0===(t=t.filter((t=>t.form!==e))).length&&this.dependencies.delete(s)})),Object.hasOwn(t,"hasQuill")&&this.quillInstances.has(e)){this.quillInstances.get(e).forEach((e=>{e.disable(),e.off("text-change"),e.off("selection-change");const t=e.container.parentElement,s=t?.querySelector(".ql-toolbar");if(s&&s.remove(),e.setText(""),t&&t.classList.contains("editor-container")){const e=t.nextElementSibling;"TEXTAREA"===e?.tagName&&(e.style.display=""),t.remove()}})),this.quillInstances.delete(e)}let s={repeater:this.repeaters,tagList:this.tagLists,charLimit:this.charLimits,quantity:this.quantityFields};for(let[t,i]of Object.entries(s)){if(0===i.size)continue;let s=Array.from(i.values()).filter((t=>t.form===e));s.length>0&&s.forEach((e=>{switch(t){case"repeater":this.removeRepeaterListeners(e.element);break;case"tagList":this.removeTagListListeners(e.element);break;case"charLimit":this.removeCharacterLimitListeners(e.element);break;case"quantity":this.removeQuantityListeners(e.element)}i.has(e.id)&&i.delete(e.id)}))}this.removeFormListeners(t.element),this.forms.delete(e),window.debouncer.cancel("form_changes")}defineSummaryTemplate(){this.summaryTemplate=!0;let e=this;this.templates.define("formSummary",{refs:{result:".result",h3:"h3",p:"p"},setup({el:t,refs:s,manyRefs:i,data:a}){const r=["sendAll",...a.config.options.ignore??[]];for(let[i,n]of Object.entries(a.changes)){if(r.includes(i)||e.isEmptyValue(n))continue;let a=Array.from(e.inputs.values()).find((e=>e.field?.dataset.field===i));if(!a)continue;let l=s.result.cloneNode(!0),o=l.querySelector("h3"),d=l.querySelector("p");const c=a.field?.querySelector("legend");o.textContent=c?c.textContent.replace("*","").trim():a.ui.label?.textContent.replace("*","").trim();const u=e.formatValueForSummary(n,a);u instanceof HTMLElement?d.replaceWith(u):d.textContent=u,t.append(l)}let n=a.config?.element?.querySelectorAll("[data-upload-field]");n&&n.forEach((e=>{let i=e.querySelector("h2")?.textContent??"Upload:",a=e.querySelectorAll(".item-grid.preview img"),r=s.result.cloneNode(!0);if(a){let e=s.result.cloneNode(!0),n=r.querySelector("h3"),l=r.querySelector("p");l?.remove(),n&&(n.textContent=i),a.forEach((t=>{t=t.cloneNode(!0),e.append(t)})),t.append(e)}})),s.result?.remove(),a.config.element.after(t),window.fade(a.config.element,!1)}})}initializeFields(e,t=null){const s={"[data-editor]":()=>this.checkForQuill(e,t),"div.quantity":()=>this.checkForQuantity(e),".repeater":()=>this.checkForRepeaters(e,t),".field.tag-list":()=>this.checkForTagLists(e),"[data-depends-on]":()=>this.checkForConditionalFields(e),"[data-limit]":()=>this.checkForCharacterLimits(e),"[data-uploader],[data-upload-field]":()=>this.checkForImageUploads(e,t),"nav.tabs":()=>this.checkForTabs(e,t),'[data-type="selector"]':()=>this.checkForSelectors(e)};for(const[t,i]of Object.entries(s))e.querySelector(t)&&i();Array.from(e.querySelectorAll(this.inputSelectors)).filter((e=>!e.closest(".ql-clipboard"))).map((e=>{this.getItem(e,t?.id)}))}checkForQuill(e,t){if(!e.querySelector("[data-editor]"))return;t&&!Object.hasOwn(t,"hasQuill")&&(t.hasQuill=!0,this.forms.set(t.id,t)),this.quillInstances.has(t.id)||this.quillInstances.set(t.id,new Set);window.jvbQuill(e).forEach((e=>{this.quillInstances.get(t.id).add(e)}))}checkForQuantity(e){e.querySelector(this.selectors.number.number)&&e.querySelectorAll(this.selectors.number.number).forEach((t=>{let s={id:window.generateID("quant"),form:e.dataset.formId,ui:window.uiFromSelectors(this.selectors.number,t),element:t};t.dataset.numId=s.id,this.quantityFields.set(s.id,s),this.addQuantityListeners(t)}))}addQuantityListeners(e){e.addEventListener("click",this.quantityClick)}removeQuantityListeners(e){e.removeEventListener("click",this.quantityClick)}handleQuantityClick(e){let t=this.quantityFields.get(e.target.closest("[data-num-id]")?.dataset.numId);if(!t)return;let s=0;if(t.ui.increase.contains(e.target)?s++:t.ui.decrease.contains(e.target)&&s--,0===s)return;this.getField(e.target);let i=t.ui.input.step;i=Math.max(i,1),e.ctrlKey&&e.shiftKey?i*=50:e.ctrlKey?i*=5:e.shiftKey&&(i*=10);let a=""===t.ui.input.value?0:parseFloat(t.ui.input.value);t.ui.input.value=a+i*s,a=parseFloat(t.ui.input.value),t.ui.input.min&&a<t.ui.input.min?(t.ui.input.value=t.ui.input.min,t.ui.decrease.disabled=!0):t.ui.input.max&&a>t.ui.input.max?(t.ui.input.value=t.ui.input.max,t.ui.increase.disabled=!0):(t.ui.decrease.disabled&&(t.ui.decrease.disabled=!1),t.ui.increase.disabled&&(t.ui.increase.disabled=!1))}checkForRepeaters(e){e.querySelector(this.selectors.repeater.repeater)&&e.querySelectorAll(this.selectors.repeater.repeater).forEach((t=>{let s={id:t.querySelector("template").className??window.generateID("repeater"),ui:window.uiFromSelectors(this.selectors.repeater,t),form:e.dataset.formId,element:t,field:this.getField(t),sortable:!1,rows:[]};if(!s.ui.add)return;let i=t.querySelector("template");this.templates.define(i.className,{manyRefs:{inputs:this.inputSelectors},setup({el:e,refs:t,manyRefs:i,data:a}){let r=s.ui.items?.children?.length??0;e.dataset.index=r,i.inputs?.forEach((t=>{window.prefixInput(t,`${a.repeater.dataset.field}:${r}:`,e,!1,!0)}))}}),window.Sortable&&(s.sortable=new Sortable(t,{handle:this.selectors.repeater.header,animation:150,onEnd:()=>{this.reindexList(t)}})),t.dataset.repeaterId=s.id,this.addRepeaterListeners(t),this.repeaters.set(s.id,s)}))}addRepeaterListeners(e){e.addEventListener("click",this.repeaterClick)}removeRepeaterListeners(e){e.removeEventListener("click",this.repeaterClick)}handleRepeaterClick(e){e.target.matches(this.selectors.repeater.add)?this.addRepeaterRow(e.target.closest("[data-repeater-id]")):e.target.matches(this.selectors.repeater.remove)&&this.removeRepeaterRow(e.target.closest("[data-index]"))}addRepeaterRow(e){let t={};t.repeater=e;let s=this.repeaters.get(e.dataset.repeaterId),i=this.templates.create(e.dataset.repeaterId,t);s.rows.push({element:i,fields:Array.from(i.querySelectorAll("[data-field]"))}),this.repeaters.set(s.id,s),s.ui.items.append(i);let a=this.getForm(e);this.initializeFields(e,a),this.a11y.announce("Row added")}removeRepeaterRow(e){let t=e.closest("[data-repeater-id]");e.remove(),this.reindexList(t),this.a11y.announce("Row removed")}checkForTagLists(e){e.querySelectorAll(this.selectors.tagList.tagList)?.forEach((t=>{let s={id:t.querySelector("template").className??window.generateID("tagList"),ui:window.uiFromSelectors(this.selectors.tagList,t),element:t,form:e.dataset.formId,format:t.dataset.tagFormat??"first_field"};if(!s.ui.input||!s.ui.add||!s.ui.items)return;t.dataset.tagListId=s.id,s.fieldName=t.dataset.field;let i=t.querySelector("template");this.templates.define(i.className,{refs:{label:this.selectors.tagList.label},manyRefs:{inputs:this.inputSelectors},setup({el:e,refs:t,manyRefs:i,data:a}){let r=s.ui.items?.children?.length??0;e.dataset.index=r,i.inputs?.forEach((e=>{let t=e.closest(".tag-item");window.prefixInput(e,`${a.fieldName}:${r}:`,t,!1,!0)})),t.label&&(t.label.textContent=a.label)}}),s.ui.inputs=Array.from(t.querySelectorAll(this.selectors.tagList.inputs)),s.ui.value=Array.from(t.querySelectorAll(this.selectors.tagList.value)),this.tagLists.set(s.id,s),this.addTagListListeners(t)}))}addTagListListeners(e){e.addEventListener("click",this.tagListClick),e.addEventListener("keypress",this.tagListInput)}removeTagListListeners(e){e.removeEventListener("click",this.tagListClick),e.removeEventListener("keypress",this.tagListInput)}handleTagListClick(e){window.targetCheck(e,this.selectors.tagList.add)?this.addTagListItem(e.target.closest("[data-tag-list-id]")):window.targetCheck(e,this.selectors.tagList.remove)&&this.removeTagListItem(e.target.closest(this.selectors.tagList.item))}addTagListItem(e){let t=this.tagLists.get(e.dataset.tagListId);if(!t)return;let s,i={},a=!1,r=!0;for(let e of t.ui.inputs){const t=e.required||"true"===e.dataset.required,s=this.getFieldValue(e);s&&(a=!0);const n=this.validateField(e);t&&!s?(this.showError(e,"This field is required"),r=!1):n||(r=!1);const l=e.name.replace("new_","");i[l]=s}if(!r){this.a11y.announce("Please correct the errors before adding");const e=t.ui.inputs.find((e=>(e.required||"true"===e.dataset.required)&&!this.getFieldValue(e)));return void(e&&e.focus())}if(!a)return this.a11y.announce("Please fill in at least one field"),void t.ui.inputs[0].focus();switch(t.format){case"first_field":s=Object.values(i)[0];break;case"all_fields":s=Object.values(i).join(", ");break;default:if(t.format.includes("{")){s=t.format;for(const[e,t]of Object.entries(i))s=s.replace(`{${e}}`,t)}else s=i[t.format]??Object.values(i)[0]}let n=this.templates.create(e.dataset.tagListId,{label:s,fieldName:t.fieldName});const l=t.ui.items?.children?.length??0;n?.querySelectorAll("input[type=hidden]")?.forEach((e=>{const s=e.dataset.field;e.name=`${t.fieldName}:${l}:${s}`,e.id=`${t.fieldName}:${l}:${s}`,e.value=i[s]||""})),t.ui.items.append(n);for(let e of t.ui.inputs)["checkbox","radio"].includes(e.type)?e.checked=!1:e.value="",this.clearValidation(e);t.ui.inputs[0]?.focus(),this.updateCollectionField(e),this.a11y.announce("Item added")}removeTagListItem(e){let t=e.closest("[data-tag-list-id]");t&&(e.remove(),this.reindexList(t),this.updateCollectionField(t),this.a11y.announce("Item removed"))}handleTagListInput(e){let t=e.target,s=t.closest("[data-tag-list-id]");if(!s)return;let i=this.tagLists.get(s.dataset.tagListId);if(i&&"Enter"===e.key)if(t===i.ui.inputs[i.ui.inputs.length-1])e.preventDefault(),this.addTagListItem(t.closest("[data-tag-list-id]"));else{e.preventDefault();let s=i.ui.inputs.indexOf(t);i.ui.inputs[s+1].focus()}}checkForConditionalFields(e){e.querySelectorAll(this.selectors.dependsOn).forEach((t=>{const s=t.dataset.dependsOn,i=t.dataset.dependsValue,a=t.dataset.dependsOperatior??"==";let r=this.forms.get(e.dataset.formId);this.dependencies.has(s)||Object.hasOwn(r.ui.fields,s)&&this.dependencies.set(s,[]);let n=this.dependencies.get(s);n&&(n.push({field:t,form:e.dataset.formId,requiredValue:i,operator:a}),this.dependencies.set(s,n)),this.checkFieldDependency(t,s)}))}checkFieldDependency(e,t){const s=this.getForm(e);if(!this.dependencies.get(t))return;const i=this.getFieldValue(s.ui.fields[t]),a=this.evaluateCondition(i,e.dataset.dependsValue,e.dataset.dependsOperatior);this.toggleFieldVisibility(e,a)}evaluateCondition(e,t,s){const i=String(e||""),a=String(t||"");switch(s){case"==":default:return i===a;case"!=":return i!==a;case">":return parseFloat(i)>parseFloat(a);case"<":return parseFloat(i)<parseFloat(a);case">=":return parseFloat(i)>=parseFloat(a);case"<=":return parseFloat(i)<=parseFloat(a);case"contains":return i.includes(a);case"empty":return""===i;case"not_empty":return""!==i}}toggleFieldVisibility(e,t){const s=e.closest(".field, fieldset");s&&(s.hidden=!t,s.querySelectorAll("input, select, textarea").forEach((e=>{e.disabled=!t,!t&&e.hasAttribute("required")?(e.dataset.wasRequired="true",e.removeAttribute("required")):t&&"true"===e.dataset.wasRequired&&(e.setAttribute("required",""),delete e.dataset.wasRequired)})))}checkForCharacterLimits(e){e.querySelector(this.selectors.limits.hasLimit)&&(this.countUpdaters=this.updateCount.bind(this),e.querySelectorAll(this.selectors.limits.hasLimit).forEach((t=>{const s=this.getFieldInput(t);if(!s)return;let i=window.generateID("limit");s.dataset.charLimitId=i,s.dataset.limit=t.dataset.maxlength;let a={element:s,form:e.dataset.formId,ui:window.uiFromSelectors(this.selectors.limits,t)};a.ui.limit&&(a.ui.limit.textContent=t.dataset.maxlength),this.charLimits.set(i,a),this.addCharacterLimitListeners(s)})))}addCharacterLimitListeners(e){e.addEventListener("input",this.countUpdaters,{passive:!0})}removeCharacterLimitListeners(e){e.removeEventListener("input",this.countUpdaters,{passive:!0})}updateCount(e){let t=e.target,s=this.charLimits.get(t.dataset.charLimitId);if(!s)return;let i=t.value.length,a=t.dataset.limit;s.ui.current&&(s.ui.current.textContent=i,s.ui.current.classList.toggle("exceeded",i>=a)),i>a&&(t.value=t.value.slice(0,a))}checkForImageUploads(e,t){this.hasUploads=!0,window.jvbUploads.scanFields(e,t.options.autoUpload,t.options.imageMeta);let s=e.querySelectorAll('[data-field-type="upload"]');s&&(t.ui.uploads={},s.forEach((e=>{t.ui.uploads[e.dataset.field]=e})))}checkForTabs(e,t){window.jvbTabs&&e.querySelector("nav.tabs")&&(t.tabs=window.jvbTabs.registerTab(e,{preCheck:(e,s)=>this.validateStep(e,t)}),t.ui.tabs=window.uiFromSelectors(this.selectors.tabs,e),t.ui.tabs.sections=Array.from(e.querySelectorAll(this.selectors.tabs.sections)),t.ui.tabs.inputs={},t.ui.tabs.sections.forEach((e=>{t.ui.tabs.inputs[e.dataset.tab]=Array.from(e.querySelectorAll(this.inputs))})),t.ui.tabs.buttons=Array.from(e.querySelectorAll(this.selectors.tabs.buttons)),t.unsubscribeTabs=window.jvbTabs.subscribe(((e,s)=>{if("tab-switched"===e&&t.ui.tabs.progress){const e=t.ui.tabs.sections.filter((e=>e.dataset.tab===s.current))[0]??!1;if(!e)return;const i=e.dataset.step,a=t.ui.sections.length;window.showProgress(t.ui.tabs.progress,i,a)}})),this.forms.set(t.id,t))}validateStep(e,t){const s=e.closest("[data-form-id]")?.dataset.formId;if(!s)return!0;if(!this.forms.get(s))return!0;return Array.from(this.inputs.values()).filter((t=>t&&t.form===s&&t.section===e.dataset.tab&&!t.element.closest("[hidden]"))).every((e=>!0===this.validateField(e.element)))}checkForSelectors(e){window.jvbSelector&&window.jvbSelector.scanExistingFields(e)}reindexList(e){const t=e.dataset.field||e.dataset.repeaterId||e.dataset.tagListId;Array.from(e.children).forEach(((e,s)=>{e.dataset.index=`${s}`;e.querySelectorAll("input, select, textarea").forEach((i=>{if("file"===i.type)return;i.dataset.field||i.name.split(":").pop();window.prefixInput(i,`${t}:${s}:`,e,!1,!0)}))})),this.updateCollectionField(e)}updateCollectionField(e){const t=e.closest("[data-field]");if(!t)return;const s=t.dataset.fieldType;if(!["repeater","tag-list"].includes(s))return;const i=this.getForm(e);if(!i)return;const a=this.getFieldValue(t);this.updateItem(t.dataset.field,a,i)}clearValidation(e){let t=this.getField(e);if(!t)return;let s=this.getItem(e);s&&(t.classList.remove("has-error","has-success"),s.ui.success&&(s.ui.success.hidden=!0),s.ui.error&&(s.ui.error.hidden=!0),s.ui.message&&(s.ui.message.hidden=!0,s.ui.message.textContent=""))}showError(e,t="Invalid field"){let s=this.getField(e);if(!s)return;let i=this.getItem(e);i&&(s.classList.remove("has-success"),s.classList.add("has-error"),i.ui.message&&(i.ui.message.hidden=!1,i.ui.message.textContent=t))}showSuccess(e,t=""){let s=this.getField(e);if(!s)return;let i=this.getItem(e);i&&(s.classList.remove("has-error"),s.classList.add("has-success"),i.ui.message&&(i.ui.message.hidden=""===t,i.ui.message.textContent=t))}handleFormSuccess(e,t){if(e.querySelectorAll(".error-message").forEach((e=>e.remove())),e.querySelectorAll(".field-error").forEach((e=>e.classList.remove("field-error"))),e.classList.add("form-success"),t.message){const s=document.createElement("div");s.className="form-success-message success-message",s.textContent=t.message,e.insertBefore(s,e.firstChild);const i=window.getIcon?.("check-circle");i&&(i.classList.add("success-icon"),s.prepend(i))}if(t.title||t.description){const s=document.createElement("div");if(s.className="success-box",t.title){const e=document.createElement("h3");e.textContent=t.title,s.appendChild(e)}if(t.description){(Array.isArray(t.description)?t.description:[t.description]).forEach((e=>{const t=document.createElement("p");t.textContent=e,s.appendChild(t)}))}e.insertBefore(s,e.firstChild)}if(e.dataset.formId){this.store.delete(e.dataset.formId).catch((e=>{console.warn("Failed to clear form cache:",e)}));const t=this.forms.get(e.dataset.formId);t&&(t.isDirty=!1,t.lastSaved=Date.now(),t.data={})}window.jvbA11y&&window.jvbA11y.announce(t.message||"Form submitted successfully")}handleFormError(e,t){if(e.querySelectorAll(".error-message").forEach((e=>e.remove())),e.querySelectorAll(".field-error, .has-error").forEach((e=>{e.classList.remove("field-error","has-error")})),e.querySelectorAll(".field").forEach((e=>{this.clearValidation(e)})),t.field){const s=e.querySelector(`[data-field="${t.field}"]`);if(s){this.showError(s,t.message),s.scrollIntoView({behavior:"smooth",block:"center"});const e=s.querySelector("input, textarea, select");e&&e.focus()}}else{const s=document.createElement("div");s.className="form-error error-message",s.textContent=t.message;const i=window.getIcon?.("close-circle");i&&(i.classList.add("error-icon"),s.prepend(i)),e.insertBefore(s,e.firstChild),e.scrollIntoView({behavior:"smooth",block:"start"})}if(window.jvbA11y){const e=t.field?`Error in ${t.field}: ${t.message}`:`Form error: ${t.message}`;window.jvbA11y.announce(e)}e.dispatchEvent(new CustomEvent("jvb-form-error",{detail:t}))}showFormStatus(e,t,s=""){let i=this.forms.get(e);i&&i.options.showStatus&&i.ui?.status?.status&&i.status!==t&&(i.status=t,i.ui.status.status.hidden=!1,i.ui.status.status.classList.toggle("loading",["uploading","saving"].includes(t)),i.ui.status.message.textContent=""===s?this.getDefaultMessage(t):s,i.ui.status.icon.className="icon icon-"+this.getDefaultIcon(t),setTimeout((()=>i.ui.status.status.hidden=!0),"submitted"===t?3e3:1e4))}getDefaultMessage(e){return{saving:"Saving changes...",autosaved:"Changes saved locally. Submit form to send to server.",uploading:"Uploading your form to server",submitted:"Successfully sent to server",pending:"Unsaved changes",restored:"Welcome back! We've restored your previous entry.",error:"Failed to save changes. Refresh and try again?",offline:"Changes will be saved when online"}[e]??e}getDefaultIcon(e){return{autosaved:"check-circle",submitted:"check-circle",restored:"history",error:"close-circle",offline:"cloud-slash",pending:"exclamation-mark"}[e]??""}showSummary(e){let t=this.templates.create("formSummary",e);e.config.element.after(t),window.fade(e.config.element,!1)}getForm(e){let t=e.closest("[data-form-id]");if(!t)return!1;let s=t.dataset.formId;if(!s)return!1;let i=this.forms.get(s);return i||!1}getField(e){return e.closest("[data-field]")}getFieldType(e){let t=this.getField(e);if(t)return t.dataset.fieldType}getFieldValue(e){let t=this.getFieldType(e),s=this.getItem(e),i=s.field?.dataset.field??!1;if(!i)return!1;switch(t){case"repeater":return this.getRepeaterValue(e,s);case"tag-list":return this.getTagListValue(e,s);case"group":return null;case"location":return this.getLocationValue(e,s);case"selector":case"upload":case"gallery":case"image":return this.getHiddenInputValue(e,s,i);case"true-false":case"toggle-text":return e.checked;case"checkbox":return e.name.endsWith("[]")?this.getCheckboxGroupValue(e,s):e.checked?e.value:"";default:return e.value}}getCheckboxGroupValue(e,t){return t.checkboxGroup||(t.checkboxGroup=t.field?.querySelectorAll(`input[type="checkbox"][name="${e.name}"]`),this.saveItem(t)),Array.from(t.checkboxGroup).filter((e=>e.checked)).map((e=>e.value))}getFieldCheckedValue(e){if("checkbox"===e.type){return"true-false"===this.getFieldType(e)?e.checked:e.checked?e.value:""}if("radio"===e.type){const t=document.querySelectorAll(`input[name="${e.name}"]`),s=Array.from(t).find((e=>e.checked));return s?s.value:""}return this.getFieldValue(e)}isEmptyValue(e){return null==e||""===e||(!(!Array.isArray(e)||0!==e.length)||"object"==typeof e&&0===Object.keys(e).length)}getRepeaterValue(e,t){const s=e.querySelector(".repeater-items");if(!s)return[];let i=["image_data","image-title","image-caption","image-description","image-alt-text"],a=[];return Array.from(s.children).forEach((e=>{let t={};e.querySelectorAll("[data-field]").forEach((e=>{if(!i.includes(e.dataset.field)){const s=this.getFieldInput(e);s&&(t[e.dataset.field]=this.getFieldValue(s))}})),a.push(t)})),a}getFieldInput(e){const t=e.querySelector("textarea[data-editor]");return t||e.querySelector(this.inputSelectors)}getTagListValue(e,t){t.container||(t.container=t.field?.querySelector(".tag-items"),this.saveItem(t));let s=[];return Array.from(t.container.children).forEach((e=>{let t=e.querySelectorAll('input[type="hidden"]'),i={};t.forEach((e=>{i[e.dataset.field]=e.value})),s.push(i)})),s}getLocationValue(e,t){t.values||(t.values=Array.from(t.field?.querySelectorAll("[data-location-field]")),this.saveItem(t));let s={};return t.values.forEach((e=>{s[e.dataset.locationField]=e.value})),s}getHiddenInputValue(e,t,s){return"INPUT"===e.tagName&&"hidden"===e.type||(e=e.querySelector('input[type="hidden"][name="'+s+'"]'))?(void 0!==t.value&&t.value===e.value||(t.value=e.value,this.saveItem(t)),t.value):null}formatValueForSummary(e,t){const s=this.getFieldType(t.element);if(this.isEmptyValue(e))return"";switch(s){case"repeater":return this.formatRepeaterForSummary(e,t);case"tag-list":return this.formatTagListForSummary(e,t);case"location":return this.formatLocationForSummary(e);case"true-false":return e?"Yes":"No";case"checkbox":return Array.isArray(e)?this.formatCheckboxGroupForSummary(e,t):this.getDisplayLabel(t,e);case"selector":case"upload":case"image":case"gallery":return this.formatHiddenFieldForSummary(e,t,s);default:return"string"==typeof e?this.getDisplayLabel(t,e):"string"==typeof e&&e.includes("\n")?this.convertLineBreaks(e):e}}formatCheckboxGroupForSummary(e,t){return e.map((e=>this.getDisplayLabel(t,e))).join(", ")}convertLineBreaks(e){const t=document.createElement("span");return t.innerHTML=e.split("\n").join("<br>"),t}formatRepeaterForSummary(e,t){const s=document.createElement("div");return s.className="summary-repeater",e.forEach(((e,i)=>{const a=document.createElement("div");a.className="summary-repeater-row";const r=document.createElement("strong");r.textContent=`Entry ${i+1}:`,a.appendChild(r);const n=document.createElement("ul");n.className="summary-repeater-fields";for(const[s,i]of Object.entries(e)){if(this.isEmptyValue(i))continue;const e=document.createElement("li"),a=t.field?.querySelector(`[data-field="${s}"]`),r=a?.closest(".field")?.querySelector("label")?.textContent.replace("*","").trim()||s;e.innerHTML=`<span class="field-label">${r}:</span> <span class="field-value">${i}</span>`,n.appendChild(e)}a.appendChild(n),s.appendChild(a)})),s}formatTagListForSummary(e,t){const s=document.createElement("div");s.className="summary-taglist";const i=document.createElement("ul");return i.className="summary-tags",e.forEach((e=>{const t=document.createElement("li");t.className="summary-tag";const s=Object.values(e).find((e=>!this.isEmptyValue(e)))||"",a=Object.entries(e).filter((([e,t])=>!this.isEmptyValue(t)));a.length>1?t.textContent=a.map((([e,t])=>t)).join(", "):t.textContent=s,i.appendChild(t)})),s.appendChild(i),s}formatLocationForSummary(e){const t=[];return e.street&&t.push(e.street),e.city&&t.push(e.city),e.province&&t.push(e.province),e.postal_code&&t.push(e.postal_code),e.country&&t.push(e.country),t.length>0?t.join(", "):e.address||""}formatHiddenFieldForSummary(e,t,s){if(["upload","gallery","image"].includes(s)){const s=t.field?.querySelector("[data-upload-field]");if(s){const e=s.querySelectorAll(".item-grid.preview img");if(e.length>0){const t=document.createElement("div");return t.className="summary-uploads",e.forEach((e=>{const s=e.cloneNode(!0);s.style.maxWidth="100px",s.style.maxHeight="100px",t.appendChild(s)})),t}}return`${e.split(",").length} file(s) uploaded`}return e}getDisplayLabel(e,t){if(!e.element)return t;const s=e.element.type;if("radio"===s){const s=e.field.querySelectorAll(`input[type="radio"][name="${e.element.name}"]`),i=Array.from(s).find((e=>e.value===t));if(i){const t=i.closest("label")||e.field.querySelector(`label[for="${i.id}"]`);if(t)return t.textContent.replace("*","").trim()}}if("checkbox"===s&&"true-false"!==this.getFieldType(e.element)){const s=e.field.querySelector(`input[type="checkbox"][value="${t}"]`);if(s){const t=s.closest("label")||e.field.querySelector(`label[for="${s.id}"]`);if(t){const e=t.querySelector("span");return e?e.textContent.trim():t.textContent.replace("*","").trim()}}}return t}getItem(e,t=null){const s=Object.hasOwn(e.dataset,"ref");let i=s?e.dataset.ref:window.generateID("input");if(s||(e.dataset.ref=i),!this.inputs.has(i)){t||(t=e.closest("[data-form-id]")?.dataset.formId??!1);let s=this.getField(e);this.inputs.set(i,{id:i,element:e,form:t,field:s,section:e.closest("[data-tab]")?.dataset.tab??!1,ui:window.uiFromSelectors(this.selectors.fields,s)})}return this.inputs.get(i)}saveItem(e){this.inputs.set(e.id,e)}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach((s=>{try{s(e,t)}catch(e){console.error("HandleSelection subscriber error:",e)}}))}destroy(){this.forms.size>0&&(Array.from(this.forms.values()).forEach((e=>{this.removeFormListeners(e)})),this.forms.clear()),this.repeaters.size>0&&(Array.from(this.repeaters.values()).forEach((e=>{this.removeRepeaterListeners(e.element),e.sortable?.destroy()})),this.repeaters.clear()),this.quantityFields.size>0&&(Array.from(this.quantityFields.values()).forEach((e=>{this.removeQuantityListeners(e.element)})),this.quantityFields.clear()),this.tagLists.size>0&&(Array.from(this.tagLists.values()).forEach((e=>{this.removeTagListListeners(e.element)})),this.tagLists.clear()),this.charLimits.size>0&&Array.from(this.charLimits.values()).forEach((e=>{e.element.removeEventListener("input",this.countUpdaters)})),this.inputs.clear(),this.forms.clear(),this.charLimits.clear()}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbForm=new e)}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.queue=window.jvbQueue,this.populate=window.jvbPopulate,this.changes=new Map,this.forms=new Map,this.inputs=new Map,this.repeaters=new Map,this.tagLists=new Map,this.charLimits=new Map,this.quantityFields=new Map,this.quillInstances=new Map,this.dependencies=new Map,this.subscribers=new Set,this.isRestoring=!1,this.hasListeners=!1,this.hasUploads=!1,this.summaryTemplate=!1,this.init()}init(){this.templates=window.jvbTemplates,this.defineSummaryTemplate(),this.initElements(),this.initListeners(),this.initStore(),this.initValidators(),this.initUploadSubscription()}initUploadSubscription(){window.jvbUploads.subscribe((e,t)=>{if(this.hasUploads&&"upload-received"===e){let e=this.getForm(t.field);e&&this.updateItem(`${t.field.dataset.field}_tempUpload`,t.id,e)}})}initElements(){this.inputSelectors="input, textarea, select",this.selectors={tabs:{nav:"nav.tabs",sections:".tab-content",progress:{progress:".progress",fill:".progress .fill",details:".progress .details",icon:".progress .icon"},buttons:"nav.tabs button"},dependsOn:"[data-depends-on]",forms:{status:{status:".fstatus",message:".fstatus .message",icon:".fstatus .icon",actions:".fstatus .actions"},restore:{container:".restore-form",restore:'[data-action="restore"]',clear:'[data-action="clear"]'}},inputs:this.inputSelectors,fields:{field:".field",label:"label",success:".success",error:".error",message:".validation-message"},repeater:{repeater:".repeater",row:".repeater-row",handle:".drag-handle",header:".repeater-row-header",remove:".remove-row",add:".add-repeater-row",template:"template",items:".repeater-items",inputs:this.inputSelectors},tagList:{tagList:".field.tag-list",input:".row",add:".add-tag",remove:".remove-tag",label:".tag-label",items:".tag-items",item:".tag-item",inputs:this.inputSelectors,value:'input[type="hidden"]'},tag:{label:".tag-label"},number:{number:".field div.quantity",increase:"button.increase",decrease:"button.decrease",input:'input[type="number"]'},limits:{hasLimit:"[data-maxlength]",limit:".limit",current:".current"}}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.blurHandler=this.handleBlur.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleSubmit.bind(this),this.quantityClick=this.handleQuantityClick.bind(this),this.repeaterClick=this.handleRepeaterClick.bind(this),this.tagListClick=this.handleTagListClick.bind(this),this.tagListInput=this.handleTagListInput.bind(this)}addFormListeners(e){e.addEventListener("click",this.clickHandler),e.addEventListener("change",this.changeHandler),e.addEventListener("input",this.inputHandler),e.addEventListener("blur",this.blurHandler),e.addEventListener("submit",this.submitHandler)}removeFormListeners(e){e.removeEventListener("click",this.clickHandler),e.removeEventListener("change",this.changeHandler),e.removeEventListener("input",this.inputHandler),e.removeEventListener("blur",this.blurHandler),e.removeEventListener("submit",this.submitHandler)}initStore(){const e=window.jvbStore.register("forms",{storeName:"forms",keyPath:"id",indexes:[{name:"src",keyPath:"src"},{name:"timestamp",keyPath:"timestamp"},{name:"formType",keyPath:"type"}],TTL:1008e4});this.store=e.forms,this.store.subscribe((e,t)=>{if("data-ready"===e){let e=this.store.getFiltered().filter(e=>e.src===window.location.pathname);for(let t of e)this.showPendingNotification(t.id,t.changes)}else"operation-status"===e&&"completed"===t.status&&t.config&&this.store.delete(t.config.id)})}showPendingNotification(e,t){let s=this.forms.get(e);if(!s)return;let i=s.element;if(!i)return void console.warn(`Form element not found for: ${e}`);s.ui.restore.container.hidden=!1;const a=async(e,t)=>{this.isRestoring=!0;let i={fields:e};await this.checkStoredUploads(e,t),this.populate.populate(t,i),this.a11y.announce("Previous changes restored"),this.isRestoring=!1,s.ui.restore.container.remove()},r=async e=>{await this.checkStoredUploads(t,i,!1),await this.store.delete(e),this.a11y.announce("Previous changes discarded"),s.ui.restore.container.remove()};s.ui.restore.restore.addEventListener("click",()=>a(t,i)),s.ui.restore.clear.addEventListener("click",async()=>r(e))}async checkStoredUploads(e,t,s=!0){let i=this.forms.get(t.dataset.formId);if(!i)return;let a=[];for(let[t,s]of Object.entries(e))if(t.includes("_tempUpload")){let e=t.replace("_tempUpload","");Object.hasOwn(i.ui.uploads,e)&&(a=[...a,...s])}a.length>0&&(s?await window.jvbUploads.restoreUploads(a):await window.jvbUploads.clearUploads(a))}initValidators(){this.validators={email:{pattern:/^[^\s@]+@[^\s@]+\.[^\s@]+$/,message:"Please enter a valid email address"},url:{pattern:/^https?:\/\/.+\..+/,message:"Please enter a valid URL starting with https://"},phone:{pattern:/^[\d\s\-+().]+$/,message:"Please enter a valid phone number"},number:{test:(e,t)=>{const s=parseFloat(e);if(isNaN(s))return"Please enter a valid number";const i=t.dataset.min,a=t.dataset.max;return void 0!==i&&s<parseFloat(i)?`Value must be at least ${i}`:!(void 0!==a&&s>parseFloat(a))||`Value must be at most ${a}`}},text:{test:(e,t)=>{const s=t.dataset.minlength,i=t.dataset.maxlength;return s&&e.length<parseInt(s)?`Must be at least ${s} characters`:!(i&&e.length>parseInt(i))||`Must be no more than ${i} characters`}}}}validateField(e){const t=this.performValidation(e);return this.updateValidationUI(e,t),t.isValid}performValidation(e){const t=e.closest(".field"),s=this.getFieldCheckedValue(e);if(!s&&!e.required)return{isValid:!0,message:""};if(e.required)if("checkbox"===e.type){if(!e.checked)return{isValid:!1,message:"This field is required"}}else if("radio"===e.type){const t=document.querySelectorAll(`input[name="${e.name}"]`);if(!Array.from(t).some(e=>e.checked))return{isValid:!1,message:"Please select an option"}}else if(!s)return{isValid:!1,message:"This field is required"};if(e.checkValidity&&!e.checkValidity())return{isValid:!1,message:e.validationMessage};if(s&&Object.hasOwn(t.dataset,"pattern")){if(!new RegExp(t.dataset.pattern).test(s))return{isValid:!1,message:t.dataset.validationMessage||"Invalid format"}}if(Object.hasOwn(t.dataset,"validate")||e.type){const i=this.validators[t.dataset.validate||e.type];if(i&&i.pattern&&!i.pattern.test(s))return{isValid:!1,message:i.message};if(i&&i.test){const e=i.test(s,t);if(!0!==e)return{isValid:!1,message:e}}}return{isValid:!0,message:""}}updateValidationUI(e,t){t.isValid?this.showSuccess(e,t.message):this.showError(e,t.message)}handleClick(e){let t=this.getForm(e.target);if(!t)return;const s=window.targetCheck(e,"[data-action]");if(s){switch(s.dataset.action){case"clear-form":this.store.delete(t.id),t.element.reset(),t.ui.status.status.hidden=!0,this.a11y.announce("Form cleared, starting fresh");break;case"dismiss-restore":t.ui.status.status.hidden=!0;break;case"next-step":e.preventDefault(),t.tabs.ui.buttons.forEach((e,s)=>{if(e.dataset.tab===t.tabs.activeTab){let e=t.tabs.ui.buttons[s+1]??!1;e&&window.jvbTabs.switchTab(e.dataset.tab,window.jvbTabs.getConfig(e))}});break;case"prev-step":e.preventDefault(),t.tabs.ui.buttons.forEach((e,s)=>{if(e.dataset.tab===t.tabs.activeTab){let e=t.tabs.ui.buttons[s-1]??!1;e&&window.jvbTabs.switchTab(e.dataset.tab,window.jvbTabs.getConfig(e))}})}}}handleChange(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getField(e.target);const s=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');if(s){if(this.dependencies.has(t.dataset.field)){this.dependencies.get(t.dataset.field).forEach(e=>{this.checkFieldDependency(e,t.dataset.field)})}const e=s.dataset.field;return this.maybeUpdateCollectionDisplay(t),void window.debouncer.schedule(`collection:${e}`,()=>this.updateCollectionField(s),150)}if(this.dependencies.has(t.dataset.field)){this.dependencies.get(t.dataset.field).forEach(e=>{this.checkFieldDependency(e,t.dataset.field)})}let i=this.getForm(e.target);this.updateItem(t.dataset.field,this.getFieldValue(e.target),i)}handleBlur(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getForm(e.target);if(!t)return;let s=this.getField(e.target).dataset.field;window.debouncer.cancel(`form:${t.id}:validate:${s}`),this.validateField(e.target);const i=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');i?this.updateCollectionField(i):this.updateItem(s,this.getFieldValue(e.target),t)}handleInput(e){if(e.target.closest("[data-ignore]")||this.isRestoring)return;let t=this.getForm(e.target);if(!t)return;let s=this.getField(e.target);if(!s)return;const i=e.target,a=s.dataset.field;this.showFormStatus(t.id,"pending"),window.debouncer.schedule(`form:${t.id}:validate:${a}`,()=>this.validateField(i),500)}async handleSubmit(e){let t=this.getForm(e.target);if(t){if(this.subscribers.size>0){if(e.preventDefault(),t.options.cache){this.cancelBackup(),await this.backup();const e=await this.store.get(t.id);this.notify("form-submit",{config:t,data:e.changes})}else this.notify("form-submit",{config:t,data:this.changes.get(t.id)?.changes??{}});t.options.endpoint&&!t.options.handled&&(console.log("Submitting form..."),await this.handleServerSave(t,this.collectFormData(t.form)))}if(t.options.showSummary){const e=await this.store.get(t.id);this.showSummary({config:t,changes:e?.changes})}}}updateItem(e,t,s){if(void 0===t)return;this.changes.has(s.id)||this.changes.set(s.id,{id:s.id,timestamp:Date.now(),src:window.location.pathname,changes:{}});let i=this.changes.get(s.id);e.includes("_tempUpload")?(Object.hasOwn(i.changes,e)||(i.changes[e]=[]),i.changes[e].push(t)):i.changes[e]=t,s.tabs&&(console.log("Updating step progress on change"),this.updateStepProgress(s)),this.changes.set(s.id,i),s.options.cache&&this.scheduleBackup(),s.options.endpoint&&!s.options.handled&&this.scheduleServerSave(s,i)}updateStepProgress(e){if(console.log("First check in updateStepProgress"),!e.tabs)return;let t=this.getCurrentStep(e);if(console.log("Current step: ",t),!t)return;t-=1;let s=this.getCurrentSection(e);if(console.log("Current section: ",s),!s)return;let i=0,a=0;if(s.childNodes.forEach(e=>{if(console.log("Child node: ",e),e.classList.contains("field")){i++;let t=this.getFieldValue(e.querySelector(this.inputSelectors));console.log("Value",t),t.length>0&&a++}}),0===i)return;let r=t+a/i,n=e.ui.tabs.sections.length;window.showProgress(e.ui.tabs.progress,r,n,`Step ${t+1} of ${n}`)}getCurrentStep(e){let t=null;return e.tabs.ui.buttons.forEach(s=>{s.dataset.tab===e.tabs.activeTab&&(t=s.dataset.step)}),t}getCurrentSection(e){console.log("Getting current section: ",e.tabs.ui.sections);let t=null;return e.tabs.ui.sections.forEach(s=>{s.dataset.tab===e.tabs.activeTab&&(t=s)}),t}scheduleBackup(){window.debouncer.schedule("form_changes",async()=>{this.changes.size>0&&await this.backup()},2e3)}cancelBackup(){window.debouncer.cancel("form_changes")}async backup(){const e=new Map;for(let[t,s]of this.changes.entries()){const i=await this.store.get(t);i?e.set(t,{...i,...s,changes:{...i.changes,...s.changes},timestamp:Date.now()}):e.set(t,s)}await this.store.saveMany(e);for(let e of this.changes.keys())this.showFormStatus(e,"autosaved");this.changes.clear()}saveCache(e){if(!this.changes.has(e))return;let t=this.changes.get(e);0!==t.size&&(this.store.save(t).then(()=>{}),this.changes.delete(e))}scheduleServerSave(e,t){window.debouncer.schedule(`form_${e.id}_server_save`,async()=>{await this.handleServerSave(e,t)},1500)}cancelServerSave(e){window.debouncer.cancel(`form_${e}_server_save`)}async handleServerSave(e,t){this.cancelServerSave(e.id),window.jvbQueue?(t.user=window.auth.getUser(),window.jvbQueue.addToQueue({endpoint:e.options.endpoint,data:t,headers:e.options.headers??{}})):await window.auth.fetch(jvbBase.api+e.options.endpoint,{body:t,headers:e.options.headers??{}})}registerForm(e,t){if(t={autoUpload:!1,imageMeta:!0,delay:1500,endpoint:Object.hasOwn(e.dataset,"save")?e.dataset.save:"",showStatus:!0,showSummary:!1,handled:Object.hasOwn(e.dataset,"handled"),cache:!0,ignore:[],...t},Object.hasOwn(e.dataset,"formId")&&this.forms.has(e.dataset.formId))return;Object.hasOwn(e.dataset,"formId")||(e.dataset.formId=window.generateID("form_"));const s=e.dataset.formId;this.addFormListeners(e);const i={element:e,id:s,status:"",options:t,ui:window.uiFromSelectors(this.selectors.forms,e)};return i.ui.fields={},e.querySelectorAll("[data-field]").forEach(e=>{i.ui.fields[e.dataset.field]=e}),this.initializeFields(e,i),this.forms.set(s,i),i}clearForm(e){const t=this.forms.get(e);if(!t)return;t.unsubscribeTabs&&t.unsubscribeTabs(),t.tabs&&window.jvbTabs.removeTab(t.element),t.cache&&this.changes.has(e)&&this.saveCache(e);for(let[t,s]of this.inputs.entries())s.form===e&&this.inputs.delete(t);if(this.dependencies.forEach((t,s)=>{0===(t=t.filter(t=>t.form!==e)).length&&this.dependencies.delete(s)}),Object.hasOwn(t,"hasQuill")&&this.quillInstances.has(e)){this.quillInstances.get(e).forEach(e=>{e.disable(),e.off("text-change"),e.off("selection-change");const t=e.container.parentElement,s=t?.querySelector(".ql-toolbar");if(s&&s.remove(),e.setText(""),t&&t.classList.contains("editor-container")){const e=t.nextElementSibling;"TEXTAREA"===e?.tagName&&(e.style.display=""),t.remove()}}),this.quillInstances.delete(e)}let s={repeater:this.repeaters,tagList:this.tagLists,charLimit:this.charLimits,quantity:this.quantityFields};for(let[t,i]of Object.entries(s)){if(0===i.size)continue;let s=Array.from(i.values()).filter(t=>t.form===e);s.length>0&&s.forEach(e=>{switch(t){case"repeater":this.removeRepeaterListeners(e.element);break;case"tagList":this.removeTagListListeners(e.element);break;case"charLimit":this.removeCharacterLimitListeners(e.element);break;case"quantity":this.removeQuantityListeners(e.element)}i.has(e.id)&&i.delete(e.id)})}this.removeFormListeners(t.element),this.forms.delete(e),window.debouncer.cancel("form_changes")}defineSummaryTemplate(){this.summaryTemplate=!0;let e=this;this.templates.define("formSummary",{refs:{result:".result",h3:"h3",p:"p"},setup({el:t,refs:s,manyRefs:i,data:a}){const r=["sendAll",...a.config.options.ignore??[]];for(let[i,n]of Object.entries(a.changes)){if(r.includes(i)||e.isEmptyValue(n))continue;let a=Array.from(e.inputs.values()).find(e=>e.field?.dataset.field===i);if(!a)continue;let l=s.result.cloneNode(!0),o=l.querySelector("h3"),d=l.querySelector("p");const c=a.field?.querySelector("legend");o.textContent=c?c.textContent.replace("*","").trim():a.ui.label?.textContent.replace("*","").trim();const u=e.formatValueForSummary(n,a);u instanceof HTMLElement?d.replaceWith(u):d.textContent=u,t.append(l)}let n=a.config?.element?.querySelectorAll("[data-upload-field]");n&&n.forEach(e=>{let i=e.querySelector("h2")?.textContent??"Upload:",a=e.querySelectorAll(".item-grid.preview img"),r=s.result.cloneNode(!0);if(a){let e=s.result.cloneNode(!0),n=r.querySelector("h3"),l=r.querySelector("p");l?.remove(),n&&(n.textContent=i),a.forEach(t=>{t=t.cloneNode(!0),e.append(t)}),t.append(e)}}),s.result?.remove(),a.config.element.after(t),window.fade(a.config.element,!1)}})}initializeFields(e,t=null){const s={"[data-editor]":()=>this.checkForQuill(e,t),"div.quantity":()=>this.checkForQuantity(e),".repeater":()=>this.checkForRepeaters(e,t),".field.tag-list":()=>this.checkForTagLists(e),"[data-depends-on]":()=>this.checkForConditionalFields(e),"[data-limit]":()=>this.checkForCharacterLimits(e),"[data-uploader],[data-upload-field]":()=>this.checkForImageUploads(e,t),"nav.tabs":()=>this.checkForTabs(e,t),'[data-type="selector"]':()=>this.checkForSelectors(e)};for(const[t,i]of Object.entries(s))e.querySelector(t)&&i();Array.from(e.querySelectorAll(this.inputSelectors)).filter(e=>!e.closest(".ql-clipboard")).map(e=>{this.getItem(e,t?.id)})}checkForQuill(e,t){if(!e.querySelector("[data-editor]"))return;t&&!Object.hasOwn(t,"hasQuill")&&(t.hasQuill=!0,this.forms.set(t.id,t)),this.quillInstances.has(t.id)||this.quillInstances.set(t.id,new Set);window.jvbQuill(e).forEach(e=>{this.quillInstances.get(t.id).add(e)})}checkForQuantity(e){e.querySelector(this.selectors.number.number)&&e.querySelectorAll(this.selectors.number.number).forEach(t=>{let s={id:window.generateID("quant"),form:e.dataset.formId,ui:window.uiFromSelectors(this.selectors.number,t),element:t};t.dataset.numId=s.id,this.quantityFields.set(s.id,s),this.addQuantityListeners(t)})}addQuantityListeners(e){e.addEventListener("click",this.quantityClick)}removeQuantityListeners(e){e.removeEventListener("click",this.quantityClick)}handleQuantityClick(e){let t=this.quantityFields.get(e.target.closest("[data-num-id]")?.dataset.numId);if(!t)return;let s=0;if(t.ui.increase.contains(e.target)?s++:t.ui.decrease.contains(e.target)&&s--,0===s)return;this.getField(e.target);let i=t.ui.input.step;i=Math.max(i,1),e.ctrlKey&&e.shiftKey?i*=50:e.ctrlKey?i*=5:e.shiftKey&&(i*=10);let a=""===t.ui.input.value?0:parseFloat(t.ui.input.value);t.ui.input.value=a+i*s,a=parseFloat(t.ui.input.value),t.ui.input.min&&a<t.ui.input.min?(t.ui.input.value=t.ui.input.min,t.ui.decrease.disabled=!0):t.ui.input.max&&a>t.ui.input.max?(t.ui.input.value=t.ui.input.max,t.ui.increase.disabled=!0):(t.ui.decrease.disabled&&(t.ui.decrease.disabled=!1),t.ui.increase.disabled&&(t.ui.increase.disabled=!1))}checkForRepeaters(e){e.querySelector(this.selectors.repeater.repeater)&&e.querySelectorAll(this.selectors.repeater.repeater).forEach(t=>{let s={id:t.querySelector("template").className??window.generateID("repeater"),ui:window.uiFromSelectors(this.selectors.repeater,t),form:e.dataset.formId,element:t,field:this.getField(t),sortable:!1,rows:[]};if(!s.ui.add)return;let i=t.querySelector("template");this.templates.define(i.className,{manyRefs:{inputs:this.inputSelectors},setup({el:e,refs:t,manyRefs:i,data:a}){let r=s.ui.items?.children?.length??0;e.dataset.index=r,i.inputs?.forEach(t=>{window.prefixInput(t,`${a.repeater.dataset.field}:${r}:`,e,!1,!0)})}}),window.Sortable&&(s.sortable=new Sortable(s.ui.items,{animation:150,draggable:this.selectors.repeater.row,handle:this.selectors.repeater.handle,ghostClass:"repeater-row-ghost",chosenClass:"repeater-row-chosen",dragClass:"dragging",onEnd:()=>{this.reindexList(s.ui.items)}})),t.dataset.repeaterId=s.id,this.addRepeaterListeners(t),this.repeaters.set(s.id,s)})}addRepeaterListeners(e){e.addEventListener("click",this.repeaterClick)}removeRepeaterListeners(e){e.removeEventListener("click",this.repeaterClick)}handleRepeaterClick(e){e.target.matches(this.selectors.repeater.add)?this.addRepeaterRow(e.target.closest("[data-repeater-id]")):e.target.matches(this.selectors.repeater.remove)&&this.removeRepeaterRow(e.target.closest("[data-index]"))}addRepeaterRow(e){let t={};t.repeater=e;let s=this.repeaters.get(e.dataset.repeaterId),i=this.templates.create(e.dataset.repeaterId,t);s.rows.push({element:i,fields:Array.from(i.querySelectorAll("[data-field]"))}),this.repeaters.set(s.id,s),s.ui.items.append(i);let a=this.getForm(e);this.initializeFields(e,a),this.reindexList(s.ui.items),this.a11y.announce("Row added")}removeRepeaterRow(e){let t=e.closest("[data-repeater-id]");e.remove();let s=this.repeaters.get(t);s&&this.reindexList(s.ui.items),this.a11y.announce("Row removed")}checkForTagLists(e){e.querySelectorAll(this.selectors.tagList.tagList)?.forEach(t=>{let s={id:t.querySelector("template").className??window.generateID("tagList"),ui:window.uiFromSelectors(this.selectors.tagList,t),element:t,form:e.dataset.formId,format:t.dataset.tagFormat??"first_field"};if(!s.ui.input||!s.ui.add||!s.ui.items)return;t.dataset.tagListId=s.id,s.fieldName=t.dataset.field;let i=t.querySelector("template");this.templates.define(i.className,{refs:{label:this.selectors.tagList.label},manyRefs:{inputs:this.inputSelectors},setup({el:e,refs:t,manyRefs:i,data:a}){let r=s.ui.items?.children?.length??0;e.dataset.index=r,i.inputs?.forEach(e=>{let t=e.closest(".tag-item");window.prefixInput(e,`${a.fieldName}:${r}:`,t,!1,!0)}),t.label&&(t.label.textContent=a.label)}}),s.ui.inputs=Array.from(t.querySelectorAll(this.selectors.tagList.inputs)),s.ui.value=Array.from(t.querySelectorAll(this.selectors.tagList.value)),this.tagLists.set(s.id,s),this.addTagListListeners(t)})}addTagListListeners(e){e.addEventListener("click",this.tagListClick),e.addEventListener("keypress",this.tagListInput)}removeTagListListeners(e){e.removeEventListener("click",this.tagListClick),e.removeEventListener("keypress",this.tagListInput)}handleTagListClick(e){window.targetCheck(e,this.selectors.tagList.add)?this.addTagListItem(e.target.closest("[data-tag-list-id]")):window.targetCheck(e,this.selectors.tagList.remove)&&this.removeTagListItem(e.target.closest(this.selectors.tagList.item))}addTagListItem(e){let t=this.tagLists.get(e.dataset.tagListId);if(!t)return;let s,i={},a=!1,r=!0;for(let e of t.ui.inputs){const t=e.required||"true"===e.dataset.required,s=this.getFieldValue(e);s&&(a=!0);const n=this.validateField(e);t&&!s?(this.showError(e,"This field is required"),r=!1):n||(r=!1);const l=e.name.replace("new_","");i[l]=s}if(!r){this.a11y.announce("Please correct the errors before adding");const e=t.ui.inputs.find(e=>(e.required||"true"===e.dataset.required)&&!this.getFieldValue(e));return void(e&&e.focus())}if(!a)return this.a11y.announce("Please fill in at least one field"),void t.ui.inputs[0].focus();switch(t.format){case"first_field":s=Object.values(i)[0];break;case"all_fields":s=Object.values(i).join(", ");break;default:if(t.format.includes("{")){s=t.format;for(const[e,t]of Object.entries(i))s=s.replace(`{${e}}`,t)}else s=i[t.format]??Object.values(i)[0]}let n=this.templates.create(e.dataset.tagListId,{label:s,fieldName:t.fieldName});const l=t.ui.items?.children?.length??0;n?.querySelectorAll("input[type=hidden]")?.forEach(e=>{const s=e.dataset.field;e.name=`${t.fieldName}:${l}:${s}`,e.id=`${t.fieldName}:${l}:${s}`,e.value=i[s]||""}),t.ui.items.append(n);for(let e of t.ui.inputs)["checkbox","radio"].includes(e.type)?e.checked=!1:e.value="",this.clearValidation(e);t.ui.inputs[0]?.focus(),this.updateCollectionField(e),this.a11y.announce("Item added")}removeTagListItem(e){let t=e.closest("[data-tag-list-id]");t&&(e.remove(),this.reindexList(t),this.updateCollectionField(t),this.a11y.announce("Item removed"))}handleTagListInput(e){let t=e.target,s=t.closest("[data-tag-list-id]");if(!s)return;let i=this.tagLists.get(s.dataset.tagListId);if(i&&"Enter"===e.key)if(t===i.ui.inputs[i.ui.inputs.length-1])e.preventDefault(),this.addTagListItem(t.closest("[data-tag-list-id]"));else{e.preventDefault();let s=i.ui.inputs.indexOf(t);i.ui.inputs[s+1].focus()}}checkForConditionalFields(e){e.querySelectorAll(this.selectors.dependsOn).forEach(t=>{const s=t.dataset.dependsOn,i=t.dataset.dependsValue,a=t.dataset.dependsOperatior??"==";let r=this.forms.get(e.dataset.formId);this.dependencies.has(s)||Object.hasOwn(r.ui.fields,s)&&this.dependencies.set(s,[]);let n=this.dependencies.get(s);n&&(n.push({field:t,form:e.dataset.formId,requiredValue:i,operator:a}),this.dependencies.set(s,n)),this.checkFieldDependency(t,s)})}checkFieldDependency(e,t){const s=this.getForm(e);if(!this.dependencies.get(t))return;const i=this.getFieldValue(s.ui.fields[t]),a=this.evaluateCondition(i,e.dataset.dependsValue,e.dataset.dependsOperatior);this.toggleFieldVisibility(e,a)}evaluateCondition(e,t,s){const i=String(e||""),a=String(t||"");switch(s){case"==":default:return i===a;case"!=":return i!==a;case">":return parseFloat(i)>parseFloat(a);case"<":return parseFloat(i)<parseFloat(a);case">=":return parseFloat(i)>=parseFloat(a);case"<=":return parseFloat(i)<=parseFloat(a);case"contains":return i.includes(a);case"empty":return""===i;case"not_empty":return""!==i}}toggleFieldVisibility(e,t){const s=e.closest(".field, fieldset");s&&(s.hidden=!t,s.querySelectorAll("input, select, textarea").forEach(e=>{e.disabled=!t,!t&&e.hasAttribute("required")?(e.dataset.wasRequired="true",e.removeAttribute("required")):t&&"true"===e.dataset.wasRequired&&(e.setAttribute("required",""),delete e.dataset.wasRequired)}))}checkForCharacterLimits(e){e.querySelector(this.selectors.limits.hasLimit)&&(this.countUpdaters=this.updateCount.bind(this),e.querySelectorAll(this.selectors.limits.hasLimit).forEach(t=>{const s=this.getFieldInput(t);if(!s)return;let i=window.generateID("limit");s.dataset.charLimitId=i,s.dataset.limit=t.dataset.maxlength;let a={element:s,form:e.dataset.formId,ui:window.uiFromSelectors(this.selectors.limits,t)};a.ui.limit&&(a.ui.limit.textContent=t.dataset.maxlength),this.charLimits.set(i,a),this.addCharacterLimitListeners(s)}))}addCharacterLimitListeners(e){e.addEventListener("input",this.countUpdaters,{passive:!0})}removeCharacterLimitListeners(e){e.removeEventListener("input",this.countUpdaters,{passive:!0})}updateCount(e){let t=e.target,s=this.charLimits.get(t.dataset.charLimitId);if(!s)return;let i=t.value.length,a=t.dataset.limit;s.ui.current&&(s.ui.current.textContent=i,s.ui.current.classList.toggle("exceeded",i>=a)),i>a&&(t.value=t.value.slice(0,a))}checkForImageUploads(e,t){this.hasUploads=!0,window.jvbUploads.scanFields(e,t.options.autoUpload,t.options.imageMeta);let s=e.querySelectorAll('[data-field-type="upload"]');s&&(t.ui.uploads={},s.forEach(e=>{t.ui.uploads[e.dataset.field]=e}))}checkForTabs(e,t){window.jvbTabs&&e.querySelector("nav.tabs")&&(t.tabs=window.jvbTabs.registerTab(e,{preCheck:(e,t,s)=>this.validateStep(e,t,s)}),t.ui.tabs=window.uiFromSelectors(this.selectors.tabs,e),t.ui.tabs.sections=Array.from(e.querySelectorAll(this.selectors.tabs.sections)),t.ui.tabs.inputs={},t.ui.tabs.sections.forEach(e=>{t.ui.tabs.inputs[e.dataset.tab]=Array.from(e.querySelectorAll(this.inputSelectors))}),t.ui.tabs.buttons=Array.from(e.querySelectorAll(this.selectors.tabs.buttons)),t.unsubscribeTabs=window.jvbTabs.subscribe((e,s)=>{if("tab-switched"===e&&t.ui.tabs.progress){const e=t.ui.tabs.sections.filter(e=>e.dataset.tab===s.current)[0]??!1;if(!e)return;const i=e.dataset.step-1,a=t.ui.tabs.sections.length;window.showProgress(t.ui.tabs.progress,i,a,`Step ${i+1} of ${a}`)}}),this.forms.set(t.id,t))}validateStep(e,t,s){let i=!1;if(s.ui.sections&&s.ui.sections.forEach((a,r)=>{if(a===e){let e=r-1;if(e>=0){let a=s.ui.sections[e];a&&a.dataset.tab===t&&(i=!0)}}}),i)return!0;const a=e.closest("[data-form-id]")?.dataset.formId;if(!a)return!0;if(!this.forms.get(a))return!0;return Array.from(this.inputs.values()).filter(t=>t&&t.form===a&&t.section===e.dataset.tab&&!t.element.closest("[hidden]")).every(e=>!0===this.validateField(e.element))}checkForSelectors(e){window.jvbSelector&&window.jvbSelector.scanExistingFields(e)}reindexList(e){let t=e.dataset.field||e.dataset.repeaterId||e.dataset.tagListId;if(!t){let s=e.closest("[data-field]");t=s.dataset.field||s.dataset.repeaterId||s.dataset.tagListId}Array.from(e.children).forEach((e,s)=>{e.dataset.index=`${s}`;let i=e.querySelector(".row-number");i&&(i.textContent=`#${s+1}`);let a=e.querySelector(".row-title"),r=a?a.dataset.label:null;e.querySelectorAll("input, select, textarea").forEach(i=>{if("file"===i.type)return;const n=Object.hasOwn(i.dataset,"field")?i.dataset.field:i.name.split(":").pop();n===r&&(a.textContent=window.escapeHtml(i.value)),window.prefixInput(i,`${t}:${s}:${n}`,e,!0,!0)})}),this.updateCollectionField(e)}updateCollectionField(e){const t=e.closest("[data-field]");if(!t)return;const s=t.dataset.fieldType;if(!["repeater","tag-list"].includes(s))return;const i=this.getForm(e);if(!i)return;const a=this.getFieldValue(t);this.updateItem(t.dataset.field,a,i)}maybeUpdateCollectionDisplay(e){let t=e.closest("[data-repeater-id],[data-tag-list-id]");if(t&&"repeater"===t.dataset.fieldType){let t=e.closest(".repeater-row");if(!t)return;let s=t.querySelector(".row-title");if(!s||!s.dataset.label)return;console.log(e.dataset.field.split(":").pop()),console.log(s.dataset.label),e.dataset.field.split(":").pop()===s.dataset.label&&(s.textContent=this.getFieldValue(e.querySelector(this.inputSelectors)))}}clearValidation(e){let t=this.getField(e);if(!t)return;let s=this.getItem(e);s&&(t.classList.remove("has-error","has-success"),s.ui.success&&(s.ui.success.hidden=!0),s.ui.error&&(s.ui.error.hidden=!0),s.ui.message&&(s.ui.message.hidden=!0,s.ui.message.textContent=""))}showError(e,t="Invalid field"){let s=this.getField(e);if(!s)return;let i=this.getItem(e);i&&(s.classList.remove("has-success"),s.classList.add("has-error"),i.ui.message&&(i.ui.message.hidden=!1,i.ui.message.textContent=t))}showSuccess(e,t=""){let s=this.getField(e);if(!s)return;let i=this.getItem(e);i&&(s.classList.remove("has-error"),s.classList.add("has-success"),i.ui.message&&(i.ui.message.hidden=""===t,i.ui.message.textContent=t))}handleFormSuccess(e,t){if(e.querySelectorAll(".error-message").forEach(e=>e.remove()),e.querySelectorAll(".field-error").forEach(e=>e.classList.remove("field-error")),e.classList.add("form-success"),t.message){const s=document.createElement("div");s.className="form-success-message success-message",s.textContent=t.message,e.insertBefore(s,e.firstChild);const i=window.getIcon?.("check-circle");i&&(i.classList.add("success-icon"),s.prepend(i))}if(t.title||t.description){const s=document.createElement("div");if(s.className="success-box",t.title){const e=document.createElement("h3");e.textContent=t.title,s.appendChild(e)}if(t.description){(Array.isArray(t.description)?t.description:[t.description]).forEach(e=>{const t=document.createElement("p");t.textContent=e,s.appendChild(t)})}e.insertBefore(s,e.firstChild)}if(e.dataset.formId){this.store.delete(e.dataset.formId).catch(e=>{console.warn("Failed to clear form cache:",e)});const t=this.forms.get(e.dataset.formId);t&&(t.isDirty=!1,t.lastSaved=Date.now(),t.data={})}window.jvbA11y&&window.jvbA11y.announce(t.message||"Form submitted successfully")}handleFormError(e,t){if(e.querySelectorAll(".error-message").forEach(e=>e.remove()),e.querySelectorAll(".field-error, .has-error").forEach(e=>{e.classList.remove("field-error","has-error")}),e.querySelectorAll(".field").forEach(e=>{this.clearValidation(e)}),t.field){const s=e.querySelector(`[data-field="${t.field}"]`);if(s){this.showError(s,t.message),s.scrollIntoView({behavior:"smooth",block:"center"});const e=s.querySelector("input, textarea, select");e&&e.focus()}}else{const s=document.createElement("div");s.className="form-error error-message",s.textContent=t.message;const i=window.getIcon?.("close-circle");i&&(i.classList.add("error-icon"),s.prepend(i)),e.insertBefore(s,e.firstChild),e.scrollIntoView({behavior:"smooth",block:"start"})}if(window.jvbA11y){const e=t.field?`Error in ${t.field}: ${t.message}`:`Form error: ${t.message}`;window.jvbA11y.announce(e)}e.dispatchEvent(new CustomEvent("jvb-form-error",{detail:t}))}showFormStatus(e,t,s=""){let i=this.forms.get(e);i&&i.options.showStatus&&i.ui?.status?.status&&i.status!==t&&(i.status=t,i.ui.status.status.hidden=!1,i.ui.status.status.classList.toggle("loading",["uploading","saving"].includes(t)),i.ui.status.message.textContent=""===s?this.getDefaultMessage(t):s,i.ui.status.icon.className="icon icon-"+this.getDefaultIcon(t),setTimeout(()=>i.ui.status.status.hidden=!0,"submitted"===t?3e3:1e4))}getDefaultMessage(e){return{saving:"Saving changes...",autosaved:"Changes saved locally. Submit form to send to server.",uploading:"Uploading your form to server",submitted:"Successfully sent to server",pending:"Unsaved changes",restored:"Welcome back! We've restored your previous entry.",error:"Failed to save changes. Refresh and try again?",offline:"Changes will be saved when online"}[e]??e}getDefaultIcon(e){return{autosaved:"check-circle",submitted:"check-circle",restored:"history",error:"close-circle",offline:"cloud-slash",pending:"exclamation-mark"}[e]??""}showSummary(e){let t=this.templates.create("formSummary",e);e.config.element.after(t),window.fade(e.config.element,!1)}getForm(e){let t=e.closest("[data-form-id]");if(!t)return!1;let s=t.dataset.formId;if(!s)return!1;let i=this.forms.get(s);return i||!1}getField(e){return e.closest("[data-field]")}getFieldType(e){let t=this.getField(e);if(t)return t.dataset.fieldType}getFieldValue(e){let t=this.getFieldType(e),s=this.getItem(e),i=s.field?.dataset.field??!1;if(!i)return!1;switch(t){case"repeater":return this.getRepeaterValue(e,s);case"tag-list":return this.getTagListValue(e,s);case"group":return null;case"location":return this.getLocationValue(e,s);case"selector":case"upload":case"gallery":case"image":return this.getHiddenInputValue(e,s,i);case"true-false":case"toggle-text":return e.checked;case"checkbox":return e.name.endsWith("[]")?this.getCheckboxGroupValue(e,s):e.checked?e.value:"";default:return e.value}}getCheckboxGroupValue(e,t){return t.checkboxGroup||(t.checkboxGroup=t.field?.querySelectorAll(`input[type="checkbox"][name="${e.name}"]`),this.saveItem(t)),Array.from(t.checkboxGroup).filter(e=>e.checked).map(e=>e.value)}getFieldCheckedValue(e){if("checkbox"===e.type){return"true-false"===this.getFieldType(e)?e.checked:e.checked?e.value:""}if("radio"===e.type){const t=document.querySelectorAll(`input[name="${e.name}"]`),s=Array.from(t).find(e=>e.checked);return s?s.value:""}return this.getFieldValue(e)}isEmptyValue(e){return null==e||""===e||(!(!Array.isArray(e)||0!==e.length)||"object"==typeof e&&0===Object.keys(e).length)}getRepeaterValue(e,t){const s=e.querySelector(".repeater-items");if(!s)return[];let i=["image_data","image-title","image-caption","image-description","image-alt-text"],a=[];return Array.from(s.children).forEach(e=>{let t={};e.querySelectorAll('input[type="hidden"]').forEach(e=>{t[e.name]=e.value}),e.querySelectorAll("[data-field]").forEach(e=>{if(!i.includes(e.dataset.field)){const s=this.getFieldInput(e);s&&(t[e.dataset.field]=this.getFieldValue(s))}}),a.push(t)}),a}getFieldInput(e){const t=e.querySelector("textarea[data-editor]");return t||e.querySelector(this.inputSelectors)}getTagListValue(e,t){t.container||(t.container=t.field?.querySelector(".tag-items"),this.saveItem(t));let s=[];return Array.from(t.container.children).forEach(e=>{let t=e.querySelectorAll('input[type="hidden"]'),i={};t.forEach(e=>{i[e.dataset.field]=e.value}),s.push(i)}),s}getLocationValue(e,t){t.values||(t.values=Array.from(t.field?.querySelectorAll("[data-location-field]")),this.saveItem(t));let s={};return t.values.forEach(e=>{s[e.dataset.locationField]=e.value}),s}getHiddenInputValue(e,t,s){return"INPUT"===e.tagName&&"hidden"===e.type||(e=e.querySelector('input[type="hidden"][name="'+s+'"]'))?(void 0!==t.value&&t.value===e.value||(t.value=e.value,this.saveItem(t)),t.value):null}formatValueForSummary(e,t){const s=this.getFieldType(t.element);if(this.isEmptyValue(e))return"";switch(s){case"repeater":return this.formatRepeaterForSummary(e,t);case"tag-list":return this.formatTagListForSummary(e,t);case"location":return this.formatLocationForSummary(e);case"true-false":return e?"Yes":"No";case"checkbox":return Array.isArray(e)?this.formatCheckboxGroupForSummary(e,t):this.getDisplayLabel(t,e);case"selector":case"upload":case"image":case"gallery":return this.formatHiddenFieldForSummary(e,t,s);default:return"string"==typeof e?this.getDisplayLabel(t,e):"string"==typeof e&&e.includes("\n")?this.convertLineBreaks(e):e}}formatCheckboxGroupForSummary(e,t){return e.map(e=>this.getDisplayLabel(t,e)).join(", ")}convertLineBreaks(e){const t=document.createElement("span");return t.innerHTML=e.split("\n").join("<br>"),t}formatRepeaterForSummary(e,t){const s=document.createElement("div");return s.className="summary-repeater",e.forEach((e,i)=>{const a=document.createElement("div");a.className="summary-repeater-row";const r=document.createElement("strong");r.textContent=`Entry ${i+1}:`,a.appendChild(r);const n=document.createElement("ul");n.className="summary-repeater-fields";for(const[s,i]of Object.entries(e)){if(this.isEmptyValue(i))continue;const e=document.createElement("li"),a=t.field?.querySelector(`[data-field="${s}"]`),r=a?.closest(".field")?.querySelector("label")?.textContent.replace("*","").trim()||s;e.innerHTML=`<span class="field-label">${r}:</span> <span class="field-value">${i}</span>`,n.appendChild(e)}a.appendChild(n),s.appendChild(a)}),s}formatTagListForSummary(e,t){const s=document.createElement("div");s.className="summary-taglist";const i=document.createElement("ul");return i.className="summary-tags",e.forEach(e=>{const t=document.createElement("li");t.className="summary-tag";const s=Object.values(e).find(e=>!this.isEmptyValue(e))||"",a=Object.entries(e).filter(([e,t])=>!this.isEmptyValue(t));a.length>1?t.textContent=a.map(([e,t])=>t).join(", "):t.textContent=s,i.appendChild(t)}),s.appendChild(i),s}formatLocationForSummary(e){const t=[];return e.street&&t.push(e.street),e.city&&t.push(e.city),e.province&&t.push(e.province),e.postal_code&&t.push(e.postal_code),e.country&&t.push(e.country),t.length>0?t.join(", "):e.address||""}formatHiddenFieldForSummary(e,t,s){if(["upload","gallery","image"].includes(s)){const s=t.field?.querySelector("[data-upload-field]");if(s){const e=s.querySelectorAll(".item-grid.preview img");if(e.length>0){const t=document.createElement("div");return t.className="summary-uploads",e.forEach(e=>{const s=e.cloneNode(!0);s.style.maxWidth="100px",s.style.maxHeight="100px",t.appendChild(s)}),t}}return`${e.split(",").length} file(s) uploaded`}return e}getDisplayLabel(e,t){if(!e.element)return t;const s=e.element.type;if("radio"===s){const s=e.field.querySelectorAll(`input[type="radio"][name="${e.element.name}"]`),i=Array.from(s).find(e=>e.value===t);if(i){const t=i.closest("label")||e.field.querySelector(`label[for="${i.id}"]`);if(t)return t.textContent.replace("*","").trim()}}if("checkbox"===s&&"true-false"!==this.getFieldType(e.element)){const s=e.field.querySelector(`input[type="checkbox"][value="${t}"]`);if(s){const t=s.closest("label")||e.field.querySelector(`label[for="${s.id}"]`);if(t){const e=t.querySelector("span");return e?e.textContent.trim():t.textContent.replace("*","").trim()}}}return t}getItem(e,t=null){const s=Object.hasOwn(e.dataset,"ref");let i=s?e.dataset.ref:window.generateID("input");if(s||(e.dataset.ref=i),!this.inputs.has(i)){t||(t=e.closest("[data-form-id]")?.dataset.formId??!1);let s=this.getField(e);this.inputs.set(i,{id:i,element:e,form:t,field:s,section:e.closest("[data-tab]")?.dataset.tab??!1,ui:window.uiFromSelectors(this.selectors.fields,s)})}return this.inputs.get(i)}saveItem(e){this.inputs.set(e.id,e)}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(s=>{try{s(e,t)}catch(e){console.error("HandleSelection subscriber error:",e)}})}destroy(){this.forms.size>0&&(Array.from(this.forms.values()).forEach(e=>{this.removeFormListeners(e)}),this.forms.clear()),this.repeaters.size>0&&(Array.from(this.repeaters.values()).forEach(e=>{this.removeRepeaterListeners(e.element),e.sortable?.destroy()}),this.repeaters.clear()),this.quantityFields.size>0&&(Array.from(this.quantityFields.values()).forEach(e=>{this.removeQuantityListeners(e.element)}),this.quantityFields.clear()),this.tagLists.size>0&&(Array.from(this.tagLists.values()).forEach(e=>{this.removeTagListListeners(e.element)}),this.tagLists.clear()),this.charLimits.size>0&&Array.from(this.charLimits.values()).forEach(e=>{e.element.removeEventListener("input",this.countUpdaters)}),this.inputs.clear(),this.forms.clear(),this.charLimits.clear()}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbForm=new e)})})})();
\ No newline at end of file
diff --git a/assets/js/min/gallery.min.js b/assets/js/min/gallery.min.js
index 482d943..a7be034 100644
--- a/assets/js/min/gallery.min.js
+++ b/assets/js/min/gallery.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.index=0,this.images=[],this.zoom={scale:1,min:1,max:4,threshold:50,x:0,y:0,startX:0,startY:0,ease:.2,panning:!1},this.swipe=this.resetSwipe(),this.activePointers=new Map,this.lastTap=0,this.initElements(),this.initModal(),this.initListeners(),this.initSubscribers()}initElements(){this.elements={imageSelector:"img[data-gallery]",gallery:{modal:"dialog.gallery",wrap:".wrap",nextButton:".next",prevButton:".prev",image:".image",leftImage:".image-left",rightImage:".image-right",counter:".counter",caption:"details .item-info"}},this.ui=window.uiFromSelectors(this.elements)}initModal(){this.modal=new window.jvbModal(this.ui.gallery.modal,{openMessage:"Opened Gallery",closeMessage:"Closed Gallery"}),this.modal.subscribe((e=>{"modal-close"===e&&this.toggleGallery(!1)}))}buildGalleryItems(e=null){let t=e?`[data-gallery="${e}"]`:this.elements.imageSelector;this.items=Array.from(document.querySelectorAll(t)).map(((e,t)=>{let i="";return i=Object.hasOwn(e.dataset,"caption")?e.parentElement.querySelector(e.dataset.caption).cloneNode(!0):e.nextElementSibling&&"FIGCAPTION"===e.nextElementSibling.tagName?e.nextElementSibling.textContent:"",{id:e.dataset.id||t,srcset:e.srcset||e.src,sizes:e.sizes||"100vw",src:e.currentSrc||e.src,full:e.dataset.full||e.src,alt:e.alt||"",element:e,caption:i}}))}initListeners(){this.clickHandler=this.handleClick.bind(this),this.pointerDownHandler=this.onPointerDown.bind(this),this.pointerMoveHandler=this.onPointerMove.bind(this),this.pointerUpHandler=this.onPointerUp.bind(this),this.wheelHandler=this.onWheel.bind(this),this.keyHandler=this.handleKeys.bind(this),document.addEventListener("click",this.clickHandler)}handleClick(e){let t=window.targetCheck(e,this.elements.imageSelector);t&&!this.modal.isOpen?(e.preventDefault(),this.buildGalleryItems(t.dataset.gallery||null),this.index=this.items.findIndex((e=>e.element===t)),this.toggleGallery(!0)):this.modal.isOpen&&(window.targetCheck(e,this.elements.gallery.nextButton)?this.nextElement():window.targetCheck(e,this.elements.gallery.prevButton)&&this.prevElement())}handleKeys(e){if(this.modal.isOpen){switch(e.key){case"ArrowLeft":e.preventDefault(),this.prevElement();break;case"ArrowRight":e.preventDefault(),this.nextElement()}e.ctrlKey&&("+"!==e.key&&"="!==e.key||(e.preventDefault(),this.handleZoom(.2)),"-"===e.key&&(e.preventDefault(),this.handleZoom(-.2)),"0"===e.key&&(e.preventDefault(),this.resetZoom()))}}onPointerDown(e){e.preventDefault(),this.swipe.startX=e.clientX,this.swipe.startY=e.clientY,this.ui.gallery.image.setPointerCapture(e.pointerId),this.activePointers.set(e.pointerId,{x:e.clientX,y:e.clientY});const t=performance.now();if(t-this.lastTap<300&&1===this.activePointers.size)return this.zoom.scale>1?this.resetZoom():this.handleZoom(1,e.clientX,e.clientY),void(this.lastTap=0);if(this.lastTap=t,2===this.activePointers.size){const e=[...this.activePointers.values()];return this.pinchStartDist=Math.hypot(e[0].x-e[1].x,e[0].y-e[1].y),void(this.pinchStartScale=this.zoom.scale)}this.zoom.scale>1&&(this.zoom.panning=!0,this.zoom.startX=e.clientX-this.zoom.x,this.zoom.startY=e.clientY-this.zoom.y,this.ui.gallery.image.style.cursor="grabbing")}onPointerMove(e){if(this.activePointers.has(e.pointerId))if(this.activePointers.set(e.pointerId,{x:e.clientX,y:e.clientY}),2!==this.activePointers.size)this.zoom.panning&&(this.zoom.x=e.clientX-this.zoom.startX,this.zoom.y=e.clientY-this.zoom.startY,this.applyTransform());else{const e=[...this.activePointers.values()],t=Math.hypot(e[0].x-e[1].x,e[0].y-e[1].y),i=this.pinchStartScale*(t/this.pinchStartDist)-this.zoom.scale,s=(e[0].x+e[1].x)/2,n=(e[0].y+e[1].y)/2;this.handleZoom(i,s,n)}}onPointerUp(e){const t=this.activePointers.get(e.pointerId);if(this.activePointers.delete(e.pointerId),this.activePointers.size<2&&(this.pinchStartDist=0),this.zoom.scale<=1&&!this.zoom.panning&&0===this.activePointers.size){const i=t?.x??e.clientX,s=t?.y??e.clientY,n=i-this.swipe.startX,o=s-this.swipe.startY;Math.abs(n)>this.zoom.threshold&&Math.abs(n)>Math.abs(o)&&(n>0?this.prevElement():this.nextElement())}0===this.activePointers.size&&(this.zoom.panning=!1,this.ui.gallery.image.style.cursor=this.zoom.scale>1?"grab":"default")}onWheel(e){if(!e.ctrlKey)return;e.preventDefault();const t=e.deltaY<0?.2:-.2;this.handleZoom(t,e.clientX,e.clientY)}handleZoom(e,t=null,i=null){const s=this.zoom.scale;let n=s+e;if(n=Math.min(this.zoom.max,Math.max(this.zoom.min,n)),n===s)return;const o=n/s;let l=this.ui.gallery.image.getBoundingClientRect();null!==t&&null!==i||(t=l.left+l.width/2,i=l.top+l.height/2);const a=t-l.left,r=i-l.top;this.zoom.x=(this.zoom.x-a)*o+a,this.zoom.y=(this.zoom.y-r)*o+r,this.zoom.scale=n,this.applyTransform(),this.notify("zoom",{scale:this.zoom.scale})}applyTransform(){this.clampPan();const e=this.ui.gallery.image;e.style.transform=`translate(${this.zoom.x}px, ${this.zoom.y}px) scale(${this.zoom.scale})`,e.style.cursor=this.zoom.scale>1?"grab":"default"}clampPan(){const e=this.ui.gallery.image,t=Math.max(0,(e.offsetWidth*this.zoom.scale-window.innerWidth)/2),i=Math.max(0,(e.offsetHeight*this.zoom.scale-window.innerHeight)/2);this.zoom.x=Math.max(-t,Math.min(t,this.zoom.x)),this.zoom.y=Math.max(-i,Math.min(i,this.zoom.y))}resetZoom(){this.zoom.scale=1,this.zoom.x=0,this.zoom.y=0,this.zoom.startX=0,this.zoom.startY=0,this.zoom.panning=!1,this.applyTransform()}resetSwipe(){return{startX:null,startY:null,endX:null,endY:null}}toggleGallery(e){e?(this.ui.gallery.image.draggable=!1,this.ui.gallery.image.style.userSelect="none",this.ui.gallery.image.addEventListener("pointerdown",this.pointerDownHandler),this.ui.gallery.image.addEventListener("pointermove",this.pointerMoveHandler),this.ui.gallery.image.addEventListener("pointerup",this.pointerUpHandler),this.ui.gallery.image.addEventListener("pointercancel",this.pointerUpHandler),window.addEventListener("wheel",this.wheelHandler,{passive:!1}),window.addEventListener("keydown",this.keyHandler),this.moveIntoView()):(this.ui.gallery.image.removeEventListener("pointerdown",this.pointerDownHandler),this.ui.gallery.image.removeEventListener("pointermove",this.pointerMoveHandler),this.ui.gallery.image.removeEventListener("pointerup",this.pointerUpHandler),this.ui.gallery.image.removeEventListener("pointercancel",this.pointerUpHandler),window.removeEventListener("wheel",this.wheelHandler),window.removeEventListener("keydown",this.keyHandler),this.resetZoom(),this.resetSwipe(),this.activePointers.clear(),this.lastTap=0),e&&!this.modal.isOpen&&this.modal.handleOpen()}moveIntoView(e=0){let t=this.index+e;t<0?t=this.items.length-1:t>=this.items.length?t=0:t===this.items.length-3&&this.notify("load-more"),this.index=t,this.updateDisplay(),this.preloadAdjacent(),this.a11y.announce(`Image ${this.index+1} of ${this.items.length}`)}nextElement(){this.resetZoom(),this.moveIntoView(1)}prevElement(){this.resetZoom(),this.moveIntoView(-1)}updateDisplay(){const e=this.items[this.index];if(!e)return;const t=this.ui.gallery.image;if(e.srcset&&(t.srcset=e.srcset,t.sizes=e.sizes),t.src=e.src,t.alt=e.alt,window.removeChildren(this.ui.gallery.caption),"object"==typeof e.caption?this.ui.gallery.caption.append(e.caption):this.ui.gallery.caption.textContent=e.caption,e.full&&e.full!==e.src){const i=new Image;i.onload=()=>{this.items[this.index]===e&&(t.src=e.full,t.removeAttribute("srcset"),t.removeAttribute("sizes"))},i.src=e.full}this.ui.gallery.counter.textContent=`${this.index+1} / ${this.items.length}`,this.ui.gallery.prevButton.disabled=this.items.length<=1,this.ui.gallery.nextButton.disabled=this.items.length<=1}preloadAdjacent(){[-1,1].forEach((e=>{const t=this.index+e;if(t>0&&t<this.items.length){const i=this.items[t];(e<0?this.ui.gallery.leftImage:this.ui.gallery.rightImage).src=i.full}}))}initSubscribers(){this.subscribers=new Set}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t={}){this.subscribers.forEach((i=>{try{i(e,t)}catch(e){console.error("Subscriber error:",e)}}))}destroy(){this.subscribers.clear(),this.toggleGallery(!1),document.removeEventListener("click",this.clickHandler)}}document.addEventListener("DOMContentLoaded",(function(){document.querySelector("dialog.gallery")&&(window.jvbGallery=new e)}))})();
\ No newline at end of file
+(()=>{class t{constructor(){this.a11y=window.jvbA11y,this.index=0,this.images=[],this.zoom={scale:1,min:1,max:4,threshold:50,x:0,y:0,startX:0,startY:0,ease:.2,panning:!1},this.swipe=this.resetSwipe(),this.activePointers=new Map,this.lastTap=0,this.initElements(),this.initModal(),this.initListeners(),this.initSubscribers()}initElements(){this.elements={imageSelector:"img[data-gallery]",gallery:{modal:"dialog.gallery",wrap:".wrap",nextButton:".next",prevButton:".prev",image:".image",leftImage:".image-left",rightImage:".image-right",counter:".counter",caption:"details .item-info"}},this.ui=window.uiFromSelectors(this.elements)}initModal(){this.modal=new window.jvbModal(this.ui.gallery.modal,{openMessage:"Opened Gallery",closeMessage:"Closed Gallery"}),this.modal.subscribe(t=>{"modal-close"===t&&this.toggleGallery(!1)})}buildGalleryItems(t=null){let e=t?`[data-gallery="${t}"]`:this.elements.imageSelector;this.items=Array.from(document.querySelectorAll(e)).map((t,e)=>{let i="";return i=Object.hasOwn(t.dataset,"caption")?t.parentElement.querySelector(t.dataset.caption).cloneNode(!0):t.nextElementSibling&&"FIGCAPTION"===t.nextElementSibling.tagName?t.nextElementSibling.textContent:"",{id:t.dataset.id||e,srcset:t.srcset||t.src,sizes:t.sizes||"100vw",src:t.currentSrc||t.src,full:t.dataset.full||t.src,alt:t.alt||"",element:t,caption:i}})}initListeners(){this.clickHandler=this.handleClick.bind(this),this.pointerDownHandler=this.onPointerDown.bind(this),this.pointerMoveHandler=this.onPointerMove.bind(this),this.pointerUpHandler=this.onPointerUp.bind(this),this.wheelHandler=this.onWheel.bind(this),this.keyHandler=this.handleKeys.bind(this),document.addEventListener("click",this.clickHandler)}handleClick(t){let e=window.targetCheck(t,this.elements.imageSelector);e&&!this.modal.isOpen?(t.preventDefault(),this.buildGalleryItems(e.dataset.gallery||null),this.index=this.items.findIndex(t=>t.element===e),this.toggleGallery(!0)):this.modal.isOpen&&(window.targetCheck(t,this.elements.gallery.nextButton)?this.nextElement():window.targetCheck(t,this.elements.gallery.prevButton)&&this.prevElement())}handleKeys(t){if(this.modal.isOpen){switch(t.key){case"ArrowLeft":t.preventDefault(),this.prevElement();break;case"ArrowRight":t.preventDefault(),this.nextElement()}t.ctrlKey&&("+"!==t.key&&"="!==t.key||(t.preventDefault(),this.handleZoom(.2)),"-"===t.key&&(t.preventDefault(),this.handleZoom(-.2)),"0"===t.key&&(t.preventDefault(),this.resetZoom()))}}onPointerDown(t){t.preventDefault(),this.swipe.startX=t.clientX,this.swipe.startY=t.clientY,this.ui.gallery.image.setPointerCapture(t.pointerId),this.activePointers.set(t.pointerId,{x:t.clientX,y:t.clientY});const e=performance.now();if(e-this.lastTap<300&&1===this.activePointers.size)return this.zoom.scale>1?this.resetZoom():this.handleZoom(1,t.clientX,t.clientY),void(this.lastTap=0);if(this.lastTap=e,2===this.activePointers.size){const t=[...this.activePointers.values()];return this.pinchStartDist=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y),void(this.pinchStartScale=this.zoom.scale)}this.zoom.scale>1&&(this.zoom.panning=!0,this.zoom.startX=t.clientX-this.zoom.x,this.zoom.startY=t.clientY-this.zoom.y,this.ui.gallery.image.style.cursor="grabbing")}onPointerMove(t){if(this.activePointers.has(t.pointerId)){if(this.activePointers.set(t.pointerId,{x:t.clientX,y:t.clientY}),2===this.activePointers.size){const t=[...this.activePointers.values()],e=Math.hypot(t[0].x-t[1].x,t[0].y-t[1].y),i=this.pinchStartScale*(e/this.pinchStartDist)-this.zoom.scale,s=(t[0].x+t[1].x)/2,n=(t[0].y+t[1].y)/2;return void this.handleZoom(i,s,n)}this.zoom.panning&&(this.zoom.x=t.clientX-this.zoom.startX,this.zoom.y=t.clientY-this.zoom.startY,this.applyTransform())}}onPointerUp(t){const e=this.activePointers.get(t.pointerId);if(this.activePointers.delete(t.pointerId),this.activePointers.size<2&&(this.pinchStartDist=0),this.zoom.scale<=1&&!this.zoom.panning&&0===this.activePointers.size){const i=e?.x??t.clientX,s=e?.y??t.clientY,n=i-this.swipe.startX,o=s-this.swipe.startY;Math.abs(n)>this.zoom.threshold&&Math.abs(n)>Math.abs(o)&&(n>0?this.prevElement():this.nextElement())}0===this.activePointers.size&&(this.zoom.panning=!1,this.ui.gallery.image.style.cursor=this.zoom.scale>1?"grab":"default")}onWheel(t){if(!t.ctrlKey)return;t.preventDefault();const e=t.deltaY<0?.2:-.2;this.handleZoom(e,t.clientX,t.clientY)}handleZoom(t,e=null,i=null){const s=this.zoom.scale;let n=s+t;if(n=Math.min(this.zoom.max,Math.max(this.zoom.min,n)),n===s)return;const o=n/s;let l=this.ui.gallery.image.getBoundingClientRect();null!==e&&null!==i||(e=l.left+l.width/2,i=l.top+l.height/2);const a=e-l.left,r=i-l.top;this.zoom.x=(this.zoom.x-a)*o+a,this.zoom.y=(this.zoom.y-r)*o+r,this.zoom.scale=n,this.applyTransform(),this.notify("zoom",{scale:this.zoom.scale})}applyTransform(){this.clampPan();const t=this.ui.gallery.image;t.style.transform=`translate(${this.zoom.x}px, ${this.zoom.y}px) scale(${this.zoom.scale})`,t.style.cursor=this.zoom.scale>1?"grab":"default"}clampPan(){const t=this.ui.gallery.image,e=Math.max(0,(t.offsetWidth*this.zoom.scale-window.innerWidth)/2),i=Math.max(0,(t.offsetHeight*this.zoom.scale-window.innerHeight)/2);this.zoom.x=Math.max(-e,Math.min(e,this.zoom.x)),this.zoom.y=Math.max(-i,Math.min(i,this.zoom.y))}resetZoom(){this.zoom.scale=1,this.zoom.x=0,this.zoom.y=0,this.zoom.startX=0,this.zoom.startY=0,this.zoom.panning=!1,this.applyTransform()}resetSwipe(){return{startX:null,startY:null,endX:null,endY:null}}toggleGallery(t){t?(this.ui.gallery.image.draggable=!1,this.ui.gallery.image.style.userSelect="none",this.ui.gallery.image.addEventListener("pointerdown",this.pointerDownHandler),this.ui.gallery.image.addEventListener("pointermove",this.pointerMoveHandler),this.ui.gallery.image.addEventListener("pointerup",this.pointerUpHandler),this.ui.gallery.image.addEventListener("pointercancel",this.pointerUpHandler),window.addEventListener("wheel",this.wheelHandler,{passive:!1}),window.addEventListener("keydown",this.keyHandler),this.moveIntoView()):(this.ui.gallery.image.removeEventListener("pointerdown",this.pointerDownHandler),this.ui.gallery.image.removeEventListener("pointermove",this.pointerMoveHandler),this.ui.gallery.image.removeEventListener("pointerup",this.pointerUpHandler),this.ui.gallery.image.removeEventListener("pointercancel",this.pointerUpHandler),window.removeEventListener("wheel",this.wheelHandler),window.removeEventListener("keydown",this.keyHandler),this.resetZoom(),this.resetSwipe(),this.activePointers.clear(),this.lastTap=0),t&&!this.modal.isOpen&&this.modal.handleOpen()}moveIntoView(t=0){let e=this.index+t;e<0?e=this.items.length-1:e>=this.items.length?e=0:e===this.items.length-3&&this.notify("load-more"),this.index=e,this.updateDisplay(),this.preloadAdjacent(),this.a11y.announce(`Image ${this.index+1} of ${this.items.length}`)}nextElement(){this.resetZoom(),this.moveIntoView(1)}prevElement(){this.resetZoom(),this.moveIntoView(-1)}updateDisplay(){const t=this.items[this.index];if(!t)return;const e=this.ui.gallery.image;if(t.srcset&&(e.srcset=t.srcset,e.sizes=t.sizes),e.src=t.src,e.alt=t.alt,window.removeChildren(this.ui.gallery.caption),"object"==typeof t.caption?this.ui.gallery.caption.append(t.caption):this.ui.gallery.caption.textContent=t.caption,t.full&&t.full!==t.src){const i=new Image;i.onload=()=>{this.items[this.index]===t&&(e.src=t.full,e.removeAttribute("srcset"),e.removeAttribute("sizes"))},i.src=t.full}this.ui.gallery.counter.textContent=`${this.index+1} / ${this.items.length}`,this.ui.gallery.prevButton.disabled=this.items.length<=1,this.ui.gallery.nextButton.disabled=this.items.length<=1}preloadAdjacent(){[-1,1].forEach(t=>{const e=this.index+t;if(e>0&&e<this.items.length){const i=this.items[e];(t<0?this.ui.gallery.leftImage:this.ui.gallery.rightImage).src=i.full}})}initSubscribers(){this.subscribers=new Set}subscribe(t){return this.subscribers.add(t),()=>this.subscribers.delete(t)}notify(t,e={}){this.subscribers.forEach(i=>{try{i(t,e)}catch(t){console.error("Subscriber error:",t)}})}destroy(){this.subscribers.clear(),this.toggleGallery(!1),document.removeEventListener("click",this.clickHandler)}}document.addEventListener("DOMContentLoaded",function(){document.querySelector("dialog.gallery")&&(window.jvbGallery=new t)})})();
\ No newline at end of file
diff --git a/assets/js/min/handleSelection.min.js b/assets/js/min/handleSelection.min.js
index f45f6bc..7fa55ff 100644
--- a/assets/js/min/handleSelection.min.js
+++ b/assets/js/min/handleSelection.min.js
@@ -1 +1 @@
-window.jvbHandleSelection=class{constructor(e,t={}){this.container=e,this.selectors=window.deepMerge({selectAll:{checkbox:"[data-select-all]",label:".selected label",span:".selected label span",target:"data-selects",count:".selected-count, .selected .info",bulkControls:".bulk-actions"},items:".item-grid",wrapper:{wrapper:":has(.item-grid, [data-select-all])",id:"selection"},item:{item:".item",idAttribute:"id",checkbox:'[name="select-item"]'},wrappers:{}},t),this.a11y=window.jvbA11y,this.selectedItems=new Set,this.lastSelected=null,this.lastSelectedWrapper=null,this.lastClicked=null,this.subscribers=new Set,this.items=new Map,this.initElements(),this.initListeners(),this.collectItems()}removeDataReferences(){let e=JSON.parse(JSON.stringify(this.selectors));return delete e.item.idAttribute,delete e.wrapper.id,delete e.selectAll.target,e}initElements(){this.index=0;let e=this.removeDataReferences();this.ui=window.uiFromSelectors(e,this.container),this.container.querySelectorAll(this.selectors.wrapper.wrapper).forEach((e=>{this.addWrapper(e)}))}addWrapper(e){let t=this.selectors.wrapper.id;Object.hasOwn(e.dataset,t)||(e.setAttribute(`data-${t}`,this.index),this.index++);let s=this.removeDataReferences().selectAll;this.ui.wrappers[e.dataset[t]]={element:e,items:e.querySelector(this.selectors.items),selectAll:window.uiFromSelectors(s,e)}}removeWrapper(e){delete this.ui.wrappers[e.dataset[this.selectors.wrapper.id]]}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.keyHandler=this.handleKeys.bind(this),this.container.addEventListener("change",this.changeHandler),this.container.addEventListener("click",this.clickHandler),document.addEventListener("keydown",this.keyHandler)}handleChange(e){if(this.container.contains(e.target))if(e.target.matches(this.selectors.selectAll.checkbox))this.handleSelectAll(e.target);else if(e.target.matches(this.selectors.item.checkbox)){const t=e.target.closest(this.selectors.item.item);if(!t)return;const s=this.getItemWrapper(e.target);if(!s)return;this.lastClicked=s.element;const i=this.getItemId(t);if(!i)return;e.target.checked?this.select(i,!1):this.deselect(i,!1),this.lastSelected=i,this.lastSelectedWrapper=s.element,this.updateSelectionUI()}}handleClick(e){if(!e.shiftKey)return;const t=e.target.closest(this.selectors.item.item);if(!t)return;const s=e.target.matches(this.selectors.item.checkbox),i=e.target.closest("label[for]");if(!s&&!i)return;const l=this.getItemWrapper(t);if(!l)return;if(!this.lastSelected||!this.lastSelectedWrapper||l.element!==this.lastSelectedWrapper)return;e.preventDefault();const c=this.getItemId(t),r=this.getWrapperChildren(l),n=r.findIndex((e=>e===this.lastSelected)),a=r.findIndex((e=>e===c));if(-1===n||-1===a)return;const[h,d]=[Math.min(n,a),Math.max(n,a)];r.slice(h,d+1).forEach((e=>{this.select(e,!0,!1)})),this.lastSelected=c,this.updateSelectionUI(),this.notify("range-selected",{selectedItems:new Set(this.selectedItems),wrapper:l})}getWrapperChildren(e){return Array.from(e.items.children).map((e=>this.getItemId(e)))}getItemWrapper(e){if(!e)return null;let t=e.closest(this.selectors.wrapper.wrapper);return t?this.getWrapper(t):null}getWrapper(e){return this.ui.wrappers[e.dataset[this.selectors.wrapper.id]]??null}handleKeys(e){"Escape"===e.key&&this.selectedItems.size>0&&(e.preventDefault(),Object.keys(this.ui.wrappers).length>1&&this.lastClicked?this.clearWrapperSelection(this.lastClicked):this.clearSelection())}handleSelectAll(e){const t=this.getItemWrapper(e);if(!t)return;const s=this.getWrapperChildren(t);e.checked?(s.forEach((e=>this.select(e,!0,!1))),this.lastSelectedWrapper=t.element):(s.forEach((e=>this.deselect(e,!0,!1))),this.lastSelectedWrapper=null),t.selectAll.span&&(t.selectAll.span.textContent=e.checked&&s.length>0?"Clear Selection":"Select All"),this.updateSelectionUI(),this.notify("select-all",{wrapper:t,checked:e.checked,ids:s,selectedItems:new Set(this.selectedItems)})}getItemId(e){if(!(!e instanceof Element)||(e=e.element??!1))return e.dataset[`${this.selectors.item.idAttribute}`]}select(e,t=!0,s=!0){if(this.selectedItems.has(e))return;this.selectedItems.add(e);let i=this.getItem(e);i&&i.element.classList.add("selected"),t&&this.setCheckboxState(e,!0),s&&this.updateSelectionUI(),this.notify("item-selected",{id:e,selectedItems:new Set(this.selectedItems)})}deselect(e,t=!0,s=!0){if(!this.selectedItems.has(e))return;this.selectedItems.delete(e);let i=this.getItem(e);i&&i.element.classList.remove("selected"),t&&this.setCheckboxState(e,!1),s&&this.updateSelectionUI(),this.notify("item-deselected",{id:e,selectedItems:new Set(this.selectedItems)})}toggle(e){this.selectedItems.has(e)?this.deselect(e):this.select(e),this.updateSelectionUI()}clearSelection(){this.selectedItems.forEach((e=>this.deselect(e,!0,!1))),this.selectedItems.clear(),this.lastSelected=null,this.lastSelectedWrapper=null;for(let e of Object.values(this.ui.wrappers))e.selectAll.checkbox&&(e.selectAll.checkbox.checked=!1),e.selectAll.span&&(e.selectAll.span.textContent="Select All");this.a11y.announce("Selection cleared"),this.updateSelectionUI(),this.notify("selection-cleared",{selectedItems:new Set})}clearWrapperSelection(e){(e=this.getWrapper(e))&&(this.getWrapperChildren(e).forEach((e=>this.deselect(e,!0,!1))),e.selectAll.checkbox&&(e.selectAll.checkbox.checked=!1),e.selectAll.span&&(e.selectAll.span.textContent="Select All"),this.a11y.announce("Selection cleared in group"),this.updateSelectionUI(),this.notify("wrapper-selection-cleared",{selectedItems:this.selectedItems}))}isSelected(e){return this.selectedItems.has(e)}getSelection(){return new Set(this.selectedItems)}setCheckboxState(e,t){const s=this.getItem(e);s&&s.checkbox&&s.checkbox.checked!==t&&(s.checkbox.checked=t)}updateSelectionUI(){if(!this.lastClicked)return;const e=this.getWrapper(this.lastClicked);if(!e||!e.selectAll)return;const t=this.selectedItems.size;let s=e.selectAll.bulkControls;s&&(s.hidden=0===t);let i=e.selectAll.count;if(i){const e=1===t?"item":"items";i.textContent=0===t?"":`${t} ${e} selected`,i.hidden=0===t}}collectItems(){this.container.querySelectorAll(this.selectors.item.item).forEach((e=>{this.setItem(e,!0)}))}getItem(e){return this.items.has(e)?this.items.get(e):this.setItem(e)}setItem(e,t=!1){let s=t?e:this.container.querySelector(`[data-${this.camelToKebab(this.selectors.item.idAttribute)}="${e}"]`);return s?(e=this.getItemId(s),this.items.has(e)||this.items.set(e,{element:s,checkbox:s.querySelector(this.selectors.item.checkbox)}),this.items.get(e)):null}camelToKebab(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach((s=>{try{s(e,t)}catch(e){console.error("HandleSelection subscriber error:",e)}}))}destroy(){this.container.removeEventListener("change",this.changeHandler),this.container.removeEventListener("click",this.clickHandler),this.container.removeEventListener("keydown",this.keyHandler),this.subscribers.clear(),this.selectedItems.clear()}};
\ No newline at end of file
+window.jvbHandleSelection=class{constructor(e,t={}){this.container=e,this.selectors=window.deepMerge({selectAll:{checkbox:"[data-select-all]",label:".selected label",span:".selected label span",target:"data-selects",count:".selected-count, .selected .info",bulkControls:".bulk-actions"},items:".item-grid",wrapper:{wrapper:":has(.item-grid, [data-select-all])",id:"selection"},item:{item:".item",idAttribute:"id",checkbox:'[name="select-item"]'},wrappers:{}},t),this.a11y=window.jvbA11y,this.selectedItems=new Set,this.lastSelected=null,this.lastSelectedWrapper=null,this.lastClicked=null,this.subscribers=new Set,this.items=new Map,this.initElements(),this.initListeners(),this.collectItems()}removeDataReferences(){let e=JSON.parse(JSON.stringify(this.selectors));return delete e.item.idAttribute,delete e.wrapper.id,delete e.selectAll.target,e}initElements(){this.index=0;let e=this.removeDataReferences();this.ui=window.uiFromSelectors(e,this.container),this.container.querySelectorAll(this.selectors.wrapper.wrapper).forEach(e=>{this.addWrapper(e)})}addWrapper(e){let t=this.selectors.wrapper.id;Object.hasOwn(e.dataset,t)||(e.setAttribute(`data-${t}`,this.index),this.index++);let s=this.removeDataReferences().selectAll;this.ui.wrappers[e.dataset[t]]={element:e,items:e.querySelector(this.selectors.items),selectAll:window.uiFromSelectors(s,e)}}removeWrapper(e){delete this.ui.wrappers[e.dataset[this.selectors.wrapper.id]]}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.keyHandler=this.handleKeys.bind(this),this.container.addEventListener("change",this.changeHandler),this.container.addEventListener("click",this.clickHandler),document.addEventListener("keydown",this.keyHandler)}handleChange(e){if(this.container.contains(e.target))if(e.target.matches(this.selectors.selectAll.checkbox))this.handleSelectAll(e.target);else if(e.target.matches(this.selectors.item.checkbox)){const t=e.target.closest(this.selectors.item.item);if(!t)return;const s=this.getItemWrapper(e.target);if(!s)return;this.lastClicked=s.element;const i=this.getItemId(t);if(!i)return;e.target.checked?this.select(i,!1):this.deselect(i,!1),this.lastSelected=i,this.lastSelectedWrapper=s.element,this.updateSelectionUI()}}handleClick(e){if(!e.shiftKey)return;const t=e.target.closest(this.selectors.item.item);if(!t)return;const s=e.target.matches(this.selectors.item.checkbox),i=e.target.closest("label[for]");if(!s&&!i)return;const l=this.getItemWrapper(t);if(!l)return;if(!this.lastSelected||!this.lastSelectedWrapper||l.element!==this.lastSelectedWrapper)return;e.preventDefault();const c=this.getItemId(t),r=this.getWrapperChildren(l),n=r.findIndex(e=>e===this.lastSelected),a=r.findIndex(e=>e===c);if(-1===n||-1===a)return;const[h,d]=[Math.min(n,a),Math.max(n,a)];r.slice(h,d+1).forEach(e=>{this.select(e,!0,!1)}),this.lastSelected=c,this.updateSelectionUI(),this.notify("range-selected",{selectedItems:new Set(this.selectedItems),wrapper:l})}getWrapperChildren(e){return Array.from(e.items.children).map(e=>this.getItemId(e))}getItemWrapper(e){if(!e)return null;let t=e.closest(this.selectors.wrapper.wrapper);return t?this.getWrapper(t):null}getWrapper(e){return this.ui.wrappers[e.dataset[this.selectors.wrapper.id]]??null}handleKeys(e){"Escape"===e.key&&this.selectedItems.size>0&&(e.preventDefault(),Object.keys(this.ui.wrappers).length>1&&this.lastClicked?this.clearWrapperSelection(this.lastClicked):this.clearSelection())}handleSelectAll(e){const t=this.getItemWrapper(e);if(!t)return;const s=this.getWrapperChildren(t);e.checked?(s.forEach(e=>this.select(e,!0,!1)),this.lastSelectedWrapper=t.element):(s.forEach(e=>this.deselect(e,!0,!1)),this.lastSelectedWrapper=null),t.selectAll.span&&(t.selectAll.span.textContent=e.checked&&s.length>0?"Clear Selection":"Select All"),this.updateSelectionUI(),this.notify("select-all",{wrapper:t,checked:e.checked,ids:s,selectedItems:new Set(this.selectedItems)})}getItemId(e){if(!(!e instanceof Element)||(e=e.element??!1))return e.dataset[`${this.selectors.item.idAttribute}`]}select(e,t=!0,s=!0){if(this.selectedItems.has(e))return;this.selectedItems.add(e);let i=this.getItem(e);i&&i.element.classList.add("selected"),t&&this.setCheckboxState(e,!0),s&&this.updateSelectionUI(),this.notify("item-selected",{id:e,selectedItems:new Set(this.selectedItems)})}deselect(e,t=!0,s=!0){if(!this.selectedItems.has(e))return;this.selectedItems.delete(e);let i=this.getItem(e);i&&i.element.classList.remove("selected"),t&&this.setCheckboxState(e,!1),s&&this.updateSelectionUI(),this.notify("item-deselected",{id:e,selectedItems:new Set(this.selectedItems)})}toggle(e){this.selectedItems.has(e)?this.deselect(e):this.select(e),this.updateSelectionUI()}clearSelection(){this.selectedItems.forEach(e=>this.deselect(e,!0,!1)),this.selectedItems.clear(),this.lastSelected=null,this.lastSelectedWrapper=null;for(let e of Object.values(this.ui.wrappers))e.selectAll.checkbox&&(e.selectAll.checkbox.checked=!1),e.selectAll.span&&(e.selectAll.span.textContent="Select All");this.a11y.announce("Selection cleared"),this.updateSelectionUI(),this.notify("selection-cleared",{selectedItems:new Set})}clearWrapperSelection(e){(e=this.getWrapper(e))&&(this.getWrapperChildren(e).forEach(e=>this.deselect(e,!0,!1)),e.selectAll.checkbox&&(e.selectAll.checkbox.checked=!1),e.selectAll.span&&(e.selectAll.span.textContent="Select All"),this.a11y.announce("Selection cleared in group"),this.updateSelectionUI(),this.notify("wrapper-selection-cleared",{selectedItems:this.selectedItems}))}isSelected(e){return this.selectedItems.has(e)}getSelection(){return new Set(this.selectedItems)}setCheckboxState(e,t){const s=this.getItem(e);s&&s.checkbox&&s.checkbox.checked!==t&&(s.checkbox.checked=t)}updateSelectionUI(){if(!this.lastClicked)return;const e=this.getWrapper(this.lastClicked);if(!e||!e.selectAll)return;const t=this.selectedItems.size;let s=e.selectAll.bulkControls;s&&(s.hidden=0===t);let i=e.selectAll.count;if(i){const e=1===t?"item":"items";i.textContent=0===t?"":`${t} ${e} selected`,i.hidden=0===t}}collectItems(){this.container.querySelectorAll(this.selectors.item.item).forEach(e=>{this.setItem(e,!0)})}getItem(e){return this.items.has(e)?this.items.get(e):this.setItem(e)}setItem(e,t=!1){let s=t?e:this.container.querySelector(`[data-${this.camelToKebab(this.selectors.item.idAttribute)}="${e}"]`);return s?(e=this.getItemId(s),this.items.has(e)||this.items.set(e,{element:s,checkbox:s.querySelector(this.selectors.item.checkbox)}),this.items.get(e)):null}camelToKebab(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(s=>{try{s(e,t)}catch(e){console.error("HandleSelection subscriber error:",e)}})}destroy(){this.container.removeEventListener("change",this.changeHandler),this.container.removeEventListener("click",this.clickHandler),this.container.removeEventListener("keydown",this.keyHandler),this.subscribers.clear(),this.selectedItems.clear()}};
\ No newline at end of file
diff --git a/assets/js/min/helcim.min.js b/assets/js/min/helcim.min.js
index 27ef5ba..ad194b1 100644
--- a/assets/js/min/helcim.min.js
+++ b/assets/js/min/helcim.min.js
@@ -1 +1 @@
-(()=>{class e extends window.jvbCheckout{constructor(e={}){super({...window.helcimConfig,...e}),this.pendingSecretToken=null}async init(){"function"!=typeof window.appendHelcimPayIframe&&console.warn("HelcimPay.js SDK not loaded — payment will initialize on first checkout"),this.isInitialized=!0,window.addEventListener("message",(e=>this.handleHelcimMessage(e))),document.dispatchEvent(new CustomEvent("checkoutReady",{detail:{checkout:this,provider:"helcim"}}))}async processPayment(e){if(this.selectedCardId)return this.submitToServer({card_id:this.selectedCardId,is_saved:!0},e);const t=await this.initializeCheckoutSession(e);if(!t.success)throw new Error(t.message||"Failed to initialize checkout");return this.pendingSecretToken=t.secretToken,this.pendingOrderData=e,window.appendHelcimPayIframe(t.checkoutToken,{type:"modal"}),new Promise(((e,t)=>{this._paymentResolve=e,this._paymentReject=t}))}async initializeCheckoutSession(e){return(await fetch(this.config.api_url+"initialize-checkout",{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":this.config.nonce},body:JSON.stringify({amount:e.total/100,customer:e.customer,items:e.items,cart_id:this.getCartId()})})).json()}handleHelcimMessage(e){const t=e.data;t&&"object"==typeof t&&("SUCCESS"===t.eventStatus?this.handleHelcimSuccess(t):"ABORTED"===t.eventStatus?this.handleHelcimCancelled():"FAILED"===t.eventStatus&&this.handleHelcimError(t))}async handleHelcimSuccess(e){try{const t=await this.submitToServer({transaction_id:e.transactionId,secret_token:this.pendingSecretToken,event_data:e},this.pendingOrderData);this.clearPending(),this._paymentResolve?.(t)}catch(e){this.clearPending(),this._paymentReject?.(e)}}handleHelcimCancelled(){this.clearPending(),window.jvbLoading?.hideLoading?.(),this.a11y.announce("Payment cancelled"),this._paymentReject?.(new Error("Payment cancelled by user"))}handleHelcimError(e){this.clearPending(),window.jvbLoading?.hideLoading?.();const t=e.errorMessage||"Payment failed";this._paymentReject?.(new Error(t))}clearPending(){this.pendingSecretToken=null,this.pendingOrderData=null}async submitToServer(e,t){if(!this.isOpen)throw new Error("Store is currently closed");const i=e.is_saved?"process-saved-payment":"validate-transaction",n=await fetch(this.config.api_url+i,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":this.config.nonce},body:JSON.stringify({...e,cart_id:this.getCartId(),amount:t.total,items:t.items,customer:{email:this.isLoggedIn?this.userEmail:t.customer.email,name:t.customer.name,phone:t.customer.phone},note:t.note,pickup_time:t.pickup_time})}),s=await n.json();if(!n.ok)throw new Error(s.message||"Payment processing failed");return this.clearCart(),s}async loadSavedCards(){try{const e=await fetch(this.config.api_url+"saved-cards",{method:"GET",headers:{"X-WP-Nonce":this.config.nonce}}),t=await e.json();t.success&&t.cards&&(this.savedCards=t.cards,this.renderSavedCards())}catch(e){console.error("Failed to load saved cards:",e)}}async loadInvoices(){try{const e=await fetch(this.config.api_url+"invoices",{headers:{"X-WP-Nonce":this.config.nonce}}),t=await e.json();if(t.success)return t.invoices||[]}catch(e){console.error("Failed to load invoices:",e)}return[]}async payInvoice(e){const t=await fetch(this.config.api_url+"initialize-checkout",{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":this.config.nonce},body:JSON.stringify({invoice_id:e})}).then((e=>e.json()));if(!t.success)throw new Error(t.message||"Failed to initialize invoice payment");return this.pendingSecretToken=t.secretToken,this.pendingOrderData={total:0,items:[],customer:{}},window.appendHelcimPayIframe(t.checkoutToken,{type:"modal"}),new Promise(((e,t)=>{this._paymentResolve=e,this._paymentReject=t}))}}document.addEventListener("DOMContentLoaded",(()=>{document.querySelector('#checkout[data-provider="helcim"]')&&(window.jvbHelcim=new e)}))})();
\ No newline at end of file
+(()=>{class e extends window.jvbCheckout{constructor(e={}){super({...window.helcimConfig,...e}),this.pendingSecretToken=null}async init(){"function"!=typeof window.appendHelcimPayIframe&&console.warn("HelcimPay.js SDK not loaded — payment will initialize on first checkout"),this.isInitialized=!0,window.addEventListener("message",e=>this.handleHelcimMessage(e)),document.dispatchEvent(new CustomEvent("checkoutReady",{detail:{checkout:this,provider:"helcim"}}))}async processPayment(e){if(this.selectedCardId)return this.submitToServer({card_id:this.selectedCardId,is_saved:!0},e);const t=await this.initializeCheckoutSession(e);if(!t.success)throw new Error(t.message||"Failed to initialize checkout");return this.pendingSecretToken=t.secretToken,this.pendingOrderData=e,window.appendHelcimPayIframe(t.checkoutToken,{type:"modal"}),new Promise((e,t)=>{this._paymentResolve=e,this._paymentReject=t})}async initializeCheckoutSession(e){return(await fetch(this.config.api_url+"initialize-checkout",{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":this.config.nonce},body:JSON.stringify({amount:e.total/100,customer:e.customer,items:e.items,cart_id:this.getCartId()})})).json()}handleHelcimMessage(e){const t=e.data;t&&"object"==typeof t&&("SUCCESS"===t.eventStatus?this.handleHelcimSuccess(t):"ABORTED"===t.eventStatus?this.handleHelcimCancelled():"FAILED"===t.eventStatus&&this.handleHelcimError(t))}async handleHelcimSuccess(e){try{const t=await this.submitToServer({transaction_id:e.transactionId,secret_token:this.pendingSecretToken,event_data:e},this.pendingOrderData);this.clearPending(),this._paymentResolve?.(t)}catch(e){this.clearPending(),this._paymentReject?.(e)}}handleHelcimCancelled(){this.clearPending(),window.jvbLoading?.hideLoading?.(),this.a11y.announce("Payment cancelled"),this._paymentReject?.(new Error("Payment cancelled by user"))}handleHelcimError(e){this.clearPending(),window.jvbLoading?.hideLoading?.();const t=e.errorMessage||"Payment failed";this._paymentReject?.(new Error(t))}clearPending(){this.pendingSecretToken=null,this.pendingOrderData=null}async submitToServer(e,t){if(!this.isOpen)throw new Error("Store is currently closed");const i=e.is_saved?"process-saved-payment":"validate-transaction",n=await fetch(this.config.api_url+i,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":this.config.nonce},body:JSON.stringify({...e,cart_id:this.getCartId(),amount:t.total,items:t.items,customer:{email:this.isLoggedIn?this.userEmail:t.customer.email,name:t.customer.name,phone:t.customer.phone},note:t.note,pickup_time:t.pickup_time})}),s=await n.json();if(!n.ok)throw new Error(s.message||"Payment processing failed");return this.clearCart(),s}async loadSavedCards(){try{const e=await fetch(this.config.api_url+"saved-cards",{method:"GET",headers:{"X-WP-Nonce":this.config.nonce}}),t=await e.json();t.success&&t.cards&&(this.savedCards=t.cards,this.renderSavedCards())}catch(e){console.error("Failed to load saved cards:",e)}}async loadInvoices(){try{const e=await fetch(this.config.api_url+"invoices",{headers:{"X-WP-Nonce":this.config.nonce}}),t=await e.json();if(t.success)return t.invoices||[]}catch(e){console.error("Failed to load invoices:",e)}return[]}async payInvoice(e){const t=await fetch(this.config.api_url+"initialize-checkout",{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":this.config.nonce},body:JSON.stringify({invoice_id:e})}).then(e=>e.json());if(!t.success)throw new Error(t.message||"Failed to initialize invoice payment");return this.pendingSecretToken=t.secretToken,this.pendingOrderData={total:0,items:[],customer:{}},window.appendHelcimPayIframe(t.checkoutToken,{type:"modal"}),new Promise((e,t)=>{this._paymentResolve=e,this._paymentReject=t})}}document.addEventListener("DOMContentLoaded",()=>{document.querySelector('#checkout[data-provider="helcim"]')&&(window.jvbHelcim=new e)})})();
\ No newline at end of file
diff --git a/assets/js/min/hours.min.js b/assets/js/min/hours.min.js
index 91bec3c..6e806ec 100644
--- a/assets/js/min/hours.min.js
+++ b/assets/js/min/hours.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.createModal(),this.setupModal(),this.currentSourceDay=null,this.days=["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],this.init()}handleChange(e){window.targetCheck(".hours-copy-btn")&&e.preventDefault({bubbles:!0})}init(){this.addCopyButtons(),this.bindEvents()}createModal(){this.modalElement=document.querySelector("dialog#copy-hours")}setupModal(){this.modal=new window.jvbModal(this.modalElement,{open:".hours-copy-btn",openMessage:"Copy hours modal opened",closeMessage:"Copy hours modal closed"}),this.modal.subscribe(((e,o)=>{switch(e){case"modal-open":let e=o.event,t=o.modal;const s=e.target.closest(".group-fields:not(.hours)");console.log(s);const n=this.getDayNameFromGroup(s);if(console.log(n),!n)return;this.currentSourceDay=n,console.log(n);const c=this.getSourceDayData(s);console.log(c);const a=t.querySelector(".source-day"),r=t.querySelector(".source-hours");console.log("Elements: ",{sourceDayEl:a,sourceHoursEl:r}),a&&(a.textContent=window.uppercaseFirst?window.uppercaseFirst(n):n.charAt(0).toUpperCase()+n.slice(1)),r&&(c.isOpen&&c.opens&&c.closes?(r.textContent=`${c.opens} - ${c.closes}`,r.className="source-hours"):(r.textContent="Closed",r.className="source-hours closed")),this.modal.modal.querySelector(`label[for=copy-to-${n}]`).hidden=!0,this.modal.modal.querySelectorAll(`label:not([for=copy-to-${n}])[hidden]`).hidden=!1;break;case"modal-close":this.applyCopyHours(),this.currentSourceDay=null}}))}addCopyButtons(){document.querySelectorAll('[data-field="openingHours"] .group.field').forEach((e=>{if(!e.querySelector(".hours-copy-btn")){const o=this.createCopyButton();e.append(o)}}))}createCopyButton(){const e=window.getTemplate("blankButton");return e.type="button",e.className="hours-copy-btn",e.title="Copy this time to other days",e.prepend(window.getIcon("copy")),e.setAttribute("data-action","copy-hours"),e}bindEvents(){this.changeHandler=this.handleChange.bind(this),this.modal.modal.addEventListener("change",this.changeHandler)}getDayNameFromGroup(e){const o=Array.from(e.classList);return this.days.find((e=>o.includes(e)))}getSourceDayData(e){console.log(e);const o=e.querySelector('input[type="checkbox"][name$=":open"]'),t=e.querySelector('input[name$=":time_opens"]'),s=e.querySelector('input[name$=":time_closes"]');return{isOpen:!!o&&o.checked,opens:t?t.value:"",closes:s?s.value:""}}applyCopyHours(){if(!this.currentSourceDay)return;const e=document.querySelector(`.group-fields.${this.currentSourceDay}`);if(!e)return;const o=this.getSourceDayData(e),t=Array.from(this.modal.modal.querySelectorAll("input:checked")).map((e=>e.value));0!==t.length?(t.forEach((e=>{this.applyHoursToDay(e,o)})),this.showSuccessFeedback(t.length)):alert("Please select at least one day to copy to.")}applyHoursToDay(e,o){const t=document.querySelector(`.group-fields.${e}`);if(!t)return;const s=t.querySelector('input[type="checkbox"][name$=":open"]'),n=t.querySelector('input[name$=":time_opens"]'),c=t.querySelector('input[name$=":time_closes"]');console.log("applying hours to days: ",{openCheckbox:s,opensInput:n,closesInput:c}),s&&(s.checked=o.isOpen,s.dispatchEvent(new Event("change",{bubbles:!0})),o.isOpen&&n&&c?(n.value=o.opens,c.value=o.closes):n&&c&&(n.value="",c.value=""))}showSuccessFeedback(e){let o=document.getElementById("copy-success-feedback");o||(o=document.createElement("div"),o.id="copy-success-feedback",o.className="copy-success-feedback",document.body.appendChild(o));const t=1===e?"Hours copied to 1 day successfully!":`Hours copied to ${e} days successfully!`;o.querySelector("span").textContent=t,o.classList.add("show"),window.debouncer.schedule("copy-success",(()=>{o.classList.remove("show")}),3e3),window.jvbA11y&&window.jvbA11y.announce&&window.jvbA11y.announce(t)}}document.addEventListener("DOMContentLoaded",(()=>{window.jvbCopy=new e}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.createModal(),this.setupModal(),this.currentSourceDay=null,this.days=["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],this.init()}handleChange(e){window.targetCheck(".hours-copy-btn")&&e.preventDefault({bubbles:!0})}init(){this.addCopyButtons(),this.bindEvents()}createModal(){this.modalElement=document.querySelector("dialog#copy-hours")}setupModal(){this.modal=new window.jvbModal(this.modalElement,{open:".hours-copy-btn",openMessage:"Copy hours modal opened",closeMessage:"Copy hours modal closed"}),this.modal.subscribe((e,o)=>{switch(e){case"modal-open":let e=o.event,t=o.modal;const s=e.target.closest('.group:not([data-field="openingHours"])'),n=this.getDayNameFromGroup(s);if(!n)return;this.currentSourceDay=n;const a=this.getSourceDayData(s);console.log(a);const c=t.querySelector(".source-day"),r=t.querySelector(".source-hours");console.log("Elements: ",{sourceDayEl:c,sourceHoursEl:r}),c&&(c.textContent=window.uppercaseFirst?window.uppercaseFirst(n):n.charAt(0).toUpperCase()+n.slice(1)),r&&(a.isOpen&&a.opens&&a.closes?(r.textContent=`${a.opens} - ${a.closes}`,r.className="source-hours"):(r.textContent="Closed",r.className="source-hours closed")),this.modal.modal.querySelector(`label[for=copy-to-${n}]`).hidden=!0,this.modal.modal.querySelectorAll(`label:not([for=copy-to-${n}])[hidden]`).hidden=!1;break;case"modal-close":this.applyCopyHours(),this.currentSourceDay=null}})}addCopyButtons(){document.querySelectorAll('[data-field="openingHours"] .group.field').forEach(e=>{if(!e.querySelector(".hours-copy-btn")){const o=this.createCopyButton();e.append(o)}})}createCopyButton(){const e=window.getTemplate("blankButton");return e.type="button",e.className="hours-copy-btn",e.title="Copy this time to other days",e.prepend(window.getIcon("copy")),e.setAttribute("data-action","copy-hours"),e}bindEvents(){this.changeHandler=this.handleChange.bind(this),this.modal.modal.addEventListener("change",this.changeHandler)}getDayNameFromGroup(e){return e.dataset.field.replace("openingHours|","")}getSourceDayData(e){const o=e.querySelector(`input[type="checkbox"][name="openingHours|${this.currentSourceDay}|isOpen"]`),t=e.querySelector(`input[name="openingHours|${this.currentSourceDay}|opens"]`),s=e.querySelector(`input[name="openingHours|${this.currentSourceDay}|closes"]`);return{isOpen:!!o&&o.checked,opens:t?t.value:"",closes:s?s.value:""}}applyCopyHours(){if(!this.currentSourceDay)return;const e=document.querySelector(`.group[data-field="openingHours|${this.currentSourceDay}"]`);if(!e)return;const o=this.getSourceDayData(e),t=Array.from(this.modal.modal.querySelectorAll("input:checked")).map(e=>e.value);0!==t.length?(t.forEach(e=>{this.applyHoursToDay(e,o)}),this.showSuccessFeedback(t.length)):alert("Please select at least one day to copy to.")}applyHoursToDay(e,o){const t=document.querySelector(`.group[data-field="openingHours|${e}`);if(!t)return;const s=t.querySelector(`input[type="checkbox"][name="openingHours|${e}|isOpen"]`),n=t.querySelector(`input[name="openingHours|${e}|opens"]`),a=t.querySelector(`input[name="openingHours|${e}|closes"]`);console.log("applying hours to days: ",{openCheckbox:s,opensInput:n,closesInput:a}),s&&(s.checked=o.isOpen,s.dispatchEvent(new Event("change",{bubbles:!0})),o.isOpen&&n&&a?(n.value=o.opens,n.dispatchEvent(new Event("change",{bubbles:!0})),a.value=o.closes,a.dispatchEvent(new Event("change",{bubbles:!0}))):n&&a&&(n.value="",n.dispatchEvent(new Event("change",{bubbles:!0})),a.value="",a.dispatchEvent(new Event("change",{bubbles:!0}))))}showSuccessFeedback(e){let o=document.getElementById("copy-success-feedback");const t=1===e?"Hours copied to 1 day successfully!":`Hours copied to ${e} days successfully!`;o.querySelector("span").textContent=t,o.classList.add("show"),window.debouncer.schedule("copy-success",()=>{o.classList.remove("show")},3e3),window.jvbA11y&&window.jvbA11y.announce&&window.jvbA11y.announce(t)}}document.addEventListener("DOMContentLoaded",()=>{window.jvbCopy=new e})})();
\ No newline at end of file
diff --git a/assets/js/min/integrations.min.js b/assets/js/min/integrations.min.js
index 6c5014a..7954060 100644
--- a/assets/js/min/integrations.min.js
+++ b/assets/js/min/integrations.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.initElements(),this.initListeners(),this.init()}initElements(){this.selectors={form:"form.integration",action:"data-action"};let e=document.querySelectorAll(this.selectors.form);this.forms=new Map,e.forEach((e=>{this.forms.set(e.dataset.service,e)}))}initListeners(){this.handleClick=this.clickHandler.bind(this),this.handleChange=this.changeHandler.bind(this),this.handleSubmit=this.submitHandler.bind(this),document.addEventListener("click",this.handleClick),document.addEventListener("change",this.handleChange),document.addEventListener("submit",this.handleSubmit)}init(){document.addEventListener("DOMContentLoaded",(()=>{this.checkForOAuthMessages()}))}checkForOAuthMessages(){const e=new URLSearchParams(window.location.search),t=e.get("success"),s=e.get("error");if(t){this.showNotification(t,"success",5e3),this.cleanURL();document.querySelectorAll("form.integration").forEach((e=>{this.updateUI(e,"connected")}))}else s&&(this.showNotification(s,"error",8e3),this.cleanURL())}cleanURL(){const e=new URL(window.location);e.searchParams.delete("success"),e.searchParams.delete("error"),window.history.replaceState({},document.title,e.pathname+e.hash)}showNotification(e,t="info",s=5e3){let n=document.querySelector(".integration-status-message");if(!n){n=document.createElement("div"),n.className="integration-status-message";const e=document.querySelector(".integration-settings")||document.querySelector("main")||document.body;e.insertBefore(n,e.firstChild)}n.textContent=e,n.className=`integration-status-message ${t}`,this.notificationTimeout&&clearTimeout(this.notificationTimeout),s>0&&(this.notificationTimeout=setTimeout((()=>{n.className="integration-status-message",n.textContent=""}),s)),this.popup&&this.addPopup(e,s)}addPopup(e,t=2e3){this.popup||(this.popup=document.querySelector(".integration-popup")||this.createPopupElement()),this.popup.textContent=e,this.popup.classList.add("showing"),setTimeout((()=>{this.popup.classList.remove("showing")}),t)}createPopupElement(){const e=document.createElement("div");return e.className="integration-popup",document.body.appendChild(e),e}clickHandler(e){if(e.target.closest(this.selectors.form)&&("BUTTON"===e.target.tagName||e.target.closest("button"))){e.preventDefault();let t="BUTTON"===e.target.tagName?e.target:e.target.closest("button");this.handleAction(t)}}changeHandler(e){if(e.target.closest(this.selectors.form))if("action"in e.target.dataset)this.handleAction(e.target);else{let t=this.getFormFromTarget(e.target);if(!t)return;t.classList.add("hasChanges"),t.querySelector(".setup .text").textContent="Unsaved Changes"}}submitHandler(e){e.target.closest(this.selectors.form)&&e.preventDefault()}getFormFromTarget(e){let t=e.closest("form")?.dataset.service;return this.forms.get(t)??!1}async handleAction(e){const t=e.closest("form"),s=t.dataset.service,n=e.dataset.action,o="BUTTON"===e.tagName,i=o&&"save_credentials"===n;if(!("confirm"in e.dataset)||confirm(e.dataset.confirm)){this.updateUI(t,"syncing");try{this.updateUI(t,"syncing");const a={service:s,action:n,user_id:window.auth.getUser(),data:{}};if(o||(a.data[e.name.replace(s+"_","")]=e.value),i){const e=new FormData(t);for(let[t,n]of e.entries())["service"].includes(t)||t.includes("nonce")||(a.data[t.replace(s+"_","")]=n)}const r=await fetch(jvbSettings.api+"integrations",{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce()},body:JSON.stringify(a)}),c=await r.json();if(r.ok&&c.success){let e="connected";switch(n){case"clear_credentials":e="disconnected";break;case"save_credentials":this.showNotification("Settings saved successfully","success")}this.updateUI(t,e),c.reload&&setTimeout((()=>{window.location.reload()}),50)}else this.updateUI(t,"error",c.message??""),this.showNotification(c.message||"Operation failed","error")}catch(e){this.updateUI(t,"error"),this.showNotification("Network error: "+e.message,"error"),console.error("API Error:",e)}}}updateUI(e,t,s=""){let n=["connected","disconnected","hasChanges","syncing","error"];if(!n.includes(t))return;s=""===s?{connected:"Set Up",disconnected:"Not Set Up",hasChanges:"Unsaved Changes",syncing:"Testing changes",error:"Something went wrong"}[t]:s,"syncing"===t?e.querySelectorAll("button").forEach((e=>{e.disabled=!0})):e.querySelectorAll("button[disabled]").forEach((e=>{e.disabled=!1})),e.classList.remove(...n),e.classList.add(t,"flash"),e.querySelector(".setup .text").textContent=s,"syncing"===t?e.querySelectorAll("button").forEach((e=>e.disabled=!0)):e.querySelectorAll("button:disabled").forEach((e=>e.disabled=!1)),setTimeout((()=>e.classList.remove("flash")),600)}}window.jvbOAuthPopup=function(e,t){const s=(window.screen.width-600)/2,n=(window.screen.height-700)/2;e+=(e.indexOf("?")>-1?"&":"?")+"popup=1";const o=window.open(e,t+"-oauth",`width=600,height=700,left=${s},top=${n},scrollbars=yes,resizable=yes,toolbar=no,menubar=no`);if(!o)return alert("Please allow popups for this site to complete the authorization process."),!1;window.jvbOAuthComplete=function(e,s,n){if(e===t)if(s){const e=document.querySelector(`.integration-card[data-service="${t}"] .setup .text`);e&&(e.textContent="Connection successful! Refreshing..."),setTimeout((()=>{jvbRefreshIntegration(t)}),1e3)}else alert("OAuth authorization failed: "+(n||"Unknown error")),jvbRefreshIntegration(t)};const i=setInterval((()=>{try{o.closed&&(clearInterval(i),setTimeout((()=>{jvbRefreshIntegration(t)}),1e3))}catch(e){}}),1e3);return!1},window.jvbRefreshIntegration=function(e){fetch(jvbSettings.api+"integrations",{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce()},body:JSON.stringify({service:e,action:"check_oauth_status"})}).then((e=>e.json())).then((t=>{if(console.log("OAuth status check result:",t),t.success&&t.authorized){const t=document.querySelector(`.integration-card[data-service="${e}"]`);if(t){t.classList.remove("disconnected"),t.classList.add("connected");const e=t.querySelector(".setup .text");e&&(e.textContent="Connected"),setTimeout((()=>{location.reload()}),1500)}}else location.reload()})).catch((e=>{console.error("Error checking OAuth status:",e),location.reload()}))},document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.integrations=new e)}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.initElements(),this.initListeners(),this.init()}initElements(){this.selectors={form:"form.integration",action:"data-action"};let e=document.querySelectorAll(this.selectors.form);this.forms=new Map,e.forEach(e=>{this.forms.set(e.dataset.service,e)})}initListeners(){this.handleClick=this.clickHandler.bind(this),this.handleChange=this.changeHandler.bind(this),this.handleSubmit=this.submitHandler.bind(this),document.addEventListener("click",this.handleClick),document.addEventListener("change",this.handleChange),document.addEventListener("submit",this.handleSubmit)}init(){document.addEventListener("DOMContentLoaded",()=>{this.checkForOAuthMessages()})}checkForOAuthMessages(){const e=new URLSearchParams(window.location.search),t=e.get("success"),s=e.get("error");if(t){this.showNotification(t,"success",5e3),this.cleanURL();document.querySelectorAll("form.integration").forEach(e=>{this.updateUI(e,"connected")})}else s&&(this.showNotification(s,"error",8e3),this.cleanURL())}cleanURL(){const e=new URL(window.location);e.searchParams.delete("success"),e.searchParams.delete("error"),window.history.replaceState({},document.title,e.pathname+e.hash)}showNotification(e,t="info",s=5e3){let n=document.querySelector(".integration-status-message");if(!n){n=document.createElement("div"),n.className="integration-status-message";const e=document.querySelector(".integration-settings")||document.querySelector("main")||document.body;e.insertBefore(n,e.firstChild)}n.textContent=e,n.className=`integration-status-message ${t}`,this.notificationTimeout&&clearTimeout(this.notificationTimeout),s>0&&(this.notificationTimeout=setTimeout(()=>{n.className="integration-status-message",n.textContent=""},s)),this.popup&&this.addPopup(e,s)}addPopup(e,t=2e3){this.popup||(this.popup=document.querySelector(".integration-popup")||this.createPopupElement()),this.popup.textContent=e,this.popup.classList.add("showing"),setTimeout(()=>{this.popup.classList.remove("showing")},t)}createPopupElement(){const e=document.createElement("div");return e.className="integration-popup",document.body.appendChild(e),e}clickHandler(e){if(e.target.closest(this.selectors.form)&&("BUTTON"===e.target.tagName||e.target.closest("button"))){e.preventDefault();let t="BUTTON"===e.target.tagName?e.target:e.target.closest("button");this.handleAction(t)}}changeHandler(e){if(e.target.closest(this.selectors.form))if("action"in e.target.dataset)this.handleAction(e.target);else{let t=this.getFormFromTarget(e.target);if(!t)return;t.classList.add("hasChanges"),t.querySelector(".setup .text").textContent="Unsaved Changes"}}submitHandler(e){e.target.closest(this.selectors.form)&&e.preventDefault()}getFormFromTarget(e){let t=e.closest("form")?.dataset.service;return this.forms.get(t)??!1}async handleAction(e){const t=e.closest("form"),s=t.dataset.service,n=e.dataset.action,o="BUTTON"===e.tagName,i=o&&"save_credentials"===n;if(!("confirm"in e.dataset)||confirm(e.dataset.confirm)){this.updateUI(t,"syncing");try{this.updateUI(t,"syncing");const a={service:s,action:n,user_id:window.auth.getUser(),data:{}};if(o||(a.data[e.name.replace(s+"_","")]=e.value),i){const e=new FormData(t);for(let[t,n]of e.entries())["service"].includes(t)||t.includes("nonce")||(a.data[t.replace(s+"_","")]=n)}const r=await fetch(jvbSettings.api+"integrations",{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce()},body:JSON.stringify(a)}),c=await r.json();if(r.ok&&c.success){let e="connected";switch(n){case"clear_credentials":e="disconnected";break;case"save_credentials":this.showNotification("Settings saved successfully","success")}this.updateUI(t,e),c.reload&&setTimeout(()=>{window.location.reload()},50)}else this.updateUI(t,"error",c.message??""),this.showNotification(c.message||"Operation failed","error")}catch(e){this.updateUI(t,"error"),this.showNotification("Network error: "+e.message,"error"),console.error("API Error:",e)}}}updateUI(e,t,s=""){let n=["connected","disconnected","hasChanges","syncing","error"];if(!n.includes(t))return;s=""===s?{connected:"Set Up",disconnected:"Not Set Up",hasChanges:"Unsaved Changes",syncing:"Testing changes",error:"Something went wrong"}[t]:s,"syncing"===t?e.querySelectorAll("button").forEach(e=>{e.disabled=!0}):e.querySelectorAll("button[disabled]").forEach(e=>{e.disabled=!1}),e.classList.remove(...n),e.classList.add(t,"flash"),e.querySelector(".setup .text").textContent=s,"syncing"===t?e.querySelectorAll("button").forEach(e=>e.disabled=!0):e.querySelectorAll("button:disabled").forEach(e=>e.disabled=!1),setTimeout(()=>e.classList.remove("flash"),600)}}window.jvbOAuthPopup=function(e,t){const s=(window.screen.width-600)/2,n=(window.screen.height-700)/2;e+=(e.indexOf("?")>-1?"&":"?")+"popup=1";const o=window.open(e,t+"-oauth",`width=600,height=700,left=${s},top=${n},scrollbars=yes,resizable=yes,toolbar=no,menubar=no`);if(!o)return alert("Please allow popups for this site to complete the authorization process."),!1;window.jvbOAuthComplete=function(e,s,n){if(e===t)if(s){const e=document.querySelector(`.integration-card[data-service="${t}"] .setup .text`);e&&(e.textContent="Connection successful! Refreshing..."),setTimeout(()=>{jvbRefreshIntegration(t)},1e3)}else alert("OAuth authorization failed: "+(n||"Unknown error")),jvbRefreshIntegration(t)};const i=setInterval(()=>{try{o.closed&&(clearInterval(i),setTimeout(()=>{jvbRefreshIntegration(t)},1e3))}catch(e){}},1e3);return!1},window.jvbRefreshIntegration=function(e){fetch(jvbSettings.api+"integrations",{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce()},body:JSON.stringify({service:e,action:"check_oauth_status"})}).then(e=>e.json()).then(t=>{if(console.log("OAuth status check result:",t),t.success&&t.authorized){const t=document.querySelector(`.integration-card[data-service="${e}"]`);if(t){t.classList.remove("disconnected"),t.classList.add("connected");const e=t.querySelector(".setup .text");e&&(e.textContent="Connected"),setTimeout(()=>{location.reload()},1500)}}else location.reload()}).catch(e=>{console.error("Error checking OAuth status:",e),location.reload()})},document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.integrations=new e)})})})();
\ No newline at end of file
diff --git a/assets/js/min/maps.min.js b/assets/js/min/maps.min.js
index 1a484ed..e41f314 100644
--- a/assets/js/min/maps.min.js
+++ b/assets/js/min/maps.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.instances=new Map,this.defaults=null,this.ready=!1,"undefined"!=typeof google&&google.maps?this.init():document.addEventListener("googleMapsReady",(()=>this.init()))}init(){this.ready=!0,this.defaults=window.jvbMapDefaults||{lat:53.5461,lng:-113.4938,zoom:14},document.querySelectorAll("[data-location-field-init]").forEach((e=>{try{const t=e.dataset.locationFieldInit;if(!t||""===t.trim())return void console.warn("Empty location field init data for field:",e);const o=JSON.parse(t);if(!o.fieldId)return void console.error("Missing fieldId in location field config");this.createLocationField(o)}catch(t){console.error("Failed to parse location field config:",t),console.error("Data attribute value:",e.dataset.locationFieldInit),console.error("Field element:",e)}}))}createLocationField(e){if(!this.ready)return console.warn("Google Maps not ready yet"),setTimeout((()=>this.createLocationField(e)),100),null;const{fieldId:t,initialCoords:o,onLocationSelected:n}=e,s=document.querySelector(`[data-field="${t}"]`);if(!s)return console.error("Field container not found:",t),null;const a=s.querySelector(".location-map");if(!a)return console.error("Required elements not found in field:",t),null;const l={center:o||this.defaults,zoom:this.defaults.zoom,mapId:this.defaults.mapId,styles:this.getMapStyles(),disableDefaultUI:!0},r=new google.maps.Map(a,l);let i=null;o&&o.lat&&o.lng&&(i=this.createMarker(r,o)),this.setupAutocomplete(s,r,i,n);const c={map:r,marker:i,fieldContainer:s,config:e};return this.instances.set(t,c),c}setupAutocomplete(e,t,o,n){console.log("Setting up autocomplete");const s=new google.maps.places.PlaceAutocompleteElement({includedRegionCodes:["ca"]});e.querySelector(".autocomplete-wrapper").append(s);let a=e.querySelector('[name="current_location[street]"]');return a&&""!==a.value&&(console.log("Saved value: ",a.value),s.value=a.value),s.addEventListener("gmp-select",(async({placePrediction:s})=>{const a=s.toPlace();await a.fetchFields({fields:["displayName","addressComponents","formattedAddress","location"]}),console.log("Display Name:",a.displayName),console.log("Formatted Address:",a.formattedAddress),console.log("Address Components:",a.addressComponents),console.log("Location:",a.location),a.viewport?t.fitBounds(a.viewport):(t.setCenter(a.location),t.setZoom(17));const l={lat:a.location.lat(),lng:a.location.lng(),address:a.displayName||a.formattedAddress||"",components:this.parseAddressComponents(a.addressComponents)};console.log("Grabbed Location: ",l),t.setCenter(a.location),o?o.position=a.location:o=this.createMarker(t,a.location),console.log("Updating field inputs"),this.updateFieldInputs(e,l),this.updateMapLinks(e,l.lat,l.lng),n&&n(l),e.dispatchEvent(new Event("change",{bubbles:!0}))})),o&&o.addListener("dragend",(t=>{const o={lat:t.latLng.lat(),lng:t.latLng.lng()};this.reverseGeocode(o.lat,o.lng,(t=>{if(t){const a={...o,address:t.formatted_address,components:t.components};s.value=a.address,this.updateFieldInputs(e,a),this.updateMapLinks(e,a.lat,a.lng),n&&n(a)}}))})),s}createMarker(e,t){return google.maps.marker&&google.maps.marker.AdvancedMarkerElement?new google.maps.marker.AdvancedMarkerElement({map:e,position:t,gmpDraggable:!0}):new google.maps.Marker({position:t,map:e,draggable:!0})}parseAddressComponents(e){const t={street:"",city:"",province:"",country:"",postal_code:""};if(!Array.isArray(e))return t;let o="",n="";return console.log("Attempting to parse address components..."),e.forEach((e=>{const s=e.types||[];console.log(e),console.log(s);const a=e.longText||e.long_name||"",l=e.shortText||e.short_name||"";s.includes("street_number")&&(o=a),s.includes("route")&&(n=a),s.includes("locality")&&(t.city=a),s.includes("administrative_area_level_1")&&(t.province=l),s.includes("country")&&(t.country=l),s.includes("postal_code")&&(t.postal_code=a)})),t.street=`${o} ${n}`.trim(),console.log("Final result: ",t),t}updateFieldInputs(e,t){const o=e.querySelector('[data-location-field="address"]'),n=e.querySelector('[data-location-field="lat"]'),s=e.querySelector('[data-location-field="lng"]');console.log("Address Input:",o),console.log("Latitude Input:",n),console.log("lngInput Input:",s),o&&(o.value=t.address||""),n&&(n.value=t.lat||""),s&&(s.value=t.lng||""),console.log("Components: ",t.components),t.components&&Object.keys(t.components).forEach((o=>{const n=e.querySelector(`[data-location-field="${o}"]`);console.log("Component input: ",n),n&&(n.value=t.components[o]||"")})),e.querySelectorAll('input[type="hidden"]').forEach((e=>{e.dispatchEvent(new Event("change",{bubbles:!0}))}))}updateMapLinks(e,t,o){const n=e.querySelector(".location-links");if(n){const e=n.querySelector(".google-maps-link"),s=n.querySelector(".apple-maps-link");e&&(e.href=`https://www.google.com/maps/search/?api=1&query=${t},${o}`),s&&(s.href=`https://maps.apple.com/?ll=${t},${o}`)}else{const n=e.querySelector(".location-preview");if(n){const e=document.createElement("div");e.className="location-links",e.innerHTML=`\n\t\t\t\t\t<a href="https://www.google.com/maps/search/?api=1&query=${t},${o}"\n\t\t\t\t\t   target="_blank" class="google-maps-link">\n\t\t\t\t\t\t<span>View in Google Maps</span>\n\t\t\t\t\t</a>\n\t\t\t\t\t<a href="https://maps.apple.com/?ll=${t},${o}"\n\t\t\t\t\t   target="_blank" class="apple-maps-link">\n\t\t\t\t\t\t<span>View in Apple Maps</span>\n\t\t\t\t\t</a>\n\t\t\t\t`,n.appendChild(e)}}}createDisplayMap(e,t){if(!this.ready)return null;const o=document.getElementById(e);if(!o)return null;const n={center:{lat:t.lat,lng:t.lng},zoom:t.zoom||this.defaults.zoom,mapId:this.defaults.mapId,styles:this.getMapStyles(),disableDefaultUI:!t.interactive,gestureHandling:t.interactive?"auto":"none"},s=new google.maps.Map(o,n);if(t.show_marker){const e=this.createMarker(s,{lat:t.lat,lng:t.lng});if(t.show_info_window&&t.info_content){const o=new google.maps.InfoWindow({content:`<div class="map-info-window">${t.info_content}</div>`});e.addListener("click",(()=>{o.open(s,e)})),!1!==t.auto_open&&setTimeout((()=>o.open(s,e)),500)}}return this.instances.set(e,{map:s,config:t}),s}reverseGeocode(e,t,o){(new google.maps.Geocoder).geocode({location:{lat:e,lng:t}},((e,t)=>{if("OK"===t&&e[0]){const t={formatted_address:e[0].formatted_address,components:this.parseAddressComponents(e[0].address_components)};o(t)}else o(null)}))}getMapStyles(){return[{elementType:"geometry",stylers:[{color:"#f5f5f5"}]},{elementType:"labels.text.fill",stylers:[{color:"#616161"}]},{elementType:"labels.text.stroke",stylers:[{color:"#f5f5f5"}]},{featureType:"road",elementType:"geometry",stylers:[{color:"#ffffff"}]},{featureType:"water",elementType:"geometry",stylers:[{color:"#c9c9c9"}]},{featureType:"poi.business",stylers:[{visibility:"off"}]},{featureType:"transit",elementType:"labels.icon",stylers:[{visibility:"off"}]}]}getInstance(e){return this.instances.get(e)}destroyInstance(e){const t=this.instances.get(e);t&&(t.map&&google.maps.event.clearInstanceListeners(t.map),this.instances.delete(e))}}document.addEventListener("DOMContentLoaded",(()=>{window.jvbMaps||(window.jvbMaps=new e)}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.instances=new Map,this.defaults=null,this.ready=!1,"undefined"!=typeof google&&google.maps?this.init():document.addEventListener("googleMapsReady",()=>this.init())}init(){this.ready=!0,this.defaults=window.jvbMapDefaults||{lat:53.5461,lng:-113.4938,zoom:14},document.querySelectorAll("[data-location-field-init]").forEach(e=>{try{const t=e.dataset.locationFieldInit;if(!t||""===t.trim())return void console.warn("Empty location field init data for field:",e);const o=JSON.parse(t);if(!o.fieldId)return void console.error("Missing fieldId in location field config");this.createLocationField(o)}catch(t){console.error("Failed to parse location field config:",t),console.error("Data attribute value:",e.dataset.locationFieldInit),console.error("Field element:",e)}})}createLocationField(e){if(!this.ready)return console.warn("Google Maps not ready yet"),setTimeout(()=>this.createLocationField(e),100),null;const{fieldId:t,initialCoords:o,onLocationSelected:n}=e,s=document.querySelector(`[data-field="${t}"]`);if(!s)return console.error("Field container not found:",t),null;const a=s.querySelector(".location-map");if(!a)return console.error("Required elements not found in field:",t),null;const l={center:o||this.defaults,zoom:this.defaults.zoom,mapId:this.defaults.mapId,styles:this.getMapStyles(),disableDefaultUI:!0},r=new google.maps.Map(a,l);let i=null;o&&o.lat&&o.lng&&(i=this.createMarker(r,o)),this.setupAutocomplete(s,r,i,n);const c={map:r,marker:i,fieldContainer:s,config:e};return this.instances.set(t,c),c}setupAutocomplete(e,t,o,n){console.log("Setting up autocomplete");const s=new google.maps.places.PlaceAutocompleteElement({includedRegionCodes:["ca"]});e.querySelector(".autocomplete-wrapper").append(s);let a=e.querySelector('[name="current_location[street]"]');return a&&""!==a.value&&(console.log("Saved value: ",a.value),s.value=a.value),s.addEventListener("gmp-select",async({placePrediction:s})=>{const a=s.toPlace();await a.fetchFields({fields:["displayName","addressComponents","formattedAddress","location"]}),console.log("Display Name:",a.displayName),console.log("Formatted Address:",a.formattedAddress),console.log("Address Components:",a.addressComponents),console.log("Location:",a.location),a.viewport?t.fitBounds(a.viewport):(t.setCenter(a.location),t.setZoom(17));const l={lat:a.location.lat(),lng:a.location.lng(),address:a.displayName||a.formattedAddress||"",components:this.parseAddressComponents(a.addressComponents)};console.log("Grabbed Location: ",l),t.setCenter(a.location),o?o.position=a.location:o=this.createMarker(t,a.location),console.log("Updating field inputs"),this.updateFieldInputs(e,l),this.updateMapLinks(e,l.lat,l.lng),n&&n(l),e.dispatchEvent(new Event("change",{bubbles:!0}))}),o&&o.addListener("dragend",t=>{const o={lat:t.latLng.lat(),lng:t.latLng.lng()};this.reverseGeocode(o.lat,o.lng,t=>{if(t){const a={...o,address:t.formatted_address,components:t.components};s.value=a.address,this.updateFieldInputs(e,a),this.updateMapLinks(e,a.lat,a.lng),n&&n(a)}})}),s}createMarker(e,t){return google.maps.marker&&google.maps.marker.AdvancedMarkerElement?new google.maps.marker.AdvancedMarkerElement({map:e,position:t,gmpDraggable:!0}):new google.maps.Marker({position:t,map:e,draggable:!0})}parseAddressComponents(e){const t={street:"",city:"",province:"",country:"",postal_code:""};if(!Array.isArray(e))return t;let o="",n="";return console.log("Attempting to parse address components..."),e.forEach(e=>{const s=e.types||[];console.log(e),console.log(s);const a=e.longText||e.long_name||"",l=e.shortText||e.short_name||"";s.includes("street_number")&&(o=a),s.includes("route")&&(n=a),s.includes("locality")&&(t.city=a),s.includes("administrative_area_level_1")&&(t.province=l),s.includes("country")&&(t.country=l),s.includes("postal_code")&&(t.postal_code=a)}),t.street=`${o} ${n}`.trim(),console.log("Final result: ",t),t}updateFieldInputs(e,t){const o=e.querySelector('[data-location-field="address"]'),n=e.querySelector('[data-location-field="lat"]'),s=e.querySelector('[data-location-field="lng"]');console.log("Address Input:",o),console.log("Latitude Input:",n),console.log("lngInput Input:",s),o&&(o.value=t.address||""),n&&(n.value=t.lat||""),s&&(s.value=t.lng||""),console.log("Components: ",t.components),t.components&&Object.keys(t.components).forEach(o=>{const n=e.querySelector(`[data-location-field="${o}"]`);console.log("Component input: ",n),n&&(n.value=t.components[o]||"")}),e.querySelectorAll('input[type="hidden"]').forEach(e=>{e.dispatchEvent(new Event("change",{bubbles:!0}))})}updateMapLinks(e,t,o){const n=e.querySelector(".location-links");if(n){const e=n.querySelector(".google-maps-link"),s=n.querySelector(".apple-maps-link");e&&(e.href=`https://www.google.com/maps/search/?api=1&query=${t},${o}`),s&&(s.href=`https://maps.apple.com/?ll=${t},${o}`)}else{const n=e.querySelector(".location-preview");if(n){const e=document.createElement("div");e.className="location-links",e.innerHTML=`\n\t\t\t\t\t<a href="https://www.google.com/maps/search/?api=1&query=${t},${o}"\n\t\t\t\t\t   target="_blank" class="google-maps-link">\n\t\t\t\t\t\t<span>View in Google Maps</span>\n\t\t\t\t\t</a>\n\t\t\t\t\t<a href="https://maps.apple.com/?ll=${t},${o}"\n\t\t\t\t\t   target="_blank" class="apple-maps-link">\n\t\t\t\t\t\t<span>View in Apple Maps</span>\n\t\t\t\t\t</a>\n\t\t\t\t`,n.appendChild(e)}}}createDisplayMap(e,t){if(!this.ready)return null;const o=document.getElementById(e);if(!o)return null;const n={center:{lat:t.lat,lng:t.lng},zoom:t.zoom||this.defaults.zoom,mapId:this.defaults.mapId,styles:this.getMapStyles(),disableDefaultUI:!t.interactive,gestureHandling:t.interactive?"auto":"none"},s=new google.maps.Map(o,n);if(t.show_marker){const e=this.createMarker(s,{lat:t.lat,lng:t.lng});if(t.show_info_window&&t.info_content){const o=new google.maps.InfoWindow({content:`<div class="map-info-window">${t.info_content}</div>`});e.addListener("click",()=>{o.open(s,e)}),!1!==t.auto_open&&setTimeout(()=>o.open(s,e),500)}}return this.instances.set(e,{map:s,config:t}),s}reverseGeocode(e,t,o){(new google.maps.Geocoder).geocode({location:{lat:e,lng:t}},(e,t)=>{if("OK"===t&&e[0]){const t={formatted_address:e[0].formatted_address,components:this.parseAddressComponents(e[0].address_components)};o(t)}else o(null)})}getMapStyles(){return[{elementType:"geometry",stylers:[{color:"#f5f5f5"}]},{elementType:"labels.text.fill",stylers:[{color:"#616161"}]},{elementType:"labels.text.stroke",stylers:[{color:"#f5f5f5"}]},{featureType:"road",elementType:"geometry",stylers:[{color:"#ffffff"}]},{featureType:"water",elementType:"geometry",stylers:[{color:"#c9c9c9"}]},{featureType:"poi.business",stylers:[{visibility:"off"}]},{featureType:"transit",elementType:"labels.icon",stylers:[{visibility:"off"}]}]}getInstance(e){return this.instances.get(e)}destroyInstance(e){const t=this.instances.get(e);t&&(t.map&&google.maps.event.clearInstanceListeners(t.map),this.instances.delete(e))}}document.addEventListener("DOMContentLoaded",()=>{window.jvbMaps||(window.jvbMaps=new e)})})();
\ No newline at end of file
diff --git a/assets/js/min/modal.min.js b/assets/js/min/modal.min.js
index 8b39e66..d5503a0 100644
--- a/assets/js/min/modal.min.js
+++ b/assets/js/min/modal.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(t,s){this.modal=t,this.a11y=window.jvbA11y,this.options={openMessage:"Opened modal",closeMessage:"Closed modal",open:null,close:"button.cancel",save:'button[type="submit"]',...s},this.subscribers=new Set,e.modalStack||(e.modalStack=[]),this.boundEscapeListener=this.handleEscapeListener.bind(this),this.boundBackdropListener=this.handleBackdropListener.bind(this),this.init()}init(){this.isOpen=!1,this.hasChanges=!1,this.initElements(),this.initEvents()}initElements(){this.elements={open:this.options.open,close:this.options.close,save:this.options.save}}handleClose(){if(e.modalStack[e.modalStack.length-1]!==this)return;this.notify("modal-close",this.modal),this.a11y.announce(this.options.closeMessage),this.modal.close(),this.isOpen=!1;const t=e.modalStack.indexOf(this);-1!==t&&e.modalStack.splice(t,1),this.showBody(),this.removeCloseListeners()}handleOpen(t){this.addCloseListeners(),this.hideBody(),this.isOpen=!0,this.modal.showModal(),this.a11y||(this.a11y=window.jvbA11y),this.a11y.trapFocus(this.modal),this.a11y.announce(this.options.openMessage),e.modalStack.push(this),this.notify("modal-open",{modal:this.modal,event:t})}addCloseListeners(){document.addEventListener("keydown",this.boundEscapeListener),document.addEventListener("click",this.boundBackdropListener)}removeCloseListeners(){document.removeEventListener("keydown",this.boundEscapeListener),document.removeEventListener("click",this.boundBackdropListener)}handleEscapeListener(t){"Escape"===t.key&&e.modalStack[e.modalStack.length-1]===this&&(t.preventDefault(),this.handleClose())}handleBackdropListener(t){t.target===this.modal&&e.modalStack[e.modalStack.length-1]===this&&this.handleClose()}hideBody(){0===e.modalStack.length&&(document.body.style.overflow="hidden")}showBody(){0===e.modalStack.length&&(document.body.style.overflow="")}initEvents(){document.addEventListener("click",this.handleClick.bind(this))}handleClick(t){this.elements.open&&window.targetCheck(t,this.elements.open)?this.handleOpen(t):this.modal.contains(t.target)&&this.elements.close&&window.targetCheck(t,this.elements.close)&&e.modalStack[e.modalStack.length-1]===this&&this.handleClose()}static getTopModal(){return e.modalStack[e.modalStack.length-1]||null}static getAllModals(){return[...e.modalStack]}static closeTopModal(){const t=e.getTopModal();t&&t.handleClose()}static closeAllModals(){for(;e.modalStack.length>0;)e.closeTopModal()}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach((s=>s(e,t)))}destroy(){this.subscribers.clear(),e.closeAllModals(),this.showBody(),this.removeCloseListeners()}}document.addEventListener("DOMContentLoaded",(()=>{window.jvbModal=e}))})();
\ No newline at end of file
+(()=>{class e{constructor(t,s){this.modal=t,this.a11y=window.jvbA11y,this.options={openMessage:"Opened modal",closeMessage:"Closed modal",open:null,close:"button.cancel",save:'button[type="submit"]',...s},this.subscribers=new Set,e.modalStack||(e.modalStack=[]),this.boundEscapeListener=this.handleEscapeListener.bind(this),this.boundBackdropListener=this.handleBackdropListener.bind(this),this.init()}init(){this.isOpen=!1,this.hasChanges=!1,this.initElements(),this.initEvents()}initElements(){this.elements={open:this.options.open,close:this.options.close,save:this.options.save}}handleClose(){if(e.modalStack[e.modalStack.length-1]!==this)return;this.notify("modal-close",this.modal),this.a11y.announce(this.options.closeMessage),this.modal.close(),this.isOpen=!1;const t=e.modalStack.indexOf(this);-1!==t&&e.modalStack.splice(t,1),this.showBody(),this.removeCloseListeners()}handleOpen(t){this.addCloseListeners(),this.hideBody(),this.isOpen=!0,this.modal.showModal(),this.a11y||(this.a11y=window.jvbA11y),this.a11y.trapFocus(this.modal),this.a11y.announce(this.options.openMessage),e.modalStack.push(this),this.notify("modal-open",{modal:this.modal,event:t})}addCloseListeners(){document.addEventListener("keydown",this.boundEscapeListener),document.addEventListener("click",this.boundBackdropListener)}removeCloseListeners(){document.removeEventListener("keydown",this.boundEscapeListener),document.removeEventListener("click",this.boundBackdropListener)}handleEscapeListener(t){"Escape"===t.key&&e.modalStack[e.modalStack.length-1]===this&&(t.preventDefault(),this.handleClose())}handleBackdropListener(t){t.target===this.modal&&e.modalStack[e.modalStack.length-1]===this&&this.handleClose()}hideBody(){0===e.modalStack.length&&(document.body.style.overflow="hidden")}showBody(){0===e.modalStack.length&&(document.body.style.overflow="")}initEvents(){document.addEventListener("click",this.handleClick.bind(this))}handleClick(t){this.elements.open&&window.targetCheck(t,this.elements.open)?this.handleOpen(t):this.modal.contains(t.target)&&this.elements.close&&window.targetCheck(t,this.elements.close)&&e.modalStack[e.modalStack.length-1]===this&&this.handleClose()}static getTopModal(){return e.modalStack[e.modalStack.length-1]||null}static getAllModals(){return[...e.modalStack]}static closeTopModal(){const t=e.getTopModal();t&&t.handleClose()}static closeAllModals(){for(;e.modalStack.length>0;)e.closeTopModal()}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(s=>s(e,t))}destroy(){this.subscribers.clear(),e.closeAllModals(),this.showBody(),this.removeCloseListeners()}}document.addEventListener("DOMContentLoaded",()=>{window.jvbModal=e})})();
\ No newline at end of file
diff --git a/assets/js/min/navigation.min.js b/assets/js/min/navigation.min.js
index 54803cd..d99198a 100644
--- a/assets/js/min/navigation.min.js
+++ b/assets/js/min/navigation.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.counter=0,this.initElements(),0!==this.navs.size&&(this.openNav=null,this.openSubmenu=null,this.releaseFocusTrap=null,this.clicked=new Set,this.initListeners())}initElements(){this.navs=new Map,document.querySelectorAll("nav:has(.submenu), nav:has(.toggle)").forEach((e=>{let t=e.id;(""===t||this.navs.has(t))&&(t=`nav-${this.counter}`,e.id=t,this.counter++),e.querySelector(".submenu")&&(e.addEventListener("mouseenter",this.handleHoverOn.bind(this)),e.addEventListener("mouseleave",this.handleHoverOff.bind(this)));let[s,n,i]=[e.querySelectorAll("nav .toggle"),e.querySelectorAll(".has-submenu"),e.querySelectorAll(".toggle:not(.main)")],a={nav:e,toggles:s,submenus:n,submenuToggles:i};this.counter++,n.forEach((e=>{e.id="submenu-"+this.counter,e.addEventListener("mouseenter",this.handleHoverOn.bind(this)),e.addEventListener("mouseleave",this.handleHoverOff.bind(this)),this.counter++})),this.navs.set(t,a)}))}initListeners(){this.clickListener=this.handleClick.bind(this),this.keysListener=this.handleKeys.bind(this),this.hoverOnListener=this.handleHoverOn.bind(this),this.hoverOffListener=this.handleHoverOff.bind(this),document.addEventListener("click",this.clickListener)}handleClick(e){if(0===this.navs.size)return;let t=e.target.closest(".toggle.main");if(t){let e=t.closest("nav"),s=!this.clicked.has(e);return e.classList.contains("open")!==s&&this.toggleNav(s,e.id),void(s?this.clicked.add(e):this.clicked.delete(e))}let s=e.target.closest('[data-action="toggle-submenu"], .has-submenu .a');if(s){let e=s.closest("li"),t=!this.clicked.has(e),n=e.classList.contains("open")!==t;return t?this.clicked.add(e):this.clicked.delete(e),void(n&&this.toggleSubmenu(t,e))}if(!this.openNav)return;let n=!0;for(let[t,s]of this.navs)if(e.target.closest("#"+t)){n=!1;break}n&&this.toggleNav(!1,this.openNav)}handleHoverOn(e){let t=e.currentTarget;this.clicked.has(t)||t.closest("nav.sidebar")||(t.classList.contains("has-submenu")?this.toggleSubmenu(!0,t):"NAV"===t.tagName&&(t.classList.contains("mobile")||this.toggleNav(!0,t.id)))}handleHoverOff(e){let t=e.currentTarget;if(!this.clicked.has(t)&&!t.closest("nav.sidebar"))if(t.classList.contains("has-submenu"))this.toggleSubmenu(!1,t);else if("NAV"===t.tagName){if(t.classList.contains("mobile"))return;let e=this.navs.get(t.id),s=!0;for(let t of e.submenus)if(this.clicked.has(t)){s=!1;break}s&&this.toggleNav(!1,t.id)}}handleKeys(e){if(this.openNav)switch(e.key){case"Escape":this.closeAll();break;case"ArrowDown":this.focusNextItem(),e.preventDefault();break;case"ArrowUp":this.focusPrevItem(),e.preventDefault()}}closeAll(){let e=this.navs.get(this.openNav);e&&this.clicked.has(e.nav)&&this.clicked.delete(e.nav),this.toggleNav(!1,this.openNav)}focusNextItem(){const e=this.getFocusableItems(),t=e.indexOf(document.activeElement);(e[t+1]||e[0]).focus()}focusPrevItem(){const e=this.getFocusableItems(),t=e.indexOf(document.activeElement);(e[t-1]||e[e.length-1]).focus()}getFocusableItems(){return Array.from(document.querySelectorAll("nav.open a, nav.open button")).filter((e=>!e.disabled&&!e.closest("[hidden]")&&!e.closest("[inert]")))}toggleNav(e,t){let s=this.navs.get(t);if(s){if(e&&t!==this.openNav&&this.toggleNav(!1,this.openNav),e?(this.openNav=t,document.addEventListener("keydown",this.keysListener),s.nav.classList.contains("mobile")&&(this.releaseFocusTrap=window.jvbA11y.trapFocus(s.nav))):(this.releaseFocusTrap&&(this.releaseFocusTrap(),this.releaseFocusTrap=null),this.openNav===t&&(this.openNav=null),document.removeEventListener("keydown",this.keysListener),s.nav.classList.contains("sidebar")||Array.from(s.submenus).forEach((e=>{e.classList.contains("open")&&this.toggleSubmenu(!1,e)})),Array.from(s.submenus).forEach((e=>{this.clicked.has(e)&&this.clicked.delete(e)}))),s.nav.classList.toggle("open",e),s.nav.classList.contains("mobile")){const t=s.nav.querySelector(":scope > ul");t&&(t.inert=!e)}s.nav.setAttribute("aria-expanded",e),e&&s.nav.querySelector("a:not(.skip-to-content)")?.focus()}}toggleSubmenu(e,t){e&&this.openSubmenu&&this.openSubmenu!==t&&!this.openSubmenu.contains(t)&&this.toggleSubmenu(!1,this.openSubmenu),e?this.openSubmenu=t:this.openSubmenu===t&&(this.openSubmenu=null,this.clicked.delete(t));let[s,n]=[t.querySelector(".toggle"),t.querySelector("a")];e||s.focus();const i=t.querySelector(":scope > ul");i&&(i.inert=!e);let a=s.getAttribute("aria-label");window.jvbA11y.announce(e?`${a} expanded`:`${a} collapsed`),t.classList.toggle("open",e),s.setAttribute("aria-expanded",e),e&&n&&n.focus()}}document.addEventListener("DOMContentLoaded",(function(){window.jvbNav=new e}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.counter=0,this.initElements(),0!==this.navs.size&&(this.openNav=null,this.openSubmenu=null,this.releaseFocusTrap=null,this.clicked=new Set,this.initListeners())}initElements(){this.navs=new Map,document.querySelectorAll("nav:has(.submenu), nav:has(.toggle)").forEach(e=>{let t=e.id;(""===t||this.navs.has(t))&&(t=`nav-${this.counter}`,e.id=t,this.counter++),e.querySelector(".submenu")&&(e.addEventListener("mouseenter",this.handleHoverOn.bind(this)),e.addEventListener("mouseleave",this.handleHoverOff.bind(this)));let[s,n,i]=[e.querySelectorAll("nav .toggle"),e.querySelectorAll(".has-submenu"),e.querySelectorAll(".toggle:not(.main)")],a={nav:e,toggles:s,submenus:n,submenuToggles:i};this.counter++,n.forEach(e=>{e.id="submenu-"+this.counter,e.addEventListener("mouseenter",this.handleHoverOn.bind(this)),e.addEventListener("mouseleave",this.handleHoverOff.bind(this)),this.counter++}),this.navs.set(t,a)})}initListeners(){this.clickListener=this.handleClick.bind(this),this.keysListener=this.handleKeys.bind(this),this.hoverOnListener=this.handleHoverOn.bind(this),this.hoverOffListener=this.handleHoverOff.bind(this),document.addEventListener("click",this.clickListener)}handleClick(e){if(0===this.navs.size)return;let t=e.target.closest(".toggle.main");if(t){let e=t.closest("nav"),s=!this.clicked.has(e);return e.classList.contains("open")!==s&&this.toggleNav(s,e.id),void(s?this.clicked.add(e):this.clicked.delete(e))}let s=e.target.closest('[data-action="toggle-submenu"], .has-submenu .a');if(s){let e=s.closest("li"),t=!this.clicked.has(e),n=e.classList.contains("open")!==t;return t?this.clicked.add(e):this.clicked.delete(e),void(n&&this.toggleSubmenu(t,e))}if(!this.openNav)return;let n=!0;for(let[t,s]of this.navs)if(e.target.closest("#"+t)){n=!1;break}n&&this.toggleNav(!1,this.openNav)}handleHoverOn(e){let t=e.currentTarget;this.clicked.has(t)||t.closest("nav.sidebar")||(t.classList.contains("has-submenu")?this.toggleSubmenu(!0,t):"NAV"===t.tagName&&(t.classList.contains("mobile")||this.toggleNav(!0,t.id)))}handleHoverOff(e){let t=e.currentTarget;if(!this.clicked.has(t)&&!t.closest("nav.sidebar"))if(t.classList.contains("has-submenu"))this.toggleSubmenu(!1,t);else if("NAV"===t.tagName){if(t.classList.contains("mobile"))return;let e=this.navs.get(t.id),s=!0;for(let t of e.submenus)if(this.clicked.has(t)){s=!1;break}s&&this.toggleNav(!1,t.id)}}handleKeys(e){if(this.openNav)switch(e.key){case"Escape":this.closeAll();break;case"ArrowDown":this.focusNextItem(),e.preventDefault();break;case"ArrowUp":this.focusPrevItem(),e.preventDefault()}}closeAll(){let e=this.navs.get(this.openNav);e&&this.clicked.has(e.nav)&&this.clicked.delete(e.nav),this.toggleNav(!1,this.openNav)}focusNextItem(){const e=this.getFocusableItems(),t=e.indexOf(document.activeElement);(e[t+1]||e[0]).focus()}focusPrevItem(){const e=this.getFocusableItems(),t=e.indexOf(document.activeElement);(e[t-1]||e[e.length-1]).focus()}getFocusableItems(){return Array.from(document.querySelectorAll("nav.open a, nav.open button")).filter(e=>!e.disabled&&!e.closest("[hidden]")&&!e.closest("[inert]"))}toggleNav(e,t){let s=this.navs.get(t);if(s){if(e&&t!==this.openNav&&this.toggleNav(!1,this.openNav),e?(this.openNav=t,document.addEventListener("keydown",this.keysListener),s.nav.classList.contains("mobile")&&(this.releaseFocusTrap=window.jvbA11y.trapFocus(s.nav))):(this.releaseFocusTrap&&(this.releaseFocusTrap(),this.releaseFocusTrap=null),this.openNav===t&&(this.openNav=null),document.removeEventListener("keydown",this.keysListener),s.nav.classList.contains("sidebar")||Array.from(s.submenus).forEach(e=>{e.classList.contains("open")&&this.toggleSubmenu(!1,e)}),Array.from(s.submenus).forEach(e=>{this.clicked.has(e)&&this.clicked.delete(e)})),s.nav.classList.toggle("open",e),s.nav.classList.contains("mobile")){const t=s.nav.querySelector(":scope > ul");t&&(t.inert=!e)}s.nav.setAttribute("aria-expanded",e),e&&s.nav.querySelector("a:not(.skip-to-content)")?.focus()}}toggleSubmenu(e,t){e&&this.openSubmenu&&this.openSubmenu!==t&&!this.openSubmenu.contains(t)&&this.toggleSubmenu(!1,this.openSubmenu),e?this.openSubmenu=t:this.openSubmenu===t&&(this.openSubmenu=null,this.clicked.delete(t));let[s,n]=[t.querySelector(".toggle"),t.querySelector("a")];e||s.focus();const i=t.querySelector(":scope > ul");i&&(i.inert=!e);let a=s.getAttribute("aria-label");window.jvbA11y.announce(e?`${a} expanded`:`${a} collapsed`),t.classList.toggle("open",e),s.setAttribute("aria-expanded",e),e&&n&&n.focus()}}document.addEventListener("DOMContentLoaded",function(){window.jvbNav=new e})})();
\ No newline at end of file
diff --git a/assets/js/min/news.min.js b/assets/js/min/news.min.js
index ed0b56c..9a90947 100644
--- a/assets/js/min/news.min.js
+++ b/assets/js/min/news.min.js
@@ -1 +1 @@
-window.newsManager=class{constructor(){this.queue=window.jvbQueue,this.loading=window.jvbLoading,this.cache=window.jvbCache,this.a11y=window.jvbA11y,this.error=window.jvbError,this.activeTab="all",this.tabs=new window.jvbTabs(document.querySelector(".replace"),{news:()=>{this.activeTab="all",this.resetFilters(),this.loadItems(!0).then((()=>{}))},mine:()=>{console.log("switching to mine tab"),this.activeTab="own",this.resetFilters(),this.filters.artist=window.auth.getUser(),this.loadItems(!0).then((()=>{}))},watching:()=>{this.activeTab="watching",this.resetFilters(),this.filters.watched=!0,this.loadItems(!0).then((()=>{}))}}),this.isLoading=!1,this.alreadyHandling=!1,this.template=new Map,this.endpoints={news:"news",vote:"news/vote"},this.resetFilters(),this.state={hasMore:!0,pages:1,items:0},this.initElements(),this.initEvents(),this.loadItems()}resetFilters(){this.filters={page:1,order:"DESC",orderby:"date",shop:null,type:null,artist:null,watched:!1}}initElements(){this.container=document.querySelector(".replace"),this.grid=this.container.querySelector(".item-grid"),this.addButton=this.container.querySelector(".add-item-btn"),this.addModal=new window.jvbModal(this.container.querySelector(".create-modal"),{render:this.renderModal.bind(this),open:this.addButton,content:"news",openMessage:"Opened modal to create a news post.",onSave:this.saveModal.bind(this)}),this.filterForm=this.container.querySelector("form"),this.dateRangeFilter=new window.jvbModal(this.container.querySelector("dialog.date-range"),{open:!1}),this.clearFilters=this.container.querySelector(".clear-filters"),this.replyModal=new window.jvbModal(this.container.querySelector(".create-response"),{open:!1,content:"response",openMessage:"Opened Response modal",onSave:this.saveCreatedResponse.bind(this)})}initEvents(){this.filterForm.addEventListener("change",(e=>{let t=e.target.value;if(!e.target.closest(".date-range"))if("custom"===t)this.handleCustomDateRange();else{let s=e.target.name;s?this.filters[s]=t:this.resetFilters(),this.loadItems(!0)}})),document.addEventListener("click",(e=>{if(e.target===this.clearFilters&&(this.filterForm.reset(),this.resetFilters(),this.loadItems(!0)),e.target.closest("button.reply")){let t=e.target.closest("button"),s=t.closest(".item").dataset.id,n="";"news"===t.dataset.type?n=t.closest(".item").querySelector(".item-info").innerHTML:(n=t.closest(".response").querySelector(".content").innerHTML,this.replyModal.modal.dataset.parent_id=t.id.replace("reply-to","")),this.replyModal.modal.dataset.id=s,this.replyModal.modal.dataset.type=t.dataset.type,this.replyModal.modal.querySelector(".original").innerHTML="<h5>Replying to:</h5>"+n,this.replyModal.handleOpen()}}))}renderModal(){}handleCustomDateRange(){this.dateRangeFilter.handleOpen();let e=this.dateRangeFilter.modal.querySelector("input.date-start"),t=this.dateRangeFilter.modal.querySelector("input.date-end"),s=this.dateRangeFilter.modal.querySelector("select");this.dateRangeFilter.modal.querySelectorAll("input, select").forEach((n=>{n.addEventListener("change",(i=>{n===e&&""!==t.value||n===t&&""!==e.value?(this.filters.dateFrom=e.value,this.filters.dateTo=t.value,this.dateRangeFilter.handleClose(),this.loadItems(!0)):n===s&&(this.filters.customDate=s.value,this.dateRangeFilter.handleClose(),this.loadItems(!0))}))}))}async saveModal(e){const t=new FormData(this.addModal.modal.querySelector("form"));t.append("user",window.auth.getUser()),this.queue.addToQueue({type:"new_news",data:t})}async loadItems(e=!0){if(!this.isLoading)try{this.isLoading=!0,this.loading.show(),e&&(this.filters.page=1,removeChildren(this.grid),this.grid.classList.remove("empty"));const t=this.buildFilters(),s=await this.cache.fetchWithCache(`${jvbSettings.api}${this.endpoints.news}?${t.toString()}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash")}},{context:"news",forceRefresh:!0});return this.renderItems(s.items||[],this.filters.page>1),s.pagination&&(this.state={hasMore:s.has_more,items:s.items,pages:s.pages}),s}catch(e){throw this.handleError(e,"loading news"),e}finally{this.isLoading=!1,this.loading.hide()}}buildFilters(){const e=JSON.parse(JSON.stringify(this.filters));let t={};for(var[s,n]of Object.entries(e))!1!==n&&null!==n&&(t[s]=n);return new URLSearchParams(t)}renderItems(e,t=!1){if(t||removeChildren(this.grid),0===e.length)return this.a11y.announceItems(0,t),void this.showEmptyState();const s=document.createDocumentFragment(),n=i=>{const a=Math.min(i+10,e.length);for(let t=i;t<a;t++){const n=e[t],i=this.createItemElement(n);s.appendChild(i)}a<e.length?requestAnimationFrame((()=>{n(a)})):(this.grid.appendChild(s),this.a11y.makeNavigable(this.grid.querySelectorAll(".item:not([data-keyboard-nav])")),this.a11y.announceItems(e.length,t,this.state.hasMore))};e.length>0?n(0):this.a11y.announceItems(0,t)}createItemElement(e){const t=window.getTemplate(`template-${this.activeTab}`);t.id=`news-${e.id}`,t.dataset.id=e.id;const[s]=t.getElementsByTagName("h3"),[n]=t.getElementsByClassName("published"),[i]=t.getElementsByClassName("artist"),[a]=t.getElementsByClassName("shop"),[o]=t.getElementsByClassName("tldr"),[r]=t.getElementsByClassName("item-info"),[l]=t.getElementsByClassName("image");[s.textContent,n.textContent,i.href,i.textContent,o.textContent,r.innerHTML]=[e.title,formatTimeAgo(e.date),e.artist.url,e.artist.name,e.tldr,e.post_content],e.shop?[a.href,a.innerHTML]=[e.shop.url,jvbSettings.icons.shop+e.shop.name]:a.hidden=!0;const[d]=t.getElementsByClassName("favourite-button");if("own"!==this.activeTab)[d.dataset.id,d.dataset.artist]=[e.id,e.artist.id],window.userFavourites.news?.includes(parseInt(e.id))?(removeChildren(d),d.append(getIcon("star-fi"))):(removeChildren(d),d.append(getIcon("star")));else{d.hidden=!0;const[s]=t.getElementsByClassName("select-checkbox"),[n]=t.getElementsByTagName("label");[s.id,s.value,n.for]=[`item-${e.id}`,e.id,`item-${e.id}`]}let h="";window.userVotes?.news?.has(e.id)&&(h=window.userVotes.news.get(e.id)),console.log(e),t.querySelector(".summary").appendChild(formatVote(e,h));let c=window.getTemplate("commentsButton");c.href=`#responses-to-${e.id}`,c.querySelector(".count").textContent=e.comments.items.length;let m=window.getTemplate("responses");m.id=`responses-to-${e.id}`,m.querySelector("summary").textContent+=" { "+e.comments.items.length+" }";let p=window.getTemplate("replyButton");return p.id="reply-to-"+e.id,p.dataset.type="news",p.dataset.action="reply",t.appendChild(p),e.comments.items.length>0&&e.comments.items.forEach((e=>{m.appendChild(this.formatComment(e))})),t.appendChild(m),t.querySelector(".vote").prepend(c,t.querySelector(".vote button")),e.image&&e.image.replace(/src="([^"]+)"/,'data-src="$1"'),t}formatComment(e,t=null){let s=window.getTemplate("response");s.id="response-"+e.id;let n=s.querySelector("summary");n.querySelector(".content").innerHTML=e.response,n.querySelector(".created").textContent=formatTimeAgo(e.created_at);let i=checkVoteStatus("response",e.id);e.content="response",s.querySelector(".footer").appendChild(formatVote(e,i)),console.log(e);let a=window.getTemplate("replyButton");a.id="reply-to-"+e.id,t&&(a.dataset.parent_id=t),a.dataset.action="reply",a.dataset.type=e.content,n.querySelector(".vote").prepend(a,n.querySelector(".vote").firstElementChild);let o=n.querySelector(".artist"),r=n.querySelector(".shop");if(console.log(e),e.artist?(e.artist.shop||r.remove(),[o.href,o.textContent,r.href,r.textContent]=[e.artist.url,e.artist.name,e.artist.shop.url,e.artist.shop.name]):(o.remove(),r.remove()),e.children.items.length>0){let t=window.getTemplate("responses");t.id="replies-to-"+e.id,t.querySelector("summary").textContent="See Responses {"+e.children.items.length+"}",e.children.items.forEach((s=>{t.appendChild(this.formatComment(s,e.id))})),s.appendChild(t)}return s}renderResponseCreate(){}saveCreatedResponse(){console.log("Saving create response"),console.log(this.replyModal.modal.id);const e=this.replyModal.modal;let t={user:window.auth.getUser(),item_id:e.dataset.id,response:e.querySelector(".ql-editor").innerHTML,content:e.dataset.type,action:"create"};e.dataset.parent_id&&(t.parent_id=e.dataset.parent_id),console.log(t),this.queue.addToQueue({type:"new_response",data:t})}showEmptyState(){const e=document.createElement("div");e.className="no-news",e.innerHTML="\n            <h3>Nothing here</h3>\n            <p>No updates here.</p>\n            <p>Add some gap fillers from the main favourites tab.</p>\n        ",this.grid.appendChild(e),this.grid.classList.add("empty"),this.a11y.announce("No favourites to show!")}hideEmptyState(){let e=this.grid.querySelector(".no-news");e&&e.remove()}handleError(e,t){console.error(`News error (${t}):`,e),window.jvbError&&window.jvbError.log(e,{component:"NewsManager",action:t}),window.jvbA11y&&window.jvbA11y.announce(`Error ${t}. ${e.message||"Please try again."}`)}};
\ No newline at end of file
+window.newsManager=class{constructor(){this.queue=window.jvbQueue,this.loading=window.jvbLoading,this.cache=window.jvbCache,this.a11y=window.jvbA11y,this.error=window.jvbError,this.activeTab="all",this.tabs=new window.jvbTabs(document.querySelector(".replace"),{news:()=>{this.activeTab="all",this.resetFilters(),this.loadItems(!0).then(()=>{})},mine:()=>{console.log("switching to mine tab"),this.activeTab="own",this.resetFilters(),this.filters.artist=window.auth.getUser(),this.loadItems(!0).then(()=>{})},watching:()=>{this.activeTab="watching",this.resetFilters(),this.filters.watched=!0,this.loadItems(!0).then(()=>{})}}),this.isLoading=!1,this.alreadyHandling=!1,this.template=new Map,this.endpoints={news:"news",vote:"news/vote"},this.resetFilters(),this.state={hasMore:!0,pages:1,items:0},this.initElements(),this.initEvents(),this.loadItems()}resetFilters(){this.filters={page:1,order:"DESC",orderby:"date",shop:null,type:null,artist:null,watched:!1}}initElements(){this.container=document.querySelector(".replace"),this.grid=this.container.querySelector(".item-grid"),this.addButton=this.container.querySelector(".add-item-btn"),this.addModal=new window.jvbModal(this.container.querySelector(".create-modal"),{render:this.renderModal.bind(this),open:this.addButton,content:"news",openMessage:"Opened modal to create a news post.",onSave:this.saveModal.bind(this)}),this.filterForm=this.container.querySelector("form"),this.dateRangeFilter=new window.jvbModal(this.container.querySelector("dialog.date-range"),{open:!1}),this.clearFilters=this.container.querySelector(".clear-filters"),this.replyModal=new window.jvbModal(this.container.querySelector(".create-response"),{open:!1,content:"response",openMessage:"Opened Response modal",onSave:this.saveCreatedResponse.bind(this)})}initEvents(){this.filterForm.addEventListener("change",e=>{let t=e.target.value;if(!e.target.closest(".date-range"))if("custom"===t)this.handleCustomDateRange();else{let s=e.target.name;s?this.filters[s]=t:this.resetFilters(),this.loadItems(!0)}}),document.addEventListener("click",e=>{if(e.target===this.clearFilters&&(this.filterForm.reset(),this.resetFilters(),this.loadItems(!0)),e.target.closest("button.reply")){let t=e.target.closest("button"),s=t.closest(".item").dataset.id,n="";"news"===t.dataset.type?n=t.closest(".item").querySelector(".item-info").innerHTML:(n=t.closest(".response").querySelector(".content").innerHTML,this.replyModal.modal.dataset.parent_id=t.id.replace("reply-to","")),this.replyModal.modal.dataset.id=s,this.replyModal.modal.dataset.type=t.dataset.type,this.replyModal.modal.querySelector(".original").innerHTML="<h5>Replying to:</h5>"+n,this.replyModal.handleOpen()}})}renderModal(){}handleCustomDateRange(){this.dateRangeFilter.handleOpen();let e=this.dateRangeFilter.modal.querySelector("input.date-start"),t=this.dateRangeFilter.modal.querySelector("input.date-end"),s=this.dateRangeFilter.modal.querySelector("select");this.dateRangeFilter.modal.querySelectorAll("input, select").forEach(n=>{n.addEventListener("change",i=>{n===e&&""!==t.value||n===t&&""!==e.value?(this.filters.dateFrom=e.value,this.filters.dateTo=t.value,this.dateRangeFilter.handleClose(),this.loadItems(!0)):n===s&&(this.filters.customDate=s.value,this.dateRangeFilter.handleClose(),this.loadItems(!0))})})}async saveModal(e){const t=new FormData(this.addModal.modal.querySelector("form"));t.append("user",window.auth.getUser()),this.queue.addToQueue({type:"new_news",data:t})}async loadItems(e=!0){if(!this.isLoading)try{this.isLoading=!0,this.loading.show(),e&&(this.filters.page=1,removeChildren(this.grid),this.grid.classList.remove("empty"));const t=this.buildFilters(),s=await this.cache.fetchWithCache(`${jvbSettings.api}${this.endpoints.news}?${t.toString()}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash")}},{context:"news",forceRefresh:!0});return this.renderItems(s.items||[],this.filters.page>1),s.pagination&&(this.state={hasMore:s.has_more,items:s.items,pages:s.pages}),s}catch(e){throw this.handleError(e,"loading news"),e}finally{this.isLoading=!1,this.loading.hide()}}buildFilters(){const e=JSON.parse(JSON.stringify(this.filters));let t={};for(var[s,n]of Object.entries(e))!1!==n&&null!==n&&(t[s]=n);return new URLSearchParams(t)}renderItems(e,t=!1){if(t||removeChildren(this.grid),0===e.length)return this.a11y.announceItems(0,t),void this.showEmptyState();const s=document.createDocumentFragment(),n=i=>{const a=Math.min(i+10,e.length);for(let t=i;t<a;t++){const n=e[t],i=this.createItemElement(n);s.appendChild(i)}a<e.length?requestAnimationFrame(()=>{n(a)}):(this.grid.appendChild(s),this.a11y.makeNavigable(this.grid.querySelectorAll(".item:not([data-keyboard-nav])")),this.a11y.announceItems(e.length,t,this.state.hasMore))};e.length>0?n(0):this.a11y.announceItems(0,t)}createItemElement(e){const t=window.getTemplate(`template-${this.activeTab}`);t.id=`news-${e.id}`,t.dataset.id=e.id;const[s]=t.getElementsByTagName("h3"),[n]=t.getElementsByClassName("published"),[i]=t.getElementsByClassName("artist"),[a]=t.getElementsByClassName("shop"),[o]=t.getElementsByClassName("tldr"),[r]=t.getElementsByClassName("item-info"),[l]=t.getElementsByClassName("image");[s.textContent,n.textContent,i.href,i.textContent,o.textContent,r.innerHTML]=[e.title,formatTimeAgo(e.date),e.artist.url,e.artist.name,e.tldr,e.post_content],e.shop?[a.href,a.innerHTML]=[e.shop.url,jvbSettings.icons.shop+e.shop.name]:a.hidden=!0;const[d]=t.getElementsByClassName("favourite-button");if("own"!==this.activeTab)[d.dataset.id,d.dataset.artist]=[e.id,e.artist.id],window.userFavourites.news?.includes(parseInt(e.id))?(removeChildren(d),d.append(getIcon("star-fi"))):(removeChildren(d),d.append(getIcon("star")));else{d.hidden=!0;const[s]=t.getElementsByClassName("select-checkbox"),[n]=t.getElementsByTagName("label");[s.id,s.value,n.for]=[`item-${e.id}`,e.id,`item-${e.id}`]}let h="";window.userVotes?.news?.has(e.id)&&(h=window.userVotes.news.get(e.id)),console.log(e),t.querySelector(".summary").appendChild(formatVote(e,h));let c=window.getTemplate("commentsButton");c.href=`#responses-to-${e.id}`,c.querySelector(".count").textContent=e.comments.items.length;let m=window.getTemplate("responses");m.id=`responses-to-${e.id}`,m.querySelector("summary").textContent+=" { "+e.comments.items.length+" }";let p=window.getTemplate("replyButton");return p.id="reply-to-"+e.id,p.dataset.type="news",p.dataset.action="reply",t.appendChild(p),e.comments.items.length>0&&e.comments.items.forEach(e=>{m.appendChild(this.formatComment(e))}),t.appendChild(m),t.querySelector(".vote").prepend(c,t.querySelector(".vote button")),e.image&&e.image.replace(/src="([^"]+)"/,'data-src="$1"'),t}formatComment(e,t=null){let s=window.getTemplate("response");s.id="response-"+e.id;let n=s.querySelector("summary");n.querySelector(".content").innerHTML=e.response,n.querySelector(".created").textContent=formatTimeAgo(e.created_at);let i=checkVoteStatus("response",e.id);e.content="response",s.querySelector(".footer").appendChild(formatVote(e,i)),console.log(e);let a=window.getTemplate("replyButton");a.id="reply-to-"+e.id,t&&(a.dataset.parent_id=t),a.dataset.action="reply",a.dataset.type=e.content,n.querySelector(".vote").prepend(a,n.querySelector(".vote").firstElementChild);let o=n.querySelector(".artist"),r=n.querySelector(".shop");if(console.log(e),e.artist?(e.artist.shop||r.remove(),[o.href,o.textContent,r.href,r.textContent]=[e.artist.url,e.artist.name,e.artist.shop.url,e.artist.shop.name]):(o.remove(),r.remove()),e.children.items.length>0){let t=window.getTemplate("responses");t.id="replies-to-"+e.id,t.querySelector("summary").textContent="See Responses {"+e.children.items.length+"}",e.children.items.forEach(s=>{t.appendChild(this.formatComment(s,e.id))}),s.appendChild(t)}return s}renderResponseCreate(){}saveCreatedResponse(){console.log("Saving create response"),console.log(this.replyModal.modal.id);const e=this.replyModal.modal;let t={user:window.auth.getUser(),item_id:e.dataset.id,response:e.querySelector(".ql-editor").innerHTML,content:e.dataset.type,action:"create"};e.dataset.parent_id&&(t.parent_id=e.dataset.parent_id),console.log(t),this.queue.addToQueue({type:"new_response",data:t})}showEmptyState(){const e=document.createElement("div");e.className="no-news",e.innerHTML="\n            <h3>Nothing here</h3>\n            <p>No updates here.</p>\n            <p>Add some gap fillers from the main favourites tab.</p>\n        ",this.grid.appendChild(e),this.grid.classList.add("empty"),this.a11y.announce("No favourites to show!")}hideEmptyState(){let e=this.grid.querySelector(".no-news");e&&e.remove()}handleError(e,t){console.error(`News error (${t}):`,e),window.jvbError&&window.jvbError.log(e,{component:"NewsManager",action:t}),window.jvbA11y&&window.jvbA11y.announce(`Error ${t}. ${e.message||"Please try again."}`)}};
\ No newline at end of file
diff --git a/assets/js/min/notificationManager.min.js b/assets/js/min/notificationManager.min.js
index a678ed9..fab6c20 100644
--- a/assets/js/min/notificationManager.min.js
+++ b/assets/js/min/notificationManager.min.js
@@ -1 +1 @@
-(()=>{class t{constructor(){this.resetFilters(),this.activeTab="all",this.isLoading=!1,this.loading=window.jvbLoading,this.container=document.querySelector(".container"),this.grid=this.container.querySelector(".notifications-list"),this.tabs=new window.jvbTabs(this.container,{all:()=>{this.resetFilters(),this.activeTab="all",this.loadNotifications()},favourite:()=>{this.resetFilters(),this.activeTab="favourite",this.filters.content="favourite",this.loadNotifications()},artist:()=>{this.resetFilters(),this.activeTab="artist",this.filters.content="artist",this.loadNotifications()},shop:()=>{this.resetFilters(),this.activeTab="shop",this.filters.content="shop",this.loadNotifications()},event:()=>{this.resetFilters(),this.activeTab="favourite",this.filters.content="favourite",this.loadNotifications()},news:()=>{this.resetFilters(),this.activeTab="news",this.filters.content="news",this.loadNotifications()},system:()=>{this.resetFilters(),this.activeTab="system",this.filters.content="system",this.loadNotifications()}}),this.loadNotifications()}resetFilters(){this.filters={content:"all",date:""},this.hasMore=!0}async loadNotifications(t=!0){if(!this.isLoading&&this.hasMore)try{this.isLoading=!0,this.loading.show(),t&&(this.filters.page=1,this.grid.classList.remove("empty"));const i=this.buildFilters();console.log(this.filters),console.log("Reset? ",this.reset);const s=await this.cache.fetchWithCache(`${jvbSettings.api}notifications?${i.toString()}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":jvbAdmin.nonce}},{context:"admin",forceRefresh:!0});return console.log(s),s}catch(t){throw this.handleError(t,"loading notifications"),t}finally{this.isLoading=!1,this.loading.hide()}}buildFilters(){const t=JSON.parse(JSON.stringify(this.filters));let i={};for(var[s,e]of Object.entries(t))!1!==e&&null!==e&&(i[s]=e);return i.context="admin",i.user=window.auth.getUser(),new URLSearchParams(i)}}document.addEventListener("DOMContentLoaded",(()=>{window.notificationsDash=new t,console.log(jvbSettings)}))})();
\ No newline at end of file
+(()=>{class t{constructor(){this.resetFilters(),this.activeTab="all",this.isLoading=!1,this.loading=window.jvbLoading,this.container=document.querySelector(".container"),this.grid=this.container.querySelector(".notifications-list"),this.tabs=new window.jvbTabs(this.container,{all:()=>{this.resetFilters(),this.activeTab="all",this.loadNotifications()},favourite:()=>{this.resetFilters(),this.activeTab="favourite",this.filters.content="favourite",this.loadNotifications()},artist:()=>{this.resetFilters(),this.activeTab="artist",this.filters.content="artist",this.loadNotifications()},shop:()=>{this.resetFilters(),this.activeTab="shop",this.filters.content="shop",this.loadNotifications()},event:()=>{this.resetFilters(),this.activeTab="favourite",this.filters.content="favourite",this.loadNotifications()},news:()=>{this.resetFilters(),this.activeTab="news",this.filters.content="news",this.loadNotifications()},system:()=>{this.resetFilters(),this.activeTab="system",this.filters.content="system",this.loadNotifications()}}),this.loadNotifications()}resetFilters(){this.filters={content:"all",date:""},this.hasMore=!0}async loadNotifications(t=!0){if(!this.isLoading&&this.hasMore)try{this.isLoading=!0,this.loading.show(),t&&(this.filters.page=1,this.grid.classList.remove("empty"));const i=this.buildFilters();console.log(this.filters),console.log("Reset? ",this.reset);const s=await this.cache.fetchWithCache(`${jvbSettings.api}notifications?${i.toString()}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":jvbAdmin.nonce}},{context:"admin",forceRefresh:!0});return console.log(s),s}catch(t){throw this.handleError(t,"loading notifications"),t}finally{this.isLoading=!1,this.loading.hide()}}buildFilters(){const t=JSON.parse(JSON.stringify(this.filters));let i={};for(var[s,e]of Object.entries(t))!1!==e&&null!==e&&(i[s]=e);return i.context="admin",i.user=window.auth.getUser(),new URLSearchParams(i)}}document.addEventListener("DOMContentLoaded",()=>{window.notificationsDash=new t,console.log(jvbSettings)})})();
\ No newline at end of file
diff --git a/assets/js/min/notifications.min.js b/assets/js/min/notifications.min.js
index f41cd0a..2876308 100644
--- a/assets/js/min/notifications.min.js
+++ b/assets/js/min/notifications.min.js
@@ -1 +1 @@
-(()=>{class t{constructor(t={}){this.popupQueue=[],this.isLoading=!1,this.cache=window.jvbCache,this.isProcessingQueue=!1,this.options={maxVisibleNotifications:5,displayDuration:{high:7e3,medium:5e3,low:3e3},position:"bottom-right",pollingInterval:6e4,...t},this.button=document.querySelector(".toggle.notifications"),this.submenu=document.querySelector(".notifications-preview"),this.toasts=document.querySelector(".toasts"),this.notificationsLoaded=!1,this.pollTimer=null,this.lastCheck=null,this.button&&this.submenu&&this.init(),this.clickListeners=this.checkClicks.bind(this),this.updateListeners()}init(){this.submenu.addEventListener("click",(t=>{const e=t.target.closest(".mark-read");if(e){const t=e.closest(".notification-preview");t&&this.markAsRead(t.dataset.id)}})),this.loadNotifications(),this.initializePolling()}checkClicks(t){if(t.target.closest(".close-toast")){let e=t.target.closest(".toast");e.classList.add("hiding"),setTimeout((()=>{e.remove(),this.updateListeners()}),300)}}updateListeners(){this.toasts.addEventListener("click",this.clickListeners)}toggleDropdown(){this.notificationsLoaded||this.loadNotifications()}async loadNotifications(t=!1){if(!this.isLoading)try{this.isLoading=!0;const t=new URLSearchParams({user:window.auth.getUser(),status:"unread",limit:5}),e=await this.cache.fetchWithCache(`${jvbSettings.api}notifications?${t.toString()}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("notifications")}},{context:"notifications",forceRefresh:!0});this.renderPreviewNotifications(e.notifications),this.updateUnreadCount(e.total),this.notificationsLoaded=!0,this.lastCheck=(new Date).toUTCString()}catch(t){console.error("Error loading notifications:",t),this.renderErrorState(t.message)}}renderErrorState(t){const e=this.submenu.querySelector("#view-all");this.submenu.querySelectorAll("li:not(#view-all)").forEach((t=>t.remove()));const i=document.createElement("li");i.className="error-state",i.innerHTML=`\n        <p>${t}</p>\n        <button onclick="window.jvbNotifications.loadNotifications()">\n            Try Again\n        </button>\n    `,this.submenu.insertBefore(i,e)}renderPreviewNotifications(t){this.submenu.querySelector("#view-all");this.submenu.querySelectorAll("li:not(#view-all)").forEach((t=>t.remove())),t.forEach((t=>{let e=window.getTemplate("notificationItem");e.classList.add(t.status,`priority-${t.priority}`),e.dataset.id=t.id,e.prepend(getIcon(t.icon));let i=e.querySelector("p"),o=e.querySelector("time");[i.textContent,o.datetime,o.textContent]=[t.message,new Date(t.created_at).toISOString(),formatTimeAgo(t.created_at)];let s=window.getTemplate("notificationActions"),n=s.querySelector("button");t.actions.length>0&&(t.actions.forEach((e=>{let i=n.cloneNode(!0);e.primary&&i.classList.add("primary"),[i.dataset.id,i.dataset.action,i.textContent]=[t.id,e.label.toLowerCase(),e.label],s.append(i)})),n.remove()),e.append(s),this.submenu.prepend(e)})),0===t.length&&this.submenu.prepend(window.getTemplate("emptyNotification"))}queuePopupNotification(t){this.popupQueue.push(t),this.processPopupQueue()}async processPopupQueue(){if(!this.isProcessingQueue&&0!==this.popupQueue.length){for(this.isProcessingQueue=!0;this.popupQueue.length>0;){const t=this.popupQueue.shift();await this.showToast(t.message,t.type,t.actions),this.popupQueue.length>0&&await new Promise((t=>setTimeout(t,300)))}this.isProcessingQueue=!1}}showToast(t,e="success",i={}){let o=window.getTemplate("notificationPopup");if(o.classList.add(e),o.querySelector("p").textContent=t,Object.entries(i).length>0){let t=window.getTemplate("notificationActions"),e=i.querySelector("button");notification.actions.forEach((i=>{let o=e.cloneNode(!0);i.primary&&o.classList.add("primary"),[o.dataset.action,o.textContent]=[i.label.toLowerCase(),i.label],t.prepend(o)}))}this.toasts.append(o),setTimeout((()=>{o.classList.add("show")}),10),setTimeout((()=>{o.classList.add("hiding"),setTimeout((()=>{o.remove()}),300)}),3e3)}createNotificationElement(t){this.showToast(t.message),this.renderPreviewNotifications([t])}removePopupNotification(t){t.classList.remove("show"),setTimeout((()=>{t.remove()}),300)}updateUnreadCount(t){let e=this.button.querySelector("span");this.button.classList.remove("has"),[e.textContent,e.ariaLabel]=["","Notifications"],t&&!isNaN(t)&&(t=parseInt(t,10))>0&&(this.button.classList.add("has"),[e.textContent,e.ariaLabel]=[t,t+" unread notification"+(t>1?"s":"")])}async markAsRead(t){try{const e=this.submenu.querySelector(`[data-id="${t}"]`);if(!e)return;e.classList.add("slide-out");const i=await fetch(`${jvbSettings.api}notifications`,{method:"POST",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash")},body:{notification:t,user:window.auth.getUser()}});if(!i.ok)throw new Error(notificationSettings.strings.error);const o=await i.json();o.success&&(setTimeout((()=>{e.remove();if(0===this.submenu.querySelectorAll(".notification-preview").length){const t=this.submenu.querySelector("#view-all"),e=document.createElement("li");e.className="empty-state fade-in",e.textContent=notificationSettings.strings.noNotifications,this.submenu.insertBefore(e,t),requestAnimationFrame((()=>{e.classList.remove("fade-in")}))}}),300),this.updateUnreadCount(o.total))}catch(t){console.error("Error marking notification as read:",t)}}initializePolling(){this.pollTimer=setInterval((()=>{this.checkNotifications()}),this.options.pollingInterval),document.addEventListener("visibilitychange",(()=>{document.hidden||this.checkNotifications()}))}async checkNotifications(){try{const t=new URLSearchParams({user:window.auth.getUser(),status:"unread"}),e=await fetch(`${jvbSettings.api}notifications?${t.toString()}`,{headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash"),"If-Modified-Since":this.lastCheck}});if(!e.ok)return;(await e.json()).has_new&&await this.loadNotifications(!0)}catch(t){console.error("Check notifications error:",t)}}destroy(){this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null)}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((e=>{"auth-loaded"===e&&(window.jvbNotifications=new t({position:"bottom-right",maxVisibleNotifications:5,displayDuration:5e3}))}))})),window.addNotification=function(t,e="info"){window.jvbNotifications.showToast(t,e)}})();
\ No newline at end of file
+(()=>{class t{constructor(t={}){this.popupQueue=[],this.isLoading=!1,this.cache=window.jvbCache,this.isProcessingQueue=!1,this.options={maxVisibleNotifications:5,displayDuration:{high:7e3,medium:5e3,low:3e3},position:"bottom-right",pollingInterval:6e4,...t},this.button=document.querySelector(".toggle.notifications"),this.submenu=document.querySelector(".notifications-preview"),this.toasts=document.querySelector(".toasts"),this.notificationsLoaded=!1,this.pollTimer=null,this.lastCheck=null,this.button&&this.submenu&&this.init(),this.clickListeners=this.checkClicks.bind(this),this.updateListeners()}init(){this.submenu.addEventListener("click",t=>{const e=t.target.closest(".mark-read");if(e){const t=e.closest(".notification-preview");t&&this.markAsRead(t.dataset.id)}}),this.loadNotifications(),this.initializePolling()}checkClicks(t){if(t.target.closest(".close-toast")){let e=t.target.closest(".toast");e.classList.add("hiding"),setTimeout(()=>{e.remove(),this.updateListeners()},300)}}updateListeners(){this.toasts.addEventListener("click",this.clickListeners)}toggleDropdown(){this.notificationsLoaded||this.loadNotifications()}async loadNotifications(t=!1){if(!this.isLoading)try{this.isLoading=!0;const t=new URLSearchParams({user:window.auth.getUser(),status:"unread",limit:5}),e=await this.cache.fetchWithCache(`${jvbSettings.api}notifications?${t.toString()}`,{method:"GET",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("notifications")}},{context:"notifications",forceRefresh:!0});this.renderPreviewNotifications(e.notifications),this.updateUnreadCount(e.total),this.notificationsLoaded=!0,this.lastCheck=(new Date).toUTCString()}catch(t){console.error("Error loading notifications:",t),this.renderErrorState(t.message)}}renderErrorState(t){const e=this.submenu.querySelector("#view-all");this.submenu.querySelectorAll("li:not(#view-all)").forEach(t=>t.remove());const i=document.createElement("li");i.className="error-state",i.innerHTML=`\n        <p>${t}</p>\n        <button onclick="window.jvbNotifications.loadNotifications()">\n            Try Again\n        </button>\n    `,this.submenu.insertBefore(i,e)}renderPreviewNotifications(t){this.submenu.querySelector("#view-all");this.submenu.querySelectorAll("li:not(#view-all)").forEach(t=>t.remove()),t.forEach(t=>{let e=window.getTemplate("notificationItem");e.classList.add(t.status,`priority-${t.priority}`),e.dataset.id=t.id,e.prepend(getIcon(t.icon));let i=e.querySelector("p"),o=e.querySelector("time");[i.textContent,o.datetime,o.textContent]=[t.message,new Date(t.created_at).toISOString(),formatTimeAgo(t.created_at)];let s=window.getTemplate("notificationActions"),n=s.querySelector("button");t.actions.length>0&&(t.actions.forEach(e=>{let i=n.cloneNode(!0);e.primary&&i.classList.add("primary"),[i.dataset.id,i.dataset.action,i.textContent]=[t.id,e.label.toLowerCase(),e.label],s.append(i)}),n.remove()),e.append(s),this.submenu.prepend(e)}),0===t.length&&this.submenu.prepend(window.getTemplate("emptyNotification"))}queuePopupNotification(t){this.popupQueue.push(t),this.processPopupQueue()}async processPopupQueue(){if(!this.isProcessingQueue&&0!==this.popupQueue.length){for(this.isProcessingQueue=!0;this.popupQueue.length>0;){const t=this.popupQueue.shift();await this.showToast(t.message,t.type,t.actions),this.popupQueue.length>0&&await new Promise(t=>setTimeout(t,300))}this.isProcessingQueue=!1}}showToast(t,e="success",i={}){let o=window.getTemplate("notificationPopup");if(o.classList.add(e),o.querySelector("p").textContent=t,Object.entries(i).length>0){let t=window.getTemplate("notificationActions"),e=i.querySelector("button");notification.actions.forEach(i=>{let o=e.cloneNode(!0);i.primary&&o.classList.add("primary"),[o.dataset.action,o.textContent]=[i.label.toLowerCase(),i.label],t.prepend(o)})}this.toasts.append(o),setTimeout(()=>{o.classList.add("show")},10),setTimeout(()=>{o.classList.add("hiding"),setTimeout(()=>{o.remove()},300)},3e3)}createNotificationElement(t){this.showToast(t.message),this.renderPreviewNotifications([t])}removePopupNotification(t){t.classList.remove("show"),setTimeout(()=>{t.remove()},300)}updateUnreadCount(t){let e=this.button.querySelector("span");this.button.classList.remove("has"),[e.textContent,e.ariaLabel]=["","Notifications"],t&&!isNaN(t)&&(t=parseInt(t,10))>0&&(this.button.classList.add("has"),[e.textContent,e.ariaLabel]=[t,t+" unread notification"+(t>1?"s":"")])}async markAsRead(t){try{const e=this.submenu.querySelector(`[data-id="${t}"]`);if(!e)return;e.classList.add("slide-out");const i=await fetch(`${jvbSettings.api}notifications`,{method:"POST",headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash")},body:{notification:t,user:window.auth.getUser()}});if(!i.ok)throw new Error(notificationSettings.strings.error);const o=await i.json();o.success&&(setTimeout(()=>{e.remove();if(0===this.submenu.querySelectorAll(".notification-preview").length){const t=this.submenu.querySelector("#view-all"),e=document.createElement("li");e.className="empty-state fade-in",e.textContent=notificationSettings.strings.noNotifications,this.submenu.insertBefore(e,t),requestAnimationFrame(()=>{e.classList.remove("fade-in")})}},300),this.updateUnreadCount(o.total))}catch(t){console.error("Error marking notification as read:",t)}}initializePolling(){this.pollTimer=setInterval(()=>{this.checkNotifications()},this.options.pollingInterval),document.addEventListener("visibilitychange",()=>{document.hidden||this.checkNotifications()})}async checkNotifications(){try{const t=new URLSearchParams({user:window.auth.getUser(),status:"unread"}),e=await fetch(`${jvbSettings.api}notifications?${t.toString()}`,{headers:{"X-WP-Nonce":window.auth.getNonce(),"X-Action-Nonce":window.auth.getNonce("dash"),"If-Modified-Since":this.lastCheck}});if(!e.ok)return;(await e.json()).has_new&&await this.loadNotifications(!0)}catch(t){console.error("Check notifications error:",t)}}destroy(){this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null)}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(e=>{"auth-loaded"===e&&(window.jvbNotifications=new t({position:"bottom-right",maxVisibleNotifications:5,displayDuration:5e3}))})}),window.addNotification=function(t,e="info"){window.jvbNotifications.showToast(t,e)}})();
\ No newline at end of file
diff --git a/assets/js/min/page-nav.min.js b/assets/js/min/page-nav.min.js
index 1d087be..930a604 100644
--- a/assets/js/min/page-nav.min.js
+++ b/assets/js/min/page-nav.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.initElements(),this.initListeners()}initElements(){this.selectors={nav:"nav.on-this-page",toggle:"button.toggle",icon:"button.toggle .icon"},this.ui=window.uiFromSelectors(this.selectors);let e=this.ui.nav.querySelectorAll("li a");this.ui.items={},this.ui.sections={},this.selectors.items=[];for(let t of e){let e=t.getAttribute("href");this.ui.items[e.replace("#","")]=t.closest("li"),this.selectors.items.push(e),this.ui.sections[e.replace("#","")]=document.querySelector(e)}this.selectors.items=this.selectors.items.join(",")}initListeners(){this.ui.toggle.addEventListener("click",(()=>{let e=this.ui.nav.classList().contains("open")?"icon-x-square":"icon-plus-square";console.log("Changing icon to: "+e),this.ui.icon.className="icon "+e})),this.observer=new IntersectionObserver((e=>{e.forEach((e=>{if(e.isIntersecting){let t=Object.keys(this.ui.items).indexOf(e.target.id),s=0;for(let e of Object.values(this.ui.items))e.classList.toggle("active",t===s),s++}}))}),{rootMargin:"-50% 0px -50% 0px",threshold:0});for(let e of Object.values(this.ui.sections))console.log("Observing section: ",e),this.observer.observe(e)}}document.addEventListener("DOMContentLoaded",(()=>{document.querySelector("nav.on-this-page")&&(window.onThisPage=new e)}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.initElements(),this.initListeners()}initElements(){this.selectors={nav:"nav.on-this-page",toggle:"button.toggle",icon:"button.toggle .icon"},this.ui=window.uiFromSelectors(this.selectors);let e=this.ui.nav.querySelectorAll("li a");this.ui.items={},this.ui.sections={},this.selectors.items=[];for(let t of e){let e=t.getAttribute("href");this.ui.items[e.replace("#","")]=t.closest("li"),this.selectors.items.push(e),this.ui.sections[e.replace("#","")]=document.querySelector(e)}this.selectors.items=this.selectors.items.join(",")}initListeners(){this.ui.toggle.addEventListener("click",()=>{let e=this.ui.nav.classList().contains("open")?"icon-x-square":"icon-plus-square";console.log("Changing icon to: "+e),this.ui.icon.className="icon "+e}),this.observer=new IntersectionObserver(e=>{e.forEach(e=>{if(e.isIntersecting){let t=Object.keys(this.ui.items).indexOf(e.target.id),s=0;for(let e of Object.values(this.ui.items))e.classList.toggle("active",t===s),s++}})},{rootMargin:"-50% 0px -50% 0px",threshold:0});for(let e of Object.values(this.ui.sections))console.log("Observing section: ",e),this.observer.observe(e)}}document.addEventListener("DOMContentLoaded",()=>{document.querySelector("nav.on-this-page")&&(window.onThisPage=new e)})})();
\ No newline at end of file
diff --git a/assets/js/min/populate.min.js b/assets/js/min/populate.min.js
index c4d0878..f8c99f2 100644
--- a/assets/js/min/populate.min.js
+++ b/assets/js/min/populate.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.templates=window.jvbTemplates,this.formHelper=window.jvbForm,this.defineTemplates(),this.data=null,this.form=null}populate(e,t={}){if(this.data=t,this.mergeRootData(),this.form=e,this.formHelper||(this.formHelper=window.jvbForm),this.formHelper){if(Object.hasOwn(this.data,"fields")&&0!==Object.keys(this.data.fields).length)for(let[t,i]of Object.entries(this.data.fields)){let a=e.querySelector(`[data-field="${t}"]`);a&&this.populateField(a,t,i)}}else requestAnimationFrame((()=>{this.populate(e,t)}))}mergeRootData(){["status","date","modified"].forEach((e=>{this.data.fields[`post_${e}`]=this.data[e]}))}populateField(e,t,i){let a=this.formHelper.getFieldType(e);if(!a||this.isEmptyValue(t))return;const l={repeater:this.populateRepeater.bind(this),"tag-list":this.populateTagList.bind(this),group:this.populateGroup.bind(this),location:this.populateLocation.bind(this),selector:this.populateTaxonomy.bind(this),user:this.populateUser.bind(this),upload:this.populateUpload.bind(this),gallery:this.populateUpload.bind(this),image:this.populateUpload.bind(this),set:this.populateMultiValue.bind(this),checkbox:this.populateMultiValue.bind(this),select:this.populateSingleValue.bind(this),radio:this.populateSingleValue.bind(this),"true-false":this.populateBoolean.bind(this),"toggle-text":this.populateBoolean.bind(this),date:this.populateDate.bind(this),time:this.populateDate.bind(this),datetime:this.populateDate.bind(this),number:this.populateNumber.bind(this),textarea:this.populateTextarea.bind(this),quantity:this.populateNumber.bind(this)};Object.hasOwn(l,a)?l[a](e,t,i):this.populateText(e,t,i)}populateRepeater(e,t,i){const a=e.querySelector(".repeater-items");let l=e.querySelector("template")?.className??!1;if(a&&l){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=[]}i&&!Array.isArray(i)&&"object"==typeof i&&(i=Object.values(i)),i&&Array.isArray(i)&&0!==i.length?(window.removeChildren(a),i.forEach(((t,i)=>{const o={...t,index:i,repeater:e},r=this.templates.create(l,o);if(!r)return;a.append(r);const s=this.formHelper.getForm(r);s&&this.formHelper.initializeFields(r,s);for(let[e,i]of Object.entries(t)){let t=r.querySelector(`[data-field="${e}"]`);t&&this.populateField(t,e,i)}}))):window.removeChildren(a)}}populateTagList(e,t,i){const a=e.querySelector(".tag-items");let l=e.querySelector("template")?.className??!1;if(a&&l){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=[]}i&&!Array.isArray(i)&&"object"==typeof i&&(i=Object.values(i)),i&&Array.isArray(i)&&0!==i.length?(window.removeChildren(a),i.forEach(((i,o)=>{const r=this.templates.create(l,{label:this.getTagLabel(i,e.dataset.tagFormat??"first_field"),fieldName:t,...i});r&&(r.querySelectorAll('input[type="hidden"]').forEach((e=>{const t=e.dataset.field;t&&void 0!==i[t]&&(e.value=i[t])})),a.append(r))}))):window.removeChildren(a)}}getTagLabel(e,t){const i=Object.values(e).filter((e=>!this.isEmptyValue(e)));switch(t){case"first_field":return i[0]??"New Item";case"all_fields":return i.join(", ")||"New Item";default:if(t.includes("{")){let i=t;for(const[t,a]of Object.entries(e))i=i.replace(`{${t}}`,a);return i}return e[t]??i[0]??"New Item"}}populateGroup(e,t,i){if(i&&"object"==typeof i){console.log(i,"Populating Group Value: ");for(let[t,a]of Object.entries(i)){let i=e.querySelector(`[data-field="${t}"]`);i&&this.populateField(i,t,a)}}}populateLocation(e,t,i){["address","lat","lng","street","city","province","postal_code","country"].forEach((t=>{if(Object.hasOwn(i,t)){let a=e.querySelector(`[data-location-field="${t}"]`);a&&(a.value=String(i[t]||""))}}))}populateTaxonomy(e,t,i){let a=this.splitIDs(i);const l=e.querySelector(`input[type="hidden"][name="${t}"]`);l&&(l.value=a.join(","),window.jvbSelector&&requestAnimationFrame((()=>{window.jvbSelector.updateFieldFromInput(l)})))}populateUser(e,t,i){this.populateTaxonomy(e,t,i)}populateUpload(e,t,i){if(e.dataset.subtype&&"timeline"===e.dataset.subtype)return void this.populateTimelineGallery(e,t,i);const a=this.splitIDs(i);if(0===a.length)return;const l=e.querySelector('input[type="hidden"]');l&&(l.value=a.join(","));const o=e.querySelector(".item-grid");e.querySelector(".progress")?.remove(),o&&(window.removeChildren(o),a.forEach((e=>{let t=this.data.images[e]??{};t.field={config:{showMeta:!0}},t.id=e,o.append(this.templates.create("uploadItem",t));let i=o.children,a=Array.from(i).filter((t=>t.dataset.id===`${e}`));a=a[0]??!1,a&&this.populateUploadMeta(a,null,e)})))}populateUploadMeta(e,t,i){const a=e.querySelector('[data-field="image_data"]');if(!a)return;let l=this.data.images[i]??!1;if(!l)return;a.dataset.attachmentId=l.id,a.setAttribute("data-ignore","");const o=["image-title","image-alt-text","image-caption"];for(const e of o){const t=a.querySelector(`[data-field="image_data|${e}"] input, [data-field="image_data|${e}"] textarea`);t&&""!==l[e]&&(t.value=window.decodeHTMLEntities(l[e]))}}populateTimelineGallery(e,t,i){if(!i||!Array.isArray(i)||0===i.length)return;let a=e.querySelector(".item-grid");if(a){window.removeChildren(a),e.querySelector(".progress")?.remove();for(let e of i){let t=this.templates.create("timelineItem",e);t&&a.append(t)}}}populateMultiValue(e,t,i){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=i.split(",").map((e=>e.trim()))}Array.isArray(i)||(i=[String(i)]);let a=e.querySelector(`select[name="${t}"]`);if(a&&a.multiple)for(let e of a.options)e.selected=i.includes(e.value);else e.querySelectorAll(`input[type="checkbox"][name="${t}[]"], input[type="checkbox"][name="${t}"]`).forEach((e=>{e.checked=i.includes(e.value)}))}populateSingleValue(e,t,i){i=String(i||"");let a=e.querySelector(`select[name="${t}"]`);if(a)return void(a.value=i);let l=e.querySelector(`input[type="radio"][value="${i}"], input[type="checkbox"][value="${i}"]`)||e.querySelector(`[name="${t}"][value="${i}"]`);l&&(l.checked=!0)}populateBoolean(e,t,i){const a=e.querySelector(`[name="${t}"], input[type="checkbox"]`);a&&(a.checked=Boolean(i))}populateDate(e,t,i){const a=e.querySelector(`[name="${t}"], input`);if(a){"object"==typeof i&&Object.hasOwn(i,"date")&&(i=i.date);try{const e=new Date(i);if(!isNaN(e.getTime()))switch(a.type){case"date":a.value=e.toISOString().split("T")[0];break;case"time":a.value=e.toTimeString().slice(0,5);break;case"datetime-local":a.value=e.toISOString().slice(0,16);break;default:a.value=i}}catch(e){a.value=i}}}populateNumber(e,t,i){const a=e.querySelector(`[name="${t}"], input[type="number"]`);a&&(a.value=Number(i)||0)}populateTextarea(e,t,i){let a=e.querySelector("textarea[data-editor], textarea");if(this.populateText(e,t,i),a?.dataset.editor){const t=e.querySelector(".ql-editor");t?t.innerHTML=i:a.dispatchEvent(new Event("change",{bubbles:!0}))}}populateText(e,t,i){let a=e.querySelector(`[name="${t}"]`)||e.querySelector("textarea[data-editor]")||e.querySelector('input:not([type="hidden"]):not([type="file"]), textarea, select');a&&(a.value=window.decodeHTMLEntities(i??""))}getFormHelper(){window.requestAnimationFrame((()=>{this.formHelper=window.jvbForm}))}splitIDs(e){return String(e).split(",").map((e=>parseInt(e.trim()))).filter((e=>!isNaN(e)&&e>0))}isEmptyValue(e){return null==e||""===e||(!(!Array.isArray(e)||0!==e.length)||"object"==typeof e&&0===Object.keys(e).length)}defineTemplates(){const e=this.templates,t=this;e.define("timelineItem",{refs:{select:'[name="select-item"]',video:"video",file:".select-item span",img:"img",details:'[data-field="image_data"] details',imgAlt:'[data-field="image-alt-text"]',imgTitle:'[data-field="image-title"]',imgDesc:'[data-field="image-caption"]'},manyRefs:{fields:".field"},setup({el:e,refs:i,manyRefs:a,data:l}){if(e.dataset.itemId=l.id,i.select){let e=i.select.closest(".preview");window.prefixInput(i.select,`${l.id}-`,e)}i.video&&i.video.remove(),i.file&&i.file.remove();let o=t.data.images[l.post_thumbnail]??!1;if(i.img&&o&&(i.img.src=o.medium||o.small||o.large||"",i.img.title=o.large.split("/").pop()??"",i.img.alt=o["image-alt-text"]??""),i.details){let e=t.data.images[l.post_thumbnail];i.details.setAttribute("data-ignore",""),i.details.dataset.attachmentId=l.post_thumbnail;let a=i.imgAlt.querySelector("input"),o=i.imgTitle.querySelector("input"),r=i.imgDesc.querySelector("textarea");window.prefixInput(a,`[${l.post_thumbnail}]`,i.imgAlt,!1,!0),window.prefixInput(o,`[${l.post_thumbnail}]`,i.imgTitle,!1,!0),window.prefixInput(r,`[${l.post_thumbnail}]`,i.imgDesc,!1,!0),Object.hasOwn(e,"image-alt-text")&&i.imgAlt&&(a.value=window.decodeHTMLEntities(e["image-alt-text"])),(Object.hasOwn(e,"image-title")||Object.hasOwn(l,"file"))&&i.imgTitle&&(o.value=window.decodeHTMLEntities(e["image-title"]||l.file.name)),Object.hasOwn(e,"image-caption")&&i.imgDesc&&(r.value=window.decodeHTMLEntities(e["image-caption"]))}if(a.fields)for(let e of a.fields){if(e.closest("[data-ignore]"))continue;if("group"===e.dataset.fieldType)continue;if("post_thumbnail"===e.dataset.field){e.remove();continue}let i=e.dataset.field;const a=e.querySelector('input:not([type="file"]), textarea, select');a&&window.prefixInput(a,`[${l.id}]`,e,!1,!0);let o=l[i]??"";t.isEmptyValue(o)||t.populateField(e,i,o)}}})}}document.addEventListener("DOMContentLoaded",(function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbPopulate=new e)}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.templates=window.jvbTemplates,this.formHelper=window.jvbForm,this.defineTemplates(),this.data=null,this.form=null}populate(e,t={}){if(this.data=t,this.mergeRootData(),this.form=e,this.formHelper||(this.formHelper=window.jvbForm),this.formHelper){if(Object.hasOwn(this.data,"fields")&&0!==Object.keys(this.data.fields).length)for(let[t,i]of Object.entries(this.data.fields)){let a=e.querySelector(`[data-field="${t}"]`);a&&this.populateField(a,t,i)}}else requestAnimationFrame(()=>{this.populate(e,t)})}mergeRootData(){["status","date","modified"].forEach(e=>{this.data.fields[`post_${e}`]=this.data[e]})}populateField(e,t,i){let a=this.formHelper.getFieldType(e);if(!a||this.isEmptyValue(t))return;const l={repeater:this.populateRepeater.bind(this),"tag-list":this.populateTagList.bind(this),group:this.populateGroup.bind(this),location:this.populateLocation.bind(this),selector:this.populateTaxonomy.bind(this),user:this.populateUser.bind(this),upload:this.populateUpload.bind(this),gallery:this.populateUpload.bind(this),image:this.populateUpload.bind(this),set:this.populateMultiValue.bind(this),checkbox:this.populateMultiValue.bind(this),select:this.populateSingleValue.bind(this),radio:this.populateSingleValue.bind(this),"true-false":this.populateBoolean.bind(this),"toggle-text":this.populateBoolean.bind(this),date:this.populateDate.bind(this),time:this.populateDate.bind(this),datetime:this.populateDate.bind(this),number:this.populateNumber.bind(this),textarea:this.populateTextarea.bind(this),quantity:this.populateNumber.bind(this)};Object.hasOwn(l,a)?l[a](e,t,i):this.populateText(e,t,i)}populateRepeater(e,t,i){const a=e.querySelector(".repeater-items");let l=e.querySelector("template")?.className??!1;if(a&&l){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=[]}i&&!Array.isArray(i)&&"object"==typeof i&&(i=Object.values(i)),i&&Array.isArray(i)&&0!==i.length?(window.removeChildren(a),i.forEach((t,i)=>{const o={...t,index:i,repeater:e},r=this.templates.create(l,o);if(!r)return;a.append(r);const s=this.formHelper.getForm(r);s&&this.formHelper.initializeFields(r,s);for(let[e,i]of Object.entries(t)){let t=r.querySelector(`[data-field="${e}"]`);t&&this.populateField(t,e,i)}})):window.removeChildren(a)}}populateTagList(e,t,i){const a=e.querySelector(".tag-items");let l=e.querySelector("template")?.className??!1;if(a&&l){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=[]}i&&!Array.isArray(i)&&"object"==typeof i&&(i=Object.values(i)),i&&Array.isArray(i)&&0!==i.length?(window.removeChildren(a),i.forEach((i,o)=>{const r=this.templates.create(l,{label:this.getTagLabel(i,e.dataset.tagFormat??"first_field"),fieldName:t,...i});r&&(r.querySelectorAll('input[type="hidden"]').forEach(e=>{const t=e.dataset.field;t&&void 0!==i[t]&&(e.value=i[t])}),a.append(r))})):window.removeChildren(a)}}getTagLabel(e,t){const i=Object.values(e).filter(e=>!this.isEmptyValue(e));switch(t){case"first_field":return i[0]??"New Item";case"all_fields":return i.join(", ")||"New Item";default:if(t.includes("{")){let i=t;for(const[t,a]of Object.entries(e))i=i.replace(`{${t}}`,a);return i}return e[t]??i[0]??"New Item"}}populateGroup(e,t,i){if(i&&"object"==typeof i){console.log(i,"Populating Group Value: ");for(let[t,a]of Object.entries(i)){let i=e.querySelector(`[data-field="${t}"]`);i&&this.populateField(i,t,a)}}}populateLocation(e,t,i){["address","lat","lng","street","city","province","postal_code","country"].forEach(t=>{if(Object.hasOwn(i,t)){let a=e.querySelector(`[data-location-field="${t}"]`);a&&(a.value=String(i[t]||""))}})}populateTaxonomy(e,t,i){let a=this.splitIDs(i);const l=e.querySelector(`input[type="hidden"][name="${t}"]`);l&&(l.value=a.join(","),window.jvbSelector&&requestAnimationFrame(()=>{window.jvbSelector.updateFieldFromInput(l)}))}populateUser(e,t,i){this.populateTaxonomy(e,t,i)}populateUpload(e,t,i){if(e.dataset.subtype&&"timeline"===e.dataset.subtype)return void this.populateTimelineGallery(e,t,i);const a=this.splitIDs(i);if(0===a.length)return;const l=e.querySelector('input[type="hidden"]');l&&(l.value=a.join(","));const o=e.querySelector(".item-grid");e.querySelector(".progress")?.remove(),o&&(window.removeChildren(o),a.forEach(e=>{let t=this.data.images[e]??{};t.field={config:{showMeta:!0}},t.id=e,o.append(this.templates.create("uploadItem",t));let i=o.children,a=Array.from(i).filter(t=>t.dataset.id===`${e}`);a=a[0]??!1,a&&this.populateUploadMeta(a,null,e)}))}populateUploadMeta(e,t,i){const a=e.querySelector('[data-field="image_data"]');if(!a)return;let l=this.data.images[i]??!1;if(!l)return;a.dataset.attachmentId=l.id,a.setAttribute("data-ignore","");const o=["image-title","image-alt-text","image-caption"];for(const e of o){const t=a.querySelector(`[data-field="image_data|${e}"] input, [data-field="image_data|${e}"] textarea`);t&&""!==l[e]&&(t.value=window.decodeHTMLEntities(l[e]))}}populateTimelineGallery(e,t,i){if(!i||!Array.isArray(i)||0===i.length)return;let a=e.querySelector(".item-grid");if(a){window.removeChildren(a),e.querySelector(".progress")?.remove();for(let e of i){let t=this.templates.create("timelineItem",e);t&&a.append(t)}}}populateMultiValue(e,t,i){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=i.split(",").map(e=>e.trim())}Array.isArray(i)||(i=[String(i)]);let a=e.querySelector(`select[name="${t}"]`);if(a&&a.multiple)for(let e of a.options)e.selected=i.includes(e.value);else e.querySelectorAll(`input[type="checkbox"][name="${t}[]"], input[type="checkbox"][name="${t}"]`).forEach(e=>{e.checked=i.includes(e.value)})}populateSingleValue(e,t,i){i=String(i||"");let a=e.querySelector(`select[name="${t}"]`);if(a)return void(a.value=i);let l=e.querySelector(`input[type="radio"][value="${i}"], input[type="checkbox"][value="${i}"]`)||e.querySelector(`[name="${t}"][value="${i}"]`);l&&(l.checked=!0)}populateBoolean(e,t,i){const a=e.querySelector(`[name="${t}"], input[type="checkbox"]`);a&&(a.checked=Boolean(i))}populateDate(e,t,i){const a=e.querySelector(`[name="${t}"], input`);if(a){"object"==typeof i&&Object.hasOwn(i,"date")&&(i=i.date);try{const e=new Date(i);if(!isNaN(e.getTime()))switch(a.type){case"date":a.value=e.toISOString().split("T")[0];break;case"time":a.value=e.toTimeString().slice(0,5);break;case"datetime-local":a.value=e.toISOString().slice(0,16);break;default:a.value=i}}catch(e){a.value=i}}}populateNumber(e,t,i){const a=e.querySelector(`[name="${t}"], input[type="number"]`);a&&(a.value=Number(i)||0)}populateTextarea(e,t,i){let a=e.querySelector("textarea[data-editor], textarea");if(this.populateText(e,t,i),a?.dataset.editor){const t=e.querySelector(".ql-editor");t?t.innerHTML=i:a.dispatchEvent(new Event("change",{bubbles:!0}))}}populateText(e,t,i){let a=e.querySelector(`[name="${t}"]`)||e.querySelector("textarea[data-editor]")||e.querySelector('input:not([type="hidden"]):not([type="file"]), textarea, select');a&&(a.value=window.decodeHTMLEntities(i??""))}getFormHelper(){window.requestAnimationFrame(()=>{this.formHelper=window.jvbForm})}splitIDs(e){return String(e).split(",").map(e=>parseInt(e.trim())).filter(e=>!isNaN(e)&&e>0)}isEmptyValue(e){return null==e||""===e||(!(!Array.isArray(e)||0!==e.length)||"object"==typeof e&&0===Object.keys(e).length)}defineTemplates(){const e=this.templates,t=this;e.define("timelineItem",{refs:{select:'[name="select-item"]',video:"video",file:".select-item span",img:"img",details:'[data-field="image_data"] details',imgAlt:'[data-field="image-alt-text"]',imgTitle:'[data-field="image-title"]',imgDesc:'[data-field="image-caption"]'},manyRefs:{fields:".field"},setup({el:e,refs:i,manyRefs:a,data:l}){if(e.dataset.itemId=l.id,i.select){let e=i.select.closest(".preview");window.prefixInput(i.select,`${l.id}-`,e)}i.video&&i.video.remove(),i.file&&i.file.remove();let o=t.data.images[l.post_thumbnail]??!1;if(i.img&&o&&(i.img.src=o.medium||o.small||o.large||"",i.img.title=o.large.split("/").pop()??"",i.img.alt=o["image-alt-text"]??""),i.details){let e=t.data.images[l.post_thumbnail];i.details.setAttribute("data-ignore",""),i.details.dataset.attachmentId=l.post_thumbnail;let a=i.imgAlt.querySelector("input"),o=i.imgTitle.querySelector("input"),r=i.imgDesc.querySelector("textarea");window.prefixInput(a,`[${l.post_thumbnail}]`,i.imgAlt,!1,!0),window.prefixInput(o,`[${l.post_thumbnail}]`,i.imgTitle,!1,!0),window.prefixInput(r,`[${l.post_thumbnail}]`,i.imgDesc,!1,!0),Object.hasOwn(e,"image-alt-text")&&i.imgAlt&&(a.value=window.decodeHTMLEntities(e["image-alt-text"])),(Object.hasOwn(e,"image-title")||Object.hasOwn(l,"file"))&&i.imgTitle&&(o.value=window.decodeHTMLEntities(e["image-title"]||l.file.name)),Object.hasOwn(e,"image-caption")&&i.imgDesc&&(r.value=window.decodeHTMLEntities(e["image-caption"]))}if(a.fields)for(let e of a.fields){if(e.closest("[data-ignore]"))continue;if("group"===e.dataset.fieldType)continue;if("post_thumbnail"===e.dataset.field){e.remove();continue}let i=e.dataset.field;const a=e.querySelector('input:not([type="file"]), textarea, select');a&&window.prefixInput(a,`[${l.id}]`,e,!1,!0);let o=l[i]??"";t.isEmptyValue(o)||t.populateField(e,i,o)}}})}}document.addEventListener("DOMContentLoaded",function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbPopulate=new e)})})})();
\ No newline at end of file
diff --git a/assets/js/min/popup.min.js b/assets/js/min/popup.min.js
index c1647d4..c981fd1 100644
--- a/assets/js/min/popup.min.js
+++ b/assets/js/min/popup.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){if(e.instance)return e.instance;e.instance=this,this.a11y=window.jvbA11y,this.popups=new Map,this.activePopup=null,this.clickHandler=this.handleClick.bind(this),this.keyHandler=this.handleEscape.bind(this),document.addEventListener("click",this.clickHandler)}registerPopup(e={}){const t=e.popup,p=e.toggle;if(!t||!p)return console.warn("Popup registration requires both popup and toggle elements"),null;const o=p.id||"."+p.className.split(" ").join("."),s={id:o,toggle:p,popup:t,name:e.name||"Popup",onOpen:e.onOpen||(()=>{}),onClose:e.onClose||(()=>{}),onEscape:e.onEscape||(()=>{}),isOpen:!1};return this.popups.set(o,s),this.getPopupAPI(o)}getPopupAPI(e){return{openPopup:t=>this.openPopup(e,t),closePopup:t=>this.closePopup(e,t),togglePopup:()=>this.togglePopup(e),isOpen:()=>this.popups.get(e)?.isOpen||!1,destroy:()=>this.popups.delete(e)}}handleClick(e){for(const[t,p]of this.popups)if(e.target===p.toggle||e.target.closest(t))return void this.togglePopup(t);if(this.activePopup){const t=this.popups.get(this.activePopup);(t&&!t.popup.contains(e.target)||t&&window.targetCheck(e,".close"))&&this.closePopup(this.activePopup)}}handleEscape(e){if("Escape"===e.key&&this.activePopup){const e=this.popups.get(this.activePopup);this.closePopup(this.activePopup,`Closed ${e.name} with escape key`),e.onEscape()}}togglePopup(e){const t=this.popups.get(e);t&&(t.isOpen?this.closePopup(e):this.openPopup(e))}openPopup(e,t){const p=this.popups.get(e);p&&(this.activePopup&&this.activePopup!==e&&this.closePopup(this.activePopup),p.isOpen=!0,this.activePopup=e,p.popup.classList.add("expanded"),p.toggle.classList.add("expanded"),p.toggle.title=`Hide ${p.name}`,p.toggle.ariaExpanded=!0,p.toggle.querySelector("span").textContent=`Close ${p.name}`,this.a11y.announce(t||`Opened ${p.name}`),p.onOpen(),document.addEventListener("keydown",this.keyHandler))}closePopup(e,t){const p=this.popups.get(e);p&&(p.isOpen=!1,this.activePopup===e&&(this.activePopup=null),p.popup.classList.remove("expanded"),p.toggle.classList.remove("expanded"),p.toggle.title=`Show ${p.name}`,p.toggle.ariaExpanded=!1,p.toggle.querySelector("span").textContent="",this.a11y.announce(t||`Closed ${p.name}`),p.onClose(),this.activePopup||document.removeEventListener("keydown",this.keyHandler))}destroy(){document.removeEventListener("click",this.clickHandler),document.removeEventListener("keydown",this.keyHandler),this.popups.clear(),this.activePopup=null}}document.addEventListener("DOMContentLoaded",(function(){window.jvbPopup=new e}))})();
\ No newline at end of file
+(()=>{class e{constructor(){if(e.instance)return e.instance;e.instance=this,this.a11y=window.jvbA11y,this.popups=new Map,this.activePopup=null,this.clickHandler=this.handleClick.bind(this),this.keyHandler=this.handleEscape.bind(this),document.addEventListener("click",this.clickHandler)}registerPopup(e={}){const t=e.popup,p=e.toggle;if(!t||!p)return console.warn("Popup registration requires both popup and toggle elements"),null;const o=p.id||"."+p.className.split(" ").join("."),s={id:o,toggle:p,popup:t,name:e.name||"Popup",onOpen:e.onOpen||(()=>{}),onClose:e.onClose||(()=>{}),onEscape:e.onEscape||(()=>{}),isOpen:!1};return this.popups.set(o,s),this.getPopupAPI(o)}getPopupAPI(e){return{openPopup:t=>this.openPopup(e,t),closePopup:t=>this.closePopup(e,t),togglePopup:()=>this.togglePopup(e),isOpen:()=>this.popups.get(e)?.isOpen||!1,destroy:()=>this.popups.delete(e)}}handleClick(e){for(const[t,p]of this.popups)if(e.target===p.toggle||window.targetCheck(e,t)||window.targetCheck(e,p.toggle.tagName+"."+[...p.toggle.classList].join(".")))return void this.togglePopup(t);if(this.activePopup){const t=this.popups.get(this.activePopup);(t&&!t.popup.contains(e.target)||t&&window.targetCheck(e,".close"))&&this.closePopup(this.activePopup)}}handleEscape(e){if("Escape"===e.key&&this.activePopup){const e=this.popups.get(this.activePopup);this.closePopup(this.activePopup,`Closed ${e.name} with escape key`),e.onEscape()}}togglePopup(e){const t=this.popups.get(e);t&&(t.isOpen?this.closePopup(e):this.openPopup(e))}openPopup(e,t){const p=this.popups.get(e);p&&(this.activePopup&&this.activePopup!==e&&this.closePopup(this.activePopup),p.isOpen=!0,this.activePopup=e,p.popup.classList.add("expanded"),p.toggle.classList.add("expanded"),p.toggle.title=`Hide ${p.name}`,p.toggle.ariaExpanded=!0,p.toggle.querySelector("span").textContent=`Close ${p.name}`,this.a11y.announce(t||`Opened ${p.name}`),p.onOpen(),document.addEventListener("keydown",this.keyHandler))}closePopup(e,t){const p=this.popups.get(e);p&&(p.isOpen=!1,this.activePopup===e&&(this.activePopup=null),p.popup.classList.remove("expanded"),p.toggle.classList.remove("expanded"),p.toggle.title=`Show ${p.name}`,p.toggle.ariaExpanded=!1,p.toggle.querySelector("span").textContent="",this.a11y.announce(t||`Closed ${p.name}`),p.onClose(),this.activePopup||document.removeEventListener("keydown",this.keyHandler))}destroy(){document.removeEventListener("click",this.clickHandler),document.removeEventListener("keydown",this.keyHandler),this.popups.clear(),this.activePopup=null}}document.addEventListener("DOMContentLoaded",function(){window.jvbPopup=new e})})();
\ No newline at end of file
diff --git a/assets/js/min/queue.min.js b/assets/js/min/queue.min.js
index 95f5a23..d003f39 100644
--- a/assets/js/min/queue.min.js
+++ b/assets/js/min/queue.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.user=window.auth.getUser(),this.user&&(this.canUpdateUI=!0,this.isProcessing=!1,this.isPolling=!1,this.queue=new Map,this.items=new Map,this.subscribers=new Set,this.loadFromStorage=!1,this.failedFetches=0,this.api=jvbSettings.api,this.endpoint="queue",this.init())}init(){this.headers={"X-WP-Nonce":window.auth.getNonce()},this.initElements(),this.initListeners(),this.initStore(),this.canUpdateUI&&this.ui.panel&&(this.popup=window.jvbPopup.registerPopup({popup:this.ui.panel,toggle:this.ui.toggle.button,name:"Queue Panel"})),this.defineTemplates()}initElements(){this.panelStatuses=["syncing","synced","pending","offline"],this.statuses=["queued","localProcessing","uploading","pending","processing","completed","failed","failed_permanent"],this.pendingStatuses=["queued","localProcessing","uploading"],this.workingStatuses=["pending","processing"],this.completedStatuses=["completed","failed","failed_permanent"],this.icons={queued:"arrows-clockwise",localProcessing:"arrows-clockwise",uploading:"syncing",pending:"cloud",processing:"syncing",completed:"cloud-check",failed:"cloud-warning",failed_permanent:"cloud-warning"},this.selectors={panel:"aside#queue",toggle:{button:"button.qtoggle",indicator:".qtoggle .indicator",count:".qtoggle .count"},refresh:{button:"#queue .m-actions .refresh",countdown:"#queue .m-actions .refresh .countdown"},popup:{popup:"#queue .popup",message:"#queue .popup span"},items:{container:"#queue .qitems"},actions:{retry:"#queue .retry-all",clear:"#queue .dismiss-all"},filters:{filter:"#queue [data-filter]",all:{label:'#queue [for="qfilter-all"]',radio:'#queue [data-filter="all"]',count:'#queue [data-filter="all"] .count'},queued:{label:'#queue [for="qfilter-queued"]',input:'#queue [data-filter="queued"]',count:'#queue [for="qfilter-queued"] .count'},localProcessing:{label:'#queue [for="qfilter-localProcessing"]',input:'#queue [data-filter="localProcessing"]',count:'#queue [for="qfilter-localProcessing"] .count'},uploading:{label:'#queue [for="qfilter-uploading"]',input:'#queue [data-filter="uploading"]',count:'#queue [for="qfilter-uploading"] .count'},pending:{label:'#queue [for="qfilter-pending"]',input:'#queue [data-filter="pending"]',count:'#queue [for="qfilter-pending"] .count'},processing:{label:'#queue [for="qfilter-processing"]',input:'#queue [data-filter="processing"]',count:'#queue [for="qfilter-processing"] .count'},completed:{label:'#queue [for="qfilter-completed"]',input:'#queue [data-filter="completed"]',count:'#queue [for="qfilter-completed"] .count'},failed:{label:'#queue [for="qfilter-failed"]',input:'#queue [data-filter="failed"]',count:'#queue [for="qfilter-failed"] .count'}},item:{type:".type",status:".status",details:".info .details",icon:".status .icon",startedAt:".started time",completed:{wrap:".completed",label:".completed span",time:".completed time"},progress:{progress:".progress",fill:".progress .fill",details:".progress .details",icon:".progress .icon"},actions:{cancel:"button.cancel",retry:"button.retry",refresh:"button.refresh",dismiss:"button.dismiss"}}},this.ui=window.uiFromSelectors(this.selectors),this.ui.panel||(this.canUpdateUI=!1)}defineTemplates(){const e=window.jvbTemplates;e.define("emptyState"),e.define("queueItem",{setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id}})}initListeners(){this.activityListeners=null,this.clickHandler=this.handleClick.bind(this),this.onlineHandler=this.handleOnline.bind(this),this.offlineHandler=this.handleOffline.bind(this),this.unloadHandler=this.handleBeforeUnload.bind(this),this.visibilityHandler=this.handleVisibilityChange.bind(this),document.addEventListener("click",this.clickHandler),window.addEventListener("online",this.onlineHandler),window.addEventListener("offline",this.offlineHandler),document.addEventListener("visibilitychange",this.visibilityHandler)}handleOnline(){this.updatePanel("synced"),this.getQueueByStatus(this.pendingStatuses).length>0&&this.processQueue()}handleOffline(){this.updatePanel("offline")}handleVisibilityChange(e){this.isPolling&&document.hidden?this.stopPolling():this.maybeStartPolling()}handleBeforeUnload(e){if(!this.ui.panel)return;return this.getQueueByStatus(this.pendingStatuses).length>0?(e.preventDefault(),e.returnValue="",""):void 0}handleClick(e){if(!window.targetCheck(e,this.selectors.panel+", "+this.selectors.toggle.button))return;if(window.targetCheck(e,this.selectors.refresh.button))return this.ui.refresh.button.classList.add("fetching"),this.store.clearCache(),this.store.clearFilters(),void this.store.fetch().finally((()=>{this.ui.refresh.button.classList.remove("fetching")}));if(window.targetCheck(e,this.selectors.actions.refresh))return void this.handleRefresh(opId);if(window.targetCheck(e,this.selectors.actions.clear))return void this.opActions("completed","dismiss").then((()=>{}));if(window.targetCheck(e,this.selectors.actions.retry))return void this.opActions("failed","retry").then((()=>{}));const t=window.targetCheck(e,"[data-action]");if(t){const e=t.closest("[data-id]")?.dataset.id;return void(e&&this.opActions(e,t.dataset.action))}const s=window.targetCheck(e,this.selectors.filters.filter);s&&this.setFilter(s.dataset.filter)}setFilter(e){Object.values(this.ui.filters).forEach((t=>{t.input?.dataset.filter===e&&(t.input.checked=!0)})),"all"===e?this.store.clearFilters():this.store.setFilter("status",e)}trackActivity(){if(!this.activityListeners){const e=["mousedown","mousemove","keypress","scroll","touchstart"];this.activityListeners=e.map((e=>{const t=()=>this.resetActivityTimer();return document.addEventListener(e,t,{passive:!0}),{event:e,handler:t}}))}this.resetActivityTimer()}resetActivityTimer(){this.activityTimer&&clearTimeout(this.activityTimer),this.activityTimer=setTimeout((()=>{this.processQueue()}),1750)}stopActivityTracking(){this.activityTimer&&(clearTimeout(this.activityTimer),this.activityTimer=null),this.activityListeners&&(this.activityListeners.forEach((({event:e,handler:t})=>{document.removeEventListener(e,t)})),this.activityListeners=null)}initStore(){if(!this.user)return;const e=window.jvbStore.register("queue",{storeName:"queue",keyPath:"id",endpoint:this.endpoint,TTL:1/0,isAuth:!0,indexes:[{name:"status",keyPath:"status"},{name:"type",keyPath:"type"}],filters:{user:window.auth.getUser()},showLoading:!1});this.store=e.queue,this.store.subscribe(((e,t)=>{switch(e){case"data-loaded":this.store.getAll().forEach((e=>{const t=this.queue.get(e.id),s=this.mapServerOperation(e);this.queue.set(s.id,s),t&&t.status!==s.status&&this.notify("operation-status",s)})),this.maybeStartPolling(),this.updateUI();break;case"items-save":this.maybeStartPolling(),this.updateUI();break;case"item-saved":t.item&&(this.queue.set(t.item.id,t.item),t.previousItem?.status!==t.item.status&&this.notify("operation-status",t.item)),this.maybeStartPolling()}}))}handleRefresh(e){const t=this.getQueue(e);if(!t)return;let s=null;if(s={content_update:t.data?.posts?Object.values(t.data.posts)[0]?.content:null,batch_creation:t.data?.content,image_upload:"uploads",video_upload:"uploads",document_upload:"uploads"}[t.type],s&&window.jvbStore){if(window.jvbStore.stores.get(s)){window.jvbStore.clearCache(s),window.jvbStore.fetch(s);const t=this.items.get(e)?.ui?.actions?.refresh;if(t){const e=t.querySelector("span").textContent;t.querySelector("span").textContent="Refreshed!",t.disabled=!0,setTimeout((()=>{t.querySelector("span").textContent=e,t.disabled=!1}),2e3)}}}else confirm("Refresh the page to see changes?")&&window.location.reload()}addToQueue(e){console.log(e,"Adding to queue:");const t={id:`u${this.user}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,endpoint:null,method:"POST",headers:{},data:{},delay:!1,canMerge:!0,popup:"Saving changes...",title:"Operation",status:"queued",timestamp:Date.now(),created_at:(new Date).toISOString(),retries:0,user:this.user,...e};if(t.headers={...this.headers,...t.headers},!t.endpoint||!t.data)return null;if(t.popup&&this.ui.popup?.message&&(this.ui.popup.message.textContent=t.popup,this.ui.popup.popup.hidden=!1,setTimeout((()=>this.ui.popup.popup.hidden=!0),2e3)),!t.delay)return this.queue.set(t.id,t),this.processOperation(t).then((()=>{})),this.store.clearCache(),this.maybeStartPolling(),this.toggleQueue(),t.id;const s=Array.from(this.getAllQueue()).filter((e=>"queued"===e.status&&e.endpoint===t.endpoint&&e.canMerge));if(s.length>0){const e=s[0];return e.data=window.deepMerge(e.data,t.data),e.timestamp=Date.now(),this.setQueue(e),this.updateOperationStatus(e.id,e.status),this.updateUI(),this.trackActivity(),e.id}return this.store.clearCache(),this.setQueue(t),this.updateOperationStatus(t.id,t.status),this.updateUI(),this.trackActivity(),t.id}async opActions(e,t){if(this.statuses.includes(e)?e=this.getQueueByStatus(e).map((e=>e.id)):"string"==typeof e&&(e=[e]),0===e.length)return;if(!["cancel","dismiss","retry"].includes(t))return;const s=["cancel","dismiss"].includes(t);s&&e.forEach((e=>{this.removeOperationUI(e)}));try{const i=await window.auth.fetch(`${this.api}${this.endpoint}`,{method:"POST",headers:{"Content-Type":"application/json",...this.headers},body:JSON.stringify({action:t,ids:Array.isArray(e)?e:[e],user:this.user})});if(!i.ok)throw new Error(`${t} failed: ${i.status}`);const n=await i.json();if(!n.success)throw new Error(n.message||`${t} operation failed`);return e.forEach((e=>{let i=this.getQueue(e);if(i&&this.notify(`${t}-operation`,i),s)this.clearQueue(e);else{let t=this.getQueue(e);t.status="queued",this.setQueue(t),this.updateOperationStatus(t.id,t.status)}})),"retry"===t&&this.trackActivity(),this.updateUI(),n}catch(s){return await window.jvbError.log(s,{component:"Queue",operation:"performQueueAction",action:t,operationIds:e,itemCount:e.length},(()=>this.opActions(e,t))),{success:!1,error:s.message}}}async processQueue(){if(this.isProcessing)return;const e=this.getQueueByStatus("queued");if(0===e.length)return void this.stopActivityTracking();this.setProcessing();for(const t of e)await this.processOperation(t);this.setProcessing(!1);0===this.getQueueByStatus("queued").length?this.stopActivityTracking():this.trackActivity(),this.toggleQueue(this.maybeStartPolling())}async processOperation(e){try{this.queue.has(e.id)||this.queue.set(e.id,e);let t,s,i=!1;if(e.data?._isFormData&&!e.data instanceof FormData&&(i=!0,e.data=await this.store.objectToFormData(e.data)),this.updateOperationStatus(e.id,"uploading"),e.data instanceof FormData?(e.data.append("id",e.id),e.data.append("user",window.auth.getUser()),t=e.data,s=e.data):(s={...e.data,id:e.id,user:window.auth.getUser()},t=JSON.stringify(s),e.headers["Content-Type"]="application/json"),"unknown"===e.endpoint||null==t)return;const n=await window.auth.fetch(`${this.api}${e.endpoint}`,{method:e.method,headers:e.headers,body:t}),a=await n.json();if(i&&(e.data={}),!n.ok||!a.success)throw new Error(a.message||`HTTP ${n.status}`);this.notify("sent-to-server",s),a.id&&e.id!==a.id?e=await this.handleServerMerge(e,a):(e.status=a.status??"pending",e.serverData=a,this.updateOperationStatus(e.id,e.status)),this.a11y.announce(`${e.title} sent to server for processing`),this.setQueue(e)}catch(t){console.error("Operation failed: ",t),e.retries++,e.lastError=t.message,e.retries>=3?e.status="failed_permanent":e.status="failed",this.updateOperationStatus(e.id,e.status),this.setQueue(e)}}async handleServerMerge(e,t){const s=this.getQueue(t.id);return s?(e.status=t.status||"pending",e.serverData=t,this.mergeOp(s,e)):(this.clearQueue(e.id),this.setQueue(t),t)}mergeOp(e,t){return e.data=window.deepMerge(e.data,t.data),e.status=t.status,Object.hasOwn(t,"serverData")&&(e.serverData=t.serverData),this.updateOperationStatus(e.id,e.status),this.removeOperationUI(t.id),this.clearQueue(t.id),e}sortByDate(e){return e.sort(((e,t)=>(e.updated_at??e.timestamp??0)-(t.updated_at??t.timestamp??0)))}sortOperations(e){const t={processing:0,uploading:1,pending:2,queued:3,localProcessing:4,failed:5,completed:6,failed_permanent:7};return e.sort(((e,s)=>{const i=(t[e.status]??99)-(t[s.status]??99);if(0!==i)return i;const n=e.updated_at??e.timestamp??0,a=s.updated_at??s.timestamp??0;return new Date(a)-new Date(n)}))}getAllQueue(){let e=new Set,t=[...Array.from(this.queue.values())];return this.loadFromStorage||(this.loadFromStorage=!0,t=[...t,...Array.from(this.store.data.values())],t=t.filter((t=>{const s=e.has(t.id);return e.add(t.id),!s}))),this.sortOperations(t)}getQueueByStatus(e){return"string"==typeof e&&(e=[e]),this.getAllQueue().filter((t=>e.includes(t.status)))}updateOperationStatus(e,t){let s=this.getQueue(e);s&&(this.statuses.includes(t)?(s.status=t,this.notify("operation-status",s),this.setQueue(s)):console.log("Invalid status: ",t))}setQueue(e){this.store.save(e),this.queue.set(e.id,e)}getQueue(e){return this.queue.has(e)?this.queue.get(e):this.store.get(e)}clearQueue(e){this.queue.delete(e),this.store.delete(e)}maybeStartPolling(){return this.getQueueByStatus([...this.pendingStatuses,...this.workingStatuses]).length>0?(this.startPolling(),!0):(this.updatePanel("synced"),!1)}startPolling(){this.isPolling||(this.isPolling=!0,this.updatePanel("pending"),this.runPollCycle())}async runPollCycle(){if(this.isPolling){try{this.ui.refresh.button.classList.add("fetching"),this.store.clearCache();let e=await this.store.fetch();if(Object.hasOwn(e,"status")&&429===e.status)return console.log("Too many requests. Waiting 30 seconds"),this.stopPolling(),void this.startCountdown(30,(()=>this.runPollCycle()));if(this.ui.refresh.button.classList.remove("fetching"),!this.maybeStartPolling())return this.stopPolling(),void this.updatePanel("synced")}catch(e){this.stopPolling(),this.updatePanel("synced"),console.error("Polling error:",e)}this.startCountdown(5,(()=>this.runPollCycle()))}}startCountdown(e,t){this.ui.refresh.countdown?(this.ui.refresh.countdown.classList.add("counting"),this.ui.refresh.countdown.textContent=e,this.countdownTimer=setInterval((()=>{--e>0?this.ui.refresh.countdown.textContent=e:(this.stopCountdown(),t&&t())}),1e3)):console.warn("Countdown element not found")}stopPolling(){this.isPolling&&(this.isPolling=!1,this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.stopCountdown())}stopCountdown(){this.countdownTimer&&(clearInterval(this.countdownTimer),this.countdownTimer=null),this.ui.refresh.countdown.classList.remove("counting"),this.ui.refresh.countdown.textContent=""}updateUI(){this.canUpdateUI&&window.debouncer.schedule("queue-ui",this.handleUpdateUI.bind(this))}handleUpdateUI(){const e=this.getAllQueue();this.ui.actions.retry.disabled=0===e.filter((e=>"failed"===e.status)).length,this.ui.actions.clear.disabled=0===e.filter((e=>"completed"===e.status)).length;let t=e.filter((e=>[...this.pendingStatuses,...this.workingStatuses].includes(e.status)));t=t.length,this.ui.toggle.count.hidden=0===t,this.ui.toggle.count.textContent=t;for(let t of this.statuses){if("failed_permanent"===t)continue;let s=e.filter((e=>e.status===t)).length;this.ui.filters[t].label.hidden=0===s,this.ui.filters[t].input.dataset.count=`${s}`,this.ui.filters[t].count.textContent=s>0?s:""}this.renderOperations()}renderOperations(){if(!this.ui.items.container)return;const e=this.store.filters?.status??"all",t="all"===e?this.getAllQueue():this.getQueueByStatus(e),s=this.sortOperations(t);if(0===s.length){window.removeChildren(this.ui.items.container);const e=window.jvbTemplates.create("emptyQueue");return this.ui.items.container.append(e),void this.a11y.announce("No items in queue")}this.ui.items.container.querySelector(".empty-group")?.remove();const i=new Set(s.map((e=>e.id)));this.items.forEach(((e,t)=>{i.has(t)||(e.element?.remove(),this.items.delete(t))})),s.forEach(((e,t)=>{let s=this.items.get(e.id);s||(s=this.createOperationElement(e)),s?.element&&(this.updateOperationUI(e.id),this.ui.items.container.append(s.element))}))}createOperationElement(e){const t=window.jvbTemplates.create("queueItem",e),s={element:t,ui:window.uiFromSelectors(this.selectors.item,t)};return this.items.set(e.id,s),s}updateOperationUI(e){let t=this.items.has(e)?this.items.get(e):this.createOperationElement(e);if(!t)return;let s=this.getQueue(e),i=t.element;i.classList.remove(...this.statuses),i.classList.add(s.status);let n=this.getProgress(s);t.ui.type&&t.ui.type.textContent!==s.title&&(t.ui.type.textContent=s.title),t.ui.status&&(t.ui.status.title=this.statusLabel(s.status)),t.ui.icon&&(t.ui.icon.className=`icon icon-${this.icons[s.status]}`),t.ui.details&&(t.ui.details.textContent=this.itemMessage(s)),t.ui.startedAt&&(t.ui.startedAt.setAttribute("datetime",s.created_at),t.ui.startedAt.textContent=window.formatTimeAgo(s.created_at));s.status;const a="completed"===s.status&&(s.completed_at||s.updated_at);if(t.ui.completed.wrap.hidden=!a,a){const e=s.completed_at??s.updated_at;t.ui.completed.label.textContent="Completed: ",t.ui.completed.time.setAttribute("datetime",e),t.ui.completed.time.textContent=window.formatTimeAgo(e)}window.showProgress(t.ui.progress,n,100,this.statusLabel(s.status)),t.ui.actions.cancel&&(t.ui.actions.cancel.hidden=this.completedStatuses.includes(s.status)),t.ui.actions.retry&&(s.retries>=3&&(t.ui.actions.retry.disabled=!0),t.ui.actions.retry.hidden="failed"!==s.status),t.ui.actions.dismiss&&(t.ui.actions.dismiss.hidden=this.pendingStatuses.includes(s.status)),t.ui.actions.refresh&&(t.ui.actions.refresh.hidden="completed"!==s.status)}getProgress(e){if(void 0!==e.progress_percentage)return e.progress_percentage;if(void 0!==e.progress)return e.progress;if(!this.statuses.includes(e.status))return 0;return{queued:10,uploading:25,pending:40,processing:70,completed:100,failed:0,failed_permanent:0}[e.status]??0}removeOperationUI(e){let t=this.items.get(e);t&&window.fade(t.element,!1)}updatePanel(e="syncing"){this.ui.panel&&this.panelStatuses.includes(e)&&(this.ui.panel.classList.remove(...this.panelStatuses),this.ui.panel.classList.add(e))}statusLabel(e){if(!this.statuses.includes(e))return"";return{queued:"Queued",localProcessing:"Processing locally",uploading:"Uploading",pending:"Waiting on server",processing:"Processing",completed:"Completed",failed:"Failed",failed_permanent:"Failed permanently",merged:"Merged"}[e]}itemMessage(e){if(Object.hasOwn(e,"message")&&""!==e.message)return e.message;if(Object.hasOwn(e,"error_message")&&e.error_message)return e.error_message;switch(e.status){case"queued":return"Waiting to send...";case"uploading":return"Sending to server...";case"pending":return e.position?`Position ${e.position} in queue`:"In server queue";case"processing":if(e.count&&void 0!==e.progress_count){const t=e.progress_count,s=e.count;return`Processing ${t}/${s} items (${Math.round(t/s*100)}%)`}return void 0!==e.progress_percentage?`${e.progress_percentage}% complete`:"Processing...";case"completed":return"Successfully completed. Refresh to see changes.";case"merged":return e.merged_into?`Merged with another operation (${e.merged_into.substring(0,8)}...)`:"Merged with another operation";case"failed":return`Failed: ${e.lastError||"Unknown error"} (Retry ${e.retries}/2)`;case"failed_permanent":return`Failed: ${e.lastError||"Unknown error"}`;default:return""}}toggleQueue(e=!0){this.ui.panel&&(this.ui.panel.hidden=!e,this.ui.toggle.button.hidden=!e)}setProcessing(e=!0){this.isProcessing=e,this.ui.toggle.button.classList.toggle("saving",e)}mapServerOperation(e){const t=this.queue.get(e.id);if(t&&t.endpoint){const s={...t,...e,endpoint:t.endpoint,method:t.method,headers:t.headers,progress_percentage:e.progress_percentage,progress_count:e.progress_count,count:e.count};e.merged_into&&this.handleMergedOperation(s)}const s=e.type?e.type.replace("_update","").replace("_","/"):"unknown",i={...e,endpoint:s,method:"POST",headers:{...this.headers}};return e.merged_into&&this.handleMergedOperation(i),i}handleMergedOperation(e){e.merged_into&&(console.log(`[Queue] Operation ${e.id} merged into ${e.merged_into}`),setTimeout((()=>{this.clearQueue(e.id),this.removeOperationFromUI(e.id)}),3e3))}subscribe(e){if(this.subscribers)return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach((s=>s(e,t)))}destroy(){this.isPolling&&this.stopPolling(),this.stopActivityTracking(),document.removeEventListener("click",this.clickHandler),this.subscribers.clear()}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbQueue=new e)}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.user=window.auth.getUser(),this.user&&(this.canUpdateUI=!0,this.isProcessing=!1,this.isPolling=!1,this.queue=new Map,this.items=new Map,this.subscribers=new Set,this.loadFromStorage=!1,this.failedFetches=0,this.api=jvbSettings.api,this.endpoint="queue",this.init())}init(){this.headers={"X-WP-Nonce":window.auth.getNonce()},this.initElements(),this.initListeners(),this.initStore(),this.canUpdateUI&&this.ui.panel&&(this.popup=window.jvbPopup.registerPopup({popup:this.ui.panel,toggle:this.ui.toggle.button,name:"Queue Panel"})),this.defineTemplates()}initElements(){this.panelStatuses=["syncing","synced","pending","offline"],this.statuses=["queued","localProcessing","uploading","pending","processing","completed","failed","failed_permanent"],this.pendingStatuses=["queued","localProcessing","uploading"],this.workingStatuses=["pending","processing"],this.completedStatuses=["completed","failed","failed_permanent"],this.icons={queued:"arrows-clockwise",localProcessing:"arrows-clockwise",uploading:"syncing",pending:"cloud",processing:"syncing",completed:"cloud-check",failed:"cloud-warning",failed_permanent:"cloud-warning"},this.selectors={panel:"aside#queue",toggle:{button:"button.qtoggle",indicator:".qtoggle .indicator",count:".qtoggle .count"},refresh:{button:"#queue .m-actions .refresh",countdown:"#queue .m-actions .refresh .countdown"},popup:{popup:"#queue .popup",message:"#queue .popup span"},items:{container:"#queue .qitems"},actions:{retry:"#queue .retry-all",clear:"#queue .dismiss-all"},filters:{filter:"#queue [data-filter]",all:{label:'#queue [for="qfilter-all"]',radio:'#queue [data-filter="all"]',count:'#queue [data-filter="all"] .count'},queued:{label:'#queue [for="qfilter-queued"]',input:'#queue [data-filter="queued"]',count:'#queue [for="qfilter-queued"] .count'},localProcessing:{label:'#queue [for="qfilter-localProcessing"]',input:'#queue [data-filter="localProcessing"]',count:'#queue [for="qfilter-localProcessing"] .count'},uploading:{label:'#queue [for="qfilter-uploading"]',input:'#queue [data-filter="uploading"]',count:'#queue [for="qfilter-uploading"] .count'},pending:{label:'#queue [for="qfilter-pending"]',input:'#queue [data-filter="pending"]',count:'#queue [for="qfilter-pending"] .count'},processing:{label:'#queue [for="qfilter-processing"]',input:'#queue [data-filter="processing"]',count:'#queue [for="qfilter-processing"] .count'},completed:{label:'#queue [for="qfilter-completed"]',input:'#queue [data-filter="completed"]',count:'#queue [for="qfilter-completed"] .count'},failed:{label:'#queue [for="qfilter-failed"]',input:'#queue [data-filter="failed"]',count:'#queue [for="qfilter-failed"] .count'}},item:{type:".type",status:".status",details:".info .details",icon:".status .icon",startedAt:".started time",completed:{wrap:".completed",label:".completed span",time:".completed time"},progress:{progress:".progress",fill:".progress .fill",details:".progress .details",icon:".progress .icon"},actions:{cancel:"button.cancel",retry:"button.retry",refresh:"button.refresh",dismiss:"button.dismiss"}}},this.ui=window.uiFromSelectors(this.selectors),this.ui.panel||(this.canUpdateUI=!1)}defineTemplates(){const e=window.jvbTemplates;e.define("emptyState"),e.define("queueItem",{setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id}})}initListeners(){this.activityListeners=null,this.clickHandler=this.handleClick.bind(this),this.onlineHandler=this.handleOnline.bind(this),this.offlineHandler=this.handleOffline.bind(this),this.unloadHandler=this.handleBeforeUnload.bind(this),this.visibilityHandler=this.handleVisibilityChange.bind(this),document.addEventListener("click",this.clickHandler),window.addEventListener("online",this.onlineHandler),window.addEventListener("offline",this.offlineHandler),document.addEventListener("visibilitychange",this.visibilityHandler)}handleOnline(){this.updatePanel("synced"),this.getQueueByStatus(this.pendingStatuses).length>0&&this.processQueue()}handleOffline(){this.updatePanel("offline")}handleVisibilityChange(e){this.isPolling&&document.hidden?this.stopPolling():this.maybeStartPolling()}handleBeforeUnload(e){if(!this.ui.panel)return;return this.getQueueByStatus(this.pendingStatuses).length>0?(e.preventDefault(),e.returnValue="",""):void 0}handleClick(e){if(!window.targetCheck(e,this.selectors.panel+", "+this.selectors.toggle.button))return;if(window.targetCheck(e,this.selectors.refresh.button))return this.ui.refresh.button.classList.add("fetching"),this.store.clearCache(),this.store.clearFilters(),void this.store.fetch().finally(()=>{this.ui.refresh.button.classList.remove("fetching")});if(window.targetCheck(e,this.selectors.actions.refresh))return void this.handleRefresh(opId);if(window.targetCheck(e,this.selectors.actions.clear))return void this.opActions("completed","dismiss").then(()=>{});if(window.targetCheck(e,this.selectors.actions.retry))return void this.opActions("failed","retry").then(()=>{});const t=window.targetCheck(e,"[data-action]");if(t){const e=t.closest("[data-id]")?.dataset.id;return void(e&&this.opActions(e,t.dataset.action))}const s=window.targetCheck(e,this.selectors.filters.filter);s&&this.setFilter(s.dataset.filter)}setFilter(e){Object.values(this.ui.filters).forEach(t=>{t.input?.dataset.filter===e&&(t.input.checked=!0)}),"all"===e?this.store.clearFilters():this.store.setFilter("status",e)}trackActivity(){if(!this.activityListeners){const e=["mousedown","mousemove","keypress","scroll","touchstart"];this.activityListeners=e.map(e=>{const t=()=>this.resetActivityTimer();return document.addEventListener(e,t,{passive:!0}),{event:e,handler:t}})}this.resetActivityTimer()}resetActivityTimer(){this.activityTimer&&clearTimeout(this.activityTimer),this.activityTimer=setTimeout(()=>{this.processQueue()},1750)}stopActivityTracking(){this.activityTimer&&(clearTimeout(this.activityTimer),this.activityTimer=null),this.activityListeners&&(this.activityListeners.forEach(({event:e,handler:t})=>{document.removeEventListener(e,t)}),this.activityListeners=null)}initStore(){if(!this.user)return;const e=window.jvbStore.register("queue",{storeName:"queue",keyPath:"id",endpoint:this.endpoint,TTL:1/0,isAuth:!0,indexes:[{name:"status",keyPath:"status"},{name:"type",keyPath:"type"}],filters:{user:window.auth.getUser()},showLoading:!1});this.store=e.queue,this.store.subscribe((e,t)=>{switch(e){case"data-loaded":this.store.getAll().forEach(e=>{const t=this.queue.get(e.id),s=this.mapServerOperation(e);this.queue.set(s.id,s),t&&t.status!==s.status&&this.notify("operation-status",s)}),this.maybeStartPolling(),this.updateUI();break;case"items-save":this.maybeStartPolling(),this.updateUI();break;case"item-saved":t.item&&(this.queue.set(t.item.id,t.item),t.previousItem?.status!==t.item.status&&this.notify("operation-status",t.item)),this.maybeStartPolling()}})}handleRefresh(e){const t=this.getQueue(e);if(!t)return;let s=null;if(s={content_update:t.data?.posts?Object.values(t.data.posts)[0]?.content:null,batch_creation:t.data?.content,image_upload:"uploads",video_upload:"uploads",document_upload:"uploads"}[t.type],s&&window.jvbStore){if(window.jvbStore.stores.get(s)){window.jvbStore.clearCache(s),window.jvbStore.fetch(s);const t=this.items.get(e)?.ui?.actions?.refresh;if(t){const e=t.querySelector("span").textContent;t.querySelector("span").textContent="Refreshed!",t.disabled=!0,setTimeout(()=>{t.querySelector("span").textContent=e,t.disabled=!1},2e3)}}}else confirm("Refresh the page to see changes?")&&window.location.reload()}addToQueue(e){console.log(e,"Adding to queue:");const t={id:`u${this.user}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,endpoint:null,method:"POST",headers:{},data:{},delay:!1,canMerge:!0,popup:"Saving changes...",title:"Operation",status:"queued",timestamp:Date.now(),created_at:(new Date).toISOString(),retries:0,user:this.user,...e};if(t.headers={...this.headers,...t.headers},!t.endpoint||!t.data)return null;if(t.popup&&this.ui.popup?.message&&(this.ui.popup.message.textContent=t.popup,this.ui.popup.popup.hidden=!1,setTimeout(()=>this.ui.popup.popup.hidden=!0,2e3)),!t.delay)return this.queue.set(t.id,t),this.processOperation(t).then(()=>{}),this.store.clearCache(),this.maybeStartPolling(),this.toggleQueue(),t.id;const s=Array.from(this.getAllQueue()).filter(e=>"queued"===e.status&&e.endpoint===t.endpoint&&e.canMerge);if(s.length>0){const e=s[0];return e.data=window.deepMerge(e.data,t.data),e.timestamp=Date.now(),this.setQueue(e),this.updateOperationStatus(e.id,e.status),this.updateUI(),this.trackActivity(),e.id}return this.store.clearCache(),this.setQueue(t),this.updateOperationStatus(t.id,t.status),this.updateUI(),this.trackActivity(),t.id}async opActions(e,t){if(this.statuses.includes(e)?e=this.getQueueByStatus(e).map(e=>e.id):"string"==typeof e&&(e=[e]),0===e.length)return;if(!["cancel","dismiss","retry"].includes(t))return;const s=["cancel","dismiss"].includes(t);s&&e.forEach(e=>{this.removeOperationUI(e)});try{const i=await window.auth.fetch(`${this.api}${this.endpoint}`,{method:"POST",headers:{"Content-Type":"application/json",...this.headers},body:JSON.stringify({action:t,ids:Array.isArray(e)?e:[e],user:this.user})});if(!i.ok)throw new Error(`${t} failed: ${i.status}`);const n=await i.json();if(!n.success)throw new Error(n.message||`${t} operation failed`);return e.forEach(e=>{let i=this.getQueue(e);if(i&&this.notify(`${t}-operation`,i),s)this.clearQueue(e);else{let t=this.getQueue(e);t.status="queued",this.setQueue(t),this.updateOperationStatus(t.id,t.status)}}),"retry"===t&&this.trackActivity(),this.updateUI(),n}catch(s){return await window.jvbError.log(s,{component:"Queue",operation:"performQueueAction",action:t,operationIds:e,itemCount:e.length},()=>this.opActions(e,t)),{success:!1,error:s.message}}}async processQueue(){if(this.isProcessing)return;const e=this.getQueueByStatus("queued");if(0===e.length)return void this.stopActivityTracking();this.setProcessing();for(const t of e)await this.processOperation(t);this.setProcessing(!1);0===this.getQueueByStatus("queued").length?this.stopActivityTracking():this.trackActivity(),this.toggleQueue(this.maybeStartPolling())}async processOperation(e){try{this.queue.has(e.id)||this.queue.set(e.id,e);let t,s,i=!1;if(e.data?._isFormData&&!e.data instanceof FormData&&(i=!0,e.data=await this.store.objectToFormData(e.data)),this.updateOperationStatus(e.id,"uploading"),e.data instanceof FormData?(e.data.append("id",e.id),e.data.append("user",window.auth.getUser()),t=e.data,s=e.data):(s={...e.data,id:e.id,user:window.auth.getUser()},t=JSON.stringify(s),e.headers["Content-Type"]="application/json"),"unknown"===e.endpoint||null==t)return;const n=await window.auth.fetch(`${this.api}${e.endpoint}`,{method:e.method,headers:e.headers,body:t}),a=await n.json();if(i&&(e.data={}),!n.ok||!a.success)throw new Error(a.message||`HTTP ${n.status}`);this.notify("sent-to-server",s),a.id&&e.id!==a.id?e=await this.handleServerMerge(e,a):(e.status=a.status??"pending",e.serverData=a,this.updateOperationStatus(e.id,e.status)),this.a11y.announce(`${e.title} sent to server for processing`),this.setQueue(e)}catch(t){console.error("Operation failed: ",t),e.retries++,e.lastError=t.message,e.retries>=3?e.status="failed_permanent":e.status="failed",this.updateOperationStatus(e.id,e.status),this.setQueue(e)}}async handleServerMerge(e,t){const s=this.getQueue(t.id);return s?(e.status=t.status||"pending",e.serverData=t,this.mergeOp(s,e)):(this.clearQueue(e.id),this.setQueue(t),t)}mergeOp(e,t){return e.data=window.deepMerge(e.data,t.data),e.status=t.status,Object.hasOwn(t,"serverData")&&(e.serverData=t.serverData),this.updateOperationStatus(e.id,e.status),this.removeOperationUI(t.id),this.clearQueue(t.id),e}sortByDate(e){return e.sort((e,t)=>(e.updated_at??e.timestamp??0)-(t.updated_at??t.timestamp??0))}sortOperations(e){const t={processing:0,uploading:1,pending:2,queued:3,localProcessing:4,failed:5,completed:6,failed_permanent:7};return e.sort((e,s)=>{const i=(t[e.status]??99)-(t[s.status]??99);if(0!==i)return i;const n=e.updated_at??e.timestamp??0,a=s.updated_at??s.timestamp??0;return new Date(a)-new Date(n)})}getAllQueue(){let e=new Set,t=[...Array.from(this.queue.values())];return this.loadFromStorage||(this.loadFromStorage=!0,t=[...t,...Array.from(this.store.data.values())],t=t.filter(t=>{const s=e.has(t.id);return e.add(t.id),!s})),this.sortOperations(t)}getQueueByStatus(e){return"string"==typeof e&&(e=[e]),this.getAllQueue().filter(t=>e.includes(t.status))}updateOperationStatus(e,t){let s=this.getQueue(e);s&&(this.statuses.includes(t)?(s.status=t,this.notify("operation-status",s),this.setQueue(s)):console.log("Invalid status: ",t))}setQueue(e){this.store.save(e),this.queue.set(e.id,e)}getQueue(e){return this.queue.has(e)?this.queue.get(e):this.store.get(e)}clearQueue(e){this.queue.delete(e),this.store.delete(e)}maybeStartPolling(){return this.getQueueByStatus([...this.pendingStatuses,...this.workingStatuses]).length>0?(this.startPolling(),!0):(this.updatePanel("synced"),!1)}startPolling(){this.isPolling||(this.isPolling=!0,this.updatePanel("pending"),this.runPollCycle())}async runPollCycle(){if(this.isPolling){try{this.ui.refresh.button.classList.add("fetching"),this.store.clearCache();let e=await this.store.fetch();if(Object.hasOwn(e,"status")&&429===e.status)return console.log("Too many requests. Waiting 30 seconds"),this.stopPolling(),void this.startCountdown(30,()=>this.runPollCycle());if(this.ui.refresh.button.classList.remove("fetching"),!this.maybeStartPolling())return this.stopPolling(),void this.updatePanel("synced")}catch(e){this.stopPolling(),this.updatePanel("synced"),console.error("Polling error:",e)}this.startCountdown(5,()=>this.runPollCycle())}}startCountdown(e,t){this.ui.refresh.countdown?(this.ui.refresh.countdown.classList.add("counting"),this.ui.refresh.countdown.textContent=e,this.countdownTimer=setInterval(()=>{--e>0?this.ui.refresh.countdown.textContent=e:(this.stopCountdown(),t&&t())},1e3)):console.warn("Countdown element not found")}stopPolling(){this.isPolling&&(this.isPolling=!1,this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.stopCountdown())}stopCountdown(){this.countdownTimer&&(clearInterval(this.countdownTimer),this.countdownTimer=null),this.ui.refresh.countdown.classList.remove("counting"),this.ui.refresh.countdown.textContent=""}updateUI(){this.canUpdateUI&&window.debouncer.schedule("queue-ui",this.handleUpdateUI.bind(this))}handleUpdateUI(){const e=this.getAllQueue();this.ui.actions.retry.disabled=0===e.filter(e=>"failed"===e.status).length,this.ui.actions.clear.disabled=0===e.filter(e=>"completed"===e.status).length;let t=e.filter(e=>[...this.pendingStatuses,...this.workingStatuses].includes(e.status));t=t.length,this.ui.toggle.count.hidden=0===t,this.ui.toggle.count.textContent=t;for(let t of this.statuses){if("failed_permanent"===t)continue;let s=e.filter(e=>e.status===t).length;this.ui.filters[t].label.hidden=0===s,this.ui.filters[t].input.dataset.count=`${s}`,this.ui.filters[t].count.textContent=s>0?s:""}this.renderOperations()}renderOperations(){if(!this.ui.items.container)return;const e=this.store.filters?.status??"all",t="all"===e?this.getAllQueue():this.getQueueByStatus(e),s=this.sortOperations(t);if(0===s.length){window.removeChildren(this.ui.items.container);const e=window.jvbTemplates.create("emptyQueue");return this.ui.items.container.append(e),void this.a11y.announce("No items in queue")}this.ui.items.container.querySelector(".empty-group")?.remove();const i=new Set(s.map(e=>e.id));this.items.forEach((e,t)=>{i.has(t)||(e.element?.remove(),this.items.delete(t))}),s.forEach((e,t)=>{let s=this.items.get(e.id);s||(s=this.createOperationElement(e)),s?.element&&(this.updateOperationUI(e.id),this.ui.items.container.append(s.element))})}createOperationElement(e){const t=window.jvbTemplates.create("queueItem",e),s={element:t,ui:window.uiFromSelectors(this.selectors.item,t)};return this.items.set(e.id,s),s}updateOperationUI(e){let t=this.items.has(e)?this.items.get(e):this.createOperationElement(e);if(!t)return;let s=this.getQueue(e),i=t.element;i.classList.remove(...this.statuses),i.classList.add(s.status);let n=this.getProgress(s);t.ui.type&&t.ui.type.textContent!==s.title&&(t.ui.type.textContent=s.title),t.ui.status&&(t.ui.status.title=this.statusLabel(s.status)),t.ui.icon&&(t.ui.icon.className=`icon icon-${this.icons[s.status]}`),t.ui.details&&(t.ui.details.textContent=this.itemMessage(s)),t.ui.startedAt&&(t.ui.startedAt.setAttribute("datetime",s.created_at),t.ui.startedAt.textContent=window.formatTimeAgo(s.created_at));s.status;const a="completed"===s.status&&(s.completed_at||s.updated_at);if(t.ui.completed.wrap.hidden=!a,a){const e=s.completed_at??s.updated_at;t.ui.completed.label.textContent="Completed: ",t.ui.completed.time.setAttribute("datetime",e),t.ui.completed.time.textContent=window.formatTimeAgo(e)}window.showProgress(t.ui.progress,n,100,this.statusLabel(s.status)),t.ui.actions.cancel&&(t.ui.actions.cancel.hidden=this.completedStatuses.includes(s.status)),t.ui.actions.retry&&(s.retries>=3&&(t.ui.actions.retry.disabled=!0),t.ui.actions.retry.hidden="failed"!==s.status),t.ui.actions.dismiss&&(t.ui.actions.dismiss.hidden=this.pendingStatuses.includes(s.status)),t.ui.actions.refresh&&(t.ui.actions.refresh.hidden="completed"!==s.status)}getProgress(e){if(void 0!==e.progress_percentage)return e.progress_percentage;if(void 0!==e.progress)return e.progress;if(!this.statuses.includes(e.status))return 0;return{queued:10,uploading:25,pending:40,processing:70,completed:100,failed:0,failed_permanent:0}[e.status]??0}removeOperationUI(e){let t=this.items.get(e);t&&window.fade(t.element,!1)}updatePanel(e="syncing"){this.ui.panel&&this.panelStatuses.includes(e)&&(this.ui.panel.classList.remove(...this.panelStatuses),this.ui.panel.classList.add(e))}statusLabel(e){if(!this.statuses.includes(e))return"";return{queued:"Queued",localProcessing:"Processing locally",uploading:"Uploading",pending:"Waiting on server",processing:"Processing",completed:"Completed",failed:"Failed",failed_permanent:"Failed permanently",merged:"Merged"}[e]}itemMessage(e){if(Object.hasOwn(e,"message")&&""!==e.message)return e.message;if(Object.hasOwn(e,"error_message")&&e.error_message)return e.error_message;switch(e.status){case"queued":return"Waiting to send...";case"uploading":return"Sending to server...";case"pending":return e.position?`Position ${e.position} in queue`:"In server queue";case"processing":if(e.count&&void 0!==e.progress_count){const t=e.progress_count,s=e.count;return`Processing ${t}/${s} items (${Math.round(t/s*100)}%)`}return void 0!==e.progress_percentage?`${e.progress_percentage}% complete`:"Processing...";case"completed":return"Successfully completed. Refresh to see changes.";case"merged":return e.merged_into?`Merged with another operation (${e.merged_into.substring(0,8)}...)`:"Merged with another operation";case"failed":return`Failed: ${e.lastError||"Unknown error"} (Retry ${e.retries}/2)`;case"failed_permanent":return`Failed: ${e.lastError||"Unknown error"}`;default:return""}}toggleQueue(e=!0){this.ui.panel&&(this.ui.panel.hidden=!e,this.ui.toggle.button.hidden=!e)}setProcessing(e=!0){this.isProcessing=e,this.ui.toggle.button.classList.toggle("saving",e)}mapServerOperation(e){const t=this.queue.get(e.id);if(t&&t.endpoint){const s={...t,...e,endpoint:t.endpoint,method:t.method,headers:t.headers,progress_percentage:e.progress_percentage,progress_count:e.progress_count,count:e.count};e.merged_into&&this.handleMergedOperation(s)}const s=e.type?e.type.replace("_update","").replace("_","/"):"unknown",i={...e,endpoint:s,method:"POST",headers:{...this.headers}};return e.merged_into&&this.handleMergedOperation(i),i}handleMergedOperation(e){e.merged_into&&(console.log(`[Queue] Operation ${e.id} merged into ${e.merged_into}`),setTimeout(()=>{this.clearQueue(e.id),this.removeOperationFromUI(e.id)},3e3))}subscribe(e){if(this.subscribers)return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(s=>s(e,t))}destroy(){this.isPolling&&this.stopPolling(),this.stopActivityTracking(),document.removeEventListener("click",this.clickHandler),this.subscribers.clear()}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbQueue=new e)})})})();
\ No newline at end of file
diff --git a/assets/js/min/quill.min.js b/assets/js/min/quill.min.js
index 0a6fbba..8f8067a 100644
--- a/assets/js/min/quill.min.js
+++ b/assets/js/min/quill.min.js
@@ -1 +1 @@
-window.jvbQuill=function(t){const n=t.querySelectorAll("textarea[data-editor=true]"),e=[];return n.forEach((t=>{let n,i,l;if(t.parentNode.querySelector(".editor-container"))n=t.parentNode.querySelector(".editor-container"),i=n.querySelector(".editor"),l=n.querySelector(".toolbar");else{n=document.createElement("div"),n.className="editor-container",i=document.createElement("div"),i.className="editor",l=document.createElement("div"),l.className="toolbar";const e=!0===t.dataset.allowimage?`<button type="button" class="ql-jvb_image">\n                    ${window.getIcon("image")}\n                </button>`:"";l.id=`toolbar-${t.id}`,l.innerHTML=`\n                <span class="ql-formats">\n                    <button type="button" class="ql-p">\n                        <i class="icon icon-paragraph"></i>\n                    </button>\n                    <button type="button" class="ql-h1">\n                        <i class="icon icon-text-h-one"></i>\n                    </button>\n                    <button type="button" class="ql-h2">\n                        <i class="icon icon-text-h-two"></i>\n                    </button>\n                    <button type="button" class="ql-h3">\n                        <i class="icon icon-text-h-three"></i>\n                    </button>\n                </span>\n                <span class="ql-formats">\n                    <button type="button" class="ql-jvb_bold">\n                        <i class="icon icon-text-b-fi"></i>\n                    </button>\n                    <button type="button" class="ql-jvb_italic">\n                        <i class="icon icon-text-italic"></i>\n                    </button>\n                    <button type="button" class="ql-jvb_underline">\n                        <i class="icon icon-text-underline"></i>\n                    </button>\n                    <button type="button" class="ql-jvb_strike">\n                        <i class="icon icon-text-strikethrough"></i>\n                    </button>\n                </span>\n                <span class="ql-formats">\n                     <button type="button" class="ql-jvb_list" value="bullet">\n                        <i class="icon icon-list-dashes"></i>\n                    </button>\n                    <button type="button" class="ql-jvb_list" value="ordered">\n                        <i class="icon icon-list-numbers"></i>\n                    </button>\n                </span>\n                <span class="ql-formats">\n                     <button type="button" class="ql-jvb_align" value="left">\n                        <i class="icon icon-text-align-left"></i>\n                    </button>\n                     <button type="button" class="ql-jvb_align" value="center">\n                        <i class="icon icon-text-align-center"></i>\n                    </button>\n                     <button type="button" class="ql-jvb_align" value="right">\n                        <i class="icon icon-text-align-right"></i>\n                    </button>\n                </span>\n                <span class="ql-formats">\n                     <button type="button" class="ql-jvb_link">\n                        <i class="icon icon-link"></i>\n                    </button>\n                    ${e}\n                </span>\n            `,n.appendChild(l),n.appendChild(i),t.parentNode.insertBefore(n,t),t.style.display="none",i.innerHTML=t.value}const o=new Quill(i,{theme:"snow",modules:{toolbar:{container:l,handlers:{p:function(){this.quill.format("header",!1)},h1:function(){this.quill.format("header",1)},h2:function(){this.quill.format("header",2)},h3:function(){this.quill.format("header",3)},jvb_bold:function(){const t=this.quill.getFormat();this.quill.format("bold",!t.bold)},jvb_italic:function(){const t=this.quill.getFormat();this.quill.format("italic",!t.italic)},jvb_strike:function(){const t=this.quill.getFormat();this.quill.format("strike",!t.strike)},jvb_underline:function(){const t=this.quill.getFormat();this.quill.format("underline",!t.underline)},jvb_align:function(t){const n=this.quill.getFormat();this.quill.format("align",t!==n.align&&t)},jvb_list:function(t){const n=this.quill.getFormat();this.quill.format("list",t!==n.list&&t)},jvb_link:function(t){if(t){const t=this.quill.getSelection();if(null==t||0===t.length)return;const n=this.quill.getFormat(t).link,e=document.createElement("dialog");e.className="quill-link-modal",e.innerHTML=`\n                                    <div class="quill-link-modal-content ">\n                                        <label for="link">Enter URL</label>\n                                        <input type="url" id="link" placeholder="Enter URL" value="${n||""}" />\n                                        <div class="buttons">\n                                            <button type="button" class="save">Save</button>\n                                            ${n?'<button type="button" class="remove">Remove</button>':""}\n                                            <button type="button" class="cancel">Cancel</button>\n                                        </div>\n                                    </div>\n                                `,document.body.appendChild(e),e.showModal();const i=e.querySelector("input");i.focus(),e.querySelector(".save").addEventListener("click",(()=>{const t=i.value;t&&this.quill.format("link",t),e.remove()}));const l=e.querySelector(".remove");l&&l.addEventListener("click",(()=>{this.quill.format("link",!1),e.remove()})),e.querySelector(".cancel").addEventListener("click",(()=>{e.remove()})),i.addEventListener("keyup",(t=>{if("Enter"===t.key){const t=i.value;t&&this.quill.format("link",t),e.remove()}}))}},jvb_image:function(){const n=t.dataset.postId||t.closest("form")?.dataset.postId,e=document.createElement("input");e.setAttribute("type","file"),e.setAttribute("accept","image/jpeg,image/png,image/gif,image/webp"),e.style.display="none",document.body.appendChild(e),e.onchange=async t=>{const i=t.target.files?.[0];if(!i)return;if(i.size>5242880)return this.quill.insertText(l.index,"File too large. Maximum size is 5MB",{color:"#f00",italic:!0},!0),void e.remove();const l=this.quill.getSelection(!0),o=new FormData;o.append("image",i),n&&o.append("post_id",n);try{const t=await fetch(`${jvbSettings.api}uploads/`,{method:"POST",headers:{"X-WP-Nonce":window.auth.getNonce()},body:o});if(!t.ok)throw new Error("Upload failed");const n=await t.json();this.quill.insertEmbed(l.index,"image",n.url)}catch(t){console.error("Upload error:",t),this.quill.insertText(l.index,"Failed to upload image. Please try again.",{color:"#f00",italic:!0},!0)}finally{e.remove()}},e.click()}}},history:{delay:2e3,maxStack:500},clipboard:{matchVisual:!1}}});e.push(o),o.on("selection-change",(function(t){if(!t)return;const n=o.getFormat(t);Object.entries({"ql-jvb_bold":"bold","ql-jvb_italic":"italic","ql-jvb_underline":"underline","ql-jvb_strike":"strike"}).forEach((([t,e])=>{const i=l.querySelector(`.${t}`);i&&i.classList.toggle("active",!!n[e])})),l.querySelectorAll(".ql-jvb_list").forEach((t=>{const e=t.getAttribute("value");t.classList.toggle("ql-active",n.list===e)})),l.querySelectorAll(".ql-jvb_align").forEach((t=>{const e=t.getAttribute("value");t.classList.toggle("ql-active",n.align===e)}));const e=l.querySelector(".ql-align");if(e){if(0===t.length){const[n]=this.quill.getLeaf(t.index);if(n&&n.domNode&&"IMG"===n.domNode.tagName)return void(e.style.display="inline-block")}e.style.display="none"}})),o.on("text-change",(()=>{t.value=o.root.innerHTML,t.dispatchEvent(new Event("change",{bubbles:!0}))}))})),e};
\ No newline at end of file
+window.jvbQuill=function(t){const n=t.querySelectorAll("textarea[data-editor=true]"),e=[];return n.forEach(t=>{let n,i,l;if(t.parentNode.querySelector(".editor-container"))n=t.parentNode.querySelector(".editor-container"),i=n.querySelector(".editor"),l=n.querySelector(".toolbar");else{n=document.createElement("div"),n.className="editor-container",i=document.createElement("div"),i.className="editor",l=document.createElement("div"),l.className="toolbar";const e=!0===t.dataset.allowimage?`<button type="button" class="ql-jvb_image">\n                    ${window.getIcon("image")}\n                </button>`:"";l.id=`toolbar-${t.id}`,l.innerHTML=`\n                <span class="ql-formats">\n                    <button type="button" class="ql-p">\n                        <i class="icon icon-paragraph"></i>\n                    </button>\n                    <button type="button" class="ql-h1">\n                        <i class="icon icon-text-h-one"></i>\n                    </button>\n                    <button type="button" class="ql-h2">\n                        <i class="icon icon-text-h-two"></i>\n                    </button>\n                    <button type="button" class="ql-h3">\n                        <i class="icon icon-text-h-three"></i>\n                    </button>\n                </span>\n                <span class="ql-formats">\n                    <button type="button" class="ql-jvb_bold">\n                        <i class="icon icon-text-b-fi"></i>\n                    </button>\n                    <button type="button" class="ql-jvb_italic">\n                        <i class="icon icon-text-italic"></i>\n                    </button>\n                    <button type="button" class="ql-jvb_underline">\n                        <i class="icon icon-text-underline"></i>\n                    </button>\n                    <button type="button" class="ql-jvb_strike">\n                        <i class="icon icon-text-strikethrough"></i>\n                    </button>\n                </span>\n                <span class="ql-formats">\n                     <button type="button" class="ql-jvb_list" value="bullet">\n                        <i class="icon icon-list-dashes"></i>\n                    </button>\n                    <button type="button" class="ql-jvb_list" value="ordered">\n                        <i class="icon icon-list-numbers"></i>\n                    </button>\n                </span>\n                <span class="ql-formats">\n                     <button type="button" class="ql-jvb_align" value="left">\n                        <i class="icon icon-text-align-left"></i>\n                    </button>\n                     <button type="button" class="ql-jvb_align" value="center">\n                        <i class="icon icon-text-align-center"></i>\n                    </button>\n                     <button type="button" class="ql-jvb_align" value="right">\n                        <i class="icon icon-text-align-right"></i>\n                    </button>\n                </span>\n                <span class="ql-formats">\n                     <button type="button" class="ql-jvb_link">\n                        <i class="icon icon-link"></i>\n                    </button>\n                    ${e}\n                </span>\n            `,n.appendChild(l),n.appendChild(i),t.parentNode.insertBefore(n,t),t.style.display="none",i.innerHTML=t.value}const o=new Quill(i,{theme:"snow",modules:{toolbar:{container:l,handlers:{p:function(){this.quill.format("header",!1)},h1:function(){this.quill.format("header",1)},h2:function(){this.quill.format("header",2)},h3:function(){this.quill.format("header",3)},jvb_bold:function(){const t=this.quill.getFormat();this.quill.format("bold",!t.bold)},jvb_italic:function(){const t=this.quill.getFormat();this.quill.format("italic",!t.italic)},jvb_strike:function(){const t=this.quill.getFormat();this.quill.format("strike",!t.strike)},jvb_underline:function(){const t=this.quill.getFormat();this.quill.format("underline",!t.underline)},jvb_align:function(t){const n=this.quill.getFormat();this.quill.format("align",t!==n.align&&t)},jvb_list:function(t){const n=this.quill.getFormat();this.quill.format("list",t!==n.list&&t)},jvb_link:function(t){if(t){const t=this.quill.getSelection();if(null==t||0===t.length)return;const n=this.quill.getFormat(t).link,e=document.createElement("dialog");e.className="quill-link-modal",e.innerHTML=`\n                                    <div class="quill-link-modal-content ">\n                                        <label for="link">Enter URL</label>\n                                        <input type="url" id="link" placeholder="Enter URL" value="${n||""}" />\n                                        <div class="buttons">\n                                            <button type="button" class="save">Save</button>\n                                            ${n?'<button type="button" class="remove">Remove</button>':""}\n                                            <button type="button" class="cancel">Cancel</button>\n                                        </div>\n                                    </div>\n                                `,document.body.appendChild(e),e.showModal();const i=e.querySelector("input");i.focus(),e.querySelector(".save").addEventListener("click",()=>{const t=i.value;t&&this.quill.format("link",t),e.remove()});const l=e.querySelector(".remove");l&&l.addEventListener("click",()=>{this.quill.format("link",!1),e.remove()}),e.querySelector(".cancel").addEventListener("click",()=>{e.remove()}),i.addEventListener("keyup",t=>{if("Enter"===t.key){const t=i.value;t&&this.quill.format("link",t),e.remove()}})}},jvb_image:function(){const n=t.dataset.postId||t.closest("form")?.dataset.postId,e=document.createElement("input");e.setAttribute("type","file"),e.setAttribute("accept","image/jpeg,image/png,image/gif,image/webp"),e.style.display="none",document.body.appendChild(e),e.onchange=async t=>{const i=t.target.files?.[0];if(!i)return;if(i.size>5242880)return this.quill.insertText(l.index,"File too large. Maximum size is 5MB",{color:"#f00",italic:!0},!0),void e.remove();const l=this.quill.getSelection(!0),o=new FormData;o.append("image",i),n&&o.append("post_id",n);try{const t=await fetch(`${jvbSettings.api}uploads/`,{method:"POST",headers:{"X-WP-Nonce":window.auth.getNonce()},body:o});if(!t.ok)throw new Error("Upload failed");const n=await t.json();this.quill.insertEmbed(l.index,"image",n.url)}catch(t){console.error("Upload error:",t),this.quill.insertText(l.index,"Failed to upload image. Please try again.",{color:"#f00",italic:!0},!0)}finally{e.remove()}},e.click()}}},history:{delay:2e3,maxStack:500},clipboard:{matchVisual:!1}}});e.push(o),o.on("selection-change",function(t){if(!t)return;const n=o.getFormat(t);Object.entries({"ql-jvb_bold":"bold","ql-jvb_italic":"italic","ql-jvb_underline":"underline","ql-jvb_strike":"strike"}).forEach(([t,e])=>{const i=l.querySelector(`.${t}`);i&&i.classList.toggle("active",!!n[e])}),l.querySelectorAll(".ql-jvb_list").forEach(t=>{const e=t.getAttribute("value");t.classList.toggle("ql-active",n.list===e)}),l.querySelectorAll(".ql-jvb_align").forEach(t=>{const e=t.getAttribute("value");t.classList.toggle("ql-active",n.align===e)});const e=l.querySelector(".ql-align");if(e){if(0===t.length){const[n]=this.quill.getLeaf(t.index);if(n&&n.domNode&&"IMG"===n.domNode.tagName)return void(e.style.display="inline-block")}e.style.display="none"}}),o.on("text-change",()=>{t.value=o.root.innerHTML,t.dispatchEvent(new Event("change",{bubbles:!0}))})}),e};
\ No newline at end of file
diff --git a/assets/js/min/referral.min.js b/assets/js/min/referral.min.js
index c59de8c..75c6822 100644
--- a/assets/js/min/referral.min.js
+++ b/assets/js/min/referral.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.container=document.querySelector("aside.referral"),this.container&&(this.a11y=window.jvbA11y,this.toggle=document.querySelector('button[data-action="toggle-referral"]'),this.hasCopy=navigator.clipboard&&navigator.clipboard.writeText,this.initElements(),this.initStore(),this.initListeners(),this.checkForReferral())}initElements(){this.selectors={copyBtn:".copy-btn",checkCode:".check-code-btn",submit:"[type=submit]",recentList:".recent-referrals-list",stats:{codeUsed:'[data-stat="code_used"]',consultations:'[data-stat="consultations"]',treatments:'[data-stat="treatments"]',rewards:'[data-stat="total_rewards"]'}},this.forms=this.container.querySelectorAll("form"),this.popup=window.jvbPopup.registerPopup({toggle:this.toggle,popup:this.container,name:"Referral Box",onOpen:()=>{this.bindEventListeners(!0)},onClose:()=>{this.bindEventListeners(!1)}}),this.tabs=null,this.container.querySelector("nav.tabs")&&(this.tabs=window.jvbTabs.registerTab(this.container,{updateURL:!1})),this.ui=window.uiFromSelectors(this.selectors),this.hasCopy||document.querySelectorAll(this.selectors.copyBtn).forEach((e=>{e.remove()}))}initStore(){if(!this.isLoggedIn())return;const e=window.jvbStore.register("referrals",[{storeName:"stats",keyPath:"user_id",endpoint:"referrals/stats",TTL:3e5,showLoading:!1,delayFetch:!0,filters:{type:"dashboard",user:window.auth.getUser()}},{storeName:"list",keyPath:"id",endpoint:"referrals",TTL:6e5,showLoading:!1,delayFetch:!0,filters:{user:window.auth.getUser(),status:"all",limit:50,offset:0}}]);this.statsStore=e.stats,this.listStore=e.list,this.statsStore&&this.statsStore.subscribe(this.handleStatsEvent.bind(this)),this.listStore&&this.listStore.subscribe(this.handleListEvent.bind(this))}initListeners(){this.clickHandler=this.handleClick.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleFormSubmit.bind(this)}bindEventListeners(e){const t=e?"addEventListener":"removeEventListener";this.forms.forEach((e=>{e[t]("submit",this.submitHandler)})),this.container[t]("click",this.clickHandler),this.container[t]("input",this.inputHandler)}isLoggedIn(){return Boolean(window.auth.getUser())}handleStatsEvent(e,t){switch(e){case"data-loaded":t.items&&t.items.length>0&&this.updateStatsDisplay();break;case"fetch-error":console.error("Error loading stats:",t.error)}}handleListEvent(e,t){switch(e){case"data-loaded":this.ui.recentList&&this.renderRecentReferrals();break;case"fetch-error":console.error("Error loading referrals:",t.error)}}updateStatsDisplay(){if(0===!this.statsStore.data.size)return;let e=this.statsStore.data.get(parseInt(window.auth.getUser()));const t={total:e.code_used||0,treated:e.treatments||0,pending:e.pending||0,rewards:"$"+parseFloat(e.total_rewards||0).toFixed(2)};Object.entries(t).forEach((([e,t])=>{const r=this.container.querySelector(`[data-stat="${e}"]`);r&&(r.textContent=t)}));const r=this.container.querySelectorAll(".stats .card");r.length>=4&&(r[0].querySelector(".stat-number").textContent=t.code_used,r[1].querySelector(".stat-number").textContent=t.consultations,r[2].querySelector(".stat-number").textContent=t.treatments,r[3].querySelector(".stat-number").textContent=t.total_rewards)}handleClick(e){const t=e.target.closest(".copy-btn, .check-code-btn, .attn");t&&(t.classList.contains("copy-btn")?this.handleCopyClick(t):t.classList.contains("check-code-btn")?this.handleCheckCode(e):t.classList.contains("attn")&&t.classList.remove("attn"))}handleCopyClick(e){const t=e.dataset.target,r=this.container.querySelector(`#${t}`);if(!r)return;const s=r.textContent.trim();this.hasCopy&&navigator.clipboard.writeText(s).then((()=>{e.classList.toggle("success"),setTimeout((()=>{e.classList.remove("success")}),1500)}))}handleError(e,t){const{message:r,code:s,field:a}=t;switch(a?this.showFieldError(e,a,r):this.showFormStatus(e,"error",r||"Something went wrong. Please try again."),s){case"duplicate_email":break;case"invalid_code":const t=e.querySelector('[name="referral_code"]');t&&(t.readOnly=!1,t.focus());break;case"turnstile_failed":window.turnstile&&e.querySelector(".cf-turnstile")&&window.turnstile.reset()}}showFieldError(e,t,r){let s=e.querySelector(`.field[data-field="${t}"]`);if(s||(s=e.querySelector(`.field[data-field="referral_${t}"]`)),!s)return void this.showFormStatus(e,"error",r);const a=s.querySelector("input, textarea, select"),i=s.querySelector(".validation-message"),n=s.querySelector(".validation-icon.error"),o=s.querySelector(".validation-icon.success");a?(s.classList.remove("has-success"),s.classList.add("has-error"),a.classList.add("error"),a.setAttribute("aria-invalid","true"),n&&(n.hidden=!1),o&&(o.hidden=!0),i&&(i.textContent=r,i.hidden=!1),a.focus(),this.a11y?.announce(`Error in ${t}: ${r}`)):this.showFormStatus(e,"error",r)}showFormStatus(e,t,r=""){const s=e.querySelector(".fstatus");if(!s)return void console.warn("No .fstatus element found in form");s.hidden=!1;const a=s.querySelector(".message");s.querySelector(".icon")?.remove(),s.querySelector(".actions")?.remove();const i={saving:"Sending...",submitted:"Sent successfully!",error:"Something went wrong",checking:"Checking code..."},n={submitted:"check-circle",error:"close-circle",checking:"loading"};if(n[t]&&window.getIcon){const e=window.getIcon(n[t]);e&&s.prepend(e)}a&&(a.textContent=r||i[t]||t),s.classList.toggle("loading",["saving","checking"].includes(t)),"submitted"===t&&setTimeout((()=>s.hidden=!0),3e3),this.a11y&&this.a11y.announce(r||i[t]||t)}clearFormErrors(e){e.querySelectorAll(".field.has-error, .field.has-success").forEach((e=>{this.clearFieldValidation(e)}));const t=e.querySelector(".fstatus");t&&(t.hidden=!0)}clearFieldValidation(e){if(!e)return;const t=e.querySelector("input, textarea, select"),r=e.querySelector(".validation-message"),s=e.querySelectorAll(".validation-icon");e.classList.remove("has-error","has-success"),t&&(t.classList.remove("error"),t.removeAttribute("aria-invalid")),s.forEach((e=>e.hidden=!0)),r&&(r.hidden=!0,r.textContent="")}handleInput(e){"referral_code"!==e.target.id&&"referral_code"!==e.target.name||(e.target.value=e.target.value.toUpperCase());const t=e.target.closest(".field");t&&t.classList.contains("has-error")&&this.clearFieldValidation(t)}async handleCheckCode(e){e.preventDefault();const t=e.target.closest("form"),r=t.querySelector('[name="referral_code"]'),s=t.querySelector(".code-status");if(!r||!s)return;const a=r.value.trim();if(a){s.hidden=!1,s.className="code-status loading",s.innerHTML='<span class="spinner"></span> Checking...';try{const e=await this.validateCodeOnly(a);e.success?this.showCodeStatus(s,`✓ Valid! Referred by ${e.referrer_name}`,"success"):this.showCodeStatus(s,e.message||"Invalid code","error")}catch(e){console.error("Error checking code:",e),this.showCodeStatus(s,"Error checking code","error")}}else this.showCodeStatus(s,"Please enter a code","error")}showCodeStatus(e,t,r){e.hidden=!1,e.className=`code-status ${r}`,e.textContent=t,"error"===r&&setTimeout((()=>{e.hidden=!0}),5e3)}async checkForReferral(){const e=this.getUrlParameter("ref"),t=this.getUrlParameter("rname"),r=this.getUrlParameter("remail"),s=this.getUrlParameter("seeReferral");if(!e&&!s)return;if(s&&!e)return this.popup.openPopup(),void this.removeUrlParameter("seeReferral");const a=this.container.querySelector('[name="referral_code"]');if(!a)return;const i=e.toUpperCase();if(a.value=i,a.readOnly=!0,t||r){const e=this.container.querySelector('[name="referral_name"]');e&&(e.value=t);const s=this.container.querySelector('[name="referral_email"]');s&&(s.value=r)}this.popup.openPopup();try{const e=await this.validateCodeOnly(i);if(e.success){const t=a.closest("form").querySelector(".code-status");t&&this.showCodeStatus(t,`✓ ${e.referrer_name} invited you!`,"success");const r=this.container.querySelector('[name="referral_name"]');r&&!r.value&&r.focus()}else a.readOnly=!1,this.showMessage("This referral link is invalid. Please enter a valid code.","error")}catch(e){console.error("Error validating code:",e),a.readOnly=!1}this.removeUrlParameter("ref"),this.removeUrlParameter("rname"),this.removeUrlParameter("remail")}getUrlParameter(e){return new URLSearchParams(window.location.search).get(e)}removeUrlParameter(e){const t=new URL(window.location);t.searchParams.delete(e),window.history.replaceState({},document.title,t.toString())}async validateCodeOnly(e){const t=await fetch(`${jvbSettings.api}referrals/code`,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce()},body:JSON.stringify({code:e})});return await t.json()}renderRecentReferrals(){let e=this.ui.recentList,t=Array.from(this.listStore.data.values());t&&0!==t.length?e.innerHTML=t.map((e=>`\n\t\t\t<div class="referral-item">\n\t\t\t\t<div class="referral-info">\n\t\t\t\t\t<strong>${window.escapeHtml(e.referee_name)}</strong>\n\t\t\t\t\t<span class="status-badge">${e.referral_status}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class="referral-date">${window.formatTimeAgo(e.referred_at)}</div>\n\t\t\t</div>\n\t\t`)).join(""):e.innerHTML='<p class="no-referrals">Share your code to get started!</p>'}async handleFormSubmit(e){e.preventDefault();const t=e.target,r=new FormData(t);this.clearFormErrors(t),this.setFormLoading(!0,t);try{let e={success:!1,message:""};if("referral-code-form"===t.id){let s={name:r.get("referral_name"),email:r.get("referral_email"),referral_code:r.get("referral_code")};const a=t.querySelector('input[name="cf-turnstile-response"]');a&&a.value&&(s["cf-turnstile-response"]=a.value),s.name&&s.email&&s.referral_code?e=await this.makeRequest("auth/register",s):e.message="Please fill in all fields"}else if("login-form"===t.id){let s={type:"login",user_email:r.get("login_email"),context:{redirect_to:window.location.href+"?seeReferral=1"}};const a=t.querySelector('input[name="cf-turnstile-response"]');a&&a.value&&(s["cf-turnstile-response"]=a.value),s.user_email?e=await this.makeRequest("auth/magic",s):e.message="Please fill in your email"}e.success?this.handleSuccess(t,e):this.handleError(t,e)}catch(e){console.error("Error submitting form:",e),this.showFormMessage(t,"Something went wrong. Please try again.","error")}finally{this.setFormLoading(!1,t)}}async makeRequest(e,t){if(!["auth/magic","auth/register"].includes(e))return{success:!1,message:"Invalid endpoint"};const r=await window.auth.fetch(`${jvbSettings.api}${e}`,{method:"POST",body:JSON.stringify(t)});if(!r.ok){const e=await r.text();console.error("Error response:",r.status,e);try{return JSON.parse(e)}catch{return{success:!1,message:"Server error"}}}return await r.json()}handleSuccess(e,t){e.style.display="none";const r=e.nextElementSibling;r&&r.classList.contains("success-content")&&(r.hidden=!1,r.scrollIntoView({behavior:"smooth",block:"center"})),this.dispatchEvent("emailSent",{email:t.email})}showFormMessage(e,t,r="error"){const s=e.querySelector(".status");if(!s)return;const a=s.querySelector(".message");a&&(a.textContent=t),s.hidden=!1,s.className=`status ${r}`,"error"===r&&setTimeout((()=>{s.hidden=!0}),5e3)}setFormLoading(e,t){t.querySelectorAll("input, button, textarea, select").forEach((t=>t.disabled=e)),e&&this.showFormStatus(t,"saving")}dispatchEvent(e,t){const r=new CustomEvent("referralWidget:"+e,{detail:t,bubbles:!0});this.container.dispatchEvent(r)}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbReferral=new e)}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.container=document.querySelector("aside.referral"),this.container&&(this.a11y=window.jvbA11y,this.toggle=document.querySelector('button[data-action="toggle-referral"]'),this.hasCopy=navigator.clipboard&&navigator.clipboard.writeText,this.initElements(),this.initStore(),this.initListeners(),this.checkForReferral())}initElements(){this.selectors={copyBtn:".copy-btn",checkCode:".check-code-btn",submit:"[type=submit]",recentList:".recent-referrals-list",stats:{codeUsed:'[data-stat="code_used"]',consultations:'[data-stat="consultations"]',treatments:'[data-stat="treatments"]',rewards:'[data-stat="total_rewards"]'}},this.forms=this.container.querySelectorAll("form"),this.popup=window.jvbPopup.registerPopup({toggle:this.toggle,popup:this.container,name:"Referral Box",onOpen:()=>{this.bindEventListeners(!0)},onClose:()=>{this.bindEventListeners(!1)}}),this.tabs=null,this.container.querySelector("nav.tabs")&&(this.tabs=window.jvbTabs.registerTab(this.container,{updateURL:!1})),this.ui=window.uiFromSelectors(this.selectors),this.hasCopy||document.querySelectorAll(this.selectors.copyBtn).forEach(e=>{e.remove()})}initStore(){if(!this.isLoggedIn())return;const e=window.jvbStore.register("referrals",[{storeName:"stats",keyPath:"user_id",endpoint:"referrals/stats",TTL:3e5,showLoading:!1,delayFetch:!0,filters:{type:"dashboard",user:window.auth.getUser()}},{storeName:"list",keyPath:"id",endpoint:"referrals",TTL:6e5,showLoading:!1,delayFetch:!0,filters:{user:window.auth.getUser(),status:"all",limit:50,offset:0}}]);this.statsStore=e.stats,this.listStore=e.list,this.statsStore&&this.statsStore.subscribe(this.handleStatsEvent.bind(this)),this.listStore&&this.listStore.subscribe(this.handleListEvent.bind(this))}initListeners(){this.clickHandler=this.handleClick.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleFormSubmit.bind(this)}bindEventListeners(e){const t=e?"addEventListener":"removeEventListener";this.forms.forEach(e=>{e[t]("submit",this.submitHandler)}),this.container[t]("click",this.clickHandler),this.container[t]("input",this.inputHandler)}isLoggedIn(){return Boolean(window.auth.getUser())}handleStatsEvent(e,t){switch(e){case"data-loaded":t.items&&t.items.length>0&&this.updateStatsDisplay();break;case"fetch-error":console.error("Error loading stats:",t.error)}}handleListEvent(e,t){switch(e){case"data-loaded":this.ui.recentList&&this.renderRecentReferrals();break;case"fetch-error":console.error("Error loading referrals:",t.error)}}updateStatsDisplay(){if(0===!this.statsStore.data.size)return;let e=this.statsStore.data.get(parseInt(window.auth.getUser()));const t={total:e.code_used||0,treated:e.treatments||0,pending:e.pending||0,rewards:"$"+parseFloat(e.total_rewards||0).toFixed(2)};Object.entries(t).forEach(([e,t])=>{const r=this.container.querySelector(`[data-stat="${e}"]`);r&&(r.textContent=t)});const r=this.container.querySelectorAll(".stats .card");r.length>=4&&(r[0].querySelector(".stat-number").textContent=t.code_used,r[1].querySelector(".stat-number").textContent=t.consultations,r[2].querySelector(".stat-number").textContent=t.treatments,r[3].querySelector(".stat-number").textContent=t.total_rewards)}handleClick(e){const t=e.target.closest(".copy-btn, .check-code-btn, .attn");t&&(t.classList.contains("copy-btn")?this.handleCopyClick(t):t.classList.contains("check-code-btn")?this.handleCheckCode(e):t.classList.contains("attn")&&t.classList.remove("attn"))}handleCopyClick(e){const t=e.dataset.target,r=this.container.querySelector(`#${t}`);if(!r)return;const s=r.textContent.trim();this.hasCopy&&navigator.clipboard.writeText(s).then(()=>{e.classList.toggle("success"),setTimeout(()=>{e.classList.remove("success")},1500)})}handleError(e,t){const{message:r,code:s,field:a}=t;switch(a?this.showFieldError(e,a,r):this.showFormStatus(e,"error",r||"Something went wrong. Please try again."),s){case"duplicate_email":break;case"invalid_code":const t=e.querySelector('[name="referral_code"]');t&&(t.readOnly=!1,t.focus());break;case"turnstile_failed":window.turnstile&&e.querySelector(".cf-turnstile")&&window.turnstile.reset()}}showFieldError(e,t,r){let s=e.querySelector(`.field[data-field="${t}"]`);if(s||(s=e.querySelector(`.field[data-field="referral_${t}"]`)),!s)return void this.showFormStatus(e,"error",r);const a=s.querySelector("input, textarea, select"),i=s.querySelector(".validation-message"),n=s.querySelector(".validation-icon.error"),o=s.querySelector(".validation-icon.success");a?(s.classList.remove("has-success"),s.classList.add("has-error"),a.classList.add("error"),a.setAttribute("aria-invalid","true"),n&&(n.hidden=!1),o&&(o.hidden=!0),i&&(i.textContent=r,i.hidden=!1),a.focus(),this.a11y?.announce(`Error in ${t}: ${r}`)):this.showFormStatus(e,"error",r)}showFormStatus(e,t,r=""){const s=e.querySelector(".fstatus");if(!s)return void console.warn("No .fstatus element found in form");s.hidden=!1;const a=s.querySelector(".message");s.querySelector(".icon")?.remove(),s.querySelector(".actions")?.remove();const i={saving:"Sending...",submitted:"Sent successfully!",error:"Something went wrong",checking:"Checking code..."},n={submitted:"check-circle",error:"close-circle",checking:"loading"};if(n[t]&&window.getIcon){const e=window.getIcon(n[t]);e&&s.prepend(e)}a&&(a.textContent=r||i[t]||t),s.classList.toggle("loading",["saving","checking"].includes(t)),"submitted"===t&&setTimeout(()=>s.hidden=!0,3e3),this.a11y&&this.a11y.announce(r||i[t]||t)}clearFormErrors(e){e.querySelectorAll(".field.has-error, .field.has-success").forEach(e=>{this.clearFieldValidation(e)});const t=e.querySelector(".fstatus");t&&(t.hidden=!0)}clearFieldValidation(e){if(!e)return;const t=e.querySelector("input, textarea, select"),r=e.querySelector(".validation-message"),s=e.querySelectorAll(".validation-icon");e.classList.remove("has-error","has-success"),t&&(t.classList.remove("error"),t.removeAttribute("aria-invalid")),s.forEach(e=>e.hidden=!0),r&&(r.hidden=!0,r.textContent="")}handleInput(e){"referral_code"!==e.target.id&&"referral_code"!==e.target.name||(e.target.value=e.target.value.toUpperCase());const t=e.target.closest(".field");t&&t.classList.contains("has-error")&&this.clearFieldValidation(t)}async handleCheckCode(e){e.preventDefault();const t=e.target.closest("form"),r=t.querySelector('[name="referral_code"]'),s=t.querySelector(".code-status");if(!r||!s)return;const a=r.value.trim();if(a){s.hidden=!1,s.className="code-status loading",s.innerHTML='<span class="spinner"></span> Checking...';try{const e=await this.validateCodeOnly(a);e.success?this.showCodeStatus(s,`✓ Valid! Referred by ${e.referrer_name}`,"success"):this.showCodeStatus(s,e.message||"Invalid code","error")}catch(e){console.error("Error checking code:",e),this.showCodeStatus(s,"Error checking code","error")}}else this.showCodeStatus(s,"Please enter a code","error")}showCodeStatus(e,t,r){e.hidden=!1,e.className=`code-status ${r}`,e.textContent=t,"error"===r&&setTimeout(()=>{e.hidden=!0},5e3)}async checkForReferral(){const e=this.getUrlParameter("ref"),t=this.getUrlParameter("rname"),r=this.getUrlParameter("remail"),s=this.getUrlParameter("seeReferral");if(!e&&!s)return;if(s&&!e)return this.popup.openPopup(),void this.removeUrlParameter("seeReferral");const a=this.container.querySelector('[name="referral_code"]');if(!a)return;const i=e.toUpperCase();if(a.value=i,a.readOnly=!0,t||r){const e=this.container.querySelector('[name="referral_name"]');e&&(e.value=t);const s=this.container.querySelector('[name="referral_email"]');s&&(s.value=r)}this.popup.openPopup();try{const e=await this.validateCodeOnly(i);if(e.success){const t=a.closest("form").querySelector(".code-status");t&&this.showCodeStatus(t,`✓ ${e.referrer_name} invited you!`,"success");const r=this.container.querySelector('[name="referral_name"]');r&&!r.value&&r.focus()}else a.readOnly=!1,this.showMessage("This referral link is invalid. Please enter a valid code.","error")}catch(e){console.error("Error validating code:",e),a.readOnly=!1}this.removeUrlParameter("ref"),this.removeUrlParameter("rname"),this.removeUrlParameter("remail")}getUrlParameter(e){return new URLSearchParams(window.location.search).get(e)}removeUrlParameter(e){const t=new URL(window.location);t.searchParams.delete(e),window.history.replaceState({},document.title,t.toString())}async validateCodeOnly(e){const t=await fetch(`${jvbSettings.api}referrals/code`,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce()},body:JSON.stringify({code:e})});return await t.json()}renderRecentReferrals(){let e=this.ui.recentList,t=Array.from(this.listStore.data.values());t&&0!==t.length?e.innerHTML=t.map(e=>`\n\t\t\t<div class="referral-item">\n\t\t\t\t<div class="referral-info">\n\t\t\t\t\t<strong>${window.escapeHtml(e.referee_name)}</strong>\n\t\t\t\t\t<span class="status-badge">${e.referral_status}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class="referral-date">${window.formatTimeAgo(e.referred_at)}</div>\n\t\t\t</div>\n\t\t`).join(""):e.innerHTML='<p class="no-referrals">Share your code to get started!</p>'}async handleFormSubmit(e){e.preventDefault();const t=e.target,r=new FormData(t);this.clearFormErrors(t),this.setFormLoading(!0,t);try{let e={success:!1,message:""};if("referral-code-form"===t.id){let s={name:r.get("referral_name"),email:r.get("referral_email"),referral_code:r.get("referral_code")};const a=t.querySelector('input[name="cf-turnstile-response"]');a&&a.value&&(s["cf-turnstile-response"]=a.value),s.name&&s.email&&s.referral_code?e=await this.makeRequest("auth/register",s):e.message="Please fill in all fields"}else if("login-form"===t.id){let s={type:"login",user_email:r.get("login_email"),context:{redirect_to:window.location.href+"?seeReferral=1"}};const a=t.querySelector('input[name="cf-turnstile-response"]');a&&a.value&&(s["cf-turnstile-response"]=a.value),s.user_email?e=await this.makeRequest("auth/magic",s):e.message="Please fill in your email"}e.success?this.handleSuccess(t,e):this.handleError(t,e)}catch(e){console.error("Error submitting form:",e),this.showFormMessage(t,"Something went wrong. Please try again.","error")}finally{this.setFormLoading(!1,t)}}async makeRequest(e,t){if(!["auth/magic","auth/register"].includes(e))return{success:!1,message:"Invalid endpoint"};const r=await window.auth.fetch(`${jvbSettings.api}${e}`,{method:"POST",body:JSON.stringify(t)});if(!r.ok){const e=await r.text();console.error("Error response:",r.status,e);try{return JSON.parse(e)}catch{return{success:!1,message:"Server error"}}}return await r.json()}handleSuccess(e,t){e.style.display="none";const r=e.nextElementSibling;r&&r.classList.contains("success-content")&&(r.hidden=!1,r.scrollIntoView({behavior:"smooth",block:"center"})),this.dispatchEvent("emailSent",{email:t.email})}showFormMessage(e,t,r="error"){const s=e.querySelector(".status");if(!s)return;const a=s.querySelector(".message");a&&(a.textContent=t),s.hidden=!1,s.className=`status ${r}`,"error"===r&&setTimeout(()=>{s.hidden=!0},5e3)}setFormLoading(e,t){t.querySelectorAll("input, button, textarea, select").forEach(t=>t.disabled=e),e&&this.showFormStatus(t,"saving")}dispatchEvent(e,t){const r=new CustomEvent("referralWidget:"+e,{detail:t,bubbles:!0});this.container.dispatchEvent(r)}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbReferral=new e)})})})();
\ No newline at end of file
diff --git a/assets/js/min/referralAdmin.min.js b/assets/js/min/referralAdmin.min.js
index 6c0bfd7..2d646ed 100644
--- a/assets/js/min/referralAdmin.min.js
+++ b/assets/js/min/referralAdmin.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.referral=window.jvbReferral,this.hasCopy=navigator.clipboard&&navigator.clipboard.writeText,this.initElements(),this.initListeners()}initElements(){this.selectors={copyBtn:".copy-btn",invite:"form.invite",adminList:".items-list.referral",dash:".replace .referral-dashboard",list:".referrals-list"},this.ui=window.uiFromSelectors(this.selectors),this.tabs=null,this.ui.dash&&(this.tabs=window.jvbTabs.registerTab(this.ui.dash),this.initViewController()),this.ui.invite&&(this.formController=window.jvbForm,this.formConfig=this.formController.registerForm(this.ui.invite,{autosave:!0,endpoint:"referrals",formStatus:!1}),this.formController.subscribe(((e,t)=>{if("form-submit"!==e)return;const i={...t.data,action:"invite"};window.jvbQueue.addToQueue({endpoint:"referrals",data:i,title:"Submitting invitations"}),this.formController.clearForm(this.formConfig.id);let s=document.querySelector('.referral-dashboard button[type="submit"]'),r=s.innerHTML;s.innerText="Invites sent to server. In line for processing.",window.debouncer.schedule("referral-submit",(function(){s.innerHTML=r}),3e3)})))}initListeners(){this.clickHandler=this.handleClick.bind(this),document.addEventListener("click",this.clickHandler),window.jvbQueue&&window.jvbQueue.subscribe(this.handleQueueEvent.bind(this))}handleClick(e){const t=e.target.closest(".copy-btn");t&&this.handleCopyClick(t)}handleCopyClick(e){const t=e.dataset.target,i=e.closest(".row").querySelector(`#${t}`);if(!i)return;const s=i.textContent.trim();this.hasCopy&&navigator.clipboard.writeText(s).then((()=>{e.classList.toggle("success"),setTimeout((()=>{e.classList.remove("success")}),1500)}))}initViewController(){this.referral.listStore&&this.ui.adminList&&(this.view=new window.jvbViews(this.ui.adminList,this.referral.listStore),this.view.subscribe(((e,t)=>{switch(e){case"item-action":this.handleItemAction(t);break;case"bulk-action":this.handleBulkAction(t)}})))}handleItemAction(e){const{action:t,itemId:i}=e;switch(t){case"remove":this.removeReferral(i);break;case"resend":this.resendInvite(i)}}async removeReferral(e){if(confirm("Remove this referral from your list?"))try{const t=await fetch(`${jvbSettings.api}referrals`,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce()},body:JSON.stringify({action:"remove",referral_id:e})});(await t.json()).success&&(this.referral.listStore&&this.referral.listStore.fetch(),this.referral.statsStore&&this.referral.statsStore.fetch(),this.a11y?.announce("Referral removed"))}catch(e){console.error("Error removing referral:",e)}}async resendInvite(e){try{const t=await fetch(`${jvbSettings.api}referrals`,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce()},body:JSON.stringify({action:"resend",referral_id:e})}),i=await t.json();i.success?this.a11y?.announce("Invitation resent"):alert(i.message||"Cannot resend yet. Wait 7 days between invites.")}catch(e){console.error("Error resending invite:",e)}}handleQueueEvent(e,t){"operation-complete"===e&&t.details&&(t.details.successful||t.details.failed)&&this.showInviteResults(t.details)}showInviteResults(e){if(!this.ui.invite)return;const t=this.ui.invite.querySelector('[data-field="invite"]');if(!t)return;const i=t.querySelector(".tag-list");if(!i)return;const s=new Map;e.successful?.forEach((e=>{s.set(e.email,{status:"success",name:e.name})})),e.failed?.forEach((e=>{s.set(e.email,{status:"error",name:e.name,reason:e.reason})}));i.querySelectorAll(".tag").forEach((e=>{const t=JSON.parse(e.dataset.value||"{}"),i=s.get(t.email);i&&this.updateTagStatus(e,i)})),this.showInviteSummary(e)}updateTagStatus(e,t){e.classList.remove("success","error");const i=e.querySelector(".status-icon");i&&i.remove(),e.classList.add(t.status);const s=document.createElement("span");s.className="status-icon",s.innerHTML="success"===t.status?window.jvbIcon("check-circle",{size:14}):window.jvbIcon("warning-circle",{size:14}),t.reason&&(s.title=t.reason);const r=e.querySelector(".remove-tag");r?e.insertBefore(s,r):e.appendChild(s)}showInviteSummary(e){const t=e.successful?.length||0,i=e.failed?.length||0;let s=`Invites sent! ${t} successful`;i>0&&(s+=`, ${i} failed`),this.formController&&this.formController.showStatus(this.ui.invite,"submitted",s),i>0&&this.showFailureDetails(e.failed)}showFailureDetails(e){const t=`\n        <details class="invite-failures" open>\n            <summary>${e.length} invitation(s) failed - click for details</summary>\n            <ul>\n                ${e.map((e=>`\n                    <li>\n                        <strong>${window.escapeHtml(e.name)}</strong>\n                        (${window.escapeHtml(e.email)}):\n                        <em>${window.escapeHtml(e.reason)}</em>\n                    </li>\n                `)).join("")}\n            </ul>\n        </details>\n    `,i=this.ui.invite.querySelector(".fstatus");if(i){const e=i.querySelector(".invite-failures");e&&e.remove(),i.insertAdjacentHTML("beforeend",t)}}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbAdminReferral=new e)}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.referral=window.jvbReferral,this.hasCopy=navigator.clipboard&&navigator.clipboard.writeText,this.initElements(),this.initListeners()}initElements(){this.selectors={copyBtn:".copy-btn",invite:"form.invite",adminList:".items-list.referral",dash:".replace .referral-dashboard",list:".referrals-list"},this.ui=window.uiFromSelectors(this.selectors),this.tabs=null,this.ui.dash&&(this.tabs=window.jvbTabs.registerTab(this.ui.dash),this.initViewController()),this.ui.invite&&(this.formController=window.jvbForm,this.formConfig=this.formController.registerForm(this.ui.invite,{autosave:!0,endpoint:"referrals",formStatus:!1}),this.formController.subscribe((e,t)=>{if("form-submit"!==e)return;const i={...t.data,action:"invite"};window.jvbQueue.addToQueue({endpoint:"referrals",data:i,title:"Submitting invitations"}),this.formController.clearForm(this.formConfig.id);let s=document.querySelector('.referral-dashboard button[type="submit"]'),r=s.innerHTML;s.innerText="Invites sent to server. In line for processing.",window.debouncer.schedule("referral-submit",function(){s.innerHTML=r},3e3)}))}initListeners(){this.clickHandler=this.handleClick.bind(this),document.addEventListener("click",this.clickHandler),window.jvbQueue&&window.jvbQueue.subscribe(this.handleQueueEvent.bind(this))}handleClick(e){const t=e.target.closest(".copy-btn");t&&this.handleCopyClick(t)}handleCopyClick(e){const t=e.dataset.target,i=e.closest(".row").querySelector(`#${t}`);if(!i)return;const s=i.textContent.trim();this.hasCopy&&navigator.clipboard.writeText(s).then(()=>{e.classList.toggle("success"),setTimeout(()=>{e.classList.remove("success")},1500)})}initViewController(){this.referral.listStore&&this.ui.adminList&&(this.view=new window.jvbViews(this.ui.adminList,this.referral.listStore),this.view.subscribe((e,t)=>{switch(e){case"item-action":this.handleItemAction(t);break;case"bulk-action":this.handleBulkAction(t)}}))}handleItemAction(e){const{action:t,itemId:i}=e;switch(t){case"remove":this.removeReferral(i);break;case"resend":this.resendInvite(i)}}async removeReferral(e){if(confirm("Remove this referral from your list?"))try{const t=await fetch(`${jvbSettings.api}referrals`,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce()},body:JSON.stringify({action:"remove",referral_id:e})});(await t.json()).success&&(this.referral.listStore&&this.referral.listStore.fetch(),this.referral.statsStore&&this.referral.statsStore.fetch(),this.a11y?.announce("Referral removed"))}catch(e){console.error("Error removing referral:",e)}}async resendInvite(e){try{const t=await fetch(`${jvbSettings.api}referrals`,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":window.auth.getNonce()},body:JSON.stringify({action:"resend",referral_id:e})}),i=await t.json();i.success?this.a11y?.announce("Invitation resent"):alert(i.message||"Cannot resend yet. Wait 7 days between invites.")}catch(e){console.error("Error resending invite:",e)}}handleQueueEvent(e,t){"operation-complete"===e&&t.details&&(t.details.successful||t.details.failed)&&this.showInviteResults(t.details)}showInviteResults(e){if(!this.ui.invite)return;const t=this.ui.invite.querySelector('[data-field="invite"]');if(!t)return;const i=t.querySelector(".tag-list");if(!i)return;const s=new Map;e.successful?.forEach(e=>{s.set(e.email,{status:"success",name:e.name})}),e.failed?.forEach(e=>{s.set(e.email,{status:"error",name:e.name,reason:e.reason})});i.querySelectorAll(".tag").forEach(e=>{const t=JSON.parse(e.dataset.value||"{}"),i=s.get(t.email);i&&this.updateTagStatus(e,i)}),this.showInviteSummary(e)}updateTagStatus(e,t){e.classList.remove("success","error");const i=e.querySelector(".status-icon");i&&i.remove(),e.classList.add(t.status);const s=document.createElement("span");s.className="status-icon",s.innerHTML="success"===t.status?window.jvbIcon("check-circle",{size:14}):window.jvbIcon("warning-circle",{size:14}),t.reason&&(s.title=t.reason);const r=e.querySelector(".remove-tag");r?e.insertBefore(s,r):e.appendChild(s)}showInviteSummary(e){const t=e.successful?.length||0,i=e.failed?.length||0;let s=`Invites sent! ${t} successful`;i>0&&(s+=`, ${i} failed`),this.formController&&this.formController.showStatus(this.ui.invite,"submitted",s),i>0&&this.showFailureDetails(e.failed)}showFailureDetails(e){const t=`\n        <details class="invite-failures" open>\n            <summary>${e.length} invitation(s) failed - click for details</summary>\n            <ul>\n                ${e.map(e=>`\n                    <li>\n                        <strong>${window.escapeHtml(e.name)}</strong>\n                        (${window.escapeHtml(e.email)}):\n                        <em>${window.escapeHtml(e.reason)}</em>\n                    </li>\n                `).join("")}\n            </ul>\n        </details>\n    `,i=this.ui.invite.querySelector(".fstatus");if(i){const e=i.querySelector(".invite-failures");e&&e.remove(),i.insertAdjacentHTML("beforeend",t)}}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbAdminReferral=new e)})})})();
\ No newline at end of file
diff --git a/assets/js/min/schema.min.js b/assets/js/min/schema.min.js
index d2db817..209bbaf 100644
--- a/assets/js/min/schema.min.js
+++ b/assets/js/min/schema.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.formController=null,this.tabsInstance=null,this.queue=window.jvbQueue,this.a11y=window.jvbA11y,this.init()}init(){if(this.formController=window.formController,window.jvbTabs){const e=document.querySelector(".jvb-seo-admin");e&&(this.tabsInstance=window.jvbTabs.registerTab(e))}this.formController&&this.formController.subscribe(((e,t)=>{"form-submit"===e&&this.handleFormSubmit(t)})),this.queue&&this.queue.subscribe(((e,t)=>{Object.hasOwn(t,"endpoint")&&"seo"===t.endpoint&&("operation-completed"===e?this.handleQueueSuccess(e,t):"operation-failed-permanent"===e&&this.handleQueueFailure(e,t))})),this.initializeForms(),this.addPreservedFieldStyles()}initializeForms(){document.querySelectorAll('form[data-save="seo"]').forEach((e=>{this.formController&&this.formController.registerForm(e,{endpoint:"seo",autosave:!1,formStatus:!1}),this.initializeTypeSwitch(e);const t=e.querySelector('[data-action="reset"]');t&&t.addEventListener("click",(()=>this.handleReset(e)))}))}handleFormSubmit(e){const t=e.config.element.dataset.content,n=e.fullData,o={endpoint:"seo",headers:{"X-WP-Nonce":window.auth.getNonce()},data:{context:t,action:"save",...n},popup:"Saving SEO configuration",title:`Saving ${t} settings`};this.queue.addToQueue(o)}async handleReset(e){const t=e.dataset.content;if(!confirm("Reset to default settings? This cannot be undone."))return;const n={endpoint:"seo",headers:{"X-WP-Nonce":window.auth.getNonce()},data:{context:t,action:"reset"},popup:"Resetting configuration",title:`Resetting ${t} to defaults`};this.queue.addToQueue(n)}handleQueueSuccess(e,t){console.log("SEO save successful:",t),this.a11y&&"function"==typeof this.a11y.announce&&this.a11y.announce("Configuration saved successfully"),"reset"===t.operation?.data?.action&&t.response?.schema&&this.reloadFormData(t.operation.data.context,t.response)}handleQueueFailure(e,t){console.error("SEO operation failed permanently:",t),this.a11y&&"function"==typeof this.a11y.announce&&this.a11y.announce(`Error: ${t.error_message||"Operation failed"}`)}reloadFormData(e,t){const n=document.querySelector(`form[data-content="${e}"]`);if(!n)return;const o=t.schema||{};Object.keys(o).forEach((e=>{const t=n.querySelector(`[name="${e}"]`);t&&("checkbox"===t.type?t.checked=!!o[e]:t.value=o[e]||"")})),this.a11y&&"function"==typeof this.a11y.announce&&this.a11y.announce("Form reset to defaults")}initializeTypeSwitch(e){const t=e.querySelector('select[name="type"]');t&&(t.addEventListener("change",(n=>{const o=e.dataset.currentType||t.dataset.initialValue,a=n.target.value;o!==a&&this.confirmTypeChange(e,t,o,a)})),t.dataset.initialValue=t.value,e.dataset.currentType=t.value)}confirmTypeChange(e,t,n,o){const a={},s=new FormData(e);for(let[e,t]of s.entries())"type"!==e&&t&&""!==t&&(a[e]=t);const i=window.getTemplate(`seo-${o}`);if(!i)return console.error("No template found for type:",o),void(t.value=n);const r=e=>e.split(":")[0],c=new Set(Object.keys(a).map(r)),l=i.querySelectorAll("[data-field]"),u=new Set(Array.from(l).map((e=>e.dataset.field)));if(0===u.size){const e=i.querySelectorAll("[name]");Array.from(e).forEach((e=>{u.add(r(e.getAttribute("name")))}))}const d=[...c].filter((e=>u.has(e))),h=[...c].filter((e=>!u.has(e)));let p=`Change schema type from ${n} to ${o}?\n\n`;d.length>0&&(p+=`✓ ${d.length} field value(s) will be preserved:\n`,p+=d.map((e=>`  • ${e}`)).join("\n"),p+="\n\n"),h.length>0&&(p+=`⚠ ${h.length} field value(s) will be lost:\n`,p+=h.map((e=>`  • ${e}`)).join("\n")),confirm(p)?this.handleTypeChange(e,t,o):(t.value=n,this.a11y&&"function"==typeof this.a11y.announce&&this.a11y.announce("Type change cancelled"))}handleTypeChange(e,t,n){const o=e.dataset.currentType||t.dataset.initialValue,a=this.collectFormData(e),s=window.getTemplate(`seo-${n}`);if(!s)return void console.error("No template found for type:",n);const i=e.querySelector(".seo-"+o);if(i&&(i.parentNode.insertBefore(s,i),i.remove()),e.dataset.currentType=n,window.jvbPopulate){this.populate=window.jvbPopulate.populate,Object.keys(a).forEach((t=>{const n=e.querySelector(`[data-field="${t}"]`);if(n){const e=a[t];this.populate(n,t,e)}}));const t=`Schema type changed to ${n}.`;this.a11y&&"function"==typeof this.a11y.announce&&this.a11y.announce(t)}}collectFormData(e){const t={},n=new FormData(e);for(let[e,o]of n.entries())if("type"!==e&&"context"!==e)if(e.includes(":")){const n=e.split(":"),a=n[0],s=parseInt(n[1]),i=n[2];t[a]||(t[a]=[]),t[a][s]||(t[a][s]={}),t[a][s][i]=o}else t[e]=o;return t}getFieldType(e){return e.classList.contains("repeater")?"repeater":"text"}addPreservedFieldStyles(){const e=document.createElement("style");e.textContent="\n            .value-preserved {\n                background-color: #e7f5e7 !important;\n                transition: background-color 0.3s ease;\n            }\n        ",document.head.appendChild(e)}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbSchema=new e)}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.formController=null,this.tabsInstance=null,this.queue=window.jvbQueue,this.a11y=window.jvbA11y,this.init()}init(){if(this.formController=window.formController,window.jvbTabs){const e=document.querySelector(".jvb-seo-admin");e&&(this.tabsInstance=window.jvbTabs.registerTab(e))}this.formController&&this.formController.subscribe((e,t)=>{"form-submit"===e&&this.handleFormSubmit(t)}),this.queue&&this.queue.subscribe((e,t)=>{Object.hasOwn(t,"endpoint")&&"seo"===t.endpoint&&("operation-completed"===e?this.handleQueueSuccess(e,t):"operation-failed-permanent"===e&&this.handleQueueFailure(e,t))}),this.initializeForms(),this.addPreservedFieldStyles()}initializeForms(){document.querySelectorAll('form[data-save="seo"]').forEach(e=>{this.formController&&this.formController.registerForm(e,{endpoint:"seo",autosave:!1,formStatus:!1}),this.initializeTypeSwitch(e);const t=e.querySelector('[data-action="reset"]');t&&t.addEventListener("click",()=>this.handleReset(e))})}handleFormSubmit(e){const t=e.config.element.dataset.content,n=e.fullData,o={endpoint:"seo",headers:{"X-WP-Nonce":window.auth.getNonce()},data:{context:t,action:"save",...n},popup:"Saving SEO configuration",title:`Saving ${t} settings`};this.queue.addToQueue(o)}async handleReset(e){const t=e.dataset.content;if(!confirm("Reset to default settings? This cannot be undone."))return;const n={endpoint:"seo",headers:{"X-WP-Nonce":window.auth.getNonce()},data:{context:t,action:"reset"},popup:"Resetting configuration",title:`Resetting ${t} to defaults`};this.queue.addToQueue(n)}handleQueueSuccess(e,t){console.log("SEO save successful:",t),this.a11y&&"function"==typeof this.a11y.announce&&this.a11y.announce("Configuration saved successfully"),"reset"===t.operation?.data?.action&&t.response?.schema&&this.reloadFormData(t.operation.data.context,t.response)}handleQueueFailure(e,t){console.error("SEO operation failed permanently:",t),this.a11y&&"function"==typeof this.a11y.announce&&this.a11y.announce(`Error: ${t.error_message||"Operation failed"}`)}reloadFormData(e,t){const n=document.querySelector(`form[data-content="${e}"]`);if(!n)return;const o=t.schema||{};Object.keys(o).forEach(e=>{const t=n.querySelector(`[name="${e}"]`);t&&("checkbox"===t.type?t.checked=!!o[e]:t.value=o[e]||"")}),this.a11y&&"function"==typeof this.a11y.announce&&this.a11y.announce("Form reset to defaults")}initializeTypeSwitch(e){const t=e.querySelector('select[name="type"]');t&&(t.addEventListener("change",n=>{const o=e.dataset.currentType||t.dataset.initialValue,a=n.target.value;o!==a&&this.confirmTypeChange(e,t,o,a)}),t.dataset.initialValue=t.value,e.dataset.currentType=t.value)}confirmTypeChange(e,t,n,o){const a={},s=new FormData(e);for(let[e,t]of s.entries())"type"!==e&&t&&""!==t&&(a[e]=t);const i=window.getTemplate(`seo-${o}`);if(!i)return console.error("No template found for type:",o),void(t.value=n);const r=e=>e.split(":")[0],c=new Set(Object.keys(a).map(r)),l=i.querySelectorAll("[data-field]"),u=new Set(Array.from(l).map(e=>e.dataset.field));if(0===u.size){const e=i.querySelectorAll("[name]");Array.from(e).forEach(e=>{u.add(r(e.getAttribute("name")))})}const d=[...c].filter(e=>u.has(e)),h=[...c].filter(e=>!u.has(e));let p=`Change schema type from ${n} to ${o}?\n\n`;d.length>0&&(p+=`✓ ${d.length} field value(s) will be preserved:\n`,p+=d.map(e=>`  • ${e}`).join("\n"),p+="\n\n"),h.length>0&&(p+=`⚠ ${h.length} field value(s) will be lost:\n`,p+=h.map(e=>`  • ${e}`).join("\n")),confirm(p)?this.handleTypeChange(e,t,o):(t.value=n,this.a11y&&"function"==typeof this.a11y.announce&&this.a11y.announce("Type change cancelled"))}handleTypeChange(e,t,n){const o=e.dataset.currentType||t.dataset.initialValue,a=this.collectFormData(e),s=window.getTemplate(`seo-${n}`);if(!s)return void console.error("No template found for type:",n);const i=e.querySelector(".seo-"+o);if(i&&(i.parentNode.insertBefore(s,i),i.remove()),e.dataset.currentType=n,window.jvbPopulate){this.populate=window.jvbPopulate.populate,Object.keys(a).forEach(t=>{const n=e.querySelector(`[data-field="${t}"]`);if(n){const e=a[t];this.populate(n,t,e)}});const t=`Schema type changed to ${n}.`;this.a11y&&"function"==typeof this.a11y.announce&&this.a11y.announce(t)}}collectFormData(e){const t={},n=new FormData(e);for(let[e,o]of n.entries())if("type"!==e&&"context"!==e)if(e.includes(":")){const n=e.split(":"),a=n[0],s=parseInt(n[1]),i=n[2];t[a]||(t[a]=[]),t[a][s]||(t[a][s]={}),t[a][s][i]=o}else t[e]=o;return t}getFieldType(e){return e.classList.contains("repeater")?"repeater":"text"}addPreservedFieldStyles(){const e=document.createElement("style");e.textContent="\n            .value-preserved {\n                background-color: #e7f5e7 !important;\n                transition: background-color 0.3s ease;\n            }\n        ",document.head.appendChild(e)}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbSchema=new e)})})})();
\ No newline at end of file
diff --git a/assets/js/min/selector.min.js b/assets/js/min/selector.min.js
index 461f2e0..fccae78 100644
--- a/assets/js/min/selector.min.js
+++ b/assets/js/min/selector.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.container=document.querySelector("dialog#jvb-selector"),this.container&&(this.a11y=window.jvbA11y,this.error=window.jvbError,this.subscribers=new Set,this.fields=new Map,this.selectedTerms=new Map,this.batchFetch=new Set,this.activeField=null,this.isInitializing=!0,this.lazyInit=!1,this.messageText={},this.init())}init(){this.initStore(),this.initElements(),this.defineTemplates(),this.initModal(),this.scanExistingFields(),this.initListeners(),this.needsCreator()&&window.jvbTaxCreator&&(this.creator=new window.jvbTaxCreator(this)),this.isInitializing=!1,this.batchFetchTaxonomies().then((()=>{}))}initStore(){const e=window.jvbStore.register("taxonomies",{storeName:"terms",keyPath:"id",showLoading:!1,indexes:[{name:"taxonomy",keyPath:"taxonomy"},{name:"parent",keyPath:"parent"},{name:"slug",keyPath:"slug"},{name:"count",keyPath:"count"}],endpoint:"terms",TTL:12e4,filters:{taxonomy:"",page:1,search:"",parent:0},required:"taxonomy",delayFetch:!0});this.store=e.terms,this.store.subscribe(this.handleStoreEvent.bind(this))}defineTemplates(){const e=window.jvbTemplates,t=this;e.define("emptyState"),e.define("selectedTerm",{refs:{name:".item-name",btn:"button"},setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id,e.dataset.taxonomy=i.taxonomy,t.name&&(t.name.textContent=i.path),t.button&&(t.button.title=`Remove ${i.name}`)}}),e.define("termListItem",{refs:{checkbox:"input",label:"label",name:"span, .term-name"},setup({el:e,refs:s,manyRefs:i,data:r}){e.dataset.id=r.id;let a=t.currentField(),n=t.selectedTerms.get(t.activeField).has(r.id),o=a.limit>0&&t.selectedTerms.get(t.activeField).size>=a.limit;if(s.checkbox&&(s.checkbox.dataset.id=r.id,s.checkbox.id=`${a.id}-${r.id}`,s.checkbox.name=`${a.id}-${a.taxonomy}-select`,s.checkbox.value=r.id,s.checkbox.disabled=!n&&o,s.checkbox.checked=n),s.label&&(s.label.htmlFor=`${a.id}-${r.id}`,s.label.title=r.path??r.name,s.label.dataset.path=r.path),s.name&&(s.name.textContent=r.show?r.path:r.name),r.hasChildren){let t={plural:a.plural,name:r.name};const s=window.jvbTemplates.create("termChildrenToggle",t);e.append(s)}}}),e.define("termChildrenToggle",{setup({el:e,refs:t,manyRefs:s,data:i}){e.ariaLabel=`View ${i.plural} nested under ${i.name}`}}),e.define("termBreadcrumb",{setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id,e.textContent=i.name,e.title=i.name}}),e.define("autocompleteItem",{setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id,e.textContent=i.path||i.name,e.title=`Select ${i.name}`}})}initElements(){this.selectors={search:{input:'[type="search"]',clear:".clear-search",container:".search-wrapper",results:".search-results"},create:{button:"button.submit-term",span:".submit-term span"},terms:{list:".items-container",wrap:".items-wrap",sentinel:".scroll-sentinel"},nav:{nav:"nav.term-navigation",back:".back-to-parent",child:".toggle-children",pathLevel:".path-level"},message:{message:"p.message",text:"p.message span"},selected:".selected-items",modal:{title:"#modal-title",content:".modal-content",count:".selection-count"},favourites:".favourite-terms",field:{toggle:'button.selector-toggle, [data-filter="taxonomy"]',value:'input[type="hidden"]',selected:".selected-items",dropdown:{list:".search-results",wrapper:".auto-wrapper"},create:{button:".auto-wrapper .submit-term",span:".auto-wrapper button span"},search:"input[data-autocomplete]",message:{message:"p.message",text:"p.message span"}}},this.ui=window.uiFromSelectors(this.selectors,this.container)}initListeners(){this.observer=new IntersectionObserver((e=>{e.forEach((e=>{e.isIntersecting&&this.nextPage()}))}),{root:this.ui.terms.sentinel,threshold:.5}),this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.inputHandler=this.handleInput.bind(this),this.focusHandler=this.handleFocus.bind(this),this.blurHandler=this.handleBlur.bind(this),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler),document.addEventListener("input",this.inputHandler),document.addEventListener("focus",this.focusHandler,!0),document.addEventListener("blur",this.blurHandler,!0)}handleClick(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;const t=this.getFieldId(e.target)||this.activeField,s=this.fields.get(t);if(!t||!s)return;if(this.creator){window.targetCheck(e,this.selectors.create.button)&&this.maybeCreateTerm(e).then((()=>{}))}const i=window.targetCheck(e,".remove-term");if(i){const e=i.closest("[data-id]").dataset.id??!1;return void(t&&e&&this.removeSelected(parseInt(e),t))}const r=window.targetCheck(e,".item.autocomplete");if(r){let e=parseInt(r.dataset.id);return this.addSelected(e,t),this.scheduleHideDropdown(t,6e3),void(s.ui.search&&(s.ui.search.value=""))}if(window.targetCheck(e,this.selectors.field.toggle))return e.preventDefault(),void this.openModal(t);if(e.target.matches(".modal-close"))return this.updateFieldValue(t),void this.modal?.handleClose();if(window.targetCheck(e,this.selectors.nav.back))return void this.navigateToParent();if(window.targetCheck(e,this.selectors.nav.child)){const t=e.target.closest("li"),s=parseInt(t.dataset.id);return void(s&&this.navigateTo(s))}const a=window.targetCheck(e,this.selectors.nav.pathLevel);if(a){const e=parseInt(a.dataset.id)??0;return void this.navigateTo(e)}if(window.targetCheck(e,this.selectors.field.dropdown))return void this.scheduleHideDropdown(t);if(window.targetCheck(e,this.selectors.search.clear)){const e=this.currentField();e&&e.ui.search&&(e.ui.search.value="",this.store.setFilters({search:"",page:1,parent:this.store.filters.parent||0})),this.ui.search.input&&(this.ui.search.input.value="")}}handleChange(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;if(!["checkbox","button"].includes(e.target.type))return;e.preventDefault(),e.stopPropagation();const t=parseInt(e.target.dataset.id);let s=this.getFieldId(e.target);e.target.checked?this.addSelected(t,s):this.removeSelected(t,s)}handleInput(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;let t=this.getFieldId(e.target)??this.activeField;if(!t)return;const s=this.fields.get(t);if(!s)return;if(["checkbox","button"].includes(e.target.type))return;e.preventDefault(),e.stopPropagation(),this.container.open||this.setField(t);let i=e.target.value.trim();this.setMessage(s,!0,`Searching for "${i}" in ${s.plural??"items"}`),window.debouncer.schedule(`${t}-search`,(async()=>{this.container.open&&window.removeChildren(this.ui.terms.list),await this.store.setFilters({taxonomy:s.taxonomy,search:i,page:1,parent:i?0:this.store.filters.parent||0})}),100)}setField(e){const t=this.fields.get(e);t?(this.activeField=e,this.setMessage(t,!0,`Loading ${t.plural}...`),this.resetFilters({taxonomy:t.taxonomy})):console.error("No field found...")}resetFilters(e){Object.hasOwn(e,"taxonomy")&&(e={page:1,search:"",parent:0,...e},this.store.setFilters(e))}handleFocus(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;const t=this.getFieldId(e.target);if(!t)return;const s=this.fields.get(t);s&&(s.hasAutocomplete||s.hasSearch)&&(window.debouncer.cancel(`${t}-search-results`),this.container.open||this.setField(t))}handleBlur(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;const t=this.getFieldId(e.target);if(!t)return;const s=this.fields.get(t);s&&s.hasAutocomplete&&!this.container.open&&(e.target.closest(".remove-item")||e.relatedTarget&&s.ui.dropdown.wrapper?.contains(e.relatedTarget)||this.scheduleHideDropdown(t))}scheduleHideDropdown(e,t=1500){const s=this.fields.get(e);s&&window.debouncer.schedule(`${e}-search-results`,(()=>{this.container.open||(this.activeField=null),s.ui.dropdown.wrapper&&(s.ui.dropdown.wrapper.hidden=!0)}),t)}initModal(){this.modalID="dialog#jvb-selector",this.container=document.querySelector(this.modalID),this.modal=new window.jvbModal(this.container,{handleForm:!1,open:null}),this.modal.subscribe(((e,t)=>{if("modal-close"===e)this.closeModal()}))}toggleModal(e,t=!0){this.fields.get(e)&&(t?this.openModal(e):this.closeModal())}openModal(e){const t=this.fields.get(e);if(!t)return;this.setField(e),this.ui.modal.title.textContent=t.isFilter?`Filter by ${t.singular}`:`Select ${t.plural}`,this.ui.search.container&&(this.ui.search.container.hidden=!t.canSearch),this.creator&&this.creator.handleOpen(t);let s=`Opened ${t.singular} selection. Choose from checkboxes, or search to filter results.`;window.removeChildren(this.ui.selected),window.removeChildren(this.ui.terms.list),this.modal.handleOpen(),this.a11y.announce(s)}openEmpty(e,t,s,i){this.emptyCallback=i;const r=`empty-${e}-${Date.now()}`;this.fields.has(r)||(this.fields.set(r,{id:r,taxonomy:e,singular:t,plural:s,canSearch:!0,canCreate:!1,hasAutocomplete:!1,isFilter:!1,isEmpty:!0,limit:0,ui:{},element:null,value:null,toggle:null,checked:!0}),this.selectedTerms.set(r,new Set)),this.setField(r),this.ui.modal.title.textContent=`Add to ${s}`,this.ui.search?.container&&(this.ui.search.container.hidden=!1),window.removeChildren(this.ui.selected),window.removeChildren(this.ui.terms.list),this.modal.handleOpen()}closeModal(){const e=this.fields.get(this.activeField);if(!e)return;if(this.updateFieldValue(this.activeField),this.observer.unobserve(this.ui.terms.sentinel),window.removeChildren(this.ui.terms.list),e.isEmpty&&this.emptyCallback){const t=Array.from(this.selectedTerms.get(this.activeField)||[]),s=t.map((e=>this.store.get(e))).filter(Boolean);this.emptyCallback({taxonomy:e.taxonomy,termIds:t,terms:s}),this.fields.delete(this.activeField),this.selectedTerms.delete(this.activeField),this.emptyCallback=null,this.bulkAssignmentTaxonomy=null}else this.notify("selected-terms",{terms:this.selectedTerms.get(this.activeField),taxonomy:e.taxonomy});this.activeField=null;let t=`Closed ${e.singular} selector.`;this.a11y.announce(t)}navigateToParent(){const e=this.store.filters.parent;if(0===e)return;let t=this.store.get(parseInt(e));if(!t)return void this.navigateTo(0);let s=t.parent;this.navigateTo(parseInt(s))}navigateTo(e=0){e=parseInt(e)??0,this.store.setFilters({parent:e,page:1}),window.removeChildren(this.ui.terms.list),this.updateBreadcrumbs(e)}nextPage(){let e=this.store.filters.page,t=Math.min(e++,this.store.lastResponse.total);this.store.setFilters({page:t})}prevPage(){let e=this.store.filters.page,t=Math.max(e-1,1);this.store.setFilters({page:t})}addTermToModal(e){const t=this.store.get(e);if(!t)return;this.currentField()&&(this.ui.selected.querySelector(`[data-id="${e}"]`)||this.ui.selected.append(this.getSelectedTermUI(t)))}getSelectedTermUI(e,t=!0){return window.jvbTemplates.create("selectedTerm",e)}scanExistingFields(e=document.body){e.querySelectorAll('[data-type="selector"], [data-field-type="selector"]').forEach((e=>{try{e.dataset.lazy?this.lazyInit=!0:this.registerField(e)}catch(t){this.error.log(t,{component:"TaxonomySelector",action:"scanExistingFields",container:e.dataset.name})}})),this.lazyInit&&this.initObserver(e)}unregisterFields(e){e.querySelectorAll('[data-type="selector"],[data-field-type="selector"]').forEach((e=>{this.fields.delete(e.dataset.fieldId)}))}initObserver(e){this.lazyObserver=new IntersectionObserver((e=>{e.forEach((e=>{e.isIntersecting&&e.target.dataset.lazy&&(delete e.target.dataset.lazy,this.registerField(e.target),this.lazyObserver.unobserve(e.target))}))}),{rootMargin:"50px"}),e.querySelectorAll('[data-type="selector"][data-lazy], [data-field-type="selector"][data-lazy]').forEach((e=>{this.lazyObserver.observe(e)}))}registerField(e,t={}){if(e.dataset.fieldId&&this.fields.has(e.dataset.fieldId))return e.dataset.fieldId;let s=e.querySelector('input[type="hidden"]');if(!s&&!Object.hasOwn(e.dataset,"filter"))return;"fieldId"in e.dataset||(e.dataset.fieldId=window.generateID("selector"));const i=e.dataset.fieldId;let r=this.selectors.field;const a=Object.hasOwn(e.dataset,"filter")&&"taxonomy"===e.dataset.filter;let n=a?e:e.querySelector("button.selector-toggle");if(0===Object.keys(t).length){if(!n)return;t={taxonomy:n.dataset.taxonomy,single:n.dataset.single,plural:n.dataset.plural,search:Object.hasOwn(n.dataset,"search"),autocomplete:Object.hasOwn(n.dataset,"autocomplete"),creatable:Object.hasOwn(n.dataset,"creatable")}}else Object.hasOwn(t,"toggle")&&(n=document.querySelector(t.toggle),r.toggle=t.toggle);const o={id:i,value:s,element:e,taxonomy:t.taxonomy??!1,singular:t.single??"",plural:t.plural??"",name:e.dataset.field,canSearch:t.search??!1,limit:t.limit??0,hasAutocomplete:t.autocomplete??!1,canCreate:t.creatable??!1,isRequired:t.required??!1,isFilter:a,toggle:n,create:{button:null,span:null},selectors:r,ui:window.uiFromSelectors(r,e),checked:!1};if(a&&!o.ui.toggle&&(o.ui.toggle=e),o.taxonomy)return o.singular&&o.plural||(console.warn("TaxonomySelector: Field missing singular/plural labels",e),o.singular=o.taxonomy.replace("jvb_",""),o.plural=o.singular+"s"),this.fields.set(i,o),this.setSelectedFromValue(i,s),this.isInitializing&&this.batchFetch.add(o.taxonomy),null!==e.offsetParent?this.updateFieldUI(i):requestIdleCallback((()=>{null!==e.offsetParent&&this.updateFieldUI(i)}),{timeout:2e3}),i;console.error("TaxonomySelector: Field missing taxonomy",e)}setSelectedFromValue(e,t){if(!e)return;let s=this.fields.get(e);if(!s)return;if(!t&&!s.isFilter)return;let i=new Set;t&&t.value.trim().split(",").map((e=>parseInt(e.trim()))).filter((e=>!isNaN(e))).forEach((e=>i.add(e))),this.selectedTerms.set(e,i)}addSelected(e,t=null){t||(t=this.activeField);const s=this.fields.get(t),i=this.store.get(e);if(!s||!i)return;const r=this.selectedTerms.get(t);0!==s.limit&&r.size>=s.limit||(r.add(parseInt(e)),this.container.open||s.isFilter||this.updateFieldValue(t),this.addTermToDisplay(e,t),this.checkLimits(t))}removeSelected(e,t=null){t||(t=this.activeField);const s=this.fields.get(t),i=this.store.get(e);if(!s||!i)return;this.selectedTerms.get(t).delete(parseInt(e));const r=!!s.ui.selected&&s.ui.selected.querySelector(`[data-id="${e}"]`);if(r&&r.remove(),this.container.open){let t=!!this.ui.selected&&this.ui.selected.querySelector(`[data-id="${e}"]`);t&&t.remove();let s=this.ui.terms.list.querySelector(`[type=checkbox][data-id="${e}"]`);s&&(s.checked=!1)}this.container.open||s.isFilter||this.updateFieldValue(t),this.checkLimits(t)}updateFieldValue(e){const t=this.fields.get(e);if(!t)return;let s=Array.from(this.selectedTerms.get(e));t.ui.value&&(t.ui.value.value=s.join(",")??"",t.ui.value.dispatchEvent(new Event("change",{bubbles:!0})))}checkLimits(e){if(!this.container.open)return;const t=this.fields.get(e);if(!t||!t.isFilter||0===t.limit)return;const s=this.selectedTerms.get(e).size>=t.limit;this.setCheckboxes(s)}updateFieldFromInput(e){const t=this.getFieldId(e);if(!t)return;this.fields.get(t)&&(this.setSelectedFromValue(t,e),this.updateFieldUI(t))}updateFieldUI(e){const t=this.fields.get(e);let s=this.selectedTerms.get(e)??new Set;t&&!t.isFilter&&0!==s.size&&Array.from(s).forEach((t=>{this.addTermToDisplay(t,e)}))}updateFieldsForTaxonomy(e){let t=Array.from(this.fields.values()).filter((t=>t.taxonomy===e));const s=Array.from(this.store.data.values()).some((t=>t&&t.taxonomy===e));t.forEach((e=>{e.toggle&&(e.toggle.disabled=!s&&!e.canCreate,e.toggle.title=s?`Select ${e.plural}`:`No ${e.singular} available`,e.checked=!0)}))}showModalTerms(e=!1){const t=this.currentField(),s=this.store.getFiltered();if(0===s.length)return(this.store.filters.page??1)&&window.removeChildren(this.ui.terms.list),this.setMessage(t,!0,""===this.store.filters.search?`No matching ${t.plural}.`:`No ${t.plural} found.`,!1),void(this.ui.terms.sentinel&&this.observer.unobserve(this.ui.terms.sentinel));this.setCreateButton(t,!0),this.ui.terms.sentinel&&(this.store.lastResponse?.has_more?this.observer.observe(this.ui.terms.sentinel):this.observer.unobserve(this.ui.terms.sentinel));const i=this.store.filters.parent??0;this.ui.nav.back.hidden=0===i,window.chunkIt(s,(t=>this.createTermElement({show:e,...t})),(e=>this.ui.terms.list.append(e)),10).then((()=>{})),s.length>0&&this.setMessage(t,!1)}createTermElement(e){return e&&e.name?window.jvbTemplates.create("termListItem",e):null}showAutocompleteTerms(){const e=this.currentField();if(!e||!e.hasAutocomplete||!e.ui.dropdown?.list)return;const t=e.ui.dropdown.list,s=this.currentTerms();window.removeChildren(t),0===s.length?this.setMessage(e,!0,`No ${e.plural} found.`,!1):(window.chunkIt(s,(e=>this.createAutocompleteTerm(e)),(e=>t.append(e))).then((()=>{})),this.setMessage(e,!1)),this.setCreateButton(e,!0),e.ui.dropdown.wrapper&&(e.ui.dropdown.wrapper.hidden=!1)}createAutocompleteTerm(e){return window.jvbTemplates.create("autocompleteItem",e)}addTermToDisplay(e,t){const s=this.store.get(e),i=this.fields.get(t);if(!s||!i)return;if(i.ui.selected&&i.ui.selected.querySelector(`[data-id="${e}"]`))return;let r=this.getSelectedTermUI(s);if(i.ui.selected&&i.ui.selected.append(r),this.container.open){this.addTermToModal(e);const t=this.ui.terms.list.querySelector(`input[value="${e}"]`);t&&(t.checked=!0)}}updateBreadcrumbs(e){const t=this.ui.nav.nav;if(!t)return;const s=Array.from(t.children).find((t=>parseInt(t.dataset.id)===e));if(s){let e=s.nextElementSibling;for(;e;){const t=e;e=e.nextElementSibling,t.remove()}}else{const s=this.store.get(e);if(!s)return;const i=window.jvbTemplates.create("termBreadcrumb",s);t.append(i)}}updateSelectionCount(){if(!this.container.open)return;const e=this.fields.get(this.activeField);if(e&&this.ui.modal.count){const t=this.selectedTerms.get(this.activeField).size;this.ui.modal.count.textContent=e.limit>0?`${t} of ${e.limit} ${e.plural} selected`:`${t} ${e.plural} selected`}}checkRendered(e,t){if(e)return Object.hasOwn(e,t.taxonomy)||(e[t.taxonomy]=new Map),e[t.taxonomy].has(t.id)}currentField(){return this.fields.get(this.activeField)??!1}currentTerms(){return this.store.getFiltered()}needsCreator(){return Array.from(this.fields.values()).some((e=>e.canCreate||e.hasAutocomplete))}getFieldId(e){if(e.dataset.fieldId)return e.dataset.fieldId;const t=e.closest("[data-field-id]");return t?.dataset.fieldId||null}setCheckboxes(e){this.ui.terms.list.querySelectorAll("input[type=checkbox]").forEach((t=>{t.checked||(t.disabled=e)}))}handleStoreEvent(e,t){const s={"data-loaded":()=>this.handleDataLoaded(),"filters-changed":()=>this.handleFiltersChanged(t),"fetch-error":()=>this.handleFetchError()};try{s[e]?.(t)}catch(t){console.error(`Error handling store event "${e}":`,t)}}handleDataLoaded(){const e=this.store.filters.taxonomy;if(e){e.split(",").map((e=>e.trim())).forEach((e=>this.updateFieldsForTaxonomy(e)))}this.container.open?this.showResults():this.activeField&&this.showResults(!0)}showResults(e=!1){const t=this.store.getFiltered(),s=this.store.filters,i=s.search&&s.search.length>0;this.notify("terms-loaded",{terms:t,filters:s}),!this.activeField&&e||(this.setMessage(this.currentField(),!1),e?this.showAutocompleteTerms():this.showModalTerms(i),this.a11y.announce(t.length))}handleFiltersChanged(e){}handleFetchError(e){const t=this.currentField();this.setMessage(t,!0,"Something went wrong.",!1);const s=this.container.open||t?.isFilter?this.ui:t?.ui,i=s?.message?.message;if(i&&!i.querySelector(".clear-cache-btn")){const e=document.createElement("button");e.className="clear-cache-btn",e.type="button",e.textContent="Clear cache and try again",e.addEventListener("click",(async()=>{e.remove(),this.store.clearCache(),this.activeField&&t&&await this.store.setFilters({taxonomy:t.taxonomy,page:1,search:"",parent:0})})),i.appendChild(e)}console.error("Store fetch error:",e)}async batchFetchTaxonomies(){if(0===this.batchFetch.size)return;const e=Array.from(this.batchFetch);this.batchFetch.clear();try{await this.store.setFilters({taxonomy:e.join(","),page:1,search:"",parent:0})}catch(e){console.error("Failed to batch fetch taxonomies:",e)}}preloadTaxonomy(e){this.store.setFilters({taxonomy:e,page:1,search:"",parent:0})}setCreateButton(e,t=!0){if(!e.canCreate||!this.creator)return;const s=this.container.open?this.ui:e.ui;if(!s.create?.button||!s.create?.span)return;const i=s.create.button;i.hidden=!t;const r=s.create.span,a=this.container.open?s.search.input:s.search;if(!a)return;let n=(this.currentTerms()??[]).map((e=>e.name)),o=a.value;const l=t&&o.length>=2&&!n.includes(o);i.hidden=!l,l&&(r.textContent=a.value??"")}async maybeCreateTerm(e){const t=this.currentField();if(!t)return;window.debouncer.cancel(`${t.id}-search-results`);let s={taxonomy:t.taxonomy,parent:this.store.filters.parent??0};if(this.container.open&&""===this.ui.search.input.value?(s.parent=this.creator.ui.parent.value??s.parent,s.name=this.creator.ui.name.value??!1):s.name=this.container.open?this.ui.search.input.value:t.ui.search.value,void 0!==s.parent&&s.name){this.setMessage(t,!0,`Creating "${s.name}"...`),this.setCreateButton(t,!1),this.container.open?window.removeChildren(this.ui.terms.list):(t.ui.search.disabled=!0,t.ui.dropdown.wrapper&&(t.ui.dropdown.wrapper.hidden=!1));let e=await this.creator.handleTermCreation(s);if(e){if(this.setMessage(t,!0,`"${e.name}" created!`,!1),this.addSelected(e.id,t.id),this.updateFieldValue(t.id),!this.container.open&&t.ui.dropdown.list){window.removeChildren(t.ui.dropdown.list);const s=this.createAutocompleteTerm(e);s&&(s.classList.add("newly-created"),t.ui.dropdown.list.append(s))}this.scheduleHideDropdown(t.id,300),this.setMessage(t,!1)}else this.setMessage(t,!1),!this.container.open&&t.ui.dropdown.wrapper&&(t.ui.dropdown.wrapper.hidden=!0);this.container.open||(t.ui.search.disabled=!1,t.ui.search.value="")}}setMessage(e,t=!0,s="",i=!0){const r=this.container.open||e.isFilter?this.ui:e.isFilter?null:e.ui;if(!r?.message?.message)return;s=""===s?`No ${e.plural??"items"} found.`:s;const a=r.message.message,n=r.message.text;a.hidden=!t,t?s&&n&&(i&&window.typeLoop&&n?(this.messageText[e.id]&&(this.messageText[e.id](),delete this.messageText[e.id]),this.messageText[e.id]=window.typeLoop(n,s)):n.textContent=s):this.messageText[e.id]&&(this.messageText[e.id](),delete this.messageText[e.id])}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t={}){this.subscribers.forEach((s=>{try{s(e,t)}catch(e){console.error("Subscriber error:",e)}}))}destroy(){this.fields.forEach(((e,t)=>{window.debouncer.cancel(`${t}-search`),window.debouncer.cancel(`${t}-search-results`)})),Object.keys(this.messageText).forEach((e=>{this.messageText[e]&&this.messageText[e]()})),this.messageText={},this.ui.terms?.sentinel&&this.observer?.unobserve(this.ui.terms.sentinel),this.observer?.disconnect(),this.lazyObserver?.disconnect(),document.removeEventListener("click",this.clickHandler),document.removeEventListener("change",this.changeHandler),document.removeEventListener("input",this.inputHandler),document.removeEventListener("focus",this.focusHandler,!0),document.removeEventListener("blur",this.blurHandler,!0),this.subscribers.clear(),this.fields.clear(),this.selectedTerms.clear(),this.batchFetch.clear(),this.creator&&(this.creator.destroy(),this.creator=null),this.store&&(this.store=null)}}document.addEventListener("DOMContentLoaded",(function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbSelector=new e)}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.container=document.querySelector("dialog#jvb-selector"),this.container&&(this.a11y=window.jvbA11y,this.error=window.jvbError,this.subscribers=new Set,this.fields=new Map,this.selectedTerms=new Map,this.batchFetch=new Set,this.activeField=null,this.isInitializing=!0,this.lazyInit=!1,this.messageText={},this.init())}init(){this.initStore(),this.initElements(),this.defineTemplates(),this.initModal(),this.scanExistingFields(),this.initListeners(),this.needsCreator()&&window.jvbTaxCreator&&(this.creator=new window.jvbTaxCreator(this)),this.isInitializing=!1,this.batchFetchTaxonomies().then(()=>{})}initStore(){const e=window.jvbStore.register("taxonomies",{storeName:"terms",keyPath:"id",showLoading:!1,indexes:[{name:"taxonomy",keyPath:"taxonomy"},{name:"parent",keyPath:"parent"},{name:"slug",keyPath:"slug"},{name:"count",keyPath:"count"}],endpoint:"terms",TTL:12e4,filters:{taxonomy:"",page:1,search:"",parent:0},required:"taxonomy",delayFetch:!0});this.store=e.terms,this.store.subscribe(this.handleStoreEvent.bind(this))}defineTemplates(){const e=window.jvbTemplates,t=this;e.define("emptyState"),e.define("selectedTerm",{refs:{name:".item-name",btn:"button"},setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id,e.dataset.taxonomy=i.taxonomy,t.name&&(t.name.textContent=i.path),t.button&&(t.button.title=`Remove ${i.name}`)}}),e.define("termListItem",{refs:{checkbox:"input",label:"label",name:"span, .term-name"},setup({el:e,refs:s,manyRefs:i,data:r}){e.dataset.id=r.id;let a=t.currentField(),n=t.selectedTerms.get(t.activeField).has(r.id),o=a.limit>0&&t.selectedTerms.get(t.activeField).size>=a.limit;if(s.checkbox&&(s.checkbox.dataset.id=r.id,s.checkbox.id=`${a.id}-${r.id}`,s.checkbox.name=`${a.id}-${a.taxonomy}-select`,s.checkbox.value=r.id,s.checkbox.disabled=!n&&o,s.checkbox.checked=n),s.label&&(s.label.htmlFor=`${a.id}-${r.id}`,s.label.title=r.path??r.name,s.label.dataset.path=r.path),s.name&&(s.name.textContent=r.show?r.path:r.name),r.hasChildren){let t={plural:a.plural,name:r.name};const s=window.jvbTemplates.create("termChildrenToggle",t);e.append(s)}}}),e.define("termChildrenToggle",{setup({el:e,refs:t,manyRefs:s,data:i}){e.ariaLabel=`View ${i.plural} nested under ${i.name}`}}),e.define("termBreadcrumb",{setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id,e.textContent=i.name,e.title=i.name}}),e.define("autocompleteItem",{setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id,e.textContent=i.path||i.name,e.title=`Select ${i.name}`}})}initElements(){this.selectors={search:{input:'[type="search"]',clear:".clear-search",container:".search-wrapper",results:".search-results"},create:{button:"button.submit-term",span:".submit-term span"},terms:{list:".items-container",wrap:".items-wrap",sentinel:".scroll-sentinel"},nav:{nav:"nav.term-navigation",back:".back-to-parent",child:".toggle-children",pathLevel:".path-level"},message:{message:"p.message",text:"p.message span"},selected:".selected-items",modal:{title:"#modal-title",content:".modal-content",count:".selection-count"},favourites:".favourite-terms",field:{toggle:'button.selector-toggle, [data-filter="taxonomy"]',value:'input[type="hidden"]',selected:".selected-items",dropdown:{list:".search-results",wrapper:".auto-wrapper"},create:{button:".auto-wrapper .submit-term",span:".auto-wrapper button span"},search:"input[data-autocomplete]",message:{message:"p.message",text:"p.message span"}}},this.ui=window.uiFromSelectors(this.selectors,this.container)}initListeners(){this.observer=new IntersectionObserver(e=>{e.forEach(e=>{e.isIntersecting&&this.nextPage()})},{root:this.ui.terms.sentinel,threshold:.5}),this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.inputHandler=this.handleInput.bind(this),this.focusHandler=this.handleFocus.bind(this),this.blurHandler=this.handleBlur.bind(this),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler),document.addEventListener("input",this.inputHandler),document.addEventListener("focus",this.focusHandler,!0),document.addEventListener("blur",this.blurHandler,!0)}handleClick(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;const t=this.getFieldId(e.target)||this.activeField,s=this.fields.get(t);if(!t||!s)return;if(this.creator){window.targetCheck(e,this.selectors.create.button)&&this.maybeCreateTerm(e).then(()=>{})}const i=window.targetCheck(e,".remove-term");if(i){const e=i.closest("[data-id]").dataset.id??!1;return void(t&&e&&this.removeSelected(parseInt(e),t))}const r=window.targetCheck(e,".item.autocomplete");if(r){let e=parseInt(r.dataset.id);return this.addSelected(e,t),this.scheduleHideDropdown(t,6e3),void(s.ui.search&&(s.ui.search.value=""))}if(window.targetCheck(e,this.selectors.field.toggle))return e.preventDefault(),void this.openModal(t);if(e.target.matches(".modal-close"))return this.updateFieldValue(t),void this.modal?.handleClose();if(window.targetCheck(e,this.selectors.nav.back))return void this.navigateToParent();if(window.targetCheck(e,this.selectors.nav.child)){const t=e.target.closest("li"),s=parseInt(t.dataset.id);return void(s&&this.navigateTo(s))}const a=window.targetCheck(e,this.selectors.nav.pathLevel);if(a){const e=parseInt(a.dataset.id)??0;return void this.navigateTo(e)}if(window.targetCheck(e,this.selectors.field.dropdown))return void this.scheduleHideDropdown(t);if(window.targetCheck(e,this.selectors.search.clear)){const e=this.currentField();e&&e.ui.search&&(e.ui.search.value="",this.store.setFilters({search:"",page:1,parent:this.store.filters.parent||0})),this.ui.search.input&&(this.ui.search.input.value="")}}handleChange(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;if(!["checkbox","button"].includes(e.target.type))return;e.preventDefault(),e.stopPropagation();const t=parseInt(e.target.dataset.id);let s=this.getFieldId(e.target);e.target.checked?this.addSelected(t,s):this.removeSelected(t,s)}handleInput(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;let t=this.getFieldId(e.target)??this.activeField;if(!t)return;const s=this.fields.get(t);if(!s)return;if(["checkbox","button"].includes(e.target.type))return;e.preventDefault(),e.stopPropagation(),this.container.open||this.setField(t);let i=e.target.value.trim();this.setMessage(s,!0,`Searching for "${i}" in ${s.plural??"items"}`),window.debouncer.schedule(`${t}-search`,async()=>{this.container.open&&window.removeChildren(this.ui.terms.list),await this.store.setFilters({taxonomy:s.taxonomy,search:i,page:1,parent:i?0:this.store.filters.parent||0})},100)}setField(e){const t=this.fields.get(e);t?(this.activeField=e,this.setMessage(t,!0,`Loading ${t.plural}...`),this.resetFilters({taxonomy:t.taxonomy})):console.error("No field found...")}resetFilters(e){Object.hasOwn(e,"taxonomy")&&(e={page:1,search:"",parent:0,...e},this.store.setFilters(e))}handleFocus(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;const t=this.getFieldId(e.target);if(!t)return;const s=this.fields.get(t);s&&(s.hasAutocomplete||s.hasSearch)&&(window.debouncer.cancel(`${t}-search-results`),this.container.open||this.setField(t))}handleBlur(e){if(!this.container.contains(e.target)&&!e.target.closest('[data-type="selector"], [data-field-type="selector"]'))return;const t=this.getFieldId(e.target);if(!t)return;const s=this.fields.get(t);s&&s.hasAutocomplete&&!this.container.open&&(e.target.closest(".remove-item")||e.relatedTarget&&s.ui.dropdown.wrapper?.contains(e.relatedTarget)||this.scheduleHideDropdown(t))}scheduleHideDropdown(e,t=1500){const s=this.fields.get(e);s&&window.debouncer.schedule(`${e}-search-results`,()=>{this.container.open||(this.activeField=null),s.ui.dropdown.wrapper&&(s.ui.dropdown.wrapper.hidden=!0)},t)}initModal(){this.modalID="dialog#jvb-selector",this.container=document.querySelector(this.modalID),this.modal=new window.jvbModal(this.container,{handleForm:!1,open:null}),this.modal.subscribe((e,t)=>{if("modal-close"===e)this.closeModal()})}toggleModal(e,t=!0){this.fields.get(e)&&(t?this.openModal(e):this.closeModal())}openModal(e){const t=this.fields.get(e);if(!t)return;this.setField(e),this.ui.modal.title.textContent=t.isFilter?`Filter by ${t.singular}`:`Select ${t.plural}`,this.ui.search.container&&(this.ui.search.container.hidden=!t.canSearch),this.creator&&this.creator.handleOpen(t);let s=`Opened ${t.singular} selection. Choose from checkboxes, or search to filter results.`;window.removeChildren(this.ui.selected),window.removeChildren(this.ui.terms.list),this.modal.handleOpen(),this.a11y.announce(s)}openEmpty(e,t,s,i){this.emptyCallback=i;const r=`empty-${e}-${Date.now()}`;this.fields.has(r)||(this.fields.set(r,{id:r,taxonomy:e,singular:t,plural:s,canSearch:!0,canCreate:!1,hasAutocomplete:!1,isFilter:!1,isEmpty:!0,limit:0,ui:{},element:null,value:null,toggle:null,checked:!0}),this.selectedTerms.set(r,new Set)),this.setField(r),this.ui.modal.title.textContent=`Add to ${s}`,this.ui.search?.container&&(this.ui.search.container.hidden=!1),window.removeChildren(this.ui.selected),window.removeChildren(this.ui.terms.list),this.modal.handleOpen()}closeModal(){const e=this.fields.get(this.activeField);if(!e)return;if(this.updateFieldValue(this.activeField),this.observer.unobserve(this.ui.terms.sentinel),window.removeChildren(this.ui.terms.list),e.isEmpty&&this.emptyCallback){const t=Array.from(this.selectedTerms.get(this.activeField)||[]),s=t.map(e=>this.store.get(e)).filter(Boolean);this.emptyCallback({taxonomy:e.taxonomy,termIds:t,terms:s}),this.fields.delete(this.activeField),this.selectedTerms.delete(this.activeField),this.emptyCallback=null,this.bulkAssignmentTaxonomy=null}else this.notify("selected-terms",{terms:this.selectedTerms.get(this.activeField),taxonomy:e.taxonomy});this.activeField=null;let t=`Closed ${e.singular} selector.`;this.a11y.announce(t)}navigateToParent(){const e=this.store.filters.parent;if(0===e)return;let t=this.store.get(parseInt(e));if(!t)return void this.navigateTo(0);let s=t.parent;this.navigateTo(parseInt(s))}navigateTo(e=0){e=parseInt(e)??0,this.store.setFilters({parent:e,page:1}),window.removeChildren(this.ui.terms.list),this.updateBreadcrumbs(e)}nextPage(){let e=this.store.filters.page,t=Math.min(e++,this.store.lastResponse.total);this.store.setFilters({page:t})}prevPage(){let e=this.store.filters.page,t=Math.max(e-1,1);this.store.setFilters({page:t})}addTermToModal(e){const t=this.store.get(e);if(!t)return;this.currentField()&&(this.ui.selected.querySelector(`[data-id="${e}"]`)||this.ui.selected.append(this.getSelectedTermUI(t)))}getSelectedTermUI(e,t=!0){return window.jvbTemplates.create("selectedTerm",e)}scanExistingFields(e=document.body){e.querySelectorAll('[data-type="selector"], [data-field-type="selector"]').forEach(e=>{try{e.dataset.lazy?this.lazyInit=!0:this.registerField(e)}catch(t){this.error.log(t,{component:"TaxonomySelector",action:"scanExistingFields",container:e.dataset.name})}}),this.lazyInit&&this.initObserver(e)}unregisterFields(e){e.querySelectorAll('[data-type="selector"],[data-field-type="selector"]').forEach(e=>{this.fields.delete(e.dataset.fieldId)})}initObserver(e){this.lazyObserver=new IntersectionObserver(e=>{e.forEach(e=>{e.isIntersecting&&e.target.dataset.lazy&&(delete e.target.dataset.lazy,this.registerField(e.target),this.lazyObserver.unobserve(e.target))})},{rootMargin:"50px"}),e.querySelectorAll('[data-type="selector"][data-lazy], [data-field-type="selector"][data-lazy]').forEach(e=>{this.lazyObserver.observe(e)})}registerField(e,t={}){if(e.dataset.fieldId&&this.fields.has(e.dataset.fieldId))return e.dataset.fieldId;let s=e.querySelector('input[type="hidden"]');if(!s&&!Object.hasOwn(e.dataset,"filter"))return;"fieldId"in e.dataset||(e.dataset.fieldId=window.generateID("selector"));const i=e.dataset.fieldId;let r=this.selectors.field;const a=Object.hasOwn(e.dataset,"filter")&&"taxonomy"===e.dataset.filter;let n=a?e:e.querySelector("button.selector-toggle");if(0===Object.keys(t).length){if(!n)return;t={taxonomy:n.dataset.taxonomy,single:n.dataset.single,plural:n.dataset.plural,search:Object.hasOwn(n.dataset,"search"),autocomplete:Object.hasOwn(n.dataset,"autocomplete"),creatable:Object.hasOwn(n.dataset,"creatable")}}else Object.hasOwn(t,"toggle")&&(n=document.querySelector(t.toggle),r.toggle=t.toggle);const o={id:i,value:s,element:e,taxonomy:t.taxonomy??!1,singular:t.single??"",plural:t.plural??"",name:e.dataset.field,canSearch:t.search??!1,limit:t.limit??0,hasAutocomplete:t.autocomplete??!1,canCreate:t.creatable??!1,isRequired:t.required??!1,isFilter:a,toggle:n,create:{button:null,span:null},selectors:r,ui:window.uiFromSelectors(r,e),checked:!1};if(a&&!o.ui.toggle&&(o.ui.toggle=e),o.taxonomy)return o.singular&&o.plural||(console.warn("TaxonomySelector: Field missing singular/plural labels",e),o.singular=o.taxonomy.replace("jvb_",""),o.plural=o.singular+"s"),this.fields.set(i,o),this.setSelectedFromValue(i,s),this.isInitializing&&this.batchFetch.add(o.taxonomy),null!==e.offsetParent?this.updateFieldUI(i):requestIdleCallback(()=>{null!==e.offsetParent&&this.updateFieldUI(i)},{timeout:2e3}),i;console.error("TaxonomySelector: Field missing taxonomy",e)}setSelectedFromValue(e,t){if(!e)return;let s=this.fields.get(e);if(!s)return;if(!t&&!s.isFilter)return;let i=new Set;t&&t.value.trim().split(",").map(e=>parseInt(e.trim())).filter(e=>!isNaN(e)).forEach(e=>i.add(e)),this.selectedTerms.set(e,i)}addSelected(e,t=null){t||(t=this.activeField);const s=this.fields.get(t),i=this.store.get(e);if(!s||!i)return;const r=this.selectedTerms.get(t);0!==s.limit&&r.size>=s.limit||(r.add(parseInt(e)),this.container.open||s.isFilter||this.updateFieldValue(t),this.addTermToDisplay(e,t),this.checkLimits(t))}removeSelected(e,t=null){t||(t=this.activeField);const s=this.fields.get(t),i=this.store.get(e);if(!s||!i)return;this.selectedTerms.get(t).delete(parseInt(e));const r=!!s.ui.selected&&s.ui.selected.querySelector(`[data-id="${e}"]`);if(r&&r.remove(),this.container.open){let t=!!this.ui.selected&&this.ui.selected.querySelector(`[data-id="${e}"]`);t&&t.remove();let s=this.ui.terms.list.querySelector(`[type=checkbox][data-id="${e}"]`);s&&(s.checked=!1)}this.container.open||s.isFilter||this.updateFieldValue(t),this.checkLimits(t)}updateFieldValue(e){const t=this.fields.get(e);if(!t)return;let s=Array.from(this.selectedTerms.get(e));t.ui.value&&(t.ui.value.value=s.join(",")??"",t.ui.value.dispatchEvent(new Event("change",{bubbles:!0})))}checkLimits(e){if(!this.container.open)return;const t=this.fields.get(e);if(!t||!t.isFilter||0===t.limit)return;const s=this.selectedTerms.get(e).size>=t.limit;this.setCheckboxes(s)}updateFieldFromInput(e){const t=this.getFieldId(e);if(!t)return;this.fields.get(t)&&(this.setSelectedFromValue(t,e),this.updateFieldUI(t))}updateFieldUI(e){const t=this.fields.get(e);let s=this.selectedTerms.get(e)??new Set;t&&!t.isFilter&&0!==s.size&&Array.from(s).forEach(t=>{this.addTermToDisplay(t,e)})}updateFieldsForTaxonomy(e){let t=Array.from(this.fields.values()).filter(t=>t.taxonomy===e);const s=Array.from(this.store.data.values()).some(t=>t&&t.taxonomy===e);t.forEach(e=>{e.toggle&&(e.toggle.disabled=!s&&!e.canCreate,e.toggle.title=s?`Select ${e.plural}`:`No ${e.singular} available`,e.checked=!0)})}showModalTerms(e=!1){const t=this.currentField(),s=this.store.getFiltered();if(0===s.length)return(this.store.filters.page??1)&&window.removeChildren(this.ui.terms.list),this.setMessage(t,!0,""===this.store.filters.search?`No matching ${t.plural}.`:`No ${t.plural} found.`,!1),void(this.ui.terms.sentinel&&this.observer.unobserve(this.ui.terms.sentinel));this.setCreateButton(t,!0),this.ui.terms.sentinel&&(this.store.lastResponse?.has_more?this.observer.observe(this.ui.terms.sentinel):this.observer.unobserve(this.ui.terms.sentinel));const i=this.store.filters.parent??0;this.ui.nav.back.hidden=0===i,window.chunkIt(s,t=>this.createTermElement({show:e,...t}),e=>this.ui.terms.list.append(e),10).then(()=>{}),s.length>0&&this.setMessage(t,!1)}createTermElement(e){return e&&e.name?window.jvbTemplates.create("termListItem",e):null}showAutocompleteTerms(){const e=this.currentField();if(!e||!e.hasAutocomplete||!e.ui.dropdown?.list)return;const t=e.ui.dropdown.list,s=this.currentTerms();window.removeChildren(t),0===s.length?this.setMessage(e,!0,`No ${e.plural} found.`,!1):(window.chunkIt(s,e=>this.createAutocompleteTerm(e),e=>t.append(e)).then(()=>{}),this.setMessage(e,!1)),this.setCreateButton(e,!0),e.ui.dropdown.wrapper&&(e.ui.dropdown.wrapper.hidden=!1)}createAutocompleteTerm(e){return window.jvbTemplates.create("autocompleteItem",e)}addTermToDisplay(e,t){const s=this.store.get(e),i=this.fields.get(t);if(!s||!i)return;if(i.ui.selected&&i.ui.selected.querySelector(`[data-id="${e}"]`))return;let r=this.getSelectedTermUI(s);if(i.ui.selected&&i.ui.selected.append(r),this.container.open){this.addTermToModal(e);const t=this.ui.terms.list.querySelector(`input[value="${e}"]`);t&&(t.checked=!0)}}updateBreadcrumbs(e){const t=this.ui.nav.nav;if(!t)return;const s=Array.from(t.children).find(t=>parseInt(t.dataset.id)===e);if(s){let e=s.nextElementSibling;for(;e;){const t=e;e=e.nextElementSibling,t.remove()}}else{const s=this.store.get(e);if(!s)return;const i=window.jvbTemplates.create("termBreadcrumb",s);t.append(i)}}updateSelectionCount(){if(!this.container.open)return;const e=this.fields.get(this.activeField);if(e&&this.ui.modal.count){const t=this.selectedTerms.get(this.activeField).size;this.ui.modal.count.textContent=e.limit>0?`${t} of ${e.limit} ${e.plural} selected`:`${t} ${e.plural} selected`}}checkRendered(e,t){if(e)return Object.hasOwn(e,t.taxonomy)||(e[t.taxonomy]=new Map),e[t.taxonomy].has(t.id)}currentField(){return this.fields.get(this.activeField)??!1}currentTerms(){return this.store.getFiltered()}needsCreator(){return Array.from(this.fields.values()).some(e=>e.canCreate||e.hasAutocomplete)}getFieldId(e){if(e.dataset.fieldId)return e.dataset.fieldId;const t=e.closest("[data-field-id]");return t?.dataset.fieldId||null}setCheckboxes(e){this.ui.terms.list.querySelectorAll("input[type=checkbox]").forEach(t=>{t.checked||(t.disabled=e)})}handleStoreEvent(e,t){const s={"data-loaded":()=>this.handleDataLoaded(),"filters-changed":()=>this.handleFiltersChanged(t),"fetch-error":()=>this.handleFetchError()};try{s[e]?.(t)}catch(t){console.error(`Error handling store event "${e}":`,t)}}handleDataLoaded(){const e=this.store.filters.taxonomy;if(e){e.split(",").map(e=>e.trim()).forEach(e=>this.updateFieldsForTaxonomy(e))}this.container.open?this.showResults():this.activeField&&this.showResults(!0)}showResults(e=!1){const t=this.store.getFiltered(),s=this.store.filters,i=s.search&&s.search.length>0;this.notify("terms-loaded",{terms:t,filters:s}),!this.activeField&&e||(this.setMessage(this.currentField(),!1),e?this.showAutocompleteTerms():this.showModalTerms(i),this.a11y.announce(t.length))}handleFiltersChanged(e){}handleFetchError(e){const t=this.currentField();this.setMessage(t,!0,"Something went wrong.",!1);const s=this.container.open||t?.isFilter?this.ui:t?.ui,i=s?.message?.message;if(i&&!i.querySelector(".clear-cache-btn")){const e=document.createElement("button");e.className="clear-cache-btn",e.type="button",e.textContent="Clear cache and try again",e.addEventListener("click",async()=>{e.remove(),this.store.clearCache(),this.activeField&&t&&await this.store.setFilters({taxonomy:t.taxonomy,page:1,search:"",parent:0})}),i.appendChild(e)}console.error("Store fetch error:",e)}async batchFetchTaxonomies(){if(0===this.batchFetch.size)return;const e=Array.from(this.batchFetch);this.batchFetch.clear();try{await this.store.setFilters({taxonomy:e.join(","),page:1,search:"",parent:0})}catch(e){console.error("Failed to batch fetch taxonomies:",e)}}preloadTaxonomy(e){this.store.setFilters({taxonomy:e,page:1,search:"",parent:0})}setCreateButton(e,t=!0){if(!e.canCreate||!this.creator)return;const s=this.container.open?this.ui:e.ui;if(!s.create?.button||!s.create?.span)return;const i=s.create.button;i.hidden=!t;const r=s.create.span,a=this.container.open?s.search.input:s.search;if(!a)return;let n=(this.currentTerms()??[]).map(e=>e.name),o=a.value;const l=t&&o.length>=2&&!n.includes(o);i.hidden=!l,l&&(r.textContent=a.value??"")}async maybeCreateTerm(e){const t=this.currentField();if(!t)return;window.debouncer.cancel(`${t.id}-search-results`);let s={taxonomy:t.taxonomy,parent:this.store.filters.parent??0};if(this.container.open&&""===this.ui.search.input.value?(s.parent=this.creator.ui.parent.value??s.parent,s.name=this.creator.ui.name.value??!1):s.name=this.container.open?this.ui.search.input.value:t.ui.search.value,void 0!==s.parent&&s.name){this.setMessage(t,!0,`Creating "${s.name}"...`),this.setCreateButton(t,!1),this.container.open?window.removeChildren(this.ui.terms.list):(t.ui.search.disabled=!0,t.ui.dropdown.wrapper&&(t.ui.dropdown.wrapper.hidden=!1));let e=await this.creator.handleTermCreation(s);if(e){if(this.setMessage(t,!0,`"${e.name}" created!`,!1),this.addSelected(e.id,t.id),this.updateFieldValue(t.id),!this.container.open&&t.ui.dropdown.list){window.removeChildren(t.ui.dropdown.list);const s=this.createAutocompleteTerm(e);s&&(s.classList.add("newly-created"),t.ui.dropdown.list.append(s))}this.scheduleHideDropdown(t.id,300),this.setMessage(t,!1)}else this.setMessage(t,!1),!this.container.open&&t.ui.dropdown.wrapper&&(t.ui.dropdown.wrapper.hidden=!0);this.container.open||(t.ui.search.disabled=!1,t.ui.search.value="")}}setMessage(e,t=!0,s="",i=!0){const r=this.container.open||e.isFilter?this.ui:e.isFilter?null:e.ui;if(!r?.message?.message)return;s=""===s?`No ${e.plural??"items"} found.`:s;const a=r.message.message,n=r.message.text;a.hidden=!t,t?s&&n&&(i&&window.typeLoop&&n?(this.messageText[e.id]&&(this.messageText[e.id](),delete this.messageText[e.id]),this.messageText[e.id]=window.typeLoop(n,s)):n.textContent=s):this.messageText[e.id]&&(this.messageText[e.id](),delete this.messageText[e.id])}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t={}){this.subscribers.forEach(s=>{try{s(e,t)}catch(e){console.error("Subscriber error:",e)}})}destroy(){this.fields.forEach((e,t)=>{window.debouncer.cancel(`${t}-search`),window.debouncer.cancel(`${t}-search-results`)}),Object.keys(this.messageText).forEach(e=>{this.messageText[e]&&this.messageText[e]()}),this.messageText={},this.ui.terms?.sentinel&&this.observer?.unobserve(this.ui.terms.sentinel),this.observer?.disconnect(),this.lazyObserver?.disconnect(),document.removeEventListener("click",this.clickHandler),document.removeEventListener("change",this.changeHandler),document.removeEventListener("input",this.inputHandler),document.removeEventListener("focus",this.focusHandler,!0),document.removeEventListener("blur",this.blurHandler,!0),this.subscribers.clear(),this.fields.clear(),this.selectedTerms.clear(),this.batchFetch.clear(),this.creator&&(this.creator.destroy(),this.creator=null),this.store&&(this.store=null)}}document.addEventListener("DOMContentLoaded",function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbSelector=new e)})})})();
\ No newline at end of file
diff --git a/assets/js/min/settings.min.js b/assets/js/min/settings.min.js
index f10869a..ee282d6 100644
--- a/assets/js/min/settings.min.js
+++ b/assets/js/min/settings.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.cache=new window.jvbCache("settings"),this.cache.loadFromCache(),this.findSettings(),this.debouncer=window.debouncer,this.isLoggedIn=null!==window.auth.getUser(),this.initListeners(),this.loadSettings(),this.subscribers=new Set}findSettings(){this.settings=document.querySelectorAll("[data-setting]")??[]}addSetting(e,t="",s=null){t=""===t?e.name:t,e.dataset.setting=t;let n=this.cache.get(t);n&&("INPUT"===e.tagName&&["checkbox","radio"].includes(e.type)?e.checked=n===e.value:"DETAILS"===e.tagName&&(e.open="on"===n)),this.debouncer.schedule("add-setting",(()=>{this.findSettings.bind(this)}),300)}loadSettings(){for(const e of this.settings){let t=e.name;if(Object.hasOwn(e.dataset,"theme"))this.checkTheme(e);else{let s=this.cache.get(t);s&&("on"===e.value?e.checked="on"===s:["checkbox","radio"].includes(e.tagName)?e.checked=e.value===s:e.value=s)}}}checkTheme(e){const t=window.matchMedia("(prefers-color-scheme: dark)");let s=this.cache.get("dark-mode");!t||s&&"off"===s?"on"===s&&(e.checked=!0):e.checked=!0}initListeners(){this.changeHandler=this.handleChange.bind(this),document.addEventListener("change",this.changeHandler)}handleChange(e){if(!Object.hasOwn(e.target.dataset,"setting"))return;let t=e.target.value;"on"===e.target.value&&(t=e.target.checked?"on":"off"),this.saveSetting(e.target.name,t)}saveSetting(e,t){let s;this.isLoggedIn&&(s=this.cache.get(e)),this.cache.set(e,t),this.isLoggedIn&&s&&s!==t&&this.saveToServer(e,t)}async saveToServer(e,t){if(!this.isLoggedIn||!["dark-mode"].includes(e))return;const s={"X-WP-Nonce":window.auth.getNonce(),"Content-Type":"application/json"},n={user:window.auth.getUser(),setting:e,value:t},i=await fetch(`${jvbSettings.api}settings`,{method:"POST",headers:s,body:JSON.stringify(n)});await i.json()}loadSetting(e){return this.cache.get(e)}loadUserSetting(e){}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach((s=>s(e,t)))}destroy(){document.removeEventListener("change",this.changeHandler),this.subscribers.clear()}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbUserSettings=new e)}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.cache=new window.jvbCache("settings"),this.cache.loadFromCache(),this.findSettings(),this.debouncer=window.debouncer,this.isLoggedIn=null!==window.auth.getUser(),this.initListeners(),this.loadSettings(),this.subscribers=new Set}findSettings(){this.settings=document.querySelectorAll("[data-setting]")??[]}addSetting(e,t="",s=null){t=""===t?e.name:t,e.dataset.setting=t;let n=this.cache.get(t);n&&("INPUT"===e.tagName&&["checkbox","radio"].includes(e.type)?e.checked=n===e.value:"DETAILS"===e.tagName&&(e.open="on"===n)),this.debouncer.schedule("add-setting",()=>{this.findSettings.bind(this)},300)}loadSettings(){for(const e of this.settings){let t=e.name;if(Object.hasOwn(e.dataset,"theme"))this.checkTheme(e);else{let s=this.cache.get(t);s&&("on"===e.value?e.checked="on"===s:["checkbox","radio"].includes(e.tagName)?e.checked=e.value===s:e.value=s)}}}checkTheme(e){const t=window.matchMedia("(prefers-color-scheme: dark)");let s=this.cache.get("dark-mode");!t||s&&"off"===s?"on"===s&&(e.checked=!0):e.checked=!0}initListeners(){this.changeHandler=this.handleChange.bind(this),document.addEventListener("change",this.changeHandler)}handleChange(e){if(!Object.hasOwn(e.target.dataset,"setting"))return;let t=e.target.value;"on"===e.target.value&&(t=e.target.checked?"on":"off"),this.saveSetting(e.target.name,t)}saveSetting(e,t){let s;this.isLoggedIn&&(s=this.cache.get(e)),this.cache.set(e,t),this.isLoggedIn&&s&&s!==t&&this.saveToServer(e,t)}async saveToServer(e,t){if(!this.isLoggedIn||!["dark-mode"].includes(e))return;const s={"X-WP-Nonce":window.auth.getNonce(),"Content-Type":"application/json"},n={user:window.auth.getUser(),setting:e,value:t},i=await fetch(`${jvbSettings.api}settings`,{method:"POST",headers:s,body:JSON.stringify(n)});await i.json()}loadSetting(e){return this.cache.get(e)}loadUserSetting(e){}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(s=>s(e,t))}destroy(){document.removeEventListener("change",this.changeHandler),this.subscribers.clear()}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbUserSettings=new e)})})})();
\ No newline at end of file
diff --git a/assets/js/min/square.min.js b/assets/js/min/square.min.js
index df7dd32..cf008bb 100644
--- a/assets/js/min/square.min.js
+++ b/assets/js/min/square.min.js
@@ -1 +1 @@
-(()=>{class e extends window.jvbCheckout{constructor(e={}){super({...window.squareConfig,...e}),this.payments=null,this.card=null}async init(){if(window.Square)try{this.payments=window.Square.payments(this.config.application_id,this.config.location_id),await this.initializePaymentMethods(),this.isInitialized=!0,document.dispatchEvent(new CustomEvent("checkoutReady",{detail:{checkout:this,provider:"square"}}))}catch(e){console.error("Failed to initialize Square payments:",e),this.handleError(e)}else console.error("Square Web Payments SDK not loaded")}async initializePaymentMethods(){if(document.getElementById("payment-container"))try{this.card=await this.payments.card({style:this.getCardStyle()}),await this.card.attach("#payment-container"),this.card.addEventListener("cardBrandChanged",(e=>{console.log("Card brand:",e.detail.cardBrand)}))}catch(e){throw console.error("Failed to initialize card:",e),e}}getCardStyle(){return{input:{fontSize:"16px",fontFamily:"inherit",color:"#333",backgroundColor:"#fff"},".input-container":{borderColor:"#ccc",borderRadius:"4px"},".input-container.is-focus":{borderColor:"#006AFF",borderWidth:"2px",outline:"2px solid #006AFF",outlineOffset:"2px"},".input-container.is-error":{borderColor:"#d63638"}}}async processPayment(e){try{let t=null;if(this.selectedCardId)t=this.selectedCardId;else{const i=await this.card.tokenize({verificationDetails:{amount:String(e.total),currencyCode:this.config.currency||"CAD",intent:"CHARGE",customerInitiated:!0,billingContact:{givenName:e.customer.name.split(" ")[0],familyName:e.customer.name.split(" ").slice(1).join(" "),email:e.customer.email,phone:e.customer.phone}}});if("OK"!==i.status){const e=i.errors?.map((e=>e.message)).join(", ")||"Unknown error";throw new Error(`Card tokenization failed: ${e}`)}t=i.token,i.details?.userChallenged&&console.log("3D Secure verification completed")}return await this.submitToServer(t,e,!!this.selectedCardId)}catch(e){throw console.error("Payment processing failed:",e),e}}async submitToServer(e,t,i=!1){if(!this.isOpen)throw new Error("Store is currently closed");const a=await fetch(this.config.api_url+"process-payment",{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":this.config.nonce},body:JSON.stringify({source_id:e,is_saved_card:i,cart_id:this.getCartId(),amount:t.total,items:t.items,customer:{email:this.isLoggedIn?this.userEmail:t.customer.email,name:t.customer.name,phone:t.customer.phone},note:t.note,pickup_time:t.pickup_time})}),r=await a.json();if(!a.ok)throw new Error(r.message||"Payment processing failed");return this.clearCart(),r}extractOrderData(e){const t=super.extractOrderData(e);return t.items=t.items.map((e=>({catalog_object_id:e.catalog_id,quantity:e.quantity,price:e.price,note:e.note}))),t}async loadSavedCards(){try{const e=await fetch(this.config.api_url+"saved-cards",{method:"GET",headers:{"X-WP-Nonce":this.config.nonce}}),t=await e.json();t.success&&t.cards&&(this.savedCards=t.cards,this.renderSavedCards())}catch(e){console.error("Failed to load saved cards:",e)}}}document.addEventListener("DOMContentLoaded",(()=>{document.querySelector('#checkout[data-provider="square"]')&&(window.squareCheckout=new e)}))})();
\ No newline at end of file
+(()=>{class e extends window.jvbCheckout{constructor(e={}){super({...window.squareConfig,...e}),this.payments=null,this.card=null}async init(){if(window.Square)try{this.payments=window.Square.payments(this.config.application_id,this.config.location_id),await this.initializePaymentMethods(),this.isInitialized=!0,document.dispatchEvent(new CustomEvent("checkoutReady",{detail:{checkout:this,provider:"square"}}))}catch(e){console.error("Failed to initialize Square payments:",e),this.handleError(e)}else console.error("Square Web Payments SDK not loaded")}async initializePaymentMethods(){if(document.getElementById("payment-container"))try{this.card=await this.payments.card({style:this.getCardStyle()}),await this.card.attach("#payment-container"),this.card.addEventListener("cardBrandChanged",e=>{console.log("Card brand:",e.detail.cardBrand)})}catch(e){throw console.error("Failed to initialize card:",e),e}}getCardStyle(){return{input:{fontSize:"16px",fontFamily:"inherit",color:"#333",backgroundColor:"#fff"},".input-container":{borderColor:"#ccc",borderRadius:"4px"},".input-container.is-focus":{borderColor:"#006AFF",borderWidth:"2px",outline:"2px solid #006AFF",outlineOffset:"2px"},".input-container.is-error":{borderColor:"#d63638"}}}async processPayment(e){try{let t=null;if(this.selectedCardId)t=this.selectedCardId;else{const i=await this.card.tokenize({verificationDetails:{amount:String(e.total),currencyCode:this.config.currency||"CAD",intent:"CHARGE",customerInitiated:!0,billingContact:{givenName:e.customer.name.split(" ")[0],familyName:e.customer.name.split(" ").slice(1).join(" "),email:e.customer.email,phone:e.customer.phone}}});if("OK"!==i.status){const e=i.errors?.map(e=>e.message).join(", ")||"Unknown error";throw new Error(`Card tokenization failed: ${e}`)}t=i.token,i.details?.userChallenged&&console.log("3D Secure verification completed")}return await this.submitToServer(t,e,!!this.selectedCardId)}catch(e){throw console.error("Payment processing failed:",e),e}}async submitToServer(e,t,i=!1){if(!this.isOpen)throw new Error("Store is currently closed");const a=await fetch(this.config.api_url+"process-payment",{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":this.config.nonce},body:JSON.stringify({source_id:e,is_saved_card:i,cart_id:this.getCartId(),amount:t.total,items:t.items,customer:{email:this.isLoggedIn?this.userEmail:t.customer.email,name:t.customer.name,phone:t.customer.phone},note:t.note,pickup_time:t.pickup_time})}),r=await a.json();if(!a.ok)throw new Error(r.message||"Payment processing failed");return this.clearCart(),r}extractOrderData(e){const t=super.extractOrderData(e);return t.items=t.items.map(e=>({catalog_object_id:e.catalog_id,quantity:e.quantity,price:e.price,note:e.note})),t}async loadSavedCards(){try{const e=await fetch(this.config.api_url+"saved-cards",{method:"GET",headers:{"X-WP-Nonce":this.config.nonce}}),t=await e.json();t.success&&t.cards&&(this.savedCards=t.cards,this.renderSavedCards())}catch(e){console.error("Failed to load saved cards:",e)}}}document.addEventListener("DOMContentLoaded",()=>{document.querySelector('#checkout[data-provider="square"]')&&(window.squareCheckout=new e)})})();
\ No newline at end of file
diff --git a/assets/js/min/tabs.min.js b/assets/js/min/tabs.min.js
index 421c197..9c3ba43 100644
--- a/assets/js/min/tabs.min.js
+++ b/assets/js/min/tabs.min.js
@@ -1 +1 @@
-(()=>{class t{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.subscribers=new Set,this.tabs=new Map,this.hasHash=!1,this.init()}init(){this.initElements(),this.initListeners()}initElements(){this.selectors={nav:".tabs",tab:"[data-tab]",active:"button.tab.active",section:".tab-content",button:"button.tab",select:"select.tab-list"}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this)}handleClick(t){let e=this.getConfig(t.target);if(!e)return;const s=t.target.closest(this.selectors.tab);s&&this.switchTab(s.dataset.tab,e)}handleChange(t){let e=this.getConfig(t.target);e&&e&&this.switchTab(t.target.value,e)}registerTab(t,e={}){if(!t)return!1;let s=window.uiFromSelectors(this.selectors,t);if(!s.nav||!s.section)return console.error("No tab navigation or section found"),!1;let i=window.generateID("tab");t.dataset.tabsId=i,s.buttons=Array.from(t.querySelectorAll(this.selectors.button)),s.sections=Array.from(t.querySelectorAll(this.selectors.section)),s.sections.forEach((t=>{t.querySelector(".tabs")&&(e.hasChildren=!0,this.registerTab(t,{parent:i}))})),s.select&&(s.options=Array.from(s.select.querySelectorAll("option")));let a={id:i,ui:s,updateURL:e.updateURL??!0};return s.nav.addEventListener("click",this.clickHandler),s.select?.addEventListener("change",this.changeHandler),this.tabs.set(i,a),this.determineActiveTab(a),a}determineActiveTab(t){if(this.getInitialTabFromHash()){let e=this.tabs.get(t.id);if(e.activeTab&&t.activeTab&&e.activeTab===t.activeTab)return}let e=t.ui.buttons[0].dataset.tab??!1;e&&this.switchTab(e,t)}getInitialTabFromHash(){if(this.hasHash||!window.location.search)return!1;const t=new URLSearchParams(window.location.search).get("tab");if(!t)return!1;return t.split("|").forEach((t=>{const e=Array.from(this.tabs.values()).find((e=>e.ui.buttons.some((e=>e.dataset.tab===t))));e&&this.switchTab(t,e)})),this.hasHash=!0,!0}removeTab(t){if(!t||!t.dataset.tabsId)return;let e=this.tabs.get(t.dataset.tabsId);e&&(e.ui.nav.removeEventListener("click",this.clickHandler),e.ui.select?.removeEventListener("change",this.changeHandler),this.tabs.delete(t.dataset.tabsId))}switchTab(t,e){if(!(e="string"==typeof e?this.tabs.get(e):e))return;let s=e.ui.sections.filter((t=>t.classList.contains("active")));Object.hasOwn(e,"preCheck")&&!e.preCheck(s[0],e)||(document.activeElement&&this.isInTabs(document.activeElement,e)&&document.activeElement.blur(),e.ui.buttons.forEach(((s,i)=>{s.classList.remove("active","previous","next");let a=s.dataset.tab===t;if(s.setAttribute("aria-selected",a),a){s.classList.add("active");let t=Math.max(i-1,0),a=Math.min(i+1,e.ui.buttons.length-1);t!==i&&e.ui.buttons[t]?.classList.add("previous"),a!==i&&e.ui.buttons[a]?.classList.add("next")}})),e.ui.sections.forEach(((e,s)=>{let i=e.dataset.tab===t;e.classList.toggle("active",i),e.setAttribute("aria-hidden",!i),e.hidden=!i})),this.notify("tab-switched",{previous:e.activeTab,current:t,config:e}),e.activeTab=t,this.tabs.set(e.id,e),e?.hasChildren&&this.updateChildTabs(e.id),e?.updateURL&&this.updateURL(e),e.ui.select&&this.maybeUpdateSelect(t,e),this.a11y.announce(`Switched to ${t} tab`))}updateChildTabs(t){Array.from(this.tabs.values()).filter((e=>e.parent===t)).forEach((t=>{let e=t.ui.buttons[0].dataset.tab??!1;e&&this.switchTab(e,t)}))}updateURL(t){if(!t.updateURL)return;let e=this.checkAncestorsHash(t);e&&window.history.pushState({tab:t.activeTab},"",`?tab=${e}`)}checkAncestorsHash(t){const e=[];let s=t;for(;s;)e.unshift(s.activeTab),s=s.parent?this.tabs.get(s.parent):null;return e.join("|")}maybeUpdateSelect(t,e){e.ui.select&&Object.hasOwn(e,"options")&&e.options.forEach((s=>{s.value!==t||(e.ui.select.value=t)}))}subscribe(t){return this.subscribers.add(t),()=>this.subscribers.delete(t)}notify(t,e){this.subscribers.forEach((s=>s(t,e)))}getConfig(t){const e=t.closest("[data-tabs-id]");if(!e)return!1;const s=this.tabs.get(e.dataset.tabsId);return s||!1}isInTabs(t,e){return e.ui.sections.some((e=>e.contains(t)))}destroy(){this.subscribers.clear(),Array.from(this.tabs.values()).forEach((t=>{t.ui.nav.removeEventListener("click",this.clickHandler),t.ui.select?.removeEventListener("change",this.changeHandler)}))}}document.addEventListener("DOMContentLoaded",(function(){window.jvbTabs=new t}))})();
\ No newline at end of file
+(()=>{class t{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.subscribers=new Set,this.tabs=new Map,this.hasHash=!1,this.init()}init(){this.initElements(),this.initListeners()}initElements(){this.selectors={nav:".tabs",tab:"[data-tab]",active:"button.tab.active",section:".tab-content",button:"button.tab",select:"select.tab-list"}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this)}handleClick(t){let e=this.getConfig(t.target);if(!e)return;const s=t.target.closest(this.selectors.tab);s&&this.switchTab(s.dataset.tab,e)}handleChange(t){let e=this.getConfig(t.target);e&&e&&this.switchTab(t.target.value,e)}registerTab(t,e={}){if(!t)return!1;let s=window.uiFromSelectors(this.selectors,t);if(!s.nav||!s.section)return console.error("No tab navigation or section found"),!1;s.nav.previousElementSibling&&s.nav.previousElementSibling.classList.contains("form-progress");let i=window.generateID("tab");t.dataset.tabsId=i,s.buttons=Array.from(t.querySelectorAll(this.selectors.button)),s.sections=Array.from(t.querySelectorAll(this.selectors.section)),s.sections.forEach(t=>{t.querySelector(".tabs")&&(e.hasChildren=!0,this.registerTab(t,{parent:i}))}),s.select&&(s.options=Array.from(s.select.querySelectorAll("option")));let a={id:i,ui:s,updateURL:e.updateURL??!0};return Object.hasOwn(e,"preCheck")&&(a.preCheck=e.preCheck),s.nav.addEventListener("click",this.clickHandler),s.select?.addEventListener("change",this.changeHandler),this.tabs.set(i,a),this.determineActiveTab(a),a}determineActiveTab(t){if(this.getInitialTabFromHash()){let e=this.tabs.get(t.id);if(e.activeTab&&t.activeTab&&e.activeTab===t.activeTab)return}let e=t.ui.buttons[0].dataset.tab??!1;e&&this.switchTab(e,t)}getInitialTabFromHash(){if(this.hasHash||!window.location.search)return!1;const t=new URLSearchParams(window.location.search).get("tab");if(!t)return!1;return t.split("|").forEach(t=>{const e=Array.from(this.tabs.values()).find(e=>e.ui.buttons.some(e=>e.dataset.tab===t));e&&this.switchTab(t,e)}),this.hasHash=!0,!0}removeTab(t){if(!t||!t.dataset.tabsId)return;let e=this.tabs.get(t.dataset.tabsId);e&&(e.ui.nav.removeEventListener("click",this.clickHandler),e.ui.select?.removeEventListener("change",this.changeHandler),this.tabs.delete(t.dataset.tabsId))}switchTab(t,e){if(!(e="string"==typeof e?this.tabs.get(e):e))return;let s=e.ui.sections.filter(t=>t.classList.contains("active"));Object.hasOwn(e,"preCheck")&&!e.preCheck(s[0],t,e)||(document.activeElement&&this.isInTabs(document.activeElement,e)&&document.activeElement.blur(),e.ui.buttons.forEach(t=>{t.classList.remove("active","previous","next")}),e.ui.buttons.forEach((s,i)=>{let a=s.dataset.tab===t;if(s.setAttribute("aria-selected",a),a){s.classList.add("active");let t=Math.max(i-1,0),a=Math.min(i+1,e.ui.buttons.length-1);t!==i&&e.ui.buttons[t]?.classList.add("previous"),a!==i&&e.ui.buttons[a]?.classList.add("next")}}),e.ui.sections.forEach((e,s)=>{let i=e.dataset.tab===t;e.classList.toggle("active",i),e.setAttribute("aria-hidden",!i),e.hidden=!i}),this.notify("tab-switched",{previous:e.activeTab,current:t,config:e}),e.activeTab=t,this.tabs.set(e.id,e),e?.hasChildren&&this.updateChildTabs(e.id),e?.updateURL&&this.updateURL(e),e.ui.select&&this.maybeUpdateSelect(t,e),this.a11y.announce(`Switched to ${t} tab`))}updateChildTabs(t){Array.from(this.tabs.values()).filter(e=>e.parent===t).forEach(t=>{let e=t.ui.buttons[0].dataset.tab??!1;e&&this.switchTab(e,t)})}updateURL(t){if(!t.updateURL)return;let e=this.checkAncestorsHash(t);e&&window.history.pushState({tab:t.activeTab},"",`?tab=${e}`)}checkAncestorsHash(t){const e=[];let s=t;for(;s;)e.unshift(s.activeTab),s=s.parent?this.tabs.get(s.parent):null;return e.join("|")}maybeUpdateSelect(t,e){e.ui.select&&Object.hasOwn(e,"options")&&e.options.forEach(s=>{s.value!==t||(e.ui.select.value=t)})}subscribe(t){return this.subscribers.add(t),()=>this.subscribers.delete(t)}notify(t,e){this.subscribers.forEach(s=>s(t,e))}getConfig(t){const e=t.closest("[data-tabs-id]");if(!e)return!1;const s=this.tabs.get(e.dataset.tabsId);return s||!1}isInTabs(t,e){return e.ui.sections.some(e=>e.contains(t))}destroy(){this.subscribers.clear(),Array.from(this.tabs.values()).forEach(t=>{t.ui.nav.removeEventListener("click",this.clickHandler),t.ui.select?.removeEventListener("change",this.changeHandler)})}}document.addEventListener("DOMContentLoaded",function(){window.jvbTabs=new t})})();
\ No newline at end of file
diff --git a/assets/js/min/uploader.min.js b/assets/js/min/uploader.min.js
index 71d196b..aea49cb 100644
--- a/assets/js/min/uploader.min.js
+++ b/assets/js/min/uploader.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.queue=window.jvbQueue,this.error=window.jvbError,this.templates=window.jvbTemplates,this.subscribers=new Set,this.initStores(),this.initWorker(),this.fields=new Map,this.uploads=new Map,this.groups=new Map,this.selected=new Map,this.selectionHandlers=new Map,this.sortables=new Map,this.changes=new Map,this.previewUrls=new Set,this.initElements(),this.initListeners(),this.defineTemplates()}defineTemplates(){const e=this.templates,t=this;e.define("uploadItem",{refs:{select:'[name="select-item"]',featured:'[name="featured"]',img:"img",video:"video",file:"label > span",details:"details",alt:'[name="image-alt-text"]',title:'[name="image-title"]',description:'[name="image-caption"]'},manyRefs:{inputs:"input, select, textarea"},setup({el:e,refs:s,manyRefs:i,data:a}){let r,o,l,d=!1;switch(Object.hasOwn(a,"file")?(e.dataset.uploadId=a.uploadId,r=t.getSubtypeFromMime(a.file.type)||"image",o="document"!==r&&t.createPreviewUrl(a.file),d=o,l=a.file.name||""):(e.dataset.id=a.id,r=t.getSubtypeFromURL(a.medium??a.src),o=a.medium??a.src,l=a["image-alt-text"]??""),e.dataset.subtype=r,s.featured&&(s.featured.value=a.uploadId),r){case"image":s.img&&(s.img.src=o,s.img.alt=l,d&&(s.img.dataset.previewUrl=d)),s.video&&s.video.remove(),s.file&&s.file.remove();break;case"video":s.video&&(s.video.src=o,s.video.alt=l,d&&(s.video.dataset.previewUrl=d)),s.img&&s.img.remove(),s.file&&s.file.remove();break;case"document":if(s.preview){let e=a.file.name.split(".").pop()?.toLowerCase()??"",t={pdf:"file-pdf",csv:"file-csv",doc:"file-doc",docx:"file-doc",txt:"file-txt",xls:"file-xls",xlsx:"file-xls"},i=window.getIcon(t[e]??"file");s.preview.innerText=a.file.name??a.title,s.preview.prepend(i)}s.img&&s.img.remove(),s.video&&s.video.remove()}if(s.details&&(Object.hasOwn(a,"field")&&Object.hasOwn(a.field,"config")&&Object.hasOwn(a.field.config,"showMeta")&&!a.field.config.showMeta?s.details.remove():(Object.hasOwn(a,"id")?s.details.dataset.attachmentId=a.id:Object.hasOwn(a,"uploadId")&&(s.details.dataset.uploadId=a.uploadId),s.details.setAttribute("data-ignore",""),"image"!==r&&s.alt?s.alt.closest(".field")?.remove():Object.hasOwn(a,"image-alt-text")&&s.alt&&(s.alt.value=a["image-alt-text"]),(Object.hasOwn(a,"title")||Object.hasOwn(a,"file"))&&s.title&&(s.title.value=a.title||a.file.name),Object.hasOwn(a,"image-caption")&&s.description&&(s.description.value=a["image-caption"]))),e.draggable="single"!==e.dataset.mode,i.inputs)for(let t of i.inputs){let s=t.closest("[data-field]")??t.closest(".radio-button")??e;window.prefixInput(t,`${a.id??a.uploadId}-`,s)}}}),e.define("imageGroup",{refs:{selectAll:"[data-select-all]",fields:".fields",details:"details",grid:".item-grid"},setup({el:t,refs:s,manyRefs:i,data:a}){if(t.dataset.groupId=a.groupId,s.selectAll){let e=s.selectAll.closest(".field");window.prefixInput(s.selectAll,`select-all-${a.groupId}`,e,!0)}let r=e.create("groupMetadata",{groupId:a.groupId});r?s.fields.append(r):s.details.remove(),s.grid&&(s.grid.dataset.groupId=a.groupId)}}),e.define("groupMetadata",{manyRefs:{inputs:"input,textarea,select"},setup({el:e,refs:t,manyRefs:s,data:i}){s.inputs&&s.inputs.forEach((e=>{let t=e.closest("[data-field]");e.dataset.groupId=i.groupId,window.prefixInput(e,`${i.groupId}-`,t)}))}}),e.define("restoreNotification",{refs:{details:".details",wrap:".wrap"},setup({el:t,refs:s,manyRefs:i,data:a}){if(s.details){let e=a.bySource.size>1?` across ${a.bySource.size} pages`:"",t=a.pendingUploads.length>1?"uploads":"upload";s.details.textContent=`${a.pendingUploads.length} ${t} can be recovered${e}`}if(!s.wrap)return void console.warn("No wrap element in template");let r=1;for(const[t,i]of a.bySource){let a={index:r,isCurrent:t===window.location.href,src:t,uploads:i};s.wrap.append(e.create("restoreField",a)),r++}}}),e.define("restoreField",{refs:{h3:"h3",a:"h3 a",grid:".item-grid"},async setup({el:e,refs:s,manyRefs:i,data:a}){let r=t.registerField(e,!1,!1,`recovery_${a.index}`);a.isCurrent?(e.open=!0,s.a?.remove(),s.h3&&(s.h3.textContent="From this page:")):s.a&&(s.a.href=a.src,s.a.title="Navigate to page and restore",s.a.textContent=a.src);let o=[...new Set(a.uploads.map((e=>e.group??"preview")))];for(let e of o){let i="preview"===e||t.stores.groups.get(e);if(!i)continue;let o=await t.createGroupElement(e,r),l=o.querySelector(".item-grid"),d=a.uploads.filter((t=>t.group===("preview"===e)?null:e));for(const[e,t]of Object.entries(i.fields??{})){let s=o.querySelector(`input[name*="${e}"]`);s&&(s.value=t)}for(let e of d){let s=await t.createUpload(e.id,t.formatFile(e),r);l.append(s)}s.grid.append(o)}}})}initStores(){const{uploads:e,groups:t}=window.jvbStore.register("uploads",[{storeName:"uploads",keyPath:"id",indexes:[{name:"field",keyPath:"field"},{name:"status",keyPath:"status"},{name:"group",keyPath:"group"},{name:"src",keyPath:"src"}]},{storeName:"groups",keyPath:"id",indexes:[{name:"field",keyPath:"field"},{name:"src",keyPath:"src"}]}]);this.stores={uploads:e,groups:t,ready:[]},this.stores.uploads.subscribe(this.handleStores.bind(this,"uploads")),this.stores.groups.subscribe(this.handleStores.bind(this,"groups")),this.queue.subscribe(((e,t)=>{if(("operation-status"===e||"cancel-operation"===e)&&["image_upload","video_upload","document_upload"].includes(t.type)){let s=[];if(t.data)if(t.data instanceof FormData){const e=this.stores.uploads.formDataToObject(t.data);s=e.upload_ids||[]}else s=t.data.upload_ids||[];if(0===s.length&&t.result&&t.result.upload_ids&&(s=t.result.upload_ids),!s||0===s.length)return void console.warn("[UploadManager] No upload_ids found for operation:",{id:t.id,type:t.type,status:t.status,hasData:!!t.data,hasResult:!!t.result});if("cancel-operation"===e)return this.handleOperationCancelled(s);this.setBulkUpload(s,"status",t.status).then((()=>{console.log(`[UploadManager] Updated ${s.length} uploads to status: ${t.status}`)})),"completed"===t.status&&("process_upload_groups"===t.type?(s.forEach((e=>{this.setBulkUpload([e],"serverProcessed",!0).then((()=>{}))})),t.result&&t.result.created_posts&&console.log("[UploadManager] Created posts:",t.result.created_posts),setTimeout((()=>{s.forEach((e=>{this.removeUpload(e).then((()=>{}))}))}),2e3)):s.forEach((e=>{this.removeUpload(e).then((()=>{}))}))),"failed"!==t.status&&"failed_permanent"!==t.status||console.error("[UploadManager] Operation failed:",{id:t.id,type:t.type,uploadIds:s,error:t.error_message})}}))}storesReady(){return 2===this.stores.ready.length}handleStores(e,t){"data-ready"===t&&(this.stores.ready.push(e),this.storesReady()&&this.checkRecovery().then((()=>{})))}initWorker(){this.worker=null,this.workerState={worker:null,tasks:new Map,restart:{count:0,max:3},settings:{timeout:3e3,maxConcurrent:3,restartAfterTimeout:!0}}}initElements(){this.selectors={fields:{field:"[data-upload-field]",input:'input[type="file"]',dropZone:".file-upload-wrapper",preview:".preview-wrap",grid:".item-grid.preview",progress:{progress:".file-upload-container .progress",fill:".file-upload-container .progress .fill",details:".file-upload-container .progress .details",icon:".file-upload-container .progress .icon"},selectAll:"[data-select-all]",actions:".selection-actions",count:".selected .info",hidden:'input[type="hidden"]'},groups:{container:".group-display",grid:".item-grid.groups",empty:".empty-group",header:".sidebar .header"},group:{item:".upload-group",actions:".selection-actions",selectAll:'[name="select-all-group"]',count:".group-header .info",fields:"details .fields",grid:".item-grid.group",total:".group-content .group-count"},items:{item:".item.upload",checkbox:'[name="select-item"]',featured:'[name="featured"]',image:"img",details:"details",progress:{progress:".progress",fill:".fill",details:".details",icon:".icon"}}}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.dragEnterHandler=this.handleDragEnter.bind(this),this.dragLeaveHandler=this.handleDragLeave.bind(this),this.dragOverHandler=this.handleDragOver.bind(this),this.dropHandler=this.handleDrop.bind(this),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler),document.addEventListener("dragenter",this.dragEnterHandler),document.addEventListener("dragleave",this.dragLeaveHandler),document.addEventListener("dragover",this.dragOverHandler),document.addEventListener("drop",this.dropHandler),window.addEventListener("beforeunload",(()=>{this.cleanupAllPreviewUrls()}))}async setUpload(e,t){const s={...{id:e,attachment:null,group:null,field:null,src:window.location.href,blob:null,status:"local_processing",operationId:null,fields:{}},...t};if(Object.preventExtensions(s),await this.stores.uploads.save(s),this.fields.has(s.field)){let e=this.fields.get(s.field);if("local_processing"===s.status)this.notify("upload-received",{field:e.element,id:s.id})}return s}createPreviewUrl(e){const t=URL.createObjectURL(e);return this.previewUrls.add(t),t}revokePreviewUrl(e){e?.startsWith("blob:")&&(URL.revokeObjectURL(e),this.previewUrls.delete(e))}formatFile(e){return e.blob?new File([e.blob],e.fields.originalName||"file",{type:e.fields.type||e.blob.type,lastModified:e.fields.lastModified||Date.now()}):null}handleClick(e){if(!window.targetCheck(e,this.selectors.fields.field))return;let t=window.targetCheck(e,this.selectors.fields.dropZone);t&&!e.target.matches("input, button, a")&&t.querySelector(this.selectors.fields.input)?.click();const s=window.targetCheck(e,"[data-action]");s&&this.handleAction(s)}handleAction(e){const t=e.dataset.action,s=this.getFieldIdFromElement(e);switch(t){case"add-to-group":this.handleAddToGroup(s).then((()=>{}));break;case"delete-group":this.handleDeleteGroup(e);break;case"delete-upload":case"remove-from-group":this.handleRemoveItem(e).then((()=>{}));break;case"upload":this.queueUploads("uploads/groups",s).then((()=>{}));break;case"restore":this.handleRestoreSelected().then((()=>{}));break;case"restore-all":this.handleRestoreAll().then((()=>{}));break;case"clear-cache":this.handleClearCache().then((()=>{}))}}handleChange(e){let t=this.getFieldIdFromElement(e.target);if(t)if(e.target.matches(this.selectors.fields.input)){const s=Array.from(e.target.files);s.length>0&&this.processFiles(t,s).then((()=>{}))}else e.target.matches(this.selectors.items.checkbox)||e.target.matches(this.selectors.items.featured)||e.target.matches('[name*="select-"]')||("post_group"===this.fields.get(t).config.destination?this.handleGroupMetaChange(e.target):this.queueUploadMeta(e));else{e.target.closest("[data-upload-id], [data-attachment-id]")&&this.queueUploadMeta(e)}}handleGroupMetaChange(e){const t=e.dataset.groupId;if(!t)return;const s=e.name;if(!s)return;const i=e.value,a=s.replace(`${t}[`,"").replace(`${t}_`,"").replace("]","");window.debouncer.schedule(`group-meta-${t}-${a}`,(async()=>{const e=this.stores.groups.get(t);e&&(e.fields||(e.fields={}),e.fields[a]=i,await this.setGroup(t,e))}),300)}handleDragEnter(e){if(!e.dataTransfer.types.includes("Files"))return;const t=e.target.closest(this.selectors.fields.dropZone);t&&(e.preventDefault(),t.classList.add("dragover"))}handleDragLeave(e){const t=e.target.closest(this.selectors.fields.dropZone);t&&!t.contains(e.relatedTarget)&&t.classList.remove("dragover")}handleDragOver(e){if(!e.dataTransfer.types.includes("Files"))return;e.target.closest(this.selectors.fields.dropZone)&&(e.preventDefault(),e.dataTransfer.dropEffect="copy")}handleDrop(e){const t=e.target.closest(this.selectors.fields.dropZone);if(!t)return;e.preventDefault(),t.classList.remove("dragover"),t.classList.add("uploading");const s=Array.from(e.dataTransfer.files);if(0===s.length)return;const i=this.getFieldIdFromElement(t);i&&(this.processFiles(i,s).then((()=>{this.updateHandlerItems(i)})),this.a11y.announce(`${s.length} file(s) dropped for upload`))}async queueUploads(e,t,s=null){let i=new FormData;const a=this.fields.get(t);if(!a)return;let r=this.stores.uploads.filterByIndex({field:t});if(0===r.length)return;const[o,l]=["uploads"===e,"uploads/groups"===e];let d,n,u,p,c;i.append("fieldId",a.id),i.append("content",a.config.content),o&&(i.append("mode",a.config.mode),i.append("field_name",a.config.repeaterPath||a.config.name),i.append("fieldId",a.id),i.append("field_type",a.config.type),i.append("subtype",a.config.subtype),i.append("item_id",a.config.itemID),i.append("destination",a.config.destination),s&&i.append("depends_on",s)),l?({posts:d,uploadMap:n,files:u}=this.collectGroups(t)):o&&({uploadMap:n,files:u}=this.collectUploads(t)),l&&i.append("posts",JSON.stringify(d)),u.forEach((e=>{i.append("files[]",e)})),i.append("upload_ids",JSON.stringify(n)),o?(p=`Uploading ${r.length} file${r.length>1?"s":""} to server...`,c=`Uploading ${r.length} file${r.length>1?"s":""}...`):l&&(p=`Creating ${d.length} ${a.config.content}${d.length>1?"s":""} from uploads...`,c=`Creating ${d.length} post${d.length>1?"s":""}...`),await this.setBulkUpload(r,"status","queued");let h=this.sendToQueue(e,i,p,c);if("uploads/groups"===e){let e=a.element.closest("details");e&&(e.open=!1),this.notify("groups_uploaded",{fieldId:t,posts:d,content:a.config.content})}return h?(a.operationId=h,await this.setBulkUpload(r,"operationId",h),await this.setBulkUpload(r,"status","uploading"),await this.setBulkGroup(t,"operationId",h),this.fields.set(a.id,a),this.notify("sent-to-queue",{field:a,operation:h})):await this.setBulkUpload(r,"status","failed"),h}async sendToQueue(e,t,s="",i="",a=!1){""===i&&(i=s);const r={endpoint:e,method:"POST",data:t,title:s,popup:i,canMerge:a,sendNow:"uploads/groups"===e,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},append:"_upload"};try{return await this.queue.addToQueue(r)}catch(e){return this.error.log(e,{component:"UploadManager",action:"sentToQueue"}),!1}}collectGroups(e){let t=this.stores.uploads.filterByIndex({field:e}),s=[],i=[],a=[];const r=this.stores.groups.filterByIndex({field:e}).filter((e=>{const t=this.getGroupUploadsInOrder(e);return t.length>0&&t.some((e=>this.formatFile(e)))}));for(const e of r){const t=this.groups.get(e.id)?.element,r=this.collectGroupFieldsFromDOM(t,e.id),o={groupId:e.id,images:[],fields:r},l=this.getGroupUploadsInOrder(e);for(const t of l){const s=this.formatFile(t);if(s){a.push(s);const r={upload_id:t.id,index:i.length},l=this.uploads.get(t.id),d=l?.element?.querySelector(`input[name="${e.id}_featured"]`);d?.checked&&(o.fields.featured=t.id),o.images.push(r),i.push(t.id)}}o.images.length>0&&s.push(o)}const o=t.filter((e=>!e.group));for(const e of o){const t={groupId:window.generateID("group"),images:[],fields:{}},r=this.formatFile(e);if(r){a.push(r);const s={upload_id:e.id,index:i.length};t.images.push(s),i.push(e.id)}t.images.length>0&&s.push(t)}return{posts:s,uploadMap:i,files:a}}getGroupUploadsInOrder(e){return e.uploads&&0!==e.uploads.length?e.uploads.map((e=>this.stores.uploads.get(e))).filter(Boolean):[]}collectGroupFieldsFromDOM(e,t){if(!e)return{};const s={};return e.querySelectorAll("input, textarea, select").forEach((e=>{const i=e.name.replace(`${t}[`,"").replace(`${t}_`,"").replace("]","");["featured","select-all"].some((e=>i.includes(e)))||e.value&&(s[i]=e.value)})),s}collectUploads(e){let t=this.stores.uploads.filterByIndex({field:e});if(0===t.length)return;let s=[],i=[];for(const e of t){const t=this.formatFile(e);t&&(i.push(t),s.push(e.id))}return{uploadMap:s,files:i}}queueUploadMeta(e){let t=e.target.closest("[data-attachment-id]")?.dataset.attachmentId,s=!1;if(!t&&(t=e.target.closest("[data-upload-id]")?.dataset.uploadId,s=!0,!t))return;if(!this.changes.has(t)){let e={};s?e.uploadId=t:e.attachmentId=t,this.changes.set(t,e)}let i=e.target.closest("[data-field]").dataset.field;this.changes.get(t)[i]=e.target.value,this.scheduleSave()}scheduleSave(){window.debouncer.schedule("upload-meta",(async()=>{if(this.changes.size>0){let e={};for(let[t,s]of this.changes.entries())console.log(t,s),e[t]=s;let t={user:window.auth.getUser(),items:e};await this.sendToQueue("uploads/meta",t,"Uploading Meta","Uploading Meta",!0),this.changes.clear()}}),2e3)}scanFields(e,t=!0,s=!0){e.querySelectorAll(this.selectors.fields.field).forEach((e=>this.registerField(e,t,s)))}registerField(e,t=!0,s=!0,i=null){const a={element:e,id:i||this.determineFieldId(e),config:this.extractFieldConfig(e,t,s),uploads:new Set,operationId:null,groups:[],ui:window.uiFromSelectors(this.selectors.fields,e),groupUI:window.uiFromSelectors(this.selectors.groups,e)};return this.fields.set(a.id,a),e.dataset.uploader=a.id,this.getSelectionHandler(a.id),"single"!==a.config.type&&this.initSortable(a.id),this.maybeLockUploads(a.id),a.id}extractFieldConfig(e,t,s){const i={autoUpload:t,showMeta:s,destination:e.dataset.destination||"meta",content:this.extractFieldContent(e),mode:e.dataset.mode||"direct",type:e.dataset.type||"single",name:e.dataset.field,itemID:this.extractFieldItemId(e)??0,maxFiles:"max-files"in e.dataset?parseInt(e.dataset.maxFiles):0,subType:e.dataset.subtype??"image",repeaterPath:null},a=e.closest("[data-index]"),r=a?.closest("[data-field][data-repeater-id]");return r&&a&&(i.repeaterPath=`${r.dataset.field}:${a.dataset.index}:${i.name}`),i}extractFieldContent(e){return e.dataset.content||e.closest("dialog")?.dataset.content||e.closest("form")?.dataset.save||null}extractFieldItemId(e){return e.dataset.itemId||e.closest("dialog")?.dataset.itemId||null}determineFieldId(e){let t=this.extractFieldContent(e);t=null===t?"":t+"_";let s=this.extractFieldItemId(e);s=null===s?"":s+"_";const i=e.dataset.field||"",a=e.closest("[data-index]"),r=a?.closest("[data-field][data-repeater-id]");return r&&a?`${t}${s}${r.dataset.field}_${a.dataset.index}_${i}`:`${t}${s}${i}`}getFieldIdFromElement(e){const t=e.closest(this.selectors.fields.field);return t?.dataset.uploader||null}updateFieldProgress(e,t,s,i){const a=this.fields.get(e);a&&window.showProgress(a.ui.progress,t,s,i)}getWorker(){return this.workerState.worker||"undefined"==typeof OffscreenCanvas||(this.workerState.worker=new Worker("worker.js"),this.workerState.worker.onmessage=e=>this.handleWorkerMessage(e),this.workerState.worker.onerror=e=>this.handleWorkerError(e)),this.workerState.worker}handleWorkerMessage(e){const{id:t,blob:s}=e.data,i=this.workerState.tasks.get(t);i&&(clearTimeout(i.timeoutId),i.resolve(s),this.workerState.tasks.delete(t))}handleWorkerError(e){this.workerState.tasks.forEach((t=>{clearTimeout(t.timeoutId),t.reject(e)})),this.workerState.tasks.clear(),this.restartWorker()}restartWorker(){this.workerState.worker&&(this.workerState.worker.terminate(),this.workerState.worker=null),this.workerState.restart.count++}async processImages(e,t=2200,s=2200){const i=[],a=[...e],r=this.workerState.settings.maxConcurrent,o=async()=>{for(;a.length>0;){const e=a.shift(),r=await this.processImage(e.file,t,s);i.push({uploadId:e.uploadId,blob:r})}};return await Promise.all(Array.from({length:Math.min(r,e.length)},(()=>o()))),i}async processImage(e,t=2200,s=2200,i=3e3){if("undefined"==typeof OffscreenCanvas)return this.resizeImage(e,t,s);try{return await this.withTimeout(this.workerImage(e,t,s),i)}catch(i){return this.resizeImage(e,t,s)}}withTimeout(e,t){return Promise.race([e,new Promise(((e,s)=>setTimeout((()=>s(new Error("Timeout"))),t)))])}async workerImage(e,t=2200,s=2200){const{settings:i,restart:a}=this.workerState;if(a.count>=a.max)throw new Error("Worker max restarts exceeded");const r=await createImageBitmap(e);let{width:o,height:l}=r;if(o>t||l>s){const e=Math.min(t/o,s/l);o=Math.round(o*e),l=Math.round(l*e)}const d=this.getWorker(),n=crypto.randomUUID();return new Promise(((t,s)=>{const a=setTimeout((()=>{this.workerState.tasks.delete(n),i.restartAfterTimeout&&this.restartWorker(),s(new Error("Timeout"))}),i.timeout);this.workerState.tasks.set(n,{resolve:t,reject:s,timeoutId:a}),d.postMessage({id:n,imageBitmap:r,width:o,height:l,type:e.type,quality:.9},[r])}))}resizeImage(e,t,s){return new Promise((i=>{const a=new Image;a.onload=()=>{URL.revokeObjectURL(a.src);let{width:r,height:o}=a;if(r>t||o>s){const e=Math.min(t/r,s/o);r=Math.round(r*e),o=Math.round(o*e)}const l=document.createElement("canvas");l.width=r,l.height=o,l.getContext("2d").drawImage(a,0,0,r,o),l.toBlob(i,e.type,.9)},a.src=URL.createObjectURL(e)}))}async processFiles(e,t){let s=this.fields.get(e);if(!s)return;s.groupUI.container&&(s.groupUI.container.hidden=!1);const i=t.length;let a=0;this.updateFieldProgress(e,0,i,"Processing files...");const r=await Promise.all(t.map((async t=>{const s=window.generateID("upload"),i=await this.setUpload(s,{id:s,field:e,status:"local_processing",fields:{originalName:t.name,originalSize:t.size,type:t.type,lastModified:t.lastModified}}),a=await this.createUpload(s,t,e);return this.uploads.set(s,{element:a,ui:window.uiFromSelectors(this.selectors.items,a)}),await this.addToGroup(s,null),{uploadId:s,upload:i,file:t}}))),o=r.filter((e=>e.file.type.startsWith("image/"))),l=r.filter((e=>!e.file.type.startsWith("image/"))),d=await this.processImages(o.map((e=>({file:e.file,uploadId:e.uploadId}))));for(const{uploadId:t,blob:s}of d){const r=o.find((e=>e.uploadId===t));r&&(r.upload.blob=s,r.upload.fields.size=s.size,r.upload.status="queued",await this.setUpload(t,r.upload),a++,this.updateFieldProgress(e,a,i,"Processing files..."))}for(const{uploadId:t,upload:s,file:r}of l)s.blob=r,s.status="queued",await this.setUpload(t,s),a++,this.updateFieldProgress(e,a,i,"Processing files...");this.maybeLockUploads(e),s.config.autoUpload&&"post_group"!==s.config.destination&&await this.queueUploads("uploads",e)}async checkRecovery(){const e=Array.from(this.stores.groups.data.values());for(const t of e){this.stores.uploads.filterByIndex({group:t.id}).length>0||await this.stores.groups.delete(t.id)}}async restoreSelectedUploads(e){let t=window.location.href,s=Array.from(this.stores.uploads.data.values()).filter((s=>e.includes(s.id)&&s.src===t)),i=[...new Set(s.map((e=>e.group)))].filter(Boolean),a=s[0].field,r=document.querySelector(`[data-uploader="${a}"]`);if(!r){if(!("crudManager"in window)||!a.startsWith(window.crudManager.content))return void console.log("No field found for "+a);{let[e,t,s]=a.split("_");if(!(parseInt(t)>0))return void console.log("No field found for "+a);window.crudManager.openEditModal(t),r=document.querySelector(`[data-uploader="${a}"]`)}}let o=this.fields.get(a);o.groupUI.container&&(o.groupUI.container.hidden=!1);let l=[];for(let e of i){let t=this.stores.groups.get(e);await this.createGroup(a,e);let i=this.groups.get(e),r=s.filter((t=>t.group===e));if(t&&this.groups.has(e)){let e=t.fields;for(const[t,s]of Object.entries(e)){let e=i.element.querySelector(`input[name*="${t}"]`);e&&(e.value=s)}}else e=null;for(let t of r){let s=await this.createUpload(t.id,this.formatFile(t),a);this.uploads.set(t.id,{element:s,ui:window.uiFromSelectors(this.selectors.items,s)}),await this.addToGroup(t.id,e),l.push(t.id)}}let d=s.filter((e=>!l.includes(e.id)));for(let e of d){let t=await this.createUpload(e.id,this.formatFile(e),a);this.uploads.set(e.id,{element:t,ui:window.uiFromSelectors(this.selectors.items,t)}),await this.addToGroup(e.id,null)}}async restoreUploads(e){const t=e.map((e=>this.stores.uploads.get(e))).filter(Boolean);0!==t.length&&await this.restoreSelectedUploads(t.map((e=>e.id)))}async clearUploads(e){await Promise.all(e.map((e=>this.clearUpload(e))))}getStatusText(e){return{received:"Image Received",local_processing:"Processing Image...",queued:"Waiting to upload...",uploading:"Uploading to Server",pending:"Successfully sent to server. In line for further processing.",processing:"Processing on server...",completed:"Upload complete!",failed:"Upload failed (will retry)",failed_permanent:"Upload failed permanently"}[e]||e}getStatusProgress(e){return{local_processing:28,queued:50,uploading:66,pending:75,processing:89,completed:100}[e]??0}async createUpload(e,t,s){let i=this.fields.get(s);if(!i)return null;let a={uploadId:e,file:t,field:i};return this.templates.create("uploadItem",a)}getSubtypeFromURL(e){if(!e||""===e)return"";const t=e.split("?")[0].toLowerCase();return[".webp",".jpg",".jpeg",".png",".gif",".svg"].some((e=>t.endsWith(e)))?"image":[".mp4",".ogg",".mov",".webm",".avi"].some((e=>t.endsWith(e)))?"video":"document"}getSubtypeFromMime(e){return e.startsWith("image/")?"image":e.startsWith("video/")?"video":"document"}async handleRemoveItem(e){console.log("Handling remove upload");const t=e.closest(this.selectors.items.item);if(!t)return;const s=t.dataset.uploadId,i=t.dataset.id;if((s||i)&&confirm("Remove this item?")){if(s)await this.removeUpload(s);else{const s=this.getFieldIdFromElement(e);t.remove(),s&&(this.updateHiddenInput(s),this.maybeLockUploads(s))}this.a11y.announce("Item removed")}}updateHiddenInput(e){const t=this.fields.get(e);if(!t?.ui.hidden)return;const s=Array.from(t.ui.grid?.querySelectorAll(this.selectors.items.item)||[]).map((e=>Object.hasOwn(e.dataset,"id")&&e.dataset.id>0?e.dataset.id:Object.hasOwn(e.dataset,"upload-id")&&e.dataset.uploadId>0?e.dataset.uploadId:e.dataset.itemId)).filter(Boolean).join(",");t.ui.hidden.value!==s&&(t.ui.hidden.value=s,t.ui.hidden.dispatchEvent(new Event("change",{bubbles:!0})))}async setBulkUpload(e,t,s){const i=Array.from(e).map((async e=>{if("string"==typeof e&&(e=await this.stores.uploads.get(e)),e)return"status"===t&&await this.setUploadStatus(e,s),e[t]=s,this.stores.uploads.save(e)}));await Promise.all(i)}async setUploadStatus(e,t){"string"==typeof e&&(e=await this.stores.uploads.get(e)),e&&e.progress&&window.showProgress(e.progress,this.getStatusProgress(t),100,this.getStatusText(t),this.queue.icons[t]??"")}async removeUpload(e){let t=this.stores.uploads.get(e);if(!t)return;const s=t.field;if(t.group){let s=this.stores.groups.get(t.group);s.uploads=s.uploads.filter((t=>t!==e)),0===s.uploads.length?await this.removeGroup(s.id,!1):await this.stores.groups.save(s)}await this.clearUpload(e),this.updateHiddenInput(s),this.maybeLockUploads(s);let i=this.selectionHandlers.get(s);i&&i.deselect(e),this.a11y.announce("Upload removed")}async clearUpload(e){const t=this.uploads.get(e);if(t&&(this.revokePreviewUrl(t.preview),t.element)){const e=t.element.dataset.previewUrl;this.revokePreviewUrl(e),t.element.remove()}this.uploads.delete(e),await this.stores.uploads.delete(e)}async handleAddToGroup(e){const t=this.selected.get(e);if(!t||0===t.size)return;let s=await this.createGroup(e);s&&(await Promise.all(Array.from(t).map((e=>this.addToGroup(e,s)))),this.selectionHandlers.get(e)?.clearSelection(),this.a11y.announce(`Created group with ${t.size} items`))}async createGroup(e,t=null){let s=this.fields.get(e);if(!s)return;t||(t=window.generateID("group"));const i=this.createGroupElement(t,e);if(!i)return null;const a=s.groupUI.empty;a?.nextSibling?s.groupUI.grid.insertBefore(i,a.nextSibling):s.groupUI.grid.append(i);const r=i.querySelector(".item-grid");r&&(r.dataset.groupId=t,this.createSortable(e,r,t));let o=this.stores.groups.data.has(t)?this.stores.groups.data.get(t):{};return await this.setGroup(t,{...o,id:t,field:e}),t}createGroupElement(e,t=null){let s={groupId:e,fieldId:t},i=this.templates.create("imageGroup",s);return this.groups.set(e,{element:i,ui:window.uiFromSelectors(this.selectors.group,i)}),this.getSelectionHandler(t)?.addWrapper(i),i}async setGroup(e,t){const s={...{id:e,src:window.location.href,uploads:[],operationId:null,field:null,fields:{}},...t};Object.preventExtensions(s),await this.stores.groups.save(s)}async setBulkGroup(e,t,s){let i=this.stores.groups.filterByIndex({field:e});if(0===i.length)return;let a=i.map((e=>{e[t]=s,this.stores.groups.save(e)}));await Promise.all(a)}async addToGroup(e,t=null){const s=this.stores.uploads.get(e),i=this.uploads.get(e);if(!s||!i)return;const a=this.fields.get(s.field);if(!a)return;if(null!==i.element?.parentElement&&(!t&&null===s.group||t===s.group))return void this.handleReorder(s.field,t);if(s.group){const t=this.stores.groups.get(s.group);t&&(t.uploads=t.uploads.filter((t=>t!==e)),0===t.uploads.length?await this.removeGroup(t.id,!1):await this.stores.groups.save(t))}i.ui.checkbox&&(i.ui.checkbox.checked=!1);const r=this.selectionHandlers.get(s.field);if(r&&r.isSelected(e)&&r.deselect(e),this.selected.get(s.field)?.has(e)&&this.selected.get(s.field).delete(e),i.ui.featured&&(i.ui.featured.hidden=!t),t){i.ui.featured&&(i.ui.featured.name=`${t}_featured`);let a=this.stores.groups.get(t);a&&(a.uploads.push(e),s.group=t,await this.stores.groups.save(a))}else s.group=null;let o=t?this.groups.get(t)?.ui.grid:a.ui.grid;o&&(o.append(i.element),t&&await this.handleReorder(s.field,t)),await this.stores.uploads.save(s)}handleDeleteGroup(e){const t=e.closest(this.selectors.group.item);if(!t)return;let s=t.dataset.groupId;if(!confirm("Delete this group? Items will be moved back to the upload area."))return;let i=this.stores.uploads.filterByIndex({group:s});Promise.all(i.map((e=>this.addToGroup(e.id,null)))).then((()=>{this.removeGroup(s,!1).then((()=>{})),this.a11y.announce("Group deleted. Items returned to upload area")}))}async removeGroup(e,t=!0){let s=this.groups.get(e),i=this.stores.groups.get(e);if(!i)return;let a=!0;t&&i.uploads.length>0&&(a=window.confirm("Keep uploads in this group?")),await Promise.all(i.uploads.map((e=>a?this.addToGroup(e,null):this.removeUpload(e))));if(this.fields.get(i.field)){const t=this.getGroupKey(i.field,e),a=this.selectionHandlers.get(t);if(a?.destroy&&a.destroy(),this.selectionHandlers.get(i.field)&&s&&s.element&&this.selectionHandlers.get(i.field).removeWrapper(s.element),this.sortables.has(t)){const e=this.sortables.get(t);e?.destroy&&e.destroy(),this.sortables.delete(t)}}s?.element&&s.element.remove(),this.groups.delete(e),await this.stores.groups.delete(e),this.a11y.announce("Group removed")}maybeLockUploads(e){let t=this.fields.get(e);if(!t||!t.ui.dropZone)return;let s=this.stores.uploads.filterByIndex({field:e}).length,i=t.config.maxFiles??0;t.ui.dropZone.hidden=i>0&&s>=i}async handleOperationCancelled(e){0!==e.length&&e.forEach((e=>{this.removeUpload(e)}))}getGroupKey(e,t=null){return t?`${e}_${t}`:`${e}`}getSelectionHandler(e){let t=this.getGroupKey(e);if(!this.selectionHandlers.has(t)){let s=this.fields.get(e);if(!s)return;if("post_group"!==s.config.destination)return;let i=new window.jvbHandleSelection(s.element,{selectAll:{checkbox:this.selectors.fields.selectAll,count:this.selectors.fields.count,bulkControls:this.selectors.fields.actions},item:{item:this.selectors.items.item,checkbox:this.selectors.items.checkbox,idAttribute:"uploadId"},wrapper:{wrapper:".preview-wrap, .upload-group",id:"groupId"}});i.subscribe(((t,s)=>{this.selected.set(e,s.selectedItems)})),this.selectionHandlers.set(t,i)}return this.selectionHandlers.get(t)}updateHandlerItems(e){let t=this.getSelectionHandler(e);t&&t.collectItems()}initSortable(e){if(!window.Sortable)return;const t=this.fields.get(e);t&&(!Sortable._multiDragMounted&&Sortable.MultiDrag&&(Sortable.mount(new Sortable.MultiDrag),Sortable._multiDragMounted=!0),this.createSortable(e,t.ui.grid,null),this.initEmptyGroupDropZone(e))}createSortable(e,t,s){if(!t)return null;const i=this.getGroupKey(e,s);if(this.sortables.has(i))return this.sortables.get(i);const a=new Sortable(t,{animation:150,draggable:".item",multiDrag:!0,selectedClass:"selected",avoidImplicitDeselect:!0,group:{name:e,pull:!0,put:!0},dragClass:"dragging",ignore:".empty-group",onStart:t=>{const s=t.item,i=s?.dataset.uploadId,a=this.selected.get(e);if(i&&(!a||!a.has(i))){const t=this.selectionHandlers.get(e);t&&t.select(i)}},onEnd:t=>this.sortableDrop(t,e)});return this.sortables.set(i,a),a}initEmptyGroupDropZone(e){const t=this.fields.get(e),s=t?.groupUI?.empty;s&&(s.addEventListener("dragover",(e=>{e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect="move",s.classList.add("drag-over")})),s.addEventListener("dragleave",(e=>{s.contains(e.relatedTarget)||s.classList.remove("drag-over")})),s.addEventListener("drop",(async t=>{t.preventDefault(),t.stopPropagation(),s.classList.remove("drag-over");const i=this.selected.get(e);if(!i||0===i.size)return;const a=await this.createGroup(e);a&&(await Promise.all(Array.from(i).map((e=>this.addToGroup(e,a)))),this.selectionHandlers.get(e)?.clearSelection())})))}async sortableDrop(e,t){const s=e.to,i=(e.items?.length>0?Array.from(e.items):[e.item]).map((e=>e.dataset.uploadId)).filter(Boolean);if(0===i.length)return;const a=s.dataset.groupId||null;for(const e of i)await this.addToGroup(e,a);await this.handleReorder(t,a),this.selectionHandlers.get(t)?.clearSelection()}handleReorder(e,t=null){let s=t?this.groups.get(t)?.ui.grid:this.fields.get(e)?.ui.grid;if(s){if(t){let e=Array.from(s.children).filter((e=>e.matches(this.selectors.items.item)&&!e.classList.contains("ghost"))).map((e=>e.dataset.uploadId)).filter((e=>e)),i=this.stores.groups.get(t);i&&(i.uploads=e,this.stores.groups.save(i).then((()=>{})))}else this.updateHiddenInput(e);this.a11y.announce("Items reordered")}else console.log("Couldn't Reorder items...")}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t={}){this.subscribers.forEach((s=>{try{s(e,t)}catch(e){console.error("Subscriber error:",e)}}))}destroy(){this.subscribers.clear(),this.previewUrls.forEach((e=>{this.revokePreviewUrl(e)})),this.previewUrls.clear()}cleanupAllPreviewUrls(){this.previewUrls.forEach((e=>this.revokePreviewUrl(e))),this.previewUrls.clear()}async handleClearCache(){const e=window.location.href,t=this.stores.uploads.filterByIndex({src:e}),s=this.stores.groups.filterByIndex({src:e});await Promise.all([...t.map((e=>this.clearUpload(e.id))),...s.map((e=>(this.groups.get(e.id)?.element?.remove(),this.groups.delete(e.id),this.stores.groups.delete(e.id))))]),this.restoreModal&&this.cleanupRestore(),this.a11y.announce("Cache cleared for this page")}async getFilesForForm(e){const t=e.querySelectorAll(this.selectors.fields.field),s=[];for(const e of t){const t=this.determineFieldId(e),i=e.dataset.field,a=this.stores.uploads.filterByIndex({field:t});for(const e of a){const t=this.formatFile(e);t&&s.push({file:t,fieldName:i,uploadId:e.id,meta:e.fields||{}})}}return s}async clearFieldFromStores(e){const t=this.stores.uploads.filterByIndex({field:e}),s=this.stores.groups.filterByIndex({field:e});await Promise.all(t.map((e=>this.clearUpload(e.id)))),await Promise.all(s.map((e=>(this.groups.get(e.id)?.element?.remove(),this.groups.delete(e.id),this.stores.groups.delete(e.id)))))}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbUploads=new e)}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.queue=window.jvbQueue,this.error=window.jvbError,this.templates=window.jvbTemplates,this.subscribers=new Set,this.initStores(),this.initWorker(),this.fields=new Map,this.uploads=new Map,this.groups=new Map,this.selected=new Map,this.selectionHandlers=new Map,this.sortables=new Map,this.changes=new Map,this.previewUrls=new Set,this.initElements(),this.initListeners(),this.defineTemplates()}defineTemplates(){const e=this.templates,t=this;e.define("uploadItem",{refs:{select:'[name="select-item"]',featured:'[name="featured"]',img:"img",video:"video",file:"label > span",details:"details",alt:'[name="image-alt-text"]',title:'[name="image-title"]',description:'[name="image-caption"]'},manyRefs:{inputs:"input, select, textarea"},setup({el:e,refs:s,manyRefs:i,data:a}){let r,o,l,d=!1;switch(Object.hasOwn(a,"file")?(e.dataset.uploadId=a.uploadId,r=t.getSubtypeFromMime(a.file.type)||"image",o="document"!==r&&t.createPreviewUrl(a.file),d=o,l=a.file.name||""):(e.dataset.id=a.id,r=t.getSubtypeFromURL(a.medium??a.src),o=a.medium??a.src,l=a["image-alt-text"]??""),e.dataset.subtype=r,s.featured&&(s.featured.value=a.uploadId),r){case"image":s.img&&(s.img.src=o,s.img.alt=l,d&&(s.img.dataset.previewUrl=d)),s.video&&s.video.remove(),s.file&&s.file.remove();break;case"video":s.video&&(s.video.src=o,s.video.alt=l,d&&(s.video.dataset.previewUrl=d)),s.img&&s.img.remove(),s.file&&s.file.remove();break;case"document":if(s.preview){let e=a.file.name.split(".").pop()?.toLowerCase()??"",t={pdf:"file-pdf",csv:"file-csv",doc:"file-doc",docx:"file-doc",txt:"file-txt",xls:"file-xls",xlsx:"file-xls"},i=window.getIcon(t[e]??"file");s.preview.innerText=a.file.name??a.title,s.preview.prepend(i)}s.img&&s.img.remove(),s.video&&s.video.remove()}if(s.details&&(Object.hasOwn(a,"field")&&Object.hasOwn(a.field,"config")&&Object.hasOwn(a.field.config,"showMeta")&&!a.field.config.showMeta?s.details.remove():(Object.hasOwn(a,"id")?s.details.dataset.attachmentId=a.id:Object.hasOwn(a,"uploadId")&&(s.details.dataset.uploadId=a.uploadId),s.details.setAttribute("data-ignore",""),"image"!==r&&s.alt?s.alt.closest(".field")?.remove():Object.hasOwn(a,"image-alt-text")&&s.alt&&(s.alt.value=a["image-alt-text"]),(Object.hasOwn(a,"title")||Object.hasOwn(a,"file"))&&s.title&&(s.title.value=a.title||a.file.name),Object.hasOwn(a,"image-caption")&&s.description&&(s.description.value=a["image-caption"]))),e.draggable="single"!==e.dataset.mode,i.inputs)for(let t of i.inputs){let s=t.closest("[data-field]")??t.closest(".radio-button")??e;window.prefixInput(t,`${a.id??a.uploadId}-`,s)}}}),e.define("imageGroup",{refs:{selectAll:"[data-select-all]",fields:".fields",details:"details",grid:".item-grid"},setup({el:t,refs:s,manyRefs:i,data:a}){if(t.dataset.groupId=a.groupId,s.selectAll){let e=s.selectAll.closest(".field");window.prefixInput(s.selectAll,`select-all-${a.groupId}`,e,!0)}let r=e.create("groupMetadata",{groupId:a.groupId});r?s.fields.append(r):s.details.remove(),s.grid&&(s.grid.dataset.groupId=a.groupId)}}),e.define("groupMetadata",{manyRefs:{inputs:"input,textarea,select"},setup({el:e,refs:t,manyRefs:s,data:i}){s.inputs&&s.inputs.forEach(e=>{let t=e.closest("[data-field]");e.dataset.groupId=i.groupId,window.prefixInput(e,`${i.groupId}-`,t)})}}),e.define("restoreNotification",{refs:{details:".details",wrap:".wrap"},setup({el:t,refs:s,manyRefs:i,data:a}){if(s.details){let e=a.bySource.size>1?` across ${a.bySource.size} pages`:"",t=a.pendingUploads.length>1?"uploads":"upload";s.details.textContent=`${a.pendingUploads.length} ${t} can be recovered${e}`}if(!s.wrap)return void console.warn("No wrap element in template");let r=1;for(const[t,i]of a.bySource){let a={index:r,isCurrent:t===window.location.href,src:t,uploads:i};s.wrap.append(e.create("restoreField",a)),r++}}}),e.define("restoreField",{refs:{h3:"h3",a:"h3 a",grid:".item-grid"},async setup({el:e,refs:s,manyRefs:i,data:a}){let r=t.registerField(e,!1,!1,`recovery_${a.index}`);a.isCurrent?(e.open=!0,s.a?.remove(),s.h3&&(s.h3.textContent="From this page:")):s.a&&(s.a.href=a.src,s.a.title="Navigate to page and restore",s.a.textContent=a.src);let o=[...new Set(a.uploads.map(e=>e.group??"preview"))];for(let e of o){let i="preview"===e||t.stores.groups.get(e);if(!i)continue;let o=await t.createGroupElement(e,r),l=o.querySelector(".item-grid"),d=a.uploads.filter(t=>t.group===("preview"===e)?null:e);for(const[e,t]of Object.entries(i.fields??{})){let s=o.querySelector(`input[name*="${e}"]`);s&&(s.value=t)}for(let e of d){let s=await t.createUpload(e.id,t.formatFile(e),r);l.append(s)}s.grid.append(o)}}})}initStores(){const{uploads:e,groups:t}=window.jvbStore.register("uploads",[{storeName:"uploads",keyPath:"id",indexes:[{name:"field",keyPath:"field"},{name:"status",keyPath:"status"},{name:"group",keyPath:"group"},{name:"src",keyPath:"src"}]},{storeName:"groups",keyPath:"id",indexes:[{name:"field",keyPath:"field"},{name:"src",keyPath:"src"}]}]);this.stores={uploads:e,groups:t,ready:[]},this.stores.uploads.subscribe(this.handleStores.bind(this,"uploads")),this.stores.groups.subscribe(this.handleStores.bind(this,"groups")),this.queue.subscribe((e,t)=>{if(("operation-status"===e||"cancel-operation"===e)&&["image_upload","video_upload","document_upload"].includes(t.type)){let s=[];if(t.data)if(t.data instanceof FormData){const e=this.stores.uploads.formDataToObject(t.data);s=e.upload_ids||[]}else s=t.data.upload_ids||[];if(0===s.length&&t.result&&t.result.upload_ids&&(s=t.result.upload_ids),!s||0===s.length)return void console.warn("[UploadManager] No upload_ids found for operation:",{id:t.id,type:t.type,status:t.status,hasData:!!t.data,hasResult:!!t.result});if("cancel-operation"===e)return this.handleOperationCancelled(s);this.setBulkUpload(s,"status",t.status).then(()=>{console.log(`[UploadManager] Updated ${s.length} uploads to status: ${t.status}`)}),"completed"===t.status&&("process_upload_groups"===t.type?(s.forEach(e=>{this.setBulkUpload([e],"serverProcessed",!0).then(()=>{})}),t.result&&t.result.created_posts&&console.log("[UploadManager] Created posts:",t.result.created_posts),setTimeout(()=>{s.forEach(e=>{this.removeUpload(e).then(()=>{})})},2e3)):s.forEach(e=>{this.removeUpload(e).then(()=>{})})),"failed"!==t.status&&"failed_permanent"!==t.status||console.error("[UploadManager] Operation failed:",{id:t.id,type:t.type,uploadIds:s,error:t.error_message})}})}storesReady(){return 2===this.stores.ready.length}handleStores(e,t){"data-ready"===t&&(this.stores.ready.push(e),this.storesReady()&&this.checkRecovery().then(()=>{}))}initWorker(){this.worker=null,this.workerState={worker:null,tasks:new Map,restart:{count:0,max:3},settings:{timeout:3e3,maxConcurrent:3,restartAfterTimeout:!0}}}initElements(){this.selectors={fields:{field:"[data-upload-field]",input:'input[type="file"]',dropZone:".file-upload-wrapper",preview:".preview-wrap",grid:".item-grid.preview",progress:{progress:".file-upload-container .progress",fill:".file-upload-container .progress .fill",details:".file-upload-container .progress .details",icon:".file-upload-container .progress .icon"},selectAll:"[data-select-all]",actions:".selection-actions",count:".selected .info",hidden:'input[type="hidden"]'},groups:{container:".group-display",grid:".item-grid.groups",empty:".empty-group",header:".sidebar .header"},group:{item:".upload-group",actions:".selection-actions",selectAll:'[name="select-all-group"]',count:".group-header .info",fields:"details .fields",grid:".item-grid.group",total:".group-content .group-count"},items:{item:".item.upload",checkbox:'[name="select-item"]',featured:'[name="featured"]',image:"img",details:"details",progress:{progress:".progress",fill:".fill",details:".details",icon:".icon"}}}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.dragEnterHandler=this.handleDragEnter.bind(this),this.dragLeaveHandler=this.handleDragLeave.bind(this),this.dragOverHandler=this.handleDragOver.bind(this),this.dropHandler=this.handleDrop.bind(this),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler),document.addEventListener("dragenter",this.dragEnterHandler),document.addEventListener("dragleave",this.dragLeaveHandler),document.addEventListener("dragover",this.dragOverHandler),document.addEventListener("drop",this.dropHandler),window.addEventListener("beforeunload",()=>{this.cleanupAllPreviewUrls()})}async setUpload(e,t){const s={...{id:e,attachment:null,group:null,field:null,src:window.location.href,blob:null,status:"local_processing",operationId:null,fields:{}},...t};if(Object.preventExtensions(s),await this.stores.uploads.save(s),this.fields.has(s.field)){let e=this.fields.get(s.field);if("local_processing"===s.status)this.notify("upload-received",{field:e.element,id:s.id})}return s}createPreviewUrl(e){const t=URL.createObjectURL(e);return this.previewUrls.add(t),t}revokePreviewUrl(e){e?.startsWith("blob:")&&(URL.revokeObjectURL(e),this.previewUrls.delete(e))}formatFile(e){return e.blob?new File([e.blob],e.fields.originalName||"file",{type:e.fields.type||e.blob.type,lastModified:e.fields.lastModified||Date.now()}):null}handleClick(e){if(!window.targetCheck(e,this.selectors.fields.field))return;let t=window.targetCheck(e,this.selectors.fields.dropZone);t&&!e.target.matches("input, button, a")&&t.querySelector(this.selectors.fields.input)?.click();const s=window.targetCheck(e,"[data-action]");s&&this.handleAction(s)}handleAction(e){const t=e.dataset.action,s=this.getFieldIdFromElement(e);switch(t){case"add-to-group":this.handleAddToGroup(s).then(()=>{});break;case"delete-group":this.handleDeleteGroup(e);break;case"delete-upload":case"remove-from-group":this.handleRemoveItem(e).then(()=>{});break;case"upload":this.queueUploads("uploads/groups",s).then(()=>{});break;case"restore":this.handleRestoreSelected().then(()=>{});break;case"restore-all":this.handleRestoreAll().then(()=>{});break;case"clear-cache":this.handleClearCache().then(()=>{})}}handleChange(e){let t=this.getFieldIdFromElement(e.target);if(!t){return void(e.target.closest("[data-upload-id], [data-attachment-id]")&&this.queueUploadMeta(e))}if(e.target.matches(this.selectors.fields.input)){const s=Array.from(e.target.files);return void(s.length>0&&this.processFiles(t,s).then(()=>{}))}e.target.matches(this.selectors.items.checkbox)||e.target.matches(this.selectors.items.featured)||e.target.matches('[name*="select-"]')||("post_group"===this.fields.get(t).config.destination?this.handleGroupMetaChange(e.target):this.queueUploadMeta(e))}handleGroupMetaChange(e){const t=e.dataset.groupId;if(!t)return;const s=e.name;if(!s)return;const i=e.value,a=s.replace(`${t}[`,"").replace(`${t}_`,"").replace("]","");window.debouncer.schedule(`group-meta-${t}-${a}`,async()=>{const e=this.stores.groups.get(t);e&&(e.fields||(e.fields={}),e.fields[a]=i,await this.setGroup(t,e))},300)}handleDragEnter(e){if(!e.dataTransfer.types.includes("Files"))return;const t=e.target.closest(this.selectors.fields.dropZone);t&&(e.preventDefault(),t.classList.add("dragover"))}handleDragLeave(e){const t=e.target.closest(this.selectors.fields.dropZone);t&&!t.contains(e.relatedTarget)&&t.classList.remove("dragover")}handleDragOver(e){if(!e.dataTransfer.types.includes("Files"))return;e.target.closest(this.selectors.fields.dropZone)&&(e.preventDefault(),e.dataTransfer.dropEffect="copy")}handleDrop(e){const t=e.target.closest(this.selectors.fields.dropZone);if(!t)return;e.preventDefault(),t.classList.remove("dragover"),t.classList.add("uploading");const s=Array.from(e.dataTransfer.files);if(0===s.length)return;const i=this.getFieldIdFromElement(t);i&&(this.processFiles(i,s).then(()=>{this.updateHandlerItems(i)}),this.a11y.announce(`${s.length} file(s) dropped for upload`))}async queueUploads(e,t,s=null){let i=new FormData;const a=this.fields.get(t);if(!a)return;let r=this.stores.uploads.filterByIndex({field:t});if(0===r.length)return;const[o,l]=["uploads"===e,"uploads/groups"===e];let d,n,u,p,c;i.append("fieldId",a.id),i.append("content",a.config.content),o&&(i.append("mode",a.config.mode),i.append("field_name",a.config.repeaterPath||a.config.name),i.append("fieldId",a.id),i.append("field_type",a.config.type),i.append("subtype",a.config.subtype),i.append("item_id",a.config.itemID),i.append("destination",a.config.destination),s&&i.append("depends_on",s)),l?({posts:d,uploadMap:n,files:u}=this.collectGroups(t)):o&&({uploadMap:n,files:u}=this.collectUploads(t)),l&&i.append("posts",JSON.stringify(d)),u.forEach(e=>{i.append("files[]",e)}),i.append("upload_ids",JSON.stringify(n)),o?(p=`Uploading ${r.length} file${r.length>1?"s":""} to server...`,c=`Uploading ${r.length} file${r.length>1?"s":""}...`):l&&(p=`Creating ${d.length} ${a.config.content}${d.length>1?"s":""} from uploads...`,c=`Creating ${d.length} post${d.length>1?"s":""}...`),await this.setBulkUpload(r,"status","queued");let h=this.sendToQueue(e,i,p,c);if("uploads/groups"===e){let e=a.element.closest("details");e&&(e.open=!1),this.notify("groups_uploaded",{fieldId:t,posts:d,content:a.config.content})}return h?(a.operationId=h,await this.setBulkUpload(r,"operationId",h),await this.setBulkUpload(r,"status","uploading"),await this.setBulkGroup(t,"operationId",h),this.fields.set(a.id,a),this.notify("sent-to-queue",{field:a,operation:h})):await this.setBulkUpload(r,"status","failed"),h}async sendToQueue(e,t,s="",i="",a=!1){""===i&&(i=s);const r={endpoint:e,method:"POST",data:t,title:s,popup:i,canMerge:a,sendNow:"uploads/groups"===e,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},append:"_upload"};try{return await this.queue.addToQueue(r)}catch(e){return this.error.log(e,{component:"UploadManager",action:"sentToQueue"}),!1}}collectGroups(e){let t=this.stores.uploads.filterByIndex({field:e}),s=[],i=[],a=[];const r=this.stores.groups.filterByIndex({field:e}).filter(e=>{const t=this.getGroupUploadsInOrder(e);return t.length>0&&t.some(e=>this.formatFile(e))});for(const e of r){const t=this.groups.get(e.id)?.element,r=this.collectGroupFieldsFromDOM(t,e.id),o={groupId:e.id,images:[],fields:r},l=this.getGroupUploadsInOrder(e);for(const t of l){const s=this.formatFile(t);if(s){a.push(s);const r={upload_id:t.id,index:i.length},l=this.uploads.get(t.id),d=l?.element?.querySelector(`input[name="${e.id}_featured"]`);d?.checked&&(o.fields.featured=t.id),o.images.push(r),i.push(t.id)}}o.images.length>0&&s.push(o)}const o=t.filter(e=>!e.group);for(const e of o){const t={groupId:window.generateID("group"),images:[],fields:{}},r=this.formatFile(e);if(r){a.push(r);const s={upload_id:e.id,index:i.length};t.images.push(s),i.push(e.id)}t.images.length>0&&s.push(t)}return{posts:s,uploadMap:i,files:a}}getGroupUploadsInOrder(e){return e.uploads&&0!==e.uploads.length?e.uploads.map(e=>this.stores.uploads.get(e)).filter(Boolean):[]}collectGroupFieldsFromDOM(e,t){if(!e)return{};const s={};return e.querySelectorAll("input, textarea, select").forEach(e=>{const i=e.name.replace(`${t}[`,"").replace(`${t}_`,"").replace("]","");["featured","select-all"].some(e=>i.includes(e))||e.value&&(s[i]=e.value)}),s}collectUploads(e){let t=this.stores.uploads.filterByIndex({field:e});if(0===t.length)return;let s=[],i=[];for(const e of t){const t=this.formatFile(e);t&&(i.push(t),s.push(e.id))}return{uploadMap:s,files:i}}queueUploadMeta(e){let t=e.target.closest("[data-attachment-id]")?.dataset.attachmentId,s=!1;if(!t&&(t=e.target.closest("[data-upload-id]")?.dataset.uploadId,s=!0,!t))return;if(!this.changes.has(t)){let e={};s?e.uploadId=t:e.attachmentId=t,this.changes.set(t,e)}let i=e.target.closest("[data-field]").dataset.field;this.changes.get(t)[i]=e.target.value,this.scheduleSave()}scheduleSave(){window.debouncer.schedule("upload-meta",async()=>{if(this.changes.size>0){let e={};for(let[t,s]of this.changes.entries())console.log(t,s),e[t]=s;let t={user:window.auth.getUser(),items:e};await this.sendToQueue("uploads/meta",t,"Uploading Meta","Uploading Meta",!0),this.changes.clear()}},2e3)}scanFields(e,t=!0,s=!0){e.querySelectorAll(this.selectors.fields.field).forEach(e=>this.registerField(e,t,s))}registerField(e,t=!0,s=!0,i=null){const a={element:e,id:i||this.determineFieldId(e),config:this.extractFieldConfig(e,t,s),uploads:new Set,operationId:null,groups:[],ui:window.uiFromSelectors(this.selectors.fields,e),groupUI:window.uiFromSelectors(this.selectors.groups,e)};return this.fields.set(a.id,a),e.dataset.uploader=a.id,this.getSelectionHandler(a.id),"single"!==a.config.type&&this.initSortable(a.id),this.maybeLockUploads(a.id),a.id}extractFieldConfig(e,t,s){const i={autoUpload:t,showMeta:s,destination:e.dataset.destination||"meta",content:this.extractFieldContent(e),mode:e.dataset.mode||"direct",type:e.dataset.type||"single",name:e.dataset.field,itemID:this.extractFieldItemId(e)??0,maxFiles:"max-files"in e.dataset?parseInt(e.dataset.maxFiles):0,subType:e.dataset.subtype??"image",repeaterPath:null},a=e.closest("[data-index]"),r=a?.closest("[data-field][data-repeater-id]");return r&&a&&(i.repeaterPath=`${r.dataset.field}:${a.dataset.index}:${i.name}`),i}extractFieldContent(e){return e.dataset.content||e.closest("dialog")?.dataset.content||e.closest("form")?.dataset.save||null}extractFieldItemId(e){return e.dataset.itemId||e.closest("dialog")?.dataset.itemId||null}determineFieldId(e){let t=this.extractFieldContent(e);t=null===t?"":t+"_";let s=this.extractFieldItemId(e);s=null===s?"":s+"_";const i=e.dataset.field||"",a=e.closest("[data-index]"),r=a?.closest("[data-field][data-repeater-id]");return r&&a?`${t}${s}${r.dataset.field}_${a.dataset.index}_${i}`:`${t}${s}${i}`}getFieldIdFromElement(e){const t=e.closest(this.selectors.fields.field);return t?.dataset.uploader||null}updateFieldProgress(e,t,s,i){const a=this.fields.get(e);a&&window.showProgress(a.ui.progress,t,s,i)}getWorker(){return this.workerState.worker||"undefined"==typeof OffscreenCanvas||(this.workerState.worker=new Worker("worker.js"),this.workerState.worker.onmessage=e=>this.handleWorkerMessage(e),this.workerState.worker.onerror=e=>this.handleWorkerError(e)),this.workerState.worker}handleWorkerMessage(e){const{id:t,blob:s}=e.data,i=this.workerState.tasks.get(t);i&&(clearTimeout(i.timeoutId),i.resolve(s),this.workerState.tasks.delete(t))}handleWorkerError(e){this.workerState.tasks.forEach(t=>{clearTimeout(t.timeoutId),t.reject(e)}),this.workerState.tasks.clear(),this.restartWorker()}restartWorker(){this.workerState.worker&&(this.workerState.worker.terminate(),this.workerState.worker=null),this.workerState.restart.count++}async processImages(e,t=2200,s=2200){const i=[],a=[...e],r=this.workerState.settings.maxConcurrent,o=async()=>{for(;a.length>0;){const e=a.shift(),r=await this.processImage(e.file,t,s);i.push({uploadId:e.uploadId,blob:r})}};return await Promise.all(Array.from({length:Math.min(r,e.length)},()=>o())),i}async processImage(e,t=2200,s=2200,i=3e3){if("undefined"==typeof OffscreenCanvas)return this.resizeImage(e,t,s);try{return await this.withTimeout(this.workerImage(e,t,s),i)}catch(i){return this.resizeImage(e,t,s)}}withTimeout(e,t){return Promise.race([e,new Promise((e,s)=>setTimeout(()=>s(new Error("Timeout")),t))])}async workerImage(e,t=2200,s=2200){const{settings:i,restart:a}=this.workerState;if(a.count>=a.max)throw new Error("Worker max restarts exceeded");const r=await createImageBitmap(e);let{width:o,height:l}=r;if(o>t||l>s){const e=Math.min(t/o,s/l);o=Math.round(o*e),l=Math.round(l*e)}const d=this.getWorker(),n=crypto.randomUUID();return new Promise((t,s)=>{const a=setTimeout(()=>{this.workerState.tasks.delete(n),i.restartAfterTimeout&&this.restartWorker(),s(new Error("Timeout"))},i.timeout);this.workerState.tasks.set(n,{resolve:t,reject:s,timeoutId:a}),d.postMessage({id:n,imageBitmap:r,width:o,height:l,type:e.type,quality:.9},[r])})}resizeImage(e,t,s){return new Promise(i=>{const a=new Image;a.onload=()=>{URL.revokeObjectURL(a.src);let{width:r,height:o}=a;if(r>t||o>s){const e=Math.min(t/r,s/o);r=Math.round(r*e),o=Math.round(o*e)}const l=document.createElement("canvas");l.width=r,l.height=o,l.getContext("2d").drawImage(a,0,0,r,o),l.toBlob(i,e.type,.9)},a.src=URL.createObjectURL(e)})}async processFiles(e,t){let s=this.fields.get(e);if(!s)return;s.groupUI.container&&(s.groupUI.container.hidden=!1);const i=t.length;let a=0;this.updateFieldProgress(e,0,i,"Processing files...");const r=await Promise.all(t.map(async t=>{const s=window.generateID("upload"),i=await this.setUpload(s,{id:s,field:e,status:"local_processing",fields:{originalName:t.name,originalSize:t.size,type:t.type,lastModified:t.lastModified}}),a=await this.createUpload(s,t,e);return this.uploads.set(s,{element:a,ui:window.uiFromSelectors(this.selectors.items,a)}),await this.addToGroup(s,null),{uploadId:s,upload:i,file:t}})),o=r.filter(e=>e.file.type.startsWith("image/")),l=r.filter(e=>!e.file.type.startsWith("image/")),d=await this.processImages(o.map(e=>({file:e.file,uploadId:e.uploadId})));for(const{uploadId:t,blob:s}of d){const r=o.find(e=>e.uploadId===t);r&&(r.upload.blob=s,r.upload.fields.size=s.size,r.upload.status="queued",await this.setUpload(t,r.upload),a++,this.updateFieldProgress(e,a,i,"Processing files..."))}for(const{uploadId:t,upload:s,file:r}of l)s.blob=r,s.status="queued",await this.setUpload(t,s),a++,this.updateFieldProgress(e,a,i,"Processing files...");this.maybeLockUploads(e),s.config.autoUpload&&"post_group"!==s.config.destination&&await this.queueUploads("uploads",e)}async checkRecovery(){const e=Array.from(this.stores.groups.data.values());for(const t of e){this.stores.uploads.filterByIndex({group:t.id}).length>0||await this.stores.groups.delete(t.id)}}async restoreSelectedUploads(e){let t=window.location.href,s=Array.from(this.stores.uploads.data.values()).filter(s=>e.includes(s.id)&&s.src===t),i=[...new Set(s.map(e=>e.group))].filter(Boolean),a=s[0].field,r=document.querySelector(`[data-uploader="${a}"]`);if(!r){if(!("crudManager"in window)||!a.startsWith(window.crudManager.content))return void console.log("No field found for "+a);{let[e,t,s]=a.split("_");if(!(parseInt(t)>0))return void console.log("No field found for "+a);window.crudManager.openEditModal(t),r=document.querySelector(`[data-uploader="${a}"]`)}}let o=this.fields.get(a);o.groupUI.container&&(o.groupUI.container.hidden=!1);let l=[];for(let e of i){let t=this.stores.groups.get(e);await this.createGroup(a,e);let i=this.groups.get(e),r=s.filter(t=>t.group===e);if(t&&this.groups.has(e)){let e=t.fields;for(const[t,s]of Object.entries(e)){let e=i.element.querySelector(`input[name*="${t}"]`);e&&(e.value=s)}}else e=null;for(let t of r){let s=await this.createUpload(t.id,this.formatFile(t),a);this.uploads.set(t.id,{element:s,ui:window.uiFromSelectors(this.selectors.items,s)}),await this.addToGroup(t.id,e),l.push(t.id)}}let d=s.filter(e=>!l.includes(e.id));for(let e of d){let t=await this.createUpload(e.id,this.formatFile(e),a);this.uploads.set(e.id,{element:t,ui:window.uiFromSelectors(this.selectors.items,t)}),await this.addToGroup(e.id,null)}}async restoreUploads(e){const t=e.map(e=>this.stores.uploads.get(e)).filter(Boolean);0!==t.length&&await this.restoreSelectedUploads(t.map(e=>e.id))}async clearUploads(e){await Promise.all(e.map(e=>this.clearUpload(e)))}getStatusText(e){return{received:"Image Received",local_processing:"Processing Image...",queued:"Waiting to upload...",uploading:"Uploading to Server",pending:"Successfully sent to server. In line for further processing.",processing:"Processing on server...",completed:"Upload complete!",failed:"Upload failed (will retry)",failed_permanent:"Upload failed permanently"}[e]||e}getStatusProgress(e){return{local_processing:28,queued:50,uploading:66,pending:75,processing:89,completed:100}[e]??0}async createUpload(e,t,s){let i=this.fields.get(s);if(!i)return null;let a={uploadId:e,file:t,field:i};return this.templates.create("uploadItem",a)}getSubtypeFromURL(e){if(!e||""===e)return"";const t=e.split("?")[0].toLowerCase();return[".webp",".jpg",".jpeg",".png",".gif",".svg"].some(e=>t.endsWith(e))?"image":[".mp4",".ogg",".mov",".webm",".avi"].some(e=>t.endsWith(e))?"video":"document"}getSubtypeFromMime(e){return e.startsWith("image/")?"image":e.startsWith("video/")?"video":"document"}async handleRemoveItem(e){console.log("Handling remove upload");const t=e.closest(this.selectors.items.item);if(!t)return;const s=t.dataset.uploadId,i=t.dataset.id;if((s||i)&&confirm("Remove this item?")){if(s)await this.removeUpload(s);else{const s=this.getFieldIdFromElement(e);t.remove(),s&&(this.updateHiddenInput(s),this.maybeLockUploads(s))}this.a11y.announce("Item removed")}}updateHiddenInput(e){const t=this.fields.get(e);if(!t?.ui.hidden)return;const s=Array.from(t.ui.grid?.querySelectorAll(this.selectors.items.item)||[]).map(e=>Object.hasOwn(e.dataset,"id")&&e.dataset.id>0?e.dataset.id:Object.hasOwn(e.dataset,"upload-id")&&e.dataset.uploadId>0?e.dataset.uploadId:e.dataset.itemId).filter(Boolean).join(",");t.ui.hidden.value!==s&&(t.ui.hidden.value=s,t.ui.hidden.dispatchEvent(new Event("change",{bubbles:!0})))}async setBulkUpload(e,t,s){const i=Array.from(e).map(async e=>{if("string"==typeof e&&(e=await this.stores.uploads.get(e)),e)return"status"===t&&await this.setUploadStatus(e,s),e[t]=s,this.stores.uploads.save(e)});await Promise.all(i)}async setUploadStatus(e,t){"string"==typeof e&&(e=await this.stores.uploads.get(e)),e&&e.progress&&window.showProgress(e.progress,this.getStatusProgress(t),100,this.getStatusText(t),this.queue.icons[t]??"")}async removeUpload(e){let t=this.stores.uploads.get(e);if(!t)return;const s=t.field;if(t.group){let s=this.stores.groups.get(t.group);s.uploads=s.uploads.filter(t=>t!==e),0===s.uploads.length?await this.removeGroup(s.id,!1):await this.stores.groups.save(s)}await this.clearUpload(e),this.updateHiddenInput(s),this.maybeLockUploads(s);let i=this.selectionHandlers.get(s);i&&i.deselect(e),this.a11y.announce("Upload removed")}async clearUpload(e){const t=this.uploads.get(e);if(t&&(this.revokePreviewUrl(t.preview),t.element)){const e=t.element.dataset.previewUrl;this.revokePreviewUrl(e),t.element.remove()}this.uploads.delete(e),await this.stores.uploads.delete(e)}async handleAddToGroup(e){const t=this.selected.get(e);if(!t||0===t.size)return;let s=await this.createGroup(e);s&&(await Promise.all(Array.from(t).map(e=>this.addToGroup(e,s))),this.selectionHandlers.get(e)?.clearSelection(),this.a11y.announce(`Created group with ${t.size} items`))}async createGroup(e,t=null){let s=this.fields.get(e);if(!s)return;t||(t=window.generateID("group"));const i=this.createGroupElement(t,e);if(!i)return null;const a=s.groupUI.empty;a?.nextSibling?s.groupUI.grid.insertBefore(i,a.nextSibling):s.groupUI.grid.append(i);const r=i.querySelector(".item-grid");r&&(r.dataset.groupId=t,this.createSortable(e,r,t));let o=this.stores.groups.data.has(t)?this.stores.groups.data.get(t):{};return await this.setGroup(t,{...o,id:t,field:e}),t}createGroupElement(e,t=null){let s={groupId:e,fieldId:t},i=this.templates.create("imageGroup",s);return this.groups.set(e,{element:i,ui:window.uiFromSelectors(this.selectors.group,i)}),this.getSelectionHandler(t)?.addWrapper(i),i}async setGroup(e,t){const s={...{id:e,src:window.location.href,uploads:[],operationId:null,field:null,fields:{}},...t};Object.preventExtensions(s),await this.stores.groups.save(s)}async setBulkGroup(e,t,s){let i=this.stores.groups.filterByIndex({field:e});if(0===i.length)return;let a=i.map(e=>{e[t]=s,this.stores.groups.save(e)});await Promise.all(a)}async addToGroup(e,t=null){const s=this.stores.uploads.get(e),i=this.uploads.get(e);if(!s||!i)return;const a=this.fields.get(s.field);if(!a)return;if(null!==i.element?.parentElement&&(!t&&null===s.group||t===s.group))return void this.handleReorder(s.field,t);if(s.group){const t=this.stores.groups.get(s.group);t&&(t.uploads=t.uploads.filter(t=>t!==e),0===t.uploads.length?await this.removeGroup(t.id,!1):await this.stores.groups.save(t))}i.ui.checkbox&&(i.ui.checkbox.checked=!1);const r=this.selectionHandlers.get(s.field);if(r&&r.isSelected(e)&&r.deselect(e),this.selected.get(s.field)?.has(e)&&this.selected.get(s.field).delete(e),i.ui.featured&&(i.ui.featured.hidden=!t),t){i.ui.featured&&(i.ui.featured.name=`${t}_featured`);let a=this.stores.groups.get(t);a&&(a.uploads.push(e),s.group=t,await this.stores.groups.save(a))}else s.group=null;let o=t?this.groups.get(t)?.ui.grid:a.ui.grid;o&&(o.append(i.element),t&&await this.handleReorder(s.field,t)),await this.stores.uploads.save(s)}handleDeleteGroup(e){const t=e.closest(this.selectors.group.item);if(!t)return;let s=t.dataset.groupId;if(!confirm("Delete this group? Items will be moved back to the upload area."))return;let i=this.stores.uploads.filterByIndex({group:s});Promise.all(i.map(e=>this.addToGroup(e.id,null))).then(()=>{this.removeGroup(s,!1).then(()=>{}),this.a11y.announce("Group deleted. Items returned to upload area")})}async removeGroup(e,t=!0){let s=this.groups.get(e),i=this.stores.groups.get(e);if(!i)return;let a=!0;t&&i.uploads.length>0&&(a=window.confirm("Keep uploads in this group?")),await Promise.all(i.uploads.map(e=>a?this.addToGroup(e,null):this.removeUpload(e)));if(this.fields.get(i.field)){const t=this.getGroupKey(i.field,e),a=this.selectionHandlers.get(t);if(a?.destroy&&a.destroy(),this.selectionHandlers.get(i.field)&&s&&s.element&&this.selectionHandlers.get(i.field).removeWrapper(s.element),this.sortables.has(t)){const e=this.sortables.get(t);e?.destroy&&e.destroy(),this.sortables.delete(t)}}s?.element&&s.element.remove(),this.groups.delete(e),await this.stores.groups.delete(e),this.a11y.announce("Group removed")}maybeLockUploads(e){let t=this.fields.get(e);if(!t||!t.ui.dropZone)return;let s=this.stores.uploads.filterByIndex({field:e}).length,i=t.config.maxFiles??0;t.ui.dropZone.hidden=i>0&&s>=i}async handleOperationCancelled(e){0!==e.length&&e.forEach(e=>{this.removeUpload(e)})}getGroupKey(e,t=null){return t?`${e}_${t}`:`${e}`}getSelectionHandler(e){let t=this.getGroupKey(e);if(!this.selectionHandlers.has(t)){let s=this.fields.get(e);if(!s)return;if("post_group"!==s.config.destination)return;let i=new window.jvbHandleSelection(s.element,{selectAll:{checkbox:this.selectors.fields.selectAll,count:this.selectors.fields.count,bulkControls:this.selectors.fields.actions},item:{item:this.selectors.items.item,checkbox:this.selectors.items.checkbox,idAttribute:"uploadId"},wrapper:{wrapper:".preview-wrap, .upload-group",id:"groupId"}});i.subscribe((t,s)=>{this.selected.set(e,s.selectedItems)}),this.selectionHandlers.set(t,i)}return this.selectionHandlers.get(t)}updateHandlerItems(e){let t=this.getSelectionHandler(e);t&&t.collectItems()}initSortable(e){if(!window.Sortable)return;const t=this.fields.get(e);t&&(!Sortable._multiDragMounted&&Sortable.MultiDrag&&(Sortable.mount(new Sortable.MultiDrag),Sortable._multiDragMounted=!0),this.createSortable(e,t.ui.grid,null),this.initEmptyGroupDropZone(e))}createSortable(e,t,s){if(!t)return null;const i=this.getGroupKey(e,s);if(this.sortables.has(i))return this.sortables.get(i);const a=new Sortable(t,{animation:150,draggable:".item",multiDrag:!0,selectedClass:"selected",avoidImplicitDeselect:!0,group:{name:e,pull:!0,put:!0},dragClass:"dragging",ignore:".empty-group",onStart:t=>{const s=t.item,i=s?.dataset.uploadId,a=this.selected.get(e);if(i&&(!a||!a.has(i))){const t=this.selectionHandlers.get(e);t&&t.select(i)}},onEnd:t=>this.sortableDrop(t,e)});return this.sortables.set(i,a),a}initEmptyGroupDropZone(e){const t=this.fields.get(e),s=t?.groupUI?.empty;s&&(s.addEventListener("dragover",e=>{e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect="move",s.classList.add("drag-over")}),s.addEventListener("dragleave",e=>{s.contains(e.relatedTarget)||s.classList.remove("drag-over")}),s.addEventListener("drop",async t=>{t.preventDefault(),t.stopPropagation(),s.classList.remove("drag-over");const i=this.selected.get(e);if(!i||0===i.size)return;const a=await this.createGroup(e);a&&(await Promise.all(Array.from(i).map(e=>this.addToGroup(e,a))),this.selectionHandlers.get(e)?.clearSelection())}))}async sortableDrop(e,t){const s=e.to,i=(e.items?.length>0?Array.from(e.items):[e.item]).map(e=>e.dataset.uploadId).filter(Boolean);if(0===i.length)return;const a=s.dataset.groupId||null;for(const e of i)await this.addToGroup(e,a);await this.handleReorder(t,a),this.selectionHandlers.get(t)?.clearSelection()}handleReorder(e,t=null){let s=t?this.groups.get(t)?.ui.grid:this.fields.get(e)?.ui.grid;if(s){if(t){let e=Array.from(s.children).filter(e=>e.matches(this.selectors.items.item)&&!e.classList.contains("ghost")).map(e=>e.dataset.uploadId).filter(e=>e),i=this.stores.groups.get(t);i&&(i.uploads=e,this.stores.groups.save(i).then(()=>{}))}else this.updateHiddenInput(e);this.a11y.announce("Items reordered")}else console.log("Couldn't Reorder items...")}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t={}){this.subscribers.forEach(s=>{try{s(e,t)}catch(e){console.error("Subscriber error:",e)}})}destroy(){this.subscribers.clear(),this.previewUrls.forEach(e=>{this.revokePreviewUrl(e)}),this.previewUrls.clear()}cleanupAllPreviewUrls(){this.previewUrls.forEach(e=>this.revokePreviewUrl(e)),this.previewUrls.clear()}async handleClearCache(){const e=window.location.href,t=this.stores.uploads.filterByIndex({src:e}),s=this.stores.groups.filterByIndex({src:e});await Promise.all([...t.map(e=>this.clearUpload(e.id)),...s.map(e=>(this.groups.get(e.id)?.element?.remove(),this.groups.delete(e.id),this.stores.groups.delete(e.id)))]),this.restoreModal&&this.cleanupRestore(),this.a11y.announce("Cache cleared for this page")}async getFilesForForm(e){const t=e.querySelectorAll(this.selectors.fields.field),s=[];for(const e of t){const t=this.determineFieldId(e),i=e.dataset.field,a=this.stores.uploads.filterByIndex({field:t});for(const e of a){const t=this.formatFile(e);t&&s.push({file:t,fieldName:i,uploadId:e.id,meta:e.fields||{}})}}return s}async clearFieldFromStores(e){const t=this.stores.uploads.filterByIndex({field:e}),s=this.stores.groups.filterByIndex({field:e});await Promise.all(t.map(e=>this.clearUpload(e.id))),await Promise.all(s.map(e=>(this.groups.get(e.id)?.element?.remove(),this.groups.delete(e.id),this.stores.groups.delete(e.id))))}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbUploads=new e)})})})();
\ No newline at end of file
diff --git a/assets/js/min/utility.min.js b/assets/js/min/utility.min.js
index f544580..efbbfc4 100644
--- a/assets/js/min/utility.min.js
+++ b/assets/js/min/utility.min.js
@@ -1 +1 @@
-(()=>{window.fade=function(e,t=!0){t?e.style.animation="fadeIn var(--transition-base)":(e.style.animation="fadeOut var(--transition-base)",window.debouncer.schedule(`remove-${e.dataset.id??e.id??e.className.replace(" ","-")}`,(()=>{e.remove()}),500))},window.formatTimeAgo=function(e,t="default"){const n=e instanceof Date?e:new Date(e),i=n-new Date,o=i<0,r=Math.floor(Math.abs(i)/1e3),a=Math.floor(r/60),s=Math.floor(a/60),l=Math.floor(s/24);if(0===a)return"Just now";let c="";if(r<10)c="a moment";else if(r<60)c="less than a minute";else if(a<5)c="a few minutes";else if(s<24)c=0===s?`${a} ${1===a?"minute":"minutes"}`:`about ${s} ${1===s?"hour":"hours"}`;else{if(!(l<7)){if("default"===t)return n.toLocaleDateString();const e={Y:n.getFullYear(),y:String(n.getFullYear()).slice(-2),F:n.toLocaleDateString("en-CA",{month:"long"}),M:n.toLocaleDateString("en-CA",{month:"short"}),m:String(n.getMonth()+1).padStart(2,"0"),n:n.getMonth()+1,d:String(n.getDate()).padStart(2,"0"),j:n.getDate(),D:n.toLocaleDateString("en-CA",{weekday:"short"}),l:n.toLocaleDateString("en-CA",{weekday:"long"}),H:String(n.getHours()).padStart(2,"0"),i:String(n.getMinutes()).padStart(2,"0"),s:String(n.getSeconds()).padStart(2,"0"),h:String(n.getHours()%12||12).padStart(2,"0"),g:n.getHours()%12||12,A:n.getHours()>=12?"PM":"AM",a:n.getHours()>=12?"pm":"am"};return t.replace(/[YyFMmnjDlHishgAa]/g,(t=>e[t]))}if(1===l)return o?"yesterday":"tomorrow";c=`about ${l} days`,c=`${l} ${1===l?"day":"days"}`}return o?`${c} ago`:`in ${c}`},window.uppercaseFirst=function(e){return e.charAt(0).toUpperCase()+e.slice(1)},window.templates=new Map,document.addEventListener("DOMContentLoaded",(()=>{window.loadTemplates()})),window.loadTemplates=function(){document.querySelectorAll("template").forEach((e=>{const t=Array.from(e.classList);if(t.length>0){const n=e.content.cloneNode(!0).firstElementChild;t.forEach((e=>{window.templates.has(e)||window.templates.set(e,n)}))}}))},window.getTemplate=function(e){return 0===window.templates.size&&window.loadTemplates(),!!window.templates.has(e)&&window.templates.get(e).cloneNode(!0)};window.jvbTemplates=new class{constructor(){this.templates=new Map,this.definitions=new Map}registerAll(e=document){e.querySelectorAll("template").forEach((e=>{e.classList.forEach((t=>{this.templates.has(t)||this.templates.set(t,e)}))}))}define(e,t={},n=null){this.definitions.set(e,{refs:t.refs||null,manyRefs:t.manyRefs||null,setup:t.setup||null,context:n})}create(e,t={}){const n=this.templates.get(e);if(!n)return console.warn(`[TemplateRegistry] Template "${e}" not found`),null;const i=n.content.cloneNode(!0).firstElementChild;if(!i)return null;const o=this.definitions.get(e),r=o?.refs?this.#e(i,o.refs):{},a=o?.manyRefs?this.#e(i,o.manyRefs,!1):{};return o?.setup?.({el:i,refs:r,manyRefs:a,data:t}),i}#e(e,t,n=!0){const i={};for(const[o,r]of Object.entries(t)){let t,a=!1;"string"==typeof r?t=r:(t=r.selector,a=!!r.required);const s=n?e.querySelector(t):e.querySelectorAll(t);a&&(n&&!s&&console.warn(`[TemplateRegistry] Required ref "${o}" not found: ${t}`),n||0!==s.length||console.warn(`[TemplateRegistry] Required manyRef "${o}" not found: ${t}`)),i[o]=n?s:Array.from(s)}return i}},document.addEventListener("DOMContentLoaded",(()=>{window.jvbTemplates.registerAll()})),window.icon=null,window.getIcon=function(e,t=""){if(void 0===e)return"";window.icon||(window.icon=document.createElement("i"),window.icon.className="icon",window.icon.ariaHidden=!0);let n=window.icon.cloneNode(!0);return t=""!==t&&["regular","bold","duotone","fill","light","thin"].includes("style")?`-${t.slice(0,2)}`:"",n.classList.add(`icon-${e}${t}`),n},window.formatNumber=function(e){return e.toString().replace(/\B(?=(\d{3})+(?!\d))/g,",")},window.formatPrice=function(e,t="CAD"){return new Intl.NumberFormat("en-CA",{style:"currency",currency:t}).format(e)},window.escapeHtml=function(e){return e?("string"==typeof e||e instanceof String||(e=String(e)),e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")):""},window.removeChildren=function(e){if(0!==e.children.length)for(;e.firstChild;)e.removeChild(e.firstChild)},window.formatDateRange=function(e,t){const n=new Date(e),i=new Date(t);return n.toDateString()===i.toDateString()?n.toLocaleDateString("en-CA",{year:"numeric",month:"short",day:"numeric"}):n.getMonth()===i.getMonth()&&n.getFullYear()===i.getFullYear()?`${n.toLocaleDateString("en-CA",{month:"short",day:"numeric"})} - ${i.getDate()}, ${i.getFullYear()}`:n.getFullYear()===i.getFullYear()?`${n.toLocaleDateString("en-CA",{month:"short",day:"numeric"})} - ${i.toLocaleDateString("en-CA",{month:"short",day:"numeric"})}, ${i.getFullYear()}`:`${n.toLocaleDateString("en-CA",{month:"short",day:"numeric",year:"numeric"})} - ${i.toLocaleDateString("en-CA",{month:"short",day:"numeric",year:"numeric"})}`},window.throttle=function(e,t=300){let n;return function(...i){n||(e.apply(this,i),n=!0,setTimeout((()=>n=!1),t))}},window.chunkIt=async function(e,t,n,i=10){const o=[];for(let t=0;t<e.length;t+=i)o.push(e.slice(t,t+i));for(const e of o){const i=document.createDocumentFragment();e.forEach((e=>{const n=t(e);n&&i.append(n)})),n(i),await new Promise((e=>requestAnimationFrame(e)))}},window.prefixInput=function(e,t,n=null,i=!1,o=!1){if(!e)return void console.warn("prefixInput called with null/undefined input");const r=e.id,a=i?t:`${t}${e.name}`;let s=null;s=n?n.querySelector(`label[for="${r}"]`):e.labels&&e.labels.length>0?e.labels[0]:"LABEL"===e.previousElementSibling?.tagName?e.previousElementSibling:"LABEL"===e.nextElementSibling?.tagName?e.nextElementSibling:e.closest("[data-field]")?.querySelector(`label[for="${r}"]`),s&&(s.htmlFor=a),e.id=a,o&&(e.name=a)},window.uppercaseFirst=function(e){return e.charAt(0).toUpperCase()+e.slice(1)},window.sanitizeHtml=function(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML},window.generateID=function(e="jvb"){return`${e}_${Date.now()}_${Math.random().toString(36).slice(2,9)}`},window.showProgress=function(e,t,n,i="",o=""){const r=t<n;e.progress&&r&&window.fade(e.progress,!0);const a=n>0?t/n*100:0;e.fill&&(e.fill.style.width=`${a}%`),e.details&&(e.details.textContent=i),e.count&&(e.count.textContent=`${t}/${n}`),e.icon&&(e.icon.className=""===o?"icon":"icon icon-"+o),e.progress&&t===n&&window.fade(e.progress,!1)},window.formatDate=function(e){if(!e)return"";const t=new Date(e),n=new Date,i=Math.floor((n-t)/864e5);return i<1?"Today":i<2?"Yesterday":i<7?`${i} days ago`:t.toLocaleDateString()},window.getPluralContent=function(e){return"artwork"===e?"artwork":e+"s"},window.showToast=function(e,t="success",n={}){window.jvbNotifications.showToast(e,t,n)},window.dateFormatter=new Intl.DateTimeFormat("en-CA",{year:"numeric",month:"long",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:"short"}),window.formatDate=function(e){return e instanceof Date&&!isNaN(e)||(e=new Date(e)),window.dateFormatter.format(e)},window.typeText=function(e,t,n=50){return new Promise((i=>{e._typeInterval&&(clearInterval(e._typeInterval),delete e._typeInterval);let o=0;e.textContent="",e._typeInterval=setInterval((()=>{o<t.length?(e.textContent+=t.charAt(o),o++):(clearInterval(e._typeInterval),delete e._typeInterval,i())}),n)}))},window.eraseText=function(e,t=10){return new Promise((n=>{e._eraseInterval&&(clearInterval(e._eraseInterval),delete e._eraseInterval);let i=e.textContent,o=i.length;e._eraseInterval=setInterval((()=>{o>0?(o--,e.textContent=i.substring(0,o)):(clearInterval(e._eraseInterval),delete e._eraseInterval,n())}),t)}))},window.typeLoop=function(e,t,n=50,i=10,o=1e3,r=250){const a=e.id||e.dataset.typeKey||`type-${Date.now()}`;e.dataset.typeKey||(e.dataset.typeKey=a),e._stopTyping&&e._stopTyping();let s=!0;const l=function(){s=!1,e._typeInterval&&(clearInterval(e._typeInterval),delete e._typeInterval),e._eraseInterval&&(clearInterval(e._eraseInterval),delete e._eraseInterval)};return e._stopTyping=l,async function(){for(;s&&(await window.typeText(e,t,n),s)&&(await new Promise((e=>setTimeout(e,o))),s)&&(await window.eraseText(e,i),s);)await new Promise((e=>setTimeout(e,r)))}(),l},window.toCamelCase=function(e){return e.replace(/-([a-z])/g,(function(e){return e[1].toUpperCase()}))},window.targetCheck=function(e,t){return Array.isArray(t)&&(t=t.join(",")),"string"==typeof t&&(e.target.closest(t)??!1)},window.getDifferences={VALUE_CREATED:"created",VALUE_UPDATED:"updated",VALUE_DELETED:"deleted",VALUE_UNCHANGED:"unchanged",map:function(e,t){if(this.isFunction(e)||this.isFunction(t))throw"Invalid argument. Function given, object expected.";if(this.isFile(e)||this.isFile(t)){const n=this.compareFiles(e,t);return n===this.VALUE_UNCHANGED?null:{type:n,data:void 0===e?t:e}}if(this.isValue(e)||this.isValue(t)){const n=this.compareValues(e,t);if(n===this.VALUE_UNCHANGED)return null;let i;switch(n){case this.VALUE_CREATED:i=t;break;case this.VALUE_DELETED:i=this.getEmptyValue(e);break;case this.VALUE_UPDATED:default:i=t}return{type:n,data:i}}let n={},i=!1;for(let o in e)if(!this.isFunction(e[o])){let r;t&&void 0!==t[o]&&(r=t[o]);const a=this.map(e[o],r);null!==a&&(a.hasOwnProperty("type")&&a.hasOwnProperty("data")?n[o]=a.data:n[o]=a,i=!0)}if(t)for(let o in t)if(!this.isFunction(t[o])&&(void 0===e||void 0===e[o])){const e=this.map(void 0,t[o]);null!==e&&(e.hasOwnProperty("type")&&e.hasOwnProperty("data")?n[o]=e.data:n[o]=e,i=!0)}return i?n:null},getEmptyValue:function(e){return this.isArray(e)?[]:this.isObject(e)?{}:"number"==typeof e?0:"boolean"!=typeof e&&""},compareValues:function(e,t){return e===t||this.isDate(e)&&this.isDate(t)&&e.getTime()===t.getTime()?this.VALUE_UNCHANGED:void 0===e?this.VALUE_CREATED:void 0===t?this.VALUE_DELETED:this.VALUE_UPDATED},isFunction:function(e){return"[object Function]"===Object.prototype.toString.call(e)},isArray:function(e){return"[object Array]"===Object.prototype.toString.call(e)},isDate:function(e){return"[object Date]"===Object.prototype.toString.call(e)},isObject:function(e){return"[object Object]"===Object.prototype.toString.call(e)},isFile:function(e){return e instanceof File},isValue:function(e){return!this.isObject(e)&&!this.isArray(e)},compareFiles:function(e,t){return!this.isFile(e)&&this.isFile(t)?this.VALUE_CREATED:this.isFile(e)&&!this.isFile(t)?this.VALUE_DELETED:this.isFile(e)&&this.isFile(t)?e.name===t.name&&e.size===t.size&&e.type===t.type&&e.lastModified===t.lastModified?this.VALUE_UNCHANGED:this.VALUE_UPDATED:this.VALUE_UNCHANGED},merge:function(e,t){if(null==e)return t;if(null==t)return e;if(this.isFunction(e)||this.isFunction(t))return t;if(this.isFile(e)||this.isFile(t))return t;if(this.isValue(e)||this.isValue(t)||this.isArray(e)||this.isArray(t))return t;if(this.isObject(e)&&this.isObject(t)){let n={};for(let t in e)this.isFunction(e[t])||(n[t]=e[t]);for(let i in t)this.isFunction(t[i])||(void 0!==e[i]?n[i]=this.merge(e[i],t[i]):n[i]=t[i]);return n}return t}},window.deepMerge=function(e,t){return window.getDifferences.merge(e,t)},window.isInt=function(e){return!isNaN(parseInt(e))&&isFinite(e)},window.isNumeric=function(e){return!isNaN(parseFloat(e))&&isFinite(e)},window.uiFromSelectors=function(e,t=null,n=!1){let i={};for(let[o,r]of Object.entries(e))i[o]="object"==typeof r?window.uiFromSelectors(r,t):t?n?t.querySelectorAll(r):t.querySelector(r):n?document.querySelectorAll(r):document.querySelector(r);return i},window.sleep=async function(e=50){return new Promise((t=>setTimeout(t,e)))};window.debouncer=new class{constructor(){this.timeouts=new Map,window.addEventListener("beforeunload",(()=>this.cleanup()))}schedule(e,t,n=1e3){this.cancel(e),this.timeouts.set(e,setTimeout((()=>{t(),this.timeouts.delete(e)}),n))}cancel(e){this.timeouts.has(e)&&(clearTimeout(this.timeouts.get(e)),this.timeouts.delete(e))}cleanup(){for(let e of this.timeouts.values())clearTimeout(e);this.timeouts.clear()}};document.body;const e=document.documentElement,t=document.querySelector(".scroll-progress .bar");let n=window.scrollY||e.scrollTop||0,i=-1,o=!1,r=0;function a(){r=Math.max(0,e.scrollHeight-window.innerHeight)}function s(e){if(!t)return;const n=r>0?e/r:0,i=Math.max(0,Math.min(1,n));t.style.transform=`scaleX(${i})`}function l(){const t=window.scrollY||e.scrollTop||0;t>n?i=1:t<n&&(i=-1),n=t,document.body.classList.toggle("scroll-up",i<0&&t>0),s(t),o=!1}function c(){window.innerWidth;let e=function(){let e=window.previousBGSize,t={Small:["Med","Large"],Med:["Large"],Large:!1};if(!t[e])return!1;let n="Small";return t[e].forEach((e=>{(t=>window.bgSizes[e])&&(n=e)})),n}();e&&(window.previousBGSize=e,document.querySelectorAll('[data-bg-img]:not([data-bg-img="'+window.previousBGSize+'"])').forEach((e=>{window.bgObserver.observe(e)})))}window.addEventListener("scroll",(()=>{o||(o=!0,requestAnimationFrame(l))}),{passive:!0}),window.previousBGSize="Small",window.bgSizes={Small:500,Med:768,Large:1024},window.bgObserver=new IntersectionObserver((e=>{e.forEach((e=>{if(e.isIntersecting){let t=e.target.dataset[`bg${window.previousBGSize}`];e.target.style.backgroundImage=`url(${t})`,e.target.dataset.bgImg=window.previousBGSize,window.bgObserver.unobserve(e.target)}}))}),{root:null,rootMargin:"0px 0px -100px 0px",threshold:0}),c(),window.addEventListener("resize",(()=>{window.debouncer.schedule("recalc-max-scroll",(()=>{a(),s(window.scrollY||e.scrollTop||0)}),20),window.debouncer.schedule("bg-resize",(()=>{c()}))})),a(),s(n),window.decodeHTMLEntities=function(e){return window.decodeHelper||(window.decodeHelper=document.createElement("textarea")),window.decodeHelper.innerHTML=e,window.decodeHelper.value},window.focusNextElement=function(){if(document.activeElement&&document.activeElement.form){var e=Array.prototype.filter.call(document.activeElement.form.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])'),(function(e){return e.offsetWidth>0||e.offsetHeight>0||e===document.activeElement})),t=e.indexOf(document.activeElement);if(t>-1)(e[t+1]||e[0]).focus()}}})();
\ No newline at end of file
+(()=>{window.fade=function(e,t=!0){t?e.style.animation="fadeIn var(--transition-base)":(e.style.animation="fadeOut var(--transition-base)",window.debouncer.schedule(`remove-${e.dataset.id??e.id??e.className.replace(" ","-")}`,()=>{e.remove()},500))},window.formatTimeAgo=function(e,t="default"){const n=e instanceof Date?e:new Date(e),i=n-new Date,o=i<0,r=Math.floor(Math.abs(i)/1e3),a=Math.floor(r/60),s=Math.floor(a/60),l=Math.floor(s/24);if(0===a)return"Just now";let c="";if(r<10)c="a moment";else if(r<60)c="less than a minute";else if(a<5)c="a few minutes";else if(s<24)c=0===s?`${a} ${1===a?"minute":"minutes"}`:`about ${s} ${1===s?"hour":"hours"}`;else{if(!(l<7)){if("default"===t)return n.toLocaleDateString();const e={Y:n.getFullYear(),y:String(n.getFullYear()).slice(-2),F:n.toLocaleDateString("en-CA",{month:"long"}),M:n.toLocaleDateString("en-CA",{month:"short"}),m:String(n.getMonth()+1).padStart(2,"0"),n:n.getMonth()+1,d:String(n.getDate()).padStart(2,"0"),j:n.getDate(),D:n.toLocaleDateString("en-CA",{weekday:"short"}),l:n.toLocaleDateString("en-CA",{weekday:"long"}),H:String(n.getHours()).padStart(2,"0"),i:String(n.getMinutes()).padStart(2,"0"),s:String(n.getSeconds()).padStart(2,"0"),h:String(n.getHours()%12||12).padStart(2,"0"),g:n.getHours()%12||12,A:n.getHours()>=12?"PM":"AM",a:n.getHours()>=12?"pm":"am"};return t.replace(/[YyFMmnjDlHishgAa]/g,t=>e[t])}if(1===l)return o?"yesterday":"tomorrow";c=`about ${l} days`,c=`${l} ${1===l?"day":"days"}`}return o?`${c} ago`:`in ${c}`},window.uppercaseFirst=function(e){return e.charAt(0).toUpperCase()+e.slice(1)},window.templates=new Map,document.addEventListener("DOMContentLoaded",()=>{window.loadTemplates()}),window.loadTemplates=function(){document.querySelectorAll("template").forEach(e=>{const t=Array.from(e.classList);if(t.length>0){const n=e.content.cloneNode(!0).firstElementChild;t.forEach(e=>{window.templates.has(e)||window.templates.set(e,n)})}})},window.getTemplate=function(e){return 0===window.templates.size&&window.loadTemplates(),!!window.templates.has(e)&&window.templates.get(e).cloneNode(!0)};window.jvbTemplates=new class{constructor(){this.templates=new Map,this.definitions=new Map}registerAll(e=document){e.querySelectorAll("template").forEach(e=>{e.classList.forEach(t=>{this.templates.has(t)||this.templates.set(t,e)})})}define(e,t={},n=null){this.definitions.set(e,{refs:t.refs||null,manyRefs:t.manyRefs||null,setup:t.setup||null,context:n})}create(e,t={}){const n=this.templates.get(e);if(!n)return console.warn(`[TemplateRegistry] Template "${e}" not found`),null;const i=n.content.cloneNode(!0).firstElementChild;if(!i)return null;const o=this.definitions.get(e),r=o?.refs?this.#e(i,o.refs):{},a=o?.manyRefs?this.#e(i,o.manyRefs,!1):{};return o?.setup?.({el:i,refs:r,manyRefs:a,data:t}),i}#e(e,t,n=!0){const i={};for(const[o,r]of Object.entries(t)){let t,a=!1;"string"==typeof r?t=r:(t=r.selector,a=!!r.required);const s=n?e.querySelector(t):e.querySelectorAll(t);a&&(n&&!s&&console.warn(`[TemplateRegistry] Required ref "${o}" not found: ${t}`),n||0!==s.length||console.warn(`[TemplateRegistry] Required manyRef "${o}" not found: ${t}`)),i[o]=n?s:Array.from(s)}return i}},document.addEventListener("DOMContentLoaded",()=>{window.jvbTemplates.registerAll()}),window.icon=null,window.getIcon=function(e,t=""){if(void 0===e)return"";window.icon||(window.icon=document.createElement("i"),window.icon.className="icon",window.icon.ariaHidden=!0);let n=window.icon.cloneNode(!0);return t=""!==t&&["regular","bold","duotone","fill","light","thin"].includes("style")?`-${t.slice(0,2)}`:"",n.classList.add(`icon-${e}${t}`),n},window.formatNumber=function(e){return e.toString().replace(/\B(?=(\d{3})+(?!\d))/g,",")},window.formatPrice=function(e,t="CAD"){return new Intl.NumberFormat("en-CA",{style:"currency",currency:t}).format(e)},window.escapeHtml=function(e){return e?("string"==typeof e||e instanceof String||(e=String(e)),e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")):""},window.removeChildren=function(e){if(0!==e.children.length)for(;e.firstChild;)e.removeChild(e.firstChild)},window.formatDateRange=function(e,t){const n=new Date(e),i=new Date(t);return n.toDateString()===i.toDateString()?n.toLocaleDateString("en-CA",{year:"numeric",month:"short",day:"numeric"}):n.getMonth()===i.getMonth()&&n.getFullYear()===i.getFullYear()?`${n.toLocaleDateString("en-CA",{month:"short",day:"numeric"})} - ${i.getDate()}, ${i.getFullYear()}`:n.getFullYear()===i.getFullYear()?`${n.toLocaleDateString("en-CA",{month:"short",day:"numeric"})} - ${i.toLocaleDateString("en-CA",{month:"short",day:"numeric"})}, ${i.getFullYear()}`:`${n.toLocaleDateString("en-CA",{month:"short",day:"numeric",year:"numeric"})} - ${i.toLocaleDateString("en-CA",{month:"short",day:"numeric",year:"numeric"})}`},window.throttle=function(e,t=300){let n;return function(...i){n||(e.apply(this,i),n=!0,setTimeout(()=>n=!1,t))}},window.chunkIt=async function(e,t,n,i=10){const o=[];for(let t=0;t<e.length;t+=i)o.push(e.slice(t,t+i));for(const e of o){const i=document.createDocumentFragment();e.forEach(e=>{const n=t(e);n&&i.append(n)}),n(i),await new Promise(e=>requestAnimationFrame(e))}},window.prefixInput=function(e,t,n=null,i=!1,o=!1){if(!e)return void console.warn("prefixInput called with null/undefined input");const r=e.id,a=i?t:`${t}${e.name}`;let s=null;s=n?n.querySelector(`label[for="${r}"]`):e.labels&&e.labels.length>0?e.labels[0]:"LABEL"===e.previousElementSibling?.tagName?e.previousElementSibling:"LABEL"===e.nextElementSibling?.tagName?e.nextElementSibling:e.closest("[data-field]")?.querySelector(`label[for="${r}"]`),s&&(s.htmlFor=a),e.id=a,o&&(e.name=a)},window.uppercaseFirst=function(e){return e.charAt(0).toUpperCase()+e.slice(1)},window.sanitizeHtml=function(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML},window.generateID=function(e="jvb"){return`${e}_${Date.now()}_${Math.random().toString(36).slice(2,9)}`},window.showProgress=function(e,t,n,i="",o=""){const r=t<n;e.progress&&r&&window.fade(e.progress,!0);const a=n>0?t/n*100:0;e.fill&&(e.fill.style.width=`${a}%`),e.details&&(e.details.textContent=i),e.count&&(e.count.textContent=`${t}/${n}`),e.icon&&(e.icon.className=""===o?"icon":"icon icon-"+o),e.progress&&t===n&&window.fade(e.progress,!1)},window.formatDate=function(e){if(!e)return"";const t=new Date(e),n=new Date,i=Math.floor((n-t)/864e5);return i<1?"Today":i<2?"Yesterday":i<7?`${i} days ago`:t.toLocaleDateString()},window.getPluralContent=function(e){return"artwork"===e?"artwork":e+"s"},window.showToast=function(e,t="success",n={}){window.jvbNotifications.showToast(e,t,n)},window.dateFormatter=new Intl.DateTimeFormat("en-CA",{year:"numeric",month:"long",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:"short"}),window.formatDate=function(e){return e instanceof Date&&!isNaN(e)||(e=new Date(e)),window.dateFormatter.format(e)},window.typeText=function(e,t,n=50){return new Promise(i=>{e._typeInterval&&(clearInterval(e._typeInterval),delete e._typeInterval);let o=0;e.textContent="",e._typeInterval=setInterval(()=>{o<t.length?(e.textContent+=t.charAt(o),o++):(clearInterval(e._typeInterval),delete e._typeInterval,i())},n)})},window.eraseText=function(e,t=10){return new Promise(n=>{e._eraseInterval&&(clearInterval(e._eraseInterval),delete e._eraseInterval);let i=e.textContent,o=i.length;e._eraseInterval=setInterval(()=>{o>0?(o--,e.textContent=i.substring(0,o)):(clearInterval(e._eraseInterval),delete e._eraseInterval,n())},t)})},window.typeLoop=function(e,t,n=50,i=10,o=1e3,r=250){const a=e.id||e.dataset.typeKey||`type-${Date.now()}`;e.dataset.typeKey||(e.dataset.typeKey=a),e._stopTyping&&e._stopTyping();let s=!0;const l=function(){s=!1,e._typeInterval&&(clearInterval(e._typeInterval),delete e._typeInterval),e._eraseInterval&&(clearInterval(e._eraseInterval),delete e._eraseInterval)};return e._stopTyping=l,async function(){for(;s&&(await window.typeText(e,t,n),s)&&(await new Promise(e=>setTimeout(e,o)),s)&&(await window.eraseText(e,i),s);)await new Promise(e=>setTimeout(e,r))}(),l},window.toCamelCase=function(e){return e.replace(/-([a-z])/g,function(e){return e[1].toUpperCase()})},window.targetCheck=function(e,t){return Array.isArray(t)&&(t=t.join(",")),"string"==typeof t&&(e.target.closest(t)??!1)},window.getDifferences={VALUE_CREATED:"created",VALUE_UPDATED:"updated",VALUE_DELETED:"deleted",VALUE_UNCHANGED:"unchanged",map:function(e,t){if(this.isFunction(e)||this.isFunction(t))throw"Invalid argument. Function given, object expected.";if(this.isFile(e)||this.isFile(t)){const n=this.compareFiles(e,t);return n===this.VALUE_UNCHANGED?null:{type:n,data:void 0===e?t:e}}if(this.isValue(e)||this.isValue(t)){const n=this.compareValues(e,t);if(n===this.VALUE_UNCHANGED)return null;let i;switch(n){case this.VALUE_CREATED:i=t;break;case this.VALUE_DELETED:i=this.getEmptyValue(e);break;case this.VALUE_UPDATED:default:i=t}return{type:n,data:i}}let n={},i=!1;for(let o in e)if(!this.isFunction(e[o])){let r;t&&void 0!==t[o]&&(r=t[o]);const a=this.map(e[o],r);null!==a&&(a.hasOwnProperty("type")&&a.hasOwnProperty("data")?n[o]=a.data:n[o]=a,i=!0)}if(t)for(let o in t)if(!this.isFunction(t[o])&&(void 0===e||void 0===e[o])){const e=this.map(void 0,t[o]);null!==e&&(e.hasOwnProperty("type")&&e.hasOwnProperty("data")?n[o]=e.data:n[o]=e,i=!0)}return i?n:null},getEmptyValue:function(e){return this.isArray(e)?[]:this.isObject(e)?{}:"number"==typeof e?0:"boolean"!=typeof e&&""},compareValues:function(e,t){return e===t||this.isDate(e)&&this.isDate(t)&&e.getTime()===t.getTime()?this.VALUE_UNCHANGED:void 0===e?this.VALUE_CREATED:void 0===t?this.VALUE_DELETED:this.VALUE_UPDATED},isFunction:function(e){return"[object Function]"===Object.prototype.toString.call(e)},isArray:function(e){return"[object Array]"===Object.prototype.toString.call(e)},isDate:function(e){return"[object Date]"===Object.prototype.toString.call(e)},isObject:function(e){return"[object Object]"===Object.prototype.toString.call(e)},isFile:function(e){return e instanceof File},isValue:function(e){return!this.isObject(e)&&!this.isArray(e)},compareFiles:function(e,t){return!this.isFile(e)&&this.isFile(t)?this.VALUE_CREATED:this.isFile(e)&&!this.isFile(t)?this.VALUE_DELETED:this.isFile(e)&&this.isFile(t)?e.name===t.name&&e.size===t.size&&e.type===t.type&&e.lastModified===t.lastModified?this.VALUE_UNCHANGED:this.VALUE_UPDATED:this.VALUE_UNCHANGED},merge:function(e,t){if(null==e)return t;if(null==t)return e;if(this.isFunction(e)||this.isFunction(t))return t;if(this.isFile(e)||this.isFile(t))return t;if(this.isValue(e)||this.isValue(t)||this.isArray(e)||this.isArray(t))return t;if(this.isObject(e)&&this.isObject(t)){let n={};for(let t in e)this.isFunction(e[t])||(n[t]=e[t]);for(let i in t)this.isFunction(t[i])||(void 0!==e[i]?n[i]=this.merge(e[i],t[i]):n[i]=t[i]);return n}return t}},window.deepMerge=function(e,t){return window.getDifferences.merge(e,t)},window.isInt=function(e){return!isNaN(parseInt(e))&&isFinite(e)},window.isNumeric=function(e){return!isNaN(parseFloat(e))&&isFinite(e)},window.uiFromSelectors=function(e,t=null,n=!1){let i={};for(let[o,r]of Object.entries(e))i[o]="object"==typeof r?window.uiFromSelectors(r,t):t?n?t.querySelectorAll(r):t.querySelector(r):n?document.querySelectorAll(r):document.querySelector(r);return i},window.sleep=async function(e=50){return new Promise(t=>setTimeout(t,e))};window.debouncer=new class{constructor(){this.timeouts=new Map,window.addEventListener("beforeunload",()=>this.cleanup())}schedule(e,t,n=1e3){this.cancel(e),this.timeouts.set(e,setTimeout(()=>{t(),this.timeouts.delete(e)},n))}cancel(e){this.timeouts.has(e)&&(clearTimeout(this.timeouts.get(e)),this.timeouts.delete(e))}cleanup(){for(let e of this.timeouts.values())clearTimeout(e);this.timeouts.clear()}};document.body;const e=document.documentElement,t=document.querySelector(".scroll-progress .bar");let n=window.scrollY||e.scrollTop||0,i=-1,o=!1,r=0;function a(){r=Math.max(0,e.scrollHeight-window.innerHeight)}function s(e){if(!t)return;const n=r>0?e/r:0,i=Math.max(0,Math.min(1,n));t.style.transform=`scaleX(${i})`}function l(){const t=window.scrollY||e.scrollTop||0;t>n?i=1:t<n&&(i=-1),n=t,document.body.classList.toggle("scroll-up",i<0&&t>0),s(t),o=!1}function c(){window.innerWidth;let e=function(){let e=window.previousBGSize,t={Small:["Med","Large"],Med:["Large"],Large:!1};if(!t[e])return!1;let n="Small";return t[e].forEach(e=>{(t=>window.bgSizes[e])&&(n=e)}),n}();e&&(window.previousBGSize=e,document.querySelectorAll('[data-bg-img]:not([data-bg-img="'+window.previousBGSize+'"])').forEach(e=>{window.bgObserver.observe(e)}))}window.addEventListener("scroll",()=>{o||(o=!0,requestAnimationFrame(l))},{passive:!0}),window.previousBGSize="Small",window.bgSizes={Small:500,Med:768,Large:1024},window.bgObserver=new IntersectionObserver(e=>{e.forEach(e=>{if(e.isIntersecting){let t=e.target.dataset[`bg${window.previousBGSize}`];e.target.style.backgroundImage=`url(${t})`,e.target.dataset.bgImg=window.previousBGSize,window.bgObserver.unobserve(e.target)}})},{root:null,rootMargin:"0px 0px -100px 0px",threshold:0}),c(),window.addEventListener("resize",()=>{window.debouncer.schedule("recalc-max-scroll",()=>{a(),s(window.scrollY||e.scrollTop||0)},20),window.debouncer.schedule("bg-resize",()=>{c()})}),a(),s(n),window.decodeHTMLEntities=function(e){return window.decodeHelper||(window.decodeHelper=document.createElement("textarea")),window.decodeHelper.innerHTML=e,window.decodeHelper.value},window.focusNextElement=function(){if(document.activeElement&&document.activeElement.form){var e=Array.prototype.filter.call(document.activeElement.form.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])'),function(e){return e.offsetWidth>0||e.offsetHeight>0||e===document.activeElement}),t=e.indexOf(document.activeElement);if(t>-1)(e[t+1]||e[0]).focus()}}})();
\ No newline at end of file
diff --git a/assets/js/min/view.min.js b/assets/js/min/view.min.js
index bdbb9b2..89e205c 100644
--- a/assets/js/min/view.min.js
+++ b/assets/js/min/view.min.js
@@ -1 +1 @@
-window.jvbViews=class{constructor(e,t){this.a11y=window.jvbA11y,this.error=window.jvbError,this.container=e,this.initElements(),this.settings=window.jvbUserSettings,this.store=t,this.isTimeline=!!document.querySelector("[data-timeline]"),this.items={list:new Map,grid:new Map,table:new Map},this.currentView=this.container.dataset.view??"grid",this.selectedItems=new Set,this.subscribers=new Set,this.init()}initElements(){this.selectors={grid:".item-grid",table:{table:"form.table",form:"table",body:"table body",header:"table thead",footer:"table tfoot",selectedColumns:".all-filters .multi-select",columns:"thead th"},bulk:{count:".bulk-controls .selected-count",control:".bulk-controls .bulk-actions",select:".bulk-controls select",selectAll:".select-all"}},this.ui=window.uiFromSelectors(this.selectors,this.container)}init(){this.store.subscribe(((e,t)=>{switch(e){case"items-saved":case"item-saved":case"item-deleted":break;case"data-loaded":this.handleItemsUpdate()}})),this.setupViewSwitcher(),this.changeHandler=this.handleChange.bind(this),this.clickHandler=this.handleClick.bind(this),this.lastSelected=null,document.addEventListener("change",this.changeHandler),document.addEventListener("click",this.clickHandler)}handleClick(e){e.target.closest(".select-item-label")&&(e.shiftKey?(e.preventDefault(),this.handleRangeSelection(e.target)):this.lastSelected=e.target.closest(".item"))}handleRangeSelection(e){if(!this.lastSelected)return void(this.lastSelected=e.closest(".item"));const t=e.closest(".item"),i=Array.from(this.container.querySelectorAll(".item")),s=i.indexOf(this.lastSelected),l=i.indexOf(t);if(-1===s||-1===l)return void(this.lastSelected=t);const r=Math.min(s,l),a=Math.max(s,l);let d=0;for(let e=r;e<=a;e++){let t=i[e];this.selectedItems.add(t.dataset.id);let s=t.querySelector(".select-item");s&&!s.checked&&(s.checked=!0,d++)}this.updateSelectionUI(),window.jvbA11y.announce(`Selected ${d} items in range.`)}handleChange(e){e.target.closest(".select-all")?this.selectAll(e.target.checked):e.target.closest(".select-item")?this.toggleSelection(e.target.closest(".item").dataset.id):e.target.closest("details.multi-select")&&this.toggleColumns(e.target.id,e.target.checked)}toggleColumns(e,t){this.ui.table.columns.filter((t=>t.className===e))}setupViewSwitcher(){document.querySelectorAll("[data-view]").forEach((e=>{this.settings.addSetting(e),e.addEventListener("click",(()=>{this.currentView=e.dataset.view,this.render()}))}));const e=document.querySelector("[data-view]:checked");e&&(this.currentView=e.dataset.view)}handleItemsUpdate(){this.render()}render(){if(!this.store)return void console.error("No store connected to renderer");const e=this.store.getFiltered();if(0===e.length)return console.log("Nothing to show"),void this.renderEmpty();switch(this.currentView){case"grid":this.renderGrid(e);break;case"table":this.renderTable(e);break;case"list":this.renderList(e)}this.updateSelectionUI()}renderEmpty(){this.toggleTable(!1),window.removeChildren(this.ui.grid);const e=window.getTemplate("emptyState");e&&(this.ui.grid.appendChild(e),this.a11y?.announce("No items found"))}renderGrid(e){this.toggleGrid(),this.toggleTable(!1),this.ui.grid.classList.remove("list-view"),this.ui.grid.classList.add("grid-view");const t=document.createDocumentFragment();e.forEach((e=>{let i=this.renderGridItem(e);t.appendChild(i)})),this.ui.grid.appendChild(t)}renderGridItem(e){if(this.items.grid.has(e.id))return this.items.grid.get(e.id);const t=window.getTemplate("gridView");t.dataset.id=e.id,e._pending&&t.classList.add("pending");let[i,s,l,r,a]=[t.querySelector("input"),t.querySelector("label"),t.querySelector("img"),t.querySelector('[data-action="edit"]'),t.querySelector('[data-action="trash"]')];return[i.value,i.id,i.checked,s.htmlFor,r.dataset.id,a.dataset.id]=[e.id,`select-${e.id}`,this.selectedItems.has(`${e.id}`),`select-${e.id}`,e.id,e.id],[l.src,l.alt]=[e.images[e.fields.post_thumbnail]?.medium??"",e.images[e.fields.post_thumbnail]?.alt??""],this.items.grid.set(e.id,t),t}toggleTable(e){if(this.ui.table.selectedColumns&&(this.ui.table.selectedColumns.hidden=!e),e&&!this.ui.table.table){let e=window.getTemplate("contentTable");this.container.append(e),this.ui.table.table=this.container.querySelector("form.table"),this.ui.table.form=this.ui.table.table.querySelector("table"),this.ui.table.header=this.ui.table.form.querySelector("thead"),this.ui.table.footer=this.ui.table.form.querySelector("tfoot"),this.ui.table.body=this.ui.table.form.querySelector("tbody"),this.ui.table.columns=this.container.querySelectorAll(this.selectors.table.columns)}this.ui.table.table&&(this.ui.table.table.hidden=!e,e?this.notify("table-view",this.ui.table.table):this.notify("not-table-view",this.ui.table.table),this.ui.table.body&&window.removeChildren(this.ui.table.body)),this.ui.table.selectedColumns&&(this.ui.table.selectedColumns.hidden=!e)}toggleGrid(){window.removeChildren(this.ui.grid)}renderTable(e){this.toggleTable(!0),this.toggleGrid(),e.forEach((e=>{let t=this.isTimeline?this.renderTimelineTableItem(e):this.renderTableItem(e);this.ui.table.body?this.ui.table.body.append(t):(this.ui.table.footer||(this.ui.table.footer=this.ui.table.table.querySelector("tfoot")),this.ui.table.form.insertBefore(t,this.ui.table.footer))})),window.jvbSelector.scanExistingFields()}renderTableItem(e){if(this.items.table.has(e.id))return this.items.table.get(e.id);const t=window.getTemplate("tableView");t.dataset.id=e.id,[t.querySelector(".select-item").id,t.querySelector(".select-item").value,t.querySelector(".select-item").checked,t.querySelector(".select-item + label").htmlFor]=[e.id,e.id,this.selectedItems.has(`${e.id}`),e.id];let i=t.querySelector(`input[name="post_status"][value="${e.status}"]`);if(i&&(i.checked=!0),Object.hasOwn(this.ui.table.table.dataset,"edit"))new window.jvbPopulate(t,e);else for(let[i,s]of Object.entries(e)){let e=t.querySelector(`[data-field="${i}"]`);if(e){let t=e.querySelector("p");"date"===e.dataset.fieldType&&(s=window.formatTimeAgo(s)),t.textContent=s}}return this.cleanupTableRow(t),this.items.table.set(e.id,t),t}renderTimelineTableItem(e){if(this.items.table.has(e.id))return this.items.table.get(e.id);const t=window.getTemplate("tableView");t.dataset.id=e.id,[t.querySelector(".select-item").id,t.querySelector(".select-item").value,t.querySelector(".select-item").checked,t.querySelector(".select-item + label").htmlFor]=[e.id,e.id,this.selectedItems.has(`${e.id}`),e.id];let i=t.querySelector(".timeline-point"),s=t,l=t.querySelector("tr.shared");return new window.jvbPopulate(l,e),this.prefixTimelineFieldNames(l,e.id),this.cleanupTableRow(l),e.fields.timeline&&"object"==typeof e.fields.timeline&&Object.entries(e.fields.timeline).forEach((([t,l],r)=>{let a=i.cloneNode(!0);a.dataset.index=r,a.dataset.imageId=t;const d={fields:l,images:e.images,taxonomies:{}};new window.jvbPopulate(a,d),this.cleanupTableRow(a);let o=e.images[l.post_thumbnail];o&&(a.querySelector(".field.upload").title=o["image-title"]),this.prefixTimelineFieldNames(a,l.id),s.insertBefore(a,i)})),i.remove(),this.items.table.set(e.id,t),t}prefixTimelineFieldNames(e,t){e.querySelectorAll("input, textarea, select").forEach((e=>{const i=e.name;if(!i||i.startsWith("[")||"form-id"===i||i.startsWith("_"))return;let s=e.nextElementSibling;e.name=`[${t}]${i}`,s&&"LABEL"===s.tagName&&(e.id=`[${t}]${e.id}`,s.htmlFor=e.id)}))}cleanupTableRow(e){e.querySelectorAll("td[data-field]").forEach((e=>{e.querySelectorAll('label:not(.select-item-label,.radio-option,[for*="select-item"])').forEach((e=>{e.closest(".radio-options")||e.remove()})),"true_false"===e.dataset.fieldType&&e.querySelector(".toggle-label")?.remove(),["checkbox","radio","select"].includes(e.dataset.fieldType)&&e.querySelector(".label")?.remove()}))}renderList(e){this.toggleGrid(),this.toggleTable(!1),this.ui.grid.classList.remove("grid-view"),this.ui.grid.classList.add("list-view"),e.forEach((e=>{let t=this.renderListItem(e);this.ui.grid.appendChild(t)}))}renderListItem(e){if(this.items.list.has(e.id))return this.items.list.get(e.id);const t=window.getTemplate("listView");t.dataset.id=e.id,e._pending&&t.classList.add("pending");let i=t.querySelector(".select-item"),s=t.querySelector(".select-item + label");[i.id,i.value,i.checked,s.htmlFor]=[e.id,e.id,this.selectedItems.has(`${e.id}`),e.id],t.querySelectorAll("[data-attr]").forEach((t=>{""!==e[t.dataset.attr]?t.textContent=e[t.dataset.attr]:t.remove()})),t.querySelectorAll("[data-field]").forEach((t=>{let i=e.fields[t.dataset.field];""!==i?"DIV"===t.tagName?t.innerHTML=i:t.textContent=i:t.remove()}));let l=t.querySelector("img");return l&&([l.src,l.alt]=[e.images[e.fields.post_thumbnail]?.medium??"",e.images[e.fields.post_thumbnail]?.alt??""]),this.items.list.set(e.id,t),t}setupTimelineDragHandler(){this.isTimeline&&"table"===this.currentView&&(this.timelineDragHandler&&this.timelineDragHandler.destroy(),this.timelineDragHandler=new window.jvbDragHandler({draggableSelector:".timeline-point",dropTargetSelector:".timeline-point",handleSelector:".drag-handle",getItemId:e=>e.dataset.imageId,getSelectedItems:()=>[],validateDrop:(e,t)=>{const i=document.querySelector(`.timeline-point[data-image-id="${e[0]}"]`);return!!i&&i.closest("tbody")===t.closest("tbody")},onDragStart:(e,t)=>{t.classList.add("is-dragging")},onDrop:(e,t)=>{const i=document.querySelector(`.timeline-point[data-image-id="${e[0]}"]`);if(!i)return;document.querySelectorAll(".drop-above, .drop-below").forEach((e=>{e.classList.remove("drop-above","drop-below")}));const s=i.closest("tbody");"above"===t.dataset.dropPosition?s.insertBefore(i,t):s.insertBefore(i,t.nextSibling),i.classList.remove("is-dragging"),this.updateTimelineOrder(s)},onDragEnd:(e,t)=>{document.querySelectorAll(".is-dragging, .drop-above, .drop-below").forEach((e=>{e.classList.remove("is-dragging","drop-above","drop-below")}))},previewElement:".drag-handle",previewOptions:{offset:{x:-20,y:-20},showCount:!1}}),this.addTimelineDragHoverLogic())}addTimelineDragHoverLogic(){let e=null;document.addEventListener("pointermove",(t=>{if(!document.querySelector(".timeline-point.is-dragging"))return;const i=t.target.closest(".timeline-point:not(.is-dragging)");if(!i)return void(e&&(e.classList.remove("drop-above","drop-below"),delete e.dataset.dropPosition,e=null));const s=i.getBoundingClientRect(),l=s.top+s.height/2,r=t.clientY<l;e&&e!==i&&(e.classList.remove("drop-above","drop-below"),delete e.dataset.dropPosition),i.classList.remove("drop-above","drop-below"),i.classList.add(r?"drop-above":"drop-below"),i.dataset.dropPosition=r?"above":"below",e=i}))}updateTimelineOrder(e){const t=parseInt(e.dataset.id),i=Array.from(e.querySelectorAll(".timeline-point")),s=this.store.get(t);if(!s)return;let l={};i.forEach(((e,t)=>{const i=e.dataset.imageId;l[i]=s.fields.timeline[i]})),s.fields.timeline=l,this.store.save(s),this.notify("order-changed",t),this.a11y?.announce(`Timeline order updated. ${i.length} steps reordered.`)}extractRowFields(e){const t={};return e.querySelectorAll("[data-field]").forEach((e=>{const i=e.dataset.field,s=e.querySelector("input, textarea, select");s&&("checkbox"===s.type?t[i]=s.checked:t[i]=s.value)})),t}toggleSelection(e){this.selectedItems.has(e)?this.selectedItems.delete(e):this.selectedItems.add(e),this.updateSelectionUI()}selectAll(e){const t=this.container.querySelectorAll(".item");e||(this.selectedItems.clear(),this.ui.bulk.selectAll.checked=!1,this.ui.bulk.select.value=""),t.forEach((t=>{e&&this.selectedItems.add(t.dataset.id),t.querySelector(".select-item").checked=e})),this.updateSelectionUI()}clearSelection(){this.selectAll(!1),this.ui.bulk.select.value=""}updateSelectionUI(){const e=this.selectedItems.size;if(this.ui.bulk.control&&(this.ui.bulk.control.hidden=0===e),this.ui.bulk.count){let t=1===e?"item":"items";this.ui.bulk.count.hidden=0===e,this.ui.bulk.count.textContent=0===e?"":`${e} ${t} selected`}}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach((i=>i(e,t)))}};
\ No newline at end of file
+window.jvbViews=class{constructor(e,t){this.a11y=window.jvbA11y,this.error=window.jvbError,this.container=e,this.initElements(),this.settings=window.jvbUserSettings,this.store=t,this.isTimeline=!!document.querySelector("[data-timeline]"),this.items={list:new Map,grid:new Map,table:new Map},this.currentView=this.container.dataset.view??"grid",this.selectedItems=new Set,this.subscribers=new Set,this.init()}initElements(){this.selectors={grid:".item-grid",table:{table:"form.table",form:"table",body:"table body",header:"table thead",footer:"table tfoot",selectedColumns:".all-filters .multi-select",columns:"thead th"},bulk:{count:".bulk-controls .selected-count",control:".bulk-controls .bulk-actions",select:".bulk-controls select",selectAll:".select-all"}},this.ui=window.uiFromSelectors(this.selectors,this.container)}init(){this.store.subscribe((e,t)=>{switch(e){case"items-saved":case"item-saved":case"item-deleted":break;case"data-loaded":this.handleItemsUpdate()}}),this.setupViewSwitcher(),this.changeHandler=this.handleChange.bind(this),this.clickHandler=this.handleClick.bind(this),this.lastSelected=null,document.addEventListener("change",this.changeHandler),document.addEventListener("click",this.clickHandler)}handleClick(e){e.target.closest(".select-item-label")&&(e.shiftKey?(e.preventDefault(),this.handleRangeSelection(e.target)):this.lastSelected=e.target.closest(".item"))}handleRangeSelection(e){if(!this.lastSelected)return void(this.lastSelected=e.closest(".item"));const t=e.closest(".item"),i=Array.from(this.container.querySelectorAll(".item")),s=i.indexOf(this.lastSelected),l=i.indexOf(t);if(-1===s||-1===l)return void(this.lastSelected=t);const r=Math.min(s,l),a=Math.max(s,l);let d=0;for(let e=r;e<=a;e++){let t=i[e];this.selectedItems.add(t.dataset.id);let s=t.querySelector(".select-item");s&&!s.checked&&(s.checked=!0,d++)}this.updateSelectionUI(),window.jvbA11y.announce(`Selected ${d} items in range.`)}handleChange(e){e.target.closest(".select-all")?this.selectAll(e.target.checked):e.target.closest(".select-item")?this.toggleSelection(e.target.closest(".item").dataset.id):e.target.closest("details.multi-select")&&this.toggleColumns(e.target.id,e.target.checked)}toggleColumns(e,t){this.ui.table.columns.filter(t=>t.className===e)}setupViewSwitcher(){document.querySelectorAll("[data-view]").forEach(e=>{this.settings.addSetting(e),e.addEventListener("click",()=>{this.currentView=e.dataset.view,this.render()})});const e=document.querySelector("[data-view]:checked");e&&(this.currentView=e.dataset.view)}handleItemsUpdate(){this.render()}render(){if(!this.store)return void console.error("No store connected to renderer");const e=this.store.getFiltered();if(0===e.length)return console.log("Nothing to show"),void this.renderEmpty();switch(this.currentView){case"grid":this.renderGrid(e);break;case"table":this.renderTable(e);break;case"list":this.renderList(e)}this.updateSelectionUI()}renderEmpty(){this.toggleTable(!1),window.removeChildren(this.ui.grid);const e=window.getTemplate("emptyState");e&&(this.ui.grid.appendChild(e),this.a11y?.announce("No items found"))}renderGrid(e){this.toggleGrid(),this.toggleTable(!1),this.ui.grid.classList.remove("list-view"),this.ui.grid.classList.add("grid-view");const t=document.createDocumentFragment();e.forEach(e=>{let i=this.renderGridItem(e);t.appendChild(i)}),this.ui.grid.appendChild(t)}renderGridItem(e){if(this.items.grid.has(e.id))return this.items.grid.get(e.id);const t=window.getTemplate("gridView");t.dataset.id=e.id,e._pending&&t.classList.add("pending");let[i,s,l,r,a]=[t.querySelector("input"),t.querySelector("label"),t.querySelector("img"),t.querySelector('[data-action="edit"]'),t.querySelector('[data-action="trash"]')];return[i.value,i.id,i.checked,s.htmlFor,r.dataset.id,a.dataset.id]=[e.id,`select-${e.id}`,this.selectedItems.has(`${e.id}`),`select-${e.id}`,e.id,e.id],[l.src,l.alt]=[e.images[e.fields.post_thumbnail]?.medium??"",e.images[e.fields.post_thumbnail]?.alt??""],this.items.grid.set(e.id,t),t}toggleTable(e){if(this.ui.table.selectedColumns&&(this.ui.table.selectedColumns.hidden=!e),e&&!this.ui.table.table){let e=window.getTemplate("contentTable");this.container.append(e),this.ui.table.table=this.container.querySelector("form.table"),this.ui.table.form=this.ui.table.table.querySelector("table"),this.ui.table.header=this.ui.table.form.querySelector("thead"),this.ui.table.footer=this.ui.table.form.querySelector("tfoot"),this.ui.table.body=this.ui.table.form.querySelector("tbody"),this.ui.table.columns=this.container.querySelectorAll(this.selectors.table.columns)}this.ui.table.table&&(this.ui.table.table.hidden=!e,e?this.notify("table-view",this.ui.table.table):this.notify("not-table-view",this.ui.table.table),this.ui.table.body&&window.removeChildren(this.ui.table.body)),this.ui.table.selectedColumns&&(this.ui.table.selectedColumns.hidden=!e)}toggleGrid(){window.removeChildren(this.ui.grid)}renderTable(e){this.toggleTable(!0),this.toggleGrid(),e.forEach(e=>{let t=this.isTimeline?this.renderTimelineTableItem(e):this.renderTableItem(e);this.ui.table.body?this.ui.table.body.append(t):(this.ui.table.footer||(this.ui.table.footer=this.ui.table.table.querySelector("tfoot")),this.ui.table.form.insertBefore(t,this.ui.table.footer))}),window.jvbSelector.scanExistingFields()}renderTableItem(e){if(this.items.table.has(e.id))return this.items.table.get(e.id);const t=window.getTemplate("tableView");t.dataset.id=e.id,[t.querySelector(".select-item").id,t.querySelector(".select-item").value,t.querySelector(".select-item").checked,t.querySelector(".select-item + label").htmlFor]=[e.id,e.id,this.selectedItems.has(`${e.id}`),e.id];let i=t.querySelector(`input[name="post_status"][value="${e.status}"]`);if(i&&(i.checked=!0),Object.hasOwn(this.ui.table.table.dataset,"edit"))new window.jvbPopulate(t,e);else for(let[i,s]of Object.entries(e)){let e=t.querySelector(`[data-field="${i}"]`);if(e){let t=e.querySelector("p");"date"===e.dataset.fieldType&&(s=window.formatTimeAgo(s)),t.textContent=s}}return this.cleanupTableRow(t),this.items.table.set(e.id,t),t}renderTimelineTableItem(e){if(this.items.table.has(e.id))return this.items.table.get(e.id);const t=window.getTemplate("tableView");t.dataset.id=e.id,[t.querySelector(".select-item").id,t.querySelector(".select-item").value,t.querySelector(".select-item").checked,t.querySelector(".select-item + label").htmlFor]=[e.id,e.id,this.selectedItems.has(`${e.id}`),e.id];let i=t.querySelector(".timeline-point"),s=t,l=t.querySelector("tr.shared");return new window.jvbPopulate(l,e),this.prefixTimelineFieldNames(l,e.id),this.cleanupTableRow(l),e.fields.timeline&&"object"==typeof e.fields.timeline&&Object.entries(e.fields.timeline).forEach(([t,l],r)=>{let a=i.cloneNode(!0);a.dataset.index=r,a.dataset.imageId=t;const d={fields:l,images:e.images,taxonomies:{}};new window.jvbPopulate(a,d),this.cleanupTableRow(a);let o=e.images[l.post_thumbnail];o&&(a.querySelector(".field.upload").title=o["image-title"]),this.prefixTimelineFieldNames(a,l.id),s.insertBefore(a,i)}),i.remove(),this.items.table.set(e.id,t),t}prefixTimelineFieldNames(e,t){e.querySelectorAll("input, textarea, select").forEach(e=>{const i=e.name;if(!i||i.startsWith("[")||"form-id"===i||i.startsWith("_"))return;let s=e.nextElementSibling;e.name=`[${t}]${i}`,s&&"LABEL"===s.tagName&&(e.id=`[${t}]${e.id}`,s.htmlFor=e.id)})}cleanupTableRow(e){e.querySelectorAll("td[data-field]").forEach(e=>{e.querySelectorAll('label:not(.select-item-label,.radio-option,[for*="select-item"])').forEach(e=>{e.closest(".radio-options")||e.remove()}),"true_false"===e.dataset.fieldType&&e.querySelector(".toggle-label")?.remove(),["checkbox","radio","select"].includes(e.dataset.fieldType)&&e.querySelector(".label")?.remove()})}renderList(e){this.toggleGrid(),this.toggleTable(!1),this.ui.grid.classList.remove("grid-view"),this.ui.grid.classList.add("list-view"),e.forEach(e=>{let t=this.renderListItem(e);this.ui.grid.appendChild(t)})}renderListItem(e){if(this.items.list.has(e.id))return this.items.list.get(e.id);const t=window.getTemplate("listView");t.dataset.id=e.id,e._pending&&t.classList.add("pending");let i=t.querySelector(".select-item"),s=t.querySelector(".select-item + label");[i.id,i.value,i.checked,s.htmlFor]=[e.id,e.id,this.selectedItems.has(`${e.id}`),e.id],t.querySelectorAll("[data-attr]").forEach(t=>{""!==e[t.dataset.attr]?t.textContent=e[t.dataset.attr]:t.remove()}),t.querySelectorAll("[data-field]").forEach(t=>{let i=e.fields[t.dataset.field];""!==i?"DIV"===t.tagName?t.innerHTML=i:t.textContent=i:t.remove()});let l=t.querySelector("img");return l&&([l.src,l.alt]=[e.images[e.fields.post_thumbnail]?.medium??"",e.images[e.fields.post_thumbnail]?.alt??""]),this.items.list.set(e.id,t),t}setupTimelineDragHandler(){this.isTimeline&&"table"===this.currentView&&(this.timelineDragHandler&&this.timelineDragHandler.destroy(),this.timelineDragHandler=new window.jvbDragHandler({draggableSelector:".timeline-point",dropTargetSelector:".timeline-point",handleSelector:".drag-handle",getItemId:e=>e.dataset.imageId,getSelectedItems:()=>[],validateDrop:(e,t)=>{const i=document.querySelector(`.timeline-point[data-image-id="${e[0]}"]`);return!!i&&i.closest("tbody")===t.closest("tbody")},onDragStart:(e,t)=>{t.classList.add("is-dragging")},onDrop:(e,t)=>{const i=document.querySelector(`.timeline-point[data-image-id="${e[0]}"]`);if(!i)return;document.querySelectorAll(".drop-above, .drop-below").forEach(e=>{e.classList.remove("drop-above","drop-below")});const s=i.closest("tbody");"above"===t.dataset.dropPosition?s.insertBefore(i,t):s.insertBefore(i,t.nextSibling),i.classList.remove("is-dragging"),this.updateTimelineOrder(s)},onDragEnd:(e,t)=>{document.querySelectorAll(".is-dragging, .drop-above, .drop-below").forEach(e=>{e.classList.remove("is-dragging","drop-above","drop-below")})},previewElement:".drag-handle",previewOptions:{offset:{x:-20,y:-20},showCount:!1}}),this.addTimelineDragHoverLogic())}addTimelineDragHoverLogic(){let e=null;document.addEventListener("pointermove",t=>{if(!document.querySelector(".timeline-point.is-dragging"))return;const i=t.target.closest(".timeline-point:not(.is-dragging)");if(!i)return void(e&&(e.classList.remove("drop-above","drop-below"),delete e.dataset.dropPosition,e=null));const s=i.getBoundingClientRect(),l=s.top+s.height/2,r=t.clientY<l;e&&e!==i&&(e.classList.remove("drop-above","drop-below"),delete e.dataset.dropPosition),i.classList.remove("drop-above","drop-below"),i.classList.add(r?"drop-above":"drop-below"),i.dataset.dropPosition=r?"above":"below",e=i})}updateTimelineOrder(e){const t=parseInt(e.dataset.id),i=Array.from(e.querySelectorAll(".timeline-point")),s=this.store.get(t);if(!s)return;let l={};i.forEach((e,t)=>{const i=e.dataset.imageId;l[i]=s.fields.timeline[i]}),s.fields.timeline=l,this.store.save(s),this.notify("order-changed",t),this.a11y?.announce(`Timeline order updated. ${i.length} steps reordered.`)}extractRowFields(e){const t={};return e.querySelectorAll("[data-field]").forEach(e=>{const i=e.dataset.field,s=e.querySelector("input, textarea, select");s&&("checkbox"===s.type?t[i]=s.checked:t[i]=s.value)}),t}toggleSelection(e){this.selectedItems.has(e)?this.selectedItems.delete(e):this.selectedItems.add(e),this.updateSelectionUI()}selectAll(e){const t=this.container.querySelectorAll(".item");e||(this.selectedItems.clear(),this.ui.bulk.selectAll.checked=!1,this.ui.bulk.select.value=""),t.forEach(t=>{e&&this.selectedItems.add(t.dataset.id),t.querySelector(".select-item").checked=e}),this.updateSelectionUI()}clearSelection(){this.selectAll(!1),this.ui.bulk.select.value=""}updateSelectionUI(){const e=this.selectedItems.size;if(this.ui.bulk.control&&(this.ui.bulk.control.hidden=0===e),this.ui.bulk.count){let t=1===e?"item":"items";this.ui.bulk.count.hidden=0===e,this.ui.bulk.count.textContent=0===e?"":`${e} ${t} selected`}}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(i=>i(e,t))}};
\ No newline at end of file
diff --git a/base/options.php b/base/options.php
index 2131220..3cc8583 100644
--- a/base/options.php
+++ b/base/options.php
@@ -87,6 +87,15 @@
 		return $this->fields;
 	}
 
+	public function getConfig(string $fieldName):array
+	{
+		$allFields = $this->fields->getFields();
+		if (array_key_exists($fieldName, $allFields)) {
+			return $allFields[$fieldName]->getConfig();
+		}
+		return [];
+	}
+
 	public function resetOptions():void
 	{
 		if (empty($this->resetFields)) {
@@ -138,6 +147,7 @@
 	{
 		$meta = Meta::forOptions();
 		$options['showFormID'] = false;
+		$options['classes'] = ['options'];
 		$result = Form::renderFormFrom(
 			$meta,
 			'options',
diff --git a/build/feed/view.asset.php b/build/feed/view.asset.php
index 83fa6e6..d8d8db8 100644
--- a/build/feed/view.asset.php
+++ b/build/feed/view.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => '7e7d1570989c0c348bd7');
+<?php return array('dependencies' => array(), 'version' => '94db3c594161e0a1a632');
diff --git a/build/feed/view.js b/build/feed/view.js
index 2360009..5da61c9 100644
--- a/build/feed/view.js
+++ b/build/feed/view.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.container=document.querySelector("section.feed-block"),this.container&&(this.a11y=window.jvbA11y,this.error=window.jvbError,this.cache=new window.jvbCache("feed"),this.templates=window.jvbTemplates,this.isFirstLoad=!0,this.config={contextId:"",context:"",highlight:null,gallery:!1,view:this.cache.get("feedView")||"grid",...this.container.dataset},this.init())}init(){this.initElements(),this.defineTemplates(),this.initListeners(),this.initFilters(),this.initStore(),this.initTaxonomies().then(e=>{}),this.processCachedFilters(),this.processURLFilters(),this.updateFilterUI(),this.initGallery()}initElements(){this.selectors={filterTrigger:"[data-filter]",filters:{actions:".filter-actions .toggle-text",container:".all-filters",showing:".all-filters summary .current",content:'[data-filter="content"]',ordering:".ordering",orderby:'[data-filter="orderby"]',order:'[data-filter="order"]',orderWrap:".order-direction",match:'[data-filter="match"]',favourites:'[data-filter="favourites"]',taxonomy:'[data-filter^="taxonomy"]'},grid:".item-grid",selected:".selected-items",buttons:{loadMore:"button.load-more",remove:".remove-term",clearFilters:"button.clear-filters",refresh:'button[data-action="refresh"]'}},this.ui=window.uiFromSelectors(this.selectors,this.container),this.ui.buttons.refresh=document.querySelector(this.selectors.buttons.refresh),["content","orderby","order","taxonomy"].forEach(e=>{let t=this.ui.filters.container.querySelectorAll(this.selectors.filters[e]);this.ui[e]=Array.from(t)}),this.contentTypes=this.ui.content.length>0?this.ui.content.map(e=>e.value):[this.container.dataset.content],this.taxonomies=this.ui.taxonomies?.length>0?Array.from(this.ui.taxonomies).map(e=>e.dataset.taxonomy):[]}getChecked(e){["content","orderby","order"].includes(e)||console.log("Invalid item to check: ",e);let t=this.ui[e];if(!t)return;let i=t.filter(e=>e.checked);return"content"===e&&i.length>0&&this.updateContentFor(i[0].value),0===i.length?t[0].value:i[0].value}initListeners(){this.popStateHandler=this.handlePopState.bind(this),this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),window.addEventListener("popstate",this.popStateHandler),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler)}initFilters(){this.allowedFilters=["content","order","orderby","favourites","match"];let e={content:this.getChecked("content"),orderby:this.getChecked("orderby"),order:this.getChecked("order"),page:1};this.config.context&&(e.context=this.config.context),this.config.contextId&&(e.contextId=this.config.contextId),this.filters=e,this.defaults={...e}}updateFilterUI(){if(this.ui.filters.container&&([this.ui.content,this.ui.orderby,this.ui.order].forEach(e=>{if(e)for(let t of e){let[e,i]=[t.dataset.filter,t.value];if(!Object.hasOwn(this.store.filters,e))break;let s=this.store.filters[e]===i;if(s){t.checked=s;break}}}),Object.hasOwn(this.store.filters,"taxonomy")))for(let[e,t]of Object.entries(this.store.filters.taxonomy))t.forEach(e=>{e=parseInt(e),this.selector.store.get(e)&&this.createTermElement(e)})}handlePopState(e){e.state?.filters&&this.processURLFilters()&&(this.store.setFilters(this.filters),this.a11y.announce("Feed filters updated from browser history"))}handleClick(e){window.targetCheck(e,this.selectors.buttons.loadMore)?this.nextPage():window.targetCheck(e,this.selectors.buttons.clearFilters)&&this.clearFilters();let t=window.targetCheck(e,this.selectors.buttons.remove);t&&this.removeSelectedTerm(t),window.targetCheck(e,this.selectors.buttons.refresh)&&(this.store.clearCache(),this.store.fetch());let i=window.targetCheck(e,'[data-filter="orderby"]');i&&"random"===i.value&&i.checked&&this.renderItems()}nextPage(){const e=(this.store.filters.page||1)+1,t=this.store.lastResponse?.pages||e;this.store.setFilters({page:Math.min(e,t)})}handleChange(e){const t=e.target;if(Object.hasOwn(t.dataset,"filter")){if(this.allowedFilters.includes(t.dataset.filter)){let e={};e[t.dataset.filter]=t.value,this.resetFilters(e)}switch(t.dataset.filter){case"content":this.updateContentFor(t.value);break;case"orderby":this.updateOrderOptions(t.value)}}}clearFilters(){this.taxFilters={},window.removeChildren(this.ui.selected),this.taxonomies.forEach(e=>{let t=this.getFieldId(e);this.selector.selectedTerms.get(t)?.clear()}),this.store.setFilters({...this.defaults,taxonomy:null}),this.updateURL(),this.saveToCacheFilters()}resetFilters(e){e={...this.store.filters,page:1,...e},this.store.setFilters(e),this.updateURL(),this.saveToCacheFilters()}getFieldId(e){return this.selector.getFieldId(this.ui.taxonomies.filter(t=>t.dataset.taxonomy===e)[0]??null)}removeSelectedTerm(e){const t=parseInt(e.dataset.id),i=e.dataset.taxonomy;Object.hasOwn(this.taxFilters,i)&&(this.taxFilters[i]=this.taxFilters[i].filter(e=>e!==t),0===this.taxFilters[i].length&&delete this.taxFilters[i]),e.remove();const s=this.getFieldId(i);s&&(this.selector.activeField=s,this.selector.removeSelected(t,s)),this.resetFilters({taxonomy:Object.keys(this.taxFilters).length>0?this.taxFilters:null})}updateContentFor(e){let t=[this.ui.taxonomies,this.ui.orderby];this.ui.filters.showing.textContent=this.ui.content.filter(t=>t.value===e)[0].dataset.label,t.forEach(t=>{t&&t.forEach(t=>{const i=t.dataset.for?.split(",")??[];t.hidden=i.length>0&&!i.includes(e),t.hidden&&t.checked&&(t.checked=!1)})})}updateOrderOptions(e){if(this.ui.filters.orderWrap){let t=this.ui.filters.orderWrap.dataset.forOrder.split(",")??[];this.ui.filters.orderWrap.hidden=!t.includes(e)}}updateFilterControls(){const e=Object.keys(this.taxFilters);this.ui.buttons.clearFilters&&(this.ui.buttons.clearFilters.hidden=0===e.length),this.ui.filters.actions&&(this.ui.filters.actions.hidden=e.length<=1)}async initTaxonomies(){this.taxFilters={},this.selector=window.jvbSelector,this.selector.subscribe((e,t)=>{"selected-terms"===e&&this.handleTaxonomyChange(t)})}handleTaxonomyChange(e){const{terms:t,taxonomy:i}=e;0!==t.size&&(this.taxFilters[i]=Array.from(t),this.resetFilters({taxonomy:this.taxFilters}),t.forEach(e=>{this.createTermElement(e)}),this.updateFilterControls())}getTaxonomyIcon(e){let t=this.ui.taxonomies.find(t=>t.dataset.taxonomy===e);return t?.dataset.icon.trim()||"tag"}createTermElement(e){const t=this.selector.store.get(e);t&&(this.ui.selected.querySelector(`[data-id="${e}"]`)||(t.icon=this.getTaxonomyIcon(t.taxonomy),this.ui.selected.append(this.templates.create("feedTerm",t))))}processCachedFilters(){Object.keys(this.filters).forEach(e=>{let t=this.cache.get(`${this.config.contextId}_${this.config.context}_${e}`);t&&t!==this.filters[e]&&(this.filters[e]=t)})}processURLFilters(){if(!this.isFirstPage())return!1;const e=new URLSearchParams(window.location.search);if(!e.toString())return!1;let t=!1;this.allowedFilters.forEach(i=>{let s=e.get(`f_${i}`);s&&(t=!0,this.filters[i]=s)});let i=!1;return e.forEach((e,s)=>{if(s.startsWith("f_tax_")){i=!0,t=!0;const r=s.replace("f_tax_","");this.taxFilters[r]=e.split(",").map(Number)}}),t&&(i&&(this.filters.taxonomy=this.taxFilters),this.resetFilters(this.filters)),!0}updateURL(){const e=new URLSearchParams;this.allowedFilters.forEach(t=>{Object.hasOwn(this.store.filters,t)&&this.store.filters[t]!==this.defaults[t]&&e.set(`f_${t}`,this.store.filters[t])});for(let[t,i]of Object.entries(this.taxFilters))i.length>0&&e.set(`f_tax_${t}`,i.join(","));const t=`${window.location.pathname}${e.toString()?"?"+e.toString():""}`;t!==window.location.pathname+window.location.search&&window.history.pushState({filters:this.store.filters},"",t)}saveToCacheFilters(){Object.keys(this.store.filters).forEach(e=>{const t=`${this.config.contextId}_${this.config.context}_${e}`;this.store.filters[e]!==this.defaults[e]?this.cache.set(t,this.store.filters[e]):this.cache.remove(t)});const e=`${this.config.contextId}_${this.config.context}_taxonomy`;Object.keys(this.taxFilters).length>0?this.cache.set(e,this.taxFilters):this.cache.remove(e)}initGallery(){this.gallery=!!this.config.gallery&&window.jvbGallery,this.gallery&&this.gallery.subscribe((e,t)=>{"load-more"===e&&this.store.lastResponse?.has_more&&this.nextPage()})}initStore(){let e=this.ui.orderby.filter(e=>!["date","date_modified","title","random"].includes(e.value)),t=[];e.forEach(e=>{t.push({name:e.value,keyPath:e.value})});const i=window.jvbStore.register("feed",{storeName:"feed",endpoint:"feed",keyPath:"id",indexes:[{name:"content",keyPath:"content"},{name:"taxonomy",keyPath:"taxonomy"},{name:"user",keyPath:"user"},{name:"date",keyPath:"date"},{name:"modified",keyPath:"modified"},{name:"title",keyPath:"title"},...t],filters:this.filters,TTL:216e5,showLoading:!0,required:"content"},2);this.store=i.feed,this.store.subscribe((e,t)=>{if("data-loaded"===e){if(this.isFirstLoad)return void(this.isFirstLoad=!1);this.renderItems(t.items),this.ui.buttons.loadMore.hidden=!0,this.store.lastResponse&&this.store.lastResponse?.has_more&&(this.ui.buttons.loadMore.hidden=!this.store.lastResponse?.has_more??!0)}})}isFirstPage(){return 1===this.store.filters.page}renderItems(e=null){e=e??this.store.getFiltered(),this.isFirstPage()&&window.removeChildren(this.ui.grid),0===e.length?(this.showEmptyState(),this.a11y.announceItems(0,this.isFirstPage())):window.chunkIt(e,e=>this.createItemElement(e),t=>{this.removePlaceholders(),this.ui.grid.append(t),this.config.gallery&&this.gallery.buildGalleryItems(".item img"),this.a11y.makeNavigable(this.ui.grid.querySelectorAll(".item:not([data-keyboard-nav])")),this.a11y.announceItems(e.length,!this.isFirstPage(),this.store.lastResponse?.has_more??!1)},5).then(()=>{}),this.updateFilterControls()}showEmptyState(){window.removeChildren(this.ui.grid),this.ui.grid.append(this.templates.create("emptyState"))}createItemElement(e){if("object"==typeof e||(e=this.store.get(e)))return this.templates.create(`feedItem${window.uppercaseFirst(e.content)}`,e)}splitIDs(e){return String(e).split(",").map(e=>parseInt(e.trim())).filter(e=>e)}isImageField(e,t){return!(!Object.hasOwn(e,"images")||0===Object.keys(e.images).length)&&this.splitIDs(t).some(t=>Object.keys(e.images).map(e=>parseInt(e)).includes(parseInt(t)))}formatImageFields(e,t,i){let s=this.splitIDs(t);if(0!==s.length)if(s.length>1){let t=e.querySelector("img");if(!t)return;s.forEach(s=>{let r=t.cloneNode(!0);this.formatImageField(r,s,i),e.append(r)}),t.remove()}else{if("IMG"!==e.tagName&&!(e=e.querySelector("img")))return;this.formatImageField(e,s[0],i)}}formatImageField(e,t,i){let s=i.images[t]??!1;s&&([e.src,e.srcset,e.alt]=[s.tiny,`${s.tiny} 50w, ${s.small} 300w, ${s.medium} 1024w`,s["image-alt-text"]])}isTaxonomyField(e,t){return!(!Object.hasOwn(e,"taxonomies")||0===Object.keys(e.taxonomies).length)&&Object.keys(e.taxonomies).includes(t)}formatTaxonomyField(e,t,i,s){if("UL"!==e.tagName||!e.querySelector("li"))return;let r=this.splitIDs(s);0===r.length&&e.remove();let o=e.querySelector("li");for(let s of r){let r=t.taxonomies[i][s]??!1;if(!r)continue;let a=o.cloneNode(!0),n=a.querySelector("a");if(!n)continue;let l=window.decodeHTMLEntities(r.title);[n.href,n.title,n.textContent]=[r.url,`See more ${l}`,l],e.append(a)}o.remove()}isTimeField(e){return"TIME"===e.tagName||null!==e.querySelector("time")}formatTimeField(e,t){("TIME"===e.tagName||(e=e.querySelector("time")))&&(e.setAttribute("datetime",t),e.textContent=window.formatTimeAgo(t,"F Y"))}formatField(e,t){e.textContent=window.decodeHTMLEntities(t)}addTimelineElements(e,t){let[i,s,r,o]=[t.querySelector("span.after-text"),t.querySelector('[data-field="number"] b'),t.querySelector('[data-field="started"] time'),t.querySelector('[data-field="updated"] time')];i&&(i.textContent=`After ${e.number} Tx`),s&&(s.textContent=e.number),r&&this.formatTimeField(r,e.fields.timeline[0].post_date),o&&this.formatTimeField(o,e.fields.timeline[e.fields.timeline.length-1].post_date)}removePlaceholders(){const e=this.ui.grid.querySelectorAll(".placeholder");e.length>0&&e.forEach(e=>e.remove())}defineTemplates(){const e=this.templates,t=this;e.define("feedTerm",{refs:{icon:".icon",span:"span"},setup({el:e,refs:t,manyRefs:i,data:s}){e.dataset.id=s.id,e.dataset.taxonomy=s.taxonomy,t.icon&&(t.icon.className=`icon icon-${s.icon}`),t.span&&(t.span.textContent=window.decodeHTMLEntities(s.name))}}),e.define("emptyState"),this.contentTypes.forEach(i=>{e.define(`feedItem${window.uppercaseFirst(i)}`,{refs:{link:"a"},manyRefs:{fields:"[data-field]"},setup({el:e,refs:i,manyRefs:s,data:r}){const o=Object.hasOwn(e.dataset,"timeline");if(s.fields){for(let e of s.fields){if(o&&["timeline","number"].includes(e.dataset.field))continue;const i=!!Object.hasOwn(r.fields,e.dataset.field)&&r.fields[e.dataset.field];i?t.isImageField(r,i)?t.formatImageField(e,i,r):t.isTaxonomyField(r,e.dataset.field)?t.formatTaxonomyField(e,r,e.dataset.field,i):t.isTimeField(e)?t.formatTimeField(e,i):t.formatField(e,i):e.remove()}i.link&&""!==r.url&&(i.link.href=r.url,i.link.title=`View ${r.fields.post_title??"Item"}`),o&&t.addTimelineElements(r,e)}}})})}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.feedBlock=new e)})})})();
\ No newline at end of file
+(()=>{class e{constructor(){this.container=document.querySelector("section.feed-block"),this.container&&(this.a11y=window.jvbA11y,this.error=window.jvbError,this.cache=new window.jvbCache("feed"),this.templates=window.jvbTemplates,this.isFirstLoad=!0,this.config={contextId:"",context:"",highlight:null,gallery:!1,view:this.cache.get("feedView")||"grid",...this.container.dataset},this.init())}init(){this.initElements(),this.defineTemplates(),this.initListeners(),this.initFilters(),this.initStore(),this.initTaxonomies().then(e=>{}),this.processCachedFilters(),this.processURLFilters(),this.updateFilterUI(),this.initGallery()}initElements(){this.selectors={filterTrigger:"[data-filter]",filters:{actions:".filter-actions .toggle-text",container:".all-filters",showing:".all-filters summary .current",content:'[data-filter="content"]',ordering:".ordering",orderby:'[data-filter="orderby"]',order:'[data-filter="order"]',orderWrap:".order-direction",match:'[data-filter="match"]',favourites:'[data-filter="favourites"]',taxonomy:'[data-filter^="taxonomy"]'},grid:".item-grid",selected:".selected-items",buttons:{loadMore:"button.load-more",remove:".remove-term",clearFilters:"button.clear-filters",refresh:'button[data-action="refresh"]'}},this.ui=window.uiFromSelectors(this.selectors,this.container),this.ui.buttons.refresh=document.querySelector(this.selectors.buttons.refresh),["content","orderby","order","taxonomy"].forEach(e=>{let t=this.ui.filters.container.querySelectorAll(this.selectors.filters[e]);this.ui[e]=Array.from(t)}),this.contentTypes=this.ui.content.length>0?this.ui.content.map(e=>e.value):[this.container.dataset.content],this.taxonomies=this.ui.taxonomies?.length>0?Array.from(this.ui.taxonomies).map(e=>e.dataset.taxonomy):[]}getChecked(e){["content","orderby","order"].includes(e)||console.log("Invalid item to check: ",e);let t=this.ui[e];if(!t)return;let i=t.filter(e=>e.checked);return"content"===e&&i.length>0&&this.updateContentFor(i[0].value),0===i.length?t[0].value:i[0].value}initListeners(){this.popStateHandler=this.handlePopState.bind(this),this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),window.addEventListener("popstate",this.popStateHandler),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler)}initFilters(){this.allowedFilters=["content","order","orderby","favourites","match"];let e={content:this.getChecked("content"),orderby:this.getChecked("orderby"),order:this.getChecked("order"),page:1};this.config.context&&(e.context=this.config.context),this.config.contextId&&(e.contextId=this.config.contextId),this.filters=e,this.defaults={...e}}updateFilterUI(){if(this.ui.filters.container&&([this.ui.content,this.ui.orderby,this.ui.order].forEach(e=>{if(e)for(let t of e){let[e,i]=[t.dataset.filter,t.value];if(!Object.hasOwn(this.store.filters,e))break;let s=this.store.filters[e]===i;if(s){t.checked=s;break}}}),Object.hasOwn(this.store.filters,"taxonomy")))for(let[e,t]of Object.entries(this.store.filters.taxonomy))t.forEach(e=>{e=parseInt(e),this.selector.store.get(e)&&this.createTermElement(e)})}handlePopState(e){e.state?.filters&&this.processURLFilters()&&(this.store.setFilters(this.filters),this.a11y.announce("Feed filters updated from browser history"))}handleClick(e){window.targetCheck(e,this.selectors.buttons.loadMore)?this.nextPage():window.targetCheck(e,this.selectors.buttons.clearFilters)&&this.clearFilters();let t=window.targetCheck(e,this.selectors.buttons.remove);t&&this.removeSelectedTerm(t),window.targetCheck(e,this.selectors.buttons.refresh)&&(this.store.clearCache(),this.store.fetch());let i=window.targetCheck(e,'[data-filter="orderby"]');i&&"random"===i.value&&i.checked&&this.renderItems()}nextPage(){const e=(this.store.filters.page||1)+1,t=this.store.lastResponse?.pages||e;this.store.setFilters({page:Math.min(e,t)})}handleChange(e){const t=e.target;if(Object.hasOwn(t.dataset,"filter")){if(this.allowedFilters.includes(t.dataset.filter)){let e={};e[t.dataset.filter]=t.value,this.resetFilters(e)}switch(t.dataset.filter){case"content":this.updateContentFor(t.value);break;case"orderby":this.updateOrderOptions(t.value)}}}clearFilters(){this.taxFilters={},window.removeChildren(this.ui.selected),this.taxonomies.forEach(e=>{let t=this.getFieldId(e);this.selector.selectedTerms.get(t)?.clear()}),this.store.setFilters({...this.defaults,taxonomy:null}),this.updateURL(),this.saveToCacheFilters()}resetFilters(e){e={...this.store.filters,page:1,...e},this.store.setFilters(e),this.updateURL(),this.saveToCacheFilters()}getFieldId(e){var t;return this.selector.getFieldId(null!==(t=this.ui.taxonomies.filter(t=>t.dataset.taxonomy===e)[0])&&void 0!==t?t:null)}removeSelectedTerm(e){const t=parseInt(e.dataset.id),i=e.dataset.taxonomy;Object.hasOwn(this.taxFilters,i)&&(this.taxFilters[i]=this.taxFilters[i].filter(e=>e!==t),0===this.taxFilters[i].length&&delete this.taxFilters[i]),e.remove();const s=this.getFieldId(i);s&&(this.selector.activeField=s,this.selector.removeSelected(t,s)),this.resetFilters({taxonomy:Object.keys(this.taxFilters).length>0?this.taxFilters:null})}updateContentFor(e){let t=[this.ui.taxonomies,this.ui.orderby];this.ui.filters.showing.textContent=this.ui.content.filter(t=>t.value===e)[0].dataset.label,t.forEach(t=>{t&&t.forEach(t=>{var i;const s=null!==(i=t.dataset.for?.split(","))&&void 0!==i?i:[];t.hidden=s.length>0&&!s.includes(e),t.hidden&&t.checked&&(t.checked=!1)})})}updateOrderOptions(e){if(this.ui.filters.orderWrap){var t;let i=null!==(t=this.ui.filters.orderWrap.dataset.forOrder.split(","))&&void 0!==t?t:[];this.ui.filters.orderWrap.hidden=!i.includes(e)}}updateFilterControls(){const e=Object.keys(this.taxFilters);this.ui.buttons.clearFilters&&(this.ui.buttons.clearFilters.hidden=0===e.length),this.ui.filters.actions&&(this.ui.filters.actions.hidden=e.length<=1)}async initTaxonomies(){this.taxFilters={},this.selector=window.jvbSelector,this.selector.subscribe((e,t)=>{"selected-terms"===e&&this.handleTaxonomyChange(t)})}handleTaxonomyChange(e){const{terms:t,taxonomy:i}=e;0!==t.size&&(this.taxFilters[i]=Array.from(t),this.resetFilters({taxonomy:this.taxFilters}),t.forEach(e=>{this.createTermElement(e)}),this.updateFilterControls())}getTaxonomyIcon(e){let t=this.ui.taxonomies.find(t=>t.dataset.taxonomy===e);return t?.dataset.icon.trim()||"tag"}createTermElement(e){const t=this.selector.store.get(e);t&&(this.ui.selected.querySelector(`[data-id="${e}"]`)||(t.icon=this.getTaxonomyIcon(t.taxonomy),this.ui.selected.append(this.templates.create("feedTerm",t))))}processCachedFilters(){Object.keys(this.filters).forEach(e=>{let t=this.cache.get(`${this.config.contextId}_${this.config.context}_${e}`);t&&t!==this.filters[e]&&(this.filters[e]=t)})}processURLFilters(){if(!this.isFirstPage())return!1;const e=new URLSearchParams(window.location.search);if(!e.toString())return!1;let t=!1;this.allowedFilters.forEach(i=>{let s=e.get(`f_${i}`);s&&(t=!0,this.filters[i]=s)});let i=!1;return e.forEach((e,s)=>{if(s.startsWith("f_tax_")){i=!0,t=!0;const r=s.replace("f_tax_","");this.taxFilters[r]=e.split(",").map(Number)}}),t&&(i&&(this.filters.taxonomy=this.taxFilters),this.resetFilters(this.filters)),!0}updateURL(){const e=new URLSearchParams;this.allowedFilters.forEach(t=>{Object.hasOwn(this.store.filters,t)&&this.store.filters[t]!==this.defaults[t]&&e.set(`f_${t}`,this.store.filters[t])});for(let[t,i]of Object.entries(this.taxFilters))i.length>0&&e.set(`f_tax_${t}`,i.join(","));const t=`${window.location.pathname}${e.toString()?"?"+e.toString():""}`;t!==window.location.pathname+window.location.search&&window.history.pushState({filters:this.store.filters},"",t)}saveToCacheFilters(){Object.keys(this.store.filters).forEach(e=>{const t=`${this.config.contextId}_${this.config.context}_${e}`;this.store.filters[e]!==this.defaults[e]?this.cache.set(t,this.store.filters[e]):this.cache.remove(t)});const e=`${this.config.contextId}_${this.config.context}_taxonomy`;Object.keys(this.taxFilters).length>0?this.cache.set(e,this.taxFilters):this.cache.remove(e)}initGallery(){this.gallery=!!this.config.gallery&&window.jvbGallery,this.gallery&&this.gallery.subscribe((e,t)=>{"load-more"===e&&this.store.lastResponse?.has_more&&this.nextPage()})}initStore(){let e=this.ui.orderby.filter(e=>!["date","date_modified","title","random"].includes(e.value)),t=[];e.forEach(e=>{t.push({name:e.value,keyPath:e.value})});const i=window.jvbStore.register("feed",{storeName:"feed",endpoint:"feed",keyPath:"id",indexes:[{name:"content",keyPath:"content"},{name:"taxonomy",keyPath:"taxonomy"},{name:"user",keyPath:"user"},{name:"date",keyPath:"date"},{name:"modified",keyPath:"modified"},{name:"title",keyPath:"title"},...t],filters:this.filters,TTL:216e5,showLoading:!0,required:"content"},2);this.store=i.feed,this.store.subscribe((e,t)=>{if("data-loaded"===e){if(this.isFirstLoad)return void(this.isFirstLoad=!1);var i;this.renderItems(t.items),this.ui.buttons.loadMore.hidden=!0,this.store.lastResponse&&this.store.lastResponse?.has_more&&(this.ui.buttons.loadMore.hidden=null===(i=!this.store.lastResponse?.has_more)||void 0===i||i)}})}isFirstPage(){return 1===this.store.filters.page}renderItems(e=null){e=null!=e?e:this.store.getFiltered(),this.isFirstPage()&&window.removeChildren(this.ui.grid),0===e.length?(this.showEmptyState(),this.a11y.announceItems(0,this.isFirstPage())):window.chunkIt(e,e=>this.createItemElement(e),t=>{var i;this.removePlaceholders(),this.ui.grid.append(t),this.config.gallery&&this.gallery.buildGalleryItems(".item img"),this.a11y.makeNavigable(this.ui.grid.querySelectorAll(".item:not([data-keyboard-nav])")),this.a11y.announceItems(e.length,!this.isFirstPage(),null!==(i=this.store.lastResponse?.has_more)&&void 0!==i&&i)},5).then(()=>{}),this.updateFilterControls()}showEmptyState(){window.removeChildren(this.ui.grid),this.ui.grid.append(this.templates.create("emptyState"))}createItemElement(e){if("object"==typeof e||(e=this.store.get(e)))return this.templates.create(`feedItem${window.uppercaseFirst(e.content)}`,e)}splitIDs(e){return String(e).split(",").map(e=>parseInt(e.trim())).filter(e=>e)}isImageField(e,t){return!(!Object.hasOwn(e,"images")||0===Object.keys(e.images).length)&&this.splitIDs(t).some(t=>Object.keys(e.images).map(e=>parseInt(e)).includes(parseInt(t)))}formatImageFields(e,t,i){let s=this.splitIDs(t);if(0!==s.length)if(s.length>1){let t=e.querySelector("img");if(!t)return;s.forEach(s=>{let r=t.cloneNode(!0);this.formatImageField(r,s,i),e.append(r)}),t.remove()}else{if("IMG"!==e.tagName&&!(e=e.querySelector("img")))return;this.formatImageField(e,s[0],i)}}formatImageField(e,t,i){var s;let r=null!==(s=i.images[t])&&void 0!==s&&s;r&&([e.src,e.srcset,e.alt]=[r.tiny,`${r.tiny} 50w, ${r.small} 300w, ${r.medium} 1024w`,r["image-alt-text"]])}isTaxonomyField(e,t){return!(!Object.hasOwn(e,"taxonomies")||0===Object.keys(e.taxonomies).length)&&Object.keys(e.taxonomies).includes(t)}formatTaxonomyField(e,t,i,s){if("UL"!==e.tagName||!e.querySelector("li"))return;let r=this.splitIDs(s);0===r.length&&e.remove();let o=e.querySelector("li");for(let s of r){var a;let r=null!==(a=t.taxonomies[i][s])&&void 0!==a&&a;if(!r)continue;let n=o.cloneNode(!0),l=n.querySelector("a");if(!l)continue;let h=window.decodeHTMLEntities(r.title);[l.href,l.title,l.textContent]=[r.url,`See more ${h}`,h],e.append(n)}o.remove()}isTimeField(e){return"TIME"===e.tagName||null!==e.querySelector("time")}formatTimeField(e,t){("TIME"===e.tagName||(e=e.querySelector("time")))&&(e.setAttribute("datetime",t),e.textContent=window.formatTimeAgo(t,"F Y"))}formatField(e,t){e.textContent=window.decodeHTMLEntities(t)}addTimelineElements(e,t){let[i,s,r,o]=[t.querySelector("span.after-text"),t.querySelector('[data-field="number"] b'),t.querySelector('[data-field="started"] time'),t.querySelector('[data-field="updated"] time')];i&&(i.textContent=`After ${e.number} Tx`),s&&(s.textContent=e.number),r&&this.formatTimeField(r,e.fields.timeline[0].post_date),o&&this.formatTimeField(o,e.fields.timeline[e.fields.timeline.length-1].post_date)}removePlaceholders(){const e=this.ui.grid.querySelectorAll(".placeholder");e.length>0&&e.forEach(e=>e.remove())}defineTemplates(){const e=this.templates,t=this;e.define("feedTerm",{refs:{icon:".icon",span:"span"},setup({el:e,refs:t,manyRefs:i,data:s}){e.dataset.id=s.id,e.dataset.taxonomy=s.taxonomy,t.icon&&(t.icon.className=`icon icon-${s.icon}`),t.span&&(t.span.textContent=window.decodeHTMLEntities(s.name))}}),e.define("emptyState"),this.contentTypes.forEach(i=>{e.define(`feedItem${window.uppercaseFirst(i)}`,{refs:{link:"a"},manyRefs:{fields:"[data-field]"},setup({el:e,refs:i,manyRefs:s,data:r}){const o=Object.hasOwn(e.dataset,"timeline");if(s.fields){for(let e of s.fields){if(o&&["timeline","number"].includes(e.dataset.field))continue;const i=!!Object.hasOwn(r.fields,e.dataset.field)&&r.fields[e.dataset.field];i?t.isImageField(r,i)?t.formatImageField(e,i,r):t.isTaxonomyField(r,e.dataset.field)?t.formatTaxonomyField(e,r,e.dataset.field,i):t.isTimeField(e)?t.formatTimeField(e,i):t.formatField(e,i):e.remove()}var a;i.link&&""!==r.url&&(i.link.href=r.url,i.link.title=`View ${null!==(a=r.fields.post_title)&&void 0!==a?a:"Item"}`),o&&t.addTimelineElements(r,e)}}})})}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.feedBlock=new e)})})})();
\ No newline at end of file
diff --git a/build/menu/style-index-rtl.css b/build/menu/style-index-rtl.css
index 8b13789..d6e39df 100644
--- a/build/menu/style-index-rtl.css
+++ b/build/menu/style-index-rtl.css
@@ -1 +1 @@
-
+.info h4{font-size:var(--txt-medium);font-weight:var(--fw-h);margin:0}.field.quantity>label{right:var(--offScreen);position:absolute}.menu-item .header{flex-wrap:nowrap}.menu-item .header h3,.menu-item .header p{margin:0;width:-moz-max-content;width:max-content}
diff --git a/build/menu/style-index.css b/build/menu/style-index.css
index 8b13789..318e70e 100644
--- a/build/menu/style-index.css
+++ b/build/menu/style-index.css
@@ -1 +1 @@
-
+.info h4{font-size:var(--txt-medium);font-weight:var(--fw-h);margin:0}.field.quantity>label{left:var(--offScreen);position:absolute}.menu-item .header{flex-wrap:nowrap}.menu-item .header h3,.menu-item .header p{margin:0;width:-moz-max-content;width:max-content}
diff --git a/checks.php b/checks.php
index 79cc829..25e4f80 100644
--- a/checks.php
+++ b/checks.php
@@ -25,11 +25,10 @@
 	if (!Site::has('limit_hours')) {
 		return true;
 	}
-	if (Options::get('open_to_public') !== 1) {
-		error_log('Not open to public');
+	if (Options::get('open_to_public') !== '1') {
 		return false;
 	}
-	if (Options::get('today_hours')) {
+	if (!empty(Options::get('today_hours'))) {
 		return jvbIsTimeBetween();
 	}
 	return jvbIsCurrentlyOpen();
diff --git a/cleanup.php b/cleanup.php
index 8aef5fa..1fd7797 100644
--- a/cleanup.php
+++ b/cleanup.php
@@ -57,9 +57,11 @@
 	}
 	wp_dequeue_script('wp-block-template-skip-link');
 
-	// Remove WordPress 6.5+ script modules
-	wp_dequeue_script_module('@wordpress/interactivity');
-	wp_deregister_script_module('@wordpress/interactivity');
+	if (!is_plugin_active('woocommerce/woocommerce.php')) {
+		wp_dequeue_script_module('@wordpress/interactivity');
+		wp_deregister_script_module('@wordpress/interactivity');
+	}
+
 	wp_dequeue_script_module('@wordpress/block-library/navigation/view');
 	wp_deregister_script_module('@wordpress/block-library/navigation/view');
 
diff --git a/inc/blocks/CustomBlocks.php b/inc/blocks/CustomBlocks.php
index 9d77437..aa3cd91 100644
--- a/inc/blocks/CustomBlocks.php
+++ b/inc/blocks/CustomBlocks.php
@@ -124,7 +124,7 @@
 			$content = $this->$method($block, $content, $parent);
 			return $isPrerender ? $this->maybeOutputCustomStyles().$content : $content;
 		} elseif (JVB_TESTING && !empty($blockName)) {
-			if (!in_array($block['blockName'], $this->getIgnore($isPrerender))) {
+			if (!str_starts_with($block['blockName'],'jvb/') && !in_array($block['blockName'], $this->getIgnore($isPrerender))) {
 				jvbDump('No method found for '.print_r($block['blockName'], true));
 			}
 		}
diff --git a/inc/blocks/FeedBlock.php b/inc/blocks/FeedBlock.php
index abe8395..223c554 100644
--- a/inc/blocks/FeedBlock.php
+++ b/inc/blocks/FeedBlock.php
@@ -155,18 +155,35 @@
 		$args['post_type'] = array_map('jvbCheckBase', $attrs['contentTypes']);
 		$this->args = $args;
 	}
-		protected function getContent():string
-		{
-			return implode(',', $this->content);
-		}
-		protected function determineTaxonomies():void
-		{
-			$taxonomies = [];
-			$ignore = [];
-			foreach ($this->content as $content) {
+	protected function getContent():string
+	{
+		return implode(',', $this->content);
+	}
+	protected function determineTaxonomies():void
+	{
+		$taxonomies = [];
+		$ignore = [];
+		foreach ($this->content as $content) {
 
-				$registrar = Registrar::getInstance($content);
-				if (!$registrar) continue;
+			$registrar = Registrar::getInstance($content);
+			if (!$registrar) continue;
+			if ($registrar->hasFeature('is_content')) {
+				foreach ($registrar->registrar->for as $c) {
+					$contentRegistrar = Registrar::getInstance($c);
+					if (!$contentRegistrar) continue;
+					$theTax = $contentRegistrar->registrar->taxonomies;
+					foreach ($theTax as $tax) {
+						if (!in_array($tax, $ignore) && !in_array($tax, $taxonomies)) {
+							$taxReg = Registrar::getInstance($tax);
+							if ($taxReg->hasFeature('show_feed')) {
+								$taxonomies[] = $tax;
+							} else {
+								$ignore[] = $tax;
+							}
+						}
+					}
+				}
+			} else {
 				$theTax = $registrar->registrar->taxonomies;
 				foreach ($theTax as $tax) {
 					if (!in_array($tax, $ignore) && !in_array($tax, $taxonomies)) {
@@ -179,8 +196,9 @@
 					}
 				}
 			}
-			$this->taxonomies = array_unique($taxonomies);
 		}
+		$this->taxonomies = array_unique($taxonomies);
+	}
 
 	protected function renderFiltersAndControls():string
 	{
@@ -199,42 +217,42 @@
 			jvbIcon('x')
 		);
 	}
-		protected function renderSearch():string
-		{
-			return sprintf(
-				'<div class="search row left nowrap">
+	protected function renderSearch():string
+	{
+		return sprintf(
+			'<div class="search row left nowrap">
 				<span class="label">Search:</span>
 				%s
 				</div>',
-				jvbSearch()
-			);
-		}
-		protected function renderContentLabels():string
-		{
-			return sprintf(
-				'<span class="label">Showing: <span class="current">%s</span></span>',
-				$this->content[0]??''
-			);
+			jvbSearch()
+		);
+	}
+	protected function renderContentLabels():string
+	{
+		return sprintf(
+			'<span class="label">Showing: <span class="current">%s</span></span>',
+			$this->content[0]??''
+		);
 
-		}
-		protected function renderContent():string
-		{
-			$favourites = '';
-			if (Site::has('favourites')) {
-				$favourites = sprintf(
-					'<input type="checkbox" id="favourites" class="btn" name="favourites" value="on" data-filter="favourites">
+	}
+	protected function renderContent():string
+	{
+		$favourites = '';
+		if (Site::has('favourites')) {
+			$favourites = sprintf(
+				'<input type="checkbox" id="favourites" class="btn" name="favourites" value="on" data-filter="favourites">
 					<label for="favourites" title="Show Favourites">%s%s<span class="screen-reader-text">Show Favourites Only</span></label>',
-					jvbIcon('heart'),
-					jvbIcon('heart', ['style' => 'fill'])
-				);
-			}
-			if (count($this->content) === 1) {
-				return empty($favourites)
-					? sprintf(
-						'<input type="hidden" data-filter="content" name="content" value="%s">',
-						implode(',', $this->content)
-					)
-					: sprintf(
+				jvbIcon('heart'),
+				jvbIcon('heart', ['style' => 'fill'])
+			);
+		}
+		if (count($this->content) === 1) {
+			return empty($favourites)
+				? sprintf(
+					'<input type="hidden" data-filter="content" name="content" value="%s">',
+					implode(',', $this->content)
+				)
+				: sprintf(
 					'<div class="content row right">
 						<input type="hidden" name="content" value="%s">
 						%s
@@ -242,42 +260,42 @@
 					implode(',', $this->content),
 					$favourites
 				);
+		}
+		$i = 0;
+		$content = implode('', array_map(function($type) use (&$i) {
+			$registrar = Registrar::getInstance($type);
+
+			$args = [
+				'post_type'	=> $registrar->getBased(),
+				'posts_per_page'	=> 1,
+				'fields'	=> 'ids',
+			];
+			if (!is_null($this->context)) {
+				$context = Registrar::getInstance($this->context);
+				switch ($context->getType()) {
+					case 'term':
+						$args['tax_query'] = [];
+						$args['tax_query'][] = [
+							'taxonomy'	=> $context->getBased(),
+							'terms'		=> $this->contextID
+						];
+						break;
+					case 'user':
+						$args['author'] = $this->contextID;
+						break;
+				}
 			}
-			$i = 0;
-			$content = implode('', array_map(function($type) use (&$i) {
-				$registrar = Registrar::getInstance($type);
+			$check = new WP_Query($args);
+			$hasPosts = !empty($check->posts);
+			wp_reset_postdata();
 
-				$args = [
-					'post_type'	=> $registrar->getBased(),
-					'posts_per_page'	=> 1,
-					'fields'	=> 'ids',
-				];
-				if (!is_null($this->context)) {
-					$context = Registrar::getInstance($this->context);
-					switch ($context->getType()) {
-						case 'term':
-							$args['tax_query'] = [];
-							$args['tax_query'][] = [
-								'taxonomy'	=> $context->getBased(),
-								'terms'		=> $this->contextID
-							];
-							break;
-						case 'user':
-							$args['author'] = $this->contextID;
-							break;
-					}
-				}
-				$check = new WP_Query($args);
-				$hasPosts = !empty($check->posts);
-				wp_reset_postdata();
-
-				$disabled = !$hasPosts;
-				$checked = $i === 0 && $hasPosts;
-				if ($hasPosts) {
-					$i++;
-				}
-				return sprintf(
-					'<input type="radio"
+			$disabled = !$hasPosts;
+			$checked = $i === 0 && $hasPosts;
+			if ($hasPosts) {
+				$i++;
+			}
+			return sprintf(
+				'<input type="radio"
 					id="filter-%s"
 					class="btn"
 					name="content"
@@ -285,60 +303,60 @@
 					data-label="%s"
 					value="%s"%s%s>
 					<label for="filter-%s" title="Show %s">%s<span class="label">%s</span></label>',
-					$type,
-					$registrar->getSingular(),
-					$type,
-					$checked ? ' checked' : '',
-					$disabled ? ' disabled' : '',
-					$type,
-					$registrar->getSingular(),
-					jvbIcon($registrar->getIcon()),
-					$registrar->getSingular()
-				);
-			}, $this->content));
+				$type,
+				$registrar->getSingular(),
+				$type,
+				$checked ? ' checked' : '',
+				$disabled ? ' disabled' : '',
+				$type,
+				$registrar->getSingular(),
+				jvbIcon($registrar->getIcon()),
+				$registrar->getSingular()
+			);
+		}, $this->content));
 
 
 
-			return sprintf(
-				'<div class="content row left nowrap"><span class="label">Showing:</span>
+		return sprintf(
+			'<div class="content row left nowrap"><span class="label">Showing:</span>
 					%s%s
 				</div>',
-				$content,
-				$favourites
-			);
+			$content,
+			$favourites
+		);
+	}
+
+	protected function renderFilters():string
+	{
+		if (empty ($this->taxonomies)) {
+			return '';
 		}
+		$inside = implode('', array_filter(array_map(function($tax) {
+			$registrar = Registrar::getInstance($tax);
+			if (!$registrar) return '';
 
-		protected function renderFilters():string
-		{
-			if (empty ($this->taxonomies)) {
-				return '';
-			}
-			$inside = implode('', array_filter(array_map(function($tax) {
-				$registrar = Registrar::getInstance($tax);
-				if (!$registrar) return '';
+			$current = BASE.$this->content[0];
+			$contentFor = $registrar->registrar->for;
+			$hidden = in_array($current, $contentFor) ? '' : ' hidden';
 
-				$current = BASE.$this->content[0];
-				$contentFor = $registrar->registrar->for;
-				$hidden = in_array($current, $contentFor) ? '' : ' hidden';
+			$selector = new TaxonomySelector(
+				'feed-'.$tax,
+				$tax,
+				[
+					'icon'	=> $registrar->getIcon(),
+					'update'=> '.selected-items-section .selected-items',
+					'types'	=> $contentFor,
+					'autocomplete'	=> false,
+					'hidden'	=> $hidden,
+					'output'	=> 'minimal',
+					'search'	=> true
+				]
+			);
+			return $selector->render();
 
-				$selector = new TaxonomySelector(
-					'feed-'.$tax,
-					$tax,
-					[
-						'icon'	=> $registrar->getIcon(),
-						'update'=> '.selected-items-section .selected-items',
-						'types'	=> $contentFor,
-						'autocomplete'	=> false,
-						'hidden'	=> $hidden,
-						'output'	=> 'minimal',
-						'search'	=> true
-					]
-				);
-				return $selector->render();
-
-			}, $this->taxonomies)));
-			return sprintf(
-				'<div class="taxonomies row left">
+		}, $this->taxonomies)));
+		return sprintf(
+			'<div class="taxonomies row left">
 				<div class="row top wrap">
 					<span class="label">Filter By:</span>
 					%s
@@ -354,155 +372,155 @@
 					</div>
 				</div>
 			</div>',
-				$inside,
-				str_replace('class="toggle-text"', 'class="toggle-text" hidden', jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY', false, ['filter' => 'match'])),
-				jvbIcon('x')
-			);
-		}
+			$inside,
+			str_replace('class="toggle-text"', 'class="toggle-text" hidden', jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY', false, ['filter' => 'match'])),
+			jvbIcon('x')
+		);
+	}
 
-		protected function renderOrderControls():string
-		{
-			$orderby = [
-				[
-					'slug'	=> 'title',
-					'icon'	=> 'alphabetical',
-					'label'	=> 'Name'
-				],
-				[
-					'slug'	=> 'date',
-					'icon'	=> 'calendar',
-					'label'	=> 'Date Created',
-				],
-				[
-					'slug'	=> 'date_modified',
-					'icon'	=> 'clock-clockwise',
-					'label'	=> 'Date Modified'
-				]
-			];
-			$custom = $this->getCustomOrdering();
-			$orderby = $orderby + $custom;
-			$orderby[] = [
-				'slug'	=> 'random',
-				'icon'	=> 'shuffle',
-				'label'	=> 'Randomly'
-			];
+	protected function renderOrderControls():string
+	{
+		$orderby = [
+			[
+				'slug'	=> 'title',
+				'icon'	=> 'alphabetical',
+				'label'	=> 'Name'
+			],
+			[
+				'slug'	=> 'date',
+				'icon'	=> 'calendar',
+				'label'	=> 'Date Created',
+			],
+			[
+				'slug'	=> 'date_modified',
+				'icon'	=> 'clock-clockwise',
+				'label'	=> 'Date Modified'
+			]
+		];
+		$custom = $this->getCustomOrdering();
+		$orderby = $orderby + $custom;
+		$orderby[] = [
+			'slug'	=> 'random',
+			'icon'	=> 'shuffle',
+			'label'	=> 'Randomly'
+		];
 
-			$custom = implode(',', array_map(function($ord) {
-				return $ord['slug'];
-			}, $custom));
+		$custom = implode(',', array_map(function($ord) {
+			return $ord['slug'];
+		}, $custom));
 
-			$i = 0;
-			$orderby = sprintf(
-				'<div class="orderby row left">
+		$i = 0;
+		$orderby = sprintf(
+			'<div class="orderby row left">
 				<span class="label">Order by:</span>%s
 				</div>',
-				implode('', array_map(function ($by) use (&$i){
-					$checked = $i === 0 ? ' checked' : '';
-					$i++;
-					return sprintf(
-						'<input type="radio" id="order-%s" class="btn" name="orderby" value="%s" data-filter="orderby"%s%s>
+			implode('', array_map(function ($by) use (&$i){
+				$checked = $i === 0 ? ' checked' : '';
+				$i++;
+				return sprintf(
+					'<input type="radio" id="order-%s" class="btn" name="orderby" value="%s" data-filter="orderby"%s%s>
 							<label for="order-%s" title="Order %s">%s<span class="label">%s</span></label>',
-						$by['slug'],
-						$by['slug'],
-						$checked,
-						empty($by['for']??[]) ? '' : ' data-for="'.implode($by['for']).'"',
-						$by['slug'],
-						$by['slug'] === 'random' ? $by['label'] : 'by '.$by['label'],
-						jvbIcon($by['icon']),
-						$by['label']
-					);
-				}, $orderby))
-			);
+					$by['slug'],
+					$by['slug'],
+					$checked,
+					empty($by['for']??[]) ? '' : ' data-for="'.implode($by['for']).'"',
+					$by['slug'],
+					$by['slug'] === 'random' ? $by['label'] : 'by '.$by['label'],
+					jvbIcon($by['icon']),
+					$by['label']
+				);
+			}, $orderby))
+		);
 
-			$order = [
-				[
-					'slug'	=> 'desc',
-					'icon'	=> 'sort-descending',
-					'label'	=> 'Descending (A-Z, 1-10)'
-				],
-				[
-					'slug'	=> 'asc',
-					'icon'	=> 'sort-ascending',
-					'label'	=> 'Ascending (Z-A, 10-1)'
-				]
-			];
+		$order = [
+			[
+				'slug'	=> 'desc',
+				'icon'	=> 'sort-descending',
+				'label'	=> 'Descending (A-Z, 1-10)'
+			],
+			[
+				'slug'	=> 'asc',
+				'icon'	=> 'sort-ascending',
+				'label'	=> 'Ascending (Z-A, 10-1)'
+			]
+		];
 
-			$i = 0;
-			$order = sprintf(
-				'<div class="order-direction row left" data-for-order="date,date_modified,title%s">
+		$i = 0;
+		$order = sprintf(
+			'<div class="order-direction row left" data-for-order="date,date_modified,title%s">
 				<span class="label">Order:</span>
 				%s
 				</div>',
-				$custom === '' ? '' : ','.$custom,
-				implode('', array_map(function ($ord) use (&$i) {
-					$checked = $i=== 0 ? ' checked' : '';
-					$i++;
-					return sprintf(
-						'<input type="radio" id="order-%s" class="btn" name="order" value="%s" data-filter="order"%s>
+			$custom === '' ? '' : ','.$custom,
+			implode('', array_map(function ($ord) use (&$i) {
+				$checked = $i=== 0 ? ' checked' : '';
+				$i++;
+				return sprintf(
+					'<input type="radio" id="order-%s" class="btn" name="order" value="%s" data-filter="order"%s>
 						<label for="order-%s" title="Sort %s">
 							%s
 							<span class="label">%s</span>
 						</label>',
-						$ord['slug'],
-						$ord['slug'],
-						$checked,
-						$ord['slug'],
-						$ord['label'],
-						jvbIcon($ord['icon']),
-						$ord['label']
-					);
-				}, $order))
-			);
+					$ord['slug'],
+					$ord['slug'],
+					$checked,
+					$ord['slug'],
+					$ord['label'],
+					jvbIcon($ord['icon']),
+					$ord['label']
+				);
+			}, $order))
+		);
 
-			return sprintf(
-				'<div class="ordering row top left nowrap">%s%s</div>',
-				$orderby,
-				$order
-			);
-		}
-			protected function getCustomOrdering():array
-			{
-				$custom = [];
-				foreach ($this->content as $content) {
-					$registrar = Registrar::getInstance($content);
-					if (!$registrar || empty($registrar->config('feed')->getCustomOrder())) {
-						continue;
-					}
-					$custom = array_merge_recursive($custom, $registrar->config('feed')->getCustomOrder());
-				}
-				return $custom;
+		return sprintf(
+			'<div class="ordering row top left nowrap">%s%s</div>',
+			$orderby,
+			$order
+		);
+	}
+	protected function getCustomOrdering():array
+	{
+		$custom = [];
+		foreach ($this->content as $content) {
+			$registrar = Registrar::getInstance($content);
+			if (!$registrar || empty($registrar->config('feed')->getCustomOrder())) {
+				continue;
 			}
-		protected function renderViewControls():string
-		{
-			$views = [
-				'grid'	=> ['slug' => 'grid', 'icon' => 'squares-four', 'label' => 'Grid View'],
-				'list'	=> ['slug' => 'list', 'icon' => 'rows', 'label' => 'List View']
-			];
+			$custom = array_merge_recursive($custom, $registrar->config('feed')->getCustomOrder());
+		}
+		return $custom;
+	}
+	protected function renderViewControls():string
+	{
+		$views = [
+			'grid'	=> ['slug' => 'grid', 'icon' => 'squares-four', 'label' => 'Grid View'],
+			'list'	=> ['slug' => 'list', 'icon' => 'rows', 'label' => 'List View']
+		];
 
-			$i = 0;
-			return sprintf(
-				'<div class="view row left nowrap"><span class="label">Switch View:</span>%s</div>',
-				implode('', array_map(function ($view) use (&$i) {
-					$checked = $i === 0 ? ' checked' : '';
-					$i++;
-					return sprintf(
-						'<input type="radio"
+		$i = 0;
+		return sprintf(
+			'<div class="view row left nowrap"><span class="label">Switch View:</span>%s</div>',
+			implode('', array_map(function ($view) use (&$i) {
+				$checked = $i === 0 ? ' checked' : '';
+				$i++;
+				return sprintf(
+					'<input type="radio"
 						data-view="%s" value="%s" class="btn" name="view" id="view-%s"%s>
 						<label for="view-%s" title="%s">
 						%s<span class="label">%s</span>
 </label>',
-						$view['slug'],
-						$view['slug'],
-						$view['slug'],
-						$checked,
-						$view['slug'],
-						$view['label'],
-						jvbIcon($view['icon']),
-						$view['label'],
-					);
-				}, $views))
-			);
-		}
+					$view['slug'],
+					$view['slug'],
+					$view['slug'],
+					$checked,
+					$view['slug'],
+					$view['label'],
+					jvbIcon($view['icon']),
+					$view['label'],
+				);
+			}, $views))
+		);
+	}
 
 	protected function renderPlaceholders():string
 	{
@@ -542,37 +560,37 @@
 		$items = $this->getItems();
 
 		$out = '<div class="item-grid">';
-			$out .= implode('',array_map(function ($ID) {
-				$content = $this->isContentTax
-					? jvbNoBase($this->content[0])
-					: jvbNoBase(get_post_type($ID));
+		$out .= implode('',array_map(function ($ID) {
+			$content = $this->isContentTax
+				? jvbNoBase($this->content[0])
+				: jvbNoBase(get_post_type($ID));
 
-				return $this->renderItem($content, $ID);
-			}, $items));
+			return $this->renderItem($content, $ID);
+		}, $items));
 		$out .= '</div>';
 		return $out;
 	}
-		protected function getItems():array
-		{
-			if ($this->isContentTax) {
-				$items = get_terms([
-					'taxonomy'	=> $this->content,
-					'fields'	=> 'ids',
-					'meta_key'	=> BASE.'date_modified',
-					'meta_type'	=> 'DATETIME',
-					'orderby'	=> 'meta_value',
-					'order'		=> 'desc',
-					'number'	=> $this->args['posts_per_page']
-				]);
-				$items = $items && !is_wp_error($items) ? $items : [];
-			} else {
-				$items = new WP_Query($this->args);
-				$this->hasMore = $items->found_posts > $this->args['posts_per_page'];
-				$items = $items->posts;
-				wp_reset_postdata();
-			}
-			return $items;
+	protected function getItems():array
+	{
+		if ($this->isContentTax) {
+			$items = get_terms([
+				'taxonomy'	=> $this->content,
+				'fields'	=> 'ids',
+				'meta_key'	=> BASE.'date_modified',
+				'meta_type'	=> 'DATETIME',
+				'orderby'	=> 'meta_value',
+				'order'		=> 'desc',
+				'number'	=> $this->args['posts_per_page']
+			]);
+			$items = $items && !is_wp_error($items) ? $items : [];
+		} else {
+			$items = new WP_Query($this->args);
+			$this->hasMore = $items->found_posts > $this->args['posts_per_page'];
+			$items = $items->posts;
+			wp_reset_postdata();
 		}
+		return $items;
+	}
 
 	protected function renderLoader():string
 	{
@@ -705,76 +723,76 @@
 			$out
 		);
 	}
-		protected function buildFeedItem(string $content, ?int $ID = null):string
-		{
-			$registrar = Registrar::getInstance($content);
-			$meta = is_null($ID) ? false :
-				match ($registrar->getType()) {
-					'post'	=> Meta::forPost($ID),
-					'term'	=> Meta::forTerm($ID),
-					'user'	=> Meta::forUser($ID),
-					default => false
-				};
+	protected function buildFeedItem(string $content, ?int $ID = null):string
+	{
+		$registrar = Registrar::getInstance($content);
+		$meta = is_null($ID) ? false :
+			match ($registrar->getType()) {
+				'post'	=> Meta::forPost($ID),
+				'term'	=> Meta::forTerm($ID),
+				'user'	=> Meta::forUser($ID),
+				default => false
+			};
 
-			[$images, $fields] = $registrar->getFeedFields();
+		[$images, $fields] = $registrar->getFeedFields();
 
-			/**
-			 * Get the main image for the feed item.
-			 * Output can be overridden with the $imagesFn
-			 */
-			$imagesFn = BASE.'render_'.$content.'_feed_item_images';
-			if (function_exists($imagesFn)) {
-				$img = $imagesFn($ID, $images);
-			} else {
-				$img = '';
-				foreach ($images as $config) {
-					$field = $config['name'];
-					$img .= $meta ? jvbFormatImage($meta->get($field), 'tiny', 'medium')
-						: $this->defaultFieldTemplate($config['type'], $field);
+		/**
+		 * Get the main image for the feed item.
+		 * Output can be overridden with the $imagesFn
+		 */
+		$imagesFn = BASE.'render_'.$content.'_feed_item_images';
+		if (function_exists($imagesFn)) {
+			$img = $imagesFn($ID, $images);
+		} else {
+			$img = '';
+			foreach ($images as $config) {
+				$field = $config['name'];
+				$img .= $meta ? jvbFormatImage($meta->get($field), 'tiny', 'medium')
+					: $this->defaultFieldTemplate($config['type'], $field);
+			}
+		}
+		$img = sprintf(
+			'<div class="images"><a href="%s">%s</a></div>',
+			$ID ? get_the_permalink($ID) : '',
+			$img
+		);
+
+		/**
+		 * Start the Details with the fields
+		 * Plugins can modify the summary title with the 'jvbFeedItemSummary' filter
+		 */
+		$summary = sprintf(
+			'<details><summary>%s</summary>',
+			apply_filters('jvbFeedItemSummary', jvbIcon('dots-three'), $content)
+		);
+		/**
+		 * Work through the fields
+		 * Each field output can be overridden with $field or $fieldType
+		 */
+		foreach ($fields as $config) {
+			$f = $config['name'];
+			$functions = [
+				BASE.'render_'.$content.'_field_'.$f, 	//Overrides field for this content
+				BASE.'render_field_'.$f,				//Overrides field with this name
+				BASE.'render_field_type_'.$config['type'] //Overrides field of this type
+			];
+
+			$didIt = false;
+			foreach ($functions as $func) {
+				if (!$didIt && function_exists($func)) {
+					$didIt = true;
+					$summary .= $func($ID, is_null($ID));
 				}
 			}
-			$img = sprintf(
-				'<div class="images"><a href="%s">%s</a></div>',
-				$ID ? get_the_permalink($ID) : '',
-				$img
-			);
+			if (!$didIt) {
+				$summary .= $this->defaultFieldTemplate($config['type'], $f, is_null($ID), $meta, $config);
+			}
 
-			/**
-			 * Start the Details with the fields
-			 * Plugins can modify the summary title with the 'jvbFeedItemSummary' filter
-			 */
-			$summary = sprintf(
-				'<details><summary>%s</summary>',
-				apply_filters('jvbFeedItemSummary', jvbIcon('dots-three'), $content)
-			);
-				/**
-				 * Work through the fields
-				 * Each field output can be overridden with $field or $fieldType
-				 */
-				foreach ($fields as $config) {
-					$f = $config['name'];
-					$functions = [
-						BASE.'render_'.$content.'_field_'.$f, 	//Overrides field for this content
-						BASE.'render_field_'.$f,				//Overrides field with this name
-						BASE.'render_field_type_'.$config['type'] //Overrides field of this type
-					];
-
-					$didIt = false;
-					foreach ($functions as $func) {
-						if (!$didIt && function_exists($func)) {
-							$didIt = true;
-							$summary .= $func($ID, is_null($ID));
-						}
-					}
-					if (!$didIt) {
-						$summary .= $this->defaultFieldTemplate($config['type'], $f, is_null($ID), $meta, $config);
-					}
-
-				}
-			$summary .= '</details>';
-
-			return $img.$summary;
 		}
+		$summary .= '</details>';
+
+		return $img.$summary;
+	}
 
 	protected function defaultFieldTemplate(string $fieldType, string $fieldName, bool $isTemplate = true, bool|Meta $meta = false, array $config = []):string
 	{
@@ -787,7 +805,7 @@
 			case 'post_title':
 				return sprintf(
 					'<h3%s>%s</h3>',
-			$data,
+					$data,
 					$meta && !empty($value) ? $value : '',
 				);
 			case 'post_date':
diff --git a/inc/blocks/FormBlock.php b/inc/blocks/FormBlock.php
index 0d447da..c34fee8 100644
--- a/inc/blocks/FormBlock.php
+++ b/inc/blocks/FormBlock.php
@@ -35,6 +35,9 @@
 	{
 		$this->cache = Cache::for('forms', WEEK_IN_SECONDS);
 
+		if (JVB_TESTING) {
+			$this->cache->flush();
+		}
 		// Initialize forms from filter
 		$this->forms = $this->registerForms();
 		$this->form_contact = apply_filters('jvb_form_contact', '');
@@ -276,7 +279,7 @@
 			}
 		}
 
-		echo '<form id="' . esc_attr($form_id) . '" data-form-id="'.esc_attr($type).'" data-save="form">';
+		echo '<form id="' . esc_attr($form_id) . '" data-form-id="'.esc_attr($type).'" data-save="form" data-handled data-step>';
 //		wp_nonce_field('jvb_form_' . $type);
 	}
 
@@ -318,7 +321,7 @@
 		echo '<div class="container">';
 		if ($total > 1) {
 			echo '<div class="form-progress">';
-			jvbRenderProgressBar('<span class="step-text">Step <span class="current">1</span> of ' . $total . '</span>');
+			jvbRenderProgressBar('<span class="step-text">Step <span class="current">1</span> of ' . $total . '</span>', false, false);
 			echo '</div>';
 		}
 
@@ -332,7 +335,7 @@
 
 				echo '<button type="button" class="tab' . $active_class . '" data-tab="' . esc_attr($slug) . '" data-step="'.$i.'" role="tab" aria-selected="' . $aria_selected . '">';
 				echo '<span class="step-number">' . $i . '</span>';
-				echo '<h2>' . esc_html($section['label'] ?? $section) . '</h2>';
+				echo '<span>' . esc_html($section['label'] ?? $section) . '</span>';
 				echo '</button>';
 				$i++;
 			}
diff --git a/inc/blocks/MenuBlock.php b/inc/blocks/MenuBlock.php
index 02f2338..11a61a3 100644
--- a/inc/blocks/MenuBlock.php
+++ b/inc/blocks/MenuBlock.php
@@ -1,6 +1,8 @@
 <?php
 namespace JVBase\blocks;
 
+use JVBase\base\Options;
+use JVBase\base\Site;
 use JVBase\managers\Cache;
 use JVBase\forms\TaxonomySelector;
 use JVBase\meta\Form;
@@ -26,14 +28,20 @@
     protected string $detailsTitle;
     protected array $details;
 	protected array|false $sections = false;
+	protected string $base;
 
     public function __construct()
     {
         $this->cache = Cache::for('menu', WEEK_IN_SECONDS)->connect('post');
+		if (Site::hasIntegration('square')) {
+			$this->base = '_square_';
+		} else if (Site::hasIntegration('helcim')) {
+			$this->base = '_helcim_';
+		}
         add_action('init', [ $this, 'registerBlock' ]);
     }
 
-    public function registerBlock()
+    public function registerBlock():void
     {
         register_block_type($this->path, [
             'render_callback' => [ $this, 'render' ]
@@ -45,7 +53,6 @@
 		if (is_post_type_archive(BASE.'menu_item')) {
 			$this->params = ['all'];
 		} elseif (is_tax(BASE.'section')) {
-			jvbDump(get_queried_object());
 			$this->params = [];
 		} else {
 			$this->params = [];
@@ -58,7 +65,9 @@
 		if (empty($this->params)) {
 			return '';
 		}
+		jvbInlineStyles('forms');
 		$key = $this->cache->generateKey($this->params);
+		$this->cache->flush();
 		return $this->cache->remember(
 			$key,
 			function() {
@@ -106,8 +115,7 @@
 	protected function getSections():array
 	{
 		if (!$this->sections) {
-			$options = Meta::forOptions('options');
-			$sections = $options->get('menu_section_order');
+			$sections = Options::get('menu_section_order');
 			if (!is_array($sections)) {
 				$sections = [];
 			}
@@ -156,27 +164,28 @@
 
 	protected function renderMenuItem(int $ID, string $slug, string $postType = 'menu_item') {
 		$meta = Meta::forPost($ID);
+		$fields = $meta->getAll();
 		$values = $meta->getAll([
 			'post_title',
-			'_square_catalog_id',
-			'price',
-			'product_variations',
+			$this->base.'item_id',
+			$this->base.'price',
+			$this->base.'product_variations',
 			'max_order',
 			'min_order',
 			'step'
 		]);
-		$priceRange = jvbCurrency($values['price']);
-		$variations = $values['product_variations'];
+		$priceRange = jvbCurrency($values[$this->base.'price']);
+		$variations = $values[$this->base.'product_variations'];
 
-		if(!empty($variations) && $variations[0]['name'] === '') {
+		if(!empty($variations) && $variations[0][$this->base.'name'] === '') {
 			$variations = [];
 		}
 
 		if (!empty($variations)) {
 			$prices = [];
 			foreach ($variations as $row) {
-				if (isset($row['price']) && is_numeric($row['price'])) {
-					$prices[] = (float) $row['price'];
+				if (isset($row[$this->base.'price']) && is_numeric($row[$this->base.'price'])) {
+					$prices[] = (float) $row[$this->base.'price'];
 				}
 			}
 			if (!empty($prices)) {
@@ -184,16 +193,16 @@
 			}
 		}
 
-		$max = $values['max_order'];
-		$min = $values['min_order'];
-		$step = $values['step'];
+		$max = $values['max_order']??'';
+		$min = $values['min_order']??'';
+		$step = $values['step']??'';
 		$max = ($max === '') ? 50 : $max;
 		$min = ($min === '') ? 0 : $min;
 		$step = ($step === '') ? 1 : $step;
 
 
 		?>
-		<div class="menu-item<?= !empty($variations) ? ' variable' : '' ?>" data-section="<?=$slug?>">
+		<div class="menu-item<?= !empty($variations) ? ' variable' : '' ?>" data-section="<?=$slug?>" data-label="<?=$values['post_title']?>" data-id="<?=$ID?>">
 			<div class="header row x-btw">
 				<h3><?= $values['post_title']?></h3>
 				<p class="price"><?= $priceRange ?></p>
@@ -201,52 +210,63 @@
 			<div class="description">
 				<?= Render::renderFrom($meta, 'post_excerpt')?>
 			</div>
-			<div class="info row right">
+			<div class="info col right">
 				<?php
 				if (empty($variations)) {
-					Form::renderFrom($meta,
+					echo Form::render(
 						$ID.'|cart_quantity',
 						[
-							'type'	=> 'number',
+							'type'	=> 'quantity',
 							'label'	=> 'Add to Order',
 							'min'	=> $min,
 							'max'	=> $max,
 							'step'	=> $step,
 							'data'	=> [
-								'price' => $values['price'],
+								'price' => $values[$this->base.'price'],
 								'id'	=> $ID,
-								'square-catalog-id'	=> $values['_square_catalog_id'],
+								'catalog-id'	=> $values[$this->base.'item_id'],
 								'name'	=> $values['post_title'],
 								'step'	=> $step,
 								'add-to-cart' => ''
 							],
 							'add'	=> 'Add 1 '.$values['post_title'].' to cart',
 							'remove'	=> 'Remove 1 '.$values['post_title'].' from cart',
-						],
-						true);
+						]);
 				} else {
 					foreach ($variations as $index =>$row) {
-						jvbDump($index, 'index');
-						jvbDump($row, 'row');
-						Form::renderFrom($meta,
+						echo sprintf(
+							'<div class="row right nowrap">
+							<div>
+								<h4>%s — %s</h4>
+								%s%s%s
+							</div>',
+							$row[$this->base.'name'],
+							jvbCurrency($row[$this->base.'price']),
+							empty($row[$this->base.'description']) ? '' : wpautop($row[$this->base.'description']),
+							empty($row[$this->base.'ingredients']) ? '' : wpautop('<b>Ingredients:</b>'.$row[$this->base.'ingredients']),
+							empty($row[$this->base.'preparation_time_duration']) ? '' : wpautop('Ready in <b>'.$row[$this->base.'preparation_time_duration'].'</b>'),
+						);
+						echo Form::render(
 							'quantity-'.$index,
+							'',
 							[
-								'type'	=> 'number',
-								'label'	=> $row['label'],
+								'type'	=> 'quantity',
+								'label'	=> $row[$this->base.'name'],
 								'min'	=> $min,
 								'max'	=> $max,
 								'step'	=> $step,
 								'data'	=> [
-									'price' => $row['price'],
-									'id'	=> $ID,
-									'square-catalog-id'	=> $row['_square_catalog_id'],
-									'name'	=> $row['label'],
+									'price' => $row[$this->base.'price'],
+									'id'	=> $ID.'-'.$row[$this->base.'sku'],
+									'catalog-id'	=> $row[$this->base.'item_id'],
+									'name'	=> $row[$this->base.'name'],
 									'add-to-cart' => ''
 								],
-								'add'	=> 'Add 1 '.$row['label'].' to cart.',
-								'remove'	=> 'Remove 1 '.$row['label'].' from cart.',
-							],
-							true);
+								'add'	=> 'Add 1 '.$row[$this->base.'name'].' to cart.',
+								'remove'	=> 'Remove 1 '.$row[$this->base.'name'].' from cart.',
+							]
+						);
+						echo '</div>';
 					}
 				}
 				?>
diff --git a/inc/helpers/time.php b/inc/helpers/time.php
index e83d48b..4c3483c 100644
--- a/inc/helpers/time.php
+++ b/inc/helpers/time.php
@@ -1,5 +1,6 @@
 <?php
 
+use JVBase\base\Options;
 use JVBase\managers\Cache;
 use JVBase\meta\Meta;
 
@@ -163,7 +164,7 @@
 		return '';
 	}
 
-	$hours = $meta->get('hours');
+	$hours = $meta->get('openingHours');
 	$byAppt = $meta->get('by_appointment');
 	$walkins = $meta->get('walkins');
 
@@ -311,10 +312,11 @@
  * @param string $timezone Timezone string (default: 'America/Edmonton')
  * @return bool True if currently open, false if closed
  */
-function jvbIsCurrentlyOpen($hours_data = null, $timezone = 'America/Edmonton') {
+function jvbIsCurrentlyOpen(?array $hours_data = null, string $timezone = 'America/Edmonton'):bool
+{
 	// Get hours data if not provided
 	if ($hours_data === null) {
-		$hours_data = get_option(BASE.'hours', []);
+		$hours_data = Options::get('openingHours');
 	}
 
 	// Return false if no hours data
@@ -331,12 +333,12 @@
 	$today_hours = $hours_data[$current_day] ?? [];
 
 	// Return false if closed today
-	if (empty($today_hours) || !($today_hours['open'] ?? false)) {
+	if (empty($today_hours) || !($today_hours['isOpen'] ?? false)) {
 		return false;
 	}
 
-	$open_time = $today_hours['time_opens'] ?? '';
-	$close_time = $today_hours['time_closes'] ?? '';
+	$open_time = $today_hours['opens'] ?? '';
+	$close_time = $today_hours['closes'] ?? '';
 
 	if (!$open_time || !$close_time) {
 		return false;
@@ -359,14 +361,14 @@
 /**
  * Check if current time is between two specified times
  *
- * @param string $start_time Start time in H:i format (e.g., '10:00')
- * @param string $end_time End time in H:i format (e.g., '15:15')
+ * @param ?string $start_time Start time in H:i format (e.g., '10:00')
+ * @param ?string $end_time End time in H:i format (e.g., '15:15')
  * @param string $timezone Timezone string (default: 'America/Edmonton')
  * @return bool True if current time is within range, false otherwise
  */
-function jvbIsTimeBetween($start_time=null, $end_time=null, $timezone = 'America/Edmonton') {
+function jvbIsTimeBetween(?string $start_time = null, ?string $end_time = null, string $timezone = 'America/Edmonton'):bool {
 	if (!$start_time && !$end_time) {
-		$hours = get_option(BASE.'today_hours');
+		$hours = Options::get('today_hours');
 		$start_time = $hours['time_start'];
 		$end_time = $hours['time_end'];
 	}
@@ -394,16 +396,15 @@
  *
  * @param array $hours_data Day-based hours data
  * @param string $timezone Timezone string
- * @return string|null Next opening time description or null if never opens
+ * @return string Next opening time description or empty string if never opens
  * @throws DateInvalidTimeZoneException
  */
-function jvbGetNextOpeningTime(array $hours_data, string $timezone = 'America/Edmonton'): ?string {
+function jvbGetNextOpeningTime(array $hours_data, string $timezone = 'America/Edmonton'):string {
 	if (!jvbHasOperatingHours($hours_data)) {
-		return null;
+		return '';
 	}
 
 	$current_time = new DateTime('now', new DateTimeZone($timezone));
-	$weekdays = jvbFullWeekdays();
 
 	// Check next 7 days
 	for ($i = 0; $i < 7; $i++) {
@@ -412,11 +413,11 @@
 		$day_name = strtolower($check_date->format('l'));
 
 		$day_data = $hours_data[$day_name] ?? [];
-		if (empty($day_data) || !($day_data['open'] ?? false)) {
+		if (empty($day_data) || !($day_data['isOpen'] ?? false)) {
 			continue;
 		}
 
-		$open_time = $day_data['time_opens'] ?? '';
+		$open_time = $day_data['opens'] ?? '';
 		if (!$open_time) {
 			continue;
 		}
@@ -437,16 +438,16 @@
 
 		// Format the result
 		if ($i === 0) {
-			return 'Opens today at ' . jvbFormat12HourTime($open_time);
+			return '&emsp;Opens today at ' . jvbFormat12HourTime($open_time);
 		} elseif ($i === 1) {
-			return 'Opens tomorrow at ' . jvbFormat12HourTime($open_time);
+			return '&emsp;Opens tomorrow at ' . jvbFormat12HourTime($open_time);
 		} else {
 			$day_formatted = ucfirst($day_name);
-			return "Opens {$day_formatted} at " . jvbFormat12HourTime($open_time);
+			return "&emsp;Opens {$day_formatted} at " . jvbFormat12HourTime($open_time);
 		}
 	}
 
-	return null;
+	return '';
 }
 
 
@@ -458,7 +459,7 @@
  */
 function jvbHasOperatingHours(array $hours_data): bool {
 	foreach ($hours_data as $day_data) {
-		if (!empty($day_data) && ($day_data['open'] ?? false)) {
+		if (!empty($day_data) && ($day_data['isOpen'] ?? false)) {
 			return true;
 		}
 	}
diff --git a/inc/helpers/ui.php b/inc/helpers/ui.php
index d0f5f4e..9ea9e3b 100644
--- a/inc/helpers/ui.php
+++ b/inc/helpers/ui.php
@@ -219,22 +219,32 @@
 
 
 
-function jvbModalActions()
+function jvbModalActions():string
 {
-    return '<div class="m-actions row">
-        <button type="button" class="cancel">'.jvbIcon('x').'<span class="screen-reader-text">Cancel</span></button>
-        <button type="submit" class="save">'.jvbIcon('floppy-disk').'<span class="screen-reader-text">Save</span></button>
-    </div>';
+    return sprintf(
+	'<div class="m-actions row">
+				<button type="button" class="cancel">%s<span class="screen-reader-text">Cancel</span></button>
+				<button type="submit" class="save">%s<span class="screen-reader-text">Save</span></button>
+			</div>',
+		jvbIcon('x'),
+		jvbIcon('floppy-disk')
+	);
 }
 
-function jvbNewModal(string $class, string $title, string $form)
+function jvbNewModal(string $class, string $id, string $title, string $form):string
 {
-    return '<dialog class="'.$class.'">
+    return sprintf(
+		'<dialog class="%s" id="%s">
         <div class="wrap">
-            <h2>'.$title.'</h2>
-            '.str_replace('</form>', jvbModalActions().'</form>', $form).'
+            <h2>%s</h2>
+            %s
         </div>
-    </dialog>';
+    </dialog>',
+		$class,
+		sanitize_title($id),
+		$title,
+		str_replace('</form>', jvbModalActions().'</form>', $form)
+	);
 }
 
 /**
diff --git a/inc/integrations/Square.php b/inc/integrations/Square.php
index a6346f1..9094778 100644
--- a/inc/integrations/Square.php
+++ b/inc/integrations/Square.php
@@ -54,7 +54,7 @@
 	 */
 	protected bool $isOAuthService = true;
 
-	protected string $orderPostType = '_sq_order';
+	protected string $orderPostType = '_square_order';
 	protected array $newOrder = [];
 	protected array $oauth = [
 		'authorize' => '',
@@ -326,7 +326,7 @@
 
 	public function registerSquarePostTypes():void
 	{
-		$orders = Registrar::forPost('_sq_orders', 'Square Order', 'Square Orders');
+		$orders = Registrar::forPost($this->orderPostType, 'Square Order', 'Square Orders');
 		$orders->make([
 			'public'	=> true
 		]);
@@ -769,7 +769,7 @@
 						'name' => $variation['name'],
 						'pricing_type' => 'FIXED_PRICING',
 						'price_money' => [
-							'amount' => intval($variation['price'] * 100), // Convert to cents
+							'amount' => intval($variation['_square_price'] * 100), // Convert to cents
 							'currency' => 'USD'
 						]
 					]
@@ -784,7 +784,7 @@
 					'name' => 'Regular',
 					'pricing_type' => 'FIXED_PRICING',
 					'price_money' => [
-						'amount' => intval(($itemData['price'] ?? 0) * 100),
+						'amount' => intval(($itemData['_square_price'] ?? 0) * 100),
 						'currency' => 'USD'
 					]
 				]
@@ -842,11 +842,10 @@
 		}
 
 		add_action('wp_login', [$this, 'trackUserLogin'], 10, 2);
-		add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']);
+        add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']);
 
 		add_filter('jvbAdditionalActions', [Checkout::class, 'render']);
 
-
 		add_filter('jvb_checkout_description', function (string $desc, string $provider) {
 			if ($provider === 'square') {
 				return 'Securely checkout with your name, email, and payments processed by Square.';
@@ -864,9 +863,8 @@
 		add_filter('jvb_checkout_browse_text', function () {
 			return 'browse our menu';
 		});
-
 		// Register queue executor types
-		$this->registerQueueTypes();
+		add_action('init', [$this, 'registerQueueTypes'], 10);
 	}
 
 	/**
@@ -895,7 +893,7 @@
 			]);
 	}
 
-	protected function registerQueueTypes(): void
+	public function registerQueueTypes(): void
 	{
 		$queue    = JVB()->queue();
 		$executor = new IntegrationExecutor();
@@ -1274,7 +1272,7 @@
 		if (!empty($product_variations) && is_array($product_variations)) {
 			foreach ($product_variations as $index => $variation_data) {
 				// Skip empty variations
-				if (empty($variation_data['name']) && empty($variation_data['sku'])) {
+				if (empty($variation_data['name']) && empty($variation_data['_square_sku'])) {
 					continue;
 				}
 
@@ -1306,7 +1304,7 @@
 								break;
 
 							case 'sku':
-								$variation['item_variation_data']['sku'] = $variation_data[$wp_field];
+								$variation['item_variation_data']['_square_sku'] = $variation_data[$wp_field];
 								break;
 
 							case 'track_inventory':
@@ -1367,15 +1365,15 @@
 
 		$defaults = [
 			'name' => 'name',
-			'id' => '_square_catalog_id',
-			'sku' => 'sku',
-			'price' => 'price',
-			'track_inventory' => 'track_inventory',
-			'service_duration' => 'service_duration',
-			'available_for_booking' => 'available_for_booking',
-			'gift_card_type' => 'gift_card_type',
-			'ingredients' => 'ingredients',
-			'preparation_time_duration' => 'preparation_time_duration'
+			'id' => '_square_item_id',
+			'sku' => '_square_sku',
+			'price' => '_square_price',
+			'track_inventory' => '_square_track_inventory',
+			'service_duration' => '_square_service_duration',
+			'available_for_booking' => '_square_available_for_booking',
+			'gift_card_type' => '_square_gift_card_type',
+			'ingredients' => '_square_ingredients',
+			'preparation_time_duration' => '_square_preparation_time_duration'
 		];
 
 		$extended = apply_filters(
@@ -1444,15 +1442,15 @@
 			'description_html' => 'post_content',
 			'abbreviation' => 'abbreviation',
 			'id' => '_square_catalog_id',
-			'sku' => 'sku',
+			'sku' => '_square_sku',
 			'category_id' => 'category',
 			'image_ids' => 'post_thumbnail',
-			'price' => 'price',
+			'price' => '_square_price',
 			'tax_ids' => 'tax_ids',
 			// Availability
-			'available_online' => 'available_online',
-			'available_for_pickup' => 'available_for_pickup',
-			'available_electronically' => 'available_electronically',
+			'available_online' => '_square_available_online',
+			'available_for_pickup' => '_square_available_for_pickup',
+			'available_electronically' => '_square_available_electronically',
 			// Modifiers
 			'modifier_list_info' => 'modifiers',
 			// Item options
@@ -1977,7 +1975,7 @@
 		wp_enqueue_script('jvb-square-checkout');
 
 		wp_localize_script('jvb-square-checkout', 'squareConfig', [
-//TODO			'isOpen'         => jvbIsOpen(),
+			'isOpen'         => jvbIsOpen()?'1':'0',
 			'application_id' => $this->credentials['client_id'] ?? '',
 			'location_id'    => $this->locationId,
 			'environment'    => $this->environment,
@@ -2290,7 +2288,7 @@
 			foreach ($item['item_data']['variations'] as $index => $variation) {
 				$var_data = [
 					'name' => $variation['item_variation_data']['name'] ?? '',
-					'sku' => $variation['item_variation_data']['sku'] ?? '',
+					'sku' => $variation['item_variation_data']['_square_sku'] ?? '',
 				];
 
 				// Extract price
@@ -2734,7 +2732,7 @@
 		if ($content_type && array_key_exists($content_type, $this->contentTypes)){
 			$array = $this->contentTypes[$content_type];
 			return array_combine(
-				array_map(fn($k) => 'sq_' . $k, array_keys($array)),
+				array_map(fn($k) => '_square_' . $k, array_keys($array)),
 				$array
 			);
 		} else if ($content_type && !array_key_exists($content_type, $this->contentTypes)) {
@@ -2742,12 +2740,10 @@
 			return [];
 		}
 		$array = $this->setBaseFields();
-		$return = array_combine(
-			array_map(fn($k) => 'sq_' . $k, array_keys($array)),
-			$array
-		);
-
-		return $return;
+        return array_combine(
+            array_map(fn($k) => '_square_' . $k, array_keys($array)),
+            $array
+        );
 	}
 	protected function setBaseFields():array
 	{
@@ -2798,6 +2794,11 @@
 				'label'       => 'Variation Name',
 				'description' => 'e.g., "Small", "Large", "Red", etc.'
 			],
+            'sku' => [
+                'type'        => 'text',
+                'label'       => 'SKU',
+                'description' => 'Stock keeping unit'
+            ],
 			'price' => [
 				'type'        => 'number',
 				'label'       => 'Price',
@@ -2805,18 +2806,17 @@
 				'max'         => 99999,
 				'description' => 'Price for this variation'
 			],
-
 			'track_inventory'	=> [
 				'type'	=> 'true_false',
 				'label'	=> 'Track Inventory',
 			],
-			'_square_item_id' => [
+			'item_id' => [
 				'type'        => 'text',
 				'label'       => 'Square Variation ID',
 				'description' => 'Square catalog ID for this variation',
 				'hidden'      => true
 			],
-			'_square_last_sync' => [
+			'last_sync' => [
 				'type'   => 'datetime',
 				'label'  => 'Last Sync',
 				'hidden' => true
@@ -2857,7 +2857,10 @@
 				];
 				break;
 		}
-		return $fields;
+		return array_combine(
+            array_map(fn($k) => '_square_' . $k, array_keys($fields)),
+            $fields
+        );
 	}
 	protected function setFoodAndBevFields():array
 	{
@@ -3341,7 +3344,7 @@
 				// Ad-hoc line item (not recommended - no tax/inventory automation)
 				$line_item['name'] = $item['name'];
 				$line_item['base_price_money'] = [
-					'amount' => (int)$item['price'],
+					'amount' => (int)$item['_square_price'],
 					'currency' => $this->getCurrency()
 				];
 			}
@@ -3433,7 +3436,7 @@
 
 		// Create order post
 		$order_post_id = wp_insert_post([
-			'post_type' => BASE . '_sq_orders',
+			'post_type' => BASE . '_square_orders',
 			'post_title' => 'Order #' . $order_data['square_order_id'],
 			'post_status' => 'publish',
 			'post_author' => $user_id // Associate with user if logged in
diff --git a/inc/managers/DashboardManager.php b/inc/managers/DashboardManager.php
index 878f2a7..73be522 100644
--- a/inc/managers/DashboardManager.php
+++ b/inc/managers/DashboardManager.php
@@ -1203,6 +1203,7 @@
 
         echo jvbNewModal(
             'edit-modal '.$type,
+			'edit-modal',
             'Edit '.ucfirst($type),
             jvbRenderForm('admin', $fields)
         );
diff --git a/inc/managers/LoginManager.php b/inc/managers/LoginManager.php
index 5e0e37b..8523b6b 100644
--- a/inc/managers/LoginManager.php
+++ b/inc/managers/LoginManager.php
@@ -630,7 +630,7 @@
 		$redirect = !empty($redirect) ? $redirect : esc_attr($_GET['redirect_to'] ?? '');
 
 		return sprintf(
-			'<form name="%sform" method="post" data-action="jvb_%s">
+			'<form name="%sform" method="post" data-action="jvb_%s" data-handled>
 				%s%s%s
 				<input type="hidden" name="action" value="jvb_%s">
 				<input type="hidden" name="redirect_to" value="%s">
diff --git a/inc/managers/ScriptLoader.php b/inc/managers/ScriptLoader.php
index 96e3ca8..c5527ff 100644
--- a/inc/managers/ScriptLoader.php
+++ b/inc/managers/ScriptLoader.php
@@ -5,7 +5,7 @@
 add_action('init', 'jvbRegisterScripts', 5);
 
 function jvbRegisterScripts() {
-	$version = '1.1.66';
+	$version = '1.1.67';
 	$strategy = [
 		'strategy'	=> 'defer',
 		'in_footer'	=> true
diff --git a/inc/managers/queue/Processor.php b/inc/managers/queue/Processor.php
index f54fadd..e735921 100644
--- a/inc/managers/queue/Processor.php
+++ b/inc/managers/queue/Processor.php
@@ -279,7 +279,7 @@
 		};
 	}
 
-	private function hasAdequateResources(): bool
+	public function hasAdequateResources(): bool
 	{
 		// Stricter thresholds for starting (50% memory, 60s minimum time)
 		$memoryLimit = $this->getMemoryLimitBytes();
diff --git a/inc/managers/queue/Queue.php b/inc/managers/queue/Queue.php
index 89a8ceb..8dd89f7 100644
--- a/inc/managers/queue/Queue.php
+++ b/inc/managers/queue/Queue.php
@@ -404,7 +404,7 @@
 
 	private function runQueueOnShutdown(): void
 	{
-		if (!has_action('shutdown', [$this, 'processQueueOnShutdown'])) {
+		if (!has_action('shutdown', [$this, 'processQueueOnShutdown']) && $this->processor->hasAdequateResources()) {
 			add_action('shutdown', [$this, 'processQueueOnShutdown'], 100);
 		}
 	}
diff --git a/inc/meta/Form.php b/inc/meta/Form.php
index dec811f..9c2f926 100644
--- a/inc/meta/Form.php
+++ b/inc/meta/Form.php
@@ -26,7 +26,10 @@
 			$value = '';
 		}
 		if (!empty($config['hidden'])) {
-			return '';
+            if ($config['type'] !== 'upload') {
+                return static::renderHidden($name, $value, $config);
+            }
+            return '';
 		}
 
 		$type = $config['type'] ?? 'text';
@@ -77,9 +80,9 @@
 				$output .= '<p>' . esc_html($d) . '</p>';
 			}
 		}
-		error_log('[Form]::renderFormFrom getting fields: '.print_r($fields, true));
+
 		$allFields = $meta->getAll($fields);
-		error_log('[Form]::renderFormFrom field values: '.print_r($allFields, true));
+
 		foreach ($allFields as $name => $value) {
 			$config = $meta->config($name);
 			$output .= static::render($name, $value, $config);
@@ -320,6 +323,15 @@
 		return static::fieldWrap($name, $input, $config);
 	}
 
+    protected static function renderHidden(string $name, mixed $value, array $config):string
+    {
+        return sprintf(
+            '<input type="hidden" name="%s" value="%s">',
+            esc_attr($name),
+            esc_attr($value)
+        );
+    }
+
 	protected static function renderTextarea(string $name, mixed $value, array $config): string
 	{
 		$rows = $config['rows'] ?? 5;
@@ -360,13 +372,19 @@
 		if (!array_key_exists('class', $config)) {
 			$config['class']=[];
 		}
-		$config['class'][] ='quantity-input';
+		$config['class'][] = 'quantity-input';
+        $config['class'][] = 'row';
+        $config['class'][] = 'nowrap';
+
+        if (array_key_exists('isCart', $config) && $config['isCart'] === true) {
+
+        }
 
 		$attrs = static::inputAttrs($name, $config);
 
 		$value = ($value !== '') ? ' value="'.esc_attr($value).'"' : '';
 		$input = sprintf(
-			'<div class="quantity">
+			'<div class="row nowrap">
 				<button type="button" class="decrease" title="%s" aria-label="Decrease %s">%s</button>
 				<input type="number"%s%s />
 				<button type="button" class="increase" title="%s" aria-label="Increase %s">%s</button>
@@ -1636,16 +1654,21 @@
 		);
 
 		$input .= '<div class="repeater-items">';
-		$rowTitle = array_key_exists('new_row', $config) ? $config['new_row'] : 'New Item';
+
+        $hasTitle = array_key_exists('row_label', $config);
 		if(!empty($rows)) {
 			foreach ($rows as $index=>$row) {
-				$input .= static::renderRepeaterRow($config['fields'], $row, $index, $name, $rowTitle);
+                $titleReference = $hasTitle ? $config['row_label'] : null;
+                $rowTitle = !is_null($titleReference) ? $row[$titleReference] : 'New Item';
+
+				$input .= static::renderRepeaterRow($config['fields'], $row, $index, $name, $rowTitle, $titleReference);
 			}
 		}
 		$input .= '</div>';
 
+        $titleReference = $hasTitle ? $config['row_label'] : null;
 		$input .= '<template class="'.uniqid('repeaterRow').'">';
-			$input .= static::renderRepeaterRow($config['fields'], [], '','',$rowTitle);
+			$input .= static::renderRepeaterRow($config['fields'], [], '','','New Item', $titleReference);
 		$input .= '</template>';
 
 		$input .= sprintf(
@@ -1657,27 +1680,25 @@
 		unset($config['label']);
 		return static::fieldWrap($name, $input, $config);
 	}
-		protected static function renderRepeaterRow(array $fields, array $values, int|string $index, string $name, string $rowTitle='New Item'):string
+		protected static function renderRepeaterRow(array $fields, array $values, int|string $index, string $name, string $rowTitle='New Item', ?string $rowTitleField = null):string
 		{
 			$displayNumber = (is_string($index)) ? $index : ($index +1);
 
+
 			$output = sprintf(
-				'<div class="repeater-row" data-index="%s">
-						<button type="button" class="remove-row" title="Remove">
-							%s
-						</button>
+				'<div class="repeater-row row nowrap" data-index="%s">
+                        <span class="drag-handle row">%s</span>
 						<details%s>
 							<summary class="row x-btw repeater-row-header">
-								<span class="drag-handle">%s</span>
 								<span class="row-number">#%s</span>
-								<span class="row-title">%s</span>
+								<span class="row-title"%s>%s</span>
 							</summary>
 							<div class="repeater-row-content">',
 				esc_attr($index),
-				jvbIcon('trash'),
+                jvbIcon('dots-six-vertical'),
 				is_string($index) ? ' open' : '',
-				jvbIcon('dots-six-vertical'),
 				$displayNumber,
+                is_null($rowTitleField) ? '' : 'data-label="'.$rowTitleField.'"',
 				$rowTitle
 			);
 			foreach ($fields as $fieldName => $fieldConfig) {
@@ -1686,7 +1707,11 @@
 				$output .= static::render($fullName, $fieldValue, $fieldConfig);
 			}
 
-			return $output.'</div></details></div>';
+			return $output.sprintf('</div></details><button type="button" class="remove-row" title="Remove">
+							%s
+						</button></div>',
+                    jvbIcon('trash'),
+                );
 		}
 
 	/**
diff --git a/inc/registrar/Posts.php b/inc/registrar/Posts.php
index 7a5bf4f..3f0f3fe 100644
--- a/inc/registrar/Posts.php
+++ b/inc/registrar/Posts.php
@@ -10,6 +10,8 @@
 	public string $postType;
 	public string $singular;
 	public string $plural;
+	public bool $baseless = false;
+	public bool $modify = false;
 	public array $labels;
 	/**
 	 * Whether a post type is intended for use publicly either via the admin interface or by front-end users.
@@ -218,10 +220,11 @@
 		$this->unbased = jvbNoBase($postType);
 		$this->postType = jvbCheckBase($postType);
 		$this->labels = $this->buildLabels($singular, $plural);
+		$this->singular = $singular;
+		$this->plural = $plural;
 	}
 
-	public function register():void
-	{
+	protected function buildArgs():array {
 		$args = array_filter(get_object_vars($this));
 //		error_log('Got Object Vars: '.print_r($args, true));
 //		error_log('Filtered: '.print_r(array_filter($args), true));
@@ -340,7 +343,18 @@
 		}
 
 
-		$registered = register_post_type($this->postType, $args);
+		return $args;
+	}
+
+	public function register():void
+	{
+		$args = $this->buildArgs();
+
+		$registered = true;
+	 	if (!$this->modify) {
+			$registered = register_post_type($this->postType, $args);
+		}
+
 		if (is_wp_error($registered)) {
 			JVB()->error()->log('JVBase\registrar\Posts', 'Could not register post type', $registered->get_error_messages());
 		}
@@ -455,4 +469,21 @@
 		return $url;
 	}
 
+	public function modifyWooCommerce() {
+		if ($this->modify) {
+			$args = $this->buildArgs();
+			unset($args['labels']);;
+			$registered = get_post_type_object($this->postType);
+
+			foreach ($args as $key => $value) {
+				$registered->$key = $value;
+			}
+
+			$labels = $registered->labels;
+			foreach ($this->labels as $key => $value) {
+				$labels->$key = $value;
+			}
+		}
+	}
+
 }
diff --git a/inc/registrar/Registrar.php b/inc/registrar/Registrar.php
index 1ecadf3..87f24d8 100644
--- a/inc/registrar/Registrar.php
+++ b/inc/registrar/Registrar.php
@@ -54,6 +54,8 @@
 	public string $prefix_with = 'by';
 
 	public bool $system = false;
+	public bool $baseless = false;
+	public bool $modify = false;
 
 	protected static array $allFlags = [
 		//Shared Flags
@@ -296,13 +298,25 @@
 			'post_parent__not_in'	=> [0], // Only get posts with a parent
 		]);
 	}
-
+	public function ensureRegistrar():void
+	{
+		$this->initRegistrar();
+	}
 	protected function initRegistrar():void {
 		$this->registrar = match ($this->type) {
 			'post' => new Posts($this->slug, $this->singular, $this->plural),
 			'term' => new Terms($this->slug, $this->singular, $this->plural),
 			default => false,
 		};
+
+
+		if ($this->baseless) {
+			$this->registrar->baseless = true;
+			$this->registrar->postType = jvbNoBase($this->slug);
+		}
+		if ($this->modify) {
+			$this->registrar->modify = true;
+		}
 	}
 
 	protected function initClasses():void {
@@ -356,6 +370,8 @@
 		return self::$instances[$slug];
 	}
 
+
+
 	/**
 	 * Adds the properties for register_post_type or register_taxonomy
 	 * @param array $args
@@ -395,6 +411,17 @@
 		return $this->args;
 	}
 
+	public function baseless():self
+	{
+		$this->baseless = true;
+		return $this;
+	}
+	public function modify():self
+	{
+		$this->modify = true;
+		return $this;
+	}
+
 	public function setFields():void
 	{
 		$this->fields = new Fields($this->type, $this);
@@ -1021,6 +1048,7 @@
 
             unset($processing[$termId]);
 		}
+
 	public function renderContent(string $content, array $block):string
 	{
 		if (!is_page($this->page)) {
diff --git a/inc/registrar/Terms.php b/inc/registrar/Terms.php
index 8fd2bec..de4f107 100644
--- a/inc/registrar/Terms.php
+++ b/inc/registrar/Terms.php
@@ -9,6 +9,7 @@
 	public string $taxonomy;
 	public string $singular;
 	public string $plural;
+	public bool $baseless = false;
 	public array $labels;
 	/**
 	 * A string or array of strings of object types this taxonomy is for
diff --git a/inc/ui/CRUDSkeleton.php b/inc/ui/CRUDSkeleton.php
index eb3ef35..e240e13 100644
--- a/inc/ui/CRUDSkeleton.php
+++ b/inc/ui/CRUDSkeleton.php
@@ -869,6 +869,7 @@
 				$form = ob_get_clean();
 				echo jvbNewModal(
 					'date-range',
+				'date-range',
 				'Filter Results by Date:',
 					$form
 				);
@@ -1564,6 +1565,7 @@
 	{
 		echo jvbNewModal(
 			'create',
+			'create',
 			'Creating <span class="count"></span> New '.$this->singular,
 			str_replace('edit-form"', 'create-form" data-noautosave', $this->editForm())
 		);
@@ -1690,6 +1692,7 @@
 	{
 		echo jvbNewModal(
 			'edit',
+			'edit',
 			'Edit your '.$this->singular,
 			$this->editForm()
 		);
@@ -1741,6 +1744,7 @@
 		$form = ob_get_clean();
 		echo jvbNewModal(
 			'bulkEdit',
+			'bulkEdit',
 			'Bulk Edit <span class="selected"></span> '.$this->plural,
 			$form
 		);
diff --git a/inc/ui/Checkout.php b/inc/ui/Checkout.php
index 29be465..f1a6d24 100644
--- a/inc/ui/Checkout.php
+++ b/inc/ui/Checkout.php
@@ -1,6 +1,8 @@
 <?php
 namespace JVBase\ui;
 
+use JVBase\base\Site;
+use JVBase\integrations\Integrations;
 use JVBase\meta\Form;
 
 if (!defined('ABSPATH')) {
@@ -26,6 +28,15 @@
  */
 class Checkout
 {
+    public static function getProvider():Integrations|false
+    {
+        foreach (Site::getIntegrations() as $integration => $active) {
+            if (in_array($integration, ['square', 'helcim']) && $active) {
+                return JVB()->connect($integration);
+            }
+        }
+        return false;
+    }
 	/**
 	 * Render the checkout aside and append to actions.
 	 *
@@ -37,15 +48,18 @@
 			return $actions;
 		}
 
-		$provider = jvbGetPaymentProvider();
+		$provider = self::getProvider();
 		if (!$provider || !$provider->isSetUp()) {
 			return $actions;
 		}
 
 		$providerName = strtolower($provider->getServiceName());
 
-		$form = '<aside id="cart" class="right main">
-			<form id="checkout" data-form-id="checkout" data-save="checkout" data-provider="' . esc_attr($providerName) . '">';
+		$form = sprintf(
+            '<aside id="cart" class="right main"><div class="wrap">
+			    <form id="checkout" data-form-id="checkout" data-save="checkout" data-provider="%s">',
+            esc_attr($providerName)
+        );
 
 		$tabs = [
 			'cartItems' => [
@@ -79,6 +93,7 @@
 				<p class="total">GRAND TOTAL: <span></span></p>
 			</div>
 		</form>
+		</div>
 		</aside>';
 
 		$form .= self::templates($providerName);
@@ -150,9 +165,23 @@
 					<th scope="col">Item</th>
 					<th scope="col">Price</th>
 					<th scope="col">Total</th>
+                    <th scope="col"></th>
 				</tr>
 				</thead>
-				<tbody></tbody>
+                <tfoot>
+                    <tr>
+                        <th scope="col"></th>
+                        <th scope="col">TAX</th>
+                        <td class="tax"></td>
+                        <th scope="col"></th>
+                    </tr>
+                    <tr>
+                        <th scope="col"></th>
+                        <th scope="col">Net:</th>
+                        <td class="total"></td>
+                        <th scope="col"></th>
+                    </tr>
+                </tfoot>
 			</table>
 		</div>
 
@@ -235,7 +264,7 @@
 		$browseText = apply_filters('jvb_checkout_browse_text', 'browse our products');
 
 		return '<template class="restoredCart">
-			<div class="restored">
+			<div class="fstatus restored">
 				<h3>Looks like we left things hanging</h3>
 				<p>We\'ve restored your cart from your last session below.</p>
 				<p>If you\'d rather start over, click the button below.</p>
@@ -245,7 +274,12 @@
 				</div>
 			</div>
 		</template>
-		<template class="cartItem">
+		<template class="mainCartItem">
+            <tbody>
+                <tr class="header"><th></th></tr>
+            </tbody>
+        </template>
+		<template class="childCartItem">
 			<tr class="item">
 				<td class="item">
 					<label for="quantity"></label>
@@ -275,11 +309,11 @@
 	 */
 	private static function toggleButton(): string
 	{
-		return '<button type="button" class="toggle-cart row" title="Your Cart"
+		return '<button type="button" class="toggle-cart row empty" title="Your Cart"
 				data-action="toggle-cart" aria-label="Open Cart"
 				aria-controls="checkout" aria-expanded="false">'
 			. jvbIcon('shopping-cart')
-			. '<span class="abs"></span><span class="abs count"></span>
+			. '<span class="abs close"></span><span class="abs count"></span>
 		</button>';
 	}
 }
diff --git a/package-lock.json b/package-lock.json
index 03e6e72..eb28fa6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
     "name": "jvb",
-    "version": "0.1.0",
+    "version": "0.2.0",
     "lockfileVersion": 3,
     "requires": true,
     "packages": {
         "": {
             "name": "jvb",
-            "version": "0.1.0",
+            "version": "0.2.0",
             "license": "GPL-2.0-or-later",
             "devDependencies": {
                 "@wordpress/scripts": "^32.1.0"
@@ -8228,9 +8228,9 @@
             "license": "MIT"
         },
         "node_modules/baseline-browser-mapping": {
-            "version": "2.10.29",
-            "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz",
-            "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==",
+            "version": "2.10.38",
+            "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz",
+            "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==",
             "dev": true,
             "license": "Apache-2.0",
             "bin": {
@@ -8639,9 +8639,9 @@
             }
         },
         "node_modules/caniuse-lite": {
-            "version": "1.0.30001792",
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz",
-            "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==",
+            "version": "1.0.30001799",
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
+            "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
             "dev": true,
             "funding": [
                 {
diff --git a/src/menu/style.scss b/src/menu/style.scss
index e69de29..401ab0a 100644
--- a/src/menu/style.scss
+++ b/src/menu/style.scss
@@ -0,0 +1,21 @@
+.info h4 {
+	margin: 0;
+	font-weight: var(--fw-h);
+	font-size: var(--txt-medium);
+}
+
+.field.quantity > label {
+	position: absolute;
+	left: var(--offScreen);
+}
+
+.menu-item {
+	.header {
+		flex-wrap:nowrap;
+
+		h3, p {
+			margin: 0;
+			width: max-content;
+		}
+	}
+}

--
Gitblit v1.10.0