/**
 * <folk-cart-shop> — group shopping carts, cosmolocal catalog, and orders.
 * Three-tab view: Carts (default) | Catalog | Orders
 */

import {
	catalogSchema, catalogDocId, type CatalogDoc,
	orderSchema, type OrderDoc,
	shoppingCartIndexSchema, shoppingCartIndexDocId, type ShoppingCartIndexDoc,
	shoppingCartSchema, shoppingCartDocId, type ShoppingCartDoc,
} from "../schemas";
import type { DocumentId } from "../../../shared/local-first/document";
import { TourEngine } from "../../../shared/tour-engine";
import { ViewHistory } from "../../../shared/view-history.js";
import { startPresenceHeartbeat } from '../../../shared/collab-presence';

class FolkCartShop extends HTMLElement {
	private shadow: ShadowRoot;
	private space = "default";
	private catalog: any[] = [];
	private orders: any[] = [];
	private carts: any[] = [];
	private payments: any[] = [];
	private groupBuys: any[] = [];
	private view: "carts" | "cart-detail" | "catalog" | "catalog-detail" | "orders" | "order-detail" | "payments" | "group-buys" = "carts";
	private selectedCartId: string | null = null;
	private selectedCart: any = null;
	private selectedCatalogItem: any = null;
	private selectedOrder: any = null;
	private detailQuantity = 1;
	private orderQueue: any[] = [];
	private orderQueueOpen = false;
	private loading = true;
	private addingUrl = false;
	private contributingAmount = false;
	private extensionInstalled = false;
	private bannerDismissed = false;
	private creatingPayment = false;
	private creatingGroupBuy = false;
	private _offlineUnsubs: (() => void)[] = [];
	private _subscribedDocIds: string[] = [];
	private _history = new ViewHistory<"carts" | "cart-detail" | "catalog" | "catalog-detail" | "orders" | "order-detail" | "payments" | "group-buys">("carts", "rcart");
	private _stopPresence: (() => void) | null = null;

	// Guided tour
	private _tour!: TourEngine;
	private static readonly TOUR_STEPS = [
		{ target: "[data-action='new-cart']", title: "Create a Cart", message: "Start a new group cart — add a name and invite contributors to add items. Use the navigation bar above to switch between Carts, Catalog, Group Buys, Orders, and Payments.", advanceOnClick: true },
	];

	constructor() {
		super();
		this.shadow = this.attachShadow({ mode: "open" });
		this._tour = new TourEngine(
			this.shadow,
			FolkCartShop.TOUR_STEPS,
			"rcart_tour_done",
			() => this.shadow.host as HTMLElement,
		);
	}

	connectedCallback() {
		this.extensionInstalled = !!(window as any).__rspaceExtensionInstalled;
		this.bannerDismissed = localStorage.getItem('rcart-ext-banner-dismissed') === 'true';

		const attr = this.getAttribute("space");
		if (attr) {
			this.space = attr;
		} else {
			const parts = window.location.pathname.split("/").filter(Boolean);
			this.space = parts.length >= 1 ? parts[0] : "default";
		}

		// Read initial view from attribute (set by server routes) or URL params
		const initView = this.getAttribute("initial-view");
		if (initView && ["carts","catalog","orders","order-detail","payments","group-buys","subscriptions"].includes(initView)) {
			this.view = initView as any;
		}
		const params = new URLSearchParams(window.location.search);
		if (params.get('tab') === 'catalog') this.view = 'catalog';
		if (params.get('tab') === 'group-buys') this.view = 'group-buys';

		if (this.space === "demo") {
			this.loadDemoData();
		} else {
			this.render();
			this.subscribeOffline();
			this.loadData();
		}
		// Auto-start tour on first visit
		if (!localStorage.getItem("rcart_tour_done")) {
			setTimeout(() => this._tour.start(), 1200);
		}
		this._stopPresence = startPresenceHeartbeat(() => ({ module: 'rcart', context: this.selectedCatalogItem?.title || this.view }));
		window.addEventListener('rspace-view-restored', this._onViewRestored as EventListener);
	}

	disconnectedCallback() {
		this._history.destroy();
		window.removeEventListener('rspace-view-restored', this._onViewRestored as EventListener);
		for (const unsub of this._offlineUnsubs) unsub();
		this._offlineUnsubs = [];
		this._stopPresence?.();
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (runtime) {
			for (const id of this._subscribedDocIds) runtime.unsubscribe(id);
		}
		this._subscribedDocIds = [];
	}

	private async subscribeOffline() {
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime?.isInitialized) return;

