← back to Dw Theme Toggle
initial scaffold — self-contained Shopify theme-toggle snippet + push script
150a0d619564611c63fb6cb342b35dd40d3f8cf4 · 2026-05-08 07:35:06 -0700 · Steve Abrams
Files touched
A .gitignoreA INSTALL.mdA push.shA snippets/theme-toggle.liquid
Diff
commit 150a0d619564611c63fb6cb342b35dd40d3f8cf4
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri May 8 07:35:06 2026 -0700
initial scaffold — self-contained Shopify theme-toggle snippet + push script
---
.gitignore | 8 ++++
INSTALL.md | 72 ++++++++++++++++++++++++++++++++
push.sh | 62 ++++++++++++++++++++++++++++
snippets/theme-toggle.liquid | 97 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 239 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1924158
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 0000000..952024f
--- /dev/null
+++ b/INSTALL.md
@@ -0,0 +1,72 @@
+# DW Theme Toggle — Shopify Install
+
+Self-contained sun/moon dark/light toggle, pinned to upper-right of every page on the Shopify theme. CSS-filter inversion (no theme-var knowledge needed), localStorage persistence, anti-flash inline script.
+
+## What's in the box
+
+- `snippets/theme-toggle.liquid` — the entire toggle (anti-flash + CSS + button + JS)
+- `push.sh` — pushes the snippet to the Shopify theme via Admin API
+- This file
+
+## Install (60 seconds)
+
+### Path A — push via API (preferred)
+
+```bash
+cd ~/Projects/dw-theme-toggle
+./push.sh # auto-targets MAIN theme on designer-laboratory-sandbox
+```
+
+Then in Shopify Admin → **Online Store → Themes → ⋯ → Edit code** → open `layout/theme.liquid` → add this single line just inside `<head>`:
+
+```liquid
+{% render 'theme-toggle' %}
+```
+
+Save. Refresh storefront. Sun/moon button appears in upper-right.
+
+### Path B — manual paste (no script)
+
+In Shopify Admin → **Online Store → Themes → ⋯ → Edit code**:
+
+1. Click **Add a new snippet** under `Snippets/` → name it `theme-toggle`.
+2. Paste the contents of `snippets/theme-toggle.liquid`. Save.
+3. Open `layout/theme.liquid`. Just inside `<head>`, add:
+ ```liquid
+ {% render 'theme-toggle' %}
+ ```
+4. Save.
+
+## Target a different theme
+
+```bash
+./push.sh 123456789012 # explicit theme ID
+```
+
+List theme IDs:
+```bash
+set -a; source ~/Projects/secrets-manager/.env; set +a
+curl -sf -H "X-Shopify-Access-Token: $SHOPIFY_ADMIN_TOKEN" \
+ https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/themes.json \
+ | python3 -m json.tool
+```
+
+## Revert
+
+In Shopify Admin → **Online Store → Themes → ⋯ → Edit code**:
+- Delete `snippets/theme-toggle.liquid`
+- Remove the `{% render 'theme-toggle' %}` line from `layout/theme.liquid`
+
+That's it — zero side-effects, no JS bundle change, no asset bloat.
+
+## Why CSS-filter inversion?
+
+Most Shopify themes don't expose a complete `[data-theme]` CSS-var system. Instead of trying to map every theme-specific variable, we use:
+
+```css
+html[data-theme="light"] body { filter: invert(1) hue-rotate(180deg); }
+```
+
+…then double-invert images / video / SVG / iframe / backgrounds so they stay correct. Result: works on Dawn, Streamline, Impulse, Empire, Refresh, custom themes — anything.
+
+Trade-off: photo accent colors shift slightly because `hue-rotate(180deg)` is a true hue flip. If you want pixel-accurate brand colors in light mode, ask and I'll write a Dawn-var override patch instead.
diff --git a/push.sh b/push.sh
new file mode 100755
index 0000000..2b0f960
--- /dev/null
+++ b/push.sh
@@ -0,0 +1,62 @@
+#!/usr/bin/env bash
+# Push the theme-toggle snippet to a Shopify theme on designer-laboratory-sandbox.
+# Usage:
+# ./push.sh # auto-detects MAIN theme on the sandbox
+# ./push.sh <theme_id> # target a specific theme (preview, dev, etc.)
+#
+# Requires: ~/Projects/secrets-manager/.env populated with SHOPIFY_ADMIN_TOKEN
+# Reads: ./snippets/theme-toggle.liquid
+# Writes: snippets/theme-toggle.liquid asset on the target theme
+#
+# The snippet is self-contained (anti-flash + button + CSS + JS in one file).
+# After push, edit theme.liquid and add ONE line just inside <head>:
+# {% render 'theme-toggle' %}
+
+set -euo pipefail
+cd "$(dirname "$0")"
+
+# Load token from canonical secrets-manager .env
+set -a; source ~/Projects/secrets-manager/.env 2>/dev/null || true; set +a
+TOKEN="${SHOPIFY_ADMIN_TOKEN:-${SHOPIFY_ACCESS_TOKEN:-}}"
+DOMAIN="designer-laboratory-sandbox.myshopify.com"
+
+if [ -z "$TOKEN" ]; then
+ echo "ERROR: SHOPIFY_ADMIN_TOKEN not in ~/Projects/secrets-manager/.env" >&2
+ exit 1
+fi
+
+API="https://$DOMAIN/admin/api/2024-10"
+
+# Decide target theme
+if [ "${1:-}" != "" ]; then
+ THEME_ID="$1"
+else
+ echo "Looking up MAIN theme on $DOMAIN..."
+ THEME_ID=$(curl -sf -H "X-Shopify-Access-Token: $TOKEN" "$API/themes.json" \
+ | python3 -c "import sys,json; print(next(t['id'] for t in json.load(sys.stdin)['themes'] if t['role']=='main'))")
+fi
+echo "Target theme ID: $THEME_ID"
+
+# Read snippet body and PUT it as snippets/theme-toggle.liquid
+BODY=$(python3 -c "
+import json
+content = open('snippets/theme-toggle.liquid').read()
+print(json.dumps({'asset': {'key': 'snippets/theme-toggle.liquid', 'value': content}}))
+")
+
+echo "Uploading snippets/theme-toggle.liquid ..."
+RESP=$(curl -sf -X PUT \
+ -H "X-Shopify-Access-Token: $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "$BODY" \
+ "$API/themes/$THEME_ID/assets.json")
+
+KEY=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['asset']['key'])")
+echo "Uploaded asset: $KEY"
+echo
+echo "Final step (one-time, in Shopify Admin → Online Store → Themes → ⋯ → Edit code):"
+echo " Open layout/theme.liquid and add this line just inside <head>:"
+echo " {% render 'theme-toggle' %}"
+echo
+echo "Preview URL:"
+echo " https://$DOMAIN?preview_theme_id=$THEME_ID"
diff --git a/snippets/theme-toggle.liquid b/snippets/theme-toggle.liquid
new file mode 100644
index 0000000..850ed94
--- /dev/null
+++ b/snippets/theme-toggle.liquid
@@ -0,0 +1,97 @@
+{%- comment -%}
+ DW Theme Toggle — sun/moon button in upper-right corner of every page.
+ Drop this snippet into theme.liquid, just inside <head>:
+ {% render 'theme-toggle' %}
+ Self-contained — no other edits required.
+{%- endcomment -%}
+<script>
+ /* Anti-flash: set data-theme on <html> before paint so the page never flashes the wrong theme on reload. */
+ (function () {
+ try {
+ var saved = localStorage.getItem('dw-theme');
+ if (!saved) {
+ saved = (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) ? 'light' : 'dark';
+ }
+ document.documentElement.setAttribute('data-theme', saved);
+ } catch (e) {
+ document.documentElement.setAttribute('data-theme', 'dark');
+ }
+ })();
+</script>
+<style>
+ /* Toggle button — upper-right of viewport, sticky so it floats over the header on every page. */
+ .dw-theme-toggle {
+ position: fixed; top: 14px; right: 16px;
+ width: 38px; height: 38px; border-radius: 50%;
+ background: rgba(0,0,0,0.45); color: #fff;
+ border: 1px solid rgba(255,255,255,0.22);
+ cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
+ font-size: 16px; line-height: 1; padding: 0;
+ transition: background 200ms, transform 200ms, color 200ms, border-color 200ms;
+ backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
+ z-index: 99999;
+ }
+ .dw-theme-toggle:hover { background: rgba(0,0,0,0.65); transform: scale(1.06); }
+ .dw-theme-toggle:focus-visible { outline: 2px solid #DDAB1E; outline-offset: 2px; }
+ .dw-theme-toggle .dw-icon-sun { display: none; }
+ .dw-theme-toggle .dw-icon-moon { display: inline; }
+
+ /* LIGHT theme — invert page colors but double-invert images/video so they stay correct.
+ This works on any Shopify theme without needing to know the theme's specific CSS vars. */
+ html[data-theme="light"] body {
+ filter: invert(1) hue-rotate(180deg);
+ background: #f5f1e8;
+ }
+ html[data-theme="light"] img,
+ html[data-theme="light"] video,
+ html[data-theme="light"] picture,
+ html[data-theme="light"] svg,
+ html[data-theme="light"] iframe,
+ html[data-theme="light"] [style*="background-image"],
+ html[data-theme="light"] .product-card img,
+ html[data-theme="light"] .product__media,
+ html[data-theme="light"] .media,
+ html[data-theme="light"] .image-bg,
+ html[data-theme="light"] .dw-theme-toggle {
+ filter: invert(1) hue-rotate(180deg);
+ }
+ html[data-theme="light"] .dw-theme-toggle {
+ background: rgba(255,255,255,0.55); color: #0a0a0a; border-color: rgba(0,0,0,0.18);
+ }
+ html[data-theme="light"] .dw-theme-toggle:hover { background: rgba(255,255,255,0.78); }
+ html[data-theme="light"] .dw-theme-toggle .dw-icon-sun { display: inline; }
+ html[data-theme="light"] .dw-theme-toggle .dw-icon-moon { display: none; }
+
+ /* Mobile sizing */
+ @media (max-width: 600px) {
+ .dw-theme-toggle { top: 10px; right: 12px; width: 34px; height: 34px; font-size: 14px; }
+ }
+</style>
+<script>
+ /* Inject the toggle button as soon as DOM is ready, then wire the click handler. */
+ (function () {
+ function build() {
+ if (document.getElementById('dw-theme-toggle')) return;
+ var btn = document.createElement('button');
+ btn.id = 'dw-theme-toggle';
+ btn.type = 'button';
+ btn.className = 'dw-theme-toggle';
+ btn.setAttribute('aria-label', 'Toggle dark/light theme');
+ btn.title = 'Toggle theme';
+ btn.innerHTML = '<span class="dw-icon-moon" aria-hidden="true">🌙</span><span class="dw-icon-sun" aria-hidden="true">☀️</span>';
+ btn.addEventListener('click', function () {
+ var cur = document.documentElement.getAttribute('data-theme') || 'dark';
+ var next = cur === 'dark' ? 'light' : 'dark';
+ document.documentElement.setAttribute('data-theme', next);
+ try { localStorage.setItem('dw-theme', next); } catch (e) {}
+ btn.setAttribute('aria-label', 'Switch to ' + (next === 'dark' ? 'light' : 'dark') + ' theme');
+ });
+ document.body.appendChild(btn);
+ }
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', build);
+ } else {
+ build();
+ }
+ })();
+</script>
(oldest)
·
back to Dw Theme Toggle
·
add local web viewer (port 9769) for token paste + theme lis 65aaf21 →