[object Object]

← back to Designer Wallcoverings

fix(theme): product-title heading + color-dot fallback no longer emit rate-limited /search?q= dead links (staged for live theme 144396058675, awaiting deploy)

4b75ccdf1cf795f4f1e9853ba765918d196b2e7e · 2026-07-30 14:58:37 -0700 · steve@designerwallcoverings.com

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 4b75ccdf1cf795f4f1e9853ba765918d196b2e7e
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Thu Jul 30 14:58:37 2026 -0700

    fix(theme): product-title heading + color-dot fallback no longer emit rate-limited /search?q= dead links (staged for live theme 144396058675, awaiting deploy)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 .../backup/color-dots.liquid                       | 245 +++++++
 .../backup/color-palette.liquid                    | 675 +++++++++++++++++
 .../backup/product-form-content.liquid             | 804 +++++++++++++++++++++
 .../theme-titlelink-fix-20260730-144953/deploy.sh  |  31 +
 .../edit/color-dots.liquid                         | 253 +++++++
 .../edit/color-palette.liquid                      | 675 +++++++++++++++++
 .../edit/product-form-content.liquid               | 800 ++++++++++++++++++++
 .../live/color-dots.liquid                         | 245 +++++++
 .../live/color-palette.liquid                      | 675 +++++++++++++++++
 .../live/product-form-content.liquid               | 804 +++++++++++++++++++++
 .../rollback.sh                                    |  24 +
 11 files changed, 5231 insertions(+)

