← back to Interiordesignershowroom
store: date + time added on product cards — New badge + 'Added …' line
78c986b3788eb712123a73c4452331da659fed28 · 2026-08-03 10:28:50 -0700 · Steve
Every storefront product card now shows when it was added to the store: a NEW
badge over the image for items added in the last 14 days (exact date+time in the
hover title), plus a muted, dated 'Added Mon D, YYYY, H:MM AM' line under the
price. New shared addedStamp() helper in render.js mirrors the admin when()
format; card-new + card-added styles added to site.css. Chosen treatment per
2026-08-03 decision (overrides the usual 'customer cards omit created date').
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M lib/render.jsM public/css/site.css
Diff
commit 78c986b3788eb712123a73c4452331da659fed28
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Aug 3 10:28:50 2026 -0700
store: date + time added on product cards — New badge + 'Added …' line
Every storefront product card now shows when it was added to the store: a NEW
badge over the image for items added in the last 14 days (exact date+time in the
hover title), plus a muted, dated 'Added Mon D, YYYY, H:MM AM' line under the
price. New shared addedStamp() helper in render.js mirrors the admin when()
format; card-new + card-added styles added to site.css. Chosen treatment per
2026-08-03 decision (overrides the usual 'customer cards omit created date').
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
lib/render.js | 24 +++++++++++++++++++++++-
public/css/site.css | 25 +++++++++++++++++++++++++
2 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/lib/render.js b/lib/render.js
index a88ae7b..e221944 100644
--- a/lib/render.js
+++ b/lib/render.js
@@ -32,6 +32,23 @@ function freshness(ts) {
+ `Price checked ${label}</span>`;
}
+// "Added to the store" signals for a storefront card, derived from created_at.
+// Returns two pieces because they live in different parts of the card: a NEW
+// badge over the image for recently-added items (< NEW_WINDOW_DAYS), and a dated
+// "Added …" line (date + time, full ISO in title=) on every card. Mirrors the
+// admin when() format so the storefront and the curation view read identically.
+const NEW_WINDOW_DAYS = 14;
+function addedStamp(ts) {
+ if (!ts) return { badge: '', line: '' };
+ const d = new Date(ts);
+ const iso = esc(d.toISOString());
+ const days = Math.floor((Date.now() - d.getTime()) / 86400e3);
+ const label = esc(d.toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }));
+ const badge = days <= NEW_WINDOW_DAYS ? `<span class="card-new" title="Added ${iso}">New</span>` : '';
+ const line = `<div class="card-added" title="Added ${iso}">🕓 Added ${label}</div>`;
+ return { badge, line };
+}
+
// FTC 16 CFR Part 255 requires a clear, conspicuous disclosure. This is not optional.
const DISCLOSURE_SHORT =
'This showroom is reader-supported. When you buy through links on our site, we may earn an affiliate commission at no extra cost to you.';
@@ -58,6 +75,10 @@ function productCard(p) {
? `<img loading="lazy" src="${esc(p.image_url)}" alt="${esc(p.title)}" onerror="this.style.display='none';this.parentElement.classList.add('noimg-fallback')">`
: '';
+ // "Added to the store" date+time: NEW badge over the image (recent items) plus a
+ // dated line in the body. Per the chosen storefront treatment (2026-08-03).
+ const { badge: newBadge, line: addedLine } = addedStamp(p.created_at);
+
return `
<article class="card" data-room="${esc(p.room)}" data-style="${esc(p.style)}" data-color="${esc(p.color)}"
data-price="${p.sale_price ?? p.price ?? 0}" data-title="${esc(p.title)}" data-network="${esc(p.network)}"
@@ -65,12 +86,13 @@ function productCard(p) {
data-id="${p.id}">
<a class="card-img" href="/go/${p.id}" rel="nofollow sponsored" target="_blank" tabindex="0"
aria-label="${esc(p.title)} — shop at ${esc(p.advertiser || p.network || '')}">
- ${imgHtml || `<div class="noimg">${esc(p.advertiser || '')}</div>`}
+ ${newBadge}${imgHtml || `<div class="noimg">${esc(p.advertiser || '')}</div>`}
</a>
<div class="card-body">
${p.brand || p.advertiser ? `<div class="card-brand">${esc(p.brand || p.advertiser || '')}</div>` : ''}
<h3 class="card-title"><a href="/go/${p.id}" rel="nofollow sponsored" target="_blank">${esc(p.title)}</a></h3>
${metaHtml}
+ ${addedLine}
<a class="buy" href="/go/${p.id}" rel="nofollow sponsored" target="_blank">Shop at ${esc(p.advertiser || p.network)} →</a>
</div>
</article>`;
diff --git a/public/css/site.css b/public/css/site.css
index fbc1e86..b52b118 100644
--- a/public/css/site.css
+++ b/public/css/site.css
@@ -402,6 +402,22 @@ h2.ruled::after {
visibility: hidden;
}
.card:hover .card-img img { transform: scale(1.03); }
+/* "New" badge for recently-added items — sits over the top-left of the image */
+.card-new {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ z-index: 2;
+ background: var(--ink, #221e19);
+ color: #e6c98a;
+ font-size: .62rem;
+ font-weight: 600;
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ padding: 3px 9px;
+ border-radius: 20px;
+ pointer-events: none;
+}
.noimg {
width: 100%;
height: 100%;
@@ -449,6 +465,15 @@ h2.ruled::after {
}
/* null price: .card-meta stays collapsed, not blank line */
.card-meta:empty { display: none; }
+/* "Added to the store" date + time — muted, sits under price, above the buy CTA */
+.card-added {
+ font-size: .68rem;
+ color: var(--muted-lt, #8a8178);
+ letter-spacing: .02em;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
.price {
font-size: .88rem;
font-weight: 500;
← 72708fc admin: keyword/id suppression rules (Suppress page) + Vacuum
·
back to Interiordesignershowroom
·
harden: intOrNull() guard on client-supplied ids reaching bi aae117f →