		try {
			// Subscribe to catalog
			const catDocId = catalogDocId(this.space) as DocumentId;
			const catDoc = await runtime.subscribe(catDocId, catalogSchema);
			this._subscribedDocIds.push(catDocId);
			if (catDoc?.items && Object.keys(catDoc.items).length > 0 && this.catalog.length === 0) {
				this.catalog = Object.values((catDoc as CatalogDoc).items).map(item => ({
					id: item.id, title: item.title, description: '',
					product_type: item.productType, status: item.status,
					tags: item.tags || [],
					created_at: new Date(item.createdAt).toISOString(),
				}));
				this.loading = false;
				this.render();
			}
			this._offlineUnsubs.push(runtime.onChange(catDocId, (doc: CatalogDoc) => {
				if (doc?.items) {
					this.catalog = Object.values(doc.items).map(item => ({
						id: item.id, title: item.title, description: '',
						product_type: item.productType, status: item.status,
						tags: item.tags || [],
						created_at: new Date(item.createdAt).toISOString(),
					}));
					this.render();
				}
			}));

			// Subscribe to shopping cart index
			const indexDocId = shoppingCartIndexDocId(this.space) as DocumentId;
			const indexDoc = await runtime.subscribe(indexDocId, shoppingCartIndexSchema);
			this._subscribedDocIds.push(indexDocId);
			if (indexDoc?.carts && Object.keys(indexDoc.carts).length > 0) {
				this.carts = Object.entries((indexDoc as ShoppingCartIndexDoc).carts).map(([id, c]) => ({ id, ...c }));
				this.render();
			}
			this._offlineUnsubs.push(runtime.onChange(indexDocId, (doc: ShoppingCartIndexDoc) => {
				if (doc?.carts) {
					this.carts = Object.entries(doc.carts).map(([id, c]) => ({ id, ...c }));
					this.render();
				}
			}));

			// Subscribe to orders
			const orderDocs = await runtime.subscribeModule('cart', 'orders', orderSchema);
			if (orderDocs.size > 0 && this.orders.length === 0) {
				const fromDocs: any[] = [];
				for (const [, doc] of orderDocs) {
					const d = doc as OrderDoc;
					if (!d?.order) continue;
					fromDocs.push({
						id: d.order.id, status: d.order.status,
						quantity: d.order.quantity, total_price: d.order.totalPrice,
						currency: d.order.currency,
						created_at: new Date(d.order.createdAt).toISOString(),
					});
				}
				if (fromDocs.length > 0) {
					this.orders = fromDocs;
					this.render();
				}
			}
		} catch { /* runtime unavailable */ }
	}

	private loadDemoData() {
		const now = Date.now();

		// Demo shopping carts
		this.carts = [
			{
				id: "demo-cart-1",
				name: "Community Garden Project",
				status: "FUNDING",
				itemCount: 8,
				totalAmount: 247.83,
				fundedAmount: 165.00,
				currency: "USD",
				createdAt: new Date(now - 7 * 86400000).toISOString(),
				updatedAt: new Date(now - 1 * 86400000).toISOString(),
			},
			{
				id: "demo-cart-2",
				name: "Office Supplies",
				status: "OPEN",
				itemCount: 3,
				totalAmount: 45.97,
				fundedAmount: 0,
				currency: "USD",
				createdAt: new Date(now - 2 * 86400000).toISOString(),
				updatedAt: new Date(now - 2 * 86400000).toISOString(),
			},
			{
				id: "demo-cart-3",
				name: "Hackathon Merch Order",
				status: "ORDERED",
				itemCount: 5,
				totalAmount: 189.50,
				fundedAmount: 189.50,
				currency: "USD",
				createdAt: new Date(now - 14 * 86400000).toISOString(),
				updatedAt: new Date(now - 3 * 86400000).toISOString(),
			},
		];

		// Demo cart detail (Community Garden Project)
		this.selectedCart = {
			id: "demo-cart-1",
			name: "Community Garden Project",
			description: "Supplies for the community garden build day",
			status: "FUNDING",
			targetAmount: 300,
			fundedAmount: 165.00,
			currency: "USD",
			createdAt: new Date(now - 7 * 86400000).toISOString(),
			updatedAt: new Date(now - 1 * 86400000).toISOString(),
			items: [
				{ id: "i1", name: "Raised Garden Bed Kit (4x8 ft)", price: 79.99, currency: "USD", quantity: 1, sourceUrl: "https://amazon.com/dp/B08ABC1234", imageUrl: null, vendor: { name: "Amazon", domain: "amazon.com", platform: "amazon" }, addedAt: new Date(now - 6 * 86400000).toISOString() },
				{ id: "i2", name: "Organic Potting Soil (40 qt)", price: 12.99, currency: "USD", quantity: 3, sourceUrl: "https://amazon.com/dp/B08DEF5678", imageUrl: null, vendor: { name: "Amazon", domain: "amazon.com", platform: "amazon" }, addedAt: new Date(now - 6 * 86400000).toISOString() },
				{ id: "i3", name: "Heirloom Seed Variety Pack", price: 24.95, currency: "USD", quantity: 2, sourceUrl: "https://etsy.com/listing/123456", imageUrl: null, vendor: { name: "GardenSeedCo", domain: "etsy.com", platform: "etsy" }, addedAt: new Date(now - 5 * 86400000).toISOString() },
				{ id: "i4", name: "Garden Trowel Set", price: 15.99, currency: "USD", quantity: 1, sourceUrl: "https://amazon.com/dp/B08GHI9012", imageUrl: null, vendor: { name: "Amazon", domain: "amazon.com", platform: "amazon" }, addedAt: new Date(now - 5 * 86400000).toISOString() },
				{ id: "i5", name: "Drip Irrigation Kit", price: 29.99, currency: "USD", quantity: 1, sourceUrl: "https://amazon.com/dp/B08JKL3456", imageUrl: null, vendor: { name: "Amazon", domain: "amazon.com", platform: "amazon" }, addedAt: new Date(now - 4 * 86400000).toISOString() },
				{ id: "i6", name: "Plant Labels (100 pack)", price: 8.99, currency: "USD", quantity: 1, sourceUrl: "https://amazon.com/dp/B08MNO7890", imageUrl: null, vendor: { name: "Amazon", domain: "amazon.com", platform: "amazon" }, addedAt: new Date(now - 4 * 86400000).toISOString() },
				{ id: "i7", name: "Gardening Gloves (3 pair)", price: 12.99, currency: "USD", quantity: 1, sourceUrl: "https://amazon.com/dp/B08PQR1234", imageUrl: null, vendor: { name: "Amazon", domain: "amazon.com", platform: "amazon" }, addedAt: new Date(now - 3 * 86400000).toISOString() },
				{ id: "i8", name: "Compost Bin (80 gal)", price: 34.99, currency: "USD", quantity: 1, sourceUrl: "https://homedepot.com/p/12345", imageUrl: null, vendor: { name: "Home Depot", domain: "homedepot.com", platform: null }, addedAt: new Date(now - 2 * 86400000).toISOString() },
			],
			contributions: [
				{ id: "c1", username: "Alice", amount: 50, currency: "USD", paymentMethod: "MANUAL", status: "confirmed", createdAt: new Date(now - 5 * 86400000).toISOString() },
				{ id: "c2", username: "Bob", amount: 40, currency: "USD", paymentMethod: "MANUAL", status: "confirmed", createdAt: new Date(now - 4 * 86400000).toISOString() },
				{ id: "c3", username: "Carol", amount: 50, currency: "USD", paymentMethod: "MANUAL", status: "confirmed", createdAt: new Date(now - 3 * 86400000).toISOString() },
				{ id: "c4", username: "Dave", amount: 25, currency: "USD", paymentMethod: "MANUAL", status: "confirmed", createdAt: new Date(now - 1 * 86400000).toISOString() },
			],
			events: [],
		};

		// Existing catalog demo data
		this.catalog = [
			{ id: "demo-cat-1", title: "The Commons", description: "A pocket book exploring shared resources and collective stewardship.", price: 12, currency: "USD", tags: ["books"], product_type: "pocket book", status: "active", image_url: "/images/catalog/catalog-the-commons.jpg", created_at: new Date(now - 30 * 86400000).toISOString() },
			{ id: "demo-cat-2", title: "Mycelium Networks", description: "Illustrated poster mapping underground fungal communication pathways.", price: 18, currency: "USD", tags: ["prints"], product_type: "poster", status: "active", image_url: "/images/catalog/catalog-mycelium-networks.jpg", created_at: new Date(now - 25 * 86400000).toISOString() },
			{ id: "demo-cat-3", title: "#DefectFi Tee", description: "Organic cotton tee with the #DefectFi campaign logo. Circuit patterns dissolving into organic roots.", price: 25, currency: "USD", tags: ["apparel"], product_type: "tee shirt", status: "active", image_url: "/images/catalog/catalog-defectfi-tee.jpg", created_at: new Date(now - 20 * 86400000).toISOString() },
			{ id: "demo-cat-4", title: "Cosmolocal Sticker Sheet", description: "Die-cut sticker set with cosmolocal motifs — nodes, mycelium, community gardens, mesh networks.", price: 5, currency: "USD", tags: ["stickers"], product_type: "sticker sheet", status: "active", image_url: "/images/catalog/catalog-cosmolocal-stickers.jpg", created_at: new Date(now - 15 * 86400000).toISOString() },
			{ id: "demo-cat-5", title: "Doughnut Economics Zine", description: "Punk zine breaking down Kate Raworth's doughnut economics framework. Cut-and-paste collage aesthetic.", price: 8, currency: "USD", tags: ["books", "zines"], product_type: "zine", status: "active", image_url: "/images/catalog/catalog-doughnut-economics.jpg", created_at: new Date(now - 10 * 86400000).toISOString() },
			{ id: "demo-cat-6", title: "rSpace Logo Patch", description: "Embroidered patch featuring the rSpace logo in teal and white on navy twill backing.", price: 6, currency: "USD", tags: ["accessories"], product_type: "embroidered patch", status: "active", image_url: "/images/catalog/catalog-rspace-patch.jpg", created_at: new Date(now - 5 * 86400000).toISOString() },
			{ id: "demo-cat-7", title: "Cosmolocal Network Tee", description: "Bella+Canvas 3001 tee with the Cosmolocal Network radial design in teal and coral.", price: 25, currency: "USD", tags: ["apparel", "cosmolocal"], product_type: "tee", required_capabilities: ["dtg-print"], status: "active", image_url: "/images/catalog/catalog-cosmolocal-tee.jpg", created_at: new Date(now - 3 * 86400000).toISOString() },
			{ id: "demo-cat-8", title: "Cosmolocal Vinyl Stickers", description: "Kiss-cut vinyl sticker sheet with network constellation patterns and holographic accents.", price: 5, currency: "USD", tags: ["stickers", "cosmolocal"], product_type: "sticker-sheet", required_capabilities: ["vinyl-cut"], status: "active", image_url: "/images/catalog/catalog-cosmolocal-vinyl-stickers.jpg", created_at: new Date(now - 1 * 86400000).toISOString() },
		];

		this.orders = [
			{
				id: "demo-ord-1001", total_price: "30.00", currency: "USD", status: "paid",
				created_at: new Date(now - 2 * 86400000).toISOString(), artifact_title: "Order #1001", quantity: 2,
				paid_at: new Date(now - 2 * 86400000 + 3600000).toISOString(), shipped_at: null,
				items: [
					{ title: "#DefectFi Tee", image_url: "/images/catalog/catalog-defectfi-tee.jpg", quantity: 1, unit_price: 25 },
					{ title: "Cosmolocal Sticker Sheet", image_url: "/images/catalog/catalog-cosmolocal-stickers.jpg", quantity: 1, unit_price: 5 },
				],
				buyer: { name: "Alice", email: "alice@example.com" },
				payment: { method: "wallet", token: "USDC", chain_id: 8453, tx_hash: "0x7a3b9c1d2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b" },
				provider: { name: "Community Print Co-op", city: "Portland, OR", turnaround: "5-7 business days" },
			},
			{
				id: "demo-ord-1002", total_price: "25.00", currency: "USD", status: "pending",
				created_at: new Date(now - 1 * 86400000).toISOString(), artifact_title: "Order #1002", quantity: 1,
				paid_at: null, shipped_at: null,
				items: [
					{ title: "Cosmolocal Network Tee", image_url: "/images/catalog/catalog-cosmolocal-tee.jpg", quantity: 1, unit_price: 25 },
				],
				buyer: { name: "Bob", email: "bob@example.com" },
				payment: { method: "wallet", token: "USDC", chain_id: 8453, tx_hash: null },
				provider: { name: "Community Print Co-op", city: "Portland, OR", turnaround: "5-7 business days" },
			},
			{
				id: "demo-ord-1003", total_price: "32.00", currency: "USD", status: "shipped",
				created_at: new Date(now - 5 * 86400000).toISOString(), artifact_title: "Order #1003", quantity: 3,
				paid_at: new Date(now - 5 * 86400000 + 1800000).toISOString(), shipped_at: new Date(now - 3 * 86400000).toISOString(),
				items: [
					{ title: "The Commons", image_url: "/images/catalog/catalog-the-commons.jpg", quantity: 2, unit_price: 12 },
					{ title: "Doughnut Economics Zine", image_url: "/images/catalog/catalog-doughnut-economics.jpg", quantity: 1, unit_price: 8 },
				],
				buyer: { name: "Carol", email: "carol@example.com" },
				payment: { method: "wallet", token: "USDC", chain_id: 8453, tx_hash: "0x1122334455667788990011223344556677889900aabbccddeeff0011223344556" },
				provider: { name: "Community Print Co-op", city: "Portland, OR", turnaround: "5-7 business days" },
			},
			{
				id: "demo-ord-1004", total_price: "28.00", currency: "USD", status: "paid",
				created_at: new Date(now - 4 * 86400000).toISOString(), artifact_title: "Order #1004", quantity: 5,
				paid_at: new Date(now - 4 * 86400000 + 7200000).toISOString(), shipped_at: null,
				items: [
					{ title: "rSpace Logo Patch", image_url: "/images/catalog/catalog-rspace-patch.jpg", quantity: 3, unit_price: 6 },
					{ title: "Cosmolocal Vinyl Stickers", image_url: "/images/catalog/catalog-cosmolocal-vinyl-stickers.jpg", quantity: 2, unit_price: 5 },
				],
				buyer: { name: "Dave", email: "dave@example.com" },
				payment: { method: "wallet", token: "ETH", chain_id: 1, tx_hash: "0xaabbccdd11223344556677889900aabbccddeeff11223344556677889900aabb" },
				provider: { name: "Community Print Co-op", city: "Portland, OR", turnaround: "5-7 business days" },
			},
			{
				id: "demo-ord-1005", total_price: "18.00", currency: "USD", status: "completed",
				created_at: new Date(now - 10 * 86400000).toISOString(), artifact_title: "Order #1005", quantity: 1,
				paid_at: new Date(now - 10 * 86400000 + 900000).toISOString(), shipped_at: new Date(now - 7 * 86400000).toISOString(),
				items: [
					{ title: "Mycelium Networks", image_url: "/images/catalog/catalog-mycelium-networks.jpg", quantity: 1, unit_price: 18 },
				],
				buyer: { name: "Eve", email: "eve@example.com" },
				payment: { method: "wallet", token: "USDC", chain_id: 11155111, tx_hash: "0xdeadbeef00112233445566778899aabbccddeeff00112233445566778899aabb" },
				provider: { name: "Community Print Co-op", city: "Portland, OR", turnaround: "5-7 business days" },
			},
		];

		this.payments = [
			{ id: "demo-pay-1", description: "Order #1001 — #DefectFi Tee + stickers", amount: "30.00", token: "USDC", chainId: 8453, recipientAddress: "0x7a3b...9a0b", status: "paid", paymentMethod: "wallet", txHash: "0x7a3b9c1d2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b", created_at: new Date(now - 2 * 86400000).toISOString(), paid_at: new Date(now - 2 * 86400000 + 3600000).toISOString() },
			{ id: "demo-pay-2", description: "Order #1003 — The Commons + zine", amount: "32.00", token: "USDC", chainId: 8453, recipientAddress: "0x1122...4556", status: "paid", paymentMethod: "wallet", txHash: "0x1122334455667788990011223344556677889900aabbccddeeff0011223344556", created_at: new Date(now - 5 * 86400000).toISOString(), paid_at: new Date(now - 5 * 86400000 + 1800000).toISOString() },
			{ id: "demo-pay-3", description: "Order #1004 — patches + vinyl stickers", amount: "28.00", token: "ETH", chainId: 1, recipientAddress: "0xaabb...aabb", status: "paid", paymentMethod: "wallet", txHash: "0xaabbccdd11223344556677889900aabbccddeeff11223344556677889900aabb", created_at: new Date(now - 4 * 86400000).toISOString(), paid_at: new Date(now - 4 * 86400000 + 7200000).toISOString() },
			{ id: "demo-pay-4", description: "Coffee tip", amount: "5.00", token: "USDC", chainId: 8453, recipientAddress: "0x1234...abcd", status: "paid", paymentMethod: "wallet", txHash: "0xfeed1234abcd5678ef901234abcd5678ef901234abcd5678ef901234abcd5678", created_at: new Date(now - 1 * 86400000).toISOString(), paid_at: new Date(now - 1 * 86400000).toISOString() },
			{ id: "demo-pay-5", description: "Invoice #42", amount: "25.00", token: "USDC", chainId: 8453, recipientAddress: "0x1234...abcd", status: "pending", paymentMethod: null, txHash: null, created_at: new Date(now - 3600000).toISOString(), paid_at: null },
		];

		this.groupBuys = [
			{
				id: "demo-gb-1", title: "Cosmolocal Network Tee", productType: "tee",
				imageUrl: "/images/catalog/catalog-cosmolocal-tee.jpg", status: "OPEN",
				totalPledged: 17,
				tiers: [
					{ min_qty: 1, per_unit: 25, currency: "USD" },
					{ min_qty: 10, per_unit: 21.25, currency: "USD" },
					{ min_qty: 25, per_unit: 18, currency: "USD" },
					{ min_qty: 50, per_unit: 15, currency: "USD" },
				],
				closesAt: new Date(now + 14 * 86400000).toISOString(),
				createdAt: new Date(now - 5 * 86400000).toISOString(),
			},
			{
				id: "demo-gb-2", title: "#DefectFi Tee", productType: "tee shirt",
				imageUrl: "/images/catalog/catalog-defectfi-tee.jpg", status: "OPEN",
				totalPledged: 8,
				tiers: [
					{ min_qty: 1, per_unit: 25, currency: "USD" },
					{ min_qty: 10, per_unit: 21.25, currency: "USD" },
					{ min_qty: 25, per_unit: 18, currency: "USD" },
					{ min_qty: 50, per_unit: 15, currency: "USD" },
				],
				closesAt: new Date(now + 21 * 86400000).toISOString(),
				createdAt: new Date(now - 2 * 86400000).toISOString(),
			},
			{
				id: "demo-gb-3", title: "Doughnut Economics Zine", productType: "zine",
				imageUrl: "/images/catalog/catalog-doughnut-economics.jpg", status: "OPEN",
				totalPledged: 32,
				tiers: [
					{ min_qty: 1, per_unit: 8, currency: "USD" },
					{ min_qty: 15, per_unit: 6.80, currency: "USD" },
					{ min_qty: 30, per_unit: 5.60, currency: "USD" },
					{ min_qty: 60, per_unit: 4.80, currency: "USD" },
				],
				closesAt: new Date(now + 10 * 86400000).toISOString(),
				createdAt: new Date(now - 8 * 86400000).toISOString(),
			},
		];

		this.loading = false;
		this.render();
	}

	private getApiBase(): string {
		const path = window.location.pathname;
		const match = path.match(/^(\/[^/]+)?\/rcart/);
		return match ? match[0] : "/rcart";
	}

	private async loadData() {
		this.loading = true;
		this.render();
		try {
			const [catRes, ordRes, cartRes] = await Promise.all([
				fetch(`${this.getApiBase()}/api/catalog?limit=50`),
				fetch(`${this.getApiBase()}/api/orders?limit=20`),
				fetch(`${this.getApiBase()}/api/shopping-carts`),
			]);
			const catData = await catRes.json();
			const ordData = await ordRes.json();
			const cartData = await cartRes.json();
			this.catalog = catData.entries || [];
			this.orders = ordData.orders || [];
			this.carts = cartData.carts || [];

			// Load payments (auth-gated, may fail for unauthenticated users)
			try {
				const payRes = await fetch(`${this.getApiBase()}/api/payments`);
				if (payRes.ok) {
					const payData = await payRes.json();
					this.payments = payData.payments || [];
				}
			} catch { /* unauthenticated */ }

			// Load group buys
			try {
				const gbRes = await fetch(`${this.getApiBase()}/api/group-buys`);
				if (gbRes.ok) {
					const gbData = await gbRes.json();
					this.groupBuys = gbData.groupBuys || [];
				}
			} catch { /* silent */ }
		} catch (e) {
			console.error("Failed to load cart data:", e);
		}
		this.loading = false;
		this.render();
	}

	private async loadCartDetail(cartId: string) {
		if (this.space === "demo") {
			this.selectedCartId = cartId;
			this.view = "cart-detail";
			this.render();
			return;
		}
		try {
			const res = await fetch(`${this.getApiBase()}/api/shopping-carts/${cartId}`);
			this.selectedCart = await res.json();
			this.selectedCartId = cartId;
			this.view = "cart-detail";
		} catch (e) {
			console.error("Failed to load cart:", e);
		}
		this.render();
	}

	private async createCart(name: string, targetAmount: number) {
		if (this.space === "demo") return;
		try {
			await fetch(`${this.getApiBase()}/api/shopping-carts`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ name, targetAmount }),
			});
			await this.loadData();
		} catch (e) {
			console.error("Failed to create cart:", e);
		}
	}

	private async addItemByUrl(cartId: string, url: string) {
		this.addingUrl = true;
		this.render();
		try {
			await fetch(`${this.getApiBase()}/api/shopping-carts/${cartId}/items`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ url }),
			});
			await this.loadCartDetail(cartId);
		} catch (e) {
			console.error("Failed to add item:", e);
		}
		this.addingUrl = false;
	}

	private async removeItem(cartId: string, itemId: string) {
		try {
			await fetch(`${this.getApiBase()}/api/shopping-carts/${cartId}/items/${itemId}`, { method: 'DELETE' });
			await this.loadCartDetail(cartId);
		} catch (e) {
			console.error("Failed to remove item:", e);
		}
	}

	private async contribute(cartId: string, amount: number, username: string) {
		try {
			await fetch(`${this.getApiBase()}/api/shopping-carts/${cartId}/contribute`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ amount, username }),
			});
			await this.loadCartDetail(cartId);
		} catch (e) {
			console.error("Failed to contribute:", e);
		}
	}

	private async contributePay(cartId: string, amount: number, username: string) {
		try {
			const res = await fetch(`${this.getApiBase()}/api/shopping-carts/${cartId}/contribute-pay`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ amount, username }),
			});
			if (!res.ok) {
				const err = await res.json();
				console.error("Failed to create payment:", err.error);
				return;
			}
			const { payUrl } = await res.json();
			window.location.href = payUrl;
		} catch (e) {
			console.error("Failed to create contribution payment:", e);
		}
	}

	// ── Main render ──

	private render() {
		const styles = this.getStyles();
		let content = '';

		if (this.loading) {
			content = `<div class="loading">Loading...</div>`;
		} else if (this.view === "carts") {
			content = this.renderCarts();
		} else if (this.view === "cart-detail") {
			content = this.renderCartDetail();
		} else if (this.view === "catalog") {
			content = this.renderCatalog();
		} else if (this.view === "catalog-detail") {
			content = this.renderCatalogDetail();
		} else if (this.view === "order-detail") {
			content = this.renderOrderDetail();
		} else if (this.view === "payments") {
			content = this.renderPayments();
		} else if (this.view === "group-buys") {
			content = this.renderGroupBuys();
		} else {
			content = this.renderOrders();
		}

		this.shadow.innerHTML = `
		<style>${styles}</style>
		<div class="toolbar">
			<button class="tab" id="btn-tour" style="font-size:0.8rem">Tour</button>
			${this.orderQueue.length > 0 ? `<button class="queue-badge" data-action="toggle-queue">Queue (${this.orderQueue.reduce((s: number, i: any) => s + i.quantity, 0)})</button>` : ''}
		</div>
		${this.orderQueueOpen && this.orderQueue.length > 0 ? this.renderOrderQueue() : ''}
		${content}
		`;

		this.bindEvents();
		this._tour.renderOverlay();
	}

	startTour() { this._tour.start(); }

	private _onViewRestored = (e: CustomEvent) => {
		if (e.detail?.moduleId !== 'rcart') return;
		this.view = e.detail.view;
		if (e.detail.view !== "cart-detail") this.selectedCartId = null;
		if (e.detail.view !== "order-detail") this.selectedOrder = null;
		this.render();
	};

	private goBack() {
		const prev = this._history.back();
		if (!prev) return;
		this.view = prev.view;
		if (prev.view !== "cart-detail") this.selectedCartId = null;
		if (prev.view !== "order-detail") this.selectedOrder = null;
		this.render();
	}

	private bindEvents() {
		// Tour button
		this.shadow.getElementById("btn-tour")?.addEventListener("click", () => this.startTour());

		// Extension banner dismiss
		this.shadow.querySelector("[data-action='dismiss-banner']")?.addEventListener("click", () => {
			this.bannerDismissed = true;
			localStorage.setItem('rcart-ext-banner-dismissed', 'true');
			this.render();
		});

		// Cart card clicks
		this.shadow.querySelectorAll("[data-cart-id]").forEach((el) => {
			el.addEventListener("click", () => {
				this._history.push(this.view);
				this._history.push("cart-detail", { cartId: (el as HTMLElement).dataset.cartId });
				this.loadCartDetail((el as HTMLElement).dataset.cartId!);
			});
		});

		// Back button
		this.shadow.querySelector("[data-action='back']")?.addEventListener("click", () => {
			this.goBack();
		});

		// New cart form
		const newCartBtn = this.shadow.querySelector("[data-action='new-cart']");
		newCartBtn?.addEventListener("click", () => {
			const form = this.shadow.querySelector(".new-cart-form") as HTMLElement;
			if (form) form.style.display = form.style.display === 'none' ? 'flex' : 'none';
		});
		this.shadow.querySelector("[data-action='create-cart']")?.addEventListener("click", () => {
			const nameInput = this.shadow.querySelector("[data-field='cart-name']") as HTMLInputElement;
			const amountInput = this.shadow.querySelector("[data-field='cart-target']") as HTMLInputElement;
			if (nameInput?.value) this.createCart(nameInput.value, parseFloat(amountInput?.value) || 0);
		});

		// Add item by URL
		this.shadow.querySelector("[data-action='add-url']")?.addEventListener("click", () => {
			const input = this.shadow.querySelector("[data-field='item-url']") as HTMLInputElement;
			if (input?.value && this.selectedCartId) {
				this.addItemByUrl(this.selectedCartId, input.value);
			}
		});

		// Remove item
		this.shadow.querySelectorAll("[data-remove-item]").forEach((el) => {
			el.addEventListener("click", (e) => {
				e.stopPropagation();
				if (this.selectedCartId) this.removeItem(this.selectedCartId, (el as HTMLElement).dataset.removeItem!);
			});
		});

		// Contribute toggle
		this.shadow.querySelector("[data-action='show-contribute']")?.addEventListener("click", () => {
			const form = this.shadow.querySelector(".contribute-form") as HTMLElement;
			if (form) form.style.display = form.style.display === 'none' ? 'flex' : 'none';
		});
		this.shadow.querySelector("[data-action='submit-contribute']")?.addEventListener("click", () => {
			const amtInput = this.shadow.querySelector("[data-field='contrib-amount']") as HTMLInputElement;
			const nameInput = this.shadow.querySelector("[data-field='contrib-name']") as HTMLInputElement;
			if (amtInput?.value && this.selectedCartId) {
				this.contribute(this.selectedCartId, parseFloat(amtInput.value), nameInput?.value || 'Anonymous');
			}
		});
		this.shadow.querySelector("[data-action='contribute-pay']")?.addEventListener("click", () => {
			const amtInput = this.shadow.querySelector("[data-field='contrib-amount']") as HTMLInputElement;
			const nameInput = this.shadow.querySelector("[data-field='contrib-name']") as HTMLInputElement;
			if (amtInput?.value && this.selectedCartId) {
				this.contributePay(this.selectedCartId, parseFloat(amtInput.value), nameInput?.value || 'Anonymous');
			}
		});

		// Payment request actions
		const newPaymentBtn = this.shadow.querySelector("[data-action='new-payment']");
		newPaymentBtn?.addEventListener("click", () => {
			const form = this.shadow.querySelector(".new-payment-form") as HTMLElement;
			if (form) form.style.display = form.style.display === 'none' ? 'flex' : 'none';
		});
		this.shadow.querySelector("[data-action='create-payment']")?.addEventListener("click", () => {
			this.createPaymentRequest();
		});
		this.shadow.querySelectorAll("[data-action='copy-pay-url']").forEach((el) => {
			el.addEventListener("click", () => {
				const payId = (el as HTMLElement).dataset.payId;
				const url = `${window.location.origin}${this.getApiBase()}/pay/${payId}`;
				navigator.clipboard.writeText(url);
				(el as HTMLElement).textContent = 'Copied!';
				setTimeout(() => { (el as HTMLElement).textContent = 'Copy Link'; }, 2000);
			});
		});

		// Group buy card clicks → navigate to group buy page
		this.shadow.querySelectorAll("[data-group-buy-id]").forEach((el) => {
			el.addEventListener("click", () => {
				const buyId = (el as HTMLElement).dataset.groupBuyId!;
				const url = `${this.getApiBase()}/buy/${buyId}`;
				window.location.href = url;
			});
		});

		// Order card clicks → detail view
		this.shadow.querySelectorAll("[data-order-id]").forEach((el) => {
			el.addEventListener("click", () => {
				this.loadOrderDetail((el as HTMLElement).dataset.orderId!);
			});
		});

		// Catalog card clicks → detail view
		this.shadow.querySelectorAll("[data-catalog-id]").forEach((el) => {
			el.addEventListener("click", () => {
				this.loadCatalogDetail((el as HTMLElement).dataset.catalogId!);
			});
		});

		// Catalog detail quantity controls
		this.shadow.querySelector("[data-action='qty-dec']")?.addEventListener("click", () => {
			if (this.detailQuantity > 1) { this.detailQuantity--; this.render(); }
		});
		this.shadow.querySelector("[data-action='qty-inc']")?.addEventListener("click", () => {
			this.detailQuantity++; this.render();
		});
		this.shadow.querySelector("[data-field='detail-qty']")?.addEventListener("change", (e) => {
			const val = parseInt((e.target as HTMLInputElement).value) || 1;
			this.detailQuantity = Math.max(1, val);
			this.render();
		});

		// Add to queue
		this.shadow.querySelector("[data-action='add-to-queue']")?.addEventListener("click", () => {
			this.addToQueue();
		});

		// Start group buy
		this.shadow.querySelector("[data-action='start-group-buy']")?.addEventListener("click", () => {
			this.startGroupBuy();
		});

		// Order queue toggle
		this.shadow.querySelectorAll("[data-action='toggle-queue']").forEach((el) => {
			el.addEventListener("click", () => {
				this.orderQueueOpen = !this.orderQueueOpen;
				this.render();
			});
		});

		// Remove from queue
		this.shadow.querySelectorAll("[data-remove-queue]").forEach((el) => {
			el.addEventListener("click", () => {
				const idx = parseInt((el as HTMLElement).dataset.removeQueue!);
				this.orderQueue.splice(idx, 1);
				if (this.orderQueue.length === 0) this.orderQueueOpen = false;
				this.render();
			});
		});
	}

	// ── Extension install banner ──

	private renderExtensionBanner(): string {
		if (this.extensionInstalled || this.bannerDismissed) return '';
		return `
			<div class="ext-banner">
				<button class="ext-banner-dismiss" data-action="dismiss-banner" title="Dismiss">&times;</button>
				<div class="ext-banner-title">Add products from any store</div>
				<div class="ext-banner-text">Install the rSpace browser extension to add items from Amazon, Etsy, Shopify, and more directly to your carts.</div>
				<a class="ext-banner-cta" href="https://rspace.online/extension" target="_blank" rel="noopener">Get Extension</a>
			</div>`;
	}

	// ── Cart list view ──

	private renderCarts(): string {
		const newCartForm = `
			<div class="new-cart-form" style="display:none">
				<input data-field="cart-name" type="text" placeholder="Cart name" class="input" />
				<input data-field="cart-target" type="number" placeholder="Target amount (optional)" class="input input-sm" />
				<button data-action="create-cart" class="btn btn-primary btn-sm">Create</button>
			</div>`;

		if (this.carts.length === 0) {
			return `
				${this.renderExtensionBanner()}
				<div class="empty">
					<p>No shopping carts yet. Create one to start group shopping.</p>
					<button data-action="new-cart" class="btn btn-primary">+ New Cart</button>
					${newCartForm}
				</div>`;
		}

		return `
			${this.renderExtensionBanner()}
			<div style="margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem;">
				<button data-action="new-cart" class="btn btn-primary btn-sm">+ New Cart</button>
				${newCartForm}
			</div>
			<div class="grid">
				${this.carts.map((cart) => {
					const pct = cart.totalAmount > 0 ? Math.min(100, Math.round((cart.fundedAmount / cart.totalAmount) * 100)) : 0;
					return `
					<div class="card card-clickable" data-cart-id="${cart.id}">
						<div style="display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:0.5rem">
							<h3 class="card-title" style="margin:0">${this.esc(cart.name)}</h3>
							<span class="status status-${cart.status.toLowerCase()}">${cart.status}</span>
						</div>
						<div class="card-meta">${cart.itemCount} item${cart.itemCount !== 1 ? 's' : ''} &bull; $${cart.totalAmount.toFixed(2)}</div>
						<div class="progress-bar">
							<div class="progress-fill" style="width: ${pct}%"></div>
						</div>
						<div class="card-meta" style="margin-top:0.25rem">$${cart.fundedAmount.toFixed(2)} / $${cart.totalAmount.toFixed(2)} funded (${pct}%)</div>
					</div>`;
				}).join("")}
			</div>`;
	}

	// ── Cart detail view ──

	private renderCartDetail(): string {
		const cart = this.selectedCart;
		if (!cart) return `<div class="loading">Loading cart...</div>`;

		const items = cart.items || [];
		const contributions = cart.contributions || [];
		const totalItemsCost = items.reduce((s: number, i: any) => s + (i.price || 0) * (i.quantity || 1), 0);
		const targetDisplay = cart.targetAmount > 0 ? cart.targetAmount : totalItemsCost;
		const pct = targetDisplay > 0 ? Math.min(100, Math.round((cart.fundedAmount / targetDisplay) * 100)) : 0;
		const remaining = Math.max(0, targetDisplay - cart.fundedAmount);

		// Group items by vendor domain
		const vendorGroups: Record<string, any[]> = {};
		for (const item of items) {
			const key = item.vendor?.domain || 'other';
			if (!vendorGroups[key]) vendorGroups[key] = [];
			vendorGroups[key].push(item);
		}

		const itemsHtml = Object.entries(vendorGroups).map(([domain, vendorItems]) => `
			<div class="vendor-group">
				<div class="vendor-header">${this.esc(domain)}</div>
				${vendorItems.map((item: any) => `
					<div class="item-row">
						${item.imageUrl ? `<img class="item-thumb" src="${this.esc(item.imageUrl)}" alt="" />` : `<div class="item-thumb item-thumb-placeholder">📦</div>`}
						<div class="item-info">
							<a class="item-name" href="${this.esc(item.sourceUrl)}" target="_blank" rel="noopener">${this.esc(item.name)}</a>
							<div class="item-meta">Qty: ${item.quantity}${item.price != null ? ` &bull; $${(item.price * item.quantity).toFixed(2)}` : ''}</div>
						</div>
						<button class="btn-icon" data-remove-item="${item.id}" title="Remove">✕</button>
					</div>
				`).join("")}
			</div>
		`).join("");

		const contribHtml = contributions.length > 0 ? contributions.map((c: any) => `
			<div class="contrib-row">
				<span class="contrib-name">${this.esc(c.username)}</span>
				<span class="contrib-amount">$${c.amount.toFixed(2)}</span>
				<span class="status status-${c.status}">${c.status}</span>
			</div>
		`).join("") : `<div class="card-meta">No contributions yet.</div>`;

		return `
			${this._history.canGoBack ? '<div style="margin-bottom:1rem"><button data-action="back" class="btn btn-sm">\u2190 Back</button></div>' : ''}

			<div class="detail-layout">
				<div class="detail-left">
					<h2 class="section-title">${this.esc(cart.name)}</h2>
					${cart.description ? `<p class="card-meta">${this.esc(cart.description)}</p>` : ''}

					<div class="url-input-row">
						<input data-field="item-url" type="url" placeholder="Paste product URL from any store..." class="input" style="flex:1" />
						<button data-action="add-url" class="btn btn-primary btn-sm" ${this.addingUrl ? 'disabled' : ''}>${this.addingUrl ? 'Adding...' : 'Add'}</button>
					</div>

					${items.length === 0
						? `<div class="empty">No items yet. Paste a URL above to add products.</div>`
						: itemsHtml}
				</div>

				<div class="detail-right">
					<div class="card">
						<h3 class="card-title">Summary</h3>
						<div class="summary-row"><span>Items cost</span><span>$${totalItemsCost.toFixed(2)}</span></div>
						<div class="summary-row"><span>Funded</span><span class="text-green">$${cart.fundedAmount.toFixed(2)}</span></div>
						<div class="summary-row"><span>Remaining</span><span>$${remaining.toFixed(2)}</span></div>
						<div class="progress-bar" style="margin:0.75rem 0">
							<div class="progress-fill" style="width: ${pct}%"></div>
						</div>
						<div class="card-meta" style="text-align:center">${pct}% funded</div>

						<button data-action="show-contribute" class="btn btn-primary" style="width:100%; margin-top:1rem">Contribute</button>
						<div class="contribute-form" style="display:none">
							<input data-field="contrib-name" type="text" placeholder="Your name" class="input" />
							<input data-field="contrib-amount" type="number" placeholder="Amount ($)" class="input" step="0.01" min="0.01" />
							<div style="display:flex; gap:0.5rem; width:100%">
								${cart.recipientAddress
									? `<button data-action="contribute-pay" class="btn btn-primary btn-sm" style="flex:1">Pay Now</button>`
									: `<button class="btn btn-sm" style="flex:1; opacity:0.5; cursor:not-allowed" disabled title="Cart owner must set a receiving wallet">Pay Now</button>`}
								<button data-action="submit-contribute" class="btn btn-sm" style="flex:1">Record Manual</button>
							</div>
						</div>
					</div>

					<div class="card" style="margin-top:1rem">
						<h3 class="card-title">Contributions</h3>
						${contribHtml}
					</div>
				</div>
			</div>`;
	}

	// ── Catalog view ──

	private renderCatalog(): string {
		if (this.catalog.length === 0) {
			return `<div class="empty">No items in the catalog yet. Ingest artifacts from rPubs or Swag Designer to list them here.</div>`;
		}

		return `<div class="grid catalog-grid">
			${this.catalog.map((entry) => `
			<div class="card catalog-card card-clickable" data-catalog-id="${entry.id}" data-collab-id="product:${entry.id || entry.title}">
				${entry.image_url ? `<div class="catalog-img"><img src="${this.esc(entry.image_url)}" alt="${this.esc(entry.title || '')}" loading="lazy" /></div>` : ""}
				<div class="catalog-body">
					<h3 class="card-title">${this.esc(entry.title || "Untitled")}</h3>
					<div class="card-meta">
						${entry.product_type ? `<span class="tag tag-type">${this.esc(entry.product_type)}</span>` : ""}
						${(entry.required_capabilities || []).map((cap: string) => `<span class="tag tag-cap">${this.esc(cap)}</span>`).join("")}
						${(entry.tags || []).map((t: string) => `<span class="tag tag-cap">${this.esc(t)}</span>`).join("")}
					</div>
					${entry.description ? `<div class="card-desc">${this.esc(entry.description)}</div>` : ""}
					${entry.dimensions ? `<div class="dims">${entry.dimensions.width_mm}x${entry.dimensions.height_mm}mm</div>` : ""}
					<div class="catalog-footer">
						${entry.price != null ? `<div class="price">$${parseFloat(entry.price).toFixed(2)} ${entry.currency || ""}</div>` : ""}
						<span class="status status-${entry.status}">${entry.status}</span>
					</div>
				</div>
			</div>
			`).join("")}
		</div>`;
	}

	// ── Catalog detail view ──

	private loadCatalogDetail(id: string) {
		const item = this.catalog.find((e: any) => e.id === id);
		if (!item) return;
		this.selectedCatalogItem = item;
		this.detailQuantity = 1;
		this._history.push(this.view);
		this.view = "catalog-detail";
		this._history.push("catalog-detail");
		this.render();
	}

	private loadOrderDetail(id: string) {
		const order = this.orders.find((o: any) => o.id === id);
		if (!order) return;
		this.selectedOrder = order;
		this._history.push(this.view);
		this.view = "order-detail";
		this._history.push("order-detail");
		this.render();
	}

	private buildDemoFulfillOptions(entry: any) {
		const basePrice = entry.price || 10;
		return {
			provider: {
				name: "Community Print Co-op",
				city: "Portland, OR",
				turnaround: "5-7 business days",
				capabilities: entry.required_capabilities || ["dtg-print"],
			},
			tiers: [
				{ min_qty: 1, per_unit: basePrice, currency: entry.currency || "USD" },
				{ min_qty: 10, per_unit: +(basePrice * 0.85).toFixed(2), currency: entry.currency || "USD" },
				{ min_qty: 25, per_unit: +(basePrice * 0.72).toFixed(2), currency: entry.currency || "USD" },
				{ min_qty: 50, per_unit: +(basePrice * 0.60).toFixed(2), currency: entry.currency || "USD" },
			],
		};
	}

	private getCurrentTierPrice(): number {
		if (!this.selectedCatalogItem) return 0;
		const opts = this.buildDemoFulfillOptions(this.selectedCatalogItem);
		let price = opts.tiers[0].per_unit;
		for (const tier of opts.tiers) {
			if (this.detailQuantity >= tier.min_qty) price = tier.per_unit;
		}
		return price;
	}

	private renderCatalogDetail(): string {
		const item = this.selectedCatalogItem;
		if (!item) return `<div class="empty">Item not found.</div>`;

		const opts = this.buildDemoFulfillOptions(item);
		const unitPrice = this.getCurrentTierPrice();
		const totalPrice = (unitPrice * this.detailQuantity).toFixed(2);
		const basePrice = opts.tiers[0].per_unit;

		return `
		<div class="detail-back">
			<button class="btn btn-sm" data-action="back">&larr; Back to catalog</button>
		</div>
		<div class="catalog-detail-layout">
			<div class="detail-image">
				${item.image_url
					? `<img src="${this.esc(item.image_url)}" alt="${this.esc(item.title)}" />`
					: `<div class="detail-image-placeholder">No image</div>`}
			</div>
			<div class="detail-panel">
				<h2 class="detail-title">${this.esc(item.title)}</h2>
				${item.product_type ? `<span class="tag tag-type" style="margin-bottom:0.5rem;display:inline-block">${this.esc(item.product_type)}</span>` : ''}
				${(item.tags || []).map((t: string) => `<span class="tag tag-cap">${this.esc(t)}</span>`).join(' ')}
				${item.description ? `<p class="detail-desc">${this.esc(item.description)}</p>` : ''}

				<div class="tier-table">
					<div class="tier-header">Volume Pricing</div>
					${opts.tiers.map((t: any, i: number) => {
						const active = this.detailQuantity >= t.min_qty && (i === opts.tiers.length - 1 || this.detailQuantity < opts.tiers[i + 1].min_qty);
						const savings = i > 0 ? Math.round((1 - t.per_unit / basePrice) * 100) : 0;
						return `<div class="tier-row ${active ? 'tier-active' : ''}">
							<span class="tier-qty">${t.min_qty}+</span>
							<span class="tier-price">$${t.per_unit.toFixed(2)}/ea</span>
							${savings > 0 ? `<span class="tier-savings">-${savings}%</span>` : `<span class="tier-savings"></span>`}
						</div>`;
					}).join('')}
				</div>

				<div class="qty-row">
					<label>Quantity</label>
					<div class="qty-controls">
						<button class="btn btn-sm" data-action="qty-dec">-</button>
						<input type="number" class="qty-input" data-field="detail-qty" value="${this.detailQuantity}" min="1" />
						<button class="btn btn-sm" data-action="qty-inc">+</button>
					</div>
				</div>

				<div class="detail-total">
					<span>Total</span>
					<span class="price">$${totalPrice} ${item.currency || 'USD'}</span>
				</div>

				<div class="provider-info">
					<div class="provider-name">${this.esc(opts.provider.name)}</div>
					<div class="provider-meta">${this.esc(opts.provider.city)} &bull; ${this.esc(opts.provider.turnaround)}</div>
				</div>

				<div class="detail-actions">
					<button class="btn btn-primary" data-action="add-to-queue">Add to Queue</button>
					<button class="btn" data-action="start-group-buy" ${this.creatingGroupBuy ? 'disabled' : ''}>${this.creatingGroupBuy ? 'Creating...' : 'Start Group Buy'}</button>
				</div>
			</div>
		</div>`;
	}

	// ── Order queue drawer ──

	private renderOrderQueue(): string {
		const total = this.orderQueue.reduce((s: number, i: any) => s + i.unitPrice * i.quantity, 0);
		return `
		<div class="queue-drawer">
			<div class="queue-header">
				<h3>Order Queue (${this.orderQueue.length})</h3>
				<button class="btn-icon" data-action="toggle-queue">&times;</button>
			</div>
			${this.orderQueue.map((item: any, idx: number) => `
				<div class="queue-item">
					<div class="queue-item-info">
						<span class="queue-item-title">${this.esc(item.title)}</span>
						<span class="queue-item-meta">Qty: ${item.quantity} &bull; $${(item.unitPrice * item.quantity).toFixed(2)}</span>
					</div>
					<button class="btn-icon" data-remove-queue="${idx}">&times;</button>
				</div>
			`).join('')}
			<div class="queue-total">
				<span>Total</span>
				<span class="price">$${total.toFixed(2)}</span>
			</div>
		</div>`;
	}

	private addToQueue() {
		if (!this.selectedCatalogItem) return;
		const item = this.selectedCatalogItem;
		const unitPrice = this.getCurrentTierPrice();
		const existing = this.orderQueue.find((q: any) => q.catalogId === item.id);
		if (existing) {
			existing.quantity += this.detailQuantity;
			existing.unitPrice = unitPrice;
		} else {
			this.orderQueue.push({
				catalogId: item.id,
				title: item.title,
				productType: item.product_type,
				imageUrl: item.image_url,
				quantity: this.detailQuantity,
				unitPrice,
				currency: item.currency || 'USD',
			});
		}
		this.render();
	}

	private async startGroupBuy() {
		if (!this.selectedCatalogItem || this.creatingGroupBuy) return;
		const item = this.selectedCatalogItem;
		const opts = this.buildDemoFulfillOptions(item);

		if (this.space === 'demo') {
			const demoId = `demo-gb-${Date.now()}`;
			const host = window.location.host;
			const shareUrl = `https://${host}/rcart/buy/${demoId}`;
			try { await navigator.clipboard.writeText(shareUrl); } catch {}
			alert(`Group buy created! Link copied.\nView all group buys in the Group Buys tab.`);
			this._history.push(this.view);
			this.view = 'group-buys';
			this._history.push('group-buys');
			this.render();
			return;
		}

		this.creatingGroupBuy = true;
		this.render();
		try {
			const res = await fetch(`${this.getApiBase()}/api/group-buys`, {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json',
					...(localStorage.getItem('encryptid-token') ? { 'Authorization': `Bearer ${localStorage.getItem('encryptid-token')}` } : {}),
				},
				body: JSON.stringify({
					catalogEntryId: item.id,
					tiers: opts.tiers,
					description: item.description || '',
				}),
			});
			if (res.ok) {
				const data = await res.json();
				try { await navigator.clipboard.writeText(data.shareUrl); } catch {}
				// Refresh group buys list and navigate to tab
				try {
					const gbRes = await fetch(`${this.getApiBase()}/api/group-buys`);
					if (gbRes.ok) {
						const gbData = await gbRes.json();
						this.groupBuys = gbData.groupBuys || [];
					}
				} catch { /* silent */ }
				this._history.push(this.view);
				this.view = 'group-buys';
				this._history.push('group-buys');
			}
		} catch (e) {
			console.error("Failed to create group buy:", e);
		}
		this.creatingGroupBuy = false;
		this.render();
	}

	// ── Group Buys view ──

	private renderGroupBuys(): string {
		const open = this.groupBuys.filter((g: any) => g.status === 'OPEN');
		const closed = this.groupBuys.filter((g: any) => g.status !== 'OPEN');

		if (this.groupBuys.length === 0) {
			return `<div class="empty">
				<p>No group buys yet. Start one from any catalog item to unlock volume pricing together.</p>
				<a class="btn btn-primary" href="${this.getApiBase()}/catalog">Browse Catalog</a>
			</div>`;
		}

		const renderCard = (gb: any) => {
			const currentTier = [...gb.tiers].reverse().find((t: any) => gb.totalPledged >= t.min_qty) || gb.tiers[0];
			const nextTier = gb.tiers.find((t: any) => t.min_qty > gb.totalPledged);
			const progressPct = nextTier ? Math.min(100, Math.round((gb.totalPledged / nextTier.min_qty) * 100)) : 100;
			const remaining = nextTier ? nextTier.min_qty - gb.totalPledged : 0;
			const closesDate = new Date(gb.closesAt);
			const daysLeft = Math.max(0, Math.ceil((closesDate.getTime() - Date.now()) / 86400000));
			const bestTier = gb.tiers[gb.tiers.length - 1];
			const savings = currentTier && gb.tiers[0] ? Math.round((1 - currentTier.per_unit / gb.tiers[0].per_unit) * 100) : 0;

			return `
			<div class="card card-clickable gb-card" data-group-buy-id="${gb.id}">
				<div class="gb-card-top">
					${gb.imageUrl ? `<img class="gb-card-img" src="${this.esc(gb.imageUrl)}" alt="${this.esc(gb.title)}" />` : `<div class="gb-card-img gb-card-img-placeholder">🤝</div>`}
					<div class="gb-card-info">
						<h3 class="card-title" style="margin:0">${this.esc(gb.title)}</h3>
						<div class="card-meta" style="margin-top:0.25rem">
							${gb.productType ? `<span class="tag tag-type">${this.esc(gb.productType)}</span>` : ''}
							<span class="status status-${gb.status.toLowerCase()}">${gb.status}</span>
						</div>
						<div class="gb-card-stats">
							<span>${gb.totalPledged} pledged</span>
							<span>${daysLeft}d left</span>
							<span>$${currentTier.per_unit.toFixed(2)}/ea${savings > 0 ? ` (-${savings}%)` : ''}</span>
						</div>
					</div>
				</div>
				<div class="gb-card-progress">
					<div class="progress-bar"><div class="progress-fill" style="width:${progressPct}%"></div></div>
					<div class="gb-card-progress-label">
						${nextTier
							? `${remaining} more to unlock $${nextTier.per_unit.toFixed(2)}/ea`
							: `Best tier unlocked — $${bestTier.per_unit.toFixed(2)}/ea`}
					</div>
				</div>
				<div class="gb-card-tiers">
					${gb.tiers.map((t: any, i: number) => {
						const reached = gb.totalPledged >= t.min_qty;
						const isCurrent = currentTier && t.min_qty === currentTier.min_qty;
						const tierSavings = i > 0 ? Math.round((1 - t.per_unit / gb.tiers[0].per_unit) * 100) : 0;
						return `<span class="gb-tier-chip ${isCurrent ? 'gb-tier-current' : ''} ${reached ? 'gb-tier-reached' : ''}">${t.min_qty}+ $${t.per_unit.toFixed(2)}${tierSavings > 0 ? ` (-${tierSavings}%)` : ''}</span>`;
					}).join('')}
				</div>
			</div>`;
		};

		return `
			${open.length > 0 ? `
				<h3 class="section-title" style="margin-bottom:0.75rem">Open Group Buys</h3>
				<div class="grid gb-grid">${open.map(renderCard).join('')}</div>
			` : ''}
			${closed.length > 0 ? `
				<h3 class="section-title" style="margin-top:1.5rem;margin-bottom:0.75rem;color:var(--rs-text-secondary)">Past Group Buys</h3>
				<div class="grid gb-grid">${closed.map(renderCard).join('')}</div>
			` : ''}
		`;
	}

	// ── Orders view ──

	private renderOrders(): string {
		if (this.orders.length === 0) {
			return `<div class="empty">No orders yet.</div>`;
		}

		return `<div class="grid">
			${this.orders.map((order) => {
				const firstItem = order.items?.[0];
				const itemCount = order.items?.reduce((s: number, i: any) => s + (i.quantity || 1), 0) || order.quantity || 0;
				return `
			<div class="card card-clickable" data-order-id="${order.id}" data-collab-id="order:${order.id}">
				<div class="order-card">
					${firstItem?.image_url ? `<img class="order-thumb" src="${this.esc(firstItem.image_url)}" alt="" />` : ''}
					<div class="order-info">
						<h3 class="card-title">${this.esc(order.artifact_title || "Order")}</h3>
						<div class="card-meta">
							${itemCount} item${itemCount !== 1 ? 's' : ''}${order.provider?.name ? ` &bull; ${this.esc(order.provider.name)}` : (order.provider_name ? ` &bull; ${this.esc(order.provider_name)}` : '')}
						</div>
						<span class="status status-${order.status}">${order.status}</span>
					</div>
					<div class="order-price">$${parseFloat(order.total_price || 0).toFixed(2)}</div>
				</div>
			</div>`;
			}).join("")}
		</div>`;
	}

	// ── Order detail view ──

	private renderOrderDetail(): string {
		const order = this.selectedOrder;
		if (!order) return `<div class="empty">Order not found.</div>`;

		const items = order.items || [];
		const firstImage = items.find((i: any) => i.image_url)?.image_url;
		const chainNames: Record<number, string> = { 8453: 'Base', 84532: 'Base Sepolia', 1: 'Ethereum', 11155111: 'Sepolia' };
		const chainName = order.payment?.chain_id ? (chainNames[order.payment.chain_id] || `Chain ${order.payment.chain_id}`) : '';

		const itemsHtml = items.map((item: any) => `
			<div class="item-row">
				${item.image_url ? `<img class="item-thumb" src="${this.esc(item.image_url)}" alt="" />` : `<div class="item-thumb item-thumb-placeholder">📦</div>`}
				<div class="item-info">
					<span class="item-name">${this.esc(item.title)}</span>
					<div class="item-meta">Qty: ${item.quantity} &bull; $${(item.unit_price * item.quantity).toFixed(2)}</div>
				</div>
			</div>
		`).join('');

		const timelineHtml = [
			order.created_at ? `<div class="item-row"><div class="item-info"><span class="item-name">Order placed</span><div class="item-meta">${new Date(order.created_at).toLocaleString()}</div></div></div>` : '',
			order.paid_at ? `<div class="item-row"><div class="item-info"><span class="item-name">Payment confirmed</span><div class="item-meta">${new Date(order.paid_at).toLocaleString()}</div></div></div>` : '',
			order.shipped_at ? `<div class="item-row"><div class="item-info"><span class="item-name">Shipped</span><div class="item-meta">${new Date(order.shipped_at).toLocaleString()}</div></div></div>` : '',
			order.status === 'completed' ? `<div class="item-row"><div class="item-info"><span class="item-name">Completed</span><div class="item-meta">${order.shipped_at ? new Date(new Date(order.shipped_at).getTime() + 3 * 86400000).toLocaleString() : ''}</div></div></div>` : '',
		].filter(Boolean).join('');

		return `
		<div class="detail-back">
			<button class="btn btn-sm" data-action="back">&larr; Back to orders</button>
		</div>
		<div class="catalog-detail-layout">
			<div class="detail-image">
				${firstImage
					? `<img src="${this.esc(firstImage)}" alt="${this.esc(order.artifact_title)}" />`
					: `<div class="detail-image-placeholder">No image</div>`}
			</div>
			<div class="detail-panel">
				<h2 class="detail-title">${this.esc(order.artifact_title || 'Order')}</h2>
				<span class="status status-${order.status}" style="margin-bottom:0.75rem;display:inline-block">${order.status}</span>

				<div class="order-detail-section">
					<div class="order-detail-label">Items</div>
					${itemsHtml}
				</div>

				<div class="detail-total">
					<span>Total</span>
					<span class="price">$${parseFloat(order.total_price).toFixed(2)} ${order.currency || 'USD'}</span>
				</div>

				${order.payment ? `
				<div class="order-detail-section">
					<div class="order-detail-label">Payment</div>
					<div class="card-meta">Method: ${this.esc(order.payment.method || 'n/a')}</div>
					<div class="card-meta">Token: ${this.esc(order.payment.token || '')} on ${chainName}</div>
					${order.payment.tx_hash ? `<div class="tx-hash">Tx: ${order.payment.tx_hash.slice(0, 10)}...${order.payment.tx_hash.slice(-6)}</div>` : `<div class="card-meta" style="color:#fbbf24">Awaiting payment</div>`}
				</div>` : ''}

				${order.buyer ? `
				<div class="order-detail-section">
					<div class="order-detail-label">Buyer</div>
					<div class="card-meta">${this.esc(order.buyer.name)}${order.buyer.email ? ` &bull; ${this.esc(order.buyer.email)}` : ''}</div>
				</div>` : ''}

				${order.provider ? `
				<div class="provider-info">
					<div class="provider-name">${this.esc(order.provider.name)}</div>
					<div class="provider-meta">${this.esc(order.provider.city)} &bull; ${this.esc(order.provider.turnaround)}</div>
				</div>` : ''}

				${timelineHtml ? `
				<div class="order-detail-section">
					<div class="order-detail-label">Timeline</div>
					${timelineHtml}
				</div>` : ''}
			</div>
		</div>`;
	}

	// ── Payments view ──

	private renderPayments(): string {
		const newPaymentForm = `
			<div class="new-payment-form" style="display:none">
				<input data-field="pay-desc" type="text" placeholder="Description (e.g. Coffee tip)" class="input" />
				<input data-field="pay-amount" type="number" step="0.01" min="0.01" placeholder="Amount" class="input input-sm" />
				<select data-field="pay-token" class="input input-sm">
					<option value="USDC">USDC</option>
					<option value="ETH">ETH</option>
				</select>
				<input data-field="pay-recipient" type="text" placeholder="Recipient address (0x...)" class="input" />
				<button data-action="create-payment" class="btn btn-primary btn-sm" ${this.creatingPayment ? 'disabled' : ''}>
					${this.creatingPayment ? 'Creating...' : 'Create'}
				</button>
			</div>`;

		if (this.payments.length === 0) {
			return `
				<div class="empty">
					<p>No payment requests yet. Create one to generate a shareable QR code.</p>
					<button data-action="new-payment" class="btn btn-primary">+ New Payment Request</button>
					${newPaymentForm}
				</div>`;
		}

		const chainNames: Record<number, string> = { 8453: 'Base', 84532: 'Base Sepolia', 1: 'Ethereum' };

		return `
			<div style="margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem;">
				<button data-action="new-payment" class="btn btn-primary btn-sm">+ New Payment Request</button>
				${newPaymentForm}
			</div>
			<div class="grid">
				${this.payments.map((pay) => `
				<div class="card">
					<div style="display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:0.5rem">
						<h3 class="card-title" style="margin:0">${this.esc(pay.description)}</h3>
						<span class="status status-${pay.status}">${pay.status}</span>
					</div>
					<div class="price">${this.esc(pay.amount)} ${this.esc(pay.token)}</div>
					<div class="card-meta">${chainNames[pay.chainId] || 'Chain ' + pay.chainId}${pay.paymentMethod ? ' &bull; via ' + pay.paymentMethod : ''}</div>
					<div class="card-meta">${new Date(pay.created_at).toLocaleDateString()}</div>
					${pay.status === 'pending' ? `
						<div style="margin-top:0.75rem; display:flex; gap:0.5rem">
							<a class="btn btn-sm" href="${this.getApiBase()}/pay/${pay.id}" target="_blank" rel="noopener">Open</a>
							<button class="btn btn-sm" data-action="copy-pay-url" data-pay-id="${pay.id}">Copy Link</button>
							<a class="btn btn-sm" href="${this.getApiBase()}/api/payments/${pay.id}/qr" target="_blank" rel="noopener">QR</a>
						</div>` : ''}
					${pay.txHash ? `<div class="card-meta" style="margin-top:0.5rem; font-family:monospace; font-size:0.7rem">Tx: ${pay.txHash.slice(0, 10)}...${pay.txHash.slice(-6)}</div>` : ''}
				</div>
				`).join("")}
			</div>`;
	}

	private async createPaymentRequest() {
		const desc = (this.shadow.querySelector('[data-field="pay-desc"]') as HTMLInputElement)?.value;
		const amount = (this.shadow.querySelector('[data-field="pay-amount"]') as HTMLInputElement)?.value;
		const token = (this.shadow.querySelector('[data-field="pay-token"]') as HTMLSelectElement)?.value || 'USDC';
		const recipient = (this.shadow.querySelector('[data-field="pay-recipient"]') as HTMLInputElement)?.value;

		if (!desc || !amount || !recipient) return;

		this.creatingPayment = true;
		this.render();

		try {
			await fetch(`${this.getApiBase()}/api/payments`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({
					description: desc,
					amount,
					token,
					recipientAddress: recipient,
					chainId: 8453,
				}),
			});
			await this.loadData();
		} catch (e) {
			console.error("Failed to create payment request:", e);
		}
		this.creatingPayment = false;
	}

	// ── Styles ──

	private getStyles(): string {
		return `
			:host { display: block; padding: 1.5rem; overflow-wrap: break-word; }
			* { box-sizing: border-box; }
			.toolbar { display: flex; gap: 8px; align-items: center; justify-content: flex-end; margin-bottom: 0.75rem; min-height: 28px; flex-wrap: wrap; }
			.tab { padding: 0.375rem 0.75rem; border-radius: 8px; border: 1px solid var(--rs-border); background: var(--rs-bg-surface); color: var(--rs-text-secondary); cursor: pointer; font-size: 0.8125rem; }
			.tab:hover { border-color: var(--rs-border-strong); color: var(--rs-text-primary); }

			.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1rem; }
			.card { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 12px; padding: 1.25rem; }
			.card:hover { border-color: var(--rs-border-strong); }
			.card-clickable { cursor: pointer; transition: border-color 0.15s, transform 0.1s; }
			.card-clickable:hover { border-color: var(--rs-primary); transform: translateY(-1px); }
			.card-title { color: var(--rs-text-primary); font-weight: 600; font-size: 1rem; margin: 0 0 0.5rem; }
			.card-meta { color: var(--rs-text-secondary); font-size: 0.8125rem; margin-bottom: 0.5rem; }
			.section-title { color: var(--rs-text-primary); font-weight: 700; font-size: 1.25rem; margin: 0 0 0.5rem; }

			.tag { display: inline-block; padding: 0.125rem 0.5rem; border-radius: 4px; font-size: 0.6875rem; margin-right: 0.25rem; }
			.tag-type { background: rgba(99,102,241,0.1); color: var(--rs-primary-hover); }
			.tag-cap { background: rgba(34,197,94,0.1); color: #4ade80; }
			.dims { color: var(--rs-text-muted); font-size: 0.75rem; margin-top: 0.5rem; }

			.status { padding: 0.125rem 0.5rem; border-radius: 999px; font-size: 0.6875rem; font-weight: 500; white-space: nowrap; }
			.status-pending, .status-open { background: rgba(251,191,36,0.15); color: #fbbf24; }
			.status-paid, .status-active, .status-confirmed, .status-funded { background: rgba(34,197,94,0.15); color: #4ade80; }
			.status-funding { background: rgba(99,102,241,0.15); color: #a5b4fc; }
			.status-completed, .status-ordered, .status-checking_out { background: rgba(99,102,241,0.15); color: #a5b4fc; }
			.status-cancelled, .status-closed { background: rgba(239,68,68,0.15); color: #f87171; }
			.status-shipped { background: rgba(56,189,248,0.15); color: #38bdf8; }

			.price { color: var(--rs-text-primary); font-weight: 600; font-size: 1rem; }

			/* Catalog product cards */
			.catalog-grid { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
			.catalog-card { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
			.catalog-img { width: 100%; aspect-ratio: 1; overflow: hidden; background: var(--rs-bg-surface-raised); }
			.catalog-img img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.3s; }
			.catalog-card:hover .catalog-img img { transform: scale(1.04); }
			.catalog-body { padding: 1rem; display: flex; flex-direction: column; flex: 1; }
			.catalog-body .card-title { margin-bottom: 0.25rem; }
			.card-desc { color: var(--rs-text-secondary); font-size: 0.8125rem; margin-bottom: 0.75rem; line-height: 1.4; flex: 1; }
			.catalog-footer { display: flex; align-items: center; justify-content: space-between; margin-top: auto; }
			.text-green { color: #4ade80; }

			.progress-bar { background: var(--rs-bg-surface-raised); border-radius: 999px; height: 8px; overflow: hidden; }
			.progress-fill { background: linear-gradient(90deg, var(--rs-primary), #8b5cf6); height: 100%; border-radius: 999px; transition: width 0.3s; }

			.btn { padding: 0.5rem 1rem; border-radius: 8px; border: 1px solid var(--rs-border); background: var(--rs-bg-surface); color: var(--rs-text-primary); cursor: pointer; font-size: 0.875rem; }
			.btn:hover { border-color: var(--rs-border-strong); }
			.btn-primary { background: var(--rs-primary-hover); border-color: var(--rs-primary); color: #fff; }
			.btn-primary:hover { background: #4338ca; }
			.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
			.btn-sm { padding: 0.375rem 0.75rem; font-size: 0.8125rem; }
			.btn-icon { background: none; border: none; color: var(--rs-text-muted); cursor: pointer; padding: 4px; font-size: 0.875rem; border-radius: 4px; }
			.btn-icon:hover { color: #f87171; background: rgba(239,68,68,0.1); }

			.input { padding: 0.5rem 0.75rem; border-radius: 8px; border: 1px solid var(--rs-input-border); background: var(--rs-input-bg); color: var(--rs-input-text); font-size: 0.875rem; }
			.input:focus { outline: none; border-color: var(--rs-primary); }
			.input-sm { max-width: 160px; }

			.new-cart-form, .contribute-form, .new-payment-form { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.5rem; flex-wrap: wrap; }

			.url-input-row { display: flex; gap: 0.5rem; margin-bottom: 1rem; }

			.detail-layout { display: grid; grid-template-columns: 1fr 320px; gap: 1.5rem; }
			@media (max-width: 768px) { .detail-layout { grid-template-columns: 1fr; } }

			.vendor-group { margin-bottom: 1rem; }
			.vendor-header { color: var(--rs-text-secondary); font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; padding: 0.5rem 0; border-bottom: 1px solid var(--rs-border-subtle); margin-bottom: 0.5rem; }
			.item-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem 0; border-bottom: 1px solid var(--rs-border-subtle); }
			.item-thumb { width: 40px; height: 40px; border-radius: 6px; object-fit: cover; background: var(--rs-bg-surface-raised); flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 1.25rem; }
			.item-thumb-placeholder { width: 40px; height: 40px; border-radius: 6px; background: var(--rs-bg-surface-raised); display: flex; align-items: center; justify-content: center; font-size: 1.25rem; }
			.item-info { flex: 1; min-width: 0; }
			.item-name { color: var(--rs-text-primary); font-size: 0.875rem; text-decoration: none; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
			.item-name:hover { color: var(--rs-primary-hover); }
			.item-meta { color: var(--rs-text-muted); font-size: 0.75rem; }

			.summary-row { display: flex; justify-content: space-between; padding: 0.375rem 0; color: var(--rs-text-primary); font-size: 0.875rem; }

			.contrib-row { display: flex; align-items: center; gap: 0.5rem; padding: 0.375rem 0; font-size: 0.8125rem; }
			.contrib-name { color: var(--rs-text-primary); flex: 1; }
			.contrib-amount { color: #4ade80; font-weight: 600; }

			.order-card { display: flex; justify-content: space-between; align-items: center; gap: 0.75rem; }
			.order-info { flex: 1; }
			.order-price { color: var(--rs-text-primary); font-weight: 600; font-size: 1.125rem; }
			.order-thumb { width: 48px; height: 48px; border-radius: 8px; object-fit: cover; flex-shrink: 0; }

			.order-detail-section { margin: 1rem 0; }
			.order-detail-label { color: var(--rs-text-secondary); font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; padding-bottom: 0.5rem; border-bottom: 1px solid var(--rs-border-subtle); margin-bottom: 0.5rem; }
			.tx-hash { font-family: monospace; font-size: 0.75rem; color: var(--rs-text-muted); margin-top: 0.25rem; word-break: break-all; }

			.ext-banner { position: relative; background: rgba(99,102,241,0.08); border: 1px solid rgba(99,102,241,0.25); border-radius: 12px; padding: 1rem 2.5rem 1rem 1.25rem; margin-bottom: 1rem; }
			.ext-banner-dismiss { position: absolute; top: 0.5rem; right: 0.75rem; background: none; border: none; color: var(--rs-text-secondary); font-size: 1.25rem; cursor: pointer; padding: 0 4px; line-height: 1; }
			.ext-banner-dismiss:hover { color: var(--rs-text-primary); }
			.ext-banner-title { color: #a5b4fc; font-weight: 600; font-size: 0.9375rem; margin-bottom: 0.25rem; }
			.ext-banner-text { color: var(--rs-text-secondary); font-size: 0.8125rem; margin-bottom: 0.75rem; line-height: 1.4; }
			.ext-banner-cta { display: inline-block; padding: 0.375rem 1rem; border-radius: 6px; background: var(--rs-primary-hover); color: #fff; font-size: 0.8125rem; font-weight: 600; text-decoration: none; }
			.ext-banner-cta:hover { background: #4338ca; }

			.empty { text-align: center; padding: 3rem; color: var(--rs-text-muted); font-size: 0.875rem; }
			.loading { text-align: center; padding: 3rem; color: var(--rs-text-secondary); }

			/* Catalog detail view */
			.detail-back { margin-bottom: 1rem; }
			.catalog-detail-layout { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; }
			.detail-image img { width: 100%; border-radius: 12px; aspect-ratio: 1; object-fit: cover; }
			.detail-image-placeholder { width: 100%; aspect-ratio: 1; background: var(--rs-bg-surface-raised); border-radius: 12px; display: flex; align-items: center; justify-content: center; color: var(--rs-text-muted); }
			.detail-title { color: var(--rs-text-primary); font-size: 1.5rem; font-weight: 700; margin: 0 0 0.5rem; }
			.detail-desc { color: var(--rs-text-secondary); font-size: 0.875rem; line-height: 1.5; margin: 0.75rem 0; }

			.tier-table { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 10px; overflow: hidden; margin: 1rem 0; }
			.tier-header { padding: 0.625rem 1rem; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--rs-text-secondary); border-bottom: 1px solid var(--rs-border); }
			.tier-row { display: flex; align-items: center; padding: 0.5rem 1rem; border-bottom: 1px solid var(--rs-border-subtle); gap: 1rem; transition: background 0.15s; }
			.tier-row:last-child { border-bottom: none; }
			.tier-active { background: rgba(99,102,241,0.08); }
			.tier-qty { color: var(--rs-text-primary); font-weight: 600; min-width: 3rem; }
			.tier-price { color: var(--rs-text-primary); flex: 1; }
			.tier-savings { color: #4ade80; font-size: 0.8125rem; font-weight: 500; min-width: 3rem; text-align: right; }

			.qty-row { display: flex; align-items: center; justify-content: space-between; margin: 1rem 0; }
			.qty-row label { color: var(--rs-text-primary); font-weight: 600; }
			.qty-controls { display: flex; align-items: center; gap: 0.5rem; }
			.qty-input { width: 60px; text-align: center; padding: 0.375rem; border-radius: 8px; border: 1px solid var(--rs-input-border); background: var(--rs-input-bg); color: var(--rs-input-text); font-size: 0.875rem; }

			.detail-total { display: flex; justify-content: space-between; align-items: center; padding: 1rem 0; border-top: 1px solid var(--rs-border); border-bottom: 1px solid var(--rs-border); }
			.detail-total span:first-child { color: var(--rs-text-primary); font-weight: 600; font-size: 1rem; }

			.provider-info { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 10px; padding: 0.75rem 1rem; margin: 1rem 0; }
			.provider-name { color: var(--rs-text-primary); font-weight: 600; font-size: 0.875rem; }
			.provider-meta { color: var(--rs-text-muted); font-size: 0.8125rem; margin-top: 0.25rem; }

			.detail-actions { display: flex; gap: 0.75rem; margin-top: 1rem; }
			.detail-actions .btn { flex: 1; text-align: center; }

			/* Order queue */
			.queue-badge { background: var(--rs-primary-hover); color: #fff; border: none; border-radius: 999px; padding: 0.375rem 0.875rem; font-size: 0.8125rem; font-weight: 600; cursor: pointer; }
			.queue-badge:hover { background: #4338ca; }
			.queue-drawer { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 12px; padding: 1rem; margin-bottom: 1rem; }
			.queue-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem; }
			.queue-header h3 { margin: 0; color: var(--rs-text-primary); font-size: 1rem; }
			.queue-item { display: flex; align-items: center; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid var(--rs-border-subtle); }
			.queue-item:last-of-type { border-bottom: none; }
			.queue-item-info { flex: 1; }
			.queue-item-title { color: var(--rs-text-primary); font-size: 0.875rem; display: block; }
			.queue-item-meta { color: var(--rs-text-muted); font-size: 0.75rem; }
			.queue-total { display: flex; justify-content: space-between; padding-top: 0.75rem; margin-top: 0.5rem; border-top: 1px solid var(--rs-border); }

			/* Group buy cards */
			.gb-grid { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
			.gb-card { padding: 1rem; }
			.gb-card-top { display: flex; gap: 0.75rem; margin-bottom: 0.75rem; }
			.gb-card-img { width: 64px; height: 64px; border-radius: 10px; object-fit: cover; flex-shrink: 0; }
			.gb-card-img-placeholder { background: var(--rs-bg-surface-raised); display: flex; align-items: center; justify-content: center; font-size: 1.5rem; }
			.gb-card-info { flex: 1; min-width: 0; }
			.gb-card-stats { display: flex; gap: 0.75rem; color: var(--rs-text-muted); font-size: 0.75rem; margin-top: 0.375rem; }
			.gb-card-progress { margin-bottom: 0.5rem; }
			.gb-card-progress-label { color: var(--rs-text-secondary); font-size: 0.75rem; margin-top: 0.25rem; }
			.gb-card-tiers { display: flex; gap: 0.375rem; flex-wrap: wrap; }
			.gb-tier-chip { padding: 0.125rem 0.5rem; border-radius: 4px; font-size: 0.6875rem; background: var(--rs-bg-surface-raised); color: var(--rs-text-muted); border: 1px solid transparent; }
			.gb-tier-reached { color: #4ade80; background: rgba(34,197,94,0.08); }
			.gb-tier-current { border-color: var(--rs-primary); color: var(--rs-primary-hover); background: rgba(99,102,241,0.1); font-weight: 600; }

			@media (max-width: 768px) {
				.catalog-detail-layout { grid-template-columns: 1fr; }
			}
			@media (max-width: 600px) {
				:host { padding: 1rem 0.75rem; }
				.grid { grid-template-columns: 1fr; }
				.gb-grid { grid-template-columns: 1fr; }
				.catalog-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
				.url-input-row { flex-direction: column; }
				.detail-actions { flex-direction: column; }
				.section-title { font-size: 1.0625rem; }
				.detail-title { font-size: 1.25rem; }
				.card { padding: 1rem; }
				.order-card { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
				.new-cart-form, .contribute-form, .new-payment-form { flex-direction: column; }
				.item-row { gap: 0.5rem; }
			}
			@media (max-width: 400px) {
				:host { padding: 0.75rem 0.5rem; }
				.catalog-grid { grid-template-columns: 1fr 1fr; }
				.card { padding: 0.75rem; border-radius: 8px; }
				.card-title { font-size: 0.875rem; }
				.section-title { font-size: 1rem; }
			}
		`;
	}

	private esc(s: string): string {
		const d = document.createElement("div");
		d.textContent = s;
		return d.innerHTML;
	}
}

customElements.define("folk-cart-shop", FolkCartShop);