diff --git a/shopify/theme-titlelink-fix-20260730-144953/backup/color-dots.liquid b/shopify/theme-titlelink-fix-20260730-144953/backup/color-dots.liquid
new file mode 100644
index 00000000..17becd29
--- /dev/null
+++ b/shopify/theme-titlelink-fix-20260730-144953/backup/color-dots.liquid
@@ -0,0 +1,245 @@
+{% assign color_dots_json_val = product.metafields.custom.color_dots_json.value %}
+{% assign color_details_val = product.metafields.custom.color_details.value %}
+{% if color_dots_json_val != blank %}
+  {% assign color_details_raw = color_dots_json_val | json %}
+{% elsif color_details_val != blank %}
+  {% assign color_details_raw = color_details_val | json %}
+{% else %}
+  {% assign color_details_raw = blank %}
+{% endif %}
+{% assign color_hex = product.metafields.custom.color_hex.value %}
+{% assign primary_hex = product.metafields.custom.primary_color_hex.value %}
+{% assign ai_colors_raw = product.metafields.dwc.ai_generated_colors.value | json %}
+{% assign color_name = product.metafields.custom.color_name.value %}
+
+{%- comment -%}
+  Color Dots v3 — 3-tier color system
+  Tier 1: color_details metafield (Gemini-enriched)
+  Tier 2: primary_color_hex + ai_generated_colors
+  Tier 3: Canvas pixel extraction from product image (onload)
+{%- endcomment -%}
+
+<div class="dw-color-dots" id="dw-color-dots-wrapper" style="display:flex;justify-content:center;gap:10px;padding:12px 0 8px;flex-wrap:wrap;min-height:30px;">
+  <div id="dw-color-dots-container" style="display:flex;justify-content:center;gap:10px;flex-wrap:wrap;"
+    {% if color_details_raw != blank and color_details_raw != 'null' %}data-colors='{{ color_details_raw | json }}'{% endif %}
+    {% if primary_hex != blank %}data-primary-hex="{{ primary_hex }}"{% endif %}
+    {% if ai_colors_raw != blank and ai_colors_raw != 'null' %}data-ai-colors='{{ ai_colors_raw }}'{% endif %}
+    {% if color_name != blank %}data-color-name="{{ color_name }}"{% endif %}
+    {% if color_hex != blank %}data-color-hex="{{ color_hex }}"{% endif %}
+    {% if product.featured_image %}data-product-image="{{ product.featured_image | img_url: '200x200' }}"{% endif %}
+  ></div>
+</div>
+
+<script>
+(function(){
+  function init() {
+   try {
+    var container = document.getElementById('dw-color-dots-container');
+    if (!container || container.dataset.rendered) return;
+    container.dataset.rendered = '1';
+
+    var C = {
+      'red':'red-wallpaper-collection','crimson':'red-wallpaper-collection','burgundy':'red-wallpaper-collection','wine':'red-wallpaper-collection','maroon':'red-wallpaper-collection',
+      'blue':'blue-wallpaper','navy':'blue-wallpaper','cobalt':'blue-wallpaper','indigo':'blue-wallpaper','sky blue':'blue-wallpaper','steel blue':'blue-wallpaper','royal blue':'blue-wallpaper',
+      'green':'green-wallpaper-collection','sage':'green-wallpaper-collection','olive':'green-wallpaper-collection','forest green':'green-wallpaper-collection','emerald':'green-wallpaper-collection','mint':'green-wallpaper-collection','moss':'green-wallpaper-collection',
+      'gold':'gold-wallpaper','golden':'gold-wallpaper','amber':'gold-wallpaper',
+      'yellow':'yellow-wallpaper-collection','mustard':'yellow-wallpaper-collection',
+      'orange':'orange','rust':'orange','terracotta':'orange',
+      'pink':'pink-wallcovering-2','blush':'pink-wallcovering-2','rose':'pink-wallcovering-2','salmon':'pink-wallcovering-2','fuchsia':'pink-wallcovering-2',
+      'purple':'purple','violet':'purple','plum':'purple','lavender':'purple','lilac':'purple','mauve':'purple',
+      'black':'black','charcoal':'black','ebony':'black','onyx':'black',
+      'white':'white-wallpaper-collection','ivory':'white-wallpaper-collection','cream':'white-wallpaper-collection','pearl':'white-wallpaper-collection','off-white':'white-wallpaper-collection',
+      'gray':'grey-silver','grey':'grey-silver','silver':'grey-silver','slate':'grey-silver',
+      'brown':'brown-wallpaper-collection','chocolate':'brown-wallpaper-collection','espresso':'brown-wallpaper-collection','walnut':'brown-wallpaper-collection','mocha':'brown-wallpaper-collection','sienna':'brown-wallpaper-collection',
+      'beige':'beige-cream-wallpaper-collection','tan':'beige-cream-wallpaper-collection','taupe':'beige-cream-wallpaper-collection','sand':'beige-cream-wallpaper-collection','khaki':'beige-cream-wallpaper-collection','camel':'beige-cream-wallpaper-collection','oatmeal':'beige-cream-wallpaper-collection','linen':'beige-cream-wallpaper-collection',
+      'coral':'coral','peach':'coral',
+      'teal':'turquoise','aqua':'turquoise','turquoise':'turquoise','cyan':'turquoise',
+      'metallic':'metallic-wallpaper-collections','copper':'metallic-wallpaper-collections','bronze':'metallic-wallpaper-collections'
+    };
+
+    var N = {
+      'red':'#DC2626','crimson':'#DC143C','burgundy':'#800020','wine':'#722F37','maroon':'#800000',
+      'blue':'#2563EB','navy':'#1B2A4A','cobalt':'#0047AB','indigo':'#3F51B5','sky blue':'#87CEEB','steel blue':'#4682B4','royal blue':'#4169E1',
+      'green':'#16A34A','sage':'#87AE73','olive':'#808000','forest green':'#228B22','emerald':'#50C878','mint':'#98FF98','moss':'#8A9A5B',
+      'gold':'#D4AF37','golden':'#FFD700','amber':'#FFBF00',
+      'yellow':'#EAB308','mustard':'#FFDB58',
+      'orange':'#EA580C','rust':'#B7410E','terracotta':'#E2725B',
+      'pink':'#EC4899','blush':'#DE5D83','rose':'#FF007F','salmon':'#FA8072','fuchsia':'#FF00FF',
+      'purple':'#9333EA','violet':'#7C3AED','plum':'#8E4585','lavender':'#E6E6FA','lilac':'#C8A2C8','mauve':'#E0B0FF',
+      'black':'#1A1A1A','charcoal':'#36454F','ebony':'#555D50','onyx':'#353839',
+      'white':'#FAFAFA','ivory':'#FFFFF0','cream':'#FFFDD0','pearl':'#F0EAD6','off-white':'#FAF9F6',
+      'gray':'#6B7280','grey':'#6B7280','silver':'#C0C0C0','slate':'#708090',
+      'brown':'#92400E','chocolate':'#7B3F00','espresso':'#3C1414','walnut':'#5C3A1D','mocha':'#967969','sienna':'#A0522D',
+      'beige':'#F5F5DC','tan':'#D2B48C','taupe':'#483C32','sand':'#C2B280','khaki':'#F0E68C','camel':'#C19A6B','oatmeal':'#B8AB8C','linen':'#FAF0E6',
+      'coral':'#FF6F61','peach':'#FFCBA4',
+      'teal':'#0D9488','aqua':'#00FFFF','turquoise':'#40E0D0','cyan':'#06B6D4',
+      'metallic':'#AAA9AD','copper':'#B87333','bronze':'#CD7F32',
+      'deep indigo':'#1B2A4A','dark navy':'#1B2A4A','midnight':'#191970'
+    };
+
+    function findColl(name) {
+      var l = (name||'').toLowerCase();
+      if (C[l]) return C[l];
+      for (var k in C) { if (l.indexOf(k) > -1) return C[k]; }
+      return null;
+    }
+
+    function makeDot(hex, name, pct) {
+      try {
+        var coll = findColl(name);
+        var a = document.createElement('a');
+        a.href = coll ? '/collections/' + coll + '?sort_by=created-descending' : '/search?q=' + encodeURIComponent(name||hex) + '&sort_by=created-descending';
+        a.title = (coll?'Shop ':'Search ') + (name||'color') + ' (' + hex + ')';
+        a.style.textDecoration = 'none';
+        var d = document.createElement('div');
+        var p = (pct == null ? 20 : pct);
+        var sz=Math.max(22,Math.min(52,16+p*0.6));d.style.cssText='width:'+sz+'px;height:'+sz+'px;border-radius:50%;background:'+hex+';border:2px solid #eee;box-shadow:0 1px 3px rgba(0,0,0,0.1);cursor:pointer;transition:transform 0.15s,box-shadow 0.15s;flex-shrink:0;';
+        d.onmouseenter = function(){this.style.transform='scale(1.3)';this.style.boxShadow='0 2px 8px rgba(0,0,0,0.2)';};
+        d.onmouseleave = function(){this.style.transform='scale(1)';this.style.boxShadow='0 1px 3px rgba(0,0,0,0.1)';};
+        a.appendChild(d);
+        return a;
+      } catch(e) { return null; }
+    }
+
+    function renderDots(colors) {
+      colors.forEach(function(c){ var el = makeDot(c.hex, c.name, (c.pct || c.percentage)); if (el) container.appendChild(el); });
+    }
+
+    /* Canvas color refs */
+    var REF = {
+      'red':[220,38,38],'navy':[27,42,74],'blue':[37,99,235],'green':[22,163,74],'sage':[135,174,115],
+      'gold':[212,175,55],'yellow':[234,179,8],'orange':[234,88,12],'pink':[236,72,153],
+      'purple':[147,51,234],'black':[26,26,26],'charcoal':[54,69,79],'white':[250,250,250],
+      'ivory':[255,255,240],'cream':[255,253,208],'gray':[107,114,128],'silver':[192,192,192],
+      'brown':[146,64,14],'beige':[245,245,220],'tan':[210,180,140],'taupe':[72,60,50],
+      'teal':[13,148,136],'coral':[255,111,97],'olive':[128,128,0],'rust':[183,65,14],
+      'burgundy':[128,0,32],'slate':[112,128,144],'sand':[194,178,128],'mint':[152,255,152],
+      'turquoise':[64,224,208],'copper':[184,115,51],'bronze':[205,127,50],'mauve':[224,176,255]
+    };
+
+    function closestName(r,g,b) {
+      var best='',bestD=1e9;
+      for(var n in REF){var c=REF[n],d=Math.sqrt(Math.pow(r-c[0],2)+Math.pow(g-c[1],2)+Math.pow(b-c[2],2));if(d<bestD){bestD=d;best=n;}}
+      return best;
+    }
+    function rgbHex(r,g,b){return '#'+[r,g,b].map(function(x){return x.toString(16).padStart(2,'0');}).join('');}
+
+    function extractFromCanvas(imgUrl) {
+      var img = new Image();
+      img.crossOrigin = 'anonymous';
+      img.onload = function() {
+        try {
+        var cv = document.createElement('canvas'), sz = 50;
+        cv.width=sz; cv.height=sz;
+        var ctx=cv.getContext('2d');
+        ctx.drawImage(img,0,0,sz,sz);
+        var px=ctx.getImageData(0,0,sz,sz).data;
+        var bk={};
+        for(var i=0;i<px.length;i+=4){
+          var kr=Math.round(px[i]/32)*32,kg=Math.round(px[i+1]/32)*32,kb=Math.round(px[i+2]/32)*32;
+          var key=kr+','+kg+','+kb;
+          if(!bk[key])bk[key]={r:0,g:0,b:0,n:0};
+          bk[key].r+=px[i];bk[key].g+=px[i+1];bk[key].b+=px[i+2];bk[key].n++;
+        }
+        var sorted=Object.values(bk).sort(function(a,b){return b.n-a.n;});
+        var tot=sz*sz,colors=[],seen={};
+        for(var j=0;j<Math.min(sorted.length,8);j++){
+          var s=sorted[j],ar=Math.round(s.r/s.n),ag=Math.round(s.g/s.n),ab=Math.round(s.b/s.n);
+          var nm=closestName(ar,ag,ab);
+          if(seen[nm])continue; seen[nm]=true;
+          colors.push({name:nm,hex:rgbHex(ar,ag,ab),percentage:Math.round(s.n/tot*100)});
+          if(colors.length>=5)break;
+        }
+        if(colors.length>0) renderDots(colors);
+        } catch(e) {
+          /* CORS or canvas security error — try without crossOrigin */
+          var img2 = new Image();
+          img2.onload = function() {
+            try {
+              var cv2 = document.createElement('canvas'), sz2 = 50;
+              cv2.width=sz2; cv2.height=sz2;
+              var ctx2=cv2.getContext('2d');
+              ctx2.drawImage(img2,0,0,sz2,sz2);
+              /* Cannot getImageData without CORS — use image dominant color via 1x1 scale */
+              cv2.width=1; cv2.height=1;
+              ctx2.drawImage(img2,0,0,1,1);
+              var p=ctx2.getImageData(0,0,1,1).data;
+              var nm=closestName(p[0],p[1],p[2]);
+              renderDots([{name:nm,hex:rgbHex(p[0],p[1],p[2]),percentage:100}]);
+            } catch(e2) {}
+          };
+          img2.src = imgUrl;
+        }
+      };
+      img.onerror = function(){};
+      img.src = imgUrl;
+    }
+
+    function getImageUrl() {
+      var url = container.dataset.productImage;
+      if (url && url.length > 5) return url.indexOf('//') === 0 ? 'https:' + url : url;
+      var sels = [
+        '.product-gallery--viewport img[src]',
+        '.product-gallery img[src]',
+        '[data-product-gallery] img[src]',
+        '.product-gallery--viewport--figure img[src]',
+        '.product__media img[src]',
+        '.product-single__photo img[src]'
+      ];
+      for (var i = 0; i < sels.length; i++) {
+        var el = document.querySelector(sels[i]);
+        if (el && el.src) return el.src;
+      }
+      return null;
+    }
+
+    /* ── Tier 1: color_details metafield ── */
+    var colorsData = container.dataset.colors;
+    if (colorsData) {
+      try {
+        /* Fix Shopify Liquid Ruby hash syntax: => becomes : */
+        var fixed = colorsData.replace(/=>/g, ':');
+        renderDots(JSON.parse(fixed));
+        return;
+      } catch(e) {}
+    }
+
+    /* ── Tier 2: primary_color_hex + ai_generated_colors ── */
+    var ph = container.dataset.primaryHex;
+    var aiRaw = container.dataset.aiColors;
+    if (ph || aiRaw) {
+      var added = {};
+      if (ph) { var pd=makeDot(ph, container.dataset.colorName||'primary'); if(pd)container.appendChild(pd); added[ph.toLowerCase()]=true; }
+      try {
+        var ai = JSON.parse((aiRaw||'[]').replace(/=>/g, ':'));
+        ai.forEach(function(nm){ var h=N[nm.toLowerCase()]; if(h&&!added[h.toLowerCase()]){var ad=makeDot(h,nm); if(ad)container.appendChild(ad);added[h.toLowerCase()]=true;} });
+      } catch(e) {}
+      if (container.children.length > 0) return;
+    }
+
+    /* ── Tier 2.5: single color_hex ── */
+    var sh = container.dataset.colorHex;
+    if (sh) { var shd=makeDot(sh, container.dataset.colorName||'color'); if(shd)container.appendChild(shd); return; }
+
+    /* ── Tier 3: Canvas extraction from product image ── */
+    var imgUrl = getImageUrl();
+    if (imgUrl) {
+      extractFromCanvas(imgUrl);
+    } else {
+      var obs = new MutationObserver(function(m, o) {
+        var url = getImageUrl();
+        if (url) { o.disconnect(); extractFromCanvas(url); }
+      });
+      obs.observe(document.body, { childList: true, subtree: true });
+      setTimeout(function(){ obs.disconnect(); }, 5000);
+    }
+   } catch(e) { /* never let color-dot rendering abort other page init */ }
+  }
+
+  if (document.readyState === 'loading') {
+    document.addEventListener('DOMContentLoaded', init);
+  } else {
+    init();
+  }
+})();
+</script>
diff --git a/shopify/theme-titlelink-fix-20260730-144953/backup/color-palette.liquid b/shopify/theme-titlelink-fix-20260730-144953/backup/color-palette.liquid
new file mode 100644
index 00000000..d6f2f1e0
--- /dev/null
+++ b/shopify/theme-titlelink-fix-20260730-144953/backup/color-palette.liquid
@@ -0,0 +1,675 @@
+{% comment %}
+  color-palette — "Colors In This Pattern" swatch spectrum.
+  Shows the pattern's real colors AND interpolated in-between colors, laid out as
+  uniform swatches centered in even rows. The TOTAL rendered dots are driven by a
+  user DENSITY SLIDER (Steve 2026-07-09d: "allow the user to grid slide the amount
+  of dots that they can see.. expand.. contract"): every real base color is kept,
+  and the remaining budget of interpolated dots is spread EVENLY across the gaps
+  (never truncating one end of the spectrum). The slider runs MIN_DOTS(6) →
+  MAX_DOTS(64) with DEFAULT 20 (the former hard cap became the default value),
+  re-renders live on drag with NO page reload, and persists to localStorage under
+  'dw_palette_dot_count' (a palette-specific key, distinct from the product-grid
+  density slider). (was Steve 2026-07-09c: a fixed 20-dot cap, MAX_SWATCHES=20.)
+
+  FIX PASS (Steve 2026-07-09, contrarian FIX-FIRST): (1) the slider 'input' handler
+  DEBOUNCES the expensive paintSpectrum() re-render ~80ms so a continuous drag doesn't
+  thrash the whole dot spectrum every tick — the numeric "Colors shown" output and the
+  minus/plus buttons still update IMMEDIATELY for snappy feedback; persistence stays on
+  'change' (drag-release). (2) When a pattern has only ONE dominant base color there is
+  genuinely nothing to interpolate, so instead of silently HIDING the control (a dead
+  end that leaves the user wondering), it stays VISIBLE but DISABLED with a title/aria
+  hint ("Only one dominant color in this pattern").
+
+  DOT CLICK TARGET (Steve 2026-07-09b): "bring up an index of many images, not
+  just that exact hex. tolerance 10% for color to pull more." A swatch click now
+  opens an IN-PAGE expanding grid of MANY products whose dominant color is within
+  a perceptual 10% tolerance (CIELAB ΔE76) of the clicked hex — CATALOG-WIDE, not
+  one collection. The grid is served by the color-index endpoint
+    POST https://photo.designerwallcoverings.com/apps/color-index  {hex, k}
+  (dwphoto → data/color-index.json, one dominant color per ACTIVE product with
+  precomputed LAB). Cards link to /products/<handle> (the roll variant, never the
+  $4.25 Sample). The family-collection href stays as the plain href so
+  middle-click / new-tab / crawlers still get a valid destination, and is the
+  graceful fallback if the endpoint is unreachable.
+  (was: Steve 2026-07-09a, a click deep-linked to the ONE nearest product+variant.)
+
+  Data tiers (mirrors color-dots.liquid, kept in sync):
+    Tier 1  custom.color_dots_json / custom.color_details  (enriched JSON: [{hex,name,pct}])
+    Tier 2  custom.primary_color_hex + dwc.ai_generated_colors (name list → hex map)
+    Tier 3  custom.color_hex (single)
+    Tier 4  Canvas pixel extraction from the product image (always yields real % — reliable fallback)
+{% endcomment %}
+
+{% comment %} Read the authoritative enriched field FIRST (color_details); legacy
+   color_dots_json is only a fallback so it can never shadow fresh enrichment. {% endcomment %}
+{% assign color_dots_json_val = product.metafields.custom.color_dots_json.value %}
+{% assign color_details_val = product.metafields.custom.color_details.value %}
+{% if color_details_val != blank %}
+  {% assign color_details_raw = color_details_val | json %}
+{% elsif color_dots_json_val != blank %}
+  {% assign color_details_raw = color_dots_json_val | json %}
+{% else %}
+  {% assign color_details_raw = blank %}
+{% endif %}
+{% assign color_hex = product.metafields.custom.color_hex.value %}
+{% assign primary_hex = product.metafields.custom.primary_color_hex.value %}
+{% assign ai_colors_raw = product.metafields.dwc.ai_generated_colors.value | json %}
+{% assign color_name = product.metafields.custom.color_name.value %}
+
+<div class="dw-color-palette" data-dw-color-palette
+  {% if color_details_raw != blank and color_details_raw != 'null' %}data-colors='{{ color_details_raw | escape }}'{% endif %}
+  {% if primary_hex != blank %}data-primary-hex="{{ primary_hex }}"{% endif %}
+  {% if ai_colors_raw != blank and ai_colors_raw != 'null' %}data-ai-colors='{{ ai_colors_raw }}'{% endif %}
+  {% if color_name != blank %}data-color-name="{{ color_name }}"{% endif %}
+  {% if color_hex != blank %}data-color-hex="{{ color_hex }}"{% endif %}
+  {% if product.featured_image %}data-product-image="{{ product.featured_image | img_url: '400x400' }}"{% endif %}
+  data-product-style="{{ product.metafields.custom.style.value | default: product.type | escape }}"
+>
+  <p class="dw-color-palette__title">Select Color for Coordinates</p>
+
+  {%- comment -%} COLOR BAR (Steve 2026-07-28): the palette renders as ONE large
+    horizontal color BAR (was individual dots), ~4× the old dot height. Drag the pill
+    handle on either END of the bar to EXPAND / CONTRACT how many colors it shows —
+    this replaces the old "Colors shown" slider. The count persists to localStorage
+    'dw_palette_dot_count'. Single-dominant-color palettes hide the handles (nothing
+    to expand). {%- endcomment -%}
+  <div class="dw-color-palette__barwrap" data-dw-cp-barwrap hidden>
+    <span class="dw-cp-handle dw-cp-handle--l" data-dw-cp-handle="l" role="slider" tabindex="0"
+          aria-label="Drag to change how many colors are shown" aria-valuemin="6" aria-valuemax="64"><i aria-hidden="true"></i></span>
+    <div class="dw-color-palette__cards" data-dw-color-palette-cards aria-live="polite"></div>
+    <span class="dw-cp-handle dw-cp-handle--r" data-dw-cp-handle="r" role="slider" tabindex="0"
+          aria-label="Drag to change how many colors are shown" aria-valuemin="6" aria-valuemax="64"><i aria-hidden="true"></i></span>
+  </div>
+
+  {%- comment -%} In-page color-index grid the dot brings up (Steve 2026-07-09b).
+    Hidden until a swatch is clicked; then filled with the catalog-wide products
+    within 10% (CIELAB ΔE76) of the clicked hex. {%- endcomment -%}
+  <div class="dw-color-index" data-dw-color-index hidden
+       data-endpoint="https://photo.designerwallcoverings.com/apps/color-index">
+    <div class="dw-color-index__head">
+      <span class="dw-color-index__swatch" data-dw-ci-swatch></span>
+      <span class="dw-color-index__label" data-dw-ci-label aria-live="polite"></span>
+      <button type="button" class="dw-color-index__close" data-dw-ci-close aria-label="Close color index">&times;</button>
+    </div>
+    <div class="dw-color-index__grid" data-dw-ci-grid aria-live="polite"></div>
+    {%- comment -%} Steve 2026-07-28: reveal +2 more rows of coordinates per click (refresh). {%- endcomment -%}
+    <button type="button" class="dw-color-index__more" data-dw-ci-more hidden aria-label="Show two more rows of coordinating wallcoverings">Show 2 more rows</button>
+  </div>
+</div>
+
+<style>
+  /* Steve 2026-07-28: relocated to a FULL-WIDTH section below the two columns. */
+  .dw-color-palette{padding:18px 0 12px;text-align:center;min-height:10px;max-width:1200px;margin:0 auto;}
+  .dw-color-palette__title{font-family:Lora,serif;font-size:15px;font-weight:500;color:#3D4246;margin:0 0 4px;letter-spacing:.01em;}
+  /* ── COLOR BAR (Steve 2026-07-28): dots → ONE large horizontal bar, ~4× the old
+     dot height, with a drag handle on each END to expand/contract the color count.
+     Replaces the density slider + the names/% legend + the thin proportion bar. ── */
+  .dw-color-palette__barwrap{position:relative;width:100%;max-width:1200px;margin:8px auto 0;box-sizing:border-box;}
+  .dw-color-palette__barwrap[hidden]{display:none;}
+  /* the bar itself: contiguous full-width color segments, no gaps, rounded ends */
+  .dw-color-palette__cards{display:flex;flex-wrap:nowrap;align-items:stretch;gap:0;width:100%;height:76px;border-radius:9px;overflow:hidden;box-shadow:inset 0 0 0 1px rgba(0,0,0,.06),0 1px 4px rgba(0,0,0,.08);}
+  .dw-swatch{flex:1 1 0;min-width:0;height:100%;display:block;border-radius:0;box-sizing:border-box;text-decoration:none;box-shadow:none;transition:filter .12s ease,box-shadow .12s ease;position:relative;}
+  a.dw-swatch{cursor:pointer;}
+  .dw-swatch:hover{filter:brightness(1.07);box-shadow:inset 0 0 0 2px rgba(255,255,255,.55),inset 0 0 0 3px rgba(0,0,0,.12);z-index:3;}
+  /* base (real pattern) colors read the same in a contiguous bar — no ring/scale */
+  .dw-swatch--base{box-shadow:none;}
+  .dw-swatch--base:hover{filter:brightness(1.07);box-shadow:inset 0 0 0 2px rgba(255,255,255,.6),inset 0 0 0 3px rgba(0,0,0,.16);z-index:3;}
+  /* END drag handles — pill grips that protrude past the bar; drag horizontally to
+     expand (outward) / contract (inward) the color count, persisted to localStorage. */
+  .dw-cp-handle{position:absolute;top:50%;transform:translateY(-50%);width:15px;height:94px;background:#fff;border:1px solid #c9c4b8;border-radius:8px;box-shadow:0 1px 5px rgba(0,0,0,.20);cursor:ew-resize;display:flex;align-items:center;justify-content:center;z-index:6;touch-action:none;-webkit-user-select:none;user-select:none;}
+  .dw-cp-handle i{display:block;width:2px;height:38px;background:#b0aa9c;border-radius:2px;box-shadow:4px 0 0 #b0aa9c,-4px 0 0 #b0aa9c;}
+  .dw-cp-handle--l{left:-6px;}
+  .dw-cp-handle--r{right:-6px;}
+  .dw-cp-handle:hover{border-color:#a8a294;}
+  .dw-cp-handle:focus-visible{outline:2px solid #b8b3a8;outline-offset:3px;}
+  .dw-color-palette__barwrap.is-single .dw-cp-handle{display:none;} /* one dominant color: nothing to expand */
+  .dw-color-palette:empty{display:none;}
+  @media (max-width:600px){
+    .dw-color-palette__cards{height:58px;border-radius:7px;}
+    .dw-cp-handle{height:74px;width:14px;}
+    .dw-cp-handle i{height:30px;}
+  }
+  /* ── In-page color-index grid the dot brings up — refined to read as a curated
+     "shop this colorway" drawer: hairline-ruled header, Lora title at the palette
+     scale, soft reveal, even gutters, hairline media. (refine 2026-07-09) ── */
+  /* Steve 2026-07-28: coordinate results serve FULL-WIDTH, exactly 2 rows; extra
+     chips are clipped (grid-auto-rows:0 + overflow:hidden) so the UI can never overflow. */
+  .dw-color-index{max-width:1200px;margin:22px auto 6px;text-align:left;border-top:1px solid #ece9e3;padding-top:16px;}
+  .dw-color-index[hidden]{display:none;}
+  @keyframes dwCiReveal{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}
+  .dw-color-index.is-open{animation:dwCiReveal .28s ease both;}
+  .dw-color-index__head{display:flex;align-items:center;gap:10px;margin:0 0 14px;}
+  .dw-color-index__swatch{width:22px;height:22px;border-radius:5px;flex:0 0 auto;box-shadow:0 0 0 1px rgba(0,0,0,.14),inset 0 -3px 5px rgba(0,0,0,.06);}
+  .dw-color-index__label{font-family:Lora,serif;font-size:14px;color:#3D4246;flex:1 1 auto;letter-spacing:.01em;}
+  .dw-color-index__label b{font-weight:600;text-transform:capitalize;}
+  .dw-color-index__close{flex:0 0 auto;background:none;border:1px solid #e0ded9;border-radius:50%;width:26px;height:26px;line-height:1;font-size:15px;color:#8a8577;cursor:pointer;padding:0;transition:color .12s ease,border-color .12s ease;}
+  .dw-color-index__close:hover{color:#3D4246;border-color:#b8b3a8;}
+  .dw-color-index__close:focus-visible{outline:2px solid #b8b3a8;outline-offset:2px;}
+  .dw-color-index__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));grid-template-rows:repeat(2,auto);grid-auto-rows:0;gap:16px;overflow:hidden;}
+  @media(min-width:1000px){.dw-color-index__grid{grid-template-columns:repeat(8,1fr);}}
+  /* "Show 2 more rows" — reveals the next 2 rows of coordinates each click (Steve 2026-07-28) */
+  .dw-color-index__more{display:block;margin:16px auto 2px;background:none;border:1px solid #d8d4cc;border-radius:999px;padding:8px 20px;font-family:Lora,serif;font-size:12px;letter-spacing:.05em;color:#3D4246;cursor:pointer;transition:border-color .12s ease,color .12s ease;}
+  .dw-color-index__more::before{content:"\21bb  ";}
+  .dw-color-index__more:hover{border-color:#b8b3a8;color:#000;}
+  .dw-color-index__more:focus-visible{outline:2px solid #b8b3a8;outline-offset:2px;}
+  .dw-color-index__more[hidden]{display:none;}
+  .dw-color-index__card{display:block;text-decoration:none;color:inherit;min-width:0!important;} /* FORCE equal-size cards — min-width:0 defeats the grid min-content blowout */
+  .dw-color-index__card img{width:100%!important;height:auto!important;aspect-ratio:1/1!important;object-fit:cover!important;display:block!important;min-width:0!important;box-shadow:0 0 0 1px #ece9e3;border-radius:3px;background:#f4f2ee;transition:box-shadow .14s ease;}
+  .dw-color-index__card:hover img{box-shadow:0 0 0 1px #b8b3a8,0 3px 12px rgba(0,0,0,.10);}
+  .dw-color-index__cap{display:block;font-family:Lora,serif;font-size:11px;color:#3D4246;margin-top:7px;line-height:1.3;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;text-transform:capitalize;}
+  .dw-color-index__card:hover .dw-color-index__cap{text-decoration:underline;text-decoration-color:#d8d4cc;}
+  .dw-color-index__cap i{display:block;font-style:normal;color:#8a8577;font-size:10px;text-transform:none;margin-top:1px;letter-spacing:.02em;}
+  .dw-color-index__empty,.dw-color-index__loading{font-family:Lora,serif;font-size:12px;color:#8a8577;padding:8px 0;}
+  @media (max-width:600px){
+    .dw-color-index__grid{grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:8px;}
+  }
+</style>
+
+<script>
+(function(){
+  function init(){
+    var root = document.querySelector('[data-dw-color-palette]');
+    if(!root || root.dataset.rendered) return;
+    var cards = root.querySelector('[data-dw-color-palette-cards]');
+    if(!cards) return;
+    root.dataset.rendered = '1';
+
+    /* name -> hex fallback map (kept in sync with color-dots.liquid) */
+    var N = {
+      'red':'#DC2626','crimson':'#DC143C','burgundy':'#800020','wine':'#722F37','maroon':'#800000',
+      'blue':'#2563EB','navy':'#1B2A4A','cobalt':'#0047AB','indigo':'#3F51B5','sky blue':'#87CEEB','steel blue':'#4682B4','royal blue':'#4169E1','blue frost':'#C4D3E0','deep indigo':'#1B2A4A','dark navy':'#1B2A4A','midnight':'#191970',
+      'green':'#16A34A','sage':'#87AE73','olive':'#808000','forest green':'#228B22','emerald':'#50C878','mint':'#98FF98','moss':'#8A9A5B','sea glass':'#C6D8CE',
+      'gold':'#D4AF37','golden':'#FFD700','amber':'#FFBF00',
+      'yellow':'#EAB308','mustard':'#FFDB58',
+      'orange':'#EA580C','rust':'#B7410E','terracotta':'#E2725B','melon':'#FDBCB4','soft melon':'#F6C9A8',
+      'pink':'#EC4899','blush':'#DE5D83','rose':'#FF007F','salmon':'#FA8072','fuchsia':'#FF00FF',
+      'purple':'#9333EA','violet':'#7C3AED','plum':'#8E4585','lavender':'#E6E6FA','lilac':'#C8A2C8','mauve':'#E0B0FF',
+      'black':'#1A1A1A','charcoal':'#36454F','ebony':'#555D50','onyx':'#353839',
+      'white':'#FAFAFA','ivory':'#FFFFF0','cream':'#FFFDD0','pearl':'#F0EAD6','off-white':'#FAF9F6','eggshell':'#F0EAD6',
+      'gray':'#6B7280','grey':'#6B7280','silver':'#C0C0C0','slate':'#708090','metallic silver':'#BFC1C2',
+      'brown':'#92400E','chocolate':'#7B3F00','espresso':'#3C1414','walnut':'#5C3A1D','mocha':'#967969','sienna':'#A0522D',
+      'beige':'#F5F5DC','tan':'#D2B48C','taupe':'#483C32','sand':'#C2B280','khaki':'#F0E68C','camel':'#C19A6B','oatmeal':'#B8AB8C','linen':'#FAF0E6',
+      'coral':'#FF6F61','peach':'#FFCBA4',
+      'teal':'#0D9488','aqua':'#00FFFF','turquoise':'#40E0D0','cyan':'#06B6D4',
+      'metallic':'#AAA9AD','copper':'#B87333','bronze':'#CD7F32'
+    };
+
+    function pctOf(c){ var p = (c.pct!=null?c.pct:(c.percentage!=null?c.percentage:null)); if(p==null) return null; p=parseFloat(p); return isNaN(p)?null:Math.round(p); }
+
+    /* ── HEX → color-family COLLECTION (handles verified live 2026-07-08) ──────
+       Derive the family from the REAL measured hex and link to that family's
+       collection. blue-wallpaper & grey-silver corrected from the old dead
+       handles (blue-wallpaper-collection / grey-wallcovering, which 404'd). */
+    var FAM_COLL = {
+      red:'red-wallpaper-collection', orange:'orange', yellow:'yellow-wallpaper-collection',
+      gold:'gold-wallpaper', green:'green-wallpaper-collection', teal:'turquoise',
+      blue:'blue-wallpaper', purple:'purple', pink:'pink-wallcovering-2',
+      brown:'brown-wallpaper-collection', beige:'beige-cream-wallpaper-collection',
+      black:'black', white:'white-wallpaper-collection', gray:'grey-silver'
+    };
+    function hexToRgb(h){
+      h=(h||'').replace('#','');
+      if(h.length===3) h=h.split('').map(function(x){return x+x;}).join('');
+      if(h.length<6) return null;
+      var r=parseInt(h.slice(0,2),16),g=parseInt(h.slice(2,4),16),b=parseInt(h.slice(4,6),16);
+      return (isNaN(r)||isNaN(g)||isNaN(b))?null:[r,g,b];
+    }
+    function familyFromHex(hex){
+      var rgb=hexToRgb(hex); if(!rgb) return null;
+      var r=rgb[0]/255,g=rgb[1]/255,b=rgb[2]/255;
+      var mx=Math.max(r,g,b),mn=Math.min(r,g,b),d=mx-mn;
+      var l=(mx+mn)/2, s=d===0?0:d/(1-Math.abs(2*l-1));
+      var hdeg=0;
+      if(d!==0){
+        if(mx===r) hdeg=60*(((g-b)/d)%6);
+        else if(mx===g) hdeg=60*(((b-r)/d)+2);
+        else hdeg=60*(((r-g)/d)+4);
+      }
+      if(hdeg<0) hdeg+=360;
+      if(s<0.18){
+        if(l>0.86) return 'white';
+        if(l<0.18) return 'black';
+        if((hdeg>=20 && hdeg<=70) && l>0.30) return 'beige';
+        return 'gray';
+      }
+      if(l>0.80 && (hdeg>=20 && hdeg<70)) return 'beige';
+      if(l<0.40 && s<0.75 && ((hdeg>=10 && hdeg<70) || hdeg>=345)) return 'brown';
+      if(l<0.16) return 'black';
+      if(hdeg<15||hdeg>=345) return 'red';
+      if(hdeg<40){ return (s<0.45 && l>0.55) ? 'beige' : 'orange'; }
+      if(hdeg<55){ return (s<0.45) ? 'beige' : (hdeg<50 ? 'gold' : 'yellow'); }
+      if(hdeg<65) return (l<0.45) ? 'gold' : 'yellow';
+      if(hdeg<170) return 'green';
+      if(hdeg<200) return 'teal';
+      if(hdeg<255) return 'blue';
+      if(hdeg<290) return 'purple';
+      return 'pink';
+    }
+    var PRODUCT_STYLE = (root.dataset.productStyle||'').trim();
+    /* The color-FAMILY collection handle for a hex (used both as the search space
+       for the nearest-specific-product resolver AND as the graceful fallback). */
+    function familyHandle(hex){
+      var fam=familyFromHex(hex); if(!fam) return null;
+      return FAM_COLL[fam] || null;
+    }
+    /* Fallback collection URL — only used if we can't resolve a single specific
+       product of that hue (edge case). (Steve 2026-07-09: DEFAULT must be a
+       specific product+variant, this is the graceful floor.) */
+    function familyHref(hex){
+      var handle=familyHandle(hex); if(!handle) return null;
+      var url='/collections/'+handle+'?sort_by=created-descending';
+      if(PRODUCT_STYLE){ url+='&filter.p.m.custom.style='+encodeURIComponent(PRODUCT_STYLE); }
+      return url;
+    }
+
+    /* ── hex → IN-PAGE INDEX of MANY products within 10% color tolerance ───────
+       Steve 2026-07-09b: "bring up an index of many images, not just that exact
+       hex. tolerance 10% for color to pull more." A dot is a palette-extracted
+       hue; clicking it opens the in-page grid (below the palette) of products
+       whose dominant color is within a perceptual 10% tolerance (CIELAB ΔE76) of
+       the clicked hex, drawn CATALOG-WIDE from the color-index endpoint. Results
+       are cached per hex so re-clicking a swatch is instant. */
+    var CI = root.querySelector('[data-dw-color-index]');
+    var CI_ENDPOINT = (CI && CI.dataset.endpoint) || 'https://photo.designerwallcoverings.com/apps/color-index';
+    var CI_K = 48;                 // fetch up to ~6 rows; show 2, reveal +2 per "Show 2 more rows" click (Steve 2026-07-28)
+    var _ciCache = {};             // hex -> Promise<{results,total,tolerance_pct}>
+    function fetchColorIndex(hex){
+      if(_ciCache[hex]) return _ciCache[hex];
+      _ciCache[hex]=fetch(CI_ENDPOINT,{
+        method:'POST', mode:'cors', headers:{'Content-Type':'application/json'},
+        body:JSON.stringify({hex:hex, k:CI_K})
+      }).then(function(r){ return r.ok?r.json():{results:[]}; })
+        .catch(function(){ return {results:[], _err:true}; });
+      return _ciCache[hex];
+    }
+    function ciEl(sel){ return CI ? CI.querySelector(sel) : null; }
+    function closeIndex(){ if(CI){ CI.classList.remove('is-open'); CI.hidden=true; } }
+    /* ── "Show 2 more rows" (Steve 2026-07-28): the coordinate grid is clipped to N
+       visible rows (grid-template-rows + overflow:hidden) so it can never overflow the
+       page; each click of the refresh button reveals +2 more rows. The button hides
+       itself once every fetched row is visible. Row height is known up-front (cards use
+       aspect-ratio:1) so scrollHeight vs clientHeight is a reliable "more remain?" test. */
+    var CI_ROW_STEP = 2, CI_START_ROWS = 2;
+    function ciApplyRows(){
+      var grid = ciEl('[data-dw-ci-grid]'); if(!grid) return;
+      var rows = parseInt(grid.getAttribute('data-rows') || String(CI_START_ROWS), 10);
+      // Hide extra cards by COUNT (rows × columns) so the grid flows to EXACTLY N clean
+      // rows — no partial 3rd-row sliver from row-gap the old overflow-clip left behind.
+      var cols = (getComputedStyle(grid).gridTemplateColumns || '').split(' ').filter(Boolean).length || 1;
+      var visible = rows * cols, hiddenAny = false;
+      for(var i=0;i<grid.children.length;i++){
+        var show = i < visible;
+        grid.children[i].style.display = show ? '' : 'none';
+        if(!show) hiddenAny = true;
+      }
+      // JS controls visibility by count, so let the grid flow naturally (no clip artefacts)
+      grid.style.gridTemplateRows = 'none';
+      grid.style.gridAutoRows = 'auto';
+      grid.style.overflow = 'visible';
+      var more = ciEl('[data-dw-ci-more]');
+      if(more){ more.hidden = !hiddenAny; }
+    }
+    if(CI){
+      var _more = ciEl('[data-dw-ci-more]');
+      if(_more) _more.addEventListener('click', function(){
+        var grid = ciEl('[data-dw-ci-grid]'); if(!grid) return;
+        var rows = parseInt(grid.getAttribute('data-rows') || String(CI_START_ROWS), 10) + CI_ROW_STEP;
+        grid.setAttribute('data-rows', String(rows));
+        ciApplyRows();
+        try{ _more.scrollIntoView({behavior:'smooth', block:'nearest'}); }catch(e){}
+      });
+      var _cx=ciEl('[data-dw-ci-close]');
+      if(_cx) _cx.addEventListener('click', closeIndex);
+      // Esc closes the drawer when it (or something inside it) is focused (a11y).
+      CI.addEventListener('keydown', function(ev){ if(ev.key==='Escape'){ closeIndex(); } });
+    }
+    /* Open the in-page index for a clicked hex. */
+    function openIndex(hex, name){
+      if(!CI) return;
+      var sw=ciEl('[data-dw-ci-swatch]'), lbl=ciEl('[data-dw-ci-label]'), grid=ciEl('[data-dw-ci-grid]');
+      var fam=familyFromHex(hex)||'';
+      if(sw) sw.style.background=hex;
+      if(lbl) lbl.innerHTML='Wallcoverings in this color — <b>'+(name||fam||hex)+'</b>';
+      if(grid) grid.innerHTML='<div class="dw-color-index__loading">Finding matches…</div>';
+      CI.hidden=false;
+      // retrigger the soft reveal even on a re-click (refine 2026-07-09)
+      CI.classList.remove('is-open'); void CI.offsetWidth; CI.classList.add('is-open');
+      CI.scrollIntoView({behavior:'smooth', block:'nearest'});
+      var _cl=ciEl('[data-dw-ci-close]'); if(_cl){ try{ _cl.focus({preventScroll:true}); }catch(e){} }
+      fetchColorIndex(hex).then(function(d){
+        if(!grid) return;
+        var res=(d&&d.results)||[];
+        if(!res.length){
+          grid.innerHTML='';
+          var empty=document.createElement('div'); empty.className='dw-color-index__empty';
+          empty.textContent = d && d._err
+            ? 'Could not load matches right now — try another color.'
+            : 'No close matches for this color yet.';
+          grid.appendChild(empty);
+          return;
+        }
+        grid.innerHTML='';
+        var frag=document.createDocumentFragment();
+        res.forEach(function(p){
+          if(!p || !p.handle) return;
+          var a=document.createElement('a'); a.className='dw-color-index__card'; a.rel='nofollow';
+          // link straight to the PDP handle — the roll/first-available variant,
+          // never the $4.25 Sample (Sample is never the default variant).
+          a.href='/products/'+encodeURIComponent(p.handle);
+          var img=document.createElement('img'); img.loading='lazy';
+          img.src=p.image||''; img.alt=p.title||'';
+          var cap=document.createElement('span'); cap.className='dw-color-index__cap';
+          cap.textContent=(p.title||'').replace(/\s*\|\s*.*$/,'');   // pattern part
+          if(p.vendor){ var v=document.createElement('i'); v.textContent=p.vendor; cap.appendChild(v); }
+          a.appendChild(img); a.appendChild(cap); frag.appendChild(a);
+        });
+        grid.appendChild(frag);
+        // reset to 2 visible rows for the new color, then show the "+2 rows" button if more remain
+        grid.setAttribute('data-rows', String(CI_START_ROWS));
+        ciApplyRows();
+        setTimeout(ciApplyRows, 60);   // re-measure once layout settles
+      });
+    }
+    /* Click handler shared by every swatch: open the in-page index for that hue.
+       Modified clicks (new tab) fall through to the plain family-collection href. */
+    function onSwatchClick(hex, ev, name){
+      ev.preventDefault();
+      openIndex(hex, name);
+    }
+
+    /* ── color math: normalize + interpolate ── */
+    function normHex(hex){
+      var rgb=hexToRgb(hex); if(!rgb) return null;
+      return '#'+rgb.map(function(x){return x.toString(16).padStart(2,'0');}).join('');
+    }
+    function mix(a,b,t){
+      var ca=hexToRgb(a),cb=hexToRgb(b); if(!ca||!cb) return a;
+      var r=Math.round(ca[0]+(cb[0]-ca[0])*t),g=Math.round(ca[1]+(cb[1]-ca[1])*t),bl=Math.round(ca[2]+(cb[2]-ca[2])*t);
+      return '#'+[r,g,bl].map(function(x){return x.toString(16).padStart(2,'0');}).join('');
+    }
+    /* ── perceptual ΔE (CIELAB, ΔE76) for VISUAL dedup of adjacent interpolated dots.
+       On a low-base, low-chroma pattern (e.g. an all-grey paisley) cranking the slider
+       high produces long runs of visually-identical adjacent swatches — meaningless
+       and it cheapens the curated feel. We skip an INTERPOLATED dot when it is within
+       ΔE < DEDUP_DE of the previously-placed swatch. Base (real) colors are NEVER
+       deduped — they're the pattern's actual colors. (refine 2026-07-09) */
+    function _rgb2lab(rgb){
+      var r=rgb[0]/255,g=rgb[1]/255,b=rgb[2]/255;
+      r=r>0.04045?Math.pow((r+0.055)/1.055,2.4):r/12.92;
+      g=g>0.04045?Math.pow((g+0.055)/1.055,2.4):g/12.92;
+      b=b>0.04045?Math.pow((b+0.055)/1.055,2.4):b/12.92;
+      var x=(r*0.4124+g*0.3576+b*0.1805)/0.95047,
+          y=(r*0.2126+g*0.7152+b*0.0722)/1.0,
+          z=(r*0.0193+g*0.1192+b*0.9505)/1.08883;
+      var f=function(t){return t>0.008856?Math.pow(t,1/3):(7.787*t)+16/116;};
+      x=f(x);y=f(y);z=f(z);
+      return [(116*y)-16, 500*(x-y), 200*(y-z)];
+    }
+    function deltaE(hexA,hexB){
+      var a=hexToRgb(hexA),b=hexToRgb(hexB); if(!a||!b) return 999;
+      var la=_rgb2lab(a),lb=_rgb2lab(b);
+      return Math.sqrt(Math.pow(la[0]-lb[0],2)+Math.pow(la[1]-lb[1],2)+Math.pow(la[2]-lb[2],2));
+    }
+    var DEDUP_DE = 2.3;   // ~1 JND: below this, two swatches are indistinguishable
+    /* TOTAL rendered dots are driven by a user DENSITY SLIDER (Steve 2026-07-09d:
+       "allow the user to grid slide the amount of dots that they can see..
+       expand.. contract"). The slider value is the target TOTAL dot count. We keep
+       EVERY real base color, then spend whatever budget is left on interpolated
+       in-between dots, distributed EVENLY across the gaps (never truncating one end
+       of the spectrum). See buildSteps. The former hard cap of 20 is now the slider
+       DEFAULT; the range is MIN_DOTS..MAX_DOTS, persisted to localStorage. */
+    var MIN_DOTS = 6, MAX_DOTS = 64, DEFAULT_DOTS = 20; /* slider bounds + default */
+    var LS_KEY = 'dw_palette_dot_count';               /* palette-specific persistence key */
+    function readDotCount(){
+      var v = DEFAULT_DOTS;
+      try { var s = localStorage.getItem(LS_KEY); if(s!=null){ var n=parseInt(s,10); if(!isNaN(n)) v=n; } } catch(e){}
+      if(v<MIN_DOTS) v=MIN_DOTS; if(v>MAX_DOTS) v=MAX_DOTS;
+      return v;
+    }
+    function writeDotCount(v){ try { localStorage.setItem(LS_KEY, String(v)); } catch(e){} }
+    var dotTarget = readDotCount();   /* current TOTAL-dot target (base + interpolated) */
+
+    /* Given the base-color count, return the number of interpolated dots to place
+       in EACH of the (n-1) gaps, so that base + sum(steps) <= `total` and the
+       leftover is spread across the earliest gaps (keeps the spectrum balanced,
+       left-to-right, rather than starving the tail). `total` = current slider value. */
+    function buildSteps(n, total){
+      var gaps = n - 1;
+      if(gaps <= 0) return [];
+      var budget = total - n;                   // interpolated dots we can afford
+      if(budget < 0) budget = 0;
+      var per = Math.floor(budget / gaps);
+      var extra = budget - per * gaps;          // spread +1 across the first `extra` gaps
+      var out = [];
+      for(var g=0; g<gaps; g++){ out.push(per + (g < extra ? 1 : 0)); }
+      return out;
+    }
+
+    /* Build one swatch. Left-click opens the IN-PAGE index of many products
+       within 10% of that hue (onSwatchClick → openIndex); the href is the family
+       collection so middle-click / new-tab / crawlers still get a valid
+       destination. (Steve 2026-07-09b: "bring up an index of many images.") */
+    function makeSwatch(hex, opts){
+      opts=opts||{};
+      var href=familyHref(hex);
+      var el=document.createElement(href?'a':'span');
+      el.className='dw-swatch'+(opts.base?' dw-swatch--base':'');
+      el.style.background=hex;
+      if(href){
+        el.href=href;
+        el.addEventListener('click', (function(nm){ return function(ev){
+          // let modified clicks (new tab / new window) use the plain-collection href
+          if(ev.metaKey||ev.ctrlKey||ev.shiftKey||ev.altKey||ev.button!==0) return;
+          onSwatchClick(hex, ev, nm);
+        }; })(opts.name));
+      }
+      var fam=familyFromHex(hex);
+      if(opts.base){
+        var lbl=(opts.name?opts.name:(fam||''));
+        el.title='Shop this '+(fam||'')+' color'+(lbl&&lbl!==fam?' — '+lbl:'')+' ('+hex+(opts.pct!=null?', '+opts.pct+'% here':'')+')';
+      } else {
+        el.title='Shop this '+(fam||'')+' color ('+hex+')';
+      }
+      return el;
+    }
+
+    /* Resolved base-color state, cached once so the density slider can re-render
+       the spectrum WITHOUT re-fetching / re-extracting the product image. */
+    var _baseList = null;    // [{hex,name,pct}] real base colors (deduped, sorted)
+    var _hasPct = false;
+
+    /* Rebuild ONLY the dot spectrum (cards) from the cached base list at the current
+       `dotTarget`. Called on first render and on every slider change. Every real base
+       color is kept; interpolated dots fill the remaining budget, evenly across gaps.
+       Legend + proportion bar reflect the real base colors and are built once (they
+       don't change with the dot count). */
+    function paintSpectrum(){
+      if(!_baseList || !_baseList.length) return;
+      var list = _baseList;
+      var stepsPerGap = buildSteps(list.length, dotTarget);
+      var frag=document.createDocumentFragment();
+      var _lastHex=null;   // last hex actually appended, for adjacent visual dedup
+      for(var i=0;i<list.length;i++){
+        // BASE colors are always shown (real pattern colors, never deduped).
+        frag.appendChild(makeSwatch(list[i].hex,{base:true,name:list[i].name,pct:list[i].pct}));
+        _lastHex=list[i].hex;
+        if(i<list.length-1){
+          var steps = stepsPerGap[i] || 0;
+          for(var k=1;k<=steps;k++){
+            var t=k/(steps+1);
+            var ih=mix(list[i].hex,list[i+1].hex,t);
+            // skip an interpolated dot that's visually identical to the previous one
+            // (dedup near-duplicates on low-chroma / low-base palettes). We still
+            // guarantee the NEXT base color renders because bases bypass this check.
+            if(_lastHex && deltaE(ih,_lastHex) < DEDUP_DE) continue;
+            frag.appendChild(makeSwatch(ih,{base:false}));
+            _lastHex=ih;
+          }
+        }
+      }
+      cards.innerHTML='';           // clear prior segments (re-render)
+      cards.appendChild(frag);
+      var _bw=root.querySelector('[data-dw-cp-barwrap]'); if(_bw) _bw.hidden=false;  // reveal the bar once it has content
+    }
+
+    /* Wire the two END drag-handles once (Steve 2026-07-28: "allow user to drag ends
+       of the bar to expand colors"). Dragging a handle OUTWARD expands the color count,
+       INWARD contracts it (~STEP_PX px of travel ≈ one color). The value is clamped to
+       [MIN_DOTS, MAX_DOTS] and persisted to localStorage; the bar re-renders live
+       (debounced) during the drag. A single-dominant-color palette has nothing to
+       expand, so the handles are hidden (is-single). Arrow keys / Home / End give the
+       handles keyboard control (a11y). */
+    var _paintTimer = null;                 // debounce handle for paintSpectrum (closure var)
+    var PAINT_DEBOUNCE_MS = 60;             // ~60ms: coalesces continuous drag ticks into one re-render
+    var HANDLE_STEP_PX = 16;                // px of horizontal drag travel per ±1 color
+    function wireHandles(){
+      var wrap = root.querySelector('[data-dw-cp-barwrap]');
+      if(!wrap) return;
+      var handles = wrap.querySelectorAll('[data-dw-cp-handle]');
+      // A single base color has nothing to interpolate — hide the handles entirely.
+      if(_baseList.length < 2){ wrap.classList.add('is-single'); return; }
+      wrap.classList.remove('is-single');
+      if(dotTarget < MIN_DOTS) dotTarget = MIN_DOTS;
+      if(dotTarget > MAX_DOTS) dotTarget = MAX_DOTS;
+      // setTarget(v, persist, immediate): update the color count; repaint the bar
+      // (immediate=now, else debounced during a continuous drag). Keeps aria in sync.
+      function setTarget(v, persist, immediate){
+        v = Math.round(v);
+        if(v < MIN_DOTS) v = MIN_DOTS; if(v > MAX_DOTS) v = MAX_DOTS;
+        var changed = (v !== dotTarget);
+        dotTarget = v;
+        handles.forEach(function(h){ h.setAttribute('aria-valuenow', String(v)); });
+        if(persist) writeDotCount(v);
+        if(!changed && !immediate) return;
+        if(immediate){
+          if(_paintTimer){ clearTimeout(_paintTimer); _paintTimer = null; }
+          paintSpectrum();
+        } else {
+          if(_paintTimer) clearTimeout(_paintTimer);
+          _paintTimer = setTimeout(function(){ _paintTimer = null; paintSpectrum(); }, PAINT_DEBOUNCE_MS);
+        }
+      }
+      handles.forEach(function(h){
+        var side = h.getAttribute('data-dw-cp-handle');   // 'l' (left) | 'r' (right)
+        var dir  = (side === 'r') ? 1 : -1;               // OUTWARD travel sign that EXPANDS
+        h.setAttribute('aria-valuenow', String(dotTarget));
+        var startX = 0, startT = dotTarget, dragging = false;
+        function onMove(ev){
+          if(!dragging) return;
+          var dpx = ev.clientX - startX;
+          setTarget(startT + dir * (dpx / HANDLE_STEP_PX), false, false);   // outward = +colors
+          ev.preventDefault();
+        }
+        function onUp(){
+          if(!dragging) return;
+          dragging = false;
+          writeDotCount(dotTarget);         // persist final count on release
+          paintSpectrum();                  // final crisp paint
+          document.removeEventListener('pointermove', onMove);
+          document.removeEventListener('pointerup', onUp);
+        }
+        h.addEventListener('pointerdown', function(ev){
+          dragging = true; startX = ev.clientX; startT = dotTarget;
+          try { h.setPointerCapture(ev.pointerId); } catch(e){}
+          document.addEventListener('pointermove', onMove);
+          document.addEventListener('pointerup', onUp);
+          ev.preventDefault();
+        });
+        // Keyboard: outward arrow = more colors, inward = fewer; Home/End = min/max.
+        h.addEventListener('keydown', function(ev){
+          var step = 0;
+          if(ev.key === 'ArrowRight' || ev.key === 'ArrowUp')        step =  dir;
+          else if(ev.key === 'ArrowLeft' || ev.key === 'ArrowDown')  step = -dir;
+          else if(ev.key === 'Home'){ setTarget(MIN_DOTS, true, true); ev.preventDefault(); return; }
+          else if(ev.key === 'End'){  setTarget(MAX_DOTS, true, true); ev.preventDefault(); return; }
+          if(step){ setTarget(dotTarget + step, true, true); ev.preventDefault(); }
+        });
+      });
+    }
+
+    function render(colors){
+      if(!colors || !colors.length) return false;
+      /* de-dupe by hex/name, keep order; resolve each to a real hex */
+      var seen={}, list=[];
+      colors.forEach(function(c){
+        if(!c) return;
+        var hex=normHex(c.hex) || normHex(N[(c.name||'').toLowerCase()]);
+        if(!hex) return;
+        if(seen[hex]) return; seen[hex]=1;
+        list.push({hex:hex, name:c.name||'', pct:pctOf(c)});
+      });
+      if(!list.length) return false;
+      var hasPct = list.some(function(c){return c.pct!=null;});
+      if(hasPct) list.sort(function(a,b){return (b.pct||0)-(a.pct||0);});
+      /* Keep the real base colors (still capped at ≤8 dominant hues — bases are the
+         pattern's actual colors; interpolation between them fills the slider budget). */
+      list = list.slice(0, 8);
+
+      /* Cache the resolved base list + wire the slider, then paint the spectrum at
+         the current (persisted) dot target. Slider re-renders call paintSpectrum
+         directly — no image re-extraction. (Steve 2026-07-09d) */
+      _baseList = list; _hasPct = hasPct;
+      wireHandles();
+      paintSpectrum();
+      /* (Steve 2026-07-28) The names+% legend and the thin proportion bar were removed
+         — the large color bar above is now the single palette element. */
+      return true;
+    }
+
+    /* Metafield fallbacks — used ONLY if canvas extraction yields nothing. */
+    function renderMeta(){
+      var ph=root.dataset.primaryHex, aiRaw=root.dataset.aiColors, sh=root.dataset.colorHex, list2=[];
+      if(ph) list2.push({hex:ph, name:root.dataset.colorName||'Primary'});
+      try { JSON.parse((aiRaw||'[]').replace(/=>/g,':')).forEach(function(nm){ var h=N[(''+nm).toLowerCase()]; if(h) list2.push({hex:h,name:nm}); }); } catch(e){}
+      if(list2.length && render(list2)) return true;
+      if(sh) return render([{hex:sh, name:root.dataset.colorName||'Color'}]);
+      return false;
+    }
+
+    /* ── Tier 1: color_details / color_dots_json metafield (BEST — local-vision enriched) ── */
+    var raw = root.dataset.colors;
+    if(raw){
+      try { if(render(JSON.parse(raw.replace(/=>/g,':')))) return; } catch(e){}
+    }
+
+    /* ── Tier 2: Canvas extraction from the product image (multi-color + %), meta as fallback ── */
+    var REF={'red':[190,55,55],'crimson':[176,40,60],'navy':[30,45,80],'blue':[70,110,175],'slate blue':[95,120,150],'powder blue':[170,196,214],'green':[70,130,80],'sage':[150,168,138],'celadon':[176,192,168],'eucalyptus':[130,146,128],'moss':[120,138,90],'olive':[124,124,70],'gold':[200,168,90],'ochre':[190,150,80],'yellow':[224,186,90],'orange':[220,120,70],'terracotta':[196,110,85],'coral':[224,140,120],'pink':[220,150,165],'blush':[224,190,190],'purple':[140,100,160],'lavender':[190,180,205],'black':[40,40,40],'charcoal':[70,74,78],'white':[248,246,240],'ivory':[240,236,224],'cream':[236,228,206],'eggshell':[224,216,196],'gray':[140,140,142],'greige':[176,168,154],'silver':[196,196,198],'brown':[130,95,70],'walnut':[110,80,55],'taupe':[150,138,120],'beige':[214,202,178],'tan':[196,176,140],'sand':[200,184,150],'stone':[186,180,166],'teal':[70,140,140],'turquoise':[110,180,175]};
+    function closestName(r,g,b){var best='',bd=1e9;for(var n in REF){var c=REF[n],d=Math.pow(r-c[0],2)+Math.pow(g-c[1],2)+Math.pow(b-c[2],2);if(d<bd){bd=d;best=n;}}return best;}
+    function hx(r,g,b){return '#'+[r,g,b].map(function(x){return x.toString(16).padStart(2,'0');}).join('');}
+    function fromCanvas(url){
+      var img=new Image(); img.crossOrigin='anonymous';
+      img.onload=function(){
+        try{
+          var sz=120,cv=document.createElement('canvas');cv.width=sz;cv.height=sz;
+          var ctx=cv.getContext('2d');ctx.drawImage(img,0,0,sz,sz);
+          var px=ctx.getImageData(0,0,sz,sz).data,bk={};
+          for(var i=0;i<px.length;i+=4){var kr=Math.round(px[i]/22)*22,kg=Math.round(px[i+1]/22)*22,kb=Math.round(px[i+2]/22)*22,k=kr+','+kg+','+kb;if(!bk[k])bk[k]={r:0,g:0,b:0,n:0};bk[k].r+=px[i];bk[k].g+=px[i+1];bk[k].b+=px[i+2];bk[k].n++;}
+          var sorted=Object.keys(bk).map(function(k){return bk[k];}).sort(function(a,b){return b.n-a.n;});
+          var tot=sz*sz,out=[],seen={};
+          for(var j=0;j<sorted.length && out.length<8;j++){var s=sorted[j],ar=Math.round(s.r/s.n),ag=Math.round(s.g/s.n),ab=Math.round(s.b/s.n),nm=closestName(ar,ag,ab),pct=Math.round(s.n/tot*100);if(seen[nm]||pct<2)continue;seen[nm]=1;out.push({name:nm,hex:hx(ar,ag,ab),pct:pct});}
+          if(!render(out.slice(0,6))) renderMeta();
+        }catch(e){ renderMeta(); }
+      };
+      img.onerror=function(){ renderMeta(); };
+      img.src=url;
+    }
+    function imgUrl(){
+      var u=root.dataset.productImage;
+      if(u&&u.length>5) return u.indexOf('//')===0?'https:'+u:u;
+      var sels=['.product-gallery--viewport img[src]','.product-gallery img[src]','[data-product-gallery] img[src]','.media-gallery__slide img[src]'];
+      for(var i=0;i<sels.length;i++){var el=document.querySelector(sels[i]);if(el&&el.src)return el.src;}
+      return null;
+    }
+    var u=imgUrl();
+    if(u){ fromCanvas(u); }
+    else {
+      var obs=new MutationObserver(function(m,o){var v=imgUrl();if(v){o.disconnect();fromCanvas(v);}});
+      obs.observe(document.body,{childList:true,subtree:true});
+      setTimeout(function(){ obs.disconnect(); if(!cards.children.length) renderMeta(); },5000);
+    }
+  }
+  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',init);}else{init();}
+})();
+</script>
+
+{%- comment -%} "More Wallcoverings Like This" (CLIP row) MOVED to sections/product.liquid so it spans the full page width, below the two-column product block (Steve 2026-07-28). {%- endcomment -%}
diff --git a/shopify/theme-titlelink-fix-20260730-144953/backup/product-form-content.liquid b/shopify/theme-titlelink-fix-20260730-144953/backup/product-form-content.liquid
new file mode 100644
index 00000000..f176dda3
--- /dev/null
+++ b/shopify/theme-titlelink-fix-20260730-144953/backup/product-form-content.liquid
@@ -0,0 +1,804 @@
+{% comment %}
+  @param product {Product}
+
+  @param show_social_media_icons {Boolean}
+
+  @param show_payment_button {Boolean}
+    Show the DPBs in the product form
+{% endcomment %}
+
+{% assign product_id = product.id %}
+{% assign selected_variant = product.selected_or_first_available_variant %}
+
+{%- liquid
+  assign _selected_variant_sku = selected_variant.sku | default: '' | downcase
+  assign _selected_variant_title = selected_variant.title | default: '' | downcase
+
+  assign _selected_variant_sku_suffix = _selected_variant_sku | slice: -7, 7
+
+  assign is_sample_sku = false
+  if _selected_variant_sku != blank and _selected_variant_sku_suffix == '-sample'
+    assign is_sample_sku = true
+  endif
+
+  assign is_sample_title = false
+  if _selected_variant_title contains 'sample'
+    assign is_sample_title = true
+  endif
+
+  assign is_sample_like = false
+  if is_sample_sku
+    assign is_sample_like = true
+  elsif is_sample_title and selected_variant.price == 0
+    assign is_sample_like = true
+  endif
+-%}
+
+<!-- -----------020------------ -->
+{% assign prods_quantity_order_min = 1 %}
+
+{% if product.metafields['global']['v_prods_quantity_order_min'] %}
+  {% if product.metafields['global']['v_prods_quantity_order_min'] != '' %}
+    {% assign prods_quantity_order_min = product.metafields['global']['v_prods_quantity_order_min'] %}
+  {% endif %}
+{% endif %}
+
+{% assign prods_quantity_order_units = 1 %}
+
+{% if product.metafields['global']['v_prods_quantity_order_units'] %}
+  {% if product.metafields['global']['v_prods_quantity_order_units'] != '' %}
+    {% assign prods_quantity_order_units = product.metafields['global']['v_prods_quantity_order_units'] %}
+  {% endif %}
+{% endif %}
+
+<!-- Minimum order quantity from custom metafields (e.g. Ralph Lauren fabrics) -->
+{% assign min_order_qty = product.metafields.custom.minimum_order_quantity | default: nil %}
+{% assign min_order_unit = product.metafields.custom.minimum_order_unit | default: '' %}
+{% assign has_min_order = false %}
+{% if min_order_qty != nil and min_order_qty != blank and min_order_qty > 1 %}
+  {% assign has_min_order = true %}
+  {% assign min_order_qty_num = min_order_qty | plus: 0 %}
+  {% if min_order_qty_num > prods_quantity_order_min %}
+    {% assign prods_quantity_order_min = min_order_qty_num %}
+  {% endif %}
+{% endif %}
+
+<!-- -------------------------- -->
+<script>
+  setTimeout(function () {
+    const qtySelector = document.querySelector('.mm_quantity .input-text.qty');
+    if (qtySelector) {
+      qtySelector.value = {{ prods_quantity_order_min }};
+    }
+  }, 1000);
+</script>
+
+{% if has_min_order %}
+<!-- Minimum order notice and validation -->
+<style>
+  .min-order-notice {
+    background: #fff3cd;
+    border: 1px solid #ffc107;
+    border-radius: 4px;
+    padding: 8px 12px;
+    margin: 10px 0;
+    font-size: 14px;
+    font-weight: 600;
+    color: #856404;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+  }
+  .min-order-notice svg {
+    flex-shrink: 0;
+  }
+  .min-order-error {
+    background: #f8d7da;
+    border: 1px solid #f5c6cb;
+    color: #721c24;
+    border-radius: 4px;
+    padding: 8px 12px;
+    margin: 8px 0 0;
+    font-size: 13px;
+    display: none;
+  }
+</style>
+<div class="min-order-notice" data-min-order-notice>
+  <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1a7 7 0 100 14A7 7 0 008 1zm0 2.5a.75.75 0 01.75.75v4a.75.75 0 01-1.5 0v-4A.75.75 0 018 3.5zm0 8a.75.75 0 100-1.5.75.75 0 000 1.5z"/></svg>
+  Minimum order: {{ min_order_qty }} {{ min_order_unit }}
+</div>
+<script>
+  (function() {
+    var MIN_QTY = {{ min_order_qty }};
+    var MIN_UNIT = {{ min_order_unit | json }};
+
+    document.addEventListener('DOMContentLoaded', function() {
+      // Find the product form (could be either .shopify-product-form or form with data-product-form)
+      var forms = document.querySelectorAll('form[action*="/cart/add"], form[data-product-form]');
+      forms.forEach(function(form) {
+        form.addEventListener('submit', function(e) {
+          var qtyInput = form.querySelector('.mm_quantity .input-text.qty') || form.querySelector('input[name="quantity"]');
+          if (!qtyInput) return;
+
+          var qty = parseInt(qtyInput.value, 10);
+          if (isNaN(qty) || qty < MIN_QTY) {
+            e.preventDefault();
+            e.stopImmediatePropagation();
+
+            // Show error message
+            var errorDiv = form.querySelector('.min-order-error');
+            if (!errorDiv) {
+              errorDiv = document.createElement('div');
+              errorDiv.className = 'min-order-error';
+              var addToCartBtn = form.querySelector('.product-add-to-cart');
+              if (addToCartBtn) {
+                addToCartBtn.appendChild(errorDiv);
+              }
+            }
+            errorDiv.textContent = 'Please enter a quantity of at least ' + MIN_QTY + ' ' + MIN_UNIT + '.';
+            errorDiv.style.display = 'block';
+
+            // Also fix the value
+            qtyInput.value = MIN_QTY;
+            var hqty = form.querySelector('.hquantity');
+            if (hqty) hqty.value = MIN_QTY;
+
+            // Hide error after 5s
+            setTimeout(function() { errorDiv.style.display = 'none'; }, 5000);
+            return false;
+          }
+        });
+      });
+
+      // Also enforce min on the minus button - prevent going below minimum
+      jQuery(document).on('click', '.minus', function() {
+        var $qty = jQuery(this).closest('.mm_quantity').find('.qty');
+        var currentVal = parseInt($qty.val(), 10);
+        if (currentVal < MIN_QTY) {
+          $qty.val(MIN_QTY);
+          jQuery(this).closest('.mm_quantity').find('.hquantity').attr('value', MIN_QTY);
+        }
+      });
+
+      // Enforce min on blur (manual typing)
+      jQuery(document).on('blur', '.mm_quantity .input-text.qty', function() {
+        var currentVal = parseInt(jQuery(this).val(), 10);
+        if (isNaN(currentVal) || currentVal < MIN_QTY) {
+          jQuery(this).val(MIN_QTY);
+          jQuery(this).closest('.mm_quantity').find('.hquantity').attr('value', MIN_QTY);
+        }
+      });
+
+      // Hide notice when sample variant is selected
+      jQuery(document).on('change', '.product-select, .single-option-selector', function() {
+        var $form = jQuery(this).closest('form, [data-product-form]');
+        var $selected = $form.find('.product-select option:selected, .product-select[type="hidden"]');
+        var selectedText = ($selected.text() || $selected.attr('data-variant-title') || '').toLowerCase();
+        var selectedSku = ($selected.attr('data-sku') || '').toLowerCase();
+        var isSample = /sample/i.test(selectedText) || /-sample$/i.test(selectedSku);
+
+        var $notice = jQuery('.min-order-notice[data-min-order-notice]');
+        if (isSample) {
+          $notice.hide();
+        } else {
+          $notice.show();
+        }
+      });
+    });
+  })();
+</script>
+{% endif %}
+
+{% for block in section.blocks %}
+  {% case block.type %}
+    {% when 'vendor' %}
+      <a class="product-vendor" href="{{ product.vendor | url_for_vendor }}" {{ block.shopify_attributes }}>{{ product.vendor }}</a>
+
+    {% when 'title' %}
+      <h1 class="product-title" {{ block.shopify_attributes }}>
+        {% if product != blank %}
+          {% if link_to %}
+            <a href="{{ product.url }}">
+              {{ product.title }}
+            </a>
+          {% else %}
+            {% comment %}{{ product.title }} {% endcomment %}
+            <a href="https://designer-laboratory-sandbox.myshopify.com/search?q={{ product.title }}">
+              {{ product.title }}
+            </a>
+          {% endif %}
+        {% else %}
+          {{ 'products.product.onboarding.title' | t }}
+        {% endif %}
+      </h1>
+      <span class="product-sku-label">SKU: </span>
+      {%- liquid
+        assign sku_raw = product.selected_or_first_available_variant.sku | default: ''
+        assign sku_display = sku_raw | replace: '-Sample', '' | replace: '-SAMPLE', '' | replace: '-sample', ''
+        assign sku_display = sku_display | replace: '--', '-' | replace: '--', '-'
+        assign sku_last_char = sku_display | slice: -1, 1
+        if sku_display != blank and sku_last_char == '-'
+          assign sku_display = sku_display | remove_last: '-'
+        endif
+      -%}
+      <span class="product-sku-value">{{ sku_display }}</span>
+    {% when '@app' %}
+      <div class="product-app" {{ block.shopify_attributes }}>
+        {% render block %}
+      </div>
+
+    {% when 'custom-liquid' %}
+      {% if block.settings.custom_liquid != blank %}
+        <div class="product__custom-liquid" {{ block.shopify_attributes }}>
+          {{ block.settings.custom_liquid }}
+        </div>
+      {% endif %}
+
+    {% when 'collapsible-tab' %}
+      {% if block.settings.collapsible_tab_heading != blank %}
+        <details class="collapsible-tab" {{ block.shopify_attributes }}>
+          <summary class="collapsible-tab__heading">
+            <span>{{ block.settings.collapsible_tab_heading | escape }}</span>
+            {%
+              render 'icons',
+              id: 'chevron-right',
+            %}
+          </summary>
+
+          {% if block.settings.collapsible_tab_text != blank %}
+            <div class="collapsible-tab__text rte">
+              {{ block.settings.collapsible_tab_text }}
+            </div>
+          {% endif %}
+        </details>
+      {% endif %}
+
+    {% when 'tabs' %}
+      {% assign tab_counter = 1 %}
+      <div class="product-tabs" {{ block.shopify_attributes }}>
+        {% if block.settings.show_product_description %}
+          <input
+            class="product-tabs__radio"
+            id="product-tabs__radio-{{ section.id }}"
+            name="{{ section.id }}"
+            tabindex="0"
+            type="radio"
+            checked="checked"
+          >
+
+          <label class="product-tabs__label" for="product-tabs__radio-{{ section.id }}">
+            {{ 'onboarding.tab_title'| t }}
+          </label>
+
+          <div class="product-tabs__panel rte" tabindex="0">
+            {% if onboarding %}
+              {{ 'onboarding.tab_content'| t }}
+            {% else %}
+              {% if product.description != blank %}
+                {{ product.description }}
+              {% else %}
+                {{ 'onboarding.tab_content'| t }}
+              {% endif %}
+            {% endif %}
+          </div>
+        {% endif %}
+
+        {% for i in (1..3) %}
+          {% assign tab_text_key = 'tab_text_' | append: i %}
+          {% assign tab_heading_key = 'tab_heading_' | append: i %}
+          {% assign tab_heading_value = block.settings[tab_heading_key] %}
+          {% assign tab_text_value = block.settings[tab_text_key] %}
+
+          {% if tab_heading_value != blank %}
+            <input
+              class="product-tabs__radio"
+              id="product-tabs__radio-{{ i }}-{{ section.id }}"
+              name="{{ section.id }}"
+              tabindex="0"
+              type="radio"
+              {% if tab_counter == 1 and block.settings.show_product_description == false %}
+                checked
+              {% endif %}
+            >
+
+            <label class="product-tabs__label" for="product-tabs__radio-{{ i }}-{{ section.id }}">
+              {{ tab_heading_value | escape }}
+            </label>
+
+            <div class="product-tabs__panel rte" tabindex="0">
+              {{ tab_text_value }}
+            </div>
+
+            {% assign tab_counter = tab_counter | plus: 1 %}
+          {% endif %}
+        {% endfor %}
+      </div>
+
+    {% when 'price' %}
+      <div class="product__price" {{ block.shopify_attributes }}>
+        {%- comment -%}
+          DW trade/designer pricing tiers (2026-06-11): any customer carrying one of these
+          professional tags sees the trade price display (15% off, ×0.85). This restores the
+          pre-app behavior where every designer/trade tier got the discount — not just 'trade'.
+          Matches *any* tag containing 'designer' (interior_designer, commercial_designer, …)
+          plus the explicit tiers below. Add new tiers to the explicit list as needed.
+        {%- endcomment -%}
+        {%- assign dw_is_trade = false -%}
+        {%- for dw_t in customer.tags -%}
+          {%- assign dw_tl = dw_t | downcase | strip -%}
+          {%- if dw_tl contains 'designer' or dw_tl == 'trade' or dw_tl == 'architect' or dw_tl == 'stager' or dw_tl == 'hospitality' or dw_tl == 'retailer' -%}
+            {%- assign dw_is_trade = true -%}
+            {%- break -%}
+          {%- endif -%}
+        {%- endfor -%}
+        <input type="hidden" id="customer_tag" value="{% if dw_is_trade %}trade{% endif %}">
+        <p class="product-price">
+          {% comment %} SA Wholesale (saw_/wbuyx/"WHO") app removed 2026-06-11 — it left the price span blank. Render native variant price unconditionally. {% endcomment %}
+            <!-- native price markup -->
+            <meta itemprop="price" content="{{ selected_variant.price | money_without_currency }}">
+            <span class="product-price-minimum money {% if dw_is_trade %}original{% endif %}">
+              {% if product != blank %}
+                {{ selected_variant.price | money }}
+              {% else %}
+                {{ 'products.product.onboarding.price' | t }}
+              {% endif %}
+            </span>
+            {% if dw_is_trade %}
+              <span
+                class="product-price-discount"
+              >
+                {% assign target_product = selected_variant %}
+                {% assign com_selected_variant_title = selected_variant.title | capitalize %}
+                {% assign com_selected_variant_sku = selected_variant.sku | default: '' | downcase %}
+                {% if com_selected_variant_title == "Sample" or com_selected_variant_sku contains "sample" %}
+                  {% comment %} {% render 'money', value: 0 %} {% endcomment %}
+                  Complimentary Sample
+                {% else %}
+                  {% assign com_variant_first_title = product.variants[0].title | capitalize %}
+                  {% if com_variant_first_title == "Sample" and product.variants[1] != blank  %}
+                    {% assign target_product = product.variants[1] %}
+                  {% endif  %}
+                  {% assign discounted_price = target_product.price | times:0.85 | round: 2 %}
+                  {% render 'money', value: discounted_price %}
+                {% endif %}
+
+              </span>
+            {% endif %}
+           {% comment %}
+            <span
+              class="product-price-compare money original {% if selected_variant.compare_at_price > selected_variant.price %}visible{% endif %}"
+            >
+              {{ selected_variant.compare_at_price | money }}
+            </span>
+           {% endcomment %}
+        </p>
+
+        {% assign variant_for_unit_price = selected_variant %}
+        {% comment %}Inject unit-price begin{% endcomment %}
+        {% comment %}
+          @param variant_for_unit_price
+            Product variant for price
+          @param tax_text
+            String containing 'tax included' text
+        {% endcomment %}
+
+        {% capture total_quantity %}
+          <span class="product-price__unit-price-total-quantity" data-unit-price-quantity>
+            {{ variant_for_unit_price.unit_price_measurement.quantity_value }}{{ variant_for_unit_price.unit_price_measurement.quantity_unit }}
+          </span>
+        {% endcapture %}
+
+
+        {% capture unit_price %}
+          <span class="product-price__unit-price-amount money" data-unit-price-amount>
+            {{ variant_for_unit_price.unit_price | money }}
+          </span>
+        {% endcapture %}
+        {% capture unit_measure %}
+          <span class="product-price__unit-price-measure" data-unit-price-measure>
+            {%- if variant_for_unit_price.unit_price_measurement.reference_value != 1 -%}
+              {{ variant_for_unit_price.unit_price_measurement.reference_value }}
+            {%- endif %}
+            {{ variant_for_unit_price.unit_price_measurement.reference_unit }}
+          </span>
+        {% endcapture %}
+
+        <div
+          class="
+            product-price__unit-price
+            {% unless variant_for_unit_price.unit_price_measurement %}hidden{% endunless %}
+          "
+          data-unit-price
+        >
+          {{ 'products.product.price_per_unit_html' | t: total_quantity: total_quantity, unit_price: unit_price, unit_measure: unit_measure | strip_newlines }}
+        </div>
+
+        {% assign variant_for_unit_price = blank %}
+        {% comment %}Inject unit-price end{% endcomment %}
+
+
+        {%- capture tax_text -%}
+        {{ 'products.product.tax_line_html' | t }}
+        {%- endcapture -%}
+
+        {%- if tax_text != blank -%}
+          <div class="
+            product-price__tax
+            {% unless selected_variant.taxable %}hidden{% endunless %}
+            "
+            data-tax-line
+          >
+            {{ tax_text }}
+          </div>
+        {%- endif -%}
+
+        {{ form | payment_terms }}
+      </div>
+
+    {% when 'form' %}
+
+      <div class="product__form" {{ block.shopify_attributes }}>
+
+        {% unless product.has_only_default_variant %}
+          <div class="product-options">
+            {%-
+              render 'product-options-dropdown',
+              product: product,
+              form_id: product_id
+            -%}
+
+            <label
+              class="
+                product-option-column-1
+                product-option-row-4
+                product-option-quantity-label
+                {% if is_sample_like %}hidden{% endif %}
+              "
+              for="default-variant-quantity-input"
+            >
+              <strong>{{ 'general.general.quantity' | t }}:</strong>
+            </label>
+            {% comment %}
+            <input
+              class="product-option-quantity"
+              id="default-variant-quantity-input"
+              data-product-quantity-input
+              type="number"
+              inputmode="numeric"
+              name="quantity"
+              value="1"
+            >
+            {% endcomment %}
+            <!-- 020 -->
+            <div class="mm_quantity mm_buttons_added product-option-row-4 {% if is_sample_like %}hidden{% endif %}">
+              <input type="hidden" class="hquantity" name="hquantity" value="{{ prods_quantity_order_min }}">
+              <input type="button" value="-" class="minus">
+              <input
+                type="text"
+                step="{{ prods_quantity_order_units }}"
+                min="{{ prods_quantity_order_min }}"
+                data-order-step="{{ prods_quantity_order_units }}"
+                data-order-min="{{ prods_quantity_order_min }}"
+                max=""
+                name="quantity"
+                value="{{ prods_quantity_order_min }}"
+                title="Qty"
+                class="input-text qty text"
+                size="4"
+                pattern=""
+                inputmode=""
+                onkeypress="mm_validate(event)"
+              >
+              <input type="button" value="+" class="plus">
+            </div>
+            <!--  -->
+
+            <div class="selector-wrapper no-js-required">
+              <label for="product-select-{{ product_id }}"></label>
+              <select
+                class="product-select"
+                name="id"
+                id="product-select-{{ product_id }}"
+                data-select-ignore
+              >
+                {% for variant in product.variants %}
+                  {% if variant.available %}
+                    {% assign com_variant_title = variant.title | capitalize %}
+                    {% if com_variant_title == "Sample" %}
+                      {% assign dl_sample_variant = variant.id %}
+                      {% assign dl_sample_variant_title = product.title | append: ' - ' | append: variant.title %}
+                      {% assign dl_sample = 'small_sample' %}
+                    {% endif %}
+
+                    {% if com_variant_title == "Large Sample" %}
+                      {% assign dl_sample_variant2 = variant.id %}
+                      {% assign dl_sample_variant_title2 = product.title | append: ' - ' | append: variant.title %}
+                      {% assign dl_sample2 = 'large_sample' %}
+                    {% endif %}
+
+                    <option
+                      {% if variant == selected_variant %}selected="selected"{% endif %}
+                      value="{{ variant.id }}"
+                      data-variant-id="{{ variant.id }}"
+                      data-sku="{{ variant.sku }}"
+                      data-vprice="{{ variant.price }}"
+                    >
+                      {{ variant.title }} - {{ variant.price | money }}
+                    </option>
+                  {% else %}
+                    <option 
+                    disabled="disabled" 
+                    data-variant-id="{{ variant.id }}" 
+                    value="{{ variant.id }}"
+                    data-sku="{{ variant.sku }}">
+                      {{ variant.title }} - {{ 'products.product.sold_out' | t }}
+                    </option>
+                  {% endif %}
+                {% endfor %}
+              </select>
+            </div>
+          </div>
+        {% else %}
+          <div class="product-options product-options-default-only">
+            <input
+              class="product-select"
+              name="id"
+              value="{{ product.variants[0].id }}"
+              type="hidden"
+              data-variant-title="{{ product.variants[0].title }}"
+              data-sku="{{ product.variants[0].sku }}"
+              data-vprice="{{ product.variants[0].price }}"
+            />
+
+            <label
+              class="
+                product-option-column-1
+                product-option-row-1
+                product-option-quantity-label
+                {% if is_sample_like %}hidden{% endif %}
+              "
+              for="quantity-input"
+            >
+              <strong>{{ 'general.general.quantity' | t }}:</strong>
+            </label>
+
+<!--             <input
+              class="product-option-quantity"
+              id="quantity-input"
+              data-product-quantity-input
+              type="number"
+              inputmode="numeric"
+              name="quantity"
+              value="1"
+            > -->
+            <!-- 020 -->
+            <div class="mm_quantity mm_buttons_added product-option-row-1 {% if is_sample_like %}hidden{% endif %}">
+              <input type="hidden" class="hquantity" name="hquantity" value="{{ prods_quantity_order_min }}">
+              <input type="button" value="-" class="minus">
+              <input
+                type="text"
+                step="{{ prods_quantity_order_units }}"
+                min="{{ prods_quantity_order_min }}"
+                data-order-step="{{ prods_quantity_order_units }}"
+                data-order-min="{{ prods_quantity_order_min }}"
+                max=""
+                name="quantity"
+                value="{{ prods_quantity_order_min }}"
+                title="Qty"
+                class="input-text qty text"
+                size="4"
+                pattern=""
+                inputmode=""
+                onkeypress="mm_validate(event)"
+              >
+              <input type="button" value="+" class="plus">
+            </div>
+            <!--  -->
+          </div>
+        {% endunless %}
+
+        {% for tag in product.tags %}
+          {% if tag contains 'custom mural' %}
+            <!-- Custom Price Calculator -->
+            <div id="custom-calculator" style="display:none" data-calculatorid="62388fabd48c8"></div>
+            <script>
+              document.querySelector(".product-options .last_variant").innerHTML = "Custom Sizes";
+
+              var appendCalc = setInterval(moveCalc, 300);
+
+              function moveCalc() {
+                var calc = document.getElementById("calculator");
+                if (calc) {
+                  document
+                    .querySelector(".product-options")
+                    .insertBefore(
+                      document.getElementById("calculator"),
+                      document.querySelector(".product-options").children[2]
+                    );
+                  clearInterval(appendCalc);
+                }
+              }
+            </script>
+            <style>
+              .product-options {
+                display: flex;
+                flex-direction: column;
+                align-items: baseline;
+                margin-left: 0;
+              }
+              .product-options .product-option-quantity-label,
+              .mm_quantity.mm_buttons_added {
+                display: none !important;
+              }
+            </style>
+          {% endif %}
+        {% endfor %}
+
+        {% if product.available %}
+          <div id="infiniteoptions-container"></div>
+          <div id="uploadery-container"></div>
+        {% endif %}
+
+        <!-- Print PDF button -->
+        {% render 'button-product-pdf-print' %}
+
+        <div
+          class="
+            product-add-to-cart
+            {% if product != blank and show_payment_button %}
+              product-smart-payments
+            {% endif %}
+          "
+        >
+          {% if selected_variant.available and template.name != 'password' %}
+            <!-- 020 -->
+            <div class="custom-contact-message"></div>
+
+            <a
+              href="#"
+              class="dl-contact-btn custom-popup-contact-form"
+              {% if variant.price == 0 %}
+                style="display:inline-block;"
+              {% else %}
+                style="display:none;"
+              {% endif %}
+            >
+              Contact Us
+            </a>
+
+            {% assign title = 'Request Sample' %}
+            {% assign showonly = blank %}
+            {% if product.tags contains "Showroom only" and product.price == 0 %}
+              {% assign showonly = 'showonly' %}
+            {% endif %}
+            {% if product.tags contains "Showroom Only" and product.price == 0 %}
+              {% assign showonly = 'showonly' %}
+            {% endif %}
+
+            {% if is_sample_sku and dl_sample == blank and dl_sample2 == blank %}
+              <a
+                href=""
+                data-dlvid="{{ selected_variant.id }}"
+                data-dlvtitle="{{ product.title | append: ' - Sample' }}"
+                class="dl-sample-btn {{ showonly }}"
+                data-product-id="{{ product.id }}"
+              >
+                {{ title }}
+              </a>
+            {% endif %}
+
+            {% if dl_sample != blank and dl_sample2 != blank and dl_sample == 'small_sample' and dl_sample2 == 'large_sample' %}
+              {% assign title = 'Small Sample' %}
+            {% endif %}
+
+            {% if dl_sample == 'small_sample' and dl_sample_variant !='' and  dl_sample_variant_title != '' %}
+              <a
+                href=""
+                data-dlvid="{{ dl_sample_variant }}"
+                data-dlvtitle="{{ dl_sample_variant_title }}"
+                class="dl-sample-btn {{ showonly }}"
+                data-product-id="{{ product.id }}"
+              >
+                {{ title }}
+              </a>
+            {% endif %}
+
+            {% if dl_sample2 == 'large_sample' and dl_sample_variant2 !='' and  dl_sample_variant_title2 != '' %}
+              <a
+                href=""
+                data-dlvid="{{ dl_sample_variant2 }}"
+                data-dlvtitle="{{ dl_sample_variant_title2 }}"
+                class="dl-sample-btn {{ showonly }}"
+                data-product-id="{{ product.id }}"
+              >
+                Large Sample
+              </a>
+            {% endif %}
+            {% for variant_item in product.variants %}
+              {% if variant_item.price == 0 and variant_item.title contains "Sample" and variant_item.title != "Sample" %}
+                {% assign showing_title = variant_item.title %}
+                {% if showing_title == "Textured Vinyl Sample" %}
+                  {% assign showing_title = "Vinyl Sample" %}
+                {% endif %}
+                <a
+                  href=""
+                  data-dlvid="{{ variant_item.id }}"
+                  data-dlvtitle="{{ variant_item.title }}"
+                  class="dl-second-sample-btn {% if variant_item.id != product.selected_or_first_available_variant.id %}unavailable{% endif %} {{ showonly }}"
+                  data-product-id="{{ product.id }}"
+                >
+                  Request {{ showing_title }}
+                </a>
+              {% endif %}
+            {% endfor %}
+
+            {% render 'spec-sheet-button' %}
+
+<!--             <input class="add-to-cart" type="submit" value="{{ 'products.product.add_to_cart' | t }}" /> -->
+            {% assign com_first_variant_title = product.selected_or_first_available_variant.title | capitalize %}
+            <input class="add-to-cart {% if is_sample_like %}hidden{% endif %} {% if product.variants.size == 1 and com_first_variant_title == "Sample" %}hidden{% endif %} {% if product.selected_or_first_available_variant.price == 0 %}unavailable{% endif %}" type="submit" value="{{ 'products.product.add_to_cart' | t }}"/>
+          {% else %}
+            <input class="add-to-cart disabled" type="submit" value="{{ 'products.product.sold_out' | t }}" disabled/>
+          {% endif %}
+
+          <!--020 -->
+          {% for t in product.tags %}
+             {% if t == 'Customize' %}
+                    <a href="#" class="dl-customize-btn custom-popup-contact-form">Customize this Pattern</a>
+             {% endif %}
+          {% endfor %}
+
+          {% if show_payment_button %}
+            {{ form | payment_button }}
+          {% endif %}
+
+          {% capture the_snippet_fave %}{% include 'socialshopwave-widget-fave' %}{% endcapture %}
+          {% unless the_snippet_fave contains 'Liquid error' %}
+          {{ the_snippet_fave }}
+          {% endunless %}
+        </div>
+        <div class="product-message"></div>
+
+        <div data-surface-pick-up></div>
+        {%-
+          render 'modal',
+          modal_id: 'surface-pick-up',
+          modal_class: 'surface-pick-up-modal',
+        -%}
+      </div>
+
+    {% when 'social' %}
+      <div class="product__social" {{ block.shopify_attributes }}>
+        {% render 'share-buttons' %}
+      </div>
+
+    {% when 'description' %}
+      {% if product != blank and product.description != blank %}
+        <div class="product-description rte" {{ block.shopify_attributes }}>
+          {{ product.description }}
+        </div>
+      {% elsif product == blank %}
+        <div class="product-description rte" {{ block.shopify_attributes }}>
+          {{ 'products.product.onboarding.description' | t }}
+        </div>
+      {% endif %}
+
+    {%- when 'rating' -%}
+      {%- if product.metafields.reviews.rating.value != blank -%}
+        <div class="product__rating rating" {{ block.shopify_attributes }}>
+          {%
+            render 'rating-stars',
+            value: product.metafields.reviews.rating.value.rating,
+            scale_max: product.metafields.reviews.rating.value.scale_max,
+          %}
+          <p class="rating__text">
+            <span aria-hidden="true">{{ product.metafields.reviews.rating.value }} / {{ product.metafields.reviews.rating.value.scale_max }}</span>
+          </p>
+          <p class="rating__count">
+            <span aria-hidden="true">({{ product.metafields.reviews.rating_count }})</span>
+            <span class="visually-hidden">{{ product.metafields.reviews.rating_count }} {{ "general.accessibility.total_reviews" | t }}</span>
+          </p>
+        </div>
+      {%- endif -%}
+  {% endcase %}
+{% endfor %}
diff --git a/shopify/theme-titlelink-fix-20260730-144953/deploy.sh b/shopify/theme-titlelink-fix-20260730-144953/deploy.sh
new file mode 100755
index 00000000..ff5e897e
--- /dev/null
+++ b/shopify/theme-titlelink-fix-20260730-144953/deploy.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+# Live deploy: push the title-link fix + color-dot fallback fix to the LIVE DW theme.
+# Customer-facing production write. Backups live in ./backup/ ; rollback with ./rollback.sh
+set -euo pipefail
+SECRETS=~/Projects/secrets-manager/.env
+STORE="designer-laboratory-sandbox.myshopify.com"
+API="2024-10"; THEME=144396058675
+THEME_TOKEN=$(grep -hE '^SHOPIFY_THEME_TOKEN=' "$SECRETS" | head -1 | cut -d= -f2- | tr -d '"'"'"' \r')
+DIR="$(cd "$(dirname "$0")" && pwd)"
+
+put () { # key  file
+  python3 - "$STORE" "$API" "$THEME" "$THEME_TOKEN" "$1" "$2" <<'PY'
+import sys, json, urllib.request, urllib.error
+store, api, theme, token, key, path = sys.argv[1:7]
+value = open(path).read()
+payload = json.dumps({"asset": {"key": key, "value": value}}).encode()
+url = f"https://{store}/admin/api/{api}/themes/{theme}/assets.json"
+req = urllib.request.Request(url, data=payload, method="PUT",
+    headers={"X-Shopify-Access-Token": token, "Content-Type": "application/json"})
+try:
+    with urllib.request.urlopen(req) as r:
+        a = json.loads(r.read()).get("asset", {})
+        print(f"PUT OK  {r.status}  {key}  updated_at={a.get('updated_at')}")
+except urllib.error.HTTPError as e:
+    print(f"PUT FAIL {e.code} {key}: {e.read().decode()[:400]}"); sys.exit(1)
+PY
+}
+
+put "snippets/product-form-content.liquid" "$DIR/edit/product-form-content.liquid"
+put "snippets/color-dots.liquid"            "$DIR/edit/color-dots.liquid"
+echo "Deploy complete."
diff --git a/shopify/theme-titlelink-fix-20260730-144953/edit/color-dots.liquid b/shopify/theme-titlelink-fix-20260730-144953/edit/color-dots.liquid
new file mode 100644
index 00000000..5f6df73c
--- /dev/null
+++ b/shopify/theme-titlelink-fix-20260730-144953/edit/color-dots.liquid
@@ -0,0 +1,253 @@
+{% assign color_dots_json_val = product.metafields.custom.color_dots_json.value %}
+{% assign color_details_val = product.metafields.custom.color_details.value %}
+{% if color_dots_json_val != blank %}
+  {% assign color_details_raw = color_dots_json_val | json %}
+{% elsif color_details_val != blank %}
+  {% assign color_details_raw = color_details_val | json %}
+{% else %}
+  {% assign color_details_raw = blank %}
+{% endif %}
+{% assign color_hex = product.metafields.custom.color_hex.value %}
+{% assign primary_hex = product.metafields.custom.primary_color_hex.value %}
+{% assign ai_colors_raw = product.metafields.dwc.ai_generated_colors.value | json %}
+{% assign color_name = product.metafields.custom.color_name.value %}
+
+{%- comment -%}
+  Color Dots v3 — 3-tier color system
+  Tier 1: color_details metafield (Gemini-enriched)
+  Tier 2: primary_color_hex + ai_generated_colors
+  Tier 3: Canvas pixel extraction from product image (onload)
+{%- endcomment -%}
+
+<div class="dw-color-dots" id="dw-color-dots-wrapper" style="display:flex;justify-content:center;gap:10px;padding:12px 0 8px;flex-wrap:wrap;min-height:30px;">
+  <div id="dw-color-dots-container" style="display:flex;justify-content:center;gap:10px;flex-wrap:wrap;"
+    {% if color_details_raw != blank and color_details_raw != 'null' %}data-colors='{{ color_details_raw | json }}'{% endif %}
+    {% if primary_hex != blank %}data-primary-hex="{{ primary_hex }}"{% endif %}
+    {% if ai_colors_raw != blank and ai_colors_raw != 'null' %}data-ai-colors='{{ ai_colors_raw }}'{% endif %}
+    {% if color_name != blank %}data-color-name="{{ color_name }}"{% endif %}
+    {% if color_hex != blank %}data-color-hex="{{ color_hex }}"{% endif %}
+    {% if product.featured_image %}data-product-image="{{ product.featured_image | img_url: '200x200' }}"{% endif %}
+  ></div>
+</div>
+
+<script>
+(function(){
+  function init() {
+   try {
+    var container = document.getElementById('dw-color-dots-container');
+    if (!container || container.dataset.rendered) return;
+    container.dataset.rendered = '1';
+
+    var C = {
+      'red':'red-wallpaper-collection','crimson':'red-wallpaper-collection','burgundy':'red-wallpaper-collection','wine':'red-wallpaper-collection','maroon':'red-wallpaper-collection',
+      'blue':'blue-wallpaper','navy':'blue-wallpaper','cobalt':'blue-wallpaper','indigo':'blue-wallpaper','sky blue':'blue-wallpaper','steel blue':'blue-wallpaper','royal blue':'blue-wallpaper',
+      'green':'green-wallpaper-collection','sage':'green-wallpaper-collection','olive':'green-wallpaper-collection','forest green':'green-wallpaper-collection','emerald':'green-wallpaper-collection','mint':'green-wallpaper-collection','moss':'green-wallpaper-collection',
+      'gold':'gold-wallpaper','golden':'gold-wallpaper','amber':'gold-wallpaper',
+      'yellow':'yellow-wallpaper-collection','mustard':'yellow-wallpaper-collection',
+      'orange':'orange','rust':'orange','terracotta':'orange',
+      'pink':'pink-wallcovering-2','blush':'pink-wallcovering-2','rose':'pink-wallcovering-2','salmon':'pink-wallcovering-2','fuchsia':'pink-wallcovering-2',
+      'purple':'purple','violet':'purple','plum':'purple','lavender':'purple','lilac':'purple','mauve':'purple',
+      'black':'black','charcoal':'black','ebony':'black','onyx':'black',
+      'white':'white-wallpaper-collection','ivory':'white-wallpaper-collection','cream':'white-wallpaper-collection','pearl':'white-wallpaper-collection','off-white':'white-wallpaper-collection',
+      'gray':'grey-silver','grey':'grey-silver','silver':'grey-silver','slate':'grey-silver',
+      'brown':'brown-wallpaper-collection','chocolate':'brown-wallpaper-collection','espresso':'brown-wallpaper-collection','walnut':'brown-wallpaper-collection','mocha':'brown-wallpaper-collection','sienna':'brown-wallpaper-collection',
+      'beige':'beige-cream-wallpaper-collection','tan':'beige-cream-wallpaper-collection','taupe':'beige-cream-wallpaper-collection','sand':'beige-cream-wallpaper-collection','khaki':'beige-cream-wallpaper-collection','camel':'beige-cream-wallpaper-collection','oatmeal':'beige-cream-wallpaper-collection','linen':'beige-cream-wallpaper-collection',
+      'coral':'coral','peach':'coral',
+      'teal':'turquoise','aqua':'turquoise','turquoise':'turquoise','cyan':'turquoise',
+      'metallic':'metallic-wallpaper-collections','copper':'metallic-wallpaper-collections','bronze':'metallic-wallpaper-collections'
+    };
+
+    var N = {
+      'red':'#DC2626','crimson':'#DC143C','burgundy':'#800020','wine':'#722F37','maroon':'#800000',
+      'blue':'#2563EB','navy':'#1B2A4A','cobalt':'#0047AB','indigo':'#3F51B5','sky blue':'#87CEEB','steel blue':'#4682B4','royal blue':'#4169E1',
+      'green':'#16A34A','sage':'#87AE73','olive':'#808000','forest green':'#228B22','emerald':'#50C878','mint':'#98FF98','moss':'#8A9A5B',
+      'gold':'#D4AF37','golden':'#FFD700','amber':'#FFBF00',
+      'yellow':'#EAB308','mustard':'#FFDB58',
+      'orange':'#EA580C','rust':'#B7410E','terracotta':'#E2725B',
+      'pink':'#EC4899','blush':'#DE5D83','rose':'#FF007F','salmon':'#FA8072','fuchsia':'#FF00FF',
+      'purple':'#9333EA','violet':'#7C3AED','plum':'#8E4585','lavender':'#E6E6FA','lilac':'#C8A2C8','mauve':'#E0B0FF',
+      'black':'#1A1A1A','charcoal':'#36454F','ebony':'#555D50','onyx':'#353839',
+      'white':'#FAFAFA','ivory':'#FFFFF0','cream':'#FFFDD0','pearl':'#F0EAD6','off-white':'#FAF9F6',
+      'gray':'#6B7280','grey':'#6B7280','silver':'#C0C0C0','slate':'#708090',
+      'brown':'#92400E','chocolate':'#7B3F00','espresso':'#3C1414','walnut':'#5C3A1D','mocha':'#967969','sienna':'#A0522D',
+      'beige':'#F5F5DC','tan':'#D2B48C','taupe':'#483C32','sand':'#C2B280','khaki':'#F0E68C','camel':'#C19A6B','oatmeal':'#B8AB8C','linen':'#FAF0E6',
+      'coral':'#FF6F61','peach':'#FFCBA4',
+      'teal':'#0D9488','aqua':'#00FFFF','turquoise':'#40E0D0','cyan':'#06B6D4',
+      'metallic':'#AAA9AD','copper':'#B87333','bronze':'#CD7F32',
+      'deep indigo':'#1B2A4A','dark navy':'#1B2A4A','midnight':'#191970'
+    };
+
+    function findColl(name) {
+      var l = (name||'').toLowerCase();
+      if (C[l]) return C[l];
+      for (var k in C) { if (l.indexOf(k) > -1) return C[k]; }
+      return null;
+    }
+
+    function makeDot(hex, name, pct) {
+      try {
+        var coll = findColl(name);
+        var d = document.createElement('div');
+        var p = (pct == null ? 20 : pct);
+        var clickable = !!coll;
+        var cursor = clickable ? 'pointer' : 'default';
+        var sz=Math.max(22,Math.min(52,16+p*0.6));d.style.cssText='width:'+sz+'px;height:'+sz+'px;border-radius:50%;background:'+hex+';border:2px solid #eee;box-shadow:0 1px 3px rgba(0,0,0,0.1);cursor:'+cursor+';transition:transform 0.15s,box-shadow 0.15s;flex-shrink:0;';
+        d.onmouseenter = function(){this.style.transform='scale(1.3)';this.style.boxShadow='0 2px 8px rgba(0,0,0,0.2)';};
+        d.onmouseleave = function(){this.style.transform='scale(1)';this.style.boxShadow='0 1px 3px rgba(0,0,0,0.1)';};
+        // No matching collection: show the swatch but DON'T emit a /search?q= link
+        // (that endpoint is rate-limited and returns local_rate_limited -> dead link).
+        if (!coll) {
+          d.title = (name||'color') + ' (' + hex + ')';
+          return d;
+        }
+        var a = document.createElement('a');
+        a.href = '/collections/' + coll + '?sort_by=created-descending';
+        a.title = 'Shop ' + (name||'color') + ' (' + hex + ')';
+        a.style.textDecoration = 'none';
+        a.appendChild(d);
+        return a;
+      } catch(e) { return null; }
+    }
+
+    function renderDots(colors) {
+      colors.forEach(function(c){ var el = makeDot(c.hex, c.name, (c.pct || c.percentage)); if (el) container.appendChild(el); });
+    }
+
+    /* Canvas color refs */
+    var REF = {
+      'red':[220,38,38],'navy':[27,42,74],'blue':[37,99,235],'green':[22,163,74],'sage':[135,174,115],
+      'gold':[212,175,55],'yellow':[234,179,8],'orange':[234,88,12],'pink':[236,72,153],
+      'purple':[147,51,234],'black':[26,26,26],'charcoal':[54,69,79],'white':[250,250,250],
+      'ivory':[255,255,240],'cream':[255,253,208],'gray':[107,114,128],'silver':[192,192,192],
+      'brown':[146,64,14],'beige':[245,245,220],'tan':[210,180,140],'taupe':[72,60,50],
+      'teal':[13,148,136],'coral':[255,111,97],'olive':[128,128,0],'rust':[183,65,14],
+      'burgundy':[128,0,32],'slate':[112,128,144],'sand':[194,178,128],'mint':[152,255,152],
+      'turquoise':[64,224,208],'copper':[184,115,51],'bronze':[205,127,50],'mauve':[224,176,255]
+    };
+
+    function closestName(r,g,b) {
+      var best='',bestD=1e9;
+      for(var n in REF){var c=REF[n],d=Math.sqrt(Math.pow(r-c[0],2)+Math.pow(g-c[1],2)+Math.pow(b-c[2],2));if(d<bestD){bestD=d;best=n;}}
+      return best;
+    }
+    function rgbHex(r,g,b){return '#'+[r,g,b].map(function(x){return x.toString(16).padStart(2,'0');}).join('');}
+
+    function extractFromCanvas(imgUrl) {
+      var img = new Image();
+      img.crossOrigin = 'anonymous';
+      img.onload = function() {
+        try {
+        var cv = document.createElement('canvas'), sz = 50;
+        cv.width=sz; cv.height=sz;
+        var ctx=cv.getContext('2d');
+        ctx.drawImage(img,0,0,sz,sz);
+        var px=ctx.getImageData(0,0,sz,sz).data;
+        var bk={};
+        for(var i=0;i<px.length;i+=4){
+          var kr=Math.round(px[i]/32)*32,kg=Math.round(px[i+1]/32)*32,kb=Math.round(px[i+2]/32)*32;
+          var key=kr+','+kg+','+kb;
+          if(!bk[key])bk[key]={r:0,g:0,b:0,n:0};
+          bk[key].r+=px[i];bk[key].g+=px[i+1];bk[key].b+=px[i+2];bk[key].n++;
+        }
+        var sorted=Object.values(bk).sort(function(a,b){return b.n-a.n;});
+        var tot=sz*sz,colors=[],seen={};
+        for(var j=0;j<Math.min(sorted.length,8);j++){
+          var s=sorted[j],ar=Math.round(s.r/s.n),ag=Math.round(s.g/s.n),ab=Math.round(s.b/s.n);
+          var nm=closestName(ar,ag,ab);
+          if(seen[nm])continue; seen[nm]=true;
+          colors.push({name:nm,hex:rgbHex(ar,ag,ab),percentage:Math.round(s.n/tot*100)});
+          if(colors.length>=5)break;
+        }
+        if(colors.length>0) renderDots(colors);
+        } catch(e) {
+          /* CORS or canvas security error — try without crossOrigin */
+          var img2 = new Image();
+          img2.onload = function() {
+            try {
+              var cv2 = document.createElement('canvas'), sz2 = 50;
+              cv2.width=sz2; cv2.height=sz2;
+              var ctx2=cv2.getContext('2d');
+              ctx2.drawImage(img2,0,0,sz2,sz2);
+              /* Cannot getImageData without CORS — use image dominant color via 1x1 scale */
+              cv2.width=1; cv2.height=1;
+              ctx2.drawImage(img2,0,0,1,1);
+              var p=ctx2.getImageData(0,0,1,1).data;
+              var nm=closestName(p[0],p[1],p[2]);
+              renderDots([{name:nm,hex:rgbHex(p[0],p[1],p[2]),percentage:100}]);
+            } catch(e2) {}
+          };
+          img2.src = imgUrl;
+        }
+      };
+      img.onerror = function(){};
+      img.src = imgUrl;
+    }
+
+    function getImageUrl() {
+      var url = container.dataset.productImage;
+      if (url && url.length > 5) return url.indexOf('//') === 0 ? 'https:' + url : url;
+      var sels = [
+        '.product-gallery--viewport img[src]',
+        '.product-gallery img[src]',
+        '[data-product-gallery] img[src]',
+        '.product-gallery--viewport--figure img[src]',
+        '.product__media img[src]',
+        '.product-single__photo img[src]'
+      ];
+      for (var i = 0; i < sels.length; i++) {
+        var el = document.querySelector(sels[i]);
+        if (el && el.src) return el.src;
+      }
+      return null;
+    }
+
+    /* ── Tier 1: color_details metafield ── */
+    var colorsData = container.dataset.colors;
+    if (colorsData) {
+      try {
+        /* Fix Shopify Liquid Ruby hash syntax: => becomes : */
+        var fixed = colorsData.replace(/=>/g, ':');
+        renderDots(JSON.parse(fixed));
+        return;
+      } catch(e) {}
+    }
+
+    /* ── Tier 2: primary_color_hex + ai_generated_colors ── */
+    var ph = container.dataset.primaryHex;
+    var aiRaw = container.dataset.aiColors;
+    if (ph || aiRaw) {
+      var added = {};
+      if (ph) { var pd=makeDot(ph, container.dataset.colorName||'primary'); if(pd)container.appendChild(pd); added[ph.toLowerCase()]=true; }
+      try {
+        var ai = JSON.parse((aiRaw||'[]').replace(/=>/g, ':'));
+        ai.forEach(function(nm){ var h=N[nm.toLowerCase()]; if(h&&!added[h.toLowerCase()]){var ad=makeDot(h,nm); if(ad)container.appendChild(ad);added[h.toLowerCase()]=true;} });
+      } catch(e) {}
+      if (container.children.length > 0) return;
+    }
+
+    /* ── Tier 2.5: single color_hex ── */
+    var sh = container.dataset.colorHex;
+    if (sh) { var shd=makeDot(sh, container.dataset.colorName||'color'); if(shd)container.appendChild(shd); return; }
+
+    /* ── Tier 3: Canvas extraction from product image ── */
+    var imgUrl = getImageUrl();
+    if (imgUrl) {
+      extractFromCanvas(imgUrl);
+    } else {
+      var obs = new MutationObserver(function(m, o) {
+        var url = getImageUrl();
+        if (url) { o.disconnect(); extractFromCanvas(url); }
+      });
+      obs.observe(document.body, { childList: true, subtree: true });
+      setTimeout(function(){ obs.disconnect(); }, 5000);
+    }
+   } catch(e) { /* never let color-dot rendering abort other page init */ }
+  }
+
+  if (document.readyState === 'loading') {
+    document.addEventListener('DOMContentLoaded', init);
+  } else {
+    init();
+  }
+})();
+</script>
diff --git a/shopify/theme-titlelink-fix-20260730-144953/edit/color-palette.liquid b/shopify/theme-titlelink-fix-20260730-144953/edit/color-palette.liquid
new file mode 100644
index 00000000..d6f2f1e0
--- /dev/null
+++ b/shopify/theme-titlelink-fix-20260730-144953/edit/color-palette.liquid
@@ -0,0 +1,675 @@
+{% comment %}
+  color-palette — "Colors In This Pattern" swatch spectrum.
+  Shows the pattern's real colors AND interpolated in-between colors, laid out as
+  uniform swatches centered in even rows. The TOTAL rendered dots are driven by a
+  user DENSITY SLIDER (Steve 2026-07-09d: "allow the user to grid slide the amount
+  of dots that they can see.. expand.. contract"): every real base color is kept,
+  and the remaining budget of interpolated dots is spread EVENLY across the gaps
+  (never truncating one end of the spectrum). The slider runs MIN_DOTS(6) →
+  MAX_DOTS(64) with DEFAULT 20 (the former hard cap became the default value),
+  re-renders live on drag with NO page reload, and persists to localStorage under
+  'dw_palette_dot_count' (a palette-specific key, distinct from the product-grid
+  density slider). (was Steve 2026-07-09c: a fixed 20-dot cap, MAX_SWATCHES=20.)
+
+  FIX PASS (Steve 2026-07-09, contrarian FIX-FIRST): (1) the slider 'input' handler
+  DEBOUNCES the expensive paintSpectrum() re-render ~80ms so a continuous drag doesn't
+  thrash the whole dot spectrum every tick — the numeric "Colors shown" output and the
+  minus/plus buttons still update IMMEDIATELY for snappy feedback; persistence stays on
+  'change' (drag-release). (2) When a pattern has only ONE dominant base color there is
+  genuinely nothing to interpolate, so instead of silently HIDING the control (a dead
+  end that leaves the user wondering), it stays VISIBLE but DISABLED with a title/aria
+  hint ("Only one dominant color in this pattern").
+
+  DOT CLICK TARGET (Steve 2026-07-09b): "bring up an index of many images, not
+  just that exact hex. tolerance 10% for color to pull more." A swatch click now
+  opens an IN-PAGE expanding grid of MANY products whose dominant color is within
+  a perceptual 10% tolerance (CIELAB ΔE76) of the clicked hex — CATALOG-WIDE, not
+  one collection. The grid is served by the color-index endpoint
+    POST https://photo.designerwallcoverings.com/apps/color-index  {hex, k}
+  (dwphoto → data/color-index.json, one dominant color per ACTIVE product with
+  precomputed LAB). Cards link to /products/<handle> (the roll variant, never the
+  $4.25 Sample). The family-collection href stays as the plain href so
+  middle-click / new-tab / crawlers still get a valid destination, and is the
+  graceful fallback if the endpoint is unreachable.
+  (was: Steve 2026-07-09a, a click deep-linked to the ONE nearest product+variant.)
+
+  Data tiers (mirrors color-dots.liquid, kept in sync):
+    Tier 1  custom.color_dots_json / custom.color_details  (enriched JSON: [{hex,name,pct}])
+    Tier 2  custom.primary_color_hex + dwc.ai_generated_colors (name list → hex map)
+    Tier 3  custom.color_hex (single)
+    Tier 4  Canvas pixel extraction from the product image (always yields real % — reliable fallback)
+{% endcomment %}
+
+{% comment %} Read the authoritative enriched field FIRST (color_details); legacy
+   color_dots_json is only a fallback so it can never shadow fresh enrichment. {% endcomment %}
+{% assign color_dots_json_val = product.metafields.custom.color_dots_json.value %}
+{% assign color_details_val = product.metafields.custom.color_details.value %}
+{% if color_details_val != blank %}
+  {% assign color_details_raw = color_details_val | json %}
+{% elsif color_dots_json_val != blank %}
+  {% assign color_details_raw = color_dots_json_val | json %}
+{% else %}
+  {% assign color_details_raw = blank %}
+{% endif %}
+{% assign color_hex = product.metafields.custom.color_hex.value %}
+{% assign primary_hex = product.metafields.custom.primary_color_hex.value %}
+{% assign ai_colors_raw = product.metafields.dwc.ai_generated_colors.value | json %}
+{% assign color_name = product.metafields.custom.color_name.value %}
+
+<div class="dw-color-palette" data-dw-color-palette
+  {% if color_details_raw != blank and color_details_raw != 'null' %}data-colors='{{ color_details_raw | escape }}'{% endif %}
+  {% if primary_hex != blank %}data-primary-hex="{{ primary_hex }}"{% endif %}
+  {% if ai_colors_raw != blank and ai_colors_raw != 'null' %}data-ai-colors='{{ ai_colors_raw }}'{% endif %}
+  {% if color_name != blank %}data-color-name="{{ color_name }}"{% endif %}
+  {% if color_hex != blank %}data-color-hex="{{ color_hex }}"{% endif %}
+  {% if product.featured_image %}data-product-image="{{ product.featured_image | img_url: '400x400' }}"{% endif %}
+  data-product-style="{{ product.metafields.custom.style.value | default: product.type | escape }}"
+>
+  <p class="dw-color-palette__title">Select Color for Coordinates</p>
+
+  {%- comment -%} COLOR BAR (Steve 2026-07-28): the palette renders as ONE large
+    horizontal color BAR (was individual dots), ~4× the old dot height. Drag the pill
+    handle on either END of the bar to EXPAND / CONTRACT how many colors it shows —
+    this replaces the old "Colors shown" slider. The count persists to localStorage
+    'dw_palette_dot_count'. Single-dominant-color palettes hide the handles (nothing
+    to expand). {%- endcomment -%}
+  <div class="dw-color-palette__barwrap" data-dw-cp-barwrap hidden>
+    <span class="dw-cp-handle dw-cp-handle--l" data-dw-cp-handle="l" role="slider" tabindex="0"
+          aria-label="Drag to change how many colors are shown" aria-valuemin="6" aria-valuemax="64"><i aria-hidden="true"></i></span>
+    <div class="dw-color-palette__cards" data-dw-color-palette-cards aria-live="polite"></div>
+    <span class="dw-cp-handle dw-cp-handle--r" data-dw-cp-handle="r" role="slider" tabindex="0"
+          aria-label="Drag to change how many colors are shown" aria-valuemin="6" aria-valuemax="64"><i aria-hidden="true"></i></span>
+  </div>
+
+  {%- comment -%} In-page color-index grid the dot brings up (Steve 2026-07-09b).
+    Hidden until a swatch is clicked; then filled with the catalog-wide products
+    within 10% (CIELAB ΔE76) of the clicked hex. {%- endcomment -%}
+  <div class="dw-color-index" data-dw-color-index hidden
+       data-endpoint="https://photo.designerwallcoverings.com/apps/color-index">
+    <div class="dw-color-index__head">
+      <span class="dw-color-index__swatch" data-dw-ci-swatch></span>
+      <span class="dw-color-index__label" data-dw-ci-label aria-live="polite"></span>
+      <button type="button" class="dw-color-index__close" data-dw-ci-close aria-label="Close color index">&times;</button>
+    </div>
+    <div class="dw-color-index__grid" data-dw-ci-grid aria-live="polite"></div>
+    {%- comment -%} Steve 2026-07-28: reveal +2 more rows of coordinates per click (refresh). {%- endcomment -%}
+    <button type="button" class="dw-color-index__more" data-dw-ci-more hidden aria-label="Show two more rows of coordinating wallcoverings">Show 2 more rows</button>
+  </div>
+</div>
+
+<style>
+  /* Steve 2026-07-28: relocated to a FULL-WIDTH section below the two columns. */
+  .dw-color-palette{padding:18px 0 12px;text-align:center;min-height:10px;max-width:1200px;margin:0 auto;}
+  .dw-color-palette__title{font-family:Lora,serif;font-size:15px;font-weight:500;color:#3D4246;margin:0 0 4px;letter-spacing:.01em;}
+  /* ── COLOR BAR (Steve 2026-07-28): dots → ONE large horizontal bar, ~4× the old
+     dot height, with a drag handle on each END to expand/contract the color count.
+     Replaces the density slider + the names/% legend + the thin proportion bar. ── */
+  .dw-color-palette__barwrap{position:relative;width:100%;max-width:1200px;margin:8px auto 0;box-sizing:border-box;}
+  .dw-color-palette__barwrap[hidden]{display:none;}
+  /* the bar itself: contiguous full-width color segments, no gaps, rounded ends */
+  .dw-color-palette__cards{display:flex;flex-wrap:nowrap;align-items:stretch;gap:0;width:100%;height:76px;border-radius:9px;overflow:hidden;box-shadow:inset 0 0 0 1px rgba(0,0,0,.06),0 1px 4px rgba(0,0,0,.08);}
+  .dw-swatch{flex:1 1 0;min-width:0;height:100%;display:block;border-radius:0;box-sizing:border-box;text-decoration:none;box-shadow:none;transition:filter .12s ease,box-shadow .12s ease;position:relative;}
+  a.dw-swatch{cursor:pointer;}
+  .dw-swatch:hover{filter:brightness(1.07);box-shadow:inset 0 0 0 2px rgba(255,255,255,.55),inset 0 0 0 3px rgba(0,0,0,.12);z-index:3;}
+  /* base (real pattern) colors read the same in a contiguous bar — no ring/scale */
+  .dw-swatch--base{box-shadow:none;}
+  .dw-swatch--base:hover{filter:brightness(1.07);box-shadow:inset 0 0 0 2px rgba(255,255,255,.6),inset 0 0 0 3px rgba(0,0,0,.16);z-index:3;}
+  /* END drag handles — pill grips that protrude past the bar; drag horizontally to
+     expand (outward) / contract (inward) the color count, persisted to localStorage. */
+  .dw-cp-handle{position:absolute;top:50%;transform:translateY(-50%);width:15px;height:94px;background:#fff;border:1px solid #c9c4b8;border-radius:8px;box-shadow:0 1px 5px rgba(0,0,0,.20);cursor:ew-resize;display:flex;align-items:center;justify-content:center;z-index:6;touch-action:none;-webkit-user-select:none;user-select:none;}
+  .dw-cp-handle i{display:block;width:2px;height:38px;background:#b0aa9c;border-radius:2px;box-shadow:4px 0 0 #b0aa9c,-4px 0 0 #b0aa9c;}
+  .dw-cp-handle--l{left:-6px;}
+  .dw-cp-handle--r{right:-6px;}
+  .dw-cp-handle:hover{border-color:#a8a294;}
+  .dw-cp-handle:focus-visible{outline:2px solid #b8b3a8;outline-offset:3px;}
+  .dw-color-palette__barwrap.is-single .dw-cp-handle{display:none;} /* one dominant color: nothing to expand */
+  .dw-color-palette:empty{display:none;}
+  @media (max-width:600px){
+    .dw-color-palette__cards{height:58px;border-radius:7px;}
+    .dw-cp-handle{height:74px;width:14px;}
+    .dw-cp-handle i{height:30px;}
+  }
+  /* ── In-page color-index grid the dot brings up — refined to read as a curated
+     "shop this colorway" drawer: hairline-ruled header, Lora title at the palette
+     scale, soft reveal, even gutters, hairline media. (refine 2026-07-09) ── */
+  /* Steve 2026-07-28: coordinate results serve FULL-WIDTH, exactly 2 rows; extra
+     chips are clipped (grid-auto-rows:0 + overflow:hidden) so the UI can never overflow. */
+  .dw-color-index{max-width:1200px;margin:22px auto 6px;text-align:left;border-top:1px solid #ece9e3;padding-top:16px;}
+  .dw-color-index[hidden]{display:none;}
+  @keyframes dwCiReveal{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}
+  .dw-color-index.is-open{animation:dwCiReveal .28s ease both;}
+  .dw-color-index__head{display:flex;align-items:center;gap:10px;margin:0 0 14px;}
+  .dw-color-index__swatch{width:22px;height:22px;border-radius:5px;flex:0 0 auto;box-shadow:0 0 0 1px rgba(0,0,0,.14),inset 0 -3px 5px rgba(0,0,0,.06);}
+  .dw-color-index__label{font-family:Lora,serif;font-size:14px;color:#3D4246;flex:1 1 auto;letter-spacing:.01em;}
+  .dw-color-index__label b{font-weight:600;text-transform:capitalize;}
+  .dw-color-index__close{flex:0 0 auto;background:none;border:1px solid #e0ded9;border-radius:50%;width:26px;height:26px;line-height:1;font-size:15px;color:#8a8577;cursor:pointer;padding:0;transition:color .12s ease,border-color .12s ease;}
+  .dw-color-index__close:hover{color:#3D4246;border-color:#b8b3a8;}
+  .dw-color-index__close:focus-visible{outline:2px solid #b8b3a8;outline-offset:2px;}
+  .dw-color-index__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));grid-template-rows:repeat(2,auto);grid-auto-rows:0;gap:16px;overflow:hidden;}
+  @media(min-width:1000px){.dw-color-index__grid{grid-template-columns:repeat(8,1fr);}}
+  /* "Show 2 more rows" — reveals the next 2 rows of coordinates each click (Steve 2026-07-28) */
+  .dw-color-index__more{display:block;margin:16px auto 2px;background:none;border:1px solid #d8d4cc;border-radius:999px;padding:8px 20px;font-family:Lora,serif;font-size:12px;letter-spacing:.05em;color:#3D4246;cursor:pointer;transition:border-color .12s ease,color .12s ease;}
+  .dw-color-index__more::before{content:"\21bb  ";}
+  .dw-color-index__more:hover{border-color:#b8b3a8;color:#000;}
+  .dw-color-index__more:focus-visible{outline:2px solid #b8b3a8;outline-offset:2px;}
+  .dw-color-index__more[hidden]{display:none;}
+  .dw-color-index__card{display:block;text-decoration:none;color:inherit;min-width:0!important;} /* FORCE equal-size cards — min-width:0 defeats the grid min-content blowout */
+  .dw-color-index__card img{width:100%!important;height:auto!important;aspect-ratio:1/1!important;object-fit:cover!important;display:block!important;min-width:0!important;box-shadow:0 0 0 1px #ece9e3;border-radius:3px;background:#f4f2ee;transition:box-shadow .14s ease;}
+  .dw-color-index__card:hover img{box-shadow:0 0 0 1px #b8b3a8,0 3px 12px rgba(0,0,0,.10);}
+  .dw-color-index__cap{display:block;font-family:Lora,serif;font-size:11px;color:#3D4246;margin-top:7px;line-height:1.3;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;text-transform:capitalize;}
+  .dw-color-index__card:hover .dw-color-index__cap{text-decoration:underline;text-decoration-color:#d8d4cc;}
+  .dw-color-index__cap i{display:block;font-style:normal;color:#8a8577;font-size:10px;text-transform:none;margin-top:1px;letter-spacing:.02em;}
+  .dw-color-index__empty,.dw-color-index__loading{font-family:Lora,serif;font-size:12px;color:#8a8577;padding:8px 0;}
+  @media (max-width:600px){
+    .dw-color-index__grid{grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:8px;}
+  }
+</style>
+
+<script>
+(function(){
+  function init(){
+    var root = document.querySelector('[data-dw-color-palette]');
+    if(!root || root.dataset.rendered) return;
+    var cards = root.querySelector('[data-dw-color-palette-cards]');
+    if(!cards) return;
+    root.dataset.rendered = '1';
+
+    /* name -> hex fallback map (kept in sync with color-dots.liquid) */
+    var N = {
+      'red':'#DC2626','crimson':'#DC143C','burgundy':'#800020','wine':'#722F37','maroon':'#800000',
+      'blue':'#2563EB','navy':'#1B2A4A','cobalt':'#0047AB','indigo':'#3F51B5','sky blue':'#87CEEB','steel blue':'#4682B4','royal blue':'#4169E1','blue frost':'#C4D3E0','deep indigo':'#1B2A4A','dark navy':'#1B2A4A','midnight':'#191970',
+      'green':'#16A34A','sage':'#87AE73','olive':'#808000','forest green':'#228B22','emerald':'#50C878','mint':'#98FF98','moss':'#8A9A5B','sea glass':'#C6D8CE',
+      'gold':'#D4AF37','golden':'#FFD700','amber':'#FFBF00',
+      'yellow':'#EAB308','mustard':'#FFDB58',
+      'orange':'#EA580C','rust':'#B7410E','terracotta':'#E2725B','melon':'#FDBCB4','soft melon':'#F6C9A8',
+      'pink':'#EC4899','blush':'#DE5D83','rose':'#FF007F','salmon':'#FA8072','fuchsia':'#FF00FF',
+      'purple':'#9333EA','violet':'#7C3AED','plum':'#8E4585','lavender':'#E6E6FA','lilac':'#C8A2C8','mauve':'#E0B0FF',
+      'black':'#1A1A1A','charcoal':'#36454F','ebony':'#555D50','onyx':'#353839',
+      'white':'#FAFAFA','ivory':'#FFFFF0','cream':'#FFFDD0','pearl':'#F0EAD6','off-white':'#FAF9F6','eggshell':'#F0EAD6',
+      'gray':'#6B7280','grey':'#6B7280','silver':'#C0C0C0','slate':'#708090','metallic silver':'#BFC1C2',
+      'brown':'#92400E','chocolate':'#7B3F00','espresso':'#3C1414','walnut':'#5C3A1D','mocha':'#967969','sienna':'#A0522D',
+      'beige':'#F5F5DC','tan':'#D2B48C','taupe':'#483C32','sand':'#C2B280','khaki':'#F0E68C','camel':'#C19A6B','oatmeal':'#B8AB8C','linen':'#FAF0E6',
+      'coral':'#FF6F61','peach':'#FFCBA4',
+      'teal':'#0D9488','aqua':'#00FFFF','turquoise':'#40E0D0','cyan':'#06B6D4',
+      'metallic':'#AAA9AD','copper':'#B87333','bronze':'#CD7F32'
+    };
+
+    function pctOf(c){ var p = (c.pct!=null?c.pct:(c.percentage!=null?c.percentage:null)); if(p==null) return null; p=parseFloat(p); return isNaN(p)?null:Math.round(p); }
+
+    /* ── HEX → color-family COLLECTION (handles verified live 2026-07-08) ──────
+       Derive the family from the REAL measured hex and link to that family's
+       collection. blue-wallpaper & grey-silver corrected from the old dead
+       handles (blue-wallpaper-collection / grey-wallcovering, which 404'd). */
+    var FAM_COLL = {
+      red:'red-wallpaper-collection', orange:'orange', yellow:'yellow-wallpaper-collection',
+      gold:'gold-wallpaper', green:'green-wallpaper-collection', teal:'turquoise',
+      blue:'blue-wallpaper', purple:'purple', pink:'pink-wallcovering-2',
+      brown:'brown-wallpaper-collection', beige:'beige-cream-wallpaper-collection',
+      black:'black', white:'white-wallpaper-collection', gray:'grey-silver'
+    };
+    function hexToRgb(h){
+      h=(h||'').replace('#','');
+      if(h.length===3) h=h.split('').map(function(x){return x+x;}).join('');
+      if(h.length<6) return null;
+      var r=parseInt(h.slice(0,2),16),g=parseInt(h.slice(2,4),16),b=parseInt(h.slice(4,6),16);
+      return (isNaN(r)||isNaN(g)||isNaN(b))?null:[r,g,b];
+    }
+    function familyFromHex(hex){
+      var rgb=hexToRgb(hex); if(!rgb) return null;
+      var r=rgb[0]/255,g=rgb[1]/255,b=rgb[2]/255;
+      var mx=Math.max(r,g,b),mn=Math.min(r,g,b),d=mx-mn;
+      var l=(mx+mn)/2, s=d===0?0:d/(1-Math.abs(2*l-1));
+      var hdeg=0;
+      if(d!==0){
+        if(mx===r) hdeg=60*(((g-b)/d)%6);
+        else if(mx===g) hdeg=60*(((b-r)/d)+2);
+        else hdeg=60*(((r-g)/d)+4);
+      }
+      if(hdeg<0) hdeg+=360;
+      if(s<0.18){
+        if(l>0.86) return 'white';
+        if(l<0.18) return 'black';
+        if((hdeg>=20 && hdeg<=70) && l>0.30) return 'beige';
+        return 'gray';
+      }
+      if(l>0.80 && (hdeg>=20 && hdeg<70)) return 'beige';
+      if(l<0.40 && s<0.75 && ((hdeg>=10 && hdeg<70) || hdeg>=345)) return 'brown';
+      if(l<0.16) return 'black';
+      if(hdeg<15||hdeg>=345) return 'red';
+      if(hdeg<40){ return (s<0.45 && l>0.55) ? 'beige' : 'orange'; }
+      if(hdeg<55){ return (s<0.45) ? 'beige' : (hdeg<50 ? 'gold' : 'yellow'); }
+      if(hdeg<65) return (l<0.45) ? 'gold' : 'yellow';
+      if(hdeg<170) return 'green';
+      if(hdeg<200) return 'teal';
+      if(hdeg<255) return 'blue';
+      if(hdeg<290) return 'purple';
+      return 'pink';
+    }
+    var PRODUCT_STYLE = (root.dataset.productStyle||'').trim();
+    /* The color-FAMILY collection handle for a hex (used both as the search space
+       for the nearest-specific-product resolver AND as the graceful fallback). */
+    function familyHandle(hex){
+      var fam=familyFromHex(hex); if(!fam) return null;
+      return FAM_COLL[fam] || null;
+    }
+    /* Fallback collection URL — only used if we can't resolve a single specific
+       product of that hue (edge case). (Steve 2026-07-09: DEFAULT must be a
+       specific product+variant, this is the graceful floor.) */
+    function familyHref(hex){
+      var handle=familyHandle(hex); if(!handle) return null;
+      var url='/collections/'+handle+'?sort_by=created-descending';
+      if(PRODUCT_STYLE){ url+='&filter.p.m.custom.style='+encodeURIComponent(PRODUCT_STYLE); }
+      return url;
+    }
+
+    /* ── hex → IN-PAGE INDEX of MANY products within 10% color tolerance ───────
+       Steve 2026-07-09b: "bring up an index of many images, not just that exact
+       hex. tolerance 10% for color to pull more." A dot is a palette-extracted
+       hue; clicking it opens the in-page grid (below the palette) of products
+       whose dominant color is within a perceptual 10% tolerance (CIELAB ΔE76) of
+       the clicked hex, drawn CATALOG-WIDE from the color-index endpoint. Results
+       are cached per hex so re-clicking a swatch is instant. */
+    var CI = root.querySelector('[data-dw-color-index]');
+    var CI_ENDPOINT = (CI && CI.dataset.endpoint) || 'https://photo.designerwallcoverings.com/apps/color-index';
+    var CI_K = 48;                 // fetch up to ~6 rows; show 2, reveal +2 per "Show 2 more rows" click (Steve 2026-07-28)
+    var _ciCache = {};             // hex -> Promise<{results,total,tolerance_pct}>
+    function fetchColorIndex(hex){
+      if(_ciCache[hex]) return _ciCache[hex];
+      _ciCache[hex]=fetch(CI_ENDPOINT,{
+        method:'POST', mode:'cors', headers:{'Content-Type':'application/json'},
+        body:JSON.stringify({hex:hex, k:CI_K})
+      }).then(function(r){ return r.ok?r.json():{results:[]}; })
+        .catch(function(){ return {results:[], _err:true}; });
+      return _ciCache[hex];
+    }
+    function ciEl(sel){ return CI ? CI.querySelector(sel) : null; }
+    function closeIndex(){ if(CI){ CI.classList.remove('is-open'); CI.hidden=true; } }
+    /* ── "Show 2 more rows" (Steve 2026-07-28): the coordinate grid is clipped to N
+       visible rows (grid-template-rows + overflow:hidden) so it can never overflow the
+       page; each click of the refresh button reveals +2 more rows. The button hides
+       itself once every fetched row is visible. Row height is known up-front (cards use
+       aspect-ratio:1) so scrollHeight vs clientHeight is a reliable "more remain?" test. */
+    var CI_ROW_STEP = 2, CI_START_ROWS = 2;
+    function ciApplyRows(){
+      var grid = ciEl('[data-dw-ci-grid]'); if(!grid) return;
+      var rows = parseInt(grid.getAttribute('data-rows') || String(CI_START_ROWS), 10);
+      // Hide extra cards by COUNT (rows × columns) so the grid flows to EXACTLY N clean
+      // rows — no partial 3rd-row sliver from row-gap the old overflow-clip left behind.
+      var cols = (getComputedStyle(grid).gridTemplateColumns || '').split(' ').filter(Boolean).length || 1;
+      var visible = rows * cols, hiddenAny = false;
+      for(var i=0;i<grid.children.length;i++){
+        var show = i < visible;
+        grid.children[i].style.display = show ? '' : 'none';
+        if(!show) hiddenAny = true;
+      }
+      // JS controls visibility by count, so let the grid flow naturally (no clip artefacts)
+      grid.style.gridTemplateRows = 'none';
+      grid.style.gridAutoRows = 'auto';
+      grid.style.overflow = 'visible';
+      var more = ciEl('[data-dw-ci-more]');
+      if(more){ more.hidden = !hiddenAny; }
+    }
+    if(CI){
+      var _more = ciEl('[data-dw-ci-more]');
+      if(_more) _more.addEventListener('click', function(){
+        var grid = ciEl('[data-dw-ci-grid]'); if(!grid) return;
+        var rows = parseInt(grid.getAttribute('data-rows') || String(CI_START_ROWS), 10) + CI_ROW_STEP;
+        grid.setAttribute('data-rows', String(rows));
+        ciApplyRows();
+        try{ _more.scrollIntoView({behavior:'smooth', block:'nearest'}); }catch(e){}
+      });
+      var _cx=ciEl('[data-dw-ci-close]');
+      if(_cx) _cx.addEventListener('click', closeIndex);
+      // Esc closes the drawer when it (or something inside it) is focused (a11y).
+      CI.addEventListener('keydown', function(ev){ if(ev.key==='Escape'){ closeIndex(); } });
+    }
+    /* Open the in-page index for a clicked hex. */
+    function openIndex(hex, name){
+      if(!CI) return;
+      var sw=ciEl('[data-dw-ci-swatch]'), lbl=ciEl('[data-dw-ci-label]'), grid=ciEl('[data-dw-ci-grid]');
+      var fam=familyFromHex(hex)||'';
+      if(sw) sw.style.background=hex;
+      if(lbl) lbl.innerHTML='Wallcoverings in this color — <b>'+(name||fam||hex)+'</b>';
+      if(grid) grid.innerHTML='<div class="dw-color-index__loading">Finding matches…</div>';
+      CI.hidden=false;
+      // retrigger the soft reveal even on a re-click (refine 2026-07-09)
+      CI.classList.remove('is-open'); void CI.offsetWidth; CI.classList.add('is-open');
+      CI.scrollIntoView({behavior:'smooth', block:'nearest'});
+      var _cl=ciEl('[data-dw-ci-close]'); if(_cl){ try{ _cl.focus({preventScroll:true}); }catch(e){} }
+      fetchColorIndex(hex).then(function(d){
+        if(!grid) return;
+        var res=(d&&d.results)||[];
+        if(!res.length){
+          grid.innerHTML='';
+          var empty=document.createElement('div'); empty.className='dw-color-index__empty';
+          empty.textContent = d && d._err
+            ? 'Could not load matches right now — try another color.'
+            : 'No close matches for this color yet.';
+          grid.appendChild(empty);
+          return;
+        }
+        grid.innerHTML='';
+        var frag=document.createDocumentFragment();
+        res.forEach(function(p){
+          if(!p || !p.handle) return;
+          var a=document.createElement('a'); a.className='dw-color-index__card'; a.rel='nofollow';
+          // link straight to the PDP handle — the roll/first-available variant,
+          // never the $4.25 Sample (Sample is never the default variant).
+          a.href='/products/'+encodeURIComponent(p.handle);
+          var img=document.createElement('img'); img.loading='lazy';
+          img.src=p.image||''; img.alt=p.title||'';
+          var cap=document.createElement('span'); cap.className='dw-color-index__cap';
+          cap.textContent=(p.title||'').replace(/\s*\|\s*.*$/,'');   // pattern part
+          if(p.vendor){ var v=document.createElement('i'); v.textContent=p.vendor; cap.appendChild(v); }
+          a.appendChild(img); a.appendChild(cap); frag.appendChild(a);
+        });
+        grid.appendChild(frag);
+        // reset to 2 visible rows for the new color, then show the "+2 rows" button if more remain
+        grid.setAttribute('data-rows', String(CI_START_ROWS));
+        ciApplyRows();
+        setTimeout(ciApplyRows, 60);   // re-measure once layout settles
+      });
+    }
+    /* Click handler shared by every swatch: open the in-page index for that hue.
+       Modified clicks (new tab) fall through to the plain family-collection href. */
+    function onSwatchClick(hex, ev, name){
+      ev.preventDefault();
+      openIndex(hex, name);
+    }
+
+    /* ── color math: normalize + interpolate ── */
+    function normHex(hex){
+      var rgb=hexToRgb(hex); if(!rgb) return null;
+      return '#'+rgb.map(function(x){return x.toString(16).padStart(2,'0');}).join('');
+    }
+    function mix(a,b,t){
+      var ca=hexToRgb(a),cb=hexToRgb(b); if(!ca||!cb) return a;
+      var r=Math.round(ca[0]+(cb[0]-ca[0])*t),g=Math.round(ca[1]+(cb[1]-ca[1])*t),bl=Math.round(ca[2]+(cb[2]-ca[2])*t);
+      return '#'+[r,g,bl].map(function(x){return x.toString(16).padStart(2,'0');}).join('');
+    }
+    /* ── perceptual ΔE (CIELAB, ΔE76) for VISUAL dedup of adjacent interpolated dots.
+       On a low-base, low-chroma pattern (e.g. an all-grey paisley) cranking the slider
+       high produces long runs of visually-identical adjacent swatches — meaningless
+       and it cheapens the curated feel. We skip an INTERPOLATED dot when it is within
+       ΔE < DEDUP_DE of the previously-placed swatch. Base (real) colors are NEVER
+       deduped — they're the pattern's actual colors. (refine 2026-07-09) */
+    function _rgb2lab(rgb){
+      var r=rgb[0]/255,g=rgb[1]/255,b=rgb[2]/255;
+      r=r>0.04045?Math.pow((r+0.055)/1.055,2.4):r/12.92;
+      g=g>0.04045?Math.pow((g+0.055)/1.055,2.4):g/12.92;
+      b=b>0.04045?Math.pow((b+0.055)/1.055,2.4):b/12.92;
+      var x=(r*0.4124+g*0.3576+b*0.1805)/0.95047,
+          y=(r*0.2126+g*0.7152+b*0.0722)/1.0,
+          z=(r*0.0193+g*0.1192+b*0.9505)/1.08883;
+      var f=function(t){return t>0.008856?Math.pow(t,1/3):(7.787*t)+16/116;};
+      x=f(x);y=f(y);z=f(z);
+      return [(116*y)-16, 500*(x-y), 200*(y-z)];
+    }
+    function deltaE(hexA,hexB){
+      var a=hexToRgb(hexA),b=hexToRgb(hexB); if(!a||!b) return 999;
+      var la=_rgb2lab(a),lb=_rgb2lab(b);
+      return Math.sqrt(Math.pow(la[0]-lb[0],2)+Math.pow(la[1]-lb[1],2)+Math.pow(la[2]-lb[2],2));
+    }
+    var DEDUP_DE = 2.3;   // ~1 JND: below this, two swatches are indistinguishable
+    /* TOTAL rendered dots are driven by a user DENSITY SLIDER (Steve 2026-07-09d:
+       "allow the user to grid slide the amount of dots that they can see..
+       expand.. contract"). The slider value is the target TOTAL dot count. We keep
+       EVERY real base color, then spend whatever budget is left on interpolated
+       in-between dots, distributed EVENLY across the gaps (never truncating one end
+       of the spectrum). See buildSteps. The former hard cap of 20 is now the slider
+       DEFAULT; the range is MIN_DOTS..MAX_DOTS, persisted to localStorage. */
+    var MIN_DOTS = 6, MAX_DOTS = 64, DEFAULT_DOTS = 20; /* slider bounds + default */
+    var LS_KEY = 'dw_palette_dot_count';               /* palette-specific persistence key */
+    function readDotCount(){
+      var v = DEFAULT_DOTS;
+      try { var s = localStorage.getItem(LS_KEY); if(s!=null){ var n=parseInt(s,10); if(!isNaN(n)) v=n; } } catch(e){}
+      if(v<MIN_DOTS) v=MIN_DOTS; if(v>MAX_DOTS) v=MAX_DOTS;
+      return v;
+    }
+    function writeDotCount(v){ try { localStorage.setItem(LS_KEY, String(v)); } catch(e){} }
+    var dotTarget = readDotCount();   /* current TOTAL-dot target (base + interpolated) */
+
+    /* Given the base-color count, return the number of interpolated dots to place
+       in EACH of the (n-1) gaps, so that base + sum(steps) <= `total` and the
+       leftover is spread across the earliest gaps (keeps the spectrum balanced,
+       left-to-right, rather than starving the tail). `total` = current slider value. */
+    function buildSteps(n, total){
+      var gaps = n - 1;
+      if(gaps <= 0) return [];
+      var budget = total - n;                   // interpolated dots we can afford
+      if(budget < 0) budget = 0;
+      var per = Math.floor(budget / gaps);
+      var extra = budget - per * gaps;          // spread +1 across the first `extra` gaps
+      var out = [];
+      for(var g=0; g<gaps; g++){ out.push(per + (g < extra ? 1 : 0)); }
+      return out;
+    }
+
+    /* Build one swatch. Left-click opens the IN-PAGE index of many products
+       within 10% of that hue (onSwatchClick → openIndex); the href is the family
+       collection so middle-click / new-tab / crawlers still get a valid
+       destination. (Steve 2026-07-09b: "bring up an index of many images.") */
+    function makeSwatch(hex, opts){
+      opts=opts||{};
+      var href=familyHref(hex);
+      var el=document.createElement(href?'a':'span');
+      el.className='dw-swatch'+(opts.base?' dw-swatch--base':'');
+      el.style.background=hex;
+      if(href){
+        el.href=href;
+        el.addEventListener('click', (function(nm){ return function(ev){
+          // let modified clicks (new tab / new window) use the plain-collection href
+          if(ev.metaKey||ev.ctrlKey||ev.shiftKey||ev.altKey||ev.button!==0) return;
+          onSwatchClick(hex, ev, nm);
+        }; })(opts.name));
+      }
+      var fam=familyFromHex(hex);
+      if(opts.base){
+        var lbl=(opts.name?opts.name:(fam||''));
+        el.title='Shop this '+(fam||'')+' color'+(lbl&&lbl!==fam?' — '+lbl:'')+' ('+hex+(opts.pct!=null?', '+opts.pct+'% here':'')+')';
+      } else {
+        el.title='Shop this '+(fam||'')+' color ('+hex+')';
+      }
+      return el;
+    }
+
+    /* Resolved base-color state, cached once so the density slider can re-render
+       the spectrum WITHOUT re-fetching / re-extracting the product image. */
+    var _baseList = null;    // [{hex,name,pct}] real base colors (deduped, sorted)
+    var _hasPct = false;
+
+    /* Rebuild ONLY the dot spectrum (cards) from the cached base list at the current
+       `dotTarget`. Called on first render and on every slider change. Every real base
+       color is kept; interpolated dots fill the remaining budget, evenly across gaps.
+       Legend + proportion bar reflect the real base colors and are built once (they
+       don't change with the dot count). */
+    function paintSpectrum(){
+      if(!_baseList || !_baseList.length) return;
+      var list = _baseList;
+      var stepsPerGap = buildSteps(list.length, dotTarget);
+      var frag=document.createDocumentFragment();
+      var _lastHex=null;   // last hex actually appended, for adjacent visual dedup
+      for(var i=0;i<list.length;i++){
+        // BASE colors are always shown (real pattern colors, never deduped).
+        frag.appendChild(makeSwatch(list[i].hex,{base:true,name:list[i].name,pct:list[i].pct}));
+        _lastHex=list[i].hex;
+        if(i<list.length-1){
+          var steps = stepsPerGap[i] || 0;
+          for(var k=1;k<=steps;k++){
+            var t=k/(steps+1);
+            var ih=mix(list[i].hex,list[i+1].hex,t);
+            // skip an interpolated dot that's visually identical to the previous one
+            // (dedup near-duplicates on low-chroma / low-base palettes). We still
+            // guarantee the NEXT base color renders because bases bypass this check.
+            if(_lastHex && deltaE(ih,_lastHex) < DEDUP_DE) continue;
+            frag.appendChild(makeSwatch(ih,{base:false}));
+            _lastHex=ih;
+          }
+        }
+      }
+      cards.innerHTML='';           // clear prior segments (re-render)
+      cards.appendChild(frag);
+      var _bw=root.querySelector('[data-dw-cp-barwrap]'); if(_bw) _bw.hidden=false;  // reveal the bar once it has content
+    }
+
+    /* Wire the two END drag-handles once (Steve 2026-07-28: "allow user to drag ends
+       of the bar to expand colors"). Dragging a handle OUTWARD expands the color count,
+       INWARD contracts it (~STEP_PX px of travel ≈ one color). The value is clamped to
+       [MIN_DOTS, MAX_DOTS] and persisted to localStorage; the bar re-renders live
+       (debounced) during the drag. A single-dominant-color palette has nothing to
+       expand, so the handles are hidden (is-single). Arrow keys / Home / End give the
+       handles keyboard control (a11y). */
+    var _paintTimer = null;                 // debounce handle for paintSpectrum (closure var)
+    var PAINT_DEBOUNCE_MS = 60;             // ~60ms: coalesces continuous drag ticks into one re-render
+    var HANDLE_STEP_PX = 16;                // px of horizontal drag travel per ±1 color
+    function wireHandles(){
+      var wrap = root.querySelector('[data-dw-cp-barwrap]');
+      if(!wrap) return;
+      var handles = wrap.querySelectorAll('[data-dw-cp-handle]');
+      // A single base color has nothing to interpolate — hide the handles entirely.
+      if(_baseList.length < 2){ wrap.classList.add('is-single'); return; }
+      wrap.classList.remove('is-single');
+      if(dotTarget < MIN_DOTS) dotTarget = MIN_DOTS;
+      if(dotTarget > MAX_DOTS) dotTarget = MAX_DOTS;
+      // setTarget(v, persist, immediate): update the color count; repaint the bar
+      // (immediate=now, else debounced during a continuous drag). Keeps aria in sync.
+      function setTarget(v, persist, immediate){
+        v = Math.round(v);
+        if(v < MIN_DOTS) v = MIN_DOTS; if(v > MAX_DOTS) v = MAX_DOTS;
+        var changed = (v !== dotTarget);
+        dotTarget = v;
+        handles.forEach(function(h){ h.setAttribute('aria-valuenow', String(v)); });
+        if(persist) writeDotCount(v);
+        if(!changed && !immediate) return;
+        if(immediate){
+          if(_paintTimer){ clearTimeout(_paintTimer); _paintTimer = null; }
+          paintSpectrum();
+        } else {
+          if(_paintTimer) clearTimeout(_paintTimer);
+          _paintTimer = setTimeout(function(){ _paintTimer = null; paintSpectrum(); }, PAINT_DEBOUNCE_MS);
+        }
+      }
+      handles.forEach(function(h){
+        var side = h.getAttribute('data-dw-cp-handle');   // 'l' (left) | 'r' (right)
+        var dir  = (side === 'r') ? 1 : -1;               // OUTWARD travel sign that EXPANDS
+        h.setAttribute('aria-valuenow', String(dotTarget));
+        var startX = 0, startT = dotTarget, dragging = false;
+        function onMove(ev){
+          if(!dragging) return;
+          var dpx = ev.clientX - startX;
+          setTarget(startT + dir * (dpx / HANDLE_STEP_PX), false, false);   // outward = +colors
+          ev.preventDefault();
+        }
+        function onUp(){
+          if(!dragging) return;
+          dragging = false;
+          writeDotCount(dotTarget);         // persist final count on release
+          paintSpectrum();                  // final crisp paint
+          document.removeEventListener('pointermove', onMove);
+          document.removeEventListener('pointerup', onUp);
+        }
+        h.addEventListener('pointerdown', function(ev){
+          dragging = true; startX = ev.clientX; startT = dotTarget;
+          try { h.setPointerCapture(ev.pointerId); } catch(e){}
+          document.addEventListener('pointermove', onMove);
+          document.addEventListener('pointerup', onUp);
+          ev.preventDefault();
+        });
+        // Keyboard: outward arrow = more colors, inward = fewer; Home/End = min/max.
+        h.addEventListener('keydown', function(ev){
+          var step = 0;
+          if(ev.key === 'ArrowRight' || ev.key === 'ArrowUp')        step =  dir;
+          else if(ev.key === 'ArrowLeft' || ev.key === 'ArrowDown')  step = -dir;
+          else if(ev.key === 'Home'){ setTarget(MIN_DOTS, true, true); ev.preventDefault(); return; }
+          else if(ev.key === 'End'){  setTarget(MAX_DOTS, true, true); ev.preventDefault(); return; }
+          if(step){ setTarget(dotTarget + step, true, true); ev.preventDefault(); }
+        });
+      });
+    }
+
+    function render(colors){
+      if(!colors || !colors.length) return false;
+      /* de-dupe by hex/name, keep order; resolve each to a real hex */
+      var seen={}, list=[];
+      colors.forEach(function(c){
+        if(!c) return;
+        var hex=normHex(c.hex) || normHex(N[(c.name||'').toLowerCase()]);
+        if(!hex) return;
+        if(seen[hex]) return; seen[hex]=1;
+        list.push({hex:hex, name:c.name||'', pct:pctOf(c)});
+      });
+      if(!list.length) return false;
+      var hasPct = list.some(function(c){return c.pct!=null;});
+      if(hasPct) list.sort(function(a,b){return (b.pct||0)-(a.pct||0);});
+      /* Keep the real base colors (still capped at ≤8 dominant hues — bases are the
+         pattern's actual colors; interpolation between them fills the slider budget). */
+      list = list.slice(0, 8);
+
+      /* Cache the resolved base list + wire the slider, then paint the spectrum at
+         the current (persisted) dot target. Slider re-renders call paintSpectrum
+         directly — no image re-extraction. (Steve 2026-07-09d) */
+      _baseList = list; _hasPct = hasPct;
+      wireHandles();
+      paintSpectrum();
+      /* (Steve 2026-07-28) The names+% legend and the thin proportion bar were removed
+         — the large color bar above is now the single palette element. */
+      return true;
+    }
+
+    /* Metafield fallbacks — used ONLY if canvas extraction yields nothing. */
+    function renderMeta(){
+      var ph=root.dataset.primaryHex, aiRaw=root.dataset.aiColors, sh=root.dataset.colorHex, list2=[];
+      if(ph) list2.push({hex:ph, name:root.dataset.colorName||'Primary'});
+      try { JSON.parse((aiRaw||'[]').replace(/=>/g,':')).forEach(function(nm){ var h=N[(''+nm).toLowerCase()]; if(h) list2.push({hex:h,name:nm}); }); } catch(e){}
+      if(list2.length && render(list2)) return true;
+      if(sh) return render([{hex:sh, name:root.dataset.colorName||'Color'}]);
+      return false;
+    }
+
+    /* ── Tier 1: color_details / color_dots_json metafield (BEST — local-vision enriched) ── */
+    var raw = root.dataset.colors;
+    if(raw){
+      try { if(render(JSON.parse(raw.replace(/=>/g,':')))) return; } catch(e){}
+    }
+
+    /* ── Tier 2: Canvas extraction from the product image (multi-color + %), meta as fallback ── */
+    var REF={'red':[190,55,55],'crimson':[176,40,60],'navy':[30,45,80],'blue':[70,110,175],'slate blue':[95,120,150],'powder blue':[170,196,214],'green':[70,130,80],'sage':[150,168,138],'celadon':[176,192,168],'eucalyptus':[130,146,128],'moss':[120,138,90],'olive':[124,124,70],'gold':[200,168,90],'ochre':[190,150,80],'yellow':[224,186,90],'orange':[220,120,70],'terracotta':[196,110,85],'coral':[224,140,120],'pink':[220,150,165],'blush':[224,190,190],'purple':[140,100,160],'lavender':[190,180,205],'black':[40,40,40],'charcoal':[70,74,78],'white':[248,246,240],'ivory':[240,236,224],'cream':[236,228,206],'eggshell':[224,216,196],'gray':[140,140,142],'greige':[176,168,154],'silver':[196,196,198],'brown':[130,95,70],'walnut':[110,80,55],'taupe':[150,138,120],'beige':[214,202,178],'tan':[196,176,140],'sand':[200,184,150],'stone':[186,180,166],'teal':[70,140,140],'turquoise':[110,180,175]};
+    function closestName(r,g,b){var best='',bd=1e9;for(var n in REF){var c=REF[n],d=Math.pow(r-c[0],2)+Math.pow(g-c[1],2)+Math.pow(b-c[2],2);if(d<bd){bd=d;best=n;}}return best;}
+    function hx(r,g,b){return '#'+[r,g,b].map(function(x){return x.toString(16).padStart(2,'0');}).join('');}
+    function fromCanvas(url){
+      var img=new Image(); img.crossOrigin='anonymous';
+      img.onload=function(){
+        try{
+          var sz=120,cv=document.createElement('canvas');cv.width=sz;cv.height=sz;
+          var ctx=cv.getContext('2d');ctx.drawImage(img,0,0,sz,sz);
+          var px=ctx.getImageData(0,0,sz,sz).data,bk={};
+          for(var i=0;i<px.length;i+=4){var kr=Math.round(px[i]/22)*22,kg=Math.round(px[i+1]/22)*22,kb=Math.round(px[i+2]/22)*22,k=kr+','+kg+','+kb;if(!bk[k])bk[k]={r:0,g:0,b:0,n:0};bk[k].r+=px[i];bk[k].g+=px[i+1];bk[k].b+=px[i+2];bk[k].n++;}
+          var sorted=Object.keys(bk).map(function(k){return bk[k];}).sort(function(a,b){return b.n-a.n;});
+          var tot=sz*sz,out=[],seen={};
+          for(var j=0;j<sorted.length && out.length<8;j++){var s=sorted[j],ar=Math.round(s.r/s.n),ag=Math.round(s.g/s.n),ab=Math.round(s.b/s.n),nm=closestName(ar,ag,ab),pct=Math.round(s.n/tot*100);if(seen[nm]||pct<2)continue;seen[nm]=1;out.push({name:nm,hex:hx(ar,ag,ab),pct:pct});}
+          if(!render(out.slice(0,6))) renderMeta();
+        }catch(e){ renderMeta(); }
+      };
+      img.onerror=function(){ renderMeta(); };
+      img.src=url;
+    }
+    function imgUrl(){
+      var u=root.dataset.productImage;
+      if(u&&u.length>5) return u.indexOf('//')===0?'https:'+u:u;
+      var sels=['.product-gallery--viewport img[src]','.product-gallery img[src]','[data-product-gallery] img[src]','.media-gallery__slide img[src]'];
+      for(var i=0;i<sels.length;i++){var el=document.querySelector(sels[i]);if(el&&el.src)return el.src;}
+      return null;
+    }
+    var u=imgUrl();
+    if(u){ fromCanvas(u); }
+    else {
+      var obs=new MutationObserver(function(m,o){var v=imgUrl();if(v){o.disconnect();fromCanvas(v);}});
+      obs.observe(document.body,{childList:true,subtree:true});
+      setTimeout(function(){ obs.disconnect(); if(!cards.children.length) renderMeta(); },5000);
+    }
+  }
+  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',init);}else{init();}
+})();
+</script>
+
+{%- comment -%} "More Wallcoverings Like This" (CLIP row) MOVED to sections/product.liquid so it spans the full page width, below the two-column product block (Steve 2026-07-28). {%- endcomment -%}
diff --git a/shopify/theme-titlelink-fix-20260730-144953/edit/product-form-content.liquid b/shopify/theme-titlelink-fix-20260730-144953/edit/product-form-content.liquid
new file mode 100644
index 00000000..cfe795c7
--- /dev/null
+++ b/shopify/theme-titlelink-fix-20260730-144953/edit/product-form-content.liquid
@@ -0,0 +1,800 @@
+{% comment %}
+  @param product {Product}
+
+  @param show_social_media_icons {Boolean}
+
+  @param show_payment_button {Boolean}
+    Show the DPBs in the product form
+{% endcomment %}
+
+{% assign product_id = product.id %}
+{% assign selected_variant = product.selected_or_first_available_variant %}
+
+{%- liquid
+  assign _selected_variant_sku = selected_variant.sku | default: '' | downcase
+  assign _selected_variant_title = selected_variant.title | default: '' | downcase
+
+  assign _selected_variant_sku_suffix = _selected_variant_sku | slice: -7, 7
+
+  assign is_sample_sku = false
+  if _selected_variant_sku != blank and _selected_variant_sku_suffix == '-sample'
+    assign is_sample_sku = true
+  endif
+
+  assign is_sample_title = false
+  if _selected_variant_title contains 'sample'
+    assign is_sample_title = true
+  endif
+
+  assign is_sample_like = false
+  if is_sample_sku
+    assign is_sample_like = true
+  elsif is_sample_title and selected_variant.price == 0
+    assign is_sample_like = true
+  endif
+-%}
+
+<!-- -----------020------------ -->
+{% assign prods_quantity_order_min = 1 %}
+
+{% if product.metafields['global']['v_prods_quantity_order_min'] %}
+  {% if product.metafields['global']['v_prods_quantity_order_min'] != '' %}
+    {% assign prods_quantity_order_min = product.metafields['global']['v_prods_quantity_order_min'] %}
+  {% endif %}
+{% endif %}
+
+{% assign prods_quantity_order_units = 1 %}
+
+{% if product.metafields['global']['v_prods_quantity_order_units'] %}
+  {% if product.metafields['global']['v_prods_quantity_order_units'] != '' %}
+    {% assign prods_quantity_order_units = product.metafields['global']['v_prods_quantity_order_units'] %}
+  {% endif %}
+{% endif %}
+
+<!-- Minimum order quantity from custom metafields (e.g. Ralph Lauren fabrics) -->
+{% assign min_order_qty = product.metafields.custom.minimum_order_quantity | default: nil %}
+{% assign min_order_unit = product.metafields.custom.minimum_order_unit | default: '' %}
+{% assign has_min_order = false %}
+{% if min_order_qty != nil and min_order_qty != blank and min_order_qty > 1 %}
+  {% assign has_min_order = true %}
+  {% assign min_order_qty_num = min_order_qty | plus: 0 %}
+  {% if min_order_qty_num > prods_quantity_order_min %}
+    {% assign prods_quantity_order_min = min_order_qty_num %}
+  {% endif %}
+{% endif %}
+
+<!-- -------------------------- -->
+<script>
+  setTimeout(function () {
+    const qtySelector = document.querySelector('.mm_quantity .input-text.qty');
+    if (qtySelector) {
+      qtySelector.value = {{ prods_quantity_order_min }};
+    }
+  }, 1000);
+</script>
+
+{% if has_min_order %}
+<!-- Minimum order notice and validation -->
+<style>
+  .min-order-notice {
+    background: #fff3cd;
+    border: 1px solid #ffc107;
+    border-radius: 4px;
+    padding: 8px 12px;
+    margin: 10px 0;
+    font-size: 14px;
+    font-weight: 600;
+    color: #856404;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+  }
+  .min-order-notice svg {
+    flex-shrink: 0;
+  }
+  .min-order-error {
+    background: #f8d7da;
+    border: 1px solid #f5c6cb;
+    color: #721c24;
+    border-radius: 4px;
+    padding: 8px 12px;
+    margin: 8px 0 0;
+    font-size: 13px;
+    display: none;
+  }
+</style>
+<div class="min-order-notice" data-min-order-notice>
+  <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1a7 7 0 100 14A7 7 0 008 1zm0 2.5a.75.75 0 01.75.75v4a.75.75 0 01-1.5 0v-4A.75.75 0 018 3.5zm0 8a.75.75 0 100-1.5.75.75 0 000 1.5z"/></svg>
+  Minimum order: {{ min_order_qty }} {{ min_order_unit }}
+</div>
+<script>
+  (function() {
+    var MIN_QTY = {{ min_order_qty }};
+    var MIN_UNIT = {{ min_order_unit | json }};
+
+    document.addEventListener('DOMContentLoaded', function() {
+      // Find the product form (could be either .shopify-product-form or form with data-product-form)
+      var forms = document.querySelectorAll('form[action*="/cart/add"], form[data-product-form]');
+      forms.forEach(function(form) {
+        form.addEventListener('submit', function(e) {
+          var qtyInput = form.querySelector('.mm_quantity .input-text.qty') || form.querySelector('input[name="quantity"]');
+          if (!qtyInput) return;
+
+          var qty = parseInt(qtyInput.value, 10);
+          if (isNaN(qty) || qty < MIN_QTY) {
+            e.preventDefault();
+            e.stopImmediatePropagation();
+
+            // Show error message
+            var errorDiv = form.querySelector('.min-order-error');
+            if (!errorDiv) {
+              errorDiv = document.createElement('div');
+              errorDiv.className = 'min-order-error';
+              var addToCartBtn = form.querySelector('.product-add-to-cart');
+              if (addToCartBtn) {
+                addToCartBtn.appendChild(errorDiv);
+              }
+            }
+            errorDiv.textContent = 'Please enter a quantity of at least ' + MIN_QTY + ' ' + MIN_UNIT + '.';
+            errorDiv.style.display = 'block';
+
+            // Also fix the value
+            qtyInput.value = MIN_QTY;
+            var hqty = form.querySelector('.hquantity');
+            if (hqty) hqty.value = MIN_QTY;
+
+            // Hide error after 5s
+            setTimeout(function() { errorDiv.style.display = 'none'; }, 5000);
+            return false;
+          }
+        });
+      });
+
+      // Also enforce min on the minus button - prevent going below minimum
+      jQuery(document).on('click', '.minus', function() {
+        var $qty = jQuery(this).closest('.mm_quantity').find('.qty');
+        var currentVal = parseInt($qty.val(), 10);
+        if (currentVal < MIN_QTY) {
+          $qty.val(MIN_QTY);
+          jQuery(this).closest('.mm_quantity').find('.hquantity').attr('value', MIN_QTY);
+        }
+      });
+
+      // Enforce min on blur (manual typing)
+      jQuery(document).on('blur', '.mm_quantity .input-text.qty', function() {
+        var currentVal = parseInt(jQuery(this).val(), 10);
+        if (isNaN(currentVal) || currentVal < MIN_QTY) {
+          jQuery(this).val(MIN_QTY);
+          jQuery(this).closest('.mm_quantity').find('.hquantity').attr('value', MIN_QTY);
+        }
+      });
+
+      // Hide notice when sample variant is selected
+      jQuery(document).on('change', '.product-select, .single-option-selector', function() {
+        var $form = jQuery(this).closest('form, [data-product-form]');
+        var $selected = $form.find('.product-select option:selected, .product-select[type="hidden"]');
+        var selectedText = ($selected.text() || $selected.attr('data-variant-title') || '').toLowerCase();
+        var selectedSku = ($selected.attr('data-sku') || '').toLowerCase();
+        var isSample = /sample/i.test(selectedText) || /-sample$/i.test(selectedSku);
+
+        var $notice = jQuery('.min-order-notice[data-min-order-notice]');
+        if (isSample) {
+          $notice.hide();
+        } else {
+          $notice.show();
+        }
+      });
+    });
+  })();
+</script>
+{% endif %}
+
+{% for block in section.blocks %}
+  {% case block.type %}
+    {% when 'vendor' %}
+      <a class="product-vendor" href="{{ product.vendor | url_for_vendor }}" {{ block.shopify_attributes }}>{{ product.vendor }}</a>
+
+    {% when 'title' %}
+      <h1 class="product-title" {{ block.shopify_attributes }}>
+        {% if product != blank %}
+          {% comment %} Always link the title to its own canonical product URL.
+             product.url is root-relative (/products/<handle>) so it resolves to the
+             storefront domain — never the raw .myshopify.com search endpoint. {% endcomment %}
+          <a href="{{ product.url }}">
+            {{ product.title }}
+          </a>
+        {% else %}
+          {{ 'products.product.onboarding.title' | t }}
+        {% endif %}
+      </h1>
+      <span class="product-sku-label">SKU: </span>
+      {%- liquid
+        assign sku_raw = product.selected_or_first_available_variant.sku | default: ''
+        assign sku_display = sku_raw | replace: '-Sample', '' | replace: '-SAMPLE', '' | replace: '-sample', ''
+        assign sku_display = sku_display | replace: '--', '-' | replace: '--', '-'
+        assign sku_last_char = sku_display | slice: -1, 1
+        if sku_display != blank and sku_last_char == '-'
+          assign sku_display = sku_display | remove_last: '-'
+        endif
+      -%}
+      <span class="product-sku-value">{{ sku_display }}</span>
+    {% when '@app' %}
+      <div class="product-app" {{ block.shopify_attributes }}>
+        {% render block %}
+      </div>
+
+    {% when 'custom-liquid' %}
+      {% if block.settings.custom_liquid != blank %}
+        <div class="product__custom-liquid" {{ block.shopify_attributes }}>
+          {{ block.settings.custom_liquid }}
+        </div>
+      {% endif %}
+
+    {% when 'collapsible-tab' %}
+      {% if block.settings.collapsible_tab_heading != blank %}
+        <details class="collapsible-tab" {{ block.shopify_attributes }}>
+          <summary class="collapsible-tab__heading">
+            <span>{{ block.settings.collapsible_tab_heading | escape }}</span>
+            {%
+              render 'icons',
+              id: 'chevron-right',
+            %}
+          </summary>
+
+          {% if block.settings.collapsible_tab_text != blank %}
+            <div class="collapsible-tab__text rte">
+              {{ block.settings.collapsible_tab_text }}
+            </div>
+          {% endif %}
+        </details>
+      {% endif %}
+
+    {% when 'tabs' %}
+      {% assign tab_counter = 1 %}
+      <div class="product-tabs" {{ block.shopify_attributes }}>
+        {% if block.settings.show_product_description %}
+          <input
+            class="product-tabs__radio"
+            id="product-tabs__radio-{{ section.id }}"
+            name="{{ section.id }}"
+            tabindex="0"
+            type="radio"
+            checked="checked"
+          >
+
+          <label class="product-tabs__label" for="product-tabs__radio-{{ section.id }}">
+            {{ 'onboarding.tab_title'| t }}
+          </label>
+
+          <div class="product-tabs__panel rte" tabindex="0">
+            {% if onboarding %}
+              {{ 'onboarding.tab_content'| t }}
+            {% else %}
+              {% if product.description != blank %}
+                {{ product.description }}
+              {% else %}
+                {{ 'onboarding.tab_content'| t }}
+              {% endif %}
+            {% endif %}
+          </div>
+        {% endif %}
+
+        {% for i in (1..3) %}
+          {% assign tab_text_key = 'tab_text_' | append: i %}
+          {% assign tab_heading_key = 'tab_heading_' | append: i %}
+          {% assign tab_heading_value = block.settings[tab_heading_key] %}
+          {% assign tab_text_value = block.settings[tab_text_key] %}
+
+          {% if tab_heading_value != blank %}
+            <input
+              class="product-tabs__radio"
+              id="product-tabs__radio-{{ i }}-{{ section.id }}"
+              name="{{ section.id }}"
+              tabindex="0"
+              type="radio"
+              {% if tab_counter == 1 and block.settings.show_product_description == false %}
+                checked
+              {% endif %}
+            >
+
+            <label class="product-tabs__label" for="product-tabs__radio-{{ i }}-{{ section.id }}">
+              {{ tab_heading_value | escape }}
+            </label>
+
+            <div class="product-tabs__panel rte" tabindex="0">
+              {{ tab_text_value }}
+            </div>
+
+            {% assign tab_counter = tab_counter | plus: 1 %}
+          {% endif %}
+        {% endfor %}
+      </div>
+
+    {% when 'price' %}
+      <div class="product__price" {{ block.shopify_attributes }}>
+        {%- comment -%}
+          DW trade/designer pricing tiers (2026-06-11): any customer carrying one of these
+          professional tags sees the trade price display (15% off, ×0.85). This restores the
+          pre-app behavior where every designer/trade tier got the discount — not just 'trade'.
+          Matches *any* tag containing 'designer' (interior_designer, commercial_designer, …)
+          plus the explicit tiers below. Add new tiers to the explicit list as needed.
+        {%- endcomment -%}
+        {%- assign dw_is_trade = false -%}
+        {%- for dw_t in customer.tags -%}
+          {%- assign dw_tl = dw_t | downcase | strip -%}
+          {%- if dw_tl contains 'designer' or dw_tl == 'trade' or dw_tl == 'architect' or dw_tl == 'stager' or dw_tl == 'hospitality' or dw_tl == 'retailer' -%}
+            {%- assign dw_is_trade = true -%}
+            {%- break -%}
+          {%- endif -%}
+        {%- endfor -%}
+        <input type="hidden" id="customer_tag" value="{% if dw_is_trade %}trade{% endif %}">
+        <p class="product-price">
+          {% comment %} SA Wholesale (saw_/wbuyx/"WHO") app removed 2026-06-11 — it left the price span blank. Render native variant price unconditionally. {% endcomment %}
+            <!-- native price markup -->
+            <meta itemprop="price" content="{{ selected_variant.price | money_without_currency }}">
+            <span class="product-price-minimum money {% if dw_is_trade %}original{% endif %}">
+              {% if product != blank %}
+                {{ selected_variant.price | money }}
+              {% else %}
+                {{ 'products.product.onboarding.price' | t }}
+              {% endif %}
+            </span>
+            {% if dw_is_trade %}
+              <span
+                class="product-price-discount"
+              >
+                {% assign target_product = selected_variant %}
+                {% assign com_selected_variant_title = selected_variant.title | capitalize %}
+                {% assign com_selected_variant_sku = selected_variant.sku | default: '' | downcase %}
+                {% if com_selected_variant_title == "Sample" or com_selected_variant_sku contains "sample" %}
+                  {% comment %} {% render 'money', value: 0 %} {% endcomment %}
+                  Complimentary Sample
+                {% else %}
+                  {% assign com_variant_first_title = product.variants[0].title | capitalize %}
+                  {% if com_variant_first_title == "Sample" and product.variants[1] != blank  %}
+                    {% assign target_product = product.variants[1] %}
+                  {% endif  %}
+                  {% assign discounted_price = target_product.price | times:0.85 | round: 2 %}
+                  {% render 'money', value: discounted_price %}
+                {% endif %}
+
+              </span>
+            {% endif %}
+           {% comment %}
+            <span
+              class="product-price-compare money original {% if selected_variant.compare_at_price > selected_variant.price %}visible{% endif %}"
+            >
+              {{ selected_variant.compare_at_price | money }}
+            </span>
+           {% endcomment %}
+        </p>
+
+        {% assign variant_for_unit_price = selected_variant %}
+        {% comment %}Inject unit-price begin{% endcomment %}
+        {% comment %}
+          @param variant_for_unit_price
+            Product variant for price
+          @param tax_text
+            String containing 'tax included' text
+        {% endcomment %}
+
+        {% capture total_quantity %}
+          <span class="product-price__unit-price-total-quantity" data-unit-price-quantity>
+            {{ variant_for_unit_price.unit_price_measurement.quantity_value }}{{ variant_for_unit_price.unit_price_measurement.quantity_unit }}
+          </span>
+        {% endcapture %}
+
+
+        {% capture unit_price %}
+          <span class="product-price__unit-price-amount money" data-unit-price-amount>
+            {{ variant_for_unit_price.unit_price | money }}
+          </span>
+        {% endcapture %}
+        {% capture unit_measure %}
+          <span class="product-price__unit-price-measure" data-unit-price-measure>
+            {%- if variant_for_unit_price.unit_price_measurement.reference_value != 1 -%}
+              {{ variant_for_unit_price.unit_price_measurement.reference_value }}
+            {%- endif %}
+            {{ variant_for_unit_price.unit_price_measurement.reference_unit }}
+          </span>
+        {% endcapture %}
+
+        <div
+          class="
+            product-price__unit-price
+            {% unless variant_for_unit_price.unit_price_measurement %}hidden{% endunless %}
+          "
+          data-unit-price
+        >
+          {{ 'products.product.price_per_unit_html' | t: total_quantity: total_quantity, unit_price: unit_price, unit_measure: unit_measure | strip_newlines }}
+        </div>
+
+        {% assign variant_for_unit_price = blank %}
+        {% comment %}Inject unit-price end{% endcomment %}
+
+
+        {%- capture tax_text -%}
+        {{ 'products.product.tax_line_html' | t }}
+        {%- endcapture -%}
+
+        {%- if tax_text != blank -%}
+          <div class="
+            product-price__tax
+            {% unless selected_variant.taxable %}hidden{% endunless %}
+            "
+            data-tax-line
+          >
+            {{ tax_text }}
+          </div>
+        {%- endif -%}
+
+        {{ form | payment_terms }}
+      </div>
+
+    {% when 'form' %}
+
+      <div class="product__form" {{ block.shopify_attributes }}>
+
+        {% unless product.has_only_default_variant %}
+          <div class="product-options">
+            {%-
+              render 'product-options-dropdown',
+              product: product,
+              form_id: product_id
+            -%}
+
+            <label
+              class="
+                product-option-column-1
+                product-option-row-4
+                product-option-quantity-label
+                {% if is_sample_like %}hidden{% endif %}
+              "
+              for="default-variant-quantity-input"
+            >
+              <strong>{{ 'general.general.quantity' | t }}:</strong>
+            </label>
+            {% comment %}
+            <input
+              class="product-option-quantity"
+              id="default-variant-quantity-input"
+              data-product-quantity-input
+              type="number"
+              inputmode="numeric"
+              name="quantity"
+              value="1"
+            >
+            {% endcomment %}
+            <!-- 020 -->
+            <div class="mm_quantity mm_buttons_added product-option-row-4 {% if is_sample_like %}hidden{% endif %}">
+              <input type="hidden" class="hquantity" name="hquantity" value="{{ prods_quantity_order_min }}">
+              <input type="button" value="-" class="minus">
+              <input
+                type="text"
+                step="{{ prods_quantity_order_units }}"
+                min="{{ prods_quantity_order_min }}"
+                data-order-step="{{ prods_quantity_order_units }}"
+                data-order-min="{{ prods_quantity_order_min }}"
+                max=""
+                name="quantity"
+                value="{{ prods_quantity_order_min }}"
+                title="Qty"
+                class="input-text qty text"
+                size="4"
+                pattern=""
+                inputmode=""
+                onkeypress="mm_validate(event)"
+              >
+              <input type="button" value="+" class="plus">
+            </div>
+            <!--  -->
+
+            <div class="selector-wrapper no-js-required">
+              <label for="product-select-{{ product_id }}"></label>
+              <select
+                class="product-select"
+                name="id"
+                id="product-select-{{ product_id }}"
+                data-select-ignore
+              >
+                {% for variant in product.variants %}
+                  {% if variant.available %}
+                    {% assign com_variant_title = variant.title | capitalize %}
+                    {% if com_variant_title == "Sample" %}
+                      {% assign dl_sample_variant = variant.id %}
+                      {% assign dl_sample_variant_title = product.title | append: ' - ' | append: variant.title %}
+                      {% assign dl_sample = 'small_sample' %}
+                    {% endif %}
+
+                    {% if com_variant_title == "Large Sample" %}
+                      {% assign dl_sample_variant2 = variant.id %}
+                      {% assign dl_sample_variant_title2 = product.title | append: ' - ' | append: variant.title %}
+                      {% assign dl_sample2 = 'large_sample' %}
+                    {% endif %}
+
+                    <option
+                      {% if variant == selected_variant %}selected="selected"{% endif %}
+                      value="{{ variant.id }}"
+                      data-variant-id="{{ variant.id }}"
+                      data-sku="{{ variant.sku }}"
+                      data-vprice="{{ variant.price }}"
+                    >
+                      {{ variant.title }} - {{ variant.price | money }}
+                    </option>
+                  {% else %}
+                    <option 
+                    disabled="disabled" 
+                    data-variant-id="{{ variant.id }}" 
+                    value="{{ variant.id }}"
+                    data-sku="{{ variant.sku }}">
+                      {{ variant.title }} - {{ 'products.product.sold_out' | t }}
+                    </option>
+                  {% endif %}
+                {% endfor %}
+              </select>
+            </div>
+          </div>
+        {% else %}
+          <div class="product-options product-options-default-only">
+            <input
+              class="product-select"
+              name="id"
+              value="{{ product.variants[0].id }}"
+              type="hidden"
+              data-variant-title="{{ product.variants[0].title }}"
+              data-sku="{{ product.variants[0].sku }}"
+              data-vprice="{{ product.variants[0].price }}"
+            />
+
+            <label
+              class="
+                product-option-column-1
+                product-option-row-1
+                product-option-quantity-label
+                {% if is_sample_like %}hidden{% endif %}
+              "
+              for="quantity-input"
+            >
+              <strong>{{ 'general.general.quantity' | t }}:</strong>
+            </label>
+
+<!--             <input
+              class="product-option-quantity"
+              id="quantity-input"
+              data-product-quantity-input
+              type="number"
+              inputmode="numeric"
+              name="quantity"
+              value="1"
+            > -->
+            <!-- 020 -->
+            <div class="mm_quantity mm_buttons_added product-option-row-1 {% if is_sample_like %}hidden{% endif %}">
+              <input type="hidden" class="hquantity" name="hquantity" value="{{ prods_quantity_order_min }}">
+              <input type="button" value="-" class="minus">
+              <input
+                type="text"
+                step="{{ prods_quantity_order_units }}"
+                min="{{ prods_quantity_order_min }}"
+                data-order-step="{{ prods_quantity_order_units }}"
+                data-order-min="{{ prods_quantity_order_min }}"
+                max=""
+                name="quantity"
+                value="{{ prods_quantity_order_min }}"
+                title="Qty"
+                class="input-text qty text"
+                size="4"
+                pattern=""
+                inputmode=""
+                onkeypress="mm_validate(event)"
+              >
+              <input type="button" value="+" class="plus">
+            </div>
+            <!--  -->
+          </div>
+        {% endunless %}
+
+        {% for tag in product.tags %}
+          {% if tag contains 'custom mural' %}
+            <!-- Custom Price Calculator -->
+            <div id="custom-calculator" style="display:none" data-calculatorid="62388fabd48c8"></div>
+            <script>
+              document.querySelector(".product-options .last_variant").innerHTML = "Custom Sizes";
+
+              var appendCalc = setInterval(moveCalc, 300);
+
+              function moveCalc() {
+                var calc = document.getElementById("calculator");
+                if (calc) {
+                  document
+                    .querySelector(".product-options")
+                    .insertBefore(
+                      document.getElementById("calculator"),
+                      document.querySelector(".product-options").children[2]
+                    );
+                  clearInterval(appendCalc);
+                }
+              }
+            </script>
+            <style>
+              .product-options {
+                display: flex;
+                flex-direction: column;
+                align-items: baseline;
+                margin-left: 0;
+              }
+              .product-options .product-option-quantity-label,
+              .mm_quantity.mm_buttons_added {
+                display: none !important;
+              }
+            </style>
+          {% endif %}
+        {% endfor %}
+
+        {% if product.available %}
+          <div id="infiniteoptions-container"></div>
+          <div id="uploadery-container"></div>
+        {% endif %}
+
+        <!-- Print PDF button -->
+        {% render 'button-product-pdf-print' %}
+
+        <div
+          class="
+            product-add-to-cart
+            {% if product != blank and show_payment_button %}
+              product-smart-payments
+            {% endif %}
+          "
+        >
+          {% if selected_variant.available and template.name != 'password' %}
+            <!-- 020 -->
+            <div class="custom-contact-message"></div>
+
+            <a
+              href="#"
+              class="dl-contact-btn custom-popup-contact-form"
+              {% if variant.price == 0 %}
+                style="display:inline-block;"
+              {% else %}
+                style="display:none;"
+              {% endif %}
+            >
+              Contact Us
+            </a>
+
+            {% assign title = 'Request Sample' %}
+            {% assign showonly = blank %}
+            {% if product.tags contains "Showroom only" and product.price == 0 %}
+              {% assign showonly = 'showonly' %}
+            {% endif %}
+            {% if product.tags contains "Showroom Only" and product.price == 0 %}
+              {% assign showonly = 'showonly' %}
+            {% endif %}
+
+            {% if is_sample_sku and dl_sample == blank and dl_sample2 == blank %}
+              <a
+                href=""
+                data-dlvid="{{ selected_variant.id }}"
+                data-dlvtitle="{{ product.title | append: ' - Sample' }}"
+                class="dl-sample-btn {{ showonly }}"
+                data-product-id="{{ product.id }}"
+              >
+                {{ title }}
+              </a>
+            {% endif %}
+
+            {% if dl_sample != blank and dl_sample2 != blank and dl_sample == 'small_sample' and dl_sample2 == 'large_sample' %}
+              {% assign title = 'Small Sample' %}
+            {% endif %}
+
+            {% if dl_sample == 'small_sample' and dl_sample_variant !='' and  dl_sample_variant_title != '' %}
+              <a
+                href=""
+                data-dlvid="{{ dl_sample_variant }}"
+                data-dlvtitle="{{ dl_sample_variant_title }}"
+                class="dl-sample-btn {{ showonly }}"
+                data-product-id="{{ product.id }}"
+              >
+                {{ title }}
+              </a>
+            {% endif %}
+
+            {% if dl_sample2 == 'large_sample' and dl_sample_variant2 !='' and  dl_sample_variant_title2 != '' %}
+              <a
+                href=""
+                data-dlvid="{{ dl_sample_variant2 }}"
+                data-dlvtitle="{{ dl_sample_variant_title2 }}"
+                class="dl-sample-btn {{ showonly }}"
+                data-product-id="{{ product.id }}"
+              >
+                Large Sample
+              </a>
+            {% endif %}
+            {% for variant_item in product.variants %}
+              {% if variant_item.price == 0 and variant_item.title contains "Sample" and variant_item.title != "Sample" %}
+                {% assign showing_title = variant_item.title %}
+                {% if showing_title == "Textured Vinyl Sample" %}
+                  {% assign showing_title = "Vinyl Sample" %}
+                {% endif %}
+                <a
+                  href=""
+                  data-dlvid="{{ variant_item.id }}"
+                  data-dlvtitle="{{ variant_item.title }}"
+                  class="dl-second-sample-btn {% if variant_item.id != product.selected_or_first_available_variant.id %}unavailable{% endif %} {{ showonly }}"
+                  data-product-id="{{ product.id }}"
+                >
+                  Request {{ showing_title }}
+                </a>
+              {% endif %}
+            {% endfor %}
+
+            {% render 'spec-sheet-button' %}
+
+<!--             <input class="add-to-cart" type="submit" value="{{ 'products.product.add_to_cart' | t }}" /> -->
+            {% assign com_first_variant_title = product.selected_or_first_available_variant.title | capitalize %}
+            <input class="add-to-cart {% if is_sample_like %}hidden{% endif %} {% if product.variants.size == 1 and com_first_variant_title == "Sample" %}hidden{% endif %} {% if product.selected_or_first_available_variant.price == 0 %}unavailable{% endif %}" type="submit" value="{{ 'products.product.add_to_cart' | t }}"/>
+          {% else %}
+            <input class="add-to-cart disabled" type="submit" value="{{ 'products.product.sold_out' | t }}" disabled/>
+          {% endif %}
+
+          <!--020 -->
+          {% for t in product.tags %}
+             {% if t == 'Customize' %}
+                    <a href="#" class="dl-customize-btn custom-popup-contact-form">Customize this Pattern</a>
+             {% endif %}
+          {% endfor %}
+
+          {% if show_payment_button %}
+            {{ form | payment_button }}
+          {% endif %}
+
+          {% capture the_snippet_fave %}{% include 'socialshopwave-widget-fave' %}{% endcapture %}
+          {% unless the_snippet_fave contains 'Liquid error' %}
+          {{ the_snippet_fave }}
+          {% endunless %}
+        </div>
+        <div class="product-message"></div>
+
+        <div data-surface-pick-up></div>
+        {%-
+          render 'modal',
+          modal_id: 'surface-pick-up',
+          modal_class: 'surface-pick-up-modal',
+        -%}
+      </div>
+
+    {% when 'social' %}
+      <div class="product__social" {{ block.shopify_attributes }}>
+        {% render 'share-buttons' %}
+      </div>
+
+    {% when 'description' %}
+      {% if product != blank and product.description != blank %}
+        <div class="product-description rte" {{ block.shopify_attributes }}>
+          {{ product.description }}
+        </div>
+      {% elsif product == blank %}
+        <div class="product-description rte" {{ block.shopify_attributes }}>
+          {{ 'products.product.onboarding.description' | t }}
+        </div>
+      {% endif %}
+
+    {%- when 'rating' -%}
+      {%- if product.metafields.reviews.rating.value != blank -%}
+        <div class="product__rating rating" {{ block.shopify_attributes }}>
+          {%
+            render 'rating-stars',
+            value: product.metafields.reviews.rating.value.rating,
+            scale_max: product.metafields.reviews.rating.value.scale_max,
+          %}
+          <p class="rating__text">
+            <span aria-hidden="true">{{ product.metafields.reviews.rating.value }} / {{ product.metafields.reviews.rating.value.scale_max }}</span>
+          </p>
+          <p class="rating__count">
+            <span aria-hidden="true">({{ product.metafields.reviews.rating_count }})</span>
+            <span class="visually-hidden">{{ product.metafields.reviews.rating_count }} {{ "general.accessibility.total_reviews" | t }}</span>
+          </p>
+        </div>
+      {%- endif -%}
+  {% endcase %}
+{% endfor %}
diff --git a/shopify/theme-titlelink-fix-20260730-144953/live/color-dots.liquid b/shopify/theme-titlelink-fix-20260730-144953/live/color-dots.liquid
new file mode 100644
index 00000000..17becd29
--- /dev/null
+++ b/shopify/theme-titlelink-fix-20260730-144953/live/color-dots.liquid
@@ -0,0 +1,245 @@
+{% assign color_dots_json_val = product.metafields.custom.color_dots_json.value %}
+{% assign color_details_val = product.metafields.custom.color_details.value %}
+{% if color_dots_json_val != blank %}
+  {% assign color_details_raw = color_dots_json_val | json %}
+{% elsif color_details_val != blank %}
+  {% assign color_details_raw = color_details_val | json %}
+{% else %}
+  {% assign color_details_raw = blank %}
+{% endif %}
+{% assign color_hex = product.metafields.custom.color_hex.value %}
+{% assign primary_hex = product.metafields.custom.primary_color_hex.value %}
+{% assign ai_colors_raw = product.metafields.dwc.ai_generated_colors.value | json %}
+{% assign color_name = product.metafields.custom.color_name.value %}
+
+{%- comment -%}
+  Color Dots v3 — 3-tier color system
+  Tier 1: color_details metafield (Gemini-enriched)
+  Tier 2: primary_color_hex + ai_generated_colors
+  Tier 3: Canvas pixel extraction from product image (onload)
+{%- endcomment -%}
+
+<div class="dw-color-dots" id="dw-color-dots-wrapper" style="display:flex;justify-content:center;gap:10px;padding:12px 0 8px;flex-wrap:wrap;min-height:30px;">
+  <div id="dw-color-dots-container" style="display:flex;justify-content:center;gap:10px;flex-wrap:wrap;"
+    {% if color_details_raw != blank and color_details_raw != 'null' %}data-colors='{{ color_details_raw | json }}'{% endif %}
+    {% if primary_hex != blank %}data-primary-hex="{{ primary_hex }}"{% endif %}
+    {% if ai_colors_raw != blank and ai_colors_raw != 'null' %}data-ai-colors='{{ ai_colors_raw }}'{% endif %}
+    {% if color_name != blank %}data-color-name="{{ color_name }}"{% endif %}
+    {% if color_hex != blank %}data-color-hex="{{ color_hex }}"{% endif %}
+    {% if product.featured_image %}data-product-image="{{ product.featured_image | img_url: '200x200' }}"{% endif %}
+  ></div>
+</div>
+
+<script>
+(function(){
+  function init() {
+   try {
+    var container = document.getElementById('dw-color-dots-container');
+    if (!container || container.dataset.rendered) return;
+    container.dataset.rendered = '1';
+
+    var C = {
+      'red':'red-wallpaper-collection','crimson':'red-wallpaper-collection','burgundy':'red-wallpaper-collection','wine':'red-wallpaper-collection','maroon':'red-wallpaper-collection',
+      'blue':'blue-wallpaper','navy':'blue-wallpaper','cobalt':'blue-wallpaper','indigo':'blue-wallpaper','sky blue':'blue-wallpaper','steel blue':'blue-wallpaper','royal blue':'blue-wallpaper',
+      'green':'green-wallpaper-collection','sage':'green-wallpaper-collection','olive':'green-wallpaper-collection','forest green':'green-wallpaper-collection','emerald':'green-wallpaper-collection','mint':'green-wallpaper-collection','moss':'green-wallpaper-collection',
+      'gold':'gold-wallpaper','golden':'gold-wallpaper','amber':'gold-wallpaper',
+      'yellow':'yellow-wallpaper-collection','mustard':'yellow-wallpaper-collection',
+      'orange':'orange','rust':'orange','terracotta':'orange',
+      'pink':'pink-wallcovering-2','blush':'pink-wallcovering-2','rose':'pink-wallcovering-2','salmon':'pink-wallcovering-2','fuchsia':'pink-wallcovering-2',
+      'purple':'purple','violet':'purple','plum':'purple','lavender':'purple','lilac':'purple','mauve':'purple',
+      'black':'black','charcoal':'black','ebony':'black','onyx':'black',
+      'white':'white-wallpaper-collection','ivory':'white-wallpaper-collection','cream':'white-wallpaper-collection','pearl':'white-wallpaper-collection','off-white':'white-wallpaper-collection',
+      'gray':'grey-silver','grey':'grey-silver','silver':'grey-silver','slate':'grey-silver',
+      'brown':'brown-wallpaper-collection','chocolate':'brown-wallpaper-collection','espresso':'brown-wallpaper-collection','walnut':'brown-wallpaper-collection','mocha':'brown-wallpaper-collection','sienna':'brown-wallpaper-collection',
+      'beige':'beige-cream-wallpaper-collection','tan':'beige-cream-wallpaper-collection','taupe':'beige-cream-wallpaper-collection','sand':'beige-cream-wallpaper-collection','khaki':'beige-cream-wallpaper-collection','camel':'beige-cream-wallpaper-collection','oatmeal':'beige-cream-wallpaper-collection','linen':'beige-cream-wallpaper-collection',
+      'coral':'coral','peach':'coral',
+      'teal':'turquoise','aqua':'turquoise','turquoise':'turquoise','cyan':'turquoise',
+      'metallic':'metallic-wallpaper-collections','copper':'metallic-wallpaper-collections','bronze':'metallic-wallpaper-collections'
+    };
+
+    var N = {
+      'red':'#DC2626','crimson':'#DC143C','burgundy':'#800020','wine':'#722F37','maroon':'#800000',
+      'blue':'#2563EB','navy':'#1B2A4A','cobalt':'#0047AB','indigo':'#3F51B5','sky blue':'#87CEEB','steel blue':'#4682B4','royal blue':'#4169E1',
+      'green':'#16A34A','sage':'#87AE73','olive':'#808000','forest green':'#228B22','emerald':'#50C878','mint':'#98FF98','moss':'#8A9A5B',
+      'gold':'#D4AF37','golden':'#FFD700','amber':'#FFBF00',
+      'yellow':'#EAB308','mustard':'#FFDB58',
+      'orange':'#EA580C','rust':'#B7410E','terracotta':'#E2725B',
+      'pink':'#EC4899','blush':'#DE5D83','rose':'#FF007F','salmon':'#FA8072','fuchsia':'#FF00FF',
+      'purple':'#9333EA','violet':'#7C3AED','plum':'#8E4585','lavender':'#E6E6FA','lilac':'#C8A2C8','mauve':'#E0B0FF',
+      'black':'#1A1A1A','charcoal':'#36454F','ebony':'#555D50','onyx':'#353839',
+      'white':'#FAFAFA','ivory':'#FFFFF0','cream':'#FFFDD0','pearl':'#F0EAD6','off-white':'#FAF9F6',
+      'gray':'#6B7280','grey':'#6B7280','silver':'#C0C0C0','slate':'#708090',
+      'brown':'#92400E','chocolate':'#7B3F00','espresso':'#3C1414','walnut':'#5C3A1D','mocha':'#967969','sienna':'#A0522D',
+      'beige':'#F5F5DC','tan':'#D2B48C','taupe':'#483C32','sand':'#C2B280','khaki':'#F0E68C','camel':'#C19A6B','oatmeal':'#B8AB8C','linen':'#FAF0E6',
+      'coral':'#FF6F61','peach':'#FFCBA4',
+      'teal':'#0D9488','aqua':'#00FFFF','turquoise':'#40E0D0','cyan':'#06B6D4',
+      'metallic':'#AAA9AD','copper':'#B87333','bronze':'#CD7F32',
+      'deep indigo':'#1B2A4A','dark navy':'#1B2A4A','midnight':'#191970'
+    };
+
+    function findColl(name) {
+      var l = (name||'').toLowerCase();
+      if (C[l]) return C[l];
+      for (var k in C) { if (l.indexOf(k) > -1) return C[k]; }
+      return null;
+    }
+
+    function makeDot(hex, name, pct) {
+      try {
+        var coll = findColl(name);
+        var a = document.createElement('a');
+        a.href = coll ? '/collections/' + coll + '?sort_by=created-descending' : '/search?q=' + encodeURIComponent(name||hex) + '&sort_by=created-descending';
+        a.title = (coll?'Shop ':'Search ') + (name||'color') + ' (' + hex + ')';
+        a.style.textDecoration = 'none';
+        var d = document.createElement('div');
+        var p = (pct == null ? 20 : pct);
+        var sz=Math.max(22,Math.min(52,16+p*0.6));d.style.cssText='width:'+sz+'px;height:'+sz+'px;border-radius:50%;background:'+hex+';border:2px solid #eee;box-shadow:0 1px 3px rgba(0,0,0,0.1);cursor:pointer;transition:transform 0.15s,box-shadow 0.15s;flex-shrink:0;';
+        d.onmouseenter = function(){this.style.transform='scale(1.3)';this.style.boxShadow='0 2px 8px rgba(0,0,0,0.2)';};
+        d.onmouseleave = function(){this.style.transform='scale(1)';this.style.boxShadow='0 1px 3px rgba(0,0,0,0.1)';};
+        a.appendChild(d);
+        return a;
+      } catch(e) { return null; }
+    }
+
+    function renderDots(colors) {
+      colors.forEach(function(c){ var el = makeDot(c.hex, c.name, (c.pct || c.percentage)); if (el) container.appendChild(el); });
+    }
+
+    /* Canvas color refs */
+    var REF = {
+      'red':[220,38,38],'navy':[27,42,74],'blue':[37,99,235],'green':[22,163,74],'sage':[135,174,115],
+      'gold':[212,175,55],'yellow':[234,179,8],'orange':[234,88,12],'pink':[236,72,153],
+      'purple':[147,51,234],'black':[26,26,26],'charcoal':[54,69,79],'white':[250,250,250],
+      'ivory':[255,255,240],'cream':[255,253,208],'gray':[107,114,128],'silver':[192,192,192],
+      'brown':[146,64,14],'beige':[245,245,220],'tan':[210,180,140],'taupe':[72,60,50],
+      'teal':[13,148,136],'coral':[255,111,97],'olive':[128,128,0],'rust':[183,65,14],
+      'burgundy':[128,0,32],'slate':[112,128,144],'sand':[194,178,128],'mint':[152,255,152],
+      'turquoise':[64,224,208],'copper':[184,115,51],'bronze':[205,127,50],'mauve':[224,176,255]
+    };
+
+    function closestName(r,g,b) {
+      var best='',bestD=1e9;
+      for(var n in REF){var c=REF[n],d=Math.sqrt(Math.pow(r-c[0],2)+Math.pow(g-c[1],2)+Math.pow(b-c[2],2));if(d<bestD){bestD=d;best=n;}}
+      return best;
+    }
+    function rgbHex(r,g,b){return '#'+[r,g,b].map(function(x){return x.toString(16).padStart(2,'0');}).join('');}
+
+    function extractFromCanvas(imgUrl) {
+      var img = new Image();
+      img.crossOrigin = 'anonymous';
+      img.onload = function() {
+        try {
+        var cv = document.createElement('canvas'), sz = 50;
+        cv.width=sz; cv.height=sz;
+        var ctx=cv.getContext('2d');
+        ctx.drawImage(img,0,0,sz,sz);
+        var px=ctx.getImageData(0,0,sz,sz).data;
+        var bk={};
+        for(var i=0;i<px.length;i+=4){
+          var kr=Math.round(px[i]/32)*32,kg=Math.round(px[i+1]/32)*32,kb=Math.round(px[i+2]/32)*32;
+          var key=kr+','+kg+','+kb;
+          if(!bk[key])bk[key]={r:0,g:0,b:0,n:0};
+          bk[key].r+=px[i];bk[key].g+=px[i+1];bk[key].b+=px[i+2];bk[key].n++;
+        }
+        var sorted=Object.values(bk).sort(function(a,b){return b.n-a.n;});
+        var tot=sz*sz,colors=[],seen={};
+        for(var j=0;j<Math.min(sorted.length,8);j++){
+          var s=sorted[j],ar=Math.round(s.r/s.n),ag=Math.round(s.g/s.n),ab=Math.round(s.b/s.n);
+          var nm=closestName(ar,ag,ab);
+          if(seen[nm])continue; seen[nm]=true;
+          colors.push({name:nm,hex:rgbHex(ar,ag,ab),percentage:Math.round(s.n/tot*100)});
+          if(colors.length>=5)break;
+        }
+        if(colors.length>0) renderDots(colors);
+        } catch(e) {
+          /* CORS or canvas security error — try without crossOrigin */
+          var img2 = new Image();
+          img2.onload = function() {
+            try {
+              var cv2 = document.createElement('canvas'), sz2 = 50;
+              cv2.width=sz2; cv2.height=sz2;
+              var ctx2=cv2.getContext('2d');
+              ctx2.drawImage(img2,0,0,sz2,sz2);
+              /* Cannot getImageData without CORS — use image dominant color via 1x1 scale */
+              cv2.width=1; cv2.height=1;
+              ctx2.drawImage(img2,0,0,1,1);
+              var p=ctx2.getImageData(0,0,1,1).data;
+              var nm=closestName(p[0],p[1],p[2]);
+              renderDots([{name:nm,hex:rgbHex(p[0],p[1],p[2]),percentage:100}]);
+            } catch(e2) {}
+          };
+          img2.src = imgUrl;
+        }
+      };
+      img.onerror = function(){};
+      img.src = imgUrl;
+    }
+
+    function getImageUrl() {
+      var url = container.dataset.productImage;
+      if (url && url.length > 5) return url.indexOf('//') === 0 ? 'https:' + url : url;
+      var sels = [
+        '.product-gallery--viewport img[src]',
+        '.product-gallery img[src]',
+        '[data-product-gallery] img[src]',
+        '.product-gallery--viewport--figure img[src]',
+        '.product__media img[src]',
+        '.product-single__photo img[src]'
+      ];
+      for (var i = 0; i < sels.length; i++) {
+        var el = document.querySelector(sels[i]);
+        if (el && el.src) return el.src;
+      }
+      return null;
+    }
+
+    /* ── Tier 1: color_details metafield ── */
+    var colorsData = container.dataset.colors;
+    if (colorsData) {
+      try {
+        /* Fix Shopify Liquid Ruby hash syntax: => becomes : */
+        var fixed = colorsData.replace(/=>/g, ':');
+        renderDots(JSON.parse(fixed));
+        return;
+      } catch(e) {}
+    }
+
+    /* ── Tier 2: primary_color_hex + ai_generated_colors ── */
+    var ph = container.dataset.primaryHex;
+    var aiRaw = container.dataset.aiColors;
+    if (ph || aiRaw) {
+      var added = {};
+      if (ph) { var pd=makeDot(ph, container.dataset.colorName||'primary'); if(pd)container.appendChild(pd); added[ph.toLowerCase()]=true; }
+      try {
+        var ai = JSON.parse((aiRaw||'[]').replace(/=>/g, ':'));
+        ai.forEach(function(nm){ var h=N[nm.toLowerCase()]; if(h&&!added[h.toLowerCase()]){var ad=makeDot(h,nm); if(ad)container.appendChild(ad);added[h.toLowerCase()]=true;} });
+      } catch(e) {}
+      if (container.children.length > 0) return;
+    }
+
+    /* ── Tier 2.5: single color_hex ── */
+    var sh = container.dataset.colorHex;
+    if (sh) { var shd=makeDot(sh, container.dataset.colorName||'color'); if(shd)container.appendChild(shd); return; }
+
+    /* ── Tier 3: Canvas extraction from product image ── */
+    var imgUrl = getImageUrl();
+    if (imgUrl) {
+      extractFromCanvas(imgUrl);
+    } else {
+      var obs = new MutationObserver(function(m, o) {
+        var url = getImageUrl();
+        if (url) { o.disconnect(); extractFromCanvas(url); }
+      });
+      obs.observe(document.body, { childList: true, subtree: true });
+      setTimeout(function(){ obs.disconnect(); }, 5000);
+    }
+   } catch(e) { /* never let color-dot rendering abort other page init */ }
+  }
+
+  if (document.readyState === 'loading') {
+    document.addEventListener('DOMContentLoaded', init);
+  } else {
+    init();
+  }
+})();
+</script>
diff --git a/shopify/theme-titlelink-fix-20260730-144953/live/color-palette.liquid b/shopify/theme-titlelink-fix-20260730-144953/live/color-palette.liquid
new file mode 100644
index 00000000..d6f2f1e0
--- /dev/null
+++ b/shopify/theme-titlelink-fix-20260730-144953/live/color-palette.liquid
@@ -0,0 +1,675 @@
+{% comment %}
+  color-palette — "Colors In This Pattern" swatch spectrum.
+  Shows the pattern's real colors AND interpolated in-between colors, laid out as
+  uniform swatches centered in even rows. The TOTAL rendered dots are driven by a
+  user DENSITY SLIDER (Steve 2026-07-09d: "allow the user to grid slide the amount
+  of dots that they can see.. expand.. contract"): every real base color is kept,
+  and the remaining budget of interpolated dots is spread EVENLY across the gaps
+  (never truncating one end of the spectrum). The slider runs MIN_DOTS(6) →
+  MAX_DOTS(64) with DEFAULT 20 (the former hard cap became the default value),
+  re-renders live on drag with NO page reload, and persists to localStorage under
+  'dw_palette_dot_count' (a palette-specific key, distinct from the product-grid
+  density slider). (was Steve 2026-07-09c: a fixed 20-dot cap, MAX_SWATCHES=20.)
+
+  FIX PASS (Steve 2026-07-09, contrarian FIX-FIRST): (1) the slider 'input' handler
+  DEBOUNCES the expensive paintSpectrum() re-render ~80ms so a continuous drag doesn't
+  thrash the whole dot spectrum every tick — the numeric "Colors shown" output and the
+  minus/plus buttons still update IMMEDIATELY for snappy feedback; persistence stays on
+  'change' (drag-release). (2) When a pattern has only ONE dominant base color there is
+  genuinely nothing to interpolate, so instead of silently HIDING the control (a dead
+  end that leaves the user wondering), it stays VISIBLE but DISABLED with a title/aria
+  hint ("Only one dominant color in this pattern").
+
+  DOT CLICK TARGET (Steve 2026-07-09b): "bring up an index of many images, not
+  just that exact hex. tolerance 10% for color to pull more." A swatch click now
+  opens an IN-PAGE expanding grid of MANY products whose dominant color is within
+  a perceptual 10% tolerance (CIELAB ΔE76) of the clicked hex — CATALOG-WIDE, not
+  one collection. The grid is served by the color-index endpoint
+    POST https://photo.designerwallcoverings.com/apps/color-index  {hex, k}
+  (dwphoto → data/color-index.json, one dominant color per ACTIVE product with
+  precomputed LAB). Cards link to /products/<handle> (the roll variant, never the
+  $4.25 Sample). The family-collection href stays as the plain href so
+  middle-click / new-tab / crawlers still get a valid destination, and is the
+  graceful fallback if the endpoint is unreachable.
+  (was: Steve 2026-07-09a, a click deep-linked to the ONE nearest product+variant.)
+
+  Data tiers (mirrors color-dots.liquid, kept in sync):
+    Tier 1  custom.color_dots_json / custom.color_details  (enriched JSON: [{hex,name,pct}])
+    Tier 2  custom.primary_color_hex + dwc.ai_generated_colors (name list → hex map)
+    Tier 3  custom.color_hex (single)
+    Tier 4  Canvas pixel extraction from the product image (always yields real % — reliable fallback)
+{% endcomment %}
+
+{% comment %} Read the authoritative enriched field FIRST (color_details); legacy
+   color_dots_json is only a fallback so it can never shadow fresh enrichment. {% endcomment %}
+{% assign color_dots_json_val = product.metafields.custom.color_dots_json.value %}
+{% assign color_details_val = product.metafields.custom.color_details.value %}
+{% if color_details_val != blank %}
+  {% assign color_details_raw = color_details_val | json %}
+{% elsif color_dots_json_val != blank %}
+  {% assign color_details_raw = color_dots_json_val | json %}
+{% else %}
+  {% assign color_details_raw = blank %}
+{% endif %}
+{% assign color_hex = product.metafields.custom.color_hex.value %}
+{% assign primary_hex = product.metafields.custom.primary_color_hex.value %}
+{% assign ai_colors_raw = product.metafields.dwc.ai_generated_colors.value | json %}
+{% assign color_name = product.metafields.custom.color_name.value %}
+
+<div class="dw-color-palette" data-dw-color-palette
+  {% if color_details_raw != blank and color_details_raw != 'null' %}data-colors='{{ color_details_raw | escape }}'{% endif %}
+  {% if primary_hex != blank %}data-primary-hex="{{ primary_hex }}"{% endif %}
+  {% if ai_colors_raw != blank and ai_colors_raw != 'null' %}data-ai-colors='{{ ai_colors_raw }}'{% endif %}
+  {% if color_name != blank %}data-color-name="{{ color_name }}"{% endif %}
+  {% if color_hex != blank %}data-color-hex="{{ color_hex }}"{% endif %}
+  {% if product.featured_image %}data-product-image="{{ product.featured_image | img_url: '400x400' }}"{% endif %}
+  data-product-style="{{ product.metafields.custom.style.value | default: product.type | escape }}"
+>
+  <p class="dw-color-palette__title">Select Color for Coordinates</p>
+
+  {%- comment -%} COLOR BAR (Steve 2026-07-28): the palette renders as ONE large
+    horizontal color BAR (was individual dots), ~4× the old dot height. Drag the pill
+    handle on either END of the bar to EXPAND / CONTRACT how many colors it shows —
+    this replaces the old "Colors shown" slider. The count persists to localStorage
+    'dw_palette_dot_count'. Single-dominant-color palettes hide the handles (nothing
+    to expand). {%- endcomment -%}
+  <div class="dw-color-palette__barwrap" data-dw-cp-barwrap hidden>
+    <span class="dw-cp-handle dw-cp-handle--l" data-dw-cp-handle="l" role="slider" tabindex="0"
+          aria-label="Drag to change how many colors are shown" aria-valuemin="6" aria-valuemax="64"><i aria-hidden="true"></i></span>
+    <div class="dw-color-palette__cards" data-dw-color-palette-cards aria-live="polite"></div>
+    <span class="dw-cp-handle dw-cp-handle--r" data-dw-cp-handle="r" role="slider" tabindex="0"
+          aria-label="Drag to change how many colors are shown" aria-valuemin="6" aria-valuemax="64"><i aria-hidden="true"></i></span>
+  </div>
+
+  {%- comment -%} In-page color-index grid the dot brings up (Steve 2026-07-09b).
+    Hidden until a swatch is clicked; then filled with the catalog-wide products
+    within 10% (CIELAB ΔE76) of the clicked hex. {%- endcomment -%}
+  <div class="dw-color-index" data-dw-color-index hidden
+       data-endpoint="https://photo.designerwallcoverings.com/apps/color-index">
+    <div class="dw-color-index__head">
+      <span class="dw-color-index__swatch" data-dw-ci-swatch></span>
+      <span class="dw-color-index__label" data-dw-ci-label aria-live="polite"></span>
+      <button type="button" class="dw-color-index__close" data-dw-ci-close aria-label="Close color index">&times;</button>
+    </div>
+    <div class="dw-color-index__grid" data-dw-ci-grid aria-live="polite"></div>
+    {%- comment -%} Steve 2026-07-28: reveal +2 more rows of coordinates per click (refresh). {%- endcomment -%}
+    <button type="button" class="dw-color-index__more" data-dw-ci-more hidden aria-label="Show two more rows of coordinating wallcoverings">Show 2 more rows</button>
+  </div>
+</div>
+
+<style>
+  /* Steve 2026-07-28: relocated to a FULL-WIDTH section below the two columns. */
+  .dw-color-palette{padding:18px 0 12px;text-align:center;min-height:10px;max-width:1200px;margin:0 auto;}
+  .dw-color-palette__title{font-family:Lora,serif;font-size:15px;font-weight:500;color:#3D4246;margin:0 0 4px;letter-spacing:.01em;}
+  /* ── COLOR BAR (Steve 2026-07-28): dots → ONE large horizontal bar, ~4× the old
+     dot height, with a drag handle on each END to expand/contract the color count.
+     Replaces the density slider + the names/% legend + the thin proportion bar. ── */
+  .dw-color-palette__barwrap{position:relative;width:100%;max-width:1200px;margin:8px auto 0;box-sizing:border-box;}
+  .dw-color-palette__barwrap[hidden]{display:none;}
+  /* the bar itself: contiguous full-width color segments, no gaps, rounded ends */
+  .dw-color-palette__cards{display:flex;flex-wrap:nowrap;align-items:stretch;gap:0;width:100%;height:76px;border-radius:9px;overflow:hidden;box-shadow:inset 0 0 0 1px rgba(0,0,0,.06),0 1px 4px rgba(0,0,0,.08);}
+  .dw-swatch{flex:1 1 0;min-width:0;height:100%;display:block;border-radius:0;box-sizing:border-box;text-decoration:none;box-shadow:none;transition:filter .12s ease,box-shadow .12s ease;position:relative;}
+  a.dw-swatch{cursor:pointer;}
+  .dw-swatch:hover{filter:brightness(1.07);box-shadow:inset 0 0 0 2px rgba(255,255,255,.55),inset 0 0 0 3px rgba(0,0,0,.12);z-index:3;}
+  /* base (real pattern) colors read the same in a contiguous bar — no ring/scale */
+  .dw-swatch--base{box-shadow:none;}
+  .dw-swatch--base:hover{filter:brightness(1.07);box-shadow:inset 0 0 0 2px rgba(255,255,255,.6),inset 0 0 0 3px rgba(0,0,0,.16);z-index:3;}
+  /* END drag handles — pill grips that protrude past the bar; drag horizontally to
+     expand (outward) / contract (inward) the color count, persisted to localStorage. */
+  .dw-cp-handle{position:absolute;top:50%;transform:translateY(-50%);width:15px;height:94px;background:#fff;border:1px solid #c9c4b8;border-radius:8px;box-shadow:0 1px 5px rgba(0,0,0,.20);cursor:ew-resize;display:flex;align-items:center;justify-content:center;z-index:6;touch-action:none;-webkit-user-select:none;user-select:none;}
+  .dw-cp-handle i{display:block;width:2px;height:38px;background:#b0aa9c;border-radius:2px;box-shadow:4px 0 0 #b0aa9c,-4px 0 0 #b0aa9c;}
+  .dw-cp-handle--l{left:-6px;}
+  .dw-cp-handle--r{right:-6px;}
+  .dw-cp-handle:hover{border-color:#a8a294;}
+  .dw-cp-handle:focus-visible{outline:2px solid #b8b3a8;outline-offset:3px;}
+  .dw-color-palette__barwrap.is-single .dw-cp-handle{display:none;} /* one dominant color: nothing to expand */
+  .dw-color-palette:empty{display:none;}
+  @media (max-width:600px){
+    .dw-color-palette__cards{height:58px;border-radius:7px;}
+    .dw-cp-handle{height:74px;width:14px;}
+    .dw-cp-handle i{height:30px;}
+  }
+  /* ── In-page color-index grid the dot brings up — refined to read as a curated
+     "shop this colorway" drawer: hairline-ruled header, Lora title at the palette
+     scale, soft reveal, even gutters, hairline media. (refine 2026-07-09) ── */
+  /* Steve 2026-07-28: coordinate results serve FULL-WIDTH, exactly 2 rows; extra
+     chips are clipped (grid-auto-rows:0 + overflow:hidden) so the UI can never overflow. */
+  .dw-color-index{max-width:1200px;margin:22px auto 6px;text-align:left;border-top:1px solid #ece9e3;padding-top:16px;}
+  .dw-color-index[hidden]{display:none;}
+  @keyframes dwCiReveal{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}
+  .dw-color-index.is-open{animation:dwCiReveal .28s ease both;}
+  .dw-color-index__head{display:flex;align-items:center;gap:10px;margin:0 0 14px;}
+  .dw-color-index__swatch{width:22px;height:22px;border-radius:5px;flex:0 0 auto;box-shadow:0 0 0 1px rgba(0,0,0,.14),inset 0 -3px 5px rgba(0,0,0,.06);}
+  .dw-color-index__label{font-family:Lora,serif;font-size:14px;color:#3D4246;flex:1 1 auto;letter-spacing:.01em;}
+  .dw-color-index__label b{font-weight:600;text-transform:capitalize;}
+  .dw-color-index__close{flex:0 0 auto;background:none;border:1px solid #e0ded9;border-radius:50%;width:26px;height:26px;line-height:1;font-size:15px;color:#8a8577;cursor:pointer;padding:0;transition:color .12s ease,border-color .12s ease;}
+  .dw-color-index__close:hover{color:#3D4246;border-color:#b8b3a8;}
+  .dw-color-index__close:focus-visible{outline:2px solid #b8b3a8;outline-offset:2px;}
+  .dw-color-index__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));grid-template-rows:repeat(2,auto);grid-auto-rows:0;gap:16px;overflow:hidden;}
+  @media(min-width:1000px){.dw-color-index__grid{grid-template-columns:repeat(8,1fr);}}
+  /* "Show 2 more rows" — reveals the next 2 rows of coordinates each click (Steve 2026-07-28) */
+  .dw-color-index__more{display:block;margin:16px auto 2px;background:none;border:1px solid #d8d4cc;border-radius:999px;padding:8px 20px;font-family:Lora,serif;font-size:12px;letter-spacing:.05em;color:#3D4246;cursor:pointer;transition:border-color .12s ease,color .12s ease;}
+  .dw-color-index__more::before{content:"\21bb  ";}
+  .dw-color-index__more:hover{border-color:#b8b3a8;color:#000;}
+  .dw-color-index__more:focus-visible{outline:2px solid #b8b3a8;outline-offset:2px;}
+  .dw-color-index__more[hidden]{display:none;}
+  .dw-color-index__card{display:block;text-decoration:none;color:inherit;min-width:0!important;} /* FORCE equal-size cards — min-width:0 defeats the grid min-content blowout */
+  .dw-color-index__card img{width:100%!important;height:auto!important;aspect-ratio:1/1!important;object-fit:cover!important;display:block!important;min-width:0!important;box-shadow:0 0 0 1px #ece9e3;border-radius:3px;background:#f4f2ee;transition:box-shadow .14s ease;}
+  .dw-color-index__card:hover img{box-shadow:0 0 0 1px #b8b3a8,0 3px 12px rgba(0,0,0,.10);}
+  .dw-color-index__cap{display:block;font-family:Lora,serif;font-size:11px;color:#3D4246;margin-top:7px;line-height:1.3;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;text-transform:capitalize;}
+  .dw-color-index__card:hover .dw-color-index__cap{text-decoration:underline;text-decoration-color:#d8d4cc;}
+  .dw-color-index__cap i{display:block;font-style:normal;color:#8a8577;font-size:10px;text-transform:none;margin-top:1px;letter-spacing:.02em;}
+  .dw-color-index__empty,.dw-color-index__loading{font-family:Lora,serif;font-size:12px;color:#8a8577;padding:8px 0;}
+  @media (max-width:600px){
+    .dw-color-index__grid{grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:8px;}
+  }
+</style>
+
+<script>
+(function(){
+  function init(){
+    var root = document.querySelector('[data-dw-color-palette]');
+    if(!root || root.dataset.rendered) return;
+    var cards = root.querySelector('[data-dw-color-palette-cards]');
+    if(!cards) return;
+    root.dataset.rendered = '1';
+
+    /* name -> hex fallback map (kept in sync with color-dots.liquid) */
+    var N = {
+      'red':'#DC2626','crimson':'#DC143C','burgundy':'#800020','wine':'#722F37','maroon':'#800000',
+      'blue':'#2563EB','navy':'#1B2A4A','cobalt':'#0047AB','indigo':'#3F51B5','sky blue':'#87CEEB','steel blue':'#4682B4','royal blue':'#4169E1','blue frost':'#C4D3E0','deep indigo':'#1B2A4A','dark navy':'#1B2A4A','midnight':'#191970',
+      'green':'#16A34A','sage':'#87AE73','olive':'#808000','forest green':'#228B22','emerald':'#50C878','mint':'#98FF98','moss':'#8A9A5B','sea glass':'#C6D8CE',
+      'gold':'#D4AF37','golden':'#FFD700','amber':'#FFBF00',
+      'yellow':'#EAB308','mustard':'#FFDB58',
+      'orange':'#EA580C','rust':'#B7410E','terracotta':'#E2725B','melon':'#FDBCB4','soft melon':'#F6C9A8',
+      'pink':'#EC4899','blush':'#DE5D83','rose':'#FF007F','salmon':'#FA8072','fuchsia':'#FF00FF',
+      'purple':'#9333EA','violet':'#7C3AED','plum':'#8E4585','lavender':'#E6E6FA','lilac':'#C8A2C8','mauve':'#E0B0FF',
+      'black':'#1A1A1A','charcoal':'#36454F','ebony':'#555D50','onyx':'#353839',
+      'white':'#FAFAFA','ivory':'#FFFFF0','cream':'#FFFDD0','pearl':'#F0EAD6','off-white':'#FAF9F6','eggshell':'#F0EAD6',
+      'gray':'#6B7280','grey':'#6B7280','silver':'#C0C0C0','slate':'#708090','metallic silver':'#BFC1C2',
+      'brown':'#92400E','chocolate':'#7B3F00','espresso':'#3C1414','walnut':'#5C3A1D','mocha':'#967969','sienna':'#A0522D',
+      'beige':'#F5F5DC','tan':'#D2B48C','taupe':'#483C32','sand':'#C2B280','khaki':'#F0E68C','camel':'#C19A6B','oatmeal':'#B8AB8C','linen':'#FAF0E6',
+      'coral':'#FF6F61','peach':'#FFCBA4',
+      'teal':'#0D9488','aqua':'#00FFFF','turquoise':'#40E0D0','cyan':'#06B6D4',
+      'metallic':'#AAA9AD','copper':'#B87333','bronze':'#CD7F32'
+    };
+
+    function pctOf(c){ var p = (c.pct!=null?c.pct:(c.percentage!=null?c.percentage:null)); if(p==null) return null; p=parseFloat(p); return isNaN(p)?null:Math.round(p); }
+
+    /* ── HEX → color-family COLLECTION (handles verified live 2026-07-08) ──────
+       Derive the family from the REAL measured hex and link to that family's
+       collection. blue-wallpaper & grey-silver corrected from the old dead
+       handles (blue-wallpaper-collection / grey-wallcovering, which 404'd). */
+    var FAM_COLL = {
+      red:'red-wallpaper-collection', orange:'orange', yellow:'yellow-wallpaper-collection',
+      gold:'gold-wallpaper', green:'green-wallpaper-collection', teal:'turquoise',
+      blue:'blue-wallpaper', purple:'purple', pink:'pink-wallcovering-2',
+      brown:'brown-wallpaper-collection', beige:'beige-cream-wallpaper-collection',
+      black:'black', white:'white-wallpaper-collection', gray:'grey-silver'
+    };
+    function hexToRgb(h){
+      h=(h||'').replace('#','');
+      if(h.length===3) h=h.split('').map(function(x){return x+x;}).join('');
+      if(h.length<6) return null;
+      var r=parseInt(h.slice(0,2),16),g=parseInt(h.slice(2,4),16),b=parseInt(h.slice(4,6),16);
+      return (isNaN(r)||isNaN(g)||isNaN(b))?null:[r,g,b];
+    }
+    function familyFromHex(hex){
+      var rgb=hexToRgb(hex); if(!rgb) return null;
+      var r=rgb[0]/255,g=rgb[1]/255,b=rgb[2]/255;
+      var mx=Math.max(r,g,b),mn=Math.min(r,g,b),d=mx-mn;
+      var l=(mx+mn)/2, s=d===0?0:d/(1-Math.abs(2*l-1));
+      var hdeg=0;
+      if(d!==0){
+        if(mx===r) hdeg=60*(((g-b)/d)%6);
+        else if(mx===g) hdeg=60*(((b-r)/d)+2);
+        else hdeg=60*(((r-g)/d)+4);
+      }
+      if(hdeg<0) hdeg+=360;
+      if(s<0.18){
+        if(l>0.86) return 'white';
+        if(l<0.18) return 'black';
+        if((hdeg>=20 && hdeg<=70) && l>0.30) return 'beige';
+        return 'gray';
+      }
+      if(l>0.80 && (hdeg>=20 && hdeg<70)) return 'beige';
+      if(l<0.40 && s<0.75 && ((hdeg>=10 && hdeg<70) || hdeg>=345)) return 'brown';
+      if(l<0.16) return 'black';
+      if(hdeg<15||hdeg>=345) return 'red';
+      if(hdeg<40){ return (s<0.45 && l>0.55) ? 'beige' : 'orange'; }
+      if(hdeg<55){ return (s<0.45) ? 'beige' : (hdeg<50 ? 'gold' : 'yellow'); }
+      if(hdeg<65) return (l<0.45) ? 'gold' : 'yellow';
+      if(hdeg<170) return 'green';
+      if(hdeg<200) return 'teal';
+      if(hdeg<255) return 'blue';
+      if(hdeg<290) return 'purple';
+      return 'pink';
+    }
+    var PRODUCT_STYLE = (root.dataset.productStyle||'').trim();
+    /* The color-FAMILY collection handle for a hex (used both as the search space
+       for the nearest-specific-product resolver AND as the graceful fallback). */
+    function familyHandle(hex){
+      var fam=familyFromHex(hex); if(!fam) return null;
+      return FAM_COLL[fam] || null;
+    }
+    /* Fallback collection URL — only used if we can't resolve a single specific
+       product of that hue (edge case). (Steve 2026-07-09: DEFAULT must be a
+       specific product+variant, this is the graceful floor.) */
+    function familyHref(hex){
+      var handle=familyHandle(hex); if(!handle) return null;
+      var url='/collections/'+handle+'?sort_by=created-descending';
+      if(PRODUCT_STYLE){ url+='&filter.p.m.custom.style='+encodeURIComponent(PRODUCT_STYLE); }
+      return url;
+    }
+
+    /* ── hex → IN-PAGE INDEX of MANY products within 10% color tolerance ───────
+       Steve 2026-07-09b: "bring up an index of many images, not just that exact
+       hex. tolerance 10% for color to pull more." A dot is a palette-extracted
+       hue; clicking it opens the in-page grid (below the palette) of products
+       whose dominant color is within a perceptual 10% tolerance (CIELAB ΔE76) of
+       the clicked hex, drawn CATALOG-WIDE from the color-index endpoint. Results
+       are cached per hex so re-clicking a swatch is instant. */
+    var CI = root.querySelector('[data-dw-color-index]');
+    var CI_ENDPOINT = (CI && CI.dataset.endpoint) || 'https://photo.designerwallcoverings.com/apps/color-index';
+    var CI_K = 48;                 // fetch up to ~6 rows; show 2, reveal +2 per "Show 2 more rows" click (Steve 2026-07-28)
+    var _ciCache = {};             // hex -> Promise<{results,total,tolerance_pct}>
+    function fetchColorIndex(hex){
+      if(_ciCache[hex]) return _ciCache[hex];
+      _ciCache[hex]=fetch(CI_ENDPOINT,{
+        method:'POST', mode:'cors', headers:{'Content-Type':'application/json'},
+        body:JSON.stringify({hex:hex, k:CI_K})
+      }).then(function(r){ return r.ok?r.json():{results:[]}; })
+        .catch(function(){ return {results:[], _err:true}; });
+      return _ciCache[hex];
+    }
+    function ciEl(sel){ return CI ? CI.querySelector(sel) : null; }
+    function closeIndex(){ if(CI){ CI.classList.remove('is-open'); CI.hidden=true; } }
+    /* ── "Show 2 more rows" (Steve 2026-07-28): the coordinate grid is clipped to N
+       visible rows (grid-template-rows + overflow:hidden) so it can never overflow the
+       page; each click of the refresh button reveals +2 more rows. The button hides
+       itself once every fetched row is visible. Row height is known up-front (cards use
+       aspect-ratio:1) so scrollHeight vs clientHeight is a reliable "more remain?" test. */
+    var CI_ROW_STEP = 2, CI_START_ROWS = 2;
+    function ciApplyRows(){
+      var grid = ciEl('[data-dw-ci-grid]'); if(!grid) return;
+      var rows = parseInt(grid.getAttribute('data-rows') || String(CI_START_ROWS), 10);
+      // Hide extra cards by COUNT (rows × columns) so the grid flows to EXACTLY N clean
+      // rows — no partial 3rd-row sliver from row-gap the old overflow-clip left behind.
+      var cols = (getComputedStyle(grid).gridTemplateColumns || '').split(' ').filter(Boolean).length || 1;
+      var visible = rows * cols, hiddenAny = false;
+      for(var i=0;i<grid.children.length;i++){
+        var show = i < visible;
+        grid.children[i].style.display = show ? '' : 'none';
+        if(!show) hiddenAny = true;
+      }
+      // JS controls visibility by count, so let the grid flow naturally (no clip artefacts)
+      grid.style.gridTemplateRows = 'none';
+      grid.style.gridAutoRows = 'auto';
+      grid.style.overflow = 'visible';
+      var more = ciEl('[data-dw-ci-more]');
+      if(more){ more.hidden = !hiddenAny; }
+    }
+    if(CI){
+      var _more = ciEl('[data-dw-ci-more]');
+      if(_more) _more.addEventListener('click', function(){
+        var grid = ciEl('[data-dw-ci-grid]'); if(!grid) return;
+        var rows = parseInt(grid.getAttribute('data-rows') || String(CI_START_ROWS), 10) + CI_ROW_STEP;
+        grid.setAttribute('data-rows', String(rows));
+        ciApplyRows();
+        try{ _more.scrollIntoView({behavior:'smooth', block:'nearest'}); }catch(e){}
+      });
+      var _cx=ciEl('[data-dw-ci-close]');
+      if(_cx) _cx.addEventListener('click', closeIndex);
+      // Esc closes the drawer when it (or something inside it) is focused (a11y).
+      CI.addEventListener('keydown', function(ev){ if(ev.key==='Escape'){ closeIndex(); } });
+    }
+    /* Open the in-page index for a clicked hex. */
+    function openIndex(hex, name){
+      if(!CI) return;
+      var sw=ciEl('[data-dw-ci-swatch]'), lbl=ciEl('[data-dw-ci-label]'), grid=ciEl('[data-dw-ci-grid]');
+      var fam=familyFromHex(hex)||'';
+      if(sw) sw.style.background=hex;
+      if(lbl) lbl.innerHTML='Wallcoverings in this color — <b>'+(name||fam||hex)+'</b>';
+      if(grid) grid.innerHTML='<div class="dw-color-index__loading">Finding matches…</div>';
+      CI.hidden=false;
+      // retrigger the soft reveal even on a re-click (refine 2026-07-09)
+      CI.classList.remove('is-open'); void CI.offsetWidth; CI.classList.add('is-open');
+      CI.scrollIntoView({behavior:'smooth', block:'nearest'});
+      var _cl=ciEl('[data-dw-ci-close]'); if(_cl){ try{ _cl.focus({preventScroll:true}); }catch(e){} }
+      fetchColorIndex(hex).then(function(d){
+        if(!grid) return;
+        var res=(d&&d.results)||[];
+        if(!res.length){
+          grid.innerHTML='';
+          var empty=document.createElement('div'); empty.className='dw-color-index__empty';
+          empty.textContent = d && d._err
+            ? 'Could not load matches right now — try another color.'
+            : 'No close matches for this color yet.';
+          grid.appendChild(empty);
+          return;
+        }
+        grid.innerHTML='';
+        var frag=document.createDocumentFragment();
+        res.forEach(function(p){
+          if(!p || !p.handle) return;
+          var a=document.createElement('a'); a.className='dw-color-index__card'; a.rel='nofollow';
+          // link straight to the PDP handle — the roll/first-available variant,
+          // never the $4.25 Sample (Sample is never the default variant).
+          a.href='/products/'+encodeURIComponent(p.handle);
+          var img=document.createElement('img'); img.loading='lazy';
+          img.src=p.image||''; img.alt=p.title||'';
+          var cap=document.createElement('span'); cap.className='dw-color-index__cap';
+          cap.textContent=(p.title||'').replace(/\s*\|\s*.*$/,'');   // pattern part
+          if(p.vendor){ var v=document.createElement('i'); v.textContent=p.vendor; cap.appendChild(v); }
+          a.appendChild(img); a.appendChild(cap); frag.appendChild(a);
+        });
+        grid.appendChild(frag);
+        // reset to 2 visible rows for the new color, then show the "+2 rows" button if more remain
+        grid.setAttribute('data-rows', String(CI_START_ROWS));
+        ciApplyRows();
+        setTimeout(ciApplyRows, 60);   // re-measure once layout settles
+      });
+    }
+    /* Click handler shared by every swatch: open the in-page index for that hue.
+       Modified clicks (new tab) fall through to the plain family-collection href. */
+    function onSwatchClick(hex, ev, name){
+      ev.preventDefault();
+      openIndex(hex, name);
+    }
+
+    /* ── color math: normalize + interpolate ── */
+    function normHex(hex){
+      var rgb=hexToRgb(hex); if(!rgb) return null;
+      return '#'+rgb.map(function(x){return x.toString(16).padStart(2,'0');}).join('');
+    }
+    function mix(a,b,t){
+      var ca=hexToRgb(a),cb=hexToRgb(b); if(!ca||!cb) return a;
+      var r=Math.round(ca[0]+(cb[0]-ca[0])*t),g=Math.round(ca[1]+(cb[1]-ca[1])*t),bl=Math.round(ca[2]+(cb[2]-ca[2])*t);
+      return '#'+[r,g,bl].map(function(x){return x.toString(16).padStart(2,'0');}).join('');
+    }
+    /* ── perceptual ΔE (CIELAB, ΔE76) for VISUAL dedup of adjacent interpolated dots.
+       On a low-base, low-chroma pattern (e.g. an all-grey paisley) cranking the slider
+       high produces long runs of visually-identical adjacent swatches — meaningless
+       and it cheapens the curated feel. We skip an INTERPOLATED dot when it is within
+       ΔE < DEDUP_DE of the previously-placed swatch. Base (real) colors are NEVER
+       deduped — they're the pattern's actual colors. (refine 2026-07-09) */
+    function _rgb2lab(rgb){
+      var r=rgb[0]/255,g=rgb[1]/255,b=rgb[2]/255;
+      r=r>0.04045?Math.pow((r+0.055)/1.055,2.4):r/12.92;
+      g=g>0.04045?Math.pow((g+0.055)/1.055,2.4):g/12.92;
+      b=b>0.04045?Math.pow((b+0.055)/1.055,2.4):b/12.92;
+      var x=(r*0.4124+g*0.3576+b*0.1805)/0.95047,
+          y=(r*0.2126+g*0.7152+b*0.0722)/1.0,
+          z=(r*0.0193+g*0.1192+b*0.9505)/1.08883;
+      var f=function(t){return t>0.008856?Math.pow(t,1/3):(7.787*t)+16/116;};
+      x=f(x);y=f(y);z=f(z);
+      return [(116*y)-16, 500*(x-y), 200*(y-z)];
+    }
+    function deltaE(hexA,hexB){
+      var a=hexToRgb(hexA),b=hexToRgb(hexB); if(!a||!b) return 999;
+      var la=_rgb2lab(a),lb=_rgb2lab(b);
+      return Math.sqrt(Math.pow(la[0]-lb[0],2)+Math.pow(la[1]-lb[1],2)+Math.pow(la[2]-lb[2],2));
+    }
+    var DEDUP_DE = 2.3;   // ~1 JND: below this, two swatches are indistinguishable
+    /* TOTAL rendered dots are driven by a user DENSITY SLIDER (Steve 2026-07-09d:
+       "allow the user to grid slide the amount of dots that they can see..
+       expand.. contract"). The slider value is the target TOTAL dot count. We keep
+       EVERY real base color, then spend whatever budget is left on interpolated
+       in-between dots, distributed EVENLY across the gaps (never truncating one end
+       of the spectrum). See buildSteps. The former hard cap of 20 is now the slider
+       DEFAULT; the range is MIN_DOTS..MAX_DOTS, persisted to localStorage. */
+    var MIN_DOTS = 6, MAX_DOTS = 64, DEFAULT_DOTS = 20; /* slider bounds + default */
+    var LS_KEY = 'dw_palette_dot_count';               /* palette-specific persistence key */
+    function readDotCount(){
+      var v = DEFAULT_DOTS;
+      try { var s = localStorage.getItem(LS_KEY); if(s!=null){ var n=parseInt(s,10); if(!isNaN(n)) v=n; } } catch(e){}
+      if(v<MIN_DOTS) v=MIN_DOTS; if(v>MAX_DOTS) v=MAX_DOTS;
+      return v;
+    }
+    function writeDotCount(v){ try { localStorage.setItem(LS_KEY, String(v)); } catch(e){} }
+    var dotTarget = readDotCount();   /* current TOTAL-dot target (base + interpolated) */
+
+    /* Given the base-color count, return the number of interpolated dots to place
+       in EACH of the (n-1) gaps, so that base + sum(steps) <= `total` and the
+       leftover is spread across the earliest gaps (keeps the spectrum balanced,
+       left-to-right, rather than starving the tail). `total` = current slider value. */
+    function buildSteps(n, total){
+      var gaps = n - 1;
+      if(gaps <= 0) return [];
+      var budget = total - n;                   // interpolated dots we can afford
+      if(budget < 0) budget = 0;
+      var per = Math.floor(budget / gaps);
+      var extra = budget - per * gaps;          // spread +1 across the first `extra` gaps
+      var out = [];
+      for(var g=0; g<gaps; g++){ out.push(per + (g < extra ? 1 : 0)); }
+      return out;
+    }
+
+    /* Build one swatch. Left-click opens the IN-PAGE index of many products
+       within 10% of that hue (onSwatchClick → openIndex); the href is the family
+       collection so middle-click / new-tab / crawlers still get a valid
+       destination. (Steve 2026-07-09b: "bring up an index of many images.") */
+    function makeSwatch(hex, opts){
+      opts=opts||{};
+      var href=familyHref(hex);
+      var el=document.createElement(href?'a':'span');
+      el.className='dw-swatch'+(opts.base?' dw-swatch--base':'');
+      el.style.background=hex;
+      if(href){
+        el.href=href;
+        el.addEventListener('click', (function(nm){ return function(ev){
+          // let modified clicks (new tab / new window) use the plain-collection href
+          if(ev.metaKey||ev.ctrlKey||ev.shiftKey||ev.altKey||ev.button!==0) return;
+          onSwatchClick(hex, ev, nm);
+        }; })(opts.name));
+      }
+      var fam=familyFromHex(hex);
+      if(opts.base){
+        var lbl=(opts.name?opts.name:(fam||''));
+        el.title='Shop this '+(fam||'')+' color'+(lbl&&lbl!==fam?' — '+lbl:'')+' ('+hex+(opts.pct!=null?', '+opts.pct+'% here':'')+')';
+      } else {
+        el.title='Shop this '+(fam||'')+' color ('+hex+')';
+      }
+      return el;
+    }
+
+    /* Resolved base-color state, cached once so the density slider can re-render
+       the spectrum WITHOUT re-fetching / re-extracting the product image. */
+    var _baseList = null;    // [{hex,name,pct}] real base colors (deduped, sorted)
+    var _hasPct = false;
+
+    /* Rebuild ONLY the dot spectrum (cards) from the cached base list at the current
+       `dotTarget`. Called on first render and on every slider change. Every real base
+       color is kept; interpolated dots fill the remaining budget, evenly across gaps.
+       Legend + proportion bar reflect the real base colors and are built once (they
+       don't change with the dot count). */
+    function paintSpectrum(){
+      if(!_baseList || !_baseList.length) return;
+      var list = _baseList;
+      var stepsPerGap = buildSteps(list.length, dotTarget);
+      var frag=document.createDocumentFragment();
+      var _lastHex=null;   // last hex actually appended, for adjacent visual dedup
+      for(var i=0;i<list.length;i++){
+        // BASE colors are always shown (real pattern colors, never deduped).
+        frag.appendChild(makeSwatch(list[i].hex,{base:true,name:list[i].name,pct:list[i].pct}));
+        _lastHex=list[i].hex;
+        if(i<list.length-1){
+          var steps = stepsPerGap[i] || 0;
+          for(var k=1;k<=steps;k++){
+            var t=k/(steps+1);
+            var ih=mix(list[i].hex,list[i+1].hex,t);
+            // skip an interpolated dot that's visually identical to the previous one
+            // (dedup near-duplicates on low-chroma / low-base palettes). We still
+            // guarantee the NEXT base color renders because bases bypass this check.
+            if(_lastHex && deltaE(ih,_lastHex) < DEDUP_DE) continue;
+            frag.appendChild(makeSwatch(ih,{base:false}));
+            _lastHex=ih;
+          }
+        }
+      }
+      cards.innerHTML='';           // clear prior segments (re-render)
+      cards.appendChild(frag);
+      var _bw=root.querySelector('[data-dw-cp-barwrap]'); if(_bw) _bw.hidden=false;  // reveal the bar once it has content
+    }
+
+    /* Wire the two END drag-handles once (Steve 2026-07-28: "allow user to drag ends
+       of the bar to expand colors"). Dragging a handle OUTWARD expands the color count,
+       INWARD contracts it (~STEP_PX px of travel ≈ one color). The value is clamped to
+       [MIN_DOTS, MAX_DOTS] and persisted to localStorage; the bar re-renders live
+       (debounced) during the drag. A single-dominant-color palette has nothing to
+       expand, so the handles are hidden (is-single). Arrow keys / Home / End give the
+       handles keyboard control (a11y). */
+    var _paintTimer = null;                 // debounce handle for paintSpectrum (closure var)
+    var PAINT_DEBOUNCE_MS = 60;             // ~60ms: coalesces continuous drag ticks into one re-render
+    var HANDLE_STEP_PX = 16;                // px of horizontal drag travel per ±1 color
+    function wireHandles(){
+      var wrap = root.querySelector('[data-dw-cp-barwrap]');
+      if(!wrap) return;
+      var handles = wrap.querySelectorAll('[data-dw-cp-handle]');
+      // A single base color has nothing to interpolate — hide the handles entirely.
+      if(_baseList.length < 2){ wrap.classList.add('is-single'); return; }
+      wrap.classList.remove('is-single');
+      if(dotTarget < MIN_DOTS) dotTarget = MIN_DOTS;
+      if(dotTarget > MAX_DOTS) dotTarget = MAX_DOTS;
+      // setTarget(v, persist, immediate): update the color count; repaint the bar
+      // (immediate=now, else debounced during a continuous drag). Keeps aria in sync.
+      function setTarget(v, persist, immediate){
+        v = Math.round(v);
+        if(v < MIN_DOTS) v = MIN_DOTS; if(v > MAX_DOTS) v = MAX_DOTS;
+        var changed = (v !== dotTarget);
+        dotTarget = v;
+        handles.forEach(function(h){ h.setAttribute('aria-valuenow', String(v)); });
+        if(persist) writeDotCount(v);
+        if(!changed && !immediate) return;
+        if(immediate){
+          if(_paintTimer){ clearTimeout(_paintTimer); _paintTimer = null; }
+          paintSpectrum();
+        } else {
+          if(_paintTimer) clearTimeout(_paintTimer);
+          _paintTimer = setTimeout(function(){ _paintTimer = null; paintSpectrum(); }, PAINT_DEBOUNCE_MS);
+        }
+      }
+      handles.forEach(function(h){
+        var side = h.getAttribute('data-dw-cp-handle');   // 'l' (left) | 'r' (right)
+        var dir  = (side === 'r') ? 1 : -1;               // OUTWARD travel sign that EXPANDS
+        h.setAttribute('aria-valuenow', String(dotTarget));
+        var startX = 0, startT = dotTarget, dragging = false;
+        function onMove(ev){
+          if(!dragging) return;
+          var dpx = ev.clientX - startX;
+          setTarget(startT + dir * (dpx / HANDLE_STEP_PX), false, false);   // outward = +colors
+          ev.preventDefault();
+        }
+        function onUp(){
+          if(!dragging) return;
+          dragging = false;
+          writeDotCount(dotTarget);         // persist final count on release
+          paintSpectrum();                  // final crisp paint
+          document.removeEventListener('pointermove', onMove);
+          document.removeEventListener('pointerup', onUp);
+        }
+        h.addEventListener('pointerdown', function(ev){
+          dragging = true; startX = ev.clientX; startT = dotTarget;
+          try { h.setPointerCapture(ev.pointerId); } catch(e){}
+          document.addEventListener('pointermove', onMove);
+          document.addEventListener('pointerup', onUp);
+          ev.preventDefault();
+        });
+        // Keyboard: outward arrow = more colors, inward = fewer; Home/End = min/max.
+        h.addEventListener('keydown', function(ev){
+          var step = 0;
+          if(ev.key === 'ArrowRight' || ev.key === 'ArrowUp')        step =  dir;
+          else if(ev.key === 'ArrowLeft' || ev.key === 'ArrowDown')  step = -dir;
+          else if(ev.key === 'Home'){ setTarget(MIN_DOTS, true, true); ev.preventDefault(); return; }
+          else if(ev.key === 'End'){  setTarget(MAX_DOTS, true, true); ev.preventDefault(); return; }
+          if(step){ setTarget(dotTarget + step, true, true); ev.preventDefault(); }
+        });
+      });
+    }
+
+    function render(colors){
+      if(!colors || !colors.length) return false;
+      /* de-dupe by hex/name, keep order; resolve each to a real hex */
+      var seen={}, list=[];
+      colors.forEach(function(c){
+        if(!c) return;
+        var hex=normHex(c.hex) || normHex(N[(c.name||'').toLowerCase()]);
+        if(!hex) return;
+        if(seen[hex]) return; seen[hex]=1;
+        list.push({hex:hex, name:c.name||'', pct:pctOf(c)});
+      });
+      if(!list.length) return false;
+      var hasPct = list.some(function(c){return c.pct!=null;});
+      if(hasPct) list.sort(function(a,b){return (b.pct||0)-(a.pct||0);});
+      /* Keep the real base colors (still capped at ≤8 dominant hues — bases are the
+         pattern's actual colors; interpolation between them fills the slider budget). */
+      list = list.slice(0, 8);
+
+      /* Cache the resolved base list + wire the slider, then paint the spectrum at
+         the current (persisted) dot target. Slider re-renders call paintSpectrum
+         directly — no image re-extraction. (Steve 2026-07-09d) */
+      _baseList = list; _hasPct = hasPct;
+      wireHandles();
+      paintSpectrum();
+      /* (Steve 2026-07-28) The names+% legend and the thin proportion bar were removed
+         — the large color bar above is now the single palette element. */
+      return true;
+    }
+
+    /* Metafield fallbacks — used ONLY if canvas extraction yields nothing. */
+    function renderMeta(){
+      var ph=root.dataset.primaryHex, aiRaw=root.dataset.aiColors, sh=root.dataset.colorHex, list2=[];
+      if(ph) list2.push({hex:ph, name:root.dataset.colorName||'Primary'});
+      try { JSON.parse((aiRaw||'[]').replace(/=>/g,':')).forEach(function(nm){ var h=N[(''+nm).toLowerCase()]; if(h) list2.push({hex:h,name:nm}); }); } catch(e){}
+      if(list2.length && render(list2)) return true;
+      if(sh) return render([{hex:sh, name:root.dataset.colorName||'Color'}]);
+      return false;
+    }
+
+    /* ── Tier 1: color_details / color_dots_json metafield (BEST — local-vision enriched) ── */
+    var raw = root.dataset.colors;
+    if(raw){
+      try { if(render(JSON.parse(raw.replace(/=>/g,':')))) return; } catch(e){}
+    }
+
+    /* ── Tier 2: Canvas extraction from the product image (multi-color + %), meta as fallback ── */
+    var REF={'red':[190,55,55],'crimson':[176,40,60],'navy':[30,45,80],'blue':[70,110,175],'slate blue':[95,120,150],'powder blue':[170,196,214],'green':[70,130,80],'sage':[150,168,138],'celadon':[176,192,168],'eucalyptus':[130,146,128],'moss':[120,138,90],'olive':[124,124,70],'gold':[200,168,90],'ochre':[190,150,80],'yellow':[224,186,90],'orange':[220,120,70],'terracotta':[196,110,85],'coral':[224,140,120],'pink':[220,150,165],'blush':[224,190,190],'purple':[140,100,160],'lavender':[190,180,205],'black':[40,40,40],'charcoal':[70,74,78],'white':[248,246,240],'ivory':[240,236,224],'cream':[236,228,206],'eggshell':[224,216,196],'gray':[140,140,142],'greige':[176,168,154],'silver':[196,196,198],'brown':[130,95,70],'walnut':[110,80,55],'taupe':[150,138,120],'beige':[214,202,178],'tan':[196,176,140],'sand':[200,184,150],'stone':[186,180,166],'teal':[70,140,140],'turquoise':[110,180,175]};
+    function closestName(r,g,b){var best='',bd=1e9;for(var n in REF){var c=REF[n],d=Math.pow(r-c[0],2)+Math.pow(g-c[1],2)+Math.pow(b-c[2],2);if(d<bd){bd=d;best=n;}}return best;}
+    function hx(r,g,b){return '#'+[r,g,b].map(function(x){return x.toString(16).padStart(2,'0');}).join('');}
+    function fromCanvas(url){
+      var img=new Image(); img.crossOrigin='anonymous';
+      img.onload=function(){
+        try{
+          var sz=120,cv=document.createElement('canvas');cv.width=sz;cv.height=sz;
+          var ctx=cv.getContext('2d');ctx.drawImage(img,0,0,sz,sz);
+          var px=ctx.getImageData(0,0,sz,sz).data,bk={};
+          for(var i=0;i<px.length;i+=4){var kr=Math.round(px[i]/22)*22,kg=Math.round(px[i+1]/22)*22,kb=Math.round(px[i+2]/22)*22,k=kr+','+kg+','+kb;if(!bk[k])bk[k]={r:0,g:0,b:0,n:0};bk[k].r+=px[i];bk[k].g+=px[i+1];bk[k].b+=px[i+2];bk[k].n++;}
+          var sorted=Object.keys(bk).map(function(k){return bk[k];}).sort(function(a,b){return b.n-a.n;});
+          var tot=sz*sz,out=[],seen={};
+          for(var j=0;j<sorted.length && out.length<8;j++){var s=sorted[j],ar=Math.round(s.r/s.n),ag=Math.round(s.g/s.n),ab=Math.round(s.b/s.n),nm=closestName(ar,ag,ab),pct=Math.round(s.n/tot*100);if(seen[nm]||pct<2)continue;seen[nm]=1;out.push({name:nm,hex:hx(ar,ag,ab),pct:pct});}
+          if(!render(out.slice(0,6))) renderMeta();
+        }catch(e){ renderMeta(); }
+      };
+      img.onerror=function(){ renderMeta(); };
+      img.src=url;
+    }
+    function imgUrl(){
+      var u=root.dataset.productImage;
+      if(u&&u.length>5) return u.indexOf('//')===0?'https:'+u:u;
+      var sels=['.product-gallery--viewport img[src]','.product-gallery img[src]','[data-product-gallery] img[src]','.media-gallery__slide img[src]'];
+      for(var i=0;i<sels.length;i++){var el=document.querySelector(sels[i]);if(el&&el.src)return el.src;}
+      return null;
+    }
+    var u=imgUrl();
+    if(u){ fromCanvas(u); }
+    else {
+      var obs=new MutationObserver(function(m,o){var v=imgUrl();if(v){o.disconnect();fromCanvas(v);}});
+      obs.observe(document.body,{childList:true,subtree:true});
+      setTimeout(function(){ obs.disconnect(); if(!cards.children.length) renderMeta(); },5000);
+    }
+  }
+  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',init);}else{init();}
+})();
+</script>
+
+{%- comment -%} "More Wallcoverings Like This" (CLIP row) MOVED to sections/product.liquid so it spans the full page width, below the two-column product block (Steve 2026-07-28). {%- endcomment -%}
diff --git a/shopify/theme-titlelink-fix-20260730-144953/live/product-form-content.liquid b/shopify/theme-titlelink-fix-20260730-144953/live/product-form-content.liquid
new file mode 100644
index 00000000..f176dda3
--- /dev/null
+++ b/shopify/theme-titlelink-fix-20260730-144953/live/product-form-content.liquid
@@ -0,0 +1,804 @@
+{% comment %}
+  @param product {Product}
+
+  @param show_social_media_icons {Boolean}
+
+  @param show_payment_button {Boolean}
+    Show the DPBs in the product form
+{% endcomment %}
+
+{% assign product_id = product.id %}
+{% assign selected_variant = product.selected_or_first_available_variant %}
+
+{%- liquid
+  assign _selected_variant_sku = selected_variant.sku | default: '' | downcase
+  assign _selected_variant_title = selected_variant.title | default: '' | downcase
+
+  assign _selected_variant_sku_suffix = _selected_variant_sku | slice: -7, 7
+
+  assign is_sample_sku = false
+  if _selected_variant_sku != blank and _selected_variant_sku_suffix == '-sample'
+    assign is_sample_sku = true
+  endif
+
+  assign is_sample_title = false
+  if _selected_variant_title contains 'sample'
+    assign is_sample_title = true
+  endif
+
+  assign is_sample_like = false
+  if is_sample_sku
+    assign is_sample_like = true
+  elsif is_sample_title and selected_variant.price == 0
+    assign is_sample_like = true
+  endif
+-%}
+
+<!-- -----------020------------ -->
+{% assign prods_quantity_order_min = 1 %}
+
+{% if product.metafields['global']['v_prods_quantity_order_min'] %}
+  {% if product.metafields['global']['v_prods_quantity_order_min'] != '' %}
+    {% assign prods_quantity_order_min = product.metafields['global']['v_prods_quantity_order_min'] %}
+  {% endif %}
+{% endif %}
+
+{% assign prods_quantity_order_units = 1 %}
+
+{% if product.metafields['global']['v_prods_quantity_order_units'] %}
+  {% if product.metafields['global']['v_prods_quantity_order_units'] != '' %}
+    {% assign prods_quantity_order_units = product.metafields['global']['v_prods_quantity_order_units'] %}
+  {% endif %}
+{% endif %}
+
+<!-- Minimum order quantity from custom metafields (e.g. Ralph Lauren fabrics) -->
+{% assign min_order_qty = product.metafields.custom.minimum_order_quantity | default: nil %}
+{% assign min_order_unit = product.metafields.custom.minimum_order_unit | default: '' %}
+{% assign has_min_order = false %}
+{% if min_order_qty != nil and min_order_qty != blank and min_order_qty > 1 %}
+  {% assign has_min_order = true %}
+  {% assign min_order_qty_num = min_order_qty | plus: 0 %}
+  {% if min_order_qty_num > prods_quantity_order_min %}
+    {% assign prods_quantity_order_min = min_order_qty_num %}
+  {% endif %}
+{% endif %}
+
+<!-- -------------------------- -->
+<script>
+  setTimeout(function () {
+    const qtySelector = document.querySelector('.mm_quantity .input-text.qty');
+    if (qtySelector) {
+      qtySelector.value = {{ prods_quantity_order_min }};
+    }
+  }, 1000);
+</script>
+
+{% if has_min_order %}
+<!-- Minimum order notice and validation -->
+<style>
+  .min-order-notice {
+    background: #fff3cd;
+    border: 1px solid #ffc107;
+    border-radius: 4px;
+    padding: 8px 12px;
+    margin: 10px 0;
+    font-size: 14px;
+    font-weight: 600;
+    color: #856404;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+  }
+  .min-order-notice svg {
+    flex-shrink: 0;
+  }
+  .min-order-error {
+    background: #f8d7da;
+    border: 1px solid #f5c6cb;
+    color: #721c24;
+    border-radius: 4px;
+    padding: 8px 12px;
+    margin: 8px 0 0;
+    font-size: 13px;
+    display: none;
+  }
+</style>
+<div class="min-order-notice" data-min-order-notice>
+  <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1a7 7 0 100 14A7 7 0 008 1zm0 2.5a.75.75 0 01.75.75v4a.75.75 0 01-1.5 0v-4A.75.75 0 018 3.5zm0 8a.75.75 0 100-1.5.75.75 0 000 1.5z"/></svg>
+  Minimum order: {{ min_order_qty }} {{ min_order_unit }}
+</div>
+<script>
+  (function() {
+    var MIN_QTY = {{ min_order_qty }};
+    var MIN_UNIT = {{ min_order_unit | json }};
+
+    document.addEventListener('DOMContentLoaded', function() {
+      // Find the product form (could be either .shopify-product-form or form with data-product-form)
+      var forms = document.querySelectorAll('form[action*="/cart/add"], form[data-product-form]');
+      forms.forEach(function(form) {
+        form.addEventListener('submit', function(e) {
+          var qtyInput = form.querySelector('.mm_quantity .input-text.qty') || form.querySelector('input[name="quantity"]');
+          if (!qtyInput) return;
+
+          var qty = parseInt(qtyInput.value, 10);
+          if (isNaN(qty) || qty < MIN_QTY) {
+            e.preventDefault();
+            e.stopImmediatePropagation();
+
+            // Show error message
+            var errorDiv = form.querySelector('.min-order-error');
+            if (!errorDiv) {
+              errorDiv = document.createElement('div');
+              errorDiv.className = 'min-order-error';
+              var addToCartBtn = form.querySelector('.product-add-to-cart');
+              if (addToCartBtn) {
+                addToCartBtn.appendChild(errorDiv);
+              }
+            }
+            errorDiv.textContent = 'Please enter a quantity of at least ' + MIN_QTY + ' ' + MIN_UNIT + '.';
+            errorDiv.style.display = 'block';
+
+            // Also fix the value
+            qtyInput.value = MIN_QTY;
+            var hqty = form.querySelector('.hquantity');
+            if (hqty) hqty.value = MIN_QTY;
+
+            // Hide error after 5s
+            setTimeout(function() { errorDiv.style.display = 'none'; }, 5000);
+            return false;
+          }
+        });
+      });
+
+      // Also enforce min on the minus button - prevent going below minimum
+      jQuery(document).on('click', '.minus', function() {
+        var $qty = jQuery(this).closest('.mm_quantity').find('.qty');
+        var currentVal = parseInt($qty.val(), 10);
+        if (currentVal < MIN_QTY) {
+          $qty.val(MIN_QTY);
+          jQuery(this).closest('.mm_quantity').find('.hquantity').attr('value', MIN_QTY);
+        }
+      });
+
+      // Enforce min on blur (manual typing)
+      jQuery(document).on('blur', '.mm_quantity .input-text.qty', function() {
+        var currentVal = parseInt(jQuery(this).val(), 10);
+        if (isNaN(currentVal) || currentVal < MIN_QTY) {
+          jQuery(this).val(MIN_QTY);
+          jQuery(this).closest('.mm_quantity').find('.hquantity').attr('value', MIN_QTY);
+        }
+      });
+
+      // Hide notice when sample variant is selected
+      jQuery(document).on('change', '.product-select, .single-option-selector', function() {
+        var $form = jQuery(this).closest('form, [data-product-form]');
+        var $selected = $form.find('.product-select option:selected, .product-select[type="hidden"]');
+        var selectedText = ($selected.text() || $selected.attr('data-variant-title') || '').toLowerCase();
+        var selectedSku = ($selected.attr('data-sku') || '').toLowerCase();
+        var isSample = /sample/i.test(selectedText) || /-sample$/i.test(selectedSku);
+
+        var $notice = jQuery('.min-order-notice[data-min-order-notice]');
+        if (isSample) {
+          $notice.hide();
+        } else {
+          $notice.show();
+        }
+      });
+    });
+  })();
+</script>
+{% endif %}
+
+{% for block in section.blocks %}
+  {% case block.type %}
+    {% when 'vendor' %}
+      <a class="product-vendor" href="{{ product.vendor | url_for_vendor }}" {{ block.shopify_attributes }}>{{ product.vendor }}</a>
+
+    {% when 'title' %}
+      <h1 class="product-title" {{ block.shopify_attributes }}>
+        {% if product != blank %}
+          {% if link_to %}
+            <a href="{{ product.url }}">
+              {{ product.title }}
+            </a>
+          {% else %}
+            {% comment %}{{ product.title }} {% endcomment %}
+            <a href="https://designer-laboratory-sandbox.myshopify.com/search?q={{ product.title }}">
+              {{ product.title }}
+            </a>
+          {% endif %}
+        {% else %}
+          {{ 'products.product.onboarding.title' | t }}
+        {% endif %}
+      </h1>
+      <span class="product-sku-label">SKU: </span>
+      {%- liquid
+        assign sku_raw = product.selected_or_first_available_variant.sku | default: ''
+        assign sku_display = sku_raw | replace: '-Sample', '' | replace: '-SAMPLE', '' | replace: '-sample', ''
+        assign sku_display = sku_display | replace: '--', '-' | replace: '--', '-'
+        assign sku_last_char = sku_display | slice: -1, 1
+        if sku_display != blank and sku_last_char == '-'
+          assign sku_display = sku_display | remove_last: '-'
+        endif
+      -%}
+      <span class="product-sku-value">{{ sku_display }}</span>
+    {% when '@app' %}
+      <div class="product-app" {{ block.shopify_attributes }}>
+        {% render block %}
+      </div>
+
+    {% when 'custom-liquid' %}
+      {% if block.settings.custom_liquid != blank %}
+        <div class="product__custom-liquid" {{ block.shopify_attributes }}>
+          {{ block.settings.custom_liquid }}
+        </div>
+      {% endif %}
+
+    {% when 'collapsible-tab' %}
+      {% if block.settings.collapsible_tab_heading != blank %}
+        <details class="collapsible-tab" {{ block.shopify_attributes }}>
+          <summary class="collapsible-tab__heading">
+            <span>{{ block.settings.collapsible_tab_heading | escape }}</span>
+            {%
+              render 'icons',
+              id: 'chevron-right',
+            %}
+          </summary>
+
+          {% if block.settings.collapsible_tab_text != blank %}
+            <div class="collapsible-tab__text rte">
+              {{ block.settings.collapsible_tab_text }}
+            </div>
+          {% endif %}
+        </details>
+      {% endif %}
+
+    {% when 'tabs' %}
+      {% assign tab_counter = 1 %}
+      <div class="product-tabs" {{ block.shopify_attributes }}>
+        {% if block.settings.show_product_description %}
+          <input
+            class="product-tabs__radio"
+            id="product-tabs__radio-{{ section.id }}"
+            name="{{ section.id }}"
+            tabindex="0"
+            type="radio"
+            checked="checked"
+          >
+
+          <label class="product-tabs__label" for="product-tabs__radio-{{ section.id }}">
+            {{ 'onboarding.tab_title'| t }}
+          </label>
+
+          <div class="product-tabs__panel rte" tabindex="0">
+            {% if onboarding %}
+              {{ 'onboarding.tab_content'| t }}
+            {% else %}
+              {% if product.description != blank %}
+                {{ product.description }}
+              {% else %}
+                {{ 'onboarding.tab_content'| t }}
+              {% endif %}
+            {% endif %}
+          </div>
+        {% endif %}
+
+        {% for i in (1..3) %}
+          {% assign tab_text_key = 'tab_text_' | append: i %}
+          {% assign tab_heading_key = 'tab_heading_' | append: i %}
+          {% assign tab_heading_value = block.settings[tab_heading_key] %}
+          {% assign tab_text_value = block.settings[tab_text_key] %}
+
+          {% if tab_heading_value != blank %}
+            <input
+              class="product-tabs__radio"
+              id="product-tabs__radio-{{ i }}-{{ section.id }}"
+              name="{{ section.id }}"
+              tabindex="0"
+              type="radio"
+              {% if tab_counter == 1 and block.settings.show_product_description == false %}
+                checked
+              {% endif %}
+            >
+
+            <label class="product-tabs__label" for="product-tabs__radio-{{ i }}-{{ section.id }}">
+              {{ tab_heading_value | escape }}
+            </label>
+
+            <div class="product-tabs__panel rte" tabindex="0">
+              {{ tab_text_value }}
+            </div>
+
+            {% assign tab_counter = tab_counter | plus: 1 %}
+          {% endif %}
+        {% endfor %}
+      </div>
+
+    {% when 'price' %}
+      <div class="product__price" {{ block.shopify_attributes }}>
+        {%- comment -%}
+          DW trade/designer pricing tiers (2026-06-11): any customer carrying one of these
+          professional tags sees the trade price display (15% off, ×0.85). This restores the
+          pre-app behavior where every designer/trade tier got the discount — not just 'trade'.
+          Matches *any* tag containing 'designer' (interior_designer, commercial_designer, …)
+          plus the explicit tiers below. Add new tiers to the explicit list as needed.
+        {%- endcomment -%}
+        {%- assign dw_is_trade = false -%}
+        {%- for dw_t in customer.tags -%}
+          {%- assign dw_tl = dw_t | downcase | strip -%}
+          {%- if dw_tl contains 'designer' or dw_tl == 'trade' or dw_tl == 'architect' or dw_tl == 'stager' or dw_tl == 'hospitality' or dw_tl == 'retailer' -%}
+            {%- assign dw_is_trade = true -%}
+            {%- break -%}
+          {%- endif -%}
+        {%- endfor -%}
+        <input type="hidden" id="customer_tag" value="{% if dw_is_trade %}trade{% endif %}">
+        <p class="product-price">
+          {% comment %} SA Wholesale (saw_/wbuyx/"WHO") app removed 2026-06-11 — it left the price span blank. Render native variant price unconditionally. {% endcomment %}
+            <!-- native price markup -->
+            <meta itemprop="price" content="{{ selected_variant.price | money_without_currency }}">
+            <span class="product-price-minimum money {% if dw_is_trade %}original{% endif %}">
+              {% if product != blank %}
+                {{ selected_variant.price | money }}
+              {% else %}
+                {{ 'products.product.onboarding.price' | t }}
+              {% endif %}
+            </span>
+            {% if dw_is_trade %}
+              <span
+                class="product-price-discount"
+              >
+                {% assign target_product = selected_variant %}
+                {% assign com_selected_variant_title = selected_variant.title | capitalize %}
+                {% assign com_selected_variant_sku = selected_variant.sku | default: '' | downcase %}
+                {% if com_selected_variant_title == "Sample" or com_selected_variant_sku contains "sample" %}
+                  {% comment %} {% render 'money', value: 0 %} {% endcomment %}
+                  Complimentary Sample
+                {% else %}
+                  {% assign com_variant_first_title = product.variants[0].title | capitalize %}
+                  {% if com_variant_first_title == "Sample" and product.variants[1] != blank  %}
+                    {% assign target_product = product.variants[1] %}
+                  {% endif  %}
+                  {% assign discounted_price = target_product.price | times:0.85 | round: 2 %}
+                  {% render 'money', value: discounted_price %}
+                {% endif %}
+
+              </span>
+            {% endif %}
+           {% comment %}
+            <span
+              class="product-price-compare money original {% if selected_variant.compare_at_price > selected_variant.price %}visible{% endif %}"
+            >
+              {{ selected_variant.compare_at_price | money }}
+            </span>
+           {% endcomment %}
+        </p>
+
+        {% assign variant_for_unit_price = selected_variant %}
+        {% comment %}Inject unit-price begin{% endcomment %}
+        {% comment %}
+          @param variant_for_unit_price
+            Product variant for price
+          @param tax_text
+            String containing 'tax included' text
+        {% endcomment %}
+
+        {% capture total_quantity %}
+          <span class="product-price__unit-price-total-quantity" data-unit-price-quantity>
+            {{ variant_for_unit_price.unit_price_measurement.quantity_value }}{{ variant_for_unit_price.unit_price_measurement.quantity_unit }}
+          </span>
+        {% endcapture %}
+
+
+        {% capture unit_price %}
+          <span class="product-price__unit-price-amount money" data-unit-price-amount>
+            {{ variant_for_unit_price.unit_price | money }}
+          </span>
+        {% endcapture %}
+        {% capture unit_measure %}
+          <span class="product-price__unit-price-measure" data-unit-price-measure>
+            {%- if variant_for_unit_price.unit_price_measurement.reference_value != 1 -%}
+              {{ variant_for_unit_price.unit_price_measurement.reference_value }}
+            {%- endif %}
+            {{ variant_for_unit_price.unit_price_measurement.reference_unit }}
+          </span>
+        {% endcapture %}
+
+        <div
+          class="
+            product-price__unit-price
+            {% unless variant_for_unit_price.unit_price_measurement %}hidden{% endunless %}
+          "
+          data-unit-price
+        >
+          {{ 'products.product.price_per_unit_html' | t: total_quantity: total_quantity, unit_price: unit_price, unit_measure: unit_measure | strip_newlines }}
+        </div>
+
+        {% assign variant_for_unit_price = blank %}
+        {% comment %}Inject unit-price end{% endcomment %}
+
+
+        {%- capture tax_text -%}
+        {{ 'products.product.tax_line_html' | t }}
+        {%- endcapture -%}
+
+        {%- if tax_text != blank -%}
+          <div class="
+            product-price__tax
+            {% unless selected_variant.taxable %}hidden{% endunless %}
+            "
+            data-tax-line
+          >
+            {{ tax_text }}
+          </div>
+        {%- endif -%}
+
+        {{ form | payment_terms }}
+      </div>
+
+    {% when 'form' %}
+
+      <div class="product__form" {{ block.shopify_attributes }}>
+
+        {% unless product.has_only_default_variant %}
+          <div class="product-options">
+            {%-
+              render 'product-options-dropdown',
+              product: product,
+              form_id: product_id
+            -%}
+
+            <label
+              class="
+                product-option-column-1
+                product-option-row-4
+                product-option-quantity-label
+                {% if is_sample_like %}hidden{% endif %}
+              "
+              for="default-variant-quantity-input"
+            >
+              <strong>{{ 'general.general.quantity' | t }}:</strong>
+            </label>
+            {% comment %}
+            <input
+              class="product-option-quantity"
+              id="default-variant-quantity-input"
+              data-product-quantity-input
+              type="number"
+              inputmode="numeric"
+              name="quantity"
+              value="1"
+            >
+            {% endcomment %}
+            <!-- 020 -->
+            <div class="mm_quantity mm_buttons_added product-option-row-4 {% if is_sample_like %}hidden{% endif %}">
+              <input type="hidden" class="hquantity" name="hquantity" value="{{ prods_quantity_order_min }}">
+              <input type="button" value="-" class="minus">
+              <input
+                type="text"
+                step="{{ prods_quantity_order_units }}"
+                min="{{ prods_quantity_order_min }}"
+                data-order-step="{{ prods_quantity_order_units }}"
+                data-order-min="{{ prods_quantity_order_min }}"
+                max=""
+                name="quantity"
+                value="{{ prods_quantity_order_min }}"
+                title="Qty"
+                class="input-text qty text"
+                size="4"
+                pattern=""
+                inputmode=""
+                onkeypress="mm_validate(event)"
+              >
+              <input type="button" value="+" class="plus">
+            </div>
+            <!--  -->
+
+            <div class="selector-wrapper no-js-required">
+              <label for="product-select-{{ product_id }}"></label>
+              <select
+                class="product-select"
+                name="id"
+                id="product-select-{{ product_id }}"
+                data-select-ignore
+              >
+                {% for variant in product.variants %}
+                  {% if variant.available %}
+                    {% assign com_variant_title = variant.title | capitalize %}
+                    {% if com_variant_title == "Sample" %}
+                      {% assign dl_sample_variant = variant.id %}
+                      {% assign dl_sample_variant_title = product.title | append: ' - ' | append: variant.title %}
+                      {% assign dl_sample = 'small_sample' %}
+                    {% endif %}
+
+                    {% if com_variant_title == "Large Sample" %}
+                      {% assign dl_sample_variant2 = variant.id %}
+                      {% assign dl_sample_variant_title2 = product.title | append: ' - ' | append: variant.title %}
+                      {% assign dl_sample2 = 'large_sample' %}
+                    {% endif %}
+
+                    <option
+                      {% if variant == selected_variant %}selected="selected"{% endif %}
+                      value="{{ variant.id }}"
+                      data-variant-id="{{ variant.id }}"
+                      data-sku="{{ variant.sku }}"
+                      data-vprice="{{ variant.price }}"
+                    >
+                      {{ variant.title }} - {{ variant.price | money }}
+                    </option>
+                  {% else %}
+                    <option 
+                    disabled="disabled" 
+                    data-variant-id="{{ variant.id }}" 
+                    value="{{ variant.id }}"
+                    data-sku="{{ variant.sku }}">
+                      {{ variant.title }} - {{ 'products.product.sold_out' | t }}
+                    </option>
+                  {% endif %}
+                {% endfor %}
+              </select>
+            </div>
+          </div>
+        {% else %}
+          <div class="product-options product-options-default-only">
+            <input
+              class="product-select"
+              name="id"
+              value="{{ product.variants[0].id }}"
+              type="hidden"
+              data-variant-title="{{ product.variants[0].title }}"
+              data-sku="{{ product.variants[0].sku }}"
+              data-vprice="{{ product.variants[0].price }}"
+            />
+
+            <label
+              class="
+                product-option-column-1
+                product-option-row-1
+                product-option-quantity-label
+                {% if is_sample_like %}hidden{% endif %}
+              "
+              for="quantity-input"
+            >
+              <strong>{{ 'general.general.quantity' | t }}:</strong>
+            </label>
+
+<!--             <input
+              class="product-option-quantity"
+              id="quantity-input"
+              data-product-quantity-input
+              type="number"
+              inputmode="numeric"
+              name="quantity"
+              value="1"
+            > -->
+            <!-- 020 -->
+            <div class="mm_quantity mm_buttons_added product-option-row-1 {% if is_sample_like %}hidden{% endif %}">
+              <input type="hidden" class="hquantity" name="hquantity" value="{{ prods_quantity_order_min }}">
+              <input type="button" value="-" class="minus">
+              <input
+                type="text"
+                step="{{ prods_quantity_order_units }}"
+                min="{{ prods_quantity_order_min }}"
+                data-order-step="{{ prods_quantity_order_units }}"
+                data-order-min="{{ prods_quantity_order_min }}"
+                max=""
+                name="quantity"
+                value="{{ prods_quantity_order_min }}"
+                title="Qty"
+                class="input-text qty text"
+                size="4"
+                pattern=""
+                inputmode=""
+                onkeypress="mm_validate(event)"
+              >
+              <input type="button" value="+" class="plus">
+            </div>
+            <!--  -->
+          </div>
+        {% endunless %}
+
+        {% for tag in product.tags %}
+          {% if tag contains 'custom mural' %}
+            <!-- Custom Price Calculator -->
+            <div id="custom-calculator" style="display:none" data-calculatorid="62388fabd48c8"></div>
+            <script>
+              document.querySelector(".product-options .last_variant").innerHTML = "Custom Sizes";
+
+              var appendCalc = setInterval(moveCalc, 300);
+
+              function moveCalc() {
+                var calc = document.getElementById("calculator");
+                if (calc) {
+                  document
+                    .querySelector(".product-options")
+                    .insertBefore(
+                      document.getElementById("calculator"),
+                      document.querySelector(".product-options").children[2]
+                    );
+                  clearInterval(appendCalc);
+                }
+              }
+            </script>
+            <style>
+              .product-options {
+                display: flex;
+                flex-direction: column;
+                align-items: baseline;
+                margin-left: 0;
+              }
+              .product-options .product-option-quantity-label,
+              .mm_quantity.mm_buttons_added {
+                display: none !important;
+              }
+            </style>
+          {% endif %}
+        {% endfor %}
+
+        {% if product.available %}
+          <div id="infiniteoptions-container"></div>
+          <div id="uploadery-container"></div>
+        {% endif %}
+
+        <!-- Print PDF button -->
+        {% render 'button-product-pdf-print' %}
+
+        <div
+          class="
+            product-add-to-cart
+            {% if product != blank and show_payment_button %}
+              product-smart-payments
+            {% endif %}
+          "
+        >
+          {% if selected_variant.available and template.name != 'password' %}
+            <!-- 020 -->
+            <div class="custom-contact-message"></div>
+
+            <a
+              href="#"
+              class="dl-contact-btn custom-popup-contact-form"
+              {% if variant.price == 0 %}
+                style="display:inline-block;"
+              {% else %}
+                style="display:none;"
+              {% endif %}
+            >
+              Contact Us
+            </a>
+
+            {% assign title = 'Request Sample' %}
+            {% assign showonly = blank %}
+            {% if product.tags contains "Showroom only" and product.price == 0 %}
+              {% assign showonly = 'showonly' %}
+            {% endif %}
+            {% if product.tags contains "Showroom Only" and product.price == 0 %}
+              {% assign showonly = 'showonly' %}
+            {% endif %}
+
+            {% if is_sample_sku and dl_sample == blank and dl_sample2 == blank %}
+              <a
+                href=""
+                data-dlvid="{{ selected_variant.id }}"
+                data-dlvtitle="{{ product.title | append: ' - Sample' }}"
+                class="dl-sample-btn {{ showonly }}"
+                data-product-id="{{ product.id }}"
+              >
+                {{ title }}
+              </a>
+            {% endif %}
+
+            {% if dl_sample != blank and dl_sample2 != blank and dl_sample == 'small_sample' and dl_sample2 == 'large_sample' %}
+              {% assign title = 'Small Sample' %}
+            {% endif %}
+
+            {% if dl_sample == 'small_sample' and dl_sample_variant !='' and  dl_sample_variant_title != '' %}
+              <a
+                href=""
+                data-dlvid="{{ dl_sample_variant }}"
+                data-dlvtitle="{{ dl_sample_variant_title }}"
+                class="dl-sample-btn {{ showonly }}"
+                data-product-id="{{ product.id }}"
+              >
+                {{ title }}
+              </a>
+            {% endif %}
+
+            {% if dl_sample2 == 'large_sample' and dl_sample_variant2 !='' and  dl_sample_variant_title2 != '' %}
+              <a
+                href=""
+                data-dlvid="{{ dl_sample_variant2 }}"
+                data-dlvtitle="{{ dl_sample_variant_title2 }}"
+                class="dl-sample-btn {{ showonly }}"
+                data-product-id="{{ product.id }}"
+              >
+                Large Sample
+              </a>
+            {% endif %}
+            {% for variant_item in product.variants %}
+              {% if variant_item.price == 0 and variant_item.title contains "Sample" and variant_item.title != "Sample" %}
+                {% assign showing_title = variant_item.title %}
+                {% if showing_title == "Textured Vinyl Sample" %}
+                  {% assign showing_title = "Vinyl Sample" %}
+                {% endif %}
+                <a
+                  href=""
+                  data-dlvid="{{ variant_item.id }}"
+                  data-dlvtitle="{{ variant_item.title }}"
+                  class="dl-second-sample-btn {% if variant_item.id != product.selected_or_first_available_variant.id %}unavailable{% endif %} {{ showonly }}"
+                  data-product-id="{{ product.id }}"
+                >
+                  Request {{ showing_title }}
+                </a>
+              {% endif %}
+            {% endfor %}
+
+            {% render 'spec-sheet-button' %}
+
+<!--             <input class="add-to-cart" type="submit" value="{{ 'products.product.add_to_cart' | t }}" /> -->
+            {% assign com_first_variant_title = product.selected_or_first_available_variant.title | capitalize %}
+            <input class="add-to-cart {% if is_sample_like %}hidden{% endif %} {% if product.variants.size == 1 and com_first_variant_title == "Sample" %}hidden{% endif %} {% if product.selected_or_first_available_variant.price == 0 %}unavailable{% endif %}" type="submit" value="{{ 'products.product.add_to_cart' | t }}"/>
+          {% else %}
+            <input class="add-to-cart disabled" type="submit" value="{{ 'products.product.sold_out' | t }}" disabled/>
+          {% endif %}
+
+          <!--020 -->
+          {% for t in product.tags %}
+             {% if t == 'Customize' %}
+                    <a href="#" class="dl-customize-btn custom-popup-contact-form">Customize this Pattern</a>
+             {% endif %}
+          {% endfor %}
+
+          {% if show_payment_button %}
+            {{ form | payment_button }}
+          {% endif %}
+
+          {% capture the_snippet_fave %}{% include 'socialshopwave-widget-fave' %}{% endcapture %}
+          {% unless the_snippet_fave contains 'Liquid error' %}
+          {{ the_snippet_fave }}
+          {% endunless %}
+        </div>
+        <div class="product-message"></div>
+
+        <div data-surface-pick-up></div>
+        {%-
+          render 'modal',
+          modal_id: 'surface-pick-up',
+          modal_class: 'surface-pick-up-modal',
+        -%}
+      </div>
+
+    {% when 'social' %}
+      <div class="product__social" {{ block.shopify_attributes }}>
+        {% render 'share-buttons' %}
+      </div>
+
+    {% when 'description' %}
+      {% if product != blank and product.description != blank %}
+        <div class="product-description rte" {{ block.shopify_attributes }}>
+          {{ product.description }}
+        </div>
+      {% elsif product == blank %}
+        <div class="product-description rte" {{ block.shopify_attributes }}>
+          {{ 'products.product.onboarding.description' | t }}
+        </div>
+      {% endif %}
+
+    {%- when 'rating' -%}
+      {%- if product.metafields.reviews.rating.value != blank -%}
+        <div class="product__rating rating" {{ block.shopify_attributes }}>
+          {%
+            render 'rating-stars',
+            value: product.metafields.reviews.rating.value.rating,
+            scale_max: product.metafields.reviews.rating.value.scale_max,
+          %}
+          <p class="rating__text">
+            <span aria-hidden="true">{{ product.metafields.reviews.rating.value }} / {{ product.metafields.reviews.rating.value.scale_max }}</span>
+          </p>
+          <p class="rating__count">
+            <span aria-hidden="true">({{ product.metafields.reviews.rating_count }})</span>
+            <span class="visually-hidden">{{ product.metafields.reviews.rating_count }} {{ "general.accessibility.total_reviews" | t }}</span>
+          </p>
+        </div>
+      {%- endif -%}
+  {% endcase %}
+{% endfor %}
diff --git a/shopify/theme-titlelink-fix-20260730-144953/rollback.sh b/shopify/theme-titlelink-fix-20260730-144953/rollback.sh
new file mode 100755
index 00000000..4ea7e323
--- /dev/null
+++ b/shopify/theme-titlelink-fix-20260730-144953/rollback.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+# Rollback: re-PUT the original pre-edit snippets (./backup/) to the LIVE DW theme.
+set -euo pipefail
+SECRETS=~/Projects/secrets-manager/.env
+STORE="designer-laboratory-sandbox.myshopify.com"
+API="2024-10"; THEME=144396058675
+THEME_TOKEN=$(grep -hE '^SHOPIFY_THEME_TOKEN=' "$SECRETS" | head -1 | cut -d= -f2- | tr -d '"'"'"' \r')
+DIR="$(cd "$(dirname "$0")" && pwd)"
+
+put () {
+  python3 - "$STORE" "$API" "$THEME" "$THEME_TOKEN" "$1" "$2" <<'PY'
+import sys, json, urllib.request, urllib.error
+store, api, theme, token, key, path = sys.argv[1:7]
+payload = json.dumps({"asset": {"key": key, "value": open(path).read()}}).encode()
+url = f"https://{store}/admin/api/{api}/themes/{theme}/assets.json"
+req = urllib.request.Request(url, data=payload, method="PUT",
+    headers={"X-Shopify-Access-Token": token, "Content-Type": "application/json"})
+with urllib.request.urlopen(req) as r:
+    print(f"ROLLBACK OK {r.status} {key}")
+PY
+}
+put "snippets/product-form-content.liquid" "$DIR/backup/product-form-content.liquid"
+put "snippets/color-dots.liquid"            "$DIR/backup/color-dots.liquid"
+echo "Rollback complete."

← f7d708ae auto-save: 2026-07-30T13:18:21 (1 files) — package-lock.json  ·  back to Designer Wallcoverings  ·  auto-save: 2026-07-30T15:49:17 (1 files) — shopify/scripts/q 9d15da2d →