diff --git a/shared b/shared
index 65c4989..322ae48 160000
--- a/shared
+++ b/shared
@@ -1 +1 @@
-Subproject commit 65c4989d08b85a821a0932e4376f2ab088f50d0c
+Subproject commit 322ae481eeb47418245e669c3585f6d2195656da
diff --git a/templates/_types/auth/header/_header.html b/templates/_types/auth/header/_header.html
new file mode 100644
index 0000000..c59a712
--- /dev/null
+++ b/templates/_types/auth/header/_header.html
@@ -0,0 +1,12 @@
+{% import 'macros/links.html' as links %}
+{% macro header_row(oob=False) %}
+ {% call links.menu_row(id='auth-row', oob=oob) %}
+ {% call links.link(account_url('/'), hx_select_search ) %}
+
+
account
+ {% endcall %}
+ {% call links.desktop_nav() %}
+ {% include "_types/auth/_nav.html" %}
+ {% endcall %}
+ {% endcall %}
+{% endmacro %}
\ No newline at end of file
diff --git a/templates/_types/auth/index.html b/templates/_types/auth/index.html
new file mode 100644
index 0000000..3c66bf1
--- /dev/null
+++ b/templates/_types/auth/index.html
@@ -0,0 +1,18 @@
+{% extends oob.extends %}
+
+
+{% block root_header_child %}
+ {% from '_types/root/_n/macros.html' import index_row with context %}
+ {% call index_row(oob.child_id, oob.header) %}
+ {% block auth_header_child %}
+ {% endblock %}
+ {% endcall %}
+{% endblock %}
+
+{% block _main_mobile_menu %}
+ {% include oob.nav %}
+{% endblock %}
+
+{% block content %}
+ {% include oob.main %}
+{% endblock %}
diff --git a/templates/_types/cart/_main_panel.html b/templates/_types/cart/_main_panel.html
new file mode 100644
index 0000000..3872387
--- /dev/null
+++ b/templates/_types/cart/_main_panel.html
@@ -0,0 +1,4 @@
+
+ {% from '_types/cart/_cart.html' import show_cart with context %}
+ {{ show_cart() }}
+
\ No newline at end of file
diff --git a/templates/_types/cart/_mini.html b/templates/_types/cart/_mini.html
new file mode 100644
index 0000000..a8255e4
--- /dev/null
+++ b/templates/_types/cart/_mini.html
@@ -0,0 +1,45 @@
+{% macro mini(oob=False, count=None) %}
+
+ {# cart_count is set by the context processor in all apps.
+ Cart app computes it from g.cart + calendar_cart_entries;
+ other apps get it from the cart internal API.
+ count param allows explicit override when macro is imported without context. #}
+ {% if count is not none %}
+ {% set _count = count %}
+ {% elif cart_count is defined and cart_count is not none %}
+ {% set _count = cart_count %}
+ {% elif cart is defined and cart is not none %}
+ {% set _count = (cart | sum(attribute="quantity")) + ((calendar_cart_entries | length) if calendar_cart_entries else 0) %}
+ {% else %}
+ {% set _count = 0 %}
+ {% endif %}
+
+ {% if _count == 0 %}
+
+ {% else %}
+
+
+
+
+
+ {{ _count }}
+
+
+ {% endif %}
+
+{% endmacro %}
diff --git a/templates/_types/cart/_nav.html b/templates/_types/cart/_nav.html
new file mode 100644
index 0000000..f5c504d
--- /dev/null
+++ b/templates/_types/cart/_nav.html
@@ -0,0 +1,2 @@
+{% from 'macros/admin_nav.html' import placeholder_nav %}
+{{ placeholder_nav() }}
diff --git a/templates/_types/cart/_oob_elements.html b/templates/_types/cart/_oob_elements.html
new file mode 100644
index 0000000..6e54a8b
--- /dev/null
+++ b/templates/_types/cart/_oob_elements.html
@@ -0,0 +1,28 @@
+{% extends 'oob_elements.html' %}
+
+{# OOB elements for HTMX navigation - all elements that need updating #}
+
+{% from '_types/root/_oob_menu.html' import mobile_menu with context %}
+
+{# Header with app title - includes cart-mini, navigation, and market-specific header #}
+
+{% block oobs %}
+
+ {% from '_types/root/_n/macros.html' import oob_header with context %}
+ {{oob_header('root-header-child', 'cart-header-child', '_types/cart/header/_header.html')}}
+
+ {% from '_types/root/header/_header.html' import header_row with context %}
+ {{ header_row(oob=True) }}
+{% endblock %}
+
+
+{% block mobile_menu %}
+ {% include '_types/cart/_nav.html' %}
+{% endblock %}
+
+
+{% block content %}
+ {% include "_types/cart/_main_panel.html" %}
+{% endblock %}
+
+
diff --git a/templates/_types/cart/checkout_error.html b/templates/_types/cart/checkout_error.html
new file mode 100644
index 0000000..a15b1e9
--- /dev/null
+++ b/templates/_types/cart/checkout_error.html
@@ -0,0 +1,38 @@
+{% extends '_types/root/index.html' %}
+
+{% block filter %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
Something went wrong.
+
+ {{ error or "Unexpected error while creating the hosted checkout session." }}
+
+ {% if order %}
+
+ Order ID: #{{ order.id }}
+
+ {% endif %}
+
+
+
+
+{% endblock %}
diff --git a/templates/_types/cart/checkout_return.html b/templates/_types/cart/checkout_return.html
new file mode 100644
index 0000000..b08a09d
--- /dev/null
+++ b/templates/_types/cart/checkout_return.html
@@ -0,0 +1,68 @@
+{% extends '_types/root/index.html' %}
+
+{% block filter %}
+
+{% endblock %}
+
+{% block aside %}
+ {# no aside content for now #}
+{% endblock %}
+
+{% block content %}
+
+ {% if order %}
+
+ {% include '_types/order/_summary.html' %}
+
+ {% else %}
+
+ We couldn’t find that order. If you reached this page from an old link, please start a new order.
+
+ {% endif %}
+ {% include '_types/order/_items.html' %}
+ {% include '_types/order/_calendar_items.html' %}
+ {% include '_types/order/_ticket_items.html' %}
+
+ {% if order.status == 'failed' and order %}
+
+
Your payment was not completed.
+
+ You can go back to your cart and try checkout again. If the problem persists,
+ please contact us and mention order #{{ order.id }}.
+
+
+ {% elif order.status == 'paid' %}
+
+
All done!
+
We’ll start processing your order shortly.
+
+ {% endif %}
+
+
+{% endblock %}
diff --git a/templates/_types/cart/header/_header.html b/templates/_types/cart/header/_header.html
new file mode 100644
index 0000000..b5d913d
--- /dev/null
+++ b/templates/_types/cart/header/_header.html
@@ -0,0 +1,12 @@
+{% import 'macros/links.html' as links %}
+{% macro header_row(oob=False) %}
+ {% call links.menu_row(id='cart-row', oob=oob) %}
+ {% call links.link(cart_url('/'), hx_select_search ) %}
+
+ cart
+ {% endcall %}
+ {% call links.desktop_nav() %}
+ {% include '_types/cart/_nav.html' %}
+ {% endcall %}
+ {% endcall %}
+{% endmacro %}
\ No newline at end of file
diff --git a/templates/_types/cart/index.html b/templates/_types/cart/index.html
new file mode 100644
index 0000000..78570d9
--- /dev/null
+++ b/templates/_types/cart/index.html
@@ -0,0 +1,22 @@
+{% extends '_types/root/_index.html' %}
+
+{% block root_header_child %}
+ {% from '_types/root/_n/macros.html' import index_row with context %}
+ {% call index_row('cart-header-child', '_types/cart/header/_header.html') %}
+ {% block cart_header_child %}
+ {% endblock %}
+ {% endcall %}
+{% endblock %}
+
+
+{% block _main_mobile_menu %}
+{% include '_types/cart/_nav.html' %}
+{% endblock %}
+
+
+{% block aside %}
+{% endblock %}
+
+{% block content %}
+ {% include '_types/cart/_main_panel.html' %}
+{% endblock %}
diff --git a/templates/_types/order/_calendar_items.html b/templates/_types/order/_calendar_items.html
new file mode 100644
index 0000000..019f048
--- /dev/null
+++ b/templates/_types/order/_calendar_items.html
@@ -0,0 +1,43 @@
+{# --- NEW: calendar bookings in this order --- #}
+ {% if order and calendar_entries %}
+
+
+ Calendar bookings in this order
+
+
+
+
+ {% endif %}
\ No newline at end of file
diff --git a/templates/_types/order/_items.html b/templates/_types/order/_items.html
new file mode 100644
index 0000000..27b2a9f
--- /dev/null
+++ b/templates/_types/order/_items.html
@@ -0,0 +1,51 @@
+{# Items list #}
+{% if order and order.items %}
+
+{% endif %}
\ No newline at end of file
diff --git a/templates/_types/order/_main_panel.html b/templates/_types/order/_main_panel.html
new file mode 100644
index 0000000..679b846
--- /dev/null
+++ b/templates/_types/order/_main_panel.html
@@ -0,0 +1,7 @@
+
+ {# Order summary card #}
+ {% include '_types/order/_summary.html' %}
+ {% include '_types/order/_items.html' %}
+ {% include '_types/order/_calendar_items.html' %}
+
+
\ No newline at end of file
diff --git a/templates/_types/order/_nav.html b/templates/_types/order/_nav.html
new file mode 100644
index 0000000..f5c504d
--- /dev/null
+++ b/templates/_types/order/_nav.html
@@ -0,0 +1,2 @@
+{% from 'macros/admin_nav.html' import placeholder_nav %}
+{{ placeholder_nav() }}
diff --git a/templates/_types/order/_oob_elements.html b/templates/_types/order/_oob_elements.html
new file mode 100644
index 0000000..31d1e17
--- /dev/null
+++ b/templates/_types/order/_oob_elements.html
@@ -0,0 +1,30 @@
+{% extends 'oob_elements.html' %}
+
+{# OOB elements for HTMX navigation - all elements that need updating #}
+
+{# Import shared OOB macros #}
+{% from '_types/root/header/_oob.html' import root_header_start, root_header_end with context %}
+{% from '_types/root/_oob_menu.html' import mobile_menu with context %}
+
+{# Header with app title - includes cart-mini, navigation, and market-specific header #}
+
+{% block oobs %}
+
+ {% from '_types/root/_n/macros.html' import oob_header with context %}
+ {{oob_header('orders-header-child', 'order-header-child', '_types/order/header/_header.html')}}
+
+ {% from '_types/order/header/_header.html' import header_row with context %}
+ {{ header_row(oob=True) }}
+{% endblock %}
+
+
+{% block mobile_menu %}
+ {% include '_types/order/_nav.html' %}
+{% endblock %}
+
+
+{% block content %}
+ {% include "_types/order/_main_panel.html" %}
+{% endblock %}
+
+
diff --git a/templates/_types/order/_summary.html b/templates/_types/order/_summary.html
new file mode 100644
index 0000000..ffe560b
--- /dev/null
+++ b/templates/_types/order/_summary.html
@@ -0,0 +1,52 @@
+
+
+ Order ID:
+ #{{ order.id }}
+
+
+
+ Created:
+ {% if order.created_at %}
+ {{ order.created_at.strftime('%-d %b %Y, %H:%M') }}
+ {% else %}
+ —
+ {% endif %}
+
+
+
+ Description:
+ {{ order.description or '–' }}
+
+
+
+ Status:
+
+ {{ order.status or 'pending' }}
+
+
+
+
+ Currency:
+ {{ order.currency or 'GBP' }}
+
+
+
+ Total:
+ {% if order.total_amount %}
+ {{ order.currency or 'GBP' }} {{ '%.2f'|format(order.total_amount) }}
+ {% else %}
+ –
+ {% endif %}
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/_types/order/_ticket_items.html b/templates/_types/order/_ticket_items.html
new file mode 100644
index 0000000..ef06c0b
--- /dev/null
+++ b/templates/_types/order/_ticket_items.html
@@ -0,0 +1,49 @@
+{# --- Tickets in this order --- #}
+ {% if order and order_tickets %}
+
+
+ Event tickets in this order
+
+
+
+
+ {% endif %}
\ No newline at end of file
diff --git a/templates/_types/order/header/_header.html b/templates/_types/order/header/_header.html
new file mode 100644
index 0000000..4d7f74b
--- /dev/null
+++ b/templates/_types/order/header/_header.html
@@ -0,0 +1,17 @@
+{% import 'macros/links.html' as links %}
+{% macro header_row(oob=False) %}
+ {% call links.menu_row(id='order-row', oob=oob) %}
+ {% call links.link(url_for('orders.order.order_detail', order_id=order.id), hx_select_search ) %}
+
+
+ Order
+
+
+ {{ order.id }}
+
+ {% endcall %}
+ {% call links.desktop_nav() %}
+ {% include '_types/order/_nav.html' %}
+ {% endcall %}
+ {% endcall %}
+{% endmacro %}
\ No newline at end of file
diff --git a/templates/_types/order/index.html b/templates/_types/order/index.html
new file mode 100644
index 0000000..c3d301e
--- /dev/null
+++ b/templates/_types/order/index.html
@@ -0,0 +1,68 @@
+{% extends '_types/orders/index.html' %}
+
+
+{% block orders_header_child %}
+ {% from '_types/root/_n/macros.html' import index_row with context %}
+ {% call index_row('order-header-child', '_types/order/header/_header.html') %}
+ {% block order_header_child %}
+ {% endblock %}
+ {% endcall %}
+{% endblock %}
+
+{% block _main_mobile_menu %}
+ {% include '_types/order/_nav.html' %}
+{% endblock %}
+
+
+
+{% block filter %}
+
+{% endblock %}
+
+{% block content %}
+ {% include '_types/order/_main_panel.html' %}
+{% endblock %}
+
+{% block aside %}
+{% endblock %}
diff --git a/templates/_types/orders/_main_panel.html b/templates/_types/orders/_main_panel.html
new file mode 100644
index 0000000..01ad410
--- /dev/null
+++ b/templates/_types/orders/_main_panel.html
@@ -0,0 +1,26 @@
+
+ {% if not orders %}
+
+ No orders yet.
+
+ {% else %}
+
+
+
+
+ | Order |
+ Created |
+ Description |
+ Total |
+ Status |
+ |
+
+
+
+ {# rows + infinite-scroll sentinel #}
+ {% include "_types/orders/_rows.html" %}
+
+
+
+ {% endif %}
+
diff --git a/templates/_types/orders/_nav.html b/templates/_types/orders/_nav.html
new file mode 100644
index 0000000..f5c504d
--- /dev/null
+++ b/templates/_types/orders/_nav.html
@@ -0,0 +1,2 @@
+{% from 'macros/admin_nav.html' import placeholder_nav %}
+{{ placeholder_nav() }}
diff --git a/templates/_types/orders/_oob_elements.html b/templates/_types/orders/_oob_elements.html
new file mode 100644
index 0000000..ab50cb1
--- /dev/null
+++ b/templates/_types/orders/_oob_elements.html
@@ -0,0 +1,38 @@
+{% extends 'oob_elements.html' %}
+
+{# OOB elements for HTMX navigation - all elements that need updating #}
+
+{# Import shared OOB macros #}
+{% from '_types/root/header/_oob.html' import root_header_start, root_header_end with context %}
+{% from '_types/root/_oob_menu.html' import mobile_menu with context %}
+
+{# Header with app title - includes cart-mini, navigation, and market-specific header #}
+
+{% block oobs %}
+
+ {% from '_types/root/_n/macros.html' import oob_header with context %}
+ {{oob_header('auth-header-child', 'orders-header-child', '_types/orders/header/_header.html')}}
+
+ {% from '_types/auth/header/_header.html' import header_row with context %}
+ {{ header_row(oob=True) }}
+{% endblock %}
+
+{% block aside %}
+ {% import '_types/browse/desktop/_filter/search.html' as s %}
+ {{ s.search(current_local_href, search, search_count, hx_select) }}
+{% endblock %}
+
+{% block filter %}
+{% include '_types/orders/_summary.html' %}
+{% endblock %}
+
+{% block mobile_menu %}
+ {% include '_types/orders/_nav.html' %}
+{% endblock %}
+
+
+{% block content %}
+ {% include "_types/orders/_main_panel.html" %}
+{% endblock %}
+
+
diff --git a/templates/_types/orders/_rows.html b/templates/_types/orders/_rows.html
new file mode 100644
index 0000000..33a459c
--- /dev/null
+++ b/templates/_types/orders/_rows.html
@@ -0,0 +1,164 @@
+{# suma_browser/templates/_types/order/_orders_rows.html #}
+
+{# --- existing rows, but split into desktop/tablet vs mobile --- #}
+{% for order in orders %}
+ {# Desktop / tablet table row #}
+
+ |
+ #{{ order.id }}
+ |
+
+ {% if order.created_at %}
+ {{ order.created_at.strftime('%-d %b %Y, %H:%M') }}
+ {% else %}
+ —
+ {% endif %}
+ |
+
+ {{ order.description or '' }}
+ |
+
+
+ {{ order.currency or 'GBP' }}
+ {{ '%.2f'|format(order.total_amount or 0) }}
+ |
+
+ {# status pill, roughly matching existing styling #}
+
+ {{ order.status or 'pending' }}
+
+ |
+
+
+ View
+
+ |
+
+
+ {# Mobile card row #}
+
+
+
+
+
+ #{{ order.id }}
+
+
+
+ {{ order.status or 'pending' }}
+
+
+
+
+ {{ order.created_at or '' }}
+
+
+
+
+ {{ order.currency or 'GBP' }}
+ {{ '%.2f'|format(order.total_amount or 0) }}
+
+
+
+ View
+
+
+
+ |
+
+{% endfor %}
+
+{# --- sentinel / end-of-results --- #}
+{% if page < total_pages|int %}
+
+ |
+ {# Mobile sentinel content #}
+
+ {% include "sentinel/mobile_content.html" %}
+
+
+ {# Desktop sentinel content #}
+
+ {% include "sentinel/desktop_content.html" %}
+
+ |
+
+{% else %}
+
+ |
+ End of results
+ |
+
+{% endif %}
diff --git a/templates/_types/orders/_summary.html b/templates/_types/orders/_summary.html
new file mode 100644
index 0000000..824a235
--- /dev/null
+++ b/templates/_types/orders/_summary.html
@@ -0,0 +1,11 @@
+
+
+
+ Recent orders placed via the checkout.
+
+
+
+ {% import '_types/browse/mobile/_filter/search.html' as s %}
+ {{ s.search(current_local_href, search, search_count, hx_select) }}
+
+
\ No newline at end of file
diff --git a/templates/_types/orders/header/_header.html b/templates/_types/orders/header/_header.html
new file mode 100644
index 0000000..32c1659
--- /dev/null
+++ b/templates/_types/orders/header/_header.html
@@ -0,0 +1,14 @@
+{% import 'macros/links.html' as links %}
+{% macro header_row(oob=False) %}
+ {% call links.menu_row(id='orders-row', oob=oob) %}
+ {% call links.link(url_for('orders.list_orders'), hx_select_search, ) %}
+
+
+ Orders
+
+ {% endcall %}
+ {% call links.desktop_nav() %}
+ {% include '_types/orders/_nav.html' %}
+ {% endcall %}
+ {% endcall %}
+{% endmacro %}
\ No newline at end of file
diff --git a/templates/_types/orders/index.html b/templates/_types/orders/index.html
new file mode 100644
index 0000000..8744c13
--- /dev/null
+++ b/templates/_types/orders/index.html
@@ -0,0 +1,29 @@
+{% extends '_types/auth/index.html' %}
+
+
+{% block auth_header_child %}
+ {% from '_types/root/_n/macros.html' import index_row with context %}
+ {% call index_row('orders-header-child', '_types/orders/header/_header.html') %}
+ {% block orders_header_child %}
+ {% endblock %}
+ {% endcall %}
+
+{% endblock %}
+
+{% block _main_mobile_menu %}
+ {% include '_types/orders/_nav.html' %}
+{% endblock %}
+
+{% block aside %}
+ {% import '_types/browse/desktop/_filter/search.html' as s %}
+ {{ s.search(current_local_href, search, search_count, hx_select) }}
+{% endblock %}
+
+
+{% block filter %}
+ {% include '_types/orders/_summary.html' %}
+{% endblock %}
+
+{% block content %}
+{% include '_types/orders/_main_panel.html' %}
+{% endblock %}
diff --git a/templates/_types/product/_cart.html b/templates/_types/product/_cart.html
new file mode 100644
index 0000000..2c68284
--- /dev/null
+++ b/templates/_types/product/_cart.html
@@ -0,0 +1,250 @@
+{% macro add(slug, cart, oob='false') %}
+{% set quantity = cart
+ | selectattr('product.slug', 'equalto', slug)
+ | sum(attribute='quantity') %}
+
+
+
+ {% if not quantity %}
+
+
+ {% else %}
+
+ {% endif %}
+
+{% endmacro %}
+
+
+
+{% macro cart_item(oob=False) %}
+
+{% set p = item.product %}
+{% set unit_price = p.special_price or p.regular_price %}
+
+
+ {% if p.image %}
+

+ {% else %}
+
+ No image
+
'market', 'product', p.slug
+ {% endif %}
+
+
+ {# Details #}
+
+
+
+
+ {% set href=url_for('market.browse.product.product_detail', product_slug=p.slug) %}
+
+ {{ p.title }}
+
+
+
+ {% if p.brand %}
+
+ {{ p.brand }}
+
+ {% endif %}
+
+ {% if item.is_deleted %}
+
+
+ This item is no longer available or price has changed
+
+ {% endif %}
+
+
+ {# Unit price #}
+
+ {% if unit_price %}
+ {% set symbol = "£" if p.regular_price_currency == "GBP" else p.regular_price_currency %}
+
+ {{ symbol }}{{ "%.2f"|format(unit_price) }}
+
+ {% if p.special_price and p.special_price != p.regular_price %}
+
+ {{ symbol }}{{ "%.2f"|format(p.regular_price) }}
+
+ {% endif %}
+ {% else %}
+
No price
+ {% endif %}
+
+
+
+
+
+ Quantity
+
+
+ {{ item.quantity }}
+
+
+
+
+
+ {% if unit_price %}
+ {% set line_total = unit_price * item.quantity %}
+ {% set symbol = "£" if p.regular_price_currency == "GBP" else p.regular_price_currency %}
+
+ Line total:
+ {{ symbol }}{{ "%.2f"|format(line_total) }}
+
+ {% endif %}
+
+
+
+
+
+{% endmacro %}