feat(rcart): clickable demo orders with detail view
Enrich 5 demo orders with items, buyer, payment, provider, and timeline. Order cards show thumbnails and item counts; clicking opens a detail view with payment info, buyer, provider, and timeline using the existing catalog-detail 2-column layout. Demo payments expanded to 5 (3 linked). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
073a64fe56
commit
fcea37b91b
|
|
@ -21,10 +21,11 @@ class FolkCartShop extends HTMLElement {
|
|||
private carts: any[] = [];
|
||||
private payments: any[] = [];
|
||||
private groupBuys: any[] = [];
|
||||
private view: "carts" | "cart-detail" | "catalog" | "catalog-detail" | "orders" | "payments" | "group-buys" = "carts";
|
||||
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;
|
||||
|
|
@ -36,7 +37,7 @@ class FolkCartShop extends HTMLElement {
|
|||
private creatingPayment = false;
|
||||
private creatingGroupBuy = false;
|
||||
private _offlineUnsubs: (() => void)[] = [];
|
||||
private _history = new ViewHistory<"carts" | "cart-detail" | "catalog" | "catalog-detail" | "orders" | "payments" | "group-buys">("carts");
|
||||
private _history = new ViewHistory<"carts" | "cart-detail" | "catalog" | "catalog-detail" | "orders" | "order-detail" | "payments" | "group-buys">("carts");
|
||||
|
||||
// Guided tour
|
||||
private _tour!: TourEngine;
|
||||
|
|
@ -69,7 +70,7 @@ class FolkCartShop extends HTMLElement {
|
|||
|
||||
// Read initial view from attribute (set by server routes) or URL params
|
||||
const initView = this.getAttribute("initial-view");
|
||||
if (initView && ["carts","catalog","orders","payments","group-buys","subscriptions"].includes(initView)) {
|
||||
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);
|
||||
|
|
@ -243,14 +244,72 @@ class FolkCartShop extends HTMLElement {
|
|||
];
|
||||
|
||||
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 },
|
||||
{ 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 },
|
||||
{ id: "demo-ord-1003", total_price: "23.00", currency: "USD", status: "shipped", created_at: new Date(now - 5 * 86400000).toISOString(), artifact_title: "Order #1003", quantity: 3 },
|
||||
{
|
||||
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: "Coffee tip", amount: "5.00", token: "USDC", chainId: 8453, recipientAddress: "0x1234...abcd", status: "paid", paymentMethod: "wallet", txHash: "0xabc123...", created_at: new Date(now - 1 * 86400000).toISOString(), paid_at: new Date(now - 1 * 86400000).toISOString() },
|
||||
{ id: "demo-pay-2", 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 },
|
||||
{ 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 = [
|
||||
|
|
@ -415,6 +474,25 @@ class FolkCartShop extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
|
|
@ -431,6 +509,8 @@ class FolkCartShop extends HTMLElement {
|
|||
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") {
|
||||
|
|
@ -460,6 +540,7 @@ class FolkCartShop extends HTMLElement {
|
|||
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();
|
||||
}
|
||||
|
||||
|
|
@ -528,6 +609,13 @@ class FolkCartShop extends HTMLElement {
|
|||
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']");
|
||||
|
|
@ -557,6 +645,13 @@ class FolkCartShop extends HTMLElement {
|
|||
});
|
||||
});
|
||||
|
||||
// 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", () => {
|
||||
|
|
@ -742,7 +837,12 @@ class FolkCartShop extends HTMLElement {
|
|||
<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" />
|
||||
<button data-action="submit-contribute" class="btn btn-primary btn-sm">Confirm</button>
|
||||
<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>
|
||||
|
||||
|
|
@ -797,6 +897,16 @@ class FolkCartShop extends HTMLElement {
|
|||
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 {
|
||||
|
|
@ -1072,21 +1182,105 @@ class FolkCartShop extends HTMLElement {
|
|||
}
|
||||
|
||||
return `<div class="grid">
|
||||
${this.orders.map((order) => `
|
||||
<div class="card" data-collab-id="order:${order.id}">
|
||||
${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">
|
||||
${order.provider_name ? `Provider: ${this.esc(order.provider_name)}` : ""}
|
||||
${order.quantity > 1 ? ` • Qty: ${order.quantity}` : ""}
|
||||
${itemCount} item${itemCount !== 1 ? 's' : ''}${order.provider?.name ? ` • ${this.esc(order.provider.name)}` : (order.provider_name ? ` • ${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} • $${(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">← 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 ? ` • ${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)} • ${this.esc(order.provider.turnaround)}</div>
|
||||
</div>` : ''}
|
||||
|
||||
${timelineHtml ? `
|
||||
<div class="order-detail-section">
|
||||
<div class="order-detail-label">Timeline</div>
|
||||
${timelineHtml}
|
||||
</div>` : ''}
|
||||
</div>
|
||||
`).join("")}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
|
@ -1260,9 +1454,14 @@ class FolkCartShop extends HTMLElement {
|
|||
.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; }
|
||||
.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; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue