← back to Silkwallpaper
snapshot before fleet-grid refine tick 6
11cdfa7718f10664c9cd8293c5208e218303591a · 2026-05-11 03:34:02 -0700 · Steve
Files touched
A public/care.htmlA public/history.htmlA public/index.html.bak.dwfoot.1778203445734A public/index.html.bak.gtagev.1778426585977A public/index.html.bak.ns-canonicalA public/index.html.bak.nspropagate.1778203385192A public/index.html.bak.nspropagate.1778209715095A public/index.html.bak.nspropagate.1778226815192A public/index.html.bak.nspropagate.1778226888840A public/index.html.bak.r1234.26158A public/index.html.pre-sort-frontendA public/sourcing.htmlA public/trade.htmlA public/vocabulary.htmlA server.js.pre-sort-skill
Diff
commit 11cdfa7718f10664c9cd8293c5208e218303591a
Author: Steve <steve@designerwallcoverings.com>
Date: Mon May 11 03:34:02 2026 -0700
snapshot before fleet-grid refine tick 6
---
public/care.html | 55 ++
public/history.html | 49 ++
public/index.html.bak.dwfoot.1778203445734 | 972 +++++++++++++++++++++
public/index.html.bak.gtagev.1778426585977 | 1034 ++++++++++++++++++++++
public/index.html.bak.ns-canonical | 1041 +++++++++++++++++++++++
public/index.html.bak.nspropagate.1778203385192 | 957 +++++++++++++++++++++
public/index.html.bak.nspropagate.1778209715095 | 972 +++++++++++++++++++++
public/index.html.bak.nspropagate.1778226815192 | 974 +++++++++++++++++++++
public/index.html.bak.nspropagate.1778226888840 | 976 +++++++++++++++++++++
public/index.html.bak.r1234.26158 | 980 +++++++++++++++++++++
public/index.html.pre-sort-frontend | 914 ++++++++++++++++++++
public/sourcing.html | 69 ++
public/trade.html | 55 ++
public/vocabulary.html | 76 ++
server.js.pre-sort-skill | 160 ++++
15 files changed, 9284 insertions(+)
diff --git a/public/care.html b/public/care.html
new file mode 100644
index 0000000..af1a803
--- /dev/null
+++ b/public/care.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<html lang="en"><head>
+<!-- editorial-gen v1 -->
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Care & installation — silkwallpaper.com</title>
+<meta name="description" content="Care & installation — SILK — IN BLACK.">
+<link rel="canonical" href="https://silkwallpaper.com/care">
+<style>
+*{margin:0;padding:0;box-sizing:border-box}
+body{font-family:Helvetica Neue;background:#FFF;color:#000;line-height:1.65;padding:60px 24px;min-height:100vh}
+.wrap{max-width:720px;margin:0 auto}
+.eyebrow{font-size:10px;letter-spacing:0.22em;text-transform:uppercase;color:#000;margin-bottom:18px}
+h1{font-family:'Helvetica Neue Bold', sans-serif;font-style:italic;font-size:48px;line-height:1.1;letter-spacing:-0.02em;color:#000;margin-bottom:32px}
+h2{font-family:'Helvetica Neue Bold', sans-serif;font-size:22px;color:#000;margin:36px 0 12px;font-weight:500}
+p{margin-bottom:16px;color:#3a322c}
+ul{margin:12px 0 20px 18px}
+li{margin-bottom:8px;color:#3a322c}
+strong{color:#000}
+a{color:#000;text-decoration:underline;text-underline-offset:3px}
+nav.foot{margin-top:56px;padding-top:24px;border-top:1px solid rgba(0,0,0,0.10);font-size:11px;letter-spacing:0.18em;text-transform:uppercase}
+nav.foot a{margin-right:22px;text-decoration:none}
+</style>
+</head><body>
+<div class="wrap">
+<div class="eyebrow">Editorial · care · Atelier Couture</div>
+<h1>Care & installation</h1>
+<p>Silk wallcoverings demand precision in installation and care. This guide outlines the necessary steps to ensure longevity and integrity, avoiding ambiguity or compromise.</p>
+
+<h2>Substrate Preparation</h2>
+<p>Surface readiness is nonnegotiable. The substrate must be flat, dry, and free of dust, grease, or residue. Use a pH-neutral cleaner to degrease the surface, then rinse thoroughly. Allow full drying before proceeding. Any imperfection in the substrate will be magnified by the material’s sensitivity. Apply a primer if the surface is porous or untreated. This step eliminates the possibility of adhesion failure or warping.</p>
+
+<h2>Paste Type</h2>
+<p>Only use a high-quality, non-acidic adhesive formulated for silk. Avoid products containing solvents or accelerants, which degrade fibers over time. Apply paste in thin, even layers to the back of the silk. Excess paste creates uneven tension and weakens the bond. Work in sections, ensuring paste is fully absorbed before pressing the material into place. Do not stretch the silk; let it conform to the surface naturally.</p>
+
+<h2>Seam Handling</h2>
+<p>Seams must be treated as structural elements. Align edges precisely before applying paste. Use a seam roller to press seams flat, ensuring no gaps or overlaps. Avoid folding or creasing the material, which introduces stress points. For large installations, use a seam tape designed for silk to reinforce joints. Trim excess material immediately after installation; leaving it exposed risks fraying or discoloration.</p>
+
+<h2>Cleaning</h2>
+<p>Clean spills or stains immediately with a damp cloth and mild, pH-neutral soap. Wipe in a single direction to prevent streaking. Do not use abrasive sponges, solvents, or steam cleaners. For persistent stains, consult the manufacturer for approved cleaning agents. Avoid direct contact with water; prolonged exposure risks irreversible damage. Dust regularly with a microfiber cloth to prevent particulate buildup, which can abrade the surface.</p>
+
+<h2>Light Exposure</h2>
+<p>Direct sunlight accelerates fading and weakens silk fibers. Install UV-filtering window films or use curtains to block harmful rays. Avoid placing silk near heat sources, such as radiators or fireplaces. Artificial lighting should be low-temperature and non-fluorescent. Rotate the wallcovering periodically if exposure is unavoidable, but this is not a substitute for protective measures. Light exposure is the primary threat to longevity.</p>
+
+<h2>Expected Lifespan</h2>
+<p>With proper installation and care, silk wallcoverings last between 8 and 12 years. This assumes no exposure to moisture, direct sunlight, or abrasive materials. Lifespan decreases by 20% for every 10% increase in light exposure or humidity. Damage from improper installation, such as stretching or poor seam handling, reduces lifespan by up to 50%. Replacement is necessary when fibers degrade beyond repair, typically marked by brittleness, discoloration, or loss of adhesion.</p>
+<nav class="foot">
+<a href="/">Browse</a>
+<a href="/history">History</a>
+<a href="/vocabulary">Vocabulary</a>
+<a href="/sourcing">Sourcing</a>
+<a href="/care">Care</a>
+<a href="/trade">Trade</a>
+<a href="/about">About</a>
+</nav>
+</div></body></html>
\ No newline at end of file
diff --git a/public/history.html b/public/history.html
new file mode 100644
index 0000000..94557e5
--- /dev/null
+++ b/public/history.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<html lang="en"><head>
+<!-- editorial-gen v1 -->
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>A short history — silkwallpaper.com</title>
+<meta name="description" content="A short history — SILK — IN BLACK.">
+<link rel="canonical" href="https://silkwallpaper.com/history">
+<style>
+*{margin:0;padding:0;box-sizing:border-box}
+body{font-family:Helvetica Neue;background:#FFF;color:#000;line-height:1.65;padding:60px 24px;min-height:100vh}
+.wrap{max-width:720px;margin:0 auto}
+.eyebrow{font-size:10px;letter-spacing:0.22em;text-transform:uppercase;color:#000;margin-bottom:18px}
+h1{font-family:'Helvetica Neue Bold', sans-serif;font-style:italic;font-size:48px;line-height:1.1;letter-spacing:-0.02em;color:#000;margin-bottom:32px}
+h2{font-family:'Helvetica Neue Bold', sans-serif;font-size:22px;color:#000;margin:36px 0 12px;font-weight:500}
+p{margin-bottom:16px;color:#3a322c}
+ul{margin:12px 0 20px 18px}
+li{margin-bottom:8px;color:#3a322c}
+strong{color:#000}
+a{color:#000;text-decoration:underline;text-underline-offset:3px}
+nav.foot{margin-top:56px;padding-top:24px;border-top:1px solid rgba(0,0,0,0.10);font-size:11px;letter-spacing:0.18em;text-transform:uppercase}
+nav.foot a{margin-right:22px;text-decoration:none}
+</style>
+</head><body>
+<div class="wrap">
+<div class="eyebrow">Editorial · history · Atelier Couture</div>
+<h1>A short history</h1>
+<p>Silk, as a medium for wallcoverings, has never been a material of compromise. It is not a surface to be layered, nor a material to be compromised by context. From its earliest use in sumptuous interiors to its modern resurrection as a defiant statement of craft, silk has occupied a singular role: to define space, to command attention, and to exist in direct opposition to the mundane. This is not a history of decoration—it is a history of defiance, of exclusivity, of a material that refuses to be tamed.</p>
+
+<h2>The Precedent of Excess: Silk in the Baroque and Rococo</h2>
+<p>Before the industrial age, silk was reserved for the most opulent interiors. In 17th- and 18th-century Europe, the material was not merely applied to walls but draped, pleated, and woven into the very architecture of power. The Medici palaces in Florence (though the term "Florence" is here only as a footnote) and the Versailles of Louis XIV were not merely adorned with silk—these spaces were constructed through it. The material was woven into tapestries, suspended as gilded banners, and even used as a form of insulation in the colder chambers of palatial halls. The mills of Lyon and Murano produced silks so dense they could withstand the weight of gilded frames, while artisans in the Low Countries developed techniques to stretch and tension silk over wooden lath, creating surfaces that shimmered with the flicker of candlelight.</p>
+
+<ul>
+ <li><strong>Key designers:</strong> The anonymous weavers of the Habsburg court, who transformed silk into a form of political propaganda.</li>
+ <li><strong>Key rooms:</strong> The Hall of Mirrors at Versailles, where silk was used to reflect and amplify the grandeur of the monarchy.</li>
+ <li><strong>Key mills:</strong> The silk looms of Lyon, which supplied the courts of Europe with materials that could not be replicated by cheaper textiles.</li>
+</ul>
+
+<h2>The Modernist Rejection: Bauhaus and the Industrial Age</h2>
+<p>The 20th century saw silk retreat from the realm of excess, only to be reimagined through the lens of modernist minimalism. The Bauhaus movement, with its obsession with function
+<nav class="foot">
+<a href="/">Browse</a>
+<a href="/history">History</a>
+<a href="/vocabulary">Vocabulary</a>
+<a href="/sourcing">Sourcing</a>
+<a href="/care">Care</a>
+<a href="/trade">Trade</a>
+<a href="/about">About</a>
+</nav>
+</div></body></html>
\ No newline at end of file
diff --git a/public/index.html.bak.dwfoot.1778203445734 b/public/index.html.bak.dwfoot.1778203445734
new file mode 100644
index 0000000..1553cc3
--- /dev/null
+++ b/public/index.html.bak.dwfoot.1778203445734
@@ -0,0 +1,972 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<!-- Google tag (gtag.js) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=G-CNTJNLTEW8"></script>
+<script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+ gtag('config', 'G-CNTJNLTEW8');
+</script>
+
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Atelier Couture — SILK — IN BLACK.</title>
+<meta name="description" content="Atelier Couture is a curated atelier dedicated entirely to silk. We do one thing — and we do it carefully.">
+<meta name="theme-color" content="#FFF">
+<link rel="canonical" href="https://silkwallpaper.com/">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
+<link rel="preload" as="image" href="/hero-bg.jpg" fetchpriority="high">
+<script type="application/ld+json">
+{
+ "@context": "https://schema.org",
+ "@type": "Organization",
+ "name": "Atelier Couture",
+ "url": "https://silkwallpaper.com/",
+ "email": "info@silkwallpaper.com"
+}
+</script>
+<script src="/zd-loader.js" defer></script>
+<style>
+/* Atelier Couture · Saint Laurent template — pure monochrome, sharp uppercase, zero ornament */
+:root {
+ --bg: #000;
+ --paper: #FFF;
+ --muted: #888;
+ --line: rgba(255,255,255,0.20);
+ --accent: #FFF;
+ --bg-soft: #0a0a0a;
+ --cols: 6;
+}
+html[data-theme="light"] {
+ --bg: #FFF;
+ --paper: #000;
+ --muted: #666;
+ --line: rgba(0,0,0,0.20);
+ --accent: #000;
+ --bg-soft: #F5F5F5;
+}
+* { margin:0; padding:0; box-sizing:border-box }
+html { scroll-behavior:smooth }
+body { font-family:'Inter', 'Helvetica Neue', sans-serif; color:var(--paper); -webkit-font-smoothing:antialiased; background:var(--bg); min-height:100vh; overflow-x:hidden }
+/* DW-FASHION-TEMPLATE-OVERRIDE */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', Georgia, serif !important; font-weight:900 !important; text-transform:uppercase !important;letter-spacing:0.04em !important; }
+/* Saint Laurent: heavy uppercase headlines, no italic serif accent */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', 'Helvetica Neue', sans-serif !important; font-weight:900 !important; font-style:normal !important; letter-spacing:0.04em !important; text-transform:uppercase !important }
+
+/* ===== Auto-hide header ===== */
+header { position:fixed; top:0; left:0; right:0; display:flex; justify-content:space-between; align-items:center; padding:22px 32px; z-index:100; background:rgba(0,0,0,0.55); backdrop-filter:blur(10px); border-bottom:1px solid var(--line); transition:transform .3s }
+.h-link { display:flex; align-items:center; gap:10px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); text-decoration:none; cursor:pointer; background:transparent; border:0 }
+.h-link:hover { opacity:0.7 }
+.h-icon { width:18px; height:18px; stroke:currentColor; fill:none; stroke-width:1.5 }
+
+/* ===== Cinema hero ===== */
+.cinema { position:relative; height:100vh; width:100%; overflow:hidden }
+.cinema-bg { position:absolute; inset:0; background-size:cover; background-position:center; background-image:url('/hero-bg.jpg') }
+.cinema-bg::after { content:""; position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.75) 100%) }
+.corner-mark { position:absolute; top:24px; left:32px; font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:#fff; text-shadow:0 1px 3px rgba(0,0,0,0.55), 0 0 1px rgba(0,0,0,0.4); z-index:5 }
+.corner-mark::before { content:"◆ "; margin-right:6px }
+.center-mark { position:absolute; top:clamp(80px,12vh,140px); left:50%; transform:translateX(-50%); font-size:clamp(28px,4vw,56px); font-weight:500; letter-spacing:0.18em; text-transform:uppercase; text-align:center; color:#fff; text-shadow:0 2px 8px rgba(0,0,0,0.65), 0 0 1px rgba(0,0,0,0.5); z-index:5 }
+.center-mark .tm { font-size:14px; vertical-align:super; opacity:0.7; margin-left:0.2em }
+.center-mark .sub { display:block; font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400; font-size:0.4em; letter-spacing:0.04em; text-transform:none; margin-top:14px; opacity:0.85 }
+.meta-line { position:absolute; bottom:32px; left:32px; font-size:10px; letter-spacing:0.28em; text-transform:uppercase; font-weight:700; color:#fff; z-index:5; opacity:0.85 }
+.meta-line .num { font-size:12px; display:block; margin-top:4px; letter-spacing:0.05em }
+.enter { position:absolute; bottom:32px; right:32px; display:flex; align-items:center; gap:14px; font-size:11px; letter-spacing:0.36em; text-transform:uppercase; font-weight:700; color:#fff; text-decoration:none; padding-bottom:6px; border-bottom:1px solid #fff; transition:gap 0.25s; z-index:5 }
+.enter:hover { gap:22px }
+.enter svg { width:24px; height:12px }
+
+/* ===== Section ===== */
+.section { padding:96px 32px }
+.section-header { display:flex; justify-content:space-between; align-items:flex-end; flex-wrap:wrap; gap:24px; margin-bottom:32px; padding-bottom:24px; border-bottom:1px solid var(--line) }
+.section-title { font-size:clamp(36px,5vw,64px); font-weight:300; letter-spacing:-0.02em; line-height:1 }
+.section-title .accent { color: var(--accent); font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400 }
+.section-eyebrow { font-size:10px; letter-spacing:0.4em; text-transform:uppercase; font-weight:700; color:var(--muted); margin-bottom:16px }
+.section-meta { font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:600; color:var(--muted); text-align:right; line-height:1.6 }
+
+/* ===== Filter bar ===== */
+.filters { display:flex; gap:8px; align-items:center; margin-bottom:18px; flex-wrap:wrap }
+.chip { padding:8px 16px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); background:transparent; border:1px solid var(--line); cursor:pointer; transition:all 0.2s; font-family:inherit }
+.chip:hover { border-color:var(--paper) }
+.chip.active { background:var(--paper); color:var(--bg); border-color:var(--paper) }
+.search { flex:0 1 280px; margin-left:auto; display:flex; align-items:center; gap:10px; border-bottom:1px solid var(--line); padding:6px 0 }
+.search input { flex:1; background:transparent; border:0; color:var(--paper); font-family:inherit; font-size:13px; outline:none; letter-spacing:0.04em }
+.search input::placeholder { color:var(--muted) }
+.search svg { width:16px; height:16px; stroke:var(--muted); fill:none; stroke-width:1.5 }
+
+/* ===== Grid-density slider ===== */
+.density { display:flex; align-items:center; gap:14px; padding:6px 0; margin-bottom:24px }
+.density label { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--muted) }
+.density input[type=range] { flex:1; max-width:240px; -webkit-appearance:none; appearance:none; height:1px; background:var(--line); outline:none }
+.density input[type=range]::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50% }
+.density input[type=range]::-moz-range-thumb { width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50%; border:0 }
+.density .ct { font-size:11px; color:var(--muted); letter-spacing:0.18em; text-transform:uppercase; font-weight:600; min-width:80px }
+
+/* ===== Stats line ===== */
+.stat-line { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:600; color:var(--muted); margin-bottom:24px }
+
+/* ===== Product grid (novasuede pattern: flush grid + slide-up overlay) ===== */
+.grid { display:grid; grid-template-columns:repeat(var(--cols), 1fr); gap:0 }
+.card { position:relative; aspect-ratio:1/1.15; cursor:pointer; overflow:hidden; border:1px solid var(--bg); transition:transform 0.4s cubic-bezier(0.2,0.8,0.2,1); background:var(--bg-soft) }
+.card:hover { transform:scale(1.04); z-index:5 }
+.card img { width:100%; height:100%; object-fit:cover; display:block; transition:transform 0.4s ease }
+.card .overlay { position:absolute; left:0; right:0; bottom:0; padding:20px 16px 14px; background:linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.85) 100%); transform:translateY(48%); opacity:0.85; transition:transform 0.3s ease, opacity 0.3s ease }
+.card:hover .overlay { transform:translateY(0); opacity:1 }
+.card .pat { font-size:13px; font-weight:600; letter-spacing:0.02em; color:#fff; line-height:1.25; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }
+.card .ven { font-size:9px; letter-spacing:0.18em; text-transform:uppercase; color:#fff; opacity:0.7; font-weight:500; margin-top:4px }
+.card .actions { margin-top:10px; display:flex; gap:6px }
+.card .sample-btn { flex:1; padding:7px 10px; background:#fff; color:#000; font-family:inherit; font-size:10px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; border:0; cursor:pointer; text-align:center; transition:all 0.2s }
+.card .sample-btn:hover { background:var(--accent); color:#fff }
+
+@media (max-width:1024px) { .grid { grid-template-columns:repeat(min(var(--cols), 4), 1fr) } }
+@media (max-width:720px) { .grid { grid-template-columns:repeat(2, 1fr) } .density { display:none } }
+
+.sentinel { height:1px }
+.loading { text-align:center; color:var(--muted); padding:32px; font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700 }
+
+/* ===== Footer ===== */
+footer { padding:64px 32px 32px; border-top:1px solid var(--line); margin-top:32px }
+.footer-grid { max-width:1400px; margin:0 auto; display:grid; grid-template-columns:2fr 1fr 1fr 1fr; gap:48px; margin-bottom:48px }
+.footer-brand { font-size:24px; font-weight:300; letter-spacing:0.32em; text-transform:uppercase; margin-bottom:12px }
+.footer-text { font-size:13px; line-height:1.6; color:var(--muted); max-width:380px }
+.footer-col h4 { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--paper); margin-bottom:18px }
+.footer-col a { display:block; font-size:13px; color:var(--muted); text-decoration:none; margin-bottom:8px; transition:color 0.2s; cursor:pointer; background:transparent; border:0; font-family:inherit; padding:0; text-align:left }
+.footer-col a:hover { color:var(--accent) }
+.footer-bottom { max-width:1400px; margin:0 auto; padding-top:24px; border-top:1px solid var(--line); display:flex; justify-content:space-between; flex-wrap:wrap; gap:16px; font-size:11px; letter-spacing:0.18em; text-transform:uppercase; color:var(--muted) }
+
+@media (max-width:720px) {
+ .corner-mark, .meta-line, .enter { font-size:10px }
+ .center-mark { font-size:24px; letter-spacing:0.12em; top:clamp(60px,9vh,90px) }
+ .section, footer { padding-left:20px; padding-right:20px }
+ .footer-grid { grid-template-columns:1fr; gap:32px }
+ .search { margin-left:0; flex:1 1 100% }
+}
+
+.theme-toggle { background:transparent; border:1px solid var(--line); width:32px; height:32px; cursor:pointer; color:var(--paper); display:inline-flex; align-items:center; justify-content:center; font-size:14px; line-height:1; transition:all .15s; padding:0 }
+.theme-toggle:hover { border-color:var(--accent); color:var(--accent) }
+
+/* ===== WCAG 2.4.7 — keyboard focus indicators ===== */
+/* Strictly :focus-visible so mouse clicks do NOT show the ring */
+a:focus-visible,
+button:focus-visible,
+input:focus-visible,
+select:focus-visible,
+textarea:focus-visible,
+[tabindex]:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+</style>
+<script>
+(function(){ try { var t = localStorage.getItem('silk_theme') || 'dark'; document.documentElement.dataset.theme = t; } catch(e){} })();
+</script>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600;700&display=swap">
+
+<meta property="og:type" content="website">
+<meta property="og:url" content="https://silkwallpaper.com/">
+<meta property="og:title" content="Silk Wallpaper">
+<meta property="og:description" content="Silk Wallpaper — Pure silk wallcoverings — luxe and lustrous.">
+<meta property="og:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<meta property="og:site_name" content="Silk Wallpaper">
+<meta property="article:publisher" content="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings">
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:title" content="Silk Wallpaper">
+<meta name="twitter:description" content="Silk Wallpaper — Pure silk wallcoverings — luxe and lustrous.">
+<meta name="twitter:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<!-- graphics-loop pass 13: footer + fb -->
+</head>
+<body>
+<style id="ns-header-modal-styles">
+ .ns-header { position: sticky; top: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; padding: 18px 36px; background: rgba(10,10,10,0.92) !important; backdrop-filter: blur(8px); border-bottom: 1px solid rgba(245,241,232,0.06); }
+ .ns-brand { display: inline-flex; align-items: center; gap: 14px; text-decoration: none; }
+ .ns-dot { width: 28px; height: 28px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, #a98b5b 0%, #5d4c32 60%, #332a1b 100%); box-shadow: 0 0 0 1px rgba(245,241,232,0.10), 0 2px 12px rgba(180,90,60,0.30); flex-shrink: 0; }
+ .ns-name { font-family: -apple-system, "SF Pro Display", "Helvetica Neue", system-ui, sans-serif; font-size: 18px !important; font-weight: 300 !important; letter-spacing: 0.02em !important; color: #f5f1e8 !important; text-transform: none !important; margin: 0 !important; }
+ .ns-name sup { font-size: 0.55em; font-weight: 300; margin-left: 1px; }
+ .ns-nav { display: flex; gap: 32px; align-items: center; list-style: none; padding: 0; margin: 0; }
+ .ns-nav a, .ns-nav button { font-family: -apple-system, "SF Pro Text", system-ui, sans-serif; font-size: 15px !important; font-weight: 400 !important; color: #f5f1e8 !important; text-decoration: none !important; background: none !important; border: 0 !important; cursor: pointer; padding: 0 !important; transition: color 200ms; letter-spacing: 0 !important; text-transform: none !important; }
+ .ns-nav a:hover, .ns-nav button:hover { color: #ffffff !important; }
+ @media (max-width: 820px) { .ns-header { padding: 14px 18px; } .ns-nav { gap: 16px; } .ns-nav a, .ns-nav button { font-size: 13px !important; } }
+ @media (max-width: 760px) {
+ .ns-header { flex-wrap: wrap; }
+ .ns-nav { display: none !important; flex-basis: 100%; flex-direction: column; align-items: flex-start; gap: 6px; padding: 14px 0 4px; }
+ .ns-nav.open { display: flex !important; }
+ .ns-nav a, .ns-nav button { padding: 10px 0 !important; border-bottom: 1px solid rgba(245,241,232,0.08); width: 100%; text-align: left; }
+ .ns-nav a:nth-child(n+5), .ns-nav button:nth-child(n+5) { display: block !important; }
+ .ns-header .mobile-toggle { display: flex !important; }
+ }
+ @media (min-width: 761px) {
+ .ns-header .mobile-toggle { display: none !important; }
+ }
+ .ns-modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.78); z-index: 200; align-items: center; justify-content: center; padding: 24px; backdrop-filter: blur(6px); }
+ .ns-modal.show { display: flex; }
+ .ns-modal-card { max-width: 460px; width: 100%; background: #141414 !important; border: 1px solid rgba(245,241,232,0.14); border-radius: 6px; padding: 28px 30px 30px; position: relative; }
+ .ns-modal-card .close-x { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; background: none; border: 1px solid rgba(245,241,232,0.18); color: rgba(245,241,232,0.6) !important; border-radius: 50%; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; }
+ .ns-modal-card .close-x:hover { color: #ffffff !important; border-color: #ffffff; }
+ .ns-modal-card h3 { font-family: -apple-system, "SF Pro Display", system-ui; font-weight: 300 !important; font-size: 22px !important; margin: 0 0 6px !important; color: #f5f1e8 !important; }
+ .ns-modal-card .sub { color: rgba(245,241,232,0.65) !important; font-size: 13px; margin: 0 0 22px; }
+ .ns-actions { display: flex; flex-direction: column; gap: 10px; }
+ .ns-action { display: flex; align-items: center; gap: 14px; padding: 14px 16px; background: rgba(245,241,232,0.04) !important; border: 1px solid rgba(245,241,232,0.12) !important; color: #f5f1e8 !important; text-decoration: none !important; cursor: pointer; transition: all 200ms; font: inherit; text-align: left; }
+ .ns-action:hover { background: rgba(245,241,232,0.08) !important; border-color: #d4b683 !important; }
+ .ns-action .ico { width: 32px; height: 32px; border-radius: 50%; background: rgba(212,182,131,0.12); display: flex; align-items: center; justify-content: center; font-size: 15px; flex-shrink: 0; }
+ .ns-action .txt { flex: 1; }
+ .ns-action .txt b { display: block; font-weight: 500; font-size: 14px; color: #f5f1e8 !important; }
+ .ns-action .txt span { font-size: 12px; color: rgba(245,241,232,0.55) !important; }
+
+ /* ─── FINAL CONTRAST PASS — kill remaining gold-solid + white-island defects ─── */
+ /* Replace all gold-fill accents with off-white (Gucci-aesthetic, no DW gold solids) */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active,
+ .drapery-btn, .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover,
+ .drapery-btn:hover, .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ /* Gold accents stay only as edge color (links, glyphs) — not fills */
+ a { color: #d4b683; }
+ a:hover { color: #f5d97a; }
+ /* Authorized-dealer footer line — small caps tracked, muted ink, no gold pill */
+ .authorized, .authorized-dealer p {
+ background: transparent !important;
+ color: rgba(245,241,232,0.5) !important;
+ font-size: 10px !important;
+ letter-spacing: 0.22em !important;
+ text-transform: uppercase !important;
+ padding: 0 !important;
+ border: none !important;
+ margin-top: 14px !important;
+ }
+ /* Kill any straggler white bg in info-card / sample-related cards */
+ .info-card, .info-card-link, .care-highlight, .feature-card, .spec-item {
+ background-color: rgba(245,241,232,0.04) !important;
+ border-color: rgba(245,241,232,0.12) !important;
+ }
+ /* Ensure modal content stays readable — Steve's earlier sweep made it #141414 with white text */
+ .modal-content { background: #141414 !important; color: #f5f1e8 !important; }
+ .modal-content * { color: #f5f1e8 !important; }
+ .modal-content input, .modal-content textarea, .modal-content select {
+ background: rgba(245,241,232,0.05) !important;
+ color: #f5f1e8 !important;
+ border-color: rgba(245,241,232,0.2) !important;
+ }
+ .modal-content label { color: rgba(245,241,232,0.6) !important; }
+ /* Force the wide-tracked NOVASUEDE wordmark to stay white */
+ .ns-wordmark, .ns-wordmark sup { color: #f5f1e8 !important; }
+
+ /* ─── Modal-internal contrast (post-QA defects) ─── */
+ /* Backdrop covers full viewport */
+ .modal { width: 100vw !important; height: 100vh !important; left: 0 !important; top: 0 !important; background: rgba(0, 0, 0, 0.78) !important; backdrop-filter: blur(6px); }
+ /* Chip — flip from white-on-white island to dark card */
+ .request-chip {
+ background: rgba(245, 241, 232, 0.06) !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.18) !important;
+ border-radius: 4px !important;
+ }
+ .request-chip * { color: #f5f1e8 !important; }
+ .request-chip .chip-swatch {
+ /* swatch dot ALWAYS displays the inline-style hex from JS — fight any sweep */
+ border: 1px solid rgba(0, 0, 0, 0.4) !important;
+ }
+ /* + Add to request — hairline white-border on transparent (no gold) */
+ .add-to-request-btn {
+ background: transparent !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.6) !important;
+ border-radius: 0 !important;
+ padding: 12px 22px !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 400 !important;
+ }
+ .add-to-request-btn:hover {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ /* Hex caption visible */
+ .preview-hex, #colorPreviewHex {
+ display: block !important;
+ color: rgba(245, 241, 232, 0.55) !important;
+ font-family: ui-monospace, "SF Mono", Menlo, monospace !important;
+ font-size: 11px !important;
+ margin-top: 4px !important;
+ letter-spacing: 0.04em !important;
+ text-transform: lowercase !important;
+ }
+ /* Color preview name — stays single, prominent */
+ #colorPreviewName {
+ color: #f5f1e8 !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 500 !important;
+ display: block !important;
+ margin-top: 12px !important;
+ }
+
+ /* ─── Mobile hamburger nav (replaces the overflow at <760px) ─── */
+ @media (max-width: 760px) {
+ header .nav-container { flex-wrap: wrap; }
+ header nav { display: none; flex-basis: 100%; flex-direction: column; gap: 6px; padding: 16px 0 6px; }
+ header nav.open { display: flex; }
+ header nav a { padding: 10px 0; border-bottom: 1px solid rgba(245,241,232,0.1); }
+ header .mobile-toggle {
+ display: flex !important;
+ flex-direction: column;
+ gap: 5px;
+ background: transparent;
+ border: 0;
+ cursor: pointer;
+ padding: 8px;
+ }
+ header .mobile-toggle span {
+ width: 22px; height: 1.5px;
+ background: #f5f1e8;
+ display: block;
+ }
+ }
+ @media (min-width: 761px) {
+ header .mobile-toggle { display: none !important; }
+ }
+
+ </style>
+<style id="ns-gold-kill">
+ /* novasuede: kill gold-solid fills, off-white instead. Edge color stays gold. */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active, .drapery-btn,
+ .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover, .drapery-btn:hover,
+ .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ </style>
+<header class="ns-header">
+ <a href="/" class="ns-brand" aria-label="Home">
+ <span class="ns-dot"></span>
+ <span class="ns-name">Atelier Couture</span>
+ </a>
+ <nav class="ns-nav" id="nsPrimaryNav" aria-label="Primary">
+ <a href="/">Home</a>
+ <a href="/about">About</a>
+ <button type="button" onclick="nsContactOpen()">Contact</button>
+ </nav>
+ <button type="button" class="mobile-toggle" aria-label="Toggle menu" aria-controls="nsPrimaryNav" aria-expanded="false" onclick="nsToggleNav(this)">
+ <span></span><span></span><span></span>
+ </button>
+ </header>
+
+<div class="ns-modal" id="nsContactModal" role="dialog" aria-modal="true" aria-labelledby="nsContactTitle" onclick="if(event.target===this)nsContactClose()">
+ <div class="ns-modal-card">
+ <button type="button" class="close-x" onclick="nsContactClose()" aria-label="Close">×</button>
+ <h3 id="nsContactTitle">How can we help?</h3>
+ <p class="sub">Trade-account replies within one business day.</p>
+ <div class="ns-actions">
+ <a class="ns-action" href="#contact" onclick="nsContactClose()"><span class="ico">📋</span><div class="txt"><b>Get a Quote</b><span>Project specs → priced estimate</span></div></a>
+ <a class="ns-action" href="#contact" onclick="nsContactClose();openModal();"><span class="ico">📦</span><div class="txt"><b>Order a Sample</b><span>Free swatch to your shop</span></div></a>
+ <a class="ns-action" href="mailto:info@novasuede.com?subject=Novasuede%20inquiry"><span class="ico">✉️</span><div class="txt"><b>Email Us</b><span>info@novasuede.com</span></div></a>
+ <a class="ns-action" href="tel:888-373-4564"><span class="ico">📞</span><div class="txt"><b>Call Us</b><span>(888) 373-4564 · M–F, 9–5 PT</span></div></a>
+ </div>
+ </div>
+ </div>
+<script>
+ function nsContactOpen() { var m = document.getElementById('nsContactModal'); if (m) m.classList.add('show'); }
+ function nsContactClose() { var m = document.getElementById('nsContactModal'); if (m) m.classList.remove('show'); }
+ function nsToggleNav(btn) {
+ var nav = document.getElementById('nsPrimaryNav');
+ if (!nav) return;
+ var open = nav.classList.toggle('open');
+ btn.setAttribute('aria-expanded', open ? 'true' : 'false');
+ }
+ document.addEventListener('keydown', function(e){ if (e.key === 'Escape') nsContactClose(); });
+</script>
+
+
+
+
+
+<header>
+ <button class="h-link" onclick="dwmOpen('Contact')" aria-label="Contact"><svg class="h-icon" viewBox="0 0 24 24"><path d="M3 7h18M3 12h18M3 17h18"/></svg><span>Contact</span></button>
+ <button class="theme-toggle" id="theme-toggle" aria-label="Theme toggle">☾</button>
+</header>
+
+<section class="cinema">
+ <div class="cinema-bg" id="heroBg"></div>
+ <div class="corner-mark">Silken Sheen</div>
+ <div class="center-mark">Silk Wallpaper</div>
+ <div class="meta-line">Silk · Luxe · Sheen · Jacquard<span class="num" id="heroNum"></span></div>
+ <a class="enter" href="#shop">Enter <svg viewBox="0 0 24 12" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M0 6h22M16 1l6 5-6 5"/></svg></a>
+</section>
+
+
+<section class="section" id="ideas">
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">New this week</h2>
+ <div class="rail" id="railNewest"></div>
+ </div>
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">Curated picks</h2>
+ <div class="rail" id="railCurated"></div>
+ </div>
+</section>
+
+<section class="section" id="shop">
+ <div class="section-header">
+ <div>
+ <div class="section-eyebrow">The Collection</div>
+ <h2 class="section-title"><span class="accent" id="totalCount">—</span> Patterns.</h2>
+ </div>
+ <div class="section-meta">Silk · Luxe · Sheen · Jacquard</div>
+ </div>
+
+ <div class="filters" id="facets">
+ <button class="chip active" data-facet="all">All</button>
+ </div>
+
+ <div class="density">
+ <label>Sort</label>
+ <select id="sortSelect" aria-label="Sort products" style="background:transparent;color:inherit;border:1px solid var(--line,rgba(255,255,255,.2));font:inherit;font-size:11px;letter-spacing:.10em;text-transform:uppercase;padding:4px 8px;cursor:pointer;outline:none;margin-right:14px">
+ <option value="newest">Newest</option>
+ <option value="color">Color</option>
+ <option value="style">Style</option>
+ <option value="sku">SKU A→Z</option>
+ <option value="title">Title A→Z</option>
+ <option value="price-asc">Price ↑</option>
+ <option value="price-desc">Price ↓</option>
+ </select>
+ <label>Grid</label>
+ <input type="range" id="densitySlider" min="4" max="12" step="1" value="6" aria-label="Grid columns">
+ <span class="ct" id="densityLabel">6 cols</span>
+ <div class="search">
+ <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5.5-5.5"/></svg>
+ <input type="text" id="searchInput" placeholder="Search by pattern, color…" autocomplete="off">
+ </div>
+ </div>
+
+ <div class="stat-line" id="statLine">Loading…</div>
+ <div class="grid" id="grid"></div>
+ <div class="loading" id="loading">Loading more…</div>
+ <div class="sentinel" id="sentinel"></div>
+</section>
+
+<footer>
+ <div class="footer-grid">
+ <div>
+ <div class="footer-brand">Silk Wallpaper</div>
+ <p class="footer-text">Silk Wallpaper is a specialty silk archive within the Designer Wallcoverings family. Real samples ship free.</p>
+ <p class="footer-text" style="margin-top:14px;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;font-weight:600;color:var(--paper);opacity:0.7"><a href="/about" style="color:inherit;text-decoration:none;border-bottom:1px solid var(--accent)">About →</a></p>
+ </div>
+ <div class="footer-col">
+ <h4>Aesthetic</h4>
+ <div id="footerFacets"></div>
+ </div>
+ <div class="footer-col">
+ <h4>Trade</h4>
+ <button onclick="dwmOpen('Inquiry')">Project Inquiry</button>
+ <button onclick="dwmOpen('Sample')">Request Sample</button>
+ <button onclick="dwmOpen('Contact')">Contact</button>
+ </div>
+
+ <div class="footer-col">
+ <h4>Social</h4>
+ <a class="fb-link" href="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings" target="_blank" rel="noopener">Facebook</a>
+ </div>
+ </div>
+ <div class="footer-bottom">
+ <span>silkwallpaper.com · Atelier Couture</span>
+ <span><a href="mailto:info@silkwallpaper.com" style="color:var(--paper);text-decoration:none;border-bottom:1px solid var(--accent)">info@silkwallpaper.com</a></span>
+ <span id="footerStat"></span>
+ </div>
+</footer>
+
+<script>
+// mini-constellation — 43 niche-stars, current site highlighted in gold
+(function(){
+ const SLUG = "silkwallpaper";
+ const NICHES = [
+ ['silkwallpaper','Silk','mat'],['silkwallcoverings','Silk W/C','mat'],['linenwallpaper','Linen','mat'],
+ ['jutewallpaper','Jute','mat'],['raffiawallcoverings','Raffia W/C','mat'],['raffiawalls','Raffia Walls','mat'],
+ ['corkwallcovering','Cork','mat'],['fabricwallpaper','Fabric','mat'],['textilewallpaper','Textile','mat'],
+ ['metallicwallpaper','Metallic','mat'],['silverleafwallpaper','Silver Leaf','mat'],['vinylwallpaper','Vinyl','mat'],
+ ['micawallpaper','Mica','mat'],['madagascarwallpaper','Madagascar','mat'],['mylarwallpaper','Mylar','mat'],
+ ['suedewallpaper','Suede','mat'],
+ ['1800swallpaper','1800s','dec'],['1890swallpaper','1890s','dec'],['1900swallpaper','1900s','dec'],
+ ['1920swallpaper','1920s','dec'],['1930swallpaper','1930s','dec'],['1940swallpaper','1940s','dec'],
+ ['1950swallpaper','1950s','dec'],['1960swallpaper','1960s','dec'],['1970swallpaper','1970s','dec'],
+ ['1980swallpaper','1980s','dec'],['retrowalls','Retro','dec'],['wallpapersback','W. Back','dec'],
+ ['agedwallpaper','Aged','cra'],['handcraftedwallpaper','Handcrafted','cra'],['museumwallpaper','Museum','cra'],
+ ['restorationwallpaper','Restoration','cra'],['pastelwallpaper','Pastel','cra'],['glitterwalls','Glitter','cra'],
+ ['greenwallcoverings','Green','cra'],['naturalwallcoverings','Natural','cra'],['saloonwallpaper','Saloon','cra'],
+ ['contractwallpaper','Contract','use'],['hotelwallcoverings','Hotel','use'],['hospitalitywallpaper','Hospitality','use'],
+ ['healthcarewallpaper','Healthcare','use'],['restaurantwallpaper','Restaurant','use'],['architecturalwallcoverings','Architectural','use']
+ ];
+ const COLOR = { mat:'#c9b687', dec:'#b86a4a', cra:'#6b8e6f', use:'#4a6b8e' };
+ const W = 1080, H = 200, BANDS = ['mat','dec','cra','use'];
+ const groups = { mat:[], dec:[], cra:[], use:[] };
+ for (const n of NICHES) groups[n[2]].push(n);
+ const svg = document.getElementById('miniConst');
+ if (!svg) return;
+ const bandW = W / BANDS.length;
+ let nodes = '';
+ BANDS.forEach((band, bi) => {
+ const list = groups[band];
+ const cx = bi * bandW + bandW / 2;
+ list.forEach((n, i) => {
+ const t = list.length === 1 ? 0.5 : i / (list.length - 1);
+ // deterministic jitter per slug
+ let h = 0; for (let k = 0; k < n[0].length; k++) h = (h * 31 + n[0].charCodeAt(k)) | 0;
+ const jx = ((h & 0xff) / 255 - 0.5) * (bandW * 0.5);
+ const jy = (((h >> 8) & 0xff) / 255 - 0.5) * 6; // tightened from 14 → 6 to prevent same-band collisions (16 mat nodes / 144px = 9px spacing)
+ const x = cx + jx, y = 28 + t * (H - 56) + jy;
+ const isCurrent = n[0] === SLUG;
+ const r = isCurrent ? 8 : 4;
+ const op = isCurrent ? 1 : 0.55;
+ nodes += `<g class="mc-node" data-slug="${n[0]}" data-label="${n[1]}" style="cursor:pointer">
+ <circle cx="${x}" cy="${y}" r="${r}" fill="url(#dna-${band})" fill-opacity="${op}" stroke="${COLOR[band]}" stroke-opacity="${isCurrent?1:0.7}" stroke-width="${isCurrent?2:1}">
+ <title>${n[1]} — ${n[0]}.com</title>
+ </circle>
+ ${isCurrent ? `<circle cx="${x}" cy="${y}" r="${r+5}" fill="none" stroke="${COLOR[band]}" stroke-opacity="0.4" stroke-width="1"><animate attributeName="r" values="${r+5};${r+12};${r+5}" dur="2.4s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" repeatCount="indefinite"/></circle>` : ''}
+ </g>`;
+ });
+ });
+ // append nodes after defs
+ svg.insertAdjacentHTML('beforeend', nodes);
+ svg.querySelectorAll('.mc-node').forEach(n => {
+ n.addEventListener('mouseenter', () => n.querySelector('circle').setAttribute('fill-opacity', '1'));
+ n.addEventListener('mouseleave', e => {
+ const isCurrent = n.dataset.slug === SLUG;
+ n.querySelector('circle').setAttribute('fill-opacity', isCurrent ? '1' : '0.55');
+ });
+ n.addEventListener('click', () => window.open('https://' + n.dataset.slug + '.com', '_blank'));
+ });
+})();
+</script>
+
+<script>
+const state = { q:'', facet:'all', page:1, pages:1, total:0, loading:false, exhausted:false };
+const LABELS = {"all":"All"};
+
+function escAttr(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
+// Image-URL allowlist: only DW Shopify CDN + designerwallcoverings.com (defends against XSS via crafted products.json)
+function safeImg(u) { return /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(u || '') ? u : '/hero-bg.jpg'; }
+function cardHTML(p) {
+ const eager = state.page === 1;
+ return '<img loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+ + '<div class="overlay">'
+ + '<div class="pat">' + escAttr(p.pattern_name || p.title) + '</div>'
+ + '<div class="ven">' + escAttr((p.vendor || '').replace(/-/g, ' ')) + '</div>'
+ + '<div class="actions">'
+ + '<button class="sample-btn" onclick="event.stopPropagation();dwmOpen(\'Sample\',' + JSON.stringify({sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}).replace(/"/g,'"') + ')">Sample</button>'
+ + '</div></div>';
+}
+
+async function loadFacets() {
+ let f;
+ try {
+ const r = await fetch('/api/facets');
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ f = await r.json();
+ } catch (e) { console.error('loadFacets failed:', e); return; }
+ const el = document.getElementById('facets');
+ for (const [k, v] of Object.entries(f.aesthetics).sort((a,b) => b[1] - a[1])) {
+ const b = document.createElement('button');
+ b.className = 'chip'; b.dataset.facet = k;
+ b.innerHTML = (LABELS[k] || k) + ' <span style="opacity:.55;font-weight:500;margin-left:4px">' + v + '</span>';
+ el.appendChild(b);
+ }
+ el.addEventListener('click', e => {
+ if (e.target.tagName !== 'BUTTON') return;
+ document.querySelectorAll('#facets button').forEach(b => b.classList.remove('active'));
+ e.target.classList.add('active');
+ state.facet = e.target.dataset.facet;
+ resetGrid();
+ });
+ document.getElementById('totalCount').textContent = f.total;
+ document.getElementById('footerStat').textContent = f.total + ' patterns · live archive';
+ // Footer aesthetic links
+ const footerEl = document.getElementById('footerFacets');
+ for (const [k] of Object.entries(f.aesthetics)) {
+ const b = document.createElement('button');
+ b.textContent = LABELS[k] || k;
+ b.onclick = () => { state.facet = k; resetGrid(); document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.facet === k)); document.getElementById('shop').scrollIntoView({behavior:'smooth'}); };
+ footerEl.appendChild(b);
+ }
+}
+
+function resetGrid() {
+ state.page = 1; state.exhausted = false;
+ document.getElementById('grid').innerHTML = '';
+ document.getElementById('loading').textContent = 'Loading…';
+ loadGridPage();
+}
+
+async function loadGridPage() {
+ if (state.loading || state.exhausted) return;
+ state.loading = true;
+ const params = new URLSearchParams({ page:state.page, limit:24 });
+ if (state.q) params.set('q', state.q);
+ if (state.facet !== 'all') params.set('aesthetic', state.facet);
+ let data;
+ try {
+ const r = await fetch('/api/products?' + params);
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ data = await r.json();
+ } catch (e) {
+ console.error('loadGridPage failed:', e);
+ document.getElementById('loading').textContent = '— offline — refresh to retry —';
+ state.loading = false;
+ return;
+ }
+ state.total = data.total; state.pages = data.pages;
+
+ if (state.page === 1) {
+ document.getElementById('statLine').textContent = data.total + ' patterns · ' + (state.facet === 'all' ? 'all aesthetics' : (LABELS[state.facet] || state.facet)) + (state.q ? ' · matching "' + state.q + '"' : '');
+ }
+ const grid = document.getElementById('grid');
+ for (const p of data.items) {
+ const a = document.createElement('a');
+ a.className = 'card'; a.href = '#'; a.onclick = (e) => { e.preventDefault(); dwmOpen('Sample',{sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}); };
+ a.innerHTML = cardHTML(p);
+ grid.appendChild(a);
+ }
+ if (state.page >= state.pages || data.items.length === 0) {
+ state.exhausted = true;
+ document.getElementById('loading').textContent = state.total > 0 ? '— end of archive · ' + state.total + ' patterns —' : '';
+ } else state.page++;
+ state.loading = false;
+}
+
+const io = new IntersectionObserver(es => { for (const e of es) if (e.isIntersecting) loadGridPage(); }, { rootMargin:'600px 0px' });
+io.observe(document.getElementById('sentinel'));
+
+document.getElementById('searchInput').addEventListener('input', e => {
+ state.q = e.target.value.trim();
+ clearTimeout(window._t);
+ window._t = setTimeout(resetGrid, 220);
+});
+
+// Density slider
+const slider = document.getElementById('densitySlider');
+const dlabel = document.getElementById('densityLabel');
+function setDensity(n) {
+ document.documentElement.style.setProperty('--cols', n);
+ dlabel.textContent = n + ' cols';
+ try { localStorage.setItem('silk_theme_density', n); } catch(e){}
+}
+slider.addEventListener('input', e => setDensity(parseInt(e.target.value)));
+const savedDensity = parseInt(localStorage.getItem('silk_theme_density') || '6');
+if (savedDensity >= 4 && savedDensity <= 12) { slider.value = savedDensity; setDensity(savedDensity); }
+
+// Theme toggle
+const tb = document.getElementById('theme-toggle');
+function setTheme(t){ document.documentElement.dataset.theme = t; try { localStorage.setItem('silk_theme', t); } catch(e){} tb.textContent = t === 'dark' ? '☾' : '☀'; }
+setTheme(document.documentElement.dataset.theme || 'dark');
+tb.addEventListener('click', () => setTheme(document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'));
+
+loadFacets();
+loadGridPage();
+</script>
+<!-- ============================================================
+ DW UNIVERSAL CONTACT MODULE — fashion-house UX
+ Inject before </body> in every DW-family sister site.
+ Self-contained: CSS + 4 modals + JS submission handlers.
+ Uses host site's CSS vars (--bg, --fg, --gold, --rule).
+ ============================================================ -->
+<style>
+ /* === MODAL BASE === */
+ .dwm{position:fixed;inset:0;background:rgba(0,0,0,0.78);display:none;align-items:center;justify-content:center;z-index:9999;padding:24px;overflow-y:auto}
+ .dwm.open{display:flex}
+ .dwm-box{background:var(--bg);border:1px solid var(--rule);max-width:520px;width:100%;padding:36px 32px;position:relative;max-height:90vh;overflow-y:auto;animation:dwm-in 0.25s ease}
+ @keyframes dwm-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+ .dwm-box h3{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-weight:400;font-size:32px;line-height:1.05;margin-bottom:6px;color:var(--fg);letter-spacing:-0.01em}
+ .dwm-box .sub{color:var(--muted);font-size:12px;letter-spacing:0.16em;text-transform:uppercase;margin-bottom:24px;font-weight:500}
+ .dwm-close{position:absolute;top:18px;right:18px;background:transparent;border:0;color:var(--muted);font-size:24px;cursor:pointer;width:32px;height:32px;line-height:1;padding:0}
+ .dwm-close:hover{color:var(--gold)}
+ .dwm-preview{display:flex;gap:14px;align-items:center;padding:12px;background:var(--bg-soft);margin-bottom:18px}
+ .dwm-preview img{width:64px;height:64px;object-fit:cover}
+ .dwm-preview .pn{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-size:15px;line-height:1.2;color:var(--fg)}
+ .dwm-preview .pc{font-size:10px;letter-spacing:0.14em;text-transform:uppercase;color:var(--muted);margin-top:4px}
+ .dwm-field{margin-bottom:14px}
+ .dwm-field label{display:block;font-size:10px;letter-spacing:0.18em;text-transform:uppercase;color:var(--muted);margin-bottom:6px;font-weight:500}
+ .dwm-field input,.dwm-field textarea,.dwm-field select{width:100%;border:0;border-bottom:1px solid var(--rule);background:transparent;padding:8px 4px;font-size:14px;font-family:inherit;color:var(--fg);outline:none;transition:border-color 0.2s}
+ .dwm-field input:focus,.dwm-field textarea:focus{border-bottom-color:var(--gold)}
+ .dwm-field textarea{min-height:60px;resize:vertical}
+ .dwm-submit{background:transparent;color:var(--muted, #c9b89e);border:1px solid var(--muted, #c9b89e);padding:14px 28px;font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:700;cursor:pointer;width:100%;margin-top:8px;transition:background 0.15s, color 0.15s}
+ .dwm-submit:hover{background:var(--muted, #c9b89e);color:var(--bg, #000)}
+
+ .dwm-submit:disabled{opacity:0.5;cursor:default}
+ .dwm-status{margin-top:14px;font-size:11px;letter-spacing:0.16em;text-transform:uppercase;text-align:center;display:none}
+ .dwm-status.ok{color:var(--sage);display:block}
+ .dwm-status.err{color:#e08070;display:block}
+ /* contact-options */
+ .dwm-options{display:flex;flex-direction:column;gap:10px}
+ .dwm-option{display:flex;align-items:center;gap:14px;padding:18px 20px;background:transparent;border:1px solid var(--rule);color:var(--fg);font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:600;cursor:pointer;text-decoration:none;transition:all 0.2s;text-align:left;width:100%}
+ .dwm-option:hover{border-color:var(--gold);color:var(--gold)}
+ .dwm-option svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:1.5;flex-shrink:0}
+ .dwm-option .lbl{flex:1;display:block}
+ .dwm-option .val{display:block;font-size:10px;letter-spacing:0.14em;text-transform:none;font-weight:500;opacity:0.65;margin-top:4px}
+
+/* graphics-loop pass 5: serif hero typography + nav legibility */
+.cinema h1, h1.headline { font-family: 'Cormorant Garamond', Georgia, serif; font-weight: 500; letter-spacing: -0.01em }
+.h-link { font-size: 13px; letter-spacing: 0.06em }
+
+/* graphics-loop pass 11: Ideas rails */
+#ideas { padding: 56px 24px 24px; max-width:1600px; margin:0 auto }
+#ideas .rail-block { margin-bottom:48px }
+#ideas .rail-eyebrow { font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:var(--muted, #888); margin-bottom:8px }
+#ideas .rail-title { font-size:clamp(22px,2.6vw,32px); font-weight:500; letter-spacing:-0.01em; margin:0 0 18px; color:var(--ink,#fff) }
+#ideas .rail { display:flex; gap:14px; overflow-x:auto; scroll-snap-type:x mandatory; padding-bottom:8px; scrollbar-width:none }
+#ideas .rail::-webkit-scrollbar { display:none }
+#ideas .rail-card { flex:0 0 240px; scroll-snap-align:start; cursor:pointer; transition:transform 0.2s }
+#ideas .rail-card:hover { transform:translateY(-3px) }
+#ideas .rail-card img { width:100%; aspect-ratio:1; object-fit:cover; display:block; background:var(--card,#222) }
+#ideas .rail-card .rc-title { font-size:13px; font-weight:500; color:var(--paper,#fff); padding:8px 0 2px; line-height:1.3 }
+#ideas .rail-card .rc-meta { font-size:11px; color:var(--muted,#888); letter-spacing:0.06em }
+@media (max-width:720px) { #ideas .rail-card { flex:0 0 160px } }
+</style>
+
+<!-- Contact options modal -->
+<div class="dwm" id="dwmContact" onclick="if(event.target===this)dwmClose('Contact')">
+ <div class="dwm-box" style="max-width:440px">
+ <button class="dwm-close" onclick="dwmClose('Contact')" aria-label="Close">×</button>
+ <h3>Contact</h3>
+ <p class="sub" id="dwmContactSub">Three ways to reach us</p>
+ <div class="dwm-options">
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Inquiry')">
+ <svg viewBox="0 0 24 24"><path d="M4 4h16v12H7l-3 3z"/></svg>
+ <span class="lbl">Send an Inquiry<span class="val">Project name · scope · all the details</span></span>
+ </button>
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Sample')">
+ <svg viewBox="0 0 24 24"><rect x="4" y="6" width="16" height="14"/><path d="M4 10h16M9 6V3h6v3"/></svg>
+ <span class="lbl">Request a Sample<span class="val">Memo sample · ships free · 3–5 business days</span></span>
+ </button>
+ <a class="dwm-option" id="dwmContactEmailLink" href="mailto:info@silkwallpaper.com">
+ <svg viewBox="0 0 24 24"><path d="M3 6h18v12H3z"/><path d="M3 6l9 7 9-7"/></svg>
+ <span class="lbl">Email Us<span class="val" id="dwmContactEmailLabel">info@silkwallpaper.com</span></span>
+ </a>
+ </div>
+ </div>
+</div>
+
+<!-- Inquiry modal -->
+<div class="dwm" id="dwmInquiry" onclick="if(event.target===this)dwmClose('Inquiry')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Inquiry')" aria-label="Close">×</button>
+ <h3>Project Inquiry</h3>
+ <p class="sub">We'll respond within one business day</p>
+ <form id="dwmInquiryForm" onsubmit="return dwmSubmit(event,'inquiry')">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Phone</label><input type="tel" name="phone"></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Project Name</label><input type="text" name="projectName" required></div>
+ <div class="dwm-field"><label>Project Scope</label><textarea name="projectScope" placeholder="Square footage · room count · timeline" required></textarea></div>
+ <div class="dwm-field"><label>Additional Information</label><textarea name="message" placeholder="Patterns of interest, finish preferences, budget"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Inquiry</button>
+ <div class="dwm-status" id="dwmInquiryStatus"></div>
+ </form>
+ </div>
+</div>
+
+<!-- Sample-request modal -->
+<div class="dwm" id="dwmSample" onclick="if(event.target===this)dwmClose('Sample')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Sample')" aria-label="Close">×</button>
+ <h3>Request a Sample</h3>
+ <p class="sub">Memo sample · ships free · 3–5 business days</p>
+ <div class="dwm-preview" id="dwmSamplePreview" style="display:none">
+ <img id="dwmSampleImg" src="" alt="">
+ <div><div class="pn" id="dwmSampleName"></div><div class="pc" id="dwmSampleSku"></div></div>
+ </div>
+ <form id="dwmSampleForm" onsubmit="return dwmSubmit(event,'sample')">
+ <input type="hidden" name="sku" id="dwmSampleSkuInput">
+ <input type="hidden" name="title" id="dwmSampleTitleInput">
+ <input type="hidden" name="image_url" id="dwmSampleImageInput">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Shipping Address</label><input type="text" name="address" placeholder="Street" required></div>
+ <div class="dwm-field" style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:8px">
+ <input type="text" name="city" placeholder="City" required>
+ <input type="text" name="state" placeholder="State" required>
+ <input type="text" name="zip" placeholder="ZIP" required>
+ </div>
+ <div class="dwm-field"><label>Notes (optional)</label><textarea name="message" placeholder="Project, sq.ft., timing"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Sample Request</button>
+ <div class="dwm-status" id="dwmSampleStatus"></div>
+ </form>
+ </div>
+</div>
+
+<script>
+ // Universal modal control
+ function dwmOpen(name, opts){
+ document.getElementById('dwm' + name).classList.add('open');
+ document.body.style.overflow = 'hidden';
+ if (name === 'Sample' && opts) {
+ document.getElementById('dwmSamplePreview').style.display = 'flex';
+ document.getElementById('dwmSampleImg').src = opts.image_url || '';
+ document.getElementById('dwmSampleName').textContent = opts.title || '';
+ document.getElementById('dwmSampleSku').textContent = opts.sku || '';
+ document.getElementById('dwmSampleSkuInput').value = opts.sku || '';
+ document.getElementById('dwmSampleTitleInput').value = opts.title || '';
+ document.getElementById('dwmSampleImageInput').value = opts.image_url || '';
+ } else if (name === 'Sample') {
+ document.getElementById('dwmSamplePreview').style.display = 'none';
+ document.getElementById('dwmSampleSkuInput').value = '';
+ document.getElementById('dwmSampleTitleInput').value = '';
+ document.getElementById('dwmSampleImageInput').value = '';
+ }
+ }
+ function dwmClose(name){ document.getElementById('dwm'+name).classList.remove('open'); document.body.style.overflow=''; }
+
+ // Universal form submit — POSTs to /api/send-{kind}
+ async function dwmSubmit(e, kind) {
+ e.preventDefault();
+ const form = e.target;
+ const data = Object.fromEntries(new FormData(form).entries());
+ const btn = form.querySelector('.dwm-submit');
+ const status = form.querySelector('.dwm-status');
+ btn.disabled = true; const orig = btn.textContent; btn.textContent = 'Sending…';
+ status.className = 'dwm-status';
+ try {
+ const r = await fetch('/api/send-' + kind, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data) });
+ if (!r.ok) throw new Error('HTTP '+r.status);
+ const j = await r.json();
+ status.textContent = kind === 'sample' ? 'Sample request sent — ships within 3–5 business days' : 'Inquiry sent — we respond within 1 business day';
+ status.className = 'dwm-status ok';
+ btn.textContent = 'Sent ✓';
+ setTimeout(() => { dwmClose(kind === 'sample' ? 'Sample' : 'Inquiry'); btn.disabled = false; btn.textContent = orig; status.className = 'dwm-status'; form.reset(); }, 2800);
+ } catch (err) {
+ status.textContent = 'Send failed — please email info@designerwallcoverings.com';
+ status.className = 'dwm-status err';
+ btn.disabled = false; btn.textContent = orig;
+ }
+ return false;
+ }
+
+ // Wire up "Memo samples ship free" badge in hero to open sample modal directly
+ document.querySelectorAll('.hero .badge, [data-open-sample]').forEach(el => {
+ el.style.cursor = 'pointer';
+ el.addEventListener('click', () => dwmOpen('Sample'));
+ });
+
+ // Wire nav "Trade" link to inquiry modal
+ document.querySelectorAll('nav a[href*="designerwallcoverings.com"]').forEach(a => {
+ a.removeAttribute('target');
+ a.removeAttribute('href');
+ a.style.cursor = 'pointer';
+ a.addEventListener('click', e => { e.preventDefault(); dwmOpen('Contact'); });
+ });
+
+ // Override product card clicks — open Sample modal pre-filled instead of redirecting to DW
+ document.addEventListener('click', e => {
+ const card = e.target.closest('.card, .rail-card');
+ if (!card) return;
+ if (card.tagName === 'A' && (card.getAttribute('href') || '').startsWith('/sample/')) {
+ e.preventDefault();
+ const img = card.querySelector('img');
+ const pat = card.querySelector('.pat');
+ const sku = card.getAttribute('href').replace('/sample/', '');
+ dwmOpen('Sample', {
+ sku,
+ title: pat ? pat.textContent.trim() : '',
+ image_url: img ? img.src : ''
+ });
+ }
+ }, true);
+// DW STANDARD: hero pulls from products.json[0].image_url so dw-collections-viewer
+// drag-drop / hero-pin updates the live front page on next deploy.
+// Always upgrades to the largest Shopify variant (strips _NxN size suffix) so
+// the full-page hero stays sharp.
+// DW STANDARD: hero rotates through top-20 NEWEST products on each visit
+// (novasuede.com pattern). Fetches /api/products?limit=100, sorts by created_at desc,
+// takes the freshest 20, picks one per visit via localStorage counter.
+// Falls back to static /hero-bg.jpg on any error.
+(async function rotateHero() {
+ try {
+ const r = await fetch('/api/products?limit=100');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || [])
+ .filter(p => p && p.image_url && /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(p.image_url))
+ .sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')))
+ .slice(0, 20);
+ if (!items.length) return;
+ let visits = 0;
+ try {
+ visits = parseInt(localStorage.getItem('hero_visits') || '0', 10) || 0;
+ localStorage.setItem('hero_visits', String(visits + 1));
+ } catch(e) {}
+ const pick = items[visits % items.length];
+ // Strip _NxN size suffix to get the largest CDN variant
+ const url = pick.image_url.replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ const bg = document.getElementById('heroBg') || document.getElementById('cinemaBg');
+ if (bg) bg.style.backgroundImage = "url('" + url + "')";
+ } catch (e) {}
+})();
+
+
+// DW STANDARD sort-select wire-up (added by fleet patcher)
+(function(){
+ const sortSel = document.getElementById('sortSelect');
+ if (!sortSel) return;
+ const KEY = location.hostname.replace(/\./g, '_') + '_sort';
+ const saved = localStorage.getItem(KEY);
+ if (saved) { try { sortSel.value = saved; } catch(e){} }
+ if (typeof state !== 'undefined') state.sort = sortSel.value;
+ sortSel.addEventListener('change', e => {
+ const v = e.target.value;
+ try { localStorage.setItem(KEY, v); } catch(e){}
+ if (typeof state !== 'undefined') state.sort = v;
+ if (typeof resetGrid === 'function') resetGrid();
+ else location.reload();
+ });
+})();
+
+// graphics-loop pass 11: hydrate Ideas rails from /api/products
+(async function hydrateIdeas() {
+ try {
+ const r = await fetch('/api/products?limit=80');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || []).filter(p => p && p.image_url);
+ if (!items.length) return;
+ const sorted = [...items].sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')));
+ const newest = sorted.slice(0, 12);
+ // Curated = deterministic offset slice (every 5th item starting at idx 3) for variety
+ const curated = items.filter((_, i) => i % 5 === 3).slice(0, 12);
+ function card(p) {
+ const url = (p.image_url || '').replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ return '<a class="rail-card" href="/sample/' + encodeURIComponent(p.handle || p.sku) + '">' +
+ '<img src="' + url + '" alt="' + (p.title || '').replace(/"/g, '"') + '" loading="lazy">' +
+ '<div class="rc-title">' + ((p.title || '').split('|')[0] || '').trim() + '</div>' +
+ '<div class="rc-meta">' + (p.vendor || '') + '</div>' +
+ '</a>';
+ }
+ const r1 = document.getElementById('railNewest');
+ const r2 = document.getElementById('railCurated');
+ if (r1) r1.innerHTML = newest.map(card).join('');
+ if (r2) r2.innerHTML = curated.map(card).join('');
+ } catch(e) {}
+})();
+</script>
+
+</body>
+</html>
diff --git a/public/index.html.bak.gtagev.1778426585977 b/public/index.html.bak.gtagev.1778426585977
new file mode 100644
index 0000000..9dbd536
--- /dev/null
+++ b/public/index.html.bak.gtagev.1778426585977
@@ -0,0 +1,1034 @@
+<!doctype html>
+<html lang="en" data-hero-mode="huge">
+<head>
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
+
+<meta charset="utf-8">
+<!-- Google tag (gtag.js) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=G-CNTJNLTEW8"></script>
+<script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+ gtag('config', 'G-CNTJNLTEW8');
+</script>
+
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>silkwallpaper — curated wallcoverings, real samples ship free.</title>
+<meta name="description" content="silkwallpaper — a curated wallcovering atelier. Free memo samples. Trade service.">
+<meta name="theme-color" content="#FFF">
+<link rel="canonical" href="https://silkwallpaper.com/">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
+<link rel="preload" as="image" href="/hero-bg.jpg" fetchpriority="high">
+<script type="application/ld+json">
+{
+ "@context": "https://schema.org",
+ "@type": "Organization",
+ "name": "silkwallpaper",
+ "url": "https://silkwallpaper.com/",
+ "email": "info@silkwallpaper.com"
+}
+</script>
+<script src="/zd-loader.js" defer></script>
+<style>
+/* silkwallpaper · Saint Laurent template — pure monochrome, sharp uppercase, zero ornament */
+:root {
+ --bg: #000;
+ --paper: #FFF;
+ --muted: #888;
+ --line: rgba(255,255,255,0.20);
+ --accent: #FFF;
+ --bg-soft: #0a0a0a;
+ --cols: 6;
+}
+html[data-theme="light"] {
+ --bg: #FFF;
+ --paper: #000;
+ --muted: #666;
+ --line: rgba(0,0,0,0.20);
+ --accent: #000;
+ --bg-soft: #F5F5F5;
+}
+* { margin:0; padding:0; box-sizing:border-box }
+html { scroll-behavior:smooth }
+body { font-family:'Inter', 'Helvetica Neue', sans-serif; color:var(--paper); -webkit-font-smoothing:antialiased; background:var(--bg); min-height:100vh; overflow-x:hidden }
+/* DW-FASHION-TEMPLATE-OVERRIDE */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', Georgia, serif !important; font-weight:900 !important; text-transform:uppercase !important;letter-spacing:0.04em !important; }
+/* Saint Laurent: heavy uppercase headlines, no italic serif accent */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', 'Helvetica Neue', sans-serif !important; font-weight:900 !important; font-style:normal !important; letter-spacing:0.04em !important; text-transform:uppercase !important }
+
+/* ===== Auto-hide header ===== */
+header { position:fixed; top:0; left:0; right:0; display:flex; justify-content:space-between; align-items:center; padding:22px 32px; z-index:100; background:rgba(0,0,0,0.55); backdrop-filter:blur(10px); border-bottom:1px solid var(--line); transition:transform .3s }
+.h-link { display:flex; align-items:center; gap:10px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); text-decoration:none; cursor:pointer; background:transparent; border:0 }
+.h-link:hover { opacity:0.7 }
+.h-icon { width:18px; height:18px; stroke:currentColor; fill:none; stroke-width:1.5 }
+
+/* ===== Cinema hero (R2: huge full-bleed photo + top-bar logo + below-photo wordmark) ===== */
+.cinema { position:relative; min-height:clamp(560px, 90vh, 1080px); width:100%; overflow:hidden }
+.cinema-bg { position:absolute; inset:0; background-size:cover; background-position:center; background-image:url('/hero-bg.jpg') }
+.cinema-bg::after { content:""; position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,0,0,0.20) 0%, rgba(0,0,0,0) 35%, rgba(0,0,0,0) 65%, rgba(0,0,0,0.55) 100%) }
+/* Hero-mode hooks (R4): both modes share the same min-height — CLS-free */
+[data-hero-mode="four-square"] .cinema-bg { display:none }
+[data-hero-mode="four-square"] .cinema-grid { display:grid; grid-template-columns:1fr 1fr; grid-template-rows:1fr 1fr; position:absolute; inset:0 }
+[data-hero-mode="four-square"] .cinema-grid .cell { background-size:cover; background-position:center; border:1px solid rgba(0,0,0,0.4) }
+.cinema-grid { display:none }
+.cinema-logo { position:absolute; top:22px; left:32px; font-family: ui-serif, Georgia, serif; font-size:18px; font-weight:500; letter-spacing:0.02em; color:#fff; text-decoration:none; z-index:6; text-shadow:0 1px 4px rgba(0,0,0,0.5) }
+/* Below-photo wordmark (R2): brand display lives below the hero, not painted on it */
+.hero-wordmark-block { position:relative; padding:38px 32px 24px; text-align:center; background:var(--bg) }
+.hero-wordmark { font-family: ui-serif, Georgia, 'Cormorant Garamond', serif; font-weight:400; font-size:clamp(48px, 9vw, 128px); letter-spacing:-0.01em; line-height:1; color:var(--paper); margin:0; text-transform:none }
+.hero-wordmark-sub { display:block; font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400; font-size:clamp(13px, 1.4vw, 17px); letter-spacing:0.04em; text-transform:none; margin-top:14px; color:var(--muted) }
+@media (max-width:760px) { .hero-wordmark { font-size:clamp(32px, 12vw, 64px) } }
+.corner-mark { position:absolute; top:24px; left:32px; font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:#fff; text-shadow:0 1px 3px rgba(0,0,0,0.55), 0 0 1px rgba(0,0,0,0.4); z-index:5 }
+.corner-mark::before { content:"◆ "; margin-right:6px }
+/* center-mark deprecated 2026-05-10 per R2 — hidden */
+.center-mark { display:none !important; position:absolute; top:clamp(80px,12vh,140px); left:50%; transform:translateX(-50%); font-size:clamp(28px,4vw,56px); font-weight:500; letter-spacing:0.18em; text-transform:uppercase; text-align:center; color:#fff; text-shadow:0 2px 8px rgba(0,0,0,0.65), 0 0 1px rgba(0,0,0,0.5); z-index:5 }
+.center-mark .tm { font-size:14px; vertical-align:super; opacity:0.7; margin-left:0.2em }
+.center-mark .sub { display:block; font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400; font-size:0.4em; letter-spacing:0.04em; text-transform:none; margin-top:14px; opacity:0.85 }
+.meta-line { position:absolute; bottom:32px; left:32px; font-size:10px; letter-spacing:0.28em; text-transform:uppercase; font-weight:700; color:#fff; z-index:5; opacity:0.85 }
+.meta-line .num { font-size:12px; display:block; margin-top:4px; letter-spacing:0.05em }
+.enter { position:absolute; bottom:32px; right:32px; display:flex; align-items:center; gap:14px; font-size:11px; letter-spacing:0.36em; text-transform:uppercase; font-weight:700; color:#fff; text-decoration:none; padding-bottom:6px; border-bottom:1px solid #fff; transition:gap 0.25s; z-index:5 }
+.enter:hover { gap:22px }
+.enter svg { width:24px; height:12px }
+
+/* ===== Section ===== */
+.section { padding:96px 32px }
+.section-header { display:flex; justify-content:space-between; align-items:flex-end; flex-wrap:wrap; gap:24px; margin-bottom:32px; padding-bottom:24px; border-bottom:1px solid var(--line) }
+.section-title { font-size:clamp(36px,5vw,64px); font-weight:300; letter-spacing:-0.02em; line-height:1 }
+.section-title .accent { color: var(--accent); font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400 }
+.section-eyebrow { font-size:10px; letter-spacing:0.4em; text-transform:uppercase; font-weight:700; color:var(--muted); margin-bottom:16px }
+.section-meta { font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:600; color:var(--muted); text-align:right; line-height:1.6 }
+
+/* ===== Filter bar ===== */
+.filters { display:flex; gap:8px; align-items:center; margin-bottom:18px; flex-wrap:wrap }
+.chip { padding:8px 16px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); background:transparent; border:1px solid var(--line); cursor:pointer; transition:all 0.2s; font-family:inherit }
+.chip:hover { border-color:var(--paper) }
+.chip.active { background:var(--paper); color:var(--bg); border-color:var(--paper) }
+.search { flex:0 1 280px; margin-left:auto; display:flex; align-items:center; gap:10px; border-bottom:1px solid var(--line); padding:6px 0 }
+.search input { flex:1; background:transparent; border:0; color:var(--paper); font-family:inherit; font-size:13px; outline:none; letter-spacing:0.04em }
+.search input::placeholder { color:var(--muted) }
+.search svg { width:16px; height:16px; stroke:var(--muted); fill:none; stroke-width:1.5 }
+
+/* ===== Grid-density slider ===== */
+.density { display:flex; align-items:center; gap:14px; padding:6px 0; margin-bottom:24px }
+.density label { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--muted) }
+.density input[type=range] { flex:1; max-width:240px; -webkit-appearance:none; appearance:none; height:1px; background:var(--line); outline:none }
+.density input[type=range]::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50% }
+.density input[type=range]::-moz-range-thumb { width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50%; border:0 }
+.density .ct { font-size:11px; color:var(--muted); letter-spacing:0.18em; text-transform:uppercase; font-weight:600; min-width:80px }
+
+/* ===== Stats line ===== */
+.stat-line { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:600; color:var(--muted); margin-bottom:24px }
+
+/* ===== Product grid (novasuede pattern: flush grid + slide-up overlay) ===== */
+.grid { display:grid; grid-template-columns:repeat(var(--cols), 1fr); gap:0 }
+.card { position:relative; aspect-ratio:1/1.15; cursor:pointer; overflow:hidden; border:1px solid var(--bg); transition:transform 0.4s cubic-bezier(0.2,0.8,0.2,1); background:var(--bg-soft) }
+.card:hover { transform:scale(1.04); z-index:5 }
+.card img { width:100%; height:100%; object-fit:cover; display:block; transition:transform 0.4s ease }
+.card .overlay { position:absolute; left:0; right:0; bottom:0; padding:20px 16px 14px; background:linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.85) 100%); transform:translateY(48%); opacity:0.85; transition:transform 0.3s ease, opacity 0.3s ease }
+.card:hover .overlay { transform:translateY(0); opacity:1 }
+.card .pat { font-size:13px; font-weight:600; letter-spacing:0.02em; color:#fff; line-height:1.25; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }
+.card .ven { font-size:9px; letter-spacing:0.18em; text-transform:uppercase; color:#fff; opacity:0.7; font-weight:500; margin-top:4px }
+.card .actions { margin-top:10px; display:flex; gap:6px }
+.card .sample-btn { flex:1; padding:7px 10px; background:#fff; color:#000; font-family:inherit; font-size:10px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; border:0; cursor:pointer; text-align:center; transition:all 0.2s }
+.card .sample-btn:hover { background:var(--accent); color:#fff }
+
+@media (max-width:1024px) { .grid { grid-template-columns:repeat(min(var(--cols), 4), 1fr) } }
+@media (max-width:720px) { .grid { grid-template-columns:repeat(2, 1fr) } .density { display:none } }
+
+.sentinel { height:1px }
+.loading { text-align:center; color:var(--muted); padding:32px; font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700 }
+
+/* ===== Footer ===== */
+footer { padding:64px 32px 32px; border-top:1px solid var(--line); margin-top:32px }
+.footer-grid { max-width:1400px; margin:0 auto; display:grid; grid-template-columns:2fr 1fr 1fr 1fr; gap:48px; margin-bottom:48px }
+.footer-brand { font-size:24px; font-weight:300; letter-spacing:0.32em; text-transform:uppercase; margin-bottom:12px }
+.footer-text { font-size:13px; line-height:1.6; color:var(--muted); max-width:380px }
+.footer-col h4 { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--paper); margin-bottom:18px }
+.footer-col a { display:block; font-size:13px; color:var(--muted); text-decoration:none; margin-bottom:8px; transition:color 0.2s; cursor:pointer; background:transparent; border:0; font-family:inherit; padding:0; text-align:left }
+.footer-col a:hover { color:var(--accent) }
+.footer-bottom { max-width:1400px; margin:0 auto; padding-top:24px; border-top:1px solid var(--line); display:flex; justify-content:space-between; flex-wrap:wrap; gap:16px; font-size:11px; letter-spacing:0.18em; text-transform:uppercase; color:var(--muted) }
+
+@media (max-width:720px) {
+ .corner-mark, .meta-line, .enter { font-size:10px }
+ .center-mark { font-size:24px; letter-spacing:0.12em; top:clamp(60px,9vh,90px) }
+ .section, footer { padding-left:20px; padding-right:20px }
+ .footer-grid { grid-template-columns:1fr; gap:32px }
+ .search { margin-left:0; flex:1 1 100% }
+}
+
+.theme-toggle { background:transparent; border:1px solid var(--line); width:32px; height:32px; cursor:pointer; color:var(--paper); display:inline-flex; align-items:center; justify-content:center; font-size:14px; line-height:1; transition:all .15s; padding:0 }
+.theme-toggle:hover { border-color:var(--accent); color:var(--accent) }
+
+/* ===== WCAG 2.4.7 — keyboard focus indicators ===== */
+/* Strictly :focus-visible so mouse clicks do NOT show the ring */
+a:focus-visible,
+button:focus-visible,
+input:focus-visible,
+select:focus-visible,
+textarea:focus-visible,
+[tabindex]:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+</style>
+<script>
+(function(){ try { var t = localStorage.getItem('silk_theme') || 'dark'; document.documentElement.dataset.theme = t; } catch(e){} })();
+</script>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600;700&display=swap">
+
+<meta property="og:type" content="website">
+<meta property="og:url" content="https://silkwallpaper.com/">
+<meta property="og:title" content="silkwallpaper — curated wallcoverings, real samples ship free.">
+<meta property="og:description" content="silkwallpaper — a curated wallcovering atelier. Free memo samples. Trade service.">
+<meta property="og:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<meta property="og:site_name" content="silkwallpaper">
+<meta property="article:publisher" content="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings">
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:title" content="silkwallpaper — curated wallcoverings, real samples ship free.">
+<meta name="twitter:description" content="silkwallpaper — a curated wallcovering atelier. Free memo samples. Trade service.">
+<meta name="twitter:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<!-- graphics-loop pass 13: footer + fb -->
+</head>
+<body>
+<style id="ns-header-modal-styles">
+ .ns-header { position: sticky; top: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; padding: 18px 36px; background: rgba(10,10,10,0.92) !important; backdrop-filter: blur(8px); border-bottom: 1px solid rgba(245,241,232,0.06); }
+ .ns-brand { display: inline-flex; align-items: center; gap: 14px; text-decoration: none; }
+ .ns-dot { width: 28px; height: 28px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, #a98b5b 0%, #5d4c32 60%, #332a1b 100%); box-shadow: 0 0 0 1px rgba(245,241,232,0.10), 0 2px 12px rgba(180,90,60,0.30); flex-shrink: 0; }
+ .ns-name { font-family: -apple-system, "SF Pro Display", "Helvetica Neue", system-ui, sans-serif; font-size: 18px !important; font-weight: 300 !important; letter-spacing: 0.02em !important; color: #f5f1e8 !important; text-transform: none !important; margin: 0 !important; }
+ .ns-name sup { font-size: 0.55em; font-weight: 300; margin-left: 1px; }
+ .ns-nav { display: flex; gap: 32px; align-items: center; list-style: none; padding: 0; margin: 0; }
+ .ns-nav a, .ns-nav button { font-family: -apple-system, "SF Pro Text", system-ui, sans-serif; font-size: 15px !important; font-weight: 400 !important; color: #f5f1e8 !important; text-decoration: none !important; background: none !important; border: 0 !important; cursor: pointer; padding: 0 !important; transition: color 200ms; letter-spacing: 0 !important; text-transform: none !important; }
+ .ns-nav a:hover, .ns-nav button:hover { color: #ffffff !important; }
+ @media (max-width: 820px) { .ns-header { padding: 14px 18px; } .ns-nav { gap: 16px; } .ns-nav a, .ns-nav button { font-size: 13px !important; } }
+ @media (max-width: 760px) {
+ .ns-header { flex-wrap: wrap; }
+ .ns-nav { display: none !important; flex-basis: 100%; flex-direction: column; align-items: flex-start; gap: 6px; padding: 14px 0 4px; }
+ .ns-nav.open { display: flex !important; }
+ .ns-nav a, .ns-nav button { padding: 10px 0 !important; border-bottom: 1px solid rgba(245,241,232,0.08); width: 100%; text-align: left; }
+ .ns-nav a:nth-child(n+5), .ns-nav button:nth-child(n+5) { display: block !important; }
+ .ns-header .mobile-toggle { display: flex !important; }
+ }
+ @media (min-width: 761px) {
+ .ns-header .mobile-toggle { display: none !important; }
+ }
+ .ns-modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.78); z-index: 200; align-items: center; justify-content: center; padding: 24px; backdrop-filter: blur(6px); }
+ .ns-modal.show { display: flex; }
+ .ns-modal-card { max-width: 460px; width: 100%; background: #141414 !important; border: 1px solid rgba(245,241,232,0.14); border-radius: 6px; padding: 28px 30px 30px; position: relative; }
+ .ns-modal-card .close-x { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; background: none; border: 1px solid rgba(245,241,232,0.18); color: rgba(245,241,232,0.6) !important; border-radius: 50%; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; }
+ .ns-modal-card .close-x:hover { color: #ffffff !important; border-color: #ffffff; }
+ .ns-modal-card h3 { font-family: -apple-system, "SF Pro Display", system-ui; font-weight: 300 !important; font-size: 22px !important; margin: 0 0 6px !important; color: #f5f1e8 !important; }
+ .ns-modal-card .sub { color: rgba(245,241,232,0.65) !important; font-size: 13px; margin: 0 0 22px; }
+ .ns-actions { display: flex; flex-direction: column; gap: 10px; }
+ .ns-action { display: flex; align-items: center; gap: 14px; padding: 14px 16px; background: rgba(245,241,232,0.04) !important; border: 1px solid rgba(245,241,232,0.12) !important; color: #f5f1e8 !important; text-decoration: none !important; cursor: pointer; transition: all 200ms; font: inherit; text-align: left; }
+ .ns-action:hover { background: rgba(245,241,232,0.08) !important; border-color: #d4b683 !important; }
+ .ns-action .ico { width: 32px; height: 32px; border-radius: 50%; background: rgba(212,182,131,0.12); display: flex; align-items: center; justify-content: center; font-size: 15px; flex-shrink: 0; }
+ .ns-action .txt { flex: 1; }
+ .ns-action .txt b { display: block; font-weight: 500; font-size: 14px; color: #f5f1e8 !important; }
+ .ns-action .txt span { font-size: 12px; color: rgba(245,241,232,0.55) !important; }
+
+ /* ─── FINAL CONTRAST PASS — kill remaining gold-solid + white-island defects ─── */
+ /* Replace all gold-fill accents with off-white (Gucci-aesthetic, no DW gold solids) */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active,
+ .drapery-btn, .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover,
+ .drapery-btn:hover, .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ /* Gold accents stay only as edge color (links, glyphs) — not fills */
+ a { color: #d4b683; }
+ a:hover { color: #f5d97a; }
+ /* Authorized-dealer footer line — small caps tracked, muted ink, no gold pill */
+ .authorized, .authorized-dealer p {
+ background: transparent !important;
+ color: rgba(245,241,232,0.5) !important;
+ font-size: 10px !important;
+ letter-spacing: 0.22em !important;
+ text-transform: uppercase !important;
+ padding: 0 !important;
+ border: none !important;
+ margin-top: 14px !important;
+ }
+ /* Kill any straggler white bg in info-card / sample-related cards */
+ .info-card, .info-card-link, .care-highlight, .feature-card, .spec-item {
+ background-color: rgba(245,241,232,0.04) !important;
+ border-color: rgba(245,241,232,0.12) !important;
+ }
+ /* Ensure modal content stays readable — Steve's earlier sweep made it #141414 with white text */
+ .modal-content { background: #141414 !important; color: #f5f1e8 !important; }
+ .modal-content * { color: #f5f1e8 !important; }
+ .modal-content input, .modal-content textarea, .modal-content select {
+ background: rgba(245,241,232,0.05) !important;
+ color: #f5f1e8 !important;
+ border-color: rgba(245,241,232,0.2) !important;
+ }
+ .modal-content label { color: rgba(245,241,232,0.6) !important; }
+ /* Force the wide-tracked NOVASUEDE wordmark to stay white */
+ .ns-wordmark, .ns-wordmark sup { color: #f5f1e8 !important; }
+
+ /* ─── Modal-internal contrast (post-QA defects) ─── */
+ /* Backdrop covers full viewport */
+ .modal { width: 100vw !important; height: 100vh !important; left: 0 !important; top: 0 !important; background: rgba(0, 0, 0, 0.78) !important; backdrop-filter: blur(6px); }
+ /* Chip — flip from white-on-white island to dark card */
+ .request-chip {
+ background: rgba(245, 241, 232, 0.06) !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.18) !important;
+ border-radius: 4px !important;
+ }
+ .request-chip * { color: #f5f1e8 !important; }
+ .request-chip .chip-swatch {
+ /* swatch dot ALWAYS displays the inline-style hex from JS — fight any sweep */
+ border: 1px solid rgba(0, 0, 0, 0.4) !important;
+ }
+ /* + Add to request — hairline white-border on transparent (no gold) */
+ .add-to-request-btn {
+ background: transparent !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.6) !important;
+ border-radius: 0 !important;
+ padding: 12px 22px !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 400 !important;
+ }
+ .add-to-request-btn:hover {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ /* Hex caption visible */
+ .preview-hex, #colorPreviewHex {
+ display: block !important;
+ color: rgba(245, 241, 232, 0.55) !important;
+ font-family: ui-monospace, "SF Mono", Menlo, monospace !important;
+ font-size: 11px !important;
+ margin-top: 4px !important;
+ letter-spacing: 0.04em !important;
+ text-transform: lowercase !important;
+ }
+ /* Color preview name — stays single, prominent */
+ #colorPreviewName {
+ color: #f5f1e8 !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 500 !important;
+ display: block !important;
+ margin-top: 12px !important;
+ }
+
+ /* ─── Mobile hamburger nav (replaces the overflow at <760px) ─── */
+ @media (max-width: 760px) {
+ header .nav-container { flex-wrap: wrap; }
+ header nav { display: none; flex-basis: 100%; flex-direction: column; gap: 6px; padding: 16px 0 6px; }
+ header nav.open { display: flex; }
+ header nav a { padding: 10px 0; border-bottom: 1px solid rgba(245,241,232,0.1); }
+ header .mobile-toggle {
+ display: flex !important;
+ flex-direction: column;
+ gap: 5px;
+ background: transparent;
+ border: 0;
+ cursor: pointer;
+ padding: 8px;
+ }
+ header .mobile-toggle span {
+ width: 22px; height: 1.5px;
+ background: #f5f1e8;
+ display: block;
+ }
+ }
+ @media (min-width: 761px) {
+ header .mobile-toggle { display: none !important; }
+ }
+
+ </style>
+<style id="ns-gold-kill">
+ /* novasuede: kill gold-solid fills, off-white instead. Edge color stays gold. */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active, .drapery-btn,
+ .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover, .drapery-btn:hover,
+ .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ </style>
+<header class="ns-header">
+ <a href="/" class="ns-brand" aria-label="Home">
+ <span class="ns-dot"></span>
+ <span class="ns-name">silkwallpaper</span>
+ </a>
+ <nav class="ns-nav" id="nsPrimaryNav" aria-label="Primary">
+ <a href="/">Home</a>
+ <a href="/about">About</a>
+ <button type="button" onclick="nsContactOpen()">Contact</button>
+ </nav>
+ <button type="button" class="mobile-toggle" aria-label="Toggle menu" aria-controls="nsPrimaryNav" aria-expanded="false" onclick="nsToggleNav(this)">
+ <span></span><span></span><span></span>
+ </button>
+ </header>
+
+<div class="ns-modal" id="nsContactModal" role="dialog" aria-modal="true" aria-labelledby="nsContactTitle" onclick="if(event.target===this)nsContactClose()">
+ <div class="ns-modal-card">
+ <button type="button" class="close-x" onclick="nsContactClose()" aria-label="Close">×</button>
+ <h3 id="nsContactTitle">How can we help?</h3>
+ <p class="sub">Trade-account replies within one business day.</p>
+ <div class="ns-actions">
+ <a class="ns-action" href="#contact" onclick="nsContactClose()"><span class="ico">📋</span><div class="txt"><b>Get a Quote</b><span>Project specs → priced estimate</span></div></a>
+ <a class="ns-action" href="#contact" onclick="nsContactClose();openModal();"><span class="ico">📦</span><div class="txt"><b>Order a Sample</b><span>Free swatch to your shop</span></div></a>
+ <a class="ns-action" href="mailto:info@silkwallpaper.com?subject=Atelier%20Couture%20inquiry"><span class="ico">✉️</span><div class="txt"><b>Email Us</b><span>info@silkwallpaper.com</span></div></a>
+ <a class="ns-action" href="tel:888-373-4564"><span class="ico">📞</span><div class="txt"><b>Call Us</b><span>(888) 373-4564 · M–F, 9–5 PT</span></div></a>
+ </div>
+ </div>
+ </div>
+<script>
+ function nsContactOpen() { var m = document.getElementById('nsContactModal'); if (m) m.classList.add('show'); }
+ function nsContactClose() { var m = document.getElementById('nsContactModal'); if (m) m.classList.remove('show'); }
+ function nsToggleNav(btn) {
+ var nav = document.getElementById('nsPrimaryNav');
+ if (!nav) return;
+ var open = nav.classList.toggle('open');
+ btn.setAttribute('aria-expanded', open ? 'true' : 'false');
+ }
+ document.addEventListener('keydown', function(e){ if (e.key === 'Escape') nsContactClose(); });
+</script>
+
+
+
+
+
+
+
+
+
+
+
+<header>
+ <button class="h-link" onclick="dwmOpen('Contact')" aria-label="Contact"><svg class="h-icon" viewBox="0 0 24 24"><path d="M3 7h18M3 12h18M3 17h18"/></svg><span>Contact</span></button>
+ <button class="theme-toggle" id="theme-toggle" aria-label="Theme toggle">☾</button>
+</header>
+
+<section class="cinema" id="hero">
+ <a class="cinema-logo" href="/" aria-label="silkwallpaper home">silkwallpaper</a>
+ <div class="cinema-bg" id="heroBg"></div>
+ <div class="cinema-grid" id="heroGrid" aria-hidden="true"></div>
+ <div class="corner-mark">Silken Sheen</div>
+ <div class="meta-line">Silk · Luxe · Sheen · Jacquard<span class="num" id="heroNum"></span></div>
+ <a class="enter" href="#shop">Enter <svg viewBox="0 0 24 12" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M0 6h22M16 1l6 5-6 5"/></svg></a>
+</section>
+<div class="hero-wordmark-block">
+ <h1 class="hero-wordmark">silkwallpaper</h1>
+ <span class="hero-wordmark-sub" id="heroWordmarkSub"></span>
+</div>
+
+
+<section class="section" id="ideas">
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">New this week</h2>
+ <div class="rail" id="railNewest"></div>
+ </div>
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">Curated picks</h2>
+ <div class="rail" id="railCurated"></div>
+ </div>
+</section>
+
+<section class="section" id="shop">
+ <div class="section-header">
+ <div>
+ <div class="section-eyebrow">The Collection</div>
+ <h2 class="section-title"><span class="accent" id="totalCount">—</span> Patterns.</h2>
+ </div>
+ <div class="section-meta">Silk · Luxe · Sheen · Jacquard</div>
+ </div>
+
+ <div class="filters" id="facets">
+ <button class="chip active" data-facet="all">All</button>
+ </div>
+
+ <div class="density">
+ <label>Sort</label>
+ <select id="sortSelect" aria-label="Sort products" style="background:transparent;color:inherit;border:1px solid var(--line,rgba(255,255,255,.2));font:inherit;font-size:11px;letter-spacing:.10em;text-transform:uppercase;padding:4px 8px;cursor:pointer;outline:none;margin-right:14px">
+ <option value="newest">Newest</option>
+ <option value="color">Color</option>
+ <option value="style">Style</option>
+ <option value="sku">SKU A→Z</option>
+ <option value="title">Title A→Z</option>
+ <option value="price-asc">Price ↑</option>
+ <option value="price-desc">Price ↓</option>
+ </select>
+ <label>Grid</label>
+ <input type="range" id="densitySlider" min="4" max="12" step="1" value="6" aria-label="Grid columns">
+ <span class="ct" id="densityLabel">6 cols</span>
+ <div class="search">
+ <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5.5-5.5"/></svg>
+ <input type="text" id="searchInput" placeholder="Search by pattern, color…" autocomplete="off">
+ </div>
+ </div>
+
+ <div class="stat-line" id="statLine">Loading…</div>
+ <div class="grid" id="grid"></div>
+ <div class="loading" id="loading">Loading more…</div>
+ <div class="sentinel" id="sentinel"></div>
+</section>
+
+<footer>
+ <div class="footer-grid">
+ <div>
+ <div class="footer-brand">silkwallpaper</div>
+ <p class="footer-text">silkwallpaper — real samples ship free.</p>
+ <p class="footer-text" style="margin-top:14px;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;font-weight:600;color:var(--paper);opacity:0.7"><a href="/about" style="color:inherit;text-decoration:none;border-bottom:1px solid var(--accent)">About →</a></p>
+ </div>
+ <div class="footer-col">
+ <h4>Aesthetic</h4>
+ <div id="footerFacets"></div>
+ </div>
+ <div class="footer-col">
+ <h4>Trade</h4>
+ <button onclick="dwmOpen('Inquiry')">Project Inquiry</button>
+ <button onclick="dwmOpen('Sample')">Request Sample</button>
+ <button onclick="dwmOpen('Contact')">Contact</button>
+ </div>
+
+ <div class="footer-col">
+ <h4>Social</h4>
+ <a class="fb-link" href="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings" target="_blank" rel="noopener">Facebook</a>
+ </div>
+ </div>
+ <div class="footer-bottom">
+ <span>silkwallpaper.com · silkwallpaper</span>
+ <span><a href="mailto:info@silkwallpaper.com" style="color:var(--paper);text-decoration:none;border-bottom:1px solid var(--accent)">info@silkwallpaper.com</a></span>
+ <span id="footerStat"></span>
+ </div>
+</footer>
+
+<script>
+// mini-constellation — 43 niche-stars, current site highlighted in gold
+(function(){
+ const SLUG = "silkwallpaper";
+ const NICHES = [
+ ['silkwallpaper','Silk','mat'],['silkwallcoverings','Silk W/C','mat'],['linenwallpaper','Linen','mat'],
+ ['jutewallpaper','Jute','mat'],['raffiawallcoverings','Raffia W/C','mat'],['raffiawalls','Raffia Walls','mat'],
+ ['corkwallcovering','Cork','mat'],['fabricwallpaper','Fabric','mat'],['textilewallpaper','Textile','mat'],
+ ['metallicwallpaper','Metallic','mat'],['silverleafwallpaper','Silver Leaf','mat'],['vinylwallpaper','Vinyl','mat'],
+ ['micawallpaper','Mica','mat'],['madagascarwallpaper','Madagascar','mat'],['mylarwallpaper','Mylar','mat'],
+ ['suedewallpaper','Suede','mat'],
+ ['1800swallpaper','1800s','dec'],['1890swallpaper','1890s','dec'],['1900swallpaper','1900s','dec'],
+ ['1920swallpaper','1920s','dec'],['1930swallpaper','1930s','dec'],['1940swallpaper','1940s','dec'],
+ ['1950swallpaper','1950s','dec'],['1960swallpaper','1960s','dec'],['1970swallpaper','1970s','dec'],
+ ['1980swallpaper','1980s','dec'],['retrowalls','Retro','dec'],['wallpapersback','W. Back','dec'],
+ ['agedwallpaper','Aged','cra'],['handcraftedwallpaper','Handcrafted','cra'],['museumwallpaper','Museum','cra'],
+ ['restorationwallpaper','Restoration','cra'],['pastelwallpaper','Pastel','cra'],['glitterwalls','Glitter','cra'],
+ ['greenwallcoverings','Green','cra'],['naturalwallcoverings','Natural','cra'],['saloonwallpaper','Saloon','cra'],
+ ['contractwallpaper','Contract','use'],['hotelwallcoverings','Hotel','use'],['hospitalitywallpaper','Hospitality','use'],
+ ['healthcarewallpaper','Healthcare','use'],['restaurantwallpaper','Restaurant','use'],['architecturalwallcoverings','Architectural','use']
+ ];
+ const COLOR = { mat:'#c9b687', dec:'#b86a4a', cra:'#6b8e6f', use:'#4a6b8e' };
+ const W = 1080, H = 200, BANDS = ['mat','dec','cra','use'];
+ const groups = { mat:[], dec:[], cra:[], use:[] };
+ for (const n of NICHES) groups[n[2]].push(n);
+ const svg = document.getElementById('miniConst');
+ if (!svg) return;
+ const bandW = W / BANDS.length;
+ let nodes = '';
+ BANDS.forEach((band, bi) => {
+ const list = groups[band];
+ const cx = bi * bandW + bandW / 2;
+ list.forEach((n, i) => {
+ const t = list.length === 1 ? 0.5 : i / (list.length - 1);
+ // deterministic jitter per slug
+ let h = 0; for (let k = 0; k < n[0].length; k++) h = (h * 31 + n[0].charCodeAt(k)) | 0;
+ const jx = ((h & 0xff) / 255 - 0.5) * (bandW * 0.5);
+ const jy = (((h >> 8) & 0xff) / 255 - 0.5) * 6; // tightened from 14 → 6 to prevent same-band collisions (16 mat nodes / 144px = 9px spacing)
+ const x = cx + jx, y = 28 + t * (H - 56) + jy;
+ const isCurrent = n[0] === SLUG;
+ const r = isCurrent ? 8 : 4;
+ const op = isCurrent ? 1 : 0.55;
+ nodes += `<g class="mc-node" data-slug="${n[0]}" data-label="${n[1]}" style="cursor:pointer">
+ <circle cx="${x}" cy="${y}" r="${r}" fill="url(#dna-${band})" fill-opacity="${op}" stroke="${COLOR[band]}" stroke-opacity="${isCurrent?1:0.7}" stroke-width="${isCurrent?2:1}">
+ <title>${n[1]} — ${n[0]}.com</title>
+ </circle>
+ ${isCurrent ? `<circle cx="${x}" cy="${y}" r="${r+5}" fill="none" stroke="${COLOR[band]}" stroke-opacity="0.4" stroke-width="1"><animate attributeName="r" values="${r+5};${r+12};${r+5}" dur="2.4s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" repeatCount="indefinite"/></circle>` : ''}
+ </g>`;
+ });
+ });
+ // append nodes after defs
+ svg.insertAdjacentHTML('beforeend', nodes);
+ svg.querySelectorAll('.mc-node').forEach(n => {
+ n.addEventListener('mouseenter', () => n.querySelector('circle').setAttribute('fill-opacity', '1'));
+ n.addEventListener('mouseleave', e => {
+ const isCurrent = n.dataset.slug === SLUG;
+ n.querySelector('circle').setAttribute('fill-opacity', isCurrent ? '1' : '0.55');
+ });
+ n.addEventListener('click', () => window.open('https://' + n.dataset.slug + '.com', '_blank'));
+ });
+})();
+</script>
+
+<script>
+const state = { q:'', facet:'all', page:1, pages:1, total:0, loading:false, exhausted:false };
+const LABELS = {"all":"All"};
+
+function escAttr(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
+// Image-URL allowlist: only DW Shopify CDN + designerwallcoverings.com (defends against XSS via crafted products.json)
+function safeImg(u) { return /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(u || '') ? u : '/hero-bg.jpg'; }
+function cardHTML(p) {
+ const eager = state.page === 1;
+ return '<img loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+ + '<div class="overlay">'
+ + '<div class="pat">' + escAttr(p.pattern_name || p.title) + '</div>'
+ + '<div class="ven">' + escAttr((p.vendor || '').replace(/-/g, ' ')) + '</div>'
+ + '<div class="actions">'
+ + '<button class="sample-btn" onclick="event.stopPropagation();dwmOpen(\'Sample\',' + JSON.stringify({sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}).replace(/"/g,'"') + ')">Sample</button>'
+ + '</div></div>';
+}
+
+async function loadFacets() {
+ let f;
+ try {
+ const r = await fetch('/api/facets');
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ f = await r.json();
+ } catch (e) { console.error('loadFacets failed:', e); return; }
+ const el = document.getElementById('facets');
+ for (const [k, v] of Object.entries(f.aesthetics).sort((a,b) => b[1] - a[1])) {
+ const b = document.createElement('button');
+ b.className = 'chip'; b.dataset.facet = k;
+ b.innerHTML = (LABELS[k] || k) + ' <span style="opacity:.55;font-weight:500;margin-left:4px">' + v + '</span>';
+ el.appendChild(b);
+ }
+ el.addEventListener('click', e => {
+ if (e.target.tagName !== 'BUTTON') return;
+ document.querySelectorAll('#facets button').forEach(b => b.classList.remove('active'));
+ e.target.classList.add('active');
+ state.facet = e.target.dataset.facet;
+ resetGrid();
+ });
+ document.getElementById('totalCount').textContent = f.total;
+ document.getElementById('footerStat').textContent = f.total + ' patterns · live archive';
+ // Footer aesthetic links
+ const footerEl = document.getElementById('footerFacets');
+ for (const [k] of Object.entries(f.aesthetics)) {
+ const b = document.createElement('button');
+ b.textContent = LABELS[k] || k;
+ b.onclick = () => { state.facet = k; resetGrid(); document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.facet === k)); document.getElementById('shop').scrollIntoView({behavior:'smooth'}); };
+ footerEl.appendChild(b);
+ }
+}
+
+function resetGrid() {
+ state.page = 1; state.exhausted = false;
+ document.getElementById('grid').innerHTML = '';
+ document.getElementById('loading').textContent = 'Loading…';
+ loadGridPage();
+}
+
+async function loadGridPage() {
+ if (state.loading || state.exhausted) return;
+ state.loading = true;
+ const params = new URLSearchParams({ page:state.page, limit:24 });
+ if (state.q) params.set('q', state.q);
+ if (state.facet !== 'all') params.set('aesthetic', state.facet);
+ let data;
+ try {
+ const r = await fetch('/api/products?' + params);
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ data = await r.json();
+ } catch (e) {
+ console.error('loadGridPage failed:', e);
+ document.getElementById('loading').textContent = '— offline — refresh to retry —';
+ state.loading = false;
+ return;
+ }
+ state.total = data.total; state.pages = data.pages;
+
+ if (state.page === 1) {
+ document.getElementById('statLine').textContent = data.total + ' patterns · ' + (state.facet === 'all' ? 'all aesthetics' : (LABELS[state.facet] || state.facet)) + (state.q ? ' · matching "' + state.q + '"' : '');
+ }
+ const grid = document.getElementById('grid');
+ for (const p of data.items) {
+ const a = document.createElement('a');
+ a.className = 'card'; a.href = '#'; a.onclick = (e) => { e.preventDefault(); dwmOpen('Sample',{sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}); };
+ a.innerHTML = cardHTML(p);
+ grid.appendChild(a);
+ }
+ if (state.page >= state.pages || data.items.length === 0) {
+ state.exhausted = true;
+ document.getElementById('loading').textContent = state.total > 0 ? '— end of archive · ' + state.total + ' patterns —' : '';
+ } else state.page++;
+ state.loading = false;
+}
+
+const io = new IntersectionObserver(es => { for (const e of es) if (e.isIntersecting) loadGridPage(); }, { rootMargin:'600px 0px' });
+io.observe(document.getElementById('sentinel'));
+
+document.getElementById('searchInput').addEventListener('input', e => {
+ state.q = e.target.value.trim();
+ clearTimeout(window._t);
+ window._t = setTimeout(resetGrid, 220);
+});
+
+// Density slider
+const slider = document.getElementById('densitySlider');
+const dlabel = document.getElementById('densityLabel');
+function setDensity(n) {
+ document.documentElement.style.setProperty('--cols', n);
+ dlabel.textContent = n + ' cols';
+ try { localStorage.setItem('silk_theme_density', n); } catch(e){}
+}
+slider.addEventListener('input', e => setDensity(parseInt(e.target.value)));
+const savedDensity = parseInt(localStorage.getItem('silk_theme_density') || '6');
+if (savedDensity >= 4 && savedDensity <= 12) { slider.value = savedDensity; setDensity(savedDensity); }
+
+// Theme toggle
+const tb = document.getElementById('theme-toggle');
+function setTheme(t){ document.documentElement.dataset.theme = t; try { localStorage.setItem('silk_theme', t); } catch(e){} tb.textContent = t === 'dark' ? '☾' : '☀'; }
+setTheme(document.documentElement.dataset.theme || 'dark');
+tb.addEventListener('click', () => setTheme(document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'));
+
+loadFacets();
+loadGridPage();
+</script>
+<!-- ============================================================
+ DW UNIVERSAL CONTACT MODULE — fashion-house UX
+ Inject before </body> in every DW-family sister site.
+ Self-contained: CSS + 4 modals + JS submission handlers.
+ Uses host site's CSS vars (--bg, --fg, --gold, --rule).
+ ============================================================ -->
+<style>
+ /* === MODAL BASE === */
+ .dwm{position:fixed;inset:0;background:rgba(0,0,0,0.78);display:none;align-items:center;justify-content:center;z-index:9999;padding:24px;overflow-y:auto}
+ .dwm.open{display:flex}
+ .dwm-box{background:var(--bg);border:1px solid var(--rule);max-width:520px;width:100%;padding:36px 32px;position:relative;max-height:90vh;overflow-y:auto;animation:dwm-in 0.25s ease}
+ @keyframes dwm-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+ .dwm-box h3{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-weight:400;font-size:32px;line-height:1.05;margin-bottom:6px;color:var(--fg);letter-spacing:-0.01em}
+ .dwm-box .sub{color:var(--muted);font-size:12px;letter-spacing:0.16em;text-transform:uppercase;margin-bottom:24px;font-weight:500}
+ .dwm-close{position:absolute;top:18px;right:18px;background:transparent;border:0;color:var(--muted);font-size:24px;cursor:pointer;width:32px;height:32px;line-height:1;padding:0}
+ .dwm-close:hover{color:var(--gold)}
+ .dwm-preview{display:flex;gap:14px;align-items:center;padding:12px;background:var(--bg-soft);margin-bottom:18px}
+ .dwm-preview img{width:64px;height:64px;object-fit:cover}
+ .dwm-preview .pn{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-size:15px;line-height:1.2;color:var(--fg)}
+ .dwm-preview .pc{font-size:10px;letter-spacing:0.14em;text-transform:uppercase;color:var(--muted);margin-top:4px}
+ .dwm-field{margin-bottom:14px}
+ .dwm-field label{display:block;font-size:10px;letter-spacing:0.18em;text-transform:uppercase;color:var(--muted);margin-bottom:6px;font-weight:500}
+ .dwm-field input,.dwm-field textarea,.dwm-field select{width:100%;border:0;border-bottom:1px solid var(--rule);background:transparent;padding:8px 4px;font-size:14px;font-family:inherit;color:var(--fg);outline:none;transition:border-color 0.2s}
+ .dwm-field input:focus,.dwm-field textarea:focus{border-bottom-color:var(--gold)}
+ .dwm-field textarea{min-height:60px;resize:vertical}
+ .dwm-submit{background:transparent;color:var(--muted, #c9b89e);border:1px solid var(--muted, #c9b89e);padding:14px 28px;font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:700;cursor:pointer;width:100%;margin-top:8px;transition:background 0.15s, color 0.15s}
+ .dwm-submit:hover{background:var(--muted, #c9b89e);color:var(--bg, #000)}
+
+ .dwm-submit:disabled{opacity:0.5;cursor:default}
+ .dwm-status{margin-top:14px;font-size:11px;letter-spacing:0.16em;text-transform:uppercase;text-align:center;display:none}
+ .dwm-status.ok{color:var(--sage);display:block}
+ .dwm-status.err{color:#e08070;display:block}
+ /* contact-options */
+ .dwm-options{display:flex;flex-direction:column;gap:10px}
+ .dwm-option{display:flex;align-items:center;gap:14px;padding:18px 20px;background:transparent;border:1px solid var(--rule);color:var(--fg);font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:600;cursor:pointer;text-decoration:none;transition:all 0.2s;text-align:left;width:100%}
+ .dwm-option:hover{border-color:var(--gold);color:var(--gold)}
+ .dwm-option svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:1.5;flex-shrink:0}
+ .dwm-option .lbl{flex:1;display:block}
+ .dwm-option .val{display:block;font-size:10px;letter-spacing:0.14em;text-transform:none;font-weight:500;opacity:0.65;margin-top:4px}
+
+/* graphics-loop pass 5: serif hero typography + nav legibility */
+.cinema h1, h1.headline { font-family: 'Cormorant Garamond', Georgia, serif; font-weight: 500; letter-spacing: -0.01em }
+.h-link { font-size: 13px; letter-spacing: 0.06em }
+
+/* graphics-loop pass 11: Ideas rails */
+#ideas { padding: 56px 24px 24px; max-width:1600px; margin:0 auto }
+#ideas .rail-block { margin-bottom:48px }
+#ideas .rail-eyebrow { font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:var(--muted, #888); margin-bottom:8px }
+#ideas .rail-title { font-size:clamp(22px,2.6vw,32px); font-weight:500; letter-spacing:-0.01em; margin:0 0 18px; color:var(--ink,#fff) }
+#ideas .rail { display:flex; gap:14px; overflow-x:auto; scroll-snap-type:x mandatory; padding-bottom:8px; scrollbar-width:none }
+#ideas .rail::-webkit-scrollbar { display:none }
+#ideas .rail-card { flex:0 0 240px; scroll-snap-align:start; cursor:pointer; transition:transform 0.2s }
+#ideas .rail-card:hover { transform:translateY(-3px) }
+#ideas .rail-card img { width:100%; aspect-ratio:1; object-fit:cover; display:block; background:var(--card,#222) }
+#ideas .rail-card .rc-title { font-size:13px; font-weight:500; color:var(--paper,#fff); padding:8px 0 2px; line-height:1.3 }
+#ideas .rail-card .rc-meta { font-size:11px; color:var(--muted,#888); letter-spacing:0.06em }
+@media (max-width:720px) { #ideas .rail-card { flex:0 0 160px } }
+</style>
+
+<!-- Contact options modal -->
+<div class="dwm" id="dwmContact" onclick="if(event.target===this)dwmClose('Contact')">
+ <div class="dwm-box" style="max-width:440px">
+ <button class="dwm-close" onclick="dwmClose('Contact')" aria-label="Close">×</button>
+ <h3>Contact</h3>
+ <p class="sub" id="dwmContactSub">Three ways to reach us</p>
+ <div class="dwm-options">
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Inquiry')">
+ <svg viewBox="0 0 24 24"><path d="M4 4h16v12H7l-3 3z"/></svg>
+ <span class="lbl">Send an Inquiry<span class="val">Project name · scope · all the details</span></span>
+ </button>
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Sample')">
+ <svg viewBox="0 0 24 24"><rect x="4" y="6" width="16" height="14"/><path d="M4 10h16M9 6V3h6v3"/></svg>
+ <span class="lbl">Request a Sample<span class="val">Memo sample · ships free · 3–5 business days</span></span>
+ </button>
+ <a class="dwm-option" id="dwmContactEmailLink" href="mailto:info@silkwallpaper.com">
+ <svg viewBox="0 0 24 24"><path d="M3 6h18v12H3z"/><path d="M3 6l9 7 9-7"/></svg>
+ <span class="lbl">Email Us<span class="val" id="dwmContactEmailLabel">info@silkwallpaper.com</span></span>
+ </a>
+ </div>
+ </div>
+</div>
+
+<!-- Inquiry modal -->
+<div class="dwm" id="dwmInquiry" onclick="if(event.target===this)dwmClose('Inquiry')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Inquiry')" aria-label="Close">×</button>
+ <h3>Project Inquiry</h3>
+ <p class="sub">We'll respond within one business day</p>
+ <form id="dwmInquiryForm" onsubmit="return dwmSubmit(event,'inquiry')">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Phone</label><input type="tel" name="phone"></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Project Name</label><input type="text" name="projectName" required></div>
+ <div class="dwm-field"><label>Project Scope</label><textarea name="projectScope" placeholder="Square footage · room count · timeline" required></textarea></div>
+ <div class="dwm-field"><label>Additional Information</label><textarea name="message" placeholder="Patterns of interest, finish preferences, budget"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Inquiry</button>
+ <div class="dwm-status" id="dwmInquiryStatus"></div>
+ </form>
+ </div>
+</div>
+
+<!-- Sample-request modal -->
+<div class="dwm" id="dwmSample" onclick="if(event.target===this)dwmClose('Sample')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Sample')" aria-label="Close">×</button>
+ <h3>Request a Sample</h3>
+ <p class="sub">Memo sample · ships free · 3–5 business days</p>
+ <div class="dwm-preview" id="dwmSamplePreview" style="display:none">
+ <img id="dwmSampleImg" src="" alt="">
+ <div><div class="pn" id="dwmSampleName"></div><div class="pc" id="dwmSampleSku"></div></div>
+ </div>
+ <form id="dwmSampleForm" onsubmit="return dwmSubmit(event,'sample')">
+ <input type="hidden" name="sku" id="dwmSampleSkuInput">
+ <input type="hidden" name="title" id="dwmSampleTitleInput">
+ <input type="hidden" name="image_url" id="dwmSampleImageInput">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Shipping Address</label><input type="text" name="address" placeholder="Street" required></div>
+ <div class="dwm-field" style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:8px">
+ <input type="text" name="city" placeholder="City" required>
+ <input type="text" name="state" placeholder="State" required>
+ <input type="text" name="zip" placeholder="ZIP" required>
+ </div>
+ <div class="dwm-field"><label>Notes (optional)</label><textarea name="message" placeholder="Project, sq.ft., timing"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Sample Request</button>
+ <div class="dwm-status" id="dwmSampleStatus"></div>
+ </form>
+ </div>
+</div>
+
+<script>
+ // Universal modal control
+ function dwmOpen(name, opts){
+ document.getElementById('dwm' + name).classList.add('open');
+ document.body.style.overflow = 'hidden';
+ if (name === 'Sample' && opts) {
+ document.getElementById('dwmSamplePreview').style.display = 'flex';
+ document.getElementById('dwmSampleImg').src = opts.image_url || '';
+ document.getElementById('dwmSampleName').textContent = opts.title || '';
+ document.getElementById('dwmSampleSku').textContent = opts.sku || '';
+ document.getElementById('dwmSampleSkuInput').value = opts.sku || '';
+ document.getElementById('dwmSampleTitleInput').value = opts.title || '';
+ document.getElementById('dwmSampleImageInput').value = opts.image_url || '';
+ } else if (name === 'Sample') {
+ document.getElementById('dwmSamplePreview').style.display = 'none';
+ document.getElementById('dwmSampleSkuInput').value = '';
+ document.getElementById('dwmSampleTitleInput').value = '';
+ document.getElementById('dwmSampleImageInput').value = '';
+ }
+ }
+ function dwmClose(name){ document.getElementById('dwm'+name).classList.remove('open'); document.body.style.overflow=''; }
+
+ // Universal form submit — POSTs to /api/send-{kind}
+ async function dwmSubmit(e, kind) {
+ e.preventDefault();
+ const form = e.target;
+ const data = Object.fromEntries(new FormData(form).entries());
+ const btn = form.querySelector('.dwm-submit');
+ const status = form.querySelector('.dwm-status');
+ btn.disabled = true; const orig = btn.textContent; btn.textContent = 'Sending…';
+ status.className = 'dwm-status';
+ try {
+ const r = await fetch('/api/send-' + kind, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data) });
+ if (!r.ok) throw new Error('HTTP '+r.status);
+ const j = await r.json();
+ status.textContent = kind === 'sample' ? 'Sample request sent — ships within 3–5 business days' : 'Inquiry sent — we respond within 1 business day';
+ status.className = 'dwm-status ok';
+ btn.textContent = 'Sent ✓';
+ setTimeout(() => { dwmClose(kind === 'sample' ? 'Sample' : 'Inquiry'); btn.disabled = false; btn.textContent = orig; status.className = 'dwm-status'; form.reset(); }, 2800);
+ } catch (err) {
+ status.textContent = 'Send failed — please email info@designerwallcoverings.com';
+ status.className = 'dwm-status err';
+ btn.disabled = false; btn.textContent = orig;
+ }
+ return false;
+ }
+
+ // Wire up "Memo samples ship free" badge in hero to open sample modal directly
+ document.querySelectorAll('.hero .badge, [data-open-sample]').forEach(el => {
+ el.style.cursor = 'pointer';
+ el.addEventListener('click', () => dwmOpen('Sample'));
+ });
+
+ // Wire nav "Trade" link to inquiry modal
+ document.querySelectorAll('nav a[href*="designerwallcoverings.com"]').forEach(a => {
+ a.removeAttribute('target');
+ a.removeAttribute('href');
+ a.style.cursor = 'pointer';
+ a.addEventListener('click', e => { e.preventDefault(); dwmOpen('Contact'); });
+ });
+
+ // Override product card clicks — open Sample modal pre-filled instead of redirecting to DW
+ document.addEventListener('click', e => {
+ const card = e.target.closest('.card, .rail-card');
+ if (!card) return;
+ if (card.tagName === 'A' && (card.getAttribute('href') || '').startsWith('/sample/')) {
+ e.preventDefault();
+ const img = card.querySelector('img');
+ const pat = card.querySelector('.pat');
+ const sku = card.getAttribute('href').replace('/sample/', '');
+ dwmOpen('Sample', {
+ sku,
+ title: pat ? pat.textContent.trim() : '',
+ image_url: img ? img.src : ''
+ });
+ }
+ }, true);
+// DW STANDARD: hero pulls from products.json[0].image_url so dw-collections-viewer
+// drag-drop / hero-pin updates the live front page on next deploy.
+// Always upgrades to the largest Shopify variant (strips _NxN size suffix) so
+// the full-page hero stays sharp.
+// DW STANDARD: hero rotates through top-20 NEWEST products on each visit
+// (novasuede.com pattern). Fetches /api/products?limit=100, sorts by created_at desc,
+// takes the freshest 20, picks one per visit via localStorage counter.
+// Falls back to static /hero-bg.jpg on any error.
+(async function rotateHero() {
+ try {
+ const r = await fetch('/api/products?limit=100');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || [])
+ .filter(p => p && p.image_url && /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(p.image_url))
+ .sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')))
+ .slice(0, 20);
+ if (!items.length) return;
+ let visits = 0;
+ try {
+ visits = parseInt(localStorage.getItem('hero_visits') || '0', 10) || 0;
+ localStorage.setItem('hero_visits', String(visits + 1));
+ } catch(e) {}
+ const pick = items[visits % items.length];
+ // Strip _NxN size suffix to get the largest CDN variant
+ const url = pick.image_url.replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ const bg = document.getElementById('heroBg') || document.getElementById('cinemaBg');
+ if (bg) bg.style.backgroundImage = "url('" + url + "')";
+ } catch (e) {}
+})();
+
+
+// DW STANDARD sort-select wire-up (added by fleet patcher)
+(function(){
+ const sortSel = document.getElementById('sortSelect');
+ if (!sortSel) return;
+ const KEY = location.hostname.replace(/\./g, '_') + '_sort';
+ const saved = localStorage.getItem(KEY);
+ if (saved) { try { sortSel.value = saved; } catch(e){} }
+ if (typeof state !== 'undefined') state.sort = sortSel.value;
+ sortSel.addEventListener('change', e => {
+ const v = e.target.value;
+ try { localStorage.setItem(KEY, v); } catch(e){}
+ if (typeof state !== 'undefined') state.sort = v;
+ if (typeof resetGrid === 'function') resetGrid();
+ else location.reload();
+ });
+})();
+
+// graphics-loop pass 11: hydrate Ideas rails from /api/products
+(async function hydrateIdeas() {
+ try {
+ const r = await fetch('/api/products?limit=80');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || []).filter(p => p && p.image_url);
+ if (!items.length) return;
+ const sorted = [...items].sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')));
+ const newest = sorted.slice(0, 12);
+ // Curated = deterministic offset slice (every 5th item starting at idx 3) for variety
+ const curated = items.filter((_, i) => i % 5 === 3).slice(0, 12);
+ function card(p) {
+ const url = (p.image_url || '').replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ return '<a class="rail-card" href="/sample/' + encodeURIComponent(p.handle || p.sku) + '">' +
+ '<img src="' + url + '" alt="' + (p.title || '').replace(/"/g, '"') + '" loading="lazy">' +
+ '<div class="rc-title">' + ((p.title || '').split('|')[0] || '').trim() + '</div>' +
+ '<div class="rc-meta">' + (p.vendor || '') + '</div>' +
+ '</a>';
+ }
+ const r1 = document.getElementById('railNewest');
+ const r2 = document.getElementById('railCurated');
+ if (r1) r1.innerHTML = newest.map(card).join('');
+ if (r2) r2.innerHTML = curated.map(card).join('');
+ } catch(e) {}
+})();
+</script>
+
+<script>
+/* R4 — Alternating hero modes (huge / four-square). Server-side default is huge (HTML attr).
+ Client-only flip starts on visit 2. Both modes share the same .cinema min-height (CLS-free).
+ Crawlers without JS always see huge. */
+(function(){
+ try {
+ var k='dw-hero-mode';
+ var prev=parseInt(localStorage.getItem(k)||'0',10);
+ var next=(prev+1)%2;
+ localStorage.setItem(k,String(next));
+ if (prev>0) {
+ var mode = next===0 ? 'huge' : 'four-square';
+ document.documentElement.dataset.heroMode = mode;
+ if (mode === 'four-square') {
+ fetch('/api/products?limit=4&page=1').then(r=>r.ok?r.json():null).then(j=>{
+ if (!j || !j.items || j.items.length < 4) {
+ document.documentElement.dataset.heroMode = 'huge';
+ console.warn('dw-hero-stale: insufficient items for four-square, falling back to huge');
+ return;
+ }
+ var grid = document.getElementById('heroGrid');
+ if (!grid) return;
+ j.items.slice(0,4).forEach(function(it){
+ var d = document.createElement('div');
+ d.className = 'cell';
+ var img = (it.image_url || it.image || '/hero-bg.jpg');
+ d.style.backgroundImage = "url('" + img + "')";
+ grid.appendChild(d);
+ });
+ }).catch(function(){
+ document.documentElement.dataset.heroMode = 'huge';
+ });
+ }
+ }
+ } catch(e) { /* no-op: keep server default */ }
+})();
+</script>
+</body>
+</html>
diff --git a/public/index.html.bak.ns-canonical b/public/index.html.bak.ns-canonical
new file mode 100644
index 0000000..601a790
--- /dev/null
+++ b/public/index.html.bak.ns-canonical
@@ -0,0 +1,1041 @@
+<!doctype html>
+<html lang="en" data-hero-mode="huge">
+<head>
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
+
+<meta charset="utf-8">
+<!-- Google tag (gtag.js) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=G-CNTJNLTEW8"></script>
+<script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+ gtag('config', 'G-CNTJNLTEW8');
+</script>
+
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>silkwallpaper — curated wallcoverings, real samples ship free.</title>
+<meta name="description" content="silkwallpaper — a curated wallcovering atelier. Free memo samples. Trade service.">
+<meta name="theme-color" content="#FFF">
+<link rel="canonical" href="https://silkwallpaper.com/">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
+<link rel="preload" as="image" href="/hero-bg.jpg" fetchpriority="high">
+<script type="application/ld+json">
+{
+ "@context": "https://schema.org",
+ "@type": "Organization",
+ "name": "silkwallpaper",
+ "url": "https://silkwallpaper.com/",
+ "email": "info@silkwallpaper.com"
+}
+</script>
+<script src="/zd-loader.js" defer></script>
+<style>
+/* silkwallpaper · Saint Laurent template — pure monochrome, sharp uppercase, zero ornament */
+:root {
+ --bg: #000;
+ --paper: #FFF;
+ --muted: #888;
+ --line: rgba(255,255,255,0.20);
+ --accent: #FFF;
+ --bg-soft: #0a0a0a;
+ --cols: 6;
+}
+html[data-theme="light"] {
+ --bg: #FFF;
+ --paper: #000;
+ --muted: #666;
+ --line: rgba(0,0,0,0.20);
+ --accent: #000;
+ --bg-soft: #F5F5F5;
+}
+* { margin:0; padding:0; box-sizing:border-box }
+html { scroll-behavior:smooth }
+body { font-family:'Inter', 'Helvetica Neue', sans-serif; color:var(--paper); -webkit-font-smoothing:antialiased; background:var(--bg); min-height:100vh; overflow-x:hidden }
+/* DW-FASHION-TEMPLATE-OVERRIDE */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', Georgia, serif !important; font-weight:900 !important; text-transform:uppercase !important;letter-spacing:0.04em !important; }
+/* Saint Laurent: heavy uppercase headlines, no italic serif accent */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', 'Helvetica Neue', sans-serif !important; font-weight:900 !important; font-style:normal !important; letter-spacing:0.04em !important; text-transform:uppercase !important }
+
+/* ===== Auto-hide header ===== */
+header { position:fixed; top:0; left:0; right:0; display:flex; justify-content:space-between; align-items:center; padding:22px 32px; z-index:100; background:rgba(0,0,0,0.55); backdrop-filter:blur(10px); border-bottom:1px solid var(--line); transition:transform .3s }
+.h-link { display:flex; align-items:center; gap:10px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); text-decoration:none; cursor:pointer; background:transparent; border:0 }
+.h-link:hover { opacity:0.7 }
+.h-icon { width:18px; height:18px; stroke:currentColor; fill:none; stroke-width:1.5 }
+
+/* ===== Cinema hero (R2: huge full-bleed photo + top-bar logo + below-photo wordmark) ===== */
+.cinema { position:relative; min-height:clamp(560px, 90vh, 1080px); width:100%; overflow:hidden }
+.cinema-bg { position:absolute; inset:0; background-size:cover; background-position:center; background-image:url('/hero-bg.jpg') }
+.cinema-bg::after { content:""; position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,0,0,0.20) 0%, rgba(0,0,0,0) 35%, rgba(0,0,0,0) 65%, rgba(0,0,0,0.55) 100%) }
+/* Hero-mode hooks (R4): both modes share the same min-height — CLS-free */
+[data-hero-mode="four-square"] .cinema-bg { display:none }
+[data-hero-mode="four-square"] .cinema-grid { display:grid; grid-template-columns:1fr 1fr; grid-template-rows:1fr 1fr; position:absolute; inset:0 }
+[data-hero-mode="four-square"] .cinema-grid .cell { background-size:cover; background-position:center; border:1px solid rgba(0,0,0,0.4) }
+.cinema-grid { display:none }
+.cinema-logo { position:absolute; top:22px; left:32px; font-family: ui-serif, Georgia, serif; font-size:18px; font-weight:500; letter-spacing:0.02em; color:#fff; text-decoration:none; z-index:6; text-shadow:0 1px 4px rgba(0,0,0,0.5) }
+/* Below-photo wordmark (R2): brand display lives below the hero, not painted on it */
+.hero-wordmark-block { position:relative; padding:38px 32px 24px; text-align:center; background:var(--bg) }
+.hero-wordmark { font-family: ui-serif, Georgia, 'Cormorant Garamond', serif; font-weight:400; font-size:clamp(48px, 9vw, 128px); letter-spacing:-0.01em; line-height:1; color:var(--paper); margin:0; text-transform:none }
+.hero-wordmark-sub { display:block; font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400; font-size:clamp(13px, 1.4vw, 17px); letter-spacing:0.04em; text-transform:none; margin-top:14px; color:var(--muted) }
+@media (max-width:760px) { .hero-wordmark { font-size:clamp(32px, 12vw, 64px) } }
+.corner-mark { position:absolute; top:24px; left:32px; font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:#fff; text-shadow:0 1px 3px rgba(0,0,0,0.55), 0 0 1px rgba(0,0,0,0.4); z-index:5 }
+.corner-mark::before { content:"◆ "; margin-right:6px }
+/* center-mark deprecated 2026-05-10 per R2 — hidden */
+.center-mark { display:none !important; position:absolute; top:clamp(80px,12vh,140px); left:50%; transform:translateX(-50%); font-size:clamp(28px,4vw,56px); font-weight:500; letter-spacing:0.18em; text-transform:uppercase; text-align:center; color:#fff; text-shadow:0 2px 8px rgba(0,0,0,0.65), 0 0 1px rgba(0,0,0,0.5); z-index:5 }
+.center-mark .tm { font-size:14px; vertical-align:super; opacity:0.7; margin-left:0.2em }
+.center-mark .sub { display:block; font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400; font-size:0.4em; letter-spacing:0.04em; text-transform:none; margin-top:14px; opacity:0.85 }
+.meta-line { position:absolute; bottom:32px; left:32px; font-size:10px; letter-spacing:0.28em; text-transform:uppercase; font-weight:700; color:#fff; z-index:5; opacity:0.85 }
+.meta-line .num { font-size:12px; display:block; margin-top:4px; letter-spacing:0.05em }
+.enter { position:absolute; bottom:32px; right:32px; display:flex; align-items:center; gap:14px; font-size:11px; letter-spacing:0.36em; text-transform:uppercase; font-weight:700; color:#fff; text-decoration:none; padding-bottom:6px; border-bottom:1px solid #fff; transition:gap 0.25s; z-index:5 }
+.enter:hover { gap:22px }
+.enter svg { width:24px; height:12px }
+
+/* ===== Section ===== */
+.section { padding:96px 32px }
+.section-header { display:flex; justify-content:space-between; align-items:flex-end; flex-wrap:wrap; gap:24px; margin-bottom:32px; padding-bottom:24px; border-bottom:1px solid var(--line) }
+.section-title { font-size:clamp(36px,5vw,64px); font-weight:300; letter-spacing:-0.02em; line-height:1 }
+.section-title .accent { color: var(--accent); font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400 }
+.section-eyebrow { font-size:10px; letter-spacing:0.4em; text-transform:uppercase; font-weight:700; color:var(--muted); margin-bottom:16px }
+.section-meta { font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:600; color:var(--muted); text-align:right; line-height:1.6 }
+
+/* ===== Filter bar ===== */
+.filters { display:flex; gap:8px; align-items:center; margin-bottom:18px; flex-wrap:wrap }
+.chip { padding:8px 16px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); background:transparent; border:1px solid var(--line); cursor:pointer; transition:all 0.2s; font-family:inherit }
+.chip:hover { border-color:var(--paper) }
+.chip.active { background:var(--paper); color:var(--bg); border-color:var(--paper) }
+.search { flex:0 1 280px; margin-left:auto; display:flex; align-items:center; gap:10px; border-bottom:1px solid var(--line); padding:6px 0 }
+.search input { flex:1; background:transparent; border:0; color:var(--paper); font-family:inherit; font-size:13px; outline:none; letter-spacing:0.04em }
+.search input::placeholder { color:var(--muted) }
+.search svg { width:16px; height:16px; stroke:var(--muted); fill:none; stroke-width:1.5 }
+
+/* ===== Grid-density slider ===== */
+.density { display:flex; align-items:center; gap:14px; padding:6px 0; margin-bottom:24px }
+.density label { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--muted) }
+.density input[type=range] { flex:1; max-width:240px; -webkit-appearance:none; appearance:none; height:1px; background:var(--line); outline:none }
+.density input[type=range]::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50% }
+.density input[type=range]::-moz-range-thumb { width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50%; border:0 }
+.density .ct { font-size:11px; color:var(--muted); letter-spacing:0.18em; text-transform:uppercase; font-weight:600; min-width:80px }
+
+/* ===== Stats line ===== */
+.stat-line { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:600; color:var(--muted); margin-bottom:24px }
+
+/* ===== Product grid (novasuede pattern: flush grid + slide-up overlay) ===== */
+.grid { display:grid; grid-template-columns:repeat(var(--cols), 1fr); gap:0 }
+.card { position:relative; aspect-ratio:1/1.15; cursor:pointer; overflow:hidden; border:1px solid var(--bg); transition:transform 0.4s cubic-bezier(0.2,0.8,0.2,1); background:var(--bg-soft) }
+.card:hover { transform:scale(1.04); z-index:5 }
+.card img { width:100%; height:100%; object-fit:cover; display:block; transition:transform 0.4s ease }
+.card .overlay { position:absolute; left:0; right:0; bottom:0; padding:20px 16px 14px; background:linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.85) 100%); transform:translateY(48%); opacity:0.85; transition:transform 0.3s ease, opacity 0.3s ease }
+.card:hover .overlay { transform:translateY(0); opacity:1 }
+.card .pat { font-size:13px; font-weight:600; letter-spacing:0.02em; color:#fff; line-height:1.25; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }
+.card .ven { font-size:9px; letter-spacing:0.18em; text-transform:uppercase; color:#fff; opacity:0.7; font-weight:500; margin-top:4px }
+.card .actions { margin-top:10px; display:flex; gap:6px }
+.card .sample-btn { flex:1; padding:7px 10px; background:#fff; color:#000; font-family:inherit; font-size:10px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; border:0; cursor:pointer; text-align:center; transition:all 0.2s }
+.card .sample-btn:hover { background:var(--accent); color:#fff }
+
+@media (max-width:1024px) { .grid { grid-template-columns:repeat(min(var(--cols), 4), 1fr) } }
+@media (max-width:720px) { .grid { grid-template-columns:repeat(2, 1fr) } .density { display:none } }
+
+.sentinel { height:1px }
+.loading { text-align:center; color:var(--muted); padding:32px; font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700 }
+
+/* ===== Footer ===== */
+footer { padding:64px 32px 32px; border-top:1px solid var(--line); margin-top:32px }
+.footer-grid { max-width:1400px; margin:0 auto; display:grid; grid-template-columns:2fr 1fr 1fr 1fr; gap:48px; margin-bottom:48px }
+.footer-brand { font-size:24px; font-weight:300; letter-spacing:0.32em; text-transform:uppercase; margin-bottom:12px }
+.footer-text { font-size:13px; line-height:1.6; color:var(--muted); max-width:380px }
+.footer-col h4 { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--paper); margin-bottom:18px }
+.footer-col a { display:block; font-size:13px; color:var(--muted); text-decoration:none; margin-bottom:8px; transition:color 0.2s; cursor:pointer; background:transparent; border:0; font-family:inherit; padding:0; text-align:left }
+.footer-col a:hover { color:var(--accent) }
+.footer-bottom { max-width:1400px; margin:0 auto; padding-top:24px; border-top:1px solid var(--line); display:flex; justify-content:space-between; flex-wrap:wrap; gap:16px; font-size:11px; letter-spacing:0.18em; text-transform:uppercase; color:var(--muted) }
+
+@media (max-width:720px) {
+ .corner-mark, .meta-line, .enter { font-size:10px }
+ .center-mark { font-size:24px; letter-spacing:0.12em; top:clamp(60px,9vh,90px) }
+ .section, footer { padding-left:20px; padding-right:20px }
+ .footer-grid { grid-template-columns:1fr; gap:32px }
+ .search { margin-left:0; flex:1 1 100% }
+}
+
+.theme-toggle { background:transparent; border:1px solid var(--line); width:32px; height:32px; cursor:pointer; color:var(--paper); display:inline-flex; align-items:center; justify-content:center; font-size:14px; line-height:1; transition:all .15s; padding:0 }
+.theme-toggle:hover { border-color:var(--accent); color:var(--accent) }
+
+/* ===== WCAG 2.4.7 — keyboard focus indicators ===== */
+/* Strictly :focus-visible so mouse clicks do NOT show the ring */
+a:focus-visible,
+button:focus-visible,
+input:focus-visible,
+select:focus-visible,
+textarea:focus-visible,
+[tabindex]:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+</style>
+<script>
+(function(){ try { var t = localStorage.getItem('silk_theme') || 'dark'; document.documentElement.dataset.theme = t; } catch(e){} })();
+</script>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600;700&display=swap">
+
+<meta property="og:type" content="website">
+<meta property="og:url" content="https://silkwallpaper.com/">
+<meta property="og:title" content="silkwallpaper — curated wallcoverings, real samples ship free.">
+<meta property="og:description" content="silkwallpaper — a curated wallcovering atelier. Free memo samples. Trade service.">
+<meta property="og:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<meta property="og:site_name" content="silkwallpaper">
+<meta property="article:publisher" content="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings">
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:title" content="silkwallpaper — curated wallcoverings, real samples ship free.">
+<meta name="twitter:description" content="silkwallpaper — a curated wallcovering atelier. Free memo samples. Trade service.">
+<meta name="twitter:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<!-- graphics-loop pass 13: footer + fb -->
+</head>
+<body>
+<style id="ns-header-modal-styles">
+ .ns-header { position: sticky; top: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; padding: 18px 36px; background: rgba(10,10,10,0.92) !important; backdrop-filter: blur(8px); border-bottom: 1px solid rgba(245,241,232,0.06); }
+ .ns-brand { display: inline-flex; align-items: center; gap: 14px; text-decoration: none; }
+ .ns-dot { width: 28px; height: 28px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, #a98b5b 0%, #5d4c32 60%, #332a1b 100%); box-shadow: 0 0 0 1px rgba(245,241,232,0.10), 0 2px 12px rgba(180,90,60,0.30); flex-shrink: 0; }
+ .ns-name { font-family: -apple-system, "SF Pro Display", "Helvetica Neue", system-ui, sans-serif; font-size: 18px !important; font-weight: 300 !important; letter-spacing: 0.02em !important; color: #f5f1e8 !important; text-transform: none !important; margin: 0 !important; }
+ .ns-name sup { font-size: 0.55em; font-weight: 300; margin-left: 1px; }
+ .ns-nav { display: flex; gap: 32px; align-items: center; list-style: none; padding: 0; margin: 0; }
+ .ns-nav a, .ns-nav button { font-family: -apple-system, "SF Pro Text", system-ui, sans-serif; font-size: 15px !important; font-weight: 400 !important; color: #f5f1e8 !important; text-decoration: none !important; background: none !important; border: 0 !important; cursor: pointer; padding: 0 !important; transition: color 200ms; letter-spacing: 0 !important; text-transform: none !important; }
+ .ns-nav a:hover, .ns-nav button:hover { color: #ffffff !important; }
+ @media (max-width: 820px) { .ns-header { padding: 14px 18px; } .ns-nav { gap: 16px; } .ns-nav a, .ns-nav button { font-size: 13px !important; } }
+ @media (max-width: 760px) {
+ .ns-header { flex-wrap: wrap; }
+ .ns-nav { display: none !important; flex-basis: 100%; flex-direction: column; align-items: flex-start; gap: 6px; padding: 14px 0 4px; }
+ .ns-nav.open { display: flex !important; }
+ .ns-nav a, .ns-nav button { padding: 10px 0 !important; border-bottom: 1px solid rgba(245,241,232,0.08); width: 100%; text-align: left; }
+ .ns-nav a:nth-child(n+5), .ns-nav button:nth-child(n+5) { display: block !important; }
+ .ns-header .mobile-toggle { display: flex !important; }
+ }
+ @media (min-width: 761px) {
+ .ns-header .mobile-toggle { display: none !important; }
+ }
+ .ns-modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.78); z-index: 200; align-items: center; justify-content: center; padding: 24px; backdrop-filter: blur(6px); }
+ .ns-modal.show { display: flex; }
+ .ns-modal-card { max-width: 460px; width: 100%; background: #141414 !important; border: 1px solid rgba(245,241,232,0.14); border-radius: 6px; padding: 28px 30px 30px; position: relative; }
+ .ns-modal-card .close-x { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; background: none; border: 1px solid rgba(245,241,232,0.18); color: rgba(245,241,232,0.6) !important; border-radius: 50%; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; }
+ .ns-modal-card .close-x:hover { color: #ffffff !important; border-color: #ffffff; }
+ .ns-modal-card h3 { font-family: -apple-system, "SF Pro Display", system-ui; font-weight: 300 !important; font-size: 22px !important; margin: 0 0 6px !important; color: #f5f1e8 !important; }
+ .ns-modal-card .sub { color: rgba(245,241,232,0.65) !important; font-size: 13px; margin: 0 0 22px; }
+ .ns-actions { display: flex; flex-direction: column; gap: 10px; }
+ .ns-action { display: flex; align-items: center; gap: 14px; padding: 14px 16px; background: rgba(245,241,232,0.04) !important; border: 1px solid rgba(245,241,232,0.12) !important; color: #f5f1e8 !important; text-decoration: none !important; cursor: pointer; transition: all 200ms; font: inherit; text-align: left; }
+ .ns-action:hover { background: rgba(245,241,232,0.08) !important; border-color: #d4b683 !important; }
+ .ns-action .ico { width: 32px; height: 32px; border-radius: 50%; background: rgba(212,182,131,0.12); display: flex; align-items: center; justify-content: center; font-size: 15px; flex-shrink: 0; }
+ .ns-action .txt { flex: 1; }
+ .ns-action .txt b { display: block; font-weight: 500; font-size: 14px; color: #f5f1e8 !important; }
+ .ns-action .txt span { font-size: 12px; color: rgba(245,241,232,0.55) !important; }
+
+ /* ─── FINAL CONTRAST PASS — kill remaining gold-solid + white-island defects ─── */
+ /* Replace all gold-fill accents with off-white (Gucci-aesthetic, no DW gold solids) */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active,
+ .drapery-btn, .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover,
+ .drapery-btn:hover, .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ /* Gold accents stay only as edge color (links, glyphs) — not fills */
+ a { color: #d4b683; }
+ a:hover { color: #f5d97a; }
+ /* Authorized-dealer footer line — small caps tracked, muted ink, no gold pill */
+ .authorized, .authorized-dealer p {
+ background: transparent !important;
+ color: rgba(245,241,232,0.5) !important;
+ font-size: 10px !important;
+ letter-spacing: 0.22em !important;
+ text-transform: uppercase !important;
+ padding: 0 !important;
+ border: none !important;
+ margin-top: 14px !important;
+ }
+ /* Kill any straggler white bg in info-card / sample-related cards */
+ .info-card, .info-card-link, .care-highlight, .feature-card, .spec-item {
+ background-color: rgba(245,241,232,0.04) !important;
+ border-color: rgba(245,241,232,0.12) !important;
+ }
+ /* Ensure modal content stays readable — Steve's earlier sweep made it #141414 with white text */
+ .modal-content { background: #141414 !important; color: #f5f1e8 !important; }
+ .modal-content * { color: #f5f1e8 !important; }
+ .modal-content input, .modal-content textarea, .modal-content select {
+ background: rgba(245,241,232,0.05) !important;
+ color: #f5f1e8 !important;
+ border-color: rgba(245,241,232,0.2) !important;
+ }
+ .modal-content label { color: rgba(245,241,232,0.6) !important; }
+ /* Force the wide-tracked NOVASUEDE wordmark to stay white */
+ .ns-wordmark, .ns-wordmark sup { color: #f5f1e8 !important; }
+
+ /* ─── Modal-internal contrast (post-QA defects) ─── */
+ /* Backdrop covers full viewport */
+ .modal { width: 100vw !important; height: 100vh !important; left: 0 !important; top: 0 !important; background: rgba(0, 0, 0, 0.78) !important; backdrop-filter: blur(6px); }
+ /* Chip — flip from white-on-white island to dark card */
+ .request-chip {
+ background: rgba(245, 241, 232, 0.06) !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.18) !important;
+ border-radius: 4px !important;
+ }
+ .request-chip * { color: #f5f1e8 !important; }
+ .request-chip .chip-swatch {
+ /* swatch dot ALWAYS displays the inline-style hex from JS — fight any sweep */
+ border: 1px solid rgba(0, 0, 0, 0.4) !important;
+ }
+ /* + Add to request — hairline white-border on transparent (no gold) */
+ .add-to-request-btn {
+ background: transparent !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.6) !important;
+ border-radius: 0 !important;
+ padding: 12px 22px !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 400 !important;
+ }
+ .add-to-request-btn:hover {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ /* Hex caption visible */
+ .preview-hex, #colorPreviewHex {
+ display: block !important;
+ color: rgba(245, 241, 232, 0.55) !important;
+ font-family: ui-monospace, "SF Mono", Menlo, monospace !important;
+ font-size: 11px !important;
+ margin-top: 4px !important;
+ letter-spacing: 0.04em !important;
+ text-transform: lowercase !important;
+ }
+ /* Color preview name — stays single, prominent */
+ #colorPreviewName {
+ color: #f5f1e8 !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 500 !important;
+ display: block !important;
+ margin-top: 12px !important;
+ }
+
+ /* ─── Mobile hamburger nav (replaces the overflow at <760px) ─── */
+ @media (max-width: 760px) {
+ header .nav-container { flex-wrap: wrap; }
+ header nav { display: none; flex-basis: 100%; flex-direction: column; gap: 6px; padding: 16px 0 6px; }
+ header nav.open { display: flex; }
+ header nav a { padding: 10px 0; border-bottom: 1px solid rgba(245,241,232,0.1); }
+ header .mobile-toggle {
+ display: flex !important;
+ flex-direction: column;
+ gap: 5px;
+ background: transparent;
+ border: 0;
+ cursor: pointer;
+ padding: 8px;
+ }
+ header .mobile-toggle span {
+ width: 22px; height: 1.5px;
+ background: #f5f1e8;
+ display: block;
+ }
+ }
+ @media (min-width: 761px) {
+ header .mobile-toggle { display: none !important; }
+ }
+
+ </style>
+<style id="ns-gold-kill">
+ /* novasuede: kill gold-solid fills, off-white instead. Edge color stays gold. */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active, .drapery-btn,
+ .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover, .drapery-btn:hover,
+ .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ </style>
+<header class="ns-header">
+ <a href="/" class="ns-brand" aria-label="Home">
+ <span class="ns-dot"></span>
+ <span class="ns-name">silkwallpaper</span>
+ </a>
+ <nav class="ns-nav" id="nsPrimaryNav" aria-label="Primary">
+ <a href="/">Home</a>
+ <a href="/about">About</a>
+ <button type="button" onclick="nsContactOpen()">Contact</button>
+ </nav>
+ <button type="button" class="mobile-toggle" aria-label="Toggle menu" aria-controls="nsPrimaryNav" aria-expanded="false" onclick="nsToggleNav(this)">
+ <span></span><span></span><span></span>
+ </button>
+ </header>
+
+<div class="ns-modal" id="nsContactModal" role="dialog" aria-modal="true" aria-labelledby="nsContactTitle" onclick="if(event.target===this)nsContactClose()">
+ <div class="ns-modal-card">
+ <button type="button" class="close-x" onclick="nsContactClose()" aria-label="Close">×</button>
+ <h3 id="nsContactTitle">How can we help?</h3>
+ <p class="sub">Trade-account replies within one business day.</p>
+ <div class="ns-actions">
+ <a class="ns-action" href="#contact" onclick="nsContactClose()"><span class="ico">📋</span><div class="txt"><b>Get a Quote</b><span>Project specs → priced estimate</span></div></a>
+ <a class="ns-action" href="#contact" onclick="nsContactClose();openModal();"><span class="ico">📦</span><div class="txt"><b>Order a Sample</b><span>Free swatch to your shop</span></div></a>
+ <a class="ns-action" href="mailto:info@silkwallpaper.com?subject=silkwallpaper%20inquiry"><span class="ico">✉️</span><div class="txt"><b>Email Us</b><span>info@silkwallpaper.com</span></div></a>
+ <a class="ns-action" href="tel:888-373-4564"><span class="ico">📞</span><div class="txt"><b>Call Us</b><span>(888) 373-4564 · M–F, 9–5 PT</span></div></a>
+ </div>
+ </div>
+ </div>
+<script>
+ function nsContactOpen() { var m = document.getElementById('nsContactModal'); if (m) m.classList.add('show'); }
+ function nsContactClose() { var m = document.getElementById('nsContactModal'); if (m) m.classList.remove('show'); }
+ function nsToggleNav(btn) {
+ var nav = document.getElementById('nsPrimaryNav');
+ if (!nav) return;
+ var open = nav.classList.toggle('open');
+ btn.setAttribute('aria-expanded', open ? 'true' : 'false');
+ }
+ document.addEventListener('keydown', function(e){ if (e.key === 'Escape') nsContactClose(); });
+</script>
+
+
+
+
+
+
+
+
+
+
+
+<header>
+ <button class="h-link" onclick="dwmOpen('Contact')" aria-label="Contact"><svg class="h-icon" viewBox="0 0 24 24"><path d="M3 7h18M3 12h18M3 17h18"/></svg><span>Contact</span></button>
+ <button class="theme-toggle" id="theme-toggle" aria-label="Theme toggle">☾</button>
+</header>
+
+<section class="cinema" id="hero">
+ <a class="cinema-logo" href="/" aria-label="silkwallpaper home">silkwallpaper</a>
+ <div class="cinema-bg" id="heroBg"></div>
+ <div class="cinema-grid" id="heroGrid" aria-hidden="true"></div>
+ <div class="corner-mark">Silken Sheen</div>
+ <div class="meta-line">Silk · Luxe · Sheen · Jacquard<span class="num" id="heroNum"></span></div>
+ <a class="enter" href="#shop">Enter <svg viewBox="0 0 24 12" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M0 6h22M16 1l6 5-6 5"/></svg></a>
+</section>
+<div class="hero-wordmark-block">
+ <h1 class="hero-wordmark">silkwallpaper</h1>
+ <span class="hero-wordmark-sub" id="heroWordmarkSub"></span>
+</div>
+
+
+<section class="section" id="ideas">
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">New this week</h2>
+ <div class="rail" id="railNewest"></div>
+ </div>
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">Curated picks</h2>
+ <div class="rail" id="railCurated"></div>
+ </div>
+</section>
+
+<section class="section" id="shop">
+ <div class="section-header">
+ <div>
+ <div class="section-eyebrow">The Collection</div>
+ <h2 class="section-title"><span class="accent" id="totalCount">—</span> Patterns.</h2>
+ </div>
+ <div class="section-meta">Silk · Luxe · Sheen · Jacquard</div>
+ </div>
+
+ <div class="filters" id="facets">
+ <button class="chip active" data-facet="all">All</button>
+ </div>
+
+ <div class="density">
+ <label>Sort</label>
+ <select id="sortSelect" aria-label="Sort products" style="background:transparent;color:inherit;border:1px solid var(--line,rgba(255,255,255,.2));font:inherit;font-size:11px;letter-spacing:.10em;text-transform:uppercase;padding:4px 8px;cursor:pointer;outline:none;margin-right:14px">
+ <option value="newest">Newest</option>
+ <option value="color">Color</option>
+ <option value="style">Style</option>
+ <option value="sku">SKU A→Z</option>
+ <option value="title">Title A→Z</option>
+ <option value="price-asc">Price ↑</option>
+ <option value="price-desc">Price ↓</option>
+ </select>
+ <label>Grid</label>
+ <input type="range" id="densitySlider" min="4" max="12" step="1" value="6" aria-label="Grid columns">
+ <span class="ct" id="densityLabel">6 cols</span>
+ <div class="search">
+ <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5.5-5.5"/></svg>
+ <input type="text" id="searchInput" placeholder="Search by pattern, color…" autocomplete="off">
+ </div>
+ </div>
+
+ <div class="stat-line" id="statLine">Loading…</div>
+ <div class="grid" id="grid"></div>
+ <div class="loading" id="loading">Loading more…</div>
+ <div class="sentinel" id="sentinel"></div>
+</section>
+
+<footer>
+ <div class="footer-grid">
+ <div>
+ <div class="footer-brand">silkwallpaper</div>
+ <p class="footer-text">silkwallpaper — real samples ship free.</p>
+ <p class="footer-text" style="margin-top:14px;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;font-weight:600;color:var(--paper);opacity:0.7"><a href="/about" style="color:inherit;text-decoration:none;border-bottom:1px solid var(--accent)">About →</a></p>
+ </div>
+ <div class="footer-col">
+ <h4>Aesthetic</h4>
+ <div id="footerFacets"></div>
+ </div>
+ <div class="footer-col">
+ <h4>Trade</h4>
+ <button onclick="dwmOpen('Inquiry')">Project Inquiry</button>
+ <button onclick="dwmOpen('Sample')">Request Sample</button>
+ <button onclick="dwmOpen('Contact')">Contact</button>
+ </div>
+
+ <div class="footer-col">
+ <h4>Social</h4>
+ <a class="fb-link" href="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings" target="_blank" rel="noopener">Facebook</a>
+ </div>
+ </div>
+ <div class="footer-bottom">
+ <span>silkwallpaper.com · silkwallpaper</span>
+ <span><a href="mailto:info@silkwallpaper.com" style="color:var(--paper);text-decoration:none;border-bottom:1px solid var(--accent)">info@silkwallpaper.com</a></span>
+ <span id="footerStat"></span>
+ </div>
+</footer>
+
+<script>
+// mini-constellation — 43 niche-stars, current site highlighted in gold
+(function(){
+ const SLUG = "silkwallpaper";
+ const NICHES = [
+ ['silkwallpaper','Silk','mat'],['silkwallcoverings','Silk W/C','mat'],['linenwallpaper','Linen','mat'],
+ ['jutewallpaper','Jute','mat'],['raffiawallcoverings','Raffia W/C','mat'],['raffiawalls','Raffia Walls','mat'],
+ ['corkwallcovering','Cork','mat'],['fabricwallpaper','Fabric','mat'],['textilewallpaper','Textile','mat'],
+ ['metallicwallpaper','Metallic','mat'],['silverleafwallpaper','Silver Leaf','mat'],['vinylwallpaper','Vinyl','mat'],
+ ['micawallpaper','Mica','mat'],['madagascarwallpaper','Madagascar','mat'],['mylarwallpaper','Mylar','mat'],
+ ['suedewallpaper','Suede','mat'],
+ ['1800swallpaper','1800s','dec'],['1890swallpaper','1890s','dec'],['1900swallpaper','1900s','dec'],
+ ['1920swallpaper','1920s','dec'],['1930swallpaper','1930s','dec'],['1940swallpaper','1940s','dec'],
+ ['1950swallpaper','1950s','dec'],['1960swallpaper','1960s','dec'],['1970swallpaper','1970s','dec'],
+ ['1980swallpaper','1980s','dec'],['retrowalls','Retro','dec'],['wallpapersback','W. Back','dec'],
+ ['agedwallpaper','Aged','cra'],['handcraftedwallpaper','Handcrafted','cra'],['museumwallpaper','Museum','cra'],
+ ['restorationwallpaper','Restoration','cra'],['pastelwallpaper','Pastel','cra'],['glitterwalls','Glitter','cra'],
+ ['greenwallcoverings','Green','cra'],['naturalwallcoverings','Natural','cra'],['saloonwallpaper','Saloon','cra'],
+ ['contractwallpaper','Contract','use'],['hotelwallcoverings','Hotel','use'],['hospitalitywallpaper','Hospitality','use'],
+ ['healthcarewallpaper','Healthcare','use'],['restaurantwallpaper','Restaurant','use'],['architecturalwallcoverings','Architectural','use']
+ ];
+ const COLOR = { mat:'#c9b687', dec:'#b86a4a', cra:'#6b8e6f', use:'#4a6b8e' };
+ const W = 1080, H = 200, BANDS = ['mat','dec','cra','use'];
+ const groups = { mat:[], dec:[], cra:[], use:[] };
+ for (const n of NICHES) groups[n[2]].push(n);
+ const svg = document.getElementById('miniConst');
+ if (!svg) return;
+ const bandW = W / BANDS.length;
+ let nodes = '';
+ BANDS.forEach((band, bi) => {
+ const list = groups[band];
+ const cx = bi * bandW + bandW / 2;
+ list.forEach((n, i) => {
+ const t = list.length === 1 ? 0.5 : i / (list.length - 1);
+ // deterministic jitter per slug
+ let h = 0; for (let k = 0; k < n[0].length; k++) h = (h * 31 + n[0].charCodeAt(k)) | 0;
+ const jx = ((h & 0xff) / 255 - 0.5) * (bandW * 0.5);
+ const jy = (((h >> 8) & 0xff) / 255 - 0.5) * 6; // tightened from 14 → 6 to prevent same-band collisions (16 mat nodes / 144px = 9px spacing)
+ const x = cx + jx, y = 28 + t * (H - 56) + jy;
+ const isCurrent = n[0] === SLUG;
+ const r = isCurrent ? 8 : 4;
+ const op = isCurrent ? 1 : 0.55;
+ nodes += `<g class="mc-node" data-slug="${n[0]}" data-label="${n[1]}" style="cursor:pointer">
+ <circle cx="${x}" cy="${y}" r="${r}" fill="url(#dna-${band})" fill-opacity="${op}" stroke="${COLOR[band]}" stroke-opacity="${isCurrent?1:0.7}" stroke-width="${isCurrent?2:1}">
+ <title>${n[1]} — ${n[0]}.com</title>
+ </circle>
+ ${isCurrent ? `<circle cx="${x}" cy="${y}" r="${r+5}" fill="none" stroke="${COLOR[band]}" stroke-opacity="0.4" stroke-width="1"><animate attributeName="r" values="${r+5};${r+12};${r+5}" dur="2.4s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" repeatCount="indefinite"/></circle>` : ''}
+ </g>`;
+ });
+ });
+ // append nodes after defs
+ svg.insertAdjacentHTML('beforeend', nodes);
+ svg.querySelectorAll('.mc-node').forEach(n => {
+ n.addEventListener('mouseenter', () => n.querySelector('circle').setAttribute('fill-opacity', '1'));
+ n.addEventListener('mouseleave', e => {
+ const isCurrent = n.dataset.slug === SLUG;
+ n.querySelector('circle').setAttribute('fill-opacity', isCurrent ? '1' : '0.55');
+ });
+ n.addEventListener('click', () => window.open('https://' + n.dataset.slug + '.com', '_blank'));
+ });
+})();
+</script>
+
+<script>
+const state = { q:'', facet:'all', page:1, pages:1, total:0, loading:false, exhausted:false };
+const LABELS = {"all":"All"};
+
+function escAttr(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
+// Image-URL allowlist: only DW Shopify CDN + designerwallcoverings.com (defends against XSS via crafted products.json)
+function safeImg(u) { return /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(u || '') ? u : '/hero-bg.jpg'; }
+function cardHTML(p) {
+ const eager = state.page === 1;
+ return '<img loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+ + '<div class="overlay">'
+ + '<div class="pat">' + escAttr(p.pattern_name || p.title) + '</div>'
+ + '<div class="ven">' + escAttr((p.vendor || '').replace(/-/g, ' ')) + '</div>'
+ + '<div class="actions">'
+ + '<button class="sample-btn" onclick="event.stopPropagation();dwmOpen(\'Sample\',' + JSON.stringify({sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}).replace(/"/g,'"') + ')">Sample</button>'
+ + '</div></div>';
+}
+
+async function loadFacets() {
+ let f;
+ try {
+ const r = await fetch('/api/facets');
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ f = await r.json();
+ } catch (e) { console.error('loadFacets failed:', e); return; }
+ const el = document.getElementById('facets');
+ for (const [k, v] of Object.entries(f.aesthetics).sort((a,b) => b[1] - a[1])) {
+ const b = document.createElement('button');
+ b.className = 'chip'; b.dataset.facet = k;
+ b.innerHTML = (LABELS[k] || k) + ' <span style="opacity:.55;font-weight:500;margin-left:4px">' + v + '</span>';
+ el.appendChild(b);
+ }
+ el.addEventListener('click', e => {
+ if (e.target.tagName !== 'BUTTON') return;
+ document.querySelectorAll('#facets button').forEach(b => b.classList.remove('active'));
+ e.target.classList.add('active');
+ state.facet = e.target.dataset.facet;
+ resetGrid();
+ });
+ document.getElementById('totalCount').textContent = f.total;
+ document.getElementById('footerStat').textContent = f.total + ' patterns · live archive';
+ // Footer aesthetic links
+ const footerEl = document.getElementById('footerFacets');
+ for (const [k] of Object.entries(f.aesthetics)) {
+ const b = document.createElement('button');
+ b.textContent = LABELS[k] || k;
+ b.onclick = () => { state.facet = k; resetGrid(); document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.facet === k)); document.getElementById('shop').scrollIntoView({behavior:'smooth'}); };
+ footerEl.appendChild(b);
+ }
+}
+
+function resetGrid() {
+ state.page = 1; state.exhausted = false;
+ document.getElementById('grid').innerHTML = '';
+ document.getElementById('loading').textContent = 'Loading…';
+ loadGridPage();
+}
+
+async function loadGridPage() {
+ if (state.loading || state.exhausted) return;
+ state.loading = true;
+ const params = new URLSearchParams({ page:state.page, limit:24 });
+ if (state.q) params.set('q', state.q);
+ if (state.facet !== 'all') params.set('aesthetic', state.facet);
+ let data;
+ try {
+ const r = await fetch('/api/products?' + params);
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ data = await r.json();
+ } catch (e) {
+ console.error('loadGridPage failed:', e);
+ document.getElementById('loading').textContent = '— offline — refresh to retry —';
+ state.loading = false;
+ return;
+ }
+ state.total = data.total; state.pages = data.pages;
+
+ if (state.page === 1) {
+ document.getElementById('statLine').textContent = data.total + ' patterns · ' + (state.facet === 'all' ? 'all aesthetics' : (LABELS[state.facet] || state.facet)) + (state.q ? ' · matching "' + state.q + '"' : '');
+ }
+ const grid = document.getElementById('grid');
+ for (const p of data.items) {
+ const a = document.createElement('a');
+ a.className = 'card'; a.href = '#'; a.onclick = (e) => { e.preventDefault(); dwmOpen('Sample',{sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}); };
+ a.innerHTML = cardHTML(p);
+ grid.appendChild(a);
+ }
+ if (state.page >= state.pages || data.items.length === 0) {
+ state.exhausted = true;
+ document.getElementById('loading').textContent = state.total > 0 ? '— end of archive · ' + state.total + ' patterns —' : '';
+ } else state.page++;
+ state.loading = false;
+}
+
+const io = new IntersectionObserver(es => { for (const e of es) if (e.isIntersecting) loadGridPage(); }, { rootMargin:'600px 0px' });
+io.observe(document.getElementById('sentinel'));
+
+document.getElementById('searchInput').addEventListener('input', e => {
+ state.q = e.target.value.trim();
+ clearTimeout(window._t);
+ window._t = setTimeout(resetGrid, 220);
+});
+
+// Density slider
+const slider = document.getElementById('densitySlider');
+const dlabel = document.getElementById('densityLabel');
+function setDensity(n) {
+ document.documentElement.style.setProperty('--cols', n);
+ dlabel.textContent = n + ' cols';
+ try { localStorage.setItem('silk_theme_density', n); } catch(e){}
+}
+slider.addEventListener('input', e => setDensity(parseInt(e.target.value)));
+const savedDensity = parseInt(localStorage.getItem('silk_theme_density') || '6');
+if (savedDensity >= 4 && savedDensity <= 12) { slider.value = savedDensity; setDensity(savedDensity); }
+
+// Theme toggle
+const tb = document.getElementById('theme-toggle');
+function setTheme(t){ document.documentElement.dataset.theme = t; try { localStorage.setItem('silk_theme', t); } catch(e){} tb.textContent = t === 'dark' ? '☾' : '☀'; }
+setTheme(document.documentElement.dataset.theme || 'dark');
+tb.addEventListener('click', () => setTheme(document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'));
+
+loadFacets();
+loadGridPage();
+</script>
+<!-- ============================================================
+ DW UNIVERSAL CONTACT MODULE — fashion-house UX
+ Inject before </body> in every DW-family sister site.
+ Self-contained: CSS + 4 modals + JS submission handlers.
+ Uses host site's CSS vars (--bg, --fg, --gold, --rule).
+ ============================================================ -->
+<style>
+ /* === MODAL BASE === */
+ .dwm{position:fixed;inset:0;background:rgba(0,0,0,0.78);display:none;align-items:center;justify-content:center;z-index:9999;padding:24px;overflow-y:auto}
+ .dwm.open{display:flex}
+ .dwm-box{background:var(--bg);border:1px solid var(--rule);max-width:520px;width:100%;padding:36px 32px;position:relative;max-height:90vh;overflow-y:auto;animation:dwm-in 0.25s ease}
+ @keyframes dwm-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+ .dwm-box h3{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-weight:400;font-size:32px;line-height:1.05;margin-bottom:6px;color:var(--fg);letter-spacing:-0.01em}
+ .dwm-box .sub{color:var(--muted);font-size:12px;letter-spacing:0.16em;text-transform:uppercase;margin-bottom:24px;font-weight:500}
+ .dwm-close{position:absolute;top:18px;right:18px;background:transparent;border:0;color:var(--muted);font-size:24px;cursor:pointer;width:32px;height:32px;line-height:1;padding:0}
+ .dwm-close:hover{color:var(--gold)}
+ .dwm-preview{display:flex;gap:14px;align-items:center;padding:12px;background:var(--bg-soft);margin-bottom:18px}
+ .dwm-preview img{width:64px;height:64px;object-fit:cover}
+ .dwm-preview .pn{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-size:15px;line-height:1.2;color:var(--fg)}
+ .dwm-preview .pc{font-size:10px;letter-spacing:0.14em;text-transform:uppercase;color:var(--muted);margin-top:4px}
+ .dwm-field{margin-bottom:14px}
+ .dwm-field label{display:block;font-size:10px;letter-spacing:0.18em;text-transform:uppercase;color:var(--muted);margin-bottom:6px;font-weight:500}
+ .dwm-field input,.dwm-field textarea,.dwm-field select{width:100%;border:0;border-bottom:1px solid var(--rule);background:transparent;padding:8px 4px;font-size:14px;font-family:inherit;color:var(--fg);outline:none;transition:border-color 0.2s}
+ .dwm-field input:focus,.dwm-field textarea:focus{border-bottom-color:var(--gold)}
+ .dwm-field textarea{min-height:60px;resize:vertical}
+ .dwm-submit{background:transparent;color:var(--muted, #c9b89e);border:1px solid var(--muted, #c9b89e);padding:14px 28px;font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:700;cursor:pointer;width:100%;margin-top:8px;transition:background 0.15s, color 0.15s}
+ .dwm-submit:hover{background:var(--muted, #c9b89e);color:var(--bg, #000)}
+
+ .dwm-submit:disabled{opacity:0.5;cursor:default}
+ .dwm-status{margin-top:14px;font-size:11px;letter-spacing:0.16em;text-transform:uppercase;text-align:center;display:none}
+ .dwm-status.ok{color:var(--sage);display:block}
+ .dwm-status.err{color:#e08070;display:block}
+ /* contact-options */
+ .dwm-options{display:flex;flex-direction:column;gap:10px}
+ .dwm-option{display:flex;align-items:center;gap:14px;padding:18px 20px;background:transparent;border:1px solid var(--rule);color:var(--fg);font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:600;cursor:pointer;text-decoration:none;transition:all 0.2s;text-align:left;width:100%}
+ .dwm-option:hover{border-color:var(--gold);color:var(--gold)}
+ .dwm-option svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:1.5;flex-shrink:0}
+ .dwm-option .lbl{flex:1;display:block}
+ .dwm-option .val{display:block;font-size:10px;letter-spacing:0.14em;text-transform:none;font-weight:500;opacity:0.65;margin-top:4px}
+
+/* graphics-loop pass 5: serif hero typography + nav legibility */
+.cinema h1, h1.headline { font-family: 'Cormorant Garamond', Georgia, serif; font-weight: 500; letter-spacing: -0.01em }
+.h-link { font-size: 13px; letter-spacing: 0.06em }
+
+/* graphics-loop pass 11: Ideas rails */
+#ideas { padding: 56px 24px 24px; max-width:1600px; margin:0 auto }
+#ideas .rail-block { margin-bottom:48px }
+#ideas .rail-eyebrow { font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:var(--muted, #888); margin-bottom:8px }
+#ideas .rail-title { font-size:clamp(22px,2.6vw,32px); font-weight:500; letter-spacing:-0.01em; margin:0 0 18px; color:var(--ink,#fff) }
+#ideas .rail { display:flex; gap:14px; overflow-x:auto; scroll-snap-type:x mandatory; padding-bottom:8px; scrollbar-width:none }
+#ideas .rail::-webkit-scrollbar { display:none }
+#ideas .rail-card { flex:0 0 240px; scroll-snap-align:start; cursor:pointer; transition:transform 0.2s }
+#ideas .rail-card:hover { transform:translateY(-3px) }
+#ideas .rail-card img { width:100%; aspect-ratio:1; object-fit:cover; display:block; background:var(--card,#222) }
+#ideas .rail-card .rc-title { font-size:13px; font-weight:500; color:var(--paper,#fff); padding:8px 0 2px; line-height:1.3 }
+#ideas .rail-card .rc-meta { font-size:11px; color:var(--muted,#888); letter-spacing:0.06em }
+@media (max-width:720px) { #ideas .rail-card { flex:0 0 160px } }
+</style>
+
+<!-- Contact options modal -->
+<div class="dwm" id="dwmContact" onclick="if(event.target===this)dwmClose('Contact')">
+ <div class="dwm-box" style="max-width:440px">
+ <button class="dwm-close" onclick="dwmClose('Contact')" aria-label="Close">×</button>
+ <h3>Contact</h3>
+ <p class="sub" id="dwmContactSub">Three ways to reach us</p>
+ <div class="dwm-options">
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Inquiry')">
+ <svg viewBox="0 0 24 24"><path d="M4 4h16v12H7l-3 3z"/></svg>
+ <span class="lbl">Send an Inquiry<span class="val">Project name · scope · all the details</span></span>
+ </button>
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Sample')">
+ <svg viewBox="0 0 24 24"><rect x="4" y="6" width="16" height="14"/><path d="M4 10h16M9 6V3h6v3"/></svg>
+ <span class="lbl">Request a Sample<span class="val">Memo sample · ships free · 3–5 business days</span></span>
+ </button>
+ <a class="dwm-option" id="dwmContactEmailLink" href="mailto:info@silkwallpaper.com">
+ <svg viewBox="0 0 24 24"><path d="M3 6h18v12H3z"/><path d="M3 6l9 7 9-7"/></svg>
+ <span class="lbl">Email Us<span class="val" id="dwmContactEmailLabel">info@silkwallpaper.com</span></span>
+ </a>
+ </div>
+ </div>
+</div>
+
+<!-- Inquiry modal -->
+<div class="dwm" id="dwmInquiry" onclick="if(event.target===this)dwmClose('Inquiry')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Inquiry')" aria-label="Close">×</button>
+ <h3>Project Inquiry</h3>
+ <p class="sub">We'll respond within one business day</p>
+ <form id="dwmInquiryForm" onsubmit="return dwmSubmit(event,'inquiry')">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Phone</label><input type="tel" name="phone"></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Project Name</label><input type="text" name="projectName" required></div>
+ <div class="dwm-field"><label>Project Scope</label><textarea name="projectScope" placeholder="Square footage · room count · timeline" required></textarea></div>
+ <div class="dwm-field"><label>Additional Information</label><textarea name="message" placeholder="Patterns of interest, finish preferences, budget"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Inquiry</button>
+ <div class="dwm-status" id="dwmInquiryStatus"></div>
+ </form>
+ </div>
+</div>
+
+<!-- Sample-request modal -->
+<div class="dwm" id="dwmSample" onclick="if(event.target===this)dwmClose('Sample')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Sample')" aria-label="Close">×</button>
+ <h3>Request a Sample</h3>
+ <p class="sub">Memo sample · ships free · 3–5 business days</p>
+ <div class="dwm-preview" id="dwmSamplePreview" style="display:none">
+ <img id="dwmSampleImg" src="" alt="">
+ <div><div class="pn" id="dwmSampleName"></div><div class="pc" id="dwmSampleSku"></div></div>
+ </div>
+ <form id="dwmSampleForm" onsubmit="return dwmSubmit(event,'sample')">
+ <input type="hidden" name="sku" id="dwmSampleSkuInput">
+ <input type="hidden" name="title" id="dwmSampleTitleInput">
+ <input type="hidden" name="image_url" id="dwmSampleImageInput">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Shipping Address</label><input type="text" name="address" placeholder="Street" required></div>
+ <div class="dwm-field" style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:8px">
+ <input type="text" name="city" placeholder="City" required>
+ <input type="text" name="state" placeholder="State" required>
+ <input type="text" name="zip" placeholder="ZIP" required>
+ </div>
+ <div class="dwm-field"><label>Notes (optional)</label><textarea name="message" placeholder="Project, sq.ft., timing"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Sample Request</button>
+ <div class="dwm-status" id="dwmSampleStatus"></div>
+ </form>
+ </div>
+</div>
+
+<script>
+ // Universal modal control
+ function dwmOpen(name, opts){
+ document.getElementById('dwm' + name).classList.add('open');
+ document.body.style.overflow = 'hidden';
+ try { if (typeof gtag === 'function') gtag('event', 'modal_open', { modal_name: name, sku: (opts && opts.sku) || '' }); } catch(_){}
+ if (name === 'Sample' && opts) {
+ document.getElementById('dwmSamplePreview').style.display = 'flex';
+ document.getElementById('dwmSampleImg').src = opts.image_url || '';
+ document.getElementById('dwmSampleName').textContent = opts.title || '';
+ document.getElementById('dwmSampleSku').textContent = opts.sku || '';
+ document.getElementById('dwmSampleSkuInput').value = opts.sku || '';
+ document.getElementById('dwmSampleTitleInput').value = opts.title || '';
+ document.getElementById('dwmSampleImageInput').value = opts.image_url || '';
+ } else if (name === 'Sample') {
+ document.getElementById('dwmSamplePreview').style.display = 'none';
+ document.getElementById('dwmSampleSkuInput').value = '';
+ document.getElementById('dwmSampleTitleInput').value = '';
+ document.getElementById('dwmSampleImageInput').value = '';
+ }
+ }
+ function dwmClose(name){ document.getElementById('dwm'+name).classList.remove('open'); document.body.style.overflow=''; }
+
+ // Universal form submit — POSTs to /api/send-{kind}
+ async function dwmSubmit(e, kind) {
+ e.preventDefault();
+ const form = e.target;
+ const data = Object.fromEntries(new FormData(form).entries());
+ const btn = form.querySelector('.dwm-submit');
+ const status = form.querySelector('.dwm-status');
+ btn.disabled = true; const orig = btn.textContent; btn.textContent = 'Sending…';
+ status.className = 'dwm-status';
+ try {
+ const r = await fetch('/api/send-' + kind, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data) });
+ if (!r.ok) throw new Error('HTTP '+r.status);
+ const j = await r.json();
+ try {
+ if (typeof gtag === 'function') {
+ if (kind === 'sample') gtag('event', 'memo_request', { sku: data.sku || '', title: data.title || '' });
+ else gtag('event', 'lead', { form_type: kind });
+ }
+ } catch(_){}
+ status.textContent = kind === 'sample' ? 'Sample request sent — ships within 3–5 business days' : 'Inquiry sent — we respond within 1 business day';
+ status.className = 'dwm-status ok';
+ btn.textContent = 'Sent ✓';
+ setTimeout(() => { dwmClose(kind === 'sample' ? 'Sample' : 'Inquiry'); btn.disabled = false; btn.textContent = orig; status.className = 'dwm-status'; form.reset(); }, 2800);
+ } catch (err) {
+ status.textContent = 'Send failed — please email info@designerwallcoverings.com';
+ status.className = 'dwm-status err';
+ btn.disabled = false; btn.textContent = orig;
+ }
+ return false;
+ }
+
+ // Wire up "Memo samples ship free" badge in hero to open sample modal directly
+ document.querySelectorAll('.hero .badge, [data-open-sample]').forEach(el => {
+ el.style.cursor = 'pointer';
+ el.addEventListener('click', () => dwmOpen('Sample'));
+ });
+
+ // Wire nav "Trade" link to inquiry modal
+ document.querySelectorAll('nav a[href*="designerwallcoverings.com"]').forEach(a => {
+ a.removeAttribute('target');
+ a.removeAttribute('href');
+ a.style.cursor = 'pointer';
+ a.addEventListener('click', e => { e.preventDefault(); dwmOpen('Contact'); });
+ });
+
+ // Override product card clicks — open Sample modal pre-filled instead of redirecting to DW
+ document.addEventListener('click', e => {
+ const card = e.target.closest('.card, .rail-card');
+ if (!card) return;
+ if (card.tagName === 'A' && (card.getAttribute('href') || '').startsWith('/sample/')) {
+ e.preventDefault();
+ const img = card.querySelector('img');
+ const pat = card.querySelector('.pat');
+ const sku = card.getAttribute('href').replace('/sample/', '');
+ dwmOpen('Sample', {
+ sku,
+ title: pat ? pat.textContent.trim() : '',
+ image_url: img ? img.src : ''
+ });
+ }
+ }, true);
+// DW STANDARD: hero pulls from products.json[0].image_url so dw-collections-viewer
+// drag-drop / hero-pin updates the live front page on next deploy.
+// Always upgrades to the largest Shopify variant (strips _NxN size suffix) so
+// the full-page hero stays sharp.
+// DW STANDARD: hero rotates through top-20 NEWEST products on each visit
+// (novasuede.com pattern). Fetches /api/products?limit=100, sorts by created_at desc,
+// takes the freshest 20, picks one per visit via localStorage counter.
+// Falls back to static /hero-bg.jpg on any error.
+(async function rotateHero() {
+ try {
+ const r = await fetch('/api/products?limit=100');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || [])
+ .filter(p => p && p.image_url && /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(p.image_url))
+ .sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')))
+ .slice(0, 20);
+ if (!items.length) return;
+ let visits = 0;
+ try {
+ visits = parseInt(localStorage.getItem('hero_visits') || '0', 10) || 0;
+ localStorage.setItem('hero_visits', String(visits + 1));
+ } catch(e) {}
+ const pick = items[visits % items.length];
+ // Strip _NxN size suffix to get the largest CDN variant
+ const url = pick.image_url.replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ const bg = document.getElementById('heroBg') || document.getElementById('cinemaBg');
+ if (bg) bg.style.backgroundImage = "url('" + url + "')";
+ } catch (e) {}
+})();
+
+
+// DW STANDARD sort-select wire-up (added by fleet patcher)
+(function(){
+ const sortSel = document.getElementById('sortSelect');
+ if (!sortSel) return;
+ const KEY = location.hostname.replace(/\./g, '_') + '_sort';
+ const saved = localStorage.getItem(KEY);
+ if (saved) { try { sortSel.value = saved; } catch(e){} }
+ if (typeof state !== 'undefined') state.sort = sortSel.value;
+ sortSel.addEventListener('change', e => {
+ const v = e.target.value;
+ try { localStorage.setItem(KEY, v); } catch(e){}
+ if (typeof state !== 'undefined') state.sort = v;
+ if (typeof resetGrid === 'function') resetGrid();
+ else location.reload();
+ });
+})();
+
+// graphics-loop pass 11: hydrate Ideas rails from /api/products
+(async function hydrateIdeas() {
+ try {
+ const r = await fetch('/api/products?limit=80');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || []).filter(p => p && p.image_url);
+ if (!items.length) return;
+ const sorted = [...items].sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')));
+ const newest = sorted.slice(0, 12);
+ // Curated = deterministic offset slice (every 5th item starting at idx 3) for variety
+ const curated = items.filter((_, i) => i % 5 === 3).slice(0, 12);
+ function card(p) {
+ const url = (p.image_url || '').replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ return '<a class="rail-card" href="/sample/' + encodeURIComponent(p.handle || p.sku) + '">' +
+ '<img src="' + url + '" alt="' + (p.title || '').replace(/"/g, '"') + '" loading="lazy">' +
+ '<div class="rc-title">' + ((p.title || '').split('|')[0] || '').trim() + '</div>' +
+ '<div class="rc-meta">' + (p.vendor || '') + '</div>' +
+ '</a>';
+ }
+ const r1 = document.getElementById('railNewest');
+ const r2 = document.getElementById('railCurated');
+ if (r1) r1.innerHTML = newest.map(card).join('');
+ if (r2) r2.innerHTML = curated.map(card).join('');
+ } catch(e) {}
+})();
+</script>
+
+<script>
+/* R4 — Alternating hero modes (huge / four-square). Server-side default is huge (HTML attr).
+ Client-only flip starts on visit 2. Both modes share the same .cinema min-height (CLS-free).
+ Crawlers without JS always see huge. */
+(function(){
+ try {
+ var k='dw-hero-mode';
+ var prev=parseInt(localStorage.getItem(k)||'0',10);
+ var next=(prev+1)%2;
+ localStorage.setItem(k,String(next));
+ if (prev>0) {
+ var mode = next===0 ? 'huge' : 'four-square';
+ document.documentElement.dataset.heroMode = mode;
+ if (mode === 'four-square') {
+ fetch('/api/products?limit=4&page=1').then(r=>r.ok?r.json():null).then(j=>{
+ if (!j || !j.items || j.items.length < 4) {
+ document.documentElement.dataset.heroMode = 'huge';
+ console.warn('dw-hero-stale: insufficient items for four-square, falling back to huge');
+ return;
+ }
+ var grid = document.getElementById('heroGrid');
+ if (!grid) return;
+ j.items.slice(0,4).forEach(function(it){
+ var d = document.createElement('div');
+ d.className = 'cell';
+ var img = (it.image_url || it.image || '/hero-bg.jpg');
+ d.style.backgroundImage = "url('" + img + "')";
+ grid.appendChild(d);
+ });
+ }).catch(function(){
+ document.documentElement.dataset.heroMode = 'huge';
+ });
+ }
+ }
+ } catch(e) { /* no-op: keep server default */ }
+})();
+</script>
+</body>
+</html>
diff --git a/public/index.html.bak.nspropagate.1778203385192 b/public/index.html.bak.nspropagate.1778203385192
new file mode 100644
index 0000000..81e1d6f
--- /dev/null
+++ b/public/index.html.bak.nspropagate.1778203385192
@@ -0,0 +1,957 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<!-- Google tag (gtag.js) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=G-CNTJNLTEW8"></script>
+<script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+ gtag('config', 'G-CNTJNLTEW8');
+</script>
+
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Atelier Couture — SILK — IN BLACK.</title>
+<meta name="description" content="Atelier Couture is a curated atelier dedicated entirely to silk. We do one thing — and we do it carefully.">
+<meta name="theme-color" content="#FFF">
+<link rel="canonical" href="https://silkwallpaper.com/">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
+<link rel="preload" as="image" href="/hero-bg.jpg" fetchpriority="high">
+<script type="application/ld+json">
+{
+ "@context": "https://schema.org",
+ "@type": "Organization",
+ "name": "Atelier Couture",
+ "url": "https://silkwallpaper.com/",
+ "email": "info@silkwallpaper.com"
+}
+</script>
+<script src="/zd-loader.js" defer></script>
+<style>
+/* Atelier Couture · Saint Laurent template — pure monochrome, sharp uppercase, zero ornament */
+:root {
+ --bg: #000;
+ --paper: #FFF;
+ --muted: #888;
+ --line: rgba(255,255,255,0.20);
+ --accent: #FFF;
+ --bg-soft: #0a0a0a;
+ --cols: 6;
+}
+html[data-theme="light"] {
+ --bg: #FFF;
+ --paper: #000;
+ --muted: #666;
+ --line: rgba(0,0,0,0.20);
+ --accent: #000;
+ --bg-soft: #F5F5F5;
+}
+* { margin:0; padding:0; box-sizing:border-box }
+html { scroll-behavior:smooth }
+body { font-family:'Inter', 'Helvetica Neue', sans-serif; color:var(--paper); -webkit-font-smoothing:antialiased; background:var(--bg); min-height:100vh; overflow-x:hidden }
+/* DW-FASHION-TEMPLATE-OVERRIDE */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', Georgia, serif !important; font-weight:900 !important; text-transform:uppercase !important;letter-spacing:0.04em !important; }
+/* Saint Laurent: heavy uppercase headlines, no italic serif accent */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', 'Helvetica Neue', sans-serif !important; font-weight:900 !important; font-style:normal !important; letter-spacing:0.04em !important; text-transform:uppercase !important }
+
+/* ===== Auto-hide header ===== */
+header { position:fixed; top:0; left:0; right:0; display:flex; justify-content:space-between; align-items:center; padding:22px 32px; z-index:100; background:rgba(0,0,0,0.55); backdrop-filter:blur(10px); border-bottom:1px solid var(--line); transition:transform .3s }
+.h-link { display:flex; align-items:center; gap:10px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); text-decoration:none; cursor:pointer; background:transparent; border:0 }
+.h-link:hover { opacity:0.7 }
+.h-icon { width:18px; height:18px; stroke:currentColor; fill:none; stroke-width:1.5 }
+
+/* ===== Cinema hero ===== */
+.cinema { position:relative; height:100vh; width:100%; overflow:hidden }
+.cinema-bg { position:absolute; inset:0; background-size:cover; background-position:center; background-image:url('/hero-bg.jpg') }
+.cinema-bg::after { content:""; position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.75) 100%) }
+.corner-mark { position:absolute; top:24px; left:32px; font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:#fff; text-shadow:0 1px 3px rgba(0,0,0,0.55), 0 0 1px rgba(0,0,0,0.4); z-index:5 }
+.corner-mark::before { content:"◆ "; margin-right:6px }
+.center-mark { position:absolute; top:clamp(80px,12vh,140px); left:50%; transform:translateX(-50%); font-size:clamp(28px,4vw,56px); font-weight:500; letter-spacing:0.18em; text-transform:uppercase; text-align:center; color:#fff; text-shadow:0 2px 8px rgba(0,0,0,0.65), 0 0 1px rgba(0,0,0,0.5); z-index:5 }
+.center-mark .tm { font-size:14px; vertical-align:super; opacity:0.7; margin-left:0.2em }
+.center-mark .sub { display:block; font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400; font-size:0.4em; letter-spacing:0.04em; text-transform:none; margin-top:14px; opacity:0.85 }
+.meta-line { position:absolute; bottom:32px; left:32px; font-size:10px; letter-spacing:0.28em; text-transform:uppercase; font-weight:700; color:#fff; z-index:5; opacity:0.85 }
+.meta-line .num { font-size:12px; display:block; margin-top:4px; letter-spacing:0.05em }
+.enter { position:absolute; bottom:32px; right:32px; display:flex; align-items:center; gap:14px; font-size:11px; letter-spacing:0.36em; text-transform:uppercase; font-weight:700; color:#fff; text-decoration:none; padding-bottom:6px; border-bottom:1px solid #fff; transition:gap 0.25s; z-index:5 }
+.enter:hover { gap:22px }
+.enter svg { width:24px; height:12px }
+
+/* ===== Section ===== */
+.section { padding:96px 32px }
+.section-header { display:flex; justify-content:space-between; align-items:flex-end; flex-wrap:wrap; gap:24px; margin-bottom:32px; padding-bottom:24px; border-bottom:1px solid var(--line) }
+.section-title { font-size:clamp(36px,5vw,64px); font-weight:300; letter-spacing:-0.02em; line-height:1 }
+.section-title .accent { color: var(--accent); font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400 }
+.section-eyebrow { font-size:10px; letter-spacing:0.4em; text-transform:uppercase; font-weight:700; color:var(--muted); margin-bottom:16px }
+.section-meta { font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:600; color:var(--muted); text-align:right; line-height:1.6 }
+
+/* ===== Filter bar ===== */
+.filters { display:flex; gap:8px; align-items:center; margin-bottom:18px; flex-wrap:wrap }
+.chip { padding:8px 16px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); background:transparent; border:1px solid var(--line); cursor:pointer; transition:all 0.2s; font-family:inherit }
+.chip:hover { border-color:var(--paper) }
+.chip.active { background:var(--paper); color:var(--bg); border-color:var(--paper) }
+.search { flex:0 1 280px; margin-left:auto; display:flex; align-items:center; gap:10px; border-bottom:1px solid var(--line); padding:6px 0 }
+.search input { flex:1; background:transparent; border:0; color:var(--paper); font-family:inherit; font-size:13px; outline:none; letter-spacing:0.04em }
+.search input::placeholder { color:var(--muted) }
+.search svg { width:16px; height:16px; stroke:var(--muted); fill:none; stroke-width:1.5 }
+
+/* ===== Grid-density slider ===== */
+.density { display:flex; align-items:center; gap:14px; padding:6px 0; margin-bottom:24px }
+.density label { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--muted) }
+.density input[type=range] { flex:1; max-width:240px; -webkit-appearance:none; appearance:none; height:1px; background:var(--line); outline:none }
+.density input[type=range]::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50% }
+.density input[type=range]::-moz-range-thumb { width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50%; border:0 }
+.density .ct { font-size:11px; color:var(--muted); letter-spacing:0.18em; text-transform:uppercase; font-weight:600; min-width:80px }
+
+/* ===== Stats line ===== */
+.stat-line { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:600; color:var(--muted); margin-bottom:24px }
+
+/* ===== Product grid (novasuede pattern: flush grid + slide-up overlay) ===== */
+.grid { display:grid; grid-template-columns:repeat(var(--cols), 1fr); gap:0 }
+.card { position:relative; aspect-ratio:1/1.15; cursor:pointer; overflow:hidden; border:1px solid var(--bg); transition:transform 0.4s cubic-bezier(0.2,0.8,0.2,1); background:var(--bg-soft) }
+.card:hover { transform:scale(1.04); z-index:5 }
+.card img { width:100%; height:100%; object-fit:cover; display:block; transition:transform 0.4s ease }
+.card .overlay { position:absolute; left:0; right:0; bottom:0; padding:20px 16px 14px; background:linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.85) 100%); transform:translateY(48%); opacity:0.85; transition:transform 0.3s ease, opacity 0.3s ease }
+.card:hover .overlay { transform:translateY(0); opacity:1 }
+.card .pat { font-size:13px; font-weight:600; letter-spacing:0.02em; color:#fff; line-height:1.25; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }
+.card .ven { font-size:9px; letter-spacing:0.18em; text-transform:uppercase; color:#fff; opacity:0.7; font-weight:500; margin-top:4px }
+.card .actions { margin-top:10px; display:flex; gap:6px }
+.card .sample-btn { flex:1; padding:7px 10px; background:#fff; color:#000; font-family:inherit; font-size:10px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; border:0; cursor:pointer; text-align:center; transition:all 0.2s }
+.card .sample-btn:hover { background:var(--accent); color:#fff }
+
+@media (max-width:1024px) { .grid { grid-template-columns:repeat(min(var(--cols), 4), 1fr) } }
+@media (max-width:720px) { .grid { grid-template-columns:repeat(2, 1fr) } .density { display:none } }
+
+.sentinel { height:1px }
+.loading { text-align:center; color:var(--muted); padding:32px; font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700 }
+
+/* ===== Footer ===== */
+footer { padding:64px 32px 32px; border-top:1px solid var(--line); margin-top:32px }
+.footer-grid { max-width:1400px; margin:0 auto; display:grid; grid-template-columns:2fr 1fr 1fr 1fr; gap:48px; margin-bottom:48px }
+.footer-brand { font-size:24px; font-weight:300; letter-spacing:0.32em; text-transform:uppercase; margin-bottom:12px }
+.footer-text { font-size:13px; line-height:1.6; color:var(--muted); max-width:380px }
+.footer-col h4 { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--paper); margin-bottom:18px }
+.footer-col a { display:block; font-size:13px; color:var(--muted); text-decoration:none; margin-bottom:8px; transition:color 0.2s; cursor:pointer; background:transparent; border:0; font-family:inherit; padding:0; text-align:left }
+.footer-col a:hover { color:var(--accent) }
+.footer-bottom { max-width:1400px; margin:0 auto; padding-top:24px; border-top:1px solid var(--line); display:flex; justify-content:space-between; flex-wrap:wrap; gap:16px; font-size:11px; letter-spacing:0.18em; text-transform:uppercase; color:var(--muted) }
+
+@media (max-width:720px) {
+ .corner-mark, .meta-line, .enter { font-size:10px }
+ .center-mark { font-size:24px; letter-spacing:0.12em; top:clamp(60px,9vh,90px) }
+ .section, footer { padding-left:20px; padding-right:20px }
+ .footer-grid { grid-template-columns:1fr; gap:32px }
+ .search { margin-left:0; flex:1 1 100% }
+}
+
+.theme-toggle { background:transparent; border:1px solid var(--line); width:32px; height:32px; cursor:pointer; color:var(--paper); display:inline-flex; align-items:center; justify-content:center; font-size:14px; line-height:1; transition:all .15s; padding:0 }
+.theme-toggle:hover { border-color:var(--accent); color:var(--accent) }
+
+/* ===== WCAG 2.4.7 — keyboard focus indicators ===== */
+/* Strictly :focus-visible so mouse clicks do NOT show the ring */
+a:focus-visible,
+button:focus-visible,
+input:focus-visible,
+select:focus-visible,
+textarea:focus-visible,
+[tabindex]:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+</style>
+<script>
+(function(){ try { var t = localStorage.getItem('silk_theme') || 'dark'; document.documentElement.dataset.theme = t; } catch(e){} })();
+</script>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600;700&display=swap">
+
+<meta property="og:type" content="website">
+<meta property="og:url" content="https://silkwallpaper.com/">
+<meta property="og:title" content="Silk Wallpaper">
+<meta property="og:description" content="Silk Wallpaper — Pure silk wallcoverings — luxe and lustrous.">
+<meta property="og:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<meta property="og:site_name" content="Silk Wallpaper">
+<meta property="article:publisher" content="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings">
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:title" content="Silk Wallpaper">
+<meta name="twitter:description" content="Silk Wallpaper — Pure silk wallcoverings — luxe and lustrous.">
+<meta name="twitter:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<!-- graphics-loop pass 13: footer + fb -->
+</head>
+<body>
+<style id="ns-header-modal-styles">
+ .ns-header { position: sticky; top: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; padding: 18px 36px; background: rgba(10,10,10,0.92) !important; backdrop-filter: blur(8px); border-bottom: 1px solid rgba(245,241,232,0.06); }
+ .ns-brand { display: inline-flex; align-items: center; gap: 14px; text-decoration: none; }
+ .ns-dot { width: 28px; height: 28px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, #a98b5b 0%, #5d4c32 60%, #332a1b 100%); box-shadow: 0 0 0 1px rgba(245,241,232,0.10), 0 2px 12px rgba(180,90,60,0.30); flex-shrink: 0; }
+ .ns-name { font-family: -apple-system, "SF Pro Display", "Helvetica Neue", system-ui, sans-serif; font-size: 18px !important; font-weight: 300 !important; letter-spacing: 0.02em !important; color: #f5f1e8 !important; text-transform: none !important; margin: 0 !important; }
+ .ns-name sup { font-size: 0.55em; font-weight: 300; margin-left: 1px; }
+ .ns-nav { display: flex; gap: 32px; align-items: center; list-style: none; padding: 0; margin: 0; }
+ .ns-nav a, .ns-nav button { font-family: -apple-system, "SF Pro Text", system-ui, sans-serif; font-size: 15px !important; font-weight: 400 !important; color: #f5f1e8 !important; text-decoration: none !important; background: none !important; border: 0 !important; cursor: pointer; padding: 0 !important; transition: color 200ms; letter-spacing: 0 !important; text-transform: none !important; }
+ .ns-nav a:hover, .ns-nav button:hover { color: #ffffff !important; }
+ @media (max-width: 820px) { .ns-header { padding: 14px 18px; } .ns-nav { gap: 16px; } .ns-nav a, .ns-nav button { font-size: 13px !important; } }
+ @media (max-width: 760px) {
+ .ns-header { flex-wrap: wrap; }
+ .ns-nav { display: none !important; flex-basis: 100%; flex-direction: column; align-items: flex-start; gap: 6px; padding: 14px 0 4px; }
+ .ns-nav.open { display: flex !important; }
+ .ns-nav a, .ns-nav button { padding: 10px 0 !important; border-bottom: 1px solid rgba(245,241,232,0.08); width: 100%; text-align: left; }
+ .ns-nav a:nth-child(n+5), .ns-nav button:nth-child(n+5) { display: block !important; }
+ .ns-header .mobile-toggle { display: flex !important; }
+ }
+ @media (min-width: 761px) {
+ .ns-header .mobile-toggle { display: none !important; }
+ }
+ .ns-modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.78); z-index: 200; align-items: center; justify-content: center; padding: 24px; backdrop-filter: blur(6px); }
+ .ns-modal.show { display: flex; }
+ .ns-modal-card { max-width: 460px; width: 100%; background: #141414 !important; border: 1px solid rgba(245,241,232,0.14); border-radius: 6px; padding: 28px 30px 30px; position: relative; }
+ .ns-modal-card .close-x { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; background: none; border: 1px solid rgba(245,241,232,0.18); color: rgba(245,241,232,0.6) !important; border-radius: 50%; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; }
+ .ns-modal-card .close-x:hover { color: #ffffff !important; border-color: #ffffff; }
+ .ns-modal-card h3 { font-family: -apple-system, "SF Pro Display", system-ui; font-weight: 300 !important; font-size: 22px !important; margin: 0 0 6px !important; color: #f5f1e8 !important; }
+ .ns-modal-card .sub { color: rgba(245,241,232,0.65) !important; font-size: 13px; margin: 0 0 22px; }
+ .ns-actions { display: flex; flex-direction: column; gap: 10px; }
+ .ns-action { display: flex; align-items: center; gap: 14px; padding: 14px 16px; background: rgba(245,241,232,0.04) !important; border: 1px solid rgba(245,241,232,0.12) !important; color: #f5f1e8 !important; text-decoration: none !important; cursor: pointer; transition: all 200ms; font: inherit; text-align: left; }
+ .ns-action:hover { background: rgba(245,241,232,0.08) !important; border-color: #d4b683 !important; }
+ .ns-action .ico { width: 32px; height: 32px; border-radius: 50%; background: rgba(212,182,131,0.12); display: flex; align-items: center; justify-content: center; font-size: 15px; flex-shrink: 0; }
+ .ns-action .txt { flex: 1; }
+ .ns-action .txt b { display: block; font-weight: 500; font-size: 14px; color: #f5f1e8 !important; }
+ .ns-action .txt span { font-size: 12px; color: rgba(245,241,232,0.55) !important; }
+
+ /* ─── FINAL CONTRAST PASS — kill remaining gold-solid + white-island defects ─── */
+ /* Replace all gold-fill accents with off-white (Gucci-aesthetic, no DW gold solids) */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active,
+ .drapery-btn, .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover,
+ .drapery-btn:hover, .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ /* Gold accents stay only as edge color (links, glyphs) — not fills */
+ a { color: #d4b683; }
+ a:hover { color: #f5d97a; }
+ /* Authorized-dealer footer line — small caps tracked, muted ink, no gold pill */
+ .authorized, .authorized-dealer p {
+ background: transparent !important;
+ color: rgba(245,241,232,0.5) !important;
+ font-size: 10px !important;
+ letter-spacing: 0.22em !important;
+ text-transform: uppercase !important;
+ padding: 0 !important;
+ border: none !important;
+ margin-top: 14px !important;
+ }
+ /* Kill any straggler white bg in info-card / sample-related cards */
+ .info-card, .info-card-link, .care-highlight, .feature-card, .spec-item {
+ background-color: rgba(245,241,232,0.04) !important;
+ border-color: rgba(245,241,232,0.12) !important;
+ }
+ /* Ensure modal content stays readable — Steve's earlier sweep made it #141414 with white text */
+ .modal-content { background: #141414 !important; color: #f5f1e8 !important; }
+ .modal-content * { color: #f5f1e8 !important; }
+ .modal-content input, .modal-content textarea, .modal-content select {
+ background: rgba(245,241,232,0.05) !important;
+ color: #f5f1e8 !important;
+ border-color: rgba(245,241,232,0.2) !important;
+ }
+ .modal-content label { color: rgba(245,241,232,0.6) !important; }
+ /* Force the wide-tracked NOVASUEDE wordmark to stay white */
+ .ns-wordmark, .ns-wordmark sup { color: #f5f1e8 !important; }
+
+ /* ─── Modal-internal contrast (post-QA defects) ─── */
+ /* Backdrop covers full viewport */
+ .modal { width: 100vw !important; height: 100vh !important; left: 0 !important; top: 0 !important; background: rgba(0, 0, 0, 0.78) !important; backdrop-filter: blur(6px); }
+ /* Chip — flip from white-on-white island to dark card */
+ .request-chip {
+ background: rgba(245, 241, 232, 0.06) !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.18) !important;
+ border-radius: 4px !important;
+ }
+ .request-chip * { color: #f5f1e8 !important; }
+ .request-chip .chip-swatch {
+ /* swatch dot ALWAYS displays the inline-style hex from JS — fight any sweep */
+ border: 1px solid rgba(0, 0, 0, 0.4) !important;
+ }
+ /* + Add to request — hairline white-border on transparent (no gold) */
+ .add-to-request-btn {
+ background: transparent !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.6) !important;
+ border-radius: 0 !important;
+ padding: 12px 22px !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 400 !important;
+ }
+ .add-to-request-btn:hover {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ /* Hex caption visible */
+ .preview-hex, #colorPreviewHex {
+ display: block !important;
+ color: rgba(245, 241, 232, 0.55) !important;
+ font-family: ui-monospace, "SF Mono", Menlo, monospace !important;
+ font-size: 11px !important;
+ margin-top: 4px !important;
+ letter-spacing: 0.04em !important;
+ text-transform: lowercase !important;
+ }
+ /* Color preview name — stays single, prominent */
+ #colorPreviewName {
+ color: #f5f1e8 !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 500 !important;
+ display: block !important;
+ margin-top: 12px !important;
+ }
+
+ /* ─── Mobile hamburger nav (replaces the overflow at <760px) ─── */
+ @media (max-width: 760px) {
+ header .nav-container { flex-wrap: wrap; }
+ header nav { display: none; flex-basis: 100%; flex-direction: column; gap: 6px; padding: 16px 0 6px; }
+ header nav.open { display: flex; }
+ header nav a { padding: 10px 0; border-bottom: 1px solid rgba(245,241,232,0.1); }
+ header .mobile-toggle {
+ display: flex !important;
+ flex-direction: column;
+ gap: 5px;
+ background: transparent;
+ border: 0;
+ cursor: pointer;
+ padding: 8px;
+ }
+ header .mobile-toggle span {
+ width: 22px; height: 1.5px;
+ background: #f5f1e8;
+ display: block;
+ }
+ }
+ @media (min-width: 761px) {
+ header .mobile-toggle { display: none !important; }
+ }
+
+ </style>
+<header class="ns-header">
+ <a href="#home" class="ns-brand" aria-label="Atelier Couture home">
+ <span class="ns-dot"></span>
+ <span class="ns-name">Atelier Couture</span>
+ </a>
+ <nav class="ns-nav" id="nsPrimaryNav" aria-label="Primary">
+ <a href="#home">Home</a>
+ <a href="#colors">Colors</a>
+ <a href="#specifications">Specifications</a>
+ <a href="fire-ratings.html">Fire Ratings</a>
+ <a href="faq.html">FAQ</a>
+ <a href="about.html">About</a>
+ <button type="button" onclick="nsContactOpen()">Contact</button>
+ </nav>
+ <button type="button" class="mobile-toggle" aria-label="Toggle menu" aria-controls="nsPrimaryNav" aria-expanded="false" onclick="nsToggleNav(this)">
+ <span></span><span></span><span></span>
+ </button>
+ </header>
+<div class="ns-modal" id="nsContactModal" role="dialog" aria-modal="true" aria-labelledby="nsContactTitle" onclick="if(event.target===this)nsContactClose()">
+ <div class="ns-modal-card">
+ <button type="button" class="close-x" onclick="nsContactClose()" aria-label="Close">×</button>
+ <h3 id="nsContactTitle">How can we help?</h3>
+ <p class="sub">Trade-account replies within one business day.</p>
+ <div class="ns-actions">
+ <a class="ns-action" href="#contact" onclick="nsContactClose()"><span class="ico">📋</span><div class="txt"><b>Get a Quote</b><span>Project specs → priced estimate</span></div></a>
+ <a class="ns-action" href="#contact" onclick="nsContactClose();openModal();"><span class="ico">📦</span><div class="txt"><b>Order a Sample</b><span>Free swatch to your shop</span></div></a>
+ <a class="ns-action" href="mailto:info@novasuede.com?subject=Novasuede%20inquiry"><span class="ico">✉️</span><div class="txt"><b>Email Us</b><span>info@novasuede.com · Designer Wallcoverings</span></div></a>
+ <a class="ns-action" href="tel:888-373-4564"><span class="ico">📞</span><div class="txt"><b>Call Us</b><span>(888) 373-4564 · M–F, 9–5 PT</span></div></a>
+ </div>
+ </div>
+ </div>
+<script>
+ function nsContactOpen() { var m = document.getElementById('nsContactModal'); if (m) m.classList.add('show'); }
+ function nsContactClose() { var m = document.getElementById('nsContactModal'); if (m) m.classList.remove('show'); }
+ function nsToggleNav(btn) {
+ var nav = document.getElementById('nsPrimaryNav');
+ if (!nav) return;
+ var open = nav.classList.toggle('open');
+ btn.setAttribute('aria-expanded', open ? 'true' : 'false');
+ }
+ document.addEventListener('keydown', function(e){ if (e.key === 'Escape') nsContactClose(); });
+</script>
+
+
+
+<header>
+ <button class="h-link" onclick="dwmOpen('Contact')" aria-label="Contact"><svg class="h-icon" viewBox="0 0 24 24"><path d="M3 7h18M3 12h18M3 17h18"/></svg><span>Contact</span></button>
+ <button class="theme-toggle" id="theme-toggle" aria-label="Theme toggle">☾</button>
+</header>
+
+<section class="cinema">
+ <div class="cinema-bg" id="heroBg"></div>
+ <div class="corner-mark">Silken Sheen</div>
+ <div class="center-mark">Silk Wallpaper</div>
+ <div class="meta-line">Silk · Luxe · Sheen · Jacquard<span class="num" id="heroNum"></span></div>
+ <a class="enter" href="#shop">Enter <svg viewBox="0 0 24 12" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M0 6h22M16 1l6 5-6 5"/></svg></a>
+</section>
+
+
+<section class="section" id="ideas">
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">New this week</h2>
+ <div class="rail" id="railNewest"></div>
+ </div>
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">Curated picks</h2>
+ <div class="rail" id="railCurated"></div>
+ </div>
+</section>
+
+<section class="section" id="shop">
+ <div class="section-header">
+ <div>
+ <div class="section-eyebrow">The Collection</div>
+ <h2 class="section-title"><span class="accent" id="totalCount">—</span> Patterns.</h2>
+ </div>
+ <div class="section-meta">Silk · Luxe · Sheen · Jacquard</div>
+ </div>
+
+ <div class="filters" id="facets">
+ <button class="chip active" data-facet="all">All</button>
+ </div>
+
+ <div class="density">
+ <label>Sort</label>
+ <select id="sortSelect" aria-label="Sort products" style="background:transparent;color:inherit;border:1px solid var(--line,rgba(255,255,255,.2));font:inherit;font-size:11px;letter-spacing:.10em;text-transform:uppercase;padding:4px 8px;cursor:pointer;outline:none;margin-right:14px">
+ <option value="newest">Newest</option>
+ <option value="color">Color</option>
+ <option value="style">Style</option>
+ <option value="sku">SKU A→Z</option>
+ <option value="title">Title A→Z</option>
+ <option value="price-asc">Price ↑</option>
+ <option value="price-desc">Price ↓</option>
+ </select>
+ <label>Grid</label>
+ <input type="range" id="densitySlider" min="4" max="12" step="1" value="6" aria-label="Grid columns">
+ <span class="ct" id="densityLabel">6 cols</span>
+ <div class="search">
+ <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5.5-5.5"/></svg>
+ <input type="text" id="searchInput" placeholder="Search by pattern, color…" autocomplete="off">
+ </div>
+ </div>
+
+ <div class="stat-line" id="statLine">Loading…</div>
+ <div class="grid" id="grid"></div>
+ <div class="loading" id="loading">Loading more…</div>
+ <div class="sentinel" id="sentinel"></div>
+</section>
+
+<footer>
+ <div class="footer-grid">
+ <div>
+ <div class="footer-brand">Silk Wallpaper</div>
+ <p class="footer-text">Silk Wallpaper is a specialty silk archive within the Designer Wallcoverings family. Real samples ship free.</p>
+ <p class="footer-text" style="margin-top:14px;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;font-weight:600;color:var(--paper);opacity:0.7"><a href="/about" style="color:inherit;text-decoration:none;border-bottom:1px solid var(--accent)">About →</a></p>
+ </div>
+ <div class="footer-col">
+ <h4>Aesthetic</h4>
+ <div id="footerFacets"></div>
+ </div>
+ <div class="footer-col">
+ <h4>Trade</h4>
+ <button onclick="dwmOpen('Inquiry')">Project Inquiry</button>
+ <button onclick="dwmOpen('Sample')">Request Sample</button>
+ <button onclick="dwmOpen('Contact')">Contact</button>
+ </div>
+
+ <div class="footer-col">
+ <h4>Social</h4>
+ <a class="fb-link" href="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings" target="_blank" rel="noopener">Facebook</a>
+ </div>
+ </div>
+ <div class="footer-bottom">
+ <span>silkwallpaper.com · Atelier Couture</span>
+ <span><a href="mailto:info@silkwallpaper.com" style="color:var(--paper);text-decoration:none;border-bottom:1px solid var(--accent)">info@silkwallpaper.com</a></span>
+ <span id="footerStat"></span>
+ </div>
+</footer>
+
+<script>
+// mini-constellation — 43 niche-stars, current site highlighted in gold
+(function(){
+ const SLUG = "silkwallpaper";
+ const NICHES = [
+ ['silkwallpaper','Silk','mat'],['silkwallcoverings','Silk W/C','mat'],['linenwallpaper','Linen','mat'],
+ ['jutewallpaper','Jute','mat'],['raffiawallcoverings','Raffia W/C','mat'],['raffiawalls','Raffia Walls','mat'],
+ ['corkwallcovering','Cork','mat'],['fabricwallpaper','Fabric','mat'],['textilewallpaper','Textile','mat'],
+ ['metallicwallpaper','Metallic','mat'],['silverleafwallpaper','Silver Leaf','mat'],['vinylwallpaper','Vinyl','mat'],
+ ['micawallpaper','Mica','mat'],['madagascarwallpaper','Madagascar','mat'],['mylarwallpaper','Mylar','mat'],
+ ['suedewallpaper','Suede','mat'],
+ ['1800swallpaper','1800s','dec'],['1890swallpaper','1890s','dec'],['1900swallpaper','1900s','dec'],
+ ['1920swallpaper','1920s','dec'],['1930swallpaper','1930s','dec'],['1940swallpaper','1940s','dec'],
+ ['1950swallpaper','1950s','dec'],['1960swallpaper','1960s','dec'],['1970swallpaper','1970s','dec'],
+ ['1980swallpaper','1980s','dec'],['retrowalls','Retro','dec'],['wallpapersback','W. Back','dec'],
+ ['agedwallpaper','Aged','cra'],['handcraftedwallpaper','Handcrafted','cra'],['museumwallpaper','Museum','cra'],
+ ['restorationwallpaper','Restoration','cra'],['pastelwallpaper','Pastel','cra'],['glitterwalls','Glitter','cra'],
+ ['greenwallcoverings','Green','cra'],['naturalwallcoverings','Natural','cra'],['saloonwallpaper','Saloon','cra'],
+ ['contractwallpaper','Contract','use'],['hotelwallcoverings','Hotel','use'],['hospitalitywallpaper','Hospitality','use'],
+ ['healthcarewallpaper','Healthcare','use'],['restaurantwallpaper','Restaurant','use'],['architecturalwallcoverings','Architectural','use']
+ ];
+ const COLOR = { mat:'#c9b687', dec:'#b86a4a', cra:'#6b8e6f', use:'#4a6b8e' };
+ const W = 1080, H = 200, BANDS = ['mat','dec','cra','use'];
+ const groups = { mat:[], dec:[], cra:[], use:[] };
+ for (const n of NICHES) groups[n[2]].push(n);
+ const svg = document.getElementById('miniConst');
+ if (!svg) return;
+ const bandW = W / BANDS.length;
+ let nodes = '';
+ BANDS.forEach((band, bi) => {
+ const list = groups[band];
+ const cx = bi * bandW + bandW / 2;
+ list.forEach((n, i) => {
+ const t = list.length === 1 ? 0.5 : i / (list.length - 1);
+ // deterministic jitter per slug
+ let h = 0; for (let k = 0; k < n[0].length; k++) h = (h * 31 + n[0].charCodeAt(k)) | 0;
+ const jx = ((h & 0xff) / 255 - 0.5) * (bandW * 0.5);
+ const jy = (((h >> 8) & 0xff) / 255 - 0.5) * 6; // tightened from 14 → 6 to prevent same-band collisions (16 mat nodes / 144px = 9px spacing)
+ const x = cx + jx, y = 28 + t * (H - 56) + jy;
+ const isCurrent = n[0] === SLUG;
+ const r = isCurrent ? 8 : 4;
+ const op = isCurrent ? 1 : 0.55;
+ nodes += `<g class="mc-node" data-slug="${n[0]}" data-label="${n[1]}" style="cursor:pointer">
+ <circle cx="${x}" cy="${y}" r="${r}" fill="url(#dna-${band})" fill-opacity="${op}" stroke="${COLOR[band]}" stroke-opacity="${isCurrent?1:0.7}" stroke-width="${isCurrent?2:1}">
+ <title>${n[1]} — ${n[0]}.com</title>
+ </circle>
+ ${isCurrent ? `<circle cx="${x}" cy="${y}" r="${r+5}" fill="none" stroke="${COLOR[band]}" stroke-opacity="0.4" stroke-width="1"><animate attributeName="r" values="${r+5};${r+12};${r+5}" dur="2.4s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" repeatCount="indefinite"/></circle>` : ''}
+ </g>`;
+ });
+ });
+ // append nodes after defs
+ svg.insertAdjacentHTML('beforeend', nodes);
+ svg.querySelectorAll('.mc-node').forEach(n => {
+ n.addEventListener('mouseenter', () => n.querySelector('circle').setAttribute('fill-opacity', '1'));
+ n.addEventListener('mouseleave', e => {
+ const isCurrent = n.dataset.slug === SLUG;
+ n.querySelector('circle').setAttribute('fill-opacity', isCurrent ? '1' : '0.55');
+ });
+ n.addEventListener('click', () => window.open('https://' + n.dataset.slug + '.com', '_blank'));
+ });
+})();
+</script>
+
+<script>
+const state = { q:'', facet:'all', page:1, pages:1, total:0, loading:false, exhausted:false };
+const LABELS = {"all":"All"};
+
+function escAttr(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
+// Image-URL allowlist: only DW Shopify CDN + designerwallcoverings.com (defends against XSS via crafted products.json)
+function safeImg(u) { return /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(u || '') ? u : '/hero-bg.jpg'; }
+function cardHTML(p) {
+ const eager = state.page === 1;
+ return '<img loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+ + '<div class="overlay">'
+ + '<div class="pat">' + escAttr(p.pattern_name || p.title) + '</div>'
+ + '<div class="ven">' + escAttr((p.vendor || '').replace(/-/g, ' ')) + '</div>'
+ + '<div class="actions">'
+ + '<button class="sample-btn" onclick="event.stopPropagation();dwmOpen(\'Sample\',' + JSON.stringify({sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}).replace(/"/g,'"') + ')">Sample</button>'
+ + '</div></div>';
+}
+
+async function loadFacets() {
+ let f;
+ try {
+ const r = await fetch('/api/facets');
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ f = await r.json();
+ } catch (e) { console.error('loadFacets failed:', e); return; }
+ const el = document.getElementById('facets');
+ for (const [k, v] of Object.entries(f.aesthetics).sort((a,b) => b[1] - a[1])) {
+ const b = document.createElement('button');
+ b.className = 'chip'; b.dataset.facet = k;
+ b.innerHTML = (LABELS[k] || k) + ' <span style="opacity:.55;font-weight:500;margin-left:4px">' + v + '</span>';
+ el.appendChild(b);
+ }
+ el.addEventListener('click', e => {
+ if (e.target.tagName !== 'BUTTON') return;
+ document.querySelectorAll('#facets button').forEach(b => b.classList.remove('active'));
+ e.target.classList.add('active');
+ state.facet = e.target.dataset.facet;
+ resetGrid();
+ });
+ document.getElementById('totalCount').textContent = f.total;
+ document.getElementById('footerStat').textContent = f.total + ' patterns · live archive';
+ // Footer aesthetic links
+ const footerEl = document.getElementById('footerFacets');
+ for (const [k] of Object.entries(f.aesthetics)) {
+ const b = document.createElement('button');
+ b.textContent = LABELS[k] || k;
+ b.onclick = () => { state.facet = k; resetGrid(); document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.facet === k)); document.getElementById('shop').scrollIntoView({behavior:'smooth'}); };
+ footerEl.appendChild(b);
+ }
+}
+
+function resetGrid() {
+ state.page = 1; state.exhausted = false;
+ document.getElementById('grid').innerHTML = '';
+ document.getElementById('loading').textContent = 'Loading…';
+ loadGridPage();
+}
+
+async function loadGridPage() {
+ if (state.loading || state.exhausted) return;
+ state.loading = true;
+ const params = new URLSearchParams({ page:state.page, limit:24 });
+ if (state.q) params.set('q', state.q);
+ if (state.facet !== 'all') params.set('aesthetic', state.facet);
+ let data;
+ try {
+ const r = await fetch('/api/products?' + params);
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ data = await r.json();
+ } catch (e) {
+ console.error('loadGridPage failed:', e);
+ document.getElementById('loading').textContent = '— offline — refresh to retry —';
+ state.loading = false;
+ return;
+ }
+ state.total = data.total; state.pages = data.pages;
+
+ if (state.page === 1) {
+ document.getElementById('statLine').textContent = data.total + ' patterns · ' + (state.facet === 'all' ? 'all aesthetics' : (LABELS[state.facet] || state.facet)) + (state.q ? ' · matching "' + state.q + '"' : '');
+ }
+ const grid = document.getElementById('grid');
+ for (const p of data.items) {
+ const a = document.createElement('a');
+ a.className = 'card'; a.href = '#'; a.onclick = (e) => { e.preventDefault(); dwmOpen('Sample',{sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}); };
+ a.innerHTML = cardHTML(p);
+ grid.appendChild(a);
+ }
+ if (state.page >= state.pages || data.items.length === 0) {
+ state.exhausted = true;
+ document.getElementById('loading').textContent = state.total > 0 ? '— end of archive · ' + state.total + ' patterns —' : '';
+ } else state.page++;
+ state.loading = false;
+}
+
+const io = new IntersectionObserver(es => { for (const e of es) if (e.isIntersecting) loadGridPage(); }, { rootMargin:'600px 0px' });
+io.observe(document.getElementById('sentinel'));
+
+document.getElementById('searchInput').addEventListener('input', e => {
+ state.q = e.target.value.trim();
+ clearTimeout(window._t);
+ window._t = setTimeout(resetGrid, 220);
+});
+
+// Density slider
+const slider = document.getElementById('densitySlider');
+const dlabel = document.getElementById('densityLabel');
+function setDensity(n) {
+ document.documentElement.style.setProperty('--cols', n);
+ dlabel.textContent = n + ' cols';
+ try { localStorage.setItem('silk_theme_density', n); } catch(e){}
+}
+slider.addEventListener('input', e => setDensity(parseInt(e.target.value)));
+const savedDensity = parseInt(localStorage.getItem('silk_theme_density') || '6');
+if (savedDensity >= 4 && savedDensity <= 12) { slider.value = savedDensity; setDensity(savedDensity); }
+
+// Theme toggle
+const tb = document.getElementById('theme-toggle');
+function setTheme(t){ document.documentElement.dataset.theme = t; try { localStorage.setItem('silk_theme', t); } catch(e){} tb.textContent = t === 'dark' ? '☾' : '☀'; }
+setTheme(document.documentElement.dataset.theme || 'dark');
+tb.addEventListener('click', () => setTheme(document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'));
+
+loadFacets();
+loadGridPage();
+</script>
+<!-- ============================================================
+ DW UNIVERSAL CONTACT MODULE — fashion-house UX
+ Inject before </body> in every DW-family sister site.
+ Self-contained: CSS + 4 modals + JS submission handlers.
+ Uses host site's CSS vars (--bg, --fg, --gold, --rule).
+ ============================================================ -->
+<style>
+ /* === MODAL BASE === */
+ .dwm{position:fixed;inset:0;background:rgba(0,0,0,0.78);display:none;align-items:center;justify-content:center;z-index:9999;padding:24px;overflow-y:auto}
+ .dwm.open{display:flex}
+ .dwm-box{background:var(--bg);border:1px solid var(--rule);max-width:520px;width:100%;padding:36px 32px;position:relative;max-height:90vh;overflow-y:auto;animation:dwm-in 0.25s ease}
+ @keyframes dwm-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+ .dwm-box h3{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-weight:400;font-size:32px;line-height:1.05;margin-bottom:6px;color:var(--fg);letter-spacing:-0.01em}
+ .dwm-box .sub{color:var(--muted);font-size:12px;letter-spacing:0.16em;text-transform:uppercase;margin-bottom:24px;font-weight:500}
+ .dwm-close{position:absolute;top:18px;right:18px;background:transparent;border:0;color:var(--muted);font-size:24px;cursor:pointer;width:32px;height:32px;line-height:1;padding:0}
+ .dwm-close:hover{color:var(--gold)}
+ .dwm-preview{display:flex;gap:14px;align-items:center;padding:12px;background:var(--bg-soft);margin-bottom:18px}
+ .dwm-preview img{width:64px;height:64px;object-fit:cover}
+ .dwm-preview .pn{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-size:15px;line-height:1.2;color:var(--fg)}
+ .dwm-preview .pc{font-size:10px;letter-spacing:0.14em;text-transform:uppercase;color:var(--muted);margin-top:4px}
+ .dwm-field{margin-bottom:14px}
+ .dwm-field label{display:block;font-size:10px;letter-spacing:0.18em;text-transform:uppercase;color:var(--muted);margin-bottom:6px;font-weight:500}
+ .dwm-field input,.dwm-field textarea,.dwm-field select{width:100%;border:0;border-bottom:1px solid var(--rule);background:transparent;padding:8px 4px;font-size:14px;font-family:inherit;color:var(--fg);outline:none;transition:border-color 0.2s}
+ .dwm-field input:focus,.dwm-field textarea:focus{border-bottom-color:var(--gold)}
+ .dwm-field textarea{min-height:60px;resize:vertical}
+ .dwm-submit{background:transparent;color:var(--muted, #c9b89e);border:1px solid var(--muted, #c9b89e);padding:14px 28px;font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:700;cursor:pointer;width:100%;margin-top:8px;transition:background 0.15s, color 0.15s}
+ .dwm-submit:hover{background:var(--muted, #c9b89e);color:var(--bg, #000)}
+
+ .dwm-submit:disabled{opacity:0.5;cursor:default}
+ .dwm-status{margin-top:14px;font-size:11px;letter-spacing:0.16em;text-transform:uppercase;text-align:center;display:none}
+ .dwm-status.ok{color:var(--sage);display:block}
+ .dwm-status.err{color:#e08070;display:block}
+ /* contact-options */
+ .dwm-options{display:flex;flex-direction:column;gap:10px}
+ .dwm-option{display:flex;align-items:center;gap:14px;padding:18px 20px;background:transparent;border:1px solid var(--rule);color:var(--fg);font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:600;cursor:pointer;text-decoration:none;transition:all 0.2s;text-align:left;width:100%}
+ .dwm-option:hover{border-color:var(--gold);color:var(--gold)}
+ .dwm-option svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:1.5;flex-shrink:0}
+ .dwm-option .lbl{flex:1;display:block}
+ .dwm-option .val{display:block;font-size:10px;letter-spacing:0.14em;text-transform:none;font-weight:500;opacity:0.65;margin-top:4px}
+
+/* graphics-loop pass 5: serif hero typography + nav legibility */
+.cinema h1, h1.headline { font-family: 'Cormorant Garamond', Georgia, serif; font-weight: 500; letter-spacing: -0.01em }
+.h-link { font-size: 13px; letter-spacing: 0.06em }
+
+/* graphics-loop pass 11: Ideas rails */
+#ideas { padding: 56px 24px 24px; max-width:1600px; margin:0 auto }
+#ideas .rail-block { margin-bottom:48px }
+#ideas .rail-eyebrow { font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:var(--muted, #888); margin-bottom:8px }
+#ideas .rail-title { font-size:clamp(22px,2.6vw,32px); font-weight:500; letter-spacing:-0.01em; margin:0 0 18px; color:var(--ink,#fff) }
+#ideas .rail { display:flex; gap:14px; overflow-x:auto; scroll-snap-type:x mandatory; padding-bottom:8px; scrollbar-width:none }
+#ideas .rail::-webkit-scrollbar { display:none }
+#ideas .rail-card { flex:0 0 240px; scroll-snap-align:start; cursor:pointer; transition:transform 0.2s }
+#ideas .rail-card:hover { transform:translateY(-3px) }
+#ideas .rail-card img { width:100%; aspect-ratio:1; object-fit:cover; display:block; background:var(--card,#222) }
+#ideas .rail-card .rc-title { font-size:13px; font-weight:500; color:var(--paper,#fff); padding:8px 0 2px; line-height:1.3 }
+#ideas .rail-card .rc-meta { font-size:11px; color:var(--muted,#888); letter-spacing:0.06em }
+@media (max-width:720px) { #ideas .rail-card { flex:0 0 160px } }
+</style>
+
+<!-- Contact options modal -->
+<div class="dwm" id="dwmContact" onclick="if(event.target===this)dwmClose('Contact')">
+ <div class="dwm-box" style="max-width:440px">
+ <button class="dwm-close" onclick="dwmClose('Contact')" aria-label="Close">×</button>
+ <h3>Contact</h3>
+ <p class="sub" id="dwmContactSub">Three ways to reach us</p>
+ <div class="dwm-options">
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Inquiry')">
+ <svg viewBox="0 0 24 24"><path d="M4 4h16v12H7l-3 3z"/></svg>
+ <span class="lbl">Send an Inquiry<span class="val">Project name · scope · all the details</span></span>
+ </button>
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Sample')">
+ <svg viewBox="0 0 24 24"><rect x="4" y="6" width="16" height="14"/><path d="M4 10h16M9 6V3h6v3"/></svg>
+ <span class="lbl">Request a Sample<span class="val">Memo sample · ships free · 3–5 business days</span></span>
+ </button>
+ <a class="dwm-option" id="dwmContactEmailLink" href="mailto:info@silkwallpaper.com">
+ <svg viewBox="0 0 24 24"><path d="M3 6h18v12H3z"/><path d="M3 6l9 7 9-7"/></svg>
+ <span class="lbl">Email Us<span class="val" id="dwmContactEmailLabel">info@silkwallpaper.com</span></span>
+ </a>
+ </div>
+ </div>
+</div>
+
+<!-- Inquiry modal -->
+<div class="dwm" id="dwmInquiry" onclick="if(event.target===this)dwmClose('Inquiry')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Inquiry')" aria-label="Close">×</button>
+ <h3>Project Inquiry</h3>
+ <p class="sub">We'll respond within one business day</p>
+ <form id="dwmInquiryForm" onsubmit="return dwmSubmit(event,'inquiry')">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Phone</label><input type="tel" name="phone"></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Project Name</label><input type="text" name="projectName" required></div>
+ <div class="dwm-field"><label>Project Scope</label><textarea name="projectScope" placeholder="Square footage · room count · timeline" required></textarea></div>
+ <div class="dwm-field"><label>Additional Information</label><textarea name="message" placeholder="Patterns of interest, finish preferences, budget"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Inquiry</button>
+ <div class="dwm-status" id="dwmInquiryStatus"></div>
+ </form>
+ </div>
+</div>
+
+<!-- Sample-request modal -->
+<div class="dwm" id="dwmSample" onclick="if(event.target===this)dwmClose('Sample')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Sample')" aria-label="Close">×</button>
+ <h3>Request a Sample</h3>
+ <p class="sub">Memo sample · ships free · 3–5 business days</p>
+ <div class="dwm-preview" id="dwmSamplePreview" style="display:none">
+ <img id="dwmSampleImg" src="" alt="">
+ <div><div class="pn" id="dwmSampleName"></div><div class="pc" id="dwmSampleSku"></div></div>
+ </div>
+ <form id="dwmSampleForm" onsubmit="return dwmSubmit(event,'sample')">
+ <input type="hidden" name="sku" id="dwmSampleSkuInput">
+ <input type="hidden" name="title" id="dwmSampleTitleInput">
+ <input type="hidden" name="image_url" id="dwmSampleImageInput">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Shipping Address</label><input type="text" name="address" placeholder="Street" required></div>
+ <div class="dwm-field" style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:8px">
+ <input type="text" name="city" placeholder="City" required>
+ <input type="text" name="state" placeholder="State" required>
+ <input type="text" name="zip" placeholder="ZIP" required>
+ </div>
+ <div class="dwm-field"><label>Notes (optional)</label><textarea name="message" placeholder="Project, sq.ft., timing"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Sample Request</button>
+ <div class="dwm-status" id="dwmSampleStatus"></div>
+ </form>
+ </div>
+</div>
+
+<script>
+ // Universal modal control
+ function dwmOpen(name, opts){
+ document.getElementById('dwm' + name).classList.add('open');
+ document.body.style.overflow = 'hidden';
+ if (name === 'Sample' && opts) {
+ document.getElementById('dwmSamplePreview').style.display = 'flex';
+ document.getElementById('dwmSampleImg').src = opts.image_url || '';
+ document.getElementById('dwmSampleName').textContent = opts.title || '';
+ document.getElementById('dwmSampleSku').textContent = opts.sku || '';
+ document.getElementById('dwmSampleSkuInput').value = opts.sku || '';
+ document.getElementById('dwmSampleTitleInput').value = opts.title || '';
+ document.getElementById('dwmSampleImageInput').value = opts.image_url || '';
+ } else if (name === 'Sample') {
+ document.getElementById('dwmSamplePreview').style.display = 'none';
+ document.getElementById('dwmSampleSkuInput').value = '';
+ document.getElementById('dwmSampleTitleInput').value = '';
+ document.getElementById('dwmSampleImageInput').value = '';
+ }
+ }
+ function dwmClose(name){ document.getElementById('dwm'+name).classList.remove('open'); document.body.style.overflow=''; }
+
+ // Universal form submit — POSTs to /api/send-{kind}
+ async function dwmSubmit(e, kind) {
+ e.preventDefault();
+ const form = e.target;
+ const data = Object.fromEntries(new FormData(form).entries());
+ const btn = form.querySelector('.dwm-submit');
+ const status = form.querySelector('.dwm-status');
+ btn.disabled = true; const orig = btn.textContent; btn.textContent = 'Sending…';
+ status.className = 'dwm-status';
+ try {
+ const r = await fetch('/api/send-' + kind, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data) });
+ if (!r.ok) throw new Error('HTTP '+r.status);
+ const j = await r.json();
+ status.textContent = kind === 'sample' ? 'Sample request sent — ships within 3–5 business days' : 'Inquiry sent — we respond within 1 business day';
+ status.className = 'dwm-status ok';
+ btn.textContent = 'Sent ✓';
+ setTimeout(() => { dwmClose(kind === 'sample' ? 'Sample' : 'Inquiry'); btn.disabled = false; btn.textContent = orig; status.className = 'dwm-status'; form.reset(); }, 2800);
+ } catch (err) {
+ status.textContent = 'Send failed — please email info@designerwallcoverings.com';
+ status.className = 'dwm-status err';
+ btn.disabled = false; btn.textContent = orig;
+ }
+ return false;
+ }
+
+ // Wire up "Memo samples ship free" badge in hero to open sample modal directly
+ document.querySelectorAll('.hero .badge, [data-open-sample]').forEach(el => {
+ el.style.cursor = 'pointer';
+ el.addEventListener('click', () => dwmOpen('Sample'));
+ });
+
+ // Wire nav "Trade" link to inquiry modal
+ document.querySelectorAll('nav a[href*="designerwallcoverings.com"]').forEach(a => {
+ a.removeAttribute('target');
+ a.removeAttribute('href');
+ a.style.cursor = 'pointer';
+ a.addEventListener('click', e => { e.preventDefault(); dwmOpen('Contact'); });
+ });
+
+ // Override product card clicks — open Sample modal pre-filled instead of redirecting to DW
+ document.addEventListener('click', e => {
+ const card = e.target.closest('.card, .rail-card');
+ if (!card) return;
+ if (card.tagName === 'A' && (card.getAttribute('href') || '').startsWith('/sample/')) {
+ e.preventDefault();
+ const img = card.querySelector('img');
+ const pat = card.querySelector('.pat');
+ const sku = card.getAttribute('href').replace('/sample/', '');
+ dwmOpen('Sample', {
+ sku,
+ title: pat ? pat.textContent.trim() : '',
+ image_url: img ? img.src : ''
+ });
+ }
+ }, true);
+// DW STANDARD: hero pulls from products.json[0].image_url so dw-collections-viewer
+// drag-drop / hero-pin updates the live front page on next deploy.
+// Always upgrades to the largest Shopify variant (strips _NxN size suffix) so
+// the full-page hero stays sharp.
+// DW STANDARD: hero rotates through top-20 NEWEST products on each visit
+// (novasuede.com pattern). Fetches /api/products?limit=100, sorts by created_at desc,
+// takes the freshest 20, picks one per visit via localStorage counter.
+// Falls back to static /hero-bg.jpg on any error.
+(async function rotateHero() {
+ try {
+ const r = await fetch('/api/products?limit=100');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || [])
+ .filter(p => p && p.image_url && /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(p.image_url))
+ .sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')))
+ .slice(0, 20);
+ if (!items.length) return;
+ let visits = 0;
+ try {
+ visits = parseInt(localStorage.getItem('hero_visits') || '0', 10) || 0;
+ localStorage.setItem('hero_visits', String(visits + 1));
+ } catch(e) {}
+ const pick = items[visits % items.length];
+ // Strip _NxN size suffix to get the largest CDN variant
+ const url = pick.image_url.replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ const bg = document.getElementById('heroBg') || document.getElementById('cinemaBg');
+ if (bg) bg.style.backgroundImage = "url('" + url + "')";
+ } catch (e) {}
+})();
+
+
+// DW STANDARD sort-select wire-up (added by fleet patcher)
+(function(){
+ const sortSel = document.getElementById('sortSelect');
+ if (!sortSel) return;
+ const KEY = location.hostname.replace(/\./g, '_') + '_sort';
+ const saved = localStorage.getItem(KEY);
+ if (saved) { try { sortSel.value = saved; } catch(e){} }
+ if (typeof state !== 'undefined') state.sort = sortSel.value;
+ sortSel.addEventListener('change', e => {
+ const v = e.target.value;
+ try { localStorage.setItem(KEY, v); } catch(e){}
+ if (typeof state !== 'undefined') state.sort = v;
+ if (typeof resetGrid === 'function') resetGrid();
+ else location.reload();
+ });
+})();
+
+// graphics-loop pass 11: hydrate Ideas rails from /api/products
+(async function hydrateIdeas() {
+ try {
+ const r = await fetch('/api/products?limit=80');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || []).filter(p => p && p.image_url);
+ if (!items.length) return;
+ const sorted = [...items].sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')));
+ const newest = sorted.slice(0, 12);
+ // Curated = deterministic offset slice (every 5th item starting at idx 3) for variety
+ const curated = items.filter((_, i) => i % 5 === 3).slice(0, 12);
+ function card(p) {
+ const url = (p.image_url || '').replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ return '<a class="rail-card" href="/sample/' + encodeURIComponent(p.handle || p.sku) + '">' +
+ '<img src="' + url + '" alt="' + (p.title || '').replace(/"/g, '"') + '" loading="lazy">' +
+ '<div class="rc-title">' + ((p.title || '').split('|')[0] || '').trim() + '</div>' +
+ '<div class="rc-meta">' + (p.vendor || '') + '</div>' +
+ '</a>';
+ }
+ const r1 = document.getElementById('railNewest');
+ const r2 = document.getElementById('railCurated');
+ if (r1) r1.innerHTML = newest.map(card).join('');
+ if (r2) r2.innerHTML = curated.map(card).join('');
+ } catch(e) {}
+})();
+</script>
+
+</body>
+</html>
diff --git a/public/index.html.bak.nspropagate.1778209715095 b/public/index.html.bak.nspropagate.1778209715095
new file mode 100644
index 0000000..3b1f900
--- /dev/null
+++ b/public/index.html.bak.nspropagate.1778209715095
@@ -0,0 +1,972 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<!-- Google tag (gtag.js) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=G-CNTJNLTEW8"></script>
+<script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+ gtag('config', 'G-CNTJNLTEW8');
+</script>
+
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Atelier Couture — SILK — IN BLACK.</title>
+<meta name="description" content="Atelier Couture is a curated atelier dedicated entirely to silk. We do one thing — and we do it carefully.">
+<meta name="theme-color" content="#FFF">
+<link rel="canonical" href="https://silkwallpaper.com/">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
+<link rel="preload" as="image" href="/hero-bg.jpg" fetchpriority="high">
+<script type="application/ld+json">
+{
+ "@context": "https://schema.org",
+ "@type": "Organization",
+ "name": "Atelier Couture",
+ "url": "https://silkwallpaper.com/",
+ "email": "info@silkwallpaper.com"
+}
+</script>
+<script src="/zd-loader.js" defer></script>
+<style>
+/* Atelier Couture · Saint Laurent template — pure monochrome, sharp uppercase, zero ornament */
+:root {
+ --bg: #000;
+ --paper: #FFF;
+ --muted: #888;
+ --line: rgba(255,255,255,0.20);
+ --accent: #FFF;
+ --bg-soft: #0a0a0a;
+ --cols: 6;
+}
+html[data-theme="light"] {
+ --bg: #FFF;
+ --paper: #000;
+ --muted: #666;
+ --line: rgba(0,0,0,0.20);
+ --accent: #000;
+ --bg-soft: #F5F5F5;
+}
+* { margin:0; padding:0; box-sizing:border-box }
+html { scroll-behavior:smooth }
+body { font-family:'Inter', 'Helvetica Neue', sans-serif; color:var(--paper); -webkit-font-smoothing:antialiased; background:var(--bg); min-height:100vh; overflow-x:hidden }
+/* DW-FASHION-TEMPLATE-OVERRIDE */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', Georgia, serif !important; font-weight:900 !important; text-transform:uppercase !important;letter-spacing:0.04em !important; }
+/* Saint Laurent: heavy uppercase headlines, no italic serif accent */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', 'Helvetica Neue', sans-serif !important; font-weight:900 !important; font-style:normal !important; letter-spacing:0.04em !important; text-transform:uppercase !important }
+
+/* ===== Auto-hide header ===== */
+header { position:fixed; top:0; left:0; right:0; display:flex; justify-content:space-between; align-items:center; padding:22px 32px; z-index:100; background:rgba(0,0,0,0.55); backdrop-filter:blur(10px); border-bottom:1px solid var(--line); transition:transform .3s }
+.h-link { display:flex; align-items:center; gap:10px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); text-decoration:none; cursor:pointer; background:transparent; border:0 }
+.h-link:hover { opacity:0.7 }
+.h-icon { width:18px; height:18px; stroke:currentColor; fill:none; stroke-width:1.5 }
+
+/* ===== Cinema hero ===== */
+.cinema { position:relative; height:100vh; width:100%; overflow:hidden }
+.cinema-bg { position:absolute; inset:0; background-size:cover; background-position:center; background-image:url('/hero-bg.jpg') }
+.cinema-bg::after { content:""; position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.75) 100%) }
+.corner-mark { position:absolute; top:24px; left:32px; font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:#fff; text-shadow:0 1px 3px rgba(0,0,0,0.55), 0 0 1px rgba(0,0,0,0.4); z-index:5 }
+.corner-mark::before { content:"◆ "; margin-right:6px }
+.center-mark { position:absolute; top:clamp(80px,12vh,140px); left:50%; transform:translateX(-50%); font-size:clamp(28px,4vw,56px); font-weight:500; letter-spacing:0.18em; text-transform:uppercase; text-align:center; color:#fff; text-shadow:0 2px 8px rgba(0,0,0,0.65), 0 0 1px rgba(0,0,0,0.5); z-index:5 }
+.center-mark .tm { font-size:14px; vertical-align:super; opacity:0.7; margin-left:0.2em }
+.center-mark .sub { display:block; font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400; font-size:0.4em; letter-spacing:0.04em; text-transform:none; margin-top:14px; opacity:0.85 }
+.meta-line { position:absolute; bottom:32px; left:32px; font-size:10px; letter-spacing:0.28em; text-transform:uppercase; font-weight:700; color:#fff; z-index:5; opacity:0.85 }
+.meta-line .num { font-size:12px; display:block; margin-top:4px; letter-spacing:0.05em }
+.enter { position:absolute; bottom:32px; right:32px; display:flex; align-items:center; gap:14px; font-size:11px; letter-spacing:0.36em; text-transform:uppercase; font-weight:700; color:#fff; text-decoration:none; padding-bottom:6px; border-bottom:1px solid #fff; transition:gap 0.25s; z-index:5 }
+.enter:hover { gap:22px }
+.enter svg { width:24px; height:12px }
+
+/* ===== Section ===== */
+.section { padding:96px 32px }
+.section-header { display:flex; justify-content:space-between; align-items:flex-end; flex-wrap:wrap; gap:24px; margin-bottom:32px; padding-bottom:24px; border-bottom:1px solid var(--line) }
+.section-title { font-size:clamp(36px,5vw,64px); font-weight:300; letter-spacing:-0.02em; line-height:1 }
+.section-title .accent { color: var(--accent); font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400 }
+.section-eyebrow { font-size:10px; letter-spacing:0.4em; text-transform:uppercase; font-weight:700; color:var(--muted); margin-bottom:16px }
+.section-meta { font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:600; color:var(--muted); text-align:right; line-height:1.6 }
+
+/* ===== Filter bar ===== */
+.filters { display:flex; gap:8px; align-items:center; margin-bottom:18px; flex-wrap:wrap }
+.chip { padding:8px 16px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); background:transparent; border:1px solid var(--line); cursor:pointer; transition:all 0.2s; font-family:inherit }
+.chip:hover { border-color:var(--paper) }
+.chip.active { background:var(--paper); color:var(--bg); border-color:var(--paper) }
+.search { flex:0 1 280px; margin-left:auto; display:flex; align-items:center; gap:10px; border-bottom:1px solid var(--line); padding:6px 0 }
+.search input { flex:1; background:transparent; border:0; color:var(--paper); font-family:inherit; font-size:13px; outline:none; letter-spacing:0.04em }
+.search input::placeholder { color:var(--muted) }
+.search svg { width:16px; height:16px; stroke:var(--muted); fill:none; stroke-width:1.5 }
+
+/* ===== Grid-density slider ===== */
+.density { display:flex; align-items:center; gap:14px; padding:6px 0; margin-bottom:24px }
+.density label { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--muted) }
+.density input[type=range] { flex:1; max-width:240px; -webkit-appearance:none; appearance:none; height:1px; background:var(--line); outline:none }
+.density input[type=range]::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50% }
+.density input[type=range]::-moz-range-thumb { width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50%; border:0 }
+.density .ct { font-size:11px; color:var(--muted); letter-spacing:0.18em; text-transform:uppercase; font-weight:600; min-width:80px }
+
+/* ===== Stats line ===== */
+.stat-line { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:600; color:var(--muted); margin-bottom:24px }
+
+/* ===== Product grid (novasuede pattern: flush grid + slide-up overlay) ===== */
+.grid { display:grid; grid-template-columns:repeat(var(--cols), 1fr); gap:0 }
+.card { position:relative; aspect-ratio:1/1.15; cursor:pointer; overflow:hidden; border:1px solid var(--bg); transition:transform 0.4s cubic-bezier(0.2,0.8,0.2,1); background:var(--bg-soft) }
+.card:hover { transform:scale(1.04); z-index:5 }
+.card img { width:100%; height:100%; object-fit:cover; display:block; transition:transform 0.4s ease }
+.card .overlay { position:absolute; left:0; right:0; bottom:0; padding:20px 16px 14px; background:linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.85) 100%); transform:translateY(48%); opacity:0.85; transition:transform 0.3s ease, opacity 0.3s ease }
+.card:hover .overlay { transform:translateY(0); opacity:1 }
+.card .pat { font-size:13px; font-weight:600; letter-spacing:0.02em; color:#fff; line-height:1.25; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }
+.card .ven { font-size:9px; letter-spacing:0.18em; text-transform:uppercase; color:#fff; opacity:0.7; font-weight:500; margin-top:4px }
+.card .actions { margin-top:10px; display:flex; gap:6px }
+.card .sample-btn { flex:1; padding:7px 10px; background:#fff; color:#000; font-family:inherit; font-size:10px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; border:0; cursor:pointer; text-align:center; transition:all 0.2s }
+.card .sample-btn:hover { background:var(--accent); color:#fff }
+
+@media (max-width:1024px) { .grid { grid-template-columns:repeat(min(var(--cols), 4), 1fr) } }
+@media (max-width:720px) { .grid { grid-template-columns:repeat(2, 1fr) } .density { display:none } }
+
+.sentinel { height:1px }
+.loading { text-align:center; color:var(--muted); padding:32px; font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700 }
+
+/* ===== Footer ===== */
+footer { padding:64px 32px 32px; border-top:1px solid var(--line); margin-top:32px }
+.footer-grid { max-width:1400px; margin:0 auto; display:grid; grid-template-columns:2fr 1fr 1fr 1fr; gap:48px; margin-bottom:48px }
+.footer-brand { font-size:24px; font-weight:300; letter-spacing:0.32em; text-transform:uppercase; margin-bottom:12px }
+.footer-text { font-size:13px; line-height:1.6; color:var(--muted); max-width:380px }
+.footer-col h4 { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--paper); margin-bottom:18px }
+.footer-col a { display:block; font-size:13px; color:var(--muted); text-decoration:none; margin-bottom:8px; transition:color 0.2s; cursor:pointer; background:transparent; border:0; font-family:inherit; padding:0; text-align:left }
+.footer-col a:hover { color:var(--accent) }
+.footer-bottom { max-width:1400px; margin:0 auto; padding-top:24px; border-top:1px solid var(--line); display:flex; justify-content:space-between; flex-wrap:wrap; gap:16px; font-size:11px; letter-spacing:0.18em; text-transform:uppercase; color:var(--muted) }
+
+@media (max-width:720px) {
+ .corner-mark, .meta-line, .enter { font-size:10px }
+ .center-mark { font-size:24px; letter-spacing:0.12em; top:clamp(60px,9vh,90px) }
+ .section, footer { padding-left:20px; padding-right:20px }
+ .footer-grid { grid-template-columns:1fr; gap:32px }
+ .search { margin-left:0; flex:1 1 100% }
+}
+
+.theme-toggle { background:transparent; border:1px solid var(--line); width:32px; height:32px; cursor:pointer; color:var(--paper); display:inline-flex; align-items:center; justify-content:center; font-size:14px; line-height:1; transition:all .15s; padding:0 }
+.theme-toggle:hover { border-color:var(--accent); color:var(--accent) }
+
+/* ===== WCAG 2.4.7 — keyboard focus indicators ===== */
+/* Strictly :focus-visible so mouse clicks do NOT show the ring */
+a:focus-visible,
+button:focus-visible,
+input:focus-visible,
+select:focus-visible,
+textarea:focus-visible,
+[tabindex]:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+</style>
+<script>
+(function(){ try { var t = localStorage.getItem('silk_theme') || 'dark'; document.documentElement.dataset.theme = t; } catch(e){} })();
+</script>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600;700&display=swap">
+
+<meta property="og:type" content="website">
+<meta property="og:url" content="https://silkwallpaper.com/">
+<meta property="og:title" content="Silk Wallpaper">
+<meta property="og:description" content="Silk Wallpaper — Pure silk wallcoverings — luxe and lustrous.">
+<meta property="og:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<meta property="og:site_name" content="Silk Wallpaper">
+<meta property="article:publisher" content="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings">
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:title" content="Silk Wallpaper">
+<meta name="twitter:description" content="Silk Wallpaper — Pure silk wallcoverings — luxe and lustrous.">
+<meta name="twitter:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<!-- graphics-loop pass 13: footer + fb -->
+</head>
+<body>
+<style id="ns-header-modal-styles">
+ .ns-header { position: sticky; top: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; padding: 18px 36px; background: rgba(10,10,10,0.92) !important; backdrop-filter: blur(8px); border-bottom: 1px solid rgba(245,241,232,0.06); }
+ .ns-brand { display: inline-flex; align-items: center; gap: 14px; text-decoration: none; }
+ .ns-dot { width: 28px; height: 28px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, #a98b5b 0%, #5d4c32 60%, #332a1b 100%); box-shadow: 0 0 0 1px rgba(245,241,232,0.10), 0 2px 12px rgba(180,90,60,0.30); flex-shrink: 0; }
+ .ns-name { font-family: -apple-system, "SF Pro Display", "Helvetica Neue", system-ui, sans-serif; font-size: 18px !important; font-weight: 300 !important; letter-spacing: 0.02em !important; color: #f5f1e8 !important; text-transform: none !important; margin: 0 !important; }
+ .ns-name sup { font-size: 0.55em; font-weight: 300; margin-left: 1px; }
+ .ns-nav { display: flex; gap: 32px; align-items: center; list-style: none; padding: 0; margin: 0; }
+ .ns-nav a, .ns-nav button { font-family: -apple-system, "SF Pro Text", system-ui, sans-serif; font-size: 15px !important; font-weight: 400 !important; color: #f5f1e8 !important; text-decoration: none !important; background: none !important; border: 0 !important; cursor: pointer; padding: 0 !important; transition: color 200ms; letter-spacing: 0 !important; text-transform: none !important; }
+ .ns-nav a:hover, .ns-nav button:hover { color: #ffffff !important; }
+ @media (max-width: 820px) { .ns-header { padding: 14px 18px; } .ns-nav { gap: 16px; } .ns-nav a, .ns-nav button { font-size: 13px !important; } }
+ @media (max-width: 760px) {
+ .ns-header { flex-wrap: wrap; }
+ .ns-nav { display: none !important; flex-basis: 100%; flex-direction: column; align-items: flex-start; gap: 6px; padding: 14px 0 4px; }
+ .ns-nav.open { display: flex !important; }
+ .ns-nav a, .ns-nav button { padding: 10px 0 !important; border-bottom: 1px solid rgba(245,241,232,0.08); width: 100%; text-align: left; }
+ .ns-nav a:nth-child(n+5), .ns-nav button:nth-child(n+5) { display: block !important; }
+ .ns-header .mobile-toggle { display: flex !important; }
+ }
+ @media (min-width: 761px) {
+ .ns-header .mobile-toggle { display: none !important; }
+ }
+ .ns-modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.78); z-index: 200; align-items: center; justify-content: center; padding: 24px; backdrop-filter: blur(6px); }
+ .ns-modal.show { display: flex; }
+ .ns-modal-card { max-width: 460px; width: 100%; background: #141414 !important; border: 1px solid rgba(245,241,232,0.14); border-radius: 6px; padding: 28px 30px 30px; position: relative; }
+ .ns-modal-card .close-x { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; background: none; border: 1px solid rgba(245,241,232,0.18); color: rgba(245,241,232,0.6) !important; border-radius: 50%; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; }
+ .ns-modal-card .close-x:hover { color: #ffffff !important; border-color: #ffffff; }
+ .ns-modal-card h3 { font-family: -apple-system, "SF Pro Display", system-ui; font-weight: 300 !important; font-size: 22px !important; margin: 0 0 6px !important; color: #f5f1e8 !important; }
+ .ns-modal-card .sub { color: rgba(245,241,232,0.65) !important; font-size: 13px; margin: 0 0 22px; }
+ .ns-actions { display: flex; flex-direction: column; gap: 10px; }
+ .ns-action { display: flex; align-items: center; gap: 14px; padding: 14px 16px; background: rgba(245,241,232,0.04) !important; border: 1px solid rgba(245,241,232,0.12) !important; color: #f5f1e8 !important; text-decoration: none !important; cursor: pointer; transition: all 200ms; font: inherit; text-align: left; }
+ .ns-action:hover { background: rgba(245,241,232,0.08) !important; border-color: #d4b683 !important; }
+ .ns-action .ico { width: 32px; height: 32px; border-radius: 50%; background: rgba(212,182,131,0.12); display: flex; align-items: center; justify-content: center; font-size: 15px; flex-shrink: 0; }
+ .ns-action .txt { flex: 1; }
+ .ns-action .txt b { display: block; font-weight: 500; font-size: 14px; color: #f5f1e8 !important; }
+ .ns-action .txt span { font-size: 12px; color: rgba(245,241,232,0.55) !important; }
+
+ /* ─── FINAL CONTRAST PASS — kill remaining gold-solid + white-island defects ─── */
+ /* Replace all gold-fill accents with off-white (Gucci-aesthetic, no DW gold solids) */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active,
+ .drapery-btn, .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover,
+ .drapery-btn:hover, .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ /* Gold accents stay only as edge color (links, glyphs) — not fills */
+ a { color: #d4b683; }
+ a:hover { color: #f5d97a; }
+ /* Authorized-dealer footer line — small caps tracked, muted ink, no gold pill */
+ .authorized, .authorized-dealer p {
+ background: transparent !important;
+ color: rgba(245,241,232,0.5) !important;
+ font-size: 10px !important;
+ letter-spacing: 0.22em !important;
+ text-transform: uppercase !important;
+ padding: 0 !important;
+ border: none !important;
+ margin-top: 14px !important;
+ }
+ /* Kill any straggler white bg in info-card / sample-related cards */
+ .info-card, .info-card-link, .care-highlight, .feature-card, .spec-item {
+ background-color: rgba(245,241,232,0.04) !important;
+ border-color: rgba(245,241,232,0.12) !important;
+ }
+ /* Ensure modal content stays readable — Steve's earlier sweep made it #141414 with white text */
+ .modal-content { background: #141414 !important; color: #f5f1e8 !important; }
+ .modal-content * { color: #f5f1e8 !important; }
+ .modal-content input, .modal-content textarea, .modal-content select {
+ background: rgba(245,241,232,0.05) !important;
+ color: #f5f1e8 !important;
+ border-color: rgba(245,241,232,0.2) !important;
+ }
+ .modal-content label { color: rgba(245,241,232,0.6) !important; }
+ /* Force the wide-tracked NOVASUEDE wordmark to stay white */
+ .ns-wordmark, .ns-wordmark sup { color: #f5f1e8 !important; }
+
+ /* ─── Modal-internal contrast (post-QA defects) ─── */
+ /* Backdrop covers full viewport */
+ .modal { width: 100vw !important; height: 100vh !important; left: 0 !important; top: 0 !important; background: rgba(0, 0, 0, 0.78) !important; backdrop-filter: blur(6px); }
+ /* Chip — flip from white-on-white island to dark card */
+ .request-chip {
+ background: rgba(245, 241, 232, 0.06) !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.18) !important;
+ border-radius: 4px !important;
+ }
+ .request-chip * { color: #f5f1e8 !important; }
+ .request-chip .chip-swatch {
+ /* swatch dot ALWAYS displays the inline-style hex from JS — fight any sweep */
+ border: 1px solid rgba(0, 0, 0, 0.4) !important;
+ }
+ /* + Add to request — hairline white-border on transparent (no gold) */
+ .add-to-request-btn {
+ background: transparent !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.6) !important;
+ border-radius: 0 !important;
+ padding: 12px 22px !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 400 !important;
+ }
+ .add-to-request-btn:hover {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ /* Hex caption visible */
+ .preview-hex, #colorPreviewHex {
+ display: block !important;
+ color: rgba(245, 241, 232, 0.55) !important;
+ font-family: ui-monospace, "SF Mono", Menlo, monospace !important;
+ font-size: 11px !important;
+ margin-top: 4px !important;
+ letter-spacing: 0.04em !important;
+ text-transform: lowercase !important;
+ }
+ /* Color preview name — stays single, prominent */
+ #colorPreviewName {
+ color: #f5f1e8 !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 500 !important;
+ display: block !important;
+ margin-top: 12px !important;
+ }
+
+ /* ─── Mobile hamburger nav (replaces the overflow at <760px) ─── */
+ @media (max-width: 760px) {
+ header .nav-container { flex-wrap: wrap; }
+ header nav { display: none; flex-basis: 100%; flex-direction: column; gap: 6px; padding: 16px 0 6px; }
+ header nav.open { display: flex; }
+ header nav a { padding: 10px 0; border-bottom: 1px solid rgba(245,241,232,0.1); }
+ header .mobile-toggle {
+ display: flex !important;
+ flex-direction: column;
+ gap: 5px;
+ background: transparent;
+ border: 0;
+ cursor: pointer;
+ padding: 8px;
+ }
+ header .mobile-toggle span {
+ width: 22px; height: 1.5px;
+ background: #f5f1e8;
+ display: block;
+ }
+ }
+ @media (min-width: 761px) {
+ header .mobile-toggle { display: none !important; }
+ }
+
+ </style>
+<style id="ns-gold-kill">
+ /* novasuede: kill gold-solid fills, off-white instead. Edge color stays gold. */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active, .drapery-btn,
+ .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover, .drapery-btn:hover,
+ .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ </style>
+<header class="ns-header">
+ <a href="/" class="ns-brand" aria-label="Home">
+ <span class="ns-dot"></span>
+ <span class="ns-name">Atelier Couture</span>
+ </a>
+ <nav class="ns-nav" id="nsPrimaryNav" aria-label="Primary">
+ <a href="/">Home</a>
+ <a href="/about">About</a>
+ <button type="button" onclick="nsContactOpen()">Contact</button>
+ </nav>
+ <button type="button" class="mobile-toggle" aria-label="Toggle menu" aria-controls="nsPrimaryNav" aria-expanded="false" onclick="nsToggleNav(this)">
+ <span></span><span></span><span></span>
+ </button>
+ </header>
+
+<div class="ns-modal" id="nsContactModal" role="dialog" aria-modal="true" aria-labelledby="nsContactTitle" onclick="if(event.target===this)nsContactClose()">
+ <div class="ns-modal-card">
+ <button type="button" class="close-x" onclick="nsContactClose()" aria-label="Close">×</button>
+ <h3 id="nsContactTitle">How can we help?</h3>
+ <p class="sub">Trade-account replies within one business day.</p>
+ <div class="ns-actions">
+ <a class="ns-action" href="#contact" onclick="nsContactClose()"><span class="ico">📋</span><div class="txt"><b>Get a Quote</b><span>Project specs → priced estimate</span></div></a>
+ <a class="ns-action" href="#contact" onclick="nsContactClose();openModal();"><span class="ico">📦</span><div class="txt"><b>Order a Sample</b><span>Free swatch to your shop</span></div></a>
+ <a class="ns-action" href="mailto:info@novasuede.com?subject=Novasuede%20inquiry"><span class="ico">✉️</span><div class="txt"><b>Email Us</b><span>info@novasuede.com</span></div></a>
+ <a class="ns-action" href="tel:888-373-4564"><span class="ico">📞</span><div class="txt"><b>Call Us</b><span>(888) 373-4564 · M–F, 9–5 PT</span></div></a>
+ </div>
+ </div>
+ </div>
+<script>
+ function nsContactOpen() { var m = document.getElementById('nsContactModal'); if (m) m.classList.add('show'); }
+ function nsContactClose() { var m = document.getElementById('nsContactModal'); if (m) m.classList.remove('show'); }
+ function nsToggleNav(btn) {
+ var nav = document.getElementById('nsPrimaryNav');
+ if (!nav) return;
+ var open = nav.classList.toggle('open');
+ btn.setAttribute('aria-expanded', open ? 'true' : 'false');
+ }
+ document.addEventListener('keydown', function(e){ if (e.key === 'Escape') nsContactClose(); });
+</script>
+
+
+
+
+
+<header>
+ <button class="h-link" onclick="dwmOpen('Contact')" aria-label="Contact"><svg class="h-icon" viewBox="0 0 24 24"><path d="M3 7h18M3 12h18M3 17h18"/></svg><span>Contact</span></button>
+ <button class="theme-toggle" id="theme-toggle" aria-label="Theme toggle">☾</button>
+</header>
+
+<section class="cinema">
+ <div class="cinema-bg" id="heroBg"></div>
+ <div class="corner-mark">Silken Sheen</div>
+ <div class="center-mark">Silk Wallpaper</div>
+ <div class="meta-line">Silk · Luxe · Sheen · Jacquard<span class="num" id="heroNum"></span></div>
+ <a class="enter" href="#shop">Enter <svg viewBox="0 0 24 12" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M0 6h22M16 1l6 5-6 5"/></svg></a>
+</section>
+
+
+<section class="section" id="ideas">
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">New this week</h2>
+ <div class="rail" id="railNewest"></div>
+ </div>
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">Curated picks</h2>
+ <div class="rail" id="railCurated"></div>
+ </div>
+</section>
+
+<section class="section" id="shop">
+ <div class="section-header">
+ <div>
+ <div class="section-eyebrow">The Collection</div>
+ <h2 class="section-title"><span class="accent" id="totalCount">—</span> Patterns.</h2>
+ </div>
+ <div class="section-meta">Silk · Luxe · Sheen · Jacquard</div>
+ </div>
+
+ <div class="filters" id="facets">
+ <button class="chip active" data-facet="all">All</button>
+ </div>
+
+ <div class="density">
+ <label>Sort</label>
+ <select id="sortSelect" aria-label="Sort products" style="background:transparent;color:inherit;border:1px solid var(--line,rgba(255,255,255,.2));font:inherit;font-size:11px;letter-spacing:.10em;text-transform:uppercase;padding:4px 8px;cursor:pointer;outline:none;margin-right:14px">
+ <option value="newest">Newest</option>
+ <option value="color">Color</option>
+ <option value="style">Style</option>
+ <option value="sku">SKU A→Z</option>
+ <option value="title">Title A→Z</option>
+ <option value="price-asc">Price ↑</option>
+ <option value="price-desc">Price ↓</option>
+ </select>
+ <label>Grid</label>
+ <input type="range" id="densitySlider" min="4" max="12" step="1" value="6" aria-label="Grid columns">
+ <span class="ct" id="densityLabel">6 cols</span>
+ <div class="search">
+ <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5.5-5.5"/></svg>
+ <input type="text" id="searchInput" placeholder="Search by pattern, color…" autocomplete="off">
+ </div>
+ </div>
+
+ <div class="stat-line" id="statLine">Loading…</div>
+ <div class="grid" id="grid"></div>
+ <div class="loading" id="loading">Loading more…</div>
+ <div class="sentinel" id="sentinel"></div>
+</section>
+
+<footer>
+ <div class="footer-grid">
+ <div>
+ <div class="footer-brand">Atelier Couture</div>
+ <p class="footer-text">Atelier Couture — real samples ship free.</p>
+ <p class="footer-text" style="margin-top:14px;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;font-weight:600;color:var(--paper);opacity:0.7"><a href="/about" style="color:inherit;text-decoration:none;border-bottom:1px solid var(--accent)">About →</a></p>
+ </div>
+ <div class="footer-col">
+ <h4>Aesthetic</h4>
+ <div id="footerFacets"></div>
+ </div>
+ <div class="footer-col">
+ <h4>Trade</h4>
+ <button onclick="dwmOpen('Inquiry')">Project Inquiry</button>
+ <button onclick="dwmOpen('Sample')">Request Sample</button>
+ <button onclick="dwmOpen('Contact')">Contact</button>
+ </div>
+
+ <div class="footer-col">
+ <h4>Social</h4>
+ <a class="fb-link" href="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings" target="_blank" rel="noopener">Facebook</a>
+ </div>
+ </div>
+ <div class="footer-bottom">
+ <span>silkwallpaper.com · Atelier Couture</span>
+ <span><a href="mailto:info@silkwallpaper.com" style="color:var(--paper);text-decoration:none;border-bottom:1px solid var(--accent)">info@silkwallpaper.com</a></span>
+ <span id="footerStat"></span>
+ </div>
+</footer>
+
+<script>
+// mini-constellation — 43 niche-stars, current site highlighted in gold
+(function(){
+ const SLUG = "silkwallpaper";
+ const NICHES = [
+ ['silkwallpaper','Silk','mat'],['silkwallcoverings','Silk W/C','mat'],['linenwallpaper','Linen','mat'],
+ ['jutewallpaper','Jute','mat'],['raffiawallcoverings','Raffia W/C','mat'],['raffiawalls','Raffia Walls','mat'],
+ ['corkwallcovering','Cork','mat'],['fabricwallpaper','Fabric','mat'],['textilewallpaper','Textile','mat'],
+ ['metallicwallpaper','Metallic','mat'],['silverleafwallpaper','Silver Leaf','mat'],['vinylwallpaper','Vinyl','mat'],
+ ['micawallpaper','Mica','mat'],['madagascarwallpaper','Madagascar','mat'],['mylarwallpaper','Mylar','mat'],
+ ['suedewallpaper','Suede','mat'],
+ ['1800swallpaper','1800s','dec'],['1890swallpaper','1890s','dec'],['1900swallpaper','1900s','dec'],
+ ['1920swallpaper','1920s','dec'],['1930swallpaper','1930s','dec'],['1940swallpaper','1940s','dec'],
+ ['1950swallpaper','1950s','dec'],['1960swallpaper','1960s','dec'],['1970swallpaper','1970s','dec'],
+ ['1980swallpaper','1980s','dec'],['retrowalls','Retro','dec'],['wallpapersback','W. Back','dec'],
+ ['agedwallpaper','Aged','cra'],['handcraftedwallpaper','Handcrafted','cra'],['museumwallpaper','Museum','cra'],
+ ['restorationwallpaper','Restoration','cra'],['pastelwallpaper','Pastel','cra'],['glitterwalls','Glitter','cra'],
+ ['greenwallcoverings','Green','cra'],['naturalwallcoverings','Natural','cra'],['saloonwallpaper','Saloon','cra'],
+ ['contractwallpaper','Contract','use'],['hotelwallcoverings','Hotel','use'],['hospitalitywallpaper','Hospitality','use'],
+ ['healthcarewallpaper','Healthcare','use'],['restaurantwallpaper','Restaurant','use'],['architecturalwallcoverings','Architectural','use']
+ ];
+ const COLOR = { mat:'#c9b687', dec:'#b86a4a', cra:'#6b8e6f', use:'#4a6b8e' };
+ const W = 1080, H = 200, BANDS = ['mat','dec','cra','use'];
+ const groups = { mat:[], dec:[], cra:[], use:[] };
+ for (const n of NICHES) groups[n[2]].push(n);
+ const svg = document.getElementById('miniConst');
+ if (!svg) return;
+ const bandW = W / BANDS.length;
+ let nodes = '';
+ BANDS.forEach((band, bi) => {
+ const list = groups[band];
+ const cx = bi * bandW + bandW / 2;
+ list.forEach((n, i) => {
+ const t = list.length === 1 ? 0.5 : i / (list.length - 1);
+ // deterministic jitter per slug
+ let h = 0; for (let k = 0; k < n[0].length; k++) h = (h * 31 + n[0].charCodeAt(k)) | 0;
+ const jx = ((h & 0xff) / 255 - 0.5) * (bandW * 0.5);
+ const jy = (((h >> 8) & 0xff) / 255 - 0.5) * 6; // tightened from 14 → 6 to prevent same-band collisions (16 mat nodes / 144px = 9px spacing)
+ const x = cx + jx, y = 28 + t * (H - 56) + jy;
+ const isCurrent = n[0] === SLUG;
+ const r = isCurrent ? 8 : 4;
+ const op = isCurrent ? 1 : 0.55;
+ nodes += `<g class="mc-node" data-slug="${n[0]}" data-label="${n[1]}" style="cursor:pointer">
+ <circle cx="${x}" cy="${y}" r="${r}" fill="url(#dna-${band})" fill-opacity="${op}" stroke="${COLOR[band]}" stroke-opacity="${isCurrent?1:0.7}" stroke-width="${isCurrent?2:1}">
+ <title>${n[1]} — ${n[0]}.com</title>
+ </circle>
+ ${isCurrent ? `<circle cx="${x}" cy="${y}" r="${r+5}" fill="none" stroke="${COLOR[band]}" stroke-opacity="0.4" stroke-width="1"><animate attributeName="r" values="${r+5};${r+12};${r+5}" dur="2.4s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" repeatCount="indefinite"/></circle>` : ''}
+ </g>`;
+ });
+ });
+ // append nodes after defs
+ svg.insertAdjacentHTML('beforeend', nodes);
+ svg.querySelectorAll('.mc-node').forEach(n => {
+ n.addEventListener('mouseenter', () => n.querySelector('circle').setAttribute('fill-opacity', '1'));
+ n.addEventListener('mouseleave', e => {
+ const isCurrent = n.dataset.slug === SLUG;
+ n.querySelector('circle').setAttribute('fill-opacity', isCurrent ? '1' : '0.55');
+ });
+ n.addEventListener('click', () => window.open('https://' + n.dataset.slug + '.com', '_blank'));
+ });
+})();
+</script>
+
+<script>
+const state = { q:'', facet:'all', page:1, pages:1, total:0, loading:false, exhausted:false };
+const LABELS = {"all":"All"};
+
+function escAttr(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
+// Image-URL allowlist: only DW Shopify CDN + designerwallcoverings.com (defends against XSS via crafted products.json)
+function safeImg(u) { return /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(u || '') ? u : '/hero-bg.jpg'; }
+function cardHTML(p) {
+ const eager = state.page === 1;
+ return '<img loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+ + '<div class="overlay">'
+ + '<div class="pat">' + escAttr(p.pattern_name || p.title) + '</div>'
+ + '<div class="ven">' + escAttr((p.vendor || '').replace(/-/g, ' ')) + '</div>'
+ + '<div class="actions">'
+ + '<button class="sample-btn" onclick="event.stopPropagation();dwmOpen(\'Sample\',' + JSON.stringify({sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}).replace(/"/g,'"') + ')">Sample</button>'
+ + '</div></div>';
+}
+
+async function loadFacets() {
+ let f;
+ try {
+ const r = await fetch('/api/facets');
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ f = await r.json();
+ } catch (e) { console.error('loadFacets failed:', e); return; }
+ const el = document.getElementById('facets');
+ for (const [k, v] of Object.entries(f.aesthetics).sort((a,b) => b[1] - a[1])) {
+ const b = document.createElement('button');
+ b.className = 'chip'; b.dataset.facet = k;
+ b.innerHTML = (LABELS[k] || k) + ' <span style="opacity:.55;font-weight:500;margin-left:4px">' + v + '</span>';
+ el.appendChild(b);
+ }
+ el.addEventListener('click', e => {
+ if (e.target.tagName !== 'BUTTON') return;
+ document.querySelectorAll('#facets button').forEach(b => b.classList.remove('active'));
+ e.target.classList.add('active');
+ state.facet = e.target.dataset.facet;
+ resetGrid();
+ });
+ document.getElementById('totalCount').textContent = f.total;
+ document.getElementById('footerStat').textContent = f.total + ' patterns · live archive';
+ // Footer aesthetic links
+ const footerEl = document.getElementById('footerFacets');
+ for (const [k] of Object.entries(f.aesthetics)) {
+ const b = document.createElement('button');
+ b.textContent = LABELS[k] || k;
+ b.onclick = () => { state.facet = k; resetGrid(); document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.facet === k)); document.getElementById('shop').scrollIntoView({behavior:'smooth'}); };
+ footerEl.appendChild(b);
+ }
+}
+
+function resetGrid() {
+ state.page = 1; state.exhausted = false;
+ document.getElementById('grid').innerHTML = '';
+ document.getElementById('loading').textContent = 'Loading…';
+ loadGridPage();
+}
+
+async function loadGridPage() {
+ if (state.loading || state.exhausted) return;
+ state.loading = true;
+ const params = new URLSearchParams({ page:state.page, limit:24 });
+ if (state.q) params.set('q', state.q);
+ if (state.facet !== 'all') params.set('aesthetic', state.facet);
+ let data;
+ try {
+ const r = await fetch('/api/products?' + params);
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ data = await r.json();
+ } catch (e) {
+ console.error('loadGridPage failed:', e);
+ document.getElementById('loading').textContent = '— offline — refresh to retry —';
+ state.loading = false;
+ return;
+ }
+ state.total = data.total; state.pages = data.pages;
+
+ if (state.page === 1) {
+ document.getElementById('statLine').textContent = data.total + ' patterns · ' + (state.facet === 'all' ? 'all aesthetics' : (LABELS[state.facet] || state.facet)) + (state.q ? ' · matching "' + state.q + '"' : '');
+ }
+ const grid = document.getElementById('grid');
+ for (const p of data.items) {
+ const a = document.createElement('a');
+ a.className = 'card'; a.href = '#'; a.onclick = (e) => { e.preventDefault(); dwmOpen('Sample',{sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}); };
+ a.innerHTML = cardHTML(p);
+ grid.appendChild(a);
+ }
+ if (state.page >= state.pages || data.items.length === 0) {
+ state.exhausted = true;
+ document.getElementById('loading').textContent = state.total > 0 ? '— end of archive · ' + state.total + ' patterns —' : '';
+ } else state.page++;
+ state.loading = false;
+}
+
+const io = new IntersectionObserver(es => { for (const e of es) if (e.isIntersecting) loadGridPage(); }, { rootMargin:'600px 0px' });
+io.observe(document.getElementById('sentinel'));
+
+document.getElementById('searchInput').addEventListener('input', e => {
+ state.q = e.target.value.trim();
+ clearTimeout(window._t);
+ window._t = setTimeout(resetGrid, 220);
+});
+
+// Density slider
+const slider = document.getElementById('densitySlider');
+const dlabel = document.getElementById('densityLabel');
+function setDensity(n) {
+ document.documentElement.style.setProperty('--cols', n);
+ dlabel.textContent = n + ' cols';
+ try { localStorage.setItem('silk_theme_density', n); } catch(e){}
+}
+slider.addEventListener('input', e => setDensity(parseInt(e.target.value)));
+const savedDensity = parseInt(localStorage.getItem('silk_theme_density') || '6');
+if (savedDensity >= 4 && savedDensity <= 12) { slider.value = savedDensity; setDensity(savedDensity); }
+
+// Theme toggle
+const tb = document.getElementById('theme-toggle');
+function setTheme(t){ document.documentElement.dataset.theme = t; try { localStorage.setItem('silk_theme', t); } catch(e){} tb.textContent = t === 'dark' ? '☾' : '☀'; }
+setTheme(document.documentElement.dataset.theme || 'dark');
+tb.addEventListener('click', () => setTheme(document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'));
+
+loadFacets();
+loadGridPage();
+</script>
+<!-- ============================================================
+ DW UNIVERSAL CONTACT MODULE — fashion-house UX
+ Inject before </body> in every DW-family sister site.
+ Self-contained: CSS + 4 modals + JS submission handlers.
+ Uses host site's CSS vars (--bg, --fg, --gold, --rule).
+ ============================================================ -->
+<style>
+ /* === MODAL BASE === */
+ .dwm{position:fixed;inset:0;background:rgba(0,0,0,0.78);display:none;align-items:center;justify-content:center;z-index:9999;padding:24px;overflow-y:auto}
+ .dwm.open{display:flex}
+ .dwm-box{background:var(--bg);border:1px solid var(--rule);max-width:520px;width:100%;padding:36px 32px;position:relative;max-height:90vh;overflow-y:auto;animation:dwm-in 0.25s ease}
+ @keyframes dwm-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+ .dwm-box h3{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-weight:400;font-size:32px;line-height:1.05;margin-bottom:6px;color:var(--fg);letter-spacing:-0.01em}
+ .dwm-box .sub{color:var(--muted);font-size:12px;letter-spacing:0.16em;text-transform:uppercase;margin-bottom:24px;font-weight:500}
+ .dwm-close{position:absolute;top:18px;right:18px;background:transparent;border:0;color:var(--muted);font-size:24px;cursor:pointer;width:32px;height:32px;line-height:1;padding:0}
+ .dwm-close:hover{color:var(--gold)}
+ .dwm-preview{display:flex;gap:14px;align-items:center;padding:12px;background:var(--bg-soft);margin-bottom:18px}
+ .dwm-preview img{width:64px;height:64px;object-fit:cover}
+ .dwm-preview .pn{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-size:15px;line-height:1.2;color:var(--fg)}
+ .dwm-preview .pc{font-size:10px;letter-spacing:0.14em;text-transform:uppercase;color:var(--muted);margin-top:4px}
+ .dwm-field{margin-bottom:14px}
+ .dwm-field label{display:block;font-size:10px;letter-spacing:0.18em;text-transform:uppercase;color:var(--muted);margin-bottom:6px;font-weight:500}
+ .dwm-field input,.dwm-field textarea,.dwm-field select{width:100%;border:0;border-bottom:1px solid var(--rule);background:transparent;padding:8px 4px;font-size:14px;font-family:inherit;color:var(--fg);outline:none;transition:border-color 0.2s}
+ .dwm-field input:focus,.dwm-field textarea:focus{border-bottom-color:var(--gold)}
+ .dwm-field textarea{min-height:60px;resize:vertical}
+ .dwm-submit{background:transparent;color:var(--muted, #c9b89e);border:1px solid var(--muted, #c9b89e);padding:14px 28px;font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:700;cursor:pointer;width:100%;margin-top:8px;transition:background 0.15s, color 0.15s}
+ .dwm-submit:hover{background:var(--muted, #c9b89e);color:var(--bg, #000)}
+
+ .dwm-submit:disabled{opacity:0.5;cursor:default}
+ .dwm-status{margin-top:14px;font-size:11px;letter-spacing:0.16em;text-transform:uppercase;text-align:center;display:none}
+ .dwm-status.ok{color:var(--sage);display:block}
+ .dwm-status.err{color:#e08070;display:block}
+ /* contact-options */
+ .dwm-options{display:flex;flex-direction:column;gap:10px}
+ .dwm-option{display:flex;align-items:center;gap:14px;padding:18px 20px;background:transparent;border:1px solid var(--rule);color:var(--fg);font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:600;cursor:pointer;text-decoration:none;transition:all 0.2s;text-align:left;width:100%}
+ .dwm-option:hover{border-color:var(--gold);color:var(--gold)}
+ .dwm-option svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:1.5;flex-shrink:0}
+ .dwm-option .lbl{flex:1;display:block}
+ .dwm-option .val{display:block;font-size:10px;letter-spacing:0.14em;text-transform:none;font-weight:500;opacity:0.65;margin-top:4px}
+
+/* graphics-loop pass 5: serif hero typography + nav legibility */
+.cinema h1, h1.headline { font-family: 'Cormorant Garamond', Georgia, serif; font-weight: 500; letter-spacing: -0.01em }
+.h-link { font-size: 13px; letter-spacing: 0.06em }
+
+/* graphics-loop pass 11: Ideas rails */
+#ideas { padding: 56px 24px 24px; max-width:1600px; margin:0 auto }
+#ideas .rail-block { margin-bottom:48px }
+#ideas .rail-eyebrow { font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:var(--muted, #888); margin-bottom:8px }
+#ideas .rail-title { font-size:clamp(22px,2.6vw,32px); font-weight:500; letter-spacing:-0.01em; margin:0 0 18px; color:var(--ink,#fff) }
+#ideas .rail { display:flex; gap:14px; overflow-x:auto; scroll-snap-type:x mandatory; padding-bottom:8px; scrollbar-width:none }
+#ideas .rail::-webkit-scrollbar { display:none }
+#ideas .rail-card { flex:0 0 240px; scroll-snap-align:start; cursor:pointer; transition:transform 0.2s }
+#ideas .rail-card:hover { transform:translateY(-3px) }
+#ideas .rail-card img { width:100%; aspect-ratio:1; object-fit:cover; display:block; background:var(--card,#222) }
+#ideas .rail-card .rc-title { font-size:13px; font-weight:500; color:var(--paper,#fff); padding:8px 0 2px; line-height:1.3 }
+#ideas .rail-card .rc-meta { font-size:11px; color:var(--muted,#888); letter-spacing:0.06em }
+@media (max-width:720px) { #ideas .rail-card { flex:0 0 160px } }
+</style>
+
+<!-- Contact options modal -->
+<div class="dwm" id="dwmContact" onclick="if(event.target===this)dwmClose('Contact')">
+ <div class="dwm-box" style="max-width:440px">
+ <button class="dwm-close" onclick="dwmClose('Contact')" aria-label="Close">×</button>
+ <h3>Contact</h3>
+ <p class="sub" id="dwmContactSub">Three ways to reach us</p>
+ <div class="dwm-options">
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Inquiry')">
+ <svg viewBox="0 0 24 24"><path d="M4 4h16v12H7l-3 3z"/></svg>
+ <span class="lbl">Send an Inquiry<span class="val">Project name · scope · all the details</span></span>
+ </button>
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Sample')">
+ <svg viewBox="0 0 24 24"><rect x="4" y="6" width="16" height="14"/><path d="M4 10h16M9 6V3h6v3"/></svg>
+ <span class="lbl">Request a Sample<span class="val">Memo sample · ships free · 3–5 business days</span></span>
+ </button>
+ <a class="dwm-option" id="dwmContactEmailLink" href="mailto:info@silkwallpaper.com">
+ <svg viewBox="0 0 24 24"><path d="M3 6h18v12H3z"/><path d="M3 6l9 7 9-7"/></svg>
+ <span class="lbl">Email Us<span class="val" id="dwmContactEmailLabel">info@silkwallpaper.com</span></span>
+ </a>
+ </div>
+ </div>
+</div>
+
+<!-- Inquiry modal -->
+<div class="dwm" id="dwmInquiry" onclick="if(event.target===this)dwmClose('Inquiry')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Inquiry')" aria-label="Close">×</button>
+ <h3>Project Inquiry</h3>
+ <p class="sub">We'll respond within one business day</p>
+ <form id="dwmInquiryForm" onsubmit="return dwmSubmit(event,'inquiry')">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Phone</label><input type="tel" name="phone"></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Project Name</label><input type="text" name="projectName" required></div>
+ <div class="dwm-field"><label>Project Scope</label><textarea name="projectScope" placeholder="Square footage · room count · timeline" required></textarea></div>
+ <div class="dwm-field"><label>Additional Information</label><textarea name="message" placeholder="Patterns of interest, finish preferences, budget"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Inquiry</button>
+ <div class="dwm-status" id="dwmInquiryStatus"></div>
+ </form>
+ </div>
+</div>
+
+<!-- Sample-request modal -->
+<div class="dwm" id="dwmSample" onclick="if(event.target===this)dwmClose('Sample')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Sample')" aria-label="Close">×</button>
+ <h3>Request a Sample</h3>
+ <p class="sub">Memo sample · ships free · 3–5 business days</p>
+ <div class="dwm-preview" id="dwmSamplePreview" style="display:none">
+ <img id="dwmSampleImg" src="" alt="">
+ <div><div class="pn" id="dwmSampleName"></div><div class="pc" id="dwmSampleSku"></div></div>
+ </div>
+ <form id="dwmSampleForm" onsubmit="return dwmSubmit(event,'sample')">
+ <input type="hidden" name="sku" id="dwmSampleSkuInput">
+ <input type="hidden" name="title" id="dwmSampleTitleInput">
+ <input type="hidden" name="image_url" id="dwmSampleImageInput">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Shipping Address</label><input type="text" name="address" placeholder="Street" required></div>
+ <div class="dwm-field" style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:8px">
+ <input type="text" name="city" placeholder="City" required>
+ <input type="text" name="state" placeholder="State" required>
+ <input type="text" name="zip" placeholder="ZIP" required>
+ </div>
+ <div class="dwm-field"><label>Notes (optional)</label><textarea name="message" placeholder="Project, sq.ft., timing"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Sample Request</button>
+ <div class="dwm-status" id="dwmSampleStatus"></div>
+ </form>
+ </div>
+</div>
+
+<script>
+ // Universal modal control
+ function dwmOpen(name, opts){
+ document.getElementById('dwm' + name).classList.add('open');
+ document.body.style.overflow = 'hidden';
+ if (name === 'Sample' && opts) {
+ document.getElementById('dwmSamplePreview').style.display = 'flex';
+ document.getElementById('dwmSampleImg').src = opts.image_url || '';
+ document.getElementById('dwmSampleName').textContent = opts.title || '';
+ document.getElementById('dwmSampleSku').textContent = opts.sku || '';
+ document.getElementById('dwmSampleSkuInput').value = opts.sku || '';
+ document.getElementById('dwmSampleTitleInput').value = opts.title || '';
+ document.getElementById('dwmSampleImageInput').value = opts.image_url || '';
+ } else if (name === 'Sample') {
+ document.getElementById('dwmSamplePreview').style.display = 'none';
+ document.getElementById('dwmSampleSkuInput').value = '';
+ document.getElementById('dwmSampleTitleInput').value = '';
+ document.getElementById('dwmSampleImageInput').value = '';
+ }
+ }
+ function dwmClose(name){ document.getElementById('dwm'+name).classList.remove('open'); document.body.style.overflow=''; }
+
+ // Universal form submit — POSTs to /api/send-{kind}
+ async function dwmSubmit(e, kind) {
+ e.preventDefault();
+ const form = e.target;
+ const data = Object.fromEntries(new FormData(form).entries());
+ const btn = form.querySelector('.dwm-submit');
+ const status = form.querySelector('.dwm-status');
+ btn.disabled = true; const orig = btn.textContent; btn.textContent = 'Sending…';
+ status.className = 'dwm-status';
+ try {
+ const r = await fetch('/api/send-' + kind, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data) });
+ if (!r.ok) throw new Error('HTTP '+r.status);
+ const j = await r.json();
+ status.textContent = kind === 'sample' ? 'Sample request sent — ships within 3–5 business days' : 'Inquiry sent — we respond within 1 business day';
+ status.className = 'dwm-status ok';
+ btn.textContent = 'Sent ✓';
+ setTimeout(() => { dwmClose(kind === 'sample' ? 'Sample' : 'Inquiry'); btn.disabled = false; btn.textContent = orig; status.className = 'dwm-status'; form.reset(); }, 2800);
+ } catch (err) {
+ status.textContent = 'Send failed — please email info@designerwallcoverings.com';
+ status.className = 'dwm-status err';
+ btn.disabled = false; btn.textContent = orig;
+ }
+ return false;
+ }
+
+ // Wire up "Memo samples ship free" badge in hero to open sample modal directly
+ document.querySelectorAll('.hero .badge, [data-open-sample]').forEach(el => {
+ el.style.cursor = 'pointer';
+ el.addEventListener('click', () => dwmOpen('Sample'));
+ });
+
+ // Wire nav "Trade" link to inquiry modal
+ document.querySelectorAll('nav a[href*="designerwallcoverings.com"]').forEach(a => {
+ a.removeAttribute('target');
+ a.removeAttribute('href');
+ a.style.cursor = 'pointer';
+ a.addEventListener('click', e => { e.preventDefault(); dwmOpen('Contact'); });
+ });
+
+ // Override product card clicks — open Sample modal pre-filled instead of redirecting to DW
+ document.addEventListener('click', e => {
+ const card = e.target.closest('.card, .rail-card');
+ if (!card) return;
+ if (card.tagName === 'A' && (card.getAttribute('href') || '').startsWith('/sample/')) {
+ e.preventDefault();
+ const img = card.querySelector('img');
+ const pat = card.querySelector('.pat');
+ const sku = card.getAttribute('href').replace('/sample/', '');
+ dwmOpen('Sample', {
+ sku,
+ title: pat ? pat.textContent.trim() : '',
+ image_url: img ? img.src : ''
+ });
+ }
+ }, true);
+// DW STANDARD: hero pulls from products.json[0].image_url so dw-collections-viewer
+// drag-drop / hero-pin updates the live front page on next deploy.
+// Always upgrades to the largest Shopify variant (strips _NxN size suffix) so
+// the full-page hero stays sharp.
+// DW STANDARD: hero rotates through top-20 NEWEST products on each visit
+// (novasuede.com pattern). Fetches /api/products?limit=100, sorts by created_at desc,
+// takes the freshest 20, picks one per visit via localStorage counter.
+// Falls back to static /hero-bg.jpg on any error.
+(async function rotateHero() {
+ try {
+ const r = await fetch('/api/products?limit=100');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || [])
+ .filter(p => p && p.image_url && /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(p.image_url))
+ .sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')))
+ .slice(0, 20);
+ if (!items.length) return;
+ let visits = 0;
+ try {
+ visits = parseInt(localStorage.getItem('hero_visits') || '0', 10) || 0;
+ localStorage.setItem('hero_visits', String(visits + 1));
+ } catch(e) {}
+ const pick = items[visits % items.length];
+ // Strip _NxN size suffix to get the largest CDN variant
+ const url = pick.image_url.replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ const bg = document.getElementById('heroBg') || document.getElementById('cinemaBg');
+ if (bg) bg.style.backgroundImage = "url('" + url + "')";
+ } catch (e) {}
+})();
+
+
+// DW STANDARD sort-select wire-up (added by fleet patcher)
+(function(){
+ const sortSel = document.getElementById('sortSelect');
+ if (!sortSel) return;
+ const KEY = location.hostname.replace(/\./g, '_') + '_sort';
+ const saved = localStorage.getItem(KEY);
+ if (saved) { try { sortSel.value = saved; } catch(e){} }
+ if (typeof state !== 'undefined') state.sort = sortSel.value;
+ sortSel.addEventListener('change', e => {
+ const v = e.target.value;
+ try { localStorage.setItem(KEY, v); } catch(e){}
+ if (typeof state !== 'undefined') state.sort = v;
+ if (typeof resetGrid === 'function') resetGrid();
+ else location.reload();
+ });
+})();
+
+// graphics-loop pass 11: hydrate Ideas rails from /api/products
+(async function hydrateIdeas() {
+ try {
+ const r = await fetch('/api/products?limit=80');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || []).filter(p => p && p.image_url);
+ if (!items.length) return;
+ const sorted = [...items].sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')));
+ const newest = sorted.slice(0, 12);
+ // Curated = deterministic offset slice (every 5th item starting at idx 3) for variety
+ const curated = items.filter((_, i) => i % 5 === 3).slice(0, 12);
+ function card(p) {
+ const url = (p.image_url || '').replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ return '<a class="rail-card" href="/sample/' + encodeURIComponent(p.handle || p.sku) + '">' +
+ '<img src="' + url + '" alt="' + (p.title || '').replace(/"/g, '"') + '" loading="lazy">' +
+ '<div class="rc-title">' + ((p.title || '').split('|')[0] || '').trim() + '</div>' +
+ '<div class="rc-meta">' + (p.vendor || '') + '</div>' +
+ '</a>';
+ }
+ const r1 = document.getElementById('railNewest');
+ const r2 = document.getElementById('railCurated');
+ if (r1) r1.innerHTML = newest.map(card).join('');
+ if (r2) r2.innerHTML = curated.map(card).join('');
+ } catch(e) {}
+})();
+</script>
+
+</body>
+</html>
diff --git a/public/index.html.bak.nspropagate.1778226815192 b/public/index.html.bak.nspropagate.1778226815192
new file mode 100644
index 0000000..ecb5678
--- /dev/null
+++ b/public/index.html.bak.nspropagate.1778226815192
@@ -0,0 +1,974 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<!-- Google tag (gtag.js) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=G-CNTJNLTEW8"></script>
+<script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+ gtag('config', 'G-CNTJNLTEW8');
+</script>
+
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Atelier Couture — SILK — IN BLACK.</title>
+<meta name="description" content="Atelier Couture is a curated atelier dedicated entirely to silk. We do one thing — and we do it carefully.">
+<meta name="theme-color" content="#FFF">
+<link rel="canonical" href="https://silkwallpaper.com/">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
+<link rel="preload" as="image" href="/hero-bg.jpg" fetchpriority="high">
+<script type="application/ld+json">
+{
+ "@context": "https://schema.org",
+ "@type": "Organization",
+ "name": "Atelier Couture",
+ "url": "https://silkwallpaper.com/",
+ "email": "info@silkwallpaper.com"
+}
+</script>
+<script src="/zd-loader.js" defer></script>
+<style>
+/* Atelier Couture · Saint Laurent template — pure monochrome, sharp uppercase, zero ornament */
+:root {
+ --bg: #000;
+ --paper: #FFF;
+ --muted: #888;
+ --line: rgba(255,255,255,0.20);
+ --accent: #FFF;
+ --bg-soft: #0a0a0a;
+ --cols: 6;
+}
+html[data-theme="light"] {
+ --bg: #FFF;
+ --paper: #000;
+ --muted: #666;
+ --line: rgba(0,0,0,0.20);
+ --accent: #000;
+ --bg-soft: #F5F5F5;
+}
+* { margin:0; padding:0; box-sizing:border-box }
+html { scroll-behavior:smooth }
+body { font-family:'Inter', 'Helvetica Neue', sans-serif; color:var(--paper); -webkit-font-smoothing:antialiased; background:var(--bg); min-height:100vh; overflow-x:hidden }
+/* DW-FASHION-TEMPLATE-OVERRIDE */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', Georgia, serif !important; font-weight:900 !important; text-transform:uppercase !important;letter-spacing:0.04em !important; }
+/* Saint Laurent: heavy uppercase headlines, no italic serif accent */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', 'Helvetica Neue', sans-serif !important; font-weight:900 !important; font-style:normal !important; letter-spacing:0.04em !important; text-transform:uppercase !important }
+
+/* ===== Auto-hide header ===== */
+header { position:fixed; top:0; left:0; right:0; display:flex; justify-content:space-between; align-items:center; padding:22px 32px; z-index:100; background:rgba(0,0,0,0.55); backdrop-filter:blur(10px); border-bottom:1px solid var(--line); transition:transform .3s }
+.h-link { display:flex; align-items:center; gap:10px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); text-decoration:none; cursor:pointer; background:transparent; border:0 }
+.h-link:hover { opacity:0.7 }
+.h-icon { width:18px; height:18px; stroke:currentColor; fill:none; stroke-width:1.5 }
+
+/* ===== Cinema hero ===== */
+.cinema { position:relative; height:100vh; width:100%; overflow:hidden }
+.cinema-bg { position:absolute; inset:0; background-size:cover; background-position:center; background-image:url('/hero-bg.jpg') }
+.cinema-bg::after { content:""; position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.75) 100%) }
+.corner-mark { position:absolute; top:24px; left:32px; font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:#fff; text-shadow:0 1px 3px rgba(0,0,0,0.55), 0 0 1px rgba(0,0,0,0.4); z-index:5 }
+.corner-mark::before { content:"◆ "; margin-right:6px }
+.center-mark { position:absolute; top:clamp(80px,12vh,140px); left:50%; transform:translateX(-50%); font-size:clamp(28px,4vw,56px); font-weight:500; letter-spacing:0.18em; text-transform:uppercase; text-align:center; color:#fff; text-shadow:0 2px 8px rgba(0,0,0,0.65), 0 0 1px rgba(0,0,0,0.5); z-index:5 }
+.center-mark .tm { font-size:14px; vertical-align:super; opacity:0.7; margin-left:0.2em }
+.center-mark .sub { display:block; font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400; font-size:0.4em; letter-spacing:0.04em; text-transform:none; margin-top:14px; opacity:0.85 }
+.meta-line { position:absolute; bottom:32px; left:32px; font-size:10px; letter-spacing:0.28em; text-transform:uppercase; font-weight:700; color:#fff; z-index:5; opacity:0.85 }
+.meta-line .num { font-size:12px; display:block; margin-top:4px; letter-spacing:0.05em }
+.enter { position:absolute; bottom:32px; right:32px; display:flex; align-items:center; gap:14px; font-size:11px; letter-spacing:0.36em; text-transform:uppercase; font-weight:700; color:#fff; text-decoration:none; padding-bottom:6px; border-bottom:1px solid #fff; transition:gap 0.25s; z-index:5 }
+.enter:hover { gap:22px }
+.enter svg { width:24px; height:12px }
+
+/* ===== Section ===== */
+.section { padding:96px 32px }
+.section-header { display:flex; justify-content:space-between; align-items:flex-end; flex-wrap:wrap; gap:24px; margin-bottom:32px; padding-bottom:24px; border-bottom:1px solid var(--line) }
+.section-title { font-size:clamp(36px,5vw,64px); font-weight:300; letter-spacing:-0.02em; line-height:1 }
+.section-title .accent { color: var(--accent); font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400 }
+.section-eyebrow { font-size:10px; letter-spacing:0.4em; text-transform:uppercase; font-weight:700; color:var(--muted); margin-bottom:16px }
+.section-meta { font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:600; color:var(--muted); text-align:right; line-height:1.6 }
+
+/* ===== Filter bar ===== */
+.filters { display:flex; gap:8px; align-items:center; margin-bottom:18px; flex-wrap:wrap }
+.chip { padding:8px 16px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); background:transparent; border:1px solid var(--line); cursor:pointer; transition:all 0.2s; font-family:inherit }
+.chip:hover { border-color:var(--paper) }
+.chip.active { background:var(--paper); color:var(--bg); border-color:var(--paper) }
+.search { flex:0 1 280px; margin-left:auto; display:flex; align-items:center; gap:10px; border-bottom:1px solid var(--line); padding:6px 0 }
+.search input { flex:1; background:transparent; border:0; color:var(--paper); font-family:inherit; font-size:13px; outline:none; letter-spacing:0.04em }
+.search input::placeholder { color:var(--muted) }
+.search svg { width:16px; height:16px; stroke:var(--muted); fill:none; stroke-width:1.5 }
+
+/* ===== Grid-density slider ===== */
+.density { display:flex; align-items:center; gap:14px; padding:6px 0; margin-bottom:24px }
+.density label { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--muted) }
+.density input[type=range] { flex:1; max-width:240px; -webkit-appearance:none; appearance:none; height:1px; background:var(--line); outline:none }
+.density input[type=range]::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50% }
+.density input[type=range]::-moz-range-thumb { width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50%; border:0 }
+.density .ct { font-size:11px; color:var(--muted); letter-spacing:0.18em; text-transform:uppercase; font-weight:600; min-width:80px }
+
+/* ===== Stats line ===== */
+.stat-line { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:600; color:var(--muted); margin-bottom:24px }
+
+/* ===== Product grid (novasuede pattern: flush grid + slide-up overlay) ===== */
+.grid { display:grid; grid-template-columns:repeat(var(--cols), 1fr); gap:0 }
+.card { position:relative; aspect-ratio:1/1.15; cursor:pointer; overflow:hidden; border:1px solid var(--bg); transition:transform 0.4s cubic-bezier(0.2,0.8,0.2,1); background:var(--bg-soft) }
+.card:hover { transform:scale(1.04); z-index:5 }
+.card img { width:100%; height:100%; object-fit:cover; display:block; transition:transform 0.4s ease }
+.card .overlay { position:absolute; left:0; right:0; bottom:0; padding:20px 16px 14px; background:linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.85) 100%); transform:translateY(48%); opacity:0.85; transition:transform 0.3s ease, opacity 0.3s ease }
+.card:hover .overlay { transform:translateY(0); opacity:1 }
+.card .pat { font-size:13px; font-weight:600; letter-spacing:0.02em; color:#fff; line-height:1.25; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }
+.card .ven { font-size:9px; letter-spacing:0.18em; text-transform:uppercase; color:#fff; opacity:0.7; font-weight:500; margin-top:4px }
+.card .actions { margin-top:10px; display:flex; gap:6px }
+.card .sample-btn { flex:1; padding:7px 10px; background:#fff; color:#000; font-family:inherit; font-size:10px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; border:0; cursor:pointer; text-align:center; transition:all 0.2s }
+.card .sample-btn:hover { background:var(--accent); color:#fff }
+
+@media (max-width:1024px) { .grid { grid-template-columns:repeat(min(var(--cols), 4), 1fr) } }
+@media (max-width:720px) { .grid { grid-template-columns:repeat(2, 1fr) } .density { display:none } }
+
+.sentinel { height:1px }
+.loading { text-align:center; color:var(--muted); padding:32px; font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700 }
+
+/* ===== Footer ===== */
+footer { padding:64px 32px 32px; border-top:1px solid var(--line); margin-top:32px }
+.footer-grid { max-width:1400px; margin:0 auto; display:grid; grid-template-columns:2fr 1fr 1fr 1fr; gap:48px; margin-bottom:48px }
+.footer-brand { font-size:24px; font-weight:300; letter-spacing:0.32em; text-transform:uppercase; margin-bottom:12px }
+.footer-text { font-size:13px; line-height:1.6; color:var(--muted); max-width:380px }
+.footer-col h4 { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--paper); margin-bottom:18px }
+.footer-col a { display:block; font-size:13px; color:var(--muted); text-decoration:none; margin-bottom:8px; transition:color 0.2s; cursor:pointer; background:transparent; border:0; font-family:inherit; padding:0; text-align:left }
+.footer-col a:hover { color:var(--accent) }
+.footer-bottom { max-width:1400px; margin:0 auto; padding-top:24px; border-top:1px solid var(--line); display:flex; justify-content:space-between; flex-wrap:wrap; gap:16px; font-size:11px; letter-spacing:0.18em; text-transform:uppercase; color:var(--muted) }
+
+@media (max-width:720px) {
+ .corner-mark, .meta-line, .enter { font-size:10px }
+ .center-mark { font-size:24px; letter-spacing:0.12em; top:clamp(60px,9vh,90px) }
+ .section, footer { padding-left:20px; padding-right:20px }
+ .footer-grid { grid-template-columns:1fr; gap:32px }
+ .search { margin-left:0; flex:1 1 100% }
+}
+
+.theme-toggle { background:transparent; border:1px solid var(--line); width:32px; height:32px; cursor:pointer; color:var(--paper); display:inline-flex; align-items:center; justify-content:center; font-size:14px; line-height:1; transition:all .15s; padding:0 }
+.theme-toggle:hover { border-color:var(--accent); color:var(--accent) }
+
+/* ===== WCAG 2.4.7 — keyboard focus indicators ===== */
+/* Strictly :focus-visible so mouse clicks do NOT show the ring */
+a:focus-visible,
+button:focus-visible,
+input:focus-visible,
+select:focus-visible,
+textarea:focus-visible,
+[tabindex]:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+</style>
+<script>
+(function(){ try { var t = localStorage.getItem('silk_theme') || 'dark'; document.documentElement.dataset.theme = t; } catch(e){} })();
+</script>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600;700&display=swap">
+
+<meta property="og:type" content="website">
+<meta property="og:url" content="https://silkwallpaper.com/">
+<meta property="og:title" content="Silk Wallpaper">
+<meta property="og:description" content="Silk Wallpaper — Pure silk wallcoverings — luxe and lustrous.">
+<meta property="og:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<meta property="og:site_name" content="Silk Wallpaper">
+<meta property="article:publisher" content="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings">
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:title" content="Silk Wallpaper">
+<meta name="twitter:description" content="Silk Wallpaper — Pure silk wallcoverings — luxe and lustrous.">
+<meta name="twitter:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<!-- graphics-loop pass 13: footer + fb -->
+</head>
+<body>
+<style id="ns-header-modal-styles">
+ .ns-header { position: sticky; top: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; padding: 18px 36px; background: rgba(10,10,10,0.92) !important; backdrop-filter: blur(8px); border-bottom: 1px solid rgba(245,241,232,0.06); }
+ .ns-brand { display: inline-flex; align-items: center; gap: 14px; text-decoration: none; }
+ .ns-dot { width: 28px; height: 28px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, #a98b5b 0%, #5d4c32 60%, #332a1b 100%); box-shadow: 0 0 0 1px rgba(245,241,232,0.10), 0 2px 12px rgba(180,90,60,0.30); flex-shrink: 0; }
+ .ns-name { font-family: -apple-system, "SF Pro Display", "Helvetica Neue", system-ui, sans-serif; font-size: 18px !important; font-weight: 300 !important; letter-spacing: 0.02em !important; color: #f5f1e8 !important; text-transform: none !important; margin: 0 !important; }
+ .ns-name sup { font-size: 0.55em; font-weight: 300; margin-left: 1px; }
+ .ns-nav { display: flex; gap: 32px; align-items: center; list-style: none; padding: 0; margin: 0; }
+ .ns-nav a, .ns-nav button { font-family: -apple-system, "SF Pro Text", system-ui, sans-serif; font-size: 15px !important; font-weight: 400 !important; color: #f5f1e8 !important; text-decoration: none !important; background: none !important; border: 0 !important; cursor: pointer; padding: 0 !important; transition: color 200ms; letter-spacing: 0 !important; text-transform: none !important; }
+ .ns-nav a:hover, .ns-nav button:hover { color: #ffffff !important; }
+ @media (max-width: 820px) { .ns-header { padding: 14px 18px; } .ns-nav { gap: 16px; } .ns-nav a, .ns-nav button { font-size: 13px !important; } }
+ @media (max-width: 760px) {
+ .ns-header { flex-wrap: wrap; }
+ .ns-nav { display: none !important; flex-basis: 100%; flex-direction: column; align-items: flex-start; gap: 6px; padding: 14px 0 4px; }
+ .ns-nav.open { display: flex !important; }
+ .ns-nav a, .ns-nav button { padding: 10px 0 !important; border-bottom: 1px solid rgba(245,241,232,0.08); width: 100%; text-align: left; }
+ .ns-nav a:nth-child(n+5), .ns-nav button:nth-child(n+5) { display: block !important; }
+ .ns-header .mobile-toggle { display: flex !important; }
+ }
+ @media (min-width: 761px) {
+ .ns-header .mobile-toggle { display: none !important; }
+ }
+ .ns-modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.78); z-index: 200; align-items: center; justify-content: center; padding: 24px; backdrop-filter: blur(6px); }
+ .ns-modal.show { display: flex; }
+ .ns-modal-card { max-width: 460px; width: 100%; background: #141414 !important; border: 1px solid rgba(245,241,232,0.14); border-radius: 6px; padding: 28px 30px 30px; position: relative; }
+ .ns-modal-card .close-x { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; background: none; border: 1px solid rgba(245,241,232,0.18); color: rgba(245,241,232,0.6) !important; border-radius: 50%; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; }
+ .ns-modal-card .close-x:hover { color: #ffffff !important; border-color: #ffffff; }
+ .ns-modal-card h3 { font-family: -apple-system, "SF Pro Display", system-ui; font-weight: 300 !important; font-size: 22px !important; margin: 0 0 6px !important; color: #f5f1e8 !important; }
+ .ns-modal-card .sub { color: rgba(245,241,232,0.65) !important; font-size: 13px; margin: 0 0 22px; }
+ .ns-actions { display: flex; flex-direction: column; gap: 10px; }
+ .ns-action { display: flex; align-items: center; gap: 14px; padding: 14px 16px; background: rgba(245,241,232,0.04) !important; border: 1px solid rgba(245,241,232,0.12) !important; color: #f5f1e8 !important; text-decoration: none !important; cursor: pointer; transition: all 200ms; font: inherit; text-align: left; }
+ .ns-action:hover { background: rgba(245,241,232,0.08) !important; border-color: #d4b683 !important; }
+ .ns-action .ico { width: 32px; height: 32px; border-radius: 50%; background: rgba(212,182,131,0.12); display: flex; align-items: center; justify-content: center; font-size: 15px; flex-shrink: 0; }
+ .ns-action .txt { flex: 1; }
+ .ns-action .txt b { display: block; font-weight: 500; font-size: 14px; color: #f5f1e8 !important; }
+ .ns-action .txt span { font-size: 12px; color: rgba(245,241,232,0.55) !important; }
+
+ /* ─── FINAL CONTRAST PASS — kill remaining gold-solid + white-island defects ─── */
+ /* Replace all gold-fill accents with off-white (Gucci-aesthetic, no DW gold solids) */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active,
+ .drapery-btn, .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover,
+ .drapery-btn:hover, .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ /* Gold accents stay only as edge color (links, glyphs) — not fills */
+ a { color: #d4b683; }
+ a:hover { color: #f5d97a; }
+ /* Authorized-dealer footer line — small caps tracked, muted ink, no gold pill */
+ .authorized, .authorized-dealer p {
+ background: transparent !important;
+ color: rgba(245,241,232,0.5) !important;
+ font-size: 10px !important;
+ letter-spacing: 0.22em !important;
+ text-transform: uppercase !important;
+ padding: 0 !important;
+ border: none !important;
+ margin-top: 14px !important;
+ }
+ /* Kill any straggler white bg in info-card / sample-related cards */
+ .info-card, .info-card-link, .care-highlight, .feature-card, .spec-item {
+ background-color: rgba(245,241,232,0.04) !important;
+ border-color: rgba(245,241,232,0.12) !important;
+ }
+ /* Ensure modal content stays readable — Steve's earlier sweep made it #141414 with white text */
+ .modal-content { background: #141414 !important; color: #f5f1e8 !important; }
+ .modal-content * { color: #f5f1e8 !important; }
+ .modal-content input, .modal-content textarea, .modal-content select {
+ background: rgba(245,241,232,0.05) !important;
+ color: #f5f1e8 !important;
+ border-color: rgba(245,241,232,0.2) !important;
+ }
+ .modal-content label { color: rgba(245,241,232,0.6) !important; }
+ /* Force the wide-tracked NOVASUEDE wordmark to stay white */
+ .ns-wordmark, .ns-wordmark sup { color: #f5f1e8 !important; }
+
+ /* ─── Modal-internal contrast (post-QA defects) ─── */
+ /* Backdrop covers full viewport */
+ .modal { width: 100vw !important; height: 100vh !important; left: 0 !important; top: 0 !important; background: rgba(0, 0, 0, 0.78) !important; backdrop-filter: blur(6px); }
+ /* Chip — flip from white-on-white island to dark card */
+ .request-chip {
+ background: rgba(245, 241, 232, 0.06) !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.18) !important;
+ border-radius: 4px !important;
+ }
+ .request-chip * { color: #f5f1e8 !important; }
+ .request-chip .chip-swatch {
+ /* swatch dot ALWAYS displays the inline-style hex from JS — fight any sweep */
+ border: 1px solid rgba(0, 0, 0, 0.4) !important;
+ }
+ /* + Add to request — hairline white-border on transparent (no gold) */
+ .add-to-request-btn {
+ background: transparent !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.6) !important;
+ border-radius: 0 !important;
+ padding: 12px 22px !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 400 !important;
+ }
+ .add-to-request-btn:hover {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ /* Hex caption visible */
+ .preview-hex, #colorPreviewHex {
+ display: block !important;
+ color: rgba(245, 241, 232, 0.55) !important;
+ font-family: ui-monospace, "SF Mono", Menlo, monospace !important;
+ font-size: 11px !important;
+ margin-top: 4px !important;
+ letter-spacing: 0.04em !important;
+ text-transform: lowercase !important;
+ }
+ /* Color preview name — stays single, prominent */
+ #colorPreviewName {
+ color: #f5f1e8 !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 500 !important;
+ display: block !important;
+ margin-top: 12px !important;
+ }
+
+ /* ─── Mobile hamburger nav (replaces the overflow at <760px) ─── */
+ @media (max-width: 760px) {
+ header .nav-container { flex-wrap: wrap; }
+ header nav { display: none; flex-basis: 100%; flex-direction: column; gap: 6px; padding: 16px 0 6px; }
+ header nav.open { display: flex; }
+ header nav a { padding: 10px 0; border-bottom: 1px solid rgba(245,241,232,0.1); }
+ header .mobile-toggle {
+ display: flex !important;
+ flex-direction: column;
+ gap: 5px;
+ background: transparent;
+ border: 0;
+ cursor: pointer;
+ padding: 8px;
+ }
+ header .mobile-toggle span {
+ width: 22px; height: 1.5px;
+ background: #f5f1e8;
+ display: block;
+ }
+ }
+ @media (min-width: 761px) {
+ header .mobile-toggle { display: none !important; }
+ }
+
+ </style>
+<style id="ns-gold-kill">
+ /* novasuede: kill gold-solid fills, off-white instead. Edge color stays gold. */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active, .drapery-btn,
+ .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover, .drapery-btn:hover,
+ .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ </style>
+<header class="ns-header">
+ <a href="/" class="ns-brand" aria-label="Home">
+ <span class="ns-dot"></span>
+ <span class="ns-name">Atelier Couture</span>
+ </a>
+ <nav class="ns-nav" id="nsPrimaryNav" aria-label="Primary">
+ <a href="/">Home</a>
+ <a href="/about">About</a>
+ <button type="button" onclick="nsContactOpen()">Contact</button>
+ </nav>
+ <button type="button" class="mobile-toggle" aria-label="Toggle menu" aria-controls="nsPrimaryNav" aria-expanded="false" onclick="nsToggleNav(this)">
+ <span></span><span></span><span></span>
+ </button>
+ </header>
+
+<div class="ns-modal" id="nsContactModal" role="dialog" aria-modal="true" aria-labelledby="nsContactTitle" onclick="if(event.target===this)nsContactClose()">
+ <div class="ns-modal-card">
+ <button type="button" class="close-x" onclick="nsContactClose()" aria-label="Close">×</button>
+ <h3 id="nsContactTitle">How can we help?</h3>
+ <p class="sub">Trade-account replies within one business day.</p>
+ <div class="ns-actions">
+ <a class="ns-action" href="#contact" onclick="nsContactClose()"><span class="ico">📋</span><div class="txt"><b>Get a Quote</b><span>Project specs → priced estimate</span></div></a>
+ <a class="ns-action" href="#contact" onclick="nsContactClose();openModal();"><span class="ico">📦</span><div class="txt"><b>Order a Sample</b><span>Free swatch to your shop</span></div></a>
+ <a class="ns-action" href="mailto:info@silkwallpaper.com?subject=Atelier%20Couture%20inquiry"><span class="ico">✉️</span><div class="txt"><b>Email Us</b><span>info@silkwallpaper.com</span></div></a>
+ <a class="ns-action" href="tel:888-373-4564"><span class="ico">📞</span><div class="txt"><b>Call Us</b><span>(888) 373-4564 · M–F, 9–5 PT</span></div></a>
+ </div>
+ </div>
+ </div>
+<script>
+ function nsContactOpen() { var m = document.getElementById('nsContactModal'); if (m) m.classList.add('show'); }
+ function nsContactClose() { var m = document.getElementById('nsContactModal'); if (m) m.classList.remove('show'); }
+ function nsToggleNav(btn) {
+ var nav = document.getElementById('nsPrimaryNav');
+ if (!nav) return;
+ var open = nav.classList.toggle('open');
+ btn.setAttribute('aria-expanded', open ? 'true' : 'false');
+ }
+ document.addEventListener('keydown', function(e){ if (e.key === 'Escape') nsContactClose(); });
+</script>
+
+
+
+
+
+
+
+<header>
+ <button class="h-link" onclick="dwmOpen('Contact')" aria-label="Contact"><svg class="h-icon" viewBox="0 0 24 24"><path d="M3 7h18M3 12h18M3 17h18"/></svg><span>Contact</span></button>
+ <button class="theme-toggle" id="theme-toggle" aria-label="Theme toggle">☾</button>
+</header>
+
+<section class="cinema">
+ <div class="cinema-bg" id="heroBg"></div>
+ <div class="corner-mark">Silken Sheen</div>
+ <div class="center-mark">Silk Wallpaper</div>
+ <div class="meta-line">Silk · Luxe · Sheen · Jacquard<span class="num" id="heroNum"></span></div>
+ <a class="enter" href="#shop">Enter <svg viewBox="0 0 24 12" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M0 6h22M16 1l6 5-6 5"/></svg></a>
+</section>
+
+
+<section class="section" id="ideas">
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">New this week</h2>
+ <div class="rail" id="railNewest"></div>
+ </div>
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">Curated picks</h2>
+ <div class="rail" id="railCurated"></div>
+ </div>
+</section>
+
+<section class="section" id="shop">
+ <div class="section-header">
+ <div>
+ <div class="section-eyebrow">The Collection</div>
+ <h2 class="section-title"><span class="accent" id="totalCount">—</span> Patterns.</h2>
+ </div>
+ <div class="section-meta">Silk · Luxe · Sheen · Jacquard</div>
+ </div>
+
+ <div class="filters" id="facets">
+ <button class="chip active" data-facet="all">All</button>
+ </div>
+
+ <div class="density">
+ <label>Sort</label>
+ <select id="sortSelect" aria-label="Sort products" style="background:transparent;color:inherit;border:1px solid var(--line,rgba(255,255,255,.2));font:inherit;font-size:11px;letter-spacing:.10em;text-transform:uppercase;padding:4px 8px;cursor:pointer;outline:none;margin-right:14px">
+ <option value="newest">Newest</option>
+ <option value="color">Color</option>
+ <option value="style">Style</option>
+ <option value="sku">SKU A→Z</option>
+ <option value="title">Title A→Z</option>
+ <option value="price-asc">Price ↑</option>
+ <option value="price-desc">Price ↓</option>
+ </select>
+ <label>Grid</label>
+ <input type="range" id="densitySlider" min="4" max="12" step="1" value="6" aria-label="Grid columns">
+ <span class="ct" id="densityLabel">6 cols</span>
+ <div class="search">
+ <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5.5-5.5"/></svg>
+ <input type="text" id="searchInput" placeholder="Search by pattern, color…" autocomplete="off">
+ </div>
+ </div>
+
+ <div class="stat-line" id="statLine">Loading…</div>
+ <div class="grid" id="grid"></div>
+ <div class="loading" id="loading">Loading more…</div>
+ <div class="sentinel" id="sentinel"></div>
+</section>
+
+<footer>
+ <div class="footer-grid">
+ <div>
+ <div class="footer-brand">Atelier Couture</div>
+ <p class="footer-text">Atelier Couture — real samples ship free.</p>
+ <p class="footer-text" style="margin-top:14px;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;font-weight:600;color:var(--paper);opacity:0.7"><a href="/about" style="color:inherit;text-decoration:none;border-bottom:1px solid var(--accent)">About →</a></p>
+ </div>
+ <div class="footer-col">
+ <h4>Aesthetic</h4>
+ <div id="footerFacets"></div>
+ </div>
+ <div class="footer-col">
+ <h4>Trade</h4>
+ <button onclick="dwmOpen('Inquiry')">Project Inquiry</button>
+ <button onclick="dwmOpen('Sample')">Request Sample</button>
+ <button onclick="dwmOpen('Contact')">Contact</button>
+ </div>
+
+ <div class="footer-col">
+ <h4>Social</h4>
+ <a class="fb-link" href="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings" target="_blank" rel="noopener">Facebook</a>
+ </div>
+ </div>
+ <div class="footer-bottom">
+ <span>silkwallpaper.com · Atelier Couture</span>
+ <span><a href="mailto:info@silkwallpaper.com" style="color:var(--paper);text-decoration:none;border-bottom:1px solid var(--accent)">info@silkwallpaper.com</a></span>
+ <span id="footerStat"></span>
+ </div>
+</footer>
+
+<script>
+// mini-constellation — 43 niche-stars, current site highlighted in gold
+(function(){
+ const SLUG = "silkwallpaper";
+ const NICHES = [
+ ['silkwallpaper','Silk','mat'],['silkwallcoverings','Silk W/C','mat'],['linenwallpaper','Linen','mat'],
+ ['jutewallpaper','Jute','mat'],['raffiawallcoverings','Raffia W/C','mat'],['raffiawalls','Raffia Walls','mat'],
+ ['corkwallcovering','Cork','mat'],['fabricwallpaper','Fabric','mat'],['textilewallpaper','Textile','mat'],
+ ['metallicwallpaper','Metallic','mat'],['silverleafwallpaper','Silver Leaf','mat'],['vinylwallpaper','Vinyl','mat'],
+ ['micawallpaper','Mica','mat'],['madagascarwallpaper','Madagascar','mat'],['mylarwallpaper','Mylar','mat'],
+ ['suedewallpaper','Suede','mat'],
+ ['1800swallpaper','1800s','dec'],['1890swallpaper','1890s','dec'],['1900swallpaper','1900s','dec'],
+ ['1920swallpaper','1920s','dec'],['1930swallpaper','1930s','dec'],['1940swallpaper','1940s','dec'],
+ ['1950swallpaper','1950s','dec'],['1960swallpaper','1960s','dec'],['1970swallpaper','1970s','dec'],
+ ['1980swallpaper','1980s','dec'],['retrowalls','Retro','dec'],['wallpapersback','W. Back','dec'],
+ ['agedwallpaper','Aged','cra'],['handcraftedwallpaper','Handcrafted','cra'],['museumwallpaper','Museum','cra'],
+ ['restorationwallpaper','Restoration','cra'],['pastelwallpaper','Pastel','cra'],['glitterwalls','Glitter','cra'],
+ ['greenwallcoverings','Green','cra'],['naturalwallcoverings','Natural','cra'],['saloonwallpaper','Saloon','cra'],
+ ['contractwallpaper','Contract','use'],['hotelwallcoverings','Hotel','use'],['hospitalitywallpaper','Hospitality','use'],
+ ['healthcarewallpaper','Healthcare','use'],['restaurantwallpaper','Restaurant','use'],['architecturalwallcoverings','Architectural','use']
+ ];
+ const COLOR = { mat:'#c9b687', dec:'#b86a4a', cra:'#6b8e6f', use:'#4a6b8e' };
+ const W = 1080, H = 200, BANDS = ['mat','dec','cra','use'];
+ const groups = { mat:[], dec:[], cra:[], use:[] };
+ for (const n of NICHES) groups[n[2]].push(n);
+ const svg = document.getElementById('miniConst');
+ if (!svg) return;
+ const bandW = W / BANDS.length;
+ let nodes = '';
+ BANDS.forEach((band, bi) => {
+ const list = groups[band];
+ const cx = bi * bandW + bandW / 2;
+ list.forEach((n, i) => {
+ const t = list.length === 1 ? 0.5 : i / (list.length - 1);
+ // deterministic jitter per slug
+ let h = 0; for (let k = 0; k < n[0].length; k++) h = (h * 31 + n[0].charCodeAt(k)) | 0;
+ const jx = ((h & 0xff) / 255 - 0.5) * (bandW * 0.5);
+ const jy = (((h >> 8) & 0xff) / 255 - 0.5) * 6; // tightened from 14 → 6 to prevent same-band collisions (16 mat nodes / 144px = 9px spacing)
+ const x = cx + jx, y = 28 + t * (H - 56) + jy;
+ const isCurrent = n[0] === SLUG;
+ const r = isCurrent ? 8 : 4;
+ const op = isCurrent ? 1 : 0.55;
+ nodes += `<g class="mc-node" data-slug="${n[0]}" data-label="${n[1]}" style="cursor:pointer">
+ <circle cx="${x}" cy="${y}" r="${r}" fill="url(#dna-${band})" fill-opacity="${op}" stroke="${COLOR[band]}" stroke-opacity="${isCurrent?1:0.7}" stroke-width="${isCurrent?2:1}">
+ <title>${n[1]} — ${n[0]}.com</title>
+ </circle>
+ ${isCurrent ? `<circle cx="${x}" cy="${y}" r="${r+5}" fill="none" stroke="${COLOR[band]}" stroke-opacity="0.4" stroke-width="1"><animate attributeName="r" values="${r+5};${r+12};${r+5}" dur="2.4s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" repeatCount="indefinite"/></circle>` : ''}
+ </g>`;
+ });
+ });
+ // append nodes after defs
+ svg.insertAdjacentHTML('beforeend', nodes);
+ svg.querySelectorAll('.mc-node').forEach(n => {
+ n.addEventListener('mouseenter', () => n.querySelector('circle').setAttribute('fill-opacity', '1'));
+ n.addEventListener('mouseleave', e => {
+ const isCurrent = n.dataset.slug === SLUG;
+ n.querySelector('circle').setAttribute('fill-opacity', isCurrent ? '1' : '0.55');
+ });
+ n.addEventListener('click', () => window.open('https://' + n.dataset.slug + '.com', '_blank'));
+ });
+})();
+</script>
+
+<script>
+const state = { q:'', facet:'all', page:1, pages:1, total:0, loading:false, exhausted:false };
+const LABELS = {"all":"All"};
+
+function escAttr(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
+// Image-URL allowlist: only DW Shopify CDN + designerwallcoverings.com (defends against XSS via crafted products.json)
+function safeImg(u) { return /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(u || '') ? u : '/hero-bg.jpg'; }
+function cardHTML(p) {
+ const eager = state.page === 1;
+ return '<img loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+ + '<div class="overlay">'
+ + '<div class="pat">' + escAttr(p.pattern_name || p.title) + '</div>'
+ + '<div class="ven">' + escAttr((p.vendor || '').replace(/-/g, ' ')) + '</div>'
+ + '<div class="actions">'
+ + '<button class="sample-btn" onclick="event.stopPropagation();dwmOpen(\'Sample\',' + JSON.stringify({sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}).replace(/"/g,'"') + ')">Sample</button>'
+ + '</div></div>';
+}
+
+async function loadFacets() {
+ let f;
+ try {
+ const r = await fetch('/api/facets');
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ f = await r.json();
+ } catch (e) { console.error('loadFacets failed:', e); return; }
+ const el = document.getElementById('facets');
+ for (const [k, v] of Object.entries(f.aesthetics).sort((a,b) => b[1] - a[1])) {
+ const b = document.createElement('button');
+ b.className = 'chip'; b.dataset.facet = k;
+ b.innerHTML = (LABELS[k] || k) + ' <span style="opacity:.55;font-weight:500;margin-left:4px">' + v + '</span>';
+ el.appendChild(b);
+ }
+ el.addEventListener('click', e => {
+ if (e.target.tagName !== 'BUTTON') return;
+ document.querySelectorAll('#facets button').forEach(b => b.classList.remove('active'));
+ e.target.classList.add('active');
+ state.facet = e.target.dataset.facet;
+ resetGrid();
+ });
+ document.getElementById('totalCount').textContent = f.total;
+ document.getElementById('footerStat').textContent = f.total + ' patterns · live archive';
+ // Footer aesthetic links
+ const footerEl = document.getElementById('footerFacets');
+ for (const [k] of Object.entries(f.aesthetics)) {
+ const b = document.createElement('button');
+ b.textContent = LABELS[k] || k;
+ b.onclick = () => { state.facet = k; resetGrid(); document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.facet === k)); document.getElementById('shop').scrollIntoView({behavior:'smooth'}); };
+ footerEl.appendChild(b);
+ }
+}
+
+function resetGrid() {
+ state.page = 1; state.exhausted = false;
+ document.getElementById('grid').innerHTML = '';
+ document.getElementById('loading').textContent = 'Loading…';
+ loadGridPage();
+}
+
+async function loadGridPage() {
+ if (state.loading || state.exhausted) return;
+ state.loading = true;
+ const params = new URLSearchParams({ page:state.page, limit:24 });
+ if (state.q) params.set('q', state.q);
+ if (state.facet !== 'all') params.set('aesthetic', state.facet);
+ let data;
+ try {
+ const r = await fetch('/api/products?' + params);
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ data = await r.json();
+ } catch (e) {
+ console.error('loadGridPage failed:', e);
+ document.getElementById('loading').textContent = '— offline — refresh to retry —';
+ state.loading = false;
+ return;
+ }
+ state.total = data.total; state.pages = data.pages;
+
+ if (state.page === 1) {
+ document.getElementById('statLine').textContent = data.total + ' patterns · ' + (state.facet === 'all' ? 'all aesthetics' : (LABELS[state.facet] || state.facet)) + (state.q ? ' · matching "' + state.q + '"' : '');
+ }
+ const grid = document.getElementById('grid');
+ for (const p of data.items) {
+ const a = document.createElement('a');
+ a.className = 'card'; a.href = '#'; a.onclick = (e) => { e.preventDefault(); dwmOpen('Sample',{sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}); };
+ a.innerHTML = cardHTML(p);
+ grid.appendChild(a);
+ }
+ if (state.page >= state.pages || data.items.length === 0) {
+ state.exhausted = true;
+ document.getElementById('loading').textContent = state.total > 0 ? '— end of archive · ' + state.total + ' patterns —' : '';
+ } else state.page++;
+ state.loading = false;
+}
+
+const io = new IntersectionObserver(es => { for (const e of es) if (e.isIntersecting) loadGridPage(); }, { rootMargin:'600px 0px' });
+io.observe(document.getElementById('sentinel'));
+
+document.getElementById('searchInput').addEventListener('input', e => {
+ state.q = e.target.value.trim();
+ clearTimeout(window._t);
+ window._t = setTimeout(resetGrid, 220);
+});
+
+// Density slider
+const slider = document.getElementById('densitySlider');
+const dlabel = document.getElementById('densityLabel');
+function setDensity(n) {
+ document.documentElement.style.setProperty('--cols', n);
+ dlabel.textContent = n + ' cols';
+ try { localStorage.setItem('silk_theme_density', n); } catch(e){}
+}
+slider.addEventListener('input', e => setDensity(parseInt(e.target.value)));
+const savedDensity = parseInt(localStorage.getItem('silk_theme_density') || '6');
+if (savedDensity >= 4 && savedDensity <= 12) { slider.value = savedDensity; setDensity(savedDensity); }
+
+// Theme toggle
+const tb = document.getElementById('theme-toggle');
+function setTheme(t){ document.documentElement.dataset.theme = t; try { localStorage.setItem('silk_theme', t); } catch(e){} tb.textContent = t === 'dark' ? '☾' : '☀'; }
+setTheme(document.documentElement.dataset.theme || 'dark');
+tb.addEventListener('click', () => setTheme(document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'));
+
+loadFacets();
+loadGridPage();
+</script>
+<!-- ============================================================
+ DW UNIVERSAL CONTACT MODULE — fashion-house UX
+ Inject before </body> in every DW-family sister site.
+ Self-contained: CSS + 4 modals + JS submission handlers.
+ Uses host site's CSS vars (--bg, --fg, --gold, --rule).
+ ============================================================ -->
+<style>
+ /* === MODAL BASE === */
+ .dwm{position:fixed;inset:0;background:rgba(0,0,0,0.78);display:none;align-items:center;justify-content:center;z-index:9999;padding:24px;overflow-y:auto}
+ .dwm.open{display:flex}
+ .dwm-box{background:var(--bg);border:1px solid var(--rule);max-width:520px;width:100%;padding:36px 32px;position:relative;max-height:90vh;overflow-y:auto;animation:dwm-in 0.25s ease}
+ @keyframes dwm-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+ .dwm-box h3{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-weight:400;font-size:32px;line-height:1.05;margin-bottom:6px;color:var(--fg);letter-spacing:-0.01em}
+ .dwm-box .sub{color:var(--muted);font-size:12px;letter-spacing:0.16em;text-transform:uppercase;margin-bottom:24px;font-weight:500}
+ .dwm-close{position:absolute;top:18px;right:18px;background:transparent;border:0;color:var(--muted);font-size:24px;cursor:pointer;width:32px;height:32px;line-height:1;padding:0}
+ .dwm-close:hover{color:var(--gold)}
+ .dwm-preview{display:flex;gap:14px;align-items:center;padding:12px;background:var(--bg-soft);margin-bottom:18px}
+ .dwm-preview img{width:64px;height:64px;object-fit:cover}
+ .dwm-preview .pn{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-size:15px;line-height:1.2;color:var(--fg)}
+ .dwm-preview .pc{font-size:10px;letter-spacing:0.14em;text-transform:uppercase;color:var(--muted);margin-top:4px}
+ .dwm-field{margin-bottom:14px}
+ .dwm-field label{display:block;font-size:10px;letter-spacing:0.18em;text-transform:uppercase;color:var(--muted);margin-bottom:6px;font-weight:500}
+ .dwm-field input,.dwm-field textarea,.dwm-field select{width:100%;border:0;border-bottom:1px solid var(--rule);background:transparent;padding:8px 4px;font-size:14px;font-family:inherit;color:var(--fg);outline:none;transition:border-color 0.2s}
+ .dwm-field input:focus,.dwm-field textarea:focus{border-bottom-color:var(--gold)}
+ .dwm-field textarea{min-height:60px;resize:vertical}
+ .dwm-submit{background:transparent;color:var(--muted, #c9b89e);border:1px solid var(--muted, #c9b89e);padding:14px 28px;font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:700;cursor:pointer;width:100%;margin-top:8px;transition:background 0.15s, color 0.15s}
+ .dwm-submit:hover{background:var(--muted, #c9b89e);color:var(--bg, #000)}
+
+ .dwm-submit:disabled{opacity:0.5;cursor:default}
+ .dwm-status{margin-top:14px;font-size:11px;letter-spacing:0.16em;text-transform:uppercase;text-align:center;display:none}
+ .dwm-status.ok{color:var(--sage);display:block}
+ .dwm-status.err{color:#e08070;display:block}
+ /* contact-options */
+ .dwm-options{display:flex;flex-direction:column;gap:10px}
+ .dwm-option{display:flex;align-items:center;gap:14px;padding:18px 20px;background:transparent;border:1px solid var(--rule);color:var(--fg);font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:600;cursor:pointer;text-decoration:none;transition:all 0.2s;text-align:left;width:100%}
+ .dwm-option:hover{border-color:var(--gold);color:var(--gold)}
+ .dwm-option svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:1.5;flex-shrink:0}
+ .dwm-option .lbl{flex:1;display:block}
+ .dwm-option .val{display:block;font-size:10px;letter-spacing:0.14em;text-transform:none;font-weight:500;opacity:0.65;margin-top:4px}
+
+/* graphics-loop pass 5: serif hero typography + nav legibility */
+.cinema h1, h1.headline { font-family: 'Cormorant Garamond', Georgia, serif; font-weight: 500; letter-spacing: -0.01em }
+.h-link { font-size: 13px; letter-spacing: 0.06em }
+
+/* graphics-loop pass 11: Ideas rails */
+#ideas { padding: 56px 24px 24px; max-width:1600px; margin:0 auto }
+#ideas .rail-block { margin-bottom:48px }
+#ideas .rail-eyebrow { font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:var(--muted, #888); margin-bottom:8px }
+#ideas .rail-title { font-size:clamp(22px,2.6vw,32px); font-weight:500; letter-spacing:-0.01em; margin:0 0 18px; color:var(--ink,#fff) }
+#ideas .rail { display:flex; gap:14px; overflow-x:auto; scroll-snap-type:x mandatory; padding-bottom:8px; scrollbar-width:none }
+#ideas .rail::-webkit-scrollbar { display:none }
+#ideas .rail-card { flex:0 0 240px; scroll-snap-align:start; cursor:pointer; transition:transform 0.2s }
+#ideas .rail-card:hover { transform:translateY(-3px) }
+#ideas .rail-card img { width:100%; aspect-ratio:1; object-fit:cover; display:block; background:var(--card,#222) }
+#ideas .rail-card .rc-title { font-size:13px; font-weight:500; color:var(--paper,#fff); padding:8px 0 2px; line-height:1.3 }
+#ideas .rail-card .rc-meta { font-size:11px; color:var(--muted,#888); letter-spacing:0.06em }
+@media (max-width:720px) { #ideas .rail-card { flex:0 0 160px } }
+</style>
+
+<!-- Contact options modal -->
+<div class="dwm" id="dwmContact" onclick="if(event.target===this)dwmClose('Contact')">
+ <div class="dwm-box" style="max-width:440px">
+ <button class="dwm-close" onclick="dwmClose('Contact')" aria-label="Close">×</button>
+ <h3>Contact</h3>
+ <p class="sub" id="dwmContactSub">Three ways to reach us</p>
+ <div class="dwm-options">
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Inquiry')">
+ <svg viewBox="0 0 24 24"><path d="M4 4h16v12H7l-3 3z"/></svg>
+ <span class="lbl">Send an Inquiry<span class="val">Project name · scope · all the details</span></span>
+ </button>
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Sample')">
+ <svg viewBox="0 0 24 24"><rect x="4" y="6" width="16" height="14"/><path d="M4 10h16M9 6V3h6v3"/></svg>
+ <span class="lbl">Request a Sample<span class="val">Memo sample · ships free · 3–5 business days</span></span>
+ </button>
+ <a class="dwm-option" id="dwmContactEmailLink" href="mailto:info@silkwallpaper.com">
+ <svg viewBox="0 0 24 24"><path d="M3 6h18v12H3z"/><path d="M3 6l9 7 9-7"/></svg>
+ <span class="lbl">Email Us<span class="val" id="dwmContactEmailLabel">info@silkwallpaper.com</span></span>
+ </a>
+ </div>
+ </div>
+</div>
+
+<!-- Inquiry modal -->
+<div class="dwm" id="dwmInquiry" onclick="if(event.target===this)dwmClose('Inquiry')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Inquiry')" aria-label="Close">×</button>
+ <h3>Project Inquiry</h3>
+ <p class="sub">We'll respond within one business day</p>
+ <form id="dwmInquiryForm" onsubmit="return dwmSubmit(event,'inquiry')">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Phone</label><input type="tel" name="phone"></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Project Name</label><input type="text" name="projectName" required></div>
+ <div class="dwm-field"><label>Project Scope</label><textarea name="projectScope" placeholder="Square footage · room count · timeline" required></textarea></div>
+ <div class="dwm-field"><label>Additional Information</label><textarea name="message" placeholder="Patterns of interest, finish preferences, budget"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Inquiry</button>
+ <div class="dwm-status" id="dwmInquiryStatus"></div>
+ </form>
+ </div>
+</div>
+
+<!-- Sample-request modal -->
+<div class="dwm" id="dwmSample" onclick="if(event.target===this)dwmClose('Sample')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Sample')" aria-label="Close">×</button>
+ <h3>Request a Sample</h3>
+ <p class="sub">Memo sample · ships free · 3–5 business days</p>
+ <div class="dwm-preview" id="dwmSamplePreview" style="display:none">
+ <img id="dwmSampleImg" src="" alt="">
+ <div><div class="pn" id="dwmSampleName"></div><div class="pc" id="dwmSampleSku"></div></div>
+ </div>
+ <form id="dwmSampleForm" onsubmit="return dwmSubmit(event,'sample')">
+ <input type="hidden" name="sku" id="dwmSampleSkuInput">
+ <input type="hidden" name="title" id="dwmSampleTitleInput">
+ <input type="hidden" name="image_url" id="dwmSampleImageInput">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Shipping Address</label><input type="text" name="address" placeholder="Street" required></div>
+ <div class="dwm-field" style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:8px">
+ <input type="text" name="city" placeholder="City" required>
+ <input type="text" name="state" placeholder="State" required>
+ <input type="text" name="zip" placeholder="ZIP" required>
+ </div>
+ <div class="dwm-field"><label>Notes (optional)</label><textarea name="message" placeholder="Project, sq.ft., timing"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Sample Request</button>
+ <div class="dwm-status" id="dwmSampleStatus"></div>
+ </form>
+ </div>
+</div>
+
+<script>
+ // Universal modal control
+ function dwmOpen(name, opts){
+ document.getElementById('dwm' + name).classList.add('open');
+ document.body.style.overflow = 'hidden';
+ if (name === 'Sample' && opts) {
+ document.getElementById('dwmSamplePreview').style.display = 'flex';
+ document.getElementById('dwmSampleImg').src = opts.image_url || '';
+ document.getElementById('dwmSampleName').textContent = opts.title || '';
+ document.getElementById('dwmSampleSku').textContent = opts.sku || '';
+ document.getElementById('dwmSampleSkuInput').value = opts.sku || '';
+ document.getElementById('dwmSampleTitleInput').value = opts.title || '';
+ document.getElementById('dwmSampleImageInput').value = opts.image_url || '';
+ } else if (name === 'Sample') {
+ document.getElementById('dwmSamplePreview').style.display = 'none';
+ document.getElementById('dwmSampleSkuInput').value = '';
+ document.getElementById('dwmSampleTitleInput').value = '';
+ document.getElementById('dwmSampleImageInput').value = '';
+ }
+ }
+ function dwmClose(name){ document.getElementById('dwm'+name).classList.remove('open'); document.body.style.overflow=''; }
+
+ // Universal form submit — POSTs to /api/send-{kind}
+ async function dwmSubmit(e, kind) {
+ e.preventDefault();
+ const form = e.target;
+ const data = Object.fromEntries(new FormData(form).entries());
+ const btn = form.querySelector('.dwm-submit');
+ const status = form.querySelector('.dwm-status');
+ btn.disabled = true; const orig = btn.textContent; btn.textContent = 'Sending…';
+ status.className = 'dwm-status';
+ try {
+ const r = await fetch('/api/send-' + kind, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data) });
+ if (!r.ok) throw new Error('HTTP '+r.status);
+ const j = await r.json();
+ status.textContent = kind === 'sample' ? 'Sample request sent — ships within 3–5 business days' : 'Inquiry sent — we respond within 1 business day';
+ status.className = 'dwm-status ok';
+ btn.textContent = 'Sent ✓';
+ setTimeout(() => { dwmClose(kind === 'sample' ? 'Sample' : 'Inquiry'); btn.disabled = false; btn.textContent = orig; status.className = 'dwm-status'; form.reset(); }, 2800);
+ } catch (err) {
+ status.textContent = 'Send failed — please email info@designerwallcoverings.com';
+ status.className = 'dwm-status err';
+ btn.disabled = false; btn.textContent = orig;
+ }
+ return false;
+ }
+
+ // Wire up "Memo samples ship free" badge in hero to open sample modal directly
+ document.querySelectorAll('.hero .badge, [data-open-sample]').forEach(el => {
+ el.style.cursor = 'pointer';
+ el.addEventListener('click', () => dwmOpen('Sample'));
+ });
+
+ // Wire nav "Trade" link to inquiry modal
+ document.querySelectorAll('nav a[href*="designerwallcoverings.com"]').forEach(a => {
+ a.removeAttribute('target');
+ a.removeAttribute('href');
+ a.style.cursor = 'pointer';
+ a.addEventListener('click', e => { e.preventDefault(); dwmOpen('Contact'); });
+ });
+
+ // Override product card clicks — open Sample modal pre-filled instead of redirecting to DW
+ document.addEventListener('click', e => {
+ const card = e.target.closest('.card, .rail-card');
+ if (!card) return;
+ if (card.tagName === 'A' && (card.getAttribute('href') || '').startsWith('/sample/')) {
+ e.preventDefault();
+ const img = card.querySelector('img');
+ const pat = card.querySelector('.pat');
+ const sku = card.getAttribute('href').replace('/sample/', '');
+ dwmOpen('Sample', {
+ sku,
+ title: pat ? pat.textContent.trim() : '',
+ image_url: img ? img.src : ''
+ });
+ }
+ }, true);
+// DW STANDARD: hero pulls from products.json[0].image_url so dw-collections-viewer
+// drag-drop / hero-pin updates the live front page on next deploy.
+// Always upgrades to the largest Shopify variant (strips _NxN size suffix) so
+// the full-page hero stays sharp.
+// DW STANDARD: hero rotates through top-20 NEWEST products on each visit
+// (novasuede.com pattern). Fetches /api/products?limit=100, sorts by created_at desc,
+// takes the freshest 20, picks one per visit via localStorage counter.
+// Falls back to static /hero-bg.jpg on any error.
+(async function rotateHero() {
+ try {
+ const r = await fetch('/api/products?limit=100');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || [])
+ .filter(p => p && p.image_url && /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(p.image_url))
+ .sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')))
+ .slice(0, 20);
+ if (!items.length) return;
+ let visits = 0;
+ try {
+ visits = parseInt(localStorage.getItem('hero_visits') || '0', 10) || 0;
+ localStorage.setItem('hero_visits', String(visits + 1));
+ } catch(e) {}
+ const pick = items[visits % items.length];
+ // Strip _NxN size suffix to get the largest CDN variant
+ const url = pick.image_url.replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ const bg = document.getElementById('heroBg') || document.getElementById('cinemaBg');
+ if (bg) bg.style.backgroundImage = "url('" + url + "')";
+ } catch (e) {}
+})();
+
+
+// DW STANDARD sort-select wire-up (added by fleet patcher)
+(function(){
+ const sortSel = document.getElementById('sortSelect');
+ if (!sortSel) return;
+ const KEY = location.hostname.replace(/\./g, '_') + '_sort';
+ const saved = localStorage.getItem(KEY);
+ if (saved) { try { sortSel.value = saved; } catch(e){} }
+ if (typeof state !== 'undefined') state.sort = sortSel.value;
+ sortSel.addEventListener('change', e => {
+ const v = e.target.value;
+ try { localStorage.setItem(KEY, v); } catch(e){}
+ if (typeof state !== 'undefined') state.sort = v;
+ if (typeof resetGrid === 'function') resetGrid();
+ else location.reload();
+ });
+})();
+
+// graphics-loop pass 11: hydrate Ideas rails from /api/products
+(async function hydrateIdeas() {
+ try {
+ const r = await fetch('/api/products?limit=80');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || []).filter(p => p && p.image_url);
+ if (!items.length) return;
+ const sorted = [...items].sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')));
+ const newest = sorted.slice(0, 12);
+ // Curated = deterministic offset slice (every 5th item starting at idx 3) for variety
+ const curated = items.filter((_, i) => i % 5 === 3).slice(0, 12);
+ function card(p) {
+ const url = (p.image_url || '').replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ return '<a class="rail-card" href="/sample/' + encodeURIComponent(p.handle || p.sku) + '">' +
+ '<img src="' + url + '" alt="' + (p.title || '').replace(/"/g, '"') + '" loading="lazy">' +
+ '<div class="rc-title">' + ((p.title || '').split('|')[0] || '').trim() + '</div>' +
+ '<div class="rc-meta">' + (p.vendor || '') + '</div>' +
+ '</a>';
+ }
+ const r1 = document.getElementById('railNewest');
+ const r2 = document.getElementById('railCurated');
+ if (r1) r1.innerHTML = newest.map(card).join('');
+ if (r2) r2.innerHTML = curated.map(card).join('');
+ } catch(e) {}
+})();
+</script>
+
+</body>
+</html>
diff --git a/public/index.html.bak.nspropagate.1778226888840 b/public/index.html.bak.nspropagate.1778226888840
new file mode 100644
index 0000000..b8eaae0
--- /dev/null
+++ b/public/index.html.bak.nspropagate.1778226888840
@@ -0,0 +1,976 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<!-- Google tag (gtag.js) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=G-CNTJNLTEW8"></script>
+<script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+ gtag('config', 'G-CNTJNLTEW8');
+</script>
+
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Atelier Couture — SILK — IN BLACK.</title>
+<meta name="description" content="Atelier Couture is a curated atelier dedicated entirely to silk. We do one thing — and we do it carefully.">
+<meta name="theme-color" content="#FFF">
+<link rel="canonical" href="https://silkwallpaper.com/">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
+<link rel="preload" as="image" href="/hero-bg.jpg" fetchpriority="high">
+<script type="application/ld+json">
+{
+ "@context": "https://schema.org",
+ "@type": "Organization",
+ "name": "Atelier Couture",
+ "url": "https://silkwallpaper.com/",
+ "email": "info@silkwallpaper.com"
+}
+</script>
+<script src="/zd-loader.js" defer></script>
+<style>
+/* Atelier Couture · Saint Laurent template — pure monochrome, sharp uppercase, zero ornament */
+:root {
+ --bg: #000;
+ --paper: #FFF;
+ --muted: #888;
+ --line: rgba(255,255,255,0.20);
+ --accent: #FFF;
+ --bg-soft: #0a0a0a;
+ --cols: 6;
+}
+html[data-theme="light"] {
+ --bg: #FFF;
+ --paper: #000;
+ --muted: #666;
+ --line: rgba(0,0,0,0.20);
+ --accent: #000;
+ --bg-soft: #F5F5F5;
+}
+* { margin:0; padding:0; box-sizing:border-box }
+html { scroll-behavior:smooth }
+body { font-family:'Inter', 'Helvetica Neue', sans-serif; color:var(--paper); -webkit-font-smoothing:antialiased; background:var(--bg); min-height:100vh; overflow-x:hidden }
+/* DW-FASHION-TEMPLATE-OVERRIDE */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', Georgia, serif !important; font-weight:900 !important; text-transform:uppercase !important;letter-spacing:0.04em !important; }
+/* Saint Laurent: heavy uppercase headlines, no italic serif accent */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', 'Helvetica Neue', sans-serif !important; font-weight:900 !important; font-style:normal !important; letter-spacing:0.04em !important; text-transform:uppercase !important }
+
+/* ===== Auto-hide header ===== */
+header { position:fixed; top:0; left:0; right:0; display:flex; justify-content:space-between; align-items:center; padding:22px 32px; z-index:100; background:rgba(0,0,0,0.55); backdrop-filter:blur(10px); border-bottom:1px solid var(--line); transition:transform .3s }
+.h-link { display:flex; align-items:center; gap:10px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); text-decoration:none; cursor:pointer; background:transparent; border:0 }
+.h-link:hover { opacity:0.7 }
+.h-icon { width:18px; height:18px; stroke:currentColor; fill:none; stroke-width:1.5 }
+
+/* ===== Cinema hero ===== */
+.cinema { position:relative; height:100vh; width:100%; overflow:hidden }
+.cinema-bg { position:absolute; inset:0; background-size:cover; background-position:center; background-image:url('/hero-bg.jpg') }
+.cinema-bg::after { content:""; position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.75) 100%) }
+.corner-mark { position:absolute; top:24px; left:32px; font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:#fff; text-shadow:0 1px 3px rgba(0,0,0,0.55), 0 0 1px rgba(0,0,0,0.4); z-index:5 }
+.corner-mark::before { content:"◆ "; margin-right:6px }
+.center-mark { position:absolute; top:clamp(80px,12vh,140px); left:50%; transform:translateX(-50%); font-size:clamp(28px,4vw,56px); font-weight:500; letter-spacing:0.18em; text-transform:uppercase; text-align:center; color:#fff; text-shadow:0 2px 8px rgba(0,0,0,0.65), 0 0 1px rgba(0,0,0,0.5); z-index:5 }
+.center-mark .tm { font-size:14px; vertical-align:super; opacity:0.7; margin-left:0.2em }
+.center-mark .sub { display:block; font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400; font-size:0.4em; letter-spacing:0.04em; text-transform:none; margin-top:14px; opacity:0.85 }
+.meta-line { position:absolute; bottom:32px; left:32px; font-size:10px; letter-spacing:0.28em; text-transform:uppercase; font-weight:700; color:#fff; z-index:5; opacity:0.85 }
+.meta-line .num { font-size:12px; display:block; margin-top:4px; letter-spacing:0.05em }
+.enter { position:absolute; bottom:32px; right:32px; display:flex; align-items:center; gap:14px; font-size:11px; letter-spacing:0.36em; text-transform:uppercase; font-weight:700; color:#fff; text-decoration:none; padding-bottom:6px; border-bottom:1px solid #fff; transition:gap 0.25s; z-index:5 }
+.enter:hover { gap:22px }
+.enter svg { width:24px; height:12px }
+
+/* ===== Section ===== */
+.section { padding:96px 32px }
+.section-header { display:flex; justify-content:space-between; align-items:flex-end; flex-wrap:wrap; gap:24px; margin-bottom:32px; padding-bottom:24px; border-bottom:1px solid var(--line) }
+.section-title { font-size:clamp(36px,5vw,64px); font-weight:300; letter-spacing:-0.02em; line-height:1 }
+.section-title .accent { color: var(--accent); font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400 }
+.section-eyebrow { font-size:10px; letter-spacing:0.4em; text-transform:uppercase; font-weight:700; color:var(--muted); margin-bottom:16px }
+.section-meta { font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:600; color:var(--muted); text-align:right; line-height:1.6 }
+
+/* ===== Filter bar ===== */
+.filters { display:flex; gap:8px; align-items:center; margin-bottom:18px; flex-wrap:wrap }
+.chip { padding:8px 16px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); background:transparent; border:1px solid var(--line); cursor:pointer; transition:all 0.2s; font-family:inherit }
+.chip:hover { border-color:var(--paper) }
+.chip.active { background:var(--paper); color:var(--bg); border-color:var(--paper) }
+.search { flex:0 1 280px; margin-left:auto; display:flex; align-items:center; gap:10px; border-bottom:1px solid var(--line); padding:6px 0 }
+.search input { flex:1; background:transparent; border:0; color:var(--paper); font-family:inherit; font-size:13px; outline:none; letter-spacing:0.04em }
+.search input::placeholder { color:var(--muted) }
+.search svg { width:16px; height:16px; stroke:var(--muted); fill:none; stroke-width:1.5 }
+
+/* ===== Grid-density slider ===== */
+.density { display:flex; align-items:center; gap:14px; padding:6px 0; margin-bottom:24px }
+.density label { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--muted) }
+.density input[type=range] { flex:1; max-width:240px; -webkit-appearance:none; appearance:none; height:1px; background:var(--line); outline:none }
+.density input[type=range]::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50% }
+.density input[type=range]::-moz-range-thumb { width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50%; border:0 }
+.density .ct { font-size:11px; color:var(--muted); letter-spacing:0.18em; text-transform:uppercase; font-weight:600; min-width:80px }
+
+/* ===== Stats line ===== */
+.stat-line { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:600; color:var(--muted); margin-bottom:24px }
+
+/* ===== Product grid (novasuede pattern: flush grid + slide-up overlay) ===== */
+.grid { display:grid; grid-template-columns:repeat(var(--cols), 1fr); gap:0 }
+.card { position:relative; aspect-ratio:1/1.15; cursor:pointer; overflow:hidden; border:1px solid var(--bg); transition:transform 0.4s cubic-bezier(0.2,0.8,0.2,1); background:var(--bg-soft) }
+.card:hover { transform:scale(1.04); z-index:5 }
+.card img { width:100%; height:100%; object-fit:cover; display:block; transition:transform 0.4s ease }
+.card .overlay { position:absolute; left:0; right:0; bottom:0; padding:20px 16px 14px; background:linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.85) 100%); transform:translateY(48%); opacity:0.85; transition:transform 0.3s ease, opacity 0.3s ease }
+.card:hover .overlay { transform:translateY(0); opacity:1 }
+.card .pat { font-size:13px; font-weight:600; letter-spacing:0.02em; color:#fff; line-height:1.25; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }
+.card .ven { font-size:9px; letter-spacing:0.18em; text-transform:uppercase; color:#fff; opacity:0.7; font-weight:500; margin-top:4px }
+.card .actions { margin-top:10px; display:flex; gap:6px }
+.card .sample-btn { flex:1; padding:7px 10px; background:#fff; color:#000; font-family:inherit; font-size:10px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; border:0; cursor:pointer; text-align:center; transition:all 0.2s }
+.card .sample-btn:hover { background:var(--accent); color:#fff }
+
+@media (max-width:1024px) { .grid { grid-template-columns:repeat(min(var(--cols), 4), 1fr) } }
+@media (max-width:720px) { .grid { grid-template-columns:repeat(2, 1fr) } .density { display:none } }
+
+.sentinel { height:1px }
+.loading { text-align:center; color:var(--muted); padding:32px; font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700 }
+
+/* ===== Footer ===== */
+footer { padding:64px 32px 32px; border-top:1px solid var(--line); margin-top:32px }
+.footer-grid { max-width:1400px; margin:0 auto; display:grid; grid-template-columns:2fr 1fr 1fr 1fr; gap:48px; margin-bottom:48px }
+.footer-brand { font-size:24px; font-weight:300; letter-spacing:0.32em; text-transform:uppercase; margin-bottom:12px }
+.footer-text { font-size:13px; line-height:1.6; color:var(--muted); max-width:380px }
+.footer-col h4 { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--paper); margin-bottom:18px }
+.footer-col a { display:block; font-size:13px; color:var(--muted); text-decoration:none; margin-bottom:8px; transition:color 0.2s; cursor:pointer; background:transparent; border:0; font-family:inherit; padding:0; text-align:left }
+.footer-col a:hover { color:var(--accent) }
+.footer-bottom { max-width:1400px; margin:0 auto; padding-top:24px; border-top:1px solid var(--line); display:flex; justify-content:space-between; flex-wrap:wrap; gap:16px; font-size:11px; letter-spacing:0.18em; text-transform:uppercase; color:var(--muted) }
+
+@media (max-width:720px) {
+ .corner-mark, .meta-line, .enter { font-size:10px }
+ .center-mark { font-size:24px; letter-spacing:0.12em; top:clamp(60px,9vh,90px) }
+ .section, footer { padding-left:20px; padding-right:20px }
+ .footer-grid { grid-template-columns:1fr; gap:32px }
+ .search { margin-left:0; flex:1 1 100% }
+}
+
+.theme-toggle { background:transparent; border:1px solid var(--line); width:32px; height:32px; cursor:pointer; color:var(--paper); display:inline-flex; align-items:center; justify-content:center; font-size:14px; line-height:1; transition:all .15s; padding:0 }
+.theme-toggle:hover { border-color:var(--accent); color:var(--accent) }
+
+/* ===== WCAG 2.4.7 — keyboard focus indicators ===== */
+/* Strictly :focus-visible so mouse clicks do NOT show the ring */
+a:focus-visible,
+button:focus-visible,
+input:focus-visible,
+select:focus-visible,
+textarea:focus-visible,
+[tabindex]:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+</style>
+<script>
+(function(){ try { var t = localStorage.getItem('silk_theme') || 'dark'; document.documentElement.dataset.theme = t; } catch(e){} })();
+</script>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600;700&display=swap">
+
+<meta property="og:type" content="website">
+<meta property="og:url" content="https://silkwallpaper.com/">
+<meta property="og:title" content="Silk Wallpaper">
+<meta property="og:description" content="Silk Wallpaper — Pure silk wallcoverings — luxe and lustrous.">
+<meta property="og:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<meta property="og:site_name" content="Silk Wallpaper">
+<meta property="article:publisher" content="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings">
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:title" content="Silk Wallpaper">
+<meta name="twitter:description" content="Silk Wallpaper — Pure silk wallcoverings — luxe and lustrous.">
+<meta name="twitter:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<!-- graphics-loop pass 13: footer + fb -->
+</head>
+<body>
+<style id="ns-header-modal-styles">
+ .ns-header { position: sticky; top: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; padding: 18px 36px; background: rgba(10,10,10,0.92) !important; backdrop-filter: blur(8px); border-bottom: 1px solid rgba(245,241,232,0.06); }
+ .ns-brand { display: inline-flex; align-items: center; gap: 14px; text-decoration: none; }
+ .ns-dot { width: 28px; height: 28px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, #a98b5b 0%, #5d4c32 60%, #332a1b 100%); box-shadow: 0 0 0 1px rgba(245,241,232,0.10), 0 2px 12px rgba(180,90,60,0.30); flex-shrink: 0; }
+ .ns-name { font-family: -apple-system, "SF Pro Display", "Helvetica Neue", system-ui, sans-serif; font-size: 18px !important; font-weight: 300 !important; letter-spacing: 0.02em !important; color: #f5f1e8 !important; text-transform: none !important; margin: 0 !important; }
+ .ns-name sup { font-size: 0.55em; font-weight: 300; margin-left: 1px; }
+ .ns-nav { display: flex; gap: 32px; align-items: center; list-style: none; padding: 0; margin: 0; }
+ .ns-nav a, .ns-nav button { font-family: -apple-system, "SF Pro Text", system-ui, sans-serif; font-size: 15px !important; font-weight: 400 !important; color: #f5f1e8 !important; text-decoration: none !important; background: none !important; border: 0 !important; cursor: pointer; padding: 0 !important; transition: color 200ms; letter-spacing: 0 !important; text-transform: none !important; }
+ .ns-nav a:hover, .ns-nav button:hover { color: #ffffff !important; }
+ @media (max-width: 820px) { .ns-header { padding: 14px 18px; } .ns-nav { gap: 16px; } .ns-nav a, .ns-nav button { font-size: 13px !important; } }
+ @media (max-width: 760px) {
+ .ns-header { flex-wrap: wrap; }
+ .ns-nav { display: none !important; flex-basis: 100%; flex-direction: column; align-items: flex-start; gap: 6px; padding: 14px 0 4px; }
+ .ns-nav.open { display: flex !important; }
+ .ns-nav a, .ns-nav button { padding: 10px 0 !important; border-bottom: 1px solid rgba(245,241,232,0.08); width: 100%; text-align: left; }
+ .ns-nav a:nth-child(n+5), .ns-nav button:nth-child(n+5) { display: block !important; }
+ .ns-header .mobile-toggle { display: flex !important; }
+ }
+ @media (min-width: 761px) {
+ .ns-header .mobile-toggle { display: none !important; }
+ }
+ .ns-modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.78); z-index: 200; align-items: center; justify-content: center; padding: 24px; backdrop-filter: blur(6px); }
+ .ns-modal.show { display: flex; }
+ .ns-modal-card { max-width: 460px; width: 100%; background: #141414 !important; border: 1px solid rgba(245,241,232,0.14); border-radius: 6px; padding: 28px 30px 30px; position: relative; }
+ .ns-modal-card .close-x { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; background: none; border: 1px solid rgba(245,241,232,0.18); color: rgba(245,241,232,0.6) !important; border-radius: 50%; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; }
+ .ns-modal-card .close-x:hover { color: #ffffff !important; border-color: #ffffff; }
+ .ns-modal-card h3 { font-family: -apple-system, "SF Pro Display", system-ui; font-weight: 300 !important; font-size: 22px !important; margin: 0 0 6px !important; color: #f5f1e8 !important; }
+ .ns-modal-card .sub { color: rgba(245,241,232,0.65) !important; font-size: 13px; margin: 0 0 22px; }
+ .ns-actions { display: flex; flex-direction: column; gap: 10px; }
+ .ns-action { display: flex; align-items: center; gap: 14px; padding: 14px 16px; background: rgba(245,241,232,0.04) !important; border: 1px solid rgba(245,241,232,0.12) !important; color: #f5f1e8 !important; text-decoration: none !important; cursor: pointer; transition: all 200ms; font: inherit; text-align: left; }
+ .ns-action:hover { background: rgba(245,241,232,0.08) !important; border-color: #d4b683 !important; }
+ .ns-action .ico { width: 32px; height: 32px; border-radius: 50%; background: rgba(212,182,131,0.12); display: flex; align-items: center; justify-content: center; font-size: 15px; flex-shrink: 0; }
+ .ns-action .txt { flex: 1; }
+ .ns-action .txt b { display: block; font-weight: 500; font-size: 14px; color: #f5f1e8 !important; }
+ .ns-action .txt span { font-size: 12px; color: rgba(245,241,232,0.55) !important; }
+
+ /* ─── FINAL CONTRAST PASS — kill remaining gold-solid + white-island defects ─── */
+ /* Replace all gold-fill accents with off-white (Gucci-aesthetic, no DW gold solids) */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active,
+ .drapery-btn, .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover,
+ .drapery-btn:hover, .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ /* Gold accents stay only as edge color (links, glyphs) — not fills */
+ a { color: #d4b683; }
+ a:hover { color: #f5d97a; }
+ /* Authorized-dealer footer line — small caps tracked, muted ink, no gold pill */
+ .authorized, .authorized-dealer p {
+ background: transparent !important;
+ color: rgba(245,241,232,0.5) !important;
+ font-size: 10px !important;
+ letter-spacing: 0.22em !important;
+ text-transform: uppercase !important;
+ padding: 0 !important;
+ border: none !important;
+ margin-top: 14px !important;
+ }
+ /* Kill any straggler white bg in info-card / sample-related cards */
+ .info-card, .info-card-link, .care-highlight, .feature-card, .spec-item {
+ background-color: rgba(245,241,232,0.04) !important;
+ border-color: rgba(245,241,232,0.12) !important;
+ }
+ /* Ensure modal content stays readable — Steve's earlier sweep made it #141414 with white text */
+ .modal-content { background: #141414 !important; color: #f5f1e8 !important; }
+ .modal-content * { color: #f5f1e8 !important; }
+ .modal-content input, .modal-content textarea, .modal-content select {
+ background: rgba(245,241,232,0.05) !important;
+ color: #f5f1e8 !important;
+ border-color: rgba(245,241,232,0.2) !important;
+ }
+ .modal-content label { color: rgba(245,241,232,0.6) !important; }
+ /* Force the wide-tracked NOVASUEDE wordmark to stay white */
+ .ns-wordmark, .ns-wordmark sup { color: #f5f1e8 !important; }
+
+ /* ─── Modal-internal contrast (post-QA defects) ─── */
+ /* Backdrop covers full viewport */
+ .modal { width: 100vw !important; height: 100vh !important; left: 0 !important; top: 0 !important; background: rgba(0, 0, 0, 0.78) !important; backdrop-filter: blur(6px); }
+ /* Chip — flip from white-on-white island to dark card */
+ .request-chip {
+ background: rgba(245, 241, 232, 0.06) !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.18) !important;
+ border-radius: 4px !important;
+ }
+ .request-chip * { color: #f5f1e8 !important; }
+ .request-chip .chip-swatch {
+ /* swatch dot ALWAYS displays the inline-style hex from JS — fight any sweep */
+ border: 1px solid rgba(0, 0, 0, 0.4) !important;
+ }
+ /* + Add to request — hairline white-border on transparent (no gold) */
+ .add-to-request-btn {
+ background: transparent !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.6) !important;
+ border-radius: 0 !important;
+ padding: 12px 22px !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 400 !important;
+ }
+ .add-to-request-btn:hover {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ /* Hex caption visible */
+ .preview-hex, #colorPreviewHex {
+ display: block !important;
+ color: rgba(245, 241, 232, 0.55) !important;
+ font-family: ui-monospace, "SF Mono", Menlo, monospace !important;
+ font-size: 11px !important;
+ margin-top: 4px !important;
+ letter-spacing: 0.04em !important;
+ text-transform: lowercase !important;
+ }
+ /* Color preview name — stays single, prominent */
+ #colorPreviewName {
+ color: #f5f1e8 !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 500 !important;
+ display: block !important;
+ margin-top: 12px !important;
+ }
+
+ /* ─── Mobile hamburger nav (replaces the overflow at <760px) ─── */
+ @media (max-width: 760px) {
+ header .nav-container { flex-wrap: wrap; }
+ header nav { display: none; flex-basis: 100%; flex-direction: column; gap: 6px; padding: 16px 0 6px; }
+ header nav.open { display: flex; }
+ header nav a { padding: 10px 0; border-bottom: 1px solid rgba(245,241,232,0.1); }
+ header .mobile-toggle {
+ display: flex !important;
+ flex-direction: column;
+ gap: 5px;
+ background: transparent;
+ border: 0;
+ cursor: pointer;
+ padding: 8px;
+ }
+ header .mobile-toggle span {
+ width: 22px; height: 1.5px;
+ background: #f5f1e8;
+ display: block;
+ }
+ }
+ @media (min-width: 761px) {
+ header .mobile-toggle { display: none !important; }
+ }
+
+ </style>
+<style id="ns-gold-kill">
+ /* novasuede: kill gold-solid fills, off-white instead. Edge color stays gold. */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active, .drapery-btn,
+ .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover, .drapery-btn:hover,
+ .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ </style>
+<header class="ns-header">
+ <a href="/" class="ns-brand" aria-label="Home">
+ <span class="ns-dot"></span>
+ <span class="ns-name">Atelier Couture</span>
+ </a>
+ <nav class="ns-nav" id="nsPrimaryNav" aria-label="Primary">
+ <a href="/">Home</a>
+ <a href="/about">About</a>
+ <button type="button" onclick="nsContactOpen()">Contact</button>
+ </nav>
+ <button type="button" class="mobile-toggle" aria-label="Toggle menu" aria-controls="nsPrimaryNav" aria-expanded="false" onclick="nsToggleNav(this)">
+ <span></span><span></span><span></span>
+ </button>
+ </header>
+
+<div class="ns-modal" id="nsContactModal" role="dialog" aria-modal="true" aria-labelledby="nsContactTitle" onclick="if(event.target===this)nsContactClose()">
+ <div class="ns-modal-card">
+ <button type="button" class="close-x" onclick="nsContactClose()" aria-label="Close">×</button>
+ <h3 id="nsContactTitle">How can we help?</h3>
+ <p class="sub">Trade-account replies within one business day.</p>
+ <div class="ns-actions">
+ <a class="ns-action" href="#contact" onclick="nsContactClose()"><span class="ico">📋</span><div class="txt"><b>Get a Quote</b><span>Project specs → priced estimate</span></div></a>
+ <a class="ns-action" href="#contact" onclick="nsContactClose();openModal();"><span class="ico">📦</span><div class="txt"><b>Order a Sample</b><span>Free swatch to your shop</span></div></a>
+ <a class="ns-action" href="mailto:info@silkwallpaper.com?subject=Atelier%20Couture%20inquiry"><span class="ico">✉️</span><div class="txt"><b>Email Us</b><span>info@silkwallpaper.com</span></div></a>
+ <a class="ns-action" href="tel:888-373-4564"><span class="ico">📞</span><div class="txt"><b>Call Us</b><span>(888) 373-4564 · M–F, 9–5 PT</span></div></a>
+ </div>
+ </div>
+ </div>
+<script>
+ function nsContactOpen() { var m = document.getElementById('nsContactModal'); if (m) m.classList.add('show'); }
+ function nsContactClose() { var m = document.getElementById('nsContactModal'); if (m) m.classList.remove('show'); }
+ function nsToggleNav(btn) {
+ var nav = document.getElementById('nsPrimaryNav');
+ if (!nav) return;
+ var open = nav.classList.toggle('open');
+ btn.setAttribute('aria-expanded', open ? 'true' : 'false');
+ }
+ document.addEventListener('keydown', function(e){ if (e.key === 'Escape') nsContactClose(); });
+</script>
+
+
+
+
+
+
+
+
+
+<header>
+ <button class="h-link" onclick="dwmOpen('Contact')" aria-label="Contact"><svg class="h-icon" viewBox="0 0 24 24"><path d="M3 7h18M3 12h18M3 17h18"/></svg><span>Contact</span></button>
+ <button class="theme-toggle" id="theme-toggle" aria-label="Theme toggle">☾</button>
+</header>
+
+<section class="cinema">
+ <div class="cinema-bg" id="heroBg"></div>
+ <div class="corner-mark">Silken Sheen</div>
+ <div class="center-mark">Silk Wallpaper</div>
+ <div class="meta-line">Silk · Luxe · Sheen · Jacquard<span class="num" id="heroNum"></span></div>
+ <a class="enter" href="#shop">Enter <svg viewBox="0 0 24 12" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M0 6h22M16 1l6 5-6 5"/></svg></a>
+</section>
+
+
+<section class="section" id="ideas">
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">New this week</h2>
+ <div class="rail" id="railNewest"></div>
+ </div>
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">Curated picks</h2>
+ <div class="rail" id="railCurated"></div>
+ </div>
+</section>
+
+<section class="section" id="shop">
+ <div class="section-header">
+ <div>
+ <div class="section-eyebrow">The Collection</div>
+ <h2 class="section-title"><span class="accent" id="totalCount">—</span> Patterns.</h2>
+ </div>
+ <div class="section-meta">Silk · Luxe · Sheen · Jacquard</div>
+ </div>
+
+ <div class="filters" id="facets">
+ <button class="chip active" data-facet="all">All</button>
+ </div>
+
+ <div class="density">
+ <label>Sort</label>
+ <select id="sortSelect" aria-label="Sort products" style="background:transparent;color:inherit;border:1px solid var(--line,rgba(255,255,255,.2));font:inherit;font-size:11px;letter-spacing:.10em;text-transform:uppercase;padding:4px 8px;cursor:pointer;outline:none;margin-right:14px">
+ <option value="newest">Newest</option>
+ <option value="color">Color</option>
+ <option value="style">Style</option>
+ <option value="sku">SKU A→Z</option>
+ <option value="title">Title A→Z</option>
+ <option value="price-asc">Price ↑</option>
+ <option value="price-desc">Price ↓</option>
+ </select>
+ <label>Grid</label>
+ <input type="range" id="densitySlider" min="4" max="12" step="1" value="6" aria-label="Grid columns">
+ <span class="ct" id="densityLabel">6 cols</span>
+ <div class="search">
+ <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5.5-5.5"/></svg>
+ <input type="text" id="searchInput" placeholder="Search by pattern, color…" autocomplete="off">
+ </div>
+ </div>
+
+ <div class="stat-line" id="statLine">Loading…</div>
+ <div class="grid" id="grid"></div>
+ <div class="loading" id="loading">Loading more…</div>
+ <div class="sentinel" id="sentinel"></div>
+</section>
+
+<footer>
+ <div class="footer-grid">
+ <div>
+ <div class="footer-brand">Atelier Couture</div>
+ <p class="footer-text">Atelier Couture — real samples ship free.</p>
+ <p class="footer-text" style="margin-top:14px;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;font-weight:600;color:var(--paper);opacity:0.7"><a href="/about" style="color:inherit;text-decoration:none;border-bottom:1px solid var(--accent)">About →</a></p>
+ </div>
+ <div class="footer-col">
+ <h4>Aesthetic</h4>
+ <div id="footerFacets"></div>
+ </div>
+ <div class="footer-col">
+ <h4>Trade</h4>
+ <button onclick="dwmOpen('Inquiry')">Project Inquiry</button>
+ <button onclick="dwmOpen('Sample')">Request Sample</button>
+ <button onclick="dwmOpen('Contact')">Contact</button>
+ </div>
+
+ <div class="footer-col">
+ <h4>Social</h4>
+ <a class="fb-link" href="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings" target="_blank" rel="noopener">Facebook</a>
+ </div>
+ </div>
+ <div class="footer-bottom">
+ <span>silkwallpaper.com · Atelier Couture</span>
+ <span><a href="mailto:info@silkwallpaper.com" style="color:var(--paper);text-decoration:none;border-bottom:1px solid var(--accent)">info@silkwallpaper.com</a></span>
+ <span id="footerStat"></span>
+ </div>
+</footer>
+
+<script>
+// mini-constellation — 43 niche-stars, current site highlighted in gold
+(function(){
+ const SLUG = "silkwallpaper";
+ const NICHES = [
+ ['silkwallpaper','Silk','mat'],['silkwallcoverings','Silk W/C','mat'],['linenwallpaper','Linen','mat'],
+ ['jutewallpaper','Jute','mat'],['raffiawallcoverings','Raffia W/C','mat'],['raffiawalls','Raffia Walls','mat'],
+ ['corkwallcovering','Cork','mat'],['fabricwallpaper','Fabric','mat'],['textilewallpaper','Textile','mat'],
+ ['metallicwallpaper','Metallic','mat'],['silverleafwallpaper','Silver Leaf','mat'],['vinylwallpaper','Vinyl','mat'],
+ ['micawallpaper','Mica','mat'],['madagascarwallpaper','Madagascar','mat'],['mylarwallpaper','Mylar','mat'],
+ ['suedewallpaper','Suede','mat'],
+ ['1800swallpaper','1800s','dec'],['1890swallpaper','1890s','dec'],['1900swallpaper','1900s','dec'],
+ ['1920swallpaper','1920s','dec'],['1930swallpaper','1930s','dec'],['1940swallpaper','1940s','dec'],
+ ['1950swallpaper','1950s','dec'],['1960swallpaper','1960s','dec'],['1970swallpaper','1970s','dec'],
+ ['1980swallpaper','1980s','dec'],['retrowalls','Retro','dec'],['wallpapersback','W. Back','dec'],
+ ['agedwallpaper','Aged','cra'],['handcraftedwallpaper','Handcrafted','cra'],['museumwallpaper','Museum','cra'],
+ ['restorationwallpaper','Restoration','cra'],['pastelwallpaper','Pastel','cra'],['glitterwalls','Glitter','cra'],
+ ['greenwallcoverings','Green','cra'],['naturalwallcoverings','Natural','cra'],['saloonwallpaper','Saloon','cra'],
+ ['contractwallpaper','Contract','use'],['hotelwallcoverings','Hotel','use'],['hospitalitywallpaper','Hospitality','use'],
+ ['healthcarewallpaper','Healthcare','use'],['restaurantwallpaper','Restaurant','use'],['architecturalwallcoverings','Architectural','use']
+ ];
+ const COLOR = { mat:'#c9b687', dec:'#b86a4a', cra:'#6b8e6f', use:'#4a6b8e' };
+ const W = 1080, H = 200, BANDS = ['mat','dec','cra','use'];
+ const groups = { mat:[], dec:[], cra:[], use:[] };
+ for (const n of NICHES) groups[n[2]].push(n);
+ const svg = document.getElementById('miniConst');
+ if (!svg) return;
+ const bandW = W / BANDS.length;
+ let nodes = '';
+ BANDS.forEach((band, bi) => {
+ const list = groups[band];
+ const cx = bi * bandW + bandW / 2;
+ list.forEach((n, i) => {
+ const t = list.length === 1 ? 0.5 : i / (list.length - 1);
+ // deterministic jitter per slug
+ let h = 0; for (let k = 0; k < n[0].length; k++) h = (h * 31 + n[0].charCodeAt(k)) | 0;
+ const jx = ((h & 0xff) / 255 - 0.5) * (bandW * 0.5);
+ const jy = (((h >> 8) & 0xff) / 255 - 0.5) * 6; // tightened from 14 → 6 to prevent same-band collisions (16 mat nodes / 144px = 9px spacing)
+ const x = cx + jx, y = 28 + t * (H - 56) + jy;
+ const isCurrent = n[0] === SLUG;
+ const r = isCurrent ? 8 : 4;
+ const op = isCurrent ? 1 : 0.55;
+ nodes += `<g class="mc-node" data-slug="${n[0]}" data-label="${n[1]}" style="cursor:pointer">
+ <circle cx="${x}" cy="${y}" r="${r}" fill="url(#dna-${band})" fill-opacity="${op}" stroke="${COLOR[band]}" stroke-opacity="${isCurrent?1:0.7}" stroke-width="${isCurrent?2:1}">
+ <title>${n[1]} — ${n[0]}.com</title>
+ </circle>
+ ${isCurrent ? `<circle cx="${x}" cy="${y}" r="${r+5}" fill="none" stroke="${COLOR[band]}" stroke-opacity="0.4" stroke-width="1"><animate attributeName="r" values="${r+5};${r+12};${r+5}" dur="2.4s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" repeatCount="indefinite"/></circle>` : ''}
+ </g>`;
+ });
+ });
+ // append nodes after defs
+ svg.insertAdjacentHTML('beforeend', nodes);
+ svg.querySelectorAll('.mc-node').forEach(n => {
+ n.addEventListener('mouseenter', () => n.querySelector('circle').setAttribute('fill-opacity', '1'));
+ n.addEventListener('mouseleave', e => {
+ const isCurrent = n.dataset.slug === SLUG;
+ n.querySelector('circle').setAttribute('fill-opacity', isCurrent ? '1' : '0.55');
+ });
+ n.addEventListener('click', () => window.open('https://' + n.dataset.slug + '.com', '_blank'));
+ });
+})();
+</script>
+
+<script>
+const state = { q:'', facet:'all', page:1, pages:1, total:0, loading:false, exhausted:false };
+const LABELS = {"all":"All"};
+
+function escAttr(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
+// Image-URL allowlist: only DW Shopify CDN + designerwallcoverings.com (defends against XSS via crafted products.json)
+function safeImg(u) { return /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(u || '') ? u : '/hero-bg.jpg'; }
+function cardHTML(p) {
+ const eager = state.page === 1;
+ return '<img loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+ + '<div class="overlay">'
+ + '<div class="pat">' + escAttr(p.pattern_name || p.title) + '</div>'
+ + '<div class="ven">' + escAttr((p.vendor || '').replace(/-/g, ' ')) + '</div>'
+ + '<div class="actions">'
+ + '<button class="sample-btn" onclick="event.stopPropagation();dwmOpen(\'Sample\',' + JSON.stringify({sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}).replace(/"/g,'"') + ')">Sample</button>'
+ + '</div></div>';
+}
+
+async function loadFacets() {
+ let f;
+ try {
+ const r = await fetch('/api/facets');
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ f = await r.json();
+ } catch (e) { console.error('loadFacets failed:', e); return; }
+ const el = document.getElementById('facets');
+ for (const [k, v] of Object.entries(f.aesthetics).sort((a,b) => b[1] - a[1])) {
+ const b = document.createElement('button');
+ b.className = 'chip'; b.dataset.facet = k;
+ b.innerHTML = (LABELS[k] || k) + ' <span style="opacity:.55;font-weight:500;margin-left:4px">' + v + '</span>';
+ el.appendChild(b);
+ }
+ el.addEventListener('click', e => {
+ if (e.target.tagName !== 'BUTTON') return;
+ document.querySelectorAll('#facets button').forEach(b => b.classList.remove('active'));
+ e.target.classList.add('active');
+ state.facet = e.target.dataset.facet;
+ resetGrid();
+ });
+ document.getElementById('totalCount').textContent = f.total;
+ document.getElementById('footerStat').textContent = f.total + ' patterns · live archive';
+ // Footer aesthetic links
+ const footerEl = document.getElementById('footerFacets');
+ for (const [k] of Object.entries(f.aesthetics)) {
+ const b = document.createElement('button');
+ b.textContent = LABELS[k] || k;
+ b.onclick = () => { state.facet = k; resetGrid(); document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.facet === k)); document.getElementById('shop').scrollIntoView({behavior:'smooth'}); };
+ footerEl.appendChild(b);
+ }
+}
+
+function resetGrid() {
+ state.page = 1; state.exhausted = false;
+ document.getElementById('grid').innerHTML = '';
+ document.getElementById('loading').textContent = 'Loading…';
+ loadGridPage();
+}
+
+async function loadGridPage() {
+ if (state.loading || state.exhausted) return;
+ state.loading = true;
+ const params = new URLSearchParams({ page:state.page, limit:24 });
+ if (state.q) params.set('q', state.q);
+ if (state.facet !== 'all') params.set('aesthetic', state.facet);
+ let data;
+ try {
+ const r = await fetch('/api/products?' + params);
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ data = await r.json();
+ } catch (e) {
+ console.error('loadGridPage failed:', e);
+ document.getElementById('loading').textContent = '— offline — refresh to retry —';
+ state.loading = false;
+ return;
+ }
+ state.total = data.total; state.pages = data.pages;
+
+ if (state.page === 1) {
+ document.getElementById('statLine').textContent = data.total + ' patterns · ' + (state.facet === 'all' ? 'all aesthetics' : (LABELS[state.facet] || state.facet)) + (state.q ? ' · matching "' + state.q + '"' : '');
+ }
+ const grid = document.getElementById('grid');
+ for (const p of data.items) {
+ const a = document.createElement('a');
+ a.className = 'card'; a.href = '#'; a.onclick = (e) => { e.preventDefault(); dwmOpen('Sample',{sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}); };
+ a.innerHTML = cardHTML(p);
+ grid.appendChild(a);
+ }
+ if (state.page >= state.pages || data.items.length === 0) {
+ state.exhausted = true;
+ document.getElementById('loading').textContent = state.total > 0 ? '— end of archive · ' + state.total + ' patterns —' : '';
+ } else state.page++;
+ state.loading = false;
+}
+
+const io = new IntersectionObserver(es => { for (const e of es) if (e.isIntersecting) loadGridPage(); }, { rootMargin:'600px 0px' });
+io.observe(document.getElementById('sentinel'));
+
+document.getElementById('searchInput').addEventListener('input', e => {
+ state.q = e.target.value.trim();
+ clearTimeout(window._t);
+ window._t = setTimeout(resetGrid, 220);
+});
+
+// Density slider
+const slider = document.getElementById('densitySlider');
+const dlabel = document.getElementById('densityLabel');
+function setDensity(n) {
+ document.documentElement.style.setProperty('--cols', n);
+ dlabel.textContent = n + ' cols';
+ try { localStorage.setItem('silk_theme_density', n); } catch(e){}
+}
+slider.addEventListener('input', e => setDensity(parseInt(e.target.value)));
+const savedDensity = parseInt(localStorage.getItem('silk_theme_density') || '6');
+if (savedDensity >= 4 && savedDensity <= 12) { slider.value = savedDensity; setDensity(savedDensity); }
+
+// Theme toggle
+const tb = document.getElementById('theme-toggle');
+function setTheme(t){ document.documentElement.dataset.theme = t; try { localStorage.setItem('silk_theme', t); } catch(e){} tb.textContent = t === 'dark' ? '☾' : '☀'; }
+setTheme(document.documentElement.dataset.theme || 'dark');
+tb.addEventListener('click', () => setTheme(document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'));
+
+loadFacets();
+loadGridPage();
+</script>
+<!-- ============================================================
+ DW UNIVERSAL CONTACT MODULE — fashion-house UX
+ Inject before </body> in every DW-family sister site.
+ Self-contained: CSS + 4 modals + JS submission handlers.
+ Uses host site's CSS vars (--bg, --fg, --gold, --rule).
+ ============================================================ -->
+<style>
+ /* === MODAL BASE === */
+ .dwm{position:fixed;inset:0;background:rgba(0,0,0,0.78);display:none;align-items:center;justify-content:center;z-index:9999;padding:24px;overflow-y:auto}
+ .dwm.open{display:flex}
+ .dwm-box{background:var(--bg);border:1px solid var(--rule);max-width:520px;width:100%;padding:36px 32px;position:relative;max-height:90vh;overflow-y:auto;animation:dwm-in 0.25s ease}
+ @keyframes dwm-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+ .dwm-box h3{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-weight:400;font-size:32px;line-height:1.05;margin-bottom:6px;color:var(--fg);letter-spacing:-0.01em}
+ .dwm-box .sub{color:var(--muted);font-size:12px;letter-spacing:0.16em;text-transform:uppercase;margin-bottom:24px;font-weight:500}
+ .dwm-close{position:absolute;top:18px;right:18px;background:transparent;border:0;color:var(--muted);font-size:24px;cursor:pointer;width:32px;height:32px;line-height:1;padding:0}
+ .dwm-close:hover{color:var(--gold)}
+ .dwm-preview{display:flex;gap:14px;align-items:center;padding:12px;background:var(--bg-soft);margin-bottom:18px}
+ .dwm-preview img{width:64px;height:64px;object-fit:cover}
+ .dwm-preview .pn{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-size:15px;line-height:1.2;color:var(--fg)}
+ .dwm-preview .pc{font-size:10px;letter-spacing:0.14em;text-transform:uppercase;color:var(--muted);margin-top:4px}
+ .dwm-field{margin-bottom:14px}
+ .dwm-field label{display:block;font-size:10px;letter-spacing:0.18em;text-transform:uppercase;color:var(--muted);margin-bottom:6px;font-weight:500}
+ .dwm-field input,.dwm-field textarea,.dwm-field select{width:100%;border:0;border-bottom:1px solid var(--rule);background:transparent;padding:8px 4px;font-size:14px;font-family:inherit;color:var(--fg);outline:none;transition:border-color 0.2s}
+ .dwm-field input:focus,.dwm-field textarea:focus{border-bottom-color:var(--gold)}
+ .dwm-field textarea{min-height:60px;resize:vertical}
+ .dwm-submit{background:transparent;color:var(--muted, #c9b89e);border:1px solid var(--muted, #c9b89e);padding:14px 28px;font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:700;cursor:pointer;width:100%;margin-top:8px;transition:background 0.15s, color 0.15s}
+ .dwm-submit:hover{background:var(--muted, #c9b89e);color:var(--bg, #000)}
+
+ .dwm-submit:disabled{opacity:0.5;cursor:default}
+ .dwm-status{margin-top:14px;font-size:11px;letter-spacing:0.16em;text-transform:uppercase;text-align:center;display:none}
+ .dwm-status.ok{color:var(--sage);display:block}
+ .dwm-status.err{color:#e08070;display:block}
+ /* contact-options */
+ .dwm-options{display:flex;flex-direction:column;gap:10px}
+ .dwm-option{display:flex;align-items:center;gap:14px;padding:18px 20px;background:transparent;border:1px solid var(--rule);color:var(--fg);font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:600;cursor:pointer;text-decoration:none;transition:all 0.2s;text-align:left;width:100%}
+ .dwm-option:hover{border-color:var(--gold);color:var(--gold)}
+ .dwm-option svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:1.5;flex-shrink:0}
+ .dwm-option .lbl{flex:1;display:block}
+ .dwm-option .val{display:block;font-size:10px;letter-spacing:0.14em;text-transform:none;font-weight:500;opacity:0.65;margin-top:4px}
+
+/* graphics-loop pass 5: serif hero typography + nav legibility */
+.cinema h1, h1.headline { font-family: 'Cormorant Garamond', Georgia, serif; font-weight: 500; letter-spacing: -0.01em }
+.h-link { font-size: 13px; letter-spacing: 0.06em }
+
+/* graphics-loop pass 11: Ideas rails */
+#ideas { padding: 56px 24px 24px; max-width:1600px; margin:0 auto }
+#ideas .rail-block { margin-bottom:48px }
+#ideas .rail-eyebrow { font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:var(--muted, #888); margin-bottom:8px }
+#ideas .rail-title { font-size:clamp(22px,2.6vw,32px); font-weight:500; letter-spacing:-0.01em; margin:0 0 18px; color:var(--ink,#fff) }
+#ideas .rail { display:flex; gap:14px; overflow-x:auto; scroll-snap-type:x mandatory; padding-bottom:8px; scrollbar-width:none }
+#ideas .rail::-webkit-scrollbar { display:none }
+#ideas .rail-card { flex:0 0 240px; scroll-snap-align:start; cursor:pointer; transition:transform 0.2s }
+#ideas .rail-card:hover { transform:translateY(-3px) }
+#ideas .rail-card img { width:100%; aspect-ratio:1; object-fit:cover; display:block; background:var(--card,#222) }
+#ideas .rail-card .rc-title { font-size:13px; font-weight:500; color:var(--paper,#fff); padding:8px 0 2px; line-height:1.3 }
+#ideas .rail-card .rc-meta { font-size:11px; color:var(--muted,#888); letter-spacing:0.06em }
+@media (max-width:720px) { #ideas .rail-card { flex:0 0 160px } }
+</style>
+
+<!-- Contact options modal -->
+<div class="dwm" id="dwmContact" onclick="if(event.target===this)dwmClose('Contact')">
+ <div class="dwm-box" style="max-width:440px">
+ <button class="dwm-close" onclick="dwmClose('Contact')" aria-label="Close">×</button>
+ <h3>Contact</h3>
+ <p class="sub" id="dwmContactSub">Three ways to reach us</p>
+ <div class="dwm-options">
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Inquiry')">
+ <svg viewBox="0 0 24 24"><path d="M4 4h16v12H7l-3 3z"/></svg>
+ <span class="lbl">Send an Inquiry<span class="val">Project name · scope · all the details</span></span>
+ </button>
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Sample')">
+ <svg viewBox="0 0 24 24"><rect x="4" y="6" width="16" height="14"/><path d="M4 10h16M9 6V3h6v3"/></svg>
+ <span class="lbl">Request a Sample<span class="val">Memo sample · ships free · 3–5 business days</span></span>
+ </button>
+ <a class="dwm-option" id="dwmContactEmailLink" href="mailto:info@silkwallpaper.com">
+ <svg viewBox="0 0 24 24"><path d="M3 6h18v12H3z"/><path d="M3 6l9 7 9-7"/></svg>
+ <span class="lbl">Email Us<span class="val" id="dwmContactEmailLabel">info@silkwallpaper.com</span></span>
+ </a>
+ </div>
+ </div>
+</div>
+
+<!-- Inquiry modal -->
+<div class="dwm" id="dwmInquiry" onclick="if(event.target===this)dwmClose('Inquiry')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Inquiry')" aria-label="Close">×</button>
+ <h3>Project Inquiry</h3>
+ <p class="sub">We'll respond within one business day</p>
+ <form id="dwmInquiryForm" onsubmit="return dwmSubmit(event,'inquiry')">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Phone</label><input type="tel" name="phone"></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Project Name</label><input type="text" name="projectName" required></div>
+ <div class="dwm-field"><label>Project Scope</label><textarea name="projectScope" placeholder="Square footage · room count · timeline" required></textarea></div>
+ <div class="dwm-field"><label>Additional Information</label><textarea name="message" placeholder="Patterns of interest, finish preferences, budget"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Inquiry</button>
+ <div class="dwm-status" id="dwmInquiryStatus"></div>
+ </form>
+ </div>
+</div>
+
+<!-- Sample-request modal -->
+<div class="dwm" id="dwmSample" onclick="if(event.target===this)dwmClose('Sample')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Sample')" aria-label="Close">×</button>
+ <h3>Request a Sample</h3>
+ <p class="sub">Memo sample · ships free · 3–5 business days</p>
+ <div class="dwm-preview" id="dwmSamplePreview" style="display:none">
+ <img id="dwmSampleImg" src="" alt="">
+ <div><div class="pn" id="dwmSampleName"></div><div class="pc" id="dwmSampleSku"></div></div>
+ </div>
+ <form id="dwmSampleForm" onsubmit="return dwmSubmit(event,'sample')">
+ <input type="hidden" name="sku" id="dwmSampleSkuInput">
+ <input type="hidden" name="title" id="dwmSampleTitleInput">
+ <input type="hidden" name="image_url" id="dwmSampleImageInput">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Shipping Address</label><input type="text" name="address" placeholder="Street" required></div>
+ <div class="dwm-field" style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:8px">
+ <input type="text" name="city" placeholder="City" required>
+ <input type="text" name="state" placeholder="State" required>
+ <input type="text" name="zip" placeholder="ZIP" required>
+ </div>
+ <div class="dwm-field"><label>Notes (optional)</label><textarea name="message" placeholder="Project, sq.ft., timing"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Sample Request</button>
+ <div class="dwm-status" id="dwmSampleStatus"></div>
+ </form>
+ </div>
+</div>
+
+<script>
+ // Universal modal control
+ function dwmOpen(name, opts){
+ document.getElementById('dwm' + name).classList.add('open');
+ document.body.style.overflow = 'hidden';
+ if (name === 'Sample' && opts) {
+ document.getElementById('dwmSamplePreview').style.display = 'flex';
+ document.getElementById('dwmSampleImg').src = opts.image_url || '';
+ document.getElementById('dwmSampleName').textContent = opts.title || '';
+ document.getElementById('dwmSampleSku').textContent = opts.sku || '';
+ document.getElementById('dwmSampleSkuInput').value = opts.sku || '';
+ document.getElementById('dwmSampleTitleInput').value = opts.title || '';
+ document.getElementById('dwmSampleImageInput').value = opts.image_url || '';
+ } else if (name === 'Sample') {
+ document.getElementById('dwmSamplePreview').style.display = 'none';
+ document.getElementById('dwmSampleSkuInput').value = '';
+ document.getElementById('dwmSampleTitleInput').value = '';
+ document.getElementById('dwmSampleImageInput').value = '';
+ }
+ }
+ function dwmClose(name){ document.getElementById('dwm'+name).classList.remove('open'); document.body.style.overflow=''; }
+
+ // Universal form submit — POSTs to /api/send-{kind}
+ async function dwmSubmit(e, kind) {
+ e.preventDefault();
+ const form = e.target;
+ const data = Object.fromEntries(new FormData(form).entries());
+ const btn = form.querySelector('.dwm-submit');
+ const status = form.querySelector('.dwm-status');
+ btn.disabled = true; const orig = btn.textContent; btn.textContent = 'Sending…';
+ status.className = 'dwm-status';
+ try {
+ const r = await fetch('/api/send-' + kind, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data) });
+ if (!r.ok) throw new Error('HTTP '+r.status);
+ const j = await r.json();
+ status.textContent = kind === 'sample' ? 'Sample request sent — ships within 3–5 business days' : 'Inquiry sent — we respond within 1 business day';
+ status.className = 'dwm-status ok';
+ btn.textContent = 'Sent ✓';
+ setTimeout(() => { dwmClose(kind === 'sample' ? 'Sample' : 'Inquiry'); btn.disabled = false; btn.textContent = orig; status.className = 'dwm-status'; form.reset(); }, 2800);
+ } catch (err) {
+ status.textContent = 'Send failed — please email info@designerwallcoverings.com';
+ status.className = 'dwm-status err';
+ btn.disabled = false; btn.textContent = orig;
+ }
+ return false;
+ }
+
+ // Wire up "Memo samples ship free" badge in hero to open sample modal directly
+ document.querySelectorAll('.hero .badge, [data-open-sample]').forEach(el => {
+ el.style.cursor = 'pointer';
+ el.addEventListener('click', () => dwmOpen('Sample'));
+ });
+
+ // Wire nav "Trade" link to inquiry modal
+ document.querySelectorAll('nav a[href*="designerwallcoverings.com"]').forEach(a => {
+ a.removeAttribute('target');
+ a.removeAttribute('href');
+ a.style.cursor = 'pointer';
+ a.addEventListener('click', e => { e.preventDefault(); dwmOpen('Contact'); });
+ });
+
+ // Override product card clicks — open Sample modal pre-filled instead of redirecting to DW
+ document.addEventListener('click', e => {
+ const card = e.target.closest('.card, .rail-card');
+ if (!card) return;
+ if (card.tagName === 'A' && (card.getAttribute('href') || '').startsWith('/sample/')) {
+ e.preventDefault();
+ const img = card.querySelector('img');
+ const pat = card.querySelector('.pat');
+ const sku = card.getAttribute('href').replace('/sample/', '');
+ dwmOpen('Sample', {
+ sku,
+ title: pat ? pat.textContent.trim() : '',
+ image_url: img ? img.src : ''
+ });
+ }
+ }, true);
+// DW STANDARD: hero pulls from products.json[0].image_url so dw-collections-viewer
+// drag-drop / hero-pin updates the live front page on next deploy.
+// Always upgrades to the largest Shopify variant (strips _NxN size suffix) so
+// the full-page hero stays sharp.
+// DW STANDARD: hero rotates through top-20 NEWEST products on each visit
+// (novasuede.com pattern). Fetches /api/products?limit=100, sorts by created_at desc,
+// takes the freshest 20, picks one per visit via localStorage counter.
+// Falls back to static /hero-bg.jpg on any error.
+(async function rotateHero() {
+ try {
+ const r = await fetch('/api/products?limit=100');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || [])
+ .filter(p => p && p.image_url && /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(p.image_url))
+ .sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')))
+ .slice(0, 20);
+ if (!items.length) return;
+ let visits = 0;
+ try {
+ visits = parseInt(localStorage.getItem('hero_visits') || '0', 10) || 0;
+ localStorage.setItem('hero_visits', String(visits + 1));
+ } catch(e) {}
+ const pick = items[visits % items.length];
+ // Strip _NxN size suffix to get the largest CDN variant
+ const url = pick.image_url.replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ const bg = document.getElementById('heroBg') || document.getElementById('cinemaBg');
+ if (bg) bg.style.backgroundImage = "url('" + url + "')";
+ } catch (e) {}
+})();
+
+
+// DW STANDARD sort-select wire-up (added by fleet patcher)
+(function(){
+ const sortSel = document.getElementById('sortSelect');
+ if (!sortSel) return;
+ const KEY = location.hostname.replace(/\./g, '_') + '_sort';
+ const saved = localStorage.getItem(KEY);
+ if (saved) { try { sortSel.value = saved; } catch(e){} }
+ if (typeof state !== 'undefined') state.sort = sortSel.value;
+ sortSel.addEventListener('change', e => {
+ const v = e.target.value;
+ try { localStorage.setItem(KEY, v); } catch(e){}
+ if (typeof state !== 'undefined') state.sort = v;
+ if (typeof resetGrid === 'function') resetGrid();
+ else location.reload();
+ });
+})();
+
+// graphics-loop pass 11: hydrate Ideas rails from /api/products
+(async function hydrateIdeas() {
+ try {
+ const r = await fetch('/api/products?limit=80');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || []).filter(p => p && p.image_url);
+ if (!items.length) return;
+ const sorted = [...items].sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')));
+ const newest = sorted.slice(0, 12);
+ // Curated = deterministic offset slice (every 5th item starting at idx 3) for variety
+ const curated = items.filter((_, i) => i % 5 === 3).slice(0, 12);
+ function card(p) {
+ const url = (p.image_url || '').replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ return '<a class="rail-card" href="/sample/' + encodeURIComponent(p.handle || p.sku) + '">' +
+ '<img src="' + url + '" alt="' + (p.title || '').replace(/"/g, '"') + '" loading="lazy">' +
+ '<div class="rc-title">' + ((p.title || '').split('|')[0] || '').trim() + '</div>' +
+ '<div class="rc-meta">' + (p.vendor || '') + '</div>' +
+ '</a>';
+ }
+ const r1 = document.getElementById('railNewest');
+ const r2 = document.getElementById('railCurated');
+ if (r1) r1.innerHTML = newest.map(card).join('');
+ if (r2) r2.innerHTML = curated.map(card).join('');
+ } catch(e) {}
+})();
+</script>
+
+</body>
+</html>
diff --git a/public/index.html.bak.r1234.26158 b/public/index.html.bak.r1234.26158
new file mode 100644
index 0000000..ce929dd
--- /dev/null
+++ b/public/index.html.bak.r1234.26158
@@ -0,0 +1,980 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
+
+<meta charset="utf-8">
+<!-- Google tag (gtag.js) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=G-CNTJNLTEW8"></script>
+<script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+ gtag('config', 'G-CNTJNLTEW8');
+</script>
+
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Atelier Couture — SILK — IN BLACK.</title>
+<meta name="description" content="Atelier Couture is a curated atelier dedicated entirely to silk. We do one thing — and we do it carefully.">
+<meta name="theme-color" content="#FFF">
+<link rel="canonical" href="https://silkwallpaper.com/">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
+<link rel="preload" as="image" href="/hero-bg.jpg" fetchpriority="high">
+<script type="application/ld+json">
+{
+ "@context": "https://schema.org",
+ "@type": "Organization",
+ "name": "Atelier Couture",
+ "url": "https://silkwallpaper.com/",
+ "email": "info@silkwallpaper.com"
+}
+</script>
+<script src="/zd-loader.js" defer></script>
+<style>
+/* Atelier Couture · Saint Laurent template — pure monochrome, sharp uppercase, zero ornament */
+:root {
+ --bg: #000;
+ --paper: #FFF;
+ --muted: #888;
+ --line: rgba(255,255,255,0.20);
+ --accent: #FFF;
+ --bg-soft: #0a0a0a;
+ --cols: 6;
+}
+html[data-theme="light"] {
+ --bg: #FFF;
+ --paper: #000;
+ --muted: #666;
+ --line: rgba(0,0,0,0.20);
+ --accent: #000;
+ --bg-soft: #F5F5F5;
+}
+* { margin:0; padding:0; box-sizing:border-box }
+html { scroll-behavior:smooth }
+body { font-family:'Inter', 'Helvetica Neue', sans-serif; color:var(--paper); -webkit-font-smoothing:antialiased; background:var(--bg); min-height:100vh; overflow-x:hidden }
+/* DW-FASHION-TEMPLATE-OVERRIDE */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', Georgia, serif !important; font-weight:900 !important; text-transform:uppercase !important;letter-spacing:0.04em !important; }
+/* Saint Laurent: heavy uppercase headlines, no italic serif accent */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', 'Helvetica Neue', sans-serif !important; font-weight:900 !important; font-style:normal !important; letter-spacing:0.04em !important; text-transform:uppercase !important }
+
+/* ===== Auto-hide header ===== */
+header { position:fixed; top:0; left:0; right:0; display:flex; justify-content:space-between; align-items:center; padding:22px 32px; z-index:100; background:rgba(0,0,0,0.55); backdrop-filter:blur(10px); border-bottom:1px solid var(--line); transition:transform .3s }
+.h-link { display:flex; align-items:center; gap:10px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); text-decoration:none; cursor:pointer; background:transparent; border:0 }
+.h-link:hover { opacity:0.7 }
+.h-icon { width:18px; height:18px; stroke:currentColor; fill:none; stroke-width:1.5 }
+
+/* ===== Cinema hero ===== */
+.cinema { position:relative; height:100vh; width:100%; overflow:hidden }
+.cinema-bg { position:absolute; inset:0; background-size:cover; background-position:center; background-image:url('/hero-bg.jpg') }
+.cinema-bg::after { content:""; position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.75) 100%) }
+.corner-mark { position:absolute; top:24px; left:32px; font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:#fff; text-shadow:0 1px 3px rgba(0,0,0,0.55), 0 0 1px rgba(0,0,0,0.4); z-index:5 }
+.corner-mark::before { content:"◆ "; margin-right:6px }
+.center-mark { position:absolute; top:clamp(80px,12vh,140px); left:50%; transform:translateX(-50%); font-size:clamp(28px,4vw,56px); font-weight:500; letter-spacing:0.18em; text-transform:uppercase; text-align:center; color:#fff; text-shadow:0 2px 8px rgba(0,0,0,0.65), 0 0 1px rgba(0,0,0,0.5); z-index:5 }
+.center-mark .tm { font-size:14px; vertical-align:super; opacity:0.7; margin-left:0.2em }
+.center-mark .sub { display:block; font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400; font-size:0.4em; letter-spacing:0.04em; text-transform:none; margin-top:14px; opacity:0.85 }
+.meta-line { position:absolute; bottom:32px; left:32px; font-size:10px; letter-spacing:0.28em; text-transform:uppercase; font-weight:700; color:#fff; z-index:5; opacity:0.85 }
+.meta-line .num { font-size:12px; display:block; margin-top:4px; letter-spacing:0.05em }
+.enter { position:absolute; bottom:32px; right:32px; display:flex; align-items:center; gap:14px; font-size:11px; letter-spacing:0.36em; text-transform:uppercase; font-weight:700; color:#fff; text-decoration:none; padding-bottom:6px; border-bottom:1px solid #fff; transition:gap 0.25s; z-index:5 }
+.enter:hover { gap:22px }
+.enter svg { width:24px; height:12px }
+
+/* ===== Section ===== */
+.section { padding:96px 32px }
+.section-header { display:flex; justify-content:space-between; align-items:flex-end; flex-wrap:wrap; gap:24px; margin-bottom:32px; padding-bottom:24px; border-bottom:1px solid var(--line) }
+.section-title { font-size:clamp(36px,5vw,64px); font-weight:300; letter-spacing:-0.02em; line-height:1 }
+.section-title .accent { color: var(--accent); font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400 }
+.section-eyebrow { font-size:10px; letter-spacing:0.4em; text-transform:uppercase; font-weight:700; color:var(--muted); margin-bottom:16px }
+.section-meta { font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:600; color:var(--muted); text-align:right; line-height:1.6 }
+
+/* ===== Filter bar ===== */
+.filters { display:flex; gap:8px; align-items:center; margin-bottom:18px; flex-wrap:wrap }
+.chip { padding:8px 16px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); background:transparent; border:1px solid var(--line); cursor:pointer; transition:all 0.2s; font-family:inherit }
+.chip:hover { border-color:var(--paper) }
+.chip.active { background:var(--paper); color:var(--bg); border-color:var(--paper) }
+.search { flex:0 1 280px; margin-left:auto; display:flex; align-items:center; gap:10px; border-bottom:1px solid var(--line); padding:6px 0 }
+.search input { flex:1; background:transparent; border:0; color:var(--paper); font-family:inherit; font-size:13px; outline:none; letter-spacing:0.04em }
+.search input::placeholder { color:var(--muted) }
+.search svg { width:16px; height:16px; stroke:var(--muted); fill:none; stroke-width:1.5 }
+
+/* ===== Grid-density slider ===== */
+.density { display:flex; align-items:center; gap:14px; padding:6px 0; margin-bottom:24px }
+.density label { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--muted) }
+.density input[type=range] { flex:1; max-width:240px; -webkit-appearance:none; appearance:none; height:1px; background:var(--line); outline:none }
+.density input[type=range]::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50% }
+.density input[type=range]::-moz-range-thumb { width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50%; border:0 }
+.density .ct { font-size:11px; color:var(--muted); letter-spacing:0.18em; text-transform:uppercase; font-weight:600; min-width:80px }
+
+/* ===== Stats line ===== */
+.stat-line { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:600; color:var(--muted); margin-bottom:24px }
+
+/* ===== Product grid (novasuede pattern: flush grid + slide-up overlay) ===== */
+.grid { display:grid; grid-template-columns:repeat(var(--cols), 1fr); gap:0 }
+.card { position:relative; aspect-ratio:1/1.15; cursor:pointer; overflow:hidden; border:1px solid var(--bg); transition:transform 0.4s cubic-bezier(0.2,0.8,0.2,1); background:var(--bg-soft) }
+.card:hover { transform:scale(1.04); z-index:5 }
+.card img { width:100%; height:100%; object-fit:cover; display:block; transition:transform 0.4s ease }
+.card .overlay { position:absolute; left:0; right:0; bottom:0; padding:20px 16px 14px; background:linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.85) 100%); transform:translateY(48%); opacity:0.85; transition:transform 0.3s ease, opacity 0.3s ease }
+.card:hover .overlay { transform:translateY(0); opacity:1 }
+.card .pat { font-size:13px; font-weight:600; letter-spacing:0.02em; color:#fff; line-height:1.25; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }
+.card .ven { font-size:9px; letter-spacing:0.18em; text-transform:uppercase; color:#fff; opacity:0.7; font-weight:500; margin-top:4px }
+.card .actions { margin-top:10px; display:flex; gap:6px }
+.card .sample-btn { flex:1; padding:7px 10px; background:#fff; color:#000; font-family:inherit; font-size:10px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; border:0; cursor:pointer; text-align:center; transition:all 0.2s }
+.card .sample-btn:hover { background:var(--accent); color:#fff }
+
+@media (max-width:1024px) { .grid { grid-template-columns:repeat(min(var(--cols), 4), 1fr) } }
+@media (max-width:720px) { .grid { grid-template-columns:repeat(2, 1fr) } .density { display:none } }
+
+.sentinel { height:1px }
+.loading { text-align:center; color:var(--muted); padding:32px; font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700 }
+
+/* ===== Footer ===== */
+footer { padding:64px 32px 32px; border-top:1px solid var(--line); margin-top:32px }
+.footer-grid { max-width:1400px; margin:0 auto; display:grid; grid-template-columns:2fr 1fr 1fr 1fr; gap:48px; margin-bottom:48px }
+.footer-brand { font-size:24px; font-weight:300; letter-spacing:0.32em; text-transform:uppercase; margin-bottom:12px }
+.footer-text { font-size:13px; line-height:1.6; color:var(--muted); max-width:380px }
+.footer-col h4 { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--paper); margin-bottom:18px }
+.footer-col a { display:block; font-size:13px; color:var(--muted); text-decoration:none; margin-bottom:8px; transition:color 0.2s; cursor:pointer; background:transparent; border:0; font-family:inherit; padding:0; text-align:left }
+.footer-col a:hover { color:var(--accent) }
+.footer-bottom { max-width:1400px; margin:0 auto; padding-top:24px; border-top:1px solid var(--line); display:flex; justify-content:space-between; flex-wrap:wrap; gap:16px; font-size:11px; letter-spacing:0.18em; text-transform:uppercase; color:var(--muted) }
+
+@media (max-width:720px) {
+ .corner-mark, .meta-line, .enter { font-size:10px }
+ .center-mark { font-size:24px; letter-spacing:0.12em; top:clamp(60px,9vh,90px) }
+ .section, footer { padding-left:20px; padding-right:20px }
+ .footer-grid { grid-template-columns:1fr; gap:32px }
+ .search { margin-left:0; flex:1 1 100% }
+}
+
+.theme-toggle { background:transparent; border:1px solid var(--line); width:32px; height:32px; cursor:pointer; color:var(--paper); display:inline-flex; align-items:center; justify-content:center; font-size:14px; line-height:1; transition:all .15s; padding:0 }
+.theme-toggle:hover { border-color:var(--accent); color:var(--accent) }
+
+/* ===== WCAG 2.4.7 — keyboard focus indicators ===== */
+/* Strictly :focus-visible so mouse clicks do NOT show the ring */
+a:focus-visible,
+button:focus-visible,
+input:focus-visible,
+select:focus-visible,
+textarea:focus-visible,
+[tabindex]:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+</style>
+<script>
+(function(){ try { var t = localStorage.getItem('silk_theme') || 'dark'; document.documentElement.dataset.theme = t; } catch(e){} })();
+</script>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600;700&display=swap">
+
+<meta property="og:type" content="website">
+<meta property="og:url" content="https://silkwallpaper.com/">
+<meta property="og:title" content="Silk Wallpaper">
+<meta property="og:description" content="Silk Wallpaper — Pure silk wallcoverings — luxe and lustrous.">
+<meta property="og:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<meta property="og:site_name" content="Silk Wallpaper">
+<meta property="article:publisher" content="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings">
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:title" content="Silk Wallpaper">
+<meta name="twitter:description" content="Silk Wallpaper — Pure silk wallcoverings — luxe and lustrous.">
+<meta name="twitter:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<!-- graphics-loop pass 13: footer + fb -->
+</head>
+<body>
+<style id="ns-header-modal-styles">
+ .ns-header { position: sticky; top: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; padding: 18px 36px; background: rgba(10,10,10,0.92) !important; backdrop-filter: blur(8px); border-bottom: 1px solid rgba(245,241,232,0.06); }
+ .ns-brand { display: inline-flex; align-items: center; gap: 14px; text-decoration: none; }
+ .ns-dot { width: 28px; height: 28px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, #a98b5b 0%, #5d4c32 60%, #332a1b 100%); box-shadow: 0 0 0 1px rgba(245,241,232,0.10), 0 2px 12px rgba(180,90,60,0.30); flex-shrink: 0; }
+ .ns-name { font-family: -apple-system, "SF Pro Display", "Helvetica Neue", system-ui, sans-serif; font-size: 18px !important; font-weight: 300 !important; letter-spacing: 0.02em !important; color: #f5f1e8 !important; text-transform: none !important; margin: 0 !important; }
+ .ns-name sup { font-size: 0.55em; font-weight: 300; margin-left: 1px; }
+ .ns-nav { display: flex; gap: 32px; align-items: center; list-style: none; padding: 0; margin: 0; }
+ .ns-nav a, .ns-nav button { font-family: -apple-system, "SF Pro Text", system-ui, sans-serif; font-size: 15px !important; font-weight: 400 !important; color: #f5f1e8 !important; text-decoration: none !important; background: none !important; border: 0 !important; cursor: pointer; padding: 0 !important; transition: color 200ms; letter-spacing: 0 !important; text-transform: none !important; }
+ .ns-nav a:hover, .ns-nav button:hover { color: #ffffff !important; }
+ @media (max-width: 820px) { .ns-header { padding: 14px 18px; } .ns-nav { gap: 16px; } .ns-nav a, .ns-nav button { font-size: 13px !important; } }
+ @media (max-width: 760px) {
+ .ns-header { flex-wrap: wrap; }
+ .ns-nav { display: none !important; flex-basis: 100%; flex-direction: column; align-items: flex-start; gap: 6px; padding: 14px 0 4px; }
+ .ns-nav.open { display: flex !important; }
+ .ns-nav a, .ns-nav button { padding: 10px 0 !important; border-bottom: 1px solid rgba(245,241,232,0.08); width: 100%; text-align: left; }
+ .ns-nav a:nth-child(n+5), .ns-nav button:nth-child(n+5) { display: block !important; }
+ .ns-header .mobile-toggle { display: flex !important; }
+ }
+ @media (min-width: 761px) {
+ .ns-header .mobile-toggle { display: none !important; }
+ }
+ .ns-modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.78); z-index: 200; align-items: center; justify-content: center; padding: 24px; backdrop-filter: blur(6px); }
+ .ns-modal.show { display: flex; }
+ .ns-modal-card { max-width: 460px; width: 100%; background: #141414 !important; border: 1px solid rgba(245,241,232,0.14); border-radius: 6px; padding: 28px 30px 30px; position: relative; }
+ .ns-modal-card .close-x { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; background: none; border: 1px solid rgba(245,241,232,0.18); color: rgba(245,241,232,0.6) !important; border-radius: 50%; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; }
+ .ns-modal-card .close-x:hover { color: #ffffff !important; border-color: #ffffff; }
+ .ns-modal-card h3 { font-family: -apple-system, "SF Pro Display", system-ui; font-weight: 300 !important; font-size: 22px !important; margin: 0 0 6px !important; color: #f5f1e8 !important; }
+ .ns-modal-card .sub { color: rgba(245,241,232,0.65) !important; font-size: 13px; margin: 0 0 22px; }
+ .ns-actions { display: flex; flex-direction: column; gap: 10px; }
+ .ns-action { display: flex; align-items: center; gap: 14px; padding: 14px 16px; background: rgba(245,241,232,0.04) !important; border: 1px solid rgba(245,241,232,0.12) !important; color: #f5f1e8 !important; text-decoration: none !important; cursor: pointer; transition: all 200ms; font: inherit; text-align: left; }
+ .ns-action:hover { background: rgba(245,241,232,0.08) !important; border-color: #d4b683 !important; }
+ .ns-action .ico { width: 32px; height: 32px; border-radius: 50%; background: rgba(212,182,131,0.12); display: flex; align-items: center; justify-content: center; font-size: 15px; flex-shrink: 0; }
+ .ns-action .txt { flex: 1; }
+ .ns-action .txt b { display: block; font-weight: 500; font-size: 14px; color: #f5f1e8 !important; }
+ .ns-action .txt span { font-size: 12px; color: rgba(245,241,232,0.55) !important; }
+
+ /* ─── FINAL CONTRAST PASS — kill remaining gold-solid + white-island defects ─── */
+ /* Replace all gold-fill accents with off-white (Gucci-aesthetic, no DW gold solids) */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active,
+ .drapery-btn, .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover,
+ .drapery-btn:hover, .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ /* Gold accents stay only as edge color (links, glyphs) — not fills */
+ a { color: #d4b683; }
+ a:hover { color: #f5d97a; }
+ /* Authorized-dealer footer line — small caps tracked, muted ink, no gold pill */
+ .authorized, .authorized-dealer p {
+ background: transparent !important;
+ color: rgba(245,241,232,0.5) !important;
+ font-size: 10px !important;
+ letter-spacing: 0.22em !important;
+ text-transform: uppercase !important;
+ padding: 0 !important;
+ border: none !important;
+ margin-top: 14px !important;
+ }
+ /* Kill any straggler white bg in info-card / sample-related cards */
+ .info-card, .info-card-link, .care-highlight, .feature-card, .spec-item {
+ background-color: rgba(245,241,232,0.04) !important;
+ border-color: rgba(245,241,232,0.12) !important;
+ }
+ /* Ensure modal content stays readable — Steve's earlier sweep made it #141414 with white text */
+ .modal-content { background: #141414 !important; color: #f5f1e8 !important; }
+ .modal-content * { color: #f5f1e8 !important; }
+ .modal-content input, .modal-content textarea, .modal-content select {
+ background: rgba(245,241,232,0.05) !important;
+ color: #f5f1e8 !important;
+ border-color: rgba(245,241,232,0.2) !important;
+ }
+ .modal-content label { color: rgba(245,241,232,0.6) !important; }
+ /* Force the wide-tracked NOVASUEDE wordmark to stay white */
+ .ns-wordmark, .ns-wordmark sup { color: #f5f1e8 !important; }
+
+ /* ─── Modal-internal contrast (post-QA defects) ─── */
+ /* Backdrop covers full viewport */
+ .modal { width: 100vw !important; height: 100vh !important; left: 0 !important; top: 0 !important; background: rgba(0, 0, 0, 0.78) !important; backdrop-filter: blur(6px); }
+ /* Chip — flip from white-on-white island to dark card */
+ .request-chip {
+ background: rgba(245, 241, 232, 0.06) !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.18) !important;
+ border-radius: 4px !important;
+ }
+ .request-chip * { color: #f5f1e8 !important; }
+ .request-chip .chip-swatch {
+ /* swatch dot ALWAYS displays the inline-style hex from JS — fight any sweep */
+ border: 1px solid rgba(0, 0, 0, 0.4) !important;
+ }
+ /* + Add to request — hairline white-border on transparent (no gold) */
+ .add-to-request-btn {
+ background: transparent !important;
+ color: #f5f1e8 !important;
+ border: 1px solid rgba(245, 241, 232, 0.6) !important;
+ border-radius: 0 !important;
+ padding: 12px 22px !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 400 !important;
+ }
+ .add-to-request-btn:hover {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ /* Hex caption visible */
+ .preview-hex, #colorPreviewHex {
+ display: block !important;
+ color: rgba(245, 241, 232, 0.55) !important;
+ font-family: ui-monospace, "SF Mono", Menlo, monospace !important;
+ font-size: 11px !important;
+ margin-top: 4px !important;
+ letter-spacing: 0.04em !important;
+ text-transform: lowercase !important;
+ }
+ /* Color preview name — stays single, prominent */
+ #colorPreviewName {
+ color: #f5f1e8 !important;
+ font-size: 11px !important;
+ letter-spacing: 0.32em !important;
+ text-transform: uppercase !important;
+ font-weight: 500 !important;
+ display: block !important;
+ margin-top: 12px !important;
+ }
+
+ /* ─── Mobile hamburger nav (replaces the overflow at <760px) ─── */
+ @media (max-width: 760px) {
+ header .nav-container { flex-wrap: wrap; }
+ header nav { display: none; flex-basis: 100%; flex-direction: column; gap: 6px; padding: 16px 0 6px; }
+ header nav.open { display: flex; }
+ header nav a { padding: 10px 0; border-bottom: 1px solid rgba(245,241,232,0.1); }
+ header .mobile-toggle {
+ display: flex !important;
+ flex-direction: column;
+ gap: 5px;
+ background: transparent;
+ border: 0;
+ cursor: pointer;
+ padding: 8px;
+ }
+ header .mobile-toggle span {
+ width: 22px; height: 1.5px;
+ background: #f5f1e8;
+ display: block;
+ }
+ }
+ @media (min-width: 761px) {
+ header .mobile-toggle { display: none !important; }
+ }
+
+ </style>
+<style id="ns-gold-kill">
+ /* novasuede: kill gold-solid fills, off-white instead. Edge color stays gold. */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active, .drapery-btn,
+ .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover, .drapery-btn:hover,
+ .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ </style>
+<header class="ns-header">
+ <a href="/" class="ns-brand" aria-label="Home">
+ <span class="ns-dot"></span>
+ <span class="ns-name">Atelier Couture</span>
+ </a>
+ <nav class="ns-nav" id="nsPrimaryNav" aria-label="Primary">
+ <a href="/">Home</a>
+ <a href="/about">About</a>
+ <button type="button" onclick="nsContactOpen()">Contact</button>
+ </nav>
+ <button type="button" class="mobile-toggle" aria-label="Toggle menu" aria-controls="nsPrimaryNav" aria-expanded="false" onclick="nsToggleNav(this)">
+ <span></span><span></span><span></span>
+ </button>
+ </header>
+
+<div class="ns-modal" id="nsContactModal" role="dialog" aria-modal="true" aria-labelledby="nsContactTitle" onclick="if(event.target===this)nsContactClose()">
+ <div class="ns-modal-card">
+ <button type="button" class="close-x" onclick="nsContactClose()" aria-label="Close">×</button>
+ <h3 id="nsContactTitle">How can we help?</h3>
+ <p class="sub">Trade-account replies within one business day.</p>
+ <div class="ns-actions">
+ <a class="ns-action" href="#contact" onclick="nsContactClose()"><span class="ico">📋</span><div class="txt"><b>Get a Quote</b><span>Project specs → priced estimate</span></div></a>
+ <a class="ns-action" href="#contact" onclick="nsContactClose();openModal();"><span class="ico">📦</span><div class="txt"><b>Order a Sample</b><span>Free swatch to your shop</span></div></a>
+ <a class="ns-action" href="mailto:info@silkwallpaper.com?subject=Atelier%20Couture%20inquiry"><span class="ico">✉️</span><div class="txt"><b>Email Us</b><span>info@silkwallpaper.com</span></div></a>
+ <a class="ns-action" href="tel:888-373-4564"><span class="ico">📞</span><div class="txt"><b>Call Us</b><span>(888) 373-4564 · M–F, 9–5 PT</span></div></a>
+ </div>
+ </div>
+ </div>
+<script>
+ function nsContactOpen() { var m = document.getElementById('nsContactModal'); if (m) m.classList.add('show'); }
+ function nsContactClose() { var m = document.getElementById('nsContactModal'); if (m) m.classList.remove('show'); }
+ function nsToggleNav(btn) {
+ var nav = document.getElementById('nsPrimaryNav');
+ if (!nav) return;
+ var open = nav.classList.toggle('open');
+ btn.setAttribute('aria-expanded', open ? 'true' : 'false');
+ }
+ document.addEventListener('keydown', function(e){ if (e.key === 'Escape') nsContactClose(); });
+</script>
+
+
+
+
+
+
+
+
+
+
+
+<header>
+ <button class="h-link" onclick="dwmOpen('Contact')" aria-label="Contact"><svg class="h-icon" viewBox="0 0 24 24"><path d="M3 7h18M3 12h18M3 17h18"/></svg><span>Contact</span></button>
+ <button class="theme-toggle" id="theme-toggle" aria-label="Theme toggle">☾</button>
+</header>
+
+<section class="cinema">
+ <div class="cinema-bg" id="heroBg"></div>
+ <div class="corner-mark">Silken Sheen</div>
+ <div class="center-mark">Silk Wallpaper</div>
+ <div class="meta-line">Silk · Luxe · Sheen · Jacquard<span class="num" id="heroNum"></span></div>
+ <a class="enter" href="#shop">Enter <svg viewBox="0 0 24 12" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M0 6h22M16 1l6 5-6 5"/></svg></a>
+</section>
+
+
+<section class="section" id="ideas">
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">New this week</h2>
+ <div class="rail" id="railNewest"></div>
+ </div>
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">Curated picks</h2>
+ <div class="rail" id="railCurated"></div>
+ </div>
+</section>
+
+<section class="section" id="shop">
+ <div class="section-header">
+ <div>
+ <div class="section-eyebrow">The Collection</div>
+ <h2 class="section-title"><span class="accent" id="totalCount">—</span> Patterns.</h2>
+ </div>
+ <div class="section-meta">Silk · Luxe · Sheen · Jacquard</div>
+ </div>
+
+ <div class="filters" id="facets">
+ <button class="chip active" data-facet="all">All</button>
+ </div>
+
+ <div class="density">
+ <label>Sort</label>
+ <select id="sortSelect" aria-label="Sort products" style="background:transparent;color:inherit;border:1px solid var(--line,rgba(255,255,255,.2));font:inherit;font-size:11px;letter-spacing:.10em;text-transform:uppercase;padding:4px 8px;cursor:pointer;outline:none;margin-right:14px">
+ <option value="newest">Newest</option>
+ <option value="color">Color</option>
+ <option value="style">Style</option>
+ <option value="sku">SKU A→Z</option>
+ <option value="title">Title A→Z</option>
+ <option value="price-asc">Price ↑</option>
+ <option value="price-desc">Price ↓</option>
+ </select>
+ <label>Grid</label>
+ <input type="range" id="densitySlider" min="4" max="12" step="1" value="6" aria-label="Grid columns">
+ <span class="ct" id="densityLabel">6 cols</span>
+ <div class="search">
+ <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5.5-5.5"/></svg>
+ <input type="text" id="searchInput" placeholder="Search by pattern, color…" autocomplete="off">
+ </div>
+ </div>
+
+ <div class="stat-line" id="statLine">Loading…</div>
+ <div class="grid" id="grid"></div>
+ <div class="loading" id="loading">Loading more…</div>
+ <div class="sentinel" id="sentinel"></div>
+</section>
+
+<footer>
+ <div class="footer-grid">
+ <div>
+ <div class="footer-brand">Atelier Couture</div>
+ <p class="footer-text">Atelier Couture — real samples ship free.</p>
+ <p class="footer-text" style="margin-top:14px;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;font-weight:600;color:var(--paper);opacity:0.7"><a href="/about" style="color:inherit;text-decoration:none;border-bottom:1px solid var(--accent)">About →</a></p>
+ </div>
+ <div class="footer-col">
+ <h4>Aesthetic</h4>
+ <div id="footerFacets"></div>
+ </div>
+ <div class="footer-col">
+ <h4>Trade</h4>
+ <button onclick="dwmOpen('Inquiry')">Project Inquiry</button>
+ <button onclick="dwmOpen('Sample')">Request Sample</button>
+ <button onclick="dwmOpen('Contact')">Contact</button>
+ </div>
+
+ <div class="footer-col">
+ <h4>Social</h4>
+ <a class="fb-link" href="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings" target="_blank" rel="noopener">Facebook</a>
+ </div>
+ </div>
+ <div class="footer-bottom">
+ <span>silkwallpaper.com · Atelier Couture</span>
+ <span><a href="mailto:info@silkwallpaper.com" style="color:var(--paper);text-decoration:none;border-bottom:1px solid var(--accent)">info@silkwallpaper.com</a></span>
+ <span id="footerStat"></span>
+ </div>
+</footer>
+
+<script>
+// mini-constellation — 43 niche-stars, current site highlighted in gold
+(function(){
+ const SLUG = "silkwallpaper";
+ const NICHES = [
+ ['silkwallpaper','Silk','mat'],['silkwallcoverings','Silk W/C','mat'],['linenwallpaper','Linen','mat'],
+ ['jutewallpaper','Jute','mat'],['raffiawallcoverings','Raffia W/C','mat'],['raffiawalls','Raffia Walls','mat'],
+ ['corkwallcovering','Cork','mat'],['fabricwallpaper','Fabric','mat'],['textilewallpaper','Textile','mat'],
+ ['metallicwallpaper','Metallic','mat'],['silverleafwallpaper','Silver Leaf','mat'],['vinylwallpaper','Vinyl','mat'],
+ ['micawallpaper','Mica','mat'],['madagascarwallpaper','Madagascar','mat'],['mylarwallpaper','Mylar','mat'],
+ ['suedewallpaper','Suede','mat'],
+ ['1800swallpaper','1800s','dec'],['1890swallpaper','1890s','dec'],['1900swallpaper','1900s','dec'],
+ ['1920swallpaper','1920s','dec'],['1930swallpaper','1930s','dec'],['1940swallpaper','1940s','dec'],
+ ['1950swallpaper','1950s','dec'],['1960swallpaper','1960s','dec'],['1970swallpaper','1970s','dec'],
+ ['1980swallpaper','1980s','dec'],['retrowalls','Retro','dec'],['wallpapersback','W. Back','dec'],
+ ['agedwallpaper','Aged','cra'],['handcraftedwallpaper','Handcrafted','cra'],['museumwallpaper','Museum','cra'],
+ ['restorationwallpaper','Restoration','cra'],['pastelwallpaper','Pastel','cra'],['glitterwalls','Glitter','cra'],
+ ['greenwallcoverings','Green','cra'],['naturalwallcoverings','Natural','cra'],['saloonwallpaper','Saloon','cra'],
+ ['contractwallpaper','Contract','use'],['hotelwallcoverings','Hotel','use'],['hospitalitywallpaper','Hospitality','use'],
+ ['healthcarewallpaper','Healthcare','use'],['restaurantwallpaper','Restaurant','use'],['architecturalwallcoverings','Architectural','use']
+ ];
+ const COLOR = { mat:'#c9b687', dec:'#b86a4a', cra:'#6b8e6f', use:'#4a6b8e' };
+ const W = 1080, H = 200, BANDS = ['mat','dec','cra','use'];
+ const groups = { mat:[], dec:[], cra:[], use:[] };
+ for (const n of NICHES) groups[n[2]].push(n);
+ const svg = document.getElementById('miniConst');
+ if (!svg) return;
+ const bandW = W / BANDS.length;
+ let nodes = '';
+ BANDS.forEach((band, bi) => {
+ const list = groups[band];
+ const cx = bi * bandW + bandW / 2;
+ list.forEach((n, i) => {
+ const t = list.length === 1 ? 0.5 : i / (list.length - 1);
+ // deterministic jitter per slug
+ let h = 0; for (let k = 0; k < n[0].length; k++) h = (h * 31 + n[0].charCodeAt(k)) | 0;
+ const jx = ((h & 0xff) / 255 - 0.5) * (bandW * 0.5);
+ const jy = (((h >> 8) & 0xff) / 255 - 0.5) * 6; // tightened from 14 → 6 to prevent same-band collisions (16 mat nodes / 144px = 9px spacing)
+ const x = cx + jx, y = 28 + t * (H - 56) + jy;
+ const isCurrent = n[0] === SLUG;
+ const r = isCurrent ? 8 : 4;
+ const op = isCurrent ? 1 : 0.55;
+ nodes += `<g class="mc-node" data-slug="${n[0]}" data-label="${n[1]}" style="cursor:pointer">
+ <circle cx="${x}" cy="${y}" r="${r}" fill="url(#dna-${band})" fill-opacity="${op}" stroke="${COLOR[band]}" stroke-opacity="${isCurrent?1:0.7}" stroke-width="${isCurrent?2:1}">
+ <title>${n[1]} — ${n[0]}.com</title>
+ </circle>
+ ${isCurrent ? `<circle cx="${x}" cy="${y}" r="${r+5}" fill="none" stroke="${COLOR[band]}" stroke-opacity="0.4" stroke-width="1"><animate attributeName="r" values="${r+5};${r+12};${r+5}" dur="2.4s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" repeatCount="indefinite"/></circle>` : ''}
+ </g>`;
+ });
+ });
+ // append nodes after defs
+ svg.insertAdjacentHTML('beforeend', nodes);
+ svg.querySelectorAll('.mc-node').forEach(n => {
+ n.addEventListener('mouseenter', () => n.querySelector('circle').setAttribute('fill-opacity', '1'));
+ n.addEventListener('mouseleave', e => {
+ const isCurrent = n.dataset.slug === SLUG;
+ n.querySelector('circle').setAttribute('fill-opacity', isCurrent ? '1' : '0.55');
+ });
+ n.addEventListener('click', () => window.open('https://' + n.dataset.slug + '.com', '_blank'));
+ });
+})();
+</script>
+
+<script>
+const state = { q:'', facet:'all', page:1, pages:1, total:0, loading:false, exhausted:false };
+const LABELS = {"all":"All"};
+
+function escAttr(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
+// Image-URL allowlist: only DW Shopify CDN + designerwallcoverings.com (defends against XSS via crafted products.json)
+function safeImg(u) { return /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(u || '') ? u : '/hero-bg.jpg'; }
+function cardHTML(p) {
+ const eager = state.page === 1;
+ return '<img loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+ + '<div class="overlay">'
+ + '<div class="pat">' + escAttr(p.pattern_name || p.title) + '</div>'
+ + '<div class="ven">' + escAttr((p.vendor || '').replace(/-/g, ' ')) + '</div>'
+ + '<div class="actions">'
+ + '<button class="sample-btn" onclick="event.stopPropagation();dwmOpen(\'Sample\',' + JSON.stringify({sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}).replace(/"/g,'"') + ')">Sample</button>'
+ + '</div></div>';
+}
+
+async function loadFacets() {
+ let f;
+ try {
+ const r = await fetch('/api/facets');
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ f = await r.json();
+ } catch (e) { console.error('loadFacets failed:', e); return; }
+ const el = document.getElementById('facets');
+ for (const [k, v] of Object.entries(f.aesthetics).sort((a,b) => b[1] - a[1])) {
+ const b = document.createElement('button');
+ b.className = 'chip'; b.dataset.facet = k;
+ b.innerHTML = (LABELS[k] || k) + ' <span style="opacity:.55;font-weight:500;margin-left:4px">' + v + '</span>';
+ el.appendChild(b);
+ }
+ el.addEventListener('click', e => {
+ if (e.target.tagName !== 'BUTTON') return;
+ document.querySelectorAll('#facets button').forEach(b => b.classList.remove('active'));
+ e.target.classList.add('active');
+ state.facet = e.target.dataset.facet;
+ resetGrid();
+ });
+ document.getElementById('totalCount').textContent = f.total;
+ document.getElementById('footerStat').textContent = f.total + ' patterns · live archive';
+ // Footer aesthetic links
+ const footerEl = document.getElementById('footerFacets');
+ for (const [k] of Object.entries(f.aesthetics)) {
+ const b = document.createElement('button');
+ b.textContent = LABELS[k] || k;
+ b.onclick = () => { state.facet = k; resetGrid(); document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.facet === k)); document.getElementById('shop').scrollIntoView({behavior:'smooth'}); };
+ footerEl.appendChild(b);
+ }
+}
+
+function resetGrid() {
+ state.page = 1; state.exhausted = false;
+ document.getElementById('grid').innerHTML = '';
+ document.getElementById('loading').textContent = 'Loading…';
+ loadGridPage();
+}
+
+async function loadGridPage() {
+ if (state.loading || state.exhausted) return;
+ state.loading = true;
+ const params = new URLSearchParams({ page:state.page, limit:24 });
+ if (state.q) params.set('q', state.q);
+ if (state.facet !== 'all') params.set('aesthetic', state.facet);
+ let data;
+ try {
+ const r = await fetch('/api/products?' + params);
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ data = await r.json();
+ } catch (e) {
+ console.error('loadGridPage failed:', e);
+ document.getElementById('loading').textContent = '— offline — refresh to retry —';
+ state.loading = false;
+ return;
+ }
+ state.total = data.total; state.pages = data.pages;
+
+ if (state.page === 1) {
+ document.getElementById('statLine').textContent = data.total + ' patterns · ' + (state.facet === 'all' ? 'all aesthetics' : (LABELS[state.facet] || state.facet)) + (state.q ? ' · matching "' + state.q + '"' : '');
+ }
+ const grid = document.getElementById('grid');
+ for (const p of data.items) {
+ const a = document.createElement('a');
+ a.className = 'card'; a.href = '#'; a.onclick = (e) => { e.preventDefault(); dwmOpen('Sample',{sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}); };
+ a.innerHTML = cardHTML(p);
+ grid.appendChild(a);
+ }
+ if (state.page >= state.pages || data.items.length === 0) {
+ state.exhausted = true;
+ document.getElementById('loading').textContent = state.total > 0 ? '— end of archive · ' + state.total + ' patterns —' : '';
+ } else state.page++;
+ state.loading = false;
+}
+
+const io = new IntersectionObserver(es => { for (const e of es) if (e.isIntersecting) loadGridPage(); }, { rootMargin:'600px 0px' });
+io.observe(document.getElementById('sentinel'));
+
+document.getElementById('searchInput').addEventListener('input', e => {
+ state.q = e.target.value.trim();
+ clearTimeout(window._t);
+ window._t = setTimeout(resetGrid, 220);
+});
+
+// Density slider
+const slider = document.getElementById('densitySlider');
+const dlabel = document.getElementById('densityLabel');
+function setDensity(n) {
+ document.documentElement.style.setProperty('--cols', n);
+ dlabel.textContent = n + ' cols';
+ try { localStorage.setItem('silk_theme_density', n); } catch(e){}
+}
+slider.addEventListener('input', e => setDensity(parseInt(e.target.value)));
+const savedDensity = parseInt(localStorage.getItem('silk_theme_density') || '6');
+if (savedDensity >= 4 && savedDensity <= 12) { slider.value = savedDensity; setDensity(savedDensity); }
+
+// Theme toggle
+const tb = document.getElementById('theme-toggle');
+function setTheme(t){ document.documentElement.dataset.theme = t; try { localStorage.setItem('silk_theme', t); } catch(e){} tb.textContent = t === 'dark' ? '☾' : '☀'; }
+setTheme(document.documentElement.dataset.theme || 'dark');
+tb.addEventListener('click', () => setTheme(document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'));
+
+loadFacets();
+loadGridPage();
+</script>
+<!-- ============================================================
+ DW UNIVERSAL CONTACT MODULE — fashion-house UX
+ Inject before </body> in every DW-family sister site.
+ Self-contained: CSS + 4 modals + JS submission handlers.
+ Uses host site's CSS vars (--bg, --fg, --gold, --rule).
+ ============================================================ -->
+<style>
+ /* === MODAL BASE === */
+ .dwm{position:fixed;inset:0;background:rgba(0,0,0,0.78);display:none;align-items:center;justify-content:center;z-index:9999;padding:24px;overflow-y:auto}
+ .dwm.open{display:flex}
+ .dwm-box{background:var(--bg);border:1px solid var(--rule);max-width:520px;width:100%;padding:36px 32px;position:relative;max-height:90vh;overflow-y:auto;animation:dwm-in 0.25s ease}
+ @keyframes dwm-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+ .dwm-box h3{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-weight:400;font-size:32px;line-height:1.05;margin-bottom:6px;color:var(--fg);letter-spacing:-0.01em}
+ .dwm-box .sub{color:var(--muted);font-size:12px;letter-spacing:0.16em;text-transform:uppercase;margin-bottom:24px;font-weight:500}
+ .dwm-close{position:absolute;top:18px;right:18px;background:transparent;border:0;color:var(--muted);font-size:24px;cursor:pointer;width:32px;height:32px;line-height:1;padding:0}
+ .dwm-close:hover{color:var(--gold)}
+ .dwm-preview{display:flex;gap:14px;align-items:center;padding:12px;background:var(--bg-soft);margin-bottom:18px}
+ .dwm-preview img{width:64px;height:64px;object-fit:cover}
+ .dwm-preview .pn{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-size:15px;line-height:1.2;color:var(--fg)}
+ .dwm-preview .pc{font-size:10px;letter-spacing:0.14em;text-transform:uppercase;color:var(--muted);margin-top:4px}
+ .dwm-field{margin-bottom:14px}
+ .dwm-field label{display:block;font-size:10px;letter-spacing:0.18em;text-transform:uppercase;color:var(--muted);margin-bottom:6px;font-weight:500}
+ .dwm-field input,.dwm-field textarea,.dwm-field select{width:100%;border:0;border-bottom:1px solid var(--rule);background:transparent;padding:8px 4px;font-size:14px;font-family:inherit;color:var(--fg);outline:none;transition:border-color 0.2s}
+ .dwm-field input:focus,.dwm-field textarea:focus{border-bottom-color:var(--gold)}
+ .dwm-field textarea{min-height:60px;resize:vertical}
+ .dwm-submit{background:transparent;color:var(--muted, #c9b89e);border:1px solid var(--muted, #c9b89e);padding:14px 28px;font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:700;cursor:pointer;width:100%;margin-top:8px;transition:background 0.15s, color 0.15s}
+ .dwm-submit:hover{background:var(--muted, #c9b89e);color:var(--bg, #000)}
+
+ .dwm-submit:disabled{opacity:0.5;cursor:default}
+ .dwm-status{margin-top:14px;font-size:11px;letter-spacing:0.16em;text-transform:uppercase;text-align:center;display:none}
+ .dwm-status.ok{color:var(--sage);display:block}
+ .dwm-status.err{color:#e08070;display:block}
+ /* contact-options */
+ .dwm-options{display:flex;flex-direction:column;gap:10px}
+ .dwm-option{display:flex;align-items:center;gap:14px;padding:18px 20px;background:transparent;border:1px solid var(--rule);color:var(--fg);font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:600;cursor:pointer;text-decoration:none;transition:all 0.2s;text-align:left;width:100%}
+ .dwm-option:hover{border-color:var(--gold);color:var(--gold)}
+ .dwm-option svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:1.5;flex-shrink:0}
+ .dwm-option .lbl{flex:1;display:block}
+ .dwm-option .val{display:block;font-size:10px;letter-spacing:0.14em;text-transform:none;font-weight:500;opacity:0.65;margin-top:4px}
+
+/* graphics-loop pass 5: serif hero typography + nav legibility */
+.cinema h1, h1.headline { font-family: 'Cormorant Garamond', Georgia, serif; font-weight: 500; letter-spacing: -0.01em }
+.h-link { font-size: 13px; letter-spacing: 0.06em }
+
+/* graphics-loop pass 11: Ideas rails */
+#ideas { padding: 56px 24px 24px; max-width:1600px; margin:0 auto }
+#ideas .rail-block { margin-bottom:48px }
+#ideas .rail-eyebrow { font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:var(--muted, #888); margin-bottom:8px }
+#ideas .rail-title { font-size:clamp(22px,2.6vw,32px); font-weight:500; letter-spacing:-0.01em; margin:0 0 18px; color:var(--ink,#fff) }
+#ideas .rail { display:flex; gap:14px; overflow-x:auto; scroll-snap-type:x mandatory; padding-bottom:8px; scrollbar-width:none }
+#ideas .rail::-webkit-scrollbar { display:none }
+#ideas .rail-card { flex:0 0 240px; scroll-snap-align:start; cursor:pointer; transition:transform 0.2s }
+#ideas .rail-card:hover { transform:translateY(-3px) }
+#ideas .rail-card img { width:100%; aspect-ratio:1; object-fit:cover; display:block; background:var(--card,#222) }
+#ideas .rail-card .rc-title { font-size:13px; font-weight:500; color:var(--paper,#fff); padding:8px 0 2px; line-height:1.3 }
+#ideas .rail-card .rc-meta { font-size:11px; color:var(--muted,#888); letter-spacing:0.06em }
+@media (max-width:720px) { #ideas .rail-card { flex:0 0 160px } }
+</style>
+
+<!-- Contact options modal -->
+<div class="dwm" id="dwmContact" onclick="if(event.target===this)dwmClose('Contact')">
+ <div class="dwm-box" style="max-width:440px">
+ <button class="dwm-close" onclick="dwmClose('Contact')" aria-label="Close">×</button>
+ <h3>Contact</h3>
+ <p class="sub" id="dwmContactSub">Three ways to reach us</p>
+ <div class="dwm-options">
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Inquiry')">
+ <svg viewBox="0 0 24 24"><path d="M4 4h16v12H7l-3 3z"/></svg>
+ <span class="lbl">Send an Inquiry<span class="val">Project name · scope · all the details</span></span>
+ </button>
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Sample')">
+ <svg viewBox="0 0 24 24"><rect x="4" y="6" width="16" height="14"/><path d="M4 10h16M9 6V3h6v3"/></svg>
+ <span class="lbl">Request a Sample<span class="val">Memo sample · ships free · 3–5 business days</span></span>
+ </button>
+ <a class="dwm-option" id="dwmContactEmailLink" href="mailto:info@silkwallpaper.com">
+ <svg viewBox="0 0 24 24"><path d="M3 6h18v12H3z"/><path d="M3 6l9 7 9-7"/></svg>
+ <span class="lbl">Email Us<span class="val" id="dwmContactEmailLabel">info@silkwallpaper.com</span></span>
+ </a>
+ </div>
+ </div>
+</div>
+
+<!-- Inquiry modal -->
+<div class="dwm" id="dwmInquiry" onclick="if(event.target===this)dwmClose('Inquiry')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Inquiry')" aria-label="Close">×</button>
+ <h3>Project Inquiry</h3>
+ <p class="sub">We'll respond within one business day</p>
+ <form id="dwmInquiryForm" onsubmit="return dwmSubmit(event,'inquiry')">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Phone</label><input type="tel" name="phone"></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Project Name</label><input type="text" name="projectName" required></div>
+ <div class="dwm-field"><label>Project Scope</label><textarea name="projectScope" placeholder="Square footage · room count · timeline" required></textarea></div>
+ <div class="dwm-field"><label>Additional Information</label><textarea name="message" placeholder="Patterns of interest, finish preferences, budget"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Inquiry</button>
+ <div class="dwm-status" id="dwmInquiryStatus"></div>
+ </form>
+ </div>
+</div>
+
+<!-- Sample-request modal -->
+<div class="dwm" id="dwmSample" onclick="if(event.target===this)dwmClose('Sample')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Sample')" aria-label="Close">×</button>
+ <h3>Request a Sample</h3>
+ <p class="sub">Memo sample · ships free · 3–5 business days</p>
+ <div class="dwm-preview" id="dwmSamplePreview" style="display:none">
+ <img id="dwmSampleImg" src="" alt="">
+ <div><div class="pn" id="dwmSampleName"></div><div class="pc" id="dwmSampleSku"></div></div>
+ </div>
+ <form id="dwmSampleForm" onsubmit="return dwmSubmit(event,'sample')">
+ <input type="hidden" name="sku" id="dwmSampleSkuInput">
+ <input type="hidden" name="title" id="dwmSampleTitleInput">
+ <input type="hidden" name="image_url" id="dwmSampleImageInput">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Shipping Address</label><input type="text" name="address" placeholder="Street" required></div>
+ <div class="dwm-field" style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:8px">
+ <input type="text" name="city" placeholder="City" required>
+ <input type="text" name="state" placeholder="State" required>
+ <input type="text" name="zip" placeholder="ZIP" required>
+ </div>
+ <div class="dwm-field"><label>Notes (optional)</label><textarea name="message" placeholder="Project, sq.ft., timing"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Sample Request</button>
+ <div class="dwm-status" id="dwmSampleStatus"></div>
+ </form>
+ </div>
+</div>
+
+<script>
+ // Universal modal control
+ function dwmOpen(name, opts){
+ document.getElementById('dwm' + name).classList.add('open');
+ document.body.style.overflow = 'hidden';
+ if (name === 'Sample' && opts) {
+ document.getElementById('dwmSamplePreview').style.display = 'flex';
+ document.getElementById('dwmSampleImg').src = opts.image_url || '';
+ document.getElementById('dwmSampleName').textContent = opts.title || '';
+ document.getElementById('dwmSampleSku').textContent = opts.sku || '';
+ document.getElementById('dwmSampleSkuInput').value = opts.sku || '';
+ document.getElementById('dwmSampleTitleInput').value = opts.title || '';
+ document.getElementById('dwmSampleImageInput').value = opts.image_url || '';
+ } else if (name === 'Sample') {
+ document.getElementById('dwmSamplePreview').style.display = 'none';
+ document.getElementById('dwmSampleSkuInput').value = '';
+ document.getElementById('dwmSampleTitleInput').value = '';
+ document.getElementById('dwmSampleImageInput').value = '';
+ }
+ }
+ function dwmClose(name){ document.getElementById('dwm'+name).classList.remove('open'); document.body.style.overflow=''; }
+
+ // Universal form submit — POSTs to /api/send-{kind}
+ async function dwmSubmit(e, kind) {
+ e.preventDefault();
+ const form = e.target;
+ const data = Object.fromEntries(new FormData(form).entries());
+ const btn = form.querySelector('.dwm-submit');
+ const status = form.querySelector('.dwm-status');
+ btn.disabled = true; const orig = btn.textContent; btn.textContent = 'Sending…';
+ status.className = 'dwm-status';
+ try {
+ const r = await fetch('/api/send-' + kind, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data) });
+ if (!r.ok) throw new Error('HTTP '+r.status);
+ const j = await r.json();
+ status.textContent = kind === 'sample' ? 'Sample request sent — ships within 3–5 business days' : 'Inquiry sent — we respond within 1 business day';
+ status.className = 'dwm-status ok';
+ btn.textContent = 'Sent ✓';
+ setTimeout(() => { dwmClose(kind === 'sample' ? 'Sample' : 'Inquiry'); btn.disabled = false; btn.textContent = orig; status.className = 'dwm-status'; form.reset(); }, 2800);
+ } catch (err) {
+ status.textContent = 'Send failed — please email info@designerwallcoverings.com';
+ status.className = 'dwm-status err';
+ btn.disabled = false; btn.textContent = orig;
+ }
+ return false;
+ }
+
+ // Wire up "Memo samples ship free" badge in hero to open sample modal directly
+ document.querySelectorAll('.hero .badge, [data-open-sample]').forEach(el => {
+ el.style.cursor = 'pointer';
+ el.addEventListener('click', () => dwmOpen('Sample'));
+ });
+
+ // Wire nav "Trade" link to inquiry modal
+ document.querySelectorAll('nav a[href*="designerwallcoverings.com"]').forEach(a => {
+ a.removeAttribute('target');
+ a.removeAttribute('href');
+ a.style.cursor = 'pointer';
+ a.addEventListener('click', e => { e.preventDefault(); dwmOpen('Contact'); });
+ });
+
+ // Override product card clicks — open Sample modal pre-filled instead of redirecting to DW
+ document.addEventListener('click', e => {
+ const card = e.target.closest('.card, .rail-card');
+ if (!card) return;
+ if (card.tagName === 'A' && (card.getAttribute('href') || '').startsWith('/sample/')) {
+ e.preventDefault();
+ const img = card.querySelector('img');
+ const pat = card.querySelector('.pat');
+ const sku = card.getAttribute('href').replace('/sample/', '');
+ dwmOpen('Sample', {
+ sku,
+ title: pat ? pat.textContent.trim() : '',
+ image_url: img ? img.src : ''
+ });
+ }
+ }, true);
+// DW STANDARD: hero pulls from products.json[0].image_url so dw-collections-viewer
+// drag-drop / hero-pin updates the live front page on next deploy.
+// Always upgrades to the largest Shopify variant (strips _NxN size suffix) so
+// the full-page hero stays sharp.
+// DW STANDARD: hero rotates through top-20 NEWEST products on each visit
+// (novasuede.com pattern). Fetches /api/products?limit=100, sorts by created_at desc,
+// takes the freshest 20, picks one per visit via localStorage counter.
+// Falls back to static /hero-bg.jpg on any error.
+(async function rotateHero() {
+ try {
+ const r = await fetch('/api/products?limit=100');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || [])
+ .filter(p => p && p.image_url && /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(p.image_url))
+ .sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')))
+ .slice(0, 20);
+ if (!items.length) return;
+ let visits = 0;
+ try {
+ visits = parseInt(localStorage.getItem('hero_visits') || '0', 10) || 0;
+ localStorage.setItem('hero_visits', String(visits + 1));
+ } catch(e) {}
+ const pick = items[visits % items.length];
+ // Strip _NxN size suffix to get the largest CDN variant
+ const url = pick.image_url.replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ const bg = document.getElementById('heroBg') || document.getElementById('cinemaBg');
+ if (bg) bg.style.backgroundImage = "url('" + url + "')";
+ } catch (e) {}
+})();
+
+
+// DW STANDARD sort-select wire-up (added by fleet patcher)
+(function(){
+ const sortSel = document.getElementById('sortSelect');
+ if (!sortSel) return;
+ const KEY = location.hostname.replace(/\./g, '_') + '_sort';
+ const saved = localStorage.getItem(KEY);
+ if (saved) { try { sortSel.value = saved; } catch(e){} }
+ if (typeof state !== 'undefined') state.sort = sortSel.value;
+ sortSel.addEventListener('change', e => {
+ const v = e.target.value;
+ try { localStorage.setItem(KEY, v); } catch(e){}
+ if (typeof state !== 'undefined') state.sort = v;
+ if (typeof resetGrid === 'function') resetGrid();
+ else location.reload();
+ });
+})();
+
+// graphics-loop pass 11: hydrate Ideas rails from /api/products
+(async function hydrateIdeas() {
+ try {
+ const r = await fetch('/api/products?limit=80');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || []).filter(p => p && p.image_url);
+ if (!items.length) return;
+ const sorted = [...items].sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')));
+ const newest = sorted.slice(0, 12);
+ // Curated = deterministic offset slice (every 5th item starting at idx 3) for variety
+ const curated = items.filter((_, i) => i % 5 === 3).slice(0, 12);
+ function card(p) {
+ const url = (p.image_url || '').replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ return '<a class="rail-card" href="/sample/' + encodeURIComponent(p.handle || p.sku) + '">' +
+ '<img src="' + url + '" alt="' + (p.title || '').replace(/"/g, '"') + '" loading="lazy">' +
+ '<div class="rc-title">' + ((p.title || '').split('|')[0] || '').trim() + '</div>' +
+ '<div class="rc-meta">' + (p.vendor || '') + '</div>' +
+ '</a>';
+ }
+ const r1 = document.getElementById('railNewest');
+ const r2 = document.getElementById('railCurated');
+ if (r1) r1.innerHTML = newest.map(card).join('');
+ if (r2) r2.innerHTML = curated.map(card).join('');
+ } catch(e) {}
+})();
+</script>
+
+</body>
+</html>
diff --git a/public/index.html.pre-sort-frontend b/public/index.html.pre-sort-frontend
new file mode 100644
index 0000000..a3986b9
--- /dev/null
+++ b/public/index.html.pre-sort-frontend
@@ -0,0 +1,914 @@
+<!doctype html>
+<html lang="en" data-hero-mode="huge">
+<head>
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
+
+<meta charset="utf-8">
+<!-- Google tag (gtag.js) -->
+<script async src="https://www.googletagmanager.com/gtag/js?id=G-CNTJNLTEW8"></script>
+<script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+ gtag('config', 'G-CNTJNLTEW8');
+</script>
+
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>silkwallpaper — curated wallcoverings, real samples ship free.</title>
+<meta name="description" content="silkwallpaper — a curated wallcovering atelier. Free memo samples. Trade service.">
+<meta name="theme-color" content="#FFF">
+<link rel="canonical" href="https://silkwallpaper.com/">
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
+<link rel="preload" as="image" href="/hero-bg.jpg" fetchpriority="high">
+<script type="application/ld+json">
+{
+ "@context": "https://schema.org",
+ "@type": "Organization",
+ "name": "silkwallpaper",
+ "url": "https://silkwallpaper.com/",
+ "email": "info@silkwallpaper.com"
+}
+</script>
+<script src="/zd-loader.js" defer></script>
+<style>
+/* silkwallpaper · Saint Laurent template — pure monochrome, sharp uppercase, zero ornament */
+:root {
+ --bg: #000;
+ --paper: #FFF;
+ --muted: #888;
+ --line: rgba(255,255,255,0.20);
+ --accent: #FFF;
+ --bg-soft: #0a0a0a;
+ --cols: 6;
+}
+html[data-theme="light"] {
+ --bg: #FFF;
+ --paper: #000;
+ --muted: #666;
+ --line: rgba(0,0,0,0.20);
+ --accent: #000;
+ --bg-soft: #F5F5F5;
+}
+* { margin:0; padding:0; box-sizing:border-box }
+html { scroll-behavior:smooth }
+body { font-family:'Inter', 'Helvetica Neue', sans-serif; color:var(--paper); -webkit-font-smoothing:antialiased; background:var(--bg); min-height:100vh; overflow-x:hidden }
+/* DW-FASHION-TEMPLATE-OVERRIDE */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', Georgia, serif !important; font-weight:900 !important; text-transform:uppercase !important;letter-spacing:0.04em !important; }
+/* Saint Laurent: heavy uppercase headlines, no italic serif accent */
+.section-title .accent, .footer-brand, .center-mark, h1, h2, h3 { font-family:'Inter', 'Helvetica Neue', sans-serif !important; font-weight:900 !important; font-style:normal !important; letter-spacing:0.04em !important; text-transform:uppercase !important }
+
+/* ===== Auto-hide header ===== */
+header { position:fixed; top:0; left:0; right:0; display:flex; justify-content:space-between; align-items:center; padding:22px 32px; z-index:100; background:rgba(0,0,0,0.55); backdrop-filter:blur(10px); border-bottom:1px solid var(--line); transition:transform .3s }
+.h-link { display:flex; align-items:center; gap:10px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); text-decoration:none; cursor:pointer; background:transparent; border:0 }
+.h-link:hover { opacity:0.7 }
+.h-icon { width:18px; height:18px; stroke:currentColor; fill:none; stroke-width:1.5 }
+
+/* ===== Cinema hero (R2: huge full-bleed photo + top-bar logo + below-photo wordmark) ===== */
+.cinema { position:relative; min-height:clamp(560px, 90vh, 1080px); width:100%; overflow:hidden }
+.cinema-bg { position:absolute; inset:0; background-size:cover; background-position:center; background-image:url('/hero-bg.jpg') }
+.cinema-bg::after { content:""; position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,0,0,0.20) 0%, rgba(0,0,0,0) 35%, rgba(0,0,0,0) 65%, rgba(0,0,0,0.55) 100%) }
+/* Hero-mode hooks (R4): both modes share the same min-height — CLS-free */
+[data-hero-mode="four-square"] .cinema-bg { display:none }
+[data-hero-mode="four-square"] .cinema-grid { display:grid; grid-template-columns:1fr 1fr; grid-template-rows:1fr 1fr; position:absolute; inset:0 }
+[data-hero-mode="four-square"] .cinema-grid .cell { background-size:cover; background-position:center; border:1px solid rgba(0,0,0,0.4) }
+.cinema-grid { display:none }
+.cinema-logo { position:absolute; top:22px; left:32px; font-family: ui-serif, Georgia, serif; font-size:18px; font-weight:500; letter-spacing:0.02em; color:#fff; text-decoration:none; z-index:6; text-shadow:0 1px 4px rgba(0,0,0,0.5) }
+/* Below-photo wordmark (R2): brand display lives below the hero, not painted on it */
+.hero-wordmark-block { position:relative; padding:38px 32px 24px; text-align:center; background:var(--bg) }
+.hero-wordmark { font-family: ui-serif, Georgia, 'Cormorant Garamond', serif; font-weight:400; font-size:clamp(48px, 9vw, 128px); letter-spacing:-0.01em; line-height:1; color:var(--paper); margin:0; text-transform:none }
+.hero-wordmark-sub { display:block; font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400; font-size:clamp(13px, 1.4vw, 17px); letter-spacing:0.04em; text-transform:none; margin-top:14px; color:var(--muted) }
+@media (max-width:760px) { .hero-wordmark { font-size:clamp(32px, 12vw, 64px) } }
+.corner-mark { position:absolute; top:24px; left:32px; font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:#fff; text-shadow:0 1px 3px rgba(0,0,0,0.55), 0 0 1px rgba(0,0,0,0.4); z-index:5 }
+.corner-mark::before { content:"◆ "; margin-right:6px }
+/* center-mark deprecated 2026-05-10 per R2 — hidden */
+.center-mark { display:none !important; position:absolute; top:clamp(80px,12vh,140px); left:50%; transform:translateX(-50%); font-size:clamp(28px,4vw,56px); font-weight:500; letter-spacing:0.18em; text-transform:uppercase; text-align:center; color:#fff; text-shadow:0 2px 8px rgba(0,0,0,0.65), 0 0 1px rgba(0,0,0,0.5); z-index:5 }
+.center-mark .tm { font-size:14px; vertical-align:super; opacity:0.7; margin-left:0.2em }
+.center-mark .sub { display:block; font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400; font-size:0.4em; letter-spacing:0.04em; text-transform:none; margin-top:14px; opacity:0.85 }
+.meta-line { position:absolute; bottom:32px; left:32px; font-size:10px; letter-spacing:0.28em; text-transform:uppercase; font-weight:700; color:#fff; z-index:5; opacity:0.85 }
+.meta-line .num { font-size:12px; display:block; margin-top:4px; letter-spacing:0.05em }
+.enter { position:absolute; bottom:32px; right:32px; display:flex; align-items:center; gap:14px; font-size:11px; letter-spacing:0.36em; text-transform:uppercase; font-weight:700; color:#fff; text-decoration:none; padding-bottom:6px; border-bottom:1px solid #fff; transition:gap 0.25s; z-index:5 }
+.enter:hover { gap:22px }
+.enter svg { width:24px; height:12px }
+
+/* ===== Section ===== */
+.section { padding:96px 32px }
+.section-header { display:flex; justify-content:space-between; align-items:flex-end; flex-wrap:wrap; gap:24px; margin-bottom:32px; padding-bottom:24px; border-bottom:1px solid var(--line) }
+.section-title { font-size:clamp(36px,5vw,64px); font-weight:300; letter-spacing:-0.02em; line-height:1 }
+.section-title .accent { color: var(--accent); font-family:'Playfair Display', Georgia, serif; font-style:italic; font-weight:400 }
+.section-eyebrow { font-size:10px; letter-spacing:0.4em; text-transform:uppercase; font-weight:700; color:var(--muted); margin-bottom:16px }
+.section-meta { font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:600; color:var(--muted); text-align:right; line-height:1.6 }
+
+/* ===== Filter bar ===== */
+.filters { display:flex; gap:8px; align-items:center; margin-bottom:18px; flex-wrap:wrap }
+.chip { padding:8px 16px; font-size:11px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--paper); background:transparent; border:1px solid var(--line); cursor:pointer; transition:all 0.2s; font-family:inherit }
+.chip:hover { border-color:var(--paper) }
+.chip.active { background:var(--paper); color:var(--bg); border-color:var(--paper) }
+.search { flex:0 1 280px; margin-left:auto; display:flex; align-items:center; gap:10px; border-bottom:1px solid var(--line); padding:6px 0 }
+.search input { flex:1; background:transparent; border:0; color:var(--paper); font-family:inherit; font-size:13px; outline:none; letter-spacing:0.04em }
+.search input::placeholder { color:var(--muted) }
+.search svg { width:16px; height:16px; stroke:var(--muted); fill:none; stroke-width:1.5 }
+
+/* ===== Grid-density slider ===== */
+.density { display:flex; align-items:center; gap:14px; padding:6px 0; margin-bottom:24px }
+.density label { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--muted) }
+.density input[type=range] { flex:1; max-width:240px; -webkit-appearance:none; appearance:none; height:1px; background:var(--line); outline:none }
+.density input[type=range]::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50% }
+.density input[type=range]::-moz-range-thumb { width:14px; height:14px; background:var(--accent); cursor:pointer; border-radius:50%; border:0 }
+.density .ct { font-size:11px; color:var(--muted); letter-spacing:0.18em; text-transform:uppercase; font-weight:600; min-width:80px }
+
+/* ===== Stats line ===== */
+.stat-line { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:600; color:var(--muted); margin-bottom:24px }
+
+/* ===== Product grid (novasuede pattern: flush grid + slide-up overlay) ===== */
+.grid { display:grid; grid-template-columns:repeat(var(--cols), 1fr); gap:0 }
+.card { position:relative; aspect-ratio:1/1.15; cursor:pointer; overflow:hidden; border:1px solid var(--bg); transition:transform 0.4s cubic-bezier(0.2,0.8,0.2,1); background:var(--bg-soft) }
+.card:hover { transform:scale(1.04); z-index:5 }
+.card img { width:100%; height:100%; object-fit:cover; display:block; transition:transform 0.4s ease }
+.card .overlay { position:absolute; left:0; right:0; bottom:0; padding:20px 16px 14px; background:linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.85) 100%); transform:translateY(48%); opacity:0.85; transition:transform 0.3s ease, opacity 0.3s ease }
+.card:hover .overlay { transform:translateY(0); opacity:1 }
+.card .pat { font-size:13px; font-weight:600; letter-spacing:0.02em; color:#fff; line-height:1.25; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }
+.card .ven { font-size:9px; letter-spacing:0.18em; text-transform:uppercase; color:#fff; opacity:0.7; font-weight:500; margin-top:4px }
+.card .actions { margin-top:10px; display:flex; gap:6px }
+.card .sample-btn { flex:1; padding:7px 10px; background:#fff; color:#000; font-family:inherit; font-size:10px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; border:0; cursor:pointer; text-align:center; transition:all 0.2s }
+.card .sample-btn:hover { background:var(--accent); color:#fff }
+
+@media (max-width:1024px) { .grid { grid-template-columns:repeat(min(var(--cols), 4), 1fr) } }
+@media (max-width:720px) { .grid { grid-template-columns:repeat(2, 1fr) } .density { display:none } }
+
+.sentinel { height:1px }
+.loading { text-align:center; color:var(--muted); padding:32px; font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700 }
+
+/* ===== Footer ===== */
+footer { padding:64px 32px 32px; border-top:1px solid var(--line); margin-top:32px }
+.footer-grid { max-width:1400px; margin:0 auto; display:grid; grid-template-columns:2fr 1fr 1fr 1fr; gap:48px; margin-bottom:48px }
+.footer-brand { font-size:24px; font-weight:300; letter-spacing:0.32em; text-transform:uppercase; margin-bottom:12px }
+.footer-text { font-size:13px; line-height:1.6; color:var(--muted); max-width:380px }
+.footer-col h4 { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--paper); margin-bottom:18px }
+.footer-col a { display:block; font-size:13px; color:var(--muted); text-decoration:none; margin-bottom:8px; transition:color 0.2s; cursor:pointer; background:transparent; border:0; font-family:inherit; padding:0; text-align:left }
+.footer-col a:hover { color:var(--accent) }
+.footer-bottom { max-width:1400px; margin:0 auto; padding-top:24px; border-top:1px solid var(--line); display:flex; justify-content:space-between; flex-wrap:wrap; gap:16px; font-size:11px; letter-spacing:0.18em; text-transform:uppercase; color:var(--muted) }
+
+@media (max-width:720px) {
+ .corner-mark, .meta-line, .enter { font-size:10px }
+ .center-mark { font-size:24px; letter-spacing:0.12em; top:clamp(60px,9vh,90px) }
+ .section, footer { padding-left:20px; padding-right:20px }
+ .footer-grid { grid-template-columns:1fr; gap:32px }
+ .search { margin-left:0; flex:1 1 100% }
+}
+
+.theme-toggle { background:transparent; border:1px solid var(--line); width:32px; height:32px; cursor:pointer; color:var(--paper); display:inline-flex; align-items:center; justify-content:center; font-size:14px; line-height:1; transition:all .15s; padding:0 }
+.theme-toggle:hover { border-color:var(--accent); color:var(--accent) }
+
+/* ===== WCAG 2.4.7 — keyboard focus indicators ===== */
+/* Strictly :focus-visible so mouse clicks do NOT show the ring */
+a:focus-visible,
+button:focus-visible,
+input:focus-visible,
+select:focus-visible,
+textarea:focus-visible,
+[tabindex]:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+</style>
+<script>
+(function(){ try { var t = localStorage.getItem('silk_theme') || 'dark'; document.documentElement.dataset.theme = t; } catch(e){} })();
+</script>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600;700&display=swap">
+
+<meta property="og:type" content="website">
+<meta property="og:url" content="https://silkwallpaper.com/">
+<meta property="og:title" content="silkwallpaper — curated wallcoverings, real samples ship free.">
+<meta property="og:description" content="silkwallpaper — a curated wallcovering atelier. Free memo samples. Trade service.">
+<meta property="og:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<meta property="og:site_name" content="silkwallpaper">
+<meta property="article:publisher" content="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings">
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:title" content="silkwallpaper — curated wallcoverings, real samples ship free.">
+<meta name="twitter:description" content="silkwallpaper — a curated wallcovering atelier. Free memo samples. Trade service.">
+<meta name="twitter:image" content="https://silkwallpaper.com/hero-bg.jpg">
+<!-- graphics-loop pass 13: footer + fb -->
+</head>
+<body>
+<style id="ns-header-modal-styles">
+ .ns-header { position: sticky; top: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; padding: 18px 36px; background: rgba(10,10,10,0.92) !important; backdrop-filter: blur(8px); border-bottom: 1px solid rgba(245,241,232,0.06); }
+ .ns-brand { display: inline-flex; align-items: center; gap: 14px; text-decoration: none; }
+ .ns-dot { width: 28px; height: 28px; border-radius: 50%; background: var(--ns-dot-bg, radial-gradient(circle at 35% 35%, #b66a4d 0%, #8a4a30 60%, #5a2d1c 100%)); box-shadow: 0 0 0 1px rgba(245,241,232,0.10), 0 2px 12px rgba(180,90,60,0.30); flex-shrink: 0; }
+ .ns-name { font-family: -apple-system, "SF Pro Display", "Helvetica Neue", system-ui, sans-serif; font-size: 18px !important; font-weight: 300 !important; letter-spacing: 0.02em !important; color: #f5f1e8 !important; text-transform: none !important; margin: 0 !important; }
+ .ns-name sup { font-size: 0.55em; font-weight: 300; margin-left: 1px; }
+ .ns-nav a, .ns-nav button { font-family: -apple-system, "SF Pro Text", system-ui, sans-serif; font-size: 15px !important; font-weight: 400 !important; color: #f5f1e8 !important; text-decoration: none !important; background: none !important; border: 0 !important; cursor: pointer; transition: color 200ms; letter-spacing: 0 !important; text-transform: none !important; }
+ .ns-nav a:hover, .ns-nav button:hover { color: #ffffff !important; }
+ /* All widths: hide nav by default, hamburger always visible on right, nav opens as right-anchored dropdown panel */
+ .ns-nav {
+ display: none !important;
+ position: absolute; top: 100%; right: 18px;
+ flex-direction: column; align-items: stretch; gap: 0;
+ background: rgba(10,10,10,0.96); backdrop-filter: blur(10px);
+ border: 1px solid rgba(245,241,232,0.10); border-top: 0;
+ min-width: 220px; padding: 10px 0; margin: 0;
+ box-shadow: 0 16px 40px rgba(0,0,0,0.55);
+ z-index: 150;
+ }
+ .ns-nav.open { display: flex !important; }
+ .ns-nav a, .ns-nav button {
+ padding: 12px 22px !important;
+ border-bottom: 1px solid rgba(245,241,232,0.06);
+ width: 100%; text-align: left;
+ }
+ .ns-nav a:last-child, .ns-nav button:last-child { border-bottom: 0; }
+ .ns-nav a:hover, .ns-nav button:hover { background: rgba(245,241,232,0.06) !important; color: #ffffff !important; }
+ @media (max-width: 480px) { .ns-nav { right: 12px; min-width: 200px; } }
+ .ns-header .mobile-toggle { display: flex !important; align-items: center; justify-content: center; width: 36px; height: 36px; background: transparent; border: 0; cursor: pointer; padding: 0; gap: 4px; flex-direction: column; }
+ .ns-header .mobile-toggle span { display: block; width: 22px; height: 1.5px; background: #f5f1e8; transition: transform 200ms, opacity 200ms; }
+ .ns-header .mobile-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(5px) rotate(45deg); }
+ .ns-header .mobile-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
+ .ns-header .mobile-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }
+ </style>
+<style id="ns-gold-kill">
+ /* novasuede: kill gold-solid fills, off-white instead. Edge color stays gold. */
+ .new-badge, .new-arrivals-pill,
+ .color-filter-btn.active, .drapery-btn,
+ .sample-btn-chip, .cb-cta {
+ background: #f5f1e8 !important;
+ color: #0a0a0a !important;
+ border-color: #f5f1e8 !important;
+ }
+ .new-badge:hover, .new-arrivals-pill:hover,
+ .color-filter-btn.active:hover, .drapery-btn:hover,
+ .sample-btn-chip:hover, .cb-cta:hover {
+ background: #fff !important;
+ color: #0a0a0a !important;
+ }
+ </style>
+<header class="ns-header">
+ <a href="/" class="ns-brand" aria-label="Home">
+ <span class="ns-dot"></span>
+ <span class="ns-name">silkwallpaper</span>
+ </a>
+ <nav class="ns-nav" id="nsPrimaryNav" aria-label="Primary">
+ <a href="/">Home</a>
+ <a href="/about">About</a>
+ <button type="button" onclick="nsContactOpen()">Contact</button>
+ </nav>
+ <button type="button" class="mobile-toggle" aria-label="Toggle menu" aria-controls="nsPrimaryNav" aria-expanded="false" onclick="nsToggleNav(this)">
+ <span></span><span></span><span></span>
+ </button>
+ </header>
+
+<div class="ns-modal" id="nsContactModal" role="dialog" aria-modal="true" aria-labelledby="nsContactTitle" onclick="if(event.target===this)nsContactClose()">
+ <div class="ns-modal-card">
+ <button type="button" class="close-x" onclick="nsContactClose()" aria-label="Close">×</button>
+ <h3 id="nsContactTitle">How can we help?</h3>
+ <p class="sub">Trade-account replies within one business day.</p>
+ <div class="ns-actions">
+ <a class="ns-action" href="#contact" onclick="nsContactClose()"><span class="ico">📋</span><div class="txt"><b>Get a Quote</b><span>Project specs → priced estimate</span></div></a>
+ <a class="ns-action" href="#contact" onclick="nsContactClose();openModal();"><span class="ico">📦</span><div class="txt"><b>Order a Sample</b><span>Free swatch to your shop</span></div></a>
+ <a class="ns-action" href="mailto:info@silkwallpaper.com?subject=silkwallpaper%20inquiry"><span class="ico">✉️</span><div class="txt"><b>Email Us</b><span>info@silkwallpaper.com</span></div></a>
+ <a class="ns-action" href="tel:888-373-4564"><span class="ico">📞</span><div class="txt"><b>Call Us</b><span>(888) 373-4564 · M–F, 9–5 PT</span></div></a>
+ </div>
+ </div>
+ </div>
+<script>
+ function nsContactOpen() { var m = document.getElementById('nsContactModal'); if (m) m.classList.add('show'); }
+ function nsContactClose() { var m = document.getElementById('nsContactModal'); if (m) m.classList.remove('show'); }
+ function nsToggleNav(btn) {
+ var nav = document.getElementById('nsPrimaryNav');
+ if (!nav) return;
+ var open = nav.classList.toggle('open');
+ btn.setAttribute('aria-expanded', open ? 'true' : 'false');
+ }
+ document.addEventListener('keydown', function(e){ if (e.key === 'Escape') nsContactClose(); });
+</script>
+
+
+
+
+
+
+
+
+
+
+
+<header>
+ <button class="h-link" onclick="dwmOpen('Contact')" aria-label="Contact"><svg class="h-icon" viewBox="0 0 24 24"><path d="M3 7h18M3 12h18M3 17h18"/></svg><span>Contact</span></button>
+ <button class="theme-toggle" id="theme-toggle" aria-label="Theme toggle">☾</button>
+</header>
+
+<section class="cinema" id="hero">
+ <a class="cinema-logo" href="/" aria-label="silkwallpaper home">silkwallpaper</a>
+ <div class="cinema-bg" id="heroBg"></div>
+ <div class="cinema-grid" id="heroGrid" aria-hidden="true"></div>
+ <div class="corner-mark">Silken Sheen</div>
+ <div class="meta-line">Silk · Luxe · Sheen · Jacquard<span class="num" id="heroNum"></span></div>
+ <a class="enter" href="#shop">Enter <svg viewBox="0 0 24 12" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M0 6h22M16 1l6 5-6 5"/></svg></a>
+</section>
+<div class="hero-wordmark-block">
+ <h1 class="hero-wordmark">silkwallpaper</h1>
+ <span class="hero-wordmark-sub" id="heroWordmarkSub"></span>
+</div>
+
+
+<section class="section" id="ideas">
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">New this week</h2>
+ <div class="rail" id="railNewest"></div>
+ </div>
+ <div class="rail-block">
+ <div class="rail-eyebrow">Ideas</div>
+ <h2 class="rail-title">Curated picks</h2>
+ <div class="rail" id="railCurated"></div>
+ </div>
+</section>
+
+<section class="section" id="shop">
+ <div class="section-header">
+ <div>
+ <div class="section-eyebrow">The Collection</div>
+ <h2 class="section-title"><span class="accent" id="totalCount">—</span> Patterns.</h2>
+ </div>
+ <div class="section-meta">Silk · Luxe · Sheen · Jacquard</div>
+ </div>
+
+ <div class="filters" id="facets">
+ <button class="chip active" data-facet="all">All</button>
+ </div>
+
+ <div class="density">
+ <label>Sort</label>
+ <select id="sortSelect" aria-label="Sort products" style="background:transparent;color:inherit;border:1px solid var(--line,rgba(255,255,255,.2));font:inherit;font-size:11px;letter-spacing:.10em;text-transform:uppercase;padding:4px 8px;cursor:pointer;outline:none;margin-right:14px">
+ <option value="newest">Newest</option>
+ <option value="color">Color</option>
+ <option value="style">Style</option>
+ <option value="sku">SKU A→Z</option>
+ <option value="title">Title A→Z</option>
+ <option value="price-asc">Price ↑</option>
+ <option value="price-desc">Price ↓</option>
+ </select>
+ <label>Grid</label>
+ <input type="range" id="densitySlider" min="4" max="12" step="1" value="6" aria-label="Grid columns">
+ <span class="ct" id="densityLabel">6 cols</span>
+ <div class="search">
+ <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5.5-5.5"/></svg>
+ <input type="text" id="searchInput" placeholder="Search by pattern, color…" autocomplete="off">
+ </div>
+ </div>
+
+ <div class="stat-line" id="statLine">Loading…</div>
+ <div class="grid" id="grid"></div>
+ <div class="loading" id="loading">Loading more…</div>
+ <div class="sentinel" id="sentinel"></div>
+</section>
+
+<footer>
+ <div class="footer-grid">
+ <div>
+ <div class="footer-brand">silkwallpaper</div>
+ <p class="footer-text">silkwallpaper — real samples ship free.</p>
+ <p class="footer-text" style="margin-top:14px;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;font-weight:600;color:var(--paper);opacity:0.7"><a href="/about" style="color:inherit;text-decoration:none;border-bottom:1px solid var(--accent)">About →</a></p>
+ </div>
+ <div class="footer-col">
+ <h4>Aesthetic</h4>
+ <div id="footerFacets"></div>
+ </div>
+ <div class="footer-col">
+ <h4>Trade</h4>
+ <button onclick="dwmOpen('Inquiry')">Project Inquiry</button>
+ <button onclick="dwmOpen('Sample')">Request Sample</button>
+ <button onclick="dwmOpen('Contact')">Contact</button>
+ </div>
+
+ <div class="footer-col">
+ <h4>Social</h4>
+ <a class="fb-link" href="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings" target="_blank" rel="noopener">Facebook</a>
+ </div>
+ </div>
+ <div class="footer-bottom">
+ <span>silkwallpaper.com · silkwallpaper</span>
+ <span><a href="mailto:info@silkwallpaper.com" style="color:var(--paper);text-decoration:none;border-bottom:1px solid var(--accent)">info@silkwallpaper.com</a></span>
+ <span id="footerStat"></span>
+ </div>
+</footer>
+
+<script>
+// mini-constellation — 43 niche-stars, current site highlighted in gold
+(function(){
+ const SLUG = "silkwallpaper";
+ const NICHES = [
+ ['silkwallpaper','Silk','mat'],['silkwallcoverings','Silk W/C','mat'],['linenwallpaper','Linen','mat'],
+ ['jutewallpaper','Jute','mat'],['raffiawallcoverings','Raffia W/C','mat'],['raffiawalls','Raffia Walls','mat'],
+ ['corkwallcovering','Cork','mat'],['fabricwallpaper','Fabric','mat'],['textilewallpaper','Textile','mat'],
+ ['metallicwallpaper','Metallic','mat'],['silverleafwallpaper','Silver Leaf','mat'],['vinylwallpaper','Vinyl','mat'],
+ ['micawallpaper','Mica','mat'],['madagascarwallpaper','Madagascar','mat'],['mylarwallpaper','Mylar','mat'],
+ ['suedewallpaper','Suede','mat'],
+ ['1800swallpaper','1800s','dec'],['1890swallpaper','1890s','dec'],['1900swallpaper','1900s','dec'],
+ ['1920swallpaper','1920s','dec'],['1930swallpaper','1930s','dec'],['1940swallpaper','1940s','dec'],
+ ['1950swallpaper','1950s','dec'],['1960swallpaper','1960s','dec'],['1970swallpaper','1970s','dec'],
+ ['1980swallpaper','1980s','dec'],['retrowalls','Retro','dec'],['wallpapersback','W. Back','dec'],
+ ['agedwallpaper','Aged','cra'],['handcraftedwallpaper','Handcrafted','cra'],['museumwallpaper','Museum','cra'],
+ ['restorationwallpaper','Restoration','cra'],['pastelwallpaper','Pastel','cra'],['glitterwalls','Glitter','cra'],
+ ['greenwallcoverings','Green','cra'],['naturalwallcoverings','Natural','cra'],['saloonwallpaper','Saloon','cra'],
+ ['contractwallpaper','Contract','use'],['hotelwallcoverings','Hotel','use'],['hospitalitywallpaper','Hospitality','use'],
+ ['healthcarewallpaper','Healthcare','use'],['restaurantwallpaper','Restaurant','use'],['architecturalwallcoverings','Architectural','use']
+ ];
+ const COLOR = { mat:'#c9b687', dec:'#b86a4a', cra:'#6b8e6f', use:'#4a6b8e' };
+ const W = 1080, H = 200, BANDS = ['mat','dec','cra','use'];
+ const groups = { mat:[], dec:[], cra:[], use:[] };
+ for (const n of NICHES) groups[n[2]].push(n);
+ const svg = document.getElementById('miniConst');
+ if (!svg) return;
+ const bandW = W / BANDS.length;
+ let nodes = '';
+ BANDS.forEach((band, bi) => {
+ const list = groups[band];
+ const cx = bi * bandW + bandW / 2;
+ list.forEach((n, i) => {
+ const t = list.length === 1 ? 0.5 : i / (list.length - 1);
+ // deterministic jitter per slug
+ let h = 0; for (let k = 0; k < n[0].length; k++) h = (h * 31 + n[0].charCodeAt(k)) | 0;
+ const jx = ((h & 0xff) / 255 - 0.5) * (bandW * 0.5);
+ const jy = (((h >> 8) & 0xff) / 255 - 0.5) * 6; // tightened from 14 → 6 to prevent same-band collisions (16 mat nodes / 144px = 9px spacing)
+ const x = cx + jx, y = 28 + t * (H - 56) + jy;
+ const isCurrent = n[0] === SLUG;
+ const r = isCurrent ? 8 : 4;
+ const op = isCurrent ? 1 : 0.55;
+ nodes += `<g class="mc-node" data-slug="${n[0]}" data-label="${n[1]}" style="cursor:pointer">
+ <circle cx="${x}" cy="${y}" r="${r}" fill="url(#dna-${band})" fill-opacity="${op}" stroke="${COLOR[band]}" stroke-opacity="${isCurrent?1:0.7}" stroke-width="${isCurrent?2:1}">
+ <title>${n[1]} — ${n[0]}.com</title>
+ </circle>
+ ${isCurrent ? `<circle cx="${x}" cy="${y}" r="${r+5}" fill="none" stroke="${COLOR[band]}" stroke-opacity="0.4" stroke-width="1"><animate attributeName="r" values="${r+5};${r+12};${r+5}" dur="2.4s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.4;0;0.4" dur="2.4s" repeatCount="indefinite"/></circle>` : ''}
+ </g>`;
+ });
+ });
+ // append nodes after defs
+ svg.insertAdjacentHTML('beforeend', nodes);
+ svg.querySelectorAll('.mc-node').forEach(n => {
+ n.addEventListener('mouseenter', () => n.querySelector('circle').setAttribute('fill-opacity', '1'));
+ n.addEventListener('mouseleave', e => {
+ const isCurrent = n.dataset.slug === SLUG;
+ n.querySelector('circle').setAttribute('fill-opacity', isCurrent ? '1' : '0.55');
+ });
+ n.addEventListener('click', () => window.open('https://' + n.dataset.slug + '.com', '_blank'));
+ });
+})();
+</script>
+
+<script>
+const state = { q:'', facet:'all', page:1, pages:1, total:0, loading:false, exhausted:false };
+const LABELS = {"all":"All"};
+
+function escAttr(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
+// Image-URL allowlist: only DW Shopify CDN + designerwallcoverings.com (defends against XSS via crafted products.json)
+function safeImg(u) { return /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(u || '') ? u : '/hero-bg.jpg'; }
+function cardHTML(p) {
+ const eager = state.page === 1;
+ return '<img loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+ + '<div class="overlay">'
+ + '<div class="pat">' + escAttr(p.pattern_name || p.title) + '</div>'
+ + '<div class="ven">' + escAttr((p.vendor || '').replace(/-/g, ' ')) + '</div>'
+ + '<div class="actions">'
+ + '<button class="sample-btn" onclick="event.stopPropagation();dwmOpen(\'Sample\',' + JSON.stringify({sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}).replace(/"/g,'"') + ')">Sample</button>'
+ + '</div></div>';
+}
+
+async function loadFacets() {
+ let f;
+ try {
+ const r = await fetch('/api/facets');
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ f = await r.json();
+ } catch (e) { console.error('loadFacets failed:', e); return; }
+ const el = document.getElementById('facets');
+ for (const [k, v] of Object.entries(f.aesthetics).sort((a,b) => b[1] - a[1])) {
+ const b = document.createElement('button');
+ b.className = 'chip'; b.dataset.facet = k;
+ b.innerHTML = (LABELS[k] || k) + ' <span style="opacity:.55;font-weight:500;margin-left:4px">' + v + '</span>';
+ el.appendChild(b);
+ }
+ el.addEventListener('click', e => {
+ if (e.target.tagName !== 'BUTTON') return;
+ document.querySelectorAll('#facets button').forEach(b => b.classList.remove('active'));
+ e.target.classList.add('active');
+ state.facet = e.target.dataset.facet;
+ resetGrid();
+ });
+ document.getElementById('totalCount').textContent = f.total;
+ document.getElementById('footerStat').textContent = f.total + ' patterns · live archive';
+ // Footer aesthetic links
+ const footerEl = document.getElementById('footerFacets');
+ for (const [k] of Object.entries(f.aesthetics)) {
+ const b = document.createElement('button');
+ b.textContent = LABELS[k] || k;
+ b.onclick = () => { state.facet = k; resetGrid(); document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.facet === k)); document.getElementById('shop').scrollIntoView({behavior:'smooth'}); };
+ footerEl.appendChild(b);
+ }
+}
+
+function resetGrid() {
+ state.page = 1; state.exhausted = false;
+ document.getElementById('grid').innerHTML = '';
+ document.getElementById('loading').textContent = 'Loading…';
+ loadGridPage();
+}
+
+async function loadGridPage() {
+ if (state.loading || state.exhausted) return;
+ state.loading = true;
+ const params = new URLSearchParams({ page:state.page, limit:24 });
+ if (state.q) params.set('q', state.q);
+ if (state.facet !== 'all') params.set('aesthetic', state.facet);
+ let data;
+ try {
+ const r = await fetch('/api/products?' + params);
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ data = await r.json();
+ } catch (e) {
+ console.error('loadGridPage failed:', e);
+ document.getElementById('loading').textContent = '— offline — refresh to retry —';
+ state.loading = false;
+ return;
+ }
+ state.total = data.total; state.pages = data.pages;
+
+ if (state.page === 1) {
+ document.getElementById('statLine').textContent = data.total + ' patterns · ' + (state.facet === 'all' ? 'all aesthetics' : (LABELS[state.facet] || state.facet)) + (state.q ? ' · matching "' + state.q + '"' : '');
+ }
+ const grid = document.getElementById('grid');
+ for (const p of data.items) {
+ const a = document.createElement('a');
+ a.className = 'card'; a.href = '#'; a.onclick = (e) => { e.preventDefault(); dwmOpen('Sample',{sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}); };
+ a.innerHTML = cardHTML(p);
+ grid.appendChild(a);
+ }
+ if (state.page >= state.pages || data.items.length === 0) {
+ state.exhausted = true;
+ document.getElementById('loading').textContent = state.total > 0 ? '— end of archive · ' + state.total + ' patterns —' : '';
+ } else state.page++;
+ state.loading = false;
+}
+
+const io = new IntersectionObserver(es => { for (const e of es) if (e.isIntersecting) loadGridPage(); }, { rootMargin:'600px 0px' });
+io.observe(document.getElementById('sentinel'));
+
+document.getElementById('searchInput').addEventListener('input', e => {
+ state.q = e.target.value.trim();
+ clearTimeout(window._t);
+ window._t = setTimeout(resetGrid, 220);
+});
+
+// Density slider
+const slider = document.getElementById('densitySlider');
+const dlabel = document.getElementById('densityLabel');
+function setDensity(n) {
+ document.documentElement.style.setProperty('--cols', n);
+ dlabel.textContent = n + ' cols';
+ try { localStorage.setItem('silk_theme_density', n); } catch(e){}
+}
+slider.addEventListener('input', e => setDensity(parseInt(e.target.value)));
+const savedDensity = parseInt(localStorage.getItem('silk_theme_density') || '6');
+if (savedDensity >= 4 && savedDensity <= 12) { slider.value = savedDensity; setDensity(savedDensity); }
+
+// Theme toggle
+const tb = document.getElementById('theme-toggle');
+function setTheme(t){ document.documentElement.dataset.theme = t; try { localStorage.setItem('silk_theme', t); } catch(e){} tb.textContent = t === 'dark' ? '☾' : '☀'; }
+setTheme(document.documentElement.dataset.theme || 'dark');
+tb.addEventListener('click', () => setTheme(document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'));
+
+loadFacets();
+loadGridPage();
+</script>
+<!-- ============================================================
+ DW UNIVERSAL CONTACT MODULE — fashion-house UX
+ Inject before </body> in every DW-family sister site.
+ Self-contained: CSS + 4 modals + JS submission handlers.
+ Uses host site's CSS vars (--bg, --fg, --gold, --rule).
+ ============================================================ -->
+<style>
+ /* === MODAL BASE === */
+ .dwm{position:fixed;inset:0;background:rgba(0,0,0,0.78);display:none;align-items:center;justify-content:center;z-index:9999;padding:24px;overflow-y:auto}
+ .dwm.open{display:flex}
+ .dwm-box{background:var(--bg);border:1px solid var(--rule);max-width:520px;width:100%;padding:36px 32px;position:relative;max-height:90vh;overflow-y:auto;animation:dwm-in 0.25s ease}
+ @keyframes dwm-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+ .dwm-box h3{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-weight:400;font-size:32px;line-height:1.05;margin-bottom:6px;color:var(--fg);letter-spacing:-0.01em}
+ .dwm-box .sub{color:var(--muted);font-size:12px;letter-spacing:0.16em;text-transform:uppercase;margin-bottom:24px;font-weight:500}
+ .dwm-close{position:absolute;top:18px;right:18px;background:transparent;border:0;color:var(--muted);font-size:24px;cursor:pointer;width:32px;height:32px;line-height:1;padding:0}
+ .dwm-close:hover{color:var(--gold)}
+ .dwm-preview{display:flex;gap:14px;align-items:center;padding:12px;background:var(--bg-soft);margin-bottom:18px}
+ .dwm-preview img{width:64px;height:64px;object-fit:cover}
+ .dwm-preview .pn{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-size:15px;line-height:1.2;color:var(--fg)}
+ .dwm-preview .pc{font-size:10px;letter-spacing:0.14em;text-transform:uppercase;color:var(--muted);margin-top:4px}
+ .dwm-field{margin-bottom:14px}
+ .dwm-field label{display:block;font-size:10px;letter-spacing:0.18em;text-transform:uppercase;color:var(--muted);margin-bottom:6px;font-weight:500}
+ .dwm-field input,.dwm-field textarea,.dwm-field select{width:100%;border:0;border-bottom:1px solid var(--rule);background:transparent;padding:8px 4px;font-size:14px;font-family:inherit;color:var(--fg);outline:none;transition:border-color 0.2s}
+ .dwm-field input:focus,.dwm-field textarea:focus{border-bottom-color:var(--gold)}
+ .dwm-field textarea{min-height:60px;resize:vertical}
+ .dwm-submit{background:transparent;color:var(--muted, #c9b89e);border:1px solid var(--muted, #c9b89e);padding:14px 28px;font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:700;cursor:pointer;width:100%;margin-top:8px;transition:background 0.15s, color 0.15s}
+ .dwm-submit:hover{background:var(--muted, #c9b89e);color:var(--bg, #000)}
+
+ .dwm-submit:disabled{opacity:0.5;cursor:default}
+ .dwm-status{margin-top:14px;font-size:11px;letter-spacing:0.16em;text-transform:uppercase;text-align:center;display:none}
+ .dwm-status.ok{color:var(--sage);display:block}
+ .dwm-status.err{color:#e08070;display:block}
+ /* contact-options */
+ .dwm-options{display:flex;flex-direction:column;gap:10px}
+ .dwm-option{display:flex;align-items:center;gap:14px;padding:18px 20px;background:transparent;border:1px solid var(--rule);color:var(--fg);font-family:inherit;font-size:11px;letter-spacing:0.20em;text-transform:uppercase;font-weight:600;cursor:pointer;text-decoration:none;transition:all 0.2s;text-align:left;width:100%}
+ .dwm-option:hover{border-color:var(--gold);color:var(--gold)}
+ .dwm-option svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:1.5;flex-shrink:0}
+ .dwm-option .lbl{flex:1;display:block}
+ .dwm-option .val{display:block;font-size:10px;letter-spacing:0.14em;text-transform:none;font-weight:500;opacity:0.65;margin-top:4px}
+
+/* graphics-loop pass 5: serif hero typography + nav legibility */
+.cinema h1, h1.headline { font-family: 'Cormorant Garamond', Georgia, serif; font-weight: 500; letter-spacing: -0.01em }
+.h-link { font-size: 13px; letter-spacing: 0.06em }
+
+/* graphics-loop pass 11: Ideas rails */
+#ideas { padding: 56px 24px 24px; max-width:1600px; margin:0 auto }
+#ideas .rail-block { margin-bottom:48px }
+#ideas .rail-eyebrow { font-size:11px; font-weight:700; letter-spacing:0.32em; text-transform:uppercase; color:var(--muted, #888); margin-bottom:8px }
+#ideas .rail-title { font-size:clamp(22px,2.6vw,32px); font-weight:500; letter-spacing:-0.01em; margin:0 0 18px; color:var(--ink,#fff) }
+#ideas .rail { display:flex; gap:14px; overflow-x:auto; scroll-snap-type:x mandatory; padding-bottom:8px; scrollbar-width:none }
+#ideas .rail::-webkit-scrollbar { display:none }
+#ideas .rail-card { flex:0 0 240px; scroll-snap-align:start; cursor:pointer; transition:transform 0.2s }
+#ideas .rail-card:hover { transform:translateY(-3px) }
+#ideas .rail-card img { width:100%; aspect-ratio:1; object-fit:cover; display:block; background:var(--card,#222) }
+#ideas .rail-card .rc-title { font-size:13px; font-weight:500; color:var(--paper,#fff); padding:8px 0 2px; line-height:1.3 }
+#ideas .rail-card .rc-meta { font-size:11px; color:var(--muted,#888); letter-spacing:0.06em }
+@media (max-width:720px) { #ideas .rail-card { flex:0 0 160px } }
+</style>
+
+<!-- Contact options modal -->
+<div class="dwm" id="dwmContact" onclick="if(event.target===this)dwmClose('Contact')">
+ <div class="dwm-box" style="max-width:440px">
+ <button class="dwm-close" onclick="dwmClose('Contact')" aria-label="Close">×</button>
+ <h3>Contact</h3>
+ <p class="sub" id="dwmContactSub">Three ways to reach us</p>
+ <div class="dwm-options">
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Inquiry')">
+ <svg viewBox="0 0 24 24"><path d="M4 4h16v12H7l-3 3z"/></svg>
+ <span class="lbl">Send an Inquiry<span class="val">Project name · scope · all the details</span></span>
+ </button>
+ <button class="dwm-option" type="button" onclick="dwmClose('Contact');dwmOpen('Sample')">
+ <svg viewBox="0 0 24 24"><rect x="4" y="6" width="16" height="14"/><path d="M4 10h16M9 6V3h6v3"/></svg>
+ <span class="lbl">Request a Sample<span class="val">Memo sample · ships free · 3–5 business days</span></span>
+ </button>
+ <a class="dwm-option" id="dwmContactEmailLink" href="mailto:info@silkwallpaper.com">
+ <svg viewBox="0 0 24 24"><path d="M3 6h18v12H3z"/><path d="M3 6l9 7 9-7"/></svg>
+ <span class="lbl">Email Us<span class="val" id="dwmContactEmailLabel">info@silkwallpaper.com</span></span>
+ </a>
+ </div>
+ </div>
+</div>
+
+<!-- Inquiry modal -->
+<div class="dwm" id="dwmInquiry" onclick="if(event.target===this)dwmClose('Inquiry')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Inquiry')" aria-label="Close">×</button>
+ <h3>Project Inquiry</h3>
+ <p class="sub">We'll respond within one business day</p>
+ <form id="dwmInquiryForm" onsubmit="return dwmSubmit(event,'inquiry')">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Phone</label><input type="tel" name="phone"></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Project Name</label><input type="text" name="projectName" required></div>
+ <div class="dwm-field"><label>Project Scope</label><textarea name="projectScope" placeholder="Square footage · room count · timeline" required></textarea></div>
+ <div class="dwm-field"><label>Additional Information</label><textarea name="message" placeholder="Patterns of interest, finish preferences, budget"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Inquiry</button>
+ <div class="dwm-status" id="dwmInquiryStatus"></div>
+ </form>
+ </div>
+</div>
+
+<!-- Sample-request modal -->
+<div class="dwm" id="dwmSample" onclick="if(event.target===this)dwmClose('Sample')">
+ <div class="dwm-box">
+ <button class="dwm-close" onclick="dwmClose('Sample')" aria-label="Close">×</button>
+ <h3>Request a Sample</h3>
+ <p class="sub">Memo sample · ships free · 3–5 business days</p>
+ <div class="dwm-preview" id="dwmSamplePreview" style="display:none">
+ <img id="dwmSampleImg" src="" alt="">
+ <div><div class="pn" id="dwmSampleName"></div><div class="pc" id="dwmSampleSku"></div></div>
+ </div>
+ <form id="dwmSampleForm" onsubmit="return dwmSubmit(event,'sample')">
+ <input type="hidden" name="sku" id="dwmSampleSkuInput">
+ <input type="hidden" name="title" id="dwmSampleTitleInput">
+ <input type="hidden" name="image_url" id="dwmSampleImageInput">
+ <div class="dwm-field"><label>Name</label><input type="text" name="name" required></div>
+ <div class="dwm-field"><label>Email</label><input type="email" name="email" required></div>
+ <div class="dwm-field"><label>Company / Trade</label><input type="text" name="company"></div>
+ <div class="dwm-field"><label>Shipping Address</label><input type="text" name="address" placeholder="Street" required></div>
+ <div class="dwm-field" style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:8px">
+ <input type="text" name="city" placeholder="City" required>
+ <input type="text" name="state" placeholder="State" required>
+ <input type="text" name="zip" placeholder="ZIP" required>
+ </div>
+ <div class="dwm-field"><label>Notes (optional)</label><textarea name="message" placeholder="Project, sq.ft., timing"></textarea></div>
+ <button type="submit" class="dwm-submit">Send Sample Request</button>
+ <div class="dwm-status" id="dwmSampleStatus"></div>
+ </form>
+ </div>
+</div>
+
+<script>
+ // Universal modal control
+ function dwmOpen(name, opts){
+ document.getElementById('dwm' + name).classList.add('open');
+ document.body.style.overflow = 'hidden';
+ try { if (typeof gtag === 'function') gtag('event', 'modal_open', { modal_name: name, sku: (opts && opts.sku) || '' }); } catch(_){}
+ if (name === 'Sample' && opts) {
+ document.getElementById('dwmSamplePreview').style.display = 'flex';
+ document.getElementById('dwmSampleImg').src = opts.image_url || '';
+ document.getElementById('dwmSampleName').textContent = opts.title || '';
+ document.getElementById('dwmSampleSku').textContent = opts.sku || '';
+ document.getElementById('dwmSampleSkuInput').value = opts.sku || '';
+ document.getElementById('dwmSampleTitleInput').value = opts.title || '';
+ document.getElementById('dwmSampleImageInput').value = opts.image_url || '';
+ } else if (name === 'Sample') {
+ document.getElementById('dwmSamplePreview').style.display = 'none';
+ document.getElementById('dwmSampleSkuInput').value = '';
+ document.getElementById('dwmSampleTitleInput').value = '';
+ document.getElementById('dwmSampleImageInput').value = '';
+ }
+ }
+ function dwmClose(name){ document.getElementById('dwm'+name).classList.remove('open'); document.body.style.overflow=''; }
+
+ // Universal form submit — POSTs to /api/send-{kind}
+ async function dwmSubmit(e, kind) {
+ e.preventDefault();
+ const form = e.target;
+ const data = Object.fromEntries(new FormData(form).entries());
+ const btn = form.querySelector('.dwm-submit');
+ const status = form.querySelector('.dwm-status');
+ btn.disabled = true; const orig = btn.textContent; btn.textContent = 'Sending…';
+ status.className = 'dwm-status';
+ try {
+ const r = await fetch('/api/send-' + kind, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data) });
+ if (!r.ok) throw new Error('HTTP '+r.status);
+ const j = await r.json();
+ try {
+ if (typeof gtag === 'function') {
+ if (kind === 'sample') gtag('event', 'memo_request', { sku: data.sku || '', title: data.title || '' });
+ else gtag('event', 'lead', { form_type: kind });
+ }
+ } catch(_){}
+ status.textContent = kind === 'sample' ? 'Sample request sent — ships within 3–5 business days' : 'Inquiry sent — we respond within 1 business day';
+ status.className = 'dwm-status ok';
+ btn.textContent = 'Sent ✓';
+ setTimeout(() => { dwmClose(kind === 'sample' ? 'Sample' : 'Inquiry'); btn.disabled = false; btn.textContent = orig; status.className = 'dwm-status'; form.reset(); }, 2800);
+ } catch (err) {
+ status.textContent = 'Send failed — please email info@designerwallcoverings.com';
+ status.className = 'dwm-status err';
+ btn.disabled = false; btn.textContent = orig;
+ }
+ return false;
+ }
+
+ // Wire up "Memo samples ship free" badge in hero to open sample modal directly
+ document.querySelectorAll('.hero .badge, [data-open-sample]').forEach(el => {
+ el.style.cursor = 'pointer';
+ el.addEventListener('click', () => dwmOpen('Sample'));
+ });
+
+ // Wire nav "Trade" link to inquiry modal
+ document.querySelectorAll('nav a[href*="designerwallcoverings.com"]').forEach(a => {
+ a.removeAttribute('target');
+ a.removeAttribute('href');
+ a.style.cursor = 'pointer';
+ a.addEventListener('click', e => { e.preventDefault(); dwmOpen('Contact'); });
+ });
+
+ // Override product card clicks — open Sample modal pre-filled instead of redirecting to DW
+ document.addEventListener('click', e => {
+ const card = e.target.closest('.card, .rail-card');
+ if (!card) return;
+ if (card.tagName === 'A' && (card.getAttribute('href') || '').startsWith('/sample/')) {
+ e.preventDefault();
+ const img = card.querySelector('img');
+ const pat = card.querySelector('.pat');
+ const sku = card.getAttribute('href').replace('/sample/', '');
+ dwmOpen('Sample', {
+ sku,
+ title: pat ? pat.textContent.trim() : '',
+ image_url: img ? img.src : ''
+ });
+ }
+ }, true);
+// DW STANDARD: hero pulls from products.json[0].image_url so dw-collections-viewer
+// drag-drop / hero-pin updates the live front page on next deploy.
+// Always upgrades to the largest Shopify variant (strips _NxN size suffix) so
+// the full-page hero stays sharp.
+// DW STANDARD: hero rotates through top-20 NEWEST products on each visit
+// (novasuede.com pattern). Fetches /api/products?limit=100, sorts by created_at desc,
+// takes the freshest 20, picks one per visit via localStorage counter.
+// Falls back to static /hero-bg.jpg on any error.
+(async function rotateHero() {
+ try {
+ const r = await fetch('/api/products?limit=100');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || [])
+ .filter(p => p && p.image_url && /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(p.image_url))
+ .sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')))
+ .slice(0, 20);
+ if (!items.length) return;
+ let visits = 0;
+ try {
+ visits = parseInt(localStorage.getItem('hero_visits') || '0', 10) || 0;
+ localStorage.setItem('hero_visits', String(visits + 1));
+ } catch(e) {}
+ const pick = items[visits % items.length];
+ // Strip _NxN size suffix to get the largest CDN variant
+ const url = pick.image_url.replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ const bg = document.getElementById('heroBg') || document.getElementById('cinemaBg');
+ if (bg) bg.style.backgroundImage = "url('" + url + "')";
+ } catch (e) {}
+})();
+
+
+// DW STANDARD sort-select wire-up (added by fleet patcher)
+(function(){
+ const sortSel = document.getElementById('sortSelect');
+ if (!sortSel) return;
+ const KEY = location.hostname.replace(/\./g, '_') + '_sort';
+ const saved = localStorage.getItem(KEY);
+ if (saved) { try { sortSel.value = saved; } catch(e){} }
+ if (typeof state !== 'undefined') state.sort = sortSel.value;
+ sortSel.addEventListener('change', e => {
+ const v = e.target.value;
+ try { localStorage.setItem(KEY, v); } catch(e){}
+ if (typeof state !== 'undefined') state.sort = v;
+ if (typeof resetGrid === 'function') resetGrid();
+ else location.reload();
+ });
+})();
+
+// graphics-loop pass 11: hydrate Ideas rails from /api/products
+(async function hydrateIdeas() {
+ try {
+ const r = await fetch('/api/products?limit=80');
+ if (!r.ok) return;
+ const d = await r.json();
+ const items = (d.items || []).filter(p => p && p.image_url);
+ if (!items.length) return;
+ const sorted = [...items].sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')));
+ const newest = sorted.slice(0, 12);
+ // Curated = deterministic offset slice (every 5th item starting at idx 3) for variety
+ const curated = items.filter((_, i) => i % 5 === 3).slice(0, 12);
+ function card(p) {
+ const url = (p.image_url || '').replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+ return '<a class="rail-card" href="/sample/' + encodeURIComponent(p.handle || p.sku) + '">' +
+ '<img src="' + url + '" alt="' + (p.title || '').replace(/"/g, '"') + '" loading="lazy">' +
+ '<div class="rc-title">' + ((p.title || '').split('|')[0] || '').trim() + '</div>' +
+ '<div class="rc-meta">' + (p.vendor || '') + '</div>' +
+ '</a>';
+ }
+ const r1 = document.getElementById('railNewest');
+ const r2 = document.getElementById('railCurated');
+ if (r1) r1.innerHTML = newest.map(card).join('');
+ if (r2) r2.innerHTML = curated.map(card).join('');
+ } catch(e) {}
+})();
+</script>
+
+<script>
+/* R4 — Alternating hero modes (huge / four-square). Server-side default is huge (HTML attr).
+ Client-only flip starts on visit 2. Both modes share the same .cinema min-height (CLS-free).
+ Crawlers without JS always see huge. */
+(function(){
+ try {
+ var k='dw-hero-mode';
+ var prev=parseInt(localStorage.getItem(k)||'0',10);
+ var next=(prev+1)%2;
+ localStorage.setItem(k,String(next));
+ if (prev>0) {
+ var mode = next===0 ? 'huge' : 'four-square';
+ document.documentElement.dataset.heroMode = mode;
+ if (mode === 'four-square') {
+ fetch('/api/products?limit=4&page=1').then(r=>r.ok?r.json():null).then(j=>{
+ if (!j || !j.items || j.items.length < 4) {
+ document.documentElement.dataset.heroMode = 'huge';
+ console.warn('dw-hero-stale: insufficient items for four-square, falling back to huge');
+ return;
+ }
+ var grid = document.getElementById('heroGrid');
+ if (!grid) return;
+ j.items.slice(0,4).forEach(function(it){
+ var d = document.createElement('div');
+ d.className = 'cell';
+ var img = (it.image_url || it.image || '/hero-bg.jpg');
+ d.style.backgroundImage = "url('" + img + "')";
+ grid.appendChild(d);
+ });
+ }).catch(function(){
+ document.documentElement.dataset.heroMode = 'huge';
+ });
+ }
+ }
+ } catch(e) { /* no-op: keep server default */ }
+})();
+</script>
+</body>
+</html>
diff --git a/public/sourcing.html b/public/sourcing.html
new file mode 100644
index 0000000..6379361
--- /dev/null
+++ b/public/sourcing.html
@@ -0,0 +1,69 @@
+<!doctype html>
+<html lang="en"><head>
+<!-- editorial-gen v1 -->
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>How we source — silkwallpaper.com</title>
+<meta name="description" content="How we source — SILK — IN BLACK.">
+<link rel="canonical" href="https://silkwallpaper.com/sourcing">
+<style>
+*{margin:0;padding:0;box-sizing:border-box}
+body{font-family:Helvetica Neue;background:#FFF;color:#000;line-height:1.65;padding:60px 24px;min-height:100vh}
+.wrap{max-width:720px;margin:0 auto}
+.eyebrow{font-size:10px;letter-spacing:0.22em;text-transform:uppercase;color:#000;margin-bottom:18px}
+h1{font-family:'Helvetica Neue Bold', sans-serif;font-style:italic;font-size:48px;line-height:1.1;letter-spacing:-0.02em;color:#000;margin-bottom:32px}
+h2{font-family:'Helvetica Neue Bold', sans-serif;font-size:22px;color:#000;margin:36px 0 12px;font-weight:500}
+p{margin-bottom:16px;color:#3a322c}
+ul{margin:12px 0 20px 18px}
+li{margin-bottom:8px;color:#3a322c}
+strong{color:#000}
+a{color:#000;text-decoration:underline;text-underline-offset:3px}
+nav.foot{margin-top:56px;padding-top:24px;border-top:1px solid rgba(0,0,0,0.10);font-size:11px;letter-spacing:0.18em;text-transform:uppercase}
+nav.foot a{margin-right:22px;text-decoration:none}
+</style>
+</head><body>
+<div class="wrap">
+<div class="eyebrow">Editorial · sourcing · Atelier Couture</div>
+<h1>How we source</h1>
+<p>We do not chase trends. We do not compromise. Our work begins with the raw material—silk—selected from mills that understand the weight of their craft. Each decision is made with the precision of a surgeon and the rigor of a sculptor, shaping the invisible into the visible.</p>
+
+<h2>Mills</h2>
+
+<p>Our partnerships are not forged lightly. We visit mills in regions where tradition and innovation intersect, seeking those who produce silk with a singular focus on quality. These mills do not sell to everyone. They do not dilute their output. Their looms are not idle, their dyes are not diluted. We work with those who have mastered the balance between artistry and consistency, ensuring that every thread meets our exacting standards.</p>
+
+<p>We do not accept anything that cannot be traced back to its origin. Every roll we consider must be accompanied by documentation that confirms its source, its weave, and its treatment. This is not bureaucracy. It is the foundation of our curation. We reject the opaque. We demand transparency.</p>
+
+<h2>Trade-Only Catalogs</h2>
+
+<p>Our relationships with suppliers are exclusive. We do not peruse public-facing catalogs. We do not solicit from those who sell to the masses. Our access is limited to trade-only resources, where the language is technical, the samples are unvarnished, and the expectations are high. These catalogs are not for decoration. They are for discernment.</p>
+
+<p>We scrutinize every entry. We do not accept what cannot be verified. We do not entertain what cannot be tested. The samples we receive are not for display. They are for evaluation. We do not settle for what is convenient. We demand what is necessary.</p>
+
+<h2>Criteria for Inclusion</h2>
+
+<p>Our criteria are not arbitrary. They are absolute. We assess every sample against three pillars: substrate, repeat, and washfastness. Each must meet our specifications, or it is discarded.</p>
+
+<ul>
+<li><strong>Substrate:</strong> We require silk that is pure, unadulterated. Any deviation from the integrity of the fiber is unacceptable. We do not accept anything that cannot withstand the rigors of time and use.</li>
+<li><strong>Repeat:</strong> We measure repeat not in inches or centimeters, but in precision. A pattern that does not align with its own logic is irrelevant. We reject anything that cannot be repeated without compromise.</li>
+<li><strong>Washfastness:</strong> We do not accept anything that cannot endure the test of water. Our standards are not theoretical. They are practical. We demand that color remains unmoved, that the fabric remains unyielding.</li>
+</ul>
+
+<p>These are not preferences. They are requirements. We do not entertain what does not meet these benchmarks. We do not entertain anything that cannot be proven.</p>
+
+<h2>What Gets Cut</h2>
+
+<p>We do not collect what cannot be justified. We do not preserve what cannot be defended. Anything that fails our criteria is discarded without hesitation. We do not waste space on the indecisive. We do not waste time on the irrelevant.</p>
+
+<p>Our cuts are not symbolic. They are definitive. We do not accept anything that cannot be replicated, that cannot be trusted, that cannot be relied upon. We do not accept anything that cannot be proven to meet our standards. We do not accept anything that cannot be justified by its own existence.</p>
+
+<p>We do not compromise. We do not apologize. We do not accept anything that cannot be held to the light and found wanting. We do not accept anything that cannot be measured, tested, and confirmed. We do not accept anything that
+<nav class="foot">
+<a href="/">Browse</a>
+<a href="/history">History</a>
+<a href="/vocabulary">Vocabulary</a>
+<a href="/sourcing">Sourcing</a>
+<a href="/care">Care</a>
+<a href="/trade">Trade</a>
+<a href="/about">About</a>
+</nav>
+</div></body></html>
\ No newline at end of file
diff --git a/public/trade.html b/public/trade.html
new file mode 100644
index 0000000..4ee68af
--- /dev/null
+++ b/public/trade.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<html lang="en"><head>
+<!-- editorial-gen v1 -->
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Trade program — silkwallpaper.com</title>
+<meta name="description" content="Trade program — SILK — IN BLACK.">
+<link rel="canonical" href="https://silkwallpaper.com/trade">
+<style>
+*{margin:0;padding:0;box-sizing:border-box}
+body{font-family:Helvetica Neue;background:#FFF;color:#000;line-height:1.65;padding:60px 24px;min-height:100vh}
+.wrap{max-width:720px;margin:0 auto}
+.eyebrow{font-size:10px;letter-spacing:0.22em;text-transform:uppercase;color:#000;margin-bottom:18px}
+h1{font-family:'Helvetica Neue Bold', sans-serif;font-style:italic;font-size:48px;line-height:1.1;letter-spacing:-0.02em;color:#000;margin-bottom:32px}
+h2{font-family:'Helvetica Neue Bold', sans-serif;font-size:22px;color:#000;margin:36px 0 12px;font-weight:500}
+p{margin-bottom:16px;color:#3a322c}
+ul{margin:12px 0 20px 18px}
+li{margin-bottom:8px;color:#3a322c}
+strong{color:#000}
+a{color:#000;text-decoration:underline;text-underline-offset:3px}
+nav.foot{margin-top:56px;padding-top:24px;border-top:1px solid rgba(0,0,0,0.10);font-size:11px;letter-spacing:0.18em;text-transform:uppercase}
+nav.foot a{margin-right:22px;text-decoration:none}
+</style>
+</head><body>
+<div class="wrap">
+<div class="eyebrow">Editorial · trade · Atelier Couture</div>
+<h1>Trade program</h1>
+<p>Studio Castel operates under a singular principle: precision in curation, exclusivity in selection, and a refusal to compromise on material integrity. This program is not for the indecisive or the casual. It is for those who demand exactitude, who understand that silk is not a commodity but a medium, and who recognize that the process of selection must be as rigorous as the final result.</p>
+
+<h2>Who Qualifies</h2>
+<p>Access is restricted to designers, architects, and contractors who meet specific criteria. Eligibility is determined by professional reputation, project scope, and a demonstrated commitment to material excellence. Applications are reviewed individually, with no exceptions. Acceptance is not a favor—it is a validation of alignment with Studio Castel’s uncompromising standards.</p>
+
+<h2>Memo Samples</h2>
+<p>Each applicant receives a single memo sample upon approval. This is not a swatch, nor a token. It is a precisely cut fragment of a single weave, accompanied by a statement of intent. The sample is non-returnable and non-transferable. It exists to confirm alignment with the material’s properties, not to suggest versatility. If the sample is deemed unsuitable, access is revoked without further discussion.</p>
+
+<h2>Lead Times</h2>
+<p>Production timelines are fixed and non-negotiable. Orders are fulfilled in sequence, with no acceleration permitted. Lead times are communicated upfront and are based on the complexity of the weave, not on urgency. Delays are not an option. The process is linear, and deviations are not tolerated.</p>
+
+<h2>NET-30 Terms</h2>
+<p>All transactions are conducted under strict NET-30 terms. Payment is due within 30 days of delivery, with no extensions or exceptions. Invoices are issued in duplicate, and reconciliation is expected without dispute. Non-payment results in immediate suspension of access, with no appeal process. The terms are absolute, and compliance is non-negotiable.</p>
+
+<h2>Custom-Print Availability</h2>
+<p>Custom-print requests are considered only if they align with the atelier’s curatorial framework. Submissions are evaluated for originality, technical feasibility, and adherence to the material’s inherent qualities. Rejected proposals are not revisited. Approved prints are produced in limited quantities, with no guarantees of future availability. The process is selective, and the outcome is definitive.</p>
+
+<h2>Designer Wallcoverings Parent Relationship</h2>
+<p>Studio Castel exists within the broader context of Designer Wallcoverings, a family of storefronts established in 1996. This relationship provides access to a legacy of material expertise, a depth of stock that transcends trends, and a commitment to the singular focus of silk. The parent entity does not interfere with curatorial decisions. It exists to support, not to dictate. The independence of Studio Castel is absolute, and its alignment with Designer Wallcoverings is purely functional.</p>
+<nav class="foot">
+<a href="/">Browse</a>
+<a href="/history">History</a>
+<a href="/vocabulary">Vocabulary</a>
+<a href="/sourcing">Sourcing</a>
+<a href="/care">Care</a>
+<a href="/trade">Trade</a>
+<a href="/about">About</a>
+</nav>
+</div></body></html>
\ No newline at end of file
diff --git a/public/vocabulary.html b/public/vocabulary.html
new file mode 100644
index 0000000..356ebf5
--- /dev/null
+++ b/public/vocabulary.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html lang="en"><head>
+<!-- editorial-gen v1 -->
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Design vocabulary — silkwallpaper.com</title>
+<meta name="description" content="Design vocabulary — SILK — IN BLACK.">
+<link rel="canonical" href="https://silkwallpaper.com/vocabulary">
+<style>
+*{margin:0;padding:0;box-sizing:border-box}
+body{font-family:Helvetica Neue;background:#FFF;color:#000;line-height:1.65;padding:60px 24px;min-height:100vh}
+.wrap{max-width:720px;margin:0 auto}
+.eyebrow{font-size:10px;letter-spacing:0.22em;text-transform:uppercase;color:#000;margin-bottom:18px}
+h1{font-family:'Helvetica Neue Bold', sans-serif;font-style:italic;font-size:48px;line-height:1.1;letter-spacing:-0.02em;color:#000;margin-bottom:32px}
+h2{font-family:'Helvetica Neue Bold', sans-serif;font-size:22px;color:#000;margin:36px 0 12px;font-weight:500}
+p{margin-bottom:16px;color:#3a322c}
+ul{margin:12px 0 20px 18px}
+li{margin-bottom:8px;color:#3a322c}
+strong{color:#000}
+a{color:#000;text-decoration:underline;text-underline-offset:3px}
+nav.foot{margin-top:56px;padding-top:24px;border-top:1px solid rgba(0,0,0,0.10);font-size:11px;letter-spacing:0.18em;text-transform:uppercase}
+nav.foot a{margin-right:22px;text-decoration:none}
+</style>
+</head><body>
+<div class="wrap">
+<div class="eyebrow">Editorial · vocabulary · Atelier Couture</div>
+<h1>Design vocabulary</h1>
+<p>Silk is not a material—it is a proposition. It demands precision, rejects compromise, and exists in the space between absence and presence. To understand it is to master its language, a lexicon of technicality that defines its worth. Below are terms that cut through the noise, revealing the anatomy of silk as it is woven, dyed, and laid into the walls of a room.</p>
+
+<h2>Weave Structure</h2>
+<p>The arrangement of warp and weft threads determines the fabric’s rigidity, porosity, and visual clarity. A tight weave creates opacity; a loose one allows light to pass through.</p>
+<ul>
+<li>Weave structure in a silk tapestry defines how the image holds its form—tighter weaves resist distortion, while looser ones permit subtle translucency.</li>
+</ul>
+
+<h2>Loom Finish</h2>
+<p>The final state of the silk after weaving, dictated by tension, friction, and the loom’s mechanical grip. It influences the fabric’s flatness and sheen.</p>
+<ul>
+<li>A silk jacquard with a smooth loom finish lies flat against the wall, while a textured loom finish introduces a tactile dimension to the surface.</li>
+</ul>
+
+<h2>Thread Count</h2>
+<p>The number of warp and weft threads per square inch, a measure of density and fineness. Higher counts equate to greater intricacy and durability.</p>
+<ul>
+<li>Thread count in a silk damask determines the sharpness of its motif—higher counts produce finer details that hold up to scrutiny.</li>
+</ul>
+
+<h2>Dye Bath</h2>
+<p>The solution in which silk is immersed to absorb color. The composition and temperature of the bath dictate hue saturation and longevity.</p>
+<ul>
+<li>A silk velvet dyed in a single bath retains uniformity across its surface, avoiding the mottling that occurs with multiple baths.</li>
+</ul>
+
+<h2>Sheen</h2>
+<p>The reflective quality of the silk’s surface, a result of fiber alignment and finishing treatments. It ranges from dead matte to high gloss.</p>
+<ul>
+<li>Sheen in a silk brocade amplifies the interplay of light and shadow, making the design appear to shift with the time of day.</li>
+</ul>
+
+<h2>Luster</h2>
+<p>The inherent brightness of the silk fiber, determined by the spinning process. It affects the fabric’s visual depth and contrast.</p>
+<ul>
+<li>Luster in a silk satin creates a mirror-like quality that reflects ambient light, intensifying the fabric’s presence in low illumination.</li>
+</ul>
+
+<h2>Crimp</h2>
+<p>The natural waviness of the silk filament, which influences the fabric’s elasticity and drape. It is reduced during processing to achieve smooth
+<nav class="foot">
+<a href="/">Browse</a>
+<a href="/history">History</a>
+<a href="/vocabulary">Vocabulary</a>
+<a href="/sourcing">Sourcing</a>
+<a href="/care">Care</a>
+<a href="/trade">Trade</a>
+<a href="/about">About</a>
+</nav>
+</div></body></html>
\ No newline at end of file
diff --git a/server.js.pre-sort-skill b/server.js.pre-sort-skill
new file mode 100644
index 0000000..9340723
--- /dev/null
+++ b/server.js.pre-sort-skill
@@ -0,0 +1,160 @@
+/**
+ * SILK WALLPAPER — DW family vertical
+ * Curated slice from live designerwallcoverings.com Shopify catalog.
+ */
+try { require('dotenv').config(); } catch (e) {}
+const express = require('express');
+const helmet = require('helmet');
+const path = require('path');
+const fs = require('fs');
+
+const PORT = process.env.PORT || 9841;
+const DW_SHOPIFY = 'https://designerwallcoverings.com';
+const __SITE = path.basename(__dirname);
+let DATA_RAW;
+try {
+ const raw = fs.readFileSync(path.join(__dirname, 'data', 'products.json'), 'utf8');
+ DATA_RAW = JSON.parse(raw);
+ if (!Array.isArray(DATA_RAW)) throw new Error('products.json must be an array');
+} catch (e) {
+ console.error(`[${__SITE}] FATAL: could not load products.json — ${e.message}`);
+ console.error(`[${__SITE}] Starting with empty catalog. Run pull script to populate data/products.json.`);
+ DATA_RAW = [];
+}
+
+function isJunk(p) {
+ if (!p.image_url || !p.image_url.trim()) return true;
+ if (!p.handle && !p.sku) return true;
+ const t = p.title || '';
+ if (/lamp|rug|pillow|throw|tripod|frame|mirror|vase|candle|sculpture|figurine/i.test(t)) return true;
+ if (/visual.{0,3}merchandiser/i.test(t)) return true;
+ if (/(?:^|\W)image[ _-]?4(?:\W|$)/i.test(t)) return true;
+ if (/bh.?90210|beverly.?hills.?90210|iconic.{0,4}bh/i.test(t)) return true;
+ return false;
+}
+
+const PRODUCTS = DATA_RAW.filter(p => !isJunk(p));
+const DROPPED = DATA_RAW.length - PRODUCTS.length;
+console.log(`Loaded ${DATA_RAW.length}, kept ${PRODUCTS.length}, dropped ${DROPPED}`);
+
+// graphics-loop pass 12: niche keyword filter — only show products that fit this site's niche
+const NICHE_POS = ["silk","luxe","sheen","jacquard","satin"];
+const NICHE_NEG = ["solid","blank","plain"];
+function nicheFit(p) {
+ const blob = ((p.title || '') + ' ' + (p.tags || []).join(' ')).toLowerCase();
+ if (NICHE_NEG.some(n => blob.includes(n.toLowerCase()))) return false;
+ return NICHE_POS.length === 0 || NICHE_POS.some(k => blob.includes(k.toLowerCase()));
+}
+const PRODUCTS_NICHE = PRODUCTS.filter(nicheFit);
+const NICHE_DROPPED = PRODUCTS.length - PRODUCTS_NICHE.length;
+console.log(`Niche filter: kept ${PRODUCTS_NICHE.length} of ${PRODUCTS.length}, dropped ${NICHE_DROPPED}`);
+// Replace PRODUCTS reference with the niche-filtered list for all downstream handlers.
+const PRODUCTS_ORIG = PRODUCTS;
+Object.defineProperty(global, '__products_niche_applied', { value: true });
+
+
+const app = express();
+// Security headers via helmet (added 2026-05-04 overnight YOLO loop)
+// DW STANDARD: sort by color/style/sku/title for any product-rendering site.
+const COLOR_RANK = {
+ black:0,gray:1,grey:1,silver:2,white:3,ivory:4,cream:5,beige:6,brown:7,tan:8,khaki:9,copper:10,
+ red:20,pink:21,coral:22,orange:23,peach:24,salmon:25,
+ yellow:30,gold:31,mustard:32,olive:33,
+ green:40,lime:41,mint:42,teal:50,aqua:51,turquoise:52,cyan:53,
+ blue:60,navy:61,indigo:62,periwinkle:63,
+ purple:70,violet:71,lavender:72,plum:73,magenta:74,fuchsia:75,
+};
+function colorRank(p) {
+ const tags = (p.tags || []).map(t => String(t).toLowerCase());
+ for (const t of tags) for (const k of Object.keys(COLOR_RANK)) if (t.includes(k)) return COLOR_RANK[k];
+ return 999;
+}
+const STYLE_TAGS = ['traditional','transitional','modern','contemporary','minimalist','art deco','victorian','mid-century','mid century','rustic','industrial','farmhouse','bohemian','boho','geometric','floral','botanical','damask','toile','grasscloth','silk','linen','vinyl','natural'];
+function styleKey(p) {
+ const tags = (p.tags || []).map(t => String(t).toLowerCase());
+ for (const s of STYLE_TAGS) if (tags.some(t => t.includes(s))) return s;
+ return 'zzz';
+}
+function sortProducts(list, mode) {
+ if (mode === 'sku') return [...list].sort((a,b) => String(a.sku || a.handle || '').localeCompare(String(b.sku || b.handle || '')));
+ if (mode === 'title') return [...list].sort((a,b) => String(a.title || '').localeCompare(String(b.title || '')));
+ if (mode === 'color') return [...list].sort((a,b) => colorRank(a) - colorRank(b) || String(a.title || '').localeCompare(String(b.title || '')));
+ if (mode === 'style') return [...list].sort((a,b) => styleKey(a).localeCompare(styleKey(b)) || String(a.title || '').localeCompare(String(b.title || '')));
+ if (mode === 'price-asc') return [...list].sort((a,b) => (Number(a.max_price) || 0) - (Number(b.max_price) || 0));
+ if (mode === 'price-desc') return [...list].sort((a,b) => (Number(b.max_price) || 0) - (Number(a.max_price) || 0));
+ return list;
+}
+
+
+app.use(helmet({ contentSecurityPolicy: false }));
+app.use(express.json({ limit: '256kb' }));
+// Universal contact module — modals, /api/send-inquiry, /api/send-sample, /zd-loader.js
+require('./_universal-contact')(app, { siteName: "silkwallpaper", zdColor: "#c8a060", zdPosition: 'right' });
+require('./_universal-auth')(app, { siteName: "silkwallpaper" });
+
+app.use(express.static(path.join(__dirname, 'public')));
+
+app.get('/api/products', (req, res) => {
+ const { q, aesthetic, vendor, page = 1, limit = 24, sort = 'newest'} = req.query;
+ let list = PRODUCTS_NICHE;
+ if (q) {
+ const needle = q.toLowerCase();
+ list = list.filter(p => (p.title || '').toLowerCase().includes(needle) || (p.tags || []).some(t => t.toLowerCase().includes(needle)));
+ }
+ if (aesthetic && aesthetic !== 'all') list = list.filter(p => p.aesthetic === aesthetic);
+ if (vendor && vendor !== 'all') list = list.filter(p => p.vendor === vendor);
+ list = sortProducts(list, sort);
+ const total = list.length;
+ const pageNum = Math.max(1, parseInt(page) || 1);
+ const lim = Math.min(60, parseInt(limit) || 24);
+ const start = (pageNum - 1) * lim;
+ res.json({ total, page: pageNum, limit: lim, pages: Math.ceil(total/lim), sort, items: list.slice(start, start + lim) });
+ // (replaced) old: pages: Math.ceil(total / lim), items: list.slice(start, start + lim) });
+});
+
+app.get('/api/sliders', (req, res) => {
+ const SLIDER_AESTHETICS = ["silk","luxury","botanical","chinoiserie","damask","natural"];
+ const out = [];
+ for (const a of SLIDER_AESTHETICS) {
+ const items = PRODUCTS_NICHE.filter(p => p.aesthetic === a).slice(0, 12);
+ if (items.length >= 4) out.push({ aesthetic: a, items });
+ }
+ res.json({ rails: out });
+});
+
+app.get('/api/facets', (req, res) => {
+ const aesthetics = {}; const vendors = {};
+ for (const p of PRODUCTS_NICHE) {
+ aesthetics[p.aesthetic] = (aesthetics[p.aesthetic] || 0) + 1;
+ vendors[p.vendor] = (vendors[p.vendor] || 0) + 1;
+ }
+ res.json({ aesthetics, vendors, total: PRODUCTS.length });
+});
+
+app.get('/api/health', (req, res) => res.json({ status: 'ok', count: PRODUCTS_NICHE.length, dropped: DROPPED }));
+
+app.get('/sample/:handle', (req, res) => {
+ const p = PRODUCTS_NICHE.find(x => x.handle === req.params.handle || x.sku === req.params.handle);
+ if (!p) return res.status(404).send('Not found');
+ res.redirect(302, p.product_url || `${DW_SHOPIFY}/products/${encodeURIComponent(p.handle)}#sample`);
+});
+
+// sitemap.xml + robots.txt for SEO
+app.get('/robots.txt', (req, res) => {
+ res.type('text/plain').send(`User-agent: *
+Allow: /
+Sitemap: https://silkwallpaper.com/sitemap.xml
+`);
+});
+app.get('/sitemap.xml', (req, res) => {
+ const urls = ['/'];
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+${urls.map(u => ` <url><loc>https://silkwallpaper.com${u}</loc><changefreq>weekly</changefreq></url>`).join('\n')}
+</urlset>`;
+ res.type('application/xml').send(xml);
+});
+
+app.listen(PORT, '127.0.0.1', () => {
+ console.log(`silkwallpaper listening on http://127.0.0.1:${PORT}`);
+});
← 5060509 sort-skill UI: add Light→Dark / Dark→Light / Color Wheel opt
·
back to Silkwallpaper
·
fleet-grid refine: chip_uniform_aspect (silkwallpaper) c58afd9 →