From 46d681c6b825d21b3f698d793c4e630c687d90ad Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 21 May 2026 21:41:53 +0000
Subject: [PATCH] =Major CustomBlocks.php overhaul, expanding block support and customization from the editor. theme.json should now be updated on new themes to set brand colours, etc. Also note: major change to .col vs .row alignment: simplifying it to .top .bottom vs the confusion of the differences for .col/.row .start and .a-start

---
 src/faq/style.scss                               |   72 
 src/drawer-menu/index.php                        |    0 
 src/feed/index.js                                |   39 
 src/fields/view.js                               |    1 
 src/summary/render.php                           |  320 +
 inc/blocks/SummaryBlock.php                      |    2 
 jvb.php                                          |    7 
 inc/admin/ContentTaxonomy.php                    |    2 
 inc/meta/Form.php                                |   45 
 src/feed/render.php                              |    4 
 inc/managers/DashboardManager.php                |    8 
 src/feed/edit.js                                 |  256 
 src/forms/style.scss                             | 5572 +++++++++++++++++++++
 src/feed/index.php                               |    2 
 src/menu/index.php                               |    0 
 src/summary/editor.scss                          |   20 
 src/video/index.js                               |   21 
 inc/ui/Checkout.php                              |    4 
 inc/ui/Tabs.php                                  |    2 
 inc/helpers/crud.php                             |    2 
 build/gmbreviews/render.php                      |    4 
 src/faq/render.php                               |    0 
 build/summary/render.php                         |    4 
 inc/managers/LoginManager.php                    |    8 
 src/video/render.php                             |    1 
 src/drawer-menu/index.js                         |   10 
 src/faq/index.js                                 |   11 
 inc/ui/CRUDSkeleton.php                          |   28 
 inc/integrations/Integrations.php                |    4 
 src/fields/edit.js                               |   29 
 src/fields/index.php                             |    0 
 src/forms/render.php                             |   55 
 src/timeline/edit.js                             |   38 
 src/feed/editor.scss                             |  128 
 src/feed/style.scss                              |  956 +++
 src/faq/index.php                                |    0 
 src/drawer-menu/save.js                          |    3 
 src/forms/view.js                                |  112 
 src/timeline/view.js                             |    0 
 src/glossary/style.scss                          |  109 
 src/feed/view.js                                 |  747 ++
 src/timeline/editor.scss                         |    0 
 src/video/block.json                             |   79 
 src/index.php                                    |    3 
 src/glossary/edit.js                             |   38 
 src/gmbreviews/block.json                        |   68 
 src/gmbreviews/edit.js                           |   69 
 src/gmbreviews/style.scss                        |  122 
 src/glossary/index.php                           |    0 
 src/glossary/editor.scss                         |    0 
 src/glossary/index.js                            |   33 
 src/glossary/view.js                             |  184 
 src/menu/style.scss                              |    0 
 src/glossary/block.json                          |   24 
 src/video/index.php                              |    0 
 src/gmbreviews/index.php                         |    0 
 src/forms/edit.js                                |  319 +
 src/forms/editor.scss                            |    0 
 src/timeline/index.js                            |   33 
 src/summary/style.scss                           |   20 
 src/fields/block.json                            |   25 
 src/gmbreviews/index.js                          |   11 
 src/menu/index.js                                |   33 
 inc/utility/Image.php                            |    6 
 src/menu/editor.scss                             |    0 
 src/gmbreviews/view.js                           |    0 
 webpack.jvb.js                                   |    4 
 src/forms/save.js                                |   23 
 src/drawer-menu/render.php                       |   38 
 build/feed/view.js                               |    2 
 src/fields/editor.scss                           |   20 
 src/menu/block.json                              |   24 
 inc/users/UserSettings.php                       |    2 
 src/feed/block.json                              |   57 
 src/fields/save.js                               |    3 
 assets/js/concise/navigation.js                  |    2 
 assets/js/min/navigation.min.js                  |    2 
 src/feed/save.js                                 |    3 
 src/faq/block.json                               |   34 
 inc/helpers/ui.php                               |   26 
 inc/forms/TaxonomySelector.php                   |   10 
 assets/js/concise/UtilityFunctions.js            |   63 
 inc/blocks/CustomBlocks.php                      | 2639 +++++++--
 assets/js/min/utility.min.js                     |    2 
 src/faq/edit.js                                  |  145 
 src/forms/index.js                               |   40 
 src/timeline/style.scss                          |  135 
 src/timeline/render.php                          |    8 
 src/faq/view.js                                  |   84 
 src/summary/index.php                            |    0 
 inc/managers/queue/executors/ContentExecutor.php |    4 
 templates/dashboard/sections/notifications.php   |    2 
 inc/managers/IconsManager.php                    |    4 
 src/summary/index.js                             |   39 
 src/drawer-menu/style.scss                       |   88 
 src/timeline/index.php                           |    0 
 src/summary/block.json                           |   32 
 src/gmbreviews/render.php                        |  207 
 inc/managers/DirectoryManager.php                |   10 
 inc/blocks/FeedBlock.php                         |   12 
 inc/managers/SEO/BreadcrumbManager.php           |   15 
 src/forms/index.php                              |    0 
 src/video/editor.scss                            |  141 
 inc/helpers/renderFields.php                     |    4 
 src/summary/save.js                              |    3 
 src/fields/index.js                              |   39 
 src/video/style.scss                             |  178 
 src/summary/edit.js                              |   29 
 src/fields/style.scss                            |   20 
 src/video/edit.js                                |  276 +
 src/video/view.js                                |   47 
 inc/helpers/media.php                            |    2 
 src/video/save.js                                |   23 
 inc/managers/ReferralManager.php                 |   16 
 src/menu/edit.js                                 |   38 
 inc/blocks/MenuBlock.php                         |    4 
 src/menu/render.php                              |    8 
 assets/css/nav.min.css                           |    2 
 src/drawer-menu/block.json                       |   27 
 src/gmbreviews/editor.scss                       |    0 
 src/menu/view.js                                 |   43 
 src/fields/render.php                            |  320 +
 src/drawer-menu/view.js                          |    1 
 src/forms/block.json                             |   47 
 build/feed/view.asset.php                        |    2 
 src/summary/view.js                              |    1 
 src/faq/editor.scss                              |   99 
 src/drawer-menu/edit.js                          |   33 
 templates/dashboard/sections/news.php            |    6 
 src/timeline/block.json                          |   23 
 src/feed/viewOld.js                              |  770 ++
 src/drawer-menu/editor.scss                      |    0 
 build/fields/render.php                          |    4 
 inc/templates.php                                |    6 
 inc/blocks/FormBlock.php                         |    4 
 src/glossary/render.php                          |    8 
 136 files changed, 14,874 insertions(+), 822 deletions(-)

diff --git a/assets/css/nav.min.css b/assets/css/nav.min.css
index 65848db..8e31743 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{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 li{width:100%;--justify:center;max-inline-size:none;padding:0;list-style:none}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 var(--base);color:var(--contrast)}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:var(--action-0);color: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-rgb),var(--op-3));border:2px solid rgba(var(--base-rgb),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-rgb),var(--op-6));border:1px solid var(--base-50)}.submenu a{height:var(--chipchip)}.open>ul.submenu{transform:scaleY(1);max-height:1000%;box-shadow:rgba(var(--base-rgb),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 var(--action-0);outline-offset:1px}nav.always{--dir:column;--justify:flex-end;position:fixed;bottom:0;right:0;width:var(--btn);z-index:var(--z-10)}nav.always.open{width:100vw;height:100vh;padding-bottom:var(--btn_);background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}nav.always>ul{z-index:1;--dir:column;--align:center;--justify:flex-start;--gap:0;height:100%;max-height:100%;position:relative;right:-300vw;width:100vw;padding:var(--btn) 0 0;overflow:hidden auto;transition:right var(--trans-base)}nav.always.open>ul{right:0}nav.always li{--wrap:wrap;--dir:row;height:max-content;--justify:flex-start;background-color:rgba(var(--base-rgb),var(--op-6))}nav.always a{padding:1rem;--justify:center;max-width:calc(100% - var(--btn))}nav.always .has-submenu>a{z-index:var(--z-3)}nav.always .has-submenu>button{width:var(--btn)}nav.always .submenu{position:relative;padding-right:4rem;top:0;border:1px solid var(--action-0);background-color:rgba(var(--contrast-rgb),var(--op-1))}nav.always .submenu li{background-color:rgba(var(--base-rgb),var(--op-3))}nav.always>.toggle{position:fixed;bottom:0;right:0;width:var(--btn);height:var(--btn);background-color:var(--base);color:var(--contrast);box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);transition:width var(--trans-base)}nav.always>.toggle:hover{background-color:var(--action-0);color:var(--action-contrast)}nav.always.open>.toggle{width:100%;background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:1000000}nav.always.open>.toggle:hover{background-color:var(--action-0);color:var(--action-contrast)}nav.always button .icon-x,nav.always.open button .icon-list{display:none}nav.always button .icon-list,nav.always.open button .icon-x{display:block;--w:32px}nav#breadcrumbs{height:max-content;--wrap:wrap;--gap:0;width:max-content;max-width:var(--full);position:absolute;background-color:rgba(var(--base-rgb),var(--op-4));font-size:var(--txt-x-small);padding:.125em;z-index:var(--z-5)}nav#breadcrumbs ol{height:max-content;--wrap:wrap}nav#breadcrumbs li{width:max-content;height:var(--chip);--wrap:nowrap}nav#breadcrumbs li::after{content:'/';color: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:var(--contrast);text-transform:none}nav#breadcrumbs a:focus{background-color:transparent;color:var(--action-0)}nav.fixed{position:fixed;left:0;box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);z-index:var(--z-9)}nav.fixed.bottom{bottom:0;width:calc(100% - var(--btn))}nav.fixed ul{--justify:space-between;width:100%;background-color:var(--base);padding:0 .25rem}nav.always.fixed>ul{padding-top:var(--btn)}nav.fixed li{flex:1}nav.fixed a{--gap:1rem;--w:var(--chip_);color:var(--contrast);font-size:var(--txt-x-small)}@media (min-width:768px){nav.fixed a{font-size:var(--txt-medium)}}nav.on-this-page{--justify:space-between;position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-5);background-color:rgba(var(--base-rgb),var(--op-45));max-width:none}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-rgb),var(--op-6));color:var(--action-contrast)}nav.on-this-page #back-to-top span{display:none}nav.on-this-page .active a{background-color:var(--action-0);color:var(--action-contrast)}nav.letters,nav.letters a,nav.letters li,nav.letters ul{height:var(--chip)}nav.letters li{max-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{max-width:none}nav.letters a{padding:.25rem .66rem}}nav.index{--justify:space-between;--padding:0;background-color:rgba(var(--base-rgb),var(--op-6))}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:calc(100% - var(--btn_));flex-shrink:1;max-width:none}nav.index li:first-of-type{flex-shrink:1;transform:scaleX(1);order:9999;width:var(--btn);height:var(--btn);max-width:none}@media (max-width:767px){.index li.adj{transform:scaleX(0);max-width:0}}nav.index a{border-bottom:4px solid transparent}nav.index .active a{border-color:var(--action-0);color:var(--contrast)}nav.index.open{--dir:column-reverse;height:var(--maxHeight);width:100%;--align:flex-end;background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}nav.index.open li{width:100%;height:var(--btn);max-width:100%!important;transform:scaleX(1);overflow:visible}nav.index.open a{--justify:flex-end;padding:0 2rem 0 0;background-color:transparent}nav.condensed,nav.condensed a,nav.condensed li,nav.condensed ul{height:max-content;width:max-content;--wrap:wrap;min-height:var(--chip)}nav.condensed{--gap:0 .25rem;width:100%}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:var(--contrast);border-color:var(--action-0)}ul.socials{--dir:row;height:max-content;--gap:.5rem;--justify:stretch;--wrap:nowrap;overflow:auto hidden;touch-action:pan-x}.always ul.socials{width:100%}ul.socials a{padding:.5rem;max-width:none}ul.socials .icon{margin:0}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:var(--action-0);color:var(--action-contrast);border-color: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:var(--base);--gap:0}.tab-content .tab-content nav.tabs{background-color:var(--base-100)}.tab-content .tab-content .tab-content nav.tabs{background-color:var(--base-200)}.tab-content nav.tabs button.active h2{color: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:center;padding:0 .5rem;background-color:var(--base);box-shadow:rgba(var(--base-rgb),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}
\ 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 var(--base);color:var(--contrast)}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:var(--action-0);color: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-rgb),var(--op-3));border:2px solid rgba(var(--base-rgb),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-rgb),var(--op-6));border:1px solid var(--base-50)}.submenu a{height:var(--chipchip)}.open>ul.submenu{transform:scaleY(1);max-height:1000%;box-shadow:rgba(var(--base-rgb),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 var(--action-0);outline-offset:1px}nav.always{overflow:visible;transition:width var(--trans-base);width:max-content}nav.always>ul{--dir:column;--align:center;--justify:flex-end;--gap:0;height:100vh;max-height:none;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%}nav.always.fixed{width:var(--btn);height:var(--btn);bottom:0;right:0;overflow:hidden}nav.always.fixed .toggle.main{background-color:var(--base)}nav.always.fixed .toggle.main:focus,nav.always.fixed .toggle.main:hover{background-color:var(--action-0);color: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-rgb),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-rgb),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-rgb),var(--op-4));font-size:var(--txt-x-small);padding:.125em;z-index:var(--z-5)}nav#breadcrumbs ol{height:max-content;--wrap:wrap}nav#breadcrumbs li{width:max-content;height:var(--chip);--wrap:nowrap}nav#breadcrumbs li::after{content:'/';color: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:var(--contrast);text-transform:none}nav#breadcrumbs a:focus{background-color:transparent;color:var(--action-0)}nav.fixed{position:fixed;box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);z-index:var(--z-9)}nav.fixed.bottom{left:0;bottom:0;width:calc(100% - var(--btn))}nav.fixed:not(.always) ul{--justify:space-between;width:100%;background-color:var(--base);padding:0 .25rem}nav.fixed:not(.always) li{flex:1}nav.fixed a{--align:center;--gap:1rem;--w:var(--chip_);color:var(--contrast);font-size:var(--txt-x-small)}@media (min-width:768px){nav.fixed a{font-size:var(--txt-medium)}}nav.on-this-page{--justify:space-between;position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-5);background-color:rgba(var(--base-rgb),var(--op-45));max-width:none}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-rgb),var(--op-6));color:var(--action-contrast)}nav.on-this-page #back-to-top span{display:none}nav.on-this-page .active a{background-color:var(--action-0);color:var(--action-contrast)}nav.letters,nav.letters a,nav.letters li,nav.letters ul{height:var(--chip)}nav.letters li{max-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{max-width:none}nav.letters a{padding:.25rem .66rem}}nav.index{--justify:space-between;--padding:0;background-color:rgba(var(--base-rgb),var(--op-6))}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:calc(100% - var(--btn_));flex-shrink:1;max-width:none}nav.index li:first-of-type{flex-shrink:1;transform:scaleX(1);order:9999;width:var(--btn);height:var(--btn);max-width:none}@media (max-width:767px){.index li.adj{transform:scaleX(0);max-width:0}}nav.index a{border-bottom:4px solid transparent}nav.index .active a{border-color:var(--action-0);color:var(--contrast)}nav.index.open{--dir:column-reverse;height:var(--maxHeight);width:100%;--align:flex-end;background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}nav.index.open li{width:100%;height:var(--btn);max-width:100%!important;transform:scaleX(1);overflow:visible}nav.index.open a{--justify:flex-end;padding:0 2rem 0 0;background-color:transparent}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:var(--contrast);border-color: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}.always ul.socials li{flex:1;--justify:center;--align:center}.always ul.socials a{display:inline-flex}ul.socials a{display:inline-block;font-size:var(--txt-x-small);padding:.25rem .5rem;max-width:none}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:var(--action-0);color:var(--action-contrast);border-color: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:var(--base);--gap:0}.tab-content .tab-content nav.tabs{background-color:var(--base-100)}.tab-content .tab-content .tab-content nav.tabs{background-color:var(--base-200)}.tab-content nav.tabs button.active h2{color: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:var(--base);box-shadow:rgba(var(--base-rgb),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: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/UtilityFunctions.js b/assets/js/concise/UtilityFunctions.js
index ca5944e..d11cdbc 100644
--- a/assets/js/concise/UtilityFunctions.js
+++ b/assets/js/concise/UtilityFunctions.js
@@ -992,12 +992,75 @@
 	{ passive: true }
 );
 
+
+window.previousBGSize = 'Small';
+window.bgSizes = {
+	Small: 500,
+	Med: 768,
+	Large: 1024
+};
+
+window.bgObserver = new IntersectionObserver((entries) => {
+		entries.forEach(entry => {
+			if (entry.isIntersecting) {
+				let newSize = entry.target.dataset[`bg${window.previousBGSize}`];
+				entry.target.style.backgroundImage = `url(${newSize})`;
+				entry.target.dataset.bgImg = window.previousBGSize;
+				window.bgObserver.unobserve(entry.target);
+			}
+		})
+	},
+	{
+		root: null,
+		rootMargin: '0px 0px -100px 0px',
+		threshold: 0
+	});
+
+function updateBG() {
+	let current = window.innerWidth;
+	let newWidth = getBGWidth(current);
+
+	if (newWidth) {
+		window.previousBGSize = newWidth;
+		document.querySelectorAll('[data-bg-img]:not([data-bg-img="'+window.previousBGSize+'"])').forEach(img => {
+			window.bgObserver.observe(img);
+		});
+	}
+
+}
+function getBGWidth(width) {
+	let prev = window.previousBGSize;
+
+	let check = {
+		Small: ['Med','Large'],
+		Med: ['Large'],
+		Large: false
+	};
+
+	if (!check[prev]) {
+		return false;
+	}
+	let next = 'Small';
+
+	check[prev].forEach(w => {
+		if (width => window.bgSizes[w]) {
+			next = w;
+		}
+	});
+	return next;
+}
+
+updateBG();
+
 // Debounced resize to recalc scrollable height
 window.addEventListener('resize', () => {
 	window.debouncer.schedule('recalc-max-scroll', () => {
 		updateMaxScroll();
 		updateScrollProgress(window.scrollY || docEl.scrollTop || 0);
 	}, 20);
+	window.debouncer.schedule('bg-resize', () => {
+		updateBG();
+	});
 });
 
 // Initial setup
diff --git a/assets/js/concise/navigation.js b/assets/js/concise/navigation.js
index 062ea24..10026f9 100644
--- a/assets/js/concise/navigation.js
+++ b/assets/js/concise/navigation.js
@@ -17,7 +17,7 @@
 		this.navs = new Map();
 		document.querySelectorAll('nav:has(.submenu), nav:has(.toggle)').forEach(nav => {
 			let navID = nav.id;
-			if (navID === '') {
+			if (navID === '' || this.navs.has(navID)) {
 				navID = `nav-${this.counter}`;
 				nav.id = navID;
 				this.counter++;
diff --git a/assets/js/min/navigation.min.js b/assets/js/min/navigation.min.js
index 308523e..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&&(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/utility.min.js b/assets/js/min/utility.min.js
index 808a5c5..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}window.addEventListener("scroll",()=>{o||(o=!0,requestAnimationFrame(l))},{passive:!0}),window.addEventListener("resize",()=>{window.debouncer.schedule("recalc-max-scroll",()=>{a(),s(window.scrollY||e.scrollTop||0)},20)}),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/build/feed/view.asset.php b/build/feed/view.asset.php
index c828fa8..0e01911 100644
--- a/build/feed/view.asset.php
+++ b/build/feed/view.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => 'c3aa1c027f932096017e');
+<?php return array('dependencies' => array(), 'version' => 'b9d79a303cb5aad9e29a');
diff --git a/build/feed/view.js b/build/feed/view.js
index a51b945..a5dae15 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.config={source:"",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(),"requestIdleCallback"in window?requestIdleCallback(()=>{this.initStore(),this.initTaxonomies(),this.processCachedFilters(),this.processURLFilters(),this.updateFilterUI(),this.initGallery()},{timeout:2e3}):setTimeout(()=>{this.initStore(),this.initTaxonomies(),this.processCachedFilters(),this.processURLFilters(),this.updateFilterUI(),this.initGallery()},100)}initElements(){this.selectors={filterTrigger:"[data-filter]",filters:{actions:".filter-actions .toggle-text",container:".filters",content:'[data-filter="content"]',orderby:'[data-filter="orderby"]',order:'[data-filter="order"]',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),this.ui.content=this.ui.filters.container.querySelectorAll('[name="content"]'),0===this.ui.content.length&&(this.ui.content=!1),this.ui.taxonomies=this.ui.filters.container.querySelectorAll("[data-taxonomy]"),0===this.ui.taxonomies.length&&(this.ui.taxonomies=!1),this.ui.orderbyWrap=this.ui.filters.container.querySelector("[data-for-order]"),0===this.ui.orderbyWrap.length&&(this.ui.orderbyWrap=!1),this.ui.order=this.ui.filters.container.querySelectorAll('[data-filter="order"]'),0===this.ui.order.length&&(this.ui.order=!1),this.ui.orderby=this.ui.filters.container.querySelectorAll('[data-filter="orderby"]'),0===this.ui.orderby.length&&(this.ui.orderby=!1),this.orderbyFilters=this.ui.orderby?Array.from(this.ui.orderby).map(e=>e.value):[],this.contentTypes=this.ui.content?Array.from(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):[]}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.contentTypes[0],orderby:"date",order:"desc",page:1};this.config.context&&(e.context=this.config.context),this.config.source&&(e.source=this.config.source),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=Array.from(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){[this.ui.taxonomies,this.ui.orderby].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.orderbyWrap){var t;let i=null!==(t=this.ui.orderbyWrap.dataset.forOrder.split(","))&&void 0!==t?t:[];this.ui.orderbyWrap.hidden=!i.includes(e)}}updateFilterControls(){const e=0===Object.keys(this.taxFilters).length;this.ui.buttons.clearFilters&&(this.ui.buttons.clearFilters.hidden=e),this.ui.filters.actions&&(this.ui.filters.actions.hidden=e)}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=Array.from(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.source}_${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.source}_${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.source}_${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.orderbyFilters.filter(e=>!["date","modified","title","random"].includes(e)),t=[];e.forEach(e=>{t.push({name:e,keyPath:e})});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"});this.store=i.feed,this.store.subscribe((e,t)=>{var i;"data-loaded"===e&&(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-1} Tx`),s&&(s.textContent=e.number-1),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
+(()=>{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.config={source:"",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(),"requestIdleCallback"in window?requestIdleCallback(()=>{this.initStore(),this.initTaxonomies(),this.processCachedFilters(),this.processURLFilters(),this.updateFilterUI(),this.initGallery()},{timeout:2e3}):setTimeout(()=>{this.initStore(),this.initTaxonomies(),this.processCachedFilters(),this.processURLFilters(),this.updateFilterUI(),this.initGallery()},100)}initElements(){this.selectors={filterTrigger:"[data-filter]",filters:{actions:".filter-actions .toggle-text",container:".filters",content:'[data-filter="content"]',orderby:'[data-filter="orderby"]',order:'[data-filter="order"]',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),this.ui.content=this.ui.filters.container.querySelectorAll('[name="content"]'),0===this.ui.content.length&&(this.ui.content=!1),this.ui.taxonomies=this.ui.filters.container.querySelectorAll("[data-taxonomy]"),0===this.ui.taxonomies.length&&(this.ui.taxonomies=!1),this.ui.orderbyWrap=this.ui.filters.container.querySelector("[data-for-order]"),0===this.ui.orderbyWrap.length&&(this.ui.orderbyWrap=!1),this.ui.order=this.ui.filters.container.querySelectorAll('[data-filter="order"]'),0===this.ui.order.length&&(this.ui.order=!1),this.ui.orderby=this.ui.filters.container.querySelectorAll('[data-filter="orderby"]'),0===this.ui.orderby.length&&(this.ui.orderby=!1),this.orderbyFilters=this.ui.orderby?Array.from(this.ui.orderby).map(e=>e.value):[],this.contentTypes=this.ui.content?Array.from(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):[]}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.contentTypes[0],orderby:"date",order:"desc",page:1};this.config.context&&(e.context=this.config.context),this.config.source&&(e.source=this.config.source),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(Array.from(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){[this.ui.taxonomies,this.ui.orderby].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.orderbyWrap){let t=this.ui.orderbyWrap.dataset.forOrder.split(",")??[];this.ui.orderbyWrap.hidden=!t.includes(e)}}updateFilterControls(){const e=0===Object.keys(this.taxFilters).length;this.ui.buttons.clearFilters&&(this.ui.buttons.clearFilters.hidden=e),this.ui.filters.actions&&(this.ui.filters.actions.hidden=e)}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=Array.from(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.source}_${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.source}_${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.source}_${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.orderbyFilters.filter(e=>!["date","modified","title","random"].includes(e)),t=[];e.forEach(e=>{t.push({name:e,keyPath:e})});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"});this.store=i.feed,this.store.subscribe((e,t)=>{"data-loaded"===e&&(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-1} Tx`),s&&(s.textContent=e.number-1),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
diff --git a/build/fields/render.php b/build/fields/render.php
index 1c2b31a..b5645eb 100644
--- a/build/fields/render.php
+++ b/build/fields/render.php
@@ -120,7 +120,7 @@
     </header>
     <section>
         <details class="bio-info">
-            <summary class="row btw">
+            <summary class="row x-btw">
                 <h2>About <?= ($artist['name'] !== '') ? $artist['name'] : strtok($artist['display_name'], ' ')?></h2>
             </summary>
             <div class="columns stack-small">
@@ -242,7 +242,7 @@
     </header>
     <section>
         <details class="bio-info">
-            <summary class="row btw">
+            <summary class="row x-btw">
                 <h2>Learn More About <?=$current->name?></h2>
             </summary>
             <div class="map">
diff --git a/build/gmbreviews/render.php b/build/gmbreviews/render.php
index ee519f8..487f394 100644
--- a/build/gmbreviews/render.php
+++ b/build/gmbreviews/render.php
@@ -134,7 +134,7 @@
 								<?= apply_filters('wpautop', $comment) ?>
 							</div>
 						<?php } ?>
-						<cite class="row start nowrap">
+						<cite class="row left nowrap">
 							<?php if (!empty($profilePhoto)) { ?>
 								<img src="<?=esc_url($profilePhoto)?>"
 									 alt="<?=esc_attr($reviewer)?>"
@@ -145,7 +145,7 @@
 								</div>
 							<?php } ?>
 
-							<div class="row start wrap">
+							<div class="row left wrap">
 								<?php if ($showRating && $rating > 0) { ?>
 									<div class="stars" title="<?= $rating ?> out of 5 stars">
 										<?php
diff --git a/build/summary/render.php b/build/summary/render.php
index 05636a1..c2c35fb 100644
--- a/build/summary/render.php
+++ b/build/summary/render.php
@@ -120,7 +120,7 @@
     </header>
     <section>
         <details class="bio-info">
-            <summary class="row btw">
+            <summary class="row x-btw">
                 <h2>About <?= ($artist['name'] !== '') ? $artist['name'] : strtok($artist['display_name'], ' ')?></h2>
             </summary>
             <div class="columns stack-small">
@@ -242,7 +242,7 @@
     </header>
     <section>
         <details class="bio-info">
-            <summary class="row btw">
+            <summary class="row x-btw">
                 <h2>Learn More About <?=$current->name?></h2>
             </summary>
             <div class="map">
diff --git a/inc/admin/ContentTaxonomy.php b/inc/admin/ContentTaxonomy.php
index 73476d4..399d3ac 100644
--- a/inc/admin/ContentTaxonomy.php
+++ b/inc/admin/ContentTaxonomy.php
@@ -290,7 +290,7 @@
 
             <?php foreach ($results['details'] as $taxonomy => $detail): ?>
                 <details>
-                    <summary class="row btw">
+                    <summary class="row x-btw">
                         <strong><?= esc_html(ucfirst($taxonomy)) ?></strong>
                         <?php if ($detail['errors'] === 0): ?>
                             <span style="color: green;">✓ Success</span>
diff --git a/inc/blocks/CustomBlocks.php b/inc/blocks/CustomBlocks.php
index 0b16235..0b5830e 100644
--- a/inc/blocks/CustomBlocks.php
+++ b/inc/blocks/CustomBlocks.php
@@ -6,6 +6,7 @@
 use JVBase\managers\Cache;
 use JVBase\managers\LoginManager;
 use JVBase\managers\SEO\BreadcrumbManager;
+use JVBase\utility\Image;
 use WP_Block;
 use WP_Query;
 
@@ -16,7 +17,16 @@
 class CustomBlocks
 {
     protected Cache $cache;
-	protected array $shouldRender = ['core/query'];
+
+	protected static ?WP_Query $currentLoop = null;
+	protected static ?int $currentQueryId = null;
+	protected static array $counters = [];
+	protected static ?WP_Query $originalQuery = null;
+	protected array $ignore = ['align','alt','area','backgroundColor','borderColor','buttonText','buttonPosition','buttonUseIcon','categories','className','columns','contentPosition','customOverlayColor','dimRatio','displayAsDropdown','displayAuthor','displayFeaturedImage','displayPostContent','displayPostContentRadio','displayPostDate','excerptLength','featuredImageAlign','fontSize','gradient','height','iconColor','iconColorValue','iconColorValue','iconBackgroundColor','iconBackgroundColorValue','id','imageFill','isDark','isLink','isSearchFieldHidden','isStackedOnMobile','isUserOverlayColor','kind','label','largestFontSize','layout','level','mediaId','mediaLink','mediaSizeSlug','mediaType','metadata','minHeight','minHeightUnit','opacity','opensInNewTab','order','orderBy','ordered','overlayMenu','placeholder','postLayout','postsToShow','query', 'queryId','ref','rel','shouldSyncIcon','showEmpty','showHierarchy','showLabel','showLabels','showOnlyTopLevel','showPostCounts','showTagCounts','size','sizeSlug','slug','smallestFontSize','tagName','taxonomy','term','textAlign','textColor','theme','title','type','url','useFeaturedImage','width','widthUnit',];
+
+	//For custom style output for nested links, etc
+	protected static array $pendingStyles = [];
+	protected static array $pendingClass = [];
     public function __construct()
     {
         $this->cache = Cache::for('blocks', WEEK_IN_SECONDS);
@@ -67,6 +77,13 @@
                 'label' => __('Callout Alt', 'jvb')
             ]
         );
+        register_block_style(
+            'core/separator',
+            [
+                'name' 	=>'logo',
+                'label' => __('With Logo', 'jvb')
+            ]
+        );
     }
 	protected function checkMethods(?string $content, array $block, ?WP_Block $parent = null, bool $isPrerender = false):?string
 	{
@@ -79,7 +96,8 @@
 		if (function_exists($function)) {
 			return $function($block, $content, $parent);
 		} else if (method_exists($this, $method)) {
-			return $this->$method($block, $content, $parent);
+			$content = $this->$method($block, $content, $parent);
+			return $isPrerender ? $this->maybeOutputCustomStyles().$content : $content;
 		} elseif (!empty($blockName) && JVB_TESTING) {
 			if (!in_array($block['blockName'], $this->getIgnore($isPrerender))) {
 				jvbDump('No method found for '.print_r($block['blockName'], true));
@@ -95,7 +113,13 @@
 			];
 			if ($isPrerender) {
 				$base = array_merge($base, [
-
+					'core/query-pagination',
+					'core/query-pagination-previous',
+					'core/query-pagination-next',
+					'core/query-pagination-numbers',
+					'core/query',
+					'core/calendar',
+					'core/archives',
 				]);
 			} else {
 				$base = array_merge($base, [
@@ -151,10 +175,10 @@
 		}
 		$icon = '';
 		if (str_contains($url[1], 'google.com/maps')) {
-			$icon = 'google-logo';
+			$icon = jvbIcon('google-logo');
 		}
 		if (str_contains($url[1], 'maps.apple.com')) {
-			$icon = 'apple-logo';
+			$icon = jvbIcon('apple-logo');
 		}
 
 		if ($icon !== '') {
@@ -163,7 +187,7 @@
 				$this->getClassesAndStyles($block['attrs']??[]),
 				esc_url($url[1]),
 				esc_html($label[1]),
-				jvbIcon($icon)
+				$icon
 			);
 		}
 
@@ -198,12 +222,18 @@
 
     public function prerender_core_columns(array $block, ?string $content, ?WP_Block $parent):?string
     {
-		$tagName = array_key_exists('tagName', $block['attrs']) ? $block['attrs']['tagName'] : 'section';
+		jvbDump($block, 'columns');
+		$attrs = $block['attrs']??[];
+		$tagName = array_key_exists('tagName', $attrs) ? $attrs['tagName'] : 'section';
 
+		$classes = ['row', 'nowrap'];
+		if (!array_key_exists('isStackedOnMobile', $attrs) || $attrs['isStackedOnMobile'] === true){
+			$classes[] = 'stack-small';
+		}
         return sprintf(
 			'<%s%s>%s</%s>',
 			$tagName,
-		   	$this->getClassesAndStyles($block['attrs']??[], ['row nowrap']),
+		   	$this->getClassesAndStyles($attrs, $classes),
 		   	$this->innerBlocks($block).'</section>',
 			$tagName
 		);
@@ -228,12 +258,36 @@
     //core_home_link
     //core_more
     //core_nextpage
+	public function prerender_core_nextpage(array $block, ?string $content, ?WP_Block $parent):?string
+	{
+
+		return str_replace('</a>', '</a></li>',str_replace('<a', '<li><a', wp_link_pages([
+			'before' 		=> '<nav class="pagination x-btw"><ul>',
+			'after'			=> '</ul></nav>',
+			'nextpagelink'	=> __('<span>Next </span>'.jvbIcon('caret-circle-right'), 'jvb'),
+			'previouspagelink'	=> __(jvbIcon('caret-circle-left').'<span> Previous</span>', 'jvb'),
+			'next_or_number'=> 'next',
+			'echo' 			=> false
+		])));
+	}
 
     public function prerender_core_separator(array $block, ?string $content, ?WP_Block $parent):?string
     {
 //		jvbDump($block, 'separator');
 //		jvbDump($parent, 'Parent');
-        return '<hr'.$this->getClassesAndStyles($block['attrs']??[]).'>';
+		$attrs = $block['attrs']??[];
+		$logo = '';
+		if (array_key_exists('className', $attrs) && $attrs['className'] === 'is-style-logo'){
+			$logo = apply_filters('jvbSeparatorLogo', 'logo');
+			if (!empty($logo)) {
+				$logo = jvbIcon($logo);
+			}
+		}
+        return sprintf(
+			'<hr%s>',
+			$this->getClassesAndStyles($attrs),
+//			$logo
+		);
     }
 
     public function prerender_core_spacer(array $block, ?string $content, ?WP_Block $parent):?string
@@ -341,29 +395,60 @@
 //		jvbDump($block, 'media text');
 //		jvbDump($parent, 'Parent');
         $ID = $this->imageID('', $block);
+		$attrs = $block['attrs']??[];
 
-		$size = array_key_exists('mediaSizeSlug', $block['attrs']??[]) ? $block['attrs']['mediaSizeSlug'] : 'large';
+		$size = array_key_exists('mediaSizeSlug', $attrs) ? $attrs['mediaSizeSlug'] : 'large';
         $imgLink = ($ID) ? $this->imageLink(true, $ID, 'tiny', $size) : '';
 
         $inner = $this->innerBlocks($block);
 
 
-		$classes = ['media-text', 'row'];
-		if (array_key_exists('isStackedOnMobile', $block['attrs']??[])) {
-			$classes[] = 'nowrap';
+		$classes = ['media-text', 'row', 'nowrap'];
+		if (!array_key_exists('isStackedOnMobile', $attrs) || $attrs['isStackedOnMobile'] === true) {
+			$classes[] = 'stack-small';
 		}
-        $content = '<div'.$this->getClassesAndStyles($block['attrs']??[], $classes).'>';
-        $content .= (array_key_exists(
-            'mediaPosition',
-            $block['attrs']??[]
-        ) && $block['attrs']['mediaPosition'] == 'right') ?
-            '<div>'.$inner.'</div><figure>'.$imgLink.'</figure>' :
-            '<figure>'.$imgLink.'</figure><div>'.$inner.'</div>';
-        $content .= '</div>';
-        return $content;
+
+		$inside = array_key_exists('mediaPosition', $attrs) && $attrs['mediaPosition'] === 'right'
+			? sprintf(
+				'<div>%s</div><figure>%s</figure>',
+				$inner, $imgLink
+			) : sprintf(
+				'<figure>%s</figure><div>%s</div>',
+				$imgLink, $inner
+			);
+
+        return sprintf(
+			'<div%s>%s</div>',
+			$this->getClassesAndStyles($attrs, $classes),
+			$inside
+		);
     }
     //core_video
 
+	public function prerender_core_video(array $block, ?string $content, ?WP_Block $parent):?string
+	{
+		jvbDump($block, 'video');
+//		jvbDump($parent, 'Parent');
+		$ID = $this->imageID('', $block);
+		if (!$ID) {
+			return '';
+		}
+
+		jvbDump($ID);
+
+		$title = (get_the_title($ID) !== '') ? '<b>'.get_the_title($ID).'</b>' : '';
+		$caption = (wp_get_attachment_caption($ID)) ?
+			'<figcaption>' .
+			$title .
+			wp_get_attachment_caption($ID) .
+			'</figcaption>' :
+			'<figcaption>' . $title . '</figcaption>';
+		$size = array_key_exists('sizeSlug', $block['attrs']??[]) ? $block['attrs']['sizeSlug'] : 'large';
+		return '<figure'.
+			$this->getClassesAndStyles($block['attrs']??[]).'>'.
+			$this->imageLink(true, $ID, 'tiny', $size) .
+			$caption.'</figure>';
+	}
 
     /**
      * Reusable blocks
@@ -379,10 +464,8 @@
     //prerender_core_classic
     public function prerender_core_heading(array $block, ?string $content, ?WP_Block $parent):?string
     {
-//		jvbDump($block, 'heading');
-//		jvbDump($parent, 'Parent');
         $level = (array_key_exists('level', $block['attrs']??[])) ? $block['attrs']['level'] : '2';
-		$content = $this->innerBlocks($block);
+		$content = $this->inside($block);
         $id = sanitize_title(wp_strip_all_tags($this->stripTagContents('small', $content)));
         return '<h'.$level.' id="'.$id.'"'.$this->getClassesAndStyles($block['attrs']??[]).'>'.
                $content.
@@ -408,9 +491,12 @@
     {
 //		jvbDump($block, 'paragraph');
 //		jvbDump($parent, 'Parent');
-        return '<p'.$this->getClassesAndStyles($block['attrs']??[]).'>'.
-               $this->innerBlocks($block).
-               '</p>';
+		$inside = $this->inside($block);
+        return empty($inside) ? '' : sprintf(
+		'<p%s>%s</p>',
+			$this->getClassesAndStyles($block['attrs']??[]),
+		   $inside
+		);
     }
 	public function prerender_core_quote(array $block, ?string $content, ?WP_Block $parent): ?string
 	{
@@ -492,33 +578,50 @@
 	}
     //core_pattern
 
-    public function prerender_core_site_logo(array $block, ?string $content, ?WP_Block $parent):?string
+    public function prerender_core_site_logo(array $block, ?string $content, ?WP_Block $parent = null):?string
     {
 //		jvbDump($block, 'site logo');
 //		jvbDump($parent, 'Parent');
+		$attrs = $block['attrs']??[];
         $open = $close = '';
 
-        if (!is_home() && !is_front_page()) {
-            $open = '<a href="'.get_home_url().'" rel="home">';
+        if ((!is_home() && !is_front_page()) && (!array_key_exists('isLink', $attrs) || $attrs['isLink'] === true)) {
+            $open = '<a href="'.get_home_url().'" rel="home" class="logo">';
             $close = '</a>';
         }
         $img = get_theme_mod('custom_logo');
-        $img = $this->image($img, 'tiny', 'thumbnail');
-        $img = str_replace('<img', '<img'.$this->getClassesAndStyles($block['attrs']??[]), $img);
+        $img = sprintf(
+			'<figure%s>%s</figure>',
+			$this->getClassesAndStyles($attrs, ['logo']),
+			$this->image($img, 'tiny', 'thumbnail')
+		);
         return $open.$img.$close;
     }
-    //core_site_title_tagline
+	public function prerender_core_site_tagline(array $block, ?string $content, ?WP_Block $parent):?string
+	{
+		$tagline = get_bloginfo('description');
+
+		return empty($tagline) ? '' : sprintf(
+			'<p%s>%s</p>',
+			$this->getClassesAndStyles($block['attrs']??[], ['tagline']),
+			$tagline
+		);
+	}
 
     public function prerender_core_site_title(array $block, ?string $content, ?WP_Block $parent):?string
     {
 //		jvbDump($block, 'site title');
 //		jvbDump($parent, 'Parent');
-        $tag = (array_key_exists('level', $block['attrs']??[])) ? $block['attrs']['level'] : 1;
+		$attrs = $block['attrs']??[];
+        $tag = (array_key_exists('level', $attrs)) ? $attrs['level'] : 1;
         $tag = ($tag == 0) ? 'p' : 'h'.$tag;
 
         $open = $close = '';
-        if (!is_front_page()) {
-            $open = '<a href="' . get_home_url() . '" rel="home">';
+        if (!is_front_page() && (!array_key_exists('isLink', $attrs) || $attrs['isLink'] === true)) {
+            $open = sprintf(
+				'<a href="%s" rel="home">',
+				get_home_url()
+			);
             $close = '</a>';
         }
         $class = ($tag === 'p') ?
@@ -526,11 +629,15 @@
             $this->getClassesAndStyles($block['attrs']??[]);
 
 
-        return '<'.$tag.$class.'>'.
-               $open.
-               get_bloginfo('name').
-               $close.
-               '</'.$tag.'>';
+        return sprintf(
+			'<%s%s>%s%s%s</%s>',
+			$tag,
+			$class,
+			$open,
+			get_bloginfo('name'),
+			$close,
+			$tag
+		);
     }
 
     /**
@@ -557,44 +664,70 @@
     {
 //		jvbDump($block, 'navigation');
 //		jvbDump($parent, 'Parent');
+//		jvbDump($block, 'navigation');
         $ID = (array_key_exists('ref', $block['attrs']??[])) ? $block['attrs']['ref'] : false;
 
         if (empty($block['innerBlocks']) && $ID && get_post($ID)) {
             $block['innerBlocks'] = parse_blocks(get_post($ID)->post_content);
         }
+		$attrs = $block['attrs']??[];
 
-        $toggle = (array_key_exists('overlayMenu', $block['attrs']??[])
-                   && $block['attrs']['overlayMenu'] == 'never') ?
-            '':
-            '<button class="toggle main"
-            data-action="toggle-menu"
-            aria-label="Open Menu"
-            aria-controls="navigation-' .$ID. '"
-            aria-expanded="false">'.
-            jvbIcon('list', ['title'=>'Toggle Menu']).
-            jvbIcon('x', ['title'=>'Toggle Menu']).
-		'</button>';
-        $class = ($toggle === '') ?
-            $this->getClassesAndStyles($block['attrs']??[], ['mobile']) :
-            $this->getClassesAndStyles($block['attrs']??[]);
-        $helpmenu = (get_the_title($ID) === 'Main') ?
-            '<nav><ul>'.jvbNotificationMenu().jvbHelpMenu().'</ul></nav>' :
-            '';
+		$toggle = '';
+		$classes = [];
+		if (!array_key_exists('overlayMenu', $attrs) || $attrs['overlayMenu'] !== 'never') {
+			$toggle = sprintf(
+				'<button class="toggle main"
+				data-action="toggle-menu"
+				aria-label="Open Menu"
+				aria-controls="navigation-%d"
+				aria-expanded="false">%s%s</button>',
+				$ID,
+				jvbIcon('list'),
+				jvbIcon('x')
+			);
+			$classes[] = 'mobile';
+			if (array_key_exists('overlayMenu', $attrs) && $attrs['overlayMenu'] === 'always') {
+				$classes[] = 'always';
+			}
+		}
+		if (!array_key_exists('layout', $attrs)) {
+			$classes[] = 'left';
+			$classes[] = 'row';
+		}
+		$class = $this->getClassesAndStyles($attrs, $classes);
+
+		$helpmenu = '';
+		$title = get_the_title($ID);
+		$isMain = false;
+		if ($title === 'Main') {
+			$isMain = true;
+			$helpmenu = sprintf(
+				'<nav><ul>%s%s</ul></nav>',
+				jvbNotificationMenu(),
+				jvbHelpMenu()
+			);
+		}
 
 
 		//Allows to add custom items to a menu, based on the menu name
-		$helpmenu = apply_filters('jvbMenuExtraAfter', $helpmenu, get_the_title($ID));
-		$main = trim(apply_filters('jvbMenuExtra', $this->innerBlocks($block), get_the_title($ID), $block));
+		$helpmenu = apply_filters('jvbMenuExtraAfter', $helpmenu, $title, $ID);
+		$main = trim(apply_filters('jvbMenuExtra', $this->innerBlocks($block), $title, $block));
 
-		$main = str_starts_with($main, '<ul') ? $main : '<ul>'.$main.'</ul>';
+		$main = str_starts_with($main, '<ul') ? $main : sprintf('<ul>%s</ul>',$main);
 
-        return '<nav'.$class.' id="navigation-' . $ID . '"aria-label="Navigation">
-            <span class="screen-reader-text">
+		$skipToContent = $isMain ? '<span class="screen-reader-text">
                 <a href="#content">Skip to Content</a>
-            </span>' .
-               $toggle .
-				$main.
-		   '</nav>'.$helpmenu;
+            </span>' : '';
+        return sprintf(
+			'<nav%s id="navigation-%d"aria-label="Navigation">
+            %s%s%s</nav>%s',
+			$class,
+			$ID,
+			$skipToContent,
+			$toggle,
+			$main,
+			$helpmenu
+		);
     }
 
     public function prerender_core_navigation_link(array $block, ?string $content, ?WP_Block $parent):?string
@@ -605,20 +738,20 @@
 		if (!array_key_exists('attrs', $block)) {
 			return '';
 		}
-        $url = (str_starts_with($block['attrs']['url'],'/')) ?
-            home_url($block['attrs']['url']) :
-            $block['attrs']['url'];
+		$attrs = $block['attrs']??[];
+        $url = (str_starts_with($attrs['url'],'/')) ?
+            home_url($attrs['url']) :
+            $attrs['url'];
         $current = (home_url($wp->request.'/') == $url);
-		$temp = $block['attrs']??[];
-		unset($temp['url']);
+		$attrs['url'] = $url;
         $classes = ($current) ?
-            $this->getClassesAndStyles($temp, ['current']):
-            $this->getClassesAndStyles($temp);
+            $this->getClassesAndStyles($attrs, ['current']):
+            $this->getClassesAndStyles($attrs);
         $aria = '';
         if ($current) {
             $aria = ' aria-current="page"';
         }
-        $linkOpen = $this->build_navigation_link($block['attrs']??[], $aria);
+        $linkOpen = $this->buildNavigationLink($attrs, $aria);
 
 
         return '<li'.$classes.'>'.$linkOpen.$block['attrs']['label'].'</a></li>';
@@ -634,22 +767,32 @@
             $block['attrs']['url'];
         $current = (home_url($wp->request) == $url);
 
-		$temp = $block['attrs']??[];
-		unset($temp['url']);
+		$attrs = $block['attrs']??[];
+		$attrs['url'] = $url;
         $classes = ($current) ?
-            $this->getClassesAndStyles($temp, ['has-submenu', 'current']):
-            $this->getClassesAndStyles($temp, ['has-submenu']);
+            $this->getClassesAndStyles($attrs, ['has-submenu', 'current']):
+            $this->getClassesAndStyles($attrs, ['has-submenu']);
 
         $aria = '';
         if ($current) {
             $aria = ' aria-current="page"';
         }
         $id = sanitize_title($block['attrs']['label']);
-        $linkOpen = $this->build_navigation_link($block['attrs'], $aria);
-        $content = '<li'.$classes.'>'.$linkOpen.$block['attrs']['label'].
-                   '</a><button class="toggle" data-action="toggle-submenu" title="Toggle Submenu" aria-label="Open '.$block['attrs']['label'].' Submenu" aria-expanded="false" aria-controls="'.$id.'-submenu">'.
-                   jvbIcon('caret-down', ['title'=>'Toggle Submenu']).
-                   '</button><ul class="submenu" id='.$id.'-submenu">';
+        $linkOpen = $this->buildNavigationLink($attrs, $aria);
+        $content = sprintf(
+			'<li%s>%s%s</a>
+						<button class="toggle" data-action="toggle-submenu" title="Toggle Submenu" aria-label="Open %s Submenu" aria-expanded="false" aria-controls="%s-submenu">
+                   			%s
+						</button>
+						<ul class="submenu" id=%s-submenu">',
+			$classes,
+			$linkOpen,
+			$attrs['label'],
+			$attrs['label'],
+			$id,
+			jvbIcon('caret-down', ['title'=>'Toggle Submenu']),
+			$id
+		);
 
         $content .= $this->innerBlocks($block);
         $content .= '</ul></li>';
@@ -657,9 +800,8 @@
         return $content;
     }
 
-    protected function build_navigation_link(array $attrs, string $aria):string
+    protected function buildNavigationLink(array $attrs, string $aria):string
     {
-        global $wp;
         $url =(str_starts_with($attrs['url'],'/')) ?
             home_url($attrs['url']) :
             $attrs['url'];
@@ -694,8 +836,88 @@
      * Theme Query Blocks
      */
     //core_post_author
+
+	public function prerender_core_post_author(array $block, ?string $content, ?WP_Block $parent):?string
+	{
+		$attrs = $block['attrs'] ?? [];
+
+		$size = 96;
+		if (array_key_exists('avatarSize',$attrs) && is_int($attrs['avatarSize'])) {
+			$size = $attrs['avatarSize'];
+		}
+		$byline = $aOpen = $aClose = $avatar = $bio = '';
+		global $post;
+		$user = get_userdata($post->post_author);
+
+		if (!array_key_exists('showAvatar', $attrs) || $this->checkAttrs('showAvatar', $attrs)){
+			$avatar = get_avatar($post->post_author, $size);
+		}
+		if (!array_key_exists('showBio', $attrs) || $this->checkAttrs('showBio', $attrs)) {
+			$bio = wpautop($user->description);
+		}
+
+		$target = '';
+		if (array_key_exists('linkTarget', $attrs) && $attrs['linkTarget']=== '_blank') {
+			$target = ' target="_blank"';
+		}
+
+		if ($this->checkAttrs('isLink', $attrs)) {
+			$aOpen = sprintf(
+				'<a href="%s"%s>',
+				get_author_posts_url($post->post_author),
+				$target
+			);
+			$aClose = '</a>';
+		}
+
+		if (array_key_exists('byline', $attrs)) {
+			$byline = sprintf(
+				'<small>%s</small> — ',
+				$attrs['byline']
+			);
+		}
+
+		$name = $user->display_name;
+
+
+		return sprintf(
+			'<div%s>%s%s%s<p>%s%s%s%s</p>%s</div>',
+			$this->getClassesAndStyles($attrs, ['row','nowrap']),
+			$aOpen,
+			$avatar,
+			$aClose,
+			$aOpen,
+			$byline,
+			$name,
+			$aClose,
+			$bio
+		);
+	}
     //core_post_author_biography
-    //core_post_author_name
+
+	public function prerender_core_post_author_name(array $block, ?string $content, ?WP_Block $parent):?string
+	{
+		$attrs = $block['attrs']??[];
+		global $post;
+		$aOpen = $aClose = '';
+		if ($this->checkAttrs('isLink', $attrs)) {
+
+			$aOpen = sprintf(
+				'<a href="%s" rel="author">',
+				get_author_posts_url($post->post_author)
+			);
+			$aClose = '</a>';
+		}
+
+		$author = get_userdata($post->post_author);
+		return sprintf(
+			'<p%s>%s%s%s</p>',
+			$this->getClassesAndStyles($attrs, ['author']),
+			$aOpen,
+			$author->display_name,
+			$aClose
+		);
+	}
     public function prerender_core_post_content(array $block, ?string $content, ?WP_Block $parent):?string
     {
 //		jvbDump($block, 'post content');
@@ -707,9 +929,21 @@
 
         if ($content == '') {
 			if(is_singular()) {
-				global $post;
+				global $post, $page;
 
-				$block['innerBlocks'] = parse_blocks($post->post_content);
+				$pages = explode('<!--nextpage-->', $post->post_content);
+				$currentContent = $pages[max(0, $page - 1)] ?? $pages[0];
+
+
+				if ($page > 1 && !str_contains($currentContent, '<!--nextpage-->')) {
+					$currentContent = str_replace('<!-- /wp:nextpage -->','', $currentContent);
+					$currentContent .= '
+					<!-- wp:nextpage -->
+					<!--nextpage-->
+					<!-- /wp:nextpage -->';
+				}
+
+				$block['innerBlocks'] = parse_blocks($currentContent);
 				$result = $this->innerBlocks($block);
 			}else {
 				$result = '';
@@ -723,31 +957,118 @@
     //core_post_date
 	public function prerender_core_post_date(array $block, ?string $content, ?WP_Block $parent):?string
 	{
+
 //		jvbDump($block, 'post date');
+//		return null;
+		$attrs = $block['attrs']??[];
+		$postDate = null;
+		$itemProp = 'datePublished';
+		$format = array_key_exists('format', $attrs) ? $attrs['format'] : 'M d, Y';
+		$dateFormat = null;
+		if (array_key_exists('displayType', $attrs)) {
+			switch ($attrs['displayType']) {
+				case 'displayType':
+					$postDate = get_post_modified_time('c');
+					$dateFormat = get_post_modified_time($format);
+					$itemProp = 'dateModified';
+					break;
+
+
+			}
+		}
+		$postDate = is_null($postDate) ? get_the_date('c') : $postDate;
+		$dateFormat = is_null($dateFormat) ? get_the_date($format) : $dateFormat;
+
+
+
+		$aOpen = $aClose = '';
+		if ($this->checkAttrs('isLink', $attrs) && !is_singular()) {
+			$aOpen = sprintf(
+				'<a href="%s">',
+				get_the_permalink()
+			);
+			$aClose = '</a>';
+		}
 //		jvbDump($parent, 'Parent');
-		$postDate = get_the_date('c');
-		return '<time datetime="'.$postDate.'" itemprop="datePublished"'.$this->getClassesAndStyles($block['attrs']??[]).'>'.get_the_date().'</time>';
+
+		return sprintf(
+			'<time datetime="%s" itemprop="%s"%s>%s%s%s</time>',
+			$postDate,
+			$itemProp,
+			$this->getClassesAndStyles($attrs),
+			$aOpen,
+			$dateFormat,
+			$aClose
+		);
 	}
     //core_post_excerpt
 	public function prerender_core_post_excerpt(array $block, ?string $content, ?WP_Block $parent):?string
 	{
-		return wpautop(get_the_excerpt());
+		$attrs = $block['attrs']??[];
+
+		$moreText = array_key_exists('moreText', $attrs) ? $attrs['moreText'] : 'Read more '.jvbIcon('arrow-circle-right');
+		$showMoreOnNewLine = !array_key_exists('showMoreOnNewLine', $attrs) || $this->checkAttrs('showMoreOnNewLine', $attrs);
+//		jvbDump($block);
+//		jvbDump($showMoreOnNewLine);
+
+		$excerpt = array_filter(explode('<p>',wpautop(get_the_excerpt())));
+		$classes = $this->getClassesAndStyles($attrs);
+		$excerpt = array_map(function ($line) use ($classes) {
+			return sprintf(
+				'<p%s>%s',
+				$classes,
+				$line
+			);
+		}, $excerpt);
+
+		if (!empty($moreText)) {
+			if ($showMoreOnNewLine) {
+				$excerpt[] = sprintf(
+					'<p%s><a href="%s" class="read-more">%s</a></p>',
+					$classes,
+					get_the_permalink(),
+					$moreText
+				);
+			} else {
+				$last = array_key_last($excerpt);
+				$excerpt[$last] = str_replace('</p>', sprintf('<a href="%s" class="read-more">%s</a>',
+				get_the_permalink(),
+				$moreText), $excerpt[$last]);
+			}
+		}
+		return implode('',$excerpt);
 	}
     public function prerender_core_post_featured_image(array $block, ?string $content, ?WP_Block $parent):?string
     {
 //		jvbDump($block, 'featured image');
 //		jvbDump($parent, 'Parent');
 		global $post;
+		$attrs = $block['attrs']??[];
 		$ID = get_post_thumbnail_id($post->ID);
-		$aOpen = $aClose = '';
-		if(!is_single($post->ID)) {
+		$aspectRatio = $aOpen = $aClose = '';
+		if(!is_single($post->ID) && $this->checkAttrs('isLink', $attrs)) {
 			$aOpen = '<a href="'.get_the_permalink($post->ID).'">';
 			$aClose = '</a>';
 		}
+		if (array_key_exists('aspectRatio', $attrs)) {
+			$aspectRatio = $attrs['aspectRatio'];
+		}
 
-        return '<figure'.$this->getClassesAndStyles($block['attrs']??[]).'>'.$aOpen.
-               apply_filters('jvbCoreFeaturedImage', $this->image($ID), $post->post_type).
-				$aClose.'</figure>';
+		$img = apply_filters('jvbCoreFeaturedImage', '', $post->post_type, $attrs);
+
+		if (empty($img)) {
+			$img = $this->image($ID);
+			$img = empty($aspectRatio) ? $img : str_replace('<img', '<img style="aspect-ratio:'.$aspectRatio.';"', $img);
+		}
+
+
+        return !empty($img) ? sprintf(
+			'<figure%s>%s%s%s</figure>',
+			$this->getClassesAndStyles($attrs),
+			$aOpen,
+			$img,
+			$aClose,
+		):'';
     }
     //core_post_navigation_link
 	public function prerender_core_post_navigation_link(array $block, ?string $content, ?WP_Block $parent):?string
@@ -801,23 +1122,27 @@
 		);
 	}
     //core_post_template
-	public function render_core_post_template(array $block, string $content):string
+	public function prerender_core_post_template(array $block, ?string $content):?string
 	{
-		global $wp_query;
-
 		$inner = '';
-		$block['innerBlocks'][0]['attrs']['tagName'] = 'li';
-		if ($wp_query->have_posts()) {
-			while ($wp_query->have_posts()) {
-				$wp_query->the_post();
 
-				$inner .= $this->innerBlocks($block);
-			}
-			wp_reset_postdata();
+		if (!static::$currentLoop) {
+			jvbDump('No loop stored');
+			return $content;
 		}
+		if (static::$currentLoop->have_posts()) {
+			while (static::$currentLoop->have_posts()) {
+				static::$currentLoop->the_post();
 
+				$inner .= sprintf(
+					'<li>%s</li>',
+					$this->innerBlocks($block, '','',$block)
+				);
+			}
+		}
 		return sprintf(
-			'<ul class="loop">%s</ul>',
+			'<ul%s>%s</ul>',
+			$this->getClassesAndStyles($block['attrs']??[], ['loop']),
 			$inner
 		);
 	}
@@ -828,16 +1153,17 @@
 			return '';
 		}
 		$terms = get_the_terms(get_the_ID(), $block['attrs']['term']);
+		$attrs = $block['attrs']??[];
 		$out = '';
 		if ($terms && !is_wp_error($terms)) {
 			$out = sprintf(
 				'<ul%s>',
-				$this->getClassesAndStyles($block['attrs'], ['term-list', 'row', 'start'])
+				$this->getClassesAndStyles($attrs, ['term-list', 'row', 'left'])
 			);
-				if (array_key_exists('prefix', $block['attrs']??[])) {
+				if (array_key_exists('prefix', $attrs)) {
 					$out .= sprintf(
 						'<li class="prefix">%s</li>',
-						$block['attrs']['prefix']
+						$attrs['prefix']
 					);
 				}
 				foreach($terms as $term) {
@@ -847,10 +1173,10 @@
 						html_entity_decode($term->name)
 					);
 				}
-			if (array_key_exists('suffix', $block['attrs'])) {
+			if (array_key_exists('suffix', $attrs)) {
 				$out .= sprintf(
 					'<li class="suffix">%s</li>',
-					$block['attrs']['suffix']
+					$attrs['suffix']
 				);
 			}
 			$out .= '</ul>';
@@ -860,145 +1186,388 @@
     //core_post_time_to_read
     public function prerender_core_post_title(array $block, ?string $content, ?WP_Block $parent):?string
     {
-//		jvbDump($block, 'post content');
 //		jvbDump($parent, 'Parent');
         $open = $close = '';
-        if (array_key_exists('isLink', $block['attrs']??[])) {
-            $rel = (array_key_exists('rel', $block['attrs']??[])) ?
+		$attrs = $block['attrs']??[];
+        if ($this->checkAttrs('isLink', $attrs)) {
+            $rel = (array_key_exists('rel', $attrs)) ?
                 ' rel="'.$block['attrs']['rel'].'"' :
                 '';
-            $target = (array_key_exists('linkTarget', $block['attrs']??[])) ?
+            $target = (array_key_exists('linkTarget', $attrs)) ?
                 ' target="'.$block['attrs']['linkTarget'].'"' :
                 '';
             $open = '<a href="' . get_the_permalink() . '"' . $rel . $target . '>';
             $close = '</a>';
         }
-        if (is_singular(BASE.'partner')) {
-            $open .= '<small>edmonton.ink partner:</small> ';
-        }
-        $level = (array_key_exists('attrs', $block) &&
-                  array_key_exists('level', $block['attrs'])) ?
-            $block['attrs']['level'] :
-            2;
-        return '<h'.$level.$this->getClassesAndStyles($block['attrs']??[]).'>'.
-               $open.get_the_title().$close.
-               '</h'.$level.'>';
+
+        $level = $attrs['level']??2;
+
+		$title = (!static::$currentLoop && !is_singular())
+			? get_the_title(get_queried_object_id())
+			: get_the_title();
+
+        return sprintf(
+			'<h%s%s>%s%s%s</h%s>',
+			$level,
+			$this->getClassesAndStyles($attrs),
+			$open,
+			$title,
+			$close,
+			$level
+		);
     }
-
-	public function render_core_query(array $block, string $content): string
+	public function prerender_core_query(array $block, ?string $content):?string
 	{
+		global $wp_query;
+		$inherit = $block['attrs']['inherit'] ?? false;
 
-//		$queryID = $block['attrs']['queryId'] ?? null;
-//		$inherit = $block['attrs']['inherit'] ?? false;
-//
-//		if ($inherit) {
-//			global $wp_query;
-//			$loop = $wp_query;
-//		} else {
-//			$args = [];
-//			foreach (($block['attrs']['query'] ?? []) as $key => $value) {
-//				if (empty($value)) {
-//					continue;
-//				}
-//				switch ($key) {
-//					case 'postType':
-//						if ($value === BASE.'progress'){
-//							$args['post_parent'] = 0;
-//						}
-//						$args['post_type'] = $value;
-//						break;
-//					case 'perPage':
-//						$args['posts_per_page'] = $value;
-//						break;
-//					case 'orderBy':
-//						$args['orderby'] = $value;
-//						break;
-//					case 'taxQuery':
-//						$taxQuery = [];
-//						foreach ($value as $tax => $terms) {
-//							$taxQuery[] = [
-//								'taxonomy' 	=> $tax,
-//								'terms'		=> $terms
-//							];
-//						}
-//						if (!empty($taxQuery)) {
-//							$args['tax_query'] = $taxQuery;
-//							if (count($taxQuery) > 1) {
-//								$args['tax_query']['relation'] = 'OR';
-//							}
-//						}
-//						break;
-//					case 'sticky':
-//						if ($value === 'ignore') {
-//							$args['ignore_sticky_posts'] = true;
-//						} else if ($value === 'exclude'){
-//							$args['post__not_in'] = get_option('sticky_posts');
-//						} else if ($value === 'only') {
-//							$args['include'] = get_option('sticky_posts');
-//						}
-//						break;
-//					case 'search':
-//						$args['s'] = $value;
-//						break;
-//					default:
-//						$args[$key] = $value;
-//						break;
-//
-//				}
-//			}
-//			$search = 'query-' . $queryID;
-//			foreach ($_GET as $key => $value) {
-//				if (str_contains($key, $search)) {
-//					$key = str_replace($search, '', $key);
-//					if ($key === 'page') {
-//						$args['paged'] = (int)$value;
-//					}
-//				}
-//			}
-//			$loop = new WP_Query($args);
-//		}
-//
-//		$inner = '';
-//		foreach ($block['innerBlocks'] as $innerBlock) {
-//			switch ($innerBlock['blockName']) {
-//				case 'core/post-template':
-//					$inner .= '<section class="item-grid">';
-//					if ($loop->have_posts()) {
-//						while ($loop->have_posts()) {
-//							$loop->the_post();
-//							$postType = get_post_type();
-//							$inner .= '<div class="item ' . jvbNoBase($postType) . '">' . $this->innerBlocks($innerBlock) . '</div>';
-//						}
-//					}
-//					$inner .= '</section>';
-//					break;
-//			}
-//		}
-//
-//		// Reset only after a custom query, not the main query
-//		if (!$inherit) {
-//			wp_reset_postdata();
-//		}
+		if ($inherit) {
+			static::$currentLoop = $wp_query;
+		} else {
+			static::$currentLoop = new WP_Query($this->buildQueryArgs($block['attrs']));
+		}
+		static::$currentQueryId = $block['attrs']['queryId'] ?? null;
 
-		$tagName = $block['attrs']['tagName'] ?? 'div';
-//		return sprintf(
-//			'<%s class="loop">%s</%s>',
-//			$tagName,
-//			$this->innerBlocks($block),
-//			$tagName
-//		);
-		return $this->innerBlocks($block);
+		static::$originalQuery = $wp_query;
+
+		$inside = $this->innerBlocks($block);
+
+		if (str_contains($inside, 'loop')) {
+			$classes = $this->getClassesAndStyles($block['attrs'] ?? [], ['loop']);
+			$classes = str_replace(' class="', '', $classes);
+			$classes = strtok($classes, '"');
+			$inside = str_replace('loop', $classes, $inside);
+		}
+
+
+		static::$currentQueryId = null;
+		static::$currentLoop = null;
+
+		wp_reset_postdata();
+		return $inside;
 	}
+//	public function render_core_query(array $block, string $content): string
+//	{
+//		$inside = $this->innerBlocks($block);
+//		if (str_contains($inside, 'loop')) {
+//			$classes = $this->getClassesAndStyles($block['attrs']??[], ['loop']);
+//			$classes = str_replace(' class="', '', $classes);
+//			$classes = strtok($classes, '"');
+//
+//			$inside = str_replace('loop', $classes, $inside);
+//		}
+//		return $inside;
+//	}
+		protected function buildQueryArgs(array $attrs): array
+		{
+			$queryID = $attrs['queryId'] ?? null;
+			$args = [];
+			foreach (($attrs['query'] ?? []) as $key => $value) {
+				if (empty($value)) continue;
+				switch ($key) {
+					case 'postType':   $args['post_type']       = $value; break;
+					case 'perPage':    $args['posts_per_page']  = $value; break;
+					case 'orderBy':    $args['orderby']         = $value; break;
+					case 'sticky':
+						match ($value) {
+							'ignore'  => $args['ignore_sticky_posts'] = true,
+							'exclude' => $args['post__not_in'] = get_option('sticky_posts'),
+							'only'    => $args['post__in']     = get_option('sticky_posts'),
+							default   => null
+						};
+						break;
+					case 'taxQuery':
+						$taxQuery = array_map(fn($tax, $terms) => [
+							'taxonomy' => $tax, 'terms' => $terms
+						], array_keys($value), $value);
+						if (count($taxQuery) > 1) $taxQuery['relation'] = 'OR';
+						$args['tax_query'] = $taxQuery;
+						break;
+					case 'search':     $args['s']    = $value; break;
+					default:           $args[$key]   = $value; break;
+				}
+			}
+
+			// Handle pagination from query string
+			$search = 'q-' . $queryID.'-';
+			foreach ($_GET as $key => $value) {
+				if (str_contains($key, $search) && str_replace($search, '', $key) === 'page') {
+					$args['paged'] = (int)$value;
+				}
+			}
+			return $args;
+		}
+
+		protected function buildPaginationUrl(int $page): string
+		{
+			$param = 'q-' . static::$currentQueryId . '-page';
+			$url = remove_query_arg($param);
+			return $page > 1 ? add_query_arg($param, $page, $url) : $url;
+		}
+
+		protected function getCurrentPage(): int
+		{
+			$param = 'q-' . static::$currentQueryId . '-page';
+			return isset($_GET[$param]) ? (int)$_GET[$param] : 1;
+		}
+//	public function render_core_query(array $block, string $content): string
+//	{
+//
+////		$queryID = $block['attrs']['queryId'] ?? null;
+////		$inherit = $block['attrs']['inherit'] ?? false;
+////
+////		if ($inherit) {
+////			global $wp_query;
+////			$loop = $wp_query;
+////		} else {
+////			$args = [];
+////			foreach (($block['attrs']['query'] ?? []) as $key => $value) {
+////				if (empty($value)) {
+////					continue;
+////				}
+////				switch ($key) {
+////					case 'postType':
+////						if ($value === BASE.'progress'){
+////							$args['post_parent'] = 0;
+////						}
+////						$args['post_type'] = $value;
+////						break;
+////					case 'perPage':
+////						$args['posts_per_page'] = $value;
+////						break;
+////					case 'orderBy':
+////						$args['orderby'] = $value;
+////						break;
+////					case 'taxQuery':
+////						$taxQuery = [];
+////						foreach ($value as $tax => $terms) {
+////							$taxQuery[] = [
+////								'taxonomy' 	=> $tax,
+////								'terms'		=> $terms
+////							];
+////						}
+////						if (!empty($taxQuery)) {
+////							$args['tax_query'] = $taxQuery;
+////							if (count($taxQuery) > 1) {
+////								$args['tax_query']['relation'] = 'OR';
+////							}
+////						}
+////						break;
+////					case 'sticky':
+////						if ($value === 'ignore') {
+////							$args['ignore_sticky_posts'] = true;
+////						} else if ($value === 'exclude'){
+////							$args['post__not_in'] = get_option('sticky_posts');
+////						} else if ($value === 'only') {
+////							$args['include'] = get_option('sticky_posts');
+////						}
+////						break;
+////					case 'search':
+////						$args['s'] = $value;
+////						break;
+////					default:
+////						$args[$key] = $value;
+////						break;
+////
+////				}
+////			}
+////			$search = 'query-' . $queryID;
+////			foreach ($_GET as $key => $value) {
+////				if (str_contains($key, $search)) {
+////					$key = str_replace($search, '', $key);
+////					if ($key === 'page') {
+////						$args['paged'] = (int)$value;
+////					}
+////				}
+////			}
+////			$loop = new WP_Query($args);
+////		}
+////
+////		$inner = '';
+////		foreach ($block['innerBlocks'] as $innerBlock) {
+////			switch ($innerBlock['blockName']) {
+////				case 'core/post-template':
+////					$inner .= '<section class="item-grid">';
+////					if ($loop->have_posts()) {
+////						while ($loop->have_posts()) {
+////							$loop->the_post();
+////							$postType = get_post_type();
+////							$inner .= '<div class="item ' . jvbNoBase($postType) . '">' . $this->innerBlocks($innerBlock) . '</div>';
+////						}
+////					}
+////					$inner .= '</section>';
+////					break;
+////			}
+////		}
+////
+////		// Reset only after a custom query, not the main query
+////		if (!$inherit) {
+////			wp_reset_postdata();
+////		}
+//
+//		$tagName = $block['attrs']['tagName'] ?? 'div';
+////		return sprintf(
+////			'<%s class="loop">%s</%s>',
+////			$tagName,
+////			$this->innerBlocks($block),
+////			$tagName
+////		);
+//		return $this->innerBlocks($block);
+//	}
 
     //core_query_no_results
+	public function prerender_core_query_no_results(array $block, ?string $content):?string
+	{
+		if (!static::$currentLoop || static::$currentLoop->have_posts()) {
+			return '';
+		}
+
+		$inside = $this->innerBlocks($block);
+		return empty($inside) ? '' : sprintf(
+			'<div%s>%s</div>',
+			$this->getClassesAndStyles($block['attrs']??[], ['no-results']),
+			$inside
+		);
+	}
     //core_query_pagination
+	public function prerender_core_query_pagination(array $block, ?string $content):?string
+	{
+		return sprintf(
+			'<nav%s>%s</nav>',
+			$this->getClassesAndStyles($block['attrs']??[], ['pagination', 'condensed','btw']),
+			$this->innerBlocks($block)
+		);
+	}
+
     //core_query_pagination_next
+
+	public function prerender_core_query_pagination_next(array $block, ?string $content, ?WP_Block $parent):?string
+	{
+		if (!static::$currentLoop) return '';
+
+		$currentPage = $this->getCurrentPage();
+		$maxPages = static::$currentLoop->max_num_pages;
+
+		if ($currentPage >= $maxPages) return '';
+
+		$nextLabel = $rArrow = '';
+		$type = get_post_type_object(get_post_type())->label;
+		if ($parent) {
+			$attrs = $parent->attributes;
+			if (array_key_exists('paginationArrow', $attrs)){
+				$rArrow = match($attrs['paginationArrow']) {
+					'chevron'	=> jvbIcon('caret-circle-right'),
+					default => jvbIcon('arrow-circle-right')
+				};
+			}
+			if (!array_key_exists('showLabel', $attrs) || $attrs['showLabel'] === true) {
+
+				$nextLabel = 'Next '.$type;
+			}
+		} else {
+			$rArrow = jvbIcon('caret-circle-right');
+		}
+
+		$aOpen = sprintf(
+			'<a class="nav next" href="%s" title="Next %s">',
+			$this->buildPaginationUrl($currentPage + 1),
+			$type
+		);
+		$aClose = '</a>';
+		return sprintf(
+			'%s%s%s%s',
+			$aOpen,
+			$nextLabel,
+			$rArrow,
+			$aClose
+		);
+	}
     //core_query_pagination_numbers
+	public function prerender_core_query_pagination_numbers(array $block, ?string $content):?string
+	{
+		if (!static::$currentLoop) return '';
+		$currentPage = $this->getCurrentPage();
+		$maxPages = (int)static::$currentLoop->max_num_pages;
+
+		$attrs = $block['attrs']??[];
+		if ($maxPages <= 1) return '';
+
+		$midSize = $attrs['midSize'] ?? 2;
+		$endSize = 1;
+
+
+		$items = '';
+		$gap = false;
+		for ($i = 1; $i <= $maxPages; $i++) {
+			if (($i <= min($endSize + 1, $maxPages)) ||
+				($i >= max(1, $currentPage - $midSize) && $i <= min($maxPages, $currentPage + $midSize)) ||
+				($i >= max(1, $maxPages - $endSize) && $i <= $maxPages)) {
+				$gap = true;
+				$items .= ($i === $currentPage)
+					? sprintf('<li aria-current="page" class="current">%d</li>', $i)
+					: sprintf('<li><a href="%s">%d</a></li>', $this->buildPaginationUrl($i), $i);
+			} elseif ($gap) {
+				$gap = false;
+				$items .= sprintf(
+					'<li class="dots"><span>%s</span></li>',
+					jvbIcon('dots-three')
+				);
+			}
+		}
+
+		return sprintf('<ul%s>%s</ul>',
+			$this->getClassesAndStyles($attrs, ['row', 'nowrap']),
+			$items
+		);
+	}
     //core_query_pagination_previous
 
+	public function prerender_core_query_pagination_previous(array $block, ?string $content, ?WP_Block $parent):?string
+	{
+
+		if (!static::$currentLoop) return '';
+
+		$currentPage = $this->getCurrentPage();
+		$maxPages = static::$currentLoop->max_num_pages;
+
+		if ($currentPage <= 1) return '';
+
+		$nextLabel = $rArrow = '';
+		$type = get_post_type_object(get_post_type())->label;
+		if ($parent) {
+			$attrs = $parent->attributes;
+			if (array_key_exists('paginationArrow', $attrs)){
+				$rArrow = match($attrs['paginationArrow']) {
+					'chevron'	=> jvbIcon('caret-circle-left'),
+					default => jvbIcon('arrow-circle-left')
+				};
+			}
+			if (!array_key_exists('showLabel', $attrs) || $attrs['showLabel'] === true) {
+
+				$nextLabel = 'Previous '.$type;
+			}
+		} else {
+			$rArrow = jvbIcon('caret-circle-left');
+		}
+
+		$aOpen = sprintf(
+			'<a class="nav prev" href="%s" title="Previous %s">',
+			$this->buildPaginationUrl($currentPage - 1),
+			$type
+		);
+		$aClose = '</a>';
+		return sprintf(
+			'%s%s%s%s',
+			$aOpen,
+			$nextLabel,
+			$rArrow,
+			$aClose
+		);
+	}
+
 	public function prerender_core_query_title(array $block, ?string $content, ?WP_Block $parent):?string
 	{
-		jvbDump($block);
+		jvbDump($block, 'query title');
 		$attr = $block['attrs'];
 		$name = '';
 		$showPrefix = $attr['showPrefix']??false;
@@ -1050,7 +1619,7 @@
 					$before = apply_filters('jvbAboveHeader', '');
 					if (!empty($before)) {
 						$before = sprintf(
-							'<aside class="pre header row btw">%s</aside>',
+							'<aside class="pre header row x-btw">%s</aside>',
 							$before
 						);
 					}
@@ -1059,7 +1628,7 @@
 					$after = apply_filters('jvbBelowHeader', $after);
 					if (!empty($after)) {
 						$after = sprintf(
-							'<aside class="sub header row btw">%s</aside>',
+							'<aside class="sub header row x-btw">%s</aside>',
 							$after
 						);
 					}
@@ -1114,14 +1683,14 @@
 //
 //				$beforeHeader = apply_filters('jvbAboveHeader', $beforeHeader);
 //				if ($beforeHeader !== '') {
-//					$beforeHeader = '<aside class="pre header row btw">'.$beforeHeader.'</aside>';
+//					$beforeHeader = '<aside class="pre header row x-btw">'.$beforeHeader.'</aside>';
 //				}
 //                $themeSwitch = jvbDarkModeToggle();
 //                $breadcrumbs = BreadcrumbManager::getInstance()->renderNavigation();
 //				$afterHeader = apply_filters('jvbBelowHeader', $afterHeader);
 //
 //				if ($afterHeader !== '') {
-//					$afterHeader = '<aside class="sub header row btw">'.$afterHeader.'</aside>';
+//					$afterHeader = '<aside class="sub header row x-btw">'.$afterHeader.'</aside>';
 //				}
 //				$footerText = '<div class="scroll-progress"><div class="bar"></div>
 //</div>';
@@ -1149,21 +1718,325 @@
      * Widgets Blocks
      */
     //core_archives
+	public function render_core_archives(array $block, string $content):string
+	{
+		jvbDump($block, 'archives');
+		$attrs = $block['attrs']??[];
+		$isDropdown = $this->checkAttrs('displayAsDropdown', $attrs);
+
+		$replace = strtok($content,'>').'>';
+		$content = str_replace($replace, '', $content);
+
+		if ($isDropdown) {
+			$content = sprintf(
+				'<div%s>%s',
+				$this->getClassesAndStyles($attrs, ['archive dropdown']),
+				$content
+			);
+		} else {
+			$content = sprintf(
+				'<ul%s>%s',
+				$this->getClassesAndStyles($attrs, ['archive-list']),
+				$content
+			);
+		}
+
+		return $content;
+	}
     //core_calendar
+	public function render_core_calendar(array $block, string $content):string
+	{
+		$content = $this->inside($block, false, $content);
+		$replace = strtok($content, '>').'>';
+		$content = str_replace($replace, '', $content);
+		return sprintf(
+			'<table%s>%s',
+			$this->getClassesAndStyles($block['attrs']??[], ['calendar']),
+			$content
+		);
+	}
     //core_categories
+	public function prerender_core_categories(array $block, ?string $content, ?WP_Block $parent):?string
+	{
+		$attrs = $block['attrs']??[];
+		$args = [
+			'taxonomy'		=> 'category',
+			'hide_empty'	=> !$this->checkAttrs('showEmpty', $attrs)
+		];
+
+		$showHierarchy = $this->checkAttrs('showHierarchy', $attrs);
+		if ($this->checkAttrs('showOnlyTopLevel', $attrs) || $showHierarchy){
+			$args['parent'] = 0;
+		}
+
+		$terms = $this->getTerms($args, $showHierarchy);
+		if (!$terms){
+			return '';
+		}
+
+
+		$showPostCounts = $this->checkAttrs('showPostCounts', $attrs);
+		$isDropdown = $this->checkAttrs('displayAsDropdown', $attrs);
+
+		if ($isDropdown) {
+			$this->counter('core_categories');
+		}
+
+		$tax = get_taxonomy($args['taxonomy']);
+		$taxonomyName = $tax->label??'Categories';
+		$taxonomySingular = $tax->labels->singular_name??'Category';
+		$inner = $this->buildTermList($terms, $taxonomyName, $taxonomySingular, $isDropdown, $showPostCounts);
+		if ($isDropdown) {
+			return sprintf(
+				'<div%s>%s</div>',
+				$this->getClassesAndStyles($attrs, ['taxonomy-dropdown']),
+				$inner
+			);
+		}
+		return sprintf(
+			'<ul%s>%s</ul>',
+			$this->getClassesAndStyles($attrs, ['taxonomy-list', jvbNoBase($args['taxonomy'])]),
+			$inner
+		);
+	}
+		public function getTerms(array $args, bool $showHierarchy = false):array|false
+		{
+			$terms = get_terms($args);
+			if (!$terms || is_wp_error($terms)) {
+				return false;
+			}
+			$terms = array_map(function ($term) {
+				return (array) $term;
+			}, $terms);
+
+			if ($showHierarchy) {
+				$terms = array_map(function ($term) use ($args) {
+					$args['parent'] = $term['term_id'];
+					$children = $this->getTerms($args, true);
+					$term['children'] = $children?:[];
+					return $term;
+				}, $terms);
+			}
+
+			return $terms;
+		}
+		protected function buildTermList(array $terms, string $taxonomyName, string $taxonomySingular, bool $isDropdown, bool $showPostCounts, bool $isOpening = true, int $level = 0):string
+		{
+			$out = '';
+			if ($isOpening) {
+				$out = $isDropdown ?
+					sprintf(
+						'<label for="taxonomy-select-%s">%s</label>
+						<select name="%s_name" id="taxonomy-select-%s"><option value="">Select %s</option>',
+						static::$counters['core_categories'],
+						$taxonomyName,
+						str_replace('-', '_',sanitize_title(strtolower($taxonomyName))),
+						static::$counters['core_categories'],
+						$taxonomyName
+					) :
+					'';
+			} elseif (!$isDropdown) {
+				$out .= '<ul>';
+			}
+
+
+			$prefix = '';
+			if ($isDropdown) {
+				$base = '&emsp;';
+				for ($i = 1; $i <= $level; $i++) {
+					$prefix .= $base;
+				}
+				$prefix .= empty($prefix) ? '' : '- ';
+			}
+
+			$theTerms = array_map(function ($term) use ($taxonomyName, $taxonomySingular, $isDropdown, $showPostCounts, $prefix, $level) {
+				if ($isDropdown) {
+					return sprintf(
+						'<option value="%s">%s%s%s</option>%s',
+						$term['slug'],
+						$prefix,
+						$term['name'],
+						$showPostCounts ? ' ('.$term['count'].')' : '',
+						empty($term['children']??[]) ? '' : $this->buildTermList($term['children'], $taxonomyName, $taxonomySingular, $isDropdown, $showPostCounts, false, $level+1)
+					);
+				}
+				return sprintf(
+					'<li><a href="%s">%s%s</a>%s</li>',
+					get_term_link($term['term_id']),
+					$term['name'],
+					$showPostCounts ? ' <span class="count">'.$term['count'].'</span>' : '',
+					empty($term['children']??[]) ? '' : $this->buildTermList($term['children'], $taxonomyName, $taxonomySingular, $isDropdown, $showPostCounts, false, $level+1)
+				);
+			}, $terms);
+
+			$out .= implode('', $theTerms);
+
+			if ($isOpening) {
+				$out .= $isDropdown ?
+					'</select>' :
+					'';
+			} else if (!$isDropdown) {
+				$out .= '</ul>';
+			}
+
+
+			return $out;
+		}
     //core_html
     //core_latest_comments
     //core_latest_posts
+	public function prerender_core_latest_posts(array $block, ?string $content, ?WP_Block $parent):?string {
+		$attrs = $block['attrs']??[];
+//		jvbDump($block, 'latest posts');
+
+		$args = [];
+		$title = 'Latest Posts';
+		$args['order'] = array_key_exists('order', $attrs) ? strtoupper($attrs['order']) : 'DESC';
+		$args['orderby'] = array_key_exists('orderBy', $attrs) ? $attrs['orderBy'] : 'date';
+		$args['posts_per_page'] = array_key_exists('postsToShow', $attrs) ? $attrs['postsToShow'] : 5;
+
+		if (array_key_exists('categories', $attrs)) {
+			$list = jvbCommaList(array_column($attrs['categories'], 'name'));
+			$args['tax_query'] = [];
+			$args['tax_query'][] = [
+				'taxonomy'	=> 'category',
+				'terms'		=> array_column($attrs['categories'], 'id')
+			];
+			$title .= ' in '.$list;
+		}
+
+		$posts = new WP_Query($args);
+
+		if (!$posts->have_posts()) {
+			return '';
+		}
+		$posts = array_map(function ($post) use ($attrs) {
+			$img = $this->checkAttrs('displayFeaturedImage', $attrs)
+				? $this->image(get_post_thumbnail_id($post->ID), 'tiny', 'thumbnail')
+				: '';
+
+			$author = $this->checkAttrs('displayAuthor', $attrs)
+				? sprintf(
+					'<a href="%s">%s</a>',
+					get_author_posts_url($post->post_author),
+					get_userdata($post->post_author)->display_name
+				)
+				: '';
+
+			$date = $this->checkAttrs('displayPostDate', $attrs)
+				? sprintf(
+					'<time datetime="%s">%s</time>',
+					date('Y-m-d', strtotime($post->post_date)),
+					date_i18n('M j, Y', strtotime($post->post_date))
+				)
+				: '';
+			$authorDate = $author;
+			if (!empty($authorDate) && !empty($date)) {
+				$authorDate .= ' | '.$date;
+			} else if (!empty($date)) {
+				$authorDate = $date;
+			}
+
+			$excerpt = '';
+			if ($this->checkAttrs('displayPostContent', $attrs)) {
+				if (array_key_exists('excerptLength', $attrs)) {
+					$excerpt = wp_trim_words(get_the_content($post->ID), $attrs['excerptLength'], '...');
+				} else {
+					$excerpt = get_the_excerpt($post->ID);
+				}
+			}
+			if (!empty($excerpt)) {
+				$excerpt = wpautop($excerpt);
+			}
+
+			return sprintf(
+				'<li>%s<p><a href="%s">%s</a>%s</p>%s</li>',
+				$img,
+				get_the_permalink($post->ID),
+				$post->post_title,
+				!empty($authorDate) ? ' <small>— '.$authorDate.'</small>' : '',
+				$excerpt
+			);
+		}, $posts->posts);
+
+		wp_reset_postdata();
+		return sprintf(
+			'<ul%s>%s</ul>',
+//			$title,
+			$this->getClassesAndStyles($attrs, ['post-list']),
+			implode('', $posts)
+		);
+	}
     //core_page_list
-    //core_page_list_item
-    //core_rss
+	public function prerender_core_page_list(array $block, ?string $content, ?WP_Block $parent):?string{
+		$attrs = $block['attrs']??[];
+		$parent = array_key_exists('parentPageID', $attrs) ? $attrs['parentPageID'] : 0;
+		$pages = new WP_Query([
+			'post_type'			=> 'page',
+			'posts_per_page'	=> -1,
+			'parent'			=> $parent
+		]);
+
+		if (!$pages->have_posts()) {
+			return '';
+		}
+		$inside = [];
+		foreach($pages->posts as $page) {
+			jvbDump($page);
+			$inside[] = sprintf(
+				'<li><a href="%s">%s</a>',
+				get_the_permalink($page->ID),
+				$page->post_title
+			);
+		}
+		wp_reset_postdata();
+		return sprintf(
+			'<ul%s>%s</ul>',
+			$this->getClassesAndStyles($attrs, ['page-list']),
+			implode('',$inside)
+		);
+	}
+    //core_page_list_item (doesn't seem to be a thing)
+//	public function prerender_core_page_list_item(array $block, ?string $content, ?WP_Block $parent):?string{
+//		return $content;
+//	}
+    //core_
+//	public function prerender_core_rss(array $block, ?string $content, ?WP_Block $parent):?string
+//	{
+//		jvbDump($block, 'rss');
+//		return $content;
+//	}
     //core_search
+	public function prerender_core_search(array $block, ?string $content, ?WP_Block $parent):?string
+	{
+//		jvbDump($block, 'search');
+		$attrs = $block['attrs']??[];
+		$label = array_key_exists('label', $attrs) && !empty($attrs['label']) ? $attrs['label'] : '';
+		if (array_key_exists('showLabel', $attrs) && $attrs['showLabel'] === false) {
+			$label = '';
+		}
+		$placeholder = array_key_exists('placeholder', $attrs) ? $attrs['placeholder'] : 'Search...';
+
+		$buttonText = array_key_exists('buttonText', $attrs) && !empty($attrs['buttonText']) ? $attrs['buttonText'] : '';
+
+		$isInside = array_key_exists('buttonPosition', $attrs) && $attrs['buttonPosition'] === 'button-inside';
+
+		$hideInput = $this->checkAttrs('isSearchFieldHidden', $attrs) || (array_key_exists('buttonPosition', $attrs) && $attrs['buttonPosition'] === 'button-only');
+
+		return str_replace('<div class="search-container row left nowrap"', sprintf(
+			'<div%s',
+			$this->getClassesAndStyles($attrs, ['search-container', 'row', 'left', 'nowrap'])
+		), jvbSearch($placeholder, uniqid(), $label, $buttonText, $isInside, $hideInput));
+	}
     //core_shortcode
 	public function prerender_core_social_link(array $block, ?string $content, ?WP_Block $parent):?string
 	{
 //		jvbDump($block, 'social link');
 //		jvbDump($parent, 'Parent');
-
+		$parentAttrs = false;
+		if ($parent) {
+			$parentAttrs = $parent->attributes;
+		}
 		$attrs = $block['attrs']??[];
 		$url = $attrs['url']??'';
 		$service = $attrs['service']?:'';
@@ -1172,16 +2045,100 @@
 		if (!$icon) {
 			$icon = jvbIcon('link');
 		}
-		return '<li><a href="'.$url.'" target="_blank" rel="nofollow" title="Find us on '.ucfirst($service).'">'.$icon.'<span class="screen-reader-text">Find us on '.ucfirst($service).'</span></a></li>';
+		$serviceName = $this->getServiceName($service);
+		$label = $parentAttrs && (!array_key_exists('className', $parentAttrs) || !str_contains($parentAttrs['className'], 'logos-only'))
+				? sprintf(
+					'<span>%s</span>',
+					$serviceName
+				)
+				: sprintf(
+					'<span class="screen-reader-text">Find us on %s</span>',
+				$serviceName
+			);
+		$pillShaped = $parentAttrs && (array_key_exists('className', $parentAttrs) && str_contains($parentAttrs['className'], 'pill-shape'))
+			? 'style="border-radius:var(--radius-outer);"'
+			: '';
+		return sprintf(
+			'<li><a href="%s" target="_blank" rel="nofollow" title="Find us on %s"%s>%s%s</a></li>',
+			$url,
+			$serviceName,
+			$pillShaped,
+			$icon,
+			$label
+		);
 	}
+		private function getServiceName(string $service) {
+			return match($service){
+				'wordpress'	=> 'WordPress',
+				default => ucfirst($service)
+			};
+		}
 	public function prerender_core_social_links(array $block, ?string $content, ?WP_Block $parent):?string
 	{
-//		jvbDump($block, 'social links');
+//		jvbDump($block['attrs']??[], 'social links');
 //		jvbDump($parent, 'Parent');
 
-		return '<ul class="socials">'.$this->innerBlocks($block).'</ul>';
+		return sprintf(
+			'<ul%s>%s</ul>',
+			$this->getClassesAndStyles($block['attrs']??[], ['socials']),
+			$this->innerBlocks($block, '','',$block)
+		);
 	}
     //core_tag_cloud
+	public function prerender_core_tag_cloud(array $block, ?string $content, ?WP_Block $parent):?string
+	{
+//		jvbDump($block, 'tag cloud');
+		$attrs = $block['attrs']??[];
+		$taxonomy = (array_key_exists('taxonomy', $attrs) && !empty($attrs['taxonomy']))
+			? $attrs['taxonomy']
+			: 'post_tag';
+		$showCounts = $this->checkAttrs('showTagCounts', $attrs);
+
+		$terms = get_terms([
+			'taxonomy'		=> $taxonomy,
+			'hide_empty'	=> true,
+		]);
+
+		if (!$terms || is_wp_error($terms)) {
+			return '';
+		}
+
+		$inside = '';
+
+		foreach ($terms as $term) {
+			$url = get_term_link($term->term_id, $taxonomy);
+			$count = $showCounts ?
+				sprintf(
+					'<span class="count">%d</span>',
+					$term->count
+				) :
+				'';
+			$size = match(true) {
+				$term->count <= 2 => 'small',
+				$term->count <= 5 => 'x-small',
+				$term->count <= 10 => 'medium',
+				$term->count <= 15 => 'x-medium',
+				$term->count <= 20 => 'large',
+				$term->count <= 25 => 'x-large',
+				$term->count <= 30 => 'xx-large',
+				$term->count > 30 => 'xxx-large',
+			};
+			$fontSize = 'font-size: var(--txt-'.$size.');';
+			$inside .= sprintf(
+				'<li class="%s");"><a href="%s" rel="tag">%s%s</a></li>',
+				$size,
+//				$fontSize,
+				$url,
+				$term->name,
+				$count
+			);
+		}
+		return sprintf(
+			'<ul%s>%s</ul>',
+			$this->getClassesAndStyles($attrs, ['term-list','cloud', jvbNoBase($taxonomy)]),
+			$inside
+		);
+	}
 
 
     /**
@@ -1208,13 +2165,21 @@
 		return trim($clean);
 	}
 
-    public function innerBlocks(array $block, string $before = '', string $after = '', bool $prerender = true):string
+    public function innerBlocks(array $block, string $before = '', string $after = '', ?array $parent = null):string
     {
+		if ($parent) {
+			$parent = new WP_Block($parent);
+		}
 		$content = '';
 		foreach ($block['innerBlocks'] as $b) {
+
+			$rendered = $parent
+				? $this->checkMethods(null, $b, $parent, true)
+				: render_block($b);
+
 			$content .= sprintf('%s%s%s',
 				$before,
-				render_block($b),
+				$rendered,
 				$after
 			);
 		}
@@ -1392,15 +2357,36 @@
 			$classes[] = 'col';
 		}
 
+
         // Merge with passed classes and styles
         $styles = array_merge($attr_styles, $styles);
         $classes = array_merge($attr_classes, $classes);
 
+		if (!empty(static::$pendingClass)) {
+			$classes = array_merge($classes, static::$pendingClass);
+			static::$pendingClass = [];
+		}
+		$classes = array_unique($classes);
+		$data = $this->getDataset($attrs);
+
         // Build attribute strings
         $class_string = !empty($classes) ? ' class="' . implode(' ', $classes) . '"' : '';
         $style_string = !empty($styles) ? ' style="' . implode(';', $styles) . '"' : '';
+		$data_string = '';
+		if (!empty($data)) {
+			foreach ($data as $d => $v) {
+				if ($d === 'bg-small') {
+					$data_string .= ' data-bg-img';
+				}
+				$data_string .= sprintf(
+					' data-%s="%s"',
+					$d,
+					$v
+				);
+			}
+		}
 
-        $return = trim($class_string . $style_string);
+        $return = trim($class_string . $style_string . $data_string);
         return ($return=='')? '' : ' '.$return;
     }
     /**
@@ -1430,18 +2416,18 @@
         $classes = [];
         foreach ($attrs as $key => $value) {
             $class = $this->getClass($key, $value, $attrs);
-            if (is_array($class)) {
-                $classes = array_merge($classes, $class);
-            } else {
-                $classes[] = $class;
-            }
+			if (is_string($class)) {
+				$class = explode(' ', $class);
+			}
+			$classes = array_merge($classes, $class);
         }
-		return array_filter($classes, function ($class) {
+		return array_unique(array_filter($classes, function ($class) {
 			return $class!=='' && !str_starts_with($class, 'wp');
-		});
+		}));
     }
     protected function getClass(string $key, string|bool|array|int $value, array $attrs):string|array
     {
+		//TODO: gradient
         switch ($key) {
             //Any additional classes the user adds
             case 'className':
@@ -1451,112 +2437,38 @@
                     default => str_replace('is-style-', '', $value),
                 };
 			case 'contentPosition':
-
-				$classes = [];
-				$pos = explode(' ', $value);
-				foreach($pos as $p) {
-					switch ($p) {
-						case 'top':
-							$classes[] = 'a-start';
-							break;
-						case 'right':
-							$classes[] = 'end';
-							break;
-						case 'bottom':
-							$classes[] = 'a-end';
-							break;
-						case 'left':
-							$classes[] = 'start';
-							break;
-					}
-				}
-				return implode(' ', $classes);
+				return $this->getContentPosition($value);
+			case 'term':
+			case 'taxonomy':
+				return jvbNoBase($value);
             //Layout attributes
             case 'layout':
-                $classes = [];
-				$type = 'row';
-                if (array_key_exists('type', $value)) {
-					$type = 'col';
-//                    if ($value['type'] === 'constrained') {
-//                        $classes[] = 'container col';
-//                    }
-                }
-				if (array_key_exists('orientation', $value)) {
-					$type = 'col';
-                    if ($value['orientation'] === 'vertical') {
-						$classes[] = 'col';
-						if (in_array('row', $classes)) {
-							$index = array_search('row', $classes);
-							unset($classes[$index]);
-						}
-					}
-                }else if (array_key_exists('type', $value) && $value['type'] === 'flex') {
-					$classes[] = 'row';
-					if (in_array('col', $classes)) {
-						$index = array_search('col', $classes);
-						unset($classes[$index]);
-					}
-				}
-//jvbDump($type);
-//jvbDump($value);
-//				$check = [$value, $attrs];
-//				foreach ($check as $ch) {
-//
-//				}
-				if (!array_key_exists('justifyContent', $value) && !array_key_exists('contentPosition', $attrs)) {
-					$classes[] = ($type === 'row') ? 'start' : 'a-start';
-				}
-				if (array_key_exists('justifyContent', $value)  && !array_key_exists('contentPosition', $attrs)) {
-					if (in_array($value['justifyContent'], ['left', 'right','space-between'])) {
-//						jvbDump($type);
-						switch ($value['justifyContent']) {
-							case 'right':
-								$classes[] = ($type === 'row') ? 'end' : 'a-end';
-								break;
-							case 'space-between':
-								$classes[] = 'btw';
-								break;
-						}
-					}
-				}
-
-
-                if (array_key_exists('flexWrap', $value)) {
-                    if ($value['flexWrap'] === 'nowrap') {
-                        $classes[] = 'nowrap';
-                    }
-                }
-                return implode(' ', $classes);
+                return $this->getLayout($value, $attrs);
             case 'align':
                 return !empty($value) ? 'align-'.$value : '';
             case 'verticalAlignment':
-                return !empty($value) ? 'v-align-'.$value : '';
-            case 'isStackedMobile':
+				switch ($value) {
+					case 'bottom':
+						$value = 'btm';
+						break;
+					case 'center':
+						$value = 'y-mid';
+					default:
+				}
+                return !empty($value) ? $value : '';
+            case 'isStackedOnMobile':
                 return ($value === true) ? 'stack-small' : '';
             case 'justifyContent':
                 return !empty($value) ? 'j-'.$value : '';
             case 'orientation':
                 return $value==='column' ? 'column' : '';
             case 'width':
+				return $this->getWidth($value);
             case 'dimRatio':
-                if (is_numeric($value)) {
-                    $width = match (true) {
-                        $value < 25 => '25',
-                        $value < 33 => '33',
-                        $value <= 50 => '50',
-                        $value < 66 => '66',
-                        $value < 75 => '75',
-                        default => 'full',
-                    };
-                    switch ($key) {
-                        case 'width':
-                            return 'width-'.$width;
-                        case 'dimRatio':
-                            return 'overlay-'.$width;
-                    }
-                }
-                return '';
-
+                return $this->getDimRatio($value);
+			case 'overlayColor':
+				return $value;
+				break;
             //Typography
             case 'textAlign':
                 return !empty($value) ? 'text-'.$value : '';
@@ -1571,171 +2483,285 @@
 
             //Style base:
             case 'style':
-                $classes = [];
-                //Margin and Padding
-				if (array_key_exists('spacing', $value)) {
-					foreach (['margin' => 'm', 'padding'=>'p'] as $search => $c) {
-						if (array_key_exists($search, $value['spacing'])) {
-							$directions = [];
-
-							// Collect ONLY preset spacing values for classes
-							foreach ($value['spacing'][$search] as $direction => $size) {
-								$presetSize = $this->getPresetSpacing($size);
-								if ($presetSize) {
-									$directions[$direction] = $presetSize;
-								}
-								// Non-preset values are skipped here and handled by inline styles below
-							}
-
-							if (empty($directions)) {
-								continue;
-							}
-
-							// Check what directions we have
-							$hasTop = isset($directions['top']);
-							$hasBottom = isset($directions['bottom']);
-							$hasLeft = isset($directions['left']);
-							$hasRight = isset($directions['right']);
-
-							// Check if axes match
-							$xMatch = $hasLeft && $hasRight && $directions['left'] === $directions['right'];
-							$yMatch = $hasTop && $hasBottom && $directions['top'] === $directions['bottom'];
-
-							// All 4 directions exist and match → p-3
-							if ($hasTop && $hasBottom && $hasLeft && $hasRight &&
-								count(array_unique($directions)) === 1) {
-								$classes[] = $c . '-' . reset($directions);
-							}
-							// Both axes match → px-3 py-2
-							elseif ($xMatch && $yMatch) {
-								$classes[] = $c . 'x-' . $directions['left'];
-								$classes[] = $c . 'y-' . $directions['top'];
-							}
-							// Only X axis matches → px-3 (+ individual for top/bottom)
-							elseif ($xMatch) {
-								$classes[] = $c . 'x-' . $directions['left'];
-								if ($hasTop) {
-									$classes[] = $c . 't-' . $directions['top'];
-								}
-								if ($hasBottom) {
-									$classes[] = $c . 'b-' . $directions['bottom'];
-								}
-							}
-							// Only Y axis matches → py-3 (+ individual for left/right)
-							elseif ($yMatch) {
-								$classes[] = $c . 'y-' . $directions['top'];
-								if ($hasLeft) {
-									$classes[] = $c . 'l-' . $directions['left'];
-								}
-								if ($hasRight) {
-									$classes[] = $c . 'r-' . $directions['right'];
-								}
-							}
-							// No matches - individual directions
-							else {
-								foreach ($directions as $direction => $size) {
-									$dir = match($direction) {
-										'top' => 't',
-										'bottom' => 'b',
-										'left' => 'l',
-										'right' => 'r',
-										default => $direction
-									};
-									$classes[] = $c . $dir . '-' . $size;
-								}
-							}
-						}
-					}
-				}
-
-                if (array_key_exists('fontSize', $value)) {
-                    if (in_array($value['fontSize'], ['small', 'large', 'extra-large', 'huge'])) {
-                        $classes[] = 'font-'.$value['fontSize'];
-                    }
-                    if (in_array('fontWeight', $value)) {
-                        $classes[] = 'text-'.$value['fontWeight'];
-                    }
-                    if (in_array('textTransform', $value)) {
-                        if (in_array($value['textTransform'], ['uppercase', 'capitalize', 'lowercase'])) {
-                            $classes[] = $value['textTransform'];
-                        }
-                    }
-                }
-                return implode(' ', $classes);
+                return $this->getPresetStyles($value);
 			case 'fontSize':
 				$classes[] = 'font-'.$value;
 				return implode(' ', $classes);
-			case 'isStackedOnMobile':
-				return ($value === true) ? 'stack-small' : '';
-			case 'width':
-				if (is_numeric($value)) {
-					$width = match (true) {
-						$value < 25 => '25',
-						$value < 33 => '33',
-						$value <= 50 => '50',
-						$value < 66 => '66',
-						$value < 75 => '75',
-						default => 'full',
-					};
-					switch ($key) {
-						case 'width':
-							return 'width-'.$width;
-						case 'dimRatio':
-							return 'overlay-'.$width;
-					}
+			case 'postLayout':
+				$classes[] = 'item-grid';
+				if (isset($attrs['columns']) && $attrs['columns']!== 3){
+					$classes[] = sprintf(
+						'split-%d',
+						$attrs['columns']
+					);
 				}
-				return '';
+				return $classes;
             default:
-				$ignore = [
-					'useFeaturedImage',
-					'opacity',
-					'borderColor',
-					'backgroundColor',
-					'textColor',
-					'minHeight',
-					'minHeightUnit',
-					'isDark',
-					'sizeSlug',
-					'isUserOverlayColor',
-					'customOverlayColor',
-					'dimRatio',
-					'placeholder',
-					'alt',
-					'imageFill',
-					'mediaSizeSlug',
-					'isLink',
-					'kind',
-					'label',
-					'type',
-					'id',
-					'url',
-					'label',
-					'shouldSyncIcon',
-					'rel',
-					'opensInNewTab',
-					'title',
-					'ref',
-					'overlayMenu',
-					'slug',
-					'theme',
-					'tagName',
-					'level',
-					'ordered',
-					'area',
-					'mediaId',
-					'mediaLink',
-					'mediaType',
-					'height', //maybe still need?
-				];
-				if (!is_admin() &&!in_array($key, $ignore)) {
+				if (JVB_TESTING && !is_admin() &&!in_array($key, $this->ignore)) {
 //					TESTING
-//					jvbDump($key, 'getClass');
-//					jvbDump($attrs);
+					jvbDump($attrs, '[getClass] '.$key);
 				}
 
                 return '';
         }
     }
+		/*** CLASS HELPERS ***/
+		private function getContentPosition(string $value):string
+		{
+			$classes = [];
+			$pos = explode(' ', $value);
+			foreach($pos as $p) {
+				switch ($p) {
+					case 'top':
+						$classes[] = 'top';
+						break;
+					case 'right':
+						$classes[] = 'right';
+						break;
+					case 'bottom':
+						$classes[] = 'btm';
+						break;
+					case 'left':
+						$classes[] = 'left';
+						break;
+				}
+			}
+			return implode(' ', $classes);
+		}
+
+		private function getLayout(array $value, array $attrs):array
+		{
+//			jvbDump($value, 'getLayout');
+			$classes = [];
+
+			$type = 'row';
+			$isRow = true;
+			//Determine type
+			if ((array_key_exists('type', $value) && !in_array($value['type'], ['flex', 'grid'])) ||
+				(array_key_exists('orientation', $value) && $value['orientation'] === 'vertical')) {
+				$type = 'col';
+				$isRow = false;
+			} elseif (array_key_exists('type', $value) && $value['type'] === 'grid') {
+				$type = 'item-grid';
+				$isRow = false;
+				if (array_key_exists('columnCount', $value) && $value['columnCount']!== 3) {
+					$classes[] = sprintf(
+						'split-%s',
+						$value['columnCount']
+					);
+				}
+			}
+
+			if (array_key_exists('justifyContent', $value)  && !array_key_exists('contentPosition', $attrs)) {
+				switch ($value['justifyContent']) {
+					case 'right':
+						$classes[] = 'right';
+						break;
+					case 'center':
+						$classes[] = 'x-mid';
+						break;
+					case 'space-between':
+						$classes[] = 'x-btw';
+						break;
+					case 'left':
+						$classes[] = 'left';
+						break;
+					case 'space-evenly':
+						$classes[] = 'x-even';
+						break;
+					case 'space-around':
+						$classes[] = 'x-around';
+						break;
+					case 'stretch':
+						$classes[] = 'stretch';
+				}
+			} else {
+				$classes[] = 'left';
+			}
+
+			if (array_key_exists('verticalAlignment', $value)) {
+				switch ($value['verticalAlignment']) {
+					case 'bottom':
+						$classes[] = 'btm';
+						break;
+					case 'top':
+						$classes[] = 'top';
+						break;
+					case 'center':
+						$classes[] = 'y-mid';
+						break;
+					case 'space-between':
+						$classes[] = 'y-btw';
+						break;
+					case 'space-around':
+						$classes[] = 'y-around';
+						break;
+					case 'space-even':
+						$classes[] = 'y-even';
+				}
+			}
+
+
+
+			if (array_key_exists('flexWrap', $value)) {
+				if ($value['flexWrap'] === 'nowrap') {
+					$classes[] = 'nowrap';
+				}
+			}
+
+			$classes[] = $type;
+			return $classes;
+		}
+
+		private function getWidth(string $value):string
+		{
+			$value = (int)str_replace('%', '', $value);
+			return sprintf(
+				'width-%d',
+				match (true) {
+					$value <= 25 => '25',
+					$value <= 33 => '33',
+					$value <= 50 => '50',
+					$value <= 66 => '66',
+					$value <= 75 => '75',
+					default => 'full',
+				}
+			);
+		}
+		private function getDimRatio(string $value):string
+		{
+			if (is_numeric($value)) {
+				return sprintf(
+					'op-%d',
+					match (true) {
+						$value <= 14 => '1',
+						$value <= 28 => '2',
+						$value <= 42 => '3',
+						$value <= 56 => '45',
+						$value <= 70 => '4',
+						$value <= 84 => '5',
+						default => '6',
+					}
+				);
+			}
+			return '';
+		}
+
+		private function getPresetStyles(array $value):string
+		{
+			$classes = [];
+			//Margin and Padding
+			if (array_key_exists('spacing', $value)) {
+				$classes = array_merge($classes, $this->buildSpacingClasses($value));
+			}
+			if (array_key_exists('color', $value)) {
+				if (array_key_exists('duotone', $value['color'])) {
+					$preset = explode('|', $value['color']['duotone']);
+					$preset = $preset[array_key_last($preset)];
+					if (str_contains($preset, '-')) {
+						$preset = explode('-', $preset);
+					} else {
+						$preset = [$preset];
+					}
+					$classes[] = 'duotone';
+					foreach ($preset as $p) {
+						$classes[] = $p;
+					}
+				}
+			}
+
+			if (array_key_exists('fontSize', $value)) {
+				if (in_array($value['fontSize'], ['small', 'large', 'extra-large', 'huge'])) {
+					$classes[] = 'font-'.$value['fontSize'];
+				}
+				if (in_array('fontWeight', $value)) {
+					$classes[] = 'text-'.$value['fontWeight'];
+				}
+				if (in_array('textTransform', $value)) {
+					if (in_array($value['textTransform'], ['uppercase', 'capitalize', 'lowercase'])) {
+						$classes[] = $value['textTransform'];
+					}
+				}
+			}
+			return implode(' ', $classes);
+		}
+			private function buildSpacingClasses(array $value):array
+			{
+				$classes = [];
+				foreach (['margin' => 'm', 'padding'=>'p'] as $search => $c) {
+					if (array_key_exists($search, $value['spacing'])) {
+						$directions = [];
+
+						// Collect ONLY preset spacing values for classes
+						foreach ($value['spacing'][$search] as $direction => $size) {
+							$presetSize = $this->getPresetSpacing($size);
+							if ($presetSize) {
+								$directions[$direction] = $presetSize;
+							}
+							// Non-preset values are skipped here and handled by inline styles below
+						}
+
+						if (empty($directions)) {
+							continue;
+						}
+
+						// Check what directions we have
+						$hasTop = isset($directions['top']);
+						$hasBottom = isset($directions['bottom']);
+						$hasLeft = isset($directions['left']);
+						$hasRight = isset($directions['right']);
+
+						// Check if axes match
+						$xMatch = $hasLeft && $hasRight && $directions['left'] === $directions['right'];
+						$yMatch = $hasTop && $hasBottom && $directions['top'] === $directions['bottom'];
+
+						// All 4 directions exist and match → p-3
+						if ($hasTop && $hasBottom && $hasLeft && $hasRight &&
+							count(array_unique($directions)) === 1) {
+							$classes[] = $c . '-' . reset($directions);
+						}
+						// Both axes match → px-3 py-2
+						elseif ($xMatch && $yMatch) {
+							$classes[] = $c . 'x-' . $directions['left'];
+							$classes[] = $c . 'y-' . $directions['top'];
+						}
+						// Only X axis matches → px-3 (+ individual for top/bottom)
+						elseif ($xMatch) {
+							$classes[] = $c . 'x-' . $directions['left'];
+							if ($hasTop) {
+								$classes[] = $c . 't-' . $directions['top'];
+							}
+							if ($hasBottom) {
+								$classes[] = $c . 'b-' . $directions['bottom'];
+							}
+						}
+						// Only Y axis matches → py-3 (+ individual for left/right)
+						elseif ($yMatch) {
+							$classes[] = $c . 'y-' . $directions['top'];
+							if ($hasLeft) {
+								$classes[] = $c . 'l-' . $directions['left'];
+							}
+							if ($hasRight) {
+								$classes[] = $c . 'r-' . $directions['right'];
+							}
+						}
+						// No matches - individual directions
+						else {
+							foreach ($directions as $direction => $size) {
+								$dir = match($direction) {
+									'top' => 't',
+									'bottom' => 'b',
+									'left' => 'l',
+									'right' => 'r',
+									default => $direction
+								};
+								$classes[] = $c . $dir . '-' . $size;
+							}
+						}
+					}
+				}
+				return $classes;
+			}
 
     protected function getInlineStyles(array $attrs):array
     {
@@ -1745,258 +2771,74 @@
         $styles = [];
         foreach ($attrs as $key => $value) {
             $style = $this->getStyle($key, $value, $attrs);
+			if (is_string($style)) {
+				$style = [$style];
+			}
             $styles = array_merge($styles, $style);
         }
         return $styles;
     }
-    protected function getStyle(string $key, string|bool|array|int $value, array $attrs):array
+    protected function getStyle(string $key, string|bool|array|int $value, array $attrs):array|string
     {
         $styles = [];
         switch ($key) {
             // Font family settings
-            case 'fontFamily':
-                if ($value === 'body') {
-                    $styles[] = 'font-family: "Open Sans", system-ui, -apple-system, sans-serif';
-                } elseif ($value === 'heading') {
-                    $styles[] = 'font-family: "Josefin Sans", system-ui, -apple-system, sans-serif';
-                } elseif (!empty($value)) {
-                    $styles[] = 'font-family: '.$value;
-                }
-                break;
+			case 'size':
+				return $this->getIconSizeStyle($value);
+
+			case 'fontFamily':
+                return $this->getFontFamilyStyle($value);
 
             // Icon color (for icon blocks)
             case 'iconColorValue':
-                if (!empty($value)) {
-                    $styles[] = 'color: '.$value;
-                }
-                break;
+				return $this->getColorStyle($value);
 
             // Minimum height settings
             case 'minHeight':
-                if (!empty($value) && isset($attrs['minHeightUnit'])) {
-                    $styles[] = 'min-height: '.$value.$attrs['minHeightUnit'];
-                } elseif (!empty($value)) {
-                    $styles[] = 'min-height: '.$value.'px'; // Default to px if no unit specified
-                }
-                break;
+                return $this->getMinHeightStyle($value, $attrs);
 
             // Background URL (for cover, media blocks)
             case 'url':
                 if (!empty($value) && str_starts_with($value, 'http')) {
-                    $styles[] = 'background-image: url('.$value.')';
+                    return 'background-image: url('.$value.')';
                 }
                 break;
 
             // Focal point for background images
             case 'focalPoint':
-				$x = (array_key_exists('x', $attrs['focalPoint'])) ? $attrs['focalPoint']['x'] * 100 : 'center';
-				$y = (array_key_exists('y', $attrs['focalPoint'])) ? $attrs['focalPoint']['y'] * 100 : 'center';
-				$styles[] = 'background-position:'.$x.' '.$y.';';
-
-                break;
+				return $this->getFocalPointStyle($value);
 
             // Complex style object
             case 'style':
-                // Border styles
-                if (isset($value['border'])) {
-                    $border = $value['border'];
+                return $this->extractStyles($value, $attrs);
 
-                    if (isset($border['radius'])) {
-                        $styles[] = 'border-radius: '.$border['radius'];
-                    }
-
-                    if (isset($border['width'])) {
-                        $styles[] = 'border-width: '.$border['width'];
-                    }
-
-                    if (isset($border['style']) && isset($border['width']) && !empty($border['style'])) {
-                        $styles[] = 'border-style: '.$border['style'];
-                    }
-
-                    if (isset($border['color'])) {
-                        $styles[] = 'border-color: '.$border['color'];
-                    }
-                }
-
-                // Color styles
-                if (isset($value['color'])) {
-                    $color = $value['color'];
-
-                    if (isset($color['background'])) {
-                        $styles[] = 'background-color: '.$color['background'];
-                    }
-
-                    if (isset($color['text'])) {
-                        $styles[] = 'color: '.$color['text'];
-                    }
-
-                    if (isset($color['gradient'])) {
-                        $styles[] = 'background: '.$color['gradient'];
-                    }
-                }
-
-                // Layout styles
-                if (isset($value['layout'])) {
-                    foreach ($value['layout'] as $layout => $option) {
-                        switch ($layout) {
-                            case 'selfStretch':
-                                if ($option === 'fixed' && isset($value['layout']['selfStretchValue'])) {
-                                    $styles[] = 'width: '.$value['layout']['selfStretchValue'];
-                                }
-                                break;
-                        }
-                    }
-                }
-
-                // Typography styles
-                if (isset($value['typography'])) {
-                    $typography = $value['typography'];
-
-                    if (isset($typography['fontSize'])) {
-                        $styles[] = 'font-size: '.$typography['fontSize'];
-                    }
-
-                    if (isset($typography['fontWeight'])) {
-                        $styles[] = 'font-weight: '.$typography['fontWeight'];
-                    }
-
-                    if (isset($typography['textDecoration'])) {
-                        $styles[] = 'text-decoration: '.$typography['textDecoration'];
-                    }
-
-                    if (isset($typography['textTransform'])) {
-                        $styles[] = 'text-transform: '.$typography['textTransform'];
-                    }
-
-                    if (isset($typography['letterSpacing'])) {
-                        $styles[] = 'letter-spacing: '.$typography['letterSpacing'];
-                    }
-
-                    if (isset($typography['lineHeight'])) {
-                        $styles[] = 'line-height: '.$typography['lineHeight'];
-                    }
-                }
-
-                // Spacing styles
-                if (isset($value['spacing'])) {
-                    $spacing = $value['spacing'];
-
-                    // Don't duplicate margin/padding that's handled by classes
-                    // Only add specific CSS values here that wouldn't work well as classes
-                    if (isset($spacing['margin'])) {
-                        foreach ($spacing['margin'] as $direction => $size) {
-                            // If not a preset value, add as inline style
-                            if (!str_contains($size, 'var:preset')) {
-                                $styles[] = 'margin-'.$direction.': '.$size;
-                            }
-                        }
-                    }
-
-                    if (isset($spacing['padding'])) {
-                        foreach ($spacing['padding'] as $direction => $size) {
-                            // If not a preset value, add as inline style
-                            if (!str_contains($size, 'var:preset')) {
-                                $styles[] = 'padding-'.$direction.': '.$size;
-                            }
-                        }
-                    }
-                }
-                break;
 			case 'dimRatio':
-				$ratio = (ceil($value /25) *25);
-				$s = 'background-color: rgba(var(--base-rgb), ';
-				switch ($ratio) {
-					case 0:
-						$s .= 'var(--rgb-subtle-hover));';
-						break;
-					case 25:
-						$s .= 'var(--rgb-light));';
-						break;
-					case 50:
-						$s .= 'var(--rgb-medium));';
-						break;
-					default:
-						$s .= 'var(--rgb-heavy));';
-						break;
-				}
-				$styles[] = $s;
-				break;
+				return $this->getDimRatioStyle($value, $attrs);
 
             // Custom styles (any other attributes that need inline styling)
             case 'backgroundType':
                 if ($value === 'video' && isset($attrs['backgroundUrl'])) {
                     // Don't set a background image for videos - it will be handled by the video element
                 } elseif (isset($attrs['backgroundUrl'])) {
-                    $styles[] = 'background-image: url('.$attrs['backgroundUrl'].')';
+                    return 'background-image: url('.$attrs['backgroundUrl'].')';
                 }
                 break;
 
 			case 'backgroundColor':
 			case 'borderColor':
 			case 'textColor':
-				$type = ($key === 'backgroundColor') ? 'background-color:' : (($key === 'borderColor') ? 'border-color:' : 'color:');
-				$defaults = apply_filters('jvbColours', ['base', 'contrast', 'action', 'secondary']);
-				$continue = true;
-				foreach ($defaults as $default) {
-					if (str_starts_with($value, $default)) {
-						$continue = false;
-						$styles[] = $type.'var(--'.$value.')';
-					}
+				$type = str_replace('Color', '-color', $key);
+				$type = str_replace('text-', '', $type);
+				if (isset($attrs['border']['width']) && $key === 'borderColor') {
+					break;
 				}
-				if ($continue) {
-					$styles[] = $type.$value;
-				}
-				break;
+				return $this->getColorStyle($value, $type);
+
             // Any other attributes that need direct styling
             default:
-				$ignore = [
-					'useFeaturedImage',
-					'opacity',
-					'textAlign',
-					'minHeightUnit',
-					'isDark',
-					'isUserOverlayColor',
-					'contentPosition',
-					'sizeSlug',
-					'customOverlayColor',
-					'alt',
-					'placeholder',
-					'imageFill',
-					'mediaSizeSlug',
-					'isLink',
-					'kind',
-					'label',
-					'type',
-					'id',
-					'url',
-					'label',
-					'shouldSyncIcon',
-					'rel',
-					'opensInNewTab',
-					'title',
-					'ref',
-					'overlayMenu',
-					'slug',
-					'theme',
-					'tagName',
-					'level',
-					'ordered',
-					'area',
-					'className',
-					'fontSize',
-					'layout',
-					'align',
-					'mediaId',
-					'mediaLink',
-					'mediaType',
-					'isStackedOnMobile',
-					'width',
-					'height', // maybe still need?
-				];
-				if (!is_admin() && !in_array($key, $ignore)) {
+				if (JVB_TESTING && !is_admin() && !in_array($key, $this->ignore)) {
 					//TESTING
-//					jvbDump($key, 'getStyle');
-//					jvbDump($attrs);
+					jvbDump($attrs, '[getStyle] '.$key);
 				}
                 // No default inline styles
                 break;
@@ -2004,6 +2846,367 @@
 
         return $styles;
     }
+		private function getIconSizeStyle(string $value):array
+		{
+			$values = explode(' ', $value);
+			$styles = [];
+			foreach ($values as $v) {
+				switch ($value) {
+					case 'has-small-icon-size':
+						$styles[] = '--w:var(--txt-x-small)';
+						break;
+					case 'has-large-icon-size':
+						$styles[] = '--w:var(--txt-large)';
+						break;
+					case 'has-huge-icon-size':
+						$styles[] = '--w:var(--txt-xx-large)';
+						break;
+					default:
+						if (JVB_TESTING) {
+							jvbDump($value, 'No preset found for size: '.print_r($value, true));
+						}
+				}
+			}
+			return $styles;
+		}
+		private function getFontFamilyStyle(string $value):string
+		{
+			return match($value) {
+				'body'		=> 'font-family: var(--body)',
+				'heading'	=> 'font-family: var(--heading)',
+				default		=> sprintf('font-family: %s', $value)
+			};
+		}
+		private function getColorStyle(string $value, ?string $type = 'color'):string
+		{
+			if (!in_array($type, ['color', 'background','background-color','border-color'])) {
+				$type = null;
+			}
+			return sprintf(
+				'%s%s',
+				$type ? $type.': ' : '',
+				$this->getColor($value)
+			);
+		}
+		private function getMinHeightStyle(string $value, array $attrs):string
+		{
+			$out = '';
+			if (!empty($value)) {
+				if (isset($attrs['minHeightUnit'])) {
+					$out = sprintf(
+						'min-height: %s%s',
+						$value,
+						$attrs['minHeightUnit']
+					);
+				} else {
+					$out = sprintf(
+						'min-height: %spx',
+						$value
+					);
+				}
+			}
+			return $out;
+		}
+
+		private function getFocalPointStyle(array $value):string
+		{
+			jvbDump($value, 'Focal Point');
+			$x = array_key_exists('x', $value) ? $value['x'] * 100 : 'center';
+			$y = array_key_exists('y', $value) ? $value['y'] * 100 : 'center';
+
+			$y = $x === $y ? '' : ' '.$y;
+			return sprintf(
+				'background-position:%s%s',
+				$x,
+				$y
+			);
+		}
+
+		private function extractStyles(array $value, array $attrs):array
+		{
+			$styles = [];
+			foreach ($value as $k => $v) {
+				switch ($k) {
+					case 'border':
+						$styles = array_merge($styles, $this->getBorderStyle($v, $attrs));
+						break;
+
+					case 'color':
+						if (isset($v['background'])) {
+							$styles[] = $this->getColorStyle($v['background'], 'background-color');
+						}
+						if (isset($v['text'])) {
+							$styles[] = $this->getColorStyle($v['text']);
+						}
+						if (isset($v['gradient'])) {
+							jvbDump($v, 'Gradient');
+						}
+						break;
+
+					case 'layout':
+						$styles = array_merge($styles, $this->getLayoutStyle($v, $attrs));
+						break;
+
+					case 'typography':
+						$styles = array_merge($styles, $this->getTypographyStyle($v, $attrs));
+						break;
+
+					case 'spacing':
+						if (isset($v['blockGap'])) {
+							if (is_array($v['blockGap'])) {
+								$inner = [];
+								foreach ($v['blockGap'] as $gap) {
+									$inner[] = sprintf(
+										'var(--sp%s)',
+										$this->getPresetSpacing($gap)
+									);
+								}
+								if (!empty($inner)) {
+									$styles[] = sprintf(
+										'--gap: %s',
+										implode(' ', $inner)
+									);
+								}
+							} else {
+								$styles[] = '--gap: var(--sp'.$this->getPresetSpacing($v['blockGap']).')';
+							}
+						}
+
+						// Don't duplicate margin/padding that's handled by classes
+						// Only add specific CSS values here that wouldn't work well as classes
+						if (isset($v['margin'])) {
+							foreach ($v['margin'] as $direction => $size) {
+								if (!str_contains($size, 'var:preset')) {
+									$styles[] = 'margin-'.$direction.': '.$size;
+								}
+							}
+						}
+
+						if (isset($v['padding'])) {
+							foreach($v['padding'] as $dir => $size) {
+								if (!str_contains($size, 'var:preset')) {
+									$styles[] = 'padding-'.$dir.': '.$size;
+								}
+							}
+						}
+						break;
+
+					case 'background':
+						if (array_key_exists('backgroundImage', $v)) {
+							$data = Image::getData($v['backgroundImage']['id']);
+							if (!empty($data) && array_key_exists('tiny', $data)) {
+								$styles[] = sprintf(
+									'background-image: url(%s)',
+									$data['tiny']
+								);
+							}
+
+						}
+						break;
+
+					case 'dimensions':
+						foreach ($v as $sk => $sv) {
+							if ($sk === 'minHeight') {
+								$styles[] = sprintf(
+									'min-height: %s',
+									$sv
+								);
+							} else {
+								jvbDump('No config set for dimension '.$sk.': '.print_r($sv, true));
+							}
+						}
+
+
+						break;
+
+					case 'elements':
+						if (!empty($v)) {
+							// Generate a unique class tied to this block instance
+							$uid = 'b-'.substr(md5(serialize($attrs)), 0, 8);
+							$this->extractElementStyles($v, $uid);
+							// We need the uid added as a class — store it for getClassesAndStyles to pick up
+							static::$pendingClass[] = $uid;
+						}
+						break;
+
+					default:
+						jvbDump('No config set for '.$k.': '.print_r($v, true));
+				}
+			}
+
+			return $styles;
+		}
+			private function getBorderStyle(array $border, array $attrs):array
+			{
+				$styles = [];
+
+				if (isset($border['radius'])) {
+					$styles[] = 'border-radius: '.$border['radius'];
+				}
+
+				if (isset($border['width']) && isset($attrs['borderColor'])) {
+					$st = $border['style'] ?? 'solid';
+					$styles[] = sprintf(
+						'border: %s %s %s',
+						$border['width'],
+						$st,
+						$this->getColor($attrs['borderColor'])
+					);
+				} elseif (isset($border['width'])) {
+					$styles[] = 'border-width: '.$border['width'];
+				} elseif (isset($border['style'])) {
+					$styles[] = 'border-style: '.$border['style'];
+				}
+
+				return $styles;
+			}
+
+			private function getLayoutStyle(array $layout, array $attrs):array
+			{
+				$styles = [];
+//				jvbDump($layout);
+
+				foreach ($layout as $l => $option) {
+					switch ($l) {
+						case 'selfStretch':
+							if ($option === 'fixed' && isset($layout['selfStretchValue'])) {
+								$styles[] = 'width: '.$layout['selfStretchValue'];
+							} elseif ($option === 'fill') {
+								$styles[] = 'flex:1';
+							}
+							break;
+						default:
+							$ignore = [
+								'selfStretchValue',
+								'flexSize',
+							];
+							if (JVB_TESTING && !in_array($l, $ignore)) {
+								jvbDump($l, 'No layout style set for: ');
+							}
+//							case 'type':
+//								if ($option === 'grid' && $value['layout']['columnCount'] !== 3) {
+//									$styles[] = sprintf(
+//										'grid-template-columns: repeat(1fr, %s)',
+//										$value['layout']['columnCount']
+//									);
+//								}
+//								break;
+					}
+				}
+				return $styles;
+			}
+
+			private function getTypographyStyle(array $typography, array $attrs):array
+			{
+				$styles = [];
+
+				if (isset($typography['fontSize'])) {
+					$styles[] = 'font-size: '.$typography['fontSize'];
+				}
+
+				if (isset($typography['fontWeight'])) {
+					$styles[] = 'font-weight: '.$typography['fontWeight'];
+				}
+
+				if (isset($typography['textDecoration'])) {
+					$styles[] = 'text-decoration: '.$typography['textDecoration'];
+				}
+
+				if (isset($typography['textTransform'])) {
+					$styles[] = 'text-transform: '.$typography['textTransform'];
+				}
+
+				if (isset($typography['letterSpacing'])) {
+					$styles[] = 'letter-spacing: '.$typography['letterSpacing'];
+				}
+
+				if (isset($typography['lineHeight'])) {
+					$styles[] = 'line-height: '.$typography['lineHeight'];
+				}
+				return $styles;
+			}
+			private function extractElementStyles(array $elements, string $uid):void
+			{
+				foreach ($elements as $element => $states) {
+					$selector = match($element) {
+						'link'    => "a",
+						'heading' => "h1,h2,h3,h4,h5,h6",
+						'button'  => "button,.button",
+						default   => $element,
+					};
+
+					foreach ($states as $state => $rules) {
+						$css = [];
+						$fullSelector = str_starts_with($state, ':')
+							? ".{$uid} {$selector}{$state}"
+							: ".{$uid} {$selector}";
+
+						if (isset($rules['color']['text'])) {
+							$css[] = 'color: '.$this->getColor($rules['color']['text']);
+						}
+						if (isset($rules['color']['background'])) {
+							$css[] = 'background-color: '.$this->getColor($rules['color']['background']);
+						}
+						if (!empty($css)) {
+							static::$pendingStyles[] = $fullSelector.' { '.implode('; ', $css).' }';
+						}
+					}
+				}
+			}
+
+	public function maybeOutputCustomStyles(): string
+	{
+		if (empty(static::$pendingStyles)) return '';
+		$out = '<style>'.implode(' ', static::$pendingStyles).'</style>';
+		static::$pendingStyles = [];
+		return $out;
+	}
+
+		private function getDimRatioStyle(int $value, array $attrs):string
+		{
+			//TODO: This likely isn't working correctly
+			jvbDump($value, 'dimRatio');
+			jvbDump($attrs, 'dimRatio attrs');
+			$s = '';
+			if (!array_key_exists('overlayColor', $attrs)) {
+				$s = 'background-color: rgba(var(--base-rgb), ';
+				if ($value <= 14) {
+					$s .= 'var(--op-1));';
+				} elseif ($value <= 28) {
+					$s .= 'var(--op-2));';
+				} elseif ($value <= 42) {
+					$s .= 'var(--op-3));';
+				} elseif ($value <= 56) {
+					$s .= 'var(--op-45));';
+				} elseif ($value <= 70) {
+					$s .= 'var(--op-4));';
+				} elseif ($value <= 84) {
+					$s .= 'var(--op-5));';
+				} else {
+					$s .= 'var(--op-6));';
+				}
+			}
+			return $s;
+		}
+
+		protected function getDataset(array $attrs):array
+		{
+			$dataset = [];
+			if (array_key_exists('style', $attrs)) {
+				if (array_key_exists('background', $attrs['style'])){
+					if (array_key_exists('backgroundImage', $attrs['style']['background'])) {
+						$id = $attrs['style']['background']['backgroundImage']['id']??false;
+						if ($id) {
+							$data = Image::getData($id);
+							$dataset['bg-small'] = $data['small'];
+							$dataset['bg-med'] = $data['medium'];
+							$dataset['bg-large'] = $data['large'];
+						}
+					}
+				}
+			}
+			return $dataset;
+		}
 
     public function formatImage(int $ID = 0, string $start = 'tiny', string $replace = 'large'):string
     {
@@ -2016,4 +3219,28 @@
 		return jvbFormatImage($ID, $start, $replace);
     }
 
+	protected function checkAttrs(string $test, array $attrs):bool
+	{
+		return array_key_exists($test, $attrs) && $attrs[$test]===true;
+	}
+
+	protected function getColor(string $value):string
+	{
+		$defaults = apply_filters('jvbColours', ['base', 'contrast', 'action', 'secondary']);
+		foreach ($defaults as $default) {
+			if (str_starts_with($value, $default)) {
+				return 'var(--'.$value.')';
+			}
+		}
+		return $value;
+	}
+
+	protected function counter(string $key):void
+	{
+		if (!array_key_exists($key, static::$counters)) {
+			static::$counters[$key] = 1;
+		} else {
+			static::$counters[$key]++;
+		}
+	}
 }
diff --git a/inc/blocks/FeedBlock.php b/inc/blocks/FeedBlock.php
index fba0d1c..c6b1f0b 100644
--- a/inc/blocks/FeedBlock.php
+++ b/inc/blocks/FeedBlock.php
@@ -193,8 +193,8 @@
 			<?php if ($hasMany) {
 				//If we have multiple content, only show the content first
 				?>
-				<details class="col a-start">
-				<summary class="row btw">
+				<details class="col left">
+				<summary class="row x-btw">
 					<span class="label">SHOWING: </span>
 					<?php
 					$labels = [];
@@ -249,7 +249,7 @@
 				if (!empty ($this->config['taxonomies'])) {
 				?>
 					<div class="filters">
-						<div class="filter-group row start">
+						<div class="filter-group row left">
 							<span class="label">FILTER BY:</span>
 
 							<?php
@@ -288,8 +288,8 @@
 						</div>
 					</div>
 				<?php } ?>
-					<div class="row btw nowrap">
-						<div class="order-by filter-group row start w-full">
+					<div class="row x-btw nowrap">
+						<div class="order-by filter-group row left w-full">
 							<span class="label">ORDER BY:</span>
 							<?php
 							//TODO: Get content types that can be sorted alphabetically
@@ -340,7 +340,7 @@
 
 						</div>
 
-						<div class="order-direction filter-group row start w-full" data-for-order="date,modified,title<?= $custom === '' ? '' : ','.$custom?>">
+						<div class="order-direction filter-group row left w-full" data-for-order="date,modified,title<?= $custom === '' ? '' : ','.$custom?>">
 							<span class="label">ORDER:</span>
 							<input type="radio" id="order-desc" class="btn" name="order" value="desc" data-filter="order" checked>
 							<label for="order-desc" title="Sort Descending (A-Z, 1-10)" class="row">
diff --git a/inc/blocks/FormBlock.php b/inc/blocks/FormBlock.php
index 768ed18..0d447da 100644
--- a/inc/blocks/FormBlock.php
+++ b/inc/blocks/FormBlock.php
@@ -324,7 +324,7 @@
 
 		// Render navigation if multiple sections
 		if (count($sections) > 1) {
-			echo '<nav class="tabs row start" role="tablist">';
+			echo '<nav class="tabs row left" role="tablist">';
 			$i = 1;
 			foreach ($sections as $slug => $section) {
 				$active_class = $i === 1 ? ' active' : '';
@@ -367,7 +367,7 @@
 			}
 
 			// Add step navigation buttons
-			echo '<div class="step-navigation row btw">';
+			echo '<div class="step-navigation row x-btw">';
 
 			if ($i > 0) {
 				echo '<button type="button" class="button secondary prev-step" data-action="prev-step">';
diff --git a/inc/blocks/MenuBlock.php b/inc/blocks/MenuBlock.php
index 679f017..89b9c60 100644
--- a/inc/blocks/MenuBlock.php
+++ b/inc/blocks/MenuBlock.php
@@ -194,14 +194,14 @@
 
 		?>
 		<div class="menu-item<?= !empty($variations) ? ' variable' : '' ?>" data-section="<?=$slug?>">
-			<div class="header row btw">
+			<div class="header row x-btw">
 				<h3><?= $values['post_title']?></h3>
 				<p class="price"><?= $priceRange ?></p>
 			</div>
 			<div class="description">
 				<?= Render::renderFrom($meta, 'post_excerpt')?>
 			</div>
-			<div class="info row end">
+			<div class="info row right">
 				<?php
 				if (empty($variations)) {
 					Form::renderFrom($meta,
diff --git a/inc/blocks/SummaryBlock.php b/inc/blocks/SummaryBlock.php
index 5538f8a..ecf01ac 100644
--- a/inc/blocks/SummaryBlock.php
+++ b/inc/blocks/SummaryBlock.php
@@ -174,7 +174,7 @@
 		$open = $this->isOpen ? ' open' : '';
         ?>
         <details class="info"<?=$open?>>
-            <summary class="row btw"><?= $this->detailsTitle ?></summary>
+            <summary class="row x-btw"><?= $this->detailsTitle ?></summary>
             <?php
             foreach ($this->details as $key => $details) {
                 if ($details === '') {
diff --git a/inc/forms/TaxonomySelector.php b/inc/forms/TaxonomySelector.php
index 5af59da..5a8795a 100644
--- a/inc/forms/TaxonomySelector.php
+++ b/inc/forms/TaxonomySelector.php
@@ -111,8 +111,8 @@
 				<div class="items-wrap">
 					<!-- Common/Favorite terms section -->
 					<details class="favourite-terms" hidden>
-						<summary class="title row btw">Your Go Tos:</summary>
-						<ul class="favourite-list row btw"></ul>
+						<summary class="title row x-btw">Your Go Tos:</summary>
+						<ul class="favourite-list row x-btw"></ul>
 					</details>
 
 					<!-- Pagination info -->
@@ -130,7 +130,7 @@
 						{ <span>loading items</span> }
 					</p>
 					<!-- Terms list -->
-					<ul class="items-container col start" role="listbox" aria-label="Available terms">
+					<ul class="items-container col top" role="listbox" aria-label="Available terms">
 						<!-- Terms will be populated here -->
 					</ul>
 
@@ -149,7 +149,7 @@
 
 				<!-- Create new term section -->
 				<details class="create-term" hidden>
-					<summary class="row btw">Add New Term</summary>
+					<summary class="row x-btw">Add New Term</summary>
 					<div class="create-new-term-section">
 						<form class="create-term" data-nocache data-form-id="create-term" data-save="terms">
 							<div class="form-group">
@@ -229,7 +229,7 @@
 		?>
 		<div class="jvb-selector <?= esc_attr($this->name) ?>"
 			 id="<?= esc_attr($this->id) ?>"<?= $hidden ?>>
-			<div class="field-group-header row btw">
+			<div class="field-group-header row x-btw">
 				<label for="<?= $this->base ?><?= esc_attr($this->config['name']) ?>-autocomplete">
 					<?= ($this->config['icon']) ? jvbIcon($this->config['icon']) : '' ?>
 					<span><?= $this->config['label'] ?></span>
diff --git a/inc/helpers/crud.php b/inc/helpers/crud.php
index c5e3611..dc646c9 100644
--- a/inc/helpers/crud.php
+++ b/inc/helpers/crud.php
@@ -314,7 +314,7 @@
     }
 
     echo '<div class="container">';
-    $nav = '<nav class="tabs row start" role="tablist">';
+    $nav = '<nav class="tabs row left" role="tablist">';
     $i = 1;
     foreach ($registrar->getSections() as $slug => $section) {
         $nav .= '<button type="button" class="tab';
diff --git a/inc/helpers/media.php b/inc/helpers/media.php
index 82c6f3f..6381b82 100644
--- a/inc/helpers/media.php
+++ b/inc/helpers/media.php
@@ -25,7 +25,7 @@
 				<img src="" alt="" class="image">
 				<img src="" alt="" class="image-right">
 				<details>
-					<summary class="row btw">DETAILS</summary>
+					<summary class="row x-btw">DETAILS</summary>
 					<div class="item-info"></div>
 				</details>
 			</div>
diff --git a/inc/helpers/renderFields.php b/inc/helpers/renderFields.php
index ccf021a..150af0e 100644
--- a/inc/helpers/renderFields.php
+++ b/inc/helpers/renderFields.php
@@ -319,7 +319,7 @@
                     $date = new Date('M j, Y', strtotime($review['date']));
                 }
 
-                $out .= '<cite class="row btw">';
+                $out .= '<cite class="row x-btw">';
                 if ($review['rating'] !== 'none') {
                     $out .= jvbFormatStarRating($review['rating']);
                 }
@@ -449,7 +449,7 @@
 					</div>
 				</div>
 				<details>
-					<summary class="row btw">
+					<summary class="row x-btw">
 						Extra Fields
 					</summary>
 					<div class="fields"></div>
diff --git a/inc/helpers/ui.php b/inc/helpers/ui.php
index 953ce1c..aed22d6 100644
--- a/inc/helpers/ui.php
+++ b/inc/helpers/ui.php
@@ -19,8 +19,8 @@
     }
 
     ?>
-    <aside id="queue" class="left col start btw main" aria-expanded="false" >
-        <div class="m-actions row start nowrap">
+    <aside id="queue" class="left col top main" aria-expanded="false" >
+        <div class="m-actions row left nowrap">
 			<button class="refresh row" title="Check now">
 				<?= jvbIcon('arrows-clockwise', ['title'=> 'Check now']) ?>
 				<span class="countdown row indicator" title="Will refresh again...">5</span>
@@ -28,7 +28,7 @@
             <div class="popup row"><span></span></div>
         </div>
 
-		<div class="header col start">
+		<div class="header col top">
 			<h2>Queue Status</h2>
 			<nav class="filters">
 				<?php
@@ -55,9 +55,9 @@
 				?>
 			</nav>
 		</div>
-		<div class="qitems item-grid col a-start nowrap">
+		<div class="qitems item-grid col left nowrap">
 		</div>
-		<div class="queue-actions row btw nowrap">
+		<div class="queue-actions row x-btw nowrap">
 			<button class="dismiss-all">Clear Completed</button>
 			<button class="retry-all">Retry Failed</button>
 		</div>
@@ -70,20 +70,20 @@
 	</button>
 	<template class="queueItem">
 		<div class="item">
-			<div class="header row btw">
+			<div class="header row x-btw">
 				<span class="type"></span>
 				<span class="status row"><?= jvbIcon('arrows-clockwise') ?><span class="screen-reader-text"></span></span>
 			</div>
 			<?php jvbRenderProgressBar('',false,false) ?>
 			<div class="info">
 				<div class="details"></div>
-				<div class="time row start">
+				<div class="time row left">
 					<?= jvbIcon('clock') ?>
 					<span class="started">Started: <time></time></span>
 					<span class="completed" hidden><span>Completed: </span><time></time></span>
 				</div>
 			</div>
-			<div class="actions row end">
+			<div class="actions row right">
 				<button class="retry" data-action="retry"><span>Retry</span><?= jvbIcon('arrows-clockwise')?></button>
 				<button class="cancel" data-action="cancel"><span>Cancel</span><?= jvbIcon('x-square')?></button>
 				<button class="refresh" data-action="refresh" title="Refresh to see changes"><span>Refresh</span><?= jvbIcon('arrows-clockwise')?></button>
@@ -212,9 +212,9 @@
  * Outputs the search bar (likely don't need anymore)
  * @return string
  */
-function jvbSearch(string $placeholder = 'Search...', string $id = 'search'):string
+function jvbSearch(string $placeholder = 'Search...', string $id = 'search', string $label = '', string $buttonText = '', bool $buttonInside = false, bool $hideSearch = false):string
 {
-	return Form::search($placeholder, $id);
+	return Form::search($placeholder, $id, $label, $buttonText, $buttonInside, $hideSearch);
 }
 
 
@@ -271,7 +271,7 @@
     </aside>
     <template class="notificationPopup">
         <div class="toast" role="status" aria-live="polite">
-            <div class="toast-content row btw">
+            <div class="toast-content row x-btw">
                 <p></p>
                 <button type="button" class="close-toast" aria-label="Close">
                     <?= jvbIcon('x') ?>
@@ -362,7 +362,7 @@
 		echo '';
 		return '';
 	}
-	$header = '<nav class="tabs row start" role="tablist">';
+	$header = '<nav class="tabs row left" role="tablist">';
 	$content = '';
 	$i = 0;
 
@@ -434,7 +434,7 @@
 		<div class="bar">
 			<div class="fill"></div>
 		</div>
-		<div class="row btw">
+		<div class="row x-btw">
 			%s
 			<div class="details">
 				%s
diff --git a/inc/integrations/Integrations.php b/inc/integrations/Integrations.php
index 3a6643a..5a14350 100644
--- a/inc/integrations/Integrations.php
+++ b/inc/integrations/Integrations.php
@@ -2945,7 +2945,7 @@
 		?>
 		<form id="<?=$this->service_name?>" class="integration <?php echo $is_connected ? 'connected' : 'disconnected'; ?>"
 			 data-service="<?php echo esc_attr($this->service_name); ?>">
-			<div class="header row btw">
+			<div class="header row x-btw">
 				<h3><?php echo esc_html($this->title); ?></h3>
 				<div class="setup">
 					<?php if ($is_connected): ?>
@@ -3056,7 +3056,7 @@
 				}
 				?>
 			</div>
-			<div class="actions row btw wrap">
+			<div class="actions row x-btw wrap">
 				<?php
 				foreach ($this->buttons as $action => $label) {
 					if (!$is_connected && $action !== 'save_credentials') {
diff --git a/inc/managers/DashboardManager.php b/inc/managers/DashboardManager.php
index eea1901..36eaff2 100644
--- a/inc/managers/DashboardManager.php
+++ b/inc/managers/DashboardManager.php
@@ -704,9 +704,9 @@
 
 		<?php
 		$menu = new Navigation('sidebar');
-		$menuClasses = ['col', 'a-start', 'nowrap'];
+		$menuClasses = ['col', 'left', 'nowrap'];
 		$itemClasses = ['col'];
-		$menu->addClass('col a-start')->hasToggle()->defaultMenuClasses($menuClasses);
+		$menu->addClass('col left')->hasToggle()->defaultMenuClasses($menuClasses);
 		$menu->defaultItemClasses($itemClasses);
 		$pages = $this->getUserAllowedPages()?:[];
 		//Dashboard
@@ -1005,7 +1005,7 @@
     {
         ?>
         <div class="approvals container">
-            <nav class="tabs row start" role="tablist">
+            <nav class="tabs row left" role="tablist">
                 <button type="button" class="tab active" data-tab="summary" role="tab" aria-selected="true">
                    <?= jvbDashIcon('infinity')?>All
                 </button>
@@ -1077,7 +1077,7 @@
 		}
 		ob_start();
         ?>
-        <nav class="tabs row start" role="tablist">
+        <nav class="tabs row left" role="tablist">
         <?php
         $i=1;
         $content = Registrar::getRegistered('post');
diff --git a/inc/managers/DirectoryManager.php b/inc/managers/DirectoryManager.php
index 1d5c15a..c81010d 100644
--- a/inc/managers/DirectoryManager.php
+++ b/inc/managers/DirectoryManager.php
@@ -373,7 +373,7 @@
 					$config = Registrar::getInstance($slug);
 					$aOpen = '<a href="'.$directory['url'].'" title="See our list of '.$directory['title'].'">';
 					$aClose = '</a>';
-					$cache .= '<li class="directory col start">
+					$cache .= '<li class="directory col left">
 						'. $aOpen.jvbIcon($config->getIcon() !== '' ? $config->getIcon() :'list-dashes').$directory['title'].$aClose;
 					if (!empty($directory['description'])) {
 						$cache .= '<div class="description">';
@@ -412,7 +412,7 @@
 		);
         if ($current !== '' && array_key_exists($current, $this->directories())) {
             $open = ($open) ? ' open' : '';
-            $cache = '<details'.$open.'><summary class="row btw">Other '.$this->referAs(true).':</summary>'.
+            $cache = '<details'.$open.'><summary class="row x-btw">Other '.$this->referAs(true).':</summary>'.
                      str_replace('id="'.$current.'"', 'id="'.$current.'" class="current"', $cache)
                      .'</details>';
         }
@@ -613,7 +613,7 @@
 			$children =$this->renderListChunk($taxonomy, $term->term_id);
 			$out .= '<li>';
 			if ($children !== '') {
-				$out .= '<details class="term"><summary class="row btw"><a href="'.get_term_link($term->term_id, $term->taxonomy).'" title="See more '.html_entity_decode($term->name).'">'.$term->name.'</a></summary>';
+				$out .= '<details class="term"><summary class="row x-btw"><a href="'.get_term_link($term->term_id, $term->taxonomy).'" title="See more '.html_entity_decode($term->name).'">'.$term->name.'</a></summary>';
 				$out .= $children;
 				$out .= '</details>';
 			} else {
@@ -668,7 +668,7 @@
 
         $out = '<ul class="list-none">';
         foreach ($list as $letter => $items) {
-            $out .= '<li id="starts-with-'.$letter.'" class="row a-start btw nowrap"><h3>'.strtoupper($letter).'</h3><ul>';
+            $out .= '<li id="starts-with-'.$letter.'" class="row top x-btw nowrap"><h3>'.strtoupper($letter).'</h3><ul>';
             foreach ($items as $item) {
                 $extra = '';
                 if (!empty($item['extra'])) {
@@ -683,7 +683,7 @@
 				$item_html = apply_filters('jvb_directory_render_item', '', $item, $type, $extra);
 
 				if (empty($item_html)) {
-					$item_html = '<li class="row btw">
+					$item_html = '<li class="row x-btw">
                     <a href="'.$item['url'].'" title="More about '.$item['name'].'">
                         '.$item['name'].'</a>'.$extra.'
                 </li>';
diff --git a/inc/managers/IconsManager.php b/inc/managers/IconsManager.php
index 3290923..f54120b 100644
--- a/inc/managers/IconsManager.php
+++ b/inc/managers/IconsManager.php
@@ -625,7 +625,7 @@
 	/**
 	 * Get raw SVG content for CSS mask-image
 	 */
-	protected function getRawSvg(string $name, ?string $style = null): ?string
+	public function getRawSvg(string $name, ?string $style = null): ?string
 	{
 		if (!$style) {
 			$style = $this->style;
@@ -678,6 +678,8 @@
 						$css .= 'details.all-filters summary::after,';
 					} elseif ($icon === 'link') {
 						$css .= 'input[type=url],';
+					} elseif ($icon === apply_filters('jvbSeparatorLogo', 'logo')) {
+						$css .= 'hr.logo::before,';
 					}
 					$css .= ".icon-{$icon}{$styleClass}{";
 					$css .= "--icon:url('data:image/svg+xml;base64,{$svg}');";
diff --git a/inc/managers/LoginManager.php b/inc/managers/LoginManager.php
index 970b5e6..68db551 100644
--- a/inc/managers/LoginManager.php
+++ b/inc/managers/LoginManager.php
@@ -549,7 +549,7 @@
 	{
 		$form = $this->action.'form';
 		?>
-		<section class="login-box col btw">
+		<section class="login-box col y-btw">
 			<h1><?=$this->labels['title']?></h1>
 			<?= $this->labels['description'] ?>
 
@@ -568,7 +568,7 @@
 			}
 			?>
 
-			<div class="options row btw">
+			<div class="options row x-btw">
 				<?php
 				switch ($this->action) {
 					case 'login': ?>
@@ -593,7 +593,7 @@
 
 			</div>
 		</section>
-		<div class="navigation row btw">
+		<div class="navigation row x-btw">
 			<a href="<?= get_home_url() ?>">Home</a>
 			<?php
 			$privacy = get_privacy_policy_url();
@@ -635,7 +635,7 @@
 				%s
 				%s
 				%s
-				 <div class="row btw nowrap">
+				 <div class="row x-btw nowrap">
 					<button type="submit" class="button button-primary button-large">%s</button>
 					%s
 				</div>
diff --git a/inc/managers/ReferralManager.php b/inc/managers/ReferralManager.php
index fe97fea..c521c15 100644
--- a/inc/managers/ReferralManager.php
+++ b/inc/managers/ReferralManager.php
@@ -1441,7 +1441,7 @@
 
 		<section class="copy">
 			<h4>Your Referral Link</h4>
-			<div class="copy-group row btw nowrap">
+			<div class="copy-group row x-btw nowrap">
 				<code id="referral-link" class="copy-target"><?= esc_url($share_url) ?></code>
 				<button type="button" class="copy-btn" data-target="referral-link" title="Copy referral link">
 					<?= jvbIcon('copy'); ?>
@@ -1452,7 +1452,7 @@
 
 
 			<h4>Your Code</h4>
-			<div class="copy-group row btw nowrap">
+			<div class="copy-group row x-btw nowrap">
 				<code id="referral-code" class="copy-target"><?= esc_html($referral_code) ?></code>
 				<button type="button" class="copy-btn" data-target="referral-code" title="Copy referral code">
 					<?= jvbIcon('copy'); ?>
@@ -1471,19 +1471,19 @@
 		</section>
 
 		<section class="stats-summary">
-			<div class="row btw">
+			<div class="row x-btw">
 				<span class="stat-label">Total Referrals</span>
 				<span class="stat-value" data-stat="total">-</span>
 			</div>
-			<div class="row btw">
+			<div class="row x-btw">
 				<span class="stat-label">Successful</span>
 				<span class="stat-value" data-stat="treated">-</span>
 			</div>
-			<div class="row btw">
+			<div class="row x-btw">
 				<span class="stat-label">Pending</span>
 				<span class="stat-value" data-stat="pending">-</span>
 			</div>
-			<div class="row btw highlight">
+			<div class="row x-btw highlight">
 				<span class="stat-label">Available Rewards</span>
 				<span class="stat-value" data-stat="rewards">$0.00</span>
 			</div>
@@ -2692,7 +2692,7 @@
 		<details open>
 			<summary>Your Code</summary>
 			<h3>Share Link</h3>
-			<div class="row btw nowrap">
+			<div class="row x-btw nowrap">
 				<code id="referral-link" class="copy-target"><?= home_url('/?ref=' . $referral_code) ?></code>
 				<button type="button" class="copy-btn" data-target="referral-link" title="Copy referral link">
 					<?= jvbIcon('copy'); ?>
@@ -2700,7 +2700,7 @@
 				</button>
 			</div>
 			<h3>Share Code</h3>
-			<div class="row btw nowrap">
+			<div class="row x-btw nowrap">
 				<code id="referral-code" class="copy-target"><?= esc_html($referral_code) ?></code>
 				<button type="button" class="copy-btn" data-target="referral-code" title="Copy referral code">
 					<?= jvbIcon('copy'); ?>
diff --git a/inc/managers/SEO/BreadcrumbManager.php b/inc/managers/SEO/BreadcrumbManager.php
index 341debf..224569f 100644
--- a/inc/managers/SEO/BreadcrumbManager.php
+++ b/inc/managers/SEO/BreadcrumbManager.php
@@ -25,7 +25,6 @@
 	private function __construct()
 	{
 		$this->cache = Cache::for('breadcrumbs', MONTH_IN_SECONDS)->connect('post')->connect('taxonomy')->connect('user');
-		$this->cache->flush();
 		if (JVB_TESTING) {
 			$this->cache->flush();
 		}
@@ -50,7 +49,9 @@
 			return [];
 		}
 
+
 		switch (true) {
+
 			case is_singular():
 				$key = get_queried_object_id();
 				break;
@@ -62,6 +63,10 @@
 				$obj = get_queried_object();
 				$key = $obj->taxonomy;
 				break;
+			case is_home():
+				$obj = get_queried_object();
+				$key = $obj->post_type;
+				break;
 			default:
 				$key = 'home';
 				break;
@@ -93,7 +98,7 @@
 		$obj = get_queried_object();
 		if (is_tax()) {
 			$crumbs = $this->addTaxonomyCrumbs($crumbs, $obj);
-		} elseif (is_singular()) {
+		} elseif (is_singular() || is_home()) {
 			$crumbs = $this->addArchiveCrumbs($crumbs, $obj);
 			$hierarchy = $this->addSingularCrumbs($crumbs, $obj);
 			$crumbs = $crumbs + $hierarchy;
@@ -211,6 +216,9 @@
 	 */
 	private function addArchiveCrumbs(array $crumbs, object $obj): array
 	{
+		if (is_singular('page') || is_home()) {
+			return $crumbs;
+		}
 		$type = is_singular() ? $obj->post_type : $obj->name;
 		$name = jvbNoBase($type);
 
@@ -235,8 +243,9 @@
 				'url'	=> get_post_type_archive_link($type)
 			];
 		} else {
+			$postTypeObject = get_post_type_object($type);
 			$crumbs[] = [
-				'name'	=> $obj->label,
+				'name'	=> $postTypeObject->label,
 				'url'	=> get_post_type_archive_link($type)
 			];
 		}
diff --git a/inc/managers/queue/executors/ContentExecutor.php b/inc/managers/queue/executors/ContentExecutor.php
index 4893787..55835f0 100644
--- a/inc/managers/queue/executors/ContentExecutor.php
+++ b/inc/managers/queue/executors/ContentExecutor.php
@@ -348,6 +348,10 @@
 					}
 				}
 			}
+			$isUpdate = $meta->get('is_update');
+			if (!(bool) $isUpdate) {
+				$meta->set('number', $index);
+			}
 
 			if ($lastKey === $index) {
 				$latestTimestamp = strtotime($post->post_date);
diff --git a/inc/meta/Form.php b/inc/meta/Form.php
index 99a128b..2cf3f05 100644
--- a/inc/meta/Form.php
+++ b/inc/meta/Form.php
@@ -463,7 +463,7 @@
 		if (!array_key_exists('class', $config)) {
 			$config['class'] = [];
 		}
-		$config['class'][] ='row btw';
+		$config['class'][] ='row x-btw';
 
 		$checked = filter_var($value, FILTER_VALIDATE_BOOLEAN);
 
@@ -784,7 +784,7 @@
 								</div>
 							</div>
 
-							<div class="selection-actions row btw" hidden>
+							<div class="selection-actions row x-btw" hidden>
 								<button type="button" data-action="add-to-group">
 									%sGroup
 								</button>
@@ -848,7 +848,7 @@
 				protected static function renderUploadItemActions(?int $attachmentId = null):string
 				{
 					return sprintf(
-						'<div class="item-actions row btw">
+						'<div class="item-actions row x-btw">
 						<div class="btn">
 							<input type="radio" class="featured btn" name="featured" id="featured%s" hidden>
 							<label for="featured">
@@ -1207,7 +1207,7 @@
 		$containerId = sprintf('%s-%s-selector', $name, $config['subtype']);
 
 		$input = sprintf(
-			'<div class="row btw">
+			'<div class="row x-btw">
     <input type="hidden" name="%s" value="%s">
     <label for="%s-autocomplete">%s<span>%s</span></label>',
 			esc_attr($name),
@@ -1224,7 +1224,7 @@
 		}
 		$plural = static::getPlural($config);
 		$input .= sprintf(
-			'<div class="selected-items row start" role="region" aria-label="Selected %s"></div>',
+			'<div class="selected-items row left" role="region" aria-label="Selected %s"></div>',
 			$plural[1]??''
 		);
 
@@ -1498,7 +1498,7 @@
 		$config['data']['tag-format'] = esc_attr($tagFormat);
 
 		$input = sprintf(
-			'<h3>%s</h3><div class="row start wrap">',
+			'<h3>%s</h3><div class="row left wrap">',
 			esc_html($config['label']??'')
 		);
 
@@ -1657,7 +1657,7 @@
 							%s
 						</button>
 						<details%s>
-							<summary class="row btw repeater-row-header">
+							<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>
@@ -1729,8 +1729,8 @@
 					<div class="items-wrap">
 						<!-- Common/Favorite terms section -->
 						<details class="favourite-terms" hidden>
-							<summary class="title row btw">Your Go Tos:</summary>
-							<ul class="favourite-list row btw"></ul>
+							<summary class="title row x-btw">Your Go Tos:</summary>
+							<ul class="favourite-list row x-btw"></ul>
 						</details>
 
 						<!-- Pagination info -->
@@ -1748,7 +1748,7 @@
 							{ <span>loading items</span> }
 						</p>
 						<!-- Terms list -->
-						<ul class="items-container col start" role="listbox" aria-label="Available terms">
+						<ul class="items-container col top" role="listbox" aria-label="Available terms">
 							<!-- Terms will be populated here -->
 						</ul>
 
@@ -1767,7 +1767,7 @@
 
 					<!-- Create new term section -->
 					<details class="create-term" hidden>
-						<summary class="row btw">Add New Term</summary>
+						<summary class="row x-btw">Add New Term</summary>
 						<div class="create-new-term-section">
 							<form class="create-term" data-nocache data-form-id="create-term" data-save="terms">
 								<div class="form-group">
@@ -1826,20 +1826,33 @@
 		);
 	}
 
-	public static function search(string $placeholder = 'Search...', string $id = 'search'):string
+	public static function search(string $placeholder = 'Search...', string $id = 'search', string $label = '', string $buttonText = '',bool $buttonInside = false,  bool $hideSearch = false):string
 	{
 		$id = sanitize_title($id);
+		$label = empty($label) ? '' : sprintf(
+			'<h3>%s</h3>',
+			$label
+		);
+		$buttonText = empty($buttonText) ? '' : sprintf(
+			'<span>%s</span>',
+			$buttonText
+		);
+		$hideSearch = $hideSearch ? ' hidden' : '';
 		return sprintf(
-			'<div class="search-container row start nowrap">
-				<input type="search" id="%s" placeholder="%s">
+			'%s<div class="search-container row left nowrap%s">
+				<input type="search" id="%s" placeholder="%s"%s>
 				<button title="Clear Search" type="button" class="clear-search" aria-label="Clear search"
 					onclick="this.previousElementSibling.value = \'\'; this.previousElementSibling.focus();">%s</button>
-				<button type="button" title="Search" class="toggle search" aria-label="Toggles search input visually" onclick="this.parentNode.classList.toggle(\'open\');this.previousElementSibling.previousElementSibling.focus();">%s</button>
+				<button type="button" title="Search" class="toggle search" aria-label="Toggles search input visually" onclick="this.parentNode.classList.toggle(\'open\');this.previousElementSibling.previousElementSibling.focus();">%s%s</button>
 				</div>',
+			$label,
+			$buttonInside ? ' insideButton' : '',
 			$id,
 			$placeholder,
+			$hideSearch,
 			jvbIcon('x', ['title' => 'Clear Search']),
-			jvbIcon('magnifying-glass')
+			jvbIcon('magnifying-glass'),
+			$buttonText
 		);
 	}
 }
diff --git a/inc/templates.php b/inc/templates.php
index cf3505d..319618e 100644
--- a/inc/templates.php
+++ b/inc/templates.php
@@ -13,7 +13,7 @@
 {
     return '<template class="response">
     <details class="response" open>
-            <summary class="row btw">
+            <summary class="row x-btw">
                 <div class="header">
 
                 </div>
@@ -65,7 +65,7 @@
 {
     return '<template class="responses">
         <details class="responses">
-            <summary class="row btw">
+            <summary class="row x-btw">
                 Comments
             </summary>
         </details>
@@ -112,7 +112,7 @@
 						<h4>'.__('From:', 'jvb').'</h4>
 						<div class="source-info col">
 							<div class="source-day"></div>
-							<div class="source-hours row start"></div>
+							<div class="source-hours row left"></div>
 						</div>
 					</div>
 
diff --git a/inc/ui/CRUDSkeleton.php b/inc/ui/CRUDSkeleton.php
index de191ce..307e0b9 100644
--- a/inc/ui/CRUDSkeleton.php
+++ b/inc/ui/CRUDSkeleton.php
@@ -578,7 +578,7 @@
 	protected function renderUploader(): void {
 		?>
 		<details open class="uploader">
-			<summary class="row btw"><?= esc_html($this->uploaderConfig['label'] ?? 'Upload Files') ?></summary>
+			<summary class="row x-btw"><?= esc_html($this->uploaderConfig['label'] ?? 'Upload Files') ?></summary>
 			<form id="uploader" data-form-id="upload_new_<?=$this->dataType ?>">
 				<?php
 				echo jvbFormRestore();
@@ -624,7 +624,7 @@
 			return;
 		}
 		?>
-		<details class="all-filters col start" data-ignore>
+		<details class="all-filters col top" data-ignore>
 			<summary>Filters</summary>
 			<?php
 
@@ -649,7 +649,7 @@
 			return;
 		}
 		?>
-		<div class="search row start nowrap">
+		<div class="search row left nowrap">
 			<span class="label">Search:</span>
 			<?= jvbSearch() ?>
 		</div>
@@ -725,7 +725,7 @@
 	protected function renderOrderControls():void
 	{
 		?>
-		<div class="radio-options order row btw w-full">
+		<div class="radio-options order row x-btw w-full">
 			<?php
 			$order = [
 				'orderby' => [
@@ -740,7 +740,7 @@
 
 			foreach ($order as $o => $option) {
 				?>
-				<div class="row start">
+				<div class="row left">
 					<span class="label"><?= ucfirst($o)?>:</span>
 					<?php
 					$i = 0;
@@ -770,7 +770,7 @@
 			return;
 		}
 		?>
-		<div class="filters row start">
+		<div class="filters row left">
 			<span class="label">Filters:</span>
 			<?php
 			foreach ($this->filters as $key => $config) {
@@ -928,7 +928,7 @@
 		ob_start();
 		?>
 		<details class="multi-select" title="Select columns" hidden>
-			<summary class="row start nowrap">
+			<summary class="row left nowrap">
 				<?= jvbDashIcon('columns') ?>
 				<span class="labels">Toggle Columns</span>
 			</summary>
@@ -961,7 +961,7 @@
 			return;
 		}
 		?>
-		<div class="bulk-controls row nowrap btw">
+		<div class="bulk-controls row nowrap x-btw">
 			<div class="bulk-select">
 				<input type="checkbox" id="select-all" class="select-all">
 				<label for="select-all" class="row"><span>Select All</span><span class="selected-count" hidden></span></label>
@@ -1132,7 +1132,7 @@
 		}
 		ob_start();
 		?>
-		<div class="item-actions row btw abs">
+		<div class="item-actions row x-btw abs">
 			<?php
 			foreach ($this->itemActions as $action) {
 				$config = $this->defaultItemActions[$action];
@@ -1173,7 +1173,7 @@
 			<div class="item <?=esc_attr($this->dataType)?> row nowrap">
 				<?= $this->renderItemSelect()?>
 				<?=$this->renderImage() ?>
-				<div class="col start w-full">
+				<div class="col top w-full">
 					<h3 data-field="post_title"></h3>
 					<p data-attr="date"></p>
 					<p data-field="price"></p>
@@ -1260,7 +1260,7 @@
 					<td class="field show-<?= esc_attr($name) ?>" data-field="<?= esc_attr($name) ?>" data-field-type="<?=$config['type']?>"<?=(in_array($name, $this->stuck)) ? ' data-stuck':''?>>
 						<?php
 						if (in_array('edit', $this->caps)) {
-							echo $makeThisDetailed ? '<details><summary class="row btw">See Value</summary>' : '';
+							echo $makeThisDetailed ? '<details><summary class="row x-btw">See Value</summary>' : '';
 							if (in_array($config['type'], ['selector', 'taxonomy', 'post'])) {
 								$config['autocomplete'] = true;
 							}
@@ -1342,7 +1342,7 @@
 					$makeThisDetailed = (in_array($config['type'], $makeDetails));
 					?>
 					<td class="field show-<?= esc_attr($name) ?>" data-field="<?= esc_attr($name) ?>" data-field-type="<?=$config['type']?>"<?=(in_array($name, $this->stuck)) ? ' data-stuck':''?>>
-						<?= $makeThisDetailed ? '<details><summary class="row btw">See Value</summary>' : '' ?>
+						<?= $makeThisDetailed ? '<details><summary class="row x-btw">See Value</summary>' : '' ?>
 						<?php
 						if (in_array($config['type'], ['selector', 'taxonomy', 'post'])) {
 							$config['autocomplete'] = true;
@@ -1375,7 +1375,7 @@
 					$makeThisDetailed = (in_array($config['type'], $makeDetails));
 					?>
 					<td class="field show-<?= esc_attr($name) ?>" data-field="<?= esc_attr($name) ?>" data-field-type="<?=$config['type']?>"<?=(in_array($name, $this->stuck)) ? ' data-stuck':''?>>
-						<?= $makeThisDetailed ? '<details><summary class="row btw">See Value</summary>' : '' ?>
+						<?= $makeThisDetailed ? '<details><summary class="row x-btw">See Value</summary>' : '' ?>
 						<?= Form::render($name, '', $config); ?>
 						<?= $makeThisDetailed ? '</details>' : '' ?>
 					</td>
@@ -1458,7 +1458,7 @@
 	protected function renderTableActions(): string {
 		ob_start();
 		?>
-		<div class="table-actions row btw nowrap">
+		<div class="table-actions row x-btw nowrap">
 			<?php if (count(array_intersect(['create', 'edit'], $this->caps)) > 0) { ?>
 				<?= jvbRenderToggleTextField(
 					'vertical',
diff --git a/inc/ui/Checkout.php b/inc/ui/Checkout.php
index 974d09f..29be465 100644
--- a/inc/ui/Checkout.php
+++ b/inc/ui/Checkout.php
@@ -74,7 +74,7 @@
 
 		$form .= jvbRenderTabs($tabs, true);
 
-		$form .= '<div class="cart-total row end">
+		$form .= '<div class="cart-total row right">
 				<p class="tax">Tax: <span></span></p>
 				<p class="total">GRAND TOTAL: <span></span></p>
 			</div>
@@ -239,7 +239,7 @@
 				<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>
-				<div class="row btw">
+				<div class="row x-btw">
 					<button type="button" data-clear-cart>' . jvbIcon('trash') . 'Clear Cart</button>
 					<button type="button" data-dismiss>' . jvbIcon('x') . 'Dismiss</button>
 				</div>
diff --git a/inc/ui/Tabs.php b/inc/ui/Tabs.php
index 27ca6e7..909184a 100644
--- a/inc/ui/Tabs.php
+++ b/inc/ui/Tabs.php
@@ -48,7 +48,7 @@
 			return '';
 		}
 
-		$header = '<nav class="tabs row start" role="tablist">';
+		$header = '<nav class="tabs row left" role="tablist">';
 		$content = '';
 		$i = 0;
 
diff --git a/inc/users/UserSettings.php b/inc/users/UserSettings.php
index 87a3659..246d3c1 100644
--- a/inc/users/UserSettings.php
+++ b/inc/users/UserSettings.php
@@ -171,7 +171,7 @@
         <div class="notification-preferences">
             <?php foreach ($favourites as $type => $items) : ?>
                 <details class="notification-group">
-                    <summary class="notification-group-header row btw">
+                    <summary class="notification-group-header row x-btw">
                         <span class="type-label"><?=jvbIcon('heart', ['style' => 'fill'])?> <?= $notify[$type] ?></span>
                         <span class="type-count">( <?= count($items); ?> )</span>
                     </summary>
diff --git a/inc/utility/Image.php b/inc/utility/Image.php
index 83b95e7..0b0afc2 100644
--- a/inc/utility/Image.php
+++ b/inc/utility/Image.php
@@ -153,4 +153,10 @@
 			}
 		);
 	}
+
+	public static function getData(int $imgID):array
+	{
+		return (new Image)->getImageData($imgID);
+	}
+
 }
diff --git a/jvb.php b/jvb.php
index 17669f9..d5da153 100644
--- a/jvb.php
+++ b/jvb.php
@@ -141,6 +141,13 @@
 	return IconsManager::for($source)->get($name, $options);
 }
 
+function jvbFullIcon(string $name, array $options = []):string
+{
+	$source = $options['source'] ?? 'icons';
+	unset($options['source']);
+	return IconsManager::for($source)->getRawSvg($name, $options['style']??null);
+}
+
 /**
  * Get a CSS data URI for an icon
  *
diff --git a/src/drawer-menu/block.json b/src/drawer-menu/block.json
new file mode 100644
index 0000000..93bc73d
--- /dev/null
+++ b/src/drawer-menu/block.json
@@ -0,0 +1,27 @@
+{
+    "$schema": "https://schemas.wp.org/trunk/block.json",
+    "apiVersion": 3,
+    "name": "jvb/drawer-menu",
+    "title": "Drawer Menu",
+    "category": "jvb",
+	"icon": "menu",
+	"version": "1.0.0",
+	"textdomain": "jvb",
+	"supports": {
+		"html": false
+	},
+	"attributes": {
+		"menuId": {
+			"type": "string",
+			"default": ""
+		},
+		"collapsed": {
+			"type": "boolean",
+			"default": true
+		}
+	},
+	"editorScript": "file:./index.js",
+	"editorStyle": "file:./index.css",
+	"style": "file:./style-index.css",
+	"render": "file:./render.php"
+}
diff --git a/src/drawer-menu/edit.js b/src/drawer-menu/edit.js
new file mode 100644
index 0000000..c37544b
--- /dev/null
+++ b/src/drawer-menu/edit.js
@@ -0,0 +1,33 @@
+import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
+import { PanelBody, ToggleControl, TextControl } from '@wordpress/components';
+
+export default function Edit({ attributes, setAttributes }) {
+	const { menuId, collapsed } = attributes;
+	const blockProps = useBlockProps();
+
+	return (
+		<>
+			<InspectorControls>
+				<PanelBody title="Drawer Settings">
+					<TextControl
+						label="Menu ID"
+						value={menuId}
+						onChange={(value) => setAttributes({ menuId: value })}
+						help="PHP-generated menu identifier"
+					/>
+					<ToggleControl
+						label="Start Collapsed"
+						checked={collapsed}
+						onChange={(value) => setAttributes({ collapsed: value })}
+					/>
+				</PanelBody>
+			</InspectorControls>
+			<div {...blockProps}>
+				<div className="drawer-menu-preview">
+					<p>Drawer Menu: {menuId || 'Not configured'}</p>
+					<p>State: {collapsed ? 'Collapsed' : 'Expanded'}</p>
+				</div>
+			</div>
+		</>
+	);
+}
diff --git a/src/drawer-menu/editor.scss b/src/drawer-menu/editor.scss
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/drawer-menu/editor.scss
diff --git a/src/drawer-menu/index.js b/src/drawer-menu/index.js
new file mode 100644
index 0000000..e81333b
--- /dev/null
+++ b/src/drawer-menu/index.js
@@ -0,0 +1,10 @@
+import { registerBlockType } from '@wordpress/blocks';
+import edit from './edit';
+import save from './save';
+import './style.scss';
+import './editor.scss';
+
+registerBlockType('jvb/drawer-menu', {
+	edit,
+	save,
+});
diff --git a/src/drawer-menu/index.php b/src/drawer-menu/index.php
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/drawer-menu/index.php
diff --git a/src/drawer-menu/render.php b/src/drawer-menu/render.php
new file mode 100644
index 0000000..05da41a
--- /dev/null
+++ b/src/drawer-menu/render.php
@@ -0,0 +1,38 @@
+<?php
+
+use JVBase\managers\Cache;
+use JVBase\ui\Navigation;
+
+$menu_id = $attributes['menuId'] ?? '';
+$collapsed = $attributes['collapsed'] ?? true;
+
+// You'd populate this from options, a filter, or however you store menu data
+$menu_items = apply_filters('jvbDrawerItems', [], $menu_id);
+
+if (empty($menu_items) || empty($menu_id)) {
+	return '<p>Please configure the drawer menu in block settings.</p>';
+}
+
+$cache = Cache::for('drawer');
+
+if (!is_front_page()) {
+	$menu_items[] = [
+		'text'	=> 'Home',
+		'url'	=> home_url(),
+		'icon'	=> 'house-simple',
+	];
+}
+$items = array_map(function($item) { return $item['text'];}, $menu_items);
+
+$key = $cache->generateKey($items);
+$menu =  $cache->remember($key,
+function () use ($menu_items, $menu_id, $collapsed) {
+	$menu = new Navigation($menu_id);
+	$menu->asDrawer($collapsed)->populateFromArray($menu_items);
+	return $menu->render();
+});
+
+global $wp;
+
+$current = home_url($wp->request.'/');
+echo str_replace($current.'"', $current.'" class="current" aria-current="page"', $menu);
diff --git a/src/drawer-menu/save.js b/src/drawer-menu/save.js
new file mode 100644
index 0000000..8432b3a
--- /dev/null
+++ b/src/drawer-menu/save.js
@@ -0,0 +1,3 @@
+export default function save() {
+	return null; // Server-side rendered
+}
diff --git a/src/drawer-menu/style.scss b/src/drawer-menu/style.scss
new file mode 100644
index 0000000..52e11af
--- /dev/null
+++ b/src/drawer-menu/style.scss
@@ -0,0 +1,88 @@
+nav.drawer {
+	position: fixed;
+	right: 0;
+	bottom: 0;
+	max-height: 100vh;
+	overflow: hidden auto;
+	width: var(--btn);
+	z-index: var(--z-5);
+	transition: var(--trans-size);
+	--dir: column-reverse;
+	background-color: var(--base);
+	border-left: 1px solid var(--base-200);
+	box-shadow:rgba(var(--base-rgb), var(--op-4)) var(--shdw-left);
+	height: auto;
+	--w: var(--chip_);
+
+	.title,
+	.section-title {
+		display: none;
+	}
+	ul .icon {
+		min-width: var(--chip_);
+	}
+
+	a,
+	.a {
+		height: var(--chipchip);
+		padding: 0 .5rem;
+		width: 100%;
+		gap: .5rem;
+		justify-content: center;
+	}
+
+	.toggle {
+		width: 100%;
+		.icon {
+			transform: rotate(0);
+			transition: var(--trans-transform);
+		}
+	}
+	&.open {
+		width: 240px;
+		.title,
+		.section-title {
+			display: block;
+		}
+
+		.toggle .icon {
+			transform: rotate(180deg);
+		}
+
+		a,
+		.a {
+			justify-content: flex-start;
+		}
+	}
+
+	ul {
+		--dir: column;
+		--gap: 0;
+		--height: auto;
+		padding: 0;
+		margin: 0;
+		height: auto;
+		width:100%;
+	}
+	li {
+		height: auto;
+		width: 100%;
+	}
+
+
+	.row {
+		width: 100%;
+	}
+
+	.menu-section {
+		border-bottom: 1px solid var(--contrast-200);
+	}
+	.section-title {
+		padding: 0.5rem var(--px);
+		font-size: var(--small);
+		text-transform: uppercase;
+		opacity: 0.6;
+		font-weight: bold;
+	}
+
+}
diff --git a/src/drawer-menu/view.js b/src/drawer-menu/view.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/drawer-menu/view.js
@@ -0,0 +1 @@
+
diff --git a/src/faq/block.json b/src/faq/block.json
new file mode 100644
index 0000000..f88d33a
--- /dev/null
+++ b/src/faq/block.json
@@ -0,0 +1,34 @@
+{
+	"$schema": "https://schemas.wp.org/trunk/block.json",
+	"apiVersion": 3,
+	"name": "jvb/faq",
+	"title": "FAQ Block",
+	"category": "jvb",
+	"icon": "info",
+	"description": "Display FAQs organized by sections with customizable ordering",
+	"keywords": ["faq", "questions", "help"],
+	"version": "1.0.0",
+	"textdomain": "jvb",
+	"attributes": {
+		"sectionOrder": {
+			"type": "array",
+			"default": []
+		},
+		"showSectionTitles": {
+			"type": "boolean",
+			"default": true
+		},
+		"collapseByDefault": {
+			"type": "boolean",
+			"default": false
+		}
+	},
+	"supports": {
+		"align": ["wide", "full"],
+		"html": false
+	},
+	"editorScript": "file:./index.js",
+	"editorStyle": "file:./index.css",
+	"style": "file:./style-index.css",
+	"viewScript": "file:./view.js"
+}
diff --git a/src/faq/edit.js b/src/faq/edit.js
new file mode 100644
index 0000000..73e36e4
--- /dev/null
+++ b/src/faq/edit.js
@@ -0,0 +1,145 @@
+import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
+import { PanelBody, ToggleControl, Notice, Button } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { useState, useEffect } from '@wordpress/element';
+import './editor.scss';
+
+export default function Edit({ attributes, setAttributes }) {
+	const { sectionOrder, showSectionTitles, collapseByDefault } = attributes;
+	const [sections, setSections] = useState([]);
+
+	// Get sections from localized script
+	const allSections = window.jvbFaq?.sections || [];
+
+	// Initialize sections with proper ordering
+	useEffect(() => {
+		if (!allSections.length) return;
+
+		if (sectionOrder.length === 0) {
+			// First time - use default order
+			const orderedSections = allSections.map(section => ({
+				id: section.id,
+				name: section.name,
+			}));
+			setSections(orderedSections);
+			setAttributes({ sectionOrder: orderedSections.map(s => s.id) });
+		} else {
+			// Use saved order, add any new sections at the end
+			const orderedSections = [];
+			const existingIds = new Set(sectionOrder);
+
+			// Add sections in saved order
+			sectionOrder.forEach(id => {
+				const section = allSections.find(s => s.id === id);
+				if (section) {
+					orderedSections.push({ id: section.id, name: section.name });
+				}
+			});
+
+			// Add any new sections that weren't in the saved order
+			allSections.forEach(section => {
+				if (!existingIds.has(section.id)) {
+					orderedSections.push({ id: section.id, name: section.name });
+				}
+			});
+
+			setSections(orderedSections);
+		}
+	}, [allSections, sectionOrder]);
+
+	const moveSection = (index, direction) => {
+		const newSections = [...sections];
+		const newIndex = direction === 'up' ? index - 1 : index + 1;
+
+		if (newIndex < 0 || newIndex >= newSections.length) return;
+
+		// Swap sections
+		[newSections[index], newSections[newIndex]] = [newSections[newIndex], newSections[index]];
+
+		setSections(newSections);
+		setAttributes({ sectionOrder: newSections.map(s => s.id) });
+	};
+
+	const blockProps = useBlockProps({
+		className: 'faq-block-editor',
+	});
+
+	return (
+		<>
+			<InspectorControls>
+				<PanelBody title={__('FAQ Settings', 'jvb')} initialOpen={true}>
+					<ToggleControl
+						label={__('Show Section Titles', 'jvb')}
+						checked={showSectionTitles}
+						onChange={(value) => setAttributes({ showSectionTitles: value })}
+						help={__('Display section names as headings', 'jvb')}
+					/>
+					<ToggleControl
+						label={__('Collapse by Default', 'jvb')}
+						checked={collapseByDefault}
+						onChange={(value) => setAttributes({ collapseByDefault: value })}
+						help={__('Questions start collapsed and expand on click', 'jvb')}
+					/>
+				</PanelBody>
+
+				<PanelBody title={__('Section Order', 'jvb')} initialOpen={false}>
+					<p className="components-base-control__help">
+						{__('Use the arrow buttons to reorder sections', 'jvb')}
+					</p>
+					{sections.length > 0 ? (
+						<div className="faq-section-list">
+							{sections.map((section, index) => (
+								<div key={section.id} className="faq-section-item">
+									<div className="faq-section-controls">
+										<Button
+											icon="arrow-up-alt2"
+											label={__('Move up', 'jvb')}
+											disabled={index === 0}
+											onClick={() => moveSection(index, 'up')}
+											className="faq-section-button"
+										/>
+										<Button
+											icon="arrow-down-alt2"
+											label={__('Move down', 'jvb')}
+											disabled={index === sections.length - 1}
+											onClick={() => moveSection(index, 'down')}
+											className="faq-section-button"
+										/>
+									</div>
+									<span className="faq-section-name">{section.name}</span>
+								</div>
+							))}
+						</div>
+					) : (
+						<Notice status="info" isDismissible={false}>
+							{__('No sections found. Create sections in the FAQ taxonomy.', 'jvb')}
+						</Notice>
+					)}
+				</PanelBody>
+			</InspectorControls>
+
+			<div {...blockProps}>
+				<div className="faq-block-preview">
+					<h3>{__('FAQ Block', 'jvb')}</h3>
+					<p>
+						{__('This block will display FAQs organized by sections.', 'jvb')}
+					</p>
+					{sections.length > 0 ? (
+						<div className="faq-sections-preview">
+							<strong>{__('Section Order:', 'jvb')}</strong>
+							<ol>
+								{sections.map((section) => (
+									<li key={section.id}>{section.name}</li>
+								))}
+							</ol>
+						</div>
+					) : (
+						<Notice status="warning" isDismissible={false}>
+							{__('No sections available. Create sections in the FAQ taxonomy.', 'jvb')}
+						</Notice>
+					)}
+				</div>
+			</div>
+		</>
+	);
+}
diff --git a/src/faq/editor.scss b/src/faq/editor.scss
new file mode 100644
index 0000000..d9955a2
--- /dev/null
+++ b/src/faq/editor.scss
@@ -0,0 +1,99 @@
+/**
+ * FAQ Block - Editor Styles
+ */
+
+.faq-block-editor {
+	padding: 2rem;
+	border: 2px dashed #ccc;
+	border-radius: 8px;
+	background: #f9f9f9;
+
+	.faq-block-preview {
+		text-align: center;
+
+		h3 {
+			margin: 0 0 0.5rem;
+			font-size: 1.25rem;
+			font-weight: 600;
+		}
+
+		> p {
+			margin: 0 0 1.5rem;
+			color: #666;
+		}
+
+		.faq-sections-preview {
+			margin-top: 1.5rem;
+			text-align: left;
+			background: white;
+			padding: 1rem;
+			border-radius: 4px;
+
+			strong {
+				display: block;
+				margin-bottom: 0.5rem;
+			}
+
+			ol {
+				margin: 0;
+				padding-left: 1.5rem;
+
+				li {
+					margin: 0.25rem 0;
+					padding: 0.25rem 0;
+				}
+			}
+		}
+	}
+}
+
+// Inspector Controls
+.faq-section-list {
+	display: flex;
+	flex-direction: column;
+	gap: 0.5rem;
+	margin-top: 0.5rem;
+}
+
+.faq-section-item {
+	display: flex;
+	align-items: center;
+	gap: 0.5rem;
+	padding: 0.5rem 0.75rem;
+	background: white;
+	border: 1px solid #ddd;
+	border-radius: 4px;
+	transition: background 150ms ease;
+
+	&:hover {
+		background: #f9f9f9;
+	}
+}
+
+.faq-section-controls {
+	display: flex;
+	gap: 0.25rem;
+	flex-shrink: 0;
+}
+
+.faq-section-button {
+	min-width: 30px !important;
+	padding: 4px !important;
+	height: 30px !important;
+
+	&:disabled {
+		opacity: 0.3;
+		cursor: not-allowed;
+	}
+}
+
+.faq-section-name {
+	flex: 1;
+	font-weight: 500;
+	padding-left: 0.5rem;
+}
+
+// Notice adjustments
+.components-panel__body .components-notice {
+	margin: 1rem 0;
+}
diff --git a/src/faq/index.js b/src/faq/index.js
new file mode 100644
index 0000000..2f2dd15
--- /dev/null
+++ b/src/faq/index.js
@@ -0,0 +1,11 @@
+import { registerBlockType } from '@wordpress/blocks';
+import Edit from './edit';
+import './style.scss';
+import './editor.scss';
+import metadata from './block.json';
+
+registerBlockType(metadata.name, {
+	edit: Edit,
+	// No save function - dynamic block rendered on server
+	save: () => null,
+});
diff --git a/src/faq/index.php b/src/faq/index.php
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/faq/index.php
diff --git a/src/faq/render.php b/src/faq/render.php
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/faq/render.php
diff --git a/src/faq/style.scss b/src/faq/style.scss
new file mode 100644
index 0000000..13564fa
--- /dev/null
+++ b/src/faq/style.scss
@@ -0,0 +1,72 @@
+nav#faq {
+	height: max-content;
+	display: block;
+	background-color: var(--base-100);
+	border-radius: var(--radius-outer);
+	padding: 1.5rem;
+	touch-action: auto;
+	ol {
+		list-style: decimal-leading-zero;
+		height: fit-content;
+		display: block;
+		counter-reset: faq;
+		li {
+			counter-increment: faq;
+			width: max-content;
+			&::before {
+				content: counter(faq);
+				display: block;
+				font-family: var(--heading);
+				font-weight: var(--fw-h-bold);
+			}
+		}
+	}
+	h2 {
+		left: 0;
+		font-size: var(--txt-large);
+		margin: .5rem 0 .5rem;
+	}
+	a {
+		padding: .5rem;
+	}
+}
+
+.faq-block {
+	padding-bottom: 3rem;
+	max-width: none;
+	width: 100%;
+	> * {
+		max-width: var(--wide);
+		margin: 1rem auto;
+	}
+	h2 {
+		margin: 5rem 0 1.5rem;
+	}
+	h3 {
+		margin: 0;
+		text-transform: none;
+	}
+	:target {
+		background-color: var(--base);
+		outline: none;
+
+		h2 {
+			background-color: var(--base);
+			padding: 1rem 1.5rem;
+			border-radius: var(--radius-outer);
+		}
+	}
+	details {
+		max-width: var(--content);
+		margin: 1rem auto;
+		padding: .75rem;
+	}
+	details + details {
+		margin-top: 3rem;
+	}
+	details .button {
+		height: fit-content;
+		display: flex;
+		margin-left: auto;
+	}
+}
diff --git a/src/faq/view.js b/src/faq/view.js
new file mode 100644
index 0000000..1de987d
--- /dev/null
+++ b/src/faq/view.js
@@ -0,0 +1,84 @@
+/**
+ * FAQ Block - Frontend Interactions
+ * Handles accordion functionality for FAQ items
+ */
+
+document.addEventListener('DOMContentLoaded', () => {
+	const faqBlocks = document.querySelectorAll('.faq-block');
+
+	faqBlocks.forEach((block) => {
+		const faqItems = block.querySelectorAll('.faq-item');
+
+		faqItems.forEach((item) => {
+			const button = item.querySelector('.faq-item__question');
+			const answer = item.querySelector('.faq-item__answer');
+
+			if (!button || !answer) return;
+
+			button.addEventListener('click', () => {
+				const isExpanded = button.getAttribute('aria-expanded') === 'true';
+
+				// Toggle this item
+				button.setAttribute('aria-expanded', !isExpanded);
+
+				if (isExpanded) {
+					// Collapse
+					answer.style.height = answer.scrollHeight + 'px';
+					// Force reflow
+					answer.offsetHeight;
+					answer.style.height = '0';
+
+					setTimeout(() => {
+						answer.style.display = 'none';
+						answer.style.height = '';
+					}, 300);
+
+					item.classList.remove('faq-item--expanded');
+				} else {
+					// Expand
+					answer.style.display = 'block';
+					answer.style.height = '0';
+					// Force reflow
+					answer.offsetHeight;
+					answer.style.height = answer.scrollHeight + 'px';
+
+					setTimeout(() => {
+						answer.style.height = 'auto';
+					}, 300);
+
+					item.classList.add('faq-item--expanded');
+				}
+			});
+
+			// Handle keyboard navigation
+			button.addEventListener('keydown', (e) => {
+				// Space or Enter triggers the button
+				if (e.key === ' ' || e.key === 'Enter') {
+					e.preventDefault();
+					button.click();
+				}
+			});
+		});
+	});
+
+	// Optional: Add URL hash navigation
+	// If URL has #faq-123, open that specific FAQ
+	if (window.location.hash) {
+		const hash = window.location.hash.substring(1);
+		const targetItem = document.querySelector(`[data-faq-id="${hash}"]`);
+
+		if (targetItem) {
+			const button = targetItem.querySelector('.faq-item__question');
+			const isExpanded = button.getAttribute('aria-expanded') === 'true';
+
+			if (!isExpanded) {
+				button.click();
+			}
+
+			// Scroll to item after a short delay
+			setTimeout(() => {
+				targetItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
+			}, 350);
+		}
+	}
+});
diff --git a/src/feed/block.json b/src/feed/block.json
new file mode 100644
index 0000000..3fec37e
--- /dev/null
+++ b/src/feed/block.json
@@ -0,0 +1,57 @@
+{
+    "$schema": "https://schemas.wp.org/trunk/block.json",
+    "apiVersion": 3,
+    "name": "jvb/feed",
+    "title": "Feed",
+    "category": "jvb",
+    "icon": "grid-view",
+    "description": "Displays a filterable feed of registered content types",
+    "keywords": [ "feed", "grid" ],
+    "version": "0.9.0",
+    "textdomain": "jvb",
+    "supports": {
+        "html": false,
+        "align": ["wide", "full"]
+    },
+    "attributes": {
+        "title": {
+            "type": "string",
+            "default": "Your Scene"
+        },
+        "inheritQuery": {
+            "type": "boolean",
+            "default": false
+        },
+        "contentTypes": {
+            "type": "array",
+            "default": ["tattoo", "artwork", "artist"],
+            "items": {
+                "type": "string"
+            }
+        },
+        "itemsPerPage": {
+            "type": "number",
+            "default": 36
+        },
+        "defaultOrder": {
+            "type": "string",
+            "default": "date_desc"
+        }
+    },
+    "selectors": {
+        "root": ".feed-block"
+    },
+    "styles": [
+        { "name": "default", "label": "Default", "isDefault": true },
+        { "name": "other", "label": "Other" }
+    ],
+    "example": {
+        "attributes": {
+            "message": "This is a notice!"
+        }
+    },
+    "editorScript": "file:./index.js",
+    "editorStyle": "file:./index.css",
+    "style": "file:./style-index.css",
+    "viewScript": "file:./view.js"
+}
diff --git a/src/feed/edit.js b/src/feed/edit.js
new file mode 100644
index 0000000..2a5228d
--- /dev/null
+++ b/src/feed/edit.js
@@ -0,0 +1,256 @@
+/**
+ * Feed Block - Edit Component
+ * Fetches available feed types from /jvb/v1/feed/types
+ * Allows configuration of content types and inherit query setting
+ */
+
+import { useEffect, useState } from '@wordpress/element';
+import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
+import {
+	PanelBody,
+	CheckboxControl,
+	ToggleControl,
+	Spinner,
+	Notice
+} from '@wordpress/components';
+import apiFetch from '@wordpress/api-fetch';
+import { __ } from '@wordpress/i18n';
+
+export default function Edit({ attributes, setAttributes }) {
+	const [feedTypes, setFeedTypes] = useState(null);
+	const [loading, setLoading] = useState(true);
+	const [error, setError] = useState(null);
+
+	const blockProps = useBlockProps({
+		className: 'feed-block-editor'
+	});
+
+	/**
+	 * Fetch available feed types on component mount
+	 */
+	useEffect(() => {
+		apiFetch({
+			path: '/jvb/v1/feed/types',
+			headers: {
+				'If-Modified-Since': localStorage.getItem('feed_types_modified'),
+			}
+		})
+			.then(types => {
+				setFeedTypes(types);
+				setLoading(false);
+
+				// Store Last-Modified for future requests
+				// (apiFetch doesn't expose response headers easily,
+				// but the server will handle 304s)
+
+				// Initialize contentTypes if not set and not inheriting
+				if (!attributes.contentTypes && !attributes.inheritQuery) {
+					const firstType = Object.keys(types)[0];
+					if (firstType) {
+						setAttributes({ contentTypes: [firstType] });
+					}
+				}
+			})
+			.catch(err => {
+				console.error('Error loading feed types:', err);
+				setError(err.message);
+				setLoading(false);
+			});
+	}, [attributes.inheritQuery]);
+
+	/**
+	 * Toggle a content type in the selection
+	 */
+	const toggleContentType = (slug, checked) => {
+		const currentTypes = attributes.contentTypes || [];
+
+		const newTypes = checked
+			? [...currentTypes, slug]
+			: currentTypes.filter(t => t !== slug);
+
+		setAttributes({ contentTypes: newTypes });
+	};
+
+	/**
+	 * Get friendly label for content type
+	 */
+	const getTypeLabel = (slug, config) => {
+		return `${config.plural} (${config.type})`;
+	};
+
+	/**
+	 * Group types by category for better UX
+	 */
+	const groupedTypes = feedTypes ? {
+		content: Object.entries(feedTypes)
+			.filter(([_, config]) => config.type === 'content'),
+		taxonomy: Object.entries(feedTypes)
+			.filter(([_, config]) => config.type === 'taxonomy')
+	} : { content: [], taxonomy: [] };
+
+	return (
+		<div {...blockProps}>
+			<InspectorControls>
+				<PanelBody
+					title={__('Feed Settings', 'jvb')}
+					initialOpen={true}
+				>
+					<ToggleControl
+						label={__('Inherit from Page Context', 'jvb')}
+						help={
+							attributes.inheritQuery
+								? __('Feed will adapt to the current page (profile, taxonomy, etc.)', 'jvb')
+								: __('Manually select content types to display', 'jvb')
+						}
+						checked={attributes.inheritQuery}
+						onChange={(value) => setAttributes({ inheritQuery: value })}
+					/>
+
+					{!attributes.inheritQuery && (
+						<>
+							{loading && (
+								<div style={{ textAlign: 'center', padding: '20px' }}>
+									<Spinner />
+									<p>{__('Loading feed types...', 'jvb')}</p>
+								</div>
+							)}
+
+							{error && (
+								<Notice status="error" isDismissible={false}>
+									{__('Error loading feed types: ', 'jvb')} {error}
+								</Notice>
+							)}
+
+							{!loading && !error && feedTypes && (
+								<>
+									{groupedTypes.content.length > 0 && (
+										<>
+											<h4>{__('Content Types', 'jvb')}</h4>
+											{groupedTypes.content.map(([slug, config]) => (
+												<CheckboxControl
+													key={slug}
+													label={getTypeLabel(slug, config)}
+													checked={
+														attributes.contentTypes?.includes(slug) || false
+													}
+													onChange={(checked) =>
+														toggleContentType(slug, checked)
+													}
+													help={
+														config.taxonomies?.length > 0
+															? `Filters: ${config.taxonomies.join(', ')}`
+															: null
+													}
+												/>
+											))}
+										</>
+									)}
+
+									{groupedTypes.taxonomy.length > 0 && (
+										<>
+											<h4 style={{ marginTop: '20px' }}>
+												{__('Content Taxonomies', 'jvb')}
+											</h4>
+											<p style={{ fontSize: '12px', color: '#757575' }}>
+												{__('These are collections that group other content', 'jvb')}
+											</p>
+											{groupedTypes.taxonomy.map(([slug, config]) => (
+												<CheckboxControl
+													key={slug}
+													label={getTypeLabel(slug, config)}
+													checked={
+														attributes.contentTypes?.includes(slug) || false
+													}
+													onChange={(checked) =>
+														toggleContentType(slug, checked)
+													}
+													help={
+														config.for_content?.length > 0
+															? `Contains: ${config.for_content.join(', ')}`
+															: null
+													}
+												/>
+											))}
+										</>
+									)}
+
+									{!attributes.contentTypes?.length && (
+										<Notice status="warning" isDismissible={false}>
+											{__('Please select at least one content type', 'jvb')}
+										</Notice>
+									)}
+								</>
+							)}
+						</>
+					)}
+				</PanelBody>
+
+				<PanelBody
+					title={__('Display Settings', 'jvb')}
+					initialOpen={false}
+				>
+					<ToggleControl
+						label={__('Show Gallery View', 'jvb')}
+						help={__('Enable lightbox for images', 'jvb')}
+						checked={attributes.enableGallery || false}
+						onChange={(value) =>
+							setAttributes({ enableGallery: value })
+						}
+					/>
+				</PanelBody>
+			</InspectorControls>
+
+			<div className="feed-block-placeholder">
+				<div className="feed-block-icon">
+					<svg width="48" height="48" viewBox="0 0 24 24" fill="none">
+						<rect x="3" y="3" width="7" height="7" fill="currentColor" opacity="0.3" />
+						<rect x="13" y="3" width="7" height="7" fill="currentColor" opacity="0.3" />
+						<rect x="3" y="13" width="7" height="7" fill="currentColor" opacity="0.3" />
+						<rect x="13" y="13" width="7" height="7" fill="currentColor" opacity="0.3" />
+					</svg>
+				</div>
+
+				<h3>{__('Feed Block', 'jvb')}</h3>
+
+				{attributes.inheritQuery ? (
+					<p className="feed-block-description">
+						{__('📍 Inheriting from page context', 'jvb')}
+					</p>
+				) : (
+					<div className="feed-block-description">
+						{attributes.contentTypes?.length > 0 ? (
+							<>
+								<p><strong>{__('Showing:', 'jvb')}</strong></p>
+								<ul style={{
+									listStyle: 'none',
+									padding: '0',
+									margin: '8px 0'
+								}}>
+									{attributes.contentTypes.map(type => {
+										const config = feedTypes?.[type];
+										return (
+											<li key={type} style={{
+												padding: '4px 0',
+												color: '#2271b1'
+											}}>
+												✓ {config?.plural || type}
+											</li>
+										);
+									})}
+								</ul>
+							</>
+						) : (
+							<p style={{ color: '#d63638' }}>
+								{__('⚠️  No content types selected', 'jvb')}
+							</p>
+						)}
+					</div>
+				)}
+
+				<p className="feed-block-note">
+					{__('Feed will be displayed on the frontend', 'jvb')}
+				</p>
+			</div>
+		</div>
+	);
+}
diff --git a/src/feed/editor.scss b/src/feed/editor.scss
new file mode 100644
index 0000000..8f0baab
--- /dev/null
+++ b/src/feed/editor.scss
@@ -0,0 +1,128 @@
+.feed-content-types {
+    margin-bottom: 16px;
+
+    .components-base-control__label {
+        margin-bottom: 8px;
+        font-weight: 500;
+    }
+
+    .components-checkbox-control {
+        margin-bottom: 8px;
+
+        &:last-child {
+            margin-bottom: 0;
+        }
+
+        .components-checkbox-control__input-container {
+            margin-right: 8px;
+        }
+    }
+}
+.feed-block {
+    border: 1px solid #ddd;
+    padding: 20px;
+    background: white;
+
+    .feed-block-preview {
+        .filter-preview {
+            display: flex;
+            gap: 8px;
+            margin: 16px 0;
+            flex-wrap: wrap;
+
+            .content-type-badge {
+                background: #f0f0f0;
+                padding: 4px 8px;
+                border-radius: 4px;
+                font-size: 12px;
+            }
+        }
+
+        .feed-grid-placeholder {
+            display: grid;
+            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+            gap: 16px;
+            margin-top: 20px;
+
+            .grid-item-placeholder {
+                background: #f0f0f0;
+                aspect-ratio: 1;
+                border-radius: 4px;
+            }
+        }
+    }
+}
+
+.feed-content-types {
+    margin-bottom: 16px;
+
+    .components-base-control__label {
+        margin-bottom: 8px;
+        font-weight: 500;
+    }
+
+    .checkbox-list {
+        border: 1px solid #ddd;
+        border-radius: 4px;
+        max-height: 200px;
+        overflow-y: auto;
+        padding: 8px;
+        background: white;
+
+        .components-checkbox-control {
+            margin: 4px 0;
+
+            &:first-child {
+                margin-top: 0;
+            }
+
+            &:last-child {
+                margin-bottom: 0;
+            }
+        }
+    }
+
+    .select-all-wrapper {
+        margin-top: 8px;
+        padding-top: 8px;
+        border-top: 1px solid #ddd;
+    }
+
+    .components-checkbox-control__input-container {
+        margin-right: 8px;
+    }
+}
+.feed-block {
+    border: 1px solid #ddd;
+    padding: 20px;
+    background: white;
+
+    .feed-block-preview {
+        .filter-preview {
+            display: flex;
+            gap: 8px;
+            margin: 16px 0;
+            flex-wrap: wrap;
+
+            .content-type-badge {
+                background: #f0f0f0;
+                padding: 4px 8px;
+                border-radius: 4px;
+                font-size: 12px;
+            }
+        }
+
+        .feed-grid-placeholder {
+            display: grid;
+            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+            gap: 16px;
+            margin-top: 20px;
+
+            .grid-item-placeholder {
+                background: #f0f0f0;
+                aspect-ratio: 1;
+                border-radius: 4px;
+            }
+        }
+    }
+}
diff --git a/src/feed/index.js b/src/feed/index.js
new file mode 100644
index 0000000..c477d23
--- /dev/null
+++ b/src/feed/index.js
@@ -0,0 +1,39 @@
+/**
+ * Registers a new block provided a unique name and an object defining its behavior.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
+ * All files containing `style` keyword are bundled together. The code used
+ * gets applied both to the front of your site and to the editor.
+ *
+ * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
+ */
+import './style.scss';
+
+/**
+ * Internal dependencies
+ */
+import Edit from './edit';
+import save from './save';
+import metadata from './block.json';
+
+/**
+ * Every block starts by registering a new block type definition.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+registerBlockType( metadata.name, {
+    /**
+     * @see ./edit.js
+     */
+    edit: Edit,
+
+    /**
+     * @see ./save.js
+     */
+    save,
+} );
diff --git a/src/feed/index.php b/src/feed/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/src/feed/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/src/feed/render.php b/src/feed/render.php
new file mode 100644
index 0000000..0112ad1
--- /dev/null
+++ b/src/feed/render.php
@@ -0,0 +1,4 @@
+<?php
+if (!defined('ABSPATH')) {
+    exit; // Exit if accessed directly
+}
diff --git a/src/feed/save.js b/src/feed/save.js
new file mode 100644
index 0000000..8169594
--- /dev/null
+++ b/src/feed/save.js
@@ -0,0 +1,3 @@
+export default function save() {
+    return null; // Dynamic block rendered by PHP
+}
diff --git a/src/feed/style.scss b/src/feed/style.scss
new file mode 100644
index 0000000..3d4e777
--- /dev/null
+++ b/src/feed/style.scss
@@ -0,0 +1,956 @@
+//.feed-block {
+//	max-width: var(--full);
+//	margin: 0 auto;
+//
+//	&:target {
+//		scroll-snap-margin-top: 5rem;
+//		scroll-margin-top: 5rem;
+//		outline: 0;
+//		border-radius: 0;
+//		padding: 0;
+//
+//		.feed-item {
+//			outline: double var(--pink-0);
+//		}
+//	}
+//}
+//
+//.loading .feed-block {
+//	opacity: .7;
+//}
+//
+//.label {
+//	display: flex;
+//	align-items: center;
+//	gap: .25rem;
+//	font-size: .9rem;
+//	a:hover {
+//		color: var(--pink-0);
+//	}
+//}
+//
+///** Filters Form **/
+//.feed-filters {
+//	margin: 2rem 0;
+//	position: sticky;
+//	top: 3rem;
+//	z-index: 15;
+//	background: rgba(var(--base-rgb),var(--op-6));
+//	padding: .25rem 3rem;
+//	details[open] summary {
+//		background-color: var(--overlay);
+//	}
+//	summary {
+//		justify-content: flex-start;
+//
+//		> * {
+//			order: 3;
+//		}
+//		.label {
+//			order: 1;
+//		}
+//		.filter-label {
+//			order: 2;
+//		}
+//		&::after {
+//			order: 4;
+//		}
+//		#favourites + label {
+//			margin-left: auto;
+//		}
+//		#favourites + label:hover,
+//		#favourites:checked + label {
+//			border-color: var(--pink-0);
+//			background-color: var(--pink-0);
+//			color: var(--white);
+//
+//		}
+//		#favourites:checked + label {
+//			animation: pop 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+//		}
+//	}
+//
+//	details[open],
+//	summary:hover {
+//		background-color: rgba(var(--base-rgb),var(--op-6));
+//	}
+//
+//	&:has(#favourites) {
+//		summary::after {
+//			margin-left: 1rem;
+//		}
+//	}
+//}
+//summary > * {
+//	order: 3;
+//}
+//summary .label {
+//	order: 1;
+//}
+//.filter-label {
+//	display: inline-block;
+//	vertical-align: middle;
+//	height: 1.3em;
+//	order: 2;
+//	margin: 0;
+//	padding: 0;
+//
+//	li {
+//		list-style: none;
+//		height: 0;
+//		overflow: hidden;
+//		&.active {
+//			height: 100%;
+//		}
+//	}
+//}
+//.filter-group {
+//	display: flex;
+//	align-items: center;
+//	gap: .5rem;
+//	flex-wrap: wrap;
+//	label {
+//		font-weight: 100;
+//	}
+//	&:has(.order-by) {
+//		justify-content: space-between;
+//	}
+//	.order-by,
+//	.order-direction {
+//		display: flex;
+//		flex-wrap: wrap;
+//		gap: .5rem;
+//		flex: 1;
+//		justify-content: flex-start;
+//		.label {
+//			width: 100%;
+//		}
+//
+//		label:has(.label) {
+//			padding: 0 .35rem;
+//			gap: .5rem;
+//			.label {
+//				font-size: 1rem;
+//			}
+//		}
+//	}
+//}
+//.filter-group >.label {
+//	width: 100%;
+//}
+//
+///**
+//Feed Grid
+// */
+//.feed-grid {
+//	display: grid;
+//	grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+//	gap: .5rem;
+//	margin-bottom: 2rem;
+//	padding: 0 4rem;
+//	--delay: 0s;
+//	--increase: .1s;
+//}
+//.feed-empty-state {
+//	grid-column: 1/-1;
+//	text-align: center;
+//	padding: 2rem;
+//	background: var(--base-100);
+//	border-radius: var(--radius);
+//	margin: 0 auto;
+//	max-width: 600px;
+//}
+///**
+//Placeholders
+// */
+//.placeholder {
+//	aspect-ratio: 1;
+//	background: var(--base);
+//	border: 1rem solid var(--base-50);
+//	border-radius: 1rem;
+//	display: flex;
+//	justify-content: center;
+//	align-items: center;
+//
+//	.icon {
+//		--w: 50%;
+//		color: var(--base-200);
+//		animation: dance 2.5s ease-in-out infinite;
+//
+//	}
+//}
+//
+///**
+//Feed Items
+// */
+//.feed-item {
+//	position: relative;
+//	border-radius: 0.5rem;
+//	overflow: hidden;
+//	background: var(--base-50);
+//	box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+//	opacity: 0;
+//	transition: opacity var(--trans-base) var(--delay);
+//	height: fit-content;
+//	padding: 0;
+//
+//	img {
+//		opacity: .7;
+//		filter: grayscale(.5) sepia(.3) blur(7px);
+//	}
+//	&[data-loaded=true] {
+//		img {
+//			opacity: 1;
+//			filter: none;
+//		}
+//	}
+//
+//	a {
+//		&::before,
+//		&::after {
+//			display: none;
+//		}
+//	}
+//	details a {
+//		font-size: clamp(1rem, 0.9306rem + 0.2222vw, 1.125rem);
+//	}
+//
+//	&[data-loaded] {
+//		opacity: 1;
+//		+ [data-loaded] {
+//			--delay: var(--delay) + var(--increase);
+//		}
+//	}
+//
+//	&.highlighted {
+//		box-shadow: 0 0 0 4px #FF0080, 0 8px 16px rgba(0, 0, 0, 0.1);
+//		animation: highlight-puls 2s ease-in-out;
+//	}
+//	&[open],
+//	&:hover {
+//		.handle {
+//			background-color: var(--overlay-pink-medium);
+//			backdrop-filter: blur(5px);
+//		}
+//	}
+//	summary {
+//		width: calc(100% - 1rem);
+//		height: 100%;
+//		aspect-ratio: 1;
+//		.handle {
+//			position: absolute;
+//			bottom: 0;
+//			left: 0;
+//			right: 0;
+//			background-color: rgba(var(--base-rgb),var(--op-3));
+//			backdrop-filter: blur(5px);
+//			border-radius: var(--radius);
+//			z-index: 1;
+//			padding: .25rem .25rem .25rem 1.1rem;
+//		}
+//		&::after {
+//			z-index: 11;
+//			position: absolute;
+//			bottom: .35rem;
+//			right: .7rem;
+//			width: 1.5rem;
+//			height: 1.5rem;
+//			cursor: pointer;
+//		}
+//	}
+//
+//	label {
+//		font-weight: normal;
+//		text-transform: none;
+//		.icon {
+//			--w: 1.5em;
+//		}
+//	}
+//}
+///**
+//Load More Button
+// */
+//.load-more {
+//	opacity: 1;
+//	margin: 1rem auto;
+//	width: 66.666%;
+//	display: flex;
+//	align-items: center;
+//	gap: .5rem;
+//	padding: .75rem 1.5rem;
+//	background: var(--base);
+//	color: var(--contrast);
+//	border-radius: 4px;
+//	font-size: var(--txt-medium);
+//	transition: all var(--trans-base);
+//	border: 2px solid transparent;
+//	&[hidden] {
+//		opacity: 0;
+//		transition: all var(--trans-base);
+//	}
+//	&:hover {
+//		background: var(--pink-50);
+//		border-color: var(--contrast);
+//		color: var(--white);
+//	}
+//}
+///**
+//favourite button
+// */
+//button.favourite {
+//	position: absolute;
+//	top: .5rem;
+//	right: .5rem;
+//	z-index: 10;
+//	background: rgba(var(--base-rgb),var(--op-4));
+//	border-radius: 50%;
+//	box-shadow: rgba(var(--base-rgb),var(--op-45)) var(--shdw-subtle);
+//	border: none;
+//	width: 2rem;
+//	height: 2rem;
+//	display: flex;
+//	justify-content: center;
+//	align-items: center;
+//	backdrop-filter: blur(5px);
+//	transition: all var(--trans-base);
+//
+//	&:hover {
+//		transform: scale(1.1);
+//		color: var(--pink-0);
+//		background: var(--base);
+//		box-shadow: 0 4px 8px rgba(0,0,0,0.15);
+//	}
+//
+//	&.favourited {
+//		animation: pop 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+//	}
+//}
+//
+//
+///** Images **/
+//.feed-image {
+//	display: block;
+//	aspect-ratio: 1;
+//	overflow: hidden;
+//	width: 100%;
+//	height: 100%;
+//}
+//.feed-images {
+//	width: 100%;
+//	height: 100%;
+//	&.multi {
+//		display :grid;
+//		grid-template-columns: repeat(3, 1fr);
+//		grid-auto-rows: 1fr;
+//		gap: 4px;
+//
+//		> a {
+//			width: 100%;
+//			height: 100%;
+//			aspect-ratio: 1;
+//		}
+//		.feed-image {
+//			grid-row: span 2;
+//			grid-column: span 2;
+//		}
+//	}
+//	img {
+//		width: 100%;
+//		height: 100%;
+//		object-fit: cover;
+//		transition: transform var(--trans-t) var(--trans-fn);
+//	}
+//	a:hover img {
+//		transform: scale(1.05);
+//	}
+//}
+//
+//.feed-item {
+//	&:nth-of-type(4n+2) .multi .feed-image {
+//		grid-column: 2 / span 2;
+//		grid-row: 1 / span 2;
+//	}
+//	&:nth-of-type(4n+3) .multi .feed-image {
+//		grid-row: 2 / span 2;
+//		grid-column: 1 / span 2;
+//	}
+//	&:nth-of-type(4n+4) .multi .feed-image {
+//		grid-column: 2 / span 2;
+//		grid-row: 2 / span 2;
+//	}
+//}
+//
+///** Item Information **/
+//.item-info {
+//	padding: .25rem;
+//	border-left: 1px solid var(--base-200);
+//	>div + div {
+//		margin-top: .5em;
+//		position: relative;
+//
+//		&::before {
+//			content: '';
+//			display: block;
+//			position: absolute;
+//			top: -.3em;
+//			left: -.25rem;
+//			width: 66.6%;
+//			border-bottom: 1px solid var(--base-200);
+//		}
+//	}
+//	h3 {
+//		margin: 0 0 .5em 0!important;
+//		font-size: 1.1rem;
+//		font-family: var(--body);
+//		font-weight: var(--fw-b);
+//	}
+//	span {
+//		text-transform: uppercase;
+//		display: flex;
+//		align-items: center;
+//	}
+//	.icon {
+//		--w: 1.1em;
+//		margin-right: .5em;
+//		display: inline-block;
+//		vertical-align: middle;
+//	}
+//}
+//.item-list {
+//	ul {
+//		margin: 0;
+//		padding: .5em 0;
+//		display: flex;
+//		flex-wrap: wrap;
+//		gap: .5rem;
+//		li {
+//			list-style: none;
+//		}
+//	}
+//	a {
+//		background-color: var(--pink-0);
+//		border: 1px solid transparent;
+//		border-radius: 4px;
+//		color: var(--light-0);
+//		padding: .25em;
+//		line-height: .8;
+//		&:visited {
+//			background-color: var(--pink-100);
+//			color: var(--white);
+//		}
+//		&:visited:hover,
+//		&:visited:focus,
+//		&:hover,
+//		&:focus {
+//			background-color: transparent;
+//			border-color: var(--contrast);
+//			color: var(--contrast);
+//		}
+//	}
+//}
+///**
+//Loading
+// */
+//.loading {
+//	opacity: .7;
+//}
+//
+//.loading-overlay {
+//	position: fixed;
+//	top: 0;
+//	left: 0;
+//	right: 0;
+//	bottom: 0;
+//	background-color: rgba(var(--base-rgb),var(--op-4));
+//	display: flex;
+//	align-items: center;
+//	justify-content: center;
+//	transition: opacity 0.3s ease, visibility 0.3s ease;
+//	z-index: 9999;
+//	opacity: 0;
+//	visibility: hidden;
+//}
+//.loading .loading-overlay {
+//	opacity: 1;
+//	visibility: visible;
+//
+//	&::after {
+//		content: '';
+//		position: absolute;
+//		z-index: -1;
+//		inset: 0;
+//		background: linear-gradient(
+//				90deg,
+//				var(--shimmer)
+//		);
+//		animation: shimmer 3s ease-in-out infinite;
+//	}
+//
+//	.wrapper {
+//		background-color: rgba(var(--base-rgb),var(--op-6));
+//		padding: 2rem;
+//		border-radius: var(--radius);
+//		text-align: center;
+//		max-width: 90%;
+//		width: 400px;
+//		height: 300px;
+//		z-index: 5;
+//		display: flex;
+//		justify-content: center;
+//		align-items: center;
+//		position: relative;
+//
+//		.spinner {
+//			--h: 150px;
+//			--w: calc(var(--h) * 2);
+//			border-top: 5px solid var(--pink-0);
+//			border-radius: 50%;
+//			position: absolute;
+//			width: var(--w);
+//			height: var(--w);
+//			top: calc(50% - var(--h));
+//			left: calc(50% - var(--h));
+//			opacity: .5;
+//			z-index: 0;
+//			animation: spin 1s var(--trans-t) infinite;
+//		}
+//		div.icon {
+//			height: 50px;
+//			width: 50px;
+//
+//			.icon {
+//				--w: 100%;
+//				svg {
+//					animation: dance 2s ease-in-out infinite;
+//					transition: color 0.3s ease;
+//				}
+//			}
+//		}
+//		.status {
+//			height: 200px;
+//			width: 100%;
+//			z-index: 5;
+//			display: flex;
+//			flex-direction: column;
+//			align-items: center;
+//
+//			h3 {
+//				margin: 1.5rem 0 .25rem!important;
+//				color: var(--contrast-200);
+//			}
+//			.message {
+//				margin: 0;
+//				max-width: 275px;
+//				color: var(--contrast-100);
+//				font-size: var(--txt-x-small);
+//				animation: flicker 2s infinite;
+//			}
+//		}
+//	}
+//}
+//
+//
+///* Animations */
+//@keyframes highlight {
+//	0%, 100% {
+//		box-shadow: none;
+//	}
+//	50% {
+//		box-shadow: 0 0 0 4px var(--pink-0);
+//	}
+//}
+//
+//@keyframes pop {
+//	0% { transform: scale(1); }
+//	50% { transform: scale(1.3); }
+//	75% { transform: scale(0.9); }
+//	100% { transform: scale(1); }
+//}
+//
+//@keyframes bubble {
+//	50%  { box-shadow: 19px 0 0 3px, 38px 0 0 7px, 57px 0 0 3px }
+//	100% { box-shadow: 19px 0 0 0, 38px 0 0 3px, 57px 0 0 7px }
+//}
+//@keyframes highlight-pulse {
+//	0%, 100% { box-shadow: 0 0 0 4px #FF0080, 0 8px 16px rgba(0, 0, 0, 0.1); }
+//	50% { box-shadow: 0 0 0 8px #FF0080, 0 12px 24px rgba(0, 0, 0, 0.15); }
+//}
+//
+//@keyframes spin {
+//	to { transform: rotate(360deg); }
+//}
+//
+//@keyframes shimmer {
+//	0% { transform: translateX(-100%); }
+//	50%, 100% { transform: translateX(100%); }
+//}
+//@keyframes dance {
+//	0%, 100% { transform: rotate(-5deg) scale(1);}
+//	50% { transform: rotate(5deg) scale(1.1); }
+//}
+//@keyframes flicker {
+//	0% { opacity: 0.6; }
+//	50% { opacity: 1; }
+//	100% { opacity: 0.6; }
+//}
+//
+//
+///**
+//Accessibility
+// */
+//
+///* Keyboard navigable feed items */
+//.feed-item[tabindex="0"] {
+//	position: relative;
+//}
+//
+//.feed-item[tabindex="0"]::after {
+//	content: '';
+//	position: absolute;
+//	top: 0;
+//	left: 0;
+//	right: 0;
+//	bottom: 0;
+//	pointer-events: none;
+//	border: 2px solid transparent;
+//	transition: border-color 0.2s ease;
+//}
+//
+//.feed-item[tabindex="0"]:focus::after {
+//	border-color: #FF0080;
+//}
+//
+//
+//.feed-block .item {
+//	summary {
+//		a {
+//			background-color: var(--action-0);
+//			display: flex;
+//			gap: .25rem;
+//			flex-wrap: nowrap;
+//			aspect-ratio: 1;
+//			position: relative;
+//		}
+//		img {
+//			width: 50%;
+//			height: 100%;
+//			object-fit: cover;
+//		}
+//
+//	}
+//}
+
+
+.feed-block {
+	grid-column: full;
+	.filters {
+		padding: 1rem 0;
+		max-width:var(--wide);
+		margin: 0 auto;
+
+		.remove-term.remove-term {
+			width: max-content;
+			height: max-content
+		}
+	}
+	.filter-group {
+		position: relative;
+		padding: 2rem 0;
+		.label {
+			position: absolute;
+			left: 0;
+		}
+		> .label {
+			top: 0;
+		}
+		[type=radio] {
+			position:absolute;
+			left: var(--offScreen);
+		}
+		button, label {
+			position: relative;
+			padding: .5rem;
+			height: max-content;
+			&:hover {
+				color: var(--action-contrast);
+			}
+		}
+		button:hover .label,
+		:checked + label .label,
+		label:hover .label {
+			opacity: 1;
+			visibility: visible;
+		}
+		&:has(label:hover) :checked + label .label,
+		button .label,
+		label .label {
+			--height: max-content;
+			opacity: 0;
+			visibility: hidden;
+			bottom: -2rem;
+			width: max-content;
+			white-space: nowrap;
+			font-weight: var(--fw-b);
+		}
+
+
+	}
+	h3 {
+		margin: 0 0 .25rem;
+		font-size: var(--medium);
+	}
+}
+/** PLACEHOLDERS **/
+.placeholder {
+	aspect-ratio: 1;
+	background: var(--base);
+	border: 1rem solid var(--base-50);
+	border-radius: 1rem;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+
+	i.icon {
+		--w: 50%;
+		color: var(--base-200);
+		animation: dance 2.5s ease-in-out infinite;
+	}
+}
+
+.item-grid {
+	padding: 0 var(--chip);
+	max-width: 100%;
+}
+/** FEED ITEM **/
+.feed.item {
+	position: relative;
+	border-radius: 0.5rem;
+	overflow: hidden;
+	background: var(--base-50);
+	box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+	height: fit-content;
+	padding: 0;
+	details {
+		z-index: var(--z-2);
+		width: 100%;
+		position: relative;
+		padding: 0;
+		summary {
+			position:absolute;
+			top: -3rem;
+			left:0;
+			width: 100%;
+			background-color: rgba(var(--base-rgb),var(--op-2));
+			backdrop-filter: blur(5px);
+			&:hover {
+				background-color: rgba(var(--action-rgb),var(--op-45));
+			}
+		}
+		&[open] {
+			padding: .25rem .5rem;
+			summary .icon {
+				opacity: 0;
+			}
+		}
+
+	}
+
+	img {
+		object-fit: cover;
+		width: 100%;
+		height: 100%;
+		//opacity: .7;
+		//filter: grayscale(.5) sepia(.3);
+		&:hover {
+			opacity: .8;
+		}
+	}
+
+	&[data-timeline] {
+		.images {
+			aspect-ratio: 3/2;
+			padding: 0 0 1rem;
+			span {
+				width: 50%;
+				position: absolute;
+				background-color: var(--action-0);
+				color: var(--action-contrast);
+				padding: .25rem .5rem;
+				&:first-of-type {
+					bottom: 0;
+					right: 50%;
+					text-align: right;
+				}
+				&:last-of-type {
+					top: 0;
+					left: 50%;
+				}
+			}
+			> a {
+				position: relative;
+				display: flex;
+				flex-wrap: nowrap;
+				width: 100%;
+				height: 100%;
+			}
+		}
+		img {
+			width: 50%;
+			object-fit: cover;
+			height: 100%;
+			&:first-of-type {
+				border-right: 1px solid var(--action-0);
+			}
+		}
+	}
+
+
+	a {
+		&::before,
+		&::after {
+			display: none;
+		}
+	}
+
+	label {
+		font-weight: normal;
+		text-transform: none;
+		.icon {
+			--w: 1.5em;
+		}
+	}
+}
+
+
+.item-grid:has([data-timeline]) {
+	grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+}
+
+.items-wrap [type=radio],
+.items-wrap [type=checkbox] {
+	position: absolute;
+	opacity: 0;
+	left: -200vw;
+}
+
+.items-wrap [type=radio] + label,
+.items-wrap [type=checkbox] + label {
+	position: relative;
+	cursor: pointer;
+}
+
+.items-wrap [type=radio] + label:hover,
+.items-wrap [type=checkbox] + label:hover {
+	color: var(--action-0);
+}
+
+.items-wrap [type=radio] + label::before,
+.items-wrap [type=checkbox] + label::before,
+.items-wrap [type=radio] + label::after,
+.items-wrap [type=checkbox] + label::after {
+	content: '';
+	position: absolute;
+	top: 50%;
+}
+
+.items-wrap [type=radio] + label::after,
+.items-wrap [type=checkbox] + label::after {
+	left: 5px;
+	transform: translateY(-70%) rotate(45deg);
+	width: 5px;
+	height: 10px;
+	border: solid var(--light-0);
+	border-width: 0 2px 2px 0;
+	display: none;
+}
+
+.items-wrap [type=radio] + label::before,
+.items-wrap [type=checkbox] + label::before {
+	left: 0;
+	transform: translateY(-50%);
+	width: 1rem;
+	height: 1rem;
+	border: 2px solid var(--contrast-200);
+	background-color: var(--base);
+	border-radius: var(--radius);
+}
+
+.items-wrap [type=radio]:hover + label::before,
+.items-wrap [type=checkbox]:hover + label::before {
+	border-color: var(--action-200);
+}
+
+.items-wrap [type=radio]:checked + label::before,
+.items-wrap [type=checkbox]:checked + label::before{
+	background-color: var(--action-0);
+	border-color: var(--action-100);
+}
+
+.items-wrap [type=radio]:checked + label::before {
+	border-radius: 50%;
+}
+
+.items-wrap [type=checkbox]:checked + label::after {
+	display: block;
+	left: 5px;
+	top: 50%;
+	transform: translateY(-70%) rotate(45deg);
+	width: .35rem;
+	height: .66rem;
+	border: solid var(--light-0);
+	border-width: 0 2px 2px 0;
+}
+
+.items-wrap :disabled + label {
+	cursor: not-allowed;
+	background-color: var(--base-50);
+	color: var(--base-200);
+	border-color: var(--base-200);
+}
+
+.items-wrap :disabled + label:hover {
+	background-color: var(--base-50);
+	color: var(--base-200);
+	border-color: var(--base-200);
+}
+
+.items-wrap :disabled + label::before {
+	border-color: var(--base-200);
+}
+
+#jvb-selector .items-wrap [type=radio] + label,
+#jvb-selector .items-wrap [type=checkbox] + label{
+	flex: 1;
+	padding-left: 2rem!important;
+	transform-origin: top center;
+	will-change: transform;
+}
+
+.feed-block + footer {
+	grid-column: full;
+	padding: 0!important;
+	margin: 0;
+	background-color: var(--base-50);
+	z-index: 0;
+	display: flex;
+	justify-content: flex-end;
+	button {
+		width: max-content;
+		margin-left: auto;
+		padding: .35rem .25rem;
+		--w: 1.3em!important;
+		flex-wrap: nowrap;
+		justify-content:flex-start;
+		transition: var(--trans-size);
+		font-size: var(--txt-x-small);
+		min-height: 0;
+		span {
+			display: none;
+			white-space: nowrap;
+		}
+		&:focus span,
+		&:hover span {
+			display: block;
+		}
+	}
+}
diff --git a/src/feed/view.js b/src/feed/view.js
new file mode 100644
index 0000000..87e79b9
--- /dev/null
+++ b/src/feed/view.js
@@ -0,0 +1,747 @@
+class FeedBlock {
+	constructor() {
+		this.container = document.querySelector('section.feed-block');
+		if(!this.container) return;
+
+		this.a11y = window.jvbA11y;
+		this.error = window.jvbError;
+		this.cache = new window.jvbCache('feed');
+		this.templates = window.jvbTemplates;
+
+		this.config = {
+			source: '',
+			context: '',
+			highlight: null,
+			gallery: false,
+			view: this.cache.get('feedView') || 'grid',
+			... this.container.dataset
+		};
+
+		this.init();
+	}
+	init() {
+		this.initElements();
+		this.defineTemplates();
+		this.initListeners();
+		this.initFilters();
+
+		if ('requestIdleCallback' in window) {
+			requestIdleCallback(() => {
+				this.initStore();
+				this.initTaxonomies();
+
+				this.processCachedFilters();
+				this.processURLFilters();
+				this.updateFilterUI();
+				this.initGallery();
+			}, { timeout: 2000 });
+		} else {
+			setTimeout(() => {
+				this.initStore();
+				this.initTaxonomies();
+
+				this.processCachedFilters();
+				this.processURLFilters();
+				this.updateFilterUI();
+				this.initGallery();
+			}, 100);
+		}
+	}
+
+	initElements() {
+		this.selectors = {
+			filterTrigger: '[data-filter]',
+			filters: {
+				actions: 	'.filter-actions .toggle-text',
+				container:	'.filters',
+				content: 	'[data-filter="content"]',
+				orderby: 	'[data-filter="orderby"]',
+				order: 		'[data-filter="order"]',
+				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);
+
+		//Add content and taxonomies
+		this.ui.content = this.ui.filters.container.querySelectorAll('[name="content"]');
+		if (this.ui.content.length === 0) this.ui.content = false;
+		this.ui.taxonomies = this.ui.filters.container.querySelectorAll('[data-taxonomy]');
+		if (this.ui.taxonomies.length === 0) this.ui.taxonomies = false;
+		this.ui.orderbyWrap = this.ui.filters.container.querySelector('[data-for-order]');
+		if (this.ui.orderbyWrap.length === 0) this.ui.orderbyWrap = false;
+		this.ui.order = this.ui.filters.container.querySelectorAll('[data-filter="order"]');
+		if (this.ui.order.length === 0) this.ui.order = false;
+		this.ui.orderby = this.ui.filters.container.querySelectorAll('[data-filter="orderby"]');
+		if (this.ui.orderby.length === 0) this.ui.orderby = false;
+
+		this.orderbyFilters = (this.ui.orderby)
+			? Array.from(this.ui.orderby).map(o => o.value)
+			: [];
+
+		this.contentTypes = (this.ui.content)
+			? Array.from(this.ui.content).map(c => c.value)
+			: [this.container.dataset.content];
+		this.taxonomies = (this.ui.taxonomies?.length > 0)
+			? Array.from(this.ui.taxonomies).map(t => t.dataset.taxonomy)
+			: [];
+	}
+
+	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 defaults = {
+			content:	this.contentTypes[0],
+			orderby:	'date',
+			order:		'desc',
+			page:		1,
+		};
+		if (this.config.context) defaults.context = this.config.context;
+		if (this.config.source) defaults.source = this.config.source;
+
+		this.filters = defaults;
+		this.defaults = {...defaults};
+	}
+	updateFilterUI() {
+		if (this.ui.filters.container) {
+			//Get cached inputs
+			let groups = [
+				this.ui.content,
+				this.ui.orderby,
+				this.ui.order
+			];
+
+			groups.forEach(group => {
+				if(group) {
+					for (let input of group) {
+						let [filter, value] = [input.dataset.filter, input.value];
+						if (!Object.hasOwn(this.store.filters, filter)) break;
+						let doit = this.store.filters[filter] === value;
+						if (doit) {
+							input.checked = doit;
+							break;
+						}
+					}
+				}
+			});
+
+			if (Object.hasOwn(this.store.filters, 'taxonomy')) {
+				for (let [taxonomy, terms] of Object.entries(this.store.filters.taxonomy)) {
+					terms.forEach(termId => {
+						termId = parseInt(termId);
+						const term = this.selector.store.get(termId);
+						if (term) {
+							this.createTermElement(termId);
+						}
+					});
+				}
+			}
+		}
+	}
+
+	handlePopState(e) {
+		if (e.state?.filters) {
+			if (this.processURLFilters()) {
+				this.store.setFilters(this.filters);
+				this.a11y.announce('Feed filters updated from browser history');
+			}
+		}
+	}
+
+	handleClick(e) {
+		if (window.targetCheck(e, this.selectors.buttons.loadMore)) {
+			this.nextPage();
+		} else if (window.targetCheck(e, this.selectors.buttons.clearFilters)) {
+			this.clearFilters();
+		}
+		let remove = window.targetCheck(e, this.selectors.buttons.remove);
+		if (remove) {
+			this.removeSelectedTerm(remove);
+		}
+
+		let refresh = window.targetCheck(e, this.selectors.buttons.refresh);
+		if (refresh) {
+			this.store.clearCache();
+			this.store.fetch();
+		}
+
+		let orderbyButton = window.targetCheck(e, '[data-filter="orderby"]');
+		if (orderbyButton && orderbyButton.value === 'random' && orderbyButton.checked) {
+			// Already selected random, just re-render to trigger new shuffle
+			this.renderItems();
+		}
+	}
+
+	nextPage() {
+		const nextPage = (this.store.filters.page || 1) + 1;
+		const maxPage = this.store.lastResponse?.pages || nextPage;
+		this.store.setFilters({ page: Math.min(nextPage, maxPage) });
+	}
+
+	handleChange(e) {
+		const target = e.target;
+		if (Object.hasOwn(target.dataset, 'filter')) {
+			if (this.allowedFilters.includes(target.dataset.filter)) {
+				let filters = {};
+				filters[target.dataset.filter] = target.value;
+				this.resetFilters(filters);
+			}
+			switch (target.dataset.filter) {
+				case 'content':
+					this.updateContentFor(target.value);
+					break;
+				case 'orderby':
+					this.updateOrderOptions(target.value);
+					break;
+			}
+		}
+	}
+
+	clearFilters() {
+		this.taxFilters = {};
+		window.removeChildren(this.ui.selected);
+
+		this.taxonomies.forEach(tax => {
+			let fieldId = this.getFieldId(tax);
+			this.selector.selectedTerms.get(fieldId)?.clear();
+		});
+
+		this.store.setFilters({
+			...this.defaults,
+			taxonomy: null
+		});
+
+		this.updateURL();
+		this.saveToCacheFilters();
+	}
+
+	resetFilters(filters) {
+		filters = {
+			...this.store.filters,
+			page: 1,
+			... filters
+		}
+		this.store.setFilters(filters);
+
+		this.updateURL();
+		this.saveToCacheFilters();
+	}
+
+	getFieldId(taxonomy) {
+		return this.selector.getFieldId(Array.from(this.ui.taxonomies).filter(tax => tax.dataset.taxonomy === taxonomy)[0]??null);
+	}
+	removeSelectedTerm(button) {
+		const termId = parseInt(button.dataset.id);
+		const taxonomy = button.dataset.taxonomy;
+
+		if (Object.hasOwn(this.taxFilters, taxonomy)){
+			this.taxFilters[taxonomy] = this.taxFilters[taxonomy]
+				.filter(id => id !== termId);
+			if (this.taxFilters[taxonomy].length === 0) {
+				delete this.taxFilters[taxonomy];
+			}
+		}
+		button.remove();
+
+		// Find the fieldId for this taxonomy
+		const field = this.getFieldId(taxonomy);
+		if (field) {
+			this.selector.activeField = field;
+			// Notify selector to remove from its selectedTerms
+			this.selector.removeSelected(termId, field);
+		}
+
+		this.resetFilters({
+			taxonomy: Object.keys(this.taxFilters).length > 0
+				? this.taxFilters
+				: null
+		});
+	}
+
+	updateContentFor(content) {
+		let checkIt = [
+			this.ui.taxonomies,
+			this.ui.orderby
+		];
+		checkIt.forEach(check => {
+			if (!check) return;
+			check.forEach(button => {
+				const forTypes = button.dataset.for?.split(',')??[];
+				button.hidden = forTypes.length > 0 && !forTypes.includes(content);
+				if (button.hidden && button.checked) {
+					button.checked = false;
+				}
+			});
+		});
+	}
+	updateOrderOptions(order) {
+		if (this.ui.orderbyWrap) {
+			let options = this.ui.orderbyWrap.dataset.forOrder.split(',')??[];
+			this.ui.orderbyWrap.hidden = !options.includes(order);
+		}
+	}
+
+	updateFilterControls() {
+		const isHidden = Object.keys(this.taxFilters).length === 0;
+		if (this.ui.buttons.clearFilters) {
+			this.ui.buttons.clearFilters.hidden = isHidden;
+		}
+		if (this.ui.filters.actions) {
+			this.ui.filters.actions.hidden = isHidden;
+		}
+	}
+
+	async initTaxonomies() {
+		this.taxFilters = {};
+		this.selector = window.jvbSelector;
+		// this.selector.scanExistingFields(this.ui.filters.container);
+		// this.taxonomies.map(tax => this.selector.batchFetch.add(tax));
+		// this.selector.batchFetchTaxonomies();
+		this.selector.subscribe((event, data) => {
+			switch (event) {
+				case 'selected-terms':
+
+					this.handleTaxonomyChange(data);
+					break;
+
+			}
+		});
+	}
+	handleTaxonomyChange(data) {
+		const {terms, taxonomy } = data;
+		if (terms.size === 0) return;
+		this.taxFilters[taxonomy] = Array.from(terms);
+		this.resetFilters({ taxonomy: this.taxFilters });
+
+		terms.forEach(t => {
+			this.createTermElement(t);
+		});
+		this.updateFilterControls();
+	}
+	getTaxonomyIcon(taxonomy) {
+		let iconButton = Array.from(this.ui.taxonomies)
+			.find(t => t.dataset.taxonomy === taxonomy);
+		return iconButton?.dataset.icon.trim() || 'tag';
+	}
+	createTermElement(termId) {
+		const term = this.selector.store.get(termId);
+		if (!term) return;
+		if (this.ui.selected.querySelector(`[data-id="${termId}"]`)) return;
+
+		term.icon = this.getTaxonomyIcon(term.taxonomy);
+		this.ui.selected.append(this.templates.create('feedTerm', term));
+	}
+
+	processCachedFilters() {
+		Object.keys(this.filters).forEach(filter => {
+			let cached = this.cache.get(`${this.config.source}_${this.config.context}_${filter}`);
+			if (cached && cached !== this.filters[filter]) {
+				this.filters[filter] = cached;
+			}
+		});
+	}
+
+	processURLFilters() {
+		if (!this.isFirstPage()) return false;
+		const params = new URLSearchParams(window.location.search);
+		if (!params.toString()) return false;
+		let shouldUpdate = false;
+		this.allowedFilters.forEach(filter => {
+			let value = params.get(`f_${filter}`);
+			if (value) {
+				shouldUpdate = true;
+				this.filters[filter] = value;
+			}
+		});
+
+		let hasTax = false;
+		params.forEach((value, key) => {
+			if (key.startsWith('f_tax_')) {
+				hasTax = true;
+				shouldUpdate = true;
+				const taxonomy = key.replace('f_tax_','');
+				this.taxFilters[taxonomy] = value.split(',').map(Number);
+			}
+		});
+		if (shouldUpdate) {
+			if (hasTax) {
+				this.filters.taxonomy = this.taxFilters;
+			}
+			this.resetFilters(this.filters);
+		}
+		return true;
+	}
+
+	updateURL() {
+		const params = new URLSearchParams();
+		this.allowedFilters.forEach(key => {
+			if (Object.hasOwn(this.store.filters, key) && this.store.filters[key] !== this.defaults[key]) {
+				params.set(`f_${key}`, this.store.filters[key]);
+			}
+		});
+
+		for (let [tax, terms] of Object.entries(this.taxFilters)) {
+			if (terms.length > 0) {
+				params.set(`f_tax_${tax}`, terms.join(','));
+			}
+		}
+
+		const newURL = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`;
+		const currentURL = window.location.pathname + window.location.search;  // Change this line
+
+		if (newURL !== currentURL) {
+			window.history.pushState({filters:this.store.filters}, '', newURL);
+		}
+	}
+
+	saveToCacheFilters() {
+		Object.keys(this.store.filters).forEach(filter => {
+			const cacheKey = `${this.config.source}_${this.config.context}_${filter}`;
+
+			if (this.store.filters[filter] !== this.defaults[filter]) {
+				this.cache.set(cacheKey, this.store.filters[filter]);
+			} else {
+				this.cache.remove(cacheKey);
+			}
+		});
+
+		const taxCacheKey = `${this.config.source}_${this.config.context}_taxonomy`;
+		if (Object.keys(this.taxFilters).length > 0) {
+			this.cache.set(taxCacheKey, this.taxFilters);
+		} else {
+			this.cache.remove(taxCacheKey);
+		}
+	}
+
+	initGallery() {
+		this.gallery = (this.config.gallery) ? window.jvbGallery : false;
+		if (this.gallery) {
+			this.gallery.subscribe((event, data) => {
+				if (event === 'load-more' && this.store.lastResponse?.has_more) {
+					this.nextPage();
+				}
+			});
+		}
+	}
+	initStore() {
+		let extraOrderby = this.orderbyFilters.filter(v => !['date','modified','title','random'].includes(v));
+		let extraIndexes = [];
+		extraOrderby.forEach(orderby =>{
+			extraIndexes.push({name:orderby, keyPath: orderby});
+		});
+		const store = 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'},
+					... extraIndexes
+				],
+				filters: this.filters,
+				TTL: 6 * 60 * 60 * 1000, //6 hours
+				showLoading: true,
+				required: 'content',
+			}
+		);
+
+		this.store = store.feed;
+
+		this.store.subscribe((event, data) => {
+			switch (event) {
+				case 'data-loaded':
+					this.renderItems(data.items);
+					this.ui.buttons.loadMore.hidden = true;
+					if (this.store.lastResponse && this.store.lastResponse?.has_more) {
+						this.ui.buttons.loadMore.hidden = !this.store.lastResponse?.has_more??true;
+					}
+					break;
+			}
+		});
+	}
+
+	isFirstPage() {
+		return this.store.filters.page === 1;
+	}
+
+	renderItems(items = null) {
+		items = items??this.store.getFiltered();
+		if (this.isFirstPage()) {
+			window.removeChildren(this.ui.grid);
+		}
+		if (items.length === 0) {
+			this.showEmptyState();
+			this.a11y.announceItems(0, this.isFirstPage());
+		} else {
+			window.chunkIt(
+				items,
+				(item) => this.createItemElement(item),
+				(fragment) => {
+					this.removePlaceholders();
+					this.ui.grid.append(fragment);
+					if (this.config.gallery) this.gallery.buildGalleryItems('.item img');
+					this.a11y.makeNavigable(this.ui.grid.querySelectorAll('.item:not([data-keyboard-nav])'));
+					this.a11y.announceItems(items.length, !this.isFirstPage(), this.store.lastResponse?.has_more??false);
+				},
+				5
+			).then(()=>{});
+		}
+
+		this.updateFilterControls();
+	}
+
+	showEmptyState() {
+		window.removeChildren(this.ui.grid);
+		this.ui.grid.append(this.templates.create('emptyState'));
+	}
+
+	createItemElement(item) {
+		if (typeof item !== 'object') {
+			item = this.store.get(item);
+			if (!item) return;
+		}
+		return this.templates.create(`feedItem${window.uppercaseFirst(item.content)}`, item);
+	}
+	splitIDs(value) {
+		return String(value).split(',').map((value) => parseInt(value.trim())).filter(value=>value);
+	}
+
+	isImageField(item, value) {
+		if (!Object.hasOwn(item, 'images') || Object.keys(item.images).length === 0) {
+			return false;
+		}
+		let values = this.splitIDs(value);
+
+		return values.some(v =>
+			Object.keys(item.images).map(k => parseInt(k)).includes(parseInt(v))
+		);
+	}
+	formatImageFields(element, value, item) {
+		let values = this.splitIDs(value); // Convert string to array first
+		if (values.length === 0) return;
+
+		if (values.length > 1) {
+			let image = element.querySelector('img');
+			if (!image) return;
+			values.forEach(imgID => {
+				let img = image.cloneNode(true);
+				this.formatImageField(img, imgID, item);
+				element.append(img);
+			});
+			image.remove();
+		} else {
+			if (element.tagName !== 'IMG') {
+				element = element.querySelector('img');
+				if (!element) return;
+			}
+			this.formatImageField(element, values[0], item);
+		}
+	}
+	formatImageField(element, value, item) {
+		let imgData = item.images[value]??false;
+		if (!imgData) return;
+		[
+			element.src,
+			element.srcset,
+			element.alt
+		] = [
+			imgData.tiny,
+			`${imgData.tiny} 50w, ${imgData.small} 300w, ${imgData.medium} 1024w`,
+			imgData['image-alt-text']
+		]
+	}
+	isTaxonomyField(item, field) {
+		if (!Object.hasOwn(item, 'taxonomies') || Object.keys(item.taxonomies).length === 0) {
+			return false;
+		}
+
+		return Object.keys(item.taxonomies).includes(field);
+	}
+	formatTaxonomyField(element, item, field, value) {
+		if (element.tagName !== 'UL' || !element.querySelector('li')) return;
+		let values = this.splitIDs(value);
+		if (values.length === 0) {
+			element.remove();
+		}
+		let listItem = element.querySelector('li');
+		for (let termID of values) {
+			let term = item.taxonomies[field][termID]??false;
+			if (!term) continue;
+			let termItem = listItem.cloneNode(true);
+			let link = termItem.querySelector('a');
+			if (!link) continue;
+
+			let title = window.decodeHTMLEntities(term.title);
+
+			[
+				link.href,
+				link.title,
+				link.textContent
+			] = [
+				term.url,
+				`See more ${title}`,
+				title
+			];
+			element.append(termItem);
+		}
+		listItem.remove();
+	}
+	isTimeField(el) {
+		return el.tagName === 'TIME' || el.querySelector('time') !== null;
+	}
+	formatTimeField(element, value) {
+		if (element.tagName !== 'TIME') {
+			element = element.querySelector('time');
+			if (!element) return;
+		}
+		element.setAttribute('datetime', value);
+		element.textContent = window.formatTimeAgo(value, 'F Y');
+	}
+	formatField(element, value) {
+		element.textContent = window.decodeHTMLEntities(value);
+	}
+
+	addTimelineElements(item, template) {
+		let [
+			afterEl,
+			number,
+			started,
+			last
+		] = [
+			template.querySelector('span.after-text'),
+			template.querySelector('[data-field="number"] b'),
+			template.querySelector('[data-field="started"] time'),
+			template.querySelector('[data-field="updated"] time')
+		];
+
+		if (afterEl) {
+			afterEl.textContent = `After ${item.number - 1} Tx`;
+		}
+		if (number) {
+			number.textContent = item.number - 1;
+		}
+		if (started) {
+			this.formatTimeField(started, item.fields.timeline[0]['post_date']);
+		}
+		if (last) {
+			this.formatTimeField(last, item.fields.timeline[item.fields.timeline.length - 1]['post_date']);
+		}
+	}
+
+	removePlaceholders() {
+		const placeholders = this.ui.grid.querySelectorAll('.placeholder');
+		if (placeholders.length > 0) {
+			placeholders.forEach(p => p.remove());
+		}
+	}
+
+	defineTemplates() {
+		const T = this.templates;
+		const f = this;
+
+		T.define('feedTerm', {
+			refs: {
+				icon: '.icon',
+				span: 'span'
+			},
+			setup({el, refs, manyRefs, data}) {
+				el.dataset.id = data.id;
+				el.dataset.taxonomy = data.taxonomy;
+				if (refs.icon) refs.icon.className = `icon icon=${data.icon}`;
+				if (refs.span) refs.span.textContent = window.decodeHTMLEntities(data.name);
+			}
+		});
+		T.define('emptyState');
+
+		this.contentTypes.forEach(content => {
+			T.define(`feedItem${window.uppercaseFirst(content)}`, {
+				refs: {
+					link: 'a',
+				},
+				manyRefs: {
+					fields: '[data-field]',
+				},
+				setup({el, refs, manyRefs, data}) {
+					const isTimeline = Object.hasOwn(el.dataset, 'timeline');
+					if (manyRefs.fields) {
+						for (let field of manyRefs.fields) {
+							if (isTimeline && ['timeline','number'].includes(field.dataset.field)) continue;
+
+							const value = Object.hasOwn(data.fields, field.dataset.field)? data.fields[field.dataset.field] : false;
+							if (!value) {
+								field.remove();
+								continue;
+							}
+							if (f.isImageField(data, value)) {
+								f.formatImageField(field, value, data);
+							} else if (f.isTaxonomyField(data, field.dataset.field)) {
+								f.formatTaxonomyField(field, data, field.dataset.field, value);
+							} else if (f.isTimeField(field)) {
+								f.formatTimeField(field, value);
+							} else {
+								f.formatField(field, value);
+							}
+						}
+						if (refs.link && data.url !== '') {
+							refs.link.href = data.url;
+							refs.link.title = `View ${data.fields['post_title']??'Item'}`;
+						}
+						if (isTimeline ) f.addTimelineElements(data, el);
+					}
+				}
+			})
+		});
+	}
+
+	// addPlaceholders() {
+	// 	let total = this.contentTypes.length;
+	// 	const fragment = document.createDocumentFragment();
+	// 	for (let i = 0; i < 12; i++) {
+	// 		let template = window.getTemplate('placeholderTemplate');
+	//
+	// 		let rand = Math.floor(Math.random() * total);
+	// 		let icon;
+	// 		if (this.ui.content && this.ui.content.length > 0) {
+	// 			icon = this.ui.content.filter((content) => { return content.value === this.contentTypes[rand]}).querySelector('.icon').cloneNode(true);
+	// 		} else {
+	// 			icon = window.getIcon(this.container.dataset.icon);
+	// 		}
+	// 		template.append(icon);
+	// 		fragment.append(template);
+	// 	}
+	// 	this.ui.grid.append(fragment);
+	// }
+}
+
+document.addEventListener('DOMContentLoaded', async function() {
+	window.auth.subscribe(event => {
+		if (event === 'auth-loaded') {
+			window.feedBlock = new FeedBlock();
+		}
+	});
+});
diff --git a/src/feed/viewOld.js b/src/feed/viewOld.js
new file mode 100644
index 0000000..c65be60
--- /dev/null
+++ b/src/feed/viewOld.js
@@ -0,0 +1,770 @@
+class FeedBlockOld {
+	constructor() {
+		this.container = document.querySelector('section.feed-block');
+		if (!this.container) {
+			return;
+		}
+
+		this.a11y = window.jvbA11y;
+		this.cache = new window.jvbCache('feed');
+		this.error = window.jvbError;
+
+		this.config = {
+			source: '',
+			context: '',
+			highlight: null,
+			gallery: false,
+			view: this.cache.get('feedView') || 'grid',
+			... this.container.dataset
+		};
+		this.initElements();
+		this.initFilters();
+
+
+		this.loadWhenAble();
+	}
+
+	loadWhenAble() {
+		if ('requestIdleCallback' in window) {
+			requestIdleCallback(() => {
+				this.initTaxonomies();
+				this.initStore();
+				this.initListeners();
+				this.initGallery();
+			}, { timeout: 2000 });
+		} else {
+			setTimeout(() => {
+				this.initTaxonomies();
+				this.initStore();
+				this.initListeners();
+				this.initGallery();
+			}, 100);
+		}
+	}
+
+	initElements() {
+		this.currentTaxonomies = new Set();			// Allowed Taxonomies, grabbed from active buttons
+		this.taxonomyFilters = {};
+		this.elements = {
+			filterTrigger: '[data-filter]',
+			filters: {
+				content: '[data-filter="content"]',
+				orderby: '[data-filter="orderby"]',
+				order: '[data-filter="order"]',
+				match: '[data-filter="match"]',
+				favourites: '[data-filter="favourites"]',
+				taxonomy: '[data-filter^="taxonomy"]'
+			},
+			selectedTax: '.selected-items',
+			clearFilter: 'button.clear-filters',
+			loadMore: 'button.load-more',
+			filterContainer: '.filters',
+			grid: '.item-grid',
+		};
+		this.ui = window.uiFromSelectors(this.elements);
+
+
+		this.ui.content = this.ui.filterContainer.querySelectorAll('[name="content"]')??false;
+		this.ui.taxonomies = this.ui.filterContainer.querySelectorAll('[data-taxonomy]');
+		if (this.ui.content && this.ui.content.length > 0) {
+			this.contentTypes = Array.from(
+				this.ui.content
+			).map(content => content.value);
+		} else {
+			this.contentTypes = [this.container.dataset['content']];
+		}
+
+		if (this.ui.taxonomies.length>0) {
+			this.taxonomies = Array.from(
+				this.ui.taxonomies,
+			).map(content => content.dataset.taxonomy);
+		} else {
+			this.taxonomies = [];
+		}
+
+
+	}
+
+	async initTaxonomies() {
+		this.selector = window.jvbSelector;
+		const buttons = document.querySelectorAll('[data-filter="taxonomy"]');
+
+		this.selector.isInitializing = true;
+		buttons.forEach((button) => {
+			const taxonomy = button.dataset.taxonomy;
+			this.currentTaxonomies.add(taxonomy);
+
+			this.selector.registerFilterButton(button, {
+				button: button,
+				buttonSelector: '[data-filter="taxonomy"]',
+				selected: this.ui.selectedTax
+			});
+
+			// Add preload listeners
+			this.addTaxonomyPreloadListeners(button, taxonomy);
+		});
+
+		this.selector.isInitializing = false;
+
+		this.selector.subscribe((event, data) => {
+			if (event === 'selected-terms') this.handleTaxonomyChange(data);
+		});
+	}
+
+	addTaxonomyPreloadListeners(button, taxonomy) {
+		const preload = () => {
+			this.selector.preloadTaxonomy(taxonomy);
+		};
+
+		// Desktop hover
+		button.addEventListener('mouseenter', preload, { once: true });
+
+		// Touch/keyboard (fires before click)
+		button.addEventListener('pointerdown', preload, { once: true });
+
+		// Keyboard focus
+		button.addEventListener('focus', preload, { once: true });
+	}
+
+	handleTaxonomyChange(data) {
+		const { terms, taxonomy } = data;
+
+		// Update only the current taxonomy's terms
+		if (terms.size > 0) {
+			this.taxonomyFilters[taxonomy] = Array.from(terms.keys());
+		} else {
+			// Remove taxonomy if no terms selected
+			delete this.taxonomyFilters[taxonomy];
+		}
+
+		// Build filters object with all taxonomies
+		let filters = {
+			page: 1
+		};
+
+		// Add taxonomy filters if any exist
+		if (Object.keys(this.taxonomyFilters).length > 0) {
+			filters.taxonomy = this.taxonomyFilters;
+		}
+
+		this.updateFilter(filters);
+	}
+
+	clearAllTaxonomies() {
+		this.taxonomyFilters = {};
+		window.removeChildren(this.ui.selectedTax);
+
+		this.updateFilter({
+			taxonomy: null,
+			page: 1
+		});
+	}
+
+	initFilters() {
+		//defaults
+		this.filters = {
+			content: 	this.contentTypes[0],
+			orderby: 	'date',
+			order: 		'desc',
+			page: 		1
+		};
+		if (this.config.context) this.filters.context = this.config.context;
+		if (this.config.source) this.filters.source = this.config.source;
+
+		//check the cache
+		this.processCachedFilters();
+		//check url
+		this.processURLFilters();
+
+		// Set initial UI state
+		this.syncUIToFilters();
+	}
+	syncUIToFilters() {
+		if (this.ui.filterContainer) {
+			// Check radio buttons
+			Object.entries(this.filters).forEach(([key, value]) => {
+				const input = this.ui.filterContainer.querySelector(`[data-filter="${key}"][value="${value}"]`);
+				if (input) {
+					input.checked = true;
+				}
+			});
+		}
+
+		// Update content-specific visibility
+		this.updateContentFor(this.filters.content);
+	}
+	nextPage() {
+		this.store.setFilter('page', this.store.filters.page++);
+	}
+
+	initStore() {
+		const store = 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: 'modified'},
+					{ name: 'title', keyPath: 'title'}
+				],
+				filters: this.filters,
+				TTL: 6 * 60 * 60 * 1000,
+				showLoading: true,
+				required: 'content',
+				delayFetch: true
+			}
+		);
+		this.store = store.feed;
+
+		this.store.subscribe((event, data) => {
+			switch (event) {
+				case 'data-loaded':
+					this.renderItems();
+					this.ui.loadMore.hidden = true;
+					if (this.store.lastResponse && this.store.lastResponse['has_more']) {
+						this.ui.loadMore.hidden = !this.store.lastResponse['has_more'];
+					}
+					break;
+			}
+		});
+	}
+
+	initGallery() {
+		this.gallery = (this.config.gallery) ? window.jvbGallery : false;
+		if (this.gallery) {
+			this.gallery.subscribe((event, data) => {
+				if (event === 'load-more' && this.store.lastResponse) {
+					if (this.store.lastResponse['has_more']) {
+						this.nextPage();
+					}
+				}
+			});
+		}
+	}
+
+	processCachedFilters() {
+		Object.keys(this.filters).forEach(filter => {
+			let cached = this.cache.get(`${this.config.source}_${this.config.context}_${filter}`);
+			if (cached && cached !== this.filters[filter]){
+				this.filters[filter] = cached;
+			}
+		});
+	}
+
+	processURLFilters() {
+		if (this.filters.page > 1) {
+			return false;
+		}
+		const params = new URLSearchParams(window.location.search);
+
+		if (!params.toString()) {
+			return false;
+		}
+		let filters = ['content', 'order', 'orderby', 'favourites', 'match'];
+		filters.forEach(filter => {
+			let value = params.get(`f_${filter}`);
+			if (value) {
+				this.filters[filter] = value;
+				let input = this.ui.filters[filter];
+				if (input) {
+					input.checked = true;
+				}
+			}
+		});
+
+		let hasTaxonomy = false;
+		// Load taxonomy filters from URL
+		params.forEach((value, key) => {
+			if (key.startsWith('f_tax_')) {
+				hasTaxonomy = true;
+				const taxonomy = key.replace('f_tax_', '');
+				if (!this.taxonomyFilters[taxonomy]) {
+					this.taxonomyFilters[taxonomy] = [];
+				}
+				this.taxonomyFilters[taxonomy] = value.split(',').map(Number);
+			}
+		});
+		if (this.ui.filterContainer && hasTaxonomy) {
+			for (let [tax, ids] in Object.entries(this.taxonomyFilters)) {
+				let button = this.ui.filterContainer.querySelector(`[data-taxonomy="${tax}"]`);
+				if (button) {
+					if (button.dataset.fieldId) {
+						let field = this.selector.get(button.dataset.fieldId);
+						field.selectedTerms = new Set(ids);
+						this.selector.initFieldDisplay(button.dataset.fieldId);
+					} else {
+						this.selector.registerField(button, {
+							button: button,
+							buttonSelector: '[data-filter="taxonomy"]',
+							selected: this.ui.selectedTax,
+							selectedItems: ids
+						});
+					}
+				}
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Update URL with current filters (for sharing/bookmarking)
+	 */
+	updateURL() {
+		const params = new URLSearchParams();
+
+		// Add simple filters
+		['content', 'order', 'orderby', 'match'].forEach(key => {
+			if (this.filters[key]) {
+				params.set(`f_${key}`, this.filters[key]);
+			}
+		});
+
+		// Add taxonomy filters
+		Object.entries(this.taxonomyFilters).forEach(([taxonomy, terms]) => {
+			if (terms.length > 0) {
+				params.set(`f_tax_${taxonomy}`, terms.join(','));
+			}
+		});
+
+		// Update URL without reload
+		const newURL = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`;
+		window.history.pushState({ filters: this.filters }, '', newURL);
+	}
+
+	renderItems() {
+		let items = this.store.getFiltered();
+		if (this.store.filters['page'] === 1) {
+			window.removeChildren(this.ui.grid);
+		}
+
+		if (items.length === 0) {
+			this.a11y.announceItems(0, this.store.filters['page'] > 0);
+			return;
+		}
+
+		const fragment = document.createDocumentFragment();
+		const batchSize = 10;
+
+		const processBatch = (startIndex) => {
+			const endIndex = Math.min(startIndex + batchSize, items.length);
+
+			for (let i = startIndex; i < endIndex; i++) {
+				const item = items[i];
+				const element = this.createItemElement(item);
+
+				fragment.appendChild(element);
+			}
+
+			if (endIndex < items.length) {
+				requestAnimationFrame(() => processBatch(endIndex));
+			} else {
+				this.removePlaceholders();
+				this.ui.grid.append(fragment);
+
+				if (this.config.gallery) {
+					this.gallery.updateGalleryItems(this.gallery.getGalleryItems());
+				}
+
+				this.a11y.makeNavigable(this.ui.grid.querySelectorAll('.item:not([data-keyboard-nav])'));
+				this.a11y.announceItems(items.length, this.store.filters['page'] > 1, this.store.hasMore);
+			}
+		};
+
+		if (items.length > 0) {
+			processBatch(0);
+		} else {
+			this.a11y.announceItems(0, this.store.filters['page'] >1, false);
+		}
+
+		if (this.ui.filters.match) {
+			this.ui.filters.match.hidden = Object.keys(this.taxonomyFilters).length === 0;
+		}
+		if (this.ui.clearFilter) {
+			this.ui.clearFilter.hidden = Object.keys(this.taxonomyFilters).length === 0;
+		}
+	}
+
+	/**
+	 *
+	 * @param {object} item
+	 */
+	createItemElement(item) {
+		let template = window.getTemplate(`feedItem${window.uppercaseFirst(item.content)}`);
+
+		const isTimeline = Object.hasOwn(template.dataset, 'timeline');
+
+		// Format fields using helpers
+		for (let [fieldName, value] of Object.entries(item.fields)) {
+			if (isTimeline && ['timeline', 'number'].includes(fieldName)) continue;
+			let el = template.querySelector(`[data-field="${fieldName}"]`);
+			if (!el) continue;
+
+			if (value === '') {
+				el.remove();
+				continue;
+			}
+
+			if (this.isImageField(item, value)) {
+				this.formatImageFields(el, value, item);
+			} else if (this.isTaxonomyField(item, fieldName)) {
+				this.formatTaxonomyField(el, item, fieldName, value);
+			} else if (this.isTimeField(el)) {
+				this.formatTimeField(el, value);
+			} else {
+				this.formatField(el, value);
+			}
+		}
+
+		// Handle link
+		let link = template.querySelector('a');
+		if (link && item.url !== '') {
+			[
+				link.href,
+				link.title
+			] = [
+				item.url,
+				`View ${item.fields['post_title']??'Item'}`
+			];
+		}
+
+		if (isTimeline) {
+			this.addTimelineElements(item, template);
+		}
+
+		return template;
+	}
+	splitIDs(value) {
+		return String(value).split(',').map((value) => parseInt(value.trim())).filter(value=>value);
+	}
+	isImageField(item, value) {
+		if (!Object.hasOwn(item, 'images') || Object.keys(item.images).length === 0) {
+			return false;
+		}
+		let values = this.splitIDs(value);
+
+		return values.some(v =>
+			Object.keys(item.images).map(k => parseInt(k)).includes(parseInt(v))
+		);
+	}
+	formatImageFields(element, value, item) {
+		let values = this.splitIDs(value); // Convert string to array first
+		if (values.length === 0) return;
+
+		if (values.length > 1) {
+			let image = element.querySelector('img');
+			if (!image) return;
+			values.forEach(imgID => {
+				let img = image.cloneNode(true);
+				this.formatImageField(img, imgID, item);
+				element.append(img);
+			});
+			image.remove();
+		} else {
+			if (element.tagName !== 'IMG') {
+				element = element.querySelector('img');
+				if (!element) return;
+			}
+			this.formatImageField(element, values[0], item);
+		}
+	}
+	formatImageField(element, value, item) {
+		let imgData = item.images[value]??false;
+		if (!imgData) return;
+		[
+			element.src,
+			element.srcset,
+			element.alt
+		] = [
+			imgData.tiny,
+			`${imgData.tiny} 50w, ${imgData.small} 300w, ${imgData.medium} 1024w`,
+			imgData['image-alt-text']
+		]
+	}
+	isTaxonomyField(item, field) {
+		if (!Object.hasOwn(item, 'taxonomies') || Object.keys(item.taxonomies).length === 0) {
+			return false;
+		}
+
+		return Object.keys(item.taxonomies).includes(field);
+	}
+	formatTaxonomyField(element, item, field, value) {
+		if (element.tagName !== 'UL' || !element.querySelector('li')) return;
+		let values = this.splitIDs(value);
+		if (values.length === 0) {
+			element.remove();
+		}
+		let listItem = element.querySelector('li');
+		for (let termID of values) {
+			let term = item.taxonomies[field][termID]??false;
+			if (!term) continue;
+			let termItem = listItem.cloneNode(true);
+			let link = termItem.querySelector('a');
+			if (!link) continue;
+
+			[
+				link.href,
+				link.title,
+				link.textContent
+			] = [
+				term.url,
+				`See more ${term.title}`,
+				term.title
+			];
+			element.append(termItem);
+		}
+		listItem.remove();
+	}
+	isTimeField(el) {
+		return el.tagName === 'TIME' || el.querySelector('time') !== null;
+	}
+	formatTimeField(element, value) {
+		if (element.tagName !== 'TIME') {
+			element = element.querySelector('time');
+			if (!element) return;
+		}
+		element.setAttribute('datetime', value);
+		element.textContent = window.formatTimeAgo(value, 'F Y');
+	}
+	formatField(element, value) {
+		element.textContent = value;
+	}
+
+	addTimelineElements(item, template) {
+		let [
+			afterEl,
+			number,
+			started,
+			last
+		] = [
+			template.querySelector('span.after-text'),
+			template.querySelector('[data-field="number"] b'),
+			template.querySelector('[data-field="started"] time'),
+			template.querySelector('[data-field="updated"] time')
+		];
+
+		if (afterEl) {
+			afterEl.textContent = `After ${item.fields.number} Tx`;
+		}
+		if (number) {
+			number.textContent = item.fields.number;
+		}
+		if (started) {
+			this.formatTimeField(started, item.fields.timeline[0]['post_date']);
+		}
+		if (last) {
+			this.formatTimeField(last, item.fields.timeline[item.fields.timeline.length - 1]['post_date']);
+		}
+	}
+
+	removePlaceholders() {
+		const placeholders = this.ui.grid.querySelectorAll('.placeholder');
+		if (placeholders.length > 0) {
+			placeholders.forEach(p => p.remove());
+		}
+	}
+
+
+	addPlaceholders() {
+		let total = this.contentTypes.length;
+		const fragment = document.createDocumentFragment();
+		for (let i = 0; i < 12; i++) {
+			let template = window.getTemplate('placeholderTemplate');
+
+			let rand = Math.floor(Math.random() * total);
+			let icon;
+			if (this.ui.content && this.ui.content.length > 0) {
+				icon = this.ui.content.filter((content) => { return content.value === this.contentTypes[rand]}).querySelector('.icon').cloneNode(true);
+			} else {
+				icon = window.getIcon(this.container.dataset.icon);
+			}
+			template.append(icon);
+			fragment.append(template);
+		}
+		this.ui.grid.append(fragment);
+	}
+
+
+
+	/**
+	 *
+	 * @param {object} filters {name: value}
+	 */
+	updateFilter(filters) {
+		//double check filters are what we're expecting
+		let allowed = ['taxonomy','favourites','match', ... Object.keys(this.filters)];
+
+		filters = Object.keys(filters)
+			.filter(key => allowed.includes(key))
+			.reduce((obj, key) => {
+				obj[key] = filters[key];
+				return obj;
+			}, {});
+
+		if (window.getDifferences.map(this.filters, filters)) {
+			this.filters = { ...this.filters, ...filters };  // Merge instead of replace
+			this.updateURL();
+			this.store.setFilters(filters);
+		}
+	}
+	/**
+	 * Update visible filters based on selected content type
+	 */
+	updateContentFor(contentType) {
+		// Update taxonomy filter visibility
+		const taxonomyButtons = this.ui.filterContainer.querySelectorAll('[data-filter="taxonomy"]');
+		taxonomyButtons.forEach(button => {
+			const forTypes = button.dataset.for?.split(',') || [];
+			button.hidden = forTypes.length > 0 && !forTypes.includes(contentType);
+		});
+
+		// Update ordering options
+		const orderButtons = this.ui.filterContainer.querySelectorAll('[data-for]');
+		orderButtons.forEach(button => {
+			const forTypes = button.dataset.for?.split(',') || [];
+			if (forTypes.length > 0) {
+				button.hidden = !forTypes.includes(contentType);
+				// Uncheck if hiding
+				if (button.hidden && button.checked) {
+					button.checked = false;
+				}
+			}
+		});
+
+		// Update order direction visibility based on selected orderby
+		const orderBy = this.ui.filterContainer.querySelector('[name="orderby"]:checked');
+		this.updateOrderDirectionVisibility(orderBy?.value);
+	}
+
+	/**
+	 * Show/hide order direction based on orderby selection
+	 */
+	updateOrderDirectionVisibility(orderBy) {
+		const orderDirection = this.ui.filterContainer.querySelector('.order-direction');
+		if (orderDirection) {
+			const forOrders = orderDirection.dataset.forOrder?.split(',') || [];
+			orderDirection.hidden = forOrders.length > 0 && !forOrders.includes(orderBy);
+		}
+	}
+	/*********************************************************************
+	LISTENERS
+	 *********************************************************************/
+	initListeners() {
+		this.popStateHandler 	= this.handlePopState.bind(this);
+		this.clickHandler		= this.handleClick.bind(this);
+		this.changeHandler		= this.handleChange.bind(this);
+		this.imageObserver = null;
+		this.resizeObserver = null;
+		if ('IntersectionObserver' in window) {
+			this.imageObserver = new IntersectionObserver(entries => {
+				entries.forEach(entry => {
+					this.loadImage(entry.target);
+					this.imageObserver.unobserve(entry.target);
+				});
+			}, {
+				rootMargin: '100px',
+				threshold: .1
+			});
+		}
+
+		if ('ResizeObserver' in window) {
+			this.resizeObserver = new ResizeObserver(() => {
+				window.debouncer.schedule(
+					'feed-update-images',
+					() => this.updateImageSizes(),
+					250
+				);
+			});
+		} else {
+			window.addEventListener('resize', () => {
+				window.debouncer.schedule(
+					'feed-update-images',
+					() => this.updateImageSizes(),
+					250
+				);
+			});
+		}
+
+		window.addEventListener('popstate', this.popStateHandler);
+		document.addEventListener('click', this.clickHandler);
+		document.addEventListener('change', this.changeHandler);
+	}
+
+	handlePopState(e) {
+		if (e.state?.filters) {
+			if (this.processURLFilters()) {
+				this.store.setFilters(this.filters);
+				this.a11y.announce('Feed filters updated from browser history');
+			}
+		}
+	}
+
+	handleClick(e) {
+		if (window.targetCheck(e, this.elements.loadMore)) {
+			this.nextPage();
+		} else if (window.targetCheck(e, this.elements.clearFilter)) {
+			this.clearAllTaxonomies();
+		} else if (window.targetCheck(e, '.remove-item')) {
+			this.handleRemoveSelectedTerm(e);
+		}
+	}
+
+	handleRemoveSelectedTerm(e) {
+		const selectedItem = e.target.closest('.selected-item');
+		if (!selectedItem) return;
+
+		const termId = parseInt(selectedItem.dataset.id);
+		const taxonomy = selectedItem.dataset.taxonomy;
+
+		// Remove from filters
+		if (this.taxonomyFilters[taxonomy]) {
+			this.taxonomyFilters[taxonomy] = this.taxonomyFilters[taxonomy]
+				.filter(id => id !== termId);
+
+			if (this.taxonomyFilters[taxonomy].length === 0) {
+				delete this.taxonomyFilters[taxonomy];
+			}
+		}
+
+		// Remove from UI
+		selectedItem.remove();
+
+		// Update filters
+		this.updateFilter({
+			taxonomy: Object.keys(this.taxonomyFilters).length > 0
+				? this.taxonomyFilters
+				: null,
+			page: 1
+		});
+	}
+
+	handleChange(e) {
+		let target = e.target;
+		if (Object.hasOwn(target.dataset, 'filter')) {
+			if (target.dataset.filter === 'content') {
+				this.updateContentFor(target.value);
+				this.updateFilter({ content: target.value, page: 1 });
+			} else if (target.dataset.filter === 'orderby') {
+				this.updateOrderDirectionVisibility(target.value);
+				this.updateFilter({ orderby: target.value, page: 1 });
+			} else if (target.dataset.filter === 'order') {
+				this.updateFilter({ order: target.value, page: 1 });
+			} else if (target.dataset.filter === 'match') {
+				this.updateFilter({ match: target.checked ? 'all' : 'any', page: 1 });
+			} else if (target.dataset.filter === 'favourites') {
+				this.updateFilter({ favourites: target.checked, page: 1 });
+			}
+		}
+	}
+}
+
+document.addEventListener('DOMContentLoaded', async function() {
+	window.auth.subscribe(event => {
+		if (event === 'auth-loaded') {
+			window.feedBlock = new FeedBlock();
+		}
+	});
+});
diff --git a/src/fields/block.json b/src/fields/block.json
new file mode 100644
index 0000000..085ccdf
--- /dev/null
+++ b/src/fields/block.json
@@ -0,0 +1,25 @@
+{
+    "$schema": "https://schemas.wp.org/trunk/block.json",
+    "apiVersion": 3,
+    "name": "jvb/fields",
+    "title": "JakeVan Fields",
+    "category": "jvb",
+    "icon": "ellipses",
+    "description": "Access data from your custom fields",
+    "keywords": [ "field", "custom", "jake" ],
+    "version": "0.9.0",
+    "textdomain": "jvb",
+    "supports": {
+        "html": false,
+        "align": ["wide", "full"]
+    },
+    "selectors": {
+        "root": ".jvb-f"
+    },
+    "styles": [],
+    "render": "file:./render.php",
+    "editorScript": "file:./index.js",
+    "editorStyle": "file:./index.css",
+    "style": "file:./style-index.css",
+    "viewScript": "file:./view.js"
+}
diff --git a/src/fields/edit.js b/src/fields/edit.js
new file mode 100644
index 0000000..6741773
--- /dev/null
+++ b/src/fields/edit.js
@@ -0,0 +1,29 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
+import { SelectControl, ToggleControl, PanelBody } from '@wordpress/components';
+
+/**
+ * Styles
+ */
+import './editor.scss';
+
+/**
+ * Edit function for Summary Block
+ */
+export default function Edit({ attributes, setAttributes }) {
+    const blockProps = useBlockProps();
+
+    return (
+        <div {...blockProps}>
+            <div className="jvb-summary-preview">
+                <h3>{__('Summary', 'jvb')}</h3>
+                <p className="jvb-list-preview-note">
+                    {__('This will inherit the current query to build the information from our custom meta on the front end.', 'jvb')}
+                </p>
+            </div>
+        </div>
+    );
+}
diff --git a/src/fields/editor.scss b/src/fields/editor.scss
new file mode 100644
index 0000000..bdf5776
--- /dev/null
+++ b/src/fields/editor.scss
@@ -0,0 +1,20 @@
+/**
+ * Directory List Block Editor Styles
+ */
+.jvb-summary-preview {
+    padding: 20px;
+    background-color: #f8f9fa;
+    border: 1px solid #e2e4e7;
+    border-radius: 4px;
+
+    h3 {
+        margin-top: 0;
+        padding-bottom: 10px;
+        border-bottom: 1px solid #ff0080;
+    }
+    &-note {
+        font-style: italic;
+        color: #555d66;
+        margin-bottom: 0;
+    }
+}
diff --git a/src/fields/index.js b/src/fields/index.js
new file mode 100644
index 0000000..c477d23
--- /dev/null
+++ b/src/fields/index.js
@@ -0,0 +1,39 @@
+/**
+ * Registers a new block provided a unique name and an object defining its behavior.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
+ * All files containing `style` keyword are bundled together. The code used
+ * gets applied both to the front of your site and to the editor.
+ *
+ * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
+ */
+import './style.scss';
+
+/**
+ * Internal dependencies
+ */
+import Edit from './edit';
+import save from './save';
+import metadata from './block.json';
+
+/**
+ * Every block starts by registering a new block type definition.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+registerBlockType( metadata.name, {
+    /**
+     * @see ./edit.js
+     */
+    edit: Edit,
+
+    /**
+     * @see ./save.js
+     */
+    save,
+} );
diff --git a/src/fields/index.php b/src/fields/index.php
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/fields/index.php
diff --git a/src/fields/render.php b/src/fields/render.php
new file mode 100644
index 0000000..b5645eb
--- /dev/null
+++ b/src/fields/render.php
@@ -0,0 +1,320 @@
+<?php
+
+use JVBase\managers\Cache;
+use JVBase\meta\Meta;
+use JVBase\meta\Render;
+use JVBase\registrar\Registrar;
+
+if (!defined('ABSPATH')) {
+    exit; // Exit if accessed directly
+}
+/**
+ * Summary Block Render
+ *
+ * @package Edmonton_Ink
+ */
+
+function jvbRenderSummaryBlock(array $attributes):string
+{
+
+    // Buffer output
+    if (is_tax()) {
+        switch (get_queried_object()->taxonomy) {
+            case BASE.'shop':
+                return jvbRenderShopSummary();
+            default:
+                return jvbRenderTermSummary();
+        }
+    } elseif (is_singular()) {
+        return jvbRenderArtistSummary();
+    }
+    return '';
+}
+
+function jvbRenderArtistSummary():string
+{
+    $current = get_queried_object();
+    $cache = Cache::for('artistSummary', WEEK_IN_SECONDS);
+    $key = $current->ID;
+    $cached = $cache->get($key);
+    if ($cached !== false) {
+        return $cached;
+    }
+
+    ob_start();
+    $meta = Meta::forPost($current->ID);
+    $artist = jvbContentFromUser((int)$current->post_author);
+
+	$registrar = Registrar::getInstance($current->post_type);
+	$sections = [];
+	if ($registrar) {
+		$sections = $registrar->getSections();
+	}
+
+
+
+
+//    $handler = JVB()->getContent(str_replace(BASE,'', $current->post_type));
+    ?>
+    <nav id="artist" class="on-this-page index">
+        <label>Jump to:
+            <button type="button" aria-label="Show Index" title="Show Index" class="toggle" aria-expanded="false">
+                <?= jvbIcon('plus-square')?>
+            </button>
+        </label>
+        <ul>
+            <li><a href="#top" title="Back to Top"><?=jvbIcon('caret-circle-up')?></a></li>
+            <li><a href="#about">About</a></li>
+            <li><a href="#styles">Styles</a></li>
+            <li><a href="#contact">Contact</a></li>
+            <li><a href="#work">Work</a></li>
+        </ul>
+    </nav>
+    <header id="top">
+        <h1><small><?=(!empty($artist['city'])) ? $artist['city']['name'] :'Edmonton'?>'s Best <?= (!empty($artist['type'])) ?
+                    $artist['type']['name']:'Tattoo Artists'?>:
+            </small><?=$artist['display_name']?></h1>
+        <div>
+			<?php if (!empty($artist['shop'])) : ?>
+            <ul class="term-list shop">
+                <li>
+                    <a href="<?=$artist['shop']['url']?>" title="Learn more about <?=$artist['shop']['name']?>">
+                        <?= strtolower($artist['shop']['name'])?>
+                    </a>
+                </li>
+            </ul>
+			<?php endif; ?>
+			<?php if (!empty($artist['city'])): ?>
+            <ul class="term-list city">
+                <li>
+                    <a href="<?=$artist['city']['url']?>" title="See who else is rocking out of <?=$artist['city']['name']?>">
+                        <?= strtolower($artist['city']['name'])?>
+                    </a>
+                </li>
+            </ul>
+			<?php endif; ?>
+            <?php $styles = $meta->get('top_styles');
+            if (!empty($styles)) {
+                ?>
+                <ul class="term-list style">
+                    <?php
+                    foreach ($styles as $style) {
+                        $term = get_term((int)$style, BASE.'style');
+                        if ($term && !is_wp_error($term)) {
+                            $link = get_term_link((int)$style, BASE.'style');
+                            ?>
+                            <li>
+                                <a href="<?=$link?>" title="Learn more about <?=html_entity_decode($term->name)?>">
+                                    <?=strtolower(html_entity_decode($term->name))?>
+                                </a>
+                            </li>
+                            <?php
+                        }
+                    }
+                    ?>
+                </ul>
+                <?php
+            }
+            ?>
+        </div>
+    </header>
+    <section>
+        <details class="bio-info">
+            <summary class="row x-btw">
+                <h2>About <?= ($artist['name'] !== '') ? $artist['name'] : strtok($artist['display_name'], ' ')?></h2>
+            </summary>
+            <div class="columns stack-small">
+                <div class="column">
+                    <?= Render::renderFrom($meta, 'image_portrait'); ?>
+                </div>
+                <div class="column">
+					<?= Render::renderFrom($meta, 'short_bio'); ?>
+                </div>
+            </div>
+            <div id="styles">
+                <h3>Works In</h3>
+                <?= jvbGetTheTerms('style', $current->ID) ?>
+            </div>
+            <div class="contact">
+                <h3>Contact:</h3>
+                <?php
+                echo jvbRenderContactInfo($current->ID, $meta);
+                echo jvbRenderLinks($current->ID, $meta);
+                ?>
+            </div>
+
+            <div id="about">
+				<?= Render::renderFrom($meta, 'bio')?>
+            </div>
+        </details>
+    </section>
+    <section id="contact" class="">
+        <h2>Contact <?=$artist['name']?></h2>
+        <?php
+        echo jvbRenderContactInfo($current->ID, 'post');
+        echo jvbRenderLinks($current->ID, 'post');
+        ?>
+    </section>
+    <?php
+    $finished = ob_get_clean();
+    $cache->set($key, $finished);
+    return $finished;
+}
+
+function jvbRenderShopSummary()
+{
+    $current = get_queried_object();
+
+    $cache = Cache::for('shop_bio', WEEK_IN_SECONDS)->connect('taxonomy');
+    $key = $current->term_id;
+    $cached = $cache->get($key);
+    if ($cached !== false) {
+        return $cached;
+    }
+
+    ob_start();
+
+    $meta = Meta::forTerm($current->term_id);
+	$fields = $meta->getAll(['average_rating', 'established', 'bio','location','hours','specialties','awards','reviews']);
+    ?>
+    <nav id="shop" class="on-this-page index">
+        <label>Jump to:
+            <button type="button" aria-label="Show Index" title="Show Index" class="toggle" aria-expanded="false">
+                <?= jvbIcon('plus-square')?>
+            </button>
+        </label>
+        <ul>
+            <li><a href="#top" title="Back to Top"><?=jvbIcon('caret-circle-up')?></a></li> <?php
+            if ($fields['rating'] !== 'none') {
+                ?>
+                <li><a href="#rating">Rating</a></li>
+                <?php
+            } elseif ($fields['opened'] !== '') {
+                ?>
+                <li><a href="#opened">Opened</a></li>
+                <?php
+            } elseif ($fields['location'] !== '') {
+                ?>
+                <li><a href="#location">Location</a></li>
+                <?php
+            } elseif ($fields['about'] !== '') {
+                ?>
+                <li><a href="#about">About</a></li>
+                <?php
+            } elseif ($fields['hours'] !== '') {
+                ?>
+                <li><a href="#hours">Hours</a></li>
+                <?php
+            } elseif ($fields['specialties'] !== '') {
+                ?>
+                <li><a href="#specialties">Specialties</a></li>
+                <?php
+            } elseif ($fields['awards'] !== '') {
+                ?>
+                <li><a href="#awards">Awards</a></li>
+                <?php
+            } elseif ($fields['reviews'] !== '') {
+                ?>
+                <li><a href="#reviews">Reviews</a></li>
+                <?php
+            }
+            ?>
+            <li><a href="#contact">Contact</a></li>
+            <li><a href="#artists">Artists</a></li>
+        </ul>
+    </nav>
+    <header id="top">
+        <div class="columns stack-small">
+            <div class="column">
+                <?=jvbFormatImage($meta->get('image'))?>
+            </div>
+            <div class="column">
+                <h1>
+                    <small><?= (get_term((int)$meta->get('city'), BASE.'city')) ?
+                            get_term((int)$meta->get('city'), BASE.'city')->name :
+                            'Edmonton'?>'s Best Tattoo Shops</small>
+                    <?=$current->name?>
+                </h1>
+                <?= jvbFormatRating($current->term_id, 'term') ?>
+				<?= Render::renderFrom($meta,   'slogan'); ?>
+            </div>
+        </div>
+    </header>
+    <section>
+        <details class="bio-info">
+            <summary class="row x-btw">
+                <h2>Learn More About <?=$current->name?></h2>
+            </summary>
+            <div class="map">
+				<?= Render::renderFrom($meta, 'location'); ?>
+            </div>
+            <div class="short-bio">
+				<?= Render::renderFrom($meta, 'short_bio'); ?>
+            </div>
+
+            <div class="contact">
+                <h3>Contact:</h3>
+                <?php
+                echo jvbRenderContactInfo($current->term_id, 'term');
+                echo jvbRenderLinks($current->term_id, 'term');
+                ?>
+            </div>
+
+            <div id="about">
+				<?= Render::renderFrom($meta, 'bio')?>
+            </div>
+        </details>
+    </section>
+    <section id="contact" class="">
+        <h2>Contact </h2>
+        <?php
+        echo jvbRenderContactInfo($current->term_id, 'term');
+        echo jvbRenderLinks($current->term_id, 'term');
+        ?>
+    </section>
+    <?= jvbRenderHours($current->term_id, 'term')?>
+
+
+    <?php
+    $finished = ob_get_clean();
+    $cache->set($key, $finished);
+    return $finished;
+}
+
+
+function jvbRenderTermSummary()
+{
+    $current = get_queried_object();
+    $cache = Cache::for('term_summary', WEEK_IN_SECONDS)->connect('taxonomy');
+    $key = $current->ID;
+    $cached = $cache->get($key);
+    if ($cached !== false) {
+        return $cached;
+    }
+
+    ob_start();
+	$tax = jvbNoBase($current->taxonomy);
+    switch ($tax) {
+        case 'style':
+            $title = 'Tattoo Artists';
+            break;
+        case 'theme':
+            $title = 'Tattoos';
+            break;
+        default:
+            $title = '';
+    }
+
+    $meta = Meta::forTerm($current->ID);
+    $fields = $meta->getAll();
+
+    ?>
+    <header id="top">
+        <h1><?= get_the_archive_title() ?></h1>
+    </header>
+
+    <?php
+    $finished = ob_get_clean();
+    $cache->set($key, $finished);
+    return $finished;
+}
diff --git a/src/fields/save.js b/src/fields/save.js
new file mode 100644
index 0000000..8169594
--- /dev/null
+++ b/src/fields/save.js
@@ -0,0 +1,3 @@
+export default function save() {
+    return null; // Dynamic block rendered by PHP
+}
diff --git a/src/fields/style.scss b/src/fields/style.scss
new file mode 100644
index 0000000..b182fe9
--- /dev/null
+++ b/src/fields/style.scss
@@ -0,0 +1,20 @@
+details > div {
+	margin: 1rem 0;
+}
+
+main > header:not(:has(img)) {
+	margin-top: 3rem!important;
+}
+
+header a::before {
+	display: none!important;
+}
+
+header + details {
+	margin: 1.5rem auto 3rem!important;
+	max-width: var(--wide);
+}
+
+main {
+	padding-top: 0!important;
+}
diff --git a/src/fields/view.js b/src/fields/view.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/fields/view.js
@@ -0,0 +1 @@
+
diff --git a/src/forms/block.json b/src/forms/block.json
new file mode 100644
index 0000000..7279b38
--- /dev/null
+++ b/src/forms/block.json
@@ -0,0 +1,47 @@
+{
+    "$schema": "https://schemas.wp.org/trunk/block.json",
+    "apiVersion": 3,
+    "name": "jvb/forms",
+    "title": "Contact Forms",
+    "category": "jvb",
+    "icon": "align-center",
+    "description": "Our custom contact forms",
+    "keywords": [ "form", "forms", "contact" ],
+    "version": "1.0.0",
+    "textdomain": "jvb",
+    "attributes": {
+        "formType": {
+            "type": "string",
+            "default": ""
+        },
+        "showLabels": {
+            "type": "boolean",
+            "default": true
+        },
+        "customEmailTo": {
+            "type": "string",
+            "default": ""
+        }
+    },
+    "supports": {
+        "html": false,
+        "align": ["wide", "full"]
+    },
+    "selectors": {
+        "root": ".jvb-form-block"
+    },
+    "styles": [
+        { "name": "default", "label": "Default", "isDefault": true }
+    ],
+    "example": {
+        "attributes": {
+            "formType": "contact",
+            "showLabels": true
+        }
+    },
+    "render": "file:./render.php",
+    "editorScript": "file:./index.js",
+    "editorStyle": "file:./editor.scss",
+    "style": "file:./style-index.css",
+    "viewScript": "file:./view.js"
+}
diff --git a/src/forms/edit.js b/src/forms/edit.js
new file mode 100644
index 0000000..3a8387b
--- /dev/null
+++ b/src/forms/edit.js
@@ -0,0 +1,319 @@
+/**
+ * edit.js
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
+import {
+    PanelBody,
+    SelectControl,
+    ToggleControl,
+    TextControl,
+    Notice
+} from '@wordpress/components';
+import { useState, useEffect } from '@wordpress/element';
+
+/**
+ * Styles
+ */
+import './editor.scss';
+
+/**
+ * Edit function for Form Block
+ */
+export default function Edit({ attributes, setAttributes }) {
+    const {
+        formType,
+        showLabels,
+        customEmailTo,
+        turnstileEnabled
+    } = attributes;
+
+    const [isPreviewVisible, setIsPreviewVisible] = useState(true);
+
+    const blockProps = useBlockProps({
+        className: `jvb-form-block ${formType ? `jvb-form-block-${formType}` : ''}`
+    });
+
+	// Get form types from localized data, with fallback
+	const getFormTypes = () => {
+		if (typeof window !== 'undefined' && window.jvbFormsData && window.jvbFormsData.formTypes) {
+			return window.jvbFormsData.formTypes;
+		}
+
+		// Fallback if data isn't available
+		return [
+			{ label: __('Select a form type', 'jvb'), value: '' },
+			{ label: __('No forms available', 'jvb'), value: '', disabled: true }
+		];
+	};
+
+	// Get available forms configuration
+	const getAvailableForms = () => {
+		console.log(window.jvbFormsData);
+		if (typeof window !== 'undefined' && window.jvbFormsData && window.jvbFormsData.availableForms) {
+			return window.jvbFormsData.availableForms;
+		}
+		return {};
+	};
+
+	const formTypes = getFormTypes();
+	const availableForms = getAvailableForms();
+
+	// Get form configuration based on selected type
+	const getCurrentFormConfig = () => {
+		if (!formType || !availableForms[formType]) {
+			return null;
+		}
+		return availableForms[formType];
+	};
+
+	// Form labels based on the selected form type
+	const getFormLabels = () => {
+		const formConfig = getCurrentFormConfig();
+
+		if (formConfig) {
+			return {
+				title: formConfig.title || __('Form', 'jvb'),
+				description: Array.isArray(formConfig.description)
+					? formConfig.description.join(' ')
+					: (formConfig.description || ''),
+				button: formConfig.submit || __('Submit', 'jvb')
+			};
+		}
+
+		return {
+			title: __('Form', 'jvb'),
+			description: formType ? __('Loading form configuration...', 'jvb') : __('Please select a form type in the sidebar', 'jvb'),
+			button: __('Submit', 'jvb')
+		};
+	};
+
+	const formLabels = getFormLabels();
+
+    // Render a preview of the form in the editor
+    const renderFormPreview = () => {
+        if (!formType) {
+            return (
+                <Notice status="warning" isDismissible={false}>
+                    {__('Please select a form type in the block settings.', 'jvb')}
+                </Notice>
+            );
+        }
+
+        let formFields = [];
+
+        switch (formType) {
+            case 'contact':
+                formFields = [
+                    { id: 'name', label: __('Name', 'jvb'), type: 'text', required: true },
+                    { id: 'email', label: __('Email', 'jvb'), type: 'email', required: true },
+                    { id: 'phone', label: __('Phone', 'jvb'), type: 'tel', required: true },
+                    { id: 'instagram', label: __('Instagram URL', 'jvb'), type: 'url' },
+                    { id: 'contact_methods', label: __('Preferred Contact', 'jvb'), type: 'checkboxes',
+                        options: [
+                            { value: 'text', label: __('Text', 'jvb') },
+                            { value: 'call', label: __('Call', 'jvb') },
+                            { value: 'email', label: __('Email', 'jvb') },
+                            { value: 'instagram', label: __('Instagram', 'jvb') }
+                        ]
+                    },
+                    { id: 'message', label: __('Your Message', 'jvb'), type: 'textarea', required: true }
+                ];
+                break;
+
+            case 'feature_request':
+                formFields = [
+                    { id: 'name', label: __('Name', 'jvb'), type: 'text', help: __('Required if you want us to follow up.', 'jvb') },
+                    { id: 'email', label: __('Email', 'jvb'), type: 'email', help: __('Required if you want us to follow up.', 'jvb') },
+                    { id: 'follow_up', label: __('Would you like me to follow up with you?', 'jvb'), type: 'checkbox' },
+                    { id: 'target_audience', label: __('This Feature is For', 'jvb'), type: 'checkboxes',
+                        options: [
+                            { value: 'artists', label: __('Artists', 'jvb') },
+                            { value: 'visitors', label: __('Site Visitors', 'jvb') },
+                            { value: 'partners', label: __('Partners', 'jvb') },
+                            { value: 'other', label: __('Other', 'jvb') }
+                        ]
+                    },
+                    { id: 'feature_name', label: __('Name your Feature', 'jvb'), type: 'text', required: true },
+                    { id: 'message', label: __('Describe Your Feature', 'jvb'), type: 'textarea', required: true }
+                ];
+                break;
+
+            case 'technical_issue':
+                formFields = [
+                    { id: 'name', label: __('Name', 'jvb'), type: 'text' },
+                    { id: 'email', label: __('Email', 'jvb'), type: 'email' },
+                    { id: 'follow_up', label: __('Would you like me to follow up with you?', 'jvb'), type: 'checkbox' },
+                    { id: 'issue_type', label: __('Type of Issue', 'jvb'), type: 'checkboxes',
+                        options: [
+                            { value: 'visual', label: __('Visual', 'jvb') },
+                            { value: 'error', label: __('Error Page', 'jvb') },
+                            { value: 'other', label: __('Other', 'jvb') }
+                        ]
+                    },
+                    { id: 'message', label: __('Please describe the issue.', 'jvb'), type: 'textarea', required: true }
+                ];
+                break;
+        }
+
+        return (
+            <>
+                <h3 className="jvb-form-title">{formLabels.title}</h3>
+                <p className="jvb-form-description">{formLabels.description}</p>
+
+                <div className="jvb-form-preview">
+                    {formFields.map((field) => (
+                        <div key={field.id} className="jvb-form-field">
+                            {showLabels && (
+                                <label htmlFor={`jvb-${field.id}`} className={field.required ? 'required' : ''}>
+                                    {field.label}
+                                </label>
+                            )}
+
+                            {field.type === 'text' && (
+                                <input
+                                    type="text"
+                                    id={`jvb-${field.id}`}
+                                    placeholder={showLabels ? '' : field.label}
+                                    disabled
+                                />
+                            )}
+
+                            {field.type === 'email' && (
+                                <input
+                                    type="email"
+                                    id={`jvb-${field.id}`}
+                                    placeholder={showLabels ? '' : field.label}
+                                    disabled
+                                />
+                            )}
+
+                            {field.type === 'tel' && (
+                                <input
+                                    type="tel"
+                                    id={`jvb-${field.id}`}
+                                    placeholder={showLabels ? '' : field.label}
+                                    disabled
+                                />
+                            )}
+
+                            {field.type === 'url' && (
+                                <input
+                                    type="url"
+                                    id={`jvb-${field.id}`}
+                                    placeholder={showLabels ? '' : field.label}
+                                    disabled
+                                />
+                            )}
+
+                            {field.type === 'textarea' && (
+                                <textarea
+                                    id={`jvb-${field.id}`}
+                                    placeholder={showLabels ? '' : field.label}
+                                    disabled
+                                    rows="4"
+                                ></textarea>
+                            )}
+
+                            {field.type === 'checkbox' && (
+                                <div className="jvb-form-checkbox">
+                                    <input
+                                        type="checkbox"
+                                        id={`jvb-${field.id}`}
+                                        disabled
+                                    />
+                                    <label htmlFor={`jvb-${field.id}`}>{field.label}</label>
+                                </div>
+                            )}
+
+                            {field.type === 'checkboxes' && field.options && (
+                                <div className="jvb-form-checkboxes">
+                                    {field.options.map((option) => (
+                                        <div key={option.value} className="jvb-form-checkbox">
+                                            <input
+                                                type="checkbox"
+                                                id={`jvb-${field.id}-${option.value}`}
+                                                disabled
+                                            />
+                                            <label htmlFor={`jvb-${field.id}-${option.value}`}>{option.label}</label>
+                                        </div>
+                                    ))}
+                                </div>
+                            )}
+
+                            {field.help && (
+                                <p className="jvb-form-help">{field.help}</p>
+                            )}
+                        </div>
+                    ))}
+
+                    {turnstileEnabled && (
+                        <div className="jvb-form-turnstile">
+                            <div className="jvb-turnstile-placeholder">
+                                <span>{__('Cloudflare Turnstile will appear here', 'jvb')}</span>
+                            </div>
+                        </div>
+                    )}
+
+                    <div className="jvb-form-submit">
+                        <button type="button" className="jvb-form-button">{formLabels.button}</button>
+                    </div>
+                </div>
+            </>
+        );
+    };
+
+    return (
+        <>
+            <InspectorControls>
+                <PanelBody title={__('Form Settings', 'jvb')}>
+                    <SelectControl
+                        label={__('Form Type', 'jvb')}
+                        value={formType}
+                        options={formTypes}
+                        onChange={(value) => setAttributes({ formType: value })}
+                    />
+
+                    <ToggleControl
+                        label={__('Show Field Labels', 'jvb')}
+                        checked={showLabels}
+                        onChange={(value) => setAttributes({ showLabels: value })}
+                        help={__('Toggle to show or hide field labels.', 'jvb')}
+                    />
+
+                    <TextControl
+                        label={__('Custom Recipient Email', 'jvb')}
+                        value={customEmailTo || ''}
+                        onChange={(value) => setAttributes({ customEmailTo: value })}
+                        help={__('Leave empty to use the default email.', 'jvb')}
+                        type="email"
+                    />
+                </PanelBody>
+
+                <PanelBody title={__('Form Preview', 'jvb')} initialOpen={false}>
+                    <ToggleControl
+                        label={__('Show Preview', 'jvb')}
+                        checked={isPreviewVisible}
+                        onChange={(value) => setIsPreviewVisible(value)}
+                    />
+
+                    <p className="components-base-control__help">
+                        {__('This is just a preview. The actual form will be rendered on the frontend.', 'jvb')}
+                    </p>
+                </PanelBody>
+            </InspectorControls>
+
+            <div {...blockProps}>
+                {isPreviewVisible ? (
+                    renderFormPreview()
+                ) : (
+                    <div className="jvb-form-placeholder">
+                        <h3>{formLabels.title}</h3>
+                        <p>{__('Form preview is hidden. Edit settings in the sidebar.', 'jvb')}</p>
+                    </div>
+                )}
+            </div>
+        </>
+    );
+}
diff --git a/src/forms/editor.scss b/src/forms/editor.scss
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/forms/editor.scss
diff --git a/src/forms/index.js b/src/forms/index.js
new file mode 100644
index 0000000..fc49c90
--- /dev/null
+++ b/src/forms/index.js
@@ -0,0 +1,40 @@
+//index.js
+/**
+ * Registers a new block provided a unique name and an object defining its behavior.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
+ * All files containing `style` keyword are bundled together. The code used
+ * gets applied both to the front of your site and to the editor.
+ *
+ * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
+ */
+import './style.scss';
+
+/**
+ * Internal dependencies
+ */
+import Edit from './edit';
+import save from './save';
+import metadata from './block.json';
+
+/**
+ * Every block starts by registering a new block type definition.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+registerBlockType( metadata.name, {
+    /**
+     * @see ./edit.js
+     */
+    edit: Edit,
+
+    /**
+     * @see ./save.js
+     */
+    save,
+} );
diff --git a/src/forms/index.php b/src/forms/index.php
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/forms/index.php
diff --git a/src/forms/render.php b/src/forms/render.php
new file mode 100644
index 0000000..9865bf3
--- /dev/null
+++ b/src/forms/render.php
@@ -0,0 +1,55 @@
+<?php
+if (!defined('ABSPATH')) {
+    exit; // Exit if accessed directly
+}
+/**
+ * Form Block Render
+ *
+ * @package Edmonton_Ink
+ */
+
+/**
+ * Renders the form block on the frontend
+ *
+ * @param array    $attributes The block attributes.
+ * @param string   $content    The block content.
+ * @param WP_Block $block      The block instance.
+ * @return string The rendered output.
+ */
+function jvbRenderFormBlock(array $attributes, string $content, WP_Block $block):string
+{
+    // Get form type from attributes
+    $form_type = isset($attributes['formType']) ? $attributes['formType'] : '';
+
+    // If no form type selected, return a message
+    if (empty($form_type)) {
+        return '<div class="jvb-form-error">No form type selected. Please edit this block and select a form type.</div>';
+    }
+
+    // Get other attributes
+    $show_labels = isset($attributes['showLabels']) ? $attributes['showLabels'] : true;
+    $custom_email_to = isset($attributes['customEmailTo']) ? $attributes['customEmailTo'] : '';
+
+    // Set custom options for the form
+    $form_options = array();
+
+    // Set custom email recipient if provided
+    if (!empty($custom_email_to)) {
+        $form_options['email_to'] = $custom_email_to;
+    }
+
+    // Render the form with the specified options
+    $form_output = JVB()->forms()->renderForm($form_type);
+
+    // Get block classes
+    $wrapper_attributes = get_block_wrapper_attributes([
+        'class' => 'jvb-forms'
+    ]);
+
+    // Return the wrapped form
+    return sprintf(
+        '<div %1$s>%2$s</div>',
+        $wrapper_attributes,
+        $form_output
+    );
+}
diff --git a/src/forms/save.js b/src/forms/save.js
new file mode 100644
index 0000000..933c127
--- /dev/null
+++ b/src/forms/save.js
@@ -0,0 +1,23 @@
+//save.js
+/**
+ * React hook that is used to mark the block wrapper element.
+ * It provides all the necessary props like the class name.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops
+ */
+import { useBlockProps } from '@wordpress/block-editor';
+
+/**
+ * The save function defines the way in which the different attributes should
+ * be combined into the final markup, which is then serialized by the block
+ * editor into `post_content`.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#save
+ *
+ * @return {WPElement} Element to render.
+ */
+export default function save() {
+    // This is a dynamic block that is rendered on the server side
+    // Return null to let WordPress handle the saving and rendering
+    return null;
+}
diff --git a/src/forms/style.scss b/src/forms/style.scss
new file mode 100644
index 0000000..d540000
--- /dev/null
+++ b/src/forms/style.scss
@@ -0,0 +1,5572 @@
+//:target {
+//	outline: none!important;
+//	padding: 0!important;
+//}
+//:root {
+//	--height: 3rem;
+//}
+//body:has(#theme-switcher:checked) {
+//
+//}
+//
+//.dashboard h1:first-of-type {
+//	margin-top: 0!important;
+//}
+//main > footer {
+//	max-width: 100%!important;
+//	position: fixed;
+//	z-index: 20;
+//	bottom: 0;
+//	left: 0;
+//	right: 0;
+//	width: 100%;
+//	margin: 4rem 0 0 0!important;
+//	height: var(--btn);
+//	padding: 0;
+//	background-color: var(--base);
+//	box-shadow: rgba(var(--base-rgb),var(--op-45)) var(--shdw);
+//}
+//main>* {
+//	max-width: min(768px, 90vw)!important;
+//	margin: 0 auto!important;
+//}
+//main h1 {
+//	margin: 0!important;
+//	font-size: var(--txt-large);
+//}
+//main h1 + p + h2 {
+//	font-size: var(--txt-medium);
+//	text-transform: none;
+//	margin: 0!important;
+//}
+//.replace {
+//	max-width: 90vw!important;
+//	margin: 1rem auto!important;
+//}
+//
+//.dash .replace {
+//	text-align: center;
+//}
+//.dash input {
+//	text-align: center;
+//}
+//.dash .true-false {
+//	display: flex;
+//	justify-content: center;
+//	margin: 0;
+//}
+//.dash button[type=submit] {
+//	width: 50%;
+//	margin: 1rem auto;
+//}
+//
+//
+//form h2 {
+//	margin: .5rem 0 1.5rem!important;
+//}
+//
+//.dashboard-nav {
+//	height: var(--btn);
+//	max-width:100vw;
+//	padding: 0 .5rem;
+//}
+//.dashboard-nav ul {
+//	height: var(--btn);
+//	overflow-x: auto;
+//}
+//.dashboard-nav li + li:before {
+//	display: none!important;
+//}
+//.dashboard-nav a {
+//	height: var(--btn);
+//	min-width: var(--btn);
+//	padding: 0 .75rem;
+//	color: var(--contrast)!important;
+//}
+//.dashboard-nav a .icon {
+//	margin: 0;
+//}
+//.dashboard-nav a span {
+//	display: none;
+//}
+//.dashboard-nav .current a:hover,
+//.dashboard-nav a:hover {
+//	background-color: var(--action-0)!important;
+//	color: var(--action-contrast)!important;
+//}
+//.dashboard-nav .current a {
+//	background-color: var(--base-100)!important;
+//	color: var(--contrast)!important;
+//}
+//.dashboard-nav .current a span {
+//	display: block;
+//}
+//
+//
+//
+///* Loading states */
+//main {
+//	opacity: 1;
+//	transition: opacity .3s ease-in-out;
+//	padding-bottom: 7rem;
+//	min-height: calc(100vh - 12rem);
+//}
+//
+//main.transitioning {
+//	opacity: 0;
+//}
+//.loading {
+//	opacity: .7;
+//}
+//
+//
+///*Upload overlay*/
+//.loading-overlay {
+//	position: fixed;
+//	top: 0;
+//	left: 0;
+//	right: 0;
+//	bottom: 0;
+//	background-color: rgba(var(--base-rgb),var(--op-4));
+//	display: flex;
+//	align-items: center;
+//	justify-content: center;
+//	opacity: 0;
+//	visibility: hidden;
+//	transition: opacity .3s ease, visibility .3s ease;
+//	z-index: 9999;
+//}
+//
+//.loading-overlay.active {
+//	opacity: 1;
+//	visibility: visible;
+//}
+//
+//.loading-overlay .wrapper {
+//	background-color: var(--base);
+//	padding: 2rem;
+//	border-radius: 8px;
+//	text-align: center;
+//	max-width: 90%;
+//	width: 400px;
+//}
+//
+//.upload-spinner {
+//	width: 50px;
+//	height: 50px;
+//	border: 5px solid var(--base-200);
+//	border-top: 5px solid var(--action-0);
+//	border-radius: 50%;
+//	margin: 0 auto 1rem;
+//	animation: spin 1s linear infinite;
+//}
+//
+//.upload-status h3 {
+//	margin: 0 0 .5rem;
+//	color: var(--contrast);
+//}
+//
+//.upload-message {
+//	margin: 0;
+//	color: var(--contrast-100);
+//	font-size: var(--txt-x-small);
+//}
+//
+//
+//
+///* Optional: Add a pulsing effect to the text */
+//.upload-message {
+//	animation: flicker 2s infinite;
+//}
+//
+//@keyframes flicker {
+//	0% { opacity: .6; }
+//	50% { opacity: 1; }
+//	100% { opacity: .6; }
+//}
+//
+//.form-section {
+//	max-height: 0;
+//	transform: scale(0);
+//	visibility: hidden;
+//}
+//form:not(:has(.form-section.active)) .form-section:first-of-type,
+//.form-section.active {
+//	max-height: fit-content;
+//	transform: scale(1);
+//	visibility: visible;
+//}
+//
+//
+//
+//.form-sections {
+//	--height: fit-content;
+//	position: fixed;
+//	bottom: 3rem;
+//	left: 0;
+//	right: 0;
+//	background-color: var(--base-100);
+//	box-shadow: rgba(var(--base-rgb),var(--op-45)) var(--shdw);
+//	z-index: 10;
+//}
+//.form-sections ul {
+//	gap: 0;
+//}
+//.form-sections li {
+//	width: 50%;
+//}
+//
+//.form-sections a:visited,
+//.form-sections a {
+//	padding: .25rem;
+//	width: 100%;
+//	color: var(--contrast);
+//}
+//.replace:not(:has(.form-section.active)) .form-sections li:first-of-type a,
+//.current a {
+//	background-color: var(--base-200);
+//	color: var(--contrast);
+//}
+//.form-sections .icon {
+//	margin: 0;
+//}
+//@media (min-width: 768px){
+//	.form-sections ul {
+//		flex-wrap: nowrap;
+//	}
+//	.form-sections li {
+//		width: 100%;
+//	}
+//}
+//
+//.submit-container {
+//	position: fixed;
+//	z-index: 20;
+//}
+//.submit-container button {
+//	padding: 0;
+//	width: 3rem;
+//	height: 3rem;
+//	color: var(--contrast-200);
+//	background-color: var(--base-200);
+//	justify-content: center;
+//	border: 3px solid transparent;
+//}
+//.submit-container .save-popup {
+//	position: absolute;
+//	z-index: -1;
+//	top: calc(50% - (1.875rem / 2));
+//	font-size: var(--txt-x-small);
+//	background-color: var(--action-0);
+//	color: var(--action-contrast);
+//	padding: .25rem .5rem;
+//	border-radius: 4px;
+//	white-space: nowrap;
+//	visibility: hidden;
+//	transition: all var(--trans-base);
+//	opacity: 0;
+//}
+//.submit-container .icon {
+//	--w: 2em;
+//}
+//.save-popup::before {
+//	content: '';
+//	position: absolute;
+//	top: 50%;
+//	transform: translateY(-50%);
+//	border-top: .5rem solid transparent;
+//	border-bottom: .5rem solid transparent;
+//}
+//.save-popup.show {
+//	opacity: 1;
+//	visibility: visible;
+//}
+//
+//@media (max-width: 767px){
+//	.submit-container {
+//		top: 3.5rem;
+//		right: .5rem;
+//	}
+//	.save-popup {
+//		right: 0;
+//	}
+//	.save-popup:before{
+//		right: -.25rem;
+//		border-left: .5rem solid var(--action-0);
+//	}
+//	.save-popup.show {
+//		right: calc(100% + .5rem);
+//	}
+//}
+//@media (min-width: 768px){
+//	.submit-container {
+//		bottom: 6rem;
+//		left: .5rem;
+//	}
+//	.save-popup {
+//		left: 0;
+//	}
+//	.save-popup:before{
+//		left: -.25rem;
+//		border-right: .5rem solid var(--action-0);
+//	}
+//	.save-popup.show {
+//		left: calc(100% + .5rem);
+//	}
+//}
+//
+//.autosaving span.save,
+//.autosaving button[type=submit] {
+//	border-color: var(--base-200);
+//	border-top-color: var(--action-0);
+//	border-bottom-color: var(--action-50);
+//	border-radius: 50%;
+//	color: var(--contrast-200);
+//	transition: color .25s var(--trans-t) var(--trans-fn);
+//	transition-property: color, background-color, border;
+//	animation: spin 1s linear infinite;
+//}
+//.autosaving .submit-container {
+//	animation: pulse 1s linear infinite;
+//}
+//
+//@keyframes spin {
+//	0% { transform: rotate(0deg); }
+//	100% { transform: rotate(360deg); }
+//}
+//@keyframes pulse {
+//	0% {
+//		transform: scale(.85);
+//		opacity: .6;
+//	}
+//	50% {
+//		transform:scale(1);
+//		opacity: 1; }
+//	100% {
+//		transform: scale(.85);
+//		opacity: .6; }
+//}
+//
+//.item-link[href=""]{
+//	display: none;
+//}
+//.item-link {
+//	position: fixed;
+//	bottom: 5rem;
+//	right: 1.5rem;
+//	text-transform: uppercase;
+//	color: var(--action-0);
+//	background-color: var(--base);
+//	border-radius: 4px;
+//	padding: .25rem .5rem;
+//	box-shadow: rgba(var(--base-rgb),var(--op-45)) var(--shdw-subtle);
+//}
+//.field {
+//	margin: 3rem .5rem;
+//}
+//.field:has([required]) label::after {
+//	content: 'required';
+//	background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="%23B7332E" viewBox="0 0 256 256"><path d="M214.86,180.12a8,8,0,0,1-11,2.74L136,142.13V216a8,8,0,0,1-16,0V142.13L52.12,182.86a8,8,0,1,1-8.23-13.72L112.45,128,43.89,86.86a8,8,0,1,1,8.23-13.72L120,113.87V40a8,8,0,0,1,16,0v73.87l67.88-40.73a8,8,0,1,1,8.23,13.72L143.55,128l68.56,41.14A8,8,0,0,1,214.86,180.12Z"></path></svg>');
+//	display: inline-block;
+//	background-position: left center;
+//	background-size: 1.2em;
+//	padding-left: 1.3em;
+//	background-repeat: no-repeat;
+//	font-weight: 100;
+//	text-transform: none;
+//	font-size: .75em;
+//	vertical-align: super;
+//
+//}
+//.field:has([required]) label {
+//	position: relative;
+//}
+//.repeater-items .field {
+//	margin: 1rem 0;
+//}
+//.taxonomy .field-group-header {
+//	display: flex;
+//	justify-content: space-between;
+//}
+//
+//.add-item-btn {
+//	padding: .5em;
+//	background: var(--base-100);
+//	border: 1px solid var(--contrast-200);
+//	border-radius: .25rem;
+//	cursor: pointer;
+//	font-size: .875rem;
+//}
+//.add-item-btn .icon {
+//	--w: 1.5em;
+//}
+//
+//.add-item-btn:hover {
+//	background: var(--base-200);
+//}
+//summary .type-label {
+//	display: flex;
+//	align-items: center;
+//	gap: 1rem;
+//}
+//summary .type-label .icon {
+//	position: relative;
+//	top: -.25em;
+//}
+//
+///** Tabs **/
+//.tabs {
+//	display: flex;
+//	margin-bottom: 1.5rem;
+//	border-bottom: 2px solid var(--base-200);
+//	flex-wrap: wrap;
+//}
+//.tabs.parent {
+//	max-width: 100vw;
+//	background-color: var(--base);
+//	padding: .5rem;
+//	width: 100vw;
+//	position: relative;
+//	left: -2.1rem;
+//	margin: 1rem 0;
+//}
+//.tab-content h2 {
+//	font-size: var(--txt-large);
+//	margin: 0!important;
+//}
+//.tab-content .tab-navigation,
+//.tab-content button[type=submit] {
+//	display: inline-flex;
+//	width: 48%;
+//	background-color: var(--action-200);
+//	color: var(--contrast-200);
+//}
+//
+//.tab-navigation.next {
+//	display: flex;
+//	margin-left: auto;
+//}
+//.tab-content .tab-navigation + button {
+//	display: inline-flex;
+//	margin-left: 3%;
+//}
+//
+//.tabs > button {
+//	padding: .75rem 1.5rem;
+//	border-radius: 0;
+//	border: none;
+//	background: none;
+//	font-size: 1.1rem;
+//	font-weight: bold;
+//	letter-spacing: .05em;
+//	text-transform: uppercase;
+//	cursor: pointer;
+//	position: relative;
+//	color: inherit;
+//}
+//.tabs > button h2 {
+//	font-size: 1.1rem;
+//	line-height: 1;
+//	margin: 0!important;
+//}
+//.tabs > button:hover,
+//.tabs > button:focus {
+//	background-color: var(--base-200);
+//}
+//.tabs > button::after {
+//	content: '';
+//	position: absolute;
+//	bottom: -2px;
+//	left: 0;
+//	width: 0;
+//	height: 3px;
+//	background-color: var(--action-50);
+//	transition: width .3s;
+//}
+//.tabs > button.active::after {
+//	width: 100%;
+//}
+//
+//.tabs > button.add-item-btn {
+//	margin-left: auto;
+//	background-color: var(--action-50);
+//	border-radius: 4px;
+//	padding: .5em;
+//	font-weight: normal;
+//	border: 1px solid var(--action-50);
+//}
+//.tabs > button.add-item-btn:focus,
+//.tabs > button.add-item-btn:hover {
+//	background-color: var(--base);
+//}
+//
+//.type-filter:not(.active) span:not(.count){
+//	display: none;
+//}
+//.type-filter:not(.active):hover span:not(.count){
+//	display: block;
+//}
+///** News **/
+//.item-grid.list-view .item.news {
+//	flex-direction: column;
+//}
+//.item.news h3 {
+//	font-size: var(--txt-medium);
+//	margin: 0!important;
+//}
+//.item.news summary .image {
+//	width: 5rem;
+//	background-color: var(--base-200);
+//	border-radius: 4px;
+//	aspect-ratio: 1;
+//}
+//.item.news summary {
+//	gap: 1rem;
+//}
+//.item.news summary .summary {
+//	width: 100%;
+//}
+//.item.news summary .header {
+//	display: flex;
+//	justify-content: space-between;
+//	padding: .5rem 0;
+//}
+//.item .vote {
+//	display: flex;
+//	justify-content: flex-end;
+//	align-items: center;
+//	gap: .5em;
+//	margin-top: auto;
+//}
+//.vote .count {
+//	white-space: nowrap;
+//}
+//.item .vote .count:nth-of-type(2) {
+//	margin-right: 2rem;
+//}
+///** Favourites **/
+//.favourites-title {
+//	display: flex;
+//	justify-content: space-between;
+//	align-items: center;
+//}
+//main button {
+//	display: flex;
+//	align-items: center;
+//	gap: .5rem;
+//}
+//
+//.my-lists {
+//	margin: .5rem 0;
+//}
+//
+//details.uploader .file-upload-container {
+//	margin: 1rem var(--mr) 1rem var(--ml);
+//	max-width: var(--content);
+//}
+//details .no-items {
+//	text-align: center;
+//	font-style: italic;
+//	background-color: var(--base-50);
+//	padding: var(--p-outer);
+//	border-radius: var(--radius);
+//}
+//
+//.controls {
+//	display: flex;
+//	flex-wrap: wrap;
+//	gap: .5rem;
+//	justify-content: space-between;
+//}
+//
+//.controls label .icon + span {
+//	transform: scaleX(0);
+//	max-width: 0;
+//}
+//
+//.type-filters:hover label:hover .icon + span,
+//.controls label:hover .icon + span,
+//.controls :checked + label .icon + span {
+//	transform: scaleX(1);
+//	max-width: fit-content;
+//}
+//.type-filters,
+//.view-controls {
+//	display: flex;
+//	flex-wrap: wrap;
+//	gap: .5rem;
+//}
+//
+///** Generalized Type Filters **/
+//details.type-filters {
+//	flex-direction: column;
+//}
+//details.type-filters form.filters {
+//	flex-direction: column;
+//}
+//
+//details.type-filters div.filters {
+//	flex-wrap: wrap;
+//}
+//
+//details.type-filters .order-options {
+//	width: 100%;
+//	display: flex;
+//}
+//details.type-filters .order-options > div {
+//	display: flex;
+//}
+//details.type-filters .order-by {
+//	width: 100%;
+//}
+//details.type-filters .order-by .radio-group-label {
+//	width: 100%;
+//	display: flex;
+//	gap: 1rem;
+//}
+//details.type-filters label {
+//	display: flex;
+//	align-items: center;
+//	gap: .5rem;
+//}
+//
+//details.type-filters label {
+//	display: flex;
+//	justify-content: center;
+//	align-items: center;
+//	padding: .35rem;
+//	white-space: nowrap;
+//	width: fit-content;
+//	height: fit-content;
+//	cursor: pointer;
+//	border: 1px solid var(--base-200);
+//	border-radius: 4px;
+//	font-size: .875rem;
+//	transition: border-color var(--trans-base);
+//	margin-bottom: .5rem;
+//}
+//.filter-toggle .icon {
+//	margin-right: .5rem;
+//}
+//details.type-filters label:hover,
+//details.type-filters input:checked + label {
+//	background-color: var(--light-0);
+//	border-color: var(--action-0);
+//	color: var(--action-0);
+//}
+//details.type-filters .order-direction {
+//	justify-content: flex-end;
+//}
+//
+///** Generalized Modal **/
+//
+///* Grid */
+//.create-item {
+//	left: auto!important;
+//	right: 1rem;
+//	bottom: calc(var(--btn) + 1rem)!important;
+//}
+//body:has(.group-display:not([hidden])) button.create-item{
+//	display: none;
+//}
+//.uploader .groups,
+//.item-grid {
+//	--padding: 0;
+//	padding: var(--padding);
+//	transition: padding var(--trans-base);
+//}
+//.uploader .groups,
+//.item-grid:not(.list-view) {
+//	display: grid;
+//	grid-template-columns: repeat(2, 50%);
+//	gap: .5rem;
+//	margin-top: 2rem;
+//}
+//.item-grid.empty {
+//	grid-template-columns: repeat(1, 1fr)!important;
+//}
+//.item-grid.empty div {
+//	text-align: center;
+//	border-radius: var(--radius);
+//	background-color: var(--base-100);
+//}
+//.item-grid.empty h3 .icon {
+//	display: inline-flex;
+//	vertical-align: middle;
+//	margin: 0 1rem;
+//}
+//.uploader .group,
+//.item {
+//	width: calc(100% - (var(--padding) * 2));
+//}
+//.item-grid .item {
+//	user-select: none;
+//	-webkit-user-select: none;
+//	-moz-user-select: none;
+//	-ms-user-select: none;
+//}
+//.item-grid .item .logo {
+//	--w: 100%;
+//	opacity: .2;
+//}
+///** Grid View **/
+//.item-grid:not(.list-view) .item {
+//	display: flex;
+//	position: relative;
+//	flex-direction: column;
+//}
+//.item-grid:not(.list-view) button.favourite,
+//.item-grid:not(.list-view) .item-select label {
+//	position: absolute;
+//	width: 3em;
+//	height: 3em;
+//	display: flex;
+//	justify-content: center;
+//	align-items: center;
+//	top: .125rem;
+//	padding: 0!important;
+//	border-radius: var(--radius);
+//	background-color: rgba(var(--base-rgb),var(--op-3));
+//	color: var(--base-200);
+//}
+//.item-grid:not(.list-view) button.favourite:hover,
+//.item-grid:not(.list-view) .item-select label:hover {
+//	background-color: rgba(var(--base-rgb),var(--op-6));
+//	color: var(--contrast);
+//}
+//.item-grid:not(.list-view) .item-select label::before {
+//	border-color: var(--base-200);
+//	position: relative;
+//	top: 0;
+//	left: 0;
+//	transform: none;
+//}
+//
+//.item-grid:not(.list-view) .item-select label::after {
+//	left: calc(50% - 4px)!important;
+//}
+//.item-grid:not(.list-view) .item-select label:hover::before {
+//	border-color: var(--contrast);
+//}
+//.item-grid:not(.list-view) .item-select label {
+//	left: .1255rem;
+//}
+//.item-grid:not(.list-view) button.favourite {
+//	right: .125rem;
+//}
+//.item-grid:not(.list-view) img {
+//	width: 100%;
+//	max-width: 100%;
+//	height: auto;
+//	aspect-ratio: 1;
+//	object-fit: cover;
+//}
+///** List View **/
+//.replace:not(:has(.list-view)) button[data-view=grid],
+//.replace:has(.list-view) button[data-view=list] {
+//	background-color: var(--base);
+//}
+//.item-grid.list-view .item {
+//	display: flex;
+//	position: relative;
+//	flex-direction: row;
+//	margin: .5rem 0;
+//	padding: .25rem .5rem;
+//	gap: .5rem;
+//}
+//.item-grid.list-view .item:nth-of-type(even){
+//	background-color: var(--base-100);
+//}
+//
+//.item-grid.list-view .item-select label{
+//	display: flex;
+//	height: 100%;
+//	justify-content: center;
+//	align-items: center;
+//	padding: 0 .5rem!important;
+//}
+//.item-grid.list-view .item-select label::after {
+//	/*left: calc(50% - 4px)!important;*/
+//}
+//.item-grid.list-view .item-select label::before {
+//	position: relative;
+//	top: 0;
+//	transform: none;
+//}
+//.item-grid.list-view .item-select label:hover {
+//	background-color: var(--base);
+//}
+//
+//.item-grid.list-view .item-select label:hover::before {
+//	border-color: var(--action-0);
+//}
+//.item-grid.list-view img {
+//	width: 200px;
+//	height: 200px;
+//	aspect-ratio: 1;
+//	object-fit: cover;
+//}
+//.item-grid.list-view .item-info {
+//	width: 100%;
+//}
+//.item-grid .item-info h3 {
+//	margin: 0!important;
+//	text-align: right;
+//	font-size: var(--txt-medium);
+//	text-transform: none;
+//}
+//.item-grid .item-info a {
+//	text-align: right;
+//	width: 100%;
+//	display: block;
+//	font-weight: normal;
+//	font-family: var(--body);
+//}
+//.item-grid .item-info a::before,
+//.item-grid .item-info a::after {
+//	display: none;
+//}
+//.item-grid.list-view .item-info .toggle-notes {
+//	display: none;
+//}
+//.item-grid.list-view .item-info .notes-content[hidden]{
+//	display: block!important;
+//}
+//.item-grid.list-view .item-info .notes-content {
+//	margin-top: 1rem;
+//}
+//.no-favourites {
+//	text-align: center;
+//}
+//.no-favourites h3 {
+//	margin: 0 auto!important;
+//}
+///**
+//Limit reached
+// */
+//.reached .current {
+//	color: var(--action-50);
+//}
+//.reached textarea,
+//.reached input {
+//	border: 2px solid var(--action-50);
+//}
+///**
+//Groups
+// */
+//.groups {
+//	gap: 1rem;
+//}
+//details.uploaded .file-upload-container,
+//details:not(.uploaded) .selection-container {
+//	display: none;
+//}
+//.uploading + .selection-container {
+//	display: flex!important;
+//}
+//.selection-container #save-changes {
+//	position: sticky;
+//	top: 3rem;
+//	left: 100%;
+//	border: 1px solid transparent;
+//	background-color: var(--action-50);
+//	box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);
+//	z-index: 5;
+//}
+//.selection-container #save-changes:hover {
+//	background-color: var(--base);
+//	border: 1px solid var(--action-50);
+//	color: 1px solid var(--action-50);
+//}
+//
+//.group {
+//	padding: 1rem .66rem;
+//	background-color: var(--base-50);
+//	border-radius: var(--radius-outer);
+//}
+//.group.empty {
+//	aspect-ratio: 1;
+//	display: flex;
+//	flex-direction: column;
+//	align-items: center;
+//	justify-content: center;
+//	border: 4px dashed var(--base-200);
+//}
+//.group.empty .items {
+//	padding: 0;
+//	margin-top: 0;
+//}
+//
+//.group .group-header {
+//	display: flex;
+//	position: relative;
+//}
+//.group .group-header label {
+//	position: absolute;
+//	top: -1.5rem;
+//}
+//.group .items {
+//	margin-top: 1rem;
+//	padding: 1rem;
+//	border-radius: var(--radius);
+//	background-color: var(--base);
+//}
+//.group .item-actions {
+//	display: flex;
+//	justify-content: space-between;
+//	width: 100%;
+//}
+//.group .item-actions > button,
+//.group .item-actions label {
+//	flex: 1;
+//	padding: .25em!important;
+//	display: flex;
+//	align-items: center;
+//	justify-content: center;
+//	margin-bottom: 0;
+//}
+//.group .item-actions :checked + label {
+//	background-color: var(--action-50);
+//	color: var(--contrast);
+//}
+//.group .item-actions :checked + label:hover {
+//	border: 1px solid var(--action-50);
+//	background-color: transparent;
+//	color: var(--action-50);
+//}
+//.group .item-actions label::before {
+//	display: none!important;
+//}
+//
+//.group-item img,
+//.preview-item img {
+//	width: 250px;
+//	height: auto;
+//	object-fit: cover;
+//	aspect-ratio: 1;
+//
+//}
+//.uploading .preview-item img,
+//.uploading .group-item img{
+//	animation: uploading 3s ease-in-out infinite alternate;
+//}
+//@keyframes uploading {
+//	0% {
+//		opacity: .6;
+//		filter: sepia(.3) grayscale(.3);
+//		transform: scale(.9);
+//	}
+//	100% {
+//		opacity: 1;
+//		filter: sepia(0) grayscale(0);
+//		transform: scale(1);
+//	}
+//}
+//
+//.image-actions {
+//	display: flex;
+//	gap: 1rem;
+//	flex-wrap: wrap;
+//}
+//
+//.group.drop {
+//	border: 2px dashed var(--action-0);
+//	background: var(--action-rgb-subtle);
+//}
+//
+//.group .items {
+//	padding: 1rem;
+//	display: grid;
+//	grid-template-columns: repeat(3, 1fr);
+//	gap: 1rem;
+//}
+//
+//.group-content {
+//	min-height: 100px;
+//	padding: 1rem;
+//}
+//
+//.group.drop {
+//	background: var(--action-rgb-subtle);
+//	border: 2px dashed var(--action-0);
+//}
+//.gallery .group-item,
+//.gallery .preview-item {
+//	cursor: grab;
+//}
+//.group-item.dragging,
+//.preview-item.dragging {
+//	cursor: grabbing;
+//	opacity: .5;
+//}
+//
+//.items {
+//	display: grid;
+//	grid-template-columns: repeat(3,1fr);
+//	gap: 1rem;
+//}
+//
+///** Gallery **/
+//.gallery .preview-item .move-image {
+//	cursor: grab;
+//	position: absolute;
+//	top: .5rem;
+//	left: .5rem;
+//	opacity: 0;
+//	transition: opacity .2s;
+//}
+//.gallery-preview {
+//	display: grid;
+//	grid-template-columns: repeat(3, 1fr);
+//	padding: .5rem;
+//	background-color: var(--base-100);
+//	border-radius: var(--radius-outer);
+//}
+//.gallery-preview .preview-item {
+//	padding: .5rem;
+//	background-color: var(--base);
+//	border-radius: var(--radius);
+//}
+//
+//.gallery .preview-item:hover .move-image {
+//	opacity: 1;
+//}
+//
+//.gallery .preview-item.sortable-ghost {
+//	opacity: .5;
+//}
+//
+//.gallery .preview-item.sortable-drag {
+//	cursor: grabbing;
+//}
+//
+//.preview-item .upload-status {
+//	position: absolute;
+//	bottom: 0;
+//	left: 0;
+//	right: 0;
+//	height: 4px;
+//	background: rgba(0,0,0,.1);
+//}
+//
+//.preview-item.error {
+//	border-color: red;
+//}
+//
+//.preview-item.complete .upload-status {
+//	display: none;
+//}
+//
+///** Settings **/
+//.notification-table {
+//	width: 100%;
+//}
+//.notification-table tr td:first-of-type {
+//	width: 100%;
+//}
+//.notification-table td {
+//	vertical-align: middle;
+//	padding: 0 .5rem;
+//}
+//.notification-table td + td {
+//	text-align: right;
+//}
+//table tr:nth-of-type(even){
+//	background-color: var(--base-200);
+//}
+//thead th {
+//	width: 50%;
+//}
+//thead tr,
+//tfoot tr {
+//	background-color: var(--base);
+//	text-transform: uppercase;
+//	padding: .5rem 0;
+//	line-height: 2;
+//	font-weight: normal;
+//}
+//tfoot th {
+//	vertical-align: middle;
+//}
+//tfoot th:first-of-type {
+//	text-align: right;
+//}
+//@media (max-width: 767px){
+//	table .radio-options {
+//		display: grid;
+//		grid-template-columns: repeat(2, 1fr);
+//	}
+//	table .radio-options label {
+//		width: 85%;
+//		margin-bottom: 0;
+//	}
+//}
+//
+///** Favourites Lists **/
+//.list-card {
+//	background-color: var(--base-50);
+//	padding: 1rem;
+//	border-radius: var(--radius);
+//}
+//.list-header {
+//	display: flex;
+//	justify-content: space-between;
+//}
+//h2 .icon {
+//	--w: 1.5em;
+//	display: inline-block;
+//	vertical-align: middle;
+//	margin-right: .75rem;
+//}
+//.list-card h3,
+//.list-header h2 {
+//	margin: 0!important;
+//	font-size: var(--txt-large);
+//}
+//.list-actions {
+//	display: flex;
+//	justify-content: space-between;
+//	gap: .5rem;
+//}
+//.list-header + .list-actions {
+//	justify-content: flex-end;
+//}
+//.create-list-btn {
+//	font-size: var(--txt-x-small);
+//}
+//.meta-stats {
+//	display: flex;
+//	justify-content: space-between;
+//	font-style: italic;
+//}
+//
+///** Repeater **/
+//.add-repeater-row {
+//	margin-left: auto;
+//	border: 1px solid var(--contrast-200);
+//}
+//
+///** Image **/
+//
+//.image-display {
+//	max-height: 0;
+//	overflow: hidden;
+//	transform: scaleY(0);
+//	transition: max-height var(--trans-t) var(--trans-fn);
+//	transition-property: max-height, transform;
+//}
+//.image-display.has-image {
+//	max-height: 100%;
+//	transform: scaleY(1);
+//	transition: max-height var(--trans-t) var(--trans-fn);
+//	transition-property: max-height, transform;
+//}
+//.file-upload-container {
+//	margin-top: 1rem;
+//}
+//
+//.file-upload-wrapper {
+//	border: 2px dashed var(--action-0);
+//	border-radius: 4px;
+//	padding: 2rem;
+//	text-align: center;
+//	transition: all .3s ease;
+//	background: var(--action-rgb-subtle);
+//	position: relative;
+//	cursor: pointer;
+//}
+//.file-upload-wrapper h2 {
+//	margin: 0!important;
+//	font-size: var(--txt-large);
+//}
+//
+//.file-upload-wrapper:hover,
+//.dragover {
+//	background: var(--action-rgb-subtle-hover);
+//	border-color: var(--action-0)!important;
+//}
+//
+//.file-upload-wrapper input[type="file"] {
+//	position: absolute;
+//	left: 0;
+//	top: 0;
+//	width: 100%;
+//	height: 100%;
+//	opacity: 0;
+//	cursor: pointer;
+//}
+//
+//.file-upload-text {
+//	color: var(--contrast);
+//	margin: 0;
+//	font-family: var(--body);
+//}
+//
+//.file-upload-text strong {
+//	color: var(--action-0);
+//	text-decoration: underline;
+//}
+//
+///* Error state */
+//.file-error {
+//	color: var(--action-0);
+//	margin-top: .5rem;
+//	font-size: .9em;
+//}
+//
+//.file-upload-container.uploading,
+//.image-display.has-image + .file-upload-container {
+//	max-height: 0;
+//	overflow: hidden;
+//	transform: scaleY(0);
+//	transition: max-height var(--trans-t) var(--trans-fn), transform var(--trans-t) var(--trans-fn);
+//	transition-property: max-height, transform;
+//}
+//
+///** Highlighted Taxonomy **/
+//.highlight-options {
+//	display: grid;
+//	grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+//	gap: .5rem;
+//	margin-top: 1rem;
+//}
+//
+//.highlight-option {
+//	display: flex;
+//	align-items: center;
+//	gap: .5rem;
+//	padding: .5rem;
+//	border: 1px solid #ddd;
+//	border-radius: 4px;
+//}
+//.highlight-option label {
+//	margin-bottom: 0;
+//	font-weight: normal;
+//}
+//
+//.highlight-option input[type=checkbox]:disabled + label {
+//	opacity: .5;
+//	cursor: not-allowed;
+//}
+//
+///** Item Selection **/
+//
+//.item-grid {
+//	--padding: 0;
+//	padding: var(--padding);
+//}
+//.item-grid.selecting {
+//	--padding: .5rem;
+//	transition: none;
+//	background-color: var(--base);
+//}
+//.item {
+//	--padding: 0;
+//	height: fit-content;
+//	padding: var(--padding);
+//	max-width: calc(100% - (var(--padding) * 2));
+//	transition: padding var(--trans-base);
+//}
+//.selecting .item {
+//	opacity: .666;
+//}
+//.selecting .item:has(.select-checkbox:checked) {
+//	--padding: .5rem;
+//	opacity: 1;
+//	background-color: var(--action-0);
+//	transition: none;
+//}
+//
+//
+//.item-grid.preview .preview-item {
+//	display: flex;
+//	gap: 2rem;
+//}
+//.preview-item .preview-image {
+//	width: 36%;
+//}
+//.preview-item .remove-file {
+//	width: 100%;
+//	background-color: var(--base);
+//	color: var(--contrast);
+//}
+//.preview-item .field:first-of-type {
+//	margin-top: 0!important;
+//}
+//.preview-item + .preview-item {
+//	margin-top: 2rem;
+//	padding-top: 2rem;
+//	border-top: 2px solid var(--contrast-200);
+//}
+//
+//.item-grid.preview:empty + .hint {
+//	display: none;
+//}
+//
+//body:has(.item-grid.empty) .bulk-controls {
+//	display: none;
+//}
+//.bulk-controls {
+//	margin: .75rem 0;
+//	display: flex;
+//	justify-content: space-between;
+//	align-items: center;
+//}
+//.bulk-select {
+//	display: flex;
+//	justify-content: space-between;
+//}
+//
+//.bulk-actions[hidden]{
+//	max-width: 0;
+//	max-height: 0;
+//	overflow: hidden;
+//	transform: scaleY(0);
+//	transition: transform var(--trans-t) var(--trans-fn);
+//	transition-property: transform, max-height;
+//	transform-origin: top;
+//	display: flex!important;
+//	align-items: center;
+//	gap: .5rem;
+//	position: relative;
+//}
+//
+//.bulk-actions {
+//	max-width: 100%;
+//	max-height: 100%;
+//	transform: scaleY(1);
+//	transition: transform var(--trans-t) var(--trans-fn);
+//	transition-property: transform, max-height;
+//	overflow:visible;
+//	transform-origin: top;
+//	display: flex;
+//	gap: .25rem;
+//}
+//.bulk-actions select {
+//	margin-right: .5rem;
+//}
+//
+//.selected-count {
+//	font-size: var(--txt-x-small);
+//	font-style: italic;
+//	font-weight: normal;
+//	margin-left: 1rem;
+//}
+//
+//.input-with-button {
+//	display: flex;
+//	gap: .5rem;
+//}
+//.input-with-button .icon {
+//	--w: 1.5em;
+//}
+///** Single Edit Modal **/
+//dialog > form .field {
+//	margin: 2rem 0;
+//}
+//.edit-modal [data-field=featured_image] img {
+//	width: 100%;
+//	height: 100%;
+//	object-fit: cover;
+//	aspect-ratio: 1;
+//}
+//
+///** Bulk Edit Modal **/
+//.bulk-edit-modal .selected {
+//	display: grid;
+//	grid-template-columns: repeat(5, 1fr);
+//	gap: .5rem;
+//	background-color: var(--base);
+//	padding: .5rem;
+//	border-radius: var(--radius-outer);
+//}
+//.bulk-edit-modal .selected input[type=checkbox] {
+//	position: absolute;
+//}
+//.bulk-edit-modal .selected label::before,
+//.bulk-edit-modal .selected label::after {
+//	display: none;
+//}
+//.bulk-edit-modal .selected label {
+//	opacity: .5;
+//	padding: .5rem!important;
+//	aspect-ratio: 1;
+//	margin-bottom: 0;
+//	max-width: 5rem;
+//	height: auto;
+//	cursor: pointer;
+//}
+//.bulk-edit-modal .selected label:has(input:checked) {
+//	opacity: 1;
+//	padding: 0!important;
+//}
+//
+//.bulk-edit-modal .selected img {
+//	max-width: 100%;
+//	object-fit: cover;
+//	aspect-ratio: 1;
+//	border-radius: var(--radius);
+//}
+//
+//dialog .term-list {
+//	display: block;
+//}
+//.pagination-info {
+//	position: sticky;
+//	background-color: rgba(var(--base-rgb),var(--op-6));
+//	top: 0;
+//}
+//.pagination-info:empty {
+//	display: none;
+//}
+//
+///** Quill Stuff **/
+//.editor-container {
+//
+//}
+//.editor-container .ql-toolbar {
+//	display: flex;
+//	justify-content: flex-start;
+//	flex-wrap: wrap;
+//	padding: .25rem;
+//	gap: 1rem;
+//	background-color: var(--base);
+//	border-top-left-radius: var(--radius);
+//	border-top-right-radius: var(--radius);
+//	border-bottom: 4px solid var(--base-50);
+//}
+//.ql-toolbar .ql-formats {
+//	display: flex;
+//	gap: .25rem;
+//}
+//.editor-container .ql-container {
+//	--padding: 1rem;
+//	background-color: var(--base);
+//	border-bottom-left-radius: var(--radius);
+//	border-bottom-right-radius: var(--radius);
+//	height: fit-content;
+//	padding: 2px;
+//}
+//.editor-container .ql-container .ql-editor {
+//	padding: var(--padding);
+//	width: calc(100% - (var(--padding) * 2.5));
+//	height: calc(100% - (var(--padding) * 2));
+//}
+//.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: var(--base-100);
+//	border: 1px solid var(--base);
+//	box-shadow: 0px 0px 5px rgba(var(--base-rgb),var(--op-6));
+//	color: var(--contrast);
+//	padding: 5px 12px;
+//	white-space: nowrap;
+//}
+//
+//#toolbar-einks_short_bio .ql-e_image {
+//	display: none;
+//}
+//
+///** Content Grid **/
+//.all-filters {
+//	position: relative;
+//	background-color: var(--base);
+//	border-radius: var(--radius-outer);
+//	padding: .5rem;
+//	display: flex;
+//	flex-direction: column;
+//	gap: .5rem;
+//}
+//.controls .radio-options {
+//	gap: .25rem 1rem;
+//}
+//.radio-options details {
+//	width: 100%;
+//}
+//.radio-options input:not(.ch) + label,
+//.controls input:not(.ch) + .radio-options label {
+//	width: fit-content!important;
+//	gap: 0;
+//	margin: 0;
+//	display: flex;
+//	padding: .25rem .5rem!important;
+//	align-items: center;
+//}
+//
+//.radio-options label span {
+//	transform: scaleX(0);
+//	max-width: 0;
+//}
+//.edit-form .radio-options label span,
+//.radio-options label:hover span,
+//.radio-options :checked + label span {
+//	transform: scaleX(1);
+//	max-width: 100%;
+//}
+//.radio-options input:not(.ch):checked + label {
+//	padding: .25rem .5rem!important;
+//}
+//.controls .radio-options input:not(.ch):checked+label {
+//	background-color: var(--base-100);
+//	border-color: var(--contrast-200);
+//	color: var(--contrast)!important;
+//	gap: .5rem;
+//}
+//
+//dialog .field {
+//	margin: 3rem 0;
+//}
+//
+//
+//.filters {
+//	display: flex;
+//	gap: 1rem;
+//	align-items: center;
+//}
+//.filters > div > label {
+//	margin: 0;
+//}
+//div.filters > div {
+//	display: flex;
+//	align-items: center;
+//	flex: 100%;
+//	gap: .5rem;
+//}
+//.filters .icon {
+//	--w: 1.5em;
+//}
+//
+//@media (min-width:768px){
+//	div.filters > div {
+//		flex: 1;
+//	}
+//}
+//
+//.view-controls.radio-options {
+//	gap: .5rem;
+//	margin-left: auto;
+//}
+//.view-controls.radio-options label {
+//	padding: .25rem!important;
+//}
+//
+//.label-group {
+//	display: flex;
+//	gap: .75rem;
+//	align-items: center;
+//	flex-wrap: wrap;
+//}
+//.label-group .icon {
+//	margin-right: .5rem;
+//}
+//.item-grid.table {
+//	display: none;
+//}
+//
+//.item-grid summary {
+//	padding: 0;
+//	gap: 0;
+//}
+//.item-grid .item-actions {
+//	position: absolute;
+//	top: .25rem;
+//	right: .25rem;
+//	gap: .25rem;
+//	display: flex;
+//}
+//.item-grid .item-actions button {
+//	width: 2em;
+//	height: 2em;
+//	border-radius: var(--radius);
+//	background-color: rgba(var(--base-rgb),var(--op-3));
+//	display: flex;
+//	justify-content: center;
+//	align-items: center;
+//}
+//.item-grid .item-actions button:focus,
+//.item-grid .item-actions button:hover {
+//	background-color: rgba(var(--base-rgb),var(--op-6));
+//	color: var(--action-0);
+//}
+//
+//
+//
+//@media (max-width: 768px){
+//	.replace {
+//		margin-top: 4rem!important;
+//	}
+//	.list-header {
+//		flex-wrap: wrap;
+//	}
+//	.list-card h3 {
+//		font-size: var(--txt-medium);
+//	}
+//	.item-grid.list-view .item {
+//		align-items: center;
+//	}
+//	.item-grid.list-view img {
+//		width: 80px;
+//		height: 80px;
+//		aspect-ratio: 1;
+//		object-fit: cover;
+//	}
+//}
+//
+//@media (min-width: 768px){
+//	.item-grid:not(.list-view) {
+//		grid-template-columns: repeat(3, 1fr);
+//	}
+//}
+//
+//.term-divider {
+//	position: relative;
+//	text-align: center;
+//	margin: 1rem 0;
+//	border-bottom: 1px solid var(--base-200);
+//}
+//
+//.term-divider span {
+//	background: var(--base);
+//	padding: 0 1rem;
+//	color: var(--contrast);
+//	font-size: .9rem;
+//	position: relative;
+//	top: .5em;
+//}
+//
+//.common-term {
+//	background: var(--base-50);
+//	border-radius: var(--radius);
+//}
+//
+//.loading-indicator {
+//	display: flex;
+//	align-items: center;
+//	justify-content: center;
+//	gap: .5rem;
+//	padding: 1rem;
+//	color: var(--contrast-100);
+//	font-size: .9rem;
+//}
+//
+//.loading-indicator svg {
+//	animation: spin 1s linear infinite;
+//}
+//
+//.pagination-info {
+//	text-align: center;
+//	padding: .5rem;
+//	font-size: .9rem;
+//	color: var(--contrast-100);
+//	border-top: 1px solid var(--base-100);
+//}
+//
+//@keyframes spin {
+//	from { transform: rotate(0deg); }
+//	to { transform: rotate(360deg); }
+//}
+//
+//
+//.term-breadcrumb {
+//	margin-bottom: 1rem;
+//	padding: .5rem;
+//	background: var(--base-50);
+//	border-radius: 4px;
+//}
+//
+//.back-to-parent {
+//	display: flex;
+//	align-items: center;
+//	gap: .5rem;
+//	border: none;
+//	background: none;
+//	color: var(--contrast);
+//	cursor: pointer;
+//	padding: .5rem;
+//	border-radius: 4px;
+//	font-size: var(--txt-x-small);
+//}
+//
+//.back-to-parent:hover {
+//	background: var(--base-100);
+//}
+//
+//.term-row {
+//	display: flex;
+//	align-items: center;
+//	gap: .5rem;
+//	width: 100%;
+//	padding: .25rem 0;
+//}
+//
+//.toggle-children {
+//	border: none;
+//	background: none;
+//	padding: .25rem;
+//	cursor: pointer;
+//	color: var(--contrast);
+//	display: flex;
+//	align-items: center;
+//	justify-content: center;
+//	margin-left: auto;
+//	border-radius: 4px;
+//}
+//
+//.toggle-children:hover {
+//	background: var(--base-50);
+//}
+//
+//.loading-indicator {
+//	display: flex;
+//	align-items: center;
+//	justify-content: center;
+//	width: 24px;
+//	height: 24px;
+//}
+//
+//.loading-indicator .loading {
+//	width: 16px;
+//	height: 16px;
+//	border: 2px solid var(--base-100);
+//	border-top-color: var(--contrast);
+//	border-radius: 50%;
+//	animation: spin 1s linear infinite;
+//}
+//
+//@keyframes spin {
+//	to { transform: rotate(360deg); }
+//}
+//
+//.term-breadcrumb {
+//	display: flex;
+//	align-items: center;
+//	gap: .5rem;
+//	margin-bottom: 1rem;
+//	padding: .5rem;
+//	background: var(--base-50);
+//	border-radius: 4px;
+//}
+//
+//.term-breadcrumb .path {
+//	display: flex;
+//	align-items: center;
+//	gap: .25rem;
+//	flex-wrap: wrap;
+//}
+//
+//.term-breadcrumb button {
+//	border: none;
+//	background: none;
+//	padding: .25rem .5rem;
+//	border-radius: 4px;
+//	cursor: pointer;
+//	color: var(--contrast);
+//	font-size: var(--txt-x-small);
+//}
+//
+//.term-breadcrumb button:hover {
+//	background: var(--base-100);
+//}
+//
+//.path-separator {
+//	color: var(--contrast-50);
+//}
+//
+//.path-level {
+//	white-space: nowrap;
+//}
+//
+//.create-new-term {
+//	margin-top: 2rem;
+//	padding-top: 1rem;
+//	border-top: 1px solid var(--base-100);
+//}
+//.create-new-term button {
+//	width: 100%;
+//}
+//
+//.suggestion-prompt {
+//	font-size: var(--txt-x-small);
+//	color: var(--contrast-50);
+//	margin-bottom: 1rem;
+//}
+//
+//.create-term-form {
+//	display: flex;
+//	flex-direction: column;
+//	gap: .5rem;
+//}
+//
+//.form-row {
+//	display: flex;
+//	align-items: center;
+//	gap: .5rem;
+//}
+//
+//.name-row {
+//	position: relative;
+//}
+//
+//.name-row input {
+//	width: 100%!important;
+//	padding: .5rem;
+//	border: 2px solid var(--base-100);
+//	border-radius: 4px;
+//	background: var(--base);
+//	color: var(--contrast);
+//}
+//
+//.name-row input:focus {
+//	border-color: var(--action-0);
+//	outline: none;
+//}
+//
+//.parent-row {
+//	font-size: var(--txt-x-small);
+//}
+//
+//.parent-row label {
+//	display: flex;
+//	align-items: center;
+//	gap: .5rem;
+//	cursor: pointer;
+//}
+//
+//.create-term-form button {
+//	display: inline-flex;
+//	align-items: center;
+//	gap: .5rem;
+//	padding: .5rem 1rem;
+//	border: none;
+//	border-radius: 4px;
+//	background: var(--action-0);
+//	color: var(--base);
+//	cursor: pointer;
+//	font-size: var(--txt-x-small);
+//	transition: all .2s ease;
+//}
+//
+//.create-term-form button:hover {
+//	background: var(--action-50);
+//}
+//
+//.create-term-form button:disabled {
+//	opacity: .5;
+//	cursor: not-allowed;
+//}
+//
+//.create-term-form button svg {
+//	width: 16px;
+//	height: 16px;
+//}
+//
+///** Search bar and new term functionality **/
+//.search-bar {
+//	position: relative;
+//	display: flex;
+//	gap: .5rem;
+//	align-items: center;
+//	z-index: 2;
+//}
+//
+//.search-bar input {
+//	flex: 1;
+//	/* Make room for button */
+//	padding: .5rem 2.5rem .5rem .5rem;
+//	border: 2px solid #ddd;
+//	border-radius: 4px;
+//	font-size: 1rem;
+//}
+//
+//.new-term-toggle {
+//	margin: 2rem 0 1rem;
+//	width: 100%;
+//	display: flex;
+//	justify-content: center;
+//	align-items: center;
+//}
+//
+///* Peek effect on search bar hover */
+//.search-bar:hover .new-term-toggle {
+//	opacity: 1;
+//	transform: translateX(-.25rem);
+//}
+//
+///* Form styling */
+//.create-term-form {
+//	position: relative;
+//	z-index: 1;
+//	background: var(--base);
+//	border: 2px solid var(--action-0);
+//	border-radius: 4px;
+//	padding: 1rem;
+//	margin-top: -2px;
+//
+//	/* Animation */
+//	transform-origin: top;
+//	transition: all .2s var(--trans-fn);
+//}
+//
+//.create-term-form:not([hidden]) {
+//	animation: slideDown .2s var(--trans-fn);
+//}
+//
+//.create-term-form[hidden] {
+//	display: block !important; /* Override hidden */
+//	transform: scaleY(0);
+//	opacity: 0;
+//	pointer-events: none;
+//}
+//
+//.form-row {
+//	display: flex;
+//	gap: .5rem;
+//	align-items: center;
+//}
+//
+//.form-row + .form-row {
+//	margin-top: .5rem;
+//}
+//
+//@keyframes slideDown {
+//	from {
+//		transform: scaleY(0);
+//		opacity: 0;
+//	}
+//	to {
+//		transform: scaleY(1);
+//		opacity: 1;
+//	}
+//}
+//
+//.news.item {
+//	padding: 1rem!important;
+//}
+//
+//.dashboard .checkbox-options label {
+//	font-weight: normal;
+//}
+//
+//.description.space {
+//	margin-bottom: 4rem;
+//}
+//
+//.invited-artist {
+//	display: flex;
+//	align-items: center;
+//	margin: 1rem 0;
+//}
+//.invited-artist input[type=text],
+//.invited-artist input[type=email] {
+//	width: calc(100% - .5rem);
+//}
+//.invited-artist label {
+//	margin: 0;
+//	font-weight: normal;
+//	white-space:nowrap;
+//}
+//.invited-artist button {
+//	height: fit-content;
+//	margin-right: .5rem;
+//}
+//.actions {
+//	display: flex;
+//	gap: 1rem;
+//	justify-content: flex-end;
+//}
+//
+//.actions .send-invites {
+//	background-color: var(--action-50);
+//}
+//.actions .send-invites:hover,
+//.actions .send-invites:focus {
+//	background-color: var(--base);
+//	border-color: var(--action-50);
+//}
+//
+//.dashboard .queue-status-panel {
+//	bottom: calc(var(--btn) + 1rem);
+//}
+//.dashboard .queue-status-toggle {
+//	bottom: 0;
+//}
+//
+//.items-list table th {
+//	padding: 0 .75rem;
+//	text-transform: none!important;
+//}
+//.items-list table.empty tbody {
+//	height: 40vh;
+//}
+//
+//table textarea,
+//table input[type=text],
+//table input[type=url],
+//table input[type=number],
+//table input[type=email] {
+//	min-width: 40vw;
+//}
+//
+//.empty tfoot {
+//	display: none;
+//}
+//
+//p.hint {
+//	margin: 0 0 .5rem 0;
+//	font-size: var(--txt-x-small);
+//	font-style: italic;
+//}
+//.item-grid + .hint {
+//	text-align: center;
+//	margin-top: 2rem;
+//}
+//
+//.image.field[data-type=single]:has(.upload-item) .file-upload-container {
+//	display: none;
+//}
+//.image.field[data-type=single] .upload-item {
+//	grid-column: 1 / -1;
+//}
+///** UPLOADER **/
+//.upload-item {
+//	position: relative;
+//	border: 1px solid var(--base-200);
+//	border-radius: 8px;
+//	background: var(--base-50);
+//	transition: all .3s ease;
+//}
+//.upload-item:hover {
+//	box-shadow: rgba(var(--base-rgb),var(--op-45)) var(--shdw);
+//	transform: translateY(-2px);
+//}
+//.upload-item[data-status=processing] {
+//	border-color: var(--action-200);
+//	background: var(--action-100);
+//}
+//.upload-item[data-status=cached] {
+//	border-color: var(--secondary-0);
+//	background: var(--secondary-200);
+//}
+//.upload-item[data-status=uploading] {
+//	border-color: var(--contrast);
+//	background-color: var(--base-50);
+//}
+//.upload-item .preview {
+//	position: relative;
+//	aspect-ratio: 1;
+//}
+//.upload-item .status {
+//	position: absolute;
+//	bottom: .25rem;
+//	right: .25rem;
+//	background-color: rgba(var(--base-rgb),var(--op-3));
+//	box-shadow: rgba(var(--base-rgb),var(--op-45)) var(--shdw);
+//	border-radius: 50%;
+//}
+//.upload-item img {
+//	width: 100%;
+//	height: 100%;
+//	object-fit: cover;
+//	transition: transform .3s ease;
+//}
+//.upload-item:hover img {
+//	transform: scale(1.05);
+//}
+//
+//.upload-item .overlay {
+//	position: absolute;
+//	top: 0;
+//	left: 0;
+//	right: 0;
+//	bottom: 0;
+//	background: rgba(var(--base-rgb),var(--op-6));
+//	display: flex;
+//	flex-direction: column;
+//	justify-content: space-between;
+//	padding: .5rem;
+//	opacity: 0;
+//	transition: opacity .3s ease;
+//}
+//.upload-item:hover .overlay,
+//.upload-item[data-status=processing] .overlay,
+//.upload-item[data-status=uploading] .overlay {
+//	opacity: 1;
+//}
+//
+//.upload-item .item-actions {
+//	position: relative;
+//}
+//
+//.submit-uploads {
+//	position: fixed;
+//	bottom: calc(var(--btn) + 1rem);
+//	right: 1rem;
+//	background-color: var(--base);
+//	height: var(--btn);
+//	box-shadow: rgba(var(--base-rgb),var(--op-45)) var(--shdw);
+//}
+///*** UPLOADER GROUPS ***/
+//.group-display {
+//	display: grid;
+//	grid-template-columns: 1fr 250px;
+//	gap: 2rem;
+//}
+//.preview-actions {
+//	position: sticky;
+//	padding: .5rem;
+//	top: calc(var(--btn) + .25rem);
+//	left: 0;
+//	background-color: var(--base-50);
+//	z-index: 5;
+//}
+//.preview-actions .selected {
+//	display: flex;
+//	justify-content: space-between;
+//	padding-right: 2rem;
+//	align-items: center;
+//}
+//.preview-actions .field {
+//	margin: 0;
+//}
+//.preview-actions .field label {
+//	margin-bottom: 0;
+//}
+//.selection-actions {
+//	display: flex;
+//	justify-content: space-between;
+//	padding: 1rem 2rem;
+//}
+//[data-status=completed] .progress {
+//	transform: scaleY(0);
+//	transform-origin: top;
+//}
+//.preview-grid {
+//	display: grid;
+//	grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+//	gap: 1rem;
+//	margin-top: 1rem;
+//	padding: .5rem;
+//	background: var(--base);
+//	border: 1px solid var(--contrast-200);
+//	border-radius: 8px;
+//	min-height: 60px;
+//}
+//.preview-grid:empty {
+//	display: none;
+//}
+//
+//
+//.upload-item .progress {
+//	position: absolute;
+//	top: 2px;
+//	right: 2px;
+//	left: 2px;
+//}
+//.upload-item .actions {
+//	display: flex;
+//	justify-content: space-between;
+//	align-items: center;
+//	position: absolute;
+//	top: 0;
+//	left: 0;
+//	right: 0;
+//}
+//.upload-item .actions > label {
+//	margin: 0 1rem;
+//	padding-left: 0!important;
+//}
+//.upload-item input[type=text],
+//.upload-item textarea,
+//.group-fields input[type=text],
+//.group-fields textarea {
+//	width: calc(100% - 1rem);
+//}
+///*.upload-item .actions > label {*/
+///*    !*position: absolute;*!*/
+///*    padding: 1rem!important;*/
+///*    margin: 0;*/
+///*    !*top: .5rem;*!*/
+///*    !*left: 0;*!*/
+///*}*/
+///*    .upload-item .actions > label::before {*/
+///*        left: calc(50% - 9px)!important;*/
+///*        top: calc(50% - 9px)!important;*/
+///*        transform: none!important;*/
+///*    }*/
+///*    .upload-item .actions > label::after {*/
+///*        left: 1.25rem!important;*/
+///*        top: 1rem!important;*/
+///*    }*/
+//.item-grid .upload-item summary {
+//	display: flex;
+//	align-items: center;
+//	font-size: var(--txt-x-small);
+//	gap: .5rem;
+//	text-transform: uppercase;
+//}
+//.upload-item:has([type=checkbox]:checked) .preview {
+//	padding: 1rem;
+//	background-color: var(--secondary-200);
+//}
+//.upload-item:has([open]) {
+//	grid-column: 1 / -1;
+//}
+//.upload-item summary .icon {
+//	--w: 1.1em;
+//}
+//
+//[draggable="true"] {
+//	cursor: grab;
+//}
+//[draggable="true"]:active {
+//	cursor: grabbing;
+//}
+//
+//.dragging {
+//	opacity: .5;
+//	transform: rotate(5deg) scale(.95);
+//	transition: all .2s ease;
+//}
+//
+//.dragover:not(.item-grid.groups) {
+//	background-color: var(--overlay-action-medium);
+//	border: 2px dashed var(--action-0);
+//}
+//
+//@keyframes dragHover {
+//	0% { transform: scale(1); }
+//	50% { transform: scale(1.02); }
+//	100% { transform: scale(1); }
+//}
+//
+//.drag-hover {
+//	animation: dragHover .6s ease-in-out infinite;
+//}
+//
+///** GROUP **/
+//.empty-group {
+//	padding: 20px;
+//	text-align: center;
+//	grid-column: 1 / -1;
+//	display: flex;
+//	justify-content: center;
+//	align-items: center;
+//	border: 2px dashed var(--action-200);
+//	border-radius: var(--radius);
+//	margin: 10px 0;
+//	cursor: pointer;
+//	transition: all .2s ease;
+//	aspect-ratio: 16/9;
+//}
+//.item-grid.groups {
+//	grid-template-columns: 1fr;
+//}
+//
+//.item-grid.group .item-grid.group {
+//	min-height: 40px;
+//	display: grid;
+//	grid-template-columns: repeat(3, 1fr);
+//	gap: .5rem;
+//	border: 2px dashed var(--action-200);
+//	border-radius: var(--radius);
+//	margin: 10px 0;
+//	cursor: pointer;
+//	transition: all .2s ease;
+//	aspect-ratio: 1;
+//	text-align: center;
+//}
+//.empty-group:hover,
+//.empty-group.dragover,
+//.item-grid.group .item-grid.group:hover,
+//.item-grid.group .item-grid.group.dragover {
+//	border-color: var(--action-0);
+//	background-color: var(--overlay-action-light);
+//	color: var(--action-50);
+//}
+//
+//.upload-group {
+//	background-color: var(--base-100);
+//	border-radius: var(--radius);
+//	border: 1px solid var(--contrast-200);
+//}
+//.group-actions {
+//	width: 100%;
+//	gap: .5rem;
+//}
+//.group-actions button {
+//	width: 100%;
+//}
+//
+//body:has(.selection-actions[hidden]) button.create-group,
+//body:has(.selection-actions[hidden]) button.add-selection-to-group {
+//	display: none;
+//}
+//
+///** RESTORE FROM CACHE **/
+//.restore-notification {
+//	border-radius: var(--radius);
+//	box-shadow: rgba(var(--base-rgb),var(--op-45)) var(--shdw);
+//	padding: 1rem;
+//	background: var(--base-200);
+//	border: 1px solid var(--contrast-200);
+//	border-top-width: 4px;
+//	border-bottom-width: 4px;
+//}
+//.restore-content {
+//	display: flex;
+//	align-items: center;
+//}
+//.restore-actions .selection-controls {
+//	display: flex;
+//	gap: .5rem;
+//}
+//
+//.restore-actions button {
+//	white-space: nowrap;
+//}
+//.restore-actions .action-buttons {
+//	margin-top: 1rem;
+//	display: flex;
+//	flex-direction: column;
+//	gap: .5rem;
+//}
+//.restore-actions .action-buttons button {
+//	width: 100%;
+//}
+//
+//label.restore-item {
+//	border-radius: .5rem;
+//	padding: 0;
+//	background-color: transparent;
+//	filter: grayscale(50);
+//	opacity: .8;
+//	transition: padding var(--trans-base);
+//	transition-property: padding, background-color;
+//	cursor: pointer;
+//}
+//label.restore-item:has(input:checked) {
+//	filter: grayscale(0);
+//	opacity: 1;
+//	padding: 1rem;
+//	background-color: var(--secondary-200);
+//}
+//
+//.upload-item .featured + label {
+//	width: 2em;
+//	height: 2em;
+//	border-radius: var(--radius);
+//	background-color: rgba(var(--base-rgb),var(--op-3));
+//	display: flex;
+//	justify-content: center;
+//	align-items: center;
+//	padding: 0!important;
+//	border: none;
+//}
+//
+//input.featured + label .icon {
+//	--w: 1.25em;
+//}
+//input.featured + label .icon + .icon {
+//	position: absolute;
+//}
+//input.featured + label .star-fill,
+//input.featured:checked + label .star {
+//	transform: scale(0);
+//}
+//input.featured:checked + label .star-fill,
+//input.featured + label .star {
+//	transform: scale(1);
+//}
+///**TODO: Double check needed **/
+///*!* Centralized Upload Manager CSS *!*/
+//
+///*!* Global Upload Status Bar *!*/
+///*.all-uploads {*/
+///*    position: fixed;*/
+///*    top: 0;*/
+///*    left: 0;*/
+///*    right: 0;*/
+///*    background: var(--surface);*/
+///*    border-bottom: 1px solid var(--border);*/
+///*    padding: .75rem 1rem;*/
+///*    z-index: 1000;*/
+///*    box-shadow: 0 2px 8px rgba(0, 0, 0, .1);*/
+///*    backdrop-filter: blur(10px);*/
+///*    display: none; !* Hidden by default *!*/
+///*}*/
+//
+///*.upload-summary {*/
+///*    display: flex;*/
+///*    justify-content: space-between;*/
+///*    align-items: center;*/
+///*    margin-bottom: .5rem;*/
+///*}*/
+//
+///*.all-uploads .active {*/
+///*    font-weight: 500;*/
+///*    color: var(--contrast);*/
+///*    font-size: .9rem;*/
+///*}*/
+//
+///*.upload-summary button {*/
+///*    padding: .5rem 1rem;*/
+///*    border: 1px solid var(--border);*/
+///*    border-radius: 4px;*/
+///*    background: var(--surface);*/
+///*    color: var(--contrast);*/
+///*    cursor: pointer;*/
+///*    transition: all .3s ease;*/
+///*    font-size: .875rem;*/
+///*    margin-left: .5rem;*/
+///*}*/
+//
+///*.upload-summary button:hover {*/
+///*    background: var(--action-0);*/
+///*    color: var(--action-contrast);*/
+///*    border-color: var(--action-0);*/
+///*}*/
+//
+///*!* Upload Drop Zones *!*/
+///*.file-upload-container {*/
+///*    position: relative;*/
+///*    padding: .25rem;*/
+///*    transition: border-color var(--trans-base),*/
+///*    background-color var(--trans-base),*/
+///*    padding var(--trans-base);*/
+///*}*/
+//
+///*.file-upload-container.dragover {*/
+///*    background-color: var(--action-rgb-subtle);*/
+///*    border-color: var(--action-0);*/
+///*    padding: .75rem;*/
+///*}*/
+///*    .dragover .file-upload-wrapper {*/
+///*        transform: scale(1.02);*/
+///*    }*/
+//
+//
+///*!* Upload Actions *!*/
+///*.upload .actions {*/
+///*    display: flex;*/
+///*    gap: .25rem;*/
+///*    justify-content: flex-end;*/
+///*}*/
+//
+///*.upload .actions button {*/
+///*    background: rgba(0, 0, 0, .7);*/
+///*    color: white;*/
+///*    border: none;*/
+///*    padding: .25rem;*/
+///*    border-radius: 4px;*/
+///*    cursor: pointer;*/
+///*    transition: background .3s ease;*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    justify-content: center;*/
+///*}*/
+//
+///*.upload .actions button:hover {*/
+///*    background: rgba(0, 0, 0, .9);*/
+///*}*/
+//
+///*.upload .actions button svg {*/
+///*    width: 16px;*/
+///*    height: 16px;*/
+///*}*/
+//
+///*!* Status Indicator *!*/
+///*.status {*/
+///*    position: absolute;*/
+///*    top: .5rem;*/
+///*    right: .5rem;*/
+///*    width: 24px;*/
+///*    height: 24px;*/
+///*    border-radius: 50%;*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    justify-content: center;*/
+///*    font-size: .8rem;*/
+///*    font-weight: bold;*/
+///*    z-index: 2;*/
+///*    box-shadow: 0 2px 4px rgba(0, 0, 0, .2);*/
+///*}*/
+//
+///*.status.processing {*/
+///*    background: var(--warning);*/
+///*    color: var(--warning-contrast);*/
+///*}*/
+//
+///*.status.processed {*/
+///*    background: var(--info);*/
+///*    color: var(--info-contrast);*/
+///*}*/
+//
+///*.status.cached {*/
+///*    background: var(--info);*/
+///*    color: var(--info-contrast);*/
+///*}*/
+//
+///*.status.uploading {*/
+///*    background: var(--action-0);*/
+///*    color: var(--action-contrast);*/
+///*}*/
+//
+///*.status.uploaded {*/
+///*    background: var(--success);*/
+///*    color: var(--success-contrast);*/
+///*}*/
+//
+///*.status.error {*/
+///*    background: var(--error);*/
+///*    color: var(--error-contrast);*/
+///*}*/
+//
+///*!* Upload Metadata *!*/
+///*.upload .metadata {*/
+///*    padding: 1rem;*/
+///*    border-top: 1px solid var(--border);*/
+///*    background: var(--surface);*/
+///*}*/
+//
+///*.upload .metadata .metadata-field {*/
+///*    margin-bottom: .75rem;*/
+///*}*/
+//
+///*.upload .metadata .metadata-field:last-child {*/
+///*    margin-bottom: 0;*/
+///*}*/
+//
+///*.upload .metadata label {*/
+///*    display: block;*/
+///*    margin-bottom: .25rem;*/
+///*    font-weight: 500;*/
+///*    font-size: .9rem;*/
+///*    color: var(--contrast);*/
+///*}*/
+//
+///*.upload .metadata input,*/
+///*.upload .metadata textarea {*/
+///*    width: 100%;*/
+///*    padding: .5rem;*/
+///*    border: 1px solid var(--border);*/
+///*    border-radius: 4px;*/
+///*    font-size: .9rem;*/
+///*    transition: border-color .3s ease, box-shadow .3s ease;*/
+///*}*/
+//
+///*.upload .metadata input:focus,*/
+///*.upload .metadata textarea:focus {*/
+///*    outline: none;*/
+///*    border-color: var(--action-0);*/
+///*    box-shadow: 0 0 0 2px var(--action-rgb-subtle);*/
+///*}*/
+//
+///*.upload .metadata textarea {*/
+///*    resize: vertical;*/
+///*    min-height: 2.5rem;*/
+///*}*/
+//
+///*!* Field Type Specific Styles *!*/
+//
+///*!* Single Image Field *!*/
+///*.upload.field[data-upload-type="single"] .upload-preview-grid {*/
+///*    grid-template-columns: 1fr;*/
+///*    max-width: 400px;*/
+///*}*/
+//
+///*!* Gallery Field *!*/
+///*.upload.field[data-upload-type="gallery"] .upload-preview-grid {*/
+///*    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));*/
+///*}*/
+//
+///*!* Avatar Field *!*/
+///*.upload.field[data-upload-type="avatar"] .upload-preview-grid {*/
+///*    grid-template-columns: 1fr;*/
+///*    max-width: 200px;*/
+///*}*/
+//
+///*.upload.field[data-upload-type="avatar"] .upload-preview {*/
+///*    aspect-ratio: 1;*/
+///*    border-radius: 50%;*/
+///*    overflow: hidden;*/
+///*}*/
+//
+///*!* Spinner Animation *!*/
+///*.spinner {*/
+///*    width: 12px;*/
+///*    height: 12px;*/
+///*    border: 2px solid transparent;*/
+///*    border-top: 2px solid currentColor;*/
+///*    border-radius: 50%;*/
+///*    animation: spin 1s linear infinite;*/
+///*}*/
+//
+///*@keyframes spin {*/
+///*    0% { transform: rotate(0deg); }*/
+///*    100% { transform: rotate(360deg); }*/
+///*}*/
+//
+///*@keyframes shimmer {*/
+///*    0% { transform: translateX(-100%); }*/
+///*    100% { transform: translateX(100%); }*/
+///*}*/
+//
+///*!* Responsive Design *!*/
+///*@media (max-width: 768px) {*/
+///*    .all-uploads {*/
+///*        position: relative;*/
+///*        top: auto;*/
+///*        left: auto;*/
+///*        right: auto;*/
+///*    }*/
+//
+///*    .upload-summary {*/
+///*        flex-direction: column;*/
+///*        gap: .5rem;*/
+///*        align-items: stretch;*/
+///*    }*/
+//
+///*    .upload-summary button {*/
+///*        margin-left: 0;*/
+///*        width: 100%;*/
+///*    }*/
+//
+///*    .upload-preview-grid {*/
+///*        grid-template-columns: 1fr;*/
+///*        gap: .75rem;*/
+///*    }*/
+//
+///*    .upload.field[data-upload-type="gallery"] .upload-preview-grid {*/
+///*        grid-template-columns: repeat(2, 1fr);*/
+///*    }*/
+///*}*/
+//
+///*!* Dark Mode Support *!*/
+///*@media (prefers-color-scheme: dark) {*/
+///*    .upload-overlay {*/
+///*        background: linear-gradient(to bottom, rgba(0,0,0,.3), rgba(0,0,0,.6));*/
+///*    }*/
+//
+///*}*/
+//
+///*!* High Contrast Mode *!*/
+///*@media (prefers-contrast: high) {*/
+///*    .upload-item {*/
+///*        border-width: 2px;*/
+///*    }*/
+//
+///*    .status {*/
+///*        border: 2px solid currentColor;*/
+///*    }*/
+//
+///*}*/
+//
+///*!* Reduced Motion *!*/
+///*@media (prefers-reduced-motion: reduce) {*/
+///*    .upload-item:hover {*/
+///*        transform: none;*/
+///*    }*/
+//
+///*    .upload-item:hover .upload-preview img {*/
+///*        transform: none;*/
+///*    }*/
+//
+///*    .spinner,*/
+///*    .shimmer {*/
+///*        animation: none;*/
+///*    }*/
+///*}*/
+//
+///*!* Focus Management *!*/
+///*.upload-item:focus-within {*/
+///*    outline: 2px solid var(--action-0);*/
+///*    outline-offset: 2px;*/
+///*}*/
+//
+///*.upload .actions button:focus {*/
+///*    outline: 2px solid var(--action-0);*/
+///*    outline-offset: 2px;*/
+///*}*/
+//
+///*!* Loading States *!*/
+///*.upload.field.processing {*/
+///*    pointer-events: none;*/
+///*    opacity: .7;*/
+///*}*/
+//
+///*.upload.field.processing::after {*/
+///*    content: '';*/
+///*    position: absolute;*/
+///*    top: 0;*/
+///*    left: 0;*/
+///*    right: 0;*/
+///*    bottom: 0;*/
+///*    background: rgba(255, 255, 255, .5);*/
+///*    backdrop-filter: blur(2px);*/
+///*    z-index: 10;*/
+///*}*/
+//
+///*!* Field Capacity Indicators *!*/
+///*.upload.field[data-at-capacity="true"] .file-upload-container {*/
+///*    opacity: .5;*/
+///*    pointer-events: none;*/
+///*}*/
+//
+///*.upload.field[data-at-capacity="true"]::before {*/
+///*    content: 'Maximum files reached';*/
+///*    position: absolute;*/
+///*    top: 50%;*/
+///*    left: 50%;*/
+///*    transform: translate(-50%, -50%);*/
+///*    background: var(--warning);*/
+///*    color: var(--warning-contrast);*/
+///*    padding: .5rem 1rem;*/
+///*    border-radius: 4px;*/
+///*    font-size: .875rem;*/
+///*    z-index: 5;*/
+///*}*/
+//
+///*!* Integration with existing form styles *!*/
+///*.upload.field {*/
+///*    position: relative;*/
+///*}*/
+//
+///*.upload.field .upload-preview-grid {*/
+///*    margin-top: .5rem;*/
+///*}*/
+//
+///*!* Ensure global status doesn't interfere with fixed elements *!*/
+///*body.has-global-upload-status {*/
+///*    padding-top: 80px;*/
+///*}*/
+//
+///*!* Animation for upload completion *!*/
+///*@keyframes uploadSuccess {*/
+///*    0% { transform: scale(1); }*/
+///*    50% { transform: scale(1.05); }*/
+///*    100% { transform: scale(1); }*/
+///*}*/
+//
+///*.upload-item[data-status="uploaded"] {*/
+///*    animation: uploadSuccess .6s ease-out;*/
+///*}*/
+///*!* Batch Processing Progress Bar *!*/
+///*.batch-progress {*/
+///*    margin: 1rem 0;*/
+///*    padding: 1rem;*/
+///*    background: var(--color-background-secondary, #f8f9fa);*/
+///*    border: 1px solid var(--color-border, #e1e5e9);*/
+///*    border-radius: 8px;*/
+///*    transition: opacity .3s ease;*/
+///*}*/
+//
+///*.batch-progress.completed {*/
+///*    background: var(--color-success-light, #d4edda);*/
+///*    border-color: var(--color-success, #28a745);*/
+///*}*/
+//
+///*.progress-info {*/
+///*    display: flex;*/
+///*    justify-content: space-between;*/
+///*    align-items: center;*/
+///*    margin-bottom: .5rem;*/
+///*    font-size: .9rem;*/
+///*}*/
+//
+///*.progress-message {*/
+///*    color: var(--color-text-secondary, #6c757d);*/
+///*    font-weight: 500;*/
+///*}*/
+//
+///*.progress-count {*/
+///*    color: var(--color-text-primary, #212529);*/
+///*    font-weight: 600;*/
+///*    font-variant-numeric: tabular-nums;*/
+///*}*/
+//
+///*.progress .bar {*/
+///*    width: 100%;*/
+///*    height: 8px;*/
+///*    background: var(--color-background-tertiary, #e9ecef);*/
+///*    border-radius: 4px;*/
+///*    overflow: hidden;*/
+///*}*/
+//
+///*.progress .fill {*/
+///*    height: 100%;*/
+///*    background: linear-gradient(90deg,*/
+///*    var(--color-primary, #007cba) 0%,*/
+///*    var(--color-primary-light, #0096dd) 100%*/
+///*    );*/
+///*    border-radius: 4px;*/
+///*    transition: width .3s ease;*/
+///*    position: relative;*/
+///*}*/
+//
+///*!* Animated progress indicator *!*/
+///*.progress .fill::after {*/
+///*    content: '';*/
+///*    position: absolute;*/
+///*    top: 0;*/
+///*    left: 0;*/
+///*    right: 0;*/
+///*    bottom: 0;*/
+///*    background: linear-gradient(*/
+///*            90deg,*/
+///*            transparent 0%,*/
+///*            rgba(255,255,255,.3) 50%,*/
+///*            transparent 100%*/
+///*    );*/
+///*    animation: progress-shine 2s ease-in-out infinite;*/
+///*}*/
+//
+///*@keyframes progress-shine {*/
+///*    0% { transform: translateX(-100%); }*/
+///*    100% { transform: translateX(100%); }*/
+///*}*/
+//
+///*!* Completed state styling *!*/
+///*.progress.completed .fill {*/
+///*    background: linear-gradient(90deg,*/
+///*    var(--color-success, #28a745) 0%,*/
+///*    var(--color-success-light, #34ce57) 100%*/
+///*    );*/
+///*}*/
+//
+///*.progress.completed .progress-message {*/
+///*    color: var(--color-success-dark, #155724);*/
+///*}*/
+//
+///*!* Dark mode support *!*/
+///*@media (prefers-color-scheme: dark) {*/
+///*    .progress {*/
+///*        background: var(--color-background-secondary-dark, #2d3436);*/
+///*        border-color: var(--color-border-dark, #636e72);*/
+///*    }*/
+//
+///*    .progress .bar {*/
+///*        background: var(--color-background-tertiary-dark, #636e72);*/
+///*    }*/
+///*}*/
+//
+///*!* ✅ Grouping System CSS *!*/
+//
+///*!* Upload field container with grouping support *!*/
+///*.upload.field.groupable {*/
+///*    display: grid;*/
+///*    grid-template-columns: 1fr 300px;*/
+///*    gap: 20px;*/
+///*    min-height: 400px;*/
+///*}*/
+//
+///*.upload.field.groupable .upload-main {*/
+///*    display: flex;*/
+///*    flex-direction: column;*/
+///*}*/
+//
+///*.upload.field.groupable .upload-sidebar {*/
+///*    border-left: 2px solid #e0e0e0;*/
+///*    padding-left: 20px;*/
+///*}*/
+//
+///*!* Preview grid for groupable uploads *!*/
+///*.upload-preview-grid.groupable {*/
+///*    min-height: 200px;*/
+///*    border: 2px dashed #ddd;*/
+///*    border-radius: 8px;*/
+///*    padding: 15px;*/
+///*    background: #fafafa;*/
+///*}*/
+//
+///*.upload-preview-grid.groupable:empty::after {*/
+///*    content: "Drop images here or click to upload";*/
+///*    display: block;*/
+///*    text-align: center;*/
+///*    color: #666;*/
+///*    font-style: italic;*/
+///*    padding: 40px 20px;*/
+///*}*/
+//
+///*!* Upload items with drag support *!*/
+///*.upload-item[draggable] {*/
+///*    cursor: grab;*/
+///*    transition: transform .2s ease, box-shadow .2s ease;*/
+///*}*/
+//
+///*.upload-item[draggable]:hover {*/
+///*    transform: translateY(-2px);*/
+///*    box-shadow: 0 4px 8px rgba(0,0,0,.1);*/
+///*}*/
+//
+///*.upload-item.dragging {*/
+///*    opacity: .5;*/
+///*    transform: rotate(5deg);*/
+///*    cursor: grabbing;*/
+///*}*/
+//
+///*!* Groups container *!*/
+///*.groups-container {*/
+///*    display: flex;*/
+///*    flex-direction: column;*/
+///*    gap: 15px;*/
+///*    max-height: 600px;*/
+///*    overflow-y: auto;*/
+///*    padding: 10px;*/
+///*    background: #f9f9f9;*/
+///*    border-radius: 8px;*/
+///*}*/
+//
+///*.groups-container .new-group-section {*/
+///*    text-align: center;*/
+///*    padding: 20px;*/
+///*    border: 2px dashed #ccc;*/
+///*    border-radius: 8px;*/
+///*    background: white;*/
+///*    cursor: pointer;*/
+///*    transition: all .2s ease;*/
+///*}*/
+//
+///*.groups-container .new-group-section:hover {*/
+///*    border-color: #2196F3;*/
+///*    background: #f0f8ff;*/
+///*}*/
+//
+///*.new-group-btn {*/
+///*    background: #2196F3;*/
+///*    color: white;*/
+///*    border: none;*/
+///*    padding: 10px 20px;*/
+///*    border-radius: 6px;*/
+///*    cursor: pointer;*/
+///*    font-size: 14px;*/
+///*    display: inline-flex;*/
+///*    align-items: center;*/
+///*    gap: 8px;*/
+///*}*/
+//
+///*.new-group-btn:hover {*/
+///*    background: #1976D2;*/
+///*}*/
+//
+///*!* Individual groups *!*/
+///*.upload-group {*/
+///*    background: white;*/
+///*    border: 1px solid #e0e0e0;*/
+///*    border-radius: 8px;*/
+///*    padding: 15px;*/
+///*    transition: all .2s ease;*/
+///*}*/
+//
+///*.upload-group.drag-hover {*/
+///*    border-color: #2196F3;*/
+///*    background: #f0f8ff;*/
+///*    box-shadow: 0 2px 8px rgba(33, 150, 243, .2);*/
+///*}*/
+//
+///*.group-header {*/
+///*    display: flex;*/
+///*    justify-content: space-between;*/
+///*    align-items: center;*/
+///*    margin-bottom: 15px;*/
+///*    padding-bottom: 10px;*/
+///*    border-bottom: 1px solid #f0f0f0;*/
+///*}*/
+//
+///*.group-info {*/
+///*    flex: 1;*/
+///*    display: flex;*/
+///*    flex-direction: column;*/
+///*    gap: 5px;*/
+///*}*/
+//
+///*.group-name {*/
+///*    border: 1px solid #ddd;*/
+///*    border-radius: 4px;*/
+///*    padding: 6px 10px;*/
+///*    font-size: 14px;*/
+///*    font-weight: 500;*/
+///*    background: white;*/
+///*    transition: border-color .2s ease;*/
+///*}*/
+//
+///*.group-name:focus {*/
+///*    outline: none;*/
+///*    border-color: #2196F3;*/
+///*    box-shadow: 0 0 0 2px rgba(33, 150, 243, .2);*/
+///*}*/
+//
+///*.group-name:empty::before {*/
+///*    content: "Enter group name...";*/
+///*    color: #999;*/
+///*    font-style: italic;*/
+///*}*/
+//
+///*.group-count {*/
+///*    font-size: 12px;*/
+///*    color: #666;*/
+///*    font-weight: normal;*/
+///*}*/
+//
+///*.group-actions {*/
+///*    display: flex;*/
+///*    gap: 8px;*/
+///*}*/
+//
+///*.delete-group {*/
+///*    background: #f44336;*/
+///*    color: white;*/
+///*    border: none;*/
+///*    padding: 6px 8px;*/
+///*    border-radius: 4px;*/
+///*    cursor: pointer;*/
+///*    font-size: 12px;*/
+///*    transition: background .2s ease;*/
+///*}*/
+//
+///*.delete-group:hover {*/
+///*    background: #d32f2f;*/
+///*}*/
+//
+///*!* Group drop zone *!*/
+///*.group-drop-zone {*/
+///*    min-height: 80px;*/
+///*    border: 2px dashed #ccc;*/
+///*    border-radius: 6px;*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    justify-content: center;*/
+///*    margin-bottom: 15px;*/
+///*    transition: all .2s ease;*/
+///*    background: #fafafa;*/
+///*}*/
+//
+///*.group-drop-zone p {*/
+///*    color: #666;*/
+///*    font-style: italic;*/
+///*    margin: 0;*/
+///*    font-size: 14px;*/
+///*}*/
+//
+///*.group-drop-zone.drag-hover {*/
+///*    border-color: #4CAF50;*/
+///*    background: #f1f8e9;*/
+///*}*/
+//
+///*!* Group items *!*/
+///*.group-items {*/
+///*    display: grid;*/
+///*    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));*/
+///*    gap: 10px;*/
+///*    margin-bottom: 15px;*/
+///*}*/
+//
+///*.group-item {*/
+///*    position: relative;*/
+///*    background: white;*/
+///*    border: 1px solid #e0e0e0;*/
+///*    border-radius: 6px;*/
+///*    overflow: hidden;*/
+///*    transition: all .2s ease;*/
+///*}*/
+//
+///*.group-item:hover {*/
+///*    box-shadow: 0 2px 8px rgba(0,0,0,.1);*/
+///*}*/
+//
+///*.group-item-preview {*/
+///*    position: relative;*/
+///*    width: 100%;*/
+///*    height: 80px;*/
+///*    overflow: hidden;*/
+///*}*/
+//
+///*.group-item-preview img {*/
+///*    width: 100%;*/
+///*    height: 100%;*/
+///*    object-fit: cover;*/
+///*}*/
+//
+///*.group-item-overlay {*/
+///*    position: absolute;*/
+///*    top: 0;*/
+///*    right: 0;*/
+///*    background: rgba(0,0,0,.7);*/
+///*    padding: 4px;*/
+///*    opacity: 0;*/
+///*    transition: opacity .2s ease;*/
+///*}*/
+//
+///*.group-item:hover .group-item-overlay {*/
+///*    opacity: 1;*/
+///*}*/
+//
+///*.remove-from-group {*/
+///*    background: #f44336;*/
+///*    color: white;*/
+///*    border: none;*/
+///*    padding: 4px 6px;*/
+///*    border-radius: 3px;*/
+///*    cursor: pointer;*/
+///*    font-size: 10px;*/
+///*}*/
+//
+///*.remove-from-group:hover {*/
+///*    background: #d32f2f;*/
+///*}*/
+//
+///*!* Group metadata fields *!*/
+///*.group-metadata {*/
+///*    padding-top: 15px;*/
+///*    border-top: 1px solid #f0f0f0;*/
+///*}*/
+//
+///*.group-metadata .metadata-field {*/
+///*    margin-bottom: 12px;*/
+///*}*/
+//
+///*.group-metadata label {*/
+///*    display: block;*/
+///*    font-size: 12px;*/
+///*    font-weight: 500;*/
+///*    color: #333;*/
+///*    margin-bottom: 4px;*/
+///*}*/
+//
+///*.group-metadata input,*/
+///*.group-metadata textarea {*/
+///*    width: 100%;*/
+///*    border: 1px solid #ddd;*/
+///*    border-radius: 4px;*/
+///*    padding: 6px 8px;*/
+///*    font-size: 13px;*/
+///*    transition: border-color .2s ease;*/
+///*}*/
+//
+///*.group-metadata input:focus,*/
+///*.group-metadata textarea:focus {*/
+///*    outline: none;*/
+///*    border-color: #2196F3;*/
+///*    box-shadow: 0 0 0 2px rgba(33, 150, 243, .2);*/
+///*}*/
+//
+///*.group-metadata textarea {*/
+///*    resize: vertical;*/
+///*    min-height: 60px;*/
+///*}*/
+//
+///*!* Drag feedback animations *!*/
+///*@keyframes dragHover {*/
+///*    0% { transform: scale(1); }*/
+///*    50% { transform: scale(1.02); }*/
+///*    100% { transform: scale(1); }*/
+///*}*/
+//
+///*.drag-hover {*/
+///*    animation: dragHover .6s ease-in-out infinite;*/
+///*}*/
+//
+///*!* Status indicators for grouped uploads *!*/
+///*.upload-group[data-status="uploading"] {*/
+///*    border-color: #FF9800;*/
+///*    background: #fff8f0;*/
+///*}*/
+//
+///*.upload-group[data-status="uploaded"] {*/
+///*    border-color: #4CAF50;*/
+///*    background: #f1f8e9;*/
+///*}*/
+//
+///*.upload-group[data-status="error"] {*/
+///*    border-color: #f44336;*/
+///*    background: #ffebee;*/
+///*}*/
+//
+///*!* Progress indicators for groups *!*/
+///*.group-progress {*/
+///*    height: 3px;*/
+///*    background: #f0f0f0;*/
+///*    border-radius: 2px;*/
+///*    overflow: hidden;*/
+///*    margin-top: 10px;*/
+///*}*/
+//
+///*.group-progress-fill {*/
+///*    height: 100%;*/
+///*    background: #2196F3;*/
+///*    transition: width .3s ease;*/
+///*    border-radius: 2px;*/
+///*}*/
+//
+///*!* Responsive design *!*/
+///*@media (max-width: 768px) {*/
+///*    .upload.field.groupable {*/
+///*        grid-template-columns: 1fr;*/
+///*        gap: 15px;*/
+///*    }*/
+//
+///*    .upload.field.groupable .upload-sidebar {*/
+///*        border-left: none;*/
+///*        border-top: 2px solid #e0e0e0;*/
+///*        padding-left: 0;*/
+///*        padding-top: 15px;*/
+///*    }*/
+//
+///*    .group-items {*/
+///*        grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));*/
+///*        gap: 8px;*/
+///*    }*/
+//
+///*    .groups-container {*/
+///*        max-height: 400px;*/
+///*    }*/
+///*}*/
+//
+///*!* Accessibility improvements *!*/
+///*.upload-item.groupable:focus {*/
+///*    outline: 2px solid #2196F3;*/
+///*    outline-offset: 2px;*/
+///*}*/
+//
+///*.group-drop-zone:focus-within {*/
+///*    border-color: #2196F3;*/
+///*    background: #f0f8ff;*/
+///*}*/
+//
+///*!* High contrast mode support *!*/
+///*@media (prefers-contrast: high) {*/
+///*    .upload-group {*/
+///*        border-width: 2px;*/
+///*    }*/
+//
+///*    .group-drop-zone {*/
+///*        border-width: 3px;*/
+///*    }*/
+//
+///*    .upload-item.groupable.dragging {*/
+///*        border: 3px solid #2196F3;*/
+///*    }*/
+///*}*/
+//
+///*!* Selection Controls *!*/
+///*.selection-controls {*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    gap: 1rem;*/
+///*    flex-wrap: wrap;*/
+///*}*/
+//
+///*.selection-actions {*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    gap: 1rem;*/
+///*    padding: .5rem 1rem;*/
+///*    background-color: var(--action-50);*/
+///*    border-radius: var(--radius);*/
+///*    color: var(--contrast);*/
+///*    font-size: .9rem;*/
+///*}*/
+//
+///*.selection-actions button {*/
+///*    background: rgba(255, 255, 255, .2);*/
+///*    border: 1px solid rgba(255, 255, 255, .3);*/
+///*    color: inherit;*/
+///*    padding: .25rem .5rem;*/
+///*    border-radius: var(--radius);*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    gap: .25rem;*/
+///*}*/
+//
+///*.selection-actions button:hover {*/
+///*    background: rgba(255, 255, 255, .3);*/
+///*}*/
+//
+///*.selection-count {*/
+///*    font-weight: bold;*/
+///*}*/
+//
+///*!* Preview Actions *!*/
+///*.preview-actions {*/
+///*    display: flex;*/
+///*    justify-content: space-between;*/
+///*    align-items: center;*/
+///*    padding: 1rem;*/
+///*    background-color: var(--base-100);*/
+///*    border-radius: var(--radius-outer);*/
+///*    margin-bottom: 1rem;*/
+///*}*/
+//
+///*!* Upload Item Enhancements *!*/
+///*.upload-item {*/
+///*    position: relative;*/
+///*    background: var(--base);*/
+///*    border-radius: var(--radius);*/
+///*    overflow: hidden;*/
+///*    cursor: pointer;*/
+///*    transition: transform .2s ease, box-shadow .2s ease;*/
+///*}*/
+//
+///*.upload-item:hover {*/
+///*    transform: translateY(-2px);*/
+///*    box-shadow: rgba(var(--base-rgb),var(--op-45)) var(--shdw);*/
+///*}*/
+//
+///*.upload-item[draggable="true"] {*/
+///*    cursor: grab;*/
+///*}*/
+//
+///*.upload-item.dragging {*/
+///*    cursor: grabbing;*/
+///*    opacity: .5;*/
+///*    transform: rotate(5deg) scale(.95);*/
+///*}*/
+//
+///*.upload-item.selected {*/
+///*    border: 2px solid var(--action-50);*/
+///*    background: rgba(255, 0, 128, .05);*/
+///*}*/
+//
+///*.upload-item .preview {*/
+///*    position: relative;*/
+///*    aspect-ratio: 1;*/
+///*}*/
+//
+///*.upload-item img {*/
+///*    width: 100%;*/
+///*    height: 100%;*/
+///*    object-fit: cover;*/
+///*}*/
+//
+///*.upload-item .overlay {*/
+///*    position: absolute;*/
+///*    top: 0;*/
+///*    left: 0;*/
+///*    right: 0;*/
+///*    bottom: 0;*/
+///*    background: rgba(0, 0, 0, .6);*/
+///*    opacity: 0;*/
+///*    transition: opacity .2s ease;*/
+///*    display: flex;*/
+///*    flex-direction: column;*/
+///*    justify-content: space-between;*/
+///*    padding: .5rem;*/
+///*}*/
+//
+///*.upload-item:hover .overlay,*/
+///*.upload-item.selected .overlay {*/
+///*    opacity: 1;*/
+///*}*/
+//
+///*.upload-item .actions input[type="checkbox"] {*/
+///*    margin: 0;*/
+///*    transform: scale(1.2);*/
+///*}*/
+//
+///*.upload-item .actions label {*/
+///*    margin: 0;*/
+///*    padding: 0;*/
+///*    cursor: pointer;*/
+///*}*/
+//
+///*!* Status Indicator *!*/
+///*.upload-item .status {*/
+///*    position: absolute;*/
+///*    top: .5rem;*/
+///*    right: .5rem;*/
+///*    width: 1.5rem;*/
+///*    height: 1.5rem;*/
+///*    border-radius: 50%;*/
+///*    background: var(--base);*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    justify-content: center;*/
+///*    box-shadow: 0 2px 4px rgba(0, 0, 0, .2);*/
+///*}*/
+//
+///*.upload-item[data-status="processing"] .status {*/
+///*    background: var(--warning);*/
+///*}*/
+//
+///*.upload-item[data-status="uploaded"] .status {*/
+///*    background: var(--success);*/
+///*}*/
+//
+///*.upload-item[data-status="error"] .status {*/
+///*    background: var(--danger);*/
+///*}*/
+//
+///*!* Group Enhancements *!*/
+///*.upload-group {*/
+///*    background: var(--base-50);*/
+///*    border-radius: var(--radius-outer);*/
+///*    padding: 1rem;*/
+///*    margin-bottom: 1rem;*/
+///*    border: 2px solid transparent;*/
+///*    transition: border-color .2s ease, background-color .2s ease;*/
+///*}*/
+//
+///*.upload-group.dragover {*/
+///*    border-color: var(--action-50);*/
+///*    background: rgba(255, 0, 128, .05);*/
+///*}*/
+//
+///*.group-header {*/
+///*    display: flex;*/
+///*    justify-content: space-between;*/
+///*    align-items: center;*/
+///*    margin-bottom: 1rem;*/
+///*}*/
+//
+///*.group-info {*/
+///*    flex: 1;*/
+///*    display: flex;*/
+///*    flex-direction: column;*/
+///*    gap: .25rem;*/
+///*}*/
+//
+///*.group-name {*/
+///*    font-size: 1.1rem;*/
+///*    font-weight: 500;*/
+///*    border: none;*/
+///*    background: transparent;*/
+///*    color: var(--text);*/
+///*    padding: .25rem 0;*/
+///*    border-bottom: 1px solid transparent;*/
+///*    transition: border-color .2s ease;*/
+///*}*/
+//
+///*.group-name:focus {*/
+///*    outline: none;*/
+///*    border-bottom-color: var(--action-50);*/
+///*}*/
+//
+///*.group-count {*/
+///*    font-size: .85rem;*/
+///*    color: var(--text-muted);*/
+///*}*/
+//
+///*.group-actions {*/
+///*    display: flex;*/
+///*    gap: .5rem;*/
+///*}*/
+//
+///*.group-actions button {*/
+///*    background: var(--base);*/
+///*    border: 1px solid var(--base-200);*/
+///*    border-radius: var(--radius);*/
+///*    padding: .5rem;*/
+///*    cursor: pointer;*/
+///*    transition: all .2s ease;*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    justify-content: center;*/
+///*}*/
+//
+///*.group-actions button:hover {*/
+///*    background: var(--action-50);*/
+///*    color: var(--contrast);*/
+///*    border-color: var(--action-50);*/
+///*}*/
+//
+///*.group-actions .delete-group:hover {*/
+///*    background: var(--danger);*/
+///*    border-color: var(--danger);*/
+///*}*/
+//
+///*!* Group Content *!*/
+///*.group-content {*/
+///*    min-height: 120px;*/
+///*}*/
+//
+///*.group-drop-zone {*/
+///*    border: 2px dashed var(--base-300);*/
+///*    border-radius: var(--radius);*/
+///*    padding: 2rem;*/
+///*    text-align: center;*/
+///*    color: var(--text-muted);*/
+///*    transition: all .2s ease;*/
+///*    cursor: pointer;*/
+///*}*/
+//
+///*.group-drop-zone:hover,*/
+///*.group-drop-zone.dragover {*/
+///*    border-color: var(--action-50);*/
+///*    background: rgba(255, 0, 128, .05);*/
+///*    color: var(--action-50);*/
+///*}*/
+//
+///*.group-items {*/
+///*    display: grid;*/
+///*    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));*/
+///*    gap: 1rem;*/
+///*    padding: 1rem 0;*/
+///*}*/
+//
+///*.group-item {*/
+///*    position: relative;*/
+///*    aspect-ratio: 1;*/
+///*    border-radius: var(--radius);*/
+///*    overflow: hidden;*/
+///*    background: var(--base);*/
+///*    transition: transform .2s ease;*/
+///*}*/
+//
+///*.group-item:hover {*/
+///*    transform: scale(1.02);*/
+///*}*/
+//
+///*.group-item img {*/
+///*    width: 100%;*/
+///*    height: 100%;*/
+///*    object-fit: cover;*/
+///*}*/
+//
+///*.group-item .overlay {*/
+///*    position: absolute;*/
+///*    top: 0;*/
+///*    left: 0;*/
+///*    right: 0;*/
+///*    bottom: 0;*/
+///*    background: rgba(0, 0, 0, .6);*/
+///*    opacity: 0;*/
+///*    transition: opacity .2s ease;*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    justify-content: center;*/
+///*    gap: .5rem;*/
+///*}*/
+//
+///*.group-item:hover .overlay {*/
+///*    opacity: 1;*/
+///*}*/
+//
+///*.group-item .overlay button {*/
+///*    background: rgba(255, 255, 255, .9);*/
+///*    border: none;*/
+///*    border-radius: 50%;*/
+///*    width: 2rem;*/
+///*    height: 2rem;*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    justify-content: center;*/
+///*    cursor: pointer;*/
+///*    transition: all .2s ease;*/
+///*}*/
+//
+///*.group-item .set-featured {*/
+///*    color: var(--warning);*/
+///*}*/
+//
+///*.group-item .set-featured:hover {*/
+///*    background: var(--warning);*/
+///*    color: white;*/
+///*}*/
+//
+///*.group-item .remove-from-group:hover {*/
+///*    background: var(--danger);*/
+///*    color: white;*/
+///*}*/
+//
+///*!* Empty Group State *!*/
+///*.empty-group {*/
+///*    border: 4px dashed var(--base-200);*/
+///*    border-radius: var(--radius);*/
+///*    padding: 2rem;*/
+///*    text-align: center;*/
+///*    color: var(--text-muted);*/
+///*    aspect-ratio: 1;*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    justify-content: center;*/
+///*    cursor: pointer;*/
+///*    transition: all .2s ease;*/
+///*}*/
+//
+///*.empty-group:hover,*/
+///*.empty-group.dragover {*/
+///*    border-color: var(--action-50);*/
+///*    background: rgba(255, 0, 128, .05);*/
+///*    color: var(--action-50);*/
+///*}*/
+//
+///*!* Sidebar *!*/
+///*.sidebar {*/
+///*    background: var(--base-50);*/
+///*    border-radius: var(--radius-outer);*/
+///*    padding: 1.5rem;*/
+///*    min-height: 400px;*/
+///*}*/
+//
+///*.sidebar .header {*/
+///*    margin-bottom: 1.5rem;*/
+///*}*/
+//
+///*.sidebar h4 {*/
+///*    margin: 0 0 .5rem 0;*/
+///*    color: var(--text);*/
+///*}*/
+//
+///*.sidebar .hint {*/
+///*    font-size: .85rem;*/
+///*    color: var(--text-muted);*/
+///*    margin: .25rem 0;*/
+///*}*/
+//
+///*.new-group {*/
+///*    width: 100%;*/
+///*    background: var(--action-50);*/
+///*    color: var(--contrast);*/
+///*    border: none;*/
+///*    border-radius: var(--radius);*/
+///*    padding: .75rem;*/
+///*    margin-bottom: 1rem;*/
+///*    cursor: pointer;*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    justify-content: center;*/
+///*    gap: .5rem;*/
+///*    font-weight: 500;*/
+///*    transition: background-color .2s ease;*/
+///*}*/
+//
+///*.new-group:hover {*/
+///*    background: rgba(255, 0, 128, .8);*/
+///*}*/
+//
+///*!* Layout *!*/
+///*.group-display {*/
+///*    display: grid;*/
+///*    grid-template-columns: 2fr 1fr;*/
+///*    gap: 2rem;*/
+///*    margin-top: 2rem;*/
+///*}*/
+//
+///*.preview-wrap {*/
+///*    min-height: 400px;*/
+///*}*/
+//
+///*.preview-grid {*/
+///*    display: grid;*/
+///*    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));*/
+///*    gap: 1rem;*/
+///*    padding: 1rem;*/
+///*    background: var(--base-100);*/
+///*    border-radius: var(--radius-outer);*/
+///*    min-height: 200px;*/
+///*}*/
+//
+///*.preview-grid:empty::after {*/
+///*    content: "No images uploaded yet. Drag files here or click to upload.";*/
+///*    grid-column: 1 / -1;*/
+///*    text-align: center;*/
+///*    color: var(--text-muted);*/
+///*    padding: 2rem;*/
+///*    border: 2px dashed var(--base-300);*/
+///*    border-radius: var(--radius);*/
+///*}*/
+//
+///*!* File Upload Container *!*/
+///*.file-upload-container.dragover {*/
+///*    border-color: var(--action-50);*/
+///*    background: rgba(255, 0, 128, .05);*/
+///*}*/
+//
+///*!* Responsive Design *!*/
+///*@media (max-width: 768px) {*/
+///*    .group-display {*/
+///*        grid-template-columns: 1fr;*/
+///*        gap: 1rem;*/
+///*    }*/
+//
+///*    .preview-actions {*/
+///*        flex-direction: column;*/
+///*        align-items: stretch;*/
+///*        gap: 1rem;*/
+///*    }*/
+//
+///*    .selection-controls {*/
+///*        justify-content: space-between;*/
+///*    }*/
+//
+///*    .preview-grid {*/
+///*        grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));*/
+///*    }*/
+//
+///*    .group-items {*/
+///*        grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));*/
+///*    }*/
+///*}*/
+//
+///*!* Animation for upload progress *!*/
+///*@keyframes uploadProgress {*/
+///*    0% { transform: translateX(-100%); }*/
+///*    100% { transform: translateX(100%); }*/
+///*}*/
+//
+///*.upload-item[data-status="uploading"] .progress .bar::after {*/
+///*    content: '';*/
+///*    position: absolute;*/
+///*    top: 0;*/
+///*    left: 0;*/
+///*    height: 100%;*/
+///*    width: 20%;*/
+///*    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .5), transparent);*/
+///*    animation: uploadProgress 1.5s infinite;*/
+///*}*/
+//
+///*!* Accessibility *!*/
+///*.upload-item:focus-within,*/
+///*.group-item:focus-within,*/
+///*.upload-group:focus-within {*/
+///*    outline: 2px solid var(--action-50);*/
+///*    outline-offset: 2px;*/
+///*}*/
+//
+///*!* High contrast mode support *!*/
+///*@media (prefers-contrast: high) {*/
+///*    .upload-item.selected {*/
+///*        border-width: 3px;*/
+///*    }*/
+//
+///*    .group-drop-zone.dragover {*/
+///*        border-width: 3px;*/
+///*    }*/
+///*}*/
+///*.restore-notification {*/
+///*    background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);*/
+///*    border: 1px solid #ffc107;*/
+///*    border-left: 4px solid #ff6b35;*/
+///*    border-radius: var(--radius-outer);*/
+///*    padding: 1.5rem;*/
+///*    margin-bottom: 1.5rem;*/
+///*    box-shadow: 0 4px 12px rgba(255, 107, 53, .15);*/
+///*    animation: slideInFromTop .4s ease-out;*/
+///*    transition: opacity .3s ease, transform .3s ease;*/
+///*}*/
+//
+///*@keyframes slideInFromTop {*/
+///*    from {*/
+///*        transform: translateY(-20px);*/
+///*        opacity: 0;*/
+///*    }*/
+///*    to {*/
+///*        transform: translateY(0);*/
+///*        opacity: 1;*/
+///*    }*/
+///*}*/
+//
+///*.restore-content {*/
+///*    display: grid;*/
+///*    grid-template-columns: auto 1fr auto;*/
+///*    gap: 1rem;*/
+///*    align-items: start;*/
+///*}*/
+//
+///*.restore-icon {*/
+///*    font-size: 1.5rem;*/
+///*    color: #ff6b35;*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    justify-content: center;*/
+///*    width: 2.5rem;*/
+///*    height: 2.5rem;*/
+///*    background: rgba(255, 107, 53, .1);*/
+///*    border-radius: 50%;*/
+///*    flex-shrink: 0;*/
+///*}*/
+//
+///*.restore-message {*/
+///*    min-width: 0; !* Allow text to wrap *!*/
+///*}*/
+//
+///*.restore-message h4 {*/
+///*    margin: 0 0 .5rem 0;*/
+///*    color: #d63384;*/
+///*    font-size: 1.1rem;*/
+///*    font-weight: 600;*/
+///*}*/
+//
+///*.restore-message p {*/
+///*    margin: 0 0 .5rem 0;*/
+///*    color: #6c5419;*/
+///*    line-height: 1.4;*/
+///*}*/
+//
+///*.restore-message p:last-child {*/
+///*    margin-bottom: 0;*/
+///*}*/
+//
+///*.restore-message .warning {*/
+///*    background: rgba(220, 53, 69, .1);*/
+///*    border: 1px solid rgba(220, 53, 69, .2);*/
+///*    border-radius: var(--radius);*/
+///*    padding: .5rem .75rem;*/
+///*    margin-top: .75rem;*/
+///*    font-size: .9rem;*/
+///*}*/
+//
+///*.restore-message .warning strong {*/
+///*    color: #dc3545;*/
+///*}*/
+//
+///*.restore-actions {*/
+///*    display: flex;*/
+///*    flex-direction: column;*/
+///*    gap: .5rem;*/
+///*    flex-shrink: 0;*/
+///*}*/
+//
+///*.start-over-btn {*/
+///*    background: #dc3545;*/
+///*    color: white;*/
+///*    border: none;*/
+///*    border-radius: var(--radius);*/
+///*    padding: .5rem 1rem;*/
+///*    font-size: .9rem;*/
+///*    font-weight: 500;*/
+///*    cursor: pointer;*/
+///*    transition: all .2s ease;*/
+///*    white-space: nowrap;*/
+///*}*/
+//
+///*.start-over-btn:hover {*/
+///*    background: #c82333;*/
+///*    transform: translateY(-1px);*/
+///*    box-shadow: 0 2px 4px rgba(220, 53, 69, .3);*/
+///*}*/
+//
+///*.start-over-btn:active {*/
+///*    transform: translateY(0);*/
+///*}*/
+//
+///*.dismiss-notification {*/
+///*    background: transparent;*/
+///*    border: 1px solid #6c5419;*/
+///*    color: #6c5419;*/
+///*    border-radius: var(--radius);*/
+///*    padding: .5rem;*/
+///*    cursor: pointer;*/
+///*    transition: all .2s ease;*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    justify-content: center;*/
+///*    width: 2rem;*/
+///*    height: 2rem;*/
+///*}*/
+//
+///*.dismiss-notification:hover {*/
+///*    background: #6c5419;*/
+///*    color: white;*/
+///*}*/
+//
+///*!* Start Over Confirmation Dialog *!*/
+///*.start-over-confirmation {*/
+///*    border: none;*/
+///*    border-radius: var(--radius-outer);*/
+///*    padding: 0;*/
+///*    box-shadow: 0 10px 30px rgba(0, 0, 0, .3);*/
+///*    max-width: 500px;*/
+///*    width: 90vw;*/
+///*}*/
+//
+///*.start-over-confirmation::backdrop {*/
+///*    background: rgba(0, 0, 0, .6);*/
+///*    backdrop-filter: blur(4px);*/
+///*}*/
+//
+///*.confirmation-content {*/
+///*    padding: 2rem;*/
+///*    background: var(--base);*/
+///*    border-radius: var(--radius-outer);*/
+///*}*/
+//
+///*.confirmation-content h3 {*/
+///*    margin: 0 0 1rem 0;*/
+///*    color: #dc3545;*/
+///*    font-size: 1.25rem;*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    gap: .5rem;*/
+///*}*/
+//
+///*.confirmation-content h3::before {*/
+///*    content: "⚠️";*/
+///*    font-size: 1.5rem;*/
+///*}*/
+//
+///*.confirmation-content p {*/
+///*    margin: 0 0 1rem 0;*/
+///*    color: var(--text);*/
+///*    line-height: 1.5;*/
+///*}*/
+//
+///*.confirmation-content ul {*/
+///*    margin: 0 0 1rem 1rem;*/
+///*    color: var(--text);*/
+///*}*/
+//
+///*.confirmation-content li {*/
+///*    margin-bottom: .25rem;*/
+///*}*/
+//
+///*.confirmation-actions {*/
+///*    display: flex;*/
+///*    gap: 1rem;*/
+///*    justify-content: flex-end;*/
+///*    margin-top: 1.5rem;*/
+///*    padding-top: 1rem;*/
+///*    border-top: 1px solid var(--base-200);*/
+///*}*/
+//
+///*.confirm-start-over {*/
+///*    background: #dc3545;*/
+///*    color: white;*/
+///*    border: none;*/
+///*    border-radius: var(--radius);*/
+///*    padding: .75rem 1.5rem;*/
+///*    font-weight: 500;*/
+///*    cursor: pointer;*/
+///*    transition: all .2s ease;*/
+///*}*/
+//
+///*.confirm-start-over:hover {*/
+///*    background: #c82333;*/
+///*}*/
+//
+///*.cancel-start-over {*/
+///*    background: var(--base-100);*/
+///*    color: var(--text);*/
+///*    border: 1px solid var(--base-300);*/
+///*    border-radius: var(--radius);*/
+///*    padding: .75rem 1.5rem;*/
+///*    cursor: pointer;*/
+///*    transition: all .2s ease;*/
+///*}*/
+//
+///*.cancel-start-over:hover {*/
+///*    background: var(--base-200);*/
+///*}*/
+//
+///*!* Responsive Design *!*/
+///*@media (max-width: 768px) {*/
+///*    .restore-content {*/
+///*        grid-template-columns: 1fr;*/
+///*        gap: 1rem;*/
+///*        text-align: center;*/
+///*    }*/
+//
+///*    .restore-icon {*/
+///*        margin: 0 auto;*/
+///*    }*/
+//
+///*    .restore-actions {*/
+///*        flex-direction: row;*/
+///*        justify-content: center;*/
+///*        align-items: center;*/
+///*    }*/
+//
+///*    .start-over-btn {*/
+///*        order: 2;*/
+///*    }*/
+//
+///*    .dismiss-notification {*/
+///*        order: 1;*/
+///*    }*/
+//
+///*    .confirmation-actions {*/
+///*        flex-direction: column-reverse;*/
+///*    }*/
+//
+///*    .confirmation-actions button {*/
+///*        width: 100%;*/
+///*    }*/
+///*}*/
+//
+///*!* Reduced motion preference *!*/
+///*@media (prefers-reduced-motion: reduce) {*/
+///*    .restore-notification {*/
+///*        animation: none;*/
+///*    }*/
+//
+///*    .start-over-btn:hover {*/
+///*        transform: none;*/
+///*    }*/
+///*}*/
+//
+///*!* High contrast mode *!*/
+///*@media (prefers-contrast: high) {*/
+///*    .restore-notification {*/
+///*        border-width: 2px;*/
+///*        border-left-width: 6px;*/
+///*    }*/
+//
+///*    .restore-message .warning {*/
+///*        border-width: 2px;*/
+///*    }*/
+///*}*/
+//
+///*!* Dark mode adjustments *!*/
+///*@media (prefers-color-scheme: dark) {*/
+///*    .restore-notification {*/
+///*        background: linear-gradient(135deg, #3d3d00 0%, #4a4a00 100%);*/
+///*        border-color: #ffc107;*/
+///*        color: #fff3cd;*/
+///*    }*/
+//
+///*    .restore-message p {*/
+///*        color: #fff3cd;*/
+///*    }*/
+//
+///*    .restore-message .warning {*/
+///*        background: rgba(220, 53, 69, .2);*/
+///*        border-color: rgba(220, 53, 69, .4);*/
+///*        color: #ffb3ba;*/
+///*    }*/
+//
+///*    .dismiss-notification {*/
+///*        border-color: #fff3cd;*/
+///*        color: #fff3cd;*/
+///*    }*/
+//
+///*    .dismiss-notification:hover {*/
+///*        background: #fff3cd;*/
+///*        color: #3d3d00;*/
+///*    }*/
+///*}*/
+//
+///*.dragover {*/
+///*    background-color: rgba(var(--primary-rgb), .1);*/
+///*    border: 2px dashed var(--primary-color);*/
+///*    border-radius: 8px;*/
+///*}*/
+//
+///*.upload-item.dragging {*/
+///*    opacity: .5;*/
+///*    transform: scale(.95);*/
+///*    transition: all .2s ease;*/
+///*}*/
+//
+///*.group-drop-zone {*/
+///*    min-height: 60px;*/
+///*    border: 2px dashed #ccc;*/
+///*    border-radius: 8px;*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    justify-content: center;*/
+///*    color: #666;*/
+///*    transition: all .2s ease;*/
+///*}*/
+//
+///*.group-drop-zone.dragover {*/
+///*    border-color: var(--primary-color);*/
+///*    background-color: rgba(var(--primary-rgb), .05);*/
+///*    color: var(--primary-color);*/
+///*}*/
+//
+///*.empty-group {*/
+///*    padding: 20px;*/
+///*    text-align: center;*/
+///*    border: 2px dashed #ddd;*/
+///*    border-radius: 8px;*/
+///*    margin: 10px 0;*/
+///*    cursor: pointer;*/
+///*    transition: all .2s ease;*/
+///*}*/
+//
+///*.empty-group:hover {*/
+///*    border-color: var(--primary-color);*/
+///*    background-color: rgba(var(--primary-rgb), .02);*/
+///*}*/
+//
+///*.group-item.featured {*/
+///*    border: 2px solid gold;*/
+///*    position: relative;*/
+///*}*/
+//
+///*.group-item.featured::after {*/
+///*    content: "⭐";*/
+///*    position: absolute;*/
+///*    top: 5px;*/
+///*    right: 5px;*/
+///*    background: gold;*/
+///*    border-radius: 50%;*/
+///*    width: 20px;*/
+///*    height: 20px;*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    justify-content: center;*/
+///*    font-size: 12px;*/
+///*}*/
+//
+///*.upload-item[draggable="true"] {*/
+///*    cursor: grab;*/
+///*}*/
+//
+///*.upload-item[draggable="true"]:active {*/
+///*    cursor: grabbing;*/
+///*}*/
+//
+///*.selection-actions {*/
+///*    display: flex;*/
+///*    align-items: center;*/
+///*    gap: 10px;*/
+///*    padding: 10px;*/
+///*    background: rgba(var(--primary-rgb), .05);*/
+///*    border-radius: 6px;*/
+///*    border: 1px solid rgba(var(--primary-rgb), .2);*/
+///*}*/
+//
+///*.group-display .sidebar {*/
+///*    min-width: 300px;*/
+///*    max-width: 350px;*/
+///*    background: #f9f9f9;*/
+///*    border-left: 1px solid #eee;*/
+///*    padding: 20px;*/
+///*}*/
+//
+///*.group-display .preview-wrap {*/
+///*    flex: 1;*/
+///*    padding: 20px;*/
+///*}*/
+//
+///*.group-display {*/
+///*    display: flex;*/
+///*    gap: 20px;*/
+///*    margin-top: 20px;*/
+///*    border: 1px solid #ddd;*/
+///*    border-radius: 8px;*/
+///*    overflow: hidden;*/
+///*}*/
+//
+///* Touch Drag & Drop Styles */
+//
+///* Original item being dragged */
+//.upload-item.dragging {
+//	opacity: .5;
+//	transform: scale(.95);
+//	transition: opacity .2s ease, transform .2s ease;
+//}
+//
+///* Touch drag preview element */
+//.drag-preview {
+//	background: var(--bg-elevated);
+//	border: 2px solid var(--accent-primary);
+//	cursor: grabbing;
+//	user-select: none;
+//	-webkit-user-select: none;
+//	-webkit-user-drag: none;
+//}
+//
+//.drag-preview * {
+//	pointer-events: none;
+//}
+//
+///* Drop target states for touch */
+//.dragover {
+//	background-color: var(--accent-primary-alpha);
+//	border: 2px dashed var(--accent-primary);
+//	transform: scale(1.02);
+//	transition: all .2s ease;
+//}
+//
+//.dragover::after {
+//	content: "Drop here";
+//	position: absolute;
+//	top: 50%;
+//	left: 50%;
+//	transform: translate(-50%, -50%);
+//	background: var(--accent-primary);
+//	color: white;
+//	padding: 8px 16px;
+//	border-radius: 4px;
+//	font-size: 14px;
+//	font-weight: 500;
+//	z-index: 1;
+//	pointer-events: none;
+//}
+//
+///* Enhanced touch targets for mobile */
+//@media (hover: none) and (pointer: coarse) {
+//	.upload-item {
+//		min-height: 44px; /* Apple's recommended minimum touch target */
+//		touch-action: manipulation;
+//	}
+//
+//	.upload-item [data-upload-id] {
+//		cursor: grab;
+//		-webkit-user-select: none;
+//		user-select: none;
+//	}
+//
+//	.upload-item.dragging {
+//		cursor: grabbing;
+//	}
+//
+//	/* Larger touch targets for action buttons */
+//	.upload-item .actions button {
+//		min-width: 44px;
+//		min-height: 44px;
+//		padding: 12px;
+//	}
+//
+//	/* Group containers more touch-friendly */
+//	.item-grid.groups .empty-group {
+//		padding: 20px;
+//		display: flex;
+//		align-items: center;
+//		justify-content: center;
+//		font-size: 16px;
+//	}
+//
+//	.group-content {
+//		padding: 0;
+//		width: 100%;
+//	}
+//
+//	/* Better visual feedback for group areas */
+//	.item-grid.group,
+//	.item-grid.groups {
+//		border: 2px solid transparent;
+//		border-radius: 8px;
+//		transition: border-color .2s ease, background-color .2s ease;
+//	}
+//
+//	.item-grid.group.dragover,
+//	.item-grid.groups.dragover {
+//		border-color: var(--accent-primary);
+//		background-color: var(--accent-primary-alpha);
+//	}
+//}
+//
+///* Haptic feedback animation */
+//@keyframes feedback {
+//	0% { transform: scale(1); }
+//	50% { transform: scale(1.05); }
+//	100% { transform: scale(1); }
+//}
+//
+//.feedback {
+//	animation: feedback .2s ease;
+//}
+//
+///* Prevent text selection during touch drag */
+//.dragging,
+//.dragging * {
+//	-webkit-user-select: none;
+//	-moz-user-select: none;
+//	-ms-user-select: none;
+//	user-select: none;
+//}
+//
+///* Long press visual feedback */
+//.upload-item:active {
+//	transform: scale(.98);
+//	transition: transform .1s ease;
+//}
+//
+///* Better spacing for touch interfaces */
+//@media (hover: none) {
+//
+//	.item-grid.groups {
+//		gap: 16px;
+//	}
+//
+//	.group-item {
+//		margin: 8px;
+//	}
+//}
+//
+///* Accessibility improvements for touch */
+//.upload-item:focus-visible {
+//	outline: 3px solid var(--accent-primary);
+//	outline-offset: 2px;
+//}
+//
+///* High contrast mode support */
+//@media (prefers-contrast: high) {
+//	.dragover {
+//		border-width: 3px;
+//		border-style: solid;
+//	}
+//
+//	.drag-preview {
+//		border-width: 3px;
+//	}
+//}
+///* Touch Drag & Drop Styles */
+//
+///* Original item being dragged */
+//.upload-item.dragging {
+//	opacity: .5;
+//	transform: scale(.95);
+//	transition: opacity .2s ease, transform .2s ease;
+//}
+//
+///* Touch drag preview element */
+//.drag-preview {
+//	background: var(--bg-elevated);
+//	border: 2px solid var(--accent-primary);
+//	cursor: grabbing;
+//	user-select: none;
+//	-webkit-user-select: none;
+//	-webkit-user-drag: none;
+//}
+//
+//.drag-preview * {
+//	pointer-events: none;
+//}
+//
+///* Multi-item drag preview */
+//.drag-preview.multi-item {
+//	/* Container styles handled in JS for dynamic positioning */
+//}
+//.drag-preview img {
+//	max-width: 200px;
+//	width: 100%;
+//	height: 100%;
+//	aspect-ratio: 1;
+//}
+//
+//.drag-preview.multi-item .selection-count-badge {
+//	background: var(--accent-primary);
+//	color: white;
+//	border: 2px solid white;
+//	font-weight: bold;
+//	text-shadow: 0 1px 2px rgba(0,0,0,.5);
+//}
+//
+///* Enhanced visual feedback for multiple selection */
+//.upload-item.selected.dragging {
+//	opacity: .3;
+//	transform: scale(.9);
+//	border: 2px solid var(--accent-primary);
+//}
+//
+///* Stacked preview animation */
+//@keyframes stack-preview {
+//	0% {
+//		transform: translateX(0) translateY(0) rotate(0deg) scale(1);
+//		opacity: 1;
+//	}
+//	100% {
+//		transform: translateX(var(--stack-x, 0)) translateY(var(--stack-y, 0)) rotate(var(--stack-rotation, 0deg)) scale(1.05);
+//		opacity: var(--stack-opacity, .8);
+//	}
+//}
+//
+//.drag-preview.multi-item > * {
+//	animation: stack-preview .2s ease-out forwards;
+//}
+//
+///* Drop target states for touch */
+//.dragover {
+//	background-color: var(--accent-primary-alpha);
+//	border: 2px dashed var(--accent-primary);
+//	transform: scale(1.02);
+//	transition: all .2s ease;
+//}
+//
+//.dragover::after {
+//	content: "Drop here";
+//	position: absolute;
+//	top: 50%;
+//	left: 50%;
+//	transform: translate(-50%, -50%);
+//	background: var(--accent-primary);
+//	color: white;
+//	padding: 8px 16px;
+//	border-radius: 4px;
+//	font-size: 14px;
+//	font-weight: 500;
+//	z-index: 1;
+//	pointer-events: none;
+//}
+//
+///* Enhanced drop feedback for multiple items */
+//.dragover.multi-drop::after {
+//	content: "Drop " attr(data-item-count) " items here";
+//	padding: 10px 20px;
+//	font-size: 16px;
+//	border-radius: 6px;
+//	box-shadow: 0 4px 12px rgba(0,0,0,.3);
+//}
+//
+///* Pulsing animation for multi-item drop zones */
+//.dragover.multi-drop {
+//	animation: multi-drop-pulse 1s infinite alternate;
+//}
+//
+//@keyframes multi-drop-pulse {
+//	0% {
+//		background-color: var(--accent-primary-alpha);
+//		transform: scale(1.02);
+//	}
+//	100% {
+//		background-color: var(--accent-secondary-alpha, var(--accent-primary-alpha));
+//		transform: scale(1.04);
+//	}
+//}
+//
+///* Enhanced touch targets for mobile */
+//@media (hover: none) and (pointer: coarse) {
+//	.upload-item {
+//		min-height: 44px; /* Apple's recommended minimum touch target */
+//		touch-action: manipulation;
+//	}
+//
+//	.upload-item [data-upload-id] {
+//		cursor: grab;
+//		-webkit-user-select: none;
+//		user-select: none;
+//	}
+//
+//	.upload-item.dragging {
+//		cursor: grabbing;
+//	}
+//
+//	/* Larger touch targets for action buttons */
+//	.upload-item .actions button {
+//		min-width: 44px;
+//		min-height: 44px;
+//		padding: 12px;
+//	}
+//
+//	/* Group containers more touch-friendly */
+//	.item-grid.groups .empty-group {
+//		padding: 1rem;
+//		margin: 1rem;
+//		display: flex;
+//		align-items: center;
+//		justify-content: center;
+//		font-size: 16px;
+//	}
+//
+//	/* Better visual feedback for group areas */
+//	.item-grid.group,
+//	.item-grid.groups {
+//		border: 2px solid transparent;
+//		border-radius: 8px;
+//		transition: border-color .2s ease, background-color .2s ease;
+//	}
+//
+//	.item-grid.group.dragover,
+//	.item-grid.groups.dragover {
+//		border-color: var(--accent-primary);
+//		background-color: var(--accent-primary-alpha);
+//	}
+//}
+//
+///* Haptic feedback animation */
+//@keyframes feedback {
+//	0% { transform: scale(1); }
+//	50% { transform: scale(1.05); }
+//	100% { transform: scale(1); }
+//}
+//
+//.feedback {
+//	animation: feedback .2s ease;
+//}
+//
+///* Prevent text selection during touch drag */
+//.dragging,
+//.dragging * {
+//	-webkit-user-select: none;
+//	-moz-user-select: none;
+//	-ms-user-select: none;
+//	user-select: none;
+//}
+//
+///* Long press visual feedback */
+//.upload-item:active {
+//	transform: scale(.98);
+//	transition: transform .1s ease;
+//}
+//
+///* Better spacing for touch interfaces */
+//@media (hover: none) {
+//
+//	.item-grid.groups {
+//		gap: 16px;
+//	}
+//
+//	.group-item {
+//		margin: 8px;
+//	}
+//}
+//
+///* Accessibility improvements for touch */
+//.upload-item:focus-visible {
+//	outline: 3px solid var(--accent-primary);
+//	outline-offset: 2px;
+//}
+//
+///* Enhanced selection states for touch */
+//.upload-item.selected {
+//	border: 2px solid var(--accent-primary);
+//	background-color: var(--accent-primary-alpha);
+//}
+//
+//.upload-item.selected.dragging {
+//	border-color: var(--accent-secondary);
+//	background-color: var(--accent-secondary-alpha);
+//}
+//
+///* Touch feedback animations */
+//.feedback-start {
+//	animation: feedback-start .3s ease;
+//}
+//
+//.feedback-success {
+//	animation: feedback-success .5s ease;
+//}
+//
+//.feedback-error {
+//	animation: feedback-error .5s ease;
+//}
+//
+//@keyframes feedback-start {
+//	0% { transform: scale(1); }
+//	50% { transform: scale(1.05); background-color: var(--accent-primary-alpha); }
+//	100% { transform: scale(1); }
+//}
+//
+//@keyframes feedback-success {
+//	0% { transform: scale(1); }
+//	25% { transform: scale(1.1); background-color: var(--success-alpha); }
+//	50% { transform: scale(1.05); }
+//	100% { transform: scale(1); }
+//}
+//
+//@keyframes feedback-error {
+//	0% { transform: translateX(0); }
+//	25% { transform: translateX(-5px); background-color: var(--error-alpha); }
+//	50% { transform: translateX(5px); }
+//	75% { transform: translateX(-3px); }
+//	100% { transform: translateX(0); }
+//}
+//
+///* Multi-selection visual enhancements */
+//.upload-item.selected::after {
+//	content: "✓";
+//	position: absolute;
+//	top: 8px;
+//	right: 8px;
+//	background: var(--accent-primary);
+//	color: white;
+//	border-radius: 50%;
+//	width: 20px;
+//	height: 20px;
+//	display: flex;
+//	align-items: center;
+//	justify-content: center;
+//	font-size: 12px;
+//	font-weight: bold;
+//	z-index: 10;
+//}
+//
+///* Selection count in drag preview */
+//.selection-count-badge {
+//	animation: badge-appear .3s ease;
+//}
+//
+//@keyframes badge-appear {
+//	0% {
+//		transform: scale(0);
+//		opacity: 0;
+//	}
+//	50% {
+//		transform: scale(1.2);
+//		opacity: 1;
+//	}
+//	100% {
+//		transform: scale(1);
+//		opacity: 1;
+//	}
+//}
+//
+///* High contrast mode support */
+//@media (prefers-contrast: high) {
+//	.dragover {
+//		border-width: 3px;
+//		border-style: solid;
+//	}
+//
+//	.drag-preview {
+//		border-width: 3px;
+//	}
+//
+//	.upload-item.selected {
+//		border-width: 3px;
+//	}
+//}
+//
+//.drag-preview.multi-item {
+//	position: relative;
+//	width: 120px !important;
+//	height: 120px !important;
+//	/* Ensure consistent sizing for multi-item previews */
+//}
+//
+//.drag-preview.multi-item img {
+//	max-width: 100%;
+//	max-height: 100%;
+//	width: 100%;
+//	height: 100%;
+//	object-fit: cover;
+//	aspect-ratio: 1;
+//	border-radius: 6px;
+//}
+//
+///* Fix stacked item positioning */
+//.drag-preview.multi-item > .upload-item {
+//	position: absolute;
+//	width: 100%;
+//	height: 100%;
+//	border-radius: 6px;
+//	overflow: hidden;
+//}
+//
+//.drag-preview.multi-item > .upload-item:nth-child(1) {
+//	top: 0;
+//	left: 0;
+//	z-index: 3;
+//	opacity: 1;
+//	transform: rotate(-2deg);
+//}
+//
+//.drag-preview.multi-item > .upload-item:nth-child(2) {
+//	top: 4px;
+//	left: 4px;
+//	z-index: 2;
+//	opacity: .85;
+//	transform: rotate(1deg);
+//}
+//
+//.drag-preview.multi-item > .upload-item:nth-child(3) {
+//	top: 8px;
+//	left: 8px;
+//	z-index: 1;
+//	opacity: .7;
+//	transform: rotate(-1deg);
+//}
+//
+///* Selection count badge positioning */
+//.drag-preview.multi-item .selection-count-badge {
+//	position: absolute;
+//	top: -8px;
+//	right: -8px;
+//	background: var(--accent-primary);
+//	color: white;
+//	border: 2px solid white;
+//	border-radius: 50%;
+//	width: 28px;
+//	height: 28px;
+//	display: flex;
+//	align-items: center;
+//	justify-content: center;
+//	font-size: 12px;
+//	font-weight: bold;
+//	font-family: system-ui, -apple-system, sans-serif;
+//	box-shadow: 0 2px 8px rgba(0,0,0,.3);
+//	z-index: 20;
+//	line-height: 1;
+//}
+//
+///* Ensure drag preview is always visible */
+//.drag-preview {
+//	pointer-events: none;
+//	z-index: 10000 !important;
+//}
+//
+///* Remove any transforms that might interfere with positioning */
+//.drag-preview.multi-item .upload-item .progress,
+//.drag-preview.multi-item .upload-item .actions,
+//.drag-preview.multi-item .upload-item .status,
+//.drag-preview.multi-item .upload-item details {
+//	display: none !important;
+//}
+//
+///* Clean up the visual during multi-drag */
+//.upload-item.selected.dragging {
+//	opacity: .3;
+//	transform: scale(.95);
+//	border: 2px dashed var(--accent-primary);
+//	background-color: var(--accent-primary-alpha);
+//}
+//
+///* Improved drop zone feedback for multiple items */
+//.dragover.multi-drop {
+//	animation: multi-drop-pulse .8s infinite ease-in-out;
+//	border-style: solid;
+//}
+//
+//@keyframes multi-drop-pulse {
+//	0%, 100% {
+//		background-color: var(--accent-primary-alpha);
+//		transform: scale(1.02);
+//		border-color: var(--accent-primary);
+//	}
+//	50% {
+//		background-color: var(--accent-secondary-alpha, var(--accent-primary-alpha));
+//		transform: scale(1.04);
+//		border-color: var(--accent-secondary, var(--accent-primary));
+//	}
+//}
+//
+///* Enhanced feedback for successful drops */
+//@keyframes drop-success {
+//	0% {
+//		background-color: var(--success-alpha);
+//		transform: scale(1.1);
+//	}
+//	100% {
+//		background-color: transparent;
+//		transform: scale(1);
+//	}
+//}
+//
+//.drop-success {
+//	animation: drop-success .6s ease-out;
+//}
+//
+//.drag-preview {
+//	border: 2px solid red !important;
+//	background: rgba(255, 0, 0, .1) !important;
+//}
+//
+//.drag-preview.multi-item {
+//	border: 2px solid blue !important;
+//	background: rgba(0, 0, 255, .1) !important;
+//}
+//
+//.dragging {
+//	opacity: .5 !important;
+//	border: 2px dashed orange !important;
+//}
+//
+//.dragover {
+//	background: rgba(0, 255, 0, .2) !important;
+//	border: 2px dashed green !important;
+//}
+//
+//.dragover.multi-drop::before {
+//	content: "Drop " attr(data-item-count) " items here";
+//	position: absolute;
+//	top: 50%;
+//	left: 50%;
+//	transform: translate(-50%, -50%);
+//	background: rgba(0, 0, 0, .8);
+//	color: white;
+//	padding: 8px 16px;
+//	border-radius: 4px;
+//	font-size: 14px;
+//	font-weight: bold;
+//	z-index: 1000;
+//	pointer-events: none;
+//}
+//
+//.drag-preview {
+//	/* Ensure drag preview is always visible */
+//	position: fixed !important;
+//	z-index: 9999 !important;
+//	pointer-events: none !important;
+//
+//	/* Improve visual appearance */
+//	box-shadow: 0 8px 25px rgba(0,0,0,.3);
+//	border-radius: 8px;
+//	overflow: hidden;
+//
+//	/* Ensure it's not affected by parent transforms or overflow */
+//	transform-style: preserve-3d;
+//}
+//
+//.drag-preview.multi-item {
+//	/* Ensure proper stacking context for multi-item previews */
+//	isolation: isolate;
+//}
+//
+//.drag-preview .upload-item {
+//	/* Ensure child items are positioned correctly */
+//	border-radius: 4px;
+//	overflow: hidden;
+//	box-shadow: 0 2px 8px rgba(0,0,0,.15);
+//}
+//
+//.drag-preview .upload-item img {
+//	/* Prevent image layout issues during drag */
+//	max-width: 100%;
+//	height: auto;
+//	display: block;
+//}
+//
+///* Debug styles - remove in production */
+//.drag-preview {
+//	border: 2px solid red !important;
+//}
+//
+//.drag-preview.multi-item {
+//	border: 2px solid blue !important;
+//}
+//
+///* Hide complex UI elements in drag preview */
+//.drag-preview details,
+//.drag-preview .actions,
+//.drag-preview .progress {
+//	display: none !important;
+//}
+//
+///* Simplify the preview to just show the image */
+//.drag-preview .preview {
+//	width: 100% !important;
+//	height: 100% !important;
+//}
+//
+//.drag-preview .preview img {
+//	width: 100% !important;
+//	height: 100% !important;
+//	object-fit: cover !important;
+//}
+//
+//
+//.upload-group summary {
+//	padding: 0!important;
+//}
+//
+//
+///* Mobile-First Image Grouping Layout - Using Existing Templates */
+//
+///* Base mobile layout - stack everything vertically */
+//@media (max-width: 768px) {
+//
+//	.item-grid.preview details,
+//	.item-grid.group details {
+//		display: none;
+//	}
+//	.group-display {
+//		display: flex;
+//		flex-direction: column;
+//		gap: 0;
+//		height: calc(100vh - var(--btn) - var(--btn));
+//		position: fixed;
+//		top: var(--btn);
+//		bottom: var(--btn);
+//		left: 0;
+//		right: 0;
+//		z-index:999;
+//	}
+//
+//	.preview-wrap,
+//	.sidebar {
+//		flex: 1;
+//		display: flex;
+//		flex-direction: column;
+//		min-height: 0;
+//		overflow: hidden;
+//		overflow-y: auto;
+//	}
+//	/* Preview section - top half of screen */
+//	.preview-wrap {
+//		background: var(--base);
+//		border-bottom: 2px solid var(--action-0);
+//	}
+//
+//	/* Preview actions - sticky at top */
+//	.preview-actions {
+//		position: sticky;
+//		top: 0;
+//		z-index: 10;
+//		background: var(--base);
+//		border-bottom: 1px solid var(--base-200);
+//		flex-shrink: 0;
+//	}
+//
+//	/* Preview grid - scrollable area */
+//	.item-grid.preview {
+//		flex: 1;
+//		grid-template-columns: repeat(3, 1fr);
+//		gap: .75rem;
+//		padding: .5rem;
+//		margin-top: 0;
+//	}
+//	.item-grid + .hint {
+//		margin-top: 0;
+//	}
+//
+//	/* Groups section - bottom half with sticky header */
+//	.sidebar {
+//		background: var(--base-200);
+//		border-top: 3px solid var(--action-50);
+//	}
+//
+//	/* Groups header - sticky */
+//	.sidebar .header {
+//		border-bottom: 1px solid var(--base-200);
+//		padding: 1rem;
+//		flex-shrink: 0;
+//	}
+//
+//	/* Create group button */
+//	.create-group-from-selection {
+//		margin: 0 1rem 1rem 1rem;
+//		flex-shrink: 0;
+//		min-height: 44px; /* Touch target */
+//		padding: .75rem 1rem;
+//		font-size: .9rem;
+//	}
+//
+//	/* Groups container - scrollable */
+//	.item-grid.groups {
+//		flex: 1;
+//		margin: 0;
+//		grid-template-columns: repeat(2, 1fr); /* Single column on mobile */
+//		gap: 1rem;
+//	}
+//
+//	/* Enhanced empty group for mobile */
+//	.empty-group {
+//		aspect-ratio: unset;
+//		grid-column: 1/-1;
+//		transition: all .2s ease;
+//	}
+//
+//	.item-grid.groups .item-grid {
+//		grid-template-columns: repeat(3, 1fr);
+//	}
+//	.item-grid.groups .item-grid details {
+//		display: none;
+//	}
+//
+//	.empty-group:hover,
+//	.empty-group.dragover {
+//		border-color: var(--action-0);
+//		background: var(--action-rgb-subtle-hover);
+//		transform: scale(1.02);
+//	}
+//
+//	/* Upload button - fixed at bottom */
+//	.submit-uploads {
+//		position: fixed !important;
+//		bottom: calc(var(--btn) + .5rem);
+//		right: .5rem;
+//		z-index: 20;
+//		height: 3rem;
+//		font-size: 1.1rem;
+//		font-weight: 600;
+//		box-shadow: rgba(var(--base-rgb),var(--op-45)) var(--shdw);
+//		border-radius: var(--radius-outer);
+//	}
+//
+//	.submit-uploads:hover {
+//		transform: translateY(-2px);
+//		box-shadow: 0 8px 25px rgba(0,0,0,.2);
+//	}
+//
+//	/* Enhanced upload items for mobile */
+//	.upload-item {
+//		border-radius: var(--radius);
+//		overflow: hidden;
+//		background: var(--base);
+//		border: 1px solid var(--base-200);
+//		transition: transform .2s ease;
+//	}
+//
+//	.upload-item:hover {
+//		transform: scale(1.02);
+//	}
+//
+//	/* Mobile-friendly actions overlay */
+//	.upload-item .actions {
+//		padding: .5rem;
+//	}
+//
+//	.upload-item .actions button {
+//		min-width: 44px;
+//		min-height: 44px;
+//		padding: .75rem;
+//		border-radius: var(--radius);
+//	}
+//
+//	/* Better checkbox targets */
+//	.upload-item .upload-select + label {
+//		min-width: 44px;
+//		min-height: 44px;
+//		display: flex;
+//		align-items: center;
+//		justify-content: center;
+//		border-radius: var(--radius);
+//	}
+//
+//	/* Enhanced group styling for mobile */
+//	.upload-group {
+//		background: var(--base-100);
+//		border-radius: var(--radius);
+//		border: 1px solid var(--base-200);
+//		padding: 1rem;
+//		margin-bottom: 1rem;
+//	}
+//
+//	.upload-group .group-header {
+//		display: flex;
+//		justify-content: space-between;
+//		align-items: center;
+//		margin-bottom: 1rem;
+//		padding-bottom: .5rem;
+//		border-bottom: 1px solid var(--base-200);
+//		flex-wrap: wrap;
+//		gap: .5rem;
+//	}
+//
+//	.upload-group .group-actions {
+//		display: flex;
+//		gap: .5rem;
+//		flex-wrap: wrap;
+//	}
+//
+//	.upload-group .group-actions button {
+//		flex: 1;
+//		min-width: 100px;
+//		min-height: 44px;
+//		padding: .5rem .75rem;
+//		font-size: .9rem;
+//		border-radius: var(--radius);
+//	}
+//
+//	.upload-group .item-grid.group {
+//		grid-template-columns: repeat(2, 1fr);
+//		gap: .5rem;
+//		margin-bottom: 1rem;
+//	}
+//
+//	.upload-group .group-item {
+//		aspect-ratio: 1;
+//		border-radius: 4px;
+//		overflow: hidden;
+//		border: 1px solid var(--base-200);
+//	}
+//
+//	/* Hide file upload container on mobile when in grouping mode */
+//	.group-display:not([hidden]) ~ .file-upload-container {
+//		display: none;
+//	}
+//
+//	/* Enhanced selection controls for mobile */
+//	.selection-controls {
+//		display: flex;
+//		flex-direction: column;
+//		gap: 0;
+//	}
+//
+//	.selection-controls .selected {
+//		display: flex;
+//		justify-content: space-between;
+//		align-items: center;
+//		gap: .5rem;
+//	}
+//
+//	.selection-actions {
+//		display: flex;
+//		padding: 0;
+//	}
+//
+//	.selection-actions button {
+//		flex: 1;
+//		padding: .25rem;
+//		font-size: .9rem;
+//		border-radius: var(--radius);
+//	}
+//
+//	/* Enhanced dragging states for mobile */
+//	.upload-item.dragging {
+//		opacity: .7;
+//		transform: scale(.95) rotate(3deg);
+//		z-index: 1000;
+//		box-shadow: 0 8px 25px rgba(0,0,0,.3);
+//	}
+//
+//	.dragover {
+//		background: var(--action-rgb-subtle) !important;
+//		border-color: var(--action-0) !important;
+//		transform: scale(1.05);
+//		animation: mobile-drop-pulse .8s infinite ease-in-out;
+//	}
+//
+//	@keyframes mobile-drop-pulse {
+//		0%, 100% {
+//			background-color: var(--action-rgb-subtle);
+//			transform: scale(1.02);
+//		}
+//		50% {
+//			background-color: var(--action-rgb-subtle-hover);
+//			transform: scale(1.04);
+//		}
+//	}
+//
+//	/* Enhanced selection states */
+//	.upload-item.selected {
+//		border: 2px solid var(--action-0);
+//		background-color: var(--action-rgb-subtle);
+//	}
+//
+//	.upload-item.selected::after {
+//		content: '✓';
+//		position: absolute;
+//		top: .5rem;
+//		right: .5rem;
+//		background: var(--action-0);
+//		color: white;
+//		border-radius: 50%;
+//		width: 24px;
+//		height: 24px;
+//		display: flex;
+//		align-items: center;
+//		justify-content: center;
+//		font-size: 12px;
+//		font-weight: bold;
+//		z-index: 10;
+//		animation: selection-pop .3s ease;
+//	}
+//
+//	@keyframes selection-pop {
+//		0% {
+//			transform: scale(0);
+//			opacity: 0;
+//		}
+//		70% {
+//			transform: scale(1.2);
+//			opacity: 1;
+//		}
+//		100% {
+//			transform: scale(1);
+//			opacity: 1;
+//		}
+//	}
+//
+//	/* Enhanced details/summary for mobile */
+//	.upload-item details summary {
+//		padding: .75rem;
+//		background: var(--base-100);
+//		border-radius: var(--radius);
+//		cursor: pointer;
+//		display: flex;
+//		align-items: center;
+//		gap: .5rem;
+//		font-size: .9rem;
+//		font-weight: 500;
+//		min-height: 44px; /* Touch target */
+//	}
+//
+//	.upload-item details[open] summary {
+//		border-radius: var(--radius) var(--radius) 0 0;
+//		border-bottom: 1px solid var(--base-200);
+//	}
+//
+//	/* Enhanced forms for mobile */
+//	.upload-meta input,
+//	.upload-meta textarea {
+//		padding: .75rem;
+//		font-size: 16px; /* Prevents zoom on iOS */
+//		border-radius: var(--radius);
+//		border: 2px solid var(--base-200);
+//		transition: border-color .2s ease;
+//	}
+//
+//	.upload-meta input:focus,
+//	.upload-meta textarea:focus {
+//		border-color: var(--action-0);
+//		outline: none;
+//		box-shadow: 0 0 0 3px var(--action-rgb-subtle);
+//	}
+//}
+//
+///* Tablet adjustments */
+//@media (min-width: 769px) and (max-width: 1024px) {
+//	.group-display {
+//		grid-template-columns: 1fr;
+//		gap: 1.5rem;
+//	}
+//
+//	.item-grid.preview {
+//		grid-template-columns: repeat(4, 1fr);
+//	}
+//
+//	.item-grid.groups {
+//		grid-template-columns: repeat(2, 1fr);
+//	}
+//}
+//
+//
+//body:has(.group-display:not([hidden])) {
+//	overflow:hidden;
+//}
+//
+//.today_hours .group-fields {
+//	width: 100%;
+//	display: flex;
+//	justify-content: center;
+//	gap: 3rem;
+//}
+//.today_hours .field {
+//	margin: 0;
+//}
+//
+//
+//.jvb-integration-connection {
+//	background: var(--bg-white, #EFEFEF);
+//	border: 2px solid #ddd;
+//	border-radius: 8px;
+//	padding: 1.5rem;
+//	margin-bottom: 1.5rem;
+//	position: relative;
+//	transition: all 0.3s ease;
+//}
+//
+//.jvb-integration-connection.connected {
+//	border-color: #22c55e;
+//	box-shadow: 0 2px 8px rgba(34, 197, 94, 0.1);
+//}
+//
+//.jvb-integration-connection.disconnected {
+//	border-color: #ef4444;
+//	box-shadow: 0 2px 8px rgba(239, 68, 68, 0.1);
+//}
+//
+///* Integration Grid */
+//.jvb-integrations-grid {
+//	display: grid;
+//	grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
+//	gap: 1.5rem;
+//	margin-top: 1rem;
+//}
+//
+///* Connection Header */
+//.jvb-connection-header {
+//	display: flex;
+//	justify-content: space-between;
+//	align-items: flex-start;
+//	margin-bottom: 1.5rem;
+//	padding-bottom: 1rem;
+//	border-bottom: 2px solid #ddd;
+//}
+//
+//.jvb-service-info h3.jvb-service-name {
+//	margin: 0 0 0.25rem 0;
+//	font-size: 1.4rem;
+//	font-weight: bold;
+//	text-transform: uppercase;
+//	letter-spacing: 1px;
+//	color: var(--bg-black, #1B1B1B);
+//	font-family: 'Courier New', monospace;
+//}
+//
+//.jvb-service-description {
+//	margin: 0;
+//	color: #666;
+//	font-size: 0.9rem;
+//	line-height: 1.4;
+//}
+//
+//.jvb-connection-status {
+//	text-align: right;
+//	min-width: 150px;
+//}
+//
+//.jvb-status-indicator {
+//	font-size: 1.2rem;
+//	margin-right: 0.5rem;
+//}
+//
+//.jvb-connection-status.connected .jvb-status-indicator {
+//	color: #22c55e;
+//}
+//
+//.jvb-connection-status.disconnected .jvb-status-indicator {
+//	color: #ef4444;
+//}
+//
+//.jvb-status-text {
+//	font-weight: bold;
+//	font-size: 0.9rem;
+//	text-transform: uppercase;
+//	letter-spacing: 0.5px;
+//	font-family: 'Courier New', monospace;
+//}
+//
+//.jvb-last-tested {
+//	display: block;
+//	color: #888;
+//	font-size: 0.75rem;
+//	margin-top: 0.25rem;
+//}
+//
+///* Settings Section */
+//.jvb-settings-section,
+//.jvb-integration-settings {
+//	margin-bottom: 1.5rem;
+//}
+//
+//.jvb-integration-settings h4 {
+//	margin: 0 0 1rem 0;
+//	font-size: 1.1rem;
+//	font-weight: bold;
+//	color: var(--bg-black, #1B1B1B);
+//	font-family: 'Courier New', monospace;
+//	text-transform: uppercase;
+//	letter-spacing: 0.5px;
+//}
+//
+///* Form Fields */
+//.jvb-form-field {
+//	margin-bottom: 1rem;
+//}
+//
+//.jvb-form-field label {
+//	display: block;
+//	font-weight: bold;
+//	margin-bottom: 0.5rem;
+//	color: var(--bg-black, #1B1B1B);
+//	font-family: 'Courier New', monospace;
+//	text-transform: uppercase;
+//	font-size: 0.85rem;
+//	letter-spacing: 0.5px;
+//}
+//
+//.jvb-form-field input,
+//.jvb-form-field select {
+//	width: 100%;
+//	padding: 0.75rem;
+//	border: 2px solid #ddd;
+//	border-radius: 4px;
+//	font-size: 0.9rem;
+//	background: white;
+//	transition: border-color 0.3s ease;
+//}
+//
+//.jvb-form-field input:focus,
+//.jvb-form-field select:focus {
+//	outline: none;
+//	border-color: var(--action-color, #FF0080);
+//	box-shadow: 0 0 0 3px rgba(255, 0, 128, 0.1);
+//}
+//
+//.jvb-form-field .description {
+//	font-size: 0.8rem;
+//	color: #666;
+//	margin-top: 0.5rem;
+//	line-height: 1.4;
+//}
+//
+///* Checkbox Labels */
+//.jvb-checkbox-label {
+//	display: flex !important;
+//	align-items: center;
+//	gap: 0.5rem;
+//	text-transform: none !important;
+//	font-weight: normal !important;
+//	cursor: pointer;
+//}
+//
+//.jvb-checkbox-label input[type="checkbox"] {
+//	width: auto !important;
+//	margin: 0;
+//}
+//
+///* Advanced Settings */
+//.jvb-advanced-settings {
+//	margin-top: 1rem;
+//	padding: 1rem;
+//	background: #f9f9f9;
+//	border-radius: 4px;
+//	border: 1px solid #e5e5e5;
+//}
+//
+//.jvb-advanced-settings summary {
+//	font-weight: bold;
+//	cursor: pointer;
+//	margin-bottom: 1rem;
+//	color: var(--action-color, #FF0080);
+//	font-family: 'Courier New', monospace;
+//	text-transform: uppercase;
+//	font-size: 0.85rem;
+//	letter-spacing: 0.5px;
+//}
+//
+///* Settings Help */
+//.jvb-settings-help {
+//	background: #f0f8ff;
+//	border: 1px solid #b3d9ff;
+//	border-radius: 4px;
+//	padding: 1rem;
+//	margin-top: 1rem;
+//}
+//
+//.jvb-settings-help ol {
+//	margin: 0.5rem 0 0 1.5rem;
+//	padding: 0;
+//}
+//
+//.jvb-settings-help a {
+//	color: var(--action-color, #FF0080);
+//	text-decoration: none;
+//	font-weight: bold;
+//}
+//
+//.jvb-settings-help a:hover {
+//	text-decoration: underline;
+//}
+//
+///* Connection Actions */
+//.jvb-connection-actions {
+//	display: flex;
+//	gap: 0.75rem;
+//	flex-wrap: wrap;
+//	border-top: 2px solid #ddd;
+//	padding-top: 1rem;
+//	align-items: center;
+//}
+//
+//.jvb-connection-actions .button {
+//	font-family: 'Courier New', monospace;
+//	font-weight: bold;
+//	text-transform: uppercase;
+//	letter-spacing: 0.5px;
+//	border-radius: 4px;
+//	padding: 0.6rem 1.2rem;
+//	font-size: 0.8rem;
+//	cursor: pointer;
+//	transition: all 0.2s ease;
+//}
+//
+//.jvb-connect-button.button-primary {
+//	background: var(--action-color, #FF0080);
+//	border-color: var(--action-color, #FF0080);
+//	color: white;
+//	box-shadow: 0 2px 4px rgba(255, 0, 128, 0.2);
+//}
+//
+//.jvb-connect-button.button-primary:hover {
+//	background: #e6007a;
+//	border-color: #e6007a;
+//	transform: translateY(-1px);
+//	box-shadow: 0 4px 8px rgba(255, 0, 128, 0.3);
+//}
+//
+//.jvb-test-button {
+//	background: white;
+//	border-color: #22c55e;
+//	color: #22c55e;
+//}
+//
+//.jvb-test-button:hover {
+//	background: #22c55e;
+//	color: white;
+//}
+//
+//.jvb-sync-button {
+//	background: white;
+//	border-color: var(--action-color, #FF0080);
+//	color: var(--action-color, #FF0080);
+//}
+//
+//.jvb-sync-button:hover {
+//	background: var(--action-color, #FF0080);
+//	color: white;
+//}
+//
+//.jvb-disconnect-button {
+//	color: #ef4444 !important;
+//	border: none !important;
+//	background: none !important;
+//	text-decoration: underline;
+//	margin-left: auto;
+//}
+//
+//.jvb-disconnect-button:hover {
+//	color: #dc2626 !important;
+//	background: none !important;
+//}
+//
+///* Loading States */
+//.jvb-integration-form.loading .button {
+//	opacity: 0.7;
+//	pointer-events: none;
+//}
+//
+//.jvb-integration-form.loading .jvb-connect-button::after {
+//	content: ' (Saving...)';
+//}
+//
+//.jvb-integration-form.loading .jvb-test-button::after {
+//	content: ' (Testing...)';
+//}
+//
+///* Responsive Design */
+//@media (max-width: 768px) {
+//	.jvb-integrations-grid {
+//		grid-template-columns: 1fr;
+//	}
+//
+//	.jvb-connection-header {
+//		flex-direction: column;
+//		gap: 1rem;
+//	}
+//
+//	.jvb-connection-status {
+//		text-align: left;
+//		min-width: auto;
+//	}
+//
+//	.jvb-connection-actions {
+//		flex-direction: column;
+//	}
+//
+//	.jvb-connection-actions .button {
+//		width: 100%;
+//		text-align: center;
+//	}
+//
+//	.jvb-disconnect-button {
+//		margin-left: 0 !important;
+//	}
+//}
+//
+//form .tabs {
+//	position: sticky;
+//	top: calc(var(--btn) + 2rem);
+//	left: 0;
+//	right: 0;
+//	z-index: 50;
+//	background: var(--base);
+//}
+//
+//.form-actions {
+//	display: flex;
+//}
+//
+//.spinner {
+//	width: 12px;
+//	height: 12px;
+//	border: 2px solid transparent;
+//	border-top: 2px solid var(--action-50);
+//	border-radius: 50%;
+//	animation: spin 1s linear infinite;
+//}
+//
+//@keyframes spin {
+//	0% { transform: rotate(0deg); }
+//	100% { transform: rotate(360deg); }
+//}
diff --git a/src/forms/view.js b/src/forms/view.js
new file mode 100644
index 0000000..788e929
--- /dev/null
+++ b/src/forms/view.js
@@ -0,0 +1,112 @@
+/**
+ * view.js
+ * Frontend JavaScript for the Form Block
+ * Handles form validation and submission
+ */
+/**
+ * view.js
+ * Frontend JavaScript for the Form Block
+ */
+class FormBlock {
+	constructor() {
+		this.controller = window.jvbForm;
+
+		document.querySelectorAll('.jvb-form-block form').forEach(form => {
+			this.controller.registerForm(form, {
+				cache: true,
+				autoUpload: false,
+				imageMeta: false,
+			});
+		});
+
+		this.controller.subscribe((event, data) => {
+			if (event === 'form-submit') {
+				this.handleFormSubmission(data).then(()=>{});
+			}
+		});
+	}
+
+	async handleFormSubmission(eventData) {
+		const { config, data } = eventData;
+		const form = config.element;
+
+		const submitData = new FormData();
+
+		// Add regular form fields
+		for (const [key, value] of Object.entries(data)) {
+			if (Array.isArray(value)) {
+				value.forEach(v => submitData.append(`${key}[]`, v));
+			} else if (typeof value === 'object' && value !== null) {
+				submitData.append(key, JSON.stringify(value));
+			} else {
+				submitData.append(key, value);
+			}
+		}
+		config.element.querySelectorAll('[name="form_id"],[name="form_type"],[name="timestamp"],[name="cf-turnstile-response"]').forEach(input => {
+			submitData.append(input.name, input.value);
+		});
+
+		// Add uploaded files
+		if (window.jvbUploads) {
+			try {
+				const files = await window.jvbUploads.getFilesForForm(form);
+
+				files.forEach(({ file, fieldName }) => {
+					submitData.append(`${fieldName}[]`, file);
+				});
+			} catch (error) {
+				console.error('Error getting files:', error);
+			}
+		}
+
+		this.controller.showFormStatus(config.id, 'uploading');
+
+		try {
+			const response = await fetch(`${jvbSettings.api}forms`, {
+				method: 'POST',
+				credentials: 'same-origin',
+				body: submitData
+			});
+
+			const result = await response.json();
+
+			if (!response.ok) {
+				this.controller.showFormStatus(config.id, 'error');
+				this.controller.handleFormError(form, result);
+				return;
+			}
+
+			this.controller.showFormStatus(config.id, 'submitted');
+			// this.controller.handleFormSuccess(form, result);
+			this.controller.showSummary({ changes: data, config: config });
+			window.jvbA11y.announce('Form successfully submitted!');
+
+			// Clean up uploaded files
+			if (window.jvbUploads) {
+				const uploadFields = form.querySelectorAll('[data-upload-field]');
+				for (const field of uploadFields) {
+					const fieldId = window.jvbUploads.determineFieldId(field);
+					await window.jvbUploads.clearFieldFromStores(fieldId);
+				}
+			}
+
+		} catch (error) {
+			console.error('Form submission error:', error);
+			this.controller.showFormStatus(config.id, 'error');
+			this.controller.handleFormError(form, {
+				message: 'Network error. Please check your connection and try again.',
+				code: 'network_error'
+			});
+		} finally {
+			await this.controller.store.delete(config.id);
+		}
+	}
+}
+
+document.addEventListener('DOMContentLoaded', async function() {
+	window.auth.subscribe(event => {
+		if (event === 'auth-loaded') {
+			new FormBlock();
+		}
+	});
+});
diff --git a/src/glossary/block.json b/src/glossary/block.json
new file mode 100644
index 0000000..2705831
--- /dev/null
+++ b/src/glossary/block.json
@@ -0,0 +1,24 @@
+{
+	"$schema": "https://schemas.wp.org/trunk/block.json",
+	"apiVersion": 3,
+	"name": "jvb/glossary",
+	"version": "0.1.0",
+	"title": "Glossary of Terms",
+	"category": "jvb",
+	"icon": "excerpt-view",
+	"description": "Outputs the terms",
+	"example": {},
+	"supports": {
+		"html": false,
+		"align": ["wide", "full"]
+	},
+	"textdomain": "jvb",
+	"selectors": {
+		"root": ".glossary"
+	},
+	"editorScript": "file:./index.js",
+	"editorStyle": "file:./index.css",
+	"style": "file:./style-index.css",
+	"render": "file:./render.php",
+	"viewScript": "file:./view.js"
+}
diff --git a/src/glossary/edit.js b/src/glossary/edit.js
new file mode 100644
index 0000000..4a115af
--- /dev/null
+++ b/src/glossary/edit.js
@@ -0,0 +1,38 @@
+/**
+ * Retrieves the translation of text.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/
+ */
+import { __ } from '@wordpress/i18n';
+
+/**
+ * React hook that is used to mark the block wrapper element.
+ * It provides all the necessary props like the class name.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops
+ */
+import { useBlockProps } from '@wordpress/block-editor';
+
+/**
+ * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
+ * Those files can contain any CSS code that gets applied to the editor.
+ *
+ * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
+ */
+import './editor.scss';
+
+/**
+ * The edit function describes the structure of your block in the context of the
+ * editor. This represents what the editor will render when the block is used.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit
+ *
+ * @return {Element} Element to render.
+ */
+export default function Edit() {
+	return (
+		<p { ...useBlockProps() }>
+			{ __( 'Will output the glossary', 'jvb' ) }
+		</p>
+	);
+}
diff --git a/src/glossary/editor.scss b/src/glossary/editor.scss
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/glossary/editor.scss
diff --git a/src/glossary/index.js b/src/glossary/index.js
new file mode 100644
index 0000000..d82621b
--- /dev/null
+++ b/src/glossary/index.js
@@ -0,0 +1,33 @@
+/**
+ * Registers a new block provided a unique name and an object defining its behavior.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
+ * All files containing `style` keyword are bundled together. The code used
+ * gets applied both to the front of your site and to the editor.
+ *
+ * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
+ */
+import './style.scss';
+
+/**
+ * Internal dependencies
+ */
+import Edit from './edit';
+import metadata from './block.json';
+
+/**
+ * Every block starts by registering a new block type definition.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+registerBlockType( metadata.name, {
+	/**
+	 * @see ./edit.js
+	 */
+	edit: Edit,
+} );
diff --git a/src/glossary/index.php b/src/glossary/index.php
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/glossary/index.php
diff --git a/src/glossary/render.php b/src/glossary/render.php
new file mode 100644
index 0000000..52aded4
--- /dev/null
+++ b/src/glossary/render.php
@@ -0,0 +1,8 @@
+<?php
+/**
+ * @see https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/block-api/block-metadata.md#render
+ */
+?>
+<p <?php echo get_block_wrapper_attributes(); ?>>
+	<?php esc_html_e( 'Menu – hello from a dynamic block!', 'menu' ); ?>
+</p>
diff --git a/src/glossary/style.scss b/src/glossary/style.scss
new file mode 100644
index 0000000..a367991
--- /dev/null
+++ b/src/glossary/style.scss
@@ -0,0 +1,109 @@
+:root {
+	--navWidth: 40vw;
+	@media (min-width: 768px) {
+		--navWidth: 22vw;
+	}
+}
+
+nav.glossary-index {
+	position: fixed;
+	top: 50%;
+	transform: translateY(-50%);
+	width: var(--navWidth);
+	right: -8px;
+	height: 60vh;
+	z-index: var(--z-3);
+
+	> ul {
+		--dir: column;
+		--align: flex-start;
+		--justify: flex-start;
+		--gap: 1px;
+		touch-action: pan-y;
+		max-height: 100%;
+		height: 100%;
+		width: 100%;
+		overflow: hidden auto;
+		scroll-behavior: smooth;
+	}
+	li, a {
+		flex: 1;
+		width: 100%;
+		height: max-content;
+		min-height: max(var(--chipchip), max-content);
+	}
+	a {
+		--justify: center;
+		padding: .25rem .5rem;
+		hyphens: auto;
+		background-color: rgba(var(--base-rgb),var(--op-45));
+		word-wrap: anywhere;
+		white-space: wrap;
+	}
+	a:hover,
+	a:focus,
+	a.active {
+		background-color: rgba(var(--action-rgb), var(--op-6));
+		color: var(--action-contrast);
+	}
+}
+.glossary dd {
+	margin-left: .5rem;
+	width: calc(100% + .75rem);
+}
+.glossary dd,
+.glossary dt {
+	position: relative;
+	left: 0;
+	transition: margin var(--trans-base),
+	left var(--trans-base),
+	width var(--trans-base);
+}
+.glossary dt:target,
+.glossary dt.active {
+	outline: none;
+	left: -1.5rem;
+	padding: 0;
+	color: var(--action-0);
+}
+	.glossary dt:target + dd,
+	.glossary dt.active + dd {
+		left: -1.5rem;
+	}
+
+main header,
+dl.glossary {
+	grid-column: full;
+	padding: 0 var(--navWidth) 0 2rem;
+	@media (min-width:768px) {
+		margin-left: auto;
+		max-width: var(--content);
+		margin-right: var(--navWidth);
+		padding-right: var(--btn);
+	}
+}
+
+@media (max-width: 768px) {
+	.glossary {
+		h2 {
+			font-size: var(--txt-medium);
+		}
+		p {
+			font-size: var(--txt-x-small);
+		}
+	}
+	.glossary-index {
+		li,a {
+			height: fit-content;
+		}
+		a {
+			font-size: var(--txt-x-small);
+			padding: .25rem;
+			min-height: 2em;
+		}
+	}
+
+	body:has(.glossary) h1 {
+		font-size: var(--txt-xx-large);
+	}
+}
diff --git a/src/glossary/view.js b/src/glossary/view.js
new file mode 100644
index 0000000..f68d59a
--- /dev/null
+++ b/src/glossary/view.js
@@ -0,0 +1,184 @@
+/**
+ * Glossary Navigation Active State Manager
+ * Handles highlighting active terms as they scroll into view
+ * and syncing navigation with scroll position
+ */
+class GlossaryNavigator {
+	constructor(glossarySelector = 'dl.glossary', navSelector = 'nav.glossary-index') {
+		this.glossary = document.querySelector(glossarySelector);
+		this.nav = document.querySelector(navSelector);
+
+		if (!this.glossary || !this.nav) return;
+
+		this.terms = this.glossary.querySelectorAll('dt[id]');
+		this.navList = this.nav.querySelector('ul');
+		this.activeClass = 'active';
+		this.currentActive = null;
+
+		this.init();
+		this.setupResizeHandler();
+	}
+
+	init() {
+		// Set up Intersection Observer with screen-size appropriate margins
+		const observerOptions = {
+			root: null, // viewport
+			rootMargin: this.getRootMargin(),
+			threshold: 0
+		};
+
+		this.observer = new IntersectionObserver(
+			(entries) => this.handleIntersection(entries),
+			observerOptions
+		);
+
+		// Observe all terms
+		this.terms.forEach(term => this.observer.observe(term));
+
+		// Also handle manual scroll for edge cases
+		this.handleScroll = this.debounce(() => this.checkActiveTerm(), 100);
+		window.addEventListener('scroll', this.handleScroll, { passive: true });
+	}
+
+	getRootMargin() {
+		// On larger screens: centered (50% from top and bottom)
+		return '-50% 0px -50% 0px';
+	}
+
+	setupResizeHandler() {
+		let resizeTimer;
+		window.addEventListener('resize', () => {
+			clearTimeout(resizeTimer);
+			resizeTimer = setTimeout(() => {
+				// Reinitialize observer with new margins on resize
+				this.reinitialize();
+			}, 250);
+		});
+	}
+
+	reinitialize() {
+		// Disconnect old observer
+		if (this.observer) {
+			this.observer.disconnect();
+		}
+
+		// Create new observer with updated margins
+		this.init();
+	}
+
+	handleIntersection(entries) {
+		// Find the entry that's intersecting
+		const intersecting = entries.find(entry => entry.isIntersecting);
+
+		if (intersecting) {
+			this.setActive(intersecting.target);
+		}
+	}
+
+	checkActiveTerm() {
+		// Fallback method to find which term is in the trigger zone
+		const remInPixels = parseFloat(getComputedStyle(document.documentElement).fontSize);
+		const margin = remInPixels * 4;
+
+		let closestTerm = null;
+		let closestDistance = Infinity;
+
+		this.terms.forEach(term => {
+			const rect = term.getBoundingClientRect();
+
+			const isInZone = rect.top + rect.height / 2 >= 0 && rect.top + rect.height / 2 <= window.innerHeight;
+
+			if (isInZone) {
+				// Find closest to the trigger point
+				const triggerPoint = window.innerHeight / 2;
+
+				const distance = Math.abs(rect.top - triggerPoint);
+
+				if (distance < closestDistance) {
+					closestDistance = distance;
+					closestTerm = term;
+				}
+			}
+		});
+
+		if (closestTerm) {
+			this.setActive(closestTerm);
+		}
+	}
+
+	setActive(term) {
+		if (this.currentActive === term) return;
+
+		// Remove active class from previous term
+		if (this.currentActive) {
+			this.currentActive.classList.remove(this.activeClass);
+		}
+
+		// Add active class to current term
+		term.classList.add(this.activeClass);
+		this.currentActive = term;
+
+		// Update navigation
+		this.updateNavigation(term.id);
+	}
+
+	updateNavigation(termId) {
+		// Remove active from all nav links
+		const navLinks = this.nav.querySelectorAll('a');
+		navLinks.forEach(link => link.classList.remove(this.activeClass));
+
+		// Find and activate corresponding nav link
+		const activeLink = this.nav.querySelector(`a[href="#${termId}"]`);
+
+		if (activeLink) {
+			activeLink.classList.add(this.activeClass);
+
+			// Scroll the nav list to center the active link
+			this.centerNavItem(activeLink);
+		}
+	}
+
+	centerNavItem(link) {
+		const listRect = this.navList.getBoundingClientRect();
+		const linkRect = link.getBoundingClientRect();
+
+		// Calculate position to center the link in the nav container
+		const scrollTop = this.navList.scrollTop;
+		const linkOffset = linkRect.top - listRect.top;
+		const centerOffset = (listRect.height / 2) - (linkRect.height / 2);
+
+		this.navList.scrollTo({
+			top: scrollTop + linkOffset - centerOffset,
+			behavior: 'smooth'
+		});
+	}
+
+	debounce(func, wait) {
+		let timeout;
+		return function executedFunction(...args) {
+			const later = () => {
+				clearTimeout(timeout);
+				func(...args);
+			};
+			clearTimeout(timeout);
+			timeout = setTimeout(later, wait);
+		};
+	}
+
+	destroy() {
+		if (this.observer) {
+			this.observer.disconnect();
+		}
+		window.removeEventListener('scroll', this.handleScroll);
+	}
+}
+
+
+// Initialize when DOM is ready
+if (document.readyState === 'loading') {
+	document.addEventListener('DOMContentLoaded', () => {
+		new GlossaryNavigator();
+	});
+} else {
+	new GlossaryNavigator();
+}
diff --git a/src/gmbreviews/block.json b/src/gmbreviews/block.json
new file mode 100644
index 0000000..78634fd
--- /dev/null
+++ b/src/gmbreviews/block.json
@@ -0,0 +1,68 @@
+{
+	"$schema": "https://schemas.wp.org/trunk/block.json",
+	"apiVersion": 3,
+	"name": "jvb/gmbreviews",
+	"title": "GMB Reviews",
+	"category": "jvb",
+	"description": "Display top-rated Google My Business reviews with statistics and action buttons",
+	"keywords": ["reviews", "google", "testimonials", "gmb", "ratings"],
+	"textdomain": "jvb",
+	"attributes": {
+		"inheritUser": {
+			"type": "boolean",
+			"default": false
+		},
+		"count": {
+			"type": "number",
+			"default": 5
+		},
+		"showRating": {
+			"type": "boolean",
+			"default": true
+		},
+		"showDate": {
+			"type": "boolean",
+			"default": true
+		},
+		"showReviewLink": {
+			"type": "boolean",
+			"default": true
+		},
+		"showViewAllLink": {
+			"type": "boolean",
+			"default": true
+		},
+		"showStats": {
+			"type": "boolean",
+			"default": true
+		},
+		"minStars": {
+			"type": "number",
+			"default": 4,
+			"minimum": 1,
+			"maximum": 5
+		}
+	},
+	"supports": {
+		"html": false,
+		"align": true,
+		"color": {
+			"text": true,
+			"background": true,
+			"link": true
+		},
+		"spacing": {
+			"margin": true,
+			"padding": true
+		},
+		"typography": {
+			"fontSize": true,
+			"lineHeight": true
+		}
+	},
+	"render": "file:./render.php",
+	"editorScript": "file:./index.js",
+	"editorStyle": "file:./index.css",
+	"style": "file:./style-index.css",
+	"viewScript": "file:./view.js"
+}
diff --git a/src/gmbreviews/edit.js b/src/gmbreviews/edit.js
new file mode 100644
index 0000000..7f7e09f
--- /dev/null
+++ b/src/gmbreviews/edit.js
@@ -0,0 +1,69 @@
+// src/gmbreviews/edit.js
+import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
+import { PanelBody, RangeControl, ToggleControl } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import ServerSideRender from '@wordpress/server-side-render';
+
+export default function Edit({ attributes, setAttributes }) {
+	const blockProps = useBlockProps();
+	const { count, inheritUser, showStats, minStars, showViewAllLink, showRating, showDate, showReviewLink } = attributes;
+
+	return (
+		<>
+			<InspectorControls>
+				<PanelBody title={__('Review Settings', 'jvb')}>
+					<ToggleControl
+						label={__('Inherit User', 'jvb')}
+						checked={inheritUser}
+						onChange={(value) => setAttributes({ inheritUser: value })}
+					/>
+					<RangeControl
+						label={__('Number of Reviews', 'jvb')}
+						value={count}
+						onChange={(value) => setAttributes({ count: value })}
+						min={1}
+						max={20}
+					/>
+					<ToggleControl
+						label={__('Show Rating', 'jvb')}
+						checked={showRating}
+						onChange={(value) => setAttributes({ showRating: value })}
+					/>
+					<ToggleControl
+						label={__('Show Date', 'jvb')}
+						checked={showDate}
+						onChange={(value) => setAttributes({ showDate: value })}
+					/>
+					<ToggleControl
+						label={__('Show Review Link', 'jvb')}
+						checked={showReviewLink}
+						onChange={(value) => setAttributes({ showReviewLink: value })}
+					/>
+					<ToggleControl
+						label={__('Show Stats', 'jvb')}
+						checked={showStats}
+						onChange={(value) => setAttributes({ showStats: value })}
+					/>
+					<ToggleControl
+						label={__('Show All Reviews Link', 'jvb')}
+						checked={showViewAllLink}
+						onChange={(value) => setAttributes({ showViewAllLink: value })}
+					/>
+					<RangeControl
+						label={__('Minimum Rating', 'jvb')}
+						value={minStars}
+						onChange={(value) => setAttributes({ minStars: value })}
+						min={1}
+						max={5}
+					/>
+				</PanelBody>
+			</InspectorControls>
+			<div {...blockProps}>
+				<ServerSideRender
+					block="jvb/gmbreviews"
+					attributes={attributes}
+				/>
+			</div>
+		</>
+	);
+}
diff --git a/src/gmbreviews/editor.scss b/src/gmbreviews/editor.scss
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/gmbreviews/editor.scss
diff --git a/src/gmbreviews/index.js b/src/gmbreviews/index.js
new file mode 100644
index 0000000..2f2dd15
--- /dev/null
+++ b/src/gmbreviews/index.js
@@ -0,0 +1,11 @@
+import { registerBlockType } from '@wordpress/blocks';
+import Edit from './edit';
+import './style.scss';
+import './editor.scss';
+import metadata from './block.json';
+
+registerBlockType(metadata.name, {
+	edit: Edit,
+	// No save function - dynamic block rendered on server
+	save: () => null,
+});
diff --git a/src/gmbreviews/index.php b/src/gmbreviews/index.php
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/gmbreviews/index.php
diff --git a/src/gmbreviews/render.php b/src/gmbreviews/render.php
new file mode 100644
index 0000000..487f394
--- /dev/null
+++ b/src/gmbreviews/render.php
@@ -0,0 +1,207 @@
+<?php
+/**
+ * GMB Reviews Block - Render Template
+ *
+ * Displays recent Google My Business reviews with a link to leave a review
+ */
+function jvbRenderGMBReviewsBlock(array $attributes): string
+{
+	$count = $attributes['count'] ?? 5;
+	$showRating = $attributes['showRating'] ?? true;
+	$showDate = $attributes['showDate'] ?? true;
+	$showReviewLink = $attributes['showReviewLink'] ?? true;
+	$showViewAllLink = $attributes['showViewAllLink'] ?? true;
+	$showStats = $attributes['showStats'] ?? true;
+	$minStars = $attributes['minStars'] ?? 4; // Only show 4+ star reviews
+	$inheritUser = $attributes['inheritUser']??null;
+	if ($inheritUser) {
+		global $post;
+		$inheritUser = $post->post_author;
+	}else {
+		$inheritUser = null;
+	}
+	try {
+		$gmb = JVB()->connect('gmb', $inheritUser);
+		if (!$gmb->isSetUp()) {
+			error_log('GMB Not set up for: '.(int)$inheritUser);
+			return '';
+		}
+		$gotReviews = $gmb->getReviews();
+		// Get all data
+		$allReviews = $gotReviews['reviews']??[];
+		$reviewUrl = $gmb->getReviewUrl();
+		$viewAllUrl = $gmb->getReviewsViewUrl();
+
+		$average = $gotReviews['averageRating']??null;
+		$total = $gotReviews['totalReviewCount']??null;
+
+		// Filter reviews by minimum stars
+		$reviews = [];
+		if (!empty($allReviews)) {
+			foreach ($allReviews as $review) {
+				$rating = $review['starRating'] ?? 0;
+				if ($rating >= $minStars) {
+					$reviews[] = $review;
+					if (count($reviews) >= $count) {
+						break; // Got enough reviews
+					}
+				}
+			}
+		}
+
+		if (empty($reviews) && empty($reviewUrl) && empty($stats)) {
+			error_log('No reviews to display...');
+			return '';
+		}
+
+		ob_start();
+		?>
+		<div class="gmb-reviews">
+			<div class="row center">
+			<?php
+			if ($showStats && !empty($average) && !empty($total)) {
+				?>
+				<p>
+					<span class="stars" title="<?= $average ?> out of 5 stars">
+						<?php
+						$fullStars = floor($average);
+						$hasHalfStar = ($average - $fullStars) >= 0.5;
+
+						for ($i = 1; $i <= 5; $i++) {
+							if ($i <= $fullStars) {
+								echo jvbIcon('star', ['style' => 'fill']);
+							} elseif ($i == $fullStars + 1 && $hasHalfStar) {
+								echo jvbIcon('star-half', ['style'=> 'fill']);
+							} else {
+								echo  jvbIcon('star', ['style' => 'light']);
+							}
+						}
+						?>
+					</span>
+					<i>Average</i>
+				</p>
+				<?php
+				if ($total > 0) {
+					?>
+					<p><i>{ <?= number_format($total ) . ' ' . _n('Review', 'Reviews', $total, 'jvb')?> Total }</i></p>
+					<?php
+				}
+			?>
+			<?php
+			}
+			?>
+
+
+		</div>
+			<?php
+			if ($showReviewLink && !empty($reviewUrl)) {
+				?>
+				<a href="<?=esc_url($reviewUrl)?>"
+				   class="button"
+				   target="_blank"
+				   rel="noopener noreferrer">
+					<?= jvbIcon('star', ['style' => 'fill']) ?>
+					Leave Your Review
+				</a>
+				<?php
+			}
+			?>
+
+		<ul>
+			<?php
+			foreach ($reviews as $review) {
+				$reviewer = $review['reviewer']['displayName'] ?? 'Anonymous';
+				$reviewer = strtok($reviewer, ' ');
+				$profilePhoto = $review['reviewer']['profilePhotoUrl'] ?? '';
+				$rating = $review['starRating'] ?? 0;
+				$rating = match($rating) {
+					'FIVE'	=> 5,
+					'FOUR'	=> 4,
+					'THREE'	=> 3,
+					'TWO'	=> 2,
+					'ONE'	=> 1,
+					default =>  $rating
+				};
+				$comment = $review['comment'] ?? '';
+				$date = $review['updateTime'] ?? '';
+				?>
+				<li>
+					<blockquote class="review">
+						<?php
+						// Review text
+						if (!empty($comment)) { ?>
+							<div class="content review">
+								<?= apply_filters('wpautop', $comment) ?>
+							</div>
+						<?php } ?>
+						<cite class="row left nowrap">
+							<?php if (!empty($profilePhoto)) { ?>
+								<img src="<?=esc_url($profilePhoto)?>"
+									 alt="<?=esc_attr($reviewer)?>"
+								'loading="lazy">
+							<?php } else { ?>
+								<div class="avatar">
+									<?= jvbIcon('user-circle')?>
+								</div>
+							<?php } ?>
+
+							<div class="row left wrap">
+								<?php if ($showRating && $rating > 0) { ?>
+									<div class="stars" title="<?= $rating ?> out of 5 stars">
+										<?php
+										for ($i = 1; $i <= 5; $i++) {
+											echo ($i <= $rating) ? jvbIcon('star', ['style' => 'fill']) : jvbIcon('star', ['style' => 'light']);
+										} ?>
+									</div>
+								<?php } ?>
+								<p><?= esc_html($reviewer)?></p>
+								<?php
+								// Date
+								if ($showDate && !empty($date)) {
+									$formatted_date = human_time_diff(strtotime($date), current_time('timestamp')) . ' ago';
+									?>
+									<time datetime="<?=esc_attr($date)?>">
+										<?= esc_html($formatted_date) ?>
+									</time>
+								<?php } ?>
+
+							</div>
+						</cite>
+					</blockquote>
+				</li>
+			<?php
+			}
+			?>
+		</ul>
+		<?php
+		// Footer with "See All Reviews" button
+		if ($showViewAllLink && !empty($viewAllUrl)) {
+			?>
+			<div class="footer">
+				<a href=" <?= esc_url($viewAllUrl) ?>"
+					class="button"
+					target="_blank"
+					rel="noopener noreferrer">
+
+					<?php
+					if ($showStats ) {
+						echo 'See All ' . number_format($total) . ' Reviews';
+					} else {
+						echo ' See All Reviews';
+					}
+					?>
+					<?= jvbIcon('arrow-square-out') ?>
+				</a>
+			</div>
+		<?php
+		}
+		?>
+		</div>
+		<?php
+		return ob_get_clean();
+
+	} catch (\Exception $e) {
+		error_log('[GMB Reviews Block] Error: ' . $e->getMessage());
+		return '';
+	}
+}
diff --git a/src/gmbreviews/style.scss b/src/gmbreviews/style.scss
new file mode 100644
index 0000000..19f3abb
--- /dev/null
+++ b/src/gmbreviews/style.scss
@@ -0,0 +1,122 @@
+.gmb-reviews {
+	max-width: none;
+	> .row.center {
+		max-width:var(--content);
+		margin: 0 auto;
+		--gap: .5rem 6rem;
+
+		p {
+			width: fit-content;
+		}
+	}
+	.button {
+		width: 66.6%;
+		margin: 0 auto 2rem;
+		display: flex;
+		height: max-content;
+	}
+	.stars {
+		display: inline-flex;
+		align-items: center;
+		justify-content: flex-start;
+		flex-wrap: nowrap;
+	}
+	ul {
+		list-style: none;
+		margin: 0;
+		padding: 0;
+		max-width: var(--full);
+		li {
+			width: 100%;
+			max-width: none;
+			padding: 4rem 1rem;
+			&:nth-of-type(odd) {
+				background-color: var(--base-50);
+				blockquote {
+					--background: var(--base-50);
+				}
+			}
+			&:nth-of-type(even) {
+				background-color: var(--base-100);
+				blockquote {
+					--background: var(--base-100);
+				}
+			}
+		}
+	}
+	blockquote {
+		margin:0 auto;
+		padding: 0;
+		max-width: var(--content);
+		.content {
+			border-width: 4px 1px;
+			&::after {
+				border-width: 4px 1px;
+			}
+			&::before {
+				border-width: 8px;
+				bottom: -4px;
+			}
+		}
+		cite {
+			position: relative;
+
+			img {
+				width: 4.5rem;
+				position: absolute;
+				left: -8rem;
+				top: 0;
+			}
+
+			p {
+				margin: 0;
+			}
+
+			.wrap {
+				--wrap: wrap;
+
+				p, time {
+					max-width: 49%;
+				}
+
+				.stars {
+					width: 100%;
+				}
+			}
+		}
+		time {
+			white-space: nowrap;
+		}
+	}
+	.stars .icon {
+		background-color: var(--action-0);
+	}
+	article {
+		padding: 1rem;
+		border-radius: var(--radius-outer);
+		background-color: var(--base);
+		header {
+			--align: center;
+			>img {
+				position: relative;
+				left: 0;
+			}
+		}
+		time {
+			font-style: italic;
+		}
+		.review {
+			padding: 1.5rem;
+		}
+
+		h4 {
+			width: max-content;
+		}
+		.icon {
+			color: var(--action-0);
+		}
+	}
+	.footer .button {
+		width: 100%;
+	}
+}
diff --git a/src/gmbreviews/view.js b/src/gmbreviews/view.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/gmbreviews/view.js
diff --git a/src/index.php b/src/index.php
new file mode 100644
index 0000000..4f2541d
--- /dev/null
+++ b/src/index.php
@@ -0,0 +1,3 @@
+<?php
+//Nothing to see here
+
diff --git a/src/menu/block.json b/src/menu/block.json
new file mode 100644
index 0000000..2f198c3
--- /dev/null
+++ b/src/menu/block.json
@@ -0,0 +1,24 @@
+{
+	"$schema": "https://schemas.wp.org/trunk/block.json",
+	"apiVersion": 3,
+	"name": "jvb/menu",
+	"version": "0.1.0",
+	"title": "Our Menu",
+	"category": "jvb",
+	"icon": "food",
+	"description": "Outputs our menu, organized by categories",
+	"example": {},
+	"supports": {
+		"html": false,
+		"align": ["wide", "full"]
+	},
+	"textdomain": "jvb",
+	"selectors": {
+		"root": ".menu-block"
+	},
+	"editorScript": "file:./index.js",
+	"editorStyle": "file:./index.css",
+	"style": "file:./style-index.css",
+	"render": "file:./render.php",
+	"viewScript": "file:./view.js"
+}
diff --git a/src/menu/edit.js b/src/menu/edit.js
new file mode 100644
index 0000000..cbb91c1
--- /dev/null
+++ b/src/menu/edit.js
@@ -0,0 +1,38 @@
+/**
+ * Retrieves the translation of text.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/
+ */
+import { __ } from '@wordpress/i18n';
+
+/**
+ * React hook that is used to mark the block wrapper element.
+ * It provides all the necessary props like the class name.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops
+ */
+import { useBlockProps } from '@wordpress/block-editor';
+
+/**
+ * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
+ * Those files can contain any CSS code that gets applied to the editor.
+ *
+ * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
+ */
+import './editor.scss';
+
+/**
+ * The edit function describes the structure of your block in the context of the
+ * editor. This represents what the editor will render when the block is used.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit
+ *
+ * @return {Element} Element to render.
+ */
+export default function Edit() {
+	return (
+		<p { ...useBlockProps() }>
+			{ __( 'Will output the menu', 'jvb' ) }
+		</p>
+	);
+}
diff --git a/src/menu/editor.scss b/src/menu/editor.scss
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/menu/editor.scss
diff --git a/src/menu/index.js b/src/menu/index.js
new file mode 100644
index 0000000..d82621b
--- /dev/null
+++ b/src/menu/index.js
@@ -0,0 +1,33 @@
+/**
+ * Registers a new block provided a unique name and an object defining its behavior.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
+ * All files containing `style` keyword are bundled together. The code used
+ * gets applied both to the front of your site and to the editor.
+ *
+ * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
+ */
+import './style.scss';
+
+/**
+ * Internal dependencies
+ */
+import Edit from './edit';
+import metadata from './block.json';
+
+/**
+ * Every block starts by registering a new block type definition.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+registerBlockType( metadata.name, {
+	/**
+	 * @see ./edit.js
+	 */
+	edit: Edit,
+} );
diff --git a/src/menu/index.php b/src/menu/index.php
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/menu/index.php
diff --git a/src/menu/render.php b/src/menu/render.php
new file mode 100644
index 0000000..52aded4
--- /dev/null
+++ b/src/menu/render.php
@@ -0,0 +1,8 @@
+<?php
+/**
+ * @see https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/block-api/block-metadata.md#render
+ */
+?>
+<p <?php echo get_block_wrapper_attributes(); ?>>
+	<?php esc_html_e( 'Menu – hello from a dynamic block!', 'menu' ); ?>
+</p>
diff --git a/src/menu/style.scss b/src/menu/style.scss
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/menu/style.scss
diff --git a/src/menu/view.js b/src/menu/view.js
new file mode 100644
index 0000000..153b0df
--- /dev/null
+++ b/src/menu/view.js
@@ -0,0 +1,43 @@
+window.details = document.querySelectorAll('details');
+window.toggles = document.querySelectorAll('.toggle-details');
+
+document.addEventListener('click', (e) => {
+	if (e.target.classList.contains('toggle-details')) {
+		e.target.classList.toggle('open');
+		let on = e.target.classList.contains('open');
+		let section = e.target.dataset.toggle;
+		if (section === 'all') {
+			toggleToggles(on);
+		}
+
+		let span = e.target.querySelector('span');
+		span.textContent = (on) ? 'Close': 'Open';
+		toggleDetails(section, on);
+	}
+});
+
+
+console.log(window.details);
+function toggleDetails(name, toggle) {
+	if (name === 'all') {
+		console.log('Toggling all!');
+		window.details.forEach(detail => {
+			console.log(detail);
+			detail.open = toggle;
+		});
+	} else {
+		for (let detail of window.details) {
+			if (detail.dataset.section === name) {
+				detail.open = toggle;
+			}
+		}
+	}
+}
+
+function toggleToggles(on) {
+	window.toggles.forEach(toggle => {
+		if (toggle.dataset.toggle !== 'all') {
+			toggle.querySelector('span').textContent = (on) ? 'Close' : 'Open';
+		}
+	});
+}
diff --git a/src/summary/block.json b/src/summary/block.json
new file mode 100644
index 0000000..2cca715
--- /dev/null
+++ b/src/summary/block.json
@@ -0,0 +1,32 @@
+{
+    "$schema": "https://schemas.wp.org/trunk/block.json",
+    "apiVersion": 3,
+    "name": "jvb/summary",
+    "title": "Archive Summary",
+    "category": "jvb",
+    "icon": "align-center",
+    "description": "Outputs the information for the given archive page, or the bio for a profile. Pairs well with the feed block.",
+    "keywords": [ "summary", "bio", "style", "term" ],
+    "version": "0.9.0",
+    "textdomain": "jvb",
+    "supports": {
+        "html": false,
+        "align": ["wide", "full"]
+    },
+    "selectors": {
+        "root": ".summary-block"
+    },
+    "styles": [
+        { "name": "default", "label": "Default", "isDefault": true }
+    ],
+    "example": {
+        "attributes": {
+            "listType": "tattoo"
+        }
+    },
+    "render": "file:./render.php",
+    "editorScript": "file:./index.js",
+    "editorStyle": "file:./index.css",
+    "style": "file:./style-index.css",
+    "viewScript": "file:./view.js"
+}
diff --git a/src/summary/edit.js b/src/summary/edit.js
new file mode 100644
index 0000000..6741773
--- /dev/null
+++ b/src/summary/edit.js
@@ -0,0 +1,29 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
+import { SelectControl, ToggleControl, PanelBody } from '@wordpress/components';
+
+/**
+ * Styles
+ */
+import './editor.scss';
+
+/**
+ * Edit function for Summary Block
+ */
+export default function Edit({ attributes, setAttributes }) {
+    const blockProps = useBlockProps();
+
+    return (
+        <div {...blockProps}>
+            <div className="jvb-summary-preview">
+                <h3>{__('Summary', 'jvb')}</h3>
+                <p className="jvb-list-preview-note">
+                    {__('This will inherit the current query to build the information from our custom meta on the front end.', 'jvb')}
+                </p>
+            </div>
+        </div>
+    );
+}
diff --git a/src/summary/editor.scss b/src/summary/editor.scss
new file mode 100644
index 0000000..bdf5776
--- /dev/null
+++ b/src/summary/editor.scss
@@ -0,0 +1,20 @@
+/**
+ * Directory List Block Editor Styles
+ */
+.jvb-summary-preview {
+    padding: 20px;
+    background-color: #f8f9fa;
+    border: 1px solid #e2e4e7;
+    border-radius: 4px;
+
+    h3 {
+        margin-top: 0;
+        padding-bottom: 10px;
+        border-bottom: 1px solid #ff0080;
+    }
+    &-note {
+        font-style: italic;
+        color: #555d66;
+        margin-bottom: 0;
+    }
+}
diff --git a/src/summary/index.js b/src/summary/index.js
new file mode 100644
index 0000000..c477d23
--- /dev/null
+++ b/src/summary/index.js
@@ -0,0 +1,39 @@
+/**
+ * Registers a new block provided a unique name and an object defining its behavior.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
+ * All files containing `style` keyword are bundled together. The code used
+ * gets applied both to the front of your site and to the editor.
+ *
+ * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
+ */
+import './style.scss';
+
+/**
+ * Internal dependencies
+ */
+import Edit from './edit';
+import save from './save';
+import metadata from './block.json';
+
+/**
+ * Every block starts by registering a new block type definition.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+registerBlockType( metadata.name, {
+    /**
+     * @see ./edit.js
+     */
+    edit: Edit,
+
+    /**
+     * @see ./save.js
+     */
+    save,
+} );
diff --git a/src/summary/index.php b/src/summary/index.php
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/summary/index.php
diff --git a/src/summary/render.php b/src/summary/render.php
new file mode 100644
index 0000000..c2c35fb
--- /dev/null
+++ b/src/summary/render.php
@@ -0,0 +1,320 @@
+<?php
+
+use JVBase\managers\Cache;
+use JVBase\meta\Meta;
+use JVBase\meta\Render;
+use JVBase\registrar\Registrar;
+
+if (!defined('ABSPATH')) {
+    exit; // Exit if accessed directly
+}
+/**
+ * Summary Block Render
+ *
+ * @package Edmonton_Ink
+ */
+
+function jvbRenderSummaryBlock(array $attributes):string
+{
+
+    // Buffer output
+    if (is_tax()) {
+        switch (get_queried_object()->taxonomy) {
+            case BASE.'shop':
+                return jvbRenderShopSummary();
+            default:
+                return jvbRenderTermSummary();
+        }
+    } elseif (is_singular()) {
+        return jvbRenderArtistSummary();
+    }
+    return '';
+}
+
+function jvbRenderArtistSummary():string
+{
+    $current = get_queried_object();
+    $cache = Cache::for('artistSummary', WEEK_IN_SECONDS);
+    $key = $current->ID;
+    $cached = $cache->get($key);
+    if ($cached !== false) {
+        return $cached;
+    }
+
+    ob_start();
+    $meta = Meta::forPost($current->ID);
+    $artist = jvbContentFromUser((int)$current->post_author);
+
+	$registrar = Registrar::getInstance($current->post_type));
+	$sections = [];
+	if ($registrar) {
+		$sections = $registrar->getSections();
+	}
+
+
+
+
+//    $handler = JVB()->getContent(str_replace(BASE,'', $current->post_type));
+    ?>
+    <nav id="artist" class="on-this-page index">
+        <label>Jump to:
+            <button type="button" aria-label="Show Index" title="Show Index" class="toggle" aria-expanded="false">
+                <?= jvbIcon('plus-square')?>
+            </button>
+        </label>
+        <ul>
+            <li><a href="#top" title="Back to Top"><?=jvbIcon('caret-circle-up')?></a></li>
+            <li><a href="#about">About</a></li>
+            <li><a href="#styles">Styles</a></li>
+            <li><a href="#contact">Contact</a></li>
+            <li><a href="#work">Work</a></li>
+        </ul>
+    </nav>
+    <header id="top">
+        <h1><small><?=(!empty($artist['city'])) ? $artist['city']['name'] :'Edmonton'?>'s Best <?= (!empty($artist['type'])) ?
+                    $artist['type']['name']:'Tattoo Artists'?>:
+            </small><?=$artist['display_name']?></h1>
+        <div>
+			<?php if (!empty($artist['shop'])) : ?>
+            <ul class="term-list shop">
+                <li>
+                    <a href="<?=$artist['shop']['url']?>" title="Learn more about <?=$artist['shop']['name']?>">
+                        <?= strtolower($artist['shop']['name'])?>
+                    </a>
+                </li>
+            </ul>
+			<?php endif; ?>
+			<?php if (!empty($artist['city'])): ?>
+            <ul class="term-list city">
+                <li>
+                    <a href="<?=$artist['city']['url']?>" title="See who else is rocking out of <?=$artist['city']['name']?>">
+                        <?= strtolower($artist['city']['name'])?>
+                    </a>
+                </li>
+            </ul>
+			<?php endif; ?>
+            <?php $styles = $meta->get('top_styles');
+            if (!empty($styles)) {
+                ?>
+                <ul class="term-list style">
+                    <?php
+                    foreach ($styles as $style) {
+                        $term = get_term((int)$style, BASE.'style');
+                        if ($term && !is_wp_error($term)) {
+                            $link = get_term_link((int)$style, BASE.'style');
+                            ?>
+                            <li>
+                                <a href="<?=$link?>" title="Learn more about <?=html_entity_decode($term->name)?>">
+                                    <?=strtolower(html_entity_decode($term->name))?>
+                                </a>
+                            </li>
+                            <?php
+                        }
+                    }
+                    ?>
+                </ul>
+                <?php
+            }
+            ?>
+        </div>
+    </header>
+    <section>
+        <details class="bio-info">
+            <summary class="row x-btw">
+                <h2>About <?= ($artist['name'] !== '') ? $artist['name'] : strtok($artist['display_name'], ' ')?></h2>
+            </summary>
+            <div class="columns stack-small">
+                <div class="column">
+                    <?= Render::renderFrom($meta, 'image_portrait'); ?>
+                </div>
+                <div class="column">
+					<?= Render::renderFrom($meta, 'short_bio'); ?>
+                </div>
+            </div>
+            <div id="styles">
+                <h3>Works In</h3>
+                <?= jvbGetTheTerms('style', $current->ID) ?>
+            </div>
+            <div class="contact">
+                <h3>Contact:</h3>
+                <?php
+                echo jvbRenderContactInfo($current->ID, $meta);
+                echo jvbRenderLinks($current->ID, $meta);
+                ?>
+            </div>
+
+            <div id="about">
+				<?= Render::renderFrom($meta, 'bio')?>
+            </div>
+        </details>
+    </section>
+    <section id="contact" class="">
+        <h2>Contact <?=$artist['name']?></h2>
+        <?php
+        echo jvbRenderContactInfo($current->ID, 'post');
+        echo jvbRenderLinks($current->ID, 'post');
+        ?>
+    </section>
+    <?php
+    $finished = ob_get_clean();
+    $cache->set($key, $finished);
+    return $finished;
+}
+
+function jvbRenderShopSummary()
+{
+    $current = get_queried_object();
+
+    $cache = Cache::for('shop_bio', WEEK_IN_SECONDS)->connect('taxonomy');
+    $key = $current->term_id;
+    $cached = $cache->get($key);
+    if ($cached !== false) {
+        return $cached;
+    }
+
+    ob_start();
+
+    $meta = Meta::forTerm($current->term_id);
+	$fields = $meta->getAll(['average_rating', 'established', 'bio','location','hours','specialties','awards','reviews']);
+    ?>
+    <nav id="shop" class="on-this-page index">
+        <label>Jump to:
+            <button type="button" aria-label="Show Index" title="Show Index" class="toggle" aria-expanded="false">
+                <?= jvbIcon('plus-square')?>
+            </button>
+        </label>
+        <ul>
+            <li><a href="#top" title="Back to Top"><?=jvbIcon('caret-circle-up')?></a></li> <?php
+            if ($fields['rating'] !== 'none') {
+                ?>
+                <li><a href="#rating">Rating</a></li>
+                <?php
+            } elseif ($fields['opened'] !== '') {
+                ?>
+                <li><a href="#opened">Opened</a></li>
+                <?php
+            } elseif ($fields['location'] !== '') {
+                ?>
+                <li><a href="#location">Location</a></li>
+                <?php
+            } elseif ($fields['about'] !== '') {
+                ?>
+                <li><a href="#about">About</a></li>
+                <?php
+            } elseif ($fields['hours'] !== '') {
+                ?>
+                <li><a href="#hours">Hours</a></li>
+                <?php
+            } elseif ($fields['specialties'] !== '') {
+                ?>
+                <li><a href="#specialties">Specialties</a></li>
+                <?php
+            } elseif ($fields['awards'] !== '') {
+                ?>
+                <li><a href="#awards">Awards</a></li>
+                <?php
+            } elseif ($fields['reviews'] !== '') {
+                ?>
+                <li><a href="#reviews">Reviews</a></li>
+                <?php
+            }
+            ?>
+            <li><a href="#contact">Contact</a></li>
+            <li><a href="#artists">Artists</a></li>
+        </ul>
+    </nav>
+    <header id="top">
+        <div class="columns stack-small">
+            <div class="column">
+                <?=jvbFormatImage($meta->get('image'))?>
+            </div>
+            <div class="column">
+                <h1>
+                    <small><?= (get_term((int)$meta->get('city'), BASE.'city')) ?
+                            get_term((int)$meta->get('city'), BASE.'city')->name :
+                            'Edmonton'?>'s Best Tattoo Shops</small>
+                    <?=$current->name?>
+                </h1>
+                <?= jvbFormatRating($current->term_id, 'term') ?>
+				<?= Render::renderFrom($meta,   'slogan'); ?>
+            </div>
+        </div>
+    </header>
+    <section>
+        <details class="bio-info">
+            <summary class="row x-btw">
+                <h2>Learn More About <?=$current->name?></h2>
+            </summary>
+            <div class="map">
+				<?= Render::renderFrom($meta, 'location'); ?>
+            </div>
+            <div class="short-bio">
+				<?= Render::renderFrom($meta, 'short_bio'); ?>
+            </div>
+
+            <div class="contact">
+                <h3>Contact:</h3>
+                <?php
+                echo jvbRenderContactInfo($current->term_id, 'term');
+                echo jvbRenderLinks($current->term_id, 'term');
+                ?>
+            </div>
+
+            <div id="about">
+				<?= Render::renderFrom($meta, 'bio')?>
+            </div>
+        </details>
+    </section>
+    <section id="contact" class="">
+        <h2>Contact </h2>
+        <?php
+        echo jvbRenderContactInfo($current->term_id, 'term');
+        echo jvbRenderLinks($current->term_id, 'term');
+        ?>
+    </section>
+    <?= jvbRenderHours($current->term_id, 'term')?>
+
+
+    <?php
+    $finished = ob_get_clean();
+    $cache->set($key, $finished);
+    return $finished;
+}
+
+
+function jvbRenderTermSummary()
+{
+    $current = get_queried_object();
+    $cache = Cache::for('term_summary', WEEK_IN_SECONDS)->connect('taxonomy');
+    $key = $current->ID;
+    $cached = $cache->get($key);
+    if ($cached !== false) {
+        return $cached;
+    }
+
+    ob_start();
+	$tax = jvbNoBase($current->taxonomy);
+    switch ($tax) {
+        case 'style':
+            $title = 'Tattoo Artists';
+            break;
+        case 'theme':
+            $title = 'Tattoos';
+            break;
+        default:
+            $title = '';
+    }
+
+    $meta = Meta::forTerm($current->ID);
+    $fields = $meta->getAll();
+
+    ?>
+    <header id="top">
+        <h1><?= get_the_archive_title() ?></h1>
+    </header>
+
+    <?php
+    $finished = ob_get_clean();
+    $cache->set($key, $finished);
+    return $finished;
+}
diff --git a/src/summary/save.js b/src/summary/save.js
new file mode 100644
index 0000000..8169594
--- /dev/null
+++ b/src/summary/save.js
@@ -0,0 +1,3 @@
+export default function save() {
+    return null; // Dynamic block rendered by PHP
+}
diff --git a/src/summary/style.scss b/src/summary/style.scss
new file mode 100644
index 0000000..b182fe9
--- /dev/null
+++ b/src/summary/style.scss
@@ -0,0 +1,20 @@
+details > div {
+	margin: 1rem 0;
+}
+
+main > header:not(:has(img)) {
+	margin-top: 3rem!important;
+}
+
+header a::before {
+	display: none!important;
+}
+
+header + details {
+	margin: 1.5rem auto 3rem!important;
+	max-width: var(--wide);
+}
+
+main {
+	padding-top: 0!important;
+}
diff --git a/src/summary/view.js b/src/summary/view.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/summary/view.js
@@ -0,0 +1 @@
+
diff --git a/src/timeline/block.json b/src/timeline/block.json
new file mode 100644
index 0000000..9e219a1
--- /dev/null
+++ b/src/timeline/block.json
@@ -0,0 +1,23 @@
+{
+	"$schema": "https://schemas.wp.org/trunk/block.json",
+	"apiVersion": 3,
+	"name": "jvb/timeline",
+	"version": "0.1.0",
+	"title": "Timeline",
+	"category": "jvb",
+	"icon": "shortcode",
+	"description": "Outputs a single timeline post in a cool way",
+	"example": {},
+	"supports": {
+		"html": false,
+		"align": ["wide", "full"]
+	},
+	"textdomain": "jvb",
+	"selectors": {
+		"root": ".timeline"
+	},
+	"editorScript": "file:./index.js",
+	"editorStyle": "file:./index.css",
+	"style": "file:./style-index.css",
+	"viewScript": "file:./view.js"
+}
diff --git a/src/timeline/edit.js b/src/timeline/edit.js
new file mode 100644
index 0000000..14f0ce6
--- /dev/null
+++ b/src/timeline/edit.js
@@ -0,0 +1,38 @@
+/**
+ * Retrieves the translation of text.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/
+ */
+import { __ } from '@wordpress/i18n';
+
+/**
+ * React hook that is used to mark the block wrapper element.
+ * It provides all the necessary props like the class name.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops
+ */
+import { useBlockProps } from '@wordpress/block-editor';
+
+/**
+ * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
+ * Those files can contain any CSS code that gets applied to the editor.
+ *
+ * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
+ */
+import './editor.scss';
+
+/**
+ * The edit function describes the structure of your block in the context of the
+ * editor. This represents what the editor will render when the block is used.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit
+ *
+ * @return {Element} Element to render.
+ */
+export default function Edit() {
+	return (
+		<p { ...useBlockProps() }>
+			{ __( 'Will output the timeline', 'jvb' ) }
+		</p>
+	);
+}
diff --git a/src/timeline/editor.scss b/src/timeline/editor.scss
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/timeline/editor.scss
diff --git a/src/timeline/index.js b/src/timeline/index.js
new file mode 100644
index 0000000..d82621b
--- /dev/null
+++ b/src/timeline/index.js
@@ -0,0 +1,33 @@
+/**
+ * Registers a new block provided a unique name and an object defining its behavior.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+import { registerBlockType } from '@wordpress/blocks';
+
+/**
+ * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
+ * All files containing `style` keyword are bundled together. The code used
+ * gets applied both to the front of your site and to the editor.
+ *
+ * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
+ */
+import './style.scss';
+
+/**
+ * Internal dependencies
+ */
+import Edit from './edit';
+import metadata from './block.json';
+
+/**
+ * Every block starts by registering a new block type definition.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
+ */
+registerBlockType( metadata.name, {
+	/**
+	 * @see ./edit.js
+	 */
+	edit: Edit,
+} );
diff --git a/src/timeline/index.php b/src/timeline/index.php
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/timeline/index.php
diff --git a/src/timeline/render.php b/src/timeline/render.php
new file mode 100644
index 0000000..52aded4
--- /dev/null
+++ b/src/timeline/render.php
@@ -0,0 +1,8 @@
+<?php
+/**
+ * @see https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/block-api/block-metadata.md#render
+ */
+?>
+<p <?php echo get_block_wrapper_attributes(); ?>>
+	<?php esc_html_e( 'Menu – hello from a dynamic block!', 'menu' ); ?>
+</p>
diff --git a/src/timeline/style.scss b/src/timeline/style.scss
new file mode 100644
index 0000000..ea20261
--- /dev/null
+++ b/src/timeline/style.scss
@@ -0,0 +1,135 @@
+main {
+	--gap: 0;
+	section:last-of-type {
+		margin-bottom: 0;
+	}
+}
+
+#at-a-glance {
+	padding: 0 10vw;
+	--gap: 0;
+	img {
+		width: 100%;
+		height: auto;
+		border: 2px solid var(--action-0);
+	}
+	h3 {
+		font-size: var(--txt-x-small);
+	}
+	.before {
+		img {
+			border-right-width: 1px;
+			border-left: 0;
+			border-top: 0;
+		}
+	}
+	.after {
+		img {
+			border-left-width: 1px;
+			border-right: 0;
+			border-bottom: 0;
+		}
+	}
+}
+
+.timeline-point.timeline-point {
+	--lineWidth: 1px;
+	--gap: 2rem;
+	padding: 0;
+	margin:0;
+	background-color: var(--base);
+	max-width: 100vw;
+	position: relative;
+	overflow: hidden;
+	img {
+		width: 40%;
+		border-radius: 4px;
+		position: sticky;
+		padding: .5rem;
+	}
+	.info {
+		padding: 1rem .5rem .5rem;
+		width: 60%;
+		position: relative;
+		h2 {
+			margin: 0 0 .5rem;
+			font-size: var(--txt-medium);
+			position: relative;
+			.icon {
+				--w: 2.5rem;
+				transform: rotate(-90deg);
+				position: absolute;
+				left: -2.5rem;
+				top: .25rem;
+				background-color: var(--action-100);
+			}
+		}
+
+	}
+	&::before,
+	&::after {
+		content: '';
+		display: block;
+		position: absolute;
+		left: 45%;
+		height: 100%;
+		width: var(--lineWidth);
+		background-color: var(--action-0);
+		//box-shadow: var(--action-shadow);
+	}
+	&::before {
+		height: 1rem;
+	}
+	&::after {
+		top: 4rem;
+	}
+	&#before-treatment::before,
+	&:last-of-type::after {
+		display: none;
+	}
+}
+@media (min-width:768px) {
+	#at-a-glance {
+		h3  {
+			font-size: var(--txt-x-large);
+		}
+	}
+	.timeline-point.timeline-point {
+		--gap: 4rem;
+		img {
+			width: 50%;
+		}
+		.info {
+			width: 50%;
+			padding: 25vh 1rem 1rem;
+			h2 {
+				.icon {
+					--w: 4rem;
+					left: -6.15rem;
+					top: 0;
+				}
+			}
+			a {
+				display: flex;
+				flex-wrap: wrap;
+				align-items: center;
+			}
+
+			time {
+				text-transform: uppercase;
+				font-size: var(--txt-x-small);
+			}
+		}
+		&::before,
+		&::after {
+			left: calc(50% + 2rem);
+		}
+
+		&::before {
+			height: calc(25vh - 2rem);
+		}
+		&::after {
+			top: calc(25vh + 6rem);
+		}
+	}
+}
diff --git a/src/timeline/view.js b/src/timeline/view.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/timeline/view.js
diff --git a/src/video/block.json b/src/video/block.json
new file mode 100644
index 0000000..29149bf
--- /dev/null
+++ b/src/video/block.json
@@ -0,0 +1,79 @@
+{
+	"$schema": "https://schemas.wp.org/trunk/block.json",
+	"apiVersion": 3,
+	"name": "jvb/video",
+	"version": "1.0.0",
+	"title": "Video Cover",
+	"category": "jvb",
+	"icon": "video-alt3",
+	"description": "Self-hosted video cover with poster and multiple format support",
+	"supports": {
+		"html": false,
+		"align": ["wide", "full"],
+		"spacing": {
+			"margin": true,
+			"padding": true
+		},
+		"color": {
+			"background": true,
+			"text": true
+		}
+	},
+	"attributes": {
+		"title": {
+			"type": "string",
+			"default": ""
+		},
+		"description": {
+			"type": "rich-text",
+			"default": ""
+		},
+		"posterId": {
+			"type": "number",
+			"default": 0
+		},
+		"posterUrl": {
+			"type": "string",
+			"default": ""
+		},
+		"videoSources": {
+			"type": "array",
+			"default": [],
+			"items": {
+				"type": "object",
+				"properties": {
+					"id": {
+						"type": "number"
+					},
+					"url": {
+						"type": "string"
+					},
+					"mime": {
+						"type": "string"
+					}
+				}
+			}
+		},
+		"fadeEffect": {
+			"type": "boolean",
+			"default": false
+		},
+		"overlayOpacity": {
+			"type": "number",
+			"default": 0
+		},
+		"contentAlignment": {
+			"type": "string",
+			"default": "center"
+		},
+		"minHeight": {
+			"type": "number",
+			"default": 0
+		}
+	},
+	"textdomain": "jvb",
+	"editorScript": "file:./index.js",
+	"viewScript": "file:./view.js",
+	"editorStyle": "file:./index.css",
+	"style": "file:./style-index.css"
+}
diff --git a/src/video/edit.js b/src/video/edit.js
new file mode 100644
index 0000000..9830e65
--- /dev/null
+++ b/src/video/edit.js
@@ -0,0 +1,276 @@
+//edit.js
+import { __ } from '@wordpress/i18n';
+import {
+	useBlockProps,
+	InspectorControls,
+	MediaUpload,
+	MediaUploadCheck,
+	InnerBlocks,
+	useInnerBlocksProps
+} from '@wordpress/block-editor';
+import {
+	PanelBody,
+	Button,
+	ToggleControl,
+	BaseControl,
+	RangeControl,
+	SelectControl
+} from '@wordpress/components';
+import './editor.scss';
+
+const ALLOWED_VIDEO_TYPES = ['video'];
+
+const INNER_BLOCKS_TEMPLATE = [
+	['core/heading', {
+		level: 1,
+		placeholder: 'Add heading...',
+		textAlign: 'center'
+	}],
+	['core/paragraph', {
+		placeholder: 'Add description...',
+		align: 'center'
+	}],
+	['core/buttons', {
+		layout: { type: 'flex', justifyContent: 'center' }
+	}]
+];
+
+
+
+export default function Edit({ attributes, setAttributes }) {
+	const {
+		posterId,
+		posterUrl,
+		videoSources,
+		fadeEffect,
+		overlayOpacity,
+		contentAlignment,
+		minHeight
+	} = attributes;
+
+	const blockProps = useBlockProps({
+		className: 'video-cover-editor',
+		style: {
+			minHeight: minHeight ? `${minHeight}px` : undefined
+		}
+	});
+
+	const innerBlocksProps = useInnerBlocksProps(
+		{ className: 'video-cover-content' },
+		{
+			template: INNER_BLOCKS_TEMPLATE,
+			templateLock: false
+		}
+	);
+
+	const onSelectPoster = (media) => {
+		setAttributes({
+			posterId: media.id,
+			posterUrl: media.url
+		});
+	};
+
+	const onSelectVideos = (mediaItems) => {
+		// multiple=true returns an array
+		const items = Array.isArray(mediaItems) ? mediaItems : [mediaItems];
+		const newSources = items
+			.filter(media => !videoSources.some(s => s.id === media.id))
+			.map(media => ({
+				id: media.id,
+				url: media.url,
+				mime: media.mime
+			}));
+
+		if (newSources.length) {
+			setAttributes({
+				videoSources: [...videoSources, ...newSources]
+			});
+		}
+	};
+
+	const removeVideoSource = (index) => {
+		const updated = [...videoSources];
+		updated.splice(index, 1);
+		setAttributes({ videoSources: updated });
+	};
+
+	const renderVideoSourceList = (sources, isMobile = false) => {
+		if (sources.length === 0) return null;
+
+		return (
+			<ul className="video-source-list">
+				{sources.map((source, index) => (
+					<li key={index} className="video-source-item">
+						<span className="video-source-mime">{source.mime}</span>
+						<Button
+							isDestructive
+							isSmall
+							onClick={() => removeVideoSource(index, isMobile)}
+						>
+							{__('Remove', 'jvb')}
+						</Button>
+					</li>
+				))}
+			</ul>
+		);
+	};
+
+	return (
+		<>
+			<InspectorControls>
+				<PanelBody title={__('Video Settings', 'jvb')} initialOpen={true}>
+					<BaseControl
+						label={__('Poster Image', 'jvb')}
+						help={__('Image shown while video loads', 'jvb')}
+					>
+						<MediaUploadCheck>
+							<MediaUpload
+								onSelect={onSelectPoster}
+								allowedTypes={['image']}
+								value={posterId}
+								render={({ open }) => (
+									<>
+										{posterUrl && (
+											<img
+												src={posterUrl}
+												alt={__('Poster preview', 'jvb')}
+												style={{ maxWidth: '100%', marginBottom: '10px' }}
+											/>
+										)}
+										<Button
+											onClick={open}
+											variant={posterUrl ? 'secondary' : 'primary'}
+										>
+											{posterUrl
+												? __('Change Poster', 'jvb')
+												: __('Select Poster', 'jvb')}
+										</Button>
+										{posterUrl && (
+											<Button
+												isDestructive
+												onClick={() => setAttributes({ posterId: 0, posterUrl: '' })}
+												style={{ marginLeft: '10px' }}
+											>
+												{__('Remove', 'jvb')}
+											</Button>
+										)}
+									</>
+								)}
+							/>
+						</MediaUploadCheck>
+					</BaseControl>
+
+					<BaseControl
+						label={__('Video Sources', 'jvb')}
+						help={__('Add multiple formats for better browser support (mp4, webm, etc.)', 'jvb')}
+					>
+						{videoSources.length > 0 && (
+							<ul className="video-source-list">
+								{videoSources.map((source, index) => (
+									<li key={index} className="video-source-item">
+										<span className="video-source-mime">{source.mime}</span>
+										<Button
+											isDestructive
+											isSmall
+											onClick={() => removeVideoSource(index)}
+										>
+											{__('Remove', 'jvb')}
+										</Button>
+									</li>
+								))}
+							</ul>
+						)}
+						<MediaUploadCheck>
+							<MediaUpload
+								multiple={true}
+								onSelect={onSelectVideos}
+								allowedTypes={ALLOWED_VIDEO_TYPES}
+								render={({ open }) => (
+									<Button onClick={open} variant="secondary">
+										{__('Add Video', 'jvb')}
+									</Button>
+								)}
+							/>
+						</MediaUploadCheck>
+					</BaseControl>
+
+					<ToggleControl
+						label={__('Fade Effect', 'jvb')}
+						help={__('Add fade class to video element', 'jvb')}
+						checked={fadeEffect}
+						onChange={(value) => setAttributes({ fadeEffect: value })}
+					/>
+				</PanelBody>
+
+				<PanelBody title={__('Overlay Settings', 'jvb')} initialOpen={true}>
+					<RangeControl
+						label={__('Overlay Opacity', 'jvb')}
+						help={__('Darken video for better text readability', 'jvb')}
+						value={overlayOpacity}
+						onChange={(value) => setAttributes({ overlayOpacity: value })}
+						min={0}
+						max={100}
+						step={5}
+					/>
+
+					<SelectControl
+						label={__('Content Alignment', 'jvb')}
+						value={contentAlignment}
+						options={[
+							{ label: __('Top Left', 'jvb'), value: 'top-left' },
+							{ label: __('Top Center', 'jvb'), value: 'top-center' },
+							{ label: __('Top Right', 'jvb'), value: 'top-right' },
+							{ label: __('Center Left', 'jvb'), value: 'center-left' },
+							{ label: __('Center', 'jvb'), value: 'center' },
+							{ label: __('Center Right', 'jvb'), value: 'center-right' },
+							{ label: __('Bottom Left', 'jvb'), value: 'bottom-left' },
+							{ label: __('Bottom Center', 'jvb'), value: 'bottom-center' },
+							{ label: __('Bottom Right', 'jvb'), value: 'bottom-right' }
+						]}
+						onChange={(value) => setAttributes({ contentAlignment: value })}
+					/>
+
+					<RangeControl
+						label={__('Minimum Height', 'jvb')}
+						help={__('Minimum height in pixels (leave 0 for auto)', 'jvb')}
+						value={minHeight}
+						onChange={(value) => setAttributes({ minHeight: value })}
+						min={0}
+						max={1000}
+						step={50}
+					/>
+				</PanelBody>
+			</InspectorControls>
+
+			<div {...blockProps}>
+				{posterUrl || videoSources.length > 0 ? (
+					<div className="video-cover-preview">
+						{posterUrl && (
+							<>
+								<img src={posterUrl} alt={__('Video poster', 'jvb')} />
+								{overlayOpacity > 0 && (
+									<div
+										className="video-overlay-preview"
+										style={{ opacity: overlayOpacity / 100 }}
+									/>
+								)}
+							</>
+						)}
+						<div className={`video-cover-content-preview align-${contentAlignment}`}>
+							<div {...innerBlocksProps} />
+						</div>
+						<div className="video-info">
+							<p>
+								{videoSources.length} {__('desktop source(s)', 'jvb')}
+							</p>
+						</div>
+					</div>
+				) : (
+					<div className="video-cover-placeholder">
+						<p>{__('Configure video sources in the sidebar →', 'jvb')}</p>
+					</div>
+				)}
+			</div>
+		</>
+	);
+}
diff --git a/src/video/editor.scss b/src/video/editor.scss
new file mode 100644
index 0000000..afc9f13
--- /dev/null
+++ b/src/video/editor.scss
@@ -0,0 +1,141 @@
+/* editor.scss */
+.video-cover-editor {
+	position: relative;
+	min-height: 200px;
+	background: #f0f0f0;
+	border: 2px dashed #ccc;
+	border-radius: 4px;
+
+	.video-cover-preview {
+		position: relative;
+		width: 100%;
+		min-height: 300px;
+
+		img {
+			width: 100%;
+			height: auto;
+			display: block;
+		}
+
+		.video-overlay-preview {
+			position: absolute;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			background: rgba(0, 0, 0, 1);
+			pointer-events: none;
+		}
+
+		.video-cover-content-preview {
+			position: absolute;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			display: flex;
+			z-index: 2;
+			padding: 2rem;
+
+			// Content alignment classes
+			&.align-top-left {
+				align-items: flex-start;
+				justify-content: flex-start;
+			}
+			&.align-top-center {
+				align-items: flex-start;
+				justify-content: center;
+			}
+			&.align-top-right {
+				align-items: flex-start;
+				justify-content: flex-end;
+			}
+			&.align-center-left {
+				align-items: center;
+				justify-content: flex-start;
+			}
+			&.align-center {
+				align-items: center;
+				justify-content: center;
+			}
+			&.align-center-right {
+				align-items: center;
+				justify-content: flex-end;
+			}
+			&.align-bottom-left {
+				align-items: flex-end;
+				justify-content: flex-start;
+			}
+			&.align-bottom-center {
+				align-items: flex-end;
+				justify-content: center;
+			}
+			&.align-bottom-right {
+				align-items: flex-end;
+				justify-content: flex-end;
+			}
+
+			.video-cover-content {
+				width: 100%;
+				max-width: 1200px;
+				color: white;
+				text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
+
+				// Style inner blocks for better visibility in editor
+				h1, h2, h3, h4, h5, h6 {
+					color: white;
+				}
+
+				p {
+					color: white;
+				}
+			}
+		}
+
+		.video-info {
+			position: absolute;
+			bottom: 0;
+			left: 0;
+			right: 0;
+			background: rgba(0, 0, 0, 0.7);
+			color: white;
+			padding: 10px;
+			font-size: 14px;
+			z-index: 3;
+
+			p {
+				margin: 0;
+			}
+		}
+	}
+
+	.video-cover-placeholder {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		min-height: 200px;
+		color: #666;
+		font-size: 16px;
+	}
+}
+
+.video-source-list {
+	list-style: none;
+	margin: 10px 0;
+	padding: 0;
+
+	.video-source-item {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 8px 12px;
+		background: #f5f5f5;
+		border-radius: 4px;
+		margin-bottom: 5px;
+
+		.video-source-mime {
+			font-family: monospace;
+			font-size: 13px;
+		}
+	}
+}
diff --git a/src/video/index.js b/src/video/index.js
new file mode 100644
index 0000000..2f9cf8b
--- /dev/null
+++ b/src/video/index.js
@@ -0,0 +1,21 @@
+/* index.js */
+import { registerBlockType } from '@wordpress/blocks';
+import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
+import './style.scss';
+import Edit from './edit';
+import metadata from './block.json';
+
+registerBlockType(metadata.name, {
+	edit: Edit,
+	save: ({ attributes }) => {
+		const blockProps = useBlockProps.save({
+			className: 'video-cover-wrapper-placeholder'
+		});
+
+		return (
+			<div {...blockProps}>
+				<InnerBlocks.Content />
+			</div>
+		);
+	}
+});
diff --git a/src/video/index.php b/src/video/index.php
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/video/index.php
diff --git a/src/video/render.php b/src/video/render.php
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/src/video/render.php
@@ -0,0 +1 @@
+<?php
diff --git a/src/video/save.js b/src/video/save.js
new file mode 100644
index 0000000..da66ed1
--- /dev/null
+++ b/src/video/save.js
@@ -0,0 +1,23 @@
+/**
+ * save.js
+ * React hook that is used to mark the block wrapper element.
+ * It provides all the necessary props like the class name.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops
+ */
+import { useBlockProps } from '@wordpress/block-editor';
+
+/**
+ * The save function defines the way in which the different attributes should
+ * be combined into the final markup, which is then serialized by the block
+ * editor into `post_content`.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#save
+ *
+ * @return {WPElement} Element to render.
+ */
+export default function save() {
+    // This is a dynamic block that is rendered on the server side
+    // Return null to let WordPress handle the saving and rendering
+    return null;
+}
diff --git a/src/video/style.scss b/src/video/style.scss
new file mode 100644
index 0000000..d16e7d9
--- /dev/null
+++ b/src/video/style.scss
@@ -0,0 +1,178 @@
+/** style.scss **/
+.video-cover {
+	position: relative;
+	width: 100%;
+	min-height: 75vh;
+	overflow: hidden;
+	display: flex;
+	.wrap {
+		background-color: var(--contrast-200);
+		//&::before {
+		//	position: absolute;
+		//	top: 0;
+		//	bottom: 0;
+		//	left: 0;
+		//	right: 0;
+		//	background-color: var(--base);
+		//	mix-blend-mode: lighten;
+		//	content: '';
+		//	z-index: 1;
+		//}
+	}
+	/* Video background */
+	.video-container {
+		position: absolute;
+		top: 0;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		min-width: 100%;
+		min-height: 100%;
+		z-index: 0;
+		display: flex;
+		background-color: var(--action-50);
+
+		&.fade {
+			animation: fadeIn 1s ease-in;
+		}
+
+		video {
+			pointer-events: none;
+			opacity: .85;
+			mix-blend-mode: multiply;
+			filter: grayscale(100%) contrast(1);
+			flex: 1 0 100%;
+			object-fit: cover;
+		}
+	}
+
+	.inner-wrap {
+		position: relative;
+		z-index: 2;
+		width: 100%;
+		padding: 2rem;
+		color: var(--action-contrast);
+
+		/* Better text readability */
+		h1, h2, h3, h4, h5, h6 {
+			word-spacing: 100vw;
+			color: var(--action-contrast);
+			text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
+			margin: 2rem 0 0;
+		}
+
+		p {
+			text-transform: uppercase;
+			letter-spacing: 2px;
+			margin: 0;
+			color: var(--action-contrast);
+			text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
+		}
+
+		.media-text {
+		}
+			.media-text figure {
+				max-width: 50%;
+			}
+			@media (min-width: 768px) {
+				.media-text {
+					--align: flex-start;
+					gap: 3rem;
+					max-width: var(--content);
+				}
+			}
+			.media-text > div {
+				width: fit-content;
+			}
+		.buttons a {
+			font-weight: var(--fw-h-bold);
+			color: var(--action-contrast);
+			border-color: var(--action-contrast);
+			&:visited {
+				color: var(--action-contrast);
+				&:hover {
+					color: var(--action-contrast);
+				}
+			}
+			&:hover {
+				background-color: var(--action-0);
+				color: var(--action-contrast);
+			}
+		}
+
+		.outline a {
+			background-color: rgba(var(--base-rgb), rgba(var(--base-rgb),var(--op-3)));
+		}
+		.buttons {
+			margin: 3rem 0;
+			li {
+				background-color: rgba(var(--action-rgb), var(--op-4));
+			}
+		}
+		/* Button styles */
+		.wp-block-button__link {
+			text-shadow: none;
+		}
+	}
+
+	/* Alignment classes */
+	&.align-top-left {
+		align-items: flex-start;
+		justify-content: flex-start;
+	}
+	&.align-top-center {
+		align-items: flex-start;
+		justify-content: center;
+	}
+	&.align-top-right {
+		align-items: flex-start;
+		justify-content: flex-end;
+	}
+	&.align-center-left {
+		align-items: center;
+		justify-content: flex-start;
+	}
+	&.align-center {
+		align-items: center;
+		justify-content: center;
+	}
+	&.align-center-right {
+		align-items: center;
+		justify-content: flex-end;
+	}
+	&.align-bottom-left {
+		align-items: flex-end;
+		justify-content: flex-start;
+	}
+	&.align-bottom-center {
+		align-items: flex-end;
+		justify-content: center;
+	}
+	&.align-bottom-right {
+		align-items: flex-end;
+		justify-content: flex-end;
+	}
+
+	/* Full-width alignment  */
+	&.alignfull {
+		width: 100vw;
+		max-width: none;
+		margin-left: calc(50% - 50vw);
+		margin-right: calc(50% - 50vw);
+	}
+
+	/* Wide alignment */
+	&.alignwide {
+		max-width: 1200px;
+	}
+}
+
+@keyframes fadeIn {
+	from {
+		opacity: 0;
+	}
+	to {
+		opacity: 1;
+	}
+}
+
diff --git a/src/video/view.js b/src/video/view.js
new file mode 100644
index 0000000..76bcd1a
--- /dev/null
+++ b/src/video/view.js
@@ -0,0 +1,47 @@
+/** view.js **/
+document.addEventListener("DOMContentLoaded", function () {
+	const lazyVideos = [].slice.call(
+		document.querySelectorAll(".video-container video")
+	);
+
+	// Build a helper to actually set sources + load
+	function loadVideo(video) {
+		const sources = video.querySelectorAll("source[data-src]");
+		sources.forEach(source => {
+			source.src = source.dataset.src;
+		});
+		video.load();
+	}
+
+	// --- 1. IntersectionObserver (best case) ---
+	if ("IntersectionObserver" in window) {
+		const lazyVideoObserver = new IntersectionObserver(
+			function (entries, observer) {
+				entries.forEach(entry => {
+					if (entry.isIntersecting) {
+						loadVideo(entry.target);
+						observer.unobserve(entry.target);
+					}
+				});
+			},
+			{
+				rootMargin: "200px 0px",
+				threshold: 0.1,
+			}
+		);
+
+		lazyVideos.forEach(video => lazyVideoObserver.observe(video));
+		return;
+	}
+
+	// --- 2. Fallback: requestIdleCallback ---
+	if ("requestIdleCallback" in window) {
+		requestIdleCallback(() => {
+			lazyVideos.forEach(video => loadVideo(video));
+		});
+		return;
+	}
+
+	// --- 3. Final fallback: load immediately ---
+	lazyVideos.forEach(video => loadVideo(video));
+});
diff --git a/templates/dashboard/sections/news.php b/templates/dashboard/sections/news.php
index 29e7757..47e1148 100644
--- a/templates/dashboard/sections/news.php
+++ b/templates/dashboard/sections/news.php
@@ -118,7 +118,7 @@
     </div>
 
     <details class="type-filters">
-        <summary class="row btw">Filters:
+        <summary class="row x-btw">Filters:
             <button class="clear-filters row">
                 <?= jvbIcon('x', ['title' => 'Clear Filters'])?>
                 <span>Clear Filters</span>
@@ -274,7 +274,7 @@
 </dialog>
 <template class="template-own">
     <details class="news item" data-keyboard-nav="true" tabindex="0">
-        <summary class="row btw">
+        <summary class="row x-btw">
             <div class="item-select">
                 <input type="checkbox" class="select-checkbox">
                 <label>
@@ -317,7 +317,7 @@
 </template>
 <template class="template-all template-watching">
     <details class="news item" data-keyboard-nav="true" tabindex="0">
-        <summary class="row btw">
+        <summary class="row x-btw">
             <button class="favourite-button" data-type="news" title="Add to watch list" onclick="toggleFavourite(this)">
 
             </button>
diff --git a/templates/dashboard/sections/notifications.php b/templates/dashboard/sections/notifications.php
index 8969360..18b672a 100644
--- a/templates/dashboard/sections/notifications.php
+++ b/templates/dashboard/sections/notifications.php
@@ -29,7 +29,7 @@
 
 ?>
 <div class="container">
-    <nav class="tabs row start" role="tablist">
+    <nav class="tabs row left" role="tablist">
     <?php
 
     $i =0;
diff --git a/webpack.jvb.js b/webpack.jvb.js
index 7995231..38b5d04 100644
--- a/webpack.jvb.js
+++ b/webpack.jvb.js
@@ -36,13 +36,13 @@
 		'quill':               './assets/js/concise/quill.js',
 		'queue':               './assets/js/concise/Queue.js',
 		'referral':            './assets/js/concise/Referral.js',
-		'referralAdmin':            './assets/js/concise/ReferralAdmin.js',
+		'referralAdmin':       './assets/js/concise/ReferralAdmin.js',
 		'shopManager':         './assets/js/concise/ShopManager.js',
 		'cache':               './assets/js/concise/SimpleCache.js',
 		'schema':              './assets/js/concise/SchemaManager.js',
 		'square':              './assets/js/concise/CheckoutSquare.js',
 		'helcim':              './assets/js/concise/CheckoutHelcim.js',
-		'checkout':              './assets/js/concise/Checkout.js',
+		'checkout':            './assets/js/concise/Checkout.js',
 		'tabs':                './assets/js/concise/Tabs.js',
 		'creator':             './assets/js/concise/TaxonomyCreator.js',
 		'selector':            './assets/js/concise/TaxonomySelector.js',

--
Gitblit v1.10.0