[object Object]

← 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

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 →