[object Object]

← 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

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 →