[object Object]

← back to Designer Wallcoverings

auto-save: 2026-06-24T11:26:36 (3 files) — shopify/stage-pdp-variant-deeplink-fix.sh vendor-scrapers/china-seas-refresh shopify/specsheet-pdf-port-5.2/

be3f4300725ef66025fcee1263ea987d32e0b743 · 2026-06-24 11:26:40 -0700 · Steve Abrams

Files touched

Diff

commit be3f4300725ef66025fcee1263ea987d32e0b743
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jun 24 11:26:40 2026 -0700

    auto-save: 2026-06-24T11:26:36 (3 files) — shopify/stage-pdp-variant-deeplink-fix.sh vendor-scrapers/china-seas-refresh shopify/specsheet-pdf-port-5.2/
---
 .../5.2-LIVE-original-spec-sheet-button.liquid     |  17 ++
 .../spec-sheet-button.liquid                       | 261 +++++++++++++++++++++
 shopify/stage-pdp-variant-deeplink-fix.sh          |   5 +-
 3 files changed, 282 insertions(+), 1 deletion(-)

diff --git a/shopify/specsheet-pdf-port-5.2/5.2-LIVE-original-spec-sheet-button.liquid b/shopify/specsheet-pdf-port-5.2/5.2-LIVE-original-spec-sheet-button.liquid
new file mode 100644
index 00000000..ac201322
--- /dev/null
+++ b/shopify/specsheet-pdf-port-5.2/5.2-LIVE-original-spec-sheet-button.liquid
@@ -0,0 +1,17 @@
+{% comment %}
+  Spec Sheet Button - Dynamic version with URL parameter
+{% endcomment %}
+
+<a href="/pages/spec-sheet?product={{ product.handle }}"
+   target="_blank"
+   class="dl-sample-btn spec-sheet-btn"
+   onclick="event.stopPropagation();"
+   style="vertical-align: top;">
+  Spec Sheet
+</a>
+
+<style>
+.spec-sheet-btn {
+  display: inline-block;
+}
+</style>
diff --git a/shopify/specsheet-pdf-port-5.2/spec-sheet-button.liquid b/shopify/specsheet-pdf-port-5.2/spec-sheet-button.liquid
new file mode 100644
index 00000000..8aaec315
--- /dev/null
+++ b/shopify/specsheet-pdf-port-5.2/spec-sheet-button.liquid
@@ -0,0 +1,261 @@
+{% comment %}
+  Spec Sheet Button — INLINE PDF download (no page navigation, no iframe).
+
+  FIX HISTORY
+  -----------
+  v1 (broken): fetch('/pages/spec-sheet') + DOMParser + html2canvas. DOMParser
+    never runs scripts, so #pdf-content was always the "Loading Product..."
+    placeholder -> 0-dim canvas -> jsPDF threw -> "Error".
+
+  v2 (broken on LIVE): hidden same-origin iframe -> /pages/spec-sheet -> call its
+    downloadPDF(). DEAD ON ARRIVAL: the storefront sends `X-Frame-Options: DENY`
+    + CSP `frame-ancestors 'none'` STORE-WIDE (verified 2026-06-23 on /, /pages/*,
+    /collections/*, /products/*). DENY blocks ALL framing — including same-origin —
+    so the iframe never renders and contentDocument is inaccessible.
+
+  v3 (this version — works against live CSP):
+    Build the spec-sheet DOM RIGHT HERE on the product page from Liquid-injected
+    product data + metafields (no fetch, no iframe, no /pages/spec-sheet
+    dependency). On click we lazy-load jsPDF + html2canvas from cdnjs (the live
+    CSP has NO script-src/default-src directive, so cdnjs is allowed — verified),
+    render the offscreen node with html2canvas (useCORS:true; the product image
+    is on cdn.shopify.com which returns `access-control-allow-origin: *`, so the
+    canvas does NOT taint), and jsPDF.save() it. One click, fully self-contained.
+
+  The standalone /pages/spec-sheet page still works for a direct visit (it has
+  {% layout none %} so it's a clean print page), but the BUTTON no longer needs it.
+{% endcomment %}
+
+{%- liquid
+  assign p = product
+  assign mf = product.metafields
+  # ---- Color: real color name only (spreadsheet/metafield/variant option), never AI ----
+  assign spec_color = ''
+  if mf.custom.color != blank
+    assign spec_color = mf.custom.color
+  elsif mf.global.Color != blank
+    assign spec_color = mf.global.Color
+  elsif mf.dwc.color != blank
+    assign spec_color = mf.dwc.color
+  endif
+  # ---- Mfr SKU ----
+  assign mfr_sku = ''
+  if mf.custom.manufacturer_sku != blank
+    assign mfr_sku = mf.custom.manufacturer_sku
+  elsif mf.dwc.manufacturer_sku != blank
+    assign mfr_sku = mf.dwc.manufacturer_sku
+  endif
+  # ---- DW SKU (strip -Sample) ----
+  assign dw_sku = p.selected_or_first_available_variant.sku | default: p.variants.first.sku
+  assign dw_sku = dw_sku | replace: '-Sample', '' | replace: '-SAMPLE', '' | replace: '-sample', ''
+-%}
+
+<div class="spec-sheet-controls">
+  <button type="button"
+          class="spec-sheet-btn pdf-download-btn"
+          data-dw-spec-trigger
+          aria-label="Download spec sheet PDF for {{ p.title | escape }}">
+    🖨️ Download PDF
+  </button>
+</div>
+
+{%- comment %} Offscreen, fully-rendered spec sheet built from Liquid data {% endcomment -%}
+<div id="dw-spec-sheet" aria-hidden="true"
+     style="position:fixed;left:-10000px;top:0;width:816px;background:#fff;z-index:-1;">
+  <div class="dw-spec-page">
+    <div class="dw-spec-header">
+      <img class="dw-spec-logo"
+           crossorigin="anonymous"
+           src="https://www.designerwallcoverings.com/cdn/shop/files/Screen_Shot_2019-02-06_at_12.08.24_PM_300x172.png?v=1613539653"
+           alt="Designer Wallcoverings">
+    </div>
+    <div class="dw-spec-body">
+      <h1 class="dw-spec-title">{{ p.title | escape }}</h1>
+      {%- if p.featured_image -%}
+        <img class="dw-spec-img" crossorigin="anonymous"
+             src="{{ p.featured_image | image_url: width: 600 }}"
+             alt="{{ p.title | escape }}">
+      {%- endif -%}
+      <div class="dw-spec-grid">
+        <div class="dw-spec-item"><strong>DW SKU:</strong><span>{{ dw_sku | escape }}</span></div>
+        {%- if spec_color != blank -%}<div class="dw-spec-item"><strong>Color:</strong><span>{{ spec_color | escape }}</span></div>{%- endif -%}
+        {%- if p.vendor != blank -%}<div class="dw-spec-item"><strong>Brand:</strong><span>{{ p.vendor | escape }}</span></div>{%- endif -%}
+        {%- assign coll = mf.custom.collection_name | default: mf.global.Collection -%}
+        {%- if coll != blank -%}<div class="dw-spec-item"><strong>Collection:</strong><span>{{ coll | escape }}</span></div>{%- endif -%}
+        {%- assign comp = mf.custom.material | default: mf.global.Content | default: mf.global.Contents -%}
+        {%- if comp != blank -%}<div class="dw-spec-item"><strong>Composition:</strong><span>{{ comp | escape }}</span></div>{%- endif -%}
+        {%- assign w = mf.custom.width | default: mf.global.width | default: mf.global.Width -%}
+        {%- if w != blank -%}<div class="dw-spec-item"><strong>Width:</strong><span>{{ w | escape }}</span></div>{%- endif -%}
+        {%- assign len = mf.custom.length | default: mf.global.length | default: mf.global.Length -%}
+        {%- if len != blank -%}<div class="dw-spec-item"><strong>Length:</strong><span>{{ len | escape }}</span></div>{%- endif -%}
+        {%- assign rep = mf.custom.pattern_repeat | default: mf.global.repeat -%}
+        {%- if rep != blank -%}<div class="dw-spec-item"><strong>Repeat:</strong><span>{{ rep | escape }}</span></div>{%- endif -%}
+        {%- assign mt = mf.custom.match_type | default: mf.global.Match | default: mf.global.MATCH -%}
+        {%- if mt != blank -%}<div class="dw-spec-item"><strong>Match:</strong><span>{{ mt | escape }}</span></div>{%- endif -%}
+        {%- assign wt = mf.custom.product_weight | default: mf.global.Weight -%}
+        {%- if wt != blank -%}<div class="dw-spec-item"><strong>Weight:</strong><span>{{ wt | escape }}</span></div>{%- endif -%}
+        {%- assign fin = mf.custom.finish | default: mf.global.Finish | default: mf.global.FINISH -%}
+        {%- if fin != blank -%}<div class="dw-spec-item"><strong>Finish:</strong><span>{{ fin | escape }}</span></div>{%- endif -%}
+        {%- assign care = mf.custom.care | default: mf.global.Cleaning -%}
+        {%- if care != blank -%}<div class="dw-spec-item"><strong>Care:</strong><span>{{ care | escape }}</span></div>{%- endif -%}
+        {%- assign fire = mf.custom.fire_rating | default: mf.global.fire_rating | default: mf.global.FLAMMABILITY -%}
+        {%- if fire != blank -%}<div class="dw-spec-item"><strong>Fire Rating:</strong><span>{{ fire | escape }}</span></div>{%- endif -%}
+        {%- assign origin = mf.custom.origin | default: mf.global.Country -%}
+        {%- if origin != blank -%}<div class="dw-spec-item"><strong>Country of Origin:</strong><span>{{ origin | escape }}</span></div>{%- endif -%}
+        {%- assign uom = mf.custom.unit_of_measure | default: mf.global.unit_of_measure -%}
+        {%- if uom != blank -%}<div class="dw-spec-item"><strong>Sold:</strong><span>{{ uom | escape }}</span></div>{%- endif -%}
+        {%- if mfr_sku != blank -%}<div class="dw-spec-item"><strong>Extra Info:</strong><span>{{ mfr_sku | escape }}</span></div>{%- endif -%}
+      </div>
+    </div>
+    <div class="dw-spec-footer">
+      <div class="dw-spec-footer-line company">Designer Wallcoverings Purchasing Agency</div>
+      <div class="dw-spec-footer-line">15442 Ventura Bl #102, Sherman Oaks, CA 91403</div>
+      <div class="dw-spec-footer-line">Phone: (888) 373-4564 | info@designerwallcoverings.com | www.designerwallcoverings.com</div>
+      <div class="dw-spec-footer-line italic">Purchasing Agency for the Wallcovering and Surface Material Industry</div>
+      <div class="dw-spec-footer-line tiny">Spec sheet information valid only when purchased through Designer Wallcoverings Purchasing</div>
+    </div>
+  </div>
+</div>
+
+<script>
+(function () {
+  if (window.__dwSpecPdfInit) return;
+  window.__dwSpecPdfInit = true;
+
+  var JSPDF_SRC = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
+  var H2C_SRC   = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js';
+
+  function loadScript(src) {
+    return new Promise(function (resolve, reject) {
+      var existing = document.querySelector('script[src="' + src + '"]');
+      if (existing) {
+        if (existing.dataset.loaded === '1') return resolve();
+        existing.addEventListener('load', function () { resolve(); });
+        existing.addEventListener('error', function () { reject(new Error('load fail ' + src)); });
+        return;
+      }
+      var s = document.createElement('script');
+      s.src = src;
+      s.async = true;
+      s.addEventListener('load', function () { s.dataset.loaded = '1'; resolve(); });
+      s.addEventListener('error', function () { reject(new Error('load fail ' + src)); });
+      document.head.appendChild(s);
+    });
+  }
+
+  function ensureLibs() {
+    var need = [];
+    if (typeof window.html2canvas !== 'function') need.push(loadScript(H2C_SRC));
+    if (!(window.jspdf && window.jspdf.jsPDF)) need.push(loadScript(JSPDF_SRC));
+    return Promise.all(need);
+  }
+
+  // Decode the protocol-relative shopify logo to avoid a tainting surprise; the
+  // product image + logo are both cdn.shopify.com (ACAO:*), so useCORS handles them.
+  async function generate(btn) {
+    var node = document.getElementById('dw-spec-sheet');
+    if (!node) throw new Error('spec node missing');
+    // Make it renderable (html2canvas needs layout; keep it offscreen, not display:none).
+    node.style.zIndex = '-1';
+    node.style.visibility = 'visible';
+
+    await ensureLibs();
+
+    var canvas = await window.html2canvas(node.querySelector('.dw-spec-page'), {
+      scale: 2,
+      useCORS: true,
+      allowTaint: false,
+      backgroundColor: '#ffffff',
+      logging: false,
+      windowWidth: 816
+    });
+
+    var jsPDF = window.jspdf.jsPDF;
+    var imgData = canvas.toDataURL('image/png');
+    var pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'letter' });
+    var imgWidth = 216;
+    var imgHeight = (canvas.height * imgWidth) / canvas.width;
+    pdf.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight);
+
+    var sku = (node.querySelector('.dw-spec-item span') || {}).textContent || 'spec';
+    pdf.save('Designer-Wallcoverings-Los-Angeles-' + sku.trim().replace(/[^A-Za-z0-9\-]+/g, '-') + '.pdf');
+  }
+
+  function onClick(e) {
+    var btn = e.currentTarget;
+    var orig = btn.textContent;
+    btn.textContent = '⏳ Generating...';
+    btn.disabled = true;
+    generate(btn).then(function () {
+      btn.textContent = '✓ Downloaded!';
+      setTimeout(function () { btn.textContent = orig; btn.disabled = false; }, 2000);
+    }).catch(function (err) {
+      console.error('Spec PDF generation failed:', err);
+      btn.textContent = '✗ Error - Try Again';
+      setTimeout(function () { btn.textContent = orig; btn.disabled = false; }, 2500);
+    });
+  }
+
+  function wire() {
+    var btns = document.querySelectorAll('[data-dw-spec-trigger]');
+    for (var i = 0; i < btns.length; i++) {
+      if (btns[i].dataset.dwWired === '1') continue;
+      btns[i].dataset.dwWired = '1';
+      btns[i].addEventListener('click', onClick);
+    }
+  }
+
+  if (document.readyState === 'loading') {
+    document.addEventListener('DOMContentLoaded', wire);
+  } else {
+    wire();
+  }
+})();
+</script>
+
+<style>
+.spec-sheet-controls {
+  display: inline-block;
+  margin: 0;
+}
+.spec-sheet-btn {
+  display: inline-block;
+  padding: 10px 16px;
+  background: #2c5f7f;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 13px;
+  font-weight: 600;
+  transition: all 0.3s ease;
+  white-space: nowrap;
+}
+.spec-sheet-btn:hover {
+  background: #1e4557;
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+}
+.spec-sheet-btn:active { transform: translateY(0); }
+.spec-sheet-btn:disabled { opacity: 0.6; cursor: not-allowed; }
+.pdf-download-btn { vertical-align: top; }
+
+/* Offscreen spec-sheet print styles (rendered by html2canvas, never shown to the user) */
+#dw-spec-sheet * { margin: 0; padding: 0; box-sizing: border-box; }
+#dw-spec-sheet .dw-spec-page { width: 816px; background: #fff; font-family: Arial, Helvetica, sans-serif; }
+#dw-spec-sheet .dw-spec-header { padding: 30px 40px; text-align: center; border-bottom: 2px solid #2c5f7f; }
+#dw-spec-sheet .dw-spec-logo { max-width: 200px; display: block; margin: 0 auto; }
+#dw-spec-sheet .dw-spec-body { padding: 30px 40px; }
+#dw-spec-sheet .dw-spec-title { font-size: 18px; color: #333; margin-bottom: 15px; font-weight: normal; text-align: center; text-transform: capitalize; }
+#dw-spec-sheet .dw-spec-img { max-width: 500px; max-height: 500px; object-fit: contain; display: block; margin: 0 auto 20px; width: 100%; }
+#dw-spec-sheet .dw-spec-grid { max-width: 560px; margin: 0 auto; display: grid; grid-template-columns: 1fr; gap: 10px; }
+#dw-spec-sheet .dw-spec-item { font-size: 14px; line-height: 1.6; display: flex; align-items: baseline; }
+#dw-spec-sheet .dw-spec-item strong { color: #333; font-weight: bold; margin-right: 6px; white-space: nowrap; }
+#dw-spec-sheet .dw-spec-item span { color: #555; flex: 1; }
+#dw-spec-sheet .dw-spec-footer { padding: 20px 40px; text-align: center; border-top: 2px solid #2c5f7f; background: #f9f9f9; }
+#dw-spec-sheet .dw-spec-footer-line { font-size: 10px; color: #666; margin: 3px 0; }
+#dw-spec-sheet .dw-spec-footer-line.company { font-size: 12px; font-weight: bold; color: #333; margin-bottom: 6px; }
+#dw-spec-sheet .dw-spec-footer-line.italic { font-style: italic; margin-top: 10px; }
+#dw-spec-sheet .dw-spec-footer-line.tiny { font-size: 9px; margin-top: 8px; }
+</style>
+
diff --git a/shopify/stage-pdp-variant-deeplink-fix.sh b/shopify/stage-pdp-variant-deeplink-fix.sh
index 77babbb9..fff4e259 100755
--- a/shopify/stage-pdp-variant-deeplink-fix.sh
+++ b/shopify/stage-pdp-variant-deeplink-fix.sh
@@ -49,7 +49,10 @@ if target_tid:
     match = [t for t in themes if str(t["id"]) == str(target_tid)]
 else:
     match = [t for t in themes if t["name"] == target_name]
-match = [t for t in match if t["role"] != "main"]   # NEVER live
+import os
+allow_live = os.environ.get("ALLOW_LIVE") == "1"
+if not allow_live:
+    match = [t for t in match if t["role"] != "main"]   # NEVER live unless ALLOW_LIVE=1 (Steve-gated)
 if not match:
     print("Target dev theme not found (or it is main). Aborting."); sys.exit(1)
 theme = match[0]; tid = theme["id"]

← 88d88f38 auto-save: 2026-06-24T10:56:28 (6 files) — DW-Programming/Im  ·  back to Designer Wallcoverings  ·  Stage DIG- concept-sample -> real $12 sample bulk-write plan 6ba8c81a →