DRTYLABS
WorkServicesBlogAboutContact
contact@drtylabs.ca
B2B/B2C E-Commerce · Brampton, Ontario

Safer Lighting

2025saferlighting.ca ↗E-CommerceB2BShopify 2.0

Sister company to Safer Electric · ESA Licence #7008996 · Real GTA warehouse · Trade contractor accounts

// THE BRIEF

Safer Lighting wholesales LED commercial fixtures, electrical boxes, PVC conduit, and trade hardware. The store ships from a real GTA warehouse. The brief: build a B2C-friendly storefront that doesn't blow the trust of professional contractors who need wholesale pricing, quantity breaks, and account-tagged discounts — without forcing them through a 72-hour login wall like the established players in the category do.

This isn't a Dawn install with a custom logo. It's a B2B catalog with two-tier pricing, manual contractor approval, classic-customer-accounts kept on the canonical domain (no shopify.com redirect), a full design-system reskin, and a Python pipeline going QuickBooks → CSV → Shopify import.

// KEY METRICS
66
Production SKUs
11
Categories
~68
Custom Sections
~50
Custom Snippets
50+
Search Synonym Groups
Two-Tier
B2B Pricing Model
// HOW WE BUILT IT

Custom Shopify 2.0 Theme on a Dawn Fork

We started from Dawn for the accessibility floor and the Shopify 2.0 sectioning, then rebuilt every surface in our own design system. ~68 sections, ~50 snippets, all custom or Dawn-rewritten. JSON section templates so every page is editable from the admin without a code touch. Vanilla JS only — zero jQuery, zero unnecessary frameworks. CSP-friendly.

Two-Tier B2B Pricing — The Hard Part

Retail customers see cost × 2.24. Approved contractors see cost × 2.16 (~3% lower at the same SKU, applied automatically when logged in and tagged). Display compare-at: sale ÷ 0.775, so every product anchors at ~22.5% off. Quantity breaks: −8% at 5 units, −15% at 25, −22% at 100. The custom snippets/price.liquid reads a pricing.b2b_price metafield and swaps the active price + strikethrough automatically when the logged-in customer carries a trade tag.

Manual Contractor Approval Workflow

Application form on /pages/contractor-application collects company name, business email, and ESA licence #. Submissions email the admin, who verifies the licence number against Ontario's public ESA contractor directory and then tags the customer approved-contractor from Shopify admin. No self-grant possible — contractors can't sign up and claim trade pricing themselves.

The Transparency Moat

Arani — the dominant competitor in this category — hides every price behind a 24-72 hour B2B login wall. Public Shopify pricing wins the contractor with a bid due in 4 hours. That's a structural moat against the established players, not just a feature. The site still protects trade pricing for approved accounts; it just doesn't gatekeep the retail catalog.

Custom Semantic Search

<safer-search> is a custom element in assets/safer-search.js. Predictive search with 50+ synonym groups mapping shopper terms to technical product types. A homeowner typing 'pot light' finds the recessed downlight catalog page. A contractor typing 'high bay' finds the UFO highbay. 'Wall pack' resolves to outdoor wallpack fixtures. Mobile uses a persistent horizontal search bar pinned under the header — not Dawn's modal-style search.

Python + Node Data Pipeline

scripts/build-products.mjs generates the canonical catalog. import-products.cjs pushes to Shopify's product import API. create-collections, create-pages, create-redirects, create-metafield-definitions are all idempotent admin scripts. Pricing CSVs flow QuickBooks export → cost-keyed pricing strategy CSV → Shopify-format import CSV. One Python script. Deterministic. Re-runnable any time prices change.

// MOMENTS THAT PROVE THE DEPTH

The receipts. Three concrete bug-fix-and-redesign moments. Anyone can stand up a Shopify theme. Diagnosing layered failures and fixing them at root cause is the work.

Pricing display bug — two stacked failures

The storefront was rendering compare-at-price with the same value as sale price, so every product looked struck through with the same number. Two stacked bugs: a CSS override forced strikethrough on every .price-item--regular regardless of sale state, AND the import data had blank Compare-at on every row. Diagnosed via live HTML inspection. Fixed at root cause — CSS gated to .price--on-sale, data regenerated from the pricing CSV with proper compare-at math.

Collection grid orphan card

10 products in a 4-column grid produced an orphan row of 2 — and Dawn's flex-based grid stretched the orphans to 100% via flex-grow: 1. Fixed by forcing CSS Grid (display: grid) at every breakpoint with explicit grid-template-columns: repeat(N, 1fr) so orphans always sit at exactly 1/N of the row.

Mobile drawer overlap with :has() and a JS fallback

The bottom phone-CTA, signup row, and Quick Order FAB all bled through the Shop submenu when it slid in. Root cause: Dawn's stock CSS hides the outer .menu-drawer__menu via .submenu-open with visibility:hidden, but the CTAs are siblings of the menu, not children. Fix: :has(.submenu-open) selectors on the wrapper containers, plus a JS-class-based fallback (body.overflow-hidden-tablet) for browsers that don't yet support :has().

// Same DNA as Safer Electric and Safer Inventory

The Safer Brand Design System lives at assets/brand-typography.css and assets/silicon-valley.css. One system, two production properties, three apps in the inventory monorepo — all consuming the same tokens.

Navy #0c1e33. Gold #d6b059 used sparingly — never as fill. Cream #f0ede6 over navy, never pure white. Cormorant Garamond for headlines (italic for emphasis). Plus Jakarta Sans for body and UI. JetBrains Mono for SKUs, prices, and licence numbers. A 3D ThreeJS hero canvas reads document.documentElement.dataset.theme and swaps particle colors live when the user toggles dark/light.

// TECH STACK
Shopify 2.0LiquidJSON Section TemplatesDawn (forked)Vanilla JavaScript (ES2022)Custom Web ComponentsCSS GridThreeJS (hero canvas)Python (pricing pipeline)Node.js (admin scripts)Shopify Admin APIQuickBooks Export PipelineGitHub → Shopify auto-deploy

// The right tool for the job

Liquid + vanilla JS, by design. We could have shipped a headless React storefront. We chose not to — Shopify's sectioning model gives the merchant control without code, the SSR is bulletproof, and the build deploys via Shopify's GitHub integration in ~30 seconds. Right tool, not newest tool.