[object Object]

← back to Apartmentwallpaper

refactor(theme): extract page-theme to shared css/js; replace overbroad [class*=card] with explicit .card,.rail-card

8f5a5fa0a2c6b5bc7dfe4ecedae48d2d0b9ba679 · 2026-06-01 13:07:11 -0700 · SteveStudio2

Single source of truth for the theme1/theme2 preview. FOUC guard moved to a
render-blocking <head> script; toggle CSS + wiring now in /page-theme.css and
/page-theme.js. The previous [class*="card"] wildcard matched any element with
'card' anywhere in a class name — narrowed to the two real card surfaces.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 8f5a5fa0a2c6b5bc7dfe4ecedae48d2d0b9ba679
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Mon Jun 1 13:07:11 2026 -0700

    refactor(theme): extract page-theme to shared css/js; replace overbroad [class*=card] with explicit .card,.rail-card
    
    Single source of truth for the theme1/theme2 preview. FOUC guard moved to a
    render-blocking <head> script; toggle CSS + wiring now in /page-theme.css and
    /page-theme.js. The previous [class*="card"] wildcard matched any element with
    'card' anywhere in a class name — narrowed to the two real card surfaces.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 public/index.html     | 95 ++++++++++++---------------------------------------
 public/page-theme.css | 39 +++++++++++++++++++++
 public/page-theme.js  | 33 ++++++++++++++++++
 3 files changed, 94 insertions(+), 73 deletions(-)

diff --git a/public/index.html b/public/index.html
index 7ece6f7..0c6b55b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -18,6 +18,26 @@
 <meta name="description" content="apartmentwallpaper is a curated atelier dedicated entirely to rental-friendly. We do one thing — and we do it carefully.">
 <meta name="theme-color" content="#F4F1EC">
 <link rel="canonical" href="https://apartmentwallpaper.com/">
+<!-- Page-theme preview (theme1/theme2). FOUC guard: set the attribute before paint,
+     in <head>, so a saved theme2 doesn't flash theme1. CSS + toggle wiring are shared. -->
+<script>
+(function(){
+  var qs = new URLSearchParams(location.search);
+  var fromUrl = qs.get('theme');
+  var urlTheme = (fromUrl === '2' || fromUrl === 'theme2') ? 'theme2'
+               : (fromUrl === '1' || fromUrl === 'theme1') ? 'theme1' : null;
+  var saved = null; try { saved = localStorage.getItem('wallco-page-theme'); } catch(e){}
+  var theme = urlTheme || ((saved === 'theme2' || saved === 'theme1') ? saved : null);
+  if (theme) {
+    var de = document.documentElement;
+    de.setAttribute('data-page-theme', theme);
+    de.setAttribute('data-theme-preview', 'on');
+    if (urlTheme) { try { localStorage.setItem('wallco-page-theme', urlTheme); } catch(e){} }
+  }
+})();
+</script>
+<link rel="stylesheet" href="/page-theme.css">
+<script src="/page-theme.js" defer></script>
 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
 <link rel="preload" as="image" href="/hero-bg.jpg" fetchpriority="high">
 <script type="application/ld+json">
@@ -236,79 +256,8 @@ textarea:focus-visible,
 <!-- End Meta Pixel -->
 </head>
 <body>
