← Back to site
Demo diagnostic — not client work

Diff: before → after

The full line-by-line code diff between the broken before/ and fixed after/ demo. See the Changed Files summary for a plain-English overview.

Demo diagnostic — not real client work. This is evidence from a self-contained sample, shown as proof of the documentation included with the Shopify bug fix.
added removed hunk header
diff --git a/before/index.html b/after/index.html index 17bcbfc..4201d22 100644 --- a/before/index.html +++ b/after/index.html @@ -1,27 +1,28 @@ <!DOCTYPE html> <!-- ============================================================ - BEFORE (BROKEN) — Shopify-style product page + cart drawer + AFTER (FIXED) — Shopify-style product page + cart drawer ============================================================ - This is a DEMO that intentionally reproduces common Shopify - theme bugs. It is NOT real client work and uses neutral, - made-up product data. + Same DEMO product, with the intentional bugs repaired. + NOT real client work; neutral, made-up product data. - Known (intentional) problems in this version: - 1. Add-to-cart button does NOT open the cart drawer. - 2. Cart count in the header does NOT visibly update. - 3. Mobile layout breaks under 430px (CTA pushed far down). - 4. Variant selector spacing is messy and cramped. - 5. Product image area is too tall on mobile. - 6. Cart feedback after adding is unclear / missing. - 7. Avoidable layout shift from an unsized image area. + Fixes applied vs. the BEFORE version: + 1. Add-to-cart now opens the cart drawer. + 2. Header cart count updates visibly on every add. + 3. Mobile layout reworked: image constrained, CTA visible earlier. + 4. Variant selector spacing cleaned up (flex + wrap + gap). + 5. Product image uses a fixed aspect ratio (no layout shift). + 6. Clear cart feedback: drawer slides in, line items + subtotal, + remove buttons, and a brief "Added" toast. + 7. Added viewport meta + sticky mobile CTA bar. See ../report/shopify-demo-diagnostic.md for the writeup. --> <html lang="en"> <head> <meta charset="UTF-8" /> - <!-- BUG: no viewport meta tag, so mobile rendering is inconsistent and zoomed --> - <title>Performance Hoodie — Shopify-style Demo (Before)</title> + <!-- FIX: proper viewport meta for correct mobile rendering --> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>Performance Hoodie — Shopify-style Demo (After)</title> <link rel="stylesheet" href="styles.css" /> </head> <body> @@ -30,16 +31,16 @@ <nav class="nav"> <a href="#">Shop</a> <a href="#">About</a> - <!-- BUG: cart count element exists but JS never updates it --> - <a href="#" class="cart-link">Cart (<span id="cart-count">0</span>)</a> + <!-- FIX: cart count is updated by JS on every add --> + <a href="#" class="cart-link" id="cart-link">Cart (<span id="cart-count">0</span>)</a> </nav> </header> - <span class="demo-flag">Demo diagnostic — BEFORE (broken)</span> + <span class="demo-flag">Demo diagnostic — AFTER (fixed)</span> <main class="product"> - <!-- BUG: image area has no fixed aspect ratio; it is huge on mobile - and causes layout shift while the gradient "loads" --> + <!-- FIX: image area uses a fixed aspect ratio so it never blows up the + mobile viewport and causes no layout shift --> <div class="product-media"> <div class="product-image"></div> <div class="thumb-row"> @@ -57,23 +58,27 @@ Tapered fit, zip side pocket, and a drawcord hood. </p> - <!-- BUG: variant options are cramped and have inconsistent spacing --> + <!-- FIX: clean, evenly spaced variant pills (flex + wrap + gap) --> <div class="variant-group"> <div class="variant-label">Color</div> <div class="variant-options"> - <button class="variant is-selected">Black</button><button class="variant">Slate</button><button class="variant">Olive</button> + <button class="variant is-selected">Black</button> + <button class="variant">Slate</button> + <button class="variant">Olive</button> </div> </div> <div class="variant-group"> <div class="variant-label">Size</div> <div class="variant-options"> - <button class="variant">S</button><button class="variant is-selected">M</button><button class="variant">L</button><button class="variant">XL</button> + <button class="variant">S</button> + <button class="variant is-selected">M</button> + <button class="variant">L</button> + <button class="variant">XL</button> </div> </div> - <!-- BUG: the add-to-cart handler updates a variable but never opens the - drawer and never updates the visible cart count --> - <button id="add-to-cart" class="add-to-cart">Add to cart</button> + <!-- FIX: this button now opens the drawer, updates the count, and shows feedback --> + <button id="add-to-cart" class="add-to-cart">Add to cart — $78.00</button> <ul class="product-meta"> <li>Free returns within 30 days</li> @@ -82,11 +87,21 @@ </div> </main> - <!-- The cart drawer markup exists but is never shown by the JS --> - <div id="cart-drawer" class="cart-drawer"> + <!-- FIX: sticky mobile CTA so the buy action is reachable without scrolling --> + <div class="sticky-cta"> + <span class="sticky-price">$78.00</span> + <button id="sticky-add" class="sticky-add">Add to cart</button> + </div> + + <!-- Brief confirmation toast --> + <div id="toast" class="toast" role="status" aria-live="polite">Added to cart</div> + + <!-- Cart drawer + dimmed backdrop --> + <div id="cart-backdrop" class="cart-backdrop"></div> + <aside id="cart-drawer" class="cart-drawer" aria-hidden="true"> <div class="cart-drawer-head"> <h2>Your cart</h2> - <button id="cart-close" class="cart-close">×</button> + <button id="cart-close" class="cart-close" aria-label="Close cart">×</button> </div> <div id="cart-items" class="cart-items"></div> <div class="cart-foot"> @@ -95,8 +110,9 @@ <span id="cart-subtotal">$0.00</span> </div> <button class="checkout-btn">Checkout</button> + <p class="cart-note">Shipping &amp; taxes calculated at checkout.</p> </div> - </div> + </aside> <script src="script.js"></script> </body> diff --git a/before/script.js b/after/script.js index 124100b..a7a9a21 100644 --- a/before/script.js +++ b/after/script.js @@ -1,19 +1,128 @@ /* ============================================================ - BEFORE (BROKEN) script — Shopify-style cart logic + AFTER (FIXED) script — Shopify-style cart logic ------------------------------------------------------------ - This intentionally reproduces a very common Shopify theme bug: - the "add to cart" click runs, but the UI never reflects it. + The add-to-cart flow now drives the UI end to end: + open drawer, render line items, update count + subtotal, + support quantity/remove, and show a brief confirmation toast. ============================================================ */ -// Simple in-memory cart state for the demo. var cart = []; var PRODUCT = { + id: "performance-hoodie", title: "Performance Hoodie", price: 78.0, }; -// Variant selection (visual only here) +// ---- Element references ---- +var countEl = document.getElementById("cart-count"); +var cartLink = document.getElementById("cart-link"); +var drawer = document.getElementById("cart-drawer"); +var backdrop = document.getElementById("cart-backdrop"); +var itemsEl = document.getElementById("cart-items"); +var subtotalEl = document.getElementById("cart-subtotal"); +var toast = document.getElementById("toast"); + +// ---- Helpers ---- +function money(n) { + return "$" + n.toFixed(2); +} + +function selectedVariant() { + // Read the currently selected color + size labels for the line item. + var labels = []; + document.querySelectorAll(".variant-group").forEach(function (group) { + var sel = group.querySelector(".variant.is-selected"); + if (sel) labels.push(sel.textContent.trim()); + }); + return labels.join(" / "); +} + +// FIX: open / close drawer by toggling classes (no more display:none dead end) +function openDrawer() { + drawer.classList.add("open"); + backdrop.classList.add("open"); + drawer.setAttribute("aria-hidden", "false"); +} +function closeDrawer() { + drawer.classList.remove("open"); + backdrop.classList.remove("open"); + drawer.setAttribute("aria-hidden", "true"); +} + +// FIX: visible cart count + a small bump animation so the change is noticed +function updateCount() { + var total = cart.reduce(function (sum, line) { return sum + line.qty; }, 0); + countEl.textContent = total; + cartLink.classList.remove("bump"); + // force reflow so the animation can replay on each add + void cartLink.offsetWidth; + cartLink.classList.add("bump"); +} + +// FIX: clear, itemized cart feedback (image, title, variant, qty, price, remove) +function renderCart() { + itemsEl.innerHTML = ""; + + if (cart.length === 0) { + itemsEl.innerHTML = '<p class="cart-empty">Your cart is empty.</p>'; + subtotalEl.textContent = money(0); + return; + } + + var subtotal = 0; + cart.forEach(function (line, index) { + subtotal += line.price * line.qty; + + var row = document.createElement("div"); + row.className = "cart-line"; + row.innerHTML = + '<div class="cart-line-img"></div>' + + '<div class="cart-line-body">' + + '<div class="cart-line-title">' + line.title + "</div>" + + '<div class="cart-line-meta">' + line.variant + "</div>" + + '<div class="cart-line-row">' + + '<div class="qty">' + + '<button data-act="dec" data-i="' + index + '" aria-label="Decrease quantity">−</button>' + + "<span>" + line.qty + "</span>" + + '<button data-act="inc" data-i="' + index + '" aria-label="Increase quantity">+</button>' + + "</div>" + + '<span class="cart-line-price">' + money(line.price * line.qty) + "</span>" + + "</div>" + + '<button class="cart-remove" data-act="remove" data-i="' + index + '">Remove</button>' + + "</div>"; + itemsEl.appendChild(row); + }); + + subtotalEl.textContent = money(subtotal); +} + +function showToast(message) { + toast.textContent = message; + toast.classList.add("show"); + clearTimeout(showToast._t); + showToast._t = setTimeout(function () { + toast.classList.remove("show"); + }, 1600); +} + +// ---- Core action: add to cart ---- +function addToCart() { + var variant = selectedVariant(); + // Merge with an existing identical line, otherwise push a new one. + var existing = cart.find(function (l) { return l.variant === variant; }); + if (existing) { + existing.qty += 1; + } else { + cart.push({ title: PRODUCT.title, variant: variant, price: PRODUCT.price, qty: 1 }); + } + updateCount(); // FIX #2: count updates visibly + renderCart(); // FIX #6: itemized feedback + openDrawer(); // FIX #1: drawer actually opens + showToast("Added to cart"); +} + +// ---- Variant selection ---- document.querySelectorAll(".variant-options").forEach(function (group) { group.querySelectorAll(".variant").forEach(function (btn) { btn.addEventListener("click", function () { @@ -25,19 +134,32 @@ document.querySelectorAll(".variant-options").forEach(function (group) { }); }); -// BUG #1 + #2 + #6: -// The handler mutates the cart array but does NONE of the UI work: -// - it never opens the cart drawer -// - it never updates the visible "Cart (0)" count in the header -// - it gives no confirmation/feedback to the shopper -// To the user, clicking "Add to cart" appears to do nothing. -document.getElementById("add-to-cart").addEventListener("click", function () { - cart.push({ title: PRODUCT.title, price: PRODUCT.price, qty: 1 }); - // (No drawer open. No count update. No feedback.) That's the bug. - console.log("Item pushed to cart array, but UI was never updated.", cart); +// ---- Wire up controls ---- +document.getElementById("add-to-cart").addEventListener("click", addToCart); +document.getElementById("sticky-add").addEventListener("click", addToCart); +document.getElementById("cart-close").addEventListener("click", closeDrawer); +backdrop.addEventListener("click", closeDrawer); + +// Quantity +/- and remove (event delegation on the items container) +itemsEl.addEventListener("click", function (e) { + var btn = e.target.closest("[data-act]"); + if (!btn) return; + var i = parseInt(btn.getAttribute("data-i"), 10); + var act = btn.getAttribute("data-act"); + + if (act === "inc") cart[i].qty += 1; + if (act === "dec") cart[i].qty = Math.max(1, cart[i].qty - 1); + if (act === "remove") cart.splice(i, 1); + + updateCount(); + renderCart(); }); -// The close button is wired, but since the drawer never opens it is useless. -document.getElementById("cart-close").addEventListener("click", function () { - document.getElementById("cart-drawer").style.display = "none"; +// Close drawer with Escape for accessibility. +document.addEventListener("keydown", function (e) { + if (e.key === "Escape") closeDrawer(); }); + +// Initial paint. +renderCart(); +updateCount(); diff --git a/before/styles.css b/after/styles.css index 2c7d0f0..b91fe87 100644 --- a/before/styles.css +++ b/after/styles.css @@ -1,6 +1,6 @@ /* ============================================================ - BEFORE (BROKEN) styles — Shopify-style product page - Intentionally contains realistic theme/layout problems. + AFTER (FIXED) styles — Shopify-style product page + Cleaner layout, mobile-first CTA, fixed image sizing. ============================================================ */ :root { @@ -8,6 +8,7 @@ --muted: #6b7280; --line: #e5e7eb; --accent: #111827; + --accent-hover: #000; } * { box-sizing: border-box; } @@ -30,16 +31,23 @@ body { .brand { font-weight: 700; letter-spacing: 1px; font-size: 15px; } .nav a { margin-left: 20px; text-decoration: none; color: var(--ink); font-size: 14px; } .cart-link { font-weight: 600; } +/* FIX: a tiny pulse so the cart count change is noticeable */ +.cart-link.bump { animation: bump .3s ease; } +@keyframes bump { + 0% { transform: scale(1); } + 40% { transform: scale(1.18); } + 100% { transform: scale(1); } +} .demo-flag { display: inline-block; margin: 14px 28px 0; padding: 4px 10px; - background: #fff4d6; - border: 1px solid #f0d999; + background: #e7f6ec; + border: 1px solid #b7e2c5; border-radius: 4px; font-size: 12px; - color: #8a6d1f; + color: #1f7a44; } /* ---------- Product layout ---------- */ @@ -52,11 +60,11 @@ body { } .product-media { flex: 1; } -/* BUG: no aspect-ratio / fixed height. The gradient block has no intrinsic - size, so on mobile it stretches very tall and shifts the layout. */ +/* FIX: fixed aspect ratio reserves space up front => no layout shift, + and the image never dominates the mobile viewport. */ .product-image { width: 100%; - min-height: 520px; + aspect-ratio: 1 / 1; background: linear-gradient(135deg, #cbd5e1 0%, #94a3b8 60%, #64748b 100%); border-radius: 10px; } @@ -71,32 +79,37 @@ body { .product-price { font-size: 22px; font-weight: 600; margin-bottom: 16px; } .product-desc { color: var(--muted); line-height: 1.6; margin-bottom: 24px; } -/* BUG: cramped, inconsistent variant spacing. Buttons jammed together, - no wrap control, uneven label gaps. */ -.variant-group { margin-bottom: 6px; } -.variant-label { font-size: 13px; color: var(--muted); margin-bottom: 2px; } -.variant-options { display: block; } +/* FIX: clean, consistent variant spacing using flex + wrap + gap */ +.variant-group { margin-bottom: 18px; } +.variant-label { font-size: 13px; color: var(--muted); margin-bottom: 8px; } +.variant-options { display: flex; flex-wrap: wrap; gap: 10px; } .variant { - padding: 6px 9px; + padding: 9px 16px; border: 1px solid var(--line); background: #fff; - font-size: 13px; + border-radius: 8px; + font-size: 14px; cursor: pointer; + transition: border-color .15s ease, background .15s ease; } -.variant.is-selected { border-color: var(--ink); background: #f3f4f6; } +.variant:hover { border-color: #9ca3af; } +.variant.is-selected { border-color: var(--ink); background: #f3f4f6; font-weight: 600; } -/* BUG: CTA sits far below a too-tall image on mobile; not sticky, no emphasis */ +/* FIX: prominent CTA with hover feedback */ .add-to-cart { - margin-top: 22px; + margin-top: 6px; width: 100%; - padding: 15px; + padding: 16px; background: var(--accent); color: #fff; border: none; - border-radius: 8px; + border-radius: 10px; font-size: 15px; + font-weight: 600; cursor: pointer; + transition: background .15s ease; } +.add-to-cart:hover { background: var(--accent-hover); } .product-meta { list-style: none; @@ -107,28 +120,126 @@ body { } .product-meta li { margin-bottom: 4px; } -/* ---------- Cart drawer (markup present, never opened by JS) ---------- */ +/* ---------- Sticky mobile CTA (hidden on desktop) ---------- */ +.sticky-cta { + display: none; + position: fixed; + bottom: 0; left: 0; right: 0; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 12px 16px; + background: #fff; + border-top: 1px solid var(--line); + box-shadow: 0 -6px 20px rgba(0,0,0,.06); + z-index: 30; +} +.sticky-price { font-weight: 700; font-size: 16px; } +.sticky-add { + flex: 1; + max-width: 220px; + padding: 13px; + background: var(--accent); + color: #fff; + border: none; + border-radius: 10px; + font-size: 15px; + font-weight: 600; + cursor: pointer; +} + +/* ---------- Toast feedback ---------- */ +.toast { + position: fixed; + top: 16px; + left: 50%; + transform: translateX(-50%) translateY(-20px); + background: #111827; + color: #fff; + padding: 10px 18px; + border-radius: 999px; + font-size: 14px; + opacity: 0; + pointer-events: none; + transition: opacity .2s ease, transform .2s ease; + z-index: 50; +} +.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); } + +/* ---------- Cart drawer + backdrop ---------- */ +.cart-backdrop { + position: fixed; + inset: 0; + background: rgba(0,0,0,.35); + opacity: 0; + visibility: hidden; + transition: opacity .25s ease, visibility .25s ease; + z-index: 40; +} +.cart-backdrop.open { opacity: 1; visibility: visible; } + .cart-drawer { position: fixed; top: 0; right: 0; - width: 360px; height: 100%; + width: 380px; + max-width: 90vw; + height: 100%; background: #fff; border-left: 1px solid var(--line); - box-shadow: -10px 0 30px rgba(0,0,0,.08); - /* BUG: drawer is hidden and there is no open state toggled by JS */ - display: none; + box-shadow: -10px 0 30px rgba(0,0,0,.12); + /* FIX: drawer slides in via a translate + .open class toggled by JS */ + transform: translateX(100%); + transition: transform .28s ease; padding: 20px; + z-index: 45; + display: flex; + flex-direction: column; } +.cart-drawer.open { transform: translateX(0); } + .cart-drawer-head { display: flex; justify-content: space-between; align-items: center; } +.cart-drawer-head h2 { font-size: 18px; margin: 0; } .cart-close { border: none; background: none; font-size: 26px; cursor: pointer; line-height: 1; } -.cart-items { margin-top: 16px; } -.cart-foot { position: absolute; bottom: 20px; left: 20px; right: 20px; } -.cart-subtotal { display: flex; justify-content: space-between; font-weight: 600; margin-bottom: 12px; } -.checkout-btn { width: 100%; padding: 14px; background: var(--accent); color: #fff; border: none; border-radius: 8px; font-size: 15px; cursor: pointer; } - -/* ---------- "Responsive" rules (intentionally inadequate) ---------- */ -/* BUG: breakpoint is 480px and only stacks the columns. It does NOT fix the - oversized image, the CTA stays buried, and nothing handles <430px well. */ -@media (max-width: 480px) { - .product { flex-direction: column; gap: 20px; } + +.cart-items { margin-top: 16px; flex: 1; overflow-y: auto; } +.cart-empty { color: var(--muted); font-size: 14px; margin-top: 20px; } + +.cart-line { display: flex; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--line); } +.cart-line-img { + width: 56px; height: 56px; border-radius: 8px; flex-shrink: 0; + background: linear-gradient(135deg, #cbd5e1, #64748b); +} +.cart-line-body { flex: 1; } +.cart-line-title { font-size: 14px; font-weight: 600; } +.cart-line-meta { font-size: 12px; color: var(--muted); margin-top: 2px; } +.cart-line-row { display: flex; align-items: center; justify-content: space-between; margin-top: 6px; } +.qty { display: inline-flex; align-items: center; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; } +.qty button { width: 28px; height: 28px; border: none; background: #f9fafb; cursor: pointer; font-size: 15px; } +.qty span { width: 30px; text-align: center; font-size: 14px; } +.cart-line-price { font-size: 14px; font-weight: 600; } +.cart-remove { border: none; background: none; color: var(--muted); font-size: 12px; cursor: pointer; text-decoration: underline; padding: 0; } + +.cart-foot { border-top: 1px solid var(--line); padding-top: 14px; } +.cart-subtotal { display: flex; justify-content: space-between; font-weight: 600; margin-bottom: 12px; font-size: 16px; } +.checkout-btn { width: 100%; padding: 14px; background: var(--accent); color: #fff; border: none; border-radius: 10px; font-size: 15px; font-weight: 600; cursor: pointer; } +.checkout-btn:hover { background: var(--accent-hover); } +.cart-note { text-align: center; color: var(--muted); font-size: 12px; margin: 10px 0 0; } + +/* ---------- Responsive: real mobile handling, incl. <430px ---------- */ +@media (max-width: 860px) { + .product { flex-direction: column; gap: 22px; margin-top: 18px; } + /* FIX: cap the image height on mobile so the CTA appears sooner */ + .product-image { aspect-ratio: 4 / 3; max-height: 46vh; } + .product-title { font-size: 24px; } + /* leave room for the sticky bar */ + body { padding-bottom: 76px; } + .sticky-cta { display: flex; } +} + +@media (max-width: 430px) { + .site-header { padding: 14px 16px; } + .demo-flag { margin: 12px 16px 0; } + .product { padding: 0 16px; } + .product-image { aspect-ratio: 3 / 2; max-height: 40vh; } + .variant { padding: 8px 14px; } }