Delete 391 dead Jinja templates replaced by sx_components/defpage

All app-level templates have been replaced by native sx component builders
and defpage declarative routes. Removes ~15,200 lines of dead HTML.

Kept: shared/browser templates (errors, ap_social, macros, root layout),
account + federation _email/magic_link, federation profile.html chain.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 15:10:56 +00:00
parent c243d17eeb
commit 544892edd9
391 changed files with 0 additions and 15208 deletions

View File

@@ -1,33 +0,0 @@
{# Card for a single market in the global listing #}
{% set pi = page_info.get(market.container_id, {}) %}
{% set page_slug = pi.get('slug', '') %}
{% set page_title = pi.get('title') %}
{% if page_slug %}
{% set market_href = market_url('/' ~ page_slug ~ '/' ~ market.slug ~ '/') %}
{% else %}
{% set market_href = '' %}
{% endif %}
<article class="rounded-xl bg-white shadow-sm border border-stone-200 p-5 flex flex-col justify-between hover:border-stone-400 transition-colors">
<div>
{% if market_href %}
<a href="{{ market_href }}" class="hover:text-emerald-700">
<h2 class="text-lg font-semibold text-stone-900">{{ market.name }}</h2>
</a>
{% else %}
<h2 class="text-lg font-semibold text-stone-900">{{ market.name }}</h2>
{% endif %}
{% if market.description %}
<p class="text-sm text-stone-600 mt-1 line-clamp-2">{{ market.description }}</p>
{% endif %}
</div>
<div class="flex flex-wrap items-center gap-1.5 mt-3">
{% if page_title %}
<a href="{{ market_url('/' ~ page_slug ~ '/') }}"
class="inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-amber-100 text-amber-800 hover:bg-amber-200">
{{ page_title }}
</a>
{% endif %}
</div>
</article>

View File

@@ -1,18 +0,0 @@
{% for market in markets %}
{% include "_types/all_markets/_card.html" %}
{% endfor %}
{% if has_more %}
{# Infinite scroll sentinel #}
{% set next_url = url_for('all_markets.markets_fragment', page=page + 1)|host %}
<div
id="sentinel-{{ page }}"
class="h-4 opacity-0 pointer-events-none"
sx-get="{{ next_url }}"
sx-trigger="intersect once delay:250ms"
sx-swap="outerHTML"
role="status"
aria-hidden="true"
>
<div class="text-center text-xs text-stone-400">loading...</div>
</div>
{% endif %}

View File

@@ -1,12 +0,0 @@
{# Markets grid #}
{% if markets %}
<div class="max-w-full px-3 py-3 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{% include "_types/all_markets/_cards.html" %}
</div>
{% else %}
<div class="px-3 py-12 text-center text-stone-400">
<i class="fa fa-store text-4xl mb-3" aria-hidden="true"></i>
<p class="text-lg">No markets available</p>
</div>
{% endif %}
<div class="pb-8"></div>

View File

@@ -1,7 +0,0 @@
{% extends '_types/root/_index.html' %}
{% block meta %}{% endblock %}
{% block content %}
{% include '_types/all_markets/_main_panel.html' %}
{% endblock %}

View File

@@ -1,7 +0,0 @@
{% import "macros/links.html" as links %}
{% if g.rights.admin %}
{% from 'macros/admin_nav.html' import admin_nav_item %}
{{admin_nav_item(
url_for('market.browse.product.admin', product_slug=slug)
)}}
{% endif %}

View File

@@ -1,5 +0,0 @@
<div class="grid grid-cols-1 sm:grid-cols-3 md:grid-cols-6 gap-3">
{% include "_types/browse/_product_cards.html" %}
</div>
<div class="pb-8"></div>

View File

@@ -1,37 +0,0 @@
{% 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('post-header-child', 'market-header-child', '_types/market/header/_header.html')}}
{% from '_types/post/header/_header.html' import header_row with context %}
{{ header_row(oob=True) }}
{% endblock %}
{% block mobile_menu %}
{% include '_types/market/mobile/_nav_panel.html' %}
{% endblock %}
{# Filter container with child summary - from browse/index.html child_summary block #}
{% block filter %}
{% include "_types/browse/mobile/_filter/summary.html" %}
{% endblock %}
{% block aside %}
{% include "_types/browse/desktop/menu.html" %}
{% endblock %}
{% block content %}
{% include "_types/browse/_main_panel.html" %}
{% endblock %}

View File

@@ -1,104 +0,0 @@
{% import 'macros/stickers.html' as stick %}
{% import '_types/product/prices.html' as prices %}
{% set prices_ns = namespace() %}
{{ prices.set_prices(p, prices_ns) }}
{% set item_href = url_for('market.browse.product.product_detail', product_slug=p.slug)|host %}
<div class="flex flex-col rounded-xl bg-white shadow hover:shadow-md transition overflow-hidden relative">
{# ❤️ like button overlay - OUTSIDE the link #}
{% if g.user %}
<div class="absolute top-2 right-2 z-10 text-6xl md:text-xl">
{% set slug = p.slug %}
{% set liked = p.is_liked or False %}
{% include "_types/browse/like/button.html" %}
</div>
{% endif %}
<a
href="{{ item_href }}"
sx-get="{{ item_href }}"
sx-target="#main-panel"
sx-select ="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
class=""
>
{# Make this relative so we can absolutely position children #}
<div class="w-full aspect-square bg-stone-100 relative">
{% if p.image %}
<figure class="inline-block w-full h-full">
<div class="relative w-full h-full">
<img
src="{{ p.image }}"
alt="no image"
class="absolute inset-0 w-full h-full object-contain object-top"
loading="lazy" decoding="async" fetchpriority="low"
/>
{% for l in p.labels %}
<img
src="{{ asset_url('labels/' + l + '.svg') }}"
alt=""
class="pointer-events-none absolute inset-0 w-full h-full object-contain object-top"
/>
{% endfor %}
</div>
<figcaption class="
mt-2 text-sm text-center
{{ 'bg-yellow-200' if p.brand in selected_brands else '' }}
text-stone-600
">
{{ p.brand }}
</figcaption>
</figure>
{% else %}
<div class="p-2 flex flex-col items-center justify-center gap-2 text-red-500 h-full relative">
<div class="text-stone-400 text-xs">No image</div>
<ul class="flex flex-row gap-1">
{% for l in p.labels %}
<li>{{ l }}</li>
{% endfor %}
</ul>
<div class="text-stone-900 text-center line-clamp-3 break-words [overflow-wrap:anywhere]">
{{ p.brand }}
</div>
</div>
{% endif %}
</div>
{# <div>{{ prices.rrp(prices_ns) }}</div> #}
{{ prices.card_price(p)}}
{% import '_types/product/_cart.html' as _cart %}
</a>
<div class="flex justify-center">
{{ _cart.add(p.slug, cart)}}
</div>
<a
href="{{ item_href }}"
sx-get="{{ item_href }}"
sx-target="#main-panel"
sx-select ="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
>
<div class="flex flex-row justify-center gap-2 p-2">
{% for s in p.stickers %}
{{ stick.sticker(
asset_url('stickers/' + s + '.svg'),
s,
True,
size=24,
found=s in selected_stickers
) }}
{% endfor %}
</div>
<div class="text-sm font-medium text-stone-800 text-center line-clamp-3 break-words [overflow-wrap:anywhere]">
{{ p.title | highlight(search) }}
</div>
</a>
</div>

View File

@@ -1,38 +0,0 @@
{% for p in products %}
{% include "_types/browse/_product_card.html" %}
{% endfor %}
{% if page < total_pages|int %}
<div
id="sentinel-{{ page }}-m"
class="block md:hidden h-[60vh] opacity-0 pointer-events-none js-mobile-sentinel"
sx-get="{{ (current_local_href ~ {'page': page + 1}|qs)|host }}"
sx-trigger="intersect once delay:250ms, sentinelmobile:retry"
sx-swap="outerHTML"
sx-media="(max-width: 767px)"
sx-retry="exponential:1000:30000"
role="status"
aria-live="polite"
aria-hidden="true"
>
{% include "sentinel/mobile_content.html" %}
</div>
<!-- DESKTOP sentinel (custom scroll container) -->
<div
id="sentinel-{{ page }}-d"
class="hidden md:block h-4 opacity-0 pointer-events-none"
sx-get="{{ (current_local_href ~ {'page': page + 1}|qs)|host}}"
sx-trigger="intersect once delay:250ms, sentinel:retry"
sx-swap="outerHTML"
sx-retry="exponential:1000:30000"
role="status"
aria-live="polite"
aria-hidden="true"
>
{% include "sentinel/desktop_content.html" %}
</div>
{% else %}
<div class="col-span-full mt-4 text-center text-xs text-stone-400">End of results</div>
{% endif %}

View File

@@ -1,40 +0,0 @@
{# Categories #}
<nav aria-label="Categories"
class="rounded-xl border bg-white shadow-sm min-h-0">
<ul class="divide-y">
{% set top_active = (sub_slug is not defined or sub_slug is none or sub_slug == '') %}
{% set href = (url_for('market.browse.browse_top', top_slug=top_slug) ~ qs)|host %}
<li>
<a
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
aria-selected="{{ 'true' if top_active else 'false' }}"
class="block px-4 py-3 text-[15px] transition {{select_colours}}">
<div class="prose prose-stone max-w-none">All products</div>
</a>
</li>
{% for sub in subs_local %}
{% set active = (sub.slug == sub_slug) %}
{% set href = (url_for('market.browse.browse_sub', top_slug=top_slug, sub_slug=sub.slug) ~ qs)|host %}
<li>
<a
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
aria-selected="{{ 'true' if active else 'false' }}"
class="block px-4 py-3 text-[15px] border-l-4 transition {{select_colours}}"
>
<div class="prose prose-stone max-w-none">{{ (sub.html_label or sub.name) | safe }}</div>
</a>
</li>
{% endfor %}
</ul>
</nav>

View File

@@ -1,40 +0,0 @@
{# Brand filter (desktop, single-select) #}
{# Brands #}
<nav aria-label="Brands"
class="rounded-xl border bg-white shadow-sm">
<h2 class="text-md mt-2 font-semibold">Brands</h2>
<ul class="divide-y">
{% for b in brands %}
{% set is_selected = (b.name in selected_brands) %}
{% if is_selected %}
{% set brand_href = (current_local_href ~ {"remove_brand": b.name, "page": None}|qs)|host %}
{% else %}
{% set brand_href = (current_local_href ~ {"add_brand": b.name, "page": None}|qs)|host %}
{% endif %}
<li>
<a
href="{{ brand_href }}"
sx-get="{{ brand_href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML" sx-push-url="true" hx-on:htmx:afterSwap="this.closest('details')?.removeAttribute('open')"
class="flex items-center gap-2 px-2 py-2 rounded transition {% if is_selected %} bg-stone-900 text-white {% else %} hover:bg-stone-50 {% endif %}">
<span class="inline-flex items-center justify-center w-5 h-5 rounded border {% if is_selected %} border-stone-900 bg-stone-900 text-white {% else %} border-stone-300 {% endif %}">
{% if is_selected %}
<svg viewBox="0 0 24 24" class="w-4 h-4" aria-hidden="true">
<path d="M5 13l4 4L19 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{% endif %}
</span>
<span class="flex-1 text-sm">{{ b.name }}</span>
{% if b.count is not none %}
<span class="{% if b.count==0 %}text-lg text-red-500{% else %}text-sm{% endif %} {% if is_selected %}opacity-90{% else %}text-stone-500{% endif %}">{{ b.count }}</span>
{% endif %}
</a>
</li>
{% endfor %}
</ul>
</nav>

View File

@@ -1,44 +0,0 @@
{% import 'macros/stickers.html' as stick %}
<ul
id="labels-details-desktop"
class="flex justify-center p-0 m-0 gap-2"
>
{% for s in labels %}
{% set is_on = (selected_labels and (s.name|lower in selected_labels)) %}
{% set qs = {"remove_label": s.name, "page":None}|qs if is_on
else {"add_label": s.name, "page":None}|qs %}
{% set href = (current_local_href ~ qs)|host %}
<li>
<a
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
role="button"
aria-pressed="{{ 'true' if is_on else 'false' }}"
title="{{ s.name }}" aria-label="{{ s.name }}"
class="flex w-full h-full flex-col items-center justify-center py-2"
>
<!-- col 1: icon -->
{{ stick.sticker(asset_url('nav-labels/' + s.name + '.svg'), s.name, is_on)}}
<!-- col 3: count (right aligned) -->
{% if s.count is not none %}
<span class="
{{'text-xs text-stone-500' if s.count != 0 else 'text-md text-red-500 font-bold'}}
leading-none justify-self-end tabular-nums">
{{ s.count }}
</span>
{% endif %}
</a>
</li>
{% endfor %}
</ul>

View File

@@ -1,38 +0,0 @@
{% import 'macros/stickers.html' as stick %}
{% set qs = {"liked": None if liked else True, "page": None}|qs %}
{% set href = (current_local_href ~ qs)|host %}
<a
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
role="button"
aria-pressed="{{ 'true' if liked else 'false' }}"
title="liked" aria-label="liked"
class="flex flex-col items-center justify-start p-1 rounded hover:bg-stone-50"
{% if liked %}
aria-label="liked and unliked"
{% else %}
aria-label="just liked"
{% endif %}
>
{% if liked %}
<i aria-hidden="true"
class="fa-solid fa-heart text-red-500 text-[40px] leading-none"
></i>
{% else %}
<i aria-hidden="true"
class="fa-solid fa-heart text-stone-300 text-[40px] leading-none"
></i>
{% endif %}
<span class="
{{'text-[10px] text-stone-500' if liked_count != 0 else 'text-md text-red-500 font-bold'}}
mt-1 leading-none tabular-nums
"
aria_label="liked count"
>
{{ liked_count }}
</span>
</a>

View File

@@ -1,44 +0,0 @@
{% macro search(current_local_href,search, search_count, hx_select) -%}
<!-- Search (1/3 width → 4/12 columns) -->
<!-- nb this does NOT oob itself!! -->
<div
id="search-desktop-wrapper"
class="flex flex-row gap-2 items-center"
>
<input
id="search-desktop"
type="text"
name="search"
aria-label="search"
class="w-full mx-1 my-3 px-3 py-2 text-md rounded-xl border-2 shadow-sm border-white placeholder-shown:border-white [&:not(:placeholder-shown)]:border-yellow-200"
sx-preserve
value="{{ search|default('', true) }}"
placeholder="search"
sx-trigger="input changed delay:300ms"
sx-target="#main-panel"
sx-select="{{hx_select}}, #search-mobile-wrapper, #search-desktop-wrapper"
sx-get="{{ (current_local_href ~ {'search': None}|qs)|host}}"
sx-swap="outerHTML"
sx-push-url="true"
sx-headers='{"X-Origin":"search-desktop", "X-Search":"true"}'
sx-sync="this:replace"
autocomplete="off"
>
<div
id="search-count-desktop"
aria-label="search count"
{% if not search_count %}
class="text-xl text-red-500"
{% endif %}
>
{% if search %}
{{search_count}}
{% endif %}
{{zap_filter}}
</div>
</div>
{% endmacro %}

View File

@@ -1,34 +0,0 @@
{% import 'macros/stickers.html' as stick %}
{% set sort_val = sort|default('az', true) %}
<ul
id="sort-details-desktop"
class="flex w-full p-0 m-0 border bg-white shadow-sm rounded-xl gap-0 [&>li]:list-none [&>li]:flex-1"
>
{% for key,label,icon in sort_options %}
{% set is_on = (sort_val == key) %}
{% set qs = {"sort": None, "page": None}|qs if is_on
else {"sort": key, "page": None}|qs %}
{% set href = (current_local_href ~ qs)|host %}
<li>
<a
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
role="button"
aria-pressed="{{ 'true' if is_on else 'false' }}"
class="flex flex-col items-center justify-center w-full h-full py-2 m-0"
>
{{ stick.sticker(asset_url(icon), label, is_on) }}
</a>
</li>
{% endfor %}
</ul>

View File

@@ -1,46 +0,0 @@
{% import 'macros/stickers.html' as stick %}
<ul
id="stickers-details-desktop"
class="flex flex-wrap justify-center w-full p-0 m-0 border bg-white shadow-sm rounded-xl gap-1 [&>li]:list-none [&>li]:basis-[20%] [&>li]:max-w-[20%] [&>li]:grow-0"
>
{% for s in stickers %}
{% set is_on = (selected_stickers and (s.name|lower in selected_stickers)) %}
{% set qs = {"remove_sticker": s.name, "page": None}|qs if is_on
else {"add_sticker": s.name, "page": None}|qs %}
{% set href = (current_local_href ~ qs)|host%}
<li>
<a
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
role="button"
aria-pressed="{{ 'true' if is_on else 'false' }}"
title="{{ s.name }}" aria-label="{{ s.name }}"
class="flex w-full h-full flex-col items-center justify-center py-2"
>
<span class="text-[11px]">{{s.name|capitalize if s.name|lower != 'sugarfree' else 'Sugar'}}</span>
<!-- col 1: icon -->
{{ stick.sticker(asset_url('stickers/' + s.name + '.svg'), s.name, is_on)}}
<!-- col 3: count (right aligned) -->
{% if s.count is not none %}
<span class="
{{'text-xs text-stone-500' if s.count != 0 else 'text-md text-red-500 font-bold'}}
leading-none justify-self-end tabular-nums">
{{ s.count }}
</span>
{% endif %}
</a>
</li>
{% endfor %}
</ul>

View File

@@ -1,37 +0,0 @@
{% import '_types/browse/desktop/_filter/search.html' as s %}
{{ s.search(current_local_href, search, search_count, hx_select) }}
<div
id="category-summary-desktop"
hxx-swap-oob="outerHTML"
>
<div class="mb-4">
<div class="text-2xl uppercase tracking-wide text-black-500">{{ category_label }}</div>
</div>
{% include "_types/browse/desktop/_filter/sort.html" %}
<nav aria-label="like" class="flex flex-row justify-center w-full p-0 m-0 border bg-white shadow-sm rounded-xl gap-1">
{% include "_types/browse/desktop/_filter/like.html" %}
{% if labels %}
{% include "_types/browse/desktop/_filter/labels.html" %}
{% endif %}
</nav>
{% if stickers %}
{% include "_types/browse/desktop/_filter/stickers.html" %}
{% endif %}
{% if subs_local and top_local_href %}
{% include "_types/browse/desktop/_category_selector.html" %}
{% endif %}
</div>
<div
id="filter-summary-desktop"
hxx-swap-oob="outerHTML"
>
{% include "_types/browse/desktop/_filter/brand.html" %}
</div>

View File

@@ -1,13 +0,0 @@
{% extends '_types/market/index.html' %}
{% block filter %}
{% include "_types/browse/mobile/_filter/summary.html" %}
{% endblock %}
{% block aside %}
{% include "_types/browse/desktop/menu.html" %}
{% endblock %}
{% block content %}
{% include "_types/browse/_main_panel.html" %}
{% endblock %}

View File

@@ -1,20 +0,0 @@
<button
class="flex items-center gap-1 {% if liked %} text-red-600 {% else %} text-stone-300 {% endif %} hover:text-red-600 transition-colors w-[1em] h-[1em]"
sx-post="{{ like_url if like_url else url_for('market.browse.product.like_toggle', product_slug=slug)|host }}"
sx-target="this"
sx-swap="outerHTML"
sx-push-url="false"
sx-headers='{"X-CSRFToken": "{{ csrf_token() }}"}'
sx-swap-settle="0ms"
{% if liked %}
aria-label="Unlike this {{ item_type if item_type else 'product' }}"
{% else %}
aria-label="Like this {{ item_type if item_type else 'product' }}"
{% endif %}
>
{% if liked %}
<i aria-hidden="true" class="fa-solid fa-heart"></i>
{% else %}
<i aria-hidden="true" class="fa-regular fa-heart"></i>
{% endif %}
</button>

View File

@@ -1,40 +0,0 @@
<nav aria-label="Brands" class="px-4 pb-3" >
{% if brands|length %}
<h2 class="text-md mt-2 font-semibold">Brands</h2>
<ul class="space-y-1 pr-1" >
{% for b in brands %}
{% set is_selected = (b.name in selected_brands) %}
<li>
{{current_local_href}}
<a
{% if is_selected %}
{% set href = (current_local_href ~ {"remove_brand": b.name, "page": None}|qs)|host %}
{% else %}
{% set href = (current_local_href ~ {"add_brand": b.name, "page": None}|qs)|host %}
{%endif%}
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
class="flex items-center gap-2 my-3 px-2 py-2 rounded transition {% if is_selected %} bg-stone-900 text-white {% else %} hover:bg-stone-50 {% endif %}">
<span class="inline-flex items-center justify-center w-5 h-5 rounded border {% if is_selected %} border-stone-900 bg-stone-900 text-white {% else %} border-stone-300 {% endif %}">
{% if is_selected %}
<svg viewBox="0 0 24 24" class="w-4 h-4" aria-hidden="true">
<path d="M5 13l4 4L19 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{% endif %}
</span>
<span class="flex-1 text-sm">{{ b.name }}</span>
{% if b.count is not none %}
<span class="text-xs {% if is_selected %}opacity-90{% else %}text-stone-500{% endif %}">{{ b.count }}</span>
{% endif %}
</a>
</li>
{% endfor %}
</ul>
{% endif %}
</nav>

View File

@@ -1,30 +0,0 @@
{% include "_types/browse/mobile/_filter/sort_ul.html" %}
{% if search or selected_labels|length or selected_stickers|length or selected_brands|length %}
{% set href = (current_local_href ~ {"clear_filters": True}|qs)|host %}
<div class = "flex flex-row justify-center">
<a
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
role="button"
title="clear filters"
aria-label="clear filters"
class="flex flex-col items-center justify-start p-1 rounded bg-stone-200 text-black cursor-pointer">
<span class="mt-1 leading-none tabular-nums"
>
clear filters
</span>
</a>
</div>
{% endif %}
<div class="flex flex-row gap-2 justify-center items center">
{% include "_types/browse/mobile/_filter/like.html" %}
{% include "_types/browse/mobile/_filter/labels.html" %}
</div>
{% include "_types/browse/mobile/_filter/stickers.html" %}
{% include "_types/browse/mobile/_filter/brand_ul.html" %}

View File

@@ -1,47 +0,0 @@
{% import 'macros/stickers.html' as stick %}
<nav aria-label="labels" class="px-4 pb-3">
{# One row only; center when not overflowing; horizontal scroll when needed #}
<ul
class="flex w-full items-start justify-center gap-3 overflow-x-auto overflow-y-visible pr-1 no-scrollbar"
>
{% for s in labels %}
{% set is_on = (selected_labels and (s.name|lower in selected_labels)) %}
{% set qs = {"remove_label": s.name, "page": None}|qs if is_on
else {"add_label": s.name, "page": None}|qs %}
{% set href = (current_local_href ~ qs)|host %}
<li class="list-none shrink-0">
<a
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
role="button"
aria-pressed="{{ 'true' if is_on else 'false' }}"
title="{{ s.name }}" aria-label="{{ s.name }}"
class="flex flex-col items-center justify-start p-1 rounded hover:bg-stone-50">
{{ stick.sticker(asset_url('nav-labels/' + s.name + '.svg'), s.name, is_on)}}
{% if s.count is not none %}
<span class="
{{'text-[10px] text-stone-500' if s.count != 0 else 'text-md text-red-500 font-bold'}}
mt-1 leading-none tabular-nums
"
>
{{ s.count }}
</span>
{% endif %}
</a>
</li>
{% endfor %}
</ul>
</nav>
{# Optional: hide horizontal scrollbar on mobile while keeping scrollable #}
<style>
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
</style>

View File

@@ -1,40 +0,0 @@
{% import 'macros/stickers.html' as stick %}
<nav aria-label="like" class="px-4 pb-3">
{% set qs = {"liked": None if liked else True, "page": None}|qs%}
{% set href = (current_local_href ~ qs)|host %}
<a
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
role="button"
aria-pressed="{{ 'true' if liked else 'false' }}"
title="liked" aria-label="liked"
class="flex flex-col items-center justify-start p-1 rounded hover:bg-stone-50"
{% if liked %}
aria-label="liked and unliked"
{% else %}
aria-label="just liked"
{% endif %}
>
{% if liked %}
<i aria-hidden="true"
class="fa-solid fa-heart text-red-500 text-[40px] leading-none"
></i>
{% else %}
<i aria-hidden="true"
class="fa-solid fa-heart text-stone-500 text-[40px] leading-none"
></i>
{% endif %}
<span class="
{{'text-[10px] text-stone-500' if liked_count != 0 else 'text-md text-red-500 font-bold'}}
mt-1 leading-none tabular-nums
"
aria_label="liked count"
>
{{ liked_count }}
</span>
</a>
</nav>

View File

@@ -1,40 +0,0 @@
{% macro search(current_local_href, search, search_count, hx_select) -%}
<div
id="search-mobile-wrapper"
class="flex flex-row gap-2 items-center flex-1 min-w-0 pr-2"
>
<input
id="search-mobile"
type="text"
name="search"
aria-label="search"
class="text-base md:text-sm col-span-5 rounded-md px-3 py-2 mb-2 w-full min-w-0 max-w-full border-2 border-stone-200 placeholder-shown:border-stone-200 [&:not(:placeholder-shown)]:border-yellow-200"
sx-preserve
value="{{ search|default('', true) }}"
placeholder="search"
sx-trigger="input changed delay:300ms"
sx-target="#main-panel"
sx-select="{{hx_select}}, #search-mobile-wrapper, #search-desktop-wrapper"
sx-get="{{ (current_local_href ~ {'search': None}|qs)|host }}"
sx-swap="outerHTML"
sx-push-url="true"
sx-headers='{"X-Origin":"search-mobile", "X-Search":"true"}'
sx-sync="this:replace"
autocomplete="off"
>
<div
id="search-count-mobile"
aria-label="search count"
{% if not search_count %}
class="text-xl text-red-500"
{% endif %}
>
{% if search %}
{{search_count}}
{% endif %}
</div>
</div>
{% endmacro %}

View File

@@ -1,33 +0,0 @@
{% import 'macros/stickers.html' as stick %}
<nav aria-label="sort" class="px-4 pb-3" >
<ul class="flex w-full items-start justify-center gap-3 overflow-x-auto overflow-y-visible pr-1 no-scrollbar">
{% for key,label,icon in sort_options %}
<li class="list-none">
<div class="flex flex-col items-center justify-center w-full">
<a
{% if sort == key %}
{% set href= (current_local_href, {"sort": None, "page": None}|qs )|host %}
{% else %}
{% set href= (current_local_href ~ {"sort": key, "page": None}|qs )|host %}
{% endif %}
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
>
{{ stick.sticker(asset_url(icon), label, sort==key) }}
</a>
</div>
</li>
{% endfor %}
</ul>
</nav>

View File

@@ -1,50 +0,0 @@
{% import 'macros/stickers.html' as stick %}
<nav aria-label="stickers" class="px-4 pb-3">
{# One row only; center when not overflowing; horizontal scroll when needed #}
<ul
class="flex w-full items-start justify-center gap-3 overflow-x-auto overflow-y-visible pr-1 no-scrollbar"
>
{% for s in stickers %}
{% set is_on = (selected_stickers and (s.name|lower in selected_stickers)) %}
{% set qs = {"remove_sticker": s.name, "page": None}|qs if is_on
else {"add_sticker": s.name, "page": None}|qs %}
{% set href = (current_local_href ~ qs)|host %}
<li class="list-none shrink-0">
<a
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
role="button"
aria-pressed="{{ 'true' if is_on else 'false' }}"
title="{{ s.name }}" aria-label="{{ s.name }}"
class="flex flex-col items-center justify-start p-1 rounded hover:bg-stone-50">
<span class="text-sm">{{s.name|capitalize if s.name|lower != 'sugarfree' else 'Sugar'}}</span>
{{ stick.sticker(asset_url('stickers/' + s.name + '.svg'), s.name, is_on) }}
{% if s.count is not none %}
<span class="
{{'text-[10px] text-stone-500' if s.count != 0 else 'text-md text-red-500 font-bold'}}
mt-1 leading-none tabular-nums
"
>
{{ s.count }}
</span>
{% endif %}
</a>
</li>
{% endfor %}
</ul>
</nav>
{# Optional: hide horizontal scrollbar on mobile while keeping scrollable #}
<style>
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
</style>

View File

@@ -1,120 +0,0 @@
{% import 'macros/stickers.html' as stick %}
{% import 'macros/layout.html' as layout %}
{% call layout.details('/filter', 'md:hidden') %}
{% call layout.filter_summary("filter-summary-mobile", current_local_href, search, search_count, hx_select) %}
<div
class="col-span-12 min-w-0 grid grid-cols-1 gap-1 bg-gray-100 px-2"
role="list">
<div class="flex flex-row items-start gap-2">
{% if sort %}
<ul class="relative inline-flex items-center justify-center gap-2">
<!-- sticker icon -->
{% for k,l,i in sort_options %}
{% if k == sort %}
{% set key = k %}
{% set label = l %}
{% set icon = i %}
<li role="listitem">
{{ stick.sticker(asset_url(icon), label, True)}}
</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
{% if liked %}
<div class="flex flex-col items-center gap-1 pb-1">
<i aria-hidden="true"
class="fa-solid fa-heart text-red-500 text-[40px] leading-none"
></i>
{% if liked_count is not none %}
<div class="
{{'text-[10px] text-stone-500' if liked_count != 0 else 'text-md text-red-500 font-bold'}}
mt-1 leading-none tabular-nums"
>
{{ liked_count }}
</div>
{% endif %}
</div>
{% endif %}
{% if selected_labels and selected_labels|length %}
<ul class="relative inline-flex items-center justify-center gap-2">
{% for st in selected_labels %}
{% for s in labels %}
{% if st == s.name %}
<li role="listitem" class="flex flex-col items-center gap-1 pb-1">
{{ stick.sticker(asset_url('nav-labels/' + s.name + '.svg'), s.name, True)}}
{% if s.count is not none %}
<div class="
{{'text-[10px] text-stone-500' if s.count != 0 else 'text-md text-red-500 font-bold'}}
mt-1 leading-none tabular-nums
"
>
{{ s.count }}
</div>
{% endif %}
</li>
{% endif %}
{% endfor %}
{% endfor %}
</ul>
{% endif %}
{% if selected_stickers and selected_stickers|length %}
<ul class="relative inline-flex items-center justify-center gap-2">
{% for st in selected_stickers %}
{% for s in stickers %}
{% if st == s.name %}
<li role="listitem" class="flex flex-col items-center gap-1 pb-1">
<!-- sticker icon -->
{{ stick.sticker(asset_url('stickers/' + s.name + '.svg'), s.name, True)}}
{% if s.count is not none %}
<span class="
{{'text-[10px] text-stone-500' if s.count != 0 else 'text-md text-red-500 font-bold'}}
mt-1 leading-none tabular-nums
"
>
{{ s.count }}
</span>
{% endif %}
</li>
{% endif %}
{% endfor %}
{% endfor %}
</ul>
{% endif %}
</div>
{% if selected_brands and selected_brands|length %}
<ul class_="w-full grid grid-cols-12 items-center gap-3 px-4 py-3">
{% for b in selected_brands %}
<li role="listitem" class="flex flex-row items-center gap-2">
{% set ns = namespace(count=0) %}
{% for brand in brands %}
{% if brand.name == b %}
{% set ns.count = brand.count %}
{% endif %}
{% endfor %}
{% if ns.count %}
<div class="text-md">{{ b }}</div>
<div class="text-md">{{ ns.count }}</div>
{% else %}
<div class="text-md text-red-500">{{ b }}</div>
<div class="text-xl text-red-500">0</div>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endcall %}
<div id="filter-details-mobile" style="display:contents">
{% include "_types/browse/mobile/_filter/index.html" %}
</div>
{% endcall %}

View File

@@ -1,7 +0,0 @@
{% import "macros/links.html" as links %}
{% if g.rights.admin %}
{% from 'macros/admin_nav.html' import admin_nav_item %}
{{admin_nav_item(
url_for('market.admin.admin')
)}}
{% endif %}

View File

@@ -1,23 +0,0 @@
{# Main panel fragment for HTMX navigation - market landing page #}
<article class="relative w-full">
{% if post.custom_excerpt %}
<div class="w-full text-center italic text-3xl p-2">
{{post.custom_excerpt|safe}}
</div>
{% endif %}
{% if post.feature_image %}
<div class="mb-3 flex justify-center">
<img
src="{{ post.feature_image }}"
alt=""
class="rounded-lg w-full md:w-3/4 object-cover"
>
</div>
{% endif %}
<div class="blog-content p-2">
{% if post.html %}
{{post.html|safe}}
{% endif %}
</div>
</article>
<div class="pb-8"></div>

View File

@@ -1,30 +0,0 @@
{% 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('post-header-child', 'market-header-child', '_types/market/header/_header.html')}}
{% from '_types/post/header/_header.html' import header_row with context %}
{{ header_row(oob=True) }}
{% endblock %}
{% block mobile_menu %}
{% include '_types/market/mobile/_nav_panel.html' %}
{% endblock %}
{% block content %}
{% include "_types/market/_main_panel.html" %}
{% endblock %}

View File

@@ -1,17 +0,0 @@
<div
class="font-bold text-xl flex-shrink-0 flex gap-2 items-center">
<div>
<i class="fa fa-shop"></i>
{{ market_title }}
</div>
<div class="flex flex-col md:flex-row md:gap-2 text-xs">
<div>
{{top_slug or ''}}
</div>
{% if sub_slug %}
<div>
{{sub_slug}}
</div>
{% endif %}
</div>
</div>

View File

@@ -1 +0,0 @@
market admin

View File

@@ -1,2 +0,0 @@
{% from 'macros/admin_nav.html' import placeholder_nav %}
{{ placeholder_nav() }}

View File

@@ -1,29 +0,0 @@
{% extends 'oob_elements.html' %}
{# OOB elements for HTMX navigation - all elements that need updating #}
{# Import shared OOB macros #}
{% 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('market-header-child', 'market-admin-header-child', '_types/market/admin/header/_header.html')}}
{% from '_types/market/header/_header.html' import header_row with context %}
{{ header_row(oob=True) }}
{% endblock %}
{% block mobile_menu %}
{% include '_types/market/admin/_nav.html' %}
{% endblock %}
{% block content %}
{% include "_types/market/admin/_main_panel.html" %}
{% endblock %}

View File

@@ -1,11 +0,0 @@
{% import 'macros/links.html' as links %}
{% macro header_row(oob=False) %}
{% call links.menu_row(id='market-admin-row', oob=oob) %}
{% call links.link(url_for('market.admin.admin'), hx_select_search) %}
{{ links.admin() }}
{% endcall %}
{% call links.desktop_nav() %}
{% include '_types/market/admin/_nav.html' %}
{% endcall %}
{% endcall %}
{% endmacro %}

View File

@@ -1,19 +0,0 @@
{% extends '_types/market/index.html' %}
{% block market_header_child %}
{% from '_types/root/_n/macros.html' import index_row with context %}
{% call index_row('market-admin-header-child', '_types/market/admin/header/_header.html') %}
{% block market_admin_header_child %}
{% endblock %}
{% endcall %}
{% endblock %}
{% block _main_mobile_menu %}
{% include '_types/market/admin/_nav.html' %}
{% endblock %}
{% block content %}
{% include '_types/market/admin/_main_panel.html' %}
{% endblock %}

View File

@@ -1,38 +0,0 @@
<!-- Desktop nav -->
<nav class="hidden md:flex gap-4 text-sm ml-2 w-full justify-end items-center">
{% set all_href = (url_for('market.browse.browse_all') ~ qs)|host %}
{% set all_active = (category_label == 'All Products') %}
<div class="relative nav-group">
<a
href="{{ all_href }}"
sx-get="{{ all_href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
aria-selected="{{ 'true' if all_active else 'false' }}"
class="block px-2 py-1 rounded text-center whitespace-normal break-words leading-snug bg-stone-200 text-black {{select_colours}}">
All
</a>
</div>
{% for cat, data in categories.items() %}
{% set cat_href = (url_for('market.browse.browse_top', top_slug=data.slug) ~ qs)|host%}
{% set cat_active = (cat == category_label) %}
<div class="relative nav-group">
<a
href="{{ cat_href }}"
sx-get="{{ cat_href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
aria-selected="{{ 'true' if cat_active else 'false' }}"
class="block px-2 py-1 rounded text-center whitespace-normal break-words leading-snug bg-stone-200 text-black {{select_colours}}"
>
{{ cat }}
</a>
</div>
{% endfor %}
{% include '_types/market/_admin.html' %}
</nav>

View File

@@ -1,11 +0,0 @@
{% import 'macros/links.html' as links %}
{% macro header_row(oob=False) %}
{% call links.menu_row(id='market-row', oob=oob) %}
{% call links.link(url_for('market.browse.home'), hx_select_search ) %}
{% include '_types/market/_title.html' %}
{% endcall %}
{% call links.desktop_nav() %}
{% include '_types/market/desktop/_nav.html' %}
{% endcall %}
{% endcall %}
{% endmacro %}

View File

@@ -1,27 +0,0 @@
{% extends '_types/root/_index.html' %}
{% block root_header_child %}
{% from '_types/root/_n/macros.html' import index_row with context %}
{% call index_row('post-header-child', '_types/post/header/_header.html') %}
{% call index_row('market-header-child', '_types/market/header/_header.html') %}
{% block market_header_child %}
{% endblock %}
{% endcall %}
{% endcall %}
{% endblock %}
{% block _main_mobile_menu %}
{% include '_types/market/mobile/_nav_panel.html' %}
{% endblock %}
{% block aside %}
{# No aside on landing page #}
{% endblock %}
{% block content %}
{% include "_types/market/_main_panel.html" %}
{% endblock %}

View File

@@ -1,23 +0,0 @@
{% extends '_types/root/_index.html' %}
{% block content %}
<div class="max-w-3xl mx-auto py-8 px-4">
<h1 class="text-2xl font-bold mb-6">Markets</h1>
{% if markets %}
<div class="grid gap-4">
{% for m in markets %}
<a href="/{{ m.page.slug }}/{{ m.slug }}/"
class="block p-6 bg-white border border-stone-200 rounded-lg hover:border-stone-400 transition-colors">
<h2 class="text-lg font-semibold">{{ m.name }}</h2>
{% if m.description %}
<p class="text-stone-600 mt-1">{{ m.description }}</p>
{% endif %}
</a>
{% endfor %}
</div>
{% else %}
<p class="text-stone-500">No markets available.</p>
{% endif %}
</div>
{% endblock %}

View File

@@ -1,110 +0,0 @@
{% from 'macros/glyphs.html' import opener %}
<div class="px-4 py-2">
<div class="divide-y">
{% set all_href = (url_for('market.browse.browse_all') ~ qs)|host %}
{% set all_active = (category_label == 'All Products') %}
<a role="option"
href="{{ all_href }}"
sx-get="{{ all_href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
aria-selected="{{ 'true' if all_active else 'false' }}"
class="block rounded-lg px-3 py-3 text-base hover:bg-stone-50 {{select_colours}}">
<div class="prose prose-stone max-w-none">
All
</div>
</a>
{% for cat, data in categories.items() %}
<details
class="group/cat py-1"
{% if top_slug == (data.slug | lower) %}open{% endif %}
>
<summary class="flex items-center justify-between cursor-pointer select-none block rounded-lg px-3 py-3 text-base hover:bg-stone-50 {% if top_slug==(data.slug | lower) %} bg-stone-900 text-white hover:bg-stone-900 {% endif %}">
{% set href = (url_for('market.browse.browse_top', top_slug=data.slug) ~ qs)|host %}
<a
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{ hx_select_search }}"
sx-swap="outerHTML"
sx-push-url="true"
aria-selected="{{ 'true' if top_slug==(data.slug | lower) else 'false' }}"
class="font-medium {{ select_colours }} flex flex-row gap-2"
>
<div>{{ cat }}</div>
<div aria-label="{{ data.count }} products">{{ data.count }}</div>
</a>
{{ opener('cat')}}
</summary>
<div class="pb-3 pl-2">
{% if data.subs %}
<!-- Viewport -->
<div
data-peek-viewport
data-peek-size-px="18"
data-peek-edge="bottom"
data-peek-mask="true"
class="m-2 bg-stone-100">
<!-- Inner list (no negative margin by default) -->
<div data-peek-inner class="grid grid-cols-1 gap-1 snap-y snap-mandatory pr-1" aria-label="Subcategories">
{% for sub in data.subs %}
{% set href = (url_for('market.browse.browse_sub', top_slug=data.slug, sub_slug=sub.slug) ~qs)|host%}
{% if top_slug==(data.slug | lower) and sub_slug == sub.slug %}
<a
class="snap-start px-2 py-3 rounded {{select_colours}} flex flex-row gap-2"
aria-selected="{{ 'true' if top_slug==(data.slug | lower) and sub_slug == sub.slug else 'false' }}"
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
>
<div>{{ sub.html_label or sub.name }}</div>
<div aria-label="{{ sub.count }} products">{{ sub.count }}</div>
</a>
{% endif %}
{% endfor %}
{% for sub in data.subs %}
{% if not (top_slug==(data.slug | lower) and sub_slug == sub.slug) %}
{% set href = (url_for('market.browse.browse_sub', top_slug=data.slug, sub_slug=sub.slug) ~ qs)|host%}
<a
class="snap-start px-2 py-3 rounded {{select_colours}} flex flex-row gap-2"
aria-selected="{{ 'true' if top_slug==(data.slug | lower) and sub_slug == sub.slug else 'false' }}"
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
>
<div>{{ sub.name }}</div>
<div aria-label="{{ sub.count }} products">{{ sub.count }}</div>
</a>
{% endif %}
{% endfor %}
</div>
</div>
{% else %}
{% set href = (url_for('market.browse.browse_top', top_slug=data.slug) ~ qs)|host%}
<a class="px-2 py-1 rounded hover:bg-stone-100 block"
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
>View all</a>
{% endif %}
</div>
</details>
{% endfor %}
{% include '_types/market/_admin.html' %}
</div>
</div>

View File

@@ -1,6 +0,0 @@
{% extends 'mobile/menu.html' %}
{% block menu %}
{% block mobile_menu %}
{% endblock %}
{% include '_types/market/mobile/_nav_panel.html' %}
{% endblock %}

View File

@@ -1,13 +0,0 @@
{# Card for a single market in a page-scoped listing #}
{% set market_href = market_url('/' ~ post.slug ~ '/' ~ market.slug ~ '/') %}
<article class="rounded-xl bg-white shadow-sm border border-stone-200 p-5 flex flex-col justify-between hover:border-stone-400 transition-colors">
<div>
<a href="{{ market_href }}" class="hover:text-emerald-700">
<h2 class="text-lg font-semibold text-stone-900">{{ market.name }}</h2>
</a>
{% if market.description %}
<p class="text-sm text-stone-600 mt-1 line-clamp-2">{{ market.description }}</p>
{% endif %}
</div>
</article>

View File

@@ -1,18 +0,0 @@
{% for market in markets %}
{% include "_types/page_markets/_card.html" %}
{% endfor %}
{% if has_more %}
{# Infinite scroll sentinel #}
{% set next_url = url_for('page_markets.markets_fragment', page=page + 1)|host %}
<div
id="sentinel-{{ page }}"
class="h-4 opacity-0 pointer-events-none"
sx-get="{{ next_url }}"
sx-trigger="intersect once delay:250ms"
sx-swap="outerHTML"
role="status"
aria-hidden="true"
>
<div class="text-center text-xs text-stone-400">loading...</div>
</div>
{% endif %}

View File

@@ -1,12 +0,0 @@
{# Markets grid for a single page #}
{% if markets %}
<div class="max-w-full px-3 py-3 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{% include "_types/page_markets/_cards.html" %}
</div>
{% else %}
<div class="px-3 py-12 text-center text-stone-400">
<i class="fa fa-store text-4xl mb-3" aria-hidden="true"></i>
<p class="text-lg">No markets for this page</p>
</div>
{% endif %}
<div class="pb-8"></div>

View File

@@ -1,15 +0,0 @@
{% extends '_types/root/_index.html' %}
{% block meta %}{% endblock %}
{% block root_header_child %}
{% from '_types/root/_n/macros.html' import index_row with context %}
{% call index_row('post-header-child', '_types/post/header/_header.html') %}
{% block post_header_child %}
{% endblock %}
{% endcall %}
{% endblock %}
{% block content %}
{% include '_types/page_markets/_main_panel.html' %}
{% endblock %}

View File

@@ -1,15 +0,0 @@
{% import 'macros/links.html' as links %}
{# Container nav from fragments (calendars, markets) #}
{% if container_nav_html %}
<div class="flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
id="entries-calendars-nav-wrapper">
{{ container_nav_html | safe }}
</div>
{% endif %}
{# Admin link #}
{% if post and has_access('blog.post.admin.admin') %}
{% call links.link(url_for('blog.post.admin.admin', slug=post.slug), hx_select_search, select_colours, True, aclass=styles.nav_button) %}
<i class="fa fa-cog" aria-hidden="true"></i>
{% endcall %}
{% endif %}

View File

@@ -1,41 +0,0 @@
{# Left scroll arrow - desktop only #}
<button
class="entries-nav-arrow hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded"
aria-label="Scroll left"
onclick="document.getElementById('associated-items-container').scrollLeft -= 200">
<i class="fa fa-chevron-left"></i>
</button>
{# Widget-driven nav items container #}
<div id="associated-items-container"
class="overflow-y-auto sm:overflow-x-auto sm:overflow-y-visible scrollbar-hide max-h-[50vh] sm:max-h-none"
style="scroll-behavior: smooth;"
data-scroll-arrows="entries-nav-arrow"
onscroll="(function(el){var arrows=document.getElementsByClassName('entries-nav-arrow');var show=window.innerWidth>=640&&el.scrollWidth>el.clientWidth;for(var i=0;i<arrows.length;i++){if(show){arrows[i].classList.remove('hidden');arrows[i].classList.add('flex')}else{arrows[i].classList.add('hidden');arrows[i].classList.remove('flex')}}})(this)">
<div class="flex flex-col sm:flex-row gap-1">
{% for wdata in container_nav_widgets %}
{% with ctx=wdata.ctx %}
{% include wdata.widget.template with context %}
{% endwith %}
{% endfor %}
</div>
</div>
<style>
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style>
{# Right scroll arrow - desktop only #}
<button
class="entries-nav-arrow hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded"
aria-label="Scroll right"
onclick="document.getElementById('associated-items-container').scrollLeft += 200">
<i class="fa fa-chevron-right"></i>
</button>

View File

@@ -1,28 +0,0 @@
{% import 'macros/links.html' as links %}
{% macro header_row(oob=False) %}
{% call links.menu_row(id='post-row', oob=oob) %}
{% call links.link(blog_url('/' + post.slug + '/'), hx_select_search ) %}
{% if post.feature_image %}
<img
src="{{ post.feature_image }}"
class="h-8 w-8 rounded-full object-cover border border-stone-300 flex-shrink-0"
>
{% endif %}
<span>
{{ post.title | truncate(160, True, '…') }}
</span>
{% endcall %}
{% call links.desktop_nav() %}
{% if page_cart_count is defined and page_cart_count > 0 %}
<a
href="{{ cart_url('/' + post.slug + '/') }}"
class="relative inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-full border border-emerald-300 bg-emerald-50 text-emerald-800 hover:bg-emerald-100 transition"
>
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
<span>{{ page_cart_count }}</span>
</a>
{% endif %}
{% include '_types/post/_nav.html' %}
{% endcall %}
{% endcall %}
{% endmacro %}

View File

@@ -1,17 +0,0 @@
{# HTMX response after add-to-cart: OOB-swap the mini cart + product buttons #}
{% import '_types/product/_cart.html' as _cart %}
{# 1. Update mini cart directly — handler already has the cart data #}
{% from 'macros/cart_icon.html' import cart_icon %}
{{ cart_icon(count=cart | sum(attribute="quantity")) }}
{# 2. Update add/remove buttons on the product #}
{{ _cart.add(d.slug, cart, oob='true') }}
{# 3. Update cart item row if visible #}
{% from '_types/product/_cart.html' import cart_item with context %}
{% if item and item.quantity > 0 %}
{{ cart_item(oob='true') }}
{% elif item %}
{{ cart_item(oob='delete') }}
{% endif %}

View File

@@ -1,250 +0,0 @@
{% macro add(slug, cart, oob='false') %}
{% set quantity = cart
| selectattr('product.slug', 'equalto', slug)
| sum(attribute='quantity') %}
<div id="cart-{{ slug }}" {% if oob=='true' %} sx-swap-oob="{{oob}}" {% endif %}>
{% if not quantity %}
<form
action="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
method="post"
sx-post="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
sx-target="#cart-mini"
sx-swap="outerHTML"
class="rounded flex items-center"
>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input
type="hidden"
name="count"
value="1"
>
<button
type="submit"
class="relative inline-flex items-center justify-center text-sm font-medium text-stone-500 hover:bg-emerald-50"
>
<span class="relative inline-flex items-center justify-center">
<i class="fa fa-cart-plus text-4xl" aria-hidden="true"></i>
<!-- black + overlaid in the center -->
</span>
</button>
</form>
{% else %}
<div class="rounded flex items-center gap-2">
<!-- minus -->
<form
action="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
method="post"
sx-post="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
sx-target="#cart-mini"
sx-swap="outerHTML"
>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input
type="hidden"
name="count"
value="{{ quantity - 1 }}"
>
<button
type="submit"
class="inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl"
>
-
</button>
</form>
<!-- basket with quantity badge -->
<a
class="relative inline-flex items-center justify-center text-emerald-700"
href="{{ cart_url('/') }}"
>
<span class="relative inline-flex items-center justify-center">
<i class="fa-solid fa-shopping-cart text-2xl" aria-hidden="true"></i>
<span
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none"
>
<span
class="flex items-center justify-center bg-black text-white rounded-full w-4 h-4 text-xs font-bold"
>
{{ quantity }}
</span>
</span>
</span>
</a>
<!-- plus -->
<form
action="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
method="post"
sx-post="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
sx-target="#cart-mini"
sx-swap="outerHTML"
>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input
type="hidden"
name="count"
value="{{ quantity + 1 }}"
>
<button
type="submit"
class="inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl"
>
+
</button>
</form>
</div>
{% endif %}
</div>
{% endmacro %}
{% macro cart_item(oob=False) %}
{% set p = item.product %}
{% set unit_price = p.special_price or p.regular_price %}
<article
id="cart-item-{{p.slug}}"
{% if oob %}
sx-swap-oob="{{oob}}"
{% endif %}
class="flex flex-col sm:flex-row gap-3 sm:gap-4 rounded-2xl bg-white shadow-sm border border-stone-200 p-3 sm:p-4 md:p-5"
>
<div class="w-full sm:w-32 shrink-0 flex justify-center sm:block">
{% if p.image %}
<img
src="{{ p.image }}"
alt="{{ p.title }}"
class="w-24 h-24 sm:w-32 sm:h-28 object-cover rounded-xl border border-stone-100"
loading="lazy"
>
{% else %}
<div
class="w-24 h-24 sm:w-32 sm:h-28 rounded-xl border border-dashed border-stone-300 flex items-center justify-center text-xs text-stone-400"
>
No image
</div>'market', 'product', p.slug
{% endif %}
</div>
{# Details #}
<div class="flex-1 min-w-0">
<div class="flex flex-col sm:flex-row sm:items-start justify-between gap-2 sm:gap-3">
<div class="min-w-0">
<h2 class="text-sm sm:text-base md:text-lg font-semibold text-stone-900">
{% set href=url_for('market.browse.product.product_detail', product_slug=p.slug) %}
<a
href="{{ href }}"
hx_get="{{href}}"
sx-target="#main-panel"
sx-select ="{{hx_select_search}}"
sx-swap="outerHTML"
sx-push-url="true"
class="hover:text-emerald-700"
>
{{ p.title }}
</a>
</h2>
{% if p.brand %}
<p class="mt-0.5 text-[0.7rem] sm:text-xs text-stone-500">
{{ p.brand }}
</p>
{% endif %}
{% if item.is_deleted %}
<p class="mt-2 inline-flex items-center gap-1 text-[0.65rem] sm:text-xs font-medium text-amber-700 bg-amber-50 border border-amber-200 rounded-full px-2 py-0.5">
<i class="fa-solid fa-triangle-exclamation text-[0.6rem]" aria-hidden="true"></i>
This item is no longer available or price has changed
</p>
{% endif %}
</div>
{# Unit price #}
<div class="text-left sm:text-right">
{% if unit_price %}
{% set symbol = "£" if p.regular_price_currency == "GBP" else p.regular_price_currency %}
<p class="text-sm sm:text-base font-semibold text-stone-900">
{{ symbol }}{{ "%.2f"|format(unit_price) }}
</p>
{% if p.special_price and p.special_price != p.regular_price %}
<p class="text-xs text-stone-400 line-through">
{{ symbol }}{{ "%.2f"|format(p.regular_price) }}
</p>
{% endif %}
{% else %}
<p class="text-xs text-stone-500">No price</p>
{% endif %}
</div>
</div>
<div class="mt-3 flex flex-col sm:flex-row sm:items-center justify-between gap-2 sm:gap-4">
<div class="flex items-center gap-2 text-xs sm:text-sm text-stone-700">
<span class="text-[0.65rem] sm:text-xs uppercase tracking-wide text-stone-500">Quantity</span>
<form
action="{{ url_for('market.browse.product.cart', product_slug=p.slug) }}"
method="post"
sx-post="{{ url_for('market.browse.product.cart', product_slug=p.slug) }}"
sx-target="#cart-mini"
sx-swap="outerHTML"
>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input
type="hidden"
name="count"
value="{{ item.quantity - 1 }}"
>
<button
type="submit"
class="inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl"
>
-
</button>
</form>
<span class="inline-flex items-center justify-center px-2 py-1 rounded-full bg-stone-100 text-[0.7rem] sm:text-xs font-medium">
{{ item.quantity }}
</span>
<form
action="{{ url_for('market.browse.product.cart', product_slug=p.slug) }}"
method="post"
sx-post="{{ url_for('market.browse.product.cart', product_slug=p.slug) }}"
sx-target="#cart-mini"
sx-swap="outerHTML"
>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input
type="hidden"
name="count"
value="{{ item.quantity + 1 }}"
>
<button
type="submit"
class="inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl"
>
+
</button>
</form>
</div>
<div class="flex items-center justify-between sm:justify-end gap-3">
{% if unit_price %}
{% set line_total = unit_price * item.quantity %}
{% set symbol = "£" if p.regular_price_currency == "GBP" else p.regular_price_currency %}
<p class="text-sm sm:text-base font-semibold text-stone-900">
Line total:
{{ symbol }}{{ "%.2f"|format(line_total) }}
</p>
{% endif %}
</div>
</div>
</div>
</article>
{% endmacro %}

View File

@@ -1,131 +0,0 @@
{# Main panel fragment for HTMX navigation - product detail content #}
{% import 'macros/stickers.html' as stick %}
{% import '_types/product/prices.html' as prices %}
{% set prices_ns = namespace() %}
{{ prices.set_prices(d, prices_ns)}}
{# Product detail grid from content block #}
<div class="mt-3 grid grid-cols-1 md:grid-cols-5 gap-6" data-gallery-root>
<div class="md:col-span-2">
{% if d.images and d.images|length > 0 %}
<div class="relative rounded-xl overflow-hidden bg-stone-100">
{# --- like button overlay in top-right --- #}
{% if g.user %}
<div class="absolute top-3 right-5 z-10 text-6xl md:text-xl">
{% set slug = d.slug %}
{% set liked = liked_by_current_user %}
{% include "_types/browse/like/button.html" %}
</div>
{% endif %}
<figure class="inline-block">
<div class="relative w-full aspect-square">
<img
data-main-img
src="{{ d.images[0] }}"
alt="{{ d.title }}"
class="w-full h-full object-contain object-top"
loading="eager" decoding="async"
/>
{% for l in d.labels %}
<img
src="{{ asset_url('labels/' + l + '.svg') }}"
alt=""
class="pointer-events-none absolute inset-0 w-full h-full object-contain object-top"
/>
{% endfor %}
</div>
<figcaption class="mt-2 text-sm text-stone-600 text-center">
{{ d.brand }}
</figcaption>
</figure>
{% if d.images|length > 1 %}
<button type="button" data-prev
class="absolute left-2 top-1/2 -translate-y-1/2 z-10 grid place-items-center w-12 h-12 md:w-14 md:h-14 rounded-full bg-white/90 hover:bg-white shadow-lg focus:outline-none focus:ring-2 focus:ring-stone-300 text-3xl md:text-4xl"
title="Previous"></button>
<button type="button" data-next
class="absolute right-2 top-1/2 -translate-y-1/2 z-10 grid place-items-center w-12 h-12 md:w-14 md:h-14 rounded-full bg-white/90 hover:bg-white shadow-lg focus:outline-none focus:ring-2 focus:ring-stone-300 text-3xl md:text-4xl"
title="Next"></button>
{% endif %}
</div>
<div class="flex flex-row justify-center">
<div class="mt-3 flex gap-2 overflow-x-auto no-scrollbar">
{% for u in d.images %}
<button type="button" data-thumb
class="shrink-0 rounded-lg overflow-hidden bg-stone-100 hover:opacity-90 ring-offset-2"
title="Image {{ loop.index }}">
<img src="{{ u }}" class="h-16 w-16 object-contain" alt="thumb {{ loop.index }}" loading="lazy" decoding="async">
</button>
<span data-image-src="{{ u }}" class="hidden"></span>
{% endfor %}
</div>
</div>
{% else %}
<div class="relative aspect-square bg-stone-100 rounded-xl flex items-center justify-center text-stone-400">
{# Even if no image, still render the like button in the corner for consistency #}
{% if g.user %}
<div class="absolute top-2 right-2 z-10">
{% set slug = d.slug %}
{% set liked = liked_by_current_user %}
{% include "_types/browse/like/button.html" %}
</div>
{% endif %}
No image
</div>
{% endif %}
<div class="p-2 flex flex-row justify-center gap-2">
{% for s in d.stickers %}
{{ stick.sticker(asset_url('stickers/' + s + '.svg'), s, True, size=40) }}
{% endfor %}
</div>
</div>
<div class="md:col-span-3">
{# Optional extras shown quietly #}
<div class="mt-2 space-y-1 text-sm text-stone-600">
{% if d.price_per_unit or d.price_per_unit_raw %}
<div>Unit price: {{ prices.price_str(d.price_per_unit, d.price_per_unit_raw, d.price_per_unit_currency) }}</div>
{% endif %}
{% if d.case_size_raw %}
<div>Case size: {{ d.case_size_raw }}</div>
{% endif %}
</div>
{% if d.description_short or d.description_html %}
<div class="mt-4 text-stone-800 space-y-3">
{% if d.description_short %}
<p class="leading-relaxed text-lg">{{ d.description_short }}</p>
{% endif %}
{% if d.description_html %}
<div class="max-w-none text-sm leading-relaxed">
{{ d.description_html | safe }}
</div>
{% endif %}
</div>
{% endif %}
{% if d.sections and d.sections|length %}
<div class="mt-8 space-y-3">
{% for sec in d.sections %}
<details class="group rounded-xl border bg-white shadow-sm open:shadow p-0">
<summary class="cursor-pointer select-none px-4 py-3 flex items-center justify-between">
<span class="font-medium">{{ sec.title }}</span>
<span class="ml-2 text-xl transition-transform group-open:rotate-180"></span>
</summary>
<div class="px-4 pb-4 max-w-none text-sm leading-relaxed">
{{ sec.html | safe }}
</div>
</details>
{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="pb-8"></div>

View File

@@ -1,106 +0,0 @@
{# --- social/meta_product.html --- #}
{# Context expected:
site, d (Product), request
#}
{# Visibility → robots: index unless soft-deleted #}
{% set robots_here = 'noindex,nofollow' if d.deleted_at else 'index,follow' %}
{# Compute canonical #}
{% set _site_url = site().url.rstrip('/') if site and site().url else '' %}
{% set _product_path = request.path if request else ('/products/' ~ (d.slug or '')) %}
{% set canonical = _site_url ~ _product_path if _site_url else (request.url if request else None) %}
{# Include common base (charset, viewport, robots default, RSS, Org/WebSite JSON-LD) #}
{% set robots_override = robots_here %}
{% include 'social/meta_base.html' %}
{# ---- Titles / descriptions ---- #}
{% set base_product_title = d.title or base_title %}
{% set og_title = base_product_title %}
{% set tw_title = base_product_title %}
{# Description: prefer short, then HTML stripped #}
{% set desc_source = d.description_short
or (d.description_html|striptags if d.description_html else '') %}
{% set description = (desc_source|trim|replace('\n',' ')|replace('\r',' ')|striptags)|truncate(160, True, '…') %}
{# ---- Image priority: product image, then first gallery image, then site default ---- #}
{% set image_url = d.image
or ((d.images|first).url if d.images and (d.images|first).url else None)
or (site().default_image if site and site().default_image else None) %}
{# ---- Price / offer helpers ---- #}
{% set price = d.special_price or d.regular_price or d.rrp %}
{% set price_currency = d.special_price_currency or d.regular_price_currency or d.rrp_currency %}
{# ---- Basic meta ---- #}
<title>{{ base_product_title }}</title>
<meta name="description" content="{{ description }}">
{% if canonical %}<link rel="canonical" href="{{ canonical }}">{% endif %}
{# ---- Open Graph ---- #}
<meta property="og:site_name" content="{{ site().title if site and site().title else '' }}">
<meta property="og:type" content="product">
<meta property="og:title" content="{{ og_title }}">
<meta property="og:description" content="{{ description }}">
{% if canonical %}<meta property="og:url" content="{{ canonical }}">{% endif %}
{% if image_url %}<meta property="og:image" content="{{ image_url }}">{% endif %}
{# Optional product OG price tags #}
{% if price and price_currency %}
<meta property="product:price:amount" content="{{ '%.2f'|format(price) }}">
<meta property="product:price:currency" content="{{ price_currency }}">
{% endif %}
{% if d.brand %}
<meta property="product:brand" content="{{ d.brand }}">
{% endif %}
{% if d.sku %}
<meta property="product:retailer_item_id" content="{{ d.sku }}">
{% endif %}
{# ---- Twitter ---- #}
<meta name="twitter:card" content="{{ 'summary_large_image' if image_url else 'summary' }}">
{% if site and site().twitter_site %}<meta name="twitter:site" content="{{ site().twitter_site }}">{% endif %}
<meta name="twitter:title" content="{{ tw_title }}">
<meta name="twitter:description" content="{{ description }}">
{% if image_url %}<meta name="twitter:image" content="{{ image_url }}">{% endif %}
{# ---- JSON-LD Product ---- #}
{% set jsonld = {
"@context": "https://schema.org",
"@type": "Product",
"name": d.title,
"image": image_url,
"description": description,
"sku": d.sku,
"brand": d.brand,
"url": canonical
} %}
{# Brand as proper object if present #}
{% if d.brand %}
{% set jsonld = jsonld | combine({
"brand": {
"@type": "Brand",
"name": d.brand
}
}) %}
{% endif %}
{# Offers if price available #}
{% if price and price_currency %}
{% set jsonld = jsonld | combine({
"offers": {
"@type": "Offer",
"price": price,
"priceCurrency": price_currency,
"url": canonical,
"availability": "https://schema.org/InStock"
}
}) %}
{% endif %}
<script type="application/ld+json">
{{ jsonld | tojson }}
</script>

View File

@@ -1,49 +0,0 @@
{% extends 'oob_elements.html' %}
{# OOB elements for HTMX navigation - product extends browse so use similar structure #}
{% import 'macros/layout.html' as layout %}
{% import 'macros/stickers.html' as stick %}
{% import '_types/product/prices.html' as prices %}
{% set prices_ns = namespace() %}
{{ prices.set_prices(d, prices_ns)}}
{# 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 %}
{% block oobs %}
{% from '_types/market/header/_header.html' import header_row with context %}
{{ header_row(oob=True) }}
{% from '_types/root/_n/macros.html' import oob_header with context %}
{{oob_header('market-header-child', 'product-header-child', '_types/product/header/_header.html')}}
{% endblock %}
{% block mobile_menu %}
{% include '_types/market/mobile/_nav_panel.html' %}
{% include '_types/browse/_admin.html' %}
{% endblock %}
{% block filter %}
{% call layout.details() %}
{% call layout.summary('blog-child-header') %}
{% endcall %}
{% call layout.menu('blog-child-menu') %}
{% endcall %}
{% endcall %}
{% call layout.details() %}
{% call layout.summary('product-child-header') %}
{% endcall %}
{% call layout.menu('item-child-menu') %}
{% endcall %}
{% endcall %}
{% endblock %}
{% block content %}
{% include '_types/product/_main_panel.html' %}
{% endblock %}

View File

@@ -1,33 +0,0 @@
{% import '_types/product/_cart.html' as _cart %}
{# ---- Price block ---- #}
{% import '_types/product/prices.html' as prices %}
{% set prices_ns = namespace() %}
{{ prices.set_prices(d, prices_ns)}}
<div class="flex flex-row items-center justify-between md:gap-2 md:px-2">
{{ _cart.add(d.slug, cart)}}
{% if prices_ns.sp_val %}
<div class="text-md font-bold text-emerald-700">
Special price
</div>
<div class="text-xl font-semibold text-emerald-700">
{{ prices.price_str(prices_ns.sp_val, prices_ns.sp_raw, prices_ns.sp_cur) }}
</div>
{% if prices_ns.sp_val and prices_ns.rp_val %}
<div class="text-base text-md line-through text-stone-500">
{{ prices.price_str(prices_ns.rp_val, prices_ns.rp_raw, prices_ns.rp_cur) }}
</div>
{% endif %}
{% elif prices_ns.rp_val %}
<div class="hidden md:block text-xl font-bold">
Our price
</div>
<div class="text-xl font-semibold">
{{ prices.price_str(prices_ns.rp_val, prices_ns.rp_raw, prices_ns.rp_cur) }}
</div>
{% endif %}
{{ prices.rrp(prices_ns) }}
</div>

View File

@@ -1,2 +0,0 @@
<i class="fa fa-shopping-bag" aria-hidden="true"></i>
<div>{{ d.title }}</div>

View File

@@ -1,2 +0,0 @@
{% from 'macros/admin_nav.html' import placeholder_nav %}
{{ placeholder_nav() }}

View File

@@ -1,40 +0,0 @@
{% 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 %}
{% block oobs %}
{% from '_types/root/_n/macros.html' import oob_header with context %}
{{oob_header('product-header-child', 'product-admin-header-child', '_types/product/admin/header/_header.html')}}
{% from '_types/product/header/_header.html' import header_row with context %}
{{ header_row(oob=True) }}
{% endblock %}
{% from '_types/root/_n/macros.html' import header with context %}
{% call header(id='product-header-child', oob=True) %}
{% call header() %}
{% from '_types/product/admin/header/_header.html' import header_row with context %}
{{header_row()}}
<div id="product-admin-header-header-child">
</div>
{% endcall %}
{% endcall %}
{% block mobile_menu %}
{% include '_types/product/admin/_nav.html' %}
{% endblock %}
{% block content %}
{% include '_types/product/_main_panel.html' %}
{% endblock %}

View File

@@ -1,11 +0,0 @@
{% import 'macros/links.html' as links %}
{% macro header_row(oob=False) %}
{% call links.menu_row(id='product-admin-row', oob=oob) %}
{% call links.link(url_for('market.browse.product.admin', product_slug=d.slug), hx_select_search ) %}
admin!!
{% endcall %}
{% call links.desktop_nav() %}
{% include '_types/product/admin/_nav.html' %}
{% endcall %}
{% endcall %}
{% endmacro %}

View File

@@ -1,39 +0,0 @@
{% extends '_types/product/index.html' %}
{% import 'macros/layout.html' as layout %}
{% block product_header_child %}
{% from '_types/root/_n/macros.html' import index_row with context %}
{% call index_row('market-header-child', '_types/product/admin/header/_header.html') %}
{% block product_admin_header_child %}
{% endblock %}
{% endcall %}
{% endblock %}
{% block ___app_title %}
{% import 'macros/links.html' as links %}
{% call links.menu_row() %}
{% call links.link(url_for('market.browse.product.admin', product_slug=slug), hx_select_search) %}
{{ links.admin() }}
{% endcall %}
{% call links.desktop_nav() %}
{% include '_types/product/admin/_nav.html' %}
{% endcall %}
{% endcall %}
{% endblock %}
{% block _main_mobile_menu %}
{% include '_types/product/admin/_nav.html' %}
{% endblock %}
{% block aside %}
{% endblock %}
{% block content %}
{% include '_types/product/_main_panel.html' %}
{% endblock %}

View File

@@ -1,15 +0,0 @@
{% import 'macros/links.html' as links %}
{% macro header_row(oob=False) %}
{% call links.menu_row(id='product-row', oob=oob) %}
{% call links.link(url_for('market.browse.product.product_detail', product_slug=d.slug), hx_select_search ) %}
{% include '_types/product/_title.html' %}
{% endcall %}
{% include '_types/product/_prices.html' %}
{% call links.desktop_nav() %}
{% include '_types/browse/_admin.html' %}
{% endcall %}
{% endcall %}
{% endmacro %}

View File

@@ -1,61 +0,0 @@
{% extends '_types/browse/index.html' %}
{% block meta %}
{% include '_types/product/_meta.html' %}
{% endblock %}
{% import 'macros/stickers.html' as stick %}
{% import '_types/product/prices.html' as prices %}
{% set prices_ns = namespace() %}
{{ prices.set_prices(d, prices_ns)}}
{% block market_header_child %}
{% from '_types/root/_n/macros.html' import index_row with context %}
{% call index_row('market-header-child', '_types/product/header/_header.html') %}
{% block product_header_child %}
{% endblock %}
{% endcall %}
{% endblock %}
{% block _main_mobile_menu %}
{% include '_types/browse/_admin.html' %}
{% endblock %}
{% block filter %}
{% call layout.details() %}
{% call layout.summary('blog-child-header') %}
{% block blog_child_summary %}
{% endblock %}
{% endcall %}
{% call layout.menu('blog-child-menu') %}
{% block post_child_menu %}
{% endblock %}
{% endcall %}
{% endcall %}
{% call layout.details() %}
{% call layout.summary('product-child-header') %}
{% block item_child_summary %}
{% endblock %}
{% endcall %}
{% call layout.menu('item-child-menu') %}
{% block item_child_menu %}
{% endblock %}
{% endcall %}
{% endcall %}
{% endblock %}
{% block aside %}
{% endblock %}
{% block content %}
{% include '_types/product/_main_panel.html' %}
{% endblock %}

View File

@@ -1,66 +0,0 @@
{# ---- Price formatting helpers ---- #}
{% set _sym = {'GBP':'£','EUR':'€','USD':'$'} %}
{% macro price_str(val, raw, cur) -%}
{%- if raw -%}
{{ raw }}
{%- elif val is number -%}
{{ (_sym.get(cur) or '') ~ ('%.2f'|format(val)) }}
{%- else -%}
{{ val or '' }}
{%- endif -%}
{%- endmacro %}
{% macro set_prices(item, ns) -%}
{% set ns.sp_val = item.special_price or (item.oe_list_price and item.oe_list_price.special) %}
{% set ns.sp_raw = item.special_price_raw or (item.oe_list_price and item.oe_list_price.special_raw) %}
{% set ns.sp_cur = item.special_price_currency or (item.oe_list_price and item.oe_list_price.special_currency) %}
{% set ns.rp_val = item.regular_price or item.rrp or (item.oe_list_price and item.oe_list_price.rrp) %}
{% set ns.rp_raw = item.regular_price_raw or item.rrp_raw or (item.oe_list_price and item.oe_list_price.rrp_raw) %}
{% set ns.rp_cur = item.regular_price_currency or item.rrp_currency or (item.oe_list_price and item.oe_list_price.rrp_currency) %}
{% set ns.case_size_count = (item.case_size_count or 1) %}
{% set ns.rrp = item.rrp_raw[0] ~ "%.2f"|format(item.rrp * (ns.case_size_count)) %}
{% set ns.rrp_raw = item.rrp_raw %}
{%- endmacro %}
{% macro rrp(ns) -%}
{% if ns.rrp %}
<div class="text-base md:text-lgtext-stone-400">
<span>rrp:</span>
<span>
{{ ns.rrp }}
</span>
</div>
{% endif %}
{%- endmacro %}
{% macro card_price(item) %}
{# price block unchanged #}
{% set _sym = {'GBP':'£','EUR':'€','USD':'$'} %}
{% set sp_val = item.special_price or (item.oe_list_price and item.oe_list_price.special) %}
{% set sp_raw = item.special_price_raw or (item.oe_list_price and item.oe_list_price.special_raw) %}
{% set sp_cur = item.special_price_currency or (item.oe_list_price and item.oe_list_price.special_currency) %}
{% set rp_val = item.regular_price or item.rrp or (item.oe_list_price and item.oe_list_price.rrp) %}
{% set rp_raw = item.regular_price_raw or item.rrp_raw or (item.oe_list_price and item.oe_list_price.rrp_raw) %}
{% set rp_cur = item.regular_price_currency or item.rrp_currency or (item.oe_list_price and item.oe_list_price.rrp_currency) %}
{% set sp_str = sp_raw if sp_raw else ( (_sym.get(sp_cur, '') ~ ('%.2f'|format(sp_val))) if sp_val is number else (sp_val or '')) %}
{% set rp_str = rp_raw if rp_raw else ( (_sym.get(rp_cur, '') ~ ('%.2f'|format(rp_val))) if rp_val is number else (rp_val or '')) %}
<div class="mt-1 flex items-baseline gap-2 justify-center">
{% if sp_val %}
<div class="text-lg font-semibold text-emerald-700">{{ sp_str }}</div>
{% if rp_val %}
<div class="text-sm line-through text-stone-500">{{ rp_str }}</div>
{% endif %}
{% elif rp_val %}
<div class="mt-1 text-lg font-semibold">{{ rp_str }}</div>
{% endif %}
</div>
{% endmacro %}

View File

@@ -1,7 +0,0 @@
<aside
id="aside"
sx-swap-oob="outerHTML"
class="hidden"
>
</aside>

View File

@@ -1,5 +0,0 @@
<div
id="filter"
sx-swap-oob="outerHTML"
>
</div>

View File

@@ -1,117 +0,0 @@
{#
Unified filter macros for browse/shop pages
Consolidates duplicate mobile/desktop filter components
#}
{% macro filter_item(href, is_on, title, icon_html, count=none, variant='desktop') %}
{#
Generic filter item (works for labels, stickers, etc.)
variant: 'desktop' or 'mobile'
#}
{% set base_class = "flex flex-col items-center justify-center" %}
{% if variant == 'mobile' %}
{% set item_class = base_class ~ " p-1 rounded hover:bg-stone-50" %}
{% set count_class = "text-[10px] text-stone-500 mt-1 leading-none tabular-nums" if count != 0 else "text-md text-red-500 font-bold mt-1 leading-none tabular-nums" %}
{% else %}
{% set item_class = base_class ~ " py-2 w-full h-full" %}
{% set count_class = "text-xs text-stone-500 leading-none justify-self-end tabular-nums" if count != 0 else "text-md text-red-500 font-bold leading-none justify-self-end tabular-nums" %}
{% endif %}
<a
href="{{ href }}"
sx-get="{{ href }}"
sx-target="#main-panel"
sx-select="{{ hx_select_search }}"
sx-swap="outerHTML"
sx-push-url="true"
role="button"
aria-pressed="{{ 'true' if is_on else 'false' }}"
title="{{ title }}"
aria-label="{{ title }}"
class="{{ item_class }}"
>
{{ icon_html | safe }}
{% if count is not none %}
<span class="{{ count_class }}">{{ count }}</span>
{% endif %}
</a>
{% endmacro %}
{% macro labels_list(labels, selected_labels, current_local_href, variant='desktop') %}
{#
Unified labels filter list
variant: 'desktop' or 'mobile'
#}
{% import 'macros/stickers.html' as stick %}
{% if variant == 'mobile' %}
<nav aria-label="labels" class="px-4 pb-3">
<ul class="flex w-full items-start justify-center gap-3 overflow-x-auto overflow-y-visible pr-1 no-scrollbar">
{% else %}
<ul id="labels-details-desktop" class="flex justify-center p-0 m-0 gap-2" >
{% endif %}
{% for s in labels %}
{% set is_on = (selected_labels and (s.name|lower in selected_labels)) %}
{% set qs = {"remove_label": s.name, "page": None}|qs if is_on else {"add_label": s.name, "page": None}|qs %}
{% set href = (current_local_href ~ qs)|host %}
<li class="{{ 'list-none shrink-0' if variant == 'mobile' else '' }}">
{{ filter_item(
href, is_on, s.name,
stick.sticker(asset_url('nav-labels/' ~ s.name ~ '.svg'), s.name, is_on),
s.count, variant
) }}
</li>
{% endfor %}
</ul>
{% if variant == 'mobile' %}
</nav>
{% endif %}
{% endmacro %}
{% macro stickers_list(stickers, selected_stickers, current_local_href, variant='desktop') %}
{#
Unified stickers filter list
variant: 'desktop' or 'mobile'
#}
{% import 'macros/stickers.html' as stick %}
{% if variant == 'mobile' %}
<nav aria-label="stickers" class="px-4 pb-3">
<ul class="flex w-full items-start justify-center gap-3 overflow-x-auto overflow-y-visible pr-1 no-scrollbar">
{% else %}
<ul id="stickers-details-desktop"
class="flex flex-wrap justify-center w-full p-0 m-0 border bg-white shadow-sm rounded-xl gap-1 [&>li]:list-none [&>li]:basis-[20%] [&>li]:max-w-[20%] [&>li]:grow-0"
>
{% endif %}
{% for s in stickers %}
{% set is_on = (selected_stickers and (s.name|lower in selected_stickers)) %}
{% set qs = {"remove_sticker": s.name, "page": None}|qs if is_on else {"add_sticker": s.name, "page": None}|qs %}
{% set href = (current_local_href ~ qs)|host %}
{% set display_name = s.name|capitalize if s.name|lower != 'sugarfree' else 'Sugar' %}
<li class="{{ 'list-none shrink-0' if variant == 'mobile' else '' }}">
{% set icon_html %}
<span class="{{ 'text-sm' if variant == 'mobile' else 'text-[11px]' }}">{{ display_name }}</span>
{{ stick.sticker(asset_url('stickers/' ~ s.name ~ '.svg'), s.name, is_on) }}
{% endset %}
{{ filter_item(href, is_on, s.name, icon_html, s.count, variant) }}
</li>
{% endfor %}
</ul>
{% if variant == 'mobile' %}
</nav>
<style>
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
</style>
{% endif %}
{% endmacro %}