From 2ea5b6cf06cc0aee5f08ab1959e1ad18fbcce0dc Mon Sep 17 00:00:00 2001 From: Daniel McGee <134719124+mcgeeandme@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:05:22 -0400 Subject: [PATCH] Theme shop tab to match printables, add dark mode support to Shopify embed Theme shop tab to match printables, add dark mode support to Shopify embed Shop tab layout - Wrap Shopify embed in v-card (elevation 2) to match printables card style - Add skeleton loaders while the Buy Button SDK initializes - Add subtitle blurb above the category filter, consistent with printables tab Dark mode support - Add useTheme() / isDark computed to track the active Vuetify theme - Convert SHOPIFY_OPTIONS to buildShopifyOptions(dark) so all SDK-rendered surfaces can be re-themed on toggle - Theme product tile background + text (previously hardcoded white text with no background, invisible in light mode) - Theme product modal background (previously always rendered white regardless of site theme) - Theme cart/checkout drawer background, header, line items, and footer (previously always rendered light mode) - Re-run initShopify() via a watcher on isDark so the embed re-renders when the user toggles theme - Round button border-radius from 0px to 4px across product, modal, and cart buttons for visual consistency with printables cards Bug fixes - Add shopifyRenderToken generation guard in initShopify() to prevent a race condition where overlapping calls (e.g. theme settling shortly after mount) could leave the product grid partially rendered with missing images - Move subtitle blurb above the category filter dropdown (was below) Files changed: src/pages/StorePage.vue --- webapp/src/views/Store.vue | 58 +++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/webapp/src/views/Store.vue b/webapp/src/views/Store.vue index 4a3c4c0..9d45fa7 100644 --- a/webapp/src/views/Store.vue +++ b/webapp/src/views/Store.vue @@ -24,7 +24,11 @@ - +

+ T-shirts, stickers, yard signs, and more — proceeds support the anti-surveillance movement. +

+ + -

- T-shirts, stickers, yard signs, and more — proceeds support the anti-surveillance movement. -

- > = { }, }; -// Modal text uses a light-on-dark or dark-on-light palette depending on the -// active Vuetify theme. The product grid tiles always render on the SDK's own -// dark card surface so their text stays white regardless of theme. +// Every surface the Shopify SDK renders (product tiles, the product modal, +// and the cart drawer) gets an explicit background + text color pulled from +// this palette, since the SDK has no awareness of the host page's theme and +// otherwise falls back to its own static (light) defaults. function buildShopifyOptions(dark: boolean) { - const modalText = dark ? '#e0e0e0' : '#4c4c4c'; - const modalText2 = dark ? '#b0b0b0' : '#6b6b6b'; + const surfaceBg = dark ? '#1e1e1e' : '#ffffff'; + const surfaceBg2 = dark ? '#2a2a2a' : '#f5f5f5'; // footer/header accent surface + const modalText = dark ? '#e0e0e0' : '#2c2c2c'; + const modalText2 = dark ? '#b0b0b0' : '#6b6b6b'; + const borderColor = dark ? '#3a3a3a' : '#e0e0e0'; return { product: { styles: { product: { + 'background-color': surfaceBg, '@media (min-width: 601px)': { 'max-width': 'calc(33% - 20px)', 'margin-left': '20px', @@ -316,7 +321,7 @@ function buildShopifyOptions(dark: boolean) { width: 'calc(33% - 20px)', }, }, - title: { 'font-family': 'Raleway, sans-serif', 'font-size': '17px', color: '#ffffff' }, + title: { 'font-family': 'Raleway, sans-serif', 'font-size': '17px', color: modalText }, button: { 'font-family': 'Raleway, sans-serif', 'font-weight': 'bold', @@ -332,10 +337,10 @@ function buildShopifyOptions(dark: boolean) { 'padding-right': '20px', }, quantityInput: { 'font-size': '16px', 'padding-top': '16px', 'padding-bottom': '16px' }, - price: { 'font-family': 'Raleway, sans-serif', 'font-size': '17px', color: '#ffffff' }, - compareAt: { 'font-family': 'Raleway, sans-serif', 'font-size': '14.45px', color: '#ffffff' }, - unitPrice: { 'font-family': 'Raleway, sans-serif', 'font-size': '14.45px', color: '#ffffff' }, - description: { 'font-family': 'Raleway, sans-serif' }, + price: { 'font-family': 'Raleway, sans-serif', 'font-size': '17px', color: modalText }, + compareAt: { 'font-family': 'Raleway, sans-serif', 'font-size': '14.45px', color: modalText2 }, + unitPrice: { 'font-family': 'Raleway, sans-serif', 'font-size': '14.45px', color: modalText2 }, + description: { 'font-family': 'Raleway, sans-serif', color: modalText2 }, }, buttonDestination: 'modal', contents: { button: false, options: false }, @@ -354,7 +359,11 @@ function buildShopifyOptions(dark: boolean) { modalProduct: { contents: { img: false, imgWithCarousel: true, button: false, buttonWithQuantity: true }, styles: { - product: { '@media (min-width: 601px)': { 'max-width': '100%', 'margin-left': '0px', 'margin-bottom': '0px' } }, + modal: { 'background-color': surfaceBg }, + product: { + 'background-color': surfaceBg, + '@media (min-width: 601px)': { 'max-width': '100%', 'margin-left': '0px', 'margin-bottom': '0px' }, + }, button: { 'font-family': 'Raleway, sans-serif', 'font-weight': 'bold', @@ -388,6 +397,13 @@ function buildShopifyOptions(dark: boolean) { }, cart: { styles: { + cart: { 'background-color': surfaceBg }, + header: { 'background-color': surfaceBg, color: modalText, 'border-color': borderColor }, + lineItems: { 'background-color': surfaceBg, color: modalText, 'border-color': borderColor }, + footer: { 'background-color': surfaceBg2, color: modalText, 'border-color': borderColor }, + title: { color: modalText }, + notice: { color: modalText2 }, + currency: { color: modalText }, button: { 'font-family': 'Raleway, sans-serif', 'font-weight': 'bold', @@ -493,10 +509,18 @@ declare global { interface Window { ShopifyBuy: any } } +// Guards against overlapping initShopify calls. If the collection or theme +// changes again before a previous onReady() promise resolves, the stale +// callback below becomes a no-op instead of racing the newer one and +// leaving the container half-rendered (some product images missing). +let shopifyRenderToken = 0; + function initShopify(id: string) { const container = shopifyContainer.value; if (!container) return; + const myToken = ++shopifyRenderToken; + shopifyReady.value = false; container.innerHTML = ''; @@ -505,6 +529,8 @@ function initShopify(id: string) { storefrontAccessToken: '78991208f7fea14aa4ac02a58f8025dd', }); window.ShopifyBuy.UI.onReady(client).then((ui: any) => { + if (myToken !== shopifyRenderToken) return; // a newer render superseded this one + ui.createComponent('collection', { id, node: container,