Browser walkthrough of the SkinLab Shopify store on desktop and mobile, plus a crawl of every sitemap and menu URL. Each finding names what we saw, the cause where we could trace it, and the fix we are asking for. Items badged Expected are placeholder content or work owned by another team: they are listed for completeness and are not counted in the severity tiles. Finding numbers are stable across revisions, so earlier correspondence still lines up. The team’s change requests are listed separately (R1–R19): they are requests, not defects, and most link to a clickable staging mock-up. Screenshots enlarge on click; links open the live store. Latest revision: 5 Oct 2026.
Store
skinlab-medical-spa-qswmpnay.myshopify.com
Theme
[Main]Skinlab-Medical-Spa x Verz Ver. 1.0 · Dawn 15.5 schema · published
Tested
11 Sep 2026, SGT · latest revision 5 Oct 2026
Viewports
1680 × 1003 and 1440 × 1000 desktop · 390 × 844 mobile · header and stat rows swept 1440→760px
Reviewed with the client and agreed as not Verz's to fix yet — placeholder content in a first preview, or work owned by another team. They keep their original finding numbers and stay on the list below for traceability, but they are excluded from the severity counts above.
F1 — Placeholder copy is live on every page First preview submission from Verz — unfinished copy is expected at this stage. Itemised in the placeholder & stub inventory below instead of counting as a launch blocker.
F3 — All three treatment pages show the same FAQ, written for one treatment The FAQs are placeholder content too. The shape of the defect still matters (one FAQ set shared by every treatment), so keep it on the list for when real copy lands.
F6 — Mega-menu subtitles are copy-pasted or shifted Content-specific — these are subtitles, and the links themselves all resolve correctly. No build fix needed.
F7 — Homepage has no meta description and 65 of 75 pages have none SEO is owned by a separate team and is scheduled post-launch. No action needed from Verz for now.
Placeholder & stub inventory
A separate pass over all 72 storefront URLs looking only for unfinished content, so the team can see the whole placeholder surface in one place and sign it off as expected. Each row says what the marker is, where it appears and how many URLs carry it. Everything here is expected at this stage — it is an inventory to work through, not a defect list. Two findings above are placeholder-driven and cross-referenced: F1 and F3.
What
Where
Detail
Lorem ipsum in the footer
71 of 72 URLs swept
The footer paragraph (“Nullam sed odio sapien…”) plus the placeholder address “123 Lorem Ipsum Dolor Street, Singapore 689578”, phone “+65 1234 5678” and skinlabmedicalspa@gmail.com. One footer block, so one fix for the whole site. (F1 reports the same footer against 81 URLs: that is the revision-1 crawl set, which adds /cart, the policy pages, a deliberate 404 and other non-sitemap extras on top of the 72 sitemap URLs this sweep covers. Same single defect, two different denominators.) The 72nd URL is /agents.md, a Shopify-generated plain-text document with no theme chrome, so the footer is on every themed page.
Five lorem review cards named Joe Doe / Joe Doe 1–4 on both/pages/customer-reviews and /pages/customer-stories, and four of the same cards on the homepage. Real stories exist on the incumbent site — see the customer-stories finding above.
The “Real Results” carousel is four slides of the same two files — before-after-1.jpg and before-after-2.jpg, each used 4× — all captioned “Pigmentation & Melasma” with lorem bodies. The same pair is the Before & After on every product page.
Every product page’s “related articles” rail shows a lorem excerpt. It traces to one source: the duplicate LDM article whose body is a lorem stub. Fixing that one article clears all 12 — every product page on the store.
/pages/our-story, /pages/press-media and /pages/corporate-social-responsibility contain their heading and the words “Coming Soon...” and nothing else — and all three are linked from the About Us menu. Also tracked as F8.
The homepage “Take the Quiz” button opens the quiz app’s own perfume demo questionnaire. The imagery is placeholder as expected; the wiring is still a defect — see F2.
The header search submit button’s accessible label renders as Translation missing: en.general.search.submit. Invisible on screen but read aloud by screen readers on every page; one missing locale key.
Ruled out by this sweep. Checked and not placeholders, so nobody chases them: the words “fragrance” and “perfume” on /products/aqua-renewal-sleeping-mask and /products/smooth-tone are genuine product copy (“free from preservatives, fragrances and colorants”, and Parfum/Fragrance in an INCI list) — unrelated to the quiz demo. No “TBC”/“TBD”, no example.com and no Liquid errors were found anywhere in the sweep, and all 72 URLs returned HTTP 200. (This sweep looked only for placeholder text — link integrity is covered by the revision-1 crawl and by F5.)
Findings
ShowOwner
F1
Placeholder copy is live on every page
Expected at this stageContent
Why this is expected: First preview submission from Verz — unfinished copy is expected at this stage. Itemised in the placeholder & stub inventory below instead of counting as a launch blocker.
Observed
The footer on all 81 crawled URLs shows a lorem paragraph (“Nullam sed odio sapien…”), the address “123 Lorem Ipsum Dolor Street, Singapore 689578”, the phone “+65 1234 5678” and a Gmail address. The homepage Before/After carousel is four identical “Pigmentation & Melasma” slides with lorem body text and the same two stock photos, which also serve as the Before & After on every product page. The reviews carousel shows “Joe Doe”, “Joe Doe 1/2/3 · 2 Months ago” with lorem text on the homepage, on /pages/customer-reviews and on /pages/customer-stories (the two pages are identical). Every product page has a ticker reading “Bundle & kit · Story telling · Shopping reels” and Soft Clean's “Complete your routine with” is four identical stock-video tiles. The About page ends with the author's note “Therapists accreditations, regular trainings, etc” rendered as body copy. The older LDM article's meta description is literally “Lorem ipsum dolor sit amet…”.
Cause
Demo section defaults and unfilled Theme Editor fields were left in place. Found by the reviewer, not the browser pass: the About note and the lorem meta description.
Fix
Fill or remove each block before launch: footer contact details and policy links, real before/after cases or hide the section, real reviews (Judge.me is installed) or hide the carousel, delete the ticker and video tiles, delete the About note, rewrite the article excerpt.
Home: Before/After slide with lorem bodyHome: “Joe Doe” reviews with lorem textFooter: lorem paragraph, fake address and phoneProduct page: “Bundle & kit · Story telling” tickerSoft Clean: four identical stock video tiles
F2
“Take the Quiz” opens the quiz app's perfume demo
HighAdmin
Observed
The homepage CTA “Not Sure Which Facial Is Right For You? Take the Quiz” opens a Recommenda quiz titled “skincare quiz” whose intro reads “answer a few quick questions and discover the fragrance that fits your vibe”, illustrated with a “QUIZ PERFUME” bottle. Clicking start did not advance in the session.
Fix
Build the real quiz in the Recommenda app (Apps → quiz 5555) or remove the CTA section until it exists.
Review
Client review: the perfume imagery is placeholder and expected at this stage. What remains a defect is the wiring — the button still opens the quiz app’s own demo questionnaire, so the quiz is unusable regardless of art. Kept as a finding for the wiring only.
All three treatment pages show the same FAQ, written for one treatment
Expected at this stageTheme dev
Why this is expected: The FAQs are placeholder content too. The shape of the defect still matters (one FAQ set shared by every treatment), so keep it on the list for when real copy lands.
Observed
Bojin Bliss Facial, Keratosis Pilaris Medical Peel and Medical Peel + High Frequency Half Back render identical FAQ answers, all about “The Medical Peel + High Frequency Back Treatment… half back (50 mins) or full back (60 mins)” and back acne. Bojin's own spec chip says 90 mins and Keratosis Pilaris says 30 mins, so two pages contradict themselves.
Cause
The FAQ section carries hard-coded defaults instead of reading a per-product metafield; the meta description is correct per product, so it is a section-level default, not product data.
Fix
Bind the FAQ block to a product metafield (list of Q&A) or make it a per-template block; write answers for the other two treatments.
Bojin Bliss Facial FAQ answers about the back treatment
F4
Treatments are sold as shippable goods and there is no booking path
LowAdmin
Observed
All 12 products, including the three facials, have requires_shipping = true. A cart containing only Bojin Bliss Facial reaches a checkout that asks for a delivery address and “Enter your shipping address to view available shipping methods”; there is no outlet or appointment step. On the product page, “book an appointment” goes to the generic Contact Us enquiry form and “schedule a consultation” links to “#”.
Cause
Treatment products were created like retail items; no booking mechanism (form, app or booking product with prefilled checkout) has been chosen yet.
Fix
Minimum ask: mark the treatment products as not requiring shipping (uncheck “This is a physical product” on the variant) so Shopify skips the delivery step and the shipping-address form. Leave the rest of the purchase flow as built.
Review
Client decision: selling treatments as shippable goods is expected — that is how the catalogue is modelled for now, and a booking path is out of scope for this build. Downgraded from High to Low, and the ask to Verz is narrowed to one thing: remove the delivery / shipping-address step from checkout so a customer buying a facial is not asked where to post it.
Checkout with only Bojin Bliss Facial: delivery address requiredPDP: “book an appointment” → Contact Us
F5
Dead links in the header, homepage and footer; three policy pages 404
HighTheme dev
Observed
Header “Customer Stories” (desktop and drawer) links to “#” although /pages/customer-stories exists. Homepage concern slider: four “learn more” links to “#”; the About block “read more” is “#”; every Signature Facials card links to “#”. Footer Payment, Shipping and Return / Exchange are “#”, and /policies/refund-policy, /policies/terms-of-service and /policies/shipping-policy return 404 (only Privacy exists).
Fix
Create the refund, shipping and terms policies in Settings → Policies and wire the footer to them; set the menu and section link fields; link the facial cards to their product pages.
Why this is expected: Content-specific — these are subtitles, and the links themselves all resolve correctly. No build fix needed.
Observed
29 menu items (58 including the mobile drawer) carry the subtitle “Breakouts, congestion, pores”: Cleanse, Tone, Moisturizer, Sunscreen, Mask, Neck Care, Scalp, Normal Skin… Where subtitles differ they are off by one row: Pigmentation → “Dark circles, eye bags”, Dark Circles → “Turkey neck, tech neck”, Dry/Dehydrated → “Breakouts…”, Ageing → “Dark spots, melasma”. “Slimming” reads “Laser, face & body” and links to /collections/hair-removal-1 although /collections/slimming exists. The mobile Treatments panel heads concern items under “SKIN TYPE”.
Fix
Re-enter each subtitle in the header section's menu blocks and point Slimming at its own collection.
Treatments panel: Slimming → “Laser, face & body”Mobile drawer: same subtitles
F7
Homepage has no meta description and 65 of 75 pages have none
Expected at this stageContent
Why this is expected: SEO is owned by a separate team and is scheduled post-launch. No action needed from Verz for now.
Observed
The homepage title is just “Skinlab Medical Spa”, it has no meta description, and its only H1 is Dawn's logo image. 65 of 75 rendered 200 pages have no meta description (every collection, most products, cart, blog index, contact, about). Of the 10 that do, three say “Coming Soon...” and four articles share an LDM table-of-contents or lorem text. /pages/blogs is an empty page duplicating the /blogs/blogs title.
Fix
Set the homepage title and description in Online Store → Preferences; add descriptions to products and collections (or a theme fallback from the first paragraph); use a text H1 on the homepage; delete /pages/blogs.
Three “Coming Soon...” pages are linked from the About Us menu
MediumContent
Observed
Our Story, Press & Media and Corporate Social Responsibility each render only the title and “Coming Soon...”, with “Coming Soon...” as their meta description.
Blog index repeats the featured article on every page; the LDM article exists twice
MediumTheme dev
Observed
The featured card repeats in the list below it and again on page 2, so “Chemical Peels vs Hydrating” and the LDM piece each appear two or three times across a four-article blog. Two live copies of the LDM article exist (Aug 05, 3.2k chars; Aug 10, 8.6k chars) with identical titles. The excerpts of “Chemical Peels vs Hydrating” and “Acne Scar Treatments” are the LDM table of contents their bodies are the LDM article’s too — see F16. Typo “oftn” in the LDM body.
Fix
Exclude the featured article from the list; delete the short LDM copy; write excerpts per article.
The awards strip lists “Her World Hair Treatment Award 2024” twice on a skin brand. Signature Facials chips label LDM Triple, IYASHI Exosome Nano Needling and Stellar M22 as “Sensitive Skin”. “Why choose us” item 03 “Manage Customer's Expectations Better” repeats item 02's body word for word. The concern slider shows “Explore sensitive skin treatments” twice out of four. The stats block reads “100K+ Satisfied Customers · 5.0 · 100k+ Reviews” while About says “over 100,000 patients”. 20 of 22 press-marquee logos have empty alt text.
Fix
Correct the award list, chips and copy in the homepage sections; state the real review count and source; add alt text to logos.
About page: Euphie described with TrichoLab's text, and a paragraph repeats
MediumContent
Observed
Under Our Sister Brands, “Euphie Skin Solutions (Malaysia)” carries TrichoLab's description (“A scalp and hair specialist that merges medical expertise with botanical science…”). The “Advanced Medi-Facial Technologies” block repeats the “Every treatment starts with clinical thinking” paragraph verbatim.
Catalogue structure: empty collections, indiscriminate filters, a recommended product that does not exist
MediumData
Observed
44 collections hold 12 products. /collections/acne, /acne-scars and /eyes are empty yet in the sitemap; /collections/frontpage (“Home page”) is indexed. The Treatments collection facets claim Neck 3, Body & Back 2, Hair Removal 2, Slimming 2, Dark Circles 2 across three facials. The Skincare page's “Recommended Products” lists “Age Shield DNA Repair SPF 43”, which has no product page (404). Only 4 of 12 products have a product type, and Bojin Bliss Facial, Soft Clean and Nano Gold are typed “cleanse”, which shows as a chip on the product image.
Fix
Prune or hide empty collections; review the metafield-driven filter values per product; add the sunscreen product or remove the recommendation; set product types.
Treatments collectionBojin Bliss Facial with “cleanse” chip
F13
Predictive search squeezes product titles into a narrow column
MediumTheme dev
Observed
Typing “peel” in the header search shows four products, but each title wraps one word per line in a column roughly 50px wide. The full results page works (5 results) but has no H1.
Fix
Fix the suggestion list's grid/min-width; add an H1 to the search template.
Medical Peel Half Back has a compare-at price of $168 below its $418 price (never rendered, but inverted). Milbon items are priced $45.78 and $58.86, which look GST-derived. Treatments say “NETT” while retail says “inclusive of GST”. The announcement bar reads “Free shipping for orders over $SGD90”.
Fix
Correct the compare-at price, round the Milbon prices, and standardise the price suffix and currency wording.
Reviews carousel throws “this.layoutDots is not a function”
LowTheme dev
Observed
On the homepage, Customer Reviews and Customer Stories pages the skinlab-reviews element calls layoutDots() in connectedCallback and on resize, but the method is never defined. The carousel still renders and the active dot updates; the resize handler never binds.
Fix
Define or remove the call.
Review
Revision-2 review: layoutDots is not a broken method, it is a missing one — 2 references, 0 definitions anywhere in the theme, so there is nothing to restore and someone has to write it. It is also the root cause of F20 (wrong active dot, dead dots 3 and 4), so this Low understates it: fix the two together.
Two of the three published articles show a different article’s body, and a fourth is a lorem stub
HighContent
Observed
The blog has four live article URLs. Three of them serve a byte-identical body (8,839 bytes, md5 c330ef6e6b) — the LDM article’s text — even though their titles and H1s are correct and different: /blogs/blogs/chemical-peels-vs-hydrating-facial-treatments-for-dry-skin and /blogs/blogs/the-acne-scar-treatments-that-work-for-deeper-skin-tones both open with “LDM and HIFU both use ultrasound energy…”. The fourth, /blogs/blogs/what-is-ldm-treatment-how-it-works-and-its-benefits-for-skin (no -1 suffix), is a 2,302-character lorem stub. It does not appear on page 1 of the blog index but is linked twice from page 2, and it is live and in sitemap.xml. Verified independently of the theme by hashing Shopify’s own article.content from /blogs/blogs.atom: three articles return md5 e82efad53a at 8,819 bytes and the stub returns 2bb38dad9e at 2,354 bytes — so the identical text is in the article records themselves, not introduced by the template.
Cause
Looks like the real article was pasted into the wrong records, and the duplicate LDM record was left behind as the original stub. It is a content-entry problem, not a template one — the template renders article.content correctly.
Fix
Put the right body in each article, delete or unpublish the lorem duplicate (and redirect its URL), and re-check the blog index. Worth auditing every article rather than just these four, because the same paste error would be invisible from the index — the titles all look right.
“Chemical Peels vs Hydrating Facial Treatments” — the body is the LDM articleThe duplicate LDM URL: a lorem stub, live and in the sitemap
F17
The article Table of Contents is typed into the body: no links, not collapsible, and it overwrites every meta description
MediumTheme dev
Observed
The “Table of Contents” on article pages is not a theme component. It is a plain nested <ul class="non-ul"> typed into the article rich-text body, with no anchors at all, and the body headings carry no id attributes — so there is nothing to link to even if the entries were links. It cannot be collapsed, and on an 18-entry list like the LDM article it pushes the article opening well below the fold.
It also has an SEO consequence that is easy to miss: the TOC is the first text in the body, and no article has an excerpt or a hand-set SEO description (/blogs/blogs.atom returns an empty summary for all four), so Shopify derives each article’s meta description and og:description from the TOC. Every description therefore reads as a table of contents rather than a summary — “Table of Contents LDM Therapy Explained Overview of the LDM Technique…”. All three published articles currently share the same description, but that is F16’s identical bodies, not this defect; fixing F16 alone would leave three distinct but still TOC-shaped descriptions.
Cause
The TOC was authored as content rather than built as a section, so it inherits none of the behaviour a TOC needs and sits inside the text Shopify summarises.
Fix
Build it as a theme block on the article template: slug each h2/h3 into an id, generate the list from those headings, wrap it in <details> (open on desktop, collapsed on mobile), and remove the hand-typed list from the body. That fixes the links, the collapse and the descriptions in one change. Note this differs from the approved Figma prototype — we have not compared the two designs here, only the behaviour.
The TOC as rendered: 18 plain list items (against 16 body headings), no links, no collapse control
F18
“Our Treatments” cards wrap the whole card in one link, so the accessible name is the entire card
MediumTheme dev
Observed
Each card in the homepage “Our Signature Facials” list is a single anchor wrapping everything: <a class="sl-st__card" href="#"> contains the image, the “Sensitive Skin” tag, the <h3> treatment name and the description. A screen reader announces one link whose name is the image alt plus the tag plus the heading plus the whole paragraph. The heading is also no longer a link target in its own right, so heading-and-link navigation both degrade.
Separately, every one of these cards still points at href="#" — see F5.
Cause
The clickable-card pattern was built by wrapping rather than by overlaying.
Fix
Put the anchor around the h3 text only, give the card position:relative, and stretch the link over the card with a pseudo-element: .sl-st__card-heading a::after { content:""; position:absolute; inset:0; }. The whole card stays clickable, the link name becomes just the treatment name, and the heading stays a heading. Reference: Automatic.css — clickable parent.
The five signature-facial cards; each is one anchor wrapping image, tag, heading and body
F19
Homepage stat dividers cut through the labels between 1210px and 1310px
MediumTheme dev
Observed
In “Skin Science Built By Doctors” the four stats sit in a CSS grid with a 1px border-left as the divider. Between 1210px and 1310px the four columns narrow to 99–111px while “Satisfied Customers” still needs more, so the label overflows its cell and crosses the neighbouring divider — up to +13.9px of overlap at 1210px. Measured every 10px from 1440 down to 1100 with fonts loaded, taking the overlap from text-node bounding boxes: clean at 1330px and above, a 0.1px touch at 1320px, overlapping from 1310px down to 1210px, and clean again at 1200px where the columns widen; the grid drops to two columns at 1190px.
This is the common laptop and tablet-landscape range, so it is not an edge case.
Cause
The four-column track carries on for about 120px below the width its longest label needs: the labels start colliding at 1310px but the 4→2 column switch does not arrive until 1190px.
Fix
Either move the 4→2 column breakpoint up to about 1330px, or let the stat labels wrap/shrink inside the narrow track (min-width:0 plus a smaller label size), or swap the border-left for a grid gap with its own divider element so text can never cross it. Validate across 1190–1330px, not just 1180 and 1440: at 1200px the four columns widen again and the overlap clears on its own, so a spot-check either side of the band will miss it.
At 1280px: the divider cuts straight through “Satisfied Customers”The same section in context at 1280px
F20
Reviews dot navigation shows the wrong slide and dots 3 and 4 do nothing
MediumTheme dev
Observed
At 1440px the reviews carousel renders four dots for four cards, but about two cards are visible at a time, so the track only has two “pages” of travel. Scrolled fully to the end — showing the last two reviews — the second dot is the one marked aria-current="true". Clicking dot 2, dot 3 and dot 4 at that same 1440px width all resolve to the same scrollLeft of 1383 (the maximum), so dots 3 and 4 are visibly dead. The exact numbers move with the viewport — the symptom is that the dot count is fixed at one-per-card while the travel is not, so reproduce it at a width where fewer than four cards are visible, not at whatever width the browser happens to open at.
Same root cause as F15: the console throws TypeError: this.layoutDots is not a function on every homepage load, so the routine that should recompute the dot count for the current viewport never runs. The neighbouring “Our Expertise” carousel, which does not throw, correctly shows two dots at 1440px and four at 390px — that contrast is the clearest evidence the dot count is meant to be viewport-aware. At 390px the reviews dots are correct (four pages, four dots, last dot active at the end), which is why this reads as desktop-only.
Cause
layoutDots is called twice (from connectedCallback and the resize handler) and defined nowhere in the theme — 2 references, 0 definitions. So dot count and active state stay at their server-rendered one-dot-per-card values, and sync() clamps the active dot to min(pages(), dots.length) = 2, which is exactly why dot 2 lights up at the end.
Fix
Define the missing method — the class already has a pages() method and a comment describing the intended behaviour (one dot per scrollable page, surplus dots hidden rather than removed, recount on resize), so the implementation is specified. Note that deleting the call would silence F15’s console error while leaving the dots exactly as dead, so the two must be fixed together rather than either alone. Fixing F15 should fix this finding; please verify both together.
Track scrolled fully to the end (Joe Doe 2 and 3, the last cards) — yet dot 2 is active and dots 3 and 4 are grey
F21
The three real customer stories were not migrated; both review pages are still lorem
MediumContent
Observed
/pages/customer-reviews is the same placeholder reviews section as the homepage — five “Joe Doe” cards with lorem bodies. There is also a second, near-identical page, /pages/customer-stories, with the same five placeholder entries; both are live (the placeholder state of both pages is also reported in F1; what is new here is the un-migrated incumbent content and the missing redirects).
The incumbent site has exactly three real, named, long-form customer stories, each on its own URL: siu-weis-first-choice-for-acne-scars, lesser-makeup-enhanced-confidence-days-for-melodie and a-severe-hormonal-acne-miracle-for-dini (confirmed against testimonials-sitemap.xml). Nothing on the new store corresponds to them, so at cutover those three URLs lose their content and their inbound links unless they are rebuilt and redirected.
Fix
Decide which page is canonical and delete or redirect the other. Migrate the three stories as individual pages or articles, then add 301s from the three incumbent /testimonials/… URLs to their new homes. If long-form stories are not coming back, the redirects should at least land on the reviews page rather than 404.
The new customer reviews page: five Joe Doe entries with lorem bodies
F22
No carousel responds to a mouse drag on desktop
LowTheme dev
Observed
None of the four homepage carousels — hero, “Our Expertise”, “Real Results” before/after, and reviews — can be dragged along the track with a mouse. Confirmed from the shipped section scripts rather than by feel: no pointerdown, mousedown, mousemove or drag handler exists anywhere in the theme, and cursor is auto on every track and section, so there is no grab affordance either. (The only mouse handlers on these sections are mouseenter/mouseleave autoplay pause on the hero and the concern slider.)
Worth being precise about what does work, because it narrows the fix: touch swipe works everywhere. The three lower carousels are native overflow-x:auto tracks with scroll-snap-type: x mandatory, so a phone swipes them for free; the hero binds touchstart/touchend on its custom element and steps exactly one slide per swipe past a 45px threshold. So the gap is desktop pointer input only — the hero still has its dots and its prev/next arrows, and each before/after comparison already has a mouse-draggable wipe slider (input[type=range]), so this is a convenience gap rather than a loss of control.
Cause
The carousels rely on native scrolling plus dot buttons; pointer-drag was never implemented.
Fix
Add a small pointer-drag handler to the shared carousel script (pointerdown/pointermove/pointerup mapped to scrollLeft, with setPointerCapture and a click-suppression threshold so card links still work), and set cursor:grab/grabbing so it looks draggable. The hero needs the same handler mapped to its transform. On the before/after track, exclude the existing wipe slider from the drag handler (ignore pointerdown originating inside [data-sl-range]), or dragging the reveal handle will scroll the carousel instead. Low severity because dots, arrows and trackpad scrolling all work today.
Hero — dots only on desktop, no drag“Our Expertise” treatment carousel“Real Results” before/after carousel
F23
The before/after carousel jumps instantly while every other carousel animates
LowTheme dev
Observed
Clicking a dot on the before/after carousel moves the track in a single frame. Sampling scrollLeft every animation frame across the transition: before/after produced 2 distinct values (0 then 1158 — an instant jump), while “Our Expertise” produced 22, reviews 37, and the hero animated over 36 frames on a 0.62s cubic-bezier. So it is the only one of the four that does not animate, which is exactly the inconsistency reported.
The cause is a leaked event argument, not a deliberate instant scroll. The dots are bound as dot.addEventListener('click', this.goTo.bind(this, i)) while the method is declared goTo(i, instant) — so a click calls goTo(i, MouseEvent), the truthy event satisfies instant, and the handler takes the branch that suspends the track’s scroll-behavior: smooth and writes scrollLeft directly. That branch is only meant for the initial goTo(start, true) on first paint.
Fix
Bind the dots to a wrapper that passes only the index (dot.addEventListener('click', function () { this.goTo(i); }.bind(this))), so the smooth scrollTo path the other carousels use is taken. The same bind(this, i) pattern is used elsewhere in the theme — it is only a bug where the method takes a second parameter, so audit for that rather than changing every binding.
Category pills are rounded in some sections and square in others
LowTheme dev
Observed
Two pill shapes are in use for the same taxonomy value. On /products/keratosis-pilaris-medical-peel-treatment both appear on one page: the product’s own pill .sl-pd__tag renders border-radius: 0px, while the related-products pill .sl-pr__tag renders border-radius: 20px — both reading “cleanse”. The homepage “Our Signature Facials” pill .sl-st__tag is also 20px.
Fix
Standardise on one shape and set the radius from a shared token rather than per section, so this cannot drift again. Note the product page pill’s 0px is simply an undeclared border-radius rather than a deliberate square treatment, so confirm with design which shape is intended before picking it. Affects .sl-pr__tag (related products, on treatment and product pages) and .sl-st__tag (homepage signature facials).
Product page, own pill: square (0px)Same page, related products: rounded (20px)
F25
The full header nav needs 1420px, so common laptops get the burger menu
LowTheme dev
Observed
The full nav (Home, About Us, Treatments, Skincare, Customer Reviews, Blogs, Contact Us) is present at 1401px and above. The header CSS carries @media screen and (max-width: 1400px), so at 1400px it collapses to the burger and stays collapsed all the way down. That puts very ordinary desktop widths — a 1366px laptop, or any part-width window on a larger screen — on the mobile menu.
The collapse is a layout switch, not an overflow fallback: .sl-h__inner is a CSS grid that swaps from brand + nav + tools at 1420px to burger + brand + tools below it, and the nav element is removed from the layout rather than merely hidden. So no amount of freed space brings it back on its own — the breakpoint itself has to move.
Measured width budget at 1440px: .sl-h__inner is grid-template-columns: 188px 817px 267px — brand 188px + nav 817px + tools 267px = 1272px of content in 1440px, leaving 40px of column gap and 128px of padding. The tools block is the search field (209px) plus a 14px gap plus the cart icon (44px) — and the collapsed layout already proves a 44px search icon works, because that is exactly what it uses (its tools block measures 88px). Swapping the desktop search for that icon takes content to about 1,107px, so adding back the existing 168px of gap and padding projects a fit at roughly 1275px. That figure is arithmetic, not an observation — the nav is display:none below the breakpoint, so it cannot be measured at that width until the breakpoint moves.
Cause
The nav breakpoint was set to the width the header needs with a 209px search field, so the desktop layout gives up as soon as that field no longer fits — even though the same header has a 44px search icon available.
Fix
Two changes, in this order: make the header search an icon that opens the existing predictive-search overlay at all widths (the mobile layout already has this control — .sl-h__mobile-search), then move the nav breakpoint from 1400px down to about 1280px. 1275px is the arithmetic fitting point, so 1280px leaves a little slack for font loading; please re-measure after the change rather than trusting the arithmetic. If more room is wanted later, the “Outlet Locations” utility link is the next cheapest thing to move into the drawer.
1420px — full nav present (the nav survives to 1401px), search bar 209px wide1366px — nav collapsed to the burger, search already an icon
Change requests not UAT findings
Changes the SkinLab team asked for after reviewing the preview. These are requests, not UAT findings: none of them is a defect, they carry no severity, and they are not counted in the tiles above. Each one names the live section it touches (the section type in the theme) so Verz can find it. Status: Requested means the team has asked for the change, Agreed and Confirmed mean the team has signed off the detail.
R1
Body font → Google Sans Flex
RequestRequested
Where
Site-wide · Theme settings → Typography (body)
Ask
Change the body font family to Google Sans Flex. Live today, body text is Figtree and headings are EB Garamond. The request covers only the body font, so headings stay as they are.
Note
If Google Sans Flex is not in Shopify’s font picker, Verz will need to self-host it (@font-face in the theme assets) rather than set it from Theme settings.
Site-wide · Buttons, product tabs and CTAs on every template
Ask
Labels render in lower case, for example “book an appointment” and the product tabs (“product description · key benefits · procedure · how it works · reviews”). Set them in sentence case: capitalise the first letter only, for example “Book an appointment”, “Product description”, “How it works”. Apply this across the site, not only on treatment pages. Other lower-case labels live today: “view all testimonials”, “learn more”, “book now”, “add to cart”, “contact us”, “book a first trial”, “schedule a consultation”.
Note
Several labels are lower case in two different ways: the tabs are lowered by CSS (text-transform: lowercase), while buttons such as “book an appointment” are typed in lower case in the section settings. Both need changing.
Mock-up, before: “book an appointment” and the product tabs in lower case (Bojin Bliss Facial)Mock-up, after: the same labels in sentence case
Header nav · skinlab_header, Treatments mega-menu and mobile drawer
Ask
Remove the Skin Type column from the Treatments menu and keep Skin Concerns. Skin type stays available as a filter on the treatments listing page (/collections/treatments).
Mock-up, before: Treatments menu with the Skin Type and Skin Concerns tabsMock-up, after: Skin Type removed, Skin Concerns only
Header nav · skinlab_header, Skincare mega-menu and mobile drawer
Ask
Collapse the Skincare sub-nav into one flat list that shows only the Shop By Category items (Cleanse, Tone, Ampoule/Serum, Moisturizer, Treat, Eye Care, Neck Care, Scalp Care, Sunscreen, Mask). The Shop by Concerns column goes.
Note
Every category item still carries the placeholder subtitle “Breakouts, congestion, pores” (F6, expected). A flat list may not need subtitles at all.
Mock-up, before: Skincare menu with the Shop By Category and Shop by Concerns tabsMock-up, after: one flat list of the categories, no tabs
Remove the small bulleted eyebrow labels across the homepage (“Best Facial Singapore” on the hero, “Our Expertise” above the awards and above “Care That Goes Beyond The Standard Facial”). The one exception: keep the “Best Facial Singapore” text in the page but visually hidden, not deleted.
Note
Hide it with a visually-hidden (screen-reader-only) class, not display:none. display:none takes the text out of the rendered content altogether.
Mock-up, before: hero with the “Best Facial Singapore” eyebrowMock-up, after: eyebrow visually hidden (still in the page for screen readers and search)Mock-up, before: the two “Our Expertise” eyebrows above the awards and the concern cardsMock-up, after: both eyebrows removed
Product cards: align prices, make them smaller, drop “inclusive of GST”
RequestRequested
Where
Product cards · skinlab_products card grid (homepage “Science-Backed Skincare” and other product grids)
Ask
Prices and add-to-cart buttons sit at different heights because titles wrap to one or two lines. Align them to a common line, and reduce the price’s font size. Also remove the “inclusive of GST” text after the price on every product card.
Remove the scrolling text ticker that reads “Bundle & kit · Story telling · Shopping reels” (skinlab_ticker), everywhere it appears: treatment pages and product pages.
Note
The press-logo marquee (“Excellence Recognized · As Featured In Various Media”, skinlab_logo_marquee) is not part of this request and stays. The ticker text is also placeholder copy (F1).
Remove the “Find the best” comparison section from the listing pages
RequestConfirmed
Where
Listing pages · skinlab_compare (“Recommended Products · Find the best · Which products suit which skin types”)
Ask
Remove the Recommended Products · Find the best comparison section from both listing pages: /collections/treatments and /collections/skincare.
Before: the “Recommended Products · Find the best” comparison table on the Skincare listingMock-up, after: section removed, so the USP cards run straight into “Start Your Aesthetic Journey”
“Complete the Regime” → combination-treatment tabs
RequestRequested
Where
Treatment pages · skinlab_products (“Related Products · Complete the Regime”)
Ask
Replace the product cards in “Complete the Regime” with tabbed combination treatments, following the staging page linked below. The heading stays, the “Related Products” eyebrow goes, and the lede “Complementary treatments for enhanced results” sits under the heading. Each tab is a skin concern (Skin concern A / B / C) and opens a panel with the combination’s name (Medical Peel + I2PL, Medical Peel + LDM, Medical Peel + Stellar M22) and one or two paragraphs of selling copy. Each panel shows an image to the left of the copy, side by side in a 2fr 3fr grid (image : copy). The image keeps a 4:3 aspect ratio and the copy is vertically centred on it (align-items: center). On mobile the image stacks above the copy. The combination tab labels are smaller than the product tabs: 12px with 1px letter-spacing. There are no product cards in the panels. Use the same tab component as the product tabs (skinlab_product_tabs), so the styling matches. There will no longer be a separate page for each combination.
Note
Tab labels and copy on the staging page are placeholders: Content supplies the real skin concerns and copy. Removing the existing combination product pages (for example /products/medical-peel-high-frequency-half-back-treatment) will need redirects. SkinLab will confirm the redirect plan. The panel images on the staging pages are placeholders from the page’s own photos; Content supplies one image per combination.
Mock-up: combination tab with a 4:3 image beside the copy (2fr 3fr, copy centred), Bojin Bliss Facial
Move the related-articles section down so it sits directly above the closing “Start Your Aesthetic Journey” CTA (skinlab_image_cta). Today it sits above “Complete the Regime”.
Note
The cards in this section show the shared article body and the typed-in table of contents (F16, F17), and one of them is the lorem LDM stub. Moving the section does not fix those.
Add a tag to the top-left corner of the product image on three cards: Best seller on Milbon Weightless Replenishing Mist, Beauty spotlight on RetinEye Dark Circle Corrector, and New on Blemish Control Essence. Each tag is a small rectangular pill: the tag’s icon followed by its label in capitals. Set the pill’s line-height: 0 so the capitals sit optically centred beside the icon (at line-height: 1 they ride about 1px high).
Note
SkinLab supplies the three tag icons. Please drive the tag from product data (a product metafield or tag) rather than hard-coding it per card, so the team can move a tag between products without a theme change. R15 uses the same tag on the product page.
Mock-up: Best seller, Beauty spotlight and New tags in the top-left corner of the product images
“Best seller” tag at the top of the product details
RequestRequested
Where
Product pages · skinlab_product (main product section), first in the info column, above the title
Ask
On a best-selling product’s page, show the Best seller tag at the top of the product info, above the title: the crown icon and BEST SELLER in a rectangular pill, the same tag as on the listing cards (R14). Pill: font-size: 12px; padding: 6px 10px; line-height: 0. Position it with order: -1 on its block (the info column becomes a flex column), so it can stay in its current place in the section’s block order.
Note
Driven by the same product data as R14, so the listing card and the product page always agree.
Mock-up: the Best seller tag at the top of the product info, above the title (Milbon Weightless Replenishing Mist)
Place the Beauty Treatment Awards 2025 badge in the top-left corner of the Bojin Bliss Facial image on the treatments listing.
Note
SkinLab supplies the badge artwork. As with R14, please drive it from the treatment’s data (for example an image metafield) so other award-winning treatments can carry a badge later.
Mock-up: the Beauty Treatment Awards 2025 badge on the Bojin Bliss Facial card
Treatment pages · skinlab_product (treatment instance), directly above the “Book an appointment” button
Ask
Add a row of three award badges directly above the “Book an appointment” button on the treatment page: Beauty Treatment Awards 2025, Best Seller and Beauty Spotlight.
Note
Shown on Bojin Bliss Facial in the mock-up. Awards differ by treatment, so the row should come from the treatment’s data (a list of award images), and hide itself when a treatment has none.
Mock-up: three award badges above “Book an appointment” on Bojin Bliss Facial
Remove the text after the price on the details pages: “inclusive of GST” on product pages and “NETT” on treatment pages. Only the amount remains, matching the product cards (R8).
Mock-up: product price without “inclusive of GST” (Milbon Weightless Replenishing Mist)Mock-up: treatment price without “NETT” (Bojin Bliss Facial)
Replace the sister-brand logos with the current 2026 set, and SL Aesthetic Clinic for SkinLab in the footer
RequestRequested
Where
Site-wide (footer) & About page · skinlab_footer “Our Sister Brands” logo blocks (every page) and skinlab_brand_cards badges on /pages/about-us
Ask
Replace every sister-brand logo with the current 2026 artwork. Use the same files that run on TrichoLab’s Shopify site. In the footer, the Euphie logo is broken: a faint, blurred beige image 80 × 41px, stretched to 50px tall. TrichoLab is the old wordmark without the “rooted in scalp science” tagline. All five footer logos are rasters smaller than their display size (41–49px tall, shown at 50px), so they blur on high-density screens. Use the SVGs from the download link below for the five footer logos and the five About-page badges. Render them as inline <svg>, not <img>. Each file paints with fill="currentColor", so the section sets the colour. Use a muted grey, not black: the mock-up uses #606060, the footer’s own body-text colour, for every mark. Inline SVG also stays sharp at any zoom, where the uploaded images looked soft. Give each <svg>role="img" and the brand name as aria-label, because the wordmarks are outlines with no text of their own. In the footer, replace SkinLab with SL Aesthetic Clinic. This site is SkinLab’s own, so it shouldn’t list itself as a sister brand. The footer then shows the same five brands as the About page.
Note
The download link below is a zip of all five logos from the 2026 set, cropped to the artwork, as SVG (the masters, cleaned for inlining: no <style>, ids or classes, which would clash once five logos share one page) and as 200px-tall transparent PNG in #606060 for any place that needs a raster. The About-page badges had the grey circle baked into each PNG; these files are transparent, so the badge’s own background colour shows behind them. In the mock-up, each logo is sized so they carry equal visual weight. A logo’s height follows its area rather than one shared height, so the near-square Euphie and SL Aesthetic Clinic marks don’t look small next to the long wordmarks. The footer logos have no alt text today.
Live footer today: Euphie broken, TrichoLab without its taglineMock-up: footer with the 2026 logos, SL Aesthetic Clinic in place of SkinLabMock-up: About Us “Our Sister Brands” with the 2026 logos
Not defects. Things a customer would notice or a team member might choose to change.
Header search submit label is “Translation missing: en.general.search.submit” Visually hidden but read by screen readers, on every page. Add the key to locales/en.default.json. open ↗
Contact form relies on browser-native validation Submitting empty shows no inline messages; required fields and a phone pattern are set. Consider inline errors. open ↗
Outlet details “68 Orchard Rd, #04-56, Plaza, Singapore 238839” truncates “Plaza Singapura”; tab labels are lowercase (“imall”). The Causeway Point Google card shows 4.9 ★ (615), which is good social proof worth surfacing. open ↗
Cart drawer is unstyled stock Dawn Serif titles and default layout next to a custom header. Style it or use the cart page. open ↗
“Pickup available at Head Office” on retail products Customers are unlikely to collect skincare from a head office; hide pickup or point it at outlets. open ↗
Contact email is a Gmail address skinlabmedicalspa@gmail.com in the footer; use the group domain.
Blog and Contact share the same hero photo The hands-and-phone image heads both pages. open ↗
Blog tag taxonomy “dry” and “oily” sit beside “acne prone skin” and “dehydrated skin”; standardise. open ↗
Mega-menu crossfade Twice within a second of hovering, both the Treatments and Skincare panels were painted at once with the hero visible through them; correct after two seconds and not reproduced on a third try. Watch for it on slower machines. open ↗
The bind(this, i) pattern is a bug class, not one instanceF23 is one occurrence of handler.bind(this, i) leaking the event into a second parameter. skinlab-reviews.goTo(i) and skinlab-concern-slider.goTo(i) are safe only because they take one argument — any future second parameter silently receives the click event. Worth a pass over the bound handlers rather than just the one fix. open ↗
The before/after wipe handle works but is not discoverable Every comparison already ships a keyboard-accessible, mouse-draggable input[type=range] with a per-comparison aria-label. The original report asked for a draggable wipe, which suggests nobody noticed the handle — a discoverability problem, not a missing feature. Give it a visible grabber and a hint on first view. open ↗
<span class="mono">.sl-h__clear</span> is dead markup behind an <span class="mono">!important</span> The header ships .sl-h__clear { display: none !important }, so the search field’s clear button can never appear. Probably deliberate, but it sits inside the header block F25 asks to be rebuilt and will confuse whoever does it. open ↗
Nobody has reviewed <span class="mono">/agents.md</span> It is in the sitemap, returns 200, and is a Shopify-generated document describing the store to shopping agents. Low stakes, but it is the one URL of the 72 that no human will read, and its contents have never been checked for this brand. open ↗
Verified working
The storefront password wall works; all 72 sitemap URLs and every header link return 200 with one H1 (search, cart and thank-you excepted, as in Dawn). No links to the old skinlab.com.sg domain, no broken images, no Liquid errors.
Add to cart opens the drawer with correct line, quantity controls and total; the cart page and checkout load (Shopify Bogus test gateway, so payment was not attempted). Cart was cleared afterwards.
Header search: predictive results appear after typing and the results page returns products and an article with facets. Collection sort and filters render with counts; the sold-out badge sits on the right product (Nano Gold).
Contact form marks all four fields required with a phone pattern; ten outlets each show a Google Maps embed, address and tel: link.
Mobile at 390px: no horizontal page overflow on nine templates, drawer opens with working accordions, product and collection pages lay out cleanly, body text at or above 12px except the hero dots.
About, blog article and treatment pages render complete content with breadcrumbs, related articles and related products.
Method
Pass one drove Chrome through the storefront at 1680px: homepage sections, both mega-menus, the quiz, a treatment page (tabs, FAQ, Before & After, ticker), a retail product page, add-to-cart → drawer → cart → checkout (twice, once with the facial alone via puppeteer), the contact form (empty submit) and outlets, footer and header search. A puppeteer crawl from the same session checked 81 URLs for status, title, H1, meta description, placeholders, hash links, JS errors, failed requests and alt text, and a 390px pass with an iPhone user agent screenshotted nine templates and measured overflow and font sizes.
Pass two was a separate reviewer with no browser, working from the crawl JSON, products.json, the screenshots and re-runnable fetch scripts. It confirmed 13 items, downgraded three, merged one, rejected one sub-claim, corrected the H1 and meta-description counts, and added the About-page author note, the lorem meta description and the featured-card repeat. Where the two passes disagreed the Review line says so.