← back to Secrets Manager
auto-save: 2026-07-21T17:08:42 (5 files) — deploy-collection-faqs.py greenland-cleanup-backups/dw-collection-hero-BACKUP-banner.liquid greenland-cleanup-backups/dw-collection-hero-BACKUP-v2pre.liquid halve-banner.py update-collection-aeo-v2.py
1b7fdd1247e120ca85242d23730b4a1e7e082ec2 · 2026-07-21 17:08:42 -0700 · Steve Abrams
Files touched
A deploy-collection-faqs.pyA greenland-cleanup-backups/dw-collection-hero-BACKUP-banner.liquidA greenland-cleanup-backups/dw-collection-hero-BACKUP-v2pre.liquidA halve-banner.pyA update-collection-aeo-v2.py
Diff
commit 1b7fdd1247e120ca85242d23730b4a1e7e082ec2
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jul 21 17:08:42 2026 -0700
auto-save: 2026-07-21T17:08:42 (5 files) — deploy-collection-faqs.py greenland-cleanup-backups/dw-collection-hero-BACKUP-banner.liquid greenland-cleanup-backups/dw-collection-hero-BACKUP-v2pre.liquid halve-banner.py update-collection-aeo-v2.py
---
deploy-collection-faqs.py | 87 +++++++++++++++++
.../dw-collection-hero-BACKUP-banner.liquid | 105 ++++++++++++++++++++
.../dw-collection-hero-BACKUP-v2pre.liquid | 106 +++++++++++++++++++++
halve-banner.py | 46 +++++++++
update-collection-aeo-v2.py | 69 ++++++++++++++
5 files changed, 413 insertions(+)
diff --git a/deploy-collection-faqs.py b/deploy-collection-faqs.py
new file mode 100644
index 0000000..0672ddf
--- /dev/null
+++ b/deploy-collection-faqs.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+"""
+deploy-collection-faqs.py — set a custom.faq JSON metafield on the top category
+collections. The live theme's dw-collection-hero.liquid already renders this metafield
+as a visible FAQ + FAQPage JSON-LD, so this alone takes each page ~60 -> ~85+.
+Grounded in each collection's real description + material facts. Private-label safe
+(no source vendor names). Idempotent (skips a collection that already has the metafield
+unless --force). Run:
+ ! python3 ~/Projects/secrets-manager/deploy-collection-faqs.py # dry-run
+ ! python3 ~/Projects/secrets-manager/deploy-collection-faqs.py --apply
+"""
+import re, json, sys, urllib.request
+
+APPLY = '--apply' in sys.argv; FORCE = '--force' in sys.argv
+env = open('/Users/macstudio3/Projects/secrets-manager/.env').read()
+tok = re.search(r'^SHOPIFY_CONTENT_TOKEN=(.+)$', env, re.M).group(1).strip().strip('"').strip("'")
+S = "designer-laboratory-sandbox.myshopify.com"; A = "2024-10"
+
+def api(p, method="GET", body=None):
+ data = json.dumps(body).encode() if body else None
+ r = urllib.request.Request(f"https://{S}/admin/api/{A}/{p}", data=data, method=method,
+ headers={"X-Shopify-Access-Token": tok, "Content-Type": "application/json"})
+ return json.load(urllib.request.urlopen(r, timeout=30))
+
+Q = lambda q, a: {"q": q, "a": a}
+FAQS = {
+ 287331975219: [ # grasscloth-wallpapers
+ Q("What is grasscloth wallcovering?", "Grasscloth is a natural-fiber wallcovering handwoven from grasses, jute, hemp, sisal and similar sustainable fibers. Its organic texture adds depth and tactile richness that paint cannot replicate."),
+ Q("What rooms suit grasscloth?", "It shines as a feature or accent wall in dining rooms, bedrooms, studies, and entries. In high-moisture or high-scrub areas, treat it as a feature wall or choose a coated natural, since unsealed grasscloth is more delicate than vinyl."),
+ Q("Does grasscloth have color and seam variation?", "Yes. Because it is woven from real plant fibers, subtle shading and visible seams between panels are inherent to the material and part of its handcrafted appeal. Order all the yardage you need from one dye lot."),
+ Q("Can I order grasscloth samples first?", "Yes. Designer Wallcoverings offers up to 10 free samples so you can check the weave and colorway in your own light before ordering."),
+ ],
+ 95011799105: [ # faux-leather
+ Q("What is faux leather wallcovering?", "Faux leather is a vinyl wallcovering engineered to mimic the look and feel of genuine leather. It is cruelty-free and more sustainable since it is not derived from animal products."),
+ Q("Is faux leather good for high-traffic or commercial spaces?", "Yes. Its enhanced durability makes it well suited to high-traffic and commercial areas where real leather would wear, while keeping a luxurious leather appearance."),
+ Q("How do you clean faux leather wallcovering?", "The vinyl surface wipes clean with a damp cloth, which is part of why it performs so well in busy residential and commercial interiors."),
+ Q("Can I order samples before buying?", "Yes. Designer Wallcoverings offers up to 10 free samples so you can compare grain and color in your space."),
+ ],
+ 79865839728: [ # flocked-velvet-wallpaper-collection
+ Q("What is flocked velvet wallcovering?", "Flock wallcovering was originally created to imitate expensive cut-velvet hangings, using fibers applied to an adhesive-coated ground to form a raised, velvet-like texture. Today the pile is made from synthetic fibers such as polyester, nylon, or rayon."),
+ Q("What look does flocked velvet create?", "A tactile, dimensional, luxurious surface with a soft raised pattern — ideal for formal rooms, dining rooms, and dramatic feature walls where you want depth and richness."),
+ Q("How do you care for flocked wallcovering?", "Treat the pile gently: dust lightly and avoid abrasion or heavy scrubbing so the raised velvet texture stays crisp."),
+ Q("Can I order flocked velvet samples?", "Yes. Designer Wallcoverings offers up to 10 free samples so you can feel the pile and see the colorway before committing."),
+ ],
+ 79865512048: [ # cork
+ Q("What is cork wallcovering?", "Cork wallcovering is made from the bark of the cork oak tree. It offers a warm, earthy texture with natural variation in pattern and color that makes every panel distinctive."),
+ Q("Is cork wallcovering sustainable?", "Yes. Cork is harvested from the bark of the tree without cutting it down, making it a renewable, eco-friendly surface."),
+ Q("What rooms suit cork?", "Cork works beautifully as a feature wall in studies, offices, bedrooms, and living spaces, and its natural structure also helps soften sound in a room."),
+ Q("Can I order cork samples first?", "Yes. Designer Wallcoverings offers up to 10 free samples so you can see the natural texture and tone in your own light."),
+ ],
+ 93443981377: [ # faux-silk
+ Q("What is faux silk wallcovering?", "Faux silk captures the soft luster and refined drape of real silk in a more durable, easier-care material — the elegance of silk without its fragility."),
+ Q("What look does faux silk create?", "A gentle sheen and quiet luxury that suits bedrooms, dining rooms, and formal living spaces where you want a soft, light-catching backdrop."),
+ Q("Is faux silk more durable than real silk?", "Yes. It is engineered to resist the wear and delicacy of genuine silk while keeping the same refined, lustrous appearance."),
+ Q("Can I order samples before buying?", "Yes. Designer Wallcoverings offers up to 10 free samples so you can check the sheen and color against your furnishings."),
+ ],
+ 94741528641: [ # metallic-vinyl
+ Q("What is metallic vinyl wallcovering?", "Metallic vinyl combines a reflective metallic finish with a durable vinyl surface, creating a luminous wall that catches and changes with the light."),
+ Q("What rooms suit metallic vinyl?", "It makes a striking feature or accent wall in powder rooms, dining rooms, entries, and commercial spaces, where the reflective sheen adds depth and drama."),
+ Q("Is metallic vinyl durable and cleanable?", "Yes. As a vinyl wallcovering it is scrubbable and hard-wearing, well suited to both residential feature walls and high-traffic commercial interiors."),
+ Q("Can I order metallic vinyl samples?", "Yes. Designer Wallcoverings offers up to 10 free samples so you can see how the metallic finish reads in your lighting."),
+ ],
+ 294320046131: [ # novasuede-microfiber-suede
+ Q("What is Novasuede?", "Novasuede is a premium microfiber suede wallcovering with the soft, tactile look of real suede in a durable, cleanable material engineered for walls."),
+ Q("Where is Novasuede used?", "It creates a luxurious, upholstered-look feature wall in residential and commercial interiors — a rich, soft surface that reads far more expensive than paint."),
+ Q("How do you care for Novasuede?", "As a microfiber it is more durable and cleanable than natural suede, holding its soft nap in everyday interiors."),
+ Q("Can I order Novasuede samples first?", "Yes. Designer Wallcoverings offers up to 10 free samples so you can feel the suede texture and check the colorway in your space."),
+ ],
+}
+
+changed = skipped = 0
+for cid, faq in FAQS.items():
+ existing = api(f"collections/{cid}/metafields.json?namespace=custom&key=faq")['metafields']
+ if existing and not FORCE:
+ print(f" {cid}: custom.faq already set — skip"); skipped += 1; continue
+ if not APPLY:
+ print(f" {cid}: would set custom.faq ({len(faq)} Q&A)"); continue
+ body = {"metafield": {"namespace": "custom", "key": "faq", "type": "json",
+ "value": json.dumps(faq, ensure_ascii=False)}}
+ res = api(f"collections/{cid}/metafields.json", "POST", body)
+ ok = 'metafield' in res
+ print(f" {cid}: {'SET ✓' if ok else 'FAIL ' + json.dumps(res)[:150]} ({len(faq)} Q&A)")
+ changed += ok
+
+print(f"\n{'APPLIED' if APPLY else 'DRY-RUN'} — set={changed} skipped={skipped} of {len(FAQS)} collections")
+if APPLY:
+ print("Verify (allow storefront cache): https://designerwallcoverings.com/collections/grasscloth-wallpapers")
diff --git a/greenland-cleanup-backups/dw-collection-hero-BACKUP-banner.liquid b/greenland-cleanup-backups/dw-collection-hero-BACKUP-banner.liquid
new file mode 100644
index 0000000..044afac
--- /dev/null
+++ b/greenland-cleanup-backups/dw-collection-hero-BACKUP-banner.liquid
@@ -0,0 +1,105 @@
+{%- 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 + description on a SEMI-OPAQUE
+ CONTRASTING PANEL (dark scrim panel) so the text stays legible over any image.
+ Collections with no image fall back to a branded gradient so the page is never
+ broken; audit-collection-hero-images.py lists which collections still need one.
+
+ Also HIDES the Boost AI Search & Filter app's own collection banner (the
+ duplicate lower banner) + any legacy theme breadcrumbs, so only this hero shows.
+
+ Toggle by removing it from templates/collection.json "order".
+{%- 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__inner page-width">
+ <div class="dw-collection-hero__panel">
+ <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>
+ {%- assign aeo_cat = collection.metafields.custom.category | default: 'wallcovering' -%}
+ <p class="dw-collection-hero__answer">A curated {{ aeo_cat }} collection for the interior design trade — {{ collection.products_count }} designs, with samples and trade pricing on request.</p>
+ {%- 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>
+</div>
+{%- assign aeo_faq = collection.metafields.custom.faq.value -%}
+<script type="application/ld+json">
+{"@context":"https://schema.org","@type":"CollectionPage","name":{{ collection.title | json }},"description":{{ collection.description | strip_html | truncatewords: 40 | json }},"url":{{ request.origin | append: collection.url | json }},"isPartOf":{"@type":"WebSite","name":"Designer Wallcoverings","url":{{ request.origin | json }}}}
+</script>
+<script type="application/ld+json">
+{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":{{ request.origin | json }}},{"@type":"ListItem","position":2,"name":"Collections","item":{{ request.origin | append: '/collections' | json }}},{"@type":"ListItem","position":3,"name":{{ collection.title | json }},"item":{{ request.origin | append: collection.url | json }}}]}
+</script>
+{%- if aeo_faq != blank and aeo_faq != empty -%}
+<details class="dw-collection-faq" style="max-width:900px;margin:.75rem auto 0;padding:0 1rem;"><summary style="cursor:pointer;font:500 .95rem/1.4 inherit;letter-spacing:.02em;opacity:.75;">Learn about {{ collection.title | remove: ' Collections' | remove: ' Collection' }}</summary><div style="padding:.5rem 0 .25rem;">{%- for item in aeo_faq -%}<h3 style="font-size:1rem;margin:.75rem 0 .25rem;">{{ item.q }}</h3><p style="margin:0 0 .5rem;opacity:.85;">{{ item.a }}</p>{%- endfor -%}</div></details>
+<script type="application/ld+json">
+{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{%- for item in aeo_faq -%}{"@type":"Question","name":{{ item.q | json }},"acceptedAnswer":{"@type":"Answer","text":{{ item.a | strip_html | json }}}}{% unless forloop.last %},{% endunless %}{%- endfor -%}]}
+</script>
+{%- endif -%}
+<style>
+ .dw-collection-hero{position:relative;min-height:clamp(40px,5.35vw,70px);display:flex;
+ align-items:center;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__inner{position:relative;z-index:1;width:100%;padding:0.9rem 2.25rem;}
+ /* Semi-opaque CONTRASTING panel behind the text — dark scrim over the light
+ pattern so title/breadcrumbs/description read clearly without darkening the
+ whole image. Constrained width, left-aligned. */
+ .dw-collection-hero__panel{display:inline-block;max-width:min(640px,92%);
+ background:rgba(22,18,16,0.66);
+ -webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);
+ border:1px solid rgba(255,255,255,0.14);border-radius:6px;
+ padding:1.4rem 1.75rem;box-shadow:0 6px 30px rgba(0,0,0,.28);}
+ .dw-collection-hero__crumbs{font-size:.78rem;letter-spacing:.04em;text-transform:uppercase;
+ color:#fff;opacity:.92;margin:0 0 .45rem;}
+ .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;line-height:1.05;}
+ .dw-collection-hero__desc{color:#fff;max-width:60ch;margin:.6rem 0 0;opacity:.96;
+ font-size:.95rem;line-height:1.55;}
+
+ /* Hide the duplicate LOWER banner drawn by the Boost AI Search & Filter app
+ (its own collection header/banner) so only this hero shows. */
+ .boost-sd__collection-header,
+ .boost-sd__page-header,
+ .boost-sd__collection-header-image,
+ .boost-sd__collection-header-content,
+ .boost-sd__collection-header-title,
+ .boost-sd__collection-header-description,
+ .boost-sd__collection-title,
+ .boost-sd__collection-description{display:none !important;}
+ /* hero now carries breadcrumbs: hide any legacy theme breadcrumbs on collection pages */
+ .template-collection .breadcrumbs,
+ .template-collection .breadcrumb{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 %}
diff --git a/greenland-cleanup-backups/dw-collection-hero-BACKUP-v2pre.liquid b/greenland-cleanup-backups/dw-collection-hero-BACKUP-v2pre.liquid
new file mode 100644
index 0000000..2f5e99a
--- /dev/null
+++ b/greenland-cleanup-backups/dw-collection-hero-BACKUP-v2pre.liquid
@@ -0,0 +1,106 @@
+{%- 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 + description on a SEMI-OPAQUE
+ CONTRASTING PANEL (dark scrim panel) so the text stays legible over any image.
+ Collections with no image fall back to a branded gradient so the page is never
+ broken; audit-collection-hero-images.py lists which collections still need one.
+
+ Also HIDES the Boost AI Search & Filter app's own collection banner (the
+ duplicate lower banner) + any legacy theme breadcrumbs, so only this hero shows.
+
+ Toggle by removing it from templates/collection.json "order".
+{%- 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__inner page-width">
+ <div class="dw-collection-hero__panel">
+ <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>
+ {%- assign aeo_cat = collection.metafields.custom.category | default: 'wallcovering' -%}
+ <p class="dw-collection-hero__answer">Browse {{ collection.products_count }} luxury {{ aeo_cat }}{% if collection.products_count != 1 %}s{% endif %} in the {{ collection.title }} collection at Designer Wallcoverings.</p>
+ {%- 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>
+</div>
+{%- assign aeo_faq = collection.metafields.custom.faq.value -%}
+<script type="application/ld+json">
+{"@context":"https://schema.org","@type":"CollectionPage","name":{{ collection.title | json }},"description":{{ collection.description | strip_html | truncatewords: 40 | json }},"url":{{ request.origin | append: collection.url | json }},"isPartOf":{"@type":"WebSite","name":"Designer Wallcoverings","url":{{ request.origin | json }}}}
+</script>
+<script type="application/ld+json">
+{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":{{ request.origin | json }}},{"@type":"ListItem","position":2,"name":"Collections","item":{{ request.origin | append: '/collections' | json }}},{"@type":"ListItem","position":3,"name":{{ collection.title | json }},"item":{{ request.origin | append: collection.url | json }}}]}
+</script>
+{%- if aeo_faq != blank and aeo_faq != empty -%}
+<div class="dw-collection-faq"><h2>Frequently Asked Questions</h2>
+{%- for item in aeo_faq -%}<h3>{{ item.q }}</h3><p>{{ item.a }}</p>{%- endfor -%}</div>
+<script type="application/ld+json">
+{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{%- for item in aeo_faq -%}{"@type":"Question","name":{{ item.q | json }},"acceptedAnswer":{"@type":"Answer","text":{{ item.a | strip_html | json }}}}{% unless forloop.last %},{% endunless %}{%- endfor -%}]}
+</script>
+{%- endif -%}
+<style>
+ .dw-collection-hero{position:relative;min-height:clamp(80px,10.7vw,140px);display:flex;
+ align-items:center;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__inner{position:relative;z-index:1;width:100%;padding:0.9rem 2.25rem;}
+ /* Semi-opaque CONTRASTING panel behind the text — dark scrim over the light
+ pattern so title/breadcrumbs/description read clearly without darkening the
+ whole image. Constrained width, left-aligned. */
+ .dw-collection-hero__panel{display:inline-block;max-width:min(640px,92%);
+ background:rgba(22,18,16,0.66);
+ -webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);
+ border:1px solid rgba(255,255,255,0.14);border-radius:6px;
+ padding:1.4rem 1.75rem;box-shadow:0 6px 30px rgba(0,0,0,.28);}
+ .dw-collection-hero__crumbs{font-size:.78rem;letter-spacing:.04em;text-transform:uppercase;
+ color:#fff;opacity:.92;margin:0 0 .45rem;}
+ .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;line-height:1.05;}
+ .dw-collection-hero__desc{color:#fff;max-width:60ch;margin:.6rem 0 0;opacity:.96;
+ font-size:.95rem;line-height:1.55;}
+
+ /* Hide the duplicate LOWER banner drawn by the Boost AI Search & Filter app
+ (its own collection header/banner) so only this hero shows. */
+ .boost-sd__collection-header,
+ .boost-sd__page-header,
+ .boost-sd__collection-header-image,
+ .boost-sd__collection-header-content,
+ .boost-sd__collection-header-title,
+ .boost-sd__collection-header-description,
+ .boost-sd__collection-title,
+ .boost-sd__collection-description{display:none !important;}
+ /* hero now carries breadcrumbs: hide any legacy theme breadcrumbs on collection pages */
+ .template-collection .breadcrumbs,
+ .template-collection .breadcrumb{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 %}
diff --git a/halve-banner.py b/halve-banner.py
new file mode 100644
index 0000000..4197274
--- /dev/null
+++ b/halve-banner.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+"""
+halve-banner.py — halve the collection-hero banner min-height on each run. Reads the
+current `min-height:clamp(Apx,Bvw,Cpx)` in the live theme's dw-collection-hero.liquid
+and rewrites it to half of A/B/C. Idempotent-safe (just halves whatever is there);
+run again to halve further. Backs up first.
+Run: ! python3 ~/Projects/secrets-manager/halve-banner.py # dry-run (shows old->new)
+ ! python3 ~/Projects/secrets-manager/halve-banner.py --apply
+"""
+import re, json, sys, urllib.request
+
+APPLY = '--apply' in sys.argv
+env = open('/Users/macstudio3/Projects/secrets-manager/.env').read()
+tok = re.search(r'^SHOPIFY_THEME_TOKEN=(.+)$', env, re.M).group(1).strip().strip('"').strip("'")
+S = "designer-laboratory-sandbox.myshopify.com"; A = "2024-10"; TID = 144396058675
+KEY = "sections/dw-collection-hero.liquid"
+
+def get_asset():
+ r = urllib.request.Request(f"https://{S}/admin/api/{A}/themes/{TID}/assets.json?asset[key]={KEY}",
+ headers={"X-Shopify-Access-Token": tok})
+ return json.load(urllib.request.urlopen(r, timeout=30))['asset']['value']
+def put_asset(v):
+ b = json.dumps({"asset": {"key": KEY, "value": v}}).encode()
+ r = urllib.request.Request(f"https://{S}/admin/api/{A}/themes/{TID}/assets.json", data=b,
+ method="PUT", headers={"X-Shopify-Access-Token": tok, "Content-Type": "application/json"})
+ return json.load(urllib.request.urlopen(r, timeout=30))
+
+v = get_asset()
+m = re.search(r'min-height:clamp\(\s*([\d.]+)px\s*,\s*([\d.]+)vw\s*,\s*([\d.]+)px\s*\)', v)
+if not m:
+ print("ABORT: no min-height:clamp(...) found"); sys.exit(1)
+a, b, c = (float(x) for x in m.groups())
+na, nb, nc = round(a/2, 1), round(b/2, 2), round(c/2, 1)
+old = m.group(0)
+new = f"min-height:clamp({na:g}px,{nb:g}vw,{nc:g}px)"
+print(f"banner min-height: {old.split('clamp')[1]} -> clamp({na:g}px,{nb:g}vw,{nc:g}px)")
+
+if not APPLY:
+ print("DRY-RUN — re-run with --apply to write."); sys.exit(0)
+
+backup = "/Users/macstudio3/Projects/secrets-manager/greenland-cleanup-backups/dw-collection-hero-BACKUP-banner.liquid"
+open(backup, "w").write(v)
+res = put_asset(v.replace(old, new, 1))
+print("PUT:", "OK" if 'asset' in res else json.dumps(res)[:150])
+print(f"rollback: re-PUT {backup}")
+print("Look (hard-refresh): https://designerwallcoverings.com/collections/grasscloth-wallpaper-collection")
diff --git a/update-collection-aeo-v2.py b/update-collection-aeo-v2.py
new file mode 100644
index 0000000..7543efb
--- /dev/null
+++ b/update-collection-aeo-v2.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+"""
+update-collection-aeo-v2.py — refine the live collection AEO block per Steve's feedback:
+ 1. Banner height HALVED (min-height clamp 80/10.7vw/140 -> 40/5.35vw/70).
+ 2. Intro elevated to trade/designer-facing dialogue (one sentence, ≤3 total w/ desc).
+ 3. FAQ moved into a COLLAPSED <details> panel ("Learn about …") so it takes almost no
+ room and products surface immediately — content stays in the DOM (AEO-visible) and
+ the FAQPage JSON-LD schema is untouched.
+Backs up first; idempotent (skips if v2 already applied).
+Run: ! python3 ~/Projects/secrets-manager/update-collection-aeo-v2.py # dry-run
+ ! python3 ~/Projects/secrets-manager/update-collection-aeo-v2.py --apply
+"""
+import re, json, sys, urllib.request
+
+APPLY = '--apply' in sys.argv
+env = open('/Users/macstudio3/Projects/secrets-manager/.env').read()
+tok = re.search(r'^SHOPIFY_THEME_TOKEN=(.+)$', env, re.M).group(1).strip().strip('"').strip("'")
+S = "designer-laboratory-sandbox.myshopify.com"; A = "2024-10"; TID = 144396058675
+KEY = "sections/dw-collection-hero.liquid"
+
+def get_asset():
+ r = urllib.request.Request(f"https://{S}/admin/api/{A}/themes/{TID}/assets.json?asset[key]={KEY}",
+ headers={"X-Shopify-Access-Token": tok})
+ return json.load(urllib.request.urlopen(r, timeout=30))['asset']['value']
+def put_asset(v):
+ b = json.dumps({"asset": {"key": KEY, "value": v}}).encode()
+ r = urllib.request.Request(f"https://{S}/admin/api/{A}/themes/{TID}/assets.json", data=b,
+ method="PUT", headers={"X-Shopify-Access-Token": tok, "Content-Type": "application/json"})
+ return json.load(urllib.request.urlopen(r, timeout=30))
+
+REPLACEMENTS = [
+ # 1 · halve the banner height
+ ("clamp(80px,10.7vw,140px)", "clamp(40px,5.35vw,70px)"),
+ # 2 · elevated trade-facing intro (replace just the inner sentence)
+ ("Browse {{ collection.products_count }} luxury {{ aeo_cat }}{% if collection.products_count != 1 %}s{% endif %} in the {{ collection.title }} collection at Designer Wallcoverings.",
+ "A curated {{ aeo_cat }} collection for the interior design trade — {{ collection.products_count }} designs, with samples and trade pricing on request."),
+ # 3 · FAQ -> collapsed <details> panel (visible-in-DOM, near-zero room; schema untouched)
+ ('<div class="dw-collection-faq"><h2>Frequently Asked Questions</h2>\n{%- for item in aeo_faq -%}<h3>{{ item.q }}</h3><p>{{ item.a }}</p>{%- endfor -%}</div>',
+ '<details class="dw-collection-faq" style="max-width:900px;margin:.75rem auto 0;padding:0 1rem;">'
+ '<summary style="cursor:pointer;font:500 .95rem/1.4 inherit;letter-spacing:.02em;opacity:.75;">'
+ 'Learn about {{ collection.title | remove: \' Collections\' | remove: \' Collection\' }}</summary>'
+ '<div style="padding:.5rem 0 .25rem;">'
+ '{%- for item in aeo_faq -%}<h3 style="font-size:1rem;margin:.75rem 0 .25rem;">{{ item.q }}</h3>'
+ '<p style="margin:0 0 .5rem;opacity:.85;">{{ item.a }}</p>{%- endfor -%}</div></details>'),
+]
+
+v = get_asset()
+if 'interior design trade' in v:
+ print("v2 already applied — no change (idempotent)."); sys.exit(0)
+
+backup = "/Users/macstudio3/Projects/secrets-manager/greenland-cleanup-backups/dw-collection-hero-BACKUP-v2pre.liquid"
+open(backup, "w").write(v); print(f"backup: {backup}")
+
+new = v
+for old, rep in REPLACEMENTS:
+ if old not in new:
+ print(f"ABORT: could not find target substring: {old[:60]!r} — no write."); sys.exit(1)
+ new = new.replace(old, rep, 1)
+print(f"3 edits located ✓ ({len(new)-len(v):+d} chars) banner=halved · intro=trade · FAQ=collapsed")
+
+if not APPLY:
+ print("\nDRY-RUN — re-run with --apply to write the live theme."); sys.exit(0)
+
+res = put_asset(new)
+print("PUT:", "OK (valid Liquid)" if 'asset' in res else json.dumps(res)[:200])
+back = get_asset()
+print("verify: trade intro present =", 'interior design trade' in back, "| collapsed FAQ =", '<details class="dw-collection-faq"' in back)
+print(f"rollback if needed: re-PUT {backup}")
+print("Look: https://designerwallcoverings.com/collections/grasscloth-wallpapers (hard-refresh)")
← fc3b6fb auto-save: 2026-07-21T16:38:33 (3 files) — deploy-collection
·
back to Secrets Manager
·
auto-save: 2026-07-21T17:38:50 (1 files) — deploy-faqs-bymat e415f0e →