[object Object]

← back to Designer Wallcoverings

collections: add background hero image (dedicated section, staged on dev theme) + audit of 66 collections missing collection.image

f001170724e91ea56d6a4f22dde35c90248076ee · 2026-06-23 12:45:07 -0700 · Steve

Files touched

Diff

commit f001170724e91ea56d6a4f22dde35c90248076ee
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jun 23 12:45:07 2026 -0700

    collections: add background hero image (dedicated section, staged on dev theme) + audit of 66 collections missing collection.image
---
 .gitignore                                   |   1 +
 shopify/audit-collection-hero-images.py      | 103 +++++++
 shopify/audit/collection-hero-gap.json       | 405 +++++++++++++++++++++++++++
 shopify/push-collection-hero-to-dev-theme.py | 104 +++++++
 shopify/sections/dw-collection-hero.liquid   |  69 +++++
 5 files changed, 682 insertions(+)

diff --git a/.gitignore b/.gitignore
index f34e79d8..f5b36f4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,3 +71,4 @@ DW-Programming/room-setting-app/data/gallery/renders.json
 DW-Programming/data/color-clean-ledger.jsonl
 shopify/scripts/gmc-race-clean/out/
 shopify/scripts/cadence/stock-2026-guard.latest.json
+shopify/theme-backups/
diff --git a/shopify/audit-collection-hero-images.py b/shopify/audit-collection-hero-images.py
new file mode 100644
index 00000000..97663a14
--- /dev/null
+++ b/shopify/audit-collection-hero-images.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+"""
+READ-ONLY audit: which DW Shopify collections lack a background/hero image
+(collection.image == null). Covers BOTH custom_collections and smart_collections.
+
+Enforces standing rule (Steve 2026-06-23): every collection page must have a
+background "bg" hero image. A collection with image==null will render the
+template's CSS fallback gradient instead of a real hero → flagged here.
+
+Usage:
+  THEME_TOKEN=shpat_xxx python3 audit-collection-hero-images.py
+  # or relies on SHOPIFY_THEME_TOKEN / SHOPIFY_ADMIN_TOKEN in secrets-manager/.env
+No writes. Outputs a summary + writes audit/collection-hero-gap.json.
+"""
+import sys, os, json, urllib.request, urllib.parse, urllib.error, datetime
+
+STORE = "designer-laboratory-sandbox.myshopify.com"
+API = "2024-10"
+SECRETS = os.path.expanduser("~/Projects/secrets-manager/.env")
+
+
+def load_token():
+    t = os.environ.get("THEME_TOKEN")
+    if t:
+        return t
+    if os.path.exists(SECRETS):
+        for line in open(SECRETS):
+            for k in ("SHOPIFY_THEME_TOKEN", "SHOPIFY_ADMIN_TOKEN", "SHOPIFY_ORDERS_TOKEN"):
+                if line.startswith(k + "="):
+                    return line.split("=", 1)[1].strip().strip('"').strip("'")
+    sys.exit("No Shopify token. Run: THEME_TOKEN=shpat_xxx python3 audit-collection-hero-images.py")
+
+
+TOK = load_token()
+H = {"X-Shopify-Access-Token": TOK, "Content-Type": "application/json"}
+
+
+def get(url):
+    req = urllib.request.Request(url, headers=H)
+    resp = urllib.request.urlopen(req)
+    body = json.load(resp)
+    link = resp.headers.get("Link", "")
+    nxt = None
+    for part in link.split(","):
+        if 'rel="next"' in part:
+            nxt = part[part.find("<") + 1:part.find(">")]
+    return body, nxt
+
+
+def fetch_all(resource):
+    out = []
+    url = f"https://{STORE}/admin/api/{API}/{resource}.json?limit=250&fields=id,handle,title,image,published_scope"
+    while url:
+        try:
+            body, url = get(url)
+        except urllib.error.HTTPError as e:
+            print(f"  {resource} HTTP {e.code}: {e.read().decode()[:200]}")
+            break
+        out.extend(body.get(resource, []))
+    return out
+
+
+print(f"token …{TOK[-4:]}  store {STORE}\n")
+custom = fetch_all("custom_collections")
+smart = fetch_all("smart_collections")
+print(f"custom_collections: {len(custom)}")
+print(f"smart_collections:  {len(smart)}")
+
+rows = []
+for kind, coll in (("custom", custom), ("smart", smart)):
+    for c in coll:
+        has_img = bool(c.get("image"))
+        rows.append({
+            "kind": kind,
+            "id": c["id"],
+            "handle": c.get("handle"),
+            "title": c.get("title"),
+            "has_image": has_img,
+            "image_src": (c.get("image") or {}).get("src"),
+        })
+
+total = len(rows)
+missing = [r for r in rows if not r["has_image"]]
+have = total - len(missing)
+print(f"\n=== HERO-IMAGE AUDIT ===")
+print(f"total collections : {total}")
+print(f"WITH hero image   : {have}")
+print(f"MISSING hero image: {len(missing)}   ({(len(missing)/total*100 if total else 0):.1f}%)")
+print(f"\nFirst 40 missing (handle — title):")
+for r in missing[:40]:
+    print(f"  /collections/{r['handle']}  —  {r['title']}  [{r['kind']}]")
+if len(missing) > 40:
+    print(f"  … and {len(missing)-40} more")
+
+os.makedirs("audit", exist_ok=True)
+out = {
+    "generated_at": datetime.datetime.now().isoformat(),
+    "store": STORE,
+    "total": total, "with_image": have, "missing_image": len(missing),
+    "missing": [{"handle": r["handle"], "title": r["title"], "kind": r["kind"], "id": r["id"]} for r in missing],
+}
+json.dump(out, open("audit/collection-hero-gap.json", "w"), indent=2)
+print(f"\nwrote audit/collection-hero-gap.json  ({len(missing)} flagged)")
diff --git a/shopify/audit/collection-hero-gap.json b/shopify/audit/collection-hero-gap.json
new file mode 100644
index 00000000..e34d66a4
--- /dev/null
+++ b/shopify/audit/collection-hero-gap.json
@@ -0,0 +1,405 @@
+{
+  "generated_at": "2026-06-23T12:37:52.255570",
+  "store": "designer-laboratory-sandbox.myshopify.com",
+  "total": 665,
+  "with_image": 599,
+  "missing_image": 66,
+  "missing": [
+    {
+      "handle": "1838",
+      "title": "1838",
+      "kind": "custom",
+      "id": 299219812403
+    },
+    {
+      "handle": "fentucci-naturals",
+      "title": "Fentucci Naturals",
+      "kind": "custom",
+      "id": 299921047603
+    },
+    {
+      "handle": "hollywood-vinyls-vol-1",
+      "title": "Hollywood Vinyls Vol. 1",
+      "kind": "custom",
+      "id": 298973855795
+    },
+    {
+      "handle": "wallpapersback",
+      "title": "WALLPAPERSBACK",
+      "kind": "custom",
+      "id": 301681508403
+    },
+    {
+      "handle": "3d-decibel",
+      "title": "3d Decibel",
+      "kind": "smart",
+      "id": 299576885299
+    },
+    {
+      "handle": "anima",
+      "title": "Anima",
+      "kind": "smart",
+      "id": 299576950835
+    },
+    {
+      "handle": "anthology-wallcoverings",
+      "title": "Anthology Wallcoverings",
+      "kind": "smart",
+      "id": 299575148595
+    },
+    {
+      "handle": "aracne",
+      "title": "Aracne",
+      "kind": "smart",
+      "id": 299576983603
+    },
+    {
+      "handle": "artisan-by-bodo-sperlein",
+      "title": "Artisan By Bodo Sperlein",
+      "kind": "smart",
+      "id": 299577016371
+    },
+    {
+      "handle": "artmura",
+      "title": "Artmura",
+      "kind": "smart",
+      "id": 301429850163
+    },
+    {
+      "handle": "bn-studio-collection",
+      "title": "Bn Studio Collection",
+      "kind": "smart",
+      "id": 299576492083
+    },
+    {
+      "handle": "bn-walls",
+      "title": "BN Walls",
+      "kind": "smart",
+      "id": 299577409587
+    },
+    {
+      "handle": "botanika",
+      "title": "Botanika",
+      "kind": "smart",
+      "id": 299577081907
+    },
+    {
+      "handle": "carnegie-wallcoverings",
+      "title": "Carnegie Wallcoverings",
+      "kind": "smart",
+      "id": 299575246899
+    },
+    {
+      "handle": "central-saint-martins-rest-time",
+      "title": "Central Saint Martins Rest Time",
+      "kind": "smart",
+      "id": 299577147443
+    },
+    {
+      "handle": "christian-fischbacher",
+      "title": "Christian Fischbacher",
+      "kind": "smart",
+      "id": 299576852531
+    },
+    {
+      "handle": "coco-d-vez-colortherapills",
+      "title": "Coco D Vez Colortherapills",
+      "kind": "smart",
+      "id": 299577180211
+    },
+    {
+      "handle": "color-stories-new",
+      "title": "Color Stories New",
+      "kind": "smart",
+      "id": 299576557619
+    },
+    {
+      "handle": "cotswolds-ybarra-amp-serret-for-coordonn",
+      "title": "Cotswolds Ybarra Amp Serret For Coordonn",
+      "kind": "smart",
+      "id": 299577212979
+    },
+    {
+      "handle": "cowtan-tout-wallpaper-collection",
+      "title": "Cowtan & Tout Wallcovering Collection",
+      "kind": "smart",
+      "id": 299575279667
+    },
+    {
+      "handle": "creative-lab",
+      "title": "Creative Lab",
+      "kind": "smart",
+      "id": 299576590387
+    },
+    {
+      "handle": "cubiq",
+      "title": "Cubiq",
+      "kind": "smart",
+      "id": 299576623155
+    },
+    {
+      "handle": "damaskus",
+      "title": "Damaskus",
+      "kind": "smart",
+      "id": 299577245747
+    },
+    {
+      "handle": "de-gournay-wallcoverings",
+      "title": "De Gournay",
+      "kind": "smart",
+      "id": 298974445619
+    },
+    {
+      "handle": "debn-studio-collection",
+      "title": "Debn Studio Collection",
+      "kind": "smart",
+      "id": 299576655923
+    },
+    {
+      "handle": "dedoodleedo",
+      "title": "Dedoodleedo",
+      "kind": "smart",
+      "id": 299576688691
+    },
+    {
+      "handle": "defiore",
+      "title": "Defiore",
+      "kind": "smart",
+      "id": 299576721459
+    },
+    {
+      "handle": "degrand-safari",
+      "title": "Degrand Safari",
+      "kind": "smart",
+      "id": 299576754227
+    },
+    {
+      "handle": "degrounded",
+      "title": "Degrounded",
+      "kind": "smart",
+      "id": 299576786995
+    },
+    {
+      "handle": "delinen-stories",
+      "title": "Delinen Stories",
+      "kind": "smart",
+      "id": 299576819763
+    },
+    {
+      "handle": "fabric-backed",
+      "title": "Fabric Backed Wallcoverings",
+      "kind": "smart",
+      "id": 299576459315
+    },
+    {
+      "handle": "fallingstar",
+      "title": "FallingStar Studio",
+      "kind": "smart",
+      "id": 301565804595
+    },
+    {
+      "handle": "fromental-wallcoverings",
+      "title": "Fromental Wallcoverings",
+      "kind": "smart",
+      "id": 299575377971
+    },
+    {
+      "handle": "groundworks-wallcoverings",
+      "title": "Groundworks Wallcoverings",
+      "kind": "smart",
+      "id": 299575410739
+    },
+    {
+      "handle": "knoll-textiles",
+      "title": "Knoll Wallcoverings",
+      "kind": "smart",
+      "id": 299574657075
+    },
+    {
+      "handle": "la-walls",
+      "title": "LA Walls",
+      "kind": "smart",
+      "id": 301587922995
+    },
+    {
+      "handle": "maharam-wallcoverings",
+      "title": "Maharam Wallcoverings",
+      "kind": "smart",
+      "id": 299575476275
+    },
+    {
+      "handle": "malibu-walls",
+      "title": "Malibu Walls",
+      "kind": "smart",
+      "id": 299962368051
+    },
+    {
+      "handle": "malibu-walls-abstract",
+      "title": "Malibu Walls \u2014 Abstract",
+      "kind": "smart",
+      "id": 299962630195
+    },
+    {
+      "handle": "malibu-walls-bedroom",
+      "title": "Malibu Walls \u2014 Bedroom",
+      "kind": "smart",
+      "id": 299962794035
+    },
+    {
+      "handle": "malibu-walls-coastal",
+      "title": "Malibu Walls \u2014 Coastal",
+      "kind": "smart",
+      "id": 299962433587
+    },
+    {
+      "handle": "malibu-walls-contemporary",
+      "title": "Malibu Walls \u2014 Contemporary",
+      "kind": "smart",
+      "id": 299962466355
+    },
+    {
+      "handle": "malibu-walls-floral",
+      "title": "Malibu Walls \u2014 Floral & Botanical",
+      "kind": "smart",
+      "id": 299962695731
+    },
+    {
+      "handle": "malibu-walls-geometric",
+      "title": "Malibu Walls \u2014 Geometric",
+      "kind": "smart",
+      "id": 299962728499
+    },
+    {
+      "handle": "malibu-walls-grasscloth",
+      "title": "Malibu Walls \u2014 Grasscloth & Woven",
+      "kind": "smart",
+      "id": 299962662963
+    },
+    {
+      "handle": "malibu-walls-living-room",
+      "title": "Malibu Walls \u2014 Living Room",
+      "kind": "smart",
+      "id": 299962826803
+    },
+    {
+      "handle": "malibu-walls-minimalist",
+      "title": "Malibu Walls \u2014 Minimalist",
+      "kind": "smart",
+      "id": 299962597427
+    },
+    {
+      "handle": "malibu-walls-scandinavian",
+      "title": "Malibu Walls \u2014 Scandinavian",
+      "kind": "smart",
+      "id": 299962564659
+    },
+    {
+      "handle": "malibu-walls-stripes",
+      "title": "Malibu Walls \u2014 Stripes",
+      "kind": "smart",
+      "id": 299962761267
+    },
+    {
+      "handle": "malibu-walls-traditional",
+      "title": "Malibu Walls \u2014 Traditional",
+      "kind": "smart",
+      "id": 299962499123
+    },
+    {
+      "handle": "malibu-walls-transitional",
+      "title": "Malibu Walls \u2014 Transitional",
+      "kind": "smart",
+      "id": 299962531891
+    },
+    {
+      "handle": "matthew-williamson-wallcoverings",
+      "title": "Matthew Williamson Wallcoverings",
+      "kind": "smart",
+      "id": 299575509043
+    },
+    {
+      "handle": "mc-escher",
+      "title": "MC Escher",
+      "kind": "smart",
+      "id": 299534286899
+    },
+    {
+      "handle": "mdc-wallcoverings",
+      "title": "MDC Wallcoverings",
+      "kind": "smart",
+      "id": 299575541811
+    },
+    {
+      "handle": "mr-perswall-wallcoverings",
+      "title": "Mr Perswall Wallcoverings",
+      "kind": "smart",
+      "id": 299575574579
+    },
+    {
+      "handle": "mural-source",
+      "title": "Mural Source",
+      "kind": "smart",
+      "id": 299577376819
+    },
+    {
+      "handle": "sanderson",
+      "title": "Sanderson Wallcoverings",
+      "kind": "smart",
+      "id": 299574591539
+    },
+    {
+      "handle": "sanderson-wallcoverings",
+      "title": "Sanderson Wallcoverings",
+      "kind": "smart",
+      "id": 299575672883
+    },
+    {
+      "handle": "schumacher",
+      "title": "Schumacher",
+      "kind": "smart",
+      "id": 301431095347
+    },
+    {
+      "handle": "showroom-lines",
+      "title": "Showroom Lines",
+      "kind": "smart",
+      "id": 301540147251
+    },
+    {
+      "handle": "spoonflower",
+      "title": "Spoonflower Wallcoverings",
+      "kind": "smart",
+      "id": 299577344051
+    },
+    {
+      "handle": "stout-wallpaper",
+      "title": "Stout Textiles Wallcoverings",
+      "kind": "smart",
+      "id": 299574689843
+    },
+    {
+      "handle": "stout-wallcoverings",
+      "title": "Stout Wallcoverings",
+      "kind": "smart",
+      "id": 298974543923
+    },
+    {
+      "handle": "stroheim-wallcoverings",
+      "title": "Stroheim Wallcoverings",
+      "kind": "smart",
+      "id": 299575705651
+    },
+    {
+      "handle": "tres-tintas",
+      "title": "Tres Tintas",
+      "kind": "smart",
+      "id": 301569179699
+    },
+    {
+      "handle": "zuber-wallcoverings",
+      "title": "Zuber Wallcoverings",
+      "kind": "smart",
+      "id": 299575803955
+    }
+  ]
+}
\ No newline at end of file
diff --git a/shopify/push-collection-hero-to-dev-theme.py b/shopify/push-collection-hero-to-dev-theme.py
new file mode 100644
index 00000000..1e40d691
--- /dev/null
+++ b/shopify/push-collection-hero-to-dev-theme.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+"""
+Stage the DW collection BACKGROUND HERO on the DEV theme only
+("Updated copy of NewWall Template [Dev]", id 143794536499).
+
+Standing rule (Steve 2026-06-23): every collection page must have a background
+"bg" hero image. The DW collection page is Boost-app-rendered (the native
+sections/collection.liquid is JS/CSS/schema only — it emits no visible HTML), so
+the hero is a DEDICATED section (sections/dw-collection-hero.liquid) placed FIRST
+in templates/collection.json "order", rendering server-side above the Boost grid.
+It shows collection.image as a cover background (title + breadcrumbs overlaid);
+collections with no image yet get a branded fallback gradient. The audit script
+(audit-collection-hero-images.py) lists which collections still need a real image.
+
+Idempotent: re-uploads the section and ensures it's first in order without
+duplicating. Backs up collection.json each run. NEVER touches the live/main theme.
+Live promotion stays with Steve (publish the dev theme or port to the main theme).
+
+Usage:
+  THEME_TOKEN=shpat_xxx python3 push-collection-hero-to-dev-theme.py
+  python3 push-collection-hero-to-dev-theme.py --remove        # unwire the hero
+  python3 push-collection-hero-to-dev-theme.py --verify-only   # read-only check
+"""
+import sys, os, json, urllib.request, urllib.parse, datetime, time
+
+STORE = "designer-laboratory-sandbox.myshopify.com"
+API = "2024-10"
+TID = 143794536499
+SECRETS = os.path.expanduser("~/Projects/secrets-manager/.env")
+SECTION_KEY = "sections/dw-collection-hero.liquid"
+TEMPLATE_KEY = "templates/collection.json"
+HERO_ID = "dw_collection_hero"
+HERO_TYPE = "dw-collection-hero"
+REMOVE = "--remove" in sys.argv
+VERIFY = "--verify-only" in sys.argv
+
+
+def token():
+    t = os.environ.get("THEME_TOKEN")
+    if t:
+        return t
+    for line in open(SECRETS):
+        if line.startswith("SHOPIFY_THEME_TOKEN="):
+            return line.split("=", 1)[1].strip().strip('"').strip("'")
+    sys.exit("No theme token. Run: THEME_TOKEN=shpat_xxx python3 " + os.path.basename(__file__))
+
+
+TOK = token()
+H = {"X-Shopify-Access-Token": TOK, "Content-Type": "application/json"}
+
+
+def get(k):
+    q = urllib.parse.urlencode({"asset[key]": k})
+    return json.load(urllib.request.urlopen(urllib.request.Request(
+        f"https://{STORE}/admin/api/{API}/themes/{TID}/assets.json?{q}", headers=H)))["asset"]["value"]
+
+
+def put(k, v):
+    req = urllib.request.Request(f"https://{STORE}/admin/api/{API}/themes/{TID}/assets.json",
+                                 headers=H, data=json.dumps({"asset": {"key": k, "value": v}}).encode(), method="PUT")
+    return json.load(urllib.request.urlopen(req))
+
+
+themes = json.load(urllib.request.urlopen(urllib.request.Request(
+    f"https://{STORE}/admin/api/{API}/themes.json", headers=H)))["themes"]
+role = {t["id"]: t["role"] for t in themes}.get(TID)
+if role == "main":
+    sys.exit("Refusing: target theme is the LIVE main theme.")
+print(f"token …{TOK[-4:]}  target {TID} role={role} (live untouched)")
+
+cj = json.loads(get(TEMPLATE_KEY))
+order = cj.get("order", [])
+wired = HERO_ID in cj.get("sections", {}) and len(order) > 0 and order[0] == HERO_ID
+print(f"currently wired (hero present + first in order): {wired}")
+
+if VERIFY:
+    sys.exit(0)
+
+os.makedirs("theme-backups", exist_ok=True)
+stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+open(f"theme-backups/collection.json.dev-{TID}.{stamp}.bak", "w").write(json.dumps(cj, indent=2))
+
+if REMOVE:
+    cj["sections"].pop(HERO_ID, None)
+    cj["order"] = [o for o in cj.get("order", []) if o != HERO_ID]
+    put(TEMPLATE_KEY, json.dumps(cj, indent=2))
+    print("removed hero from collection.json order (section asset left in place).")
+    sys.exit(0)
+
+# upload/refresh the section
+here = os.path.dirname(os.path.abspath(__file__))
+put(SECTION_KEY, open(os.path.join(here, SECTION_KEY)).read())
+# ensure wired + first in order
+cj.setdefault("sections", {})[HERO_ID] = {"type": HERO_TYPE, "settings": {"show_description": True}}
+cj["order"] = [HERO_ID] + [o for o in cj.get("order", []) if o != HERO_ID]
+put(TEMPLATE_KEY, json.dumps(cj, indent=2))
+
+time.sleep(2)
+back = json.loads(get(TEMPLATE_KEY))
+ok = HERO_ID in back["sections"] and back["order"][0] == HERO_ID
+print(f"staged on dev. verify: {'OK' if ok else 'FAILED'}  order={back['order']}")
+print("\nPREVIEW (open while logged into Shopify admin — unpublished theme):")
+for h in ("artmura", "de-gournay-wallcoverings"):
+    print(f"  https://www.designerwallcoverings.com/collections/{h}?preview_theme_id={TID}")
diff --git a/shopify/sections/dw-collection-hero.liquid b/shopify/sections/dw-collection-hero.liquid
new file mode 100644
index 00000000..c8d61d01
--- /dev/null
+++ b/shopify/sections/dw-collection-hero.liquid
@@ -0,0 +1,69 @@
+{%- comment -%}
+  DW Collection Hero — Steve standing rule (2026-06-23): every collection page
+  must have a background "bg" hero image. Renders collection.image as a full-bleed
+  cover background with the title + breadcrumbs overlaid (gradient scrim for
+  legibility). Collections with no image yet fall back to a branded gradient so
+  the page is never broken; the audit (audit-collection-hero-images.py) lists
+  which collections still need a real image.
+
+  Added as its own section so it can be toggled by removing it from
+  templates/collection.json "order". Renders above the Boost-app product grid.
+{%- endcomment -%}
+{%- liquid
+  assign dw_has_hero = false
+  if collection.image
+    assign dw_has_hero = true
+  endif
+-%}
+<div class="dw-collection-hero{% unless dw_has_hero %} dw-collection-hero--fallback{% endunless %}"
+     {% if dw_has_hero %}style="background-image:url({{ collection.image | img_url: '2048x' }});"{% endif %}
+     data-dw-collection-hero>
+  <div class="dw-collection-hero__scrim"></div>
+  <div class="dw-collection-hero__inner page-width">
+    <nav class="dw-collection-hero__crumbs" aria-label="Breadcrumb">
+      <a href="/">Home</a> <span aria-hidden="true">/</span>
+      <a href="/collections">Collections</a> <span aria-hidden="true">/</span>
+      <span aria-current="page">{{ collection.title }}</span>
+    </nav>
+    <h1 class="dw-collection-hero__title">{{ collection.title }}</h1>
+    {%- if section.settings.show_description and collection.description != blank -%}
+      <div class="dw-collection-hero__desc">{{ collection.description | strip_html | truncatewords: 32 }}</div>
+    {%- endif -%}
+  </div>
+</div>
+<style>
+  .dw-collection-hero{position:relative;min-height:clamp(220px,30vw,400px);display:flex;
+    align-items:flex-end;background-size:cover;background-position:center;background-repeat:no-repeat;
+    margin:0 0 1.5rem;overflow:hidden;}
+  .dw-collection-hero--fallback{background:linear-gradient(135deg,#1c1c1c 0%,#3a3a3a 55%,#2a2320 100%);}
+  .dw-collection-hero__scrim{position:absolute;inset:0;pointer-events:none;
+    background:linear-gradient(180deg,rgba(0,0,0,0) 28%,rgba(0,0,0,.6) 100%);}
+  .dw-collection-hero__inner{position:relative;z-index:1;width:100%;padding:2rem 2.25rem;color:#fff;}
+  .dw-collection-hero__crumbs{font-size:.78rem;letter-spacing:.04em;text-transform:uppercase;
+    opacity:.9;margin:0 0 .4rem;}
+  .dw-collection-hero__crumbs a,.dw-collection-hero__crumbs span{color:#fff;text-decoration:none;}
+  .dw-collection-hero__crumbs a:hover{text-decoration:underline;}
+  .dw-collection-hero__title{color:#fff;font-size:clamp(1.8rem,3.4vw,3rem);margin:.1rem 0 0;
+    letter-spacing:.5px;text-shadow:0 1px 18px rgba(0,0,0,.45);}
+  .dw-collection-hero__desc{color:#fff;max-width:62ch;margin:.6rem 0 0;opacity:.95;
+    font-size:.95rem;line-height:1.5;text-shadow:0 1px 10px rgba(0,0,0,.4);}
+  /* hero now carries title + breadcrumbs: hide any legacy duplicates on collection pages */
+  .template-collection .breadcrumbs{display:none;}
+</style>
+
+{% schema %}
+{
+  "name": "DW Collection Hero",
+  "settings": [
+    {
+      "type": "checkbox",
+      "id": "show_description",
+      "label": "Show collection description in hero",
+      "default": true
+    }
+  ],
+  "presets": [
+    { "name": "DW Collection Hero" }
+  ]
+}
+{% endschema %}

← d7042d8c Audit: repeat+match are OPTIONAL specs (plain/free-match rol  ·  back to Designer Wallcoverings  ·  auto-save: 2026-06-23T12:51:44 (8 files) — shopify/scripts/c ec1ab72f →