← back to Fentucci Theme Note
TK-10311: Fentucci site-wide quote-only specs-confirmed-at-sample theme note — injector + revert (backup-first, ledgered)
7ab8c6fe4aeb6fb6bbd9c78e3bd9357726419bca · 2026-08-31 12:47:20 -0700 · Steve Abrams
Files touched
A .gitignoreA apply-quote-note.mjsA backups/apply-src-MAIN-145121607731-2026-08-31T19-47-03-911Z.liquid.bakA backups/preview-MAIN-145121607731.liquidA backups/product-description-meta.MAIN-2026-08-31T19-46-06-718Z.liquid.bakA revert-quote-note.mjs
Diff
commit 7ab8c6fe4aeb6fb6bbd9c78e3bd9357726419bca
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 31 12:47:20 2026 -0700
TK-10311: Fentucci site-wide quote-only specs-confirmed-at-sample theme note — injector + revert (backup-first, ledgered)
---
.gitignore | 5 +
apply-quote-note.mjs | 108 +++++++++++
...45121607731-2026-08-31T19-47-03-911Z.liquid.bak | 208 ++++++++++++++++++++
backups/preview-MAIN-145121607731.liquid | 215 +++++++++++++++++++++
...n-meta.MAIN-2026-08-31T19-46-06-718Z.liquid.bak | 208 ++++++++++++++++++++
revert-quote-note.mjs | 20 ++
6 files changed, 764 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ff2422c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
diff --git a/apply-quote-note.mjs b/apply-quote-note.mjs
new file mode 100644
index 0000000..fd4ba99
--- /dev/null
+++ b/apply-quote-note.mjs
@@ -0,0 +1,108 @@
+#!/usr/bin/env node
+// TK-10311 / TK-00034 — inject the DTD-approved (6/6 A) site-wide quote-only "specs confirmed
+// with your sample" trust note into the LIVE theme's snippets/product-description-meta.liquid.
+// Steve APPROVED the content 2026-08-08; Steve in-session GO 2026-08-31 to execute now that the
+// full-access token carries write_themes. Writes to the CURRENT role=main theme (the memo's
+// 144396058675 is now unpublished; the live/published theme moved to a new id).
+//
+// Reversible: backs up the exact current asset first (backups/), re-PUT that file to revert.
+// Idempotent: refuses if the block is already present.
+import fs from 'node:fs';
+
+const SECRETS = '/Users/macstudio3/Projects/secrets-manager/.env';
+const STORE = 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = fs.readFileSync(SECRETS, 'utf8').match(/^SHOPIFY_FULL_ACCESS_TOKEN=(.+)$/m)[1].trim();
+const REST = `https://${STORE}/admin/api/2024-10`;
+const H = { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' };
+const KEY = 'snippets/product-description-meta.liquid';
+const APPLY = process.argv.includes('--apply');
+const BACKDIR = new URL('./backups/', import.meta.url).pathname;
+const LEDGER = `${process.env.HOME}/.claude/yolo-queue/executed-reversible/ledger.jsonl`;
+fs.mkdirSync(BACKDIR, { recursive: true });
+
+// DTD-approved block (positive framing, site-wide quote-only condition) + minimal CSS.
+const CSS_MARK = '.spec-note--quote{';
+const CSS = `\n<style>.spec-note--quote{font-size:.85em;color:#6b6b6b;font-style:italic;margin-top:6px}</style>\n`;
+const BLOCK_MARK = 'spec-note--quote';
+const BLOCK = `
+ {%- comment -%} TK-10311/TK-00034: site-wide quote-only "specs confirmed at sample" trust note (DTD 6/6 A, Steve-approved 2026-08-08) {%- endcomment -%}
+ {%- if product.metafields.custom.price_mode == 'quote_only' or product.tags contains 'quotes' -%}
+ <p class="spec-note spec-note--quote">Exact width & specifications are confirmed with your complimentary sample.</p>
+ {%- endif -%}
+`;
+
+async function getAsset() {
+ const r = await fetch(`${REST}/themes/${MAIN}/assets.json?asset[key]=${encodeURIComponent(KEY)}`, { headers: H });
+ const j = await r.json();
+ return j.asset;
+}
+async function mainThemeId() {
+ const r = await fetch(`${REST}/themes.json?fields=id,role,name`, { headers: H });
+ const j = await r.json();
+ const m = (j.themes || []).find(t => t.role === 'main');
+ if (!m) throw new Error('no role=main theme found');
+ return m;
+}
+
+let MAIN;
+async function main() {
+ const m = await mainThemeId();
+ MAIN = m.id;
+ console.log(`live main theme: ${MAIN} "${m.name}"`);
+ const asset = await getAsset();
+ if (!asset || asset.value == null) throw new Error(`${KEY} not found on theme ${MAIN}`);
+ const orig = asset.value;
+ // backup
+ const bak = `${BACKDIR}apply-src-MAIN-${MAIN}-${new Date().toISOString().replace(/[:.]/g, '-')}.liquid.bak`;
+ fs.writeFileSync(bak, orig);
+ console.log(`backed up ${orig.length} bytes -> ${bak}`);
+
+ if (orig.includes(BLOCK_MARK)) { console.log('IDEMPOTENT: quote-note block already present — nothing to do.'); return; }
+
+ // 1) inject the liquid block: right after the AI Rooms {% endif %} that closes the spec rows,
+ // inside .dw-specs-more (anchor = the "</div>\n </details>" that closes the specs panel).
+ const anchor = '</div>\n </details>';
+ let updated;
+ if (orig.includes(anchor)) {
+ updated = orig.replace(anchor, `${BLOCK} </div>\n </details>`);
+ } else {
+ // fallback: inject before the closing of the top-level specs container "</div>\n{% endif %}"
+ const fb = '</div>\n{% endif %}';
+ if (!orig.includes(fb)) throw new Error('no injection anchor found — refusing to write');
+ updated = orig.replace(fb, `${BLOCK}</div>\n{% endif %}`);
+ }
+ // 2) inject CSS once (prepend near top)
+ if (!updated.includes(CSS_MARK)) updated = CSS + updated;
+
+ if (updated === orig) throw new Error('no change produced — refusing');
+ console.log(`change: +${updated.length - orig.length} bytes; block injected=${updated.includes(BLOCK_MARK)}; css injected=${updated.includes(CSS_MARK)}`);
+
+ if (!APPLY) {
+ const preview = `${BACKDIR}preview-MAIN-${MAIN}.liquid`;
+ fs.writeFileSync(preview, updated);
+ console.log(`DRY-RUN. proposed asset written to ${preview} (not PUT).`);
+ return;
+ }
+
+ const put = await fetch(`${REST}/themes/${MAIN}/assets.json`, {
+ method: 'PUT', headers: H, body: JSON.stringify({ asset: { key: KEY, value: updated } }),
+ });
+ const pj = await put.json();
+ if (!put.ok || !pj.asset) throw new Error(`PUT failed ${put.status}: ${JSON.stringify(pj).slice(0, 200)}`);
+ console.log(`PUT ok: ${pj.asset.key} @ ${pj.asset.updated_at} size ${pj.asset.size}`);
+
+ // verify by re-GET
+ const after = await getAsset();
+ const ok = (after.value || '').includes(BLOCK_MARK) && (after.value || '').includes(CSS_MARK);
+ console.log(`VERIFY re-GET: block present=${(after.value||'').includes(BLOCK_MARK)} css present=${(after.value||'').includes(CSS_MARK)} => ${ok ? 'OK' : 'FAIL'}`);
+
+ fs.appendFileSync(LEDGER, JSON.stringify({
+ ts: new Date().toISOString(), agent: 'vp-dw-commerce', ticket: 'TK-10311',
+ action: `inject quote-only specs-confirmed-at-sample note into ${KEY} on live main theme ${MAIN}`,
+ blast_radius: 1,
+ undo_cmd: `node ~/Projects/fentucci-theme-note/revert-quote-note.mjs "${bak}"`,
+ verify: `re-GET ${KEY} contains spec-note--quote = ${ok}`,
+ }) + '\n');
+ if (!ok) process.exit(2);
+}
+main().catch(e => { console.error('FATAL', e.message); process.exit(1); });
diff --git a/backups/apply-src-MAIN-145121607731-2026-08-31T19-47-03-911Z.liquid.bak b/backups/apply-src-MAIN-145121607731-2026-08-31T19-47-03-911Z.liquid.bak
new file mode 100644
index 0000000..6254538
--- /dev/null
+++ b/backups/apply-src-MAIN-145121607731-2026-08-31T19-47-03-911Z.liquid.bak
@@ -0,0 +1,208 @@
+<style>
+.dw-specs { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 16px 0; }
+.dw-specs-title { font-size: 13px; font-weight: 600; color: #888; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 8px; padding-bottom: 6px; border-bottom: 1px solid #eee; }
+.dw-spec-row { display: flex; padding: 6px 0; border-bottom: 1px solid #f5f5f5; font-size: 15px; line-height: 1.4; }
+.dw-spec-row:last-child { border-bottom: none; }
+.dw-spec-label { font-weight: 600; color: #333; width: 40%; min-width: 120px; flex-shrink: 0; }
+.dw-spec-value { color: #555; flex: 1; }
+/* Collapsed "more specifications" panel (Steve 2026-07-28): only Product Type,
+ Pattern Name, Color and SKU stay visible; everything else lives in here. */
+.dw-specs-more { margin: 0; }
+/* Steve TK-10095: darker/heavier summary so it no longer blends in + a rotating chevron toggle icon */
+.dw-specs-more > summary { cursor: pointer; list-style: none; font-size: 14px; font-weight: 700; color: #1a1a1a; text-transform: uppercase; letter-spacing: 0.05em; padding: 11px 0; display: flex; align-items: center; gap: 9px; -webkit-user-select: none; user-select: none; border-bottom: 1px solid #e3e3e3; }
+.dw-specs-more > summary::-webkit-details-marker { display: none; }
+.dw-specs-more > summary::before { content: ""; width: 7px; height: 7px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(-45deg); transition: transform 0.2s ease; flex-shrink: 0; position: relative; top: -1px; }
+.dw-specs-more[open] > summary::before { transform: rotate(45deg); top: -2px; }
+.dw-specs-more > summary::after { content: "+"; margin-left: auto; font-size: 18px; font-weight: 400; color: #999; line-height: 1; }
+.dw-specs-more[open] > summary::after { content: "\2013"; }
+.dw-specs-more > summary:hover { color: #000; }
+.dw-specs-more[open] > summary { border-bottom: none; }
+.dw-specs-more__body { padding-top: 2px; }
+/* Pattern description moved into the specs block when short enough (long ones stay wide) */
+.dw-specs-desc { margin-top: 14px; padding-top: 12px; border-top: 1px solid #eee; }
+.dw-specs-desc__label { font-size: 13px; font-weight: 600; color: #888; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px; }
+.dw-specs-desc__body { font-size: 15px; color: #555; line-height: 1.5; }
+.dw-specs-desc__body p { margin: 0 0 8px; }
+.dw-specs-desc__body p:last-child { margin-bottom: 0; }
+</style>
+
+{% comment %} DW Specs — canonical namespace: custom.* — falls back to specs.* then global.* for legacy products {% endcomment %}
+
+{% assign has_specs = false %}
+{% assign w = product.metafields.custom.width.value | default: product.metafields.specs.width.value | default: product.metafields.global.Width.value | default: product.metafields.global.width.value %}
+{% comment %} bugfix 2026-07-16: specs.material (e.g. grasscloth lines) was missing from the chain, silently blanking Material; add specs.material + dwc.contents/content {% endcomment %}
+{% assign mat = product.metafields.custom.material.value | default: product.metafields.specs.material.value | default: product.metafields.specs.composition.value | default: product.metafields.dwc.contents.value | default: product.metafields.dwc.content.value | default: product.metafields.global.Content.value | default: product.metafields.global.Contents.value | default: product.metafields.global.Construction.value %}
+{% comment %} envelope-guard: unwrap a leaked raw metafield JSON wrapper e.g. {"type":"single_line_text_field","value":"Paper"} if one ever slips through, and prefer the canonical global.Material when the value is still an envelope {% endcomment %}
+{% if mat contains '"single_line_text_field"' and mat contains '"value"' %}{% assign mat = mat | split: '"value": "' | last | split: '"}' | first | default: product.metafields.global.Material.value %}{% endif %}
+{% assign col = product.metafields.custom.collection_name.value | default: product.metafields.specs.collection.value | default: product.metafields.global.Collection.value %}
+{% assign rep = product.metafields.custom.pattern_repeat.value | default: product.metafields.custom.repeat.value | default: product.metafields.specs.pattern_repeat.value | default: product.metafields.specs.repeat_v.value | default: product.metafields.dwc.repeat.value | default: product.metafields.global.repeat.value | default: product.metafields.global['Vert-Rpt'].value %}
+{% assign fin = product.metafields.custom.finish.value | default: product.metafields.specs.finish.value | default: product.metafields.global.Finish.value | default: product.metafields.global.FINISH.value %}
+{% assign care = product.metafields.custom.care.value | default: product.metafields.specs.care.value | default: product.metafields.global.Cleaning.value | default: product.metafields.global['Clean-Code'].value | default: product.metafields.global['Cleaning-Code'].value %}
+{% assign fire = product.metafields.custom.fire_rating.value | default: product.metafields.specs.fire_rating.value | default: product.metafields.global.fire_rating.value | default: product.metafields.global.FLAMMABILITY.value %}
+{% assign match = product.metafields.custom.match_type.value | default: product.metafields.specs.match_type.value | default: product.metafields.global.MATCH.value | default: product.metafields.global.Match.value %}
+{% assign app = product.metafields.custom.application.value | default: product.metafields.specs.application.value | default: product.metafields.global.application.value %}
+{% assign len = product.metafields.custom.length.value | default: product.metafields.global.length.value | default: product.metafields.global.Length.value %}
+{% assign pkg = product.metafields.custom.packaging.value | default: product.metafields.global.packaged.value | default: product.metafields.global.Packaged.value %}
+{% assign uom = product.metafields.custom.unit_of_measure.value | default: product.metafields.global.unit_of_measure.value %}
+{% assign coo = product.metafields.custom.origin.value | default: product.metafields.global.Country.value | default: product.metafields.global['Country-of-Origin'].value %}
+{% assign dur = product.metafields.custom.wyzenbeek.value | default: product.metafields.global['Wyzenbeek-#'].value %}
+{% assign mart = product.metafields.custom.martindale.value | default: product.metafields.global['Martindale-#'].value %}
+{% assign abr = product.metafields.custom.abrasion.value | default: product.metafields.specs.abrasion.value %}
+{% assign bk = product.metafields.custom.backing.value | default: product.metafields.specs.backing.value | default: product.metafields.global.Substrate.value | default: product.metafields.global.substrate.value %}
+{% assign brand = product.metafields.custom.designer.value | default: product.metafields.custom.brand.value | default: product.metafields.global.Brand.value %}
+{% assign wt = product.metafields.custom.product_weight.value | default: product.metafields.global.Weight.value %}
+{% comment %} spec-in-description cleanup 2026-07-28: rows for facts that had no home (were stuck in body_html tables/lists). Text-typed custom.* metafields written by specfix-all.mjs. {% endcomment %}
+{% assign certs = product.metafields.custom.certifications.value | default: product.metafields.specs.certifications.value %}
+{% assign gram = product.metafields.custom.grammage.value | default: product.metafields.specs.grammage.value %}
+{% assign lightfast = product.metafields.custom.light_fastness.value | default: product.metafields.specs.light_fastness.value %}
+{% comment %} residual spec rows 2026-07-29 (TK-10030 finish): peel-stick + compliance + roll-detail facts that had no home; fresh custom.* text keys (canonical was product_reference). {% endcomment %}
+{% assign wash = product.metafields.custom.washability.value %}
+{% assign removal = product.metafields.custom.removal.value %}
+{% assign adhesive = product.metafields.custom.adhesive.value %}
+{% assign coverage = product.metafields.custom.coverage.value | default: product.metafields.specs.coverage.value %}
+{% assign wallcov = product.metafields.custom.wall_coverage.value %}
+{% assign grade = product.metafields.custom.grade.value %}
+{% assign leadtime = product.metafields.custom.leadtime.value %}
+{% assign dims = product.metafields.custom.dimensions.value %}
+{% assign hrep = product.metafields.custom.horizontal_repeat.value %}
+{% assign prop65 = product.metafields.custom.prop65.value %}
+{% assign catb = product.metafields.custom.catb117.value %}
+{% assign minordtxt = product.metafields.custom.min_order.value %}
+{% comment %} blast-wide additions 2026-07-16 — TEXT-typed sources only (custom.style/type/horz_repeat/match/packaged are product_reference, never rendered) {% endcomment %}
+{% assign color = product.metafields.custom.color.value | default: product.metafields.specs.color.value | default: product.metafields.global.color.value | default: product.metafields.global.Color.value | default: product.metafields.dwc.color.value %}
+{% assign pname = product.metafields.custom.pattern_name.value | default: product.metafields.dwc.pattern_name.value %}
+{% assign style = product.metafields.specs.style.value | default: product.metafields.global.Style.value %}
+{% comment %} Type row dropped 2026-07-16: global.Type is redundant with product_type AND holds the banned word "Wallpapers" on ~14 products {% endcomment %}
+{% assign minord = product.metafields.custom.minimum.value | default: product.metafields.dwc.minimum_order_quantity.value | default: product.metafields.global.v_prods_quantity_order_min.value %}
+{% comment %} derive a human order unit from uom so "Minimum Order" isn't a bare integer; increment row dropped as redundant with min+packaged {% endcomment %}
+{% assign ulabel = '' %}
+{% if uom contains 'Double Roll' %}{% assign ulabel = 'double rolls' %}{% elsif uom contains 'Roll' %}{% assign ulabel = 'rolls' %}{% elsif uom contains 'Yard' %}{% assign ulabel = 'yards' %}{% elsif uom contains 'Meter' %}{% assign ulabel = 'meters' %}{% endif %}
+{% assign mfr = product.metafields.custom.manufacturer_sku.value | default: product.metafields.dwc.manufacturer_sku.value %}
+
+{% comment %} Double-roll lines (Thibaut/Malibu Wallpaper/Malibu Walls/York): explicit S/R + D/R
+ length metafields (D/R = 2 x S/R). AI Rooms note. Assigned up front so the collapsed-panel
+ presence check (has_more) can see them. (Steve 2026-07-08 "2 and 2") {% endcomment %}
+{% assign srlen = product.metafields.global.single_roll_length.value %}
+{% assign drlen = product.metafields.global.double_roll_length.value %}
+{% assign ai_note = product.metafields.specs.ai_rooms_note.value %}
+{% comment %} customer-facing DW SKU for the always-visible core: prefer the SELLABLE
+ (non-Sample) variant's SKU (the clean DW sku, e.g. KRAL-2023); only if every variant
+ is a Sample do we fall back to the first variant's SKU with the Sample suffix stripped
+ — handles both '-Sample' and a bare 'SAMPLE' suffix (Steve 2026-07-28). {% endcomment %}
+{% assign dwsku = '' %}
+{% for v in product.variants %}
+ {% unless v.title == 'Sample' or v.sku contains 'Sample' or v.sku contains 'SAMPLE' %}{% assign dwsku = v.sku %}{% break %}{% endunless %}
+{% endfor %}
+{% if dwsku == blank %}
+ {% assign dwsku = product.selected_or_first_available_variant.sku | default: product.variants.first.sku | remove: '-Sample' | remove: '-SAMPLE' | remove: 'Sample' | remove: 'SAMPLE' %}
+{% endif %}
+
+{% if product.type != blank or dwsku != blank or pname != blank or color != blank or w != blank or mat != blank or col != blank %}{% assign has_specs = true %}{% endif %}
+{% comment %} anything beyond the always-visible core → collapsed panel {% endcomment %}
+{% assign has_more = false %}
+{% if match != blank or mat != blank or bk != blank or wt != blank or col != blank or style != blank or brand != blank or fin != blank or fire != blank or care != blank or app != blank or pkg != blank or uom != blank or minord != blank or coo != blank or dur != blank or mart != blank or abr != blank or certs != blank or gram != blank or lightfast != blank or wash != blank or removal != blank or adhesive != blank or coverage != blank or wallcov != blank or grade != blank or leadtime != blank or dims != blank or hrep != blank or prop65 != blank or catb != blank or minordtxt != blank or ai_note != blank %}{% assign has_more = true %}{% endif %}
+
+{% if has_specs %}
+<div class="dw-specs">
+ <div class="dw-specs-title">Specifications</div>
+
+ {%- comment -%} ALWAYS-VISIBLE CORE (Steve 2026-07-28): Product Type, Pattern Name, Color, SKU {%- endcomment -%}
+ {% if product.type != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Product Type</span><span class="dw-spec-value">{{ product.type }}</span></div>{% endif %}
+ {% if pname != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Pattern Name</span><span class="dw-spec-value">{{ pname }}</span></div>{% endif %}
+ {% if color != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Color</span><span class="dw-spec-value">{{ color }}</span></div>{% endif %}
+ {% if dwsku != blank %}<div class="dw-spec-row"><span class="dw-spec-label">SKU</span><span class="dw-spec-value">{{ dwsku }}</span></div>{% endif %}
+ {%- comment -%} Width / Length / Pattern Repeat promoted to always-visible core (Steve TK-10095) {%- endcomment -%}
+ {% if w != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Width</span><span class="dw-spec-value">{{ w }}</span></div>{% endif %}
+ {% if srlen != blank and drlen != blank %}
+ <div class="dw-spec-row"><span class="dw-spec-label">Length S/R</span><span class="dw-spec-value">{{ srlen }}</span></div>
+ <div class="dw-spec-row"><span class="dw-spec-label">Length D/R</span><span class="dw-spec-value">{{ drlen }}</span></div>
+ {% elsif len != blank %}<div class="dw-spec-row"><span class="dw-spec-label">{% if product.vendor == 'Designtex' %}Bolt size{% else %}Length{% endif %}</span><span class="dw-spec-value">{{ len }}</span></div>{% endif %}
+ {% if rep != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Pattern Repeat</span><span class="dw-spec-value">{{ rep }}</span></div>{% endif %}
+
+ {%- comment -%} Everything else → collapsed "More specifications" panel (Steve 2026-07-28) {%- endcomment -%}
+ {% if has_more %}
+ <details class="dw-specs-more">
+ <summary>More specifications</summary>
+ <div class="dw-specs-more__body">
+ {% if match != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Match</span><span class="dw-spec-value">{{ match }}</span></div>{% endif %}
+ {% if mat != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Material</span><span class="dw-spec-value">{{ mat }}</span></div>{% endif %}
+ {% if bk != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Backing</span><span class="dw-spec-value">{{ bk }}</span></div>{% endif %}
+ {% if wt != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Weight</span><span class="dw-spec-value">{{ wt }}</span></div>{% endif %}
+ {% if gram != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Grammage</span><span class="dw-spec-value">{{ gram }}</span></div>{% endif %}
+ {% if lightfast != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Light Fastness</span><span class="dw-spec-value">{{ lightfast }}</span></div>{% endif %}
+ {% if col != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Collection</span><span class="dw-spec-value">{{ col }}</span></div>{% endif %}
+ {% if style != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Style</span><span class="dw-spec-value">{{ style }}</span></div>{% endif %}
+ {% if brand != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Designer</span><span class="dw-spec-value">{{ brand }}</span></div>{% endif %}
+ {% if fin != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Finish</span><span class="dw-spec-value">{{ fin }}</span></div>{% endif %}
+ {% if fire != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Fire Rating</span><span class="dw-spec-value">{{ fire }}</span></div>{% endif %}
+ {% if certs != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Certifications</span><span class="dw-spec-value">{{ certs }}</span></div>{% endif %}
+ {% if dims != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Dimensions</span><span class="dw-spec-value">{{ dims }}</span></div>{% endif %}
+ {% if coverage != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Coverage</span><span class="dw-spec-value">{{ coverage }}</span></div>{% endif %}
+ {% if wallcov != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Wall Coverage</span><span class="dw-spec-value">{{ wallcov }}</span></div>{% endif %}
+ {% if hrep != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Horizontal Repeat</span><span class="dw-spec-value">{{ hrep }}</span></div>{% endif %}
+ {% if grade != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Grade</span><span class="dw-spec-value">{{ grade }}</span></div>{% endif %}
+ {% if wash != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Washability</span><span class="dw-spec-value">{{ wash }}</span></div>{% endif %}
+ {% if removal != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Removal</span><span class="dw-spec-value">{{ removal }}</span></div>{% endif %}
+ {% if adhesive != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Adhesive</span><span class="dw-spec-value">{{ adhesive }}</span></div>{% endif %}
+ {% if leadtime != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Lead Time</span><span class="dw-spec-value">{{ leadtime }}</span></div>{% endif %}
+ {% if minordtxt != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Minimum Order</span><span class="dw-spec-value">{{ minordtxt }}</span></div>{% endif %}
+ {% if prop65 != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Prop 65</span><span class="dw-spec-value">{{ prop65 }}</span></div>{% endif %}
+ {% if catb != blank %}<div class="dw-spec-row"><span class="dw-spec-label">CA TB117</span><span class="dw-spec-value">{{ catb }}</span></div>{% endif %}
+ {% if care != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Care</span><span class="dw-spec-value">{{ care }}</span></div>{% endif %}
+ {% if app != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Application</span><span class="dw-spec-value">{{ app }}</span></div>{% endif %}
+ {% if pkg != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Packaged</span><span class="dw-spec-value">{{ pkg }}</span></div>{% endif %}
+ {% if uom != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Unit</span><span class="dw-spec-value">{{ uom }}</span></div>{% endif %}
+ {% if minord != blank and minord != '1' and minord != 1 %}<div class="dw-spec-row"><span class="dw-spec-label">Minimum Order</span><span class="dw-spec-value">{{ minord }}{% if ulabel != blank %} {{ ulabel }}{% endif %}</span></div>{% endif %}
+ {% if coo != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Origin</span><span class="dw-spec-value">{{ coo }}</span></div>{% endif %}
+ {% comment %} mfr SKU row removed 2026-07-16 (DTD verdict A): raw mfr code is a private-label reverse-lookup key — backend metafield only, never customer-facing {% endcomment %}
+ {% if dur != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Wyzenbeek</span><span class="dw-spec-value">{{ dur }}</span></div>{% endif %}
+ {% if mart != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Martindale</span><span class="dw-spec-value">{{ mart }}</span></div>{% endif %}
+ {% if abr != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Abrasion</span><span class="dw-spec-value">{{ abr }}</span></div>{% endif %}
+ {% if ai_note != blank %}
+ <div class="dw-spec-row" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee;">
+ <span class="dw-spec-label" style="color: #b45309; font-size: 13px;">AI Rooms</span>
+ <span class="dw-spec-value" style="color: #92400e; font-size: 13px; font-style: italic;">{{ ai_note }}</span>
+ </div>
+ {% endif %}
+ </div>
+ </details>
+ {% endif %}
+
+ {%- comment -%} PATTERN DESCRIPTION moved into the specs block when short enough (Steve 2026-07-28).
+ Long descriptions (e.g. Kravet-family products) exceed DESC_MAX chars and stay in the wide
+ gallery area instead — product-gallery.liquid gates on the SAME threshold so it renders in
+ exactly one place. DESC_MAX is tunable. {%- endcomment -%}
+ {% assign desc_len = product.description | strip_html | strip | size %}
+ {% if product.description != blank and desc_len <= 1500 %}
+ <div class="dw-specs-desc">
+ <div class="dw-specs-desc__label">Description</div>
+ <div class="dw-specs-desc__body rte" itemprop="description">{{ product.description }}</div>
+ </div>
+ {% endif %}
+</div>
+{% endif %}
+
+{% comment %}
+Contact popup form
+{% endcomment %}
+<div id="custom-popup-overlay">
+ <div class="custom-popup-main">
+ <div class="custom-close">×</div>
+ <div class="content-main">
+ <div class="popup-content">
+ <h2 style="text-align:center;">Send us a message</h2>
+ {% form 'contact' %}
+ {% if form.errors %}<div class="error-message" style="display:none;"><span>{{ 'general.contact.error' | t }}</span></div>{% endif %}
+ {% if form.posted_successfully? %}<div class="success-message" style="display:none;">{{ 'general.contact.success' | t }}</div>{% endif %}
+ <div class="field-wrap sku"><input type="text" value="{{ product.variants.last.sku }}" name="contact[SKU]" readonly="true"></div>
+ <div class="field-wrap name"><input type="text" placeholder="Name" value="" name="contact2[name]" class="{% if form.errors contains 'author' %}error{% endif %}" required></div>
+ <div class="field-wrap email"><input type="email" name="contact[email]" autocorrect="off" autocapitalize="off" value="{% if form.email %}{{ form.email }}{% elsif customer %}{{ customer.email }}{% endif %}" class="{% if form.errors contains 'email' %}input--error{% endif %}" placeholder="Email" required></div>
+ <div class="field-wrap phone"><input type="text" placeholder="Phone number" value="" name="contact[phone number]" required></div>
+ <div class="field-wrap message"><textarea name="contact[message]" placeholder="Message" rows="5" required></textarea></div>
+ <input type="hidden" class="comment-check" value="" />
+ <input style="background-color:#000;color:#fff;" type="submit" value="{{ 'general.contact.submit' | t }}">
+ {% endform %}
+ </div>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/backups/preview-MAIN-145121607731.liquid b/backups/preview-MAIN-145121607731.liquid
new file mode 100644
index 0000000..ba8f7f9
--- /dev/null
+++ b/backups/preview-MAIN-145121607731.liquid
@@ -0,0 +1,215 @@
+
+<style>.spec-note--quote{font-size:.85em;color:#6b6b6b;font-style:italic;margin-top:6px}</style>
+<style>
+.dw-specs { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 16px 0; }
+.dw-specs-title { font-size: 13px; font-weight: 600; color: #888; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 8px; padding-bottom: 6px; border-bottom: 1px solid #eee; }
+.dw-spec-row { display: flex; padding: 6px 0; border-bottom: 1px solid #f5f5f5; font-size: 15px; line-height: 1.4; }
+.dw-spec-row:last-child { border-bottom: none; }
+.dw-spec-label { font-weight: 600; color: #333; width: 40%; min-width: 120px; flex-shrink: 0; }
+.dw-spec-value { color: #555; flex: 1; }
+/* Collapsed "more specifications" panel (Steve 2026-07-28): only Product Type,
+ Pattern Name, Color and SKU stay visible; everything else lives in here. */
+.dw-specs-more { margin: 0; }
+/* Steve TK-10095: darker/heavier summary so it no longer blends in + a rotating chevron toggle icon */
+.dw-specs-more > summary { cursor: pointer; list-style: none; font-size: 14px; font-weight: 700; color: #1a1a1a; text-transform: uppercase; letter-spacing: 0.05em; padding: 11px 0; display: flex; align-items: center; gap: 9px; -webkit-user-select: none; user-select: none; border-bottom: 1px solid #e3e3e3; }
+.dw-specs-more > summary::-webkit-details-marker { display: none; }
+.dw-specs-more > summary::before { content: ""; width: 7px; height: 7px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(-45deg); transition: transform 0.2s ease; flex-shrink: 0; position: relative; top: -1px; }
+.dw-specs-more[open] > summary::before { transform: rotate(45deg); top: -2px; }
+.dw-specs-more > summary::after { content: "+"; margin-left: auto; font-size: 18px; font-weight: 400; color: #999; line-height: 1; }
+.dw-specs-more[open] > summary::after { content: "\2013"; }
+.dw-specs-more > summary:hover { color: #000; }
+.dw-specs-more[open] > summary { border-bottom: none; }
+.dw-specs-more__body { padding-top: 2px; }
+/* Pattern description moved into the specs block when short enough (long ones stay wide) */
+.dw-specs-desc { margin-top: 14px; padding-top: 12px; border-top: 1px solid #eee; }
+.dw-specs-desc__label { font-size: 13px; font-weight: 600; color: #888; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px; }
+.dw-specs-desc__body { font-size: 15px; color: #555; line-height: 1.5; }
+.dw-specs-desc__body p { margin: 0 0 8px; }
+.dw-specs-desc__body p:last-child { margin-bottom: 0; }
+</style>
+
+{% comment %} DW Specs — canonical namespace: custom.* — falls back to specs.* then global.* for legacy products {% endcomment %}
+
+{% assign has_specs = false %}
+{% assign w = product.metafields.custom.width.value | default: product.metafields.specs.width.value | default: product.metafields.global.Width.value | default: product.metafields.global.width.value %}
+{% comment %} bugfix 2026-07-16: specs.material (e.g. grasscloth lines) was missing from the chain, silently blanking Material; add specs.material + dwc.contents/content {% endcomment %}
+{% assign mat = product.metafields.custom.material.value | default: product.metafields.specs.material.value | default: product.metafields.specs.composition.value | default: product.metafields.dwc.contents.value | default: product.metafields.dwc.content.value | default: product.metafields.global.Content.value | default: product.metafields.global.Contents.value | default: product.metafields.global.Construction.value %}
+{% comment %} envelope-guard: unwrap a leaked raw metafield JSON wrapper e.g. {"type":"single_line_text_field","value":"Paper"} if one ever slips through, and prefer the canonical global.Material when the value is still an envelope {% endcomment %}
+{% if mat contains '"single_line_text_field"' and mat contains '"value"' %}{% assign mat = mat | split: '"value": "' | last | split: '"}' | first | default: product.metafields.global.Material.value %}{% endif %}
+{% assign col = product.metafields.custom.collection_name.value | default: product.metafields.specs.collection.value | default: product.metafields.global.Collection.value %}
+{% assign rep = product.metafields.custom.pattern_repeat.value | default: product.metafields.custom.repeat.value | default: product.metafields.specs.pattern_repeat.value | default: product.metafields.specs.repeat_v.value | default: product.metafields.dwc.repeat.value | default: product.metafields.global.repeat.value | default: product.metafields.global['Vert-Rpt'].value %}
+{% assign fin = product.metafields.custom.finish.value | default: product.metafields.specs.finish.value | default: product.metafields.global.Finish.value | default: product.metafields.global.FINISH.value %}
+{% assign care = product.metafields.custom.care.value | default: product.metafields.specs.care.value | default: product.metafields.global.Cleaning.value | default: product.metafields.global['Clean-Code'].value | default: product.metafields.global['Cleaning-Code'].value %}
+{% assign fire = product.metafields.custom.fire_rating.value | default: product.metafields.specs.fire_rating.value | default: product.metafields.global.fire_rating.value | default: product.metafields.global.FLAMMABILITY.value %}
+{% assign match = product.metafields.custom.match_type.value | default: product.metafields.specs.match_type.value | default: product.metafields.global.MATCH.value | default: product.metafields.global.Match.value %}
+{% assign app = product.metafields.custom.application.value | default: product.metafields.specs.application.value | default: product.metafields.global.application.value %}
+{% assign len = product.metafields.custom.length.value | default: product.metafields.global.length.value | default: product.metafields.global.Length.value %}
+{% assign pkg = product.metafields.custom.packaging.value | default: product.metafields.global.packaged.value | default: product.metafields.global.Packaged.value %}
+{% assign uom = product.metafields.custom.unit_of_measure.value | default: product.metafields.global.unit_of_measure.value %}
+{% assign coo = product.metafields.custom.origin.value | default: product.metafields.global.Country.value | default: product.metafields.global['Country-of-Origin'].value %}
+{% assign dur = product.metafields.custom.wyzenbeek.value | default: product.metafields.global['Wyzenbeek-#'].value %}
+{% assign mart = product.metafields.custom.martindale.value | default: product.metafields.global['Martindale-#'].value %}
+{% assign abr = product.metafields.custom.abrasion.value | default: product.metafields.specs.abrasion.value %}
+{% assign bk = product.metafields.custom.backing.value | default: product.metafields.specs.backing.value | default: product.metafields.global.Substrate.value | default: product.metafields.global.substrate.value %}
+{% assign brand = product.metafields.custom.designer.value | default: product.metafields.custom.brand.value | default: product.metafields.global.Brand.value %}
+{% assign wt = product.metafields.custom.product_weight.value | default: product.metafields.global.Weight.value %}
+{% comment %} spec-in-description cleanup 2026-07-28: rows for facts that had no home (were stuck in body_html tables/lists). Text-typed custom.* metafields written by specfix-all.mjs. {% endcomment %}
+{% assign certs = product.metafields.custom.certifications.value | default: product.metafields.specs.certifications.value %}
+{% assign gram = product.metafields.custom.grammage.value | default: product.metafields.specs.grammage.value %}
+{% assign lightfast = product.metafields.custom.light_fastness.value | default: product.metafields.specs.light_fastness.value %}
+{% comment %} residual spec rows 2026-07-29 (TK-10030 finish): peel-stick + compliance + roll-detail facts that had no home; fresh custom.* text keys (canonical was product_reference). {% endcomment %}
+{% assign wash = product.metafields.custom.washability.value %}
+{% assign removal = product.metafields.custom.removal.value %}
+{% assign adhesive = product.metafields.custom.adhesive.value %}
+{% assign coverage = product.metafields.custom.coverage.value | default: product.metafields.specs.coverage.value %}
+{% assign wallcov = product.metafields.custom.wall_coverage.value %}
+{% assign grade = product.metafields.custom.grade.value %}
+{% assign leadtime = product.metafields.custom.leadtime.value %}
+{% assign dims = product.metafields.custom.dimensions.value %}
+{% assign hrep = product.metafields.custom.horizontal_repeat.value %}
+{% assign prop65 = product.metafields.custom.prop65.value %}
+{% assign catb = product.metafields.custom.catb117.value %}
+{% assign minordtxt = product.metafields.custom.min_order.value %}
+{% comment %} blast-wide additions 2026-07-16 — TEXT-typed sources only (custom.style/type/horz_repeat/match/packaged are product_reference, never rendered) {% endcomment %}
+{% assign color = product.metafields.custom.color.value | default: product.metafields.specs.color.value | default: product.metafields.global.color.value | default: product.metafields.global.Color.value | default: product.metafields.dwc.color.value %}
+{% assign pname = product.metafields.custom.pattern_name.value | default: product.metafields.dwc.pattern_name.value %}
+{% assign style = product.metafields.specs.style.value | default: product.metafields.global.Style.value %}
+{% comment %} Type row dropped 2026-07-16: global.Type is redundant with product_type AND holds the banned word "Wallpapers" on ~14 products {% endcomment %}
+{% assign minord = product.metafields.custom.minimum.value | default: product.metafields.dwc.minimum_order_quantity.value | default: product.metafields.global.v_prods_quantity_order_min.value %}
+{% comment %} derive a human order unit from uom so "Minimum Order" isn't a bare integer; increment row dropped as redundant with min+packaged {% endcomment %}
+{% assign ulabel = '' %}
+{% if uom contains 'Double Roll' %}{% assign ulabel = 'double rolls' %}{% elsif uom contains 'Roll' %}{% assign ulabel = 'rolls' %}{% elsif uom contains 'Yard' %}{% assign ulabel = 'yards' %}{% elsif uom contains 'Meter' %}{% assign ulabel = 'meters' %}{% endif %}
+{% assign mfr = product.metafields.custom.manufacturer_sku.value | default: product.metafields.dwc.manufacturer_sku.value %}
+
+{% comment %} Double-roll lines (Thibaut/Malibu Wallpaper/Malibu Walls/York): explicit S/R + D/R
+ length metafields (D/R = 2 x S/R). AI Rooms note. Assigned up front so the collapsed-panel
+ presence check (has_more) can see them. (Steve 2026-07-08 "2 and 2") {% endcomment %}
+{% assign srlen = product.metafields.global.single_roll_length.value %}
+{% assign drlen = product.metafields.global.double_roll_length.value %}
+{% assign ai_note = product.metafields.specs.ai_rooms_note.value %}
+{% comment %} customer-facing DW SKU for the always-visible core: prefer the SELLABLE
+ (non-Sample) variant's SKU (the clean DW sku, e.g. KRAL-2023); only if every variant
+ is a Sample do we fall back to the first variant's SKU with the Sample suffix stripped
+ — handles both '-Sample' and a bare 'SAMPLE' suffix (Steve 2026-07-28). {% endcomment %}
+{% assign dwsku = '' %}
+{% for v in product.variants %}
+ {% unless v.title == 'Sample' or v.sku contains 'Sample' or v.sku contains 'SAMPLE' %}{% assign dwsku = v.sku %}{% break %}{% endunless %}
+{% endfor %}
+{% if dwsku == blank %}
+ {% assign dwsku = product.selected_or_first_available_variant.sku | default: product.variants.first.sku | remove: '-Sample' | remove: '-SAMPLE' | remove: 'Sample' | remove: 'SAMPLE' %}
+{% endif %}
+
+{% if product.type != blank or dwsku != blank or pname != blank or color != blank or w != blank or mat != blank or col != blank %}{% assign has_specs = true %}{% endif %}
+{% comment %} anything beyond the always-visible core → collapsed panel {% endcomment %}
+{% assign has_more = false %}
+{% if match != blank or mat != blank or bk != blank or wt != blank or col != blank or style != blank or brand != blank or fin != blank or fire != blank or care != blank or app != blank or pkg != blank or uom != blank or minord != blank or coo != blank or dur != blank or mart != blank or abr != blank or certs != blank or gram != blank or lightfast != blank or wash != blank or removal != blank or adhesive != blank or coverage != blank or wallcov != blank or grade != blank or leadtime != blank or dims != blank or hrep != blank or prop65 != blank or catb != blank or minordtxt != blank or ai_note != blank %}{% assign has_more = true %}{% endif %}
+
+{% if has_specs %}
+<div class="dw-specs">
+ <div class="dw-specs-title">Specifications</div>
+
+ {%- comment -%} ALWAYS-VISIBLE CORE (Steve 2026-07-28): Product Type, Pattern Name, Color, SKU {%- endcomment -%}
+ {% if product.type != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Product Type</span><span class="dw-spec-value">{{ product.type }}</span></div>{% endif %}
+ {% if pname != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Pattern Name</span><span class="dw-spec-value">{{ pname }}</span></div>{% endif %}
+ {% if color != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Color</span><span class="dw-spec-value">{{ color }}</span></div>{% endif %}
+ {% if dwsku != blank %}<div class="dw-spec-row"><span class="dw-spec-label">SKU</span><span class="dw-spec-value">{{ dwsku }}</span></div>{% endif %}
+ {%- comment -%} Width / Length / Pattern Repeat promoted to always-visible core (Steve TK-10095) {%- endcomment -%}
+ {% if w != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Width</span><span class="dw-spec-value">{{ w }}</span></div>{% endif %}
+ {% if srlen != blank and drlen != blank %}
+ <div class="dw-spec-row"><span class="dw-spec-label">Length S/R</span><span class="dw-spec-value">{{ srlen }}</span></div>
+ <div class="dw-spec-row"><span class="dw-spec-label">Length D/R</span><span class="dw-spec-value">{{ drlen }}</span></div>
+ {% elsif len != blank %}<div class="dw-spec-row"><span class="dw-spec-label">{% if product.vendor == 'Designtex' %}Bolt size{% else %}Length{% endif %}</span><span class="dw-spec-value">{{ len }}</span></div>{% endif %}
+ {% if rep != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Pattern Repeat</span><span class="dw-spec-value">{{ rep }}</span></div>{% endif %}
+
+ {%- comment -%} Everything else → collapsed "More specifications" panel (Steve 2026-07-28) {%- endcomment -%}
+ {% if has_more %}
+ <details class="dw-specs-more">
+ <summary>More specifications</summary>
+ <div class="dw-specs-more__body">
+ {% if match != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Match</span><span class="dw-spec-value">{{ match }}</span></div>{% endif %}
+ {% if mat != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Material</span><span class="dw-spec-value">{{ mat }}</span></div>{% endif %}
+ {% if bk != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Backing</span><span class="dw-spec-value">{{ bk }}</span></div>{% endif %}
+ {% if wt != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Weight</span><span class="dw-spec-value">{{ wt }}</span></div>{% endif %}
+ {% if gram != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Grammage</span><span class="dw-spec-value">{{ gram }}</span></div>{% endif %}
+ {% if lightfast != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Light Fastness</span><span class="dw-spec-value">{{ lightfast }}</span></div>{% endif %}
+ {% if col != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Collection</span><span class="dw-spec-value">{{ col }}</span></div>{% endif %}
+ {% if style != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Style</span><span class="dw-spec-value">{{ style }}</span></div>{% endif %}
+ {% if brand != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Designer</span><span class="dw-spec-value">{{ brand }}</span></div>{% endif %}
+ {% if fin != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Finish</span><span class="dw-spec-value">{{ fin }}</span></div>{% endif %}
+ {% if fire != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Fire Rating</span><span class="dw-spec-value">{{ fire }}</span></div>{% endif %}
+ {% if certs != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Certifications</span><span class="dw-spec-value">{{ certs }}</span></div>{% endif %}
+ {% if dims != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Dimensions</span><span class="dw-spec-value">{{ dims }}</span></div>{% endif %}
+ {% if coverage != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Coverage</span><span class="dw-spec-value">{{ coverage }}</span></div>{% endif %}
+ {% if wallcov != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Wall Coverage</span><span class="dw-spec-value">{{ wallcov }}</span></div>{% endif %}
+ {% if hrep != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Horizontal Repeat</span><span class="dw-spec-value">{{ hrep }}</span></div>{% endif %}
+ {% if grade != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Grade</span><span class="dw-spec-value">{{ grade }}</span></div>{% endif %}
+ {% if wash != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Washability</span><span class="dw-spec-value">{{ wash }}</span></div>{% endif %}
+ {% if removal != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Removal</span><span class="dw-spec-value">{{ removal }}</span></div>{% endif %}
+ {% if adhesive != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Adhesive</span><span class="dw-spec-value">{{ adhesive }}</span></div>{% endif %}
+ {% if leadtime != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Lead Time</span><span class="dw-spec-value">{{ leadtime }}</span></div>{% endif %}
+ {% if minordtxt != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Minimum Order</span><span class="dw-spec-value">{{ minordtxt }}</span></div>{% endif %}
+ {% if prop65 != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Prop 65</span><span class="dw-spec-value">{{ prop65 }}</span></div>{% endif %}
+ {% if catb != blank %}<div class="dw-spec-row"><span class="dw-spec-label">CA TB117</span><span class="dw-spec-value">{{ catb }}</span></div>{% endif %}
+ {% if care != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Care</span><span class="dw-spec-value">{{ care }}</span></div>{% endif %}
+ {% if app != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Application</span><span class="dw-spec-value">{{ app }}</span></div>{% endif %}
+ {% if pkg != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Packaged</span><span class="dw-spec-value">{{ pkg }}</span></div>{% endif %}
+ {% if uom != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Unit</span><span class="dw-spec-value">{{ uom }}</span></div>{% endif %}
+ {% if minord != blank and minord != '1' and minord != 1 %}<div class="dw-spec-row"><span class="dw-spec-label">Minimum Order</span><span class="dw-spec-value">{{ minord }}{% if ulabel != blank %} {{ ulabel }}{% endif %}</span></div>{% endif %}
+ {% if coo != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Origin</span><span class="dw-spec-value">{{ coo }}</span></div>{% endif %}
+ {% comment %} mfr SKU row removed 2026-07-16 (DTD verdict A): raw mfr code is a private-label reverse-lookup key — backend metafield only, never customer-facing {% endcomment %}
+ {% if dur != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Wyzenbeek</span><span class="dw-spec-value">{{ dur }}</span></div>{% endif %}
+ {% if mart != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Martindale</span><span class="dw-spec-value">{{ mart }}</span></div>{% endif %}
+ {% if abr != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Abrasion</span><span class="dw-spec-value">{{ abr }}</span></div>{% endif %}
+ {% if ai_note != blank %}
+ <div class="dw-spec-row" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee;">
+ <span class="dw-spec-label" style="color: #b45309; font-size: 13px;">AI Rooms</span>
+ <span class="dw-spec-value" style="color: #92400e; font-size: 13px; font-style: italic;">{{ ai_note }}</span>
+ </div>
+ {% endif %}
+
+ {%- comment -%} TK-10311/TK-00034: site-wide quote-only "specs confirmed at sample" trust note (DTD 6/6 A, Steve-approved 2026-08-08) {%- endcomment -%}
+ {%- if product.metafields.custom.price_mode == 'quote_only' or product.tags contains 'quotes' -%}
+ <p class="spec-note spec-note--quote">Exact width & specifications are confirmed with your complimentary sample.</p>
+ {%- endif -%}
+ </div>
+ </details>
+ {% endif %}
+
+ {%- comment -%} PATTERN DESCRIPTION moved into the specs block when short enough (Steve 2026-07-28).
+ Long descriptions (e.g. Kravet-family products) exceed DESC_MAX chars and stay in the wide
+ gallery area instead — product-gallery.liquid gates on the SAME threshold so it renders in
+ exactly one place. DESC_MAX is tunable. {%- endcomment -%}
+ {% assign desc_len = product.description | strip_html | strip | size %}
+ {% if product.description != blank and desc_len <= 1500 %}
+ <div class="dw-specs-desc">
+ <div class="dw-specs-desc__label">Description</div>
+ <div class="dw-specs-desc__body rte" itemprop="description">{{ product.description }}</div>
+ </div>
+ {% endif %}
+</div>
+{% endif %}
+
+{% comment %}
+Contact popup form
+{% endcomment %}
+<div id="custom-popup-overlay">
+ <div class="custom-popup-main">
+ <div class="custom-close">×</div>
+ <div class="content-main">
+ <div class="popup-content">
+ <h2 style="text-align:center;">Send us a message</h2>
+ {% form 'contact' %}
+ {% if form.errors %}<div class="error-message" style="display:none;"><span>{{ 'general.contact.error' | t }}</span></div>{% endif %}
+ {% if form.posted_successfully? %}<div class="success-message" style="display:none;">{{ 'general.contact.success' | t }}</div>{% endif %}
+ <div class="field-wrap sku"><input type="text" value="{{ product.variants.last.sku }}" name="contact[SKU]" readonly="true"></div>
+ <div class="field-wrap name"><input type="text" placeholder="Name" value="" name="contact2[name]" class="{% if form.errors contains 'author' %}error{% endif %}" required></div>
+ <div class="field-wrap email"><input type="email" name="contact[email]" autocorrect="off" autocapitalize="off" value="{% if form.email %}{{ form.email }}{% elsif customer %}{{ customer.email }}{% endif %}" class="{% if form.errors contains 'email' %}input--error{% endif %}" placeholder="Email" required></div>
+ <div class="field-wrap phone"><input type="text" placeholder="Phone number" value="" name="contact[phone number]" required></div>
+ <div class="field-wrap message"><textarea name="contact[message]" placeholder="Message" rows="5" required></textarea></div>
+ <input type="hidden" class="comment-check" value="" />
+ <input style="background-color:#000;color:#fff;" type="submit" value="{{ 'general.contact.submit' | t }}">
+ {% endform %}
+ </div>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/backups/product-description-meta.MAIN-2026-08-31T19-46-06-718Z.liquid.bak b/backups/product-description-meta.MAIN-2026-08-31T19-46-06-718Z.liquid.bak
new file mode 100644
index 0000000..6254538
--- /dev/null
+++ b/backups/product-description-meta.MAIN-2026-08-31T19-46-06-718Z.liquid.bak
@@ -0,0 +1,208 @@
+<style>
+.dw-specs { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 16px 0; }
+.dw-specs-title { font-size: 13px; font-weight: 600; color: #888; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 8px; padding-bottom: 6px; border-bottom: 1px solid #eee; }
+.dw-spec-row { display: flex; padding: 6px 0; border-bottom: 1px solid #f5f5f5; font-size: 15px; line-height: 1.4; }
+.dw-spec-row:last-child { border-bottom: none; }
+.dw-spec-label { font-weight: 600; color: #333; width: 40%; min-width: 120px; flex-shrink: 0; }
+.dw-spec-value { color: #555; flex: 1; }
+/* Collapsed "more specifications" panel (Steve 2026-07-28): only Product Type,
+ Pattern Name, Color and SKU stay visible; everything else lives in here. */
+.dw-specs-more { margin: 0; }
+/* Steve TK-10095: darker/heavier summary so it no longer blends in + a rotating chevron toggle icon */
+.dw-specs-more > summary { cursor: pointer; list-style: none; font-size: 14px; font-weight: 700; color: #1a1a1a; text-transform: uppercase; letter-spacing: 0.05em; padding: 11px 0; display: flex; align-items: center; gap: 9px; -webkit-user-select: none; user-select: none; border-bottom: 1px solid #e3e3e3; }
+.dw-specs-more > summary::-webkit-details-marker { display: none; }
+.dw-specs-more > summary::before { content: ""; width: 7px; height: 7px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(-45deg); transition: transform 0.2s ease; flex-shrink: 0; position: relative; top: -1px; }
+.dw-specs-more[open] > summary::before { transform: rotate(45deg); top: -2px; }
+.dw-specs-more > summary::after { content: "+"; margin-left: auto; font-size: 18px; font-weight: 400; color: #999; line-height: 1; }
+.dw-specs-more[open] > summary::after { content: "\2013"; }
+.dw-specs-more > summary:hover { color: #000; }
+.dw-specs-more[open] > summary { border-bottom: none; }
+.dw-specs-more__body { padding-top: 2px; }
+/* Pattern description moved into the specs block when short enough (long ones stay wide) */
+.dw-specs-desc { margin-top: 14px; padding-top: 12px; border-top: 1px solid #eee; }
+.dw-specs-desc__label { font-size: 13px; font-weight: 600; color: #888; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px; }
+.dw-specs-desc__body { font-size: 15px; color: #555; line-height: 1.5; }
+.dw-specs-desc__body p { margin: 0 0 8px; }
+.dw-specs-desc__body p:last-child { margin-bottom: 0; }
+</style>
+
+{% comment %} DW Specs — canonical namespace: custom.* — falls back to specs.* then global.* for legacy products {% endcomment %}
+
+{% assign has_specs = false %}
+{% assign w = product.metafields.custom.width.value | default: product.metafields.specs.width.value | default: product.metafields.global.Width.value | default: product.metafields.global.width.value %}
+{% comment %} bugfix 2026-07-16: specs.material (e.g. grasscloth lines) was missing from the chain, silently blanking Material; add specs.material + dwc.contents/content {% endcomment %}
+{% assign mat = product.metafields.custom.material.value | default: product.metafields.specs.material.value | default: product.metafields.specs.composition.value | default: product.metafields.dwc.contents.value | default: product.metafields.dwc.content.value | default: product.metafields.global.Content.value | default: product.metafields.global.Contents.value | default: product.metafields.global.Construction.value %}
+{% comment %} envelope-guard: unwrap a leaked raw metafield JSON wrapper e.g. {"type":"single_line_text_field","value":"Paper"} if one ever slips through, and prefer the canonical global.Material when the value is still an envelope {% endcomment %}
+{% if mat contains '"single_line_text_field"' and mat contains '"value"' %}{% assign mat = mat | split: '"value": "' | last | split: '"}' | first | default: product.metafields.global.Material.value %}{% endif %}
+{% assign col = product.metafields.custom.collection_name.value | default: product.metafields.specs.collection.value | default: product.metafields.global.Collection.value %}
+{% assign rep = product.metafields.custom.pattern_repeat.value | default: product.metafields.custom.repeat.value | default: product.metafields.specs.pattern_repeat.value | default: product.metafields.specs.repeat_v.value | default: product.metafields.dwc.repeat.value | default: product.metafields.global.repeat.value | default: product.metafields.global['Vert-Rpt'].value %}
+{% assign fin = product.metafields.custom.finish.value | default: product.metafields.specs.finish.value | default: product.metafields.global.Finish.value | default: product.metafields.global.FINISH.value %}
+{% assign care = product.metafields.custom.care.value | default: product.metafields.specs.care.value | default: product.metafields.global.Cleaning.value | default: product.metafields.global['Clean-Code'].value | default: product.metafields.global['Cleaning-Code'].value %}
+{% assign fire = product.metafields.custom.fire_rating.value | default: product.metafields.specs.fire_rating.value | default: product.metafields.global.fire_rating.value | default: product.metafields.global.FLAMMABILITY.value %}
+{% assign match = product.metafields.custom.match_type.value | default: product.metafields.specs.match_type.value | default: product.metafields.global.MATCH.value | default: product.metafields.global.Match.value %}
+{% assign app = product.metafields.custom.application.value | default: product.metafields.specs.application.value | default: product.metafields.global.application.value %}
+{% assign len = product.metafields.custom.length.value | default: product.metafields.global.length.value | default: product.metafields.global.Length.value %}
+{% assign pkg = product.metafields.custom.packaging.value | default: product.metafields.global.packaged.value | default: product.metafields.global.Packaged.value %}
+{% assign uom = product.metafields.custom.unit_of_measure.value | default: product.metafields.global.unit_of_measure.value %}
+{% assign coo = product.metafields.custom.origin.value | default: product.metafields.global.Country.value | default: product.metafields.global['Country-of-Origin'].value %}
+{% assign dur = product.metafields.custom.wyzenbeek.value | default: product.metafields.global['Wyzenbeek-#'].value %}
+{% assign mart = product.metafields.custom.martindale.value | default: product.metafields.global['Martindale-#'].value %}
+{% assign abr = product.metafields.custom.abrasion.value | default: product.metafields.specs.abrasion.value %}
+{% assign bk = product.metafields.custom.backing.value | default: product.metafields.specs.backing.value | default: product.metafields.global.Substrate.value | default: product.metafields.global.substrate.value %}
+{% assign brand = product.metafields.custom.designer.value | default: product.metafields.custom.brand.value | default: product.metafields.global.Brand.value %}
+{% assign wt = product.metafields.custom.product_weight.value | default: product.metafields.global.Weight.value %}
+{% comment %} spec-in-description cleanup 2026-07-28: rows for facts that had no home (were stuck in body_html tables/lists). Text-typed custom.* metafields written by specfix-all.mjs. {% endcomment %}
+{% assign certs = product.metafields.custom.certifications.value | default: product.metafields.specs.certifications.value %}
+{% assign gram = product.metafields.custom.grammage.value | default: product.metafields.specs.grammage.value %}
+{% assign lightfast = product.metafields.custom.light_fastness.value | default: product.metafields.specs.light_fastness.value %}
+{% comment %} residual spec rows 2026-07-29 (TK-10030 finish): peel-stick + compliance + roll-detail facts that had no home; fresh custom.* text keys (canonical was product_reference). {% endcomment %}
+{% assign wash = product.metafields.custom.washability.value %}
+{% assign removal = product.metafields.custom.removal.value %}
+{% assign adhesive = product.metafields.custom.adhesive.value %}
+{% assign coverage = product.metafields.custom.coverage.value | default: product.metafields.specs.coverage.value %}
+{% assign wallcov = product.metafields.custom.wall_coverage.value %}
+{% assign grade = product.metafields.custom.grade.value %}
+{% assign leadtime = product.metafields.custom.leadtime.value %}
+{% assign dims = product.metafields.custom.dimensions.value %}
+{% assign hrep = product.metafields.custom.horizontal_repeat.value %}
+{% assign prop65 = product.metafields.custom.prop65.value %}
+{% assign catb = product.metafields.custom.catb117.value %}
+{% assign minordtxt = product.metafields.custom.min_order.value %}
+{% comment %} blast-wide additions 2026-07-16 — TEXT-typed sources only (custom.style/type/horz_repeat/match/packaged are product_reference, never rendered) {% endcomment %}
+{% assign color = product.metafields.custom.color.value | default: product.metafields.specs.color.value | default: product.metafields.global.color.value | default: product.metafields.global.Color.value | default: product.metafields.dwc.color.value %}
+{% assign pname = product.metafields.custom.pattern_name.value | default: product.metafields.dwc.pattern_name.value %}
+{% assign style = product.metafields.specs.style.value | default: product.metafields.global.Style.value %}
+{% comment %} Type row dropped 2026-07-16: global.Type is redundant with product_type AND holds the banned word "Wallpapers" on ~14 products {% endcomment %}
+{% assign minord = product.metafields.custom.minimum.value | default: product.metafields.dwc.minimum_order_quantity.value | default: product.metafields.global.v_prods_quantity_order_min.value %}
+{% comment %} derive a human order unit from uom so "Minimum Order" isn't a bare integer; increment row dropped as redundant with min+packaged {% endcomment %}
+{% assign ulabel = '' %}
+{% if uom contains 'Double Roll' %}{% assign ulabel = 'double rolls' %}{% elsif uom contains 'Roll' %}{% assign ulabel = 'rolls' %}{% elsif uom contains 'Yard' %}{% assign ulabel = 'yards' %}{% elsif uom contains 'Meter' %}{% assign ulabel = 'meters' %}{% endif %}
+{% assign mfr = product.metafields.custom.manufacturer_sku.value | default: product.metafields.dwc.manufacturer_sku.value %}
+
+{% comment %} Double-roll lines (Thibaut/Malibu Wallpaper/Malibu Walls/York): explicit S/R + D/R
+ length metafields (D/R = 2 x S/R). AI Rooms note. Assigned up front so the collapsed-panel
+ presence check (has_more) can see them. (Steve 2026-07-08 "2 and 2") {% endcomment %}
+{% assign srlen = product.metafields.global.single_roll_length.value %}
+{% assign drlen = product.metafields.global.double_roll_length.value %}
+{% assign ai_note = product.metafields.specs.ai_rooms_note.value %}
+{% comment %} customer-facing DW SKU for the always-visible core: prefer the SELLABLE
+ (non-Sample) variant's SKU (the clean DW sku, e.g. KRAL-2023); only if every variant
+ is a Sample do we fall back to the first variant's SKU with the Sample suffix stripped
+ — handles both '-Sample' and a bare 'SAMPLE' suffix (Steve 2026-07-28). {% endcomment %}
+{% assign dwsku = '' %}
+{% for v in product.variants %}
+ {% unless v.title == 'Sample' or v.sku contains 'Sample' or v.sku contains 'SAMPLE' %}{% assign dwsku = v.sku %}{% break %}{% endunless %}
+{% endfor %}
+{% if dwsku == blank %}
+ {% assign dwsku = product.selected_or_first_available_variant.sku | default: product.variants.first.sku | remove: '-Sample' | remove: '-SAMPLE' | remove: 'Sample' | remove: 'SAMPLE' %}
+{% endif %}
+
+{% if product.type != blank or dwsku != blank or pname != blank or color != blank or w != blank or mat != blank or col != blank %}{% assign has_specs = true %}{% endif %}
+{% comment %} anything beyond the always-visible core → collapsed panel {% endcomment %}
+{% assign has_more = false %}
+{% if match != blank or mat != blank or bk != blank or wt != blank or col != blank or style != blank or brand != blank or fin != blank or fire != blank or care != blank or app != blank or pkg != blank or uom != blank or minord != blank or coo != blank or dur != blank or mart != blank or abr != blank or certs != blank or gram != blank or lightfast != blank or wash != blank or removal != blank or adhesive != blank or coverage != blank or wallcov != blank or grade != blank or leadtime != blank or dims != blank or hrep != blank or prop65 != blank or catb != blank or minordtxt != blank or ai_note != blank %}{% assign has_more = true %}{% endif %}
+
+{% if has_specs %}
+<div class="dw-specs">
+ <div class="dw-specs-title">Specifications</div>
+
+ {%- comment -%} ALWAYS-VISIBLE CORE (Steve 2026-07-28): Product Type, Pattern Name, Color, SKU {%- endcomment -%}
+ {% if product.type != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Product Type</span><span class="dw-spec-value">{{ product.type }}</span></div>{% endif %}
+ {% if pname != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Pattern Name</span><span class="dw-spec-value">{{ pname }}</span></div>{% endif %}
+ {% if color != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Color</span><span class="dw-spec-value">{{ color }}</span></div>{% endif %}
+ {% if dwsku != blank %}<div class="dw-spec-row"><span class="dw-spec-label">SKU</span><span class="dw-spec-value">{{ dwsku }}</span></div>{% endif %}
+ {%- comment -%} Width / Length / Pattern Repeat promoted to always-visible core (Steve TK-10095) {%- endcomment -%}
+ {% if w != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Width</span><span class="dw-spec-value">{{ w }}</span></div>{% endif %}
+ {% if srlen != blank and drlen != blank %}
+ <div class="dw-spec-row"><span class="dw-spec-label">Length S/R</span><span class="dw-spec-value">{{ srlen }}</span></div>
+ <div class="dw-spec-row"><span class="dw-spec-label">Length D/R</span><span class="dw-spec-value">{{ drlen }}</span></div>
+ {% elsif len != blank %}<div class="dw-spec-row"><span class="dw-spec-label">{% if product.vendor == 'Designtex' %}Bolt size{% else %}Length{% endif %}</span><span class="dw-spec-value">{{ len }}</span></div>{% endif %}
+ {% if rep != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Pattern Repeat</span><span class="dw-spec-value">{{ rep }}</span></div>{% endif %}
+
+ {%- comment -%} Everything else → collapsed "More specifications" panel (Steve 2026-07-28) {%- endcomment -%}
+ {% if has_more %}
+ <details class="dw-specs-more">
+ <summary>More specifications</summary>
+ <div class="dw-specs-more__body">
+ {% if match != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Match</span><span class="dw-spec-value">{{ match }}</span></div>{% endif %}
+ {% if mat != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Material</span><span class="dw-spec-value">{{ mat }}</span></div>{% endif %}
+ {% if bk != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Backing</span><span class="dw-spec-value">{{ bk }}</span></div>{% endif %}
+ {% if wt != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Weight</span><span class="dw-spec-value">{{ wt }}</span></div>{% endif %}
+ {% if gram != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Grammage</span><span class="dw-spec-value">{{ gram }}</span></div>{% endif %}
+ {% if lightfast != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Light Fastness</span><span class="dw-spec-value">{{ lightfast }}</span></div>{% endif %}
+ {% if col != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Collection</span><span class="dw-spec-value">{{ col }}</span></div>{% endif %}
+ {% if style != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Style</span><span class="dw-spec-value">{{ style }}</span></div>{% endif %}
+ {% if brand != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Designer</span><span class="dw-spec-value">{{ brand }}</span></div>{% endif %}
+ {% if fin != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Finish</span><span class="dw-spec-value">{{ fin }}</span></div>{% endif %}
+ {% if fire != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Fire Rating</span><span class="dw-spec-value">{{ fire }}</span></div>{% endif %}
+ {% if certs != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Certifications</span><span class="dw-spec-value">{{ certs }}</span></div>{% endif %}
+ {% if dims != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Dimensions</span><span class="dw-spec-value">{{ dims }}</span></div>{% endif %}
+ {% if coverage != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Coverage</span><span class="dw-spec-value">{{ coverage }}</span></div>{% endif %}
+ {% if wallcov != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Wall Coverage</span><span class="dw-spec-value">{{ wallcov }}</span></div>{% endif %}
+ {% if hrep != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Horizontal Repeat</span><span class="dw-spec-value">{{ hrep }}</span></div>{% endif %}
+ {% if grade != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Grade</span><span class="dw-spec-value">{{ grade }}</span></div>{% endif %}
+ {% if wash != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Washability</span><span class="dw-spec-value">{{ wash }}</span></div>{% endif %}
+ {% if removal != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Removal</span><span class="dw-spec-value">{{ removal }}</span></div>{% endif %}
+ {% if adhesive != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Adhesive</span><span class="dw-spec-value">{{ adhesive }}</span></div>{% endif %}
+ {% if leadtime != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Lead Time</span><span class="dw-spec-value">{{ leadtime }}</span></div>{% endif %}
+ {% if minordtxt != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Minimum Order</span><span class="dw-spec-value">{{ minordtxt }}</span></div>{% endif %}
+ {% if prop65 != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Prop 65</span><span class="dw-spec-value">{{ prop65 }}</span></div>{% endif %}
+ {% if catb != blank %}<div class="dw-spec-row"><span class="dw-spec-label">CA TB117</span><span class="dw-spec-value">{{ catb }}</span></div>{% endif %}
+ {% if care != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Care</span><span class="dw-spec-value">{{ care }}</span></div>{% endif %}
+ {% if app != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Application</span><span class="dw-spec-value">{{ app }}</span></div>{% endif %}
+ {% if pkg != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Packaged</span><span class="dw-spec-value">{{ pkg }}</span></div>{% endif %}
+ {% if uom != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Unit</span><span class="dw-spec-value">{{ uom }}</span></div>{% endif %}
+ {% if minord != blank and minord != '1' and minord != 1 %}<div class="dw-spec-row"><span class="dw-spec-label">Minimum Order</span><span class="dw-spec-value">{{ minord }}{% if ulabel != blank %} {{ ulabel }}{% endif %}</span></div>{% endif %}
+ {% if coo != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Origin</span><span class="dw-spec-value">{{ coo }}</span></div>{% endif %}
+ {% comment %} mfr SKU row removed 2026-07-16 (DTD verdict A): raw mfr code is a private-label reverse-lookup key — backend metafield only, never customer-facing {% endcomment %}
+ {% if dur != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Wyzenbeek</span><span class="dw-spec-value">{{ dur }}</span></div>{% endif %}
+ {% if mart != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Martindale</span><span class="dw-spec-value">{{ mart }}</span></div>{% endif %}
+ {% if abr != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Abrasion</span><span class="dw-spec-value">{{ abr }}</span></div>{% endif %}
+ {% if ai_note != blank %}
+ <div class="dw-spec-row" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee;">
+ <span class="dw-spec-label" style="color: #b45309; font-size: 13px;">AI Rooms</span>
+ <span class="dw-spec-value" style="color: #92400e; font-size: 13px; font-style: italic;">{{ ai_note }}</span>
+ </div>
+ {% endif %}
+ </div>
+ </details>
+ {% endif %}
+
+ {%- comment -%} PATTERN DESCRIPTION moved into the specs block when short enough (Steve 2026-07-28).
+ Long descriptions (e.g. Kravet-family products) exceed DESC_MAX chars and stay in the wide
+ gallery area instead — product-gallery.liquid gates on the SAME threshold so it renders in
+ exactly one place. DESC_MAX is tunable. {%- endcomment -%}
+ {% assign desc_len = product.description | strip_html | strip | size %}
+ {% if product.description != blank and desc_len <= 1500 %}
+ <div class="dw-specs-desc">
+ <div class="dw-specs-desc__label">Description</div>
+ <div class="dw-specs-desc__body rte" itemprop="description">{{ product.description }}</div>
+ </div>
+ {% endif %}
+</div>
+{% endif %}
+
+{% comment %}
+Contact popup form
+{% endcomment %}
+<div id="custom-popup-overlay">
+ <div class="custom-popup-main">
+ <div class="custom-close">×</div>
+ <div class="content-main">
+ <div class="popup-content">
+ <h2 style="text-align:center;">Send us a message</h2>
+ {% form 'contact' %}
+ {% if form.errors %}<div class="error-message" style="display:none;"><span>{{ 'general.contact.error' | t }}</span></div>{% endif %}
+ {% if form.posted_successfully? %}<div class="success-message" style="display:none;">{{ 'general.contact.success' | t }}</div>{% endif %}
+ <div class="field-wrap sku"><input type="text" value="{{ product.variants.last.sku }}" name="contact[SKU]" readonly="true"></div>
+ <div class="field-wrap name"><input type="text" placeholder="Name" value="" name="contact2[name]" class="{% if form.errors contains 'author' %}error{% endif %}" required></div>
+ <div class="field-wrap email"><input type="email" name="contact[email]" autocorrect="off" autocapitalize="off" value="{% if form.email %}{{ form.email }}{% elsif customer %}{{ customer.email }}{% endif %}" class="{% if form.errors contains 'email' %}input--error{% endif %}" placeholder="Email" required></div>
+ <div class="field-wrap phone"><input type="text" placeholder="Phone number" value="" name="contact[phone number]" required></div>
+ <div class="field-wrap message"><textarea name="contact[message]" placeholder="Message" rows="5" required></textarea></div>
+ <input type="hidden" class="comment-check" value="" />
+ <input style="background-color:#000;color:#fff;" type="submit" value="{{ 'general.contact.submit' | t }}">
+ {% endform %}
+ </div>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/revert-quote-note.mjs b/revert-quote-note.mjs
new file mode 100644
index 0000000..22de6b0
--- /dev/null
+++ b/revert-quote-note.mjs
@@ -0,0 +1,20 @@
+#!/usr/bin/env node
+// TK-10311 — revert the quote-note theme write by re-PUTting the exact backup taken before it.
+// Usage: node revert-quote-note.mjs <path-to-backup.liquid.bak>
+import fs from 'node:fs';
+const SECRETS = '/Users/macstudio3/Projects/secrets-manager/.env';
+const STORE = 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = fs.readFileSync(SECRETS, 'utf8').match(/^SHOPIFY_FULL_ACCESS_TOKEN=(.+)$/m)[1].trim();
+const REST = `https://${STORE}/admin/api/2024-10`;
+const H = { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' };
+const KEY = 'snippets/product-description-meta.liquid';
+const bak = process.argv[2];
+if (!bak || !fs.existsSync(bak)) { console.error('need an existing backup file path'); process.exit(1); }
+const value = fs.readFileSync(bak, 'utf8');
+(async () => {
+ const tr = await fetch(`${REST}/themes.json?fields=id,role`, { headers: H });
+ const MAIN = (await tr.json()).themes.find(t => t.role === 'main').id;
+ const r = await fetch(`${REST}/themes/${MAIN}/assets.json`, { method: 'PUT', headers: H, body: JSON.stringify({ asset: { key: KEY, value } }) });
+ const j = await r.json();
+ console.log(r.ok ? `REVERTED ${KEY} on theme ${MAIN} @ ${j.asset?.updated_at}` : `FAIL ${r.status} ${JSON.stringify(j).slice(0,200)}`);
+})();
(oldest)
·
back to Fentucci Theme Note
·
auto-data-snapshot: 2026-08-31T13:13:14 (1 data files) — bac f078b0b →