-<!-- Theme toggle (theme1 / theme2) — query-param-gated preview surface
-     (Steve 2026-05-29). ?theme=2 in URL flips to theme2 and persists in
-     localStorage. Toggle UI hidden until theme2 mode active. Same key
-     ('wallco-page-theme') as wallco.ai for cross-site continuity. -->
-<script>
-(function(){
-  var qs = new URLSearchParams(location.search);
-  var fromUrl = qs.get('theme');
-  var urlTheme = (fromUrl === '2' || fromUrl === 'theme2') ? 'theme2'
-               : (fromUrl === '1' || fromUrl === 'theme1') ? 'theme1'
-               : null;
-  var saved = null; try { saved = localStorage.getItem('wallco-page-theme'); } catch(e){}
-  var theme = urlTheme || ((saved === 'theme2' || saved === 'theme1') ? saved : null);
-  if (theme) {
-    document.documentElement.setAttribute('data-page-theme', theme);
-    // Sticky opt-in flag: once the preview is engaged (valid URL param or a saved
-    // choice) the toggle stays visible in BOTH themes, so clicking "Theme 1" isn't
-    // a one-way trap. Default visitors who never opted in still never see it.
-    document.documentElement.setAttribute('data-theme-preview', 'on');
-    // Persist only when the URL explicitly carried a VALID theme, so an
-    // unrelated link with ?theme=garbage can't silently rewrite the stored choice.
-    if (urlTheme) { try { localStorage.setItem('wallco-page-theme', urlTheme); } catch(e){} }
-  }
-})();
-</script>
-<style id="page-theme-css-fleet">
-  [data-page-theme="theme2"] body{
-    background:#fff !important; color:#0a0a0a !important;
-  }
-  [data-page-theme="theme2"] h1,
-  [data-page-theme="theme2"] h2,
-  [data-page-theme="theme2"] h3{ letter-spacing:-0.01em; font-weight:500; }
-  [data-page-theme="theme2"] .card,
-  [data-page-theme="theme2"] [class*="card"]{
-    background:#fafafa !important; border:1px solid #e5e5e5 !important; box-shadow:none !important;
-  }
-  #page-theme-toggle{ display:none; }
-  [data-theme-preview="on"] #page-theme-toggle{ display:inline-flex; }
-  #page-theme-toggle{
-    position:fixed; top:96px; right:14px; z-index:90;
-    gap:0; align-items:center;
-    background:rgba(255,255,255,.92); border:1px solid #d8d2c5;
-    border-radius:999px; padding:3px; backdrop-filter:blur(6px);
-    box-shadow:0 2px 10px rgba(0,0,0,.08);
-    font:600 11px ui-sans-serif,system-ui; letter-spacing:.06em; text-transform:uppercase;
-  }
-  #page-theme-toggle button{
-    border:0; background:transparent; color:#5a5048; cursor:pointer;
-    padding:6px 14px; border-radius:999px; font:inherit; transition:all .15s;
-  }
-  #page-theme-toggle button[aria-pressed="true"]{ background:#1a1714; color:#faf7f2; }
-  #page-theme-toggle button:hover:not([aria-pressed="true"]){ color:#1a1714; }
-</style>
-<div id="page-theme-toggle" role="group" aria-label="Page theme">
-  <button type="button" data-theme="theme1" aria-pressed="false">Theme 1</button>
-  <button type="button" data-theme="theme2" aria-pressed="false">Theme 2</button>
-</div>
-<script>
-(function(){
-  var cur = document.documentElement.getAttribute('data-page-theme') || 'theme1';
-  var btns = document.querySelectorAll('#page-theme-toggle button');
-  function sync(t){ btns.forEach(function(b){ b.setAttribute('aria-pressed', b.dataset.theme === t ? 'true' : 'false'); }); }
-  sync(cur);
-  btns.forEach(function(b){
-    b.addEventListener('click', function(){
-      var t = b.dataset.theme;
-      document.documentElement.setAttribute('data-page-theme', t);
-      try { localStorage.setItem('wallco-page-theme', t); } catch(e){}
-      sync(t);
-    });
-  });
-})();
-</script>
+<!-- Page-theme (theme1/theme2) is wired in <head>: inline FOUC guard + /page-theme.css
+     + /page-theme.js. Shared across index + editorial pages. -->
 
 <style id="ns-header-modal-styles">
         .ns-header { position: sticky; top: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; padding: 18px 36px; background: rgba(10,10,10,0.92) !important; backdrop-filter: blur(8px); border-bottom: 1px solid rgba(245,241,232,0.06); }
