← back to Wallco Ai
fix wallco.ai age-gate stuck overlay: drop inline display:flex, control via .is-open class; ESC also skips
e10e4dd8899a30e9e55cf5174ac7de2b3b2e2f29 · 2026-05-11 16:34:53 -0700 · Steve
Files touched
A scripts/__pycache__/spoonflower_lib.cpython-314.pycM scripts/spoonflower_lib.pyM server.js
Diff
commit e10e4dd8899a30e9e55cf5174ac7de2b3b2e2f29
Author: Steve <steve@designerwallcoverings.com>
Date: Mon May 11 16:34:53 2026 -0700
fix wallco.ai age-gate stuck overlay: drop inline display:flex, control via .is-open class; ESC also skips
---
.../__pycache__/spoonflower_lib.cpython-314.pyc | Bin 0 -> 7474 bytes
scripts/spoonflower_lib.py | 76 +++++++++++++++++----
server.js | 28 ++++++--
3 files changed, 86 insertions(+), 18 deletions(-)
diff --git a/scripts/__pycache__/spoonflower_lib.cpython-314.pyc b/scripts/__pycache__/spoonflower_lib.cpython-314.pyc
new file mode 100644
index 0000000..a23e463
Binary files /dev/null and b/scripts/__pycache__/spoonflower_lib.cpython-314.pyc differ
diff --git a/scripts/spoonflower_lib.py b/scripts/spoonflower_lib.py
index 2b03e22..f977aac 100644
--- a/scripts/spoonflower_lib.py
+++ b/scripts/spoonflower_lib.py
@@ -35,48 +35,96 @@ def creds() -> Tuple[str, str]:
def login(page, email: str, password: str) -> bool:
- """Sign in to Spoonflower. Returns True on success."""
+ """Sign in to Spoonflower. Returns True on success.
+
+ Spoonflower's login is a classic server-rendered Rails form with these IDs:
+ #email (type=email) · #password · #submit · name="remember_me"
+ We target IDs first; fallback selectors only as last resort.
+ """
print("[login] navigating", file=sys.stderr, flush=True)
- page.goto("https://www.spoonflower.com/login", wait_until="networkidle", timeout=60000)
+ page.goto("https://www.spoonflower.com/login",
+ wait_until="domcontentloaded", timeout=60000)
time.sleep(2)
- for sel in ('input[type="email"]', 'input[type="text"]',
- 'input[placeholder*="email" i]'):
+ # Email — precise ID first
+ filled_email = False
+ for sel in ('#email', 'input[name="email"]', 'input[type="email"]'):
try:
loc = page.locator(sel).first
if loc.is_visible(timeout=4000):
loc.fill(email)
+ filled_email = True
break
except Exception:
continue
+ if not filled_email:
+ print("[login] could not fill email", file=sys.stderr, flush=True)
+ return False
- for sel in ('input[type="password"]',):
+ # Password
+ filled_pw = False
+ for sel in ('#password', 'input[name="password"]', 'input[type="password"]'):
try:
loc = page.locator(sel).first
if loc.is_visible(timeout=4000):
loc.fill(password)
+ filled_pw = True
break
except Exception:
continue
+ if not filled_pw:
+ print("[login] could not fill password", file=sys.stderr, flush=True)
+ return False
- for sel in ('button[type="submit"]',
- 'button:has-text("Sign In")',
- 'button:has-text("Log In")'):
+ # Submit — the form submit button has id="submit"; fall back to form.submit()
+ clicked = False
+ for sel in ('input#submit', 'input[type="submit"][name="submit"]',
+ 'button#submit', 'input[type="submit"]'):
try:
loc = page.locator(sel).first
if loc.is_visible(timeout=3000):
loc.click()
+ clicked = True
break
except Exception:
continue
+ if not clicked:
+ # Last resort — JS submit
+ try:
+ page.evaluate("() => document.querySelector('#email')?.form?.submit()")
+ clicked = True
+ except Exception:
+ pass
+
+ if not clicked:
+ print("[login] could not submit", file=sys.stderr, flush=True)
+ return False
- page.wait_for_load_state("networkidle", timeout=60000)
+ try:
+ page.wait_for_load_state("networkidle", timeout=45000)
+ except Exception:
+ pass
time.sleep(3)
- # Heuristic: signed-in pages expose /en/my-account or avatar
- body = page.url + " " + (page.content()[:2000] if page.content() else "")
- ok = ("logout" in body.lower() or "my-account" in body.lower()
- or "Sign Out" in body or "my-spoonflower" in body.lower())
- print(f"[login] signed_in={ok} url={page.url}", file=sys.stderr, flush=True)
+
+ # Signed-in detection — try a few signals
+ url = page.url
+ cookies = page.context.cookies()
+ cookie_names = {c.get("name", "") for c in cookies}
+ has_session = any(n.startswith("_spoonflower") or "session" in n.lower()
+ for n in cookie_names)
+
+ # Try to navigate to a member-only page; if we end up at /login, fail.
+ try:
+ page.goto("https://www.spoonflower.com/en/my-spoonflower",
+ wait_until="domcontentloaded", timeout=30000)
+ time.sleep(2)
+ member_url_ok = "/login" not in page.url
+ except Exception:
+ member_url_ok = False
+
+ ok = has_session and member_url_ok
+ print(f"[login] signed_in={ok} session_cookie={has_session} url={page.url}",
+ file=sys.stderr, flush=True)
return ok
diff --git a/server.js b/server.js
index db86eaf..9e4952b 100644
--- a/server.js
+++ b/server.js
@@ -247,7 +247,11 @@ const FOOTER = `<footer class="site-footer">
const HAMBURGER_JS = `
<!-- First-visit Age Prompt (drives age-adaptive UI per ageBand) -->
-<div id="age-prompt-overlay" hidden style="position:fixed;inset:0;background:rgba(15,14,12,.85);z-index:9999;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(8px)">
+<style>
+ #age-prompt-overlay { position:fixed; inset:0; background:rgba(15,14,12,.85); z-index:9999; align-items:center; justify-content:center; backdrop-filter:blur(8px); display:none; }
+ #age-prompt-overlay.is-open { display:flex; }
+</style>
+<div id="age-prompt-overlay" aria-hidden="true">
<div style="background:#fff;border-radius:12px;padding:36px 40px;max-width:480px;width:90%;box-shadow:0 24px 64px rgba(0,0,0,.4);font-family:'Inter',system-ui,sans-serif">
<h2 style="font-family:'Cormorant Garamond',serif;font-weight:300;font-size:26px;margin:0 0 6px;color:#1a1a1a">Welcome to wallco.ai</h2>
<p style="margin:0 0 22px;color:#666;font-size:14px;line-height:1.5">Tell us your age so we can show you the version of this site that's easiest for you to use.</p>
@@ -293,24 +297,40 @@ const HAMBURGER_JS = `
return 'elder';
}
var overlay = document.getElementById('age-prompt-overlay');
+ function closeOverlay(){
+ if (!overlay) return;
+ overlay.classList.remove('is-open');
+ overlay.setAttribute('aria-hidden','true');
+ }
var seen = localStorage.getItem('wallco-age') || localStorage.getItem('wallco-age-skipped');
if (!seen && overlay) {
- overlay.hidden = false;
+ overlay.classList.add('is-open');
+ overlay.setAttribute('aria-hidden','false');
setTimeout(function(){ var i = document.getElementById('age-input'); if (i) i.focus(); }, 100);
document.getElementById('age-submit').addEventListener('click', function(){
var a = parseInt(document.getElementById('age-input').value, 10);
if (!a || a < 3 || a > 99) { document.getElementById('age-input').style.borderColor='#c00'; return; }
localStorage.setItem('wallco-age', String(a));
document.documentElement.setAttribute('data-age-band', ageBand(a));
- overlay.hidden = true;
+ closeOverlay();
});
document.getElementById('age-skip').addEventListener('click', function(){
localStorage.setItem('wallco-age-skipped', '1');
- overlay.hidden = true;
+ closeOverlay();
});
document.getElementById('age-input').addEventListener('keydown', function(e){
if (e.key === 'Enter') document.getElementById('age-submit').click();
});
+ // Escape key also skips
+ document.addEventListener('keydown', function(e){
+ if (e.key === 'Escape' && overlay.classList.contains('is-open')) {
+ localStorage.setItem('wallco-age-skipped', '1');
+ closeOverlay();
+ }
+ });
+ } else if (overlay) {
+ // Already seen — make sure overlay never reappears
+ closeOverlay();
}
})();
</script>`;
← f3dc9ad add finish_dns.sh — CF DNS + certbot + health check script f
·
back to Wallco Ai
·
go-live launcher: scripts/golive.sh + /admin/golive web chec 047f1d6 →