diff --git a/public/page-theme.css b/public/page-theme.css
new file mode 100644
index 0000000..3765b10
--- /dev/null
+++ b/public/page-theme.css
@@ -0,0 +1,39 @@
+/* Shared page-theme (theme1 / theme2) preview surface — single source of truth
+   across index + editorial pages (Steve 2026-06-01). Pairs with the inline
+   FOUC-guard bootstrap in each page <head> and /page-theme.js (toggle wiring).
+   Key ('wallco-page-theme') matches wallco.ai for cross-site continuity. */
+
+[data-page-theme="theme2"] body{
+  background:#fff !important; color:#0a0a0a !important;
+}
+[data-page-theme="theme2"] h1,
+[data-page-theme="theme2"] h2,
+[data-page-theme="theme2"] h3{ letter-spacing:-0.01em; font-weight:500; }
+
+/* Explicit card classes only — the previous [class*="card"] wildcard also matched
+   unrelated elements (anything with "card" anywhere in a class name). These are the
+   two real card surfaces on the catalog page; editorial pages have neither, so this
+   simply no-ops there. */
+[data-page-theme="theme2"] .card,
+[data-page-theme="theme2"] .rail-card{
+  background:#fafafa !important; border:1px solid #e5e5e5 !important; box-shadow:none !important;
+}
+
+#page-theme-toggle{ display:none; }
+/* Sticky opt-in: visible in BOTH themes once preview engaged, so "Theme 1" isn't a
+   one-way trap. Default visitors who never opted in never see it. */
+[data-theme-preview="on"] #page-theme-toggle{ display:inline-flex; }
+#page-theme-toggle{
+  position:fixed; top:96px; right:14px; z-index:90;
+  gap:0; align-items:center;
+  background:rgba(255,255,255,.92); border:1px solid #d8d2c5;
+  border-radius:999px; padding:3px; backdrop-filter:blur(6px);
+  box-shadow:0 2px 10px rgba(0,0,0,.08);
+  font:600 11px ui-sans-serif,system-ui; letter-spacing:.06em; text-transform:uppercase;
+}
+#page-theme-toggle button{
+  border:0; background:transparent; color:#5a5048; cursor:pointer;
+  padding:6px 14px; border-radius:999px; font:inherit; transition:all .15s;
+}
+#page-theme-toggle button[aria-pressed="true"]{ background:#1a1714; color:#faf7f2; }
+#page-theme-toggle button:hover:not([aria-pressed="true"]){ color:#1a1714; }
diff --git a/public/page-theme.js b/public/page-theme.js
new file mode 100644
index 0000000..6c3958b
--- /dev/null
+++ b/public/page-theme.js
@@ -0,0 +1,33 @@
+/* Shared page-theme toggle wiring (Steve 2026-06-01). Loaded with `defer` on every
+   page. The render-blocking FOUC guard (inline <head> script) already set
+   data-page-theme / data-theme-preview before paint; this only injects + wires the
+   toggle UI, which is CSS-hidden until preview mode, so late injection never flashes.
+   Reads/writes localStorage('wallco-page-theme') — same key as wallco.ai. */
+(function(){
+  var bar = document.createElement('div');
+  bar.id = 'page-theme-toggle';
+  bar.setAttribute('role','group');
+  bar.setAttribute('aria-label','Page theme');
+  bar.innerHTML =
+    '<button type="button" data-theme="theme1" aria-pressed="false">Theme 1</button>' +
+    '<button type="button" data-theme="theme2" aria-pressed="false">Theme 2</button>';
+  document.body.appendChild(bar);
+
+  var cur = document.documentElement.getAttribute('data-page-theme') || 'theme1';
+  var btns = bar.querySelectorAll('button');
+  function sync(t){ btns.forEach(function(b){ b.setAttribute('aria-pressed', b.dataset.theme === t ? 'true' : 'false'); }); }
+  sync(cur);
+
+  btns.forEach(function(b){
+    b.addEventListener('click', function(){
+      var t = b.dataset.theme;
+      var de = document.documentElement;
+      de.setAttribute('data-page-theme', t);
+      // Clicking the toggle is an explicit opt-in → keep it visible (sticky) so the
+      // user can switch back; otherwise picking Theme 1 would hide the control.
+      de.setAttribute('data-theme-preview','on');
+      try { localStorage.setItem('wallco-page-theme', t); } catch(e){}
+      sync(t);
+    });
+  });
+})();

← 66f45ff fix(facets): drive /api/facets from live filter state so chi  ·  back to Apartmentwallpaper  ·  fix(theme): carry theme1/theme2 across editorial pages so th 0ec5a9e →