Design System

A design system with the building blocks and reusable components for your Shopify theme.

Base Reset Styles

Global stylesheet configurations and normalisation rules applied to raw element selectors.

Our storefront normalises elements (margins, padding, box-sizing) and establishes core defaults:
  • box-sizing: border-box set globally on all elements.
  • Antialiased text-rendering enabled on the body container.
  • Default heading margins normalised using fluid modular spacing tokens.

Colour Palette Tokens

Active branding colour tokens populated straight from design variables.

Background
rgb(var(--color-bg))
#FFFFFF · Page background · surfaces
Surface alt
rgb(var(--color-surface-alt))
#EEEEEE · Alt panels · sold-out pill
Surface warm
rgb(var(--color-surface-warm))
#F4F1EA · Warm panels
Ink
rgb(var(--color-ink))
#363636 · Text · scrim base
Muted
rgb(var(--color-muted))
#786453 · Secondary text
Border
rgb(var(--color-border))
#A8AAAC · Strokes (alias sg-grey)
Accent
rgb(var(--color-accent))
#374426 · Brand forest (alias sg-forest)
Accent deep
rgb(var(--color-accent-deep))
#2A3420 · Hover / active accent
Accent foreground
rgb(var(--color-accent-foreground))
#FFFFFF · Text on accent
Sale
rgb(var(--color-sale))
#DE0F3F · Sale price · badges (alias sg-berry)
Ink foreground
rgb(var(--color-ink-foreground))
#F2F1EE · Text on dark
Black
rgb(var(--color-black))
#1A1A1A · True black
Green
rgb(var(--color-green))
#94A73B · Bright accent
Taupe
rgb(var(--color-taupe))
#826E60 · Taupe (alias sg-gold)
Taupe deep
rgb(var(--color-taupe-deep))
#5C4E43 · Deep taupe
Magenta
rgb(var(--color-magenta))
#FF6CA7 · Pink accent (alias sg-pink)
Blush
rgb(var(--color-blush))
#FAD6E4 · Blush tint
Cream
rgb(var(--color-cream))
#F2F1EE · Alpha-ramp base
Hairline
var(--color-hairline)
Ink @ 10% -- rules
Scrim
var(--color-scrim)
Ink @ 40% -- every overlay
Cream 80
var(--color-cream-80)
Overlay ramp
Cream 60
var(--color-cream-60)
Overlay ramp
Cream 40
var(--color-cream-40)
Overlay ramp · L2 nav children
White 80
var(--color-white-80)
Overlay ramp
White 70
var(--color-white-70)
Overlay ramp
White 15
var(--color-white-15)
On-dark ramp
White 10
var(--color-white-10)
On-dark ramp

Typography Scale

Consistent modular typography scales and size specifications.

Token Specs Preview
--text-h1 Size: 38px → 60px @750
Weight: 700
Line-height: 1
Tracking: -0.5px
Made for your hair
--text-h2 Size: 30px → 48px @750
Weight: 500
Line-height: 1.05
Tracking: -0.5px
Salon-grade care
--text-h3 Size: 26px → 40px @750
Weight: 500
Line-height: 1.1
Tracking: -0.5px
Our exclusive brands
--text-h4 Size: 18px → 24px @750
Weight: 500
Line-height: 1.2
Tracking: -0.5px
The full salon experience
--text-body-lg Size: 16px → 17px @750
Weight: 400
Line-height: 1.5
Shop 70+ professional brands online, or book your next visit at one of our salons.
--text-body Size: 16px
Weight: 400
Line-height: 1.5
Shampoo, conditioner, treatments and masks from the brands our stylists use in the chair.
--text-body-sm Size: 13px
Weight: 400
Line-height: 1.5
Free returns within 30 days. Secure checkout.
--text-label Size: 12px
Weight: 500
Line-height: 1
Tracking: +1.5px · uppercase
New arrivals
--card-name-size Size: 14px → 16px @750
Line-height: 1.15 → 1.3
Clamp height: 58px → 64px
MUK Fat Volumising Powder 10g

Spacing Scale

Standard layout padding and component margin specifications.

--space-1 4px
--space-2 8px
--space-3 12px
--space-4 16px
--space-5 24px
--space-6 32px
--space-7 48px
--space-8 64px
--space-9 96px

Logos

Primary and secondary brand logo vector assets registered inside assets/images.json.

Synergy Hair -- primary logo logo-primary.png -- default, light backgrounds
Synergy Hair -- monochrome logo logo-mono.png -- mono contexts, print
Synergy Hair -- reversed logo logo-reversed.png -- dark / forest backgrounds

Favicon

Standard 32x32 browser tab and device shortcut icon assets.

Synergy Hair favicon
1024px square master exported from the design (forest mark tile). Upload via theme settings favicon.
assets/favicon.png · theme-color rgb(var(--color-accent))

Icons

Registered flat-file SVGs inside assets/icons.json with currentColor configurations.

arrow-right
calendar-blank
caret-down
caret-left
caret-right
caret-up
check-circle
close
envelope-simple
facebook-logo
gift
heart
instagram-logo
magnifying-glass
map-pin
package
phone
shopping-bag
sparkle
star
truck
twitter-logo
user

Fonts

Web font families loaded and registered inside assets/fonts.json.

DM Sans
Heading, body and label/mono roles -- one family by design
Variable wght axis · 400 / 500 / 700 · self-hosted woff2 (latin + latin-ext) · SIL OFL 1.1

Accordion row

One native disclosure row per render (details/summary) - caret and plus/minus dialects, three heading sizes, closed and open states. Group behaviour belongs to parent sections.

Caret dialect - closed and open masters (base 14px/500)

Description

Panel slot content supplied by the caller. The row styles its box only, never this content's typography.

Closed (master m3qm8)
Description

Panel slot content supplied by the caller. The row styles its box only, never this content's typography.

Open, server-rendered (master Tf5Fk - title and icon green, caret rotated)

Heading sizes - md 16px (FAQ variant) and lg 20px/600 (gift-card variant)

How to use

Panel slot content supplied by the caller. The row styles its box only, never this content's typography.

md - 16px/500
Terms and conditions

Panel slot content supplied by the caller. The row styles its box only, never this content's typography.

lg - 20px/600, open

Plus dialect (PDP product details) - plus/minus swap, closed and open

Ingredients

Panel slot content supplied by the caller. The row styles its box only, never this content's typography.

Plus closed (plus-thin glyph)
Best for

Panel slot content supplied by the caller. The row styles its box only, never this content's typography.

Plus open (cross-faded to minus-thin)

Stacked rows - exactly one 1px hairline between siblings, no doubled borders

Shipping

Long-answer specimen: the open panel must settle to its content height with no clipping, whatever the answer's length. This paragraph exists to be taller than one line so the height animation end state is verifiable in the runtime probe.

Returns

Panel slot content supplied by the caller. The row styles its box only, never this content's typography.

Salon stock

Panel slot content supplied by the caller. The row styles its box only, never this content's typography.

Three stacked rows (middle one interactive - click to open)

Narrow cell - two-line title grows the header, no clipping

Can I use the same products at home as in the salon?

Panel slot content supplied by the caller. The row styles its box only, never this content's typography.

Wrapped title (mobile reference: row grows, metrics unchanged)

Button

Shared button primitive - primary, secondary and inverse variants across the full state set, with anchor, submit and full-width render modes.

Primary - state set (default / hover / active / focus-visible / disabled / loading)

Default
Hover
Active
Focus visible
Disabled
Loading

Secondary - state set (default / hover / active / focus-visible / disabled / loading)

Default
Hover
Active
Focus visible
Disabled
Loading

Inverse (for dark panels) - default plus convention states

Default
Hover
Active
Focus visible
Disabled

Render modes - anchor link, submit type, full width

Shop now Anchor (url param)
Submit type
Full width (300px host)
Shop now Disabled anchor (aria-disabled)

Icon

One parametric Phosphor-thin icon primitive - glyph selected per instance by handle from assets/icons.json, colour via currentColor, size parametric.

Icon snippet - full registered set (22 handles, default 24px, currentColor)

magnifying-glass
user
heart
shopping-bag
package
map-pin
check-circle
sparkle
gift
truck
star
arrow-right
caret-right
instagram-logo
facebook-logo
twitter-logo
envelope-simple
phone
close
caret-left
caret-up
caret-down

Size variants (16 / 24 / 28 / 40)

Colour inheritance (host sets color - ink / sale / accent)

Labelled variant (role="img" + aria-label)

Unknown handle (renders nothing, no error)

Section header

Shared section header composite - optional uppercase caption above a parameterised heading with an optional right-slot text-link, bottom-aligned to the title. Caption and link are genuinely optional (omitted from the DOM when blank), heading tag and title size are parameterised, and host colour overrides stay host-owned.

Master specimen (caption + title + shop-now link, y1Fuv)

Bestsellers

Loved by our stylists

Caption + title + link (design master)

Optional-element boundaries - blank caption omits the element, blank link omits the slot

Just landed

Title only (no caption, no link)

Introducing Synergy

Shop by hair type

Caption only (no link slot)

Keep reading

Link only (no caption)

Heading tags - h1 through h4 (outline semantics)

Collection heading

h1

Section heading

h3

Minor heading

h4

Size modifiers - host-measured title sizes

Just landed

md - 34px (N5zix)

Shop by hair type

sm - 32px (ykpQa)

Reviews

xs - 30px (DhQyI)

Shop by category

2xs - 28px (xkKxD)

@synergyhairnz

3xs - 24px (xaNY2)

Host colour overrides - accent-deep title, muted caption (SpsxK / DhQyI)

Best sellers edit

Title accent-deep (SpsxK)

More from the journal

More from the journal

Caption muted grey (JL4Ph)

Pill

Square-cornered label primitive - default, active, selected and sold-out states across filter, buy-box and static render modes. Selection logic stays host-owned.

State set (default / active / selected / sold-out)

Default
Active (aria-pressed)
Selected (variant)
Sold out (aria-disabled)

Filter bar specimen - default row with active selection (host gap 12px)

Filter row (active = green fill)

Buy-box specimen - variant sizes with sold-out kept in DOM

Size pills (selected = accent fill, white label)

Render modes - anchor link, static span

Conditioner Anchor (url param)
Applied: Olaplex Static span (interactive: false)
Focus visible (forced)

Sale badge

Inert uppercase overlay badge - sale, save-percent, best-seller, out-of-stock and category-tag variants. Placement is host-owned; the badge is a static span.

Variant set (sale / save-percent / best-seller / out-of-stock / category-tag)

Sale Sale (default)
Save 16% Save percent (16, compact)
Best seller Best seller
Out of stock Out of stock
Hair Care Category tag (free label)

Fallback and override paths

Sale Unknown variant (falls back to sale)
Sold out Label override on base (sold-out gallery wording)
Category Category tag (default key, no label)

Positioning - the badge is static (placement is host-owned)

Sale Host positions the overlay; the snippet ships no positioning styles

Form input

Shared form field primitive - label bound to its control across text, email, tel, number, select, textarea and checkbox types, with focus, error, disabled and counter states.

Control types (default)

text
email
tel
number
select
textarea (120px)
checkbox

Textarea counter (maxlength 500)

0 / 500 characters

counter: 0 / 500 characters (types live)

Focus state (2px --color-accent outline @ 2px offset)

focus visible

Error state (border --color-sale, inline message, aria-invalid)

Please enter a valid email address

error

Disabled state

disabled input
disabled checkbox

Checked checkbox (accent fill, white check)

checked

Select icon override (gift-card date pattern)

select with icon param (caret-down default; calendar unregistered)

Gift card form

Gift-card buy-box field set (boards x14Rp/UjQsv desktop, pCE88/rd2LL mobile) - a radio group of pill denomination labels over visually-hidden native radios (one per variant), the recipient field stack through the shared form-input primitive using Shopify's native gift-card recipient properties (Recipient email / Recipient name / Message / send_on, with the __shopify_send_gift_card_to_recipient validation checkbox and __shopify_offset offset), the From line-item property, a live character counter and a delivery-date select that populates 90 days of schedule options client-side. Message cap is Shopify's native 200 (message_max param), not the drawn 0/500 (brief caveat 12). The Other-amount toggle is build-to-convention (the trigger is undrawn) and ships off (show_other_amount, default false) pending the client decision on the 25-500 mechanism; the demo matrix renders it enabled. Demo renders the live E-Gift Card product inside a real (non-submitting) product form.

Default state (live gift-card product, board UjQsv / rd2LL)

Amount:

0 / 200 characters

Other-amount revealed state (show_other_amount on, build-to-convention)

Amount:

0 / 200 characters

Notify me

Out-of-stock email-capture panel - grey panel with a one-row email form (form-input + button), pluggable transport, and build-to-convention submitting/success/error states. Rendered by buy-box on unavailable products; anchor target of the mobile sticky bar CTA.

Idle panel (board DwyOz / TyYFK)

Email me when it's back in stock

idle (390px column = mobile board width)

Wide specimen (board column width)

Email me when it's back in stock

idle wide (form row: input flexes, button fixed)

Error state (build-to-convention)

Email me when it's back in stock

error: data-state=error, field--error border, aria-invalid, sale message (drive by submitting an invalid email)

Interactive states (runtime-driven)

Email me when it's back in stock

idle: submit empty -> error; valid email -> submitting (button disabled + spinner) -> success (aria-live confirmation, form hidden, focus moved, notify-me:submitted dispatched)

Price

Money primitive across regular, sale-with-compare-at, range and muted states, sized per context (buy box, card, cart line, cart total, upsell). Compare-at always strikes; the SAVE pill stays parent-composed.

Buy box (24px, desktop heading metrics)

$36.95
Regular
Sale price$39.95Regular price$62.90
Sale with compare-at
$58.95
Equal compare (renders regular)
$36.95
Muted (sold out)
$25.00 – $500.00
Range (gift card)

Product card (card-name metrics)

$49.00
Card regular (accent deep)
Sale price$49.00Regular price$58.00
Card sale (compare strikes)

Cart and upsell contexts

$36.95
Cart line unit price
Sale price$49.00Regular price$58.00
Cart line total (compare stacked)
$24.95
Upsell (pair it with)

Buy box

PDP purchase column (masters g8Lbt desktop default / ryEI8 sale / rrG96 out-of-stock / iTkcH multi-variant; yICIe + T6sSo mobile) - vendor eyebrow + rating row, h1 title, price row composed from price + save badge + OOS chip, blush savings block, tagline, conditional benefits list, hairline divider, variant option pills (selected echo right-aligned in the head, sold-out pills kept in the DOM with aria-disabled per note ddbIH), live low-stock and size-help notes, and the purchase row (quantity-stepper + Add to bag at 48px) that swaps for the sold-out block + notify-me panel when the selected variant is unavailable. States render in Liquid (data-state default | sale | sold-out, data-multi-variant) and re-render client-side on pill selection against a serialized variant table - publishes product:variant-change, AJAX add through routes.cart_add_url with a native-submit fallback, dispatches cart:item-added + cart:add (flyout opens). Sticky is the product-hero column's concern (note w63wsk); the box exposes data-buy-box for the mobile sticky bar. Demo renders live store products - sale/OOS states are data-driven. Notify-me specimens duplicate id="notify-me" on this page (harmless; single instance on a real PDP).

Default state (live store product, board g8Lbt)

Eleven

Eleven Miracle Hair Treatment 125ml

$31.00
Default: vendor + rating, h1, price row, options, purchase row

Multi-variant (board iTkcH - pills + selected echo + stock note)

Ghd

Ghd Original Hair Straightener

$267.00
data-multi-variant renders when the product has more than 2 variants; click a pill - the echo, price, savings and notes update without reload

Sale state (board ryEI8 - data-driven)

K18

K18 Leave-In Molecular Repair Mask 50ml

$123.00
Renders sale only when the live variant carries compare_at_price > price (crimson price, struck compare, SAVE chip, blush savings block)

Out-of-stock state (board rrG96 - data-driven)

Olaplex

Olaplex No.5 Bond Maintenance Conditioner 250ml

$57.00
Sold-out product: OOS chip beside the price, purchase form replaced by the sold-out block + notify-me panel (id="notify-me" duplicates across gallery specimens - single instance on a real PDP)

Benefits (conditional metafield block)

Fanola

Fanola No Yellow Shampoo 350ml

$25.00
Renders the "Why you'll love it" list only when custom.benefits has entries; absent otherwise

Sticky add bar

Mobile-only docked add-to-cart bar (Pencil ZXAJ8 in rGMtZ; sale A7AeB, OOS hYeJj, gift card SnLOG; behaviour note w63wsk MOBILE paragraph) - product name + price left, a 40px green plus-icon add button right that submits the main product form via the form attribute (no duplicate form; the buy box keeps owning the add). Fixed to the viewport bottom at 64px content height + env(safe-area-inset-bottom), docks once the inline Add to bag scrolls out (IntersectionObserver on data-buy-box-atc - data-visible + transform, hidden = visibility hidden + inert), swaps to a 44px Notify me accent-deep text button on out-of-stock (scrolls to the notify-me panel, which owns the arrival focus), and shows "from price_min" on gift cards. Mirrors product:variant-change against a serialized variant table; publishes --sticky-add-bar-height while docked. Mobile only - display none at 750px and above (desktop uses the pinned buy column). Stages are transformed boxes so the fixed bar resolves inside each specimen (scrim-demo idiom); OOS and gift-card cells are data-driven and the dev store currently carries no product for either.

Docked - default state (live store product, board ZXAJ8)

Scroll the real inline add out of view - the bar docks above the stage floor (IntersectionObserver, no scroll listeners).

Eleven Miracle Hair Treatment 125ml

$31.00
Default: name + price left, 40px green plus button right; add submits the main form (form attribute)

Docked - sale state (board A7AeB, data-driven)

No sale product on the store - compare_at_price > price drives this state (board A7AeB).

Sale: crimson 14px/500 price, struck compare in grey, 8px gap (price snippet sale state)

Docked - out-of-stock state (board hYeJj, data-driven)

Amika The Shield Anti-Humidity Spray 221ml

$46.00
OOS: Notify me scrolls to the notify-me panel (id=notify-me anchor contract; the panel owns the arrival focus)

Docked - gift-card state (board SnLOG, data-driven)

No gift-card product on the store - product.gift_card? drives the "from price_min" line (board SnLOG).

Gift card: "from" + price_min as money, plus-icon add submits with the selected denomination

Hidden state (default on load) + variant mirroring

Eleven Miracle Hair Treatment 125ml

$31.00
Hidden until the inline add scrolls out (data-visible=false, translateY + visibility hidden, inert - Tab never lands inside); price mirrors product:variant-change from a buy box

Product hero

Section - the PDP hero (masters t87YC/l7ogg desktop 1440, rGMtZ/O58Ns mobile 390): the product-gallery snippet in the left column beside the sticky buy column - buy-box, then the product-details accordion group, then the pair-it-with rail, in that fixed order (blocks rejected: the order is the w63wsk sticky contract). Owns composition + layout only; every primitive renders through its one snippet. Desktop >=750px a two-column grid (gallery flexes, 440px column, 48px gap, board insets 0 20px 80px - caveat 1 board literal, content capped at --page-width); mobile a single 32px-gap stack (gallery full-bleed by its snippet, buy column inset 16, mobile band paddings details 32/16 + upsell 32/16/64/16 section-owned). Sticky idiom (note w63wsk): position sticky on the column at >=1024px ONLY - the sanctioned second, behaviour-only media query (brief caveat 2); pins when its top reaches the viewport, releases when the gallery ends (natural sticky end inside the shared grid parent); taller-than-viewport columns scroll-then-pin via top: min(0px, 100vh - 100%); below 1024px never pins; the mobile pattern is the separate sticky-add-bar. enable_sticky_column is the merchant off-switch (collapses the sticky query to static at every width). PDP-only (enabled_on product templates) - the live instance renders through templates/product.preview-product-hero.json at /products/<handle>?view=preview-product-hero, never on /pages/preview-sections. Section atom - this entry is the placement anchor in build order.

Composed hero (live store product, board columns 856 + 440)

Eleven

Eleven Miracle Hair Treatment 125ml

$31.00
Description

Eleven Miracle Hair Treatment is a lightweight leave-in hair treatment that delivers 11 benefits your hair will LOVE:

1. Adds shine, smoothness and softness
2. Control frizz & fly aways
3. Moisturise
4. Strengthen fragile hair
5. Prevent split ends
6. Detangle & create manageability
7. Protect against heat styling
8. Enhance natural body
9. Repair dry damaged hair
10. Protect hair colour with UVA & UVB filters
11. Prevent sun damage
With Australian desert lime, wattle seed and desert peach to nourish hair, Miracle Hair Treatment is a must-have for all hair types.
How to use
  1. Directions for use: Simply pump and apply to damp hair. Do not rinse.
Best for

Suitable for all hair types.

Gallery left, buy column right; sticky pins the column at >=1024px viewport width and releases at the gallery end

Buy column alone (sticky rail anatomy at the board's 440px)

Eleven

Eleven Miracle Hair Treatment 125ml

$31.00
Description

Eleven Miracle Hair Treatment is a lightweight leave-in hair treatment that delivers 11 benefits your hair will LOVE:

1. Adds shine, smoothness and softness
2. Control frizz & fly aways
3. Moisturise
4. Strengthen fragile hair
5. Prevent split ends
6. Detangle & create manageability
7. Protect against heat styling
8. Enhance natural body
9. Repair dry damaged hair
10. Protect hair colour with UVA & UVB filters
11. Prevent sun damage
With Australian desert lime, wattle seed and desert peach to nourish hair, Miracle Hair Treatment is a must-have for all hair types.
How to use
  1. Directions for use: Simply pump and apply to damp hair. Do not rinse.
Best for

Suitable for all hair types.

Column anatomy: buy-box (32px gap) - details - upsell (52px Frame 46 rhythm); mobile it stacks in the same order

What's inside

Cream itemised bundle-contents panel on the product.bundle buy box (masters mTKY7 desktop / P0xaGY mobile) - header stack, one link row per component product (70px white thumb, vendor eyebrow, 2-line-clamped name, the component product's own live price via the price snippet), optional gift-with-purchase row, hairline divider, and the totals pair ("Bought separately" = component-price sum = the bundle's compare-at; "You save" = difference with a whole percent in the sale colour). Per-item prices come from the component products at render time, never typed (note CPjrb). Replaces the Size/Options variant picker - a fixed kit has no variant axis; whole rows link to the component PDPs. With no component products the snippet renders nothing.

Default (live bundle product, per-item prices from products)

Default panel (380px buy column)

Variants - subtitle override, no-saving guard, empty guard

Subtitle override

No components - this cell renders nothing (empty guard).

Empty guard (renders nothing)

Value prop

Icon-above-text benefit block - one 26px accent icon, a body-large title and an optional body-small supporting sentence. Fluid width, non-interactive, composed in multi-column sibling rows.

Master specimen (icon 26px accent / title body-lg / text body-sm)

Overnight delivery

Order before 3pm for fast nationwide shipping.

Icon + title + text (design master)

Optional-text boundary - blank text omits the description element

Salon-grade brands

No text (two elements only)

15 salons nationwide

Find your nearest Synergy Hair salon.

Alternate icon handle

Row specimen - three sibling value props (fluid width, no fixed 300px)

Overnight delivery

Order before 3pm for fast nationwide shipping.

Salon-grade brands

Professional formulas trusted by stylists.

Samples with every order

Two complimentary samples, picked at checkout.

Three-up row (host-owned column gap)

Savings block

Blush buy-box band demonstrating the discount - valued-at compare line, you-save row and optional percent chip. Sale and bundle states; no saving renders nothing (zero DOM).

Sale state (show_percent: true)

Valued at $88.60

You save $48.65

55% off
Sale with percent chip (design fixture: valued $88.60, price $39.95, save $48.65)

Bundle state (chip omitted)

Valued at $146.60

You save $54.60

Bundle valued-at sum, no chip (valued $146.60, price $92.00, save $54.60)

Hidden state (renders nothing)

Equal compare-at - zero DOM nodes above this caption

Computed figures (not the design's sample amounts)

Valued at $67.90

You save $15.00

22% off
$67.90 valued / $52.90 price - percent is computed: 22% off

Breadcrumb

Template-aware navigation trail - full-width white bar with an edge-to-edge hairline, contained centred list, linked ancestors, aria-current page crumb and aria-hidden hyphen separators. 2-4 crumb depths, ellipsis truncation, JSON-LD emitted.

Depth 2 (cart: Home / Bag)

Depth 2 (collection: Home / Hair Care)

Depth 3 (article: Home / Journal / Article)

Depth 3 (metaobject parent: Home / Salons / Salon)

Depth 4 (product: Home / Shop / Hair Care / Product)

Long current crumb (truncates with ellipsis, no page scroll)

Separator detail (aria-hidden hyphens between linked ancestors)

Page header

Page title band - the full-width white bar at the top of store pages directly under the breadcrumb: optional uppercase forest eyebrow (contact variant), the page H1 (60px/500/-1.5px desktop, 38px mobile), an optional mono "Last updated" meta line (13px black desktop / 12px mobile, rendered only when a date is passed) and an optional body-large intro (lh 1.25 at a 720px cap; contact lh 1.6 at 640px). Edge-to-edge bottom hairline, inner content contained at --page-margin 48/16 (the generic boards' 20px literal inset is a recorded board defect - the page-margin token wins). Masters Wnpmn/VPpGU (desktop), tVovC/S9qI5 (mobile).

Generic (title + intro, max-width 720)

Last-updated meta (generic + mono meta line)

Contact variant (eyebrow + narrow intro, lh 1.6)

Title only (meta and intro omitted - no empty nodes)

Announcement bar

Full-width forest announcement strip - up to three messages rotating 5s per message with a 600ms crossfade, one visible at a time. DM Sans 12px, weight 500 / lh 1.4 desktop and 400 / lh 1 mobile (mobile master diverges as drawn); truck icon off by default; single message renders static with no timer.

Master state (3 messages, message 1 active)

Free Express Shipping On Orders Over $80

Free Express Shipping On Orders Over $80

Single-message static state (no timer)

Free Express Shipping On Orders Over $80

Optional leading truck icon (per-message setting, off by default)

Free Express Shipping On Orders Over $80

USP banner

Full-width blush benefits strip above the footer - static 3-up icon + label row with hairline dividers on desktop; single-message rotating strip on mobile (4s dwell, 300ms crossfade, paused when hidden, suppressed under reduced motion). Icons render desktop only, per the mobile master.

Desktop master state (static 3-up, dividers, 18px icons)

  • Free express shipping on orders over $80
  • Stylist approved pro brands
  • 15 salons nationwide

Demo scope: the section stylesheet is scoped to the section bundle, so this matrix re-declares the identical rules below. The real section instance (with rotation JS) lives on /pages/preview-sections.

Mobile master state (single message, 14px, icon hidden)

  • Free express shipping on orders over $80

Optional icon select variants (truck / gift / sparkle / star)

  • Order before 3pm for overnight delivery
  • Free gift with purchases over $120
  • New arrivals every week
  • Over 17,000 five-star reviews

Hero duo

Two-image hero banner (660px desktop, base fill ink) with a bottom-up scrim, bottom-anchored content row (52px headline left, primary + secondary CTAs right at inset 20) and the embedded blush USP strip in normal flow below the banner. The master clips the strip at y=660 (designer open item 7) - the build renders it; its fidelity is carried by the resolved values, not the visual diff.

Default — banner + embedded USP strip

Lift Your Hair Game with K18.

  • Free express shipping on orders $80+
  • Stylist-approved pro brands
  • 15 salons nationwide

State — show_usp_strip: false

Lift Your Hair Game with K18.

Scrim

Shared dismissal overlay - one fixed ink-40% layer rendered once on the layout, shown by any overlay owner via the is-open class and a --scrim-top panel-bottom offset. Hidden by default, fades 200ms, dispatches scrim:click for the owning overlay to close itself.

Hidden (default) - opacity 0, pointer-events none

Page content beneath stays clickable while hidden.

Visible at --scrim-top: 240px (design geometry - below the open panel)

Fill rgb(var(--color-ink) / 0.4), fades 200ms var(--ease-standard); starts at the panel's bottom edge.

Demo owner - open/close + scrim:click closes

Click the scrim (or the toggle) to close; the scrim dispatches scrim:click for the owning overlay.

Quantity stepper

Shared quantity control - 48px hairline frame with a 48px centred value cell (DM Sans 16/500) and thin minus/plus button zones. Minus disables at min, plus at an inventory max; publishes change + a composed quantity:change event. The purchase-row context shows the PDP buy-box placement.

States

default (value 3)
min-disabled (value 1)
max-capped (value 5 of 5, defensive)

Context: PDP purchase row (PtSiV)

Drawn reference covers the value cell and count only; the flanking button zones and the purchase-row Add to bag button are build-to-convention for this demo.

Cart line item

One cart page line row (boards vKnqb desktop / D4eHoF mobile) - 96px/80px white product thumb (hairline frame mobile only), vendor eyebrow, linked name (16/500 desktop, 14/600 mobile, -0.5px), desktop-only unit price, mobile-only variant meta, the shared quantity-stepper, the line total and the 32px/28px remove control (thin close glyph, border-grey to ink on hover). Compare-at lines stack the struck grey compare beneath a sale-coloured total per the system-wide sale convention (note ddbIH) - the desktop board's unstruck black variant is not reproduced (caveat 2). One DOM, two layouts on the single 750px query; last-row hairline suppressed at desktop only. Rows render live store products (the boards' Muk/NAK placeholder inconsistency is not reproduced); quantity and remove controls render real but inert in the demo - the live cart page owns the commerce contract. Snippet atom - the live instance renders inside sections/cart-items.

Desktop (board vKnqb)

Row 1 is the regular specimen (force_regular: true); Row 2 is the compare-at specimen (price + $19.05 override). Quantity and remove controls render real but inert - no cart is attached in the demo context.

Mobile (board D4eHoF, 390)

The mobile stage pins the 390px board width. The row's own mobile layout shows below 750px; above it the desktop grid applies (the stage is a width frame, not a viewport fork).

Order summary

Cart page summary card (boards V8WTb/xDBAv desktop 400px / jpB62/ztTQ2 mobile) - cream #F2F1EE card (32px / 24px-16px padding), 24px/500 heading, subtotal / conditional savings / conditional shipping lines (16px, labels 600 -0.5px, values 16/500 right-aligned at 4px row gaps), promo row (46px white form-input + 46px ink Apply button, 8px gap), the 1px white hairline divider, total row (17/16 label + 12px "Tax included" note against a 24px/500 amount), checkout through the button snippet (green, full-width, 45px, submit of the host cart form) over the centred continue-shopping text-link, the six real payment marks (53x30 white tiles, 4px gaps - the boards' hand-drawn text/ellipse tiles are placeholders) and the centred 13px lock + reassurance row. Payment marks are excluded from board comparison (layer disabled on both boards, caveat 3); page vocabulary stays the page's own ("You're saving:" / "Free" / secure note - not the flyout's "Discount:" / "$19.95", caveat 1). The promo Apply runs its own fetch (never the host form submit): /discount/{code} then a cart.js re-read - applied codes prove through cart discount allocations, otherwise the invalid message; publishes cart:promo-applied then cart:updated (the section re-render is the host's). Free shipping at or above the threshold renders "Free", below it the flat settings rate, else "Calculated at checkout".

Default state (desktop board V8WTb/xDBAv - 400px column, live cart figures)

Order summary

Subtotal:
$0.00
Shipping:
$20.00

Total: Tax included
$0.00
  • Visa
  • Mastercard
  • American Express
  • PayPal
  • Afterpay
  • Zip

Secure checkout. Easy 30-day returns.

Savings line (renders only when the cart carries a discount - empty cart shows subtotal / shipping / total only)

Order summary

Subtotal:
$0.00
Shipping:
$20.00

Total: Tax included
$0.00
  • Visa
  • Mastercard
  • American Express
  • PayPal
  • Afterpay
  • Zip

Secure checkout. Easy 30-day returns.

Promo error state (type any text and press Apply - an unrecognised code shows the invalid message)

Order summary

Subtotal:
$0.00
Shipping:
$20.00

Total: Tax included
$0.00
  • Visa
  • Mastercard
  • American Express
  • PayPal
  • Afterpay
  • Zip

Secure checkout. Easy 30-day returns.

Shipping below threshold with no flat rate (label state)

Order summary

Subtotal:
$0.00
Shipping:
$20.00

Total: Tax included
$0.00
  • Visa
  • Mastercard
  • American Express
  • PayPal
  • Afterpay
  • Zip

Secure checkout. Easy 30-day returns.

All specimens render through the live cart - with an empty session cart the line values show zero figures and no savings row; the checkout button keeps its host-form submit contract visible (the live cart page instance is the real submit inside sections/cart-items). Fixture figures on the boards (subtotal $207.00, saving $18.00) are placeholder cart contents - the snippet always renders live cart values through the money filter.

Rating stars

Score display primitive - compact mode (one star + score, optionally the parenthesised review count) across the buy box, pair-it-with and product card sizes, and full mode (five-star row, gap 2px) with nearest-half fractional fill. Purely presentational: the wrapper carries role="img" and the translated label; glyphs and count are aria-hidden. Blank rating renders nothing.

Compact - one star plus score (buy box 14px)

Buy box - 4.8 (124), default 14px
Pair-it-with - 13px, lh 1.3 count
Product card - 20px star, 12px score, no count

Compact variants

Score only (no count param)
hide_count - count supplied but suppressed
Whole-number score (5, never 5.0)

Full - five-star row (PDP reviews, 14px, gap 2px)

5.0 - all awarded
4.8 - rounds to 5 awarded
3.5 - three filled + one half + one muted
3.4 - nearest half is 3.5: three filled + one half + one muted
0 - all muted
With "Based on 1,284 reviews" line

Empty state

Blank rating - renders nothing (zero DOM)

Filter row

Filter facet row - hidden native checkbox bound to a drawn 20x20 box, label and live parenthesised count as one full-width hit target. Default, hover (border and label darken, no fill, no movement), selected (forest fill, cream check cross-fade), unavailable (hairline box, 60% opacity, stays in the DOM) and keyboard-focus states.

State matrix (264px sidebar width)

Rows top to bottom: default - hover - selected (real checked input) - unavailable (count 0, disabled, 60%).

Keyboard focus (2px --color-accent ring @ 2px offset)

Filter group

Filter facet group - a 44px disclosure header (title plus plus/minus indicator) over an optional 40px search field, an options list of filter rows and an optional "Show all n" reveal, closed by a 1px hairline divider. Open/closed native details with a 300ms height animation; open groups turn the title and indicator green; searchable groups filter rows client-side; open state survives section re-renders. Open master (U3wKiz), closed (p6bUn), plain and Price-dialect groups plus a live interactive specimen.

State matrix (264px sidebar width)

Brand

Open - searchable + show-more (master U3wKiz; Brand dialect)
Hair type

Closed - plain (closed frame p6bUn header metrics)
Price

Closed - no search, no show-more (Price dialect)
Hair concern

Open on load - no search, no show-more (Hair Concern; open = green title + minus)

Search filtering + show-more reveal (interactive)

Brand

Type to filter rows client-side; "Show all 8" reveals the capped rows and removes itself.

Filters sidebar

Collection/search filter rail - the 264px desktop column of filter facet groups (one filter-group per results.filters entry, Brand first) under a 24px/500 "Filters" title. Applies immediately on checkbox change - serialise, pushState (preserving sort/q, dropping page), one Section Rendering fetch with aria-busy on the form, then a `facets:changed` CustomEvent (detail.source = the rail) for the grid, header chips and mobile drawer; external `facets:changed` and popstate morph the rail's own markup from the same response. Groups with active selections open on load; Brand opens by default with search + show-more; the in-facet search field stays unrendered (drawn enabled:false in every rail instance). The brand facet is suppressed with hide_vendor_facet: true on brand collection pages (designer note t3mwe). Desktop only - the rail hides below the 750px breakpoint where the mobile filter drawer takes over. Master iNmzh in f3JVd; internals owned by the accepted filter-group/filter-row snippets.

Rail at 264px (master iNmzh captured moment)

Filters

Brand

Hair Concern

Hair Type

Hair Texture

Price

Brand open + show-more; Hair Concern open; Hair Type, Hair Texture, Price closed. No in-facet search (drawn enabled:false in every rail instance). Checkbox state is fixture-rendered only - facet application is a live-collection behaviour.

Brand page (hide_vendor_facet: true)

Filters

Hair Concern

Hair Type

Hair Texture

Price

The vendor/brand facet never renders; the aside carries data-brand-page="true".

Collection header

Section - the full-width header band at the top of collection, search and blog pages. One component, five states (note r3GuZ), all toggled on one section via data attributes: default (title - description - quick links - count - sort), applied-filters (chips with x + Clear all above the meta row), hero-image (720x420 media LEFT of the copy, copy vertically centred; mobile leads with a full-bleed 220px image - note tNVsq), search-results (eyebrow "Search results for", title = escaped query, count badge, description + quick links off) and no-results (apology copy in the description slot, quick links stay on as the recovery path, count + sort off). Quick links are TEXT pills, never thumbnails (note r3GuZ - 4:5 portrait category photography does not crop to a landscape thumb); they render as repeatable quick_link blocks (label + link, max 8). Title DM Sans 60px/500/-1.5px desktop, 48px/-1px mobile; chips fill #374426 ($sg-accent/$sg-forest normalised); quick-link pills via the pill snippet. Masters Y4mkj (desktop 1440) / A0l4jZ (mobile 390); state specimens vpxMT (filtered), ogTt5 (hero), k9j2pP (search), lxbj3 (no results). Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

Default (collection, no filters)

Haircare

Salon-grade shampoos, conditioners and treatments chosen by our stylists for every hair type.

47 products

With applied filters (chips + Clear all)

Haircare

Salon-grade shampoos, conditioners and treatments chosen by our stylists for every hair type.

Olaplex Damage repair
Clear all

12 products

Hero image (720 left desktop, copy centred)

Haircare

Salon-grade care built around repair - the range our stylists reach for first.

38 products

Search results (eyebrow + query title + count)

Search results for

olaplex 121

121 products

No results (apology copy, quick links stay on)

Search results for

kérastase dupe

We couldn't find anything matching "kérastase dupe". Try a different search, or browse the range below.

Mobile filter drawer

Mobile filter overlay - a full-height 340px panel sliding in from the LEFT (400ms ease-entrance, reduced-motion safe; cart-flyout overlay conventions - designer sign-off flagged) over the shared scrim: 64px header ("Filters" title, 24px close), a body that is the only scroll region rendering the SAME filter-group instances as the desktop rail (one source of filter markup; Brand and Hair Concern open on load, Brand with search + show-more, Price neither, per A2R6j), and a pinned footer with the full-width primary Show results (n) button plus the Clear all text action. Opens from the collection-header mobile Filters button (filter-drawer:open); selection applies immediately - Show results is close-and-reveal; Esc / scrim / close / Show results close; focus trapped and returned; matchMedia force-closes above 750px where the rail takes over. Spec-only - the only designer words are X3wHD3 ("full height, same groups, sticky Show results button") and A2R6j; design-source-visual: off, no board exists.

Mobile drawer pinned open (spec-only - X3wHD3 / A2R6j, no board)

Full-height 340px panel sliding from the left; 64px header + 24px close; the same filter-group instances the desktop rail renders (Brand open with search + show-more, Hair Concern open, others closed); pinned footer with the full-width Show results (46) primary button and Clear all. Left-edge slide, footer Clear all and the 400ms ease-entrance motion are cart-flyout convention transfers - flagged for designer sign-off. Facet application is a live-collection behaviour; checkbox state here is fixture-rendered only.

Filter bar

Mobile category filter band - a horizontally scrollable, non-wrapping row of pill anchors (12px gap, overflow with the scrollbar hidden). The pill matching the current URL carries the active green fill and aria-current="page"; the "All" pill is snippet-generated. Shown as the DS master row, the Journal category set, and a 390px overflow specimen.

Component master (DS cell - Shampoo active, 12px gap)

In-context specimen (Journal categories - All pill active)

Overflow specimen (narrow container - All active, scroll, no scrollbar)

Top to bottom: DS master row (active = green fill) - Journal categories with the snippet-generated All pill active - 390px-wide scroll region (drag or shift-wheel to scroll; no scrollbar by design).

A-Z jump bar

Brands A-Z index bar (masters uZxeO desktop / NIhBT mobile, note MO0PS) -- 26 fixed 44x44 letter cells, 8px gap, DM Sans 16px lh 1. Letters with live brands are anchor links to the directory's #brands-<letter> groups; letters without render as inert spans in --color-border (computed from live_letters, never hard-coded). CSS flex-wrap reproduces the design's 7/7/7/5 mobile rows emergently at a 358px column (Pencil draws explicit frames only because it has no flex wrap); single row at desktop widths. Static in flow, works with zero JS; the host directory's IntersectionObserver owns the .is-active + aria-current active-letter state ($sg-accent -- build-to-convention, designer to confirm). Rendered as the first child of the brands-directory section.

Master specimen (uZxeO desktop / NIhBT mobile, 2026-08-09 dead set)

Live letters from data -- I, Q, U, V, Y dead (computed, not hard-coded)

All letters live -- no dead spans render

live_letters = full alphabet (26 links)

No letters live -- all 26 render as dead spans

Empty live set (all dead -- boundary state)

Active letter state (build-to-convention -- $sg-accent + aria-current)

Host sets .is-active + aria-current on jump (below: forced on K for the specimen)

No results

Search/404 empty state (boards COJ4N, wKHQc, V8n0OX, ihE0A): an intro row (search - "No results for" eyebrow over the query-echo h1; 404 - "404" h1 beside a decorative green "0", aria-hidden) over a max-560px message block with the did-you-mean suggestions row and two buttons (button snippet, 45px, radius 0). Search variant primary "Browse bestsellers" (bestsellers collection) + secondary "Shop all products"; 404 primary "Back to home" (root). Did-you-mean is progressive enhancement - a no-results-suggestions custom element fetches predictive query suggestions (limit 3) on load and removes itself when the response is empty or errors, so the row only ever exists with real links; the demo seeds it because the fetch only runs against a live query. Suggestion links 24/600 in $sg-border (2.3:1 - designed as-is, caveat 7) with the mandatory hover/focus treatment to green + underline over 200ms standard easing. Mobile (single 750px query): 48px headline, 20/500 body copy, suggestions stack, CTAs stacked full-width at 12px gaps. Leftover board artefacts (hero image, logo, product count) are excluded - not built.

Search variant (eyebrow + query echo, did-you-mean, CTAs)

Live page fetches predictive query suggestions into the did-you-mean row; the demo seeds it so the state is visible without the fetch.

Search variant without suggestions (row absent, single 40px body-to-actions gap)

404 variant ("404" h1 + decorative green 0, Back to home primary)

404

The page you're looking for may have moved, sold out, or never existed. Let's get you back to great hair.

Mega menu

Global navigation overlay - the white full-bleed panel hanging from the header's bottom edge (top: var(--header-height)): link columns from the menu linklist (level-2 links as grey 12px/500 captions, level-3 links as 13px/400 rows at a 12px gap, 200px columns), a 4-tile brand grid (197px square tiles, 4px gaps, 1px rgba(54,54,54,0.102) stroke, image fill) with a "View all n brands" text-link beneath, and an optional off-by-default featured panel (340px, image 340x200, eyebrow, H4 title 24px/500, body copy, primary button). Opens on a ~100ms hover-intent delay with opacity 0-1 + translateY(-8px-0) at 200ms --ease-entrance; closes on mouse-leave, Esc, focus-out or scrim click; the shared scrim runs from the panel's bottom edge (ink 40%, header never dimmed). Master U2PPJI; the brands master LsGIe is built as the panel's brands mode - five 220px columns from the header's brands link list above a full-width footer row (reversal of MO0PS, 3 Sep 2026); the featured panel layout uses the LsGIe geometry since LgPdC is clipped in the master.

Hair Care panel (master U2PPJI, 1440)

Featured panel (optional, off by default - LsGIe geometry)

Empty tile state (placeholder fallback)

Shop by brand

Nav drawer

Global mobile navigation - a fixed full-viewport white overlay sliding in from the left at 750px and below only: a 64px top bar (150x44 logo tap, 44x44 close), 13 flush 56px rows (DM Sans 13px/500 ink, Sale in the sale colour, caret-right 20 in muted on the six drill rows only; Brands is a caret-less direct link per MO0PS) plus a "My account" row, and a CTA block pinned to the bottom (hairline top, 24/16/32 padding) with the full-width 48px Book button and tel:/mailto: contact rows. Master PDGXA; drawer open/close motion is build-to-convention (400ms slide-from-left, cart-flyout parity).

Level 1 master (PDGXA, 390)

Live specimen (main-menu linklist)

Nav drawer level 2

Level-2 mobile nav panel (master MvPsR, 390) - slides over the level-1 drawer per drilled category, one panel per category (Brands/Gifts never promise one per MO0PS). Back row 48px (caret-left-thin 20 ink + "All categories" 13/400, hairline bottom), 64px category title (h4 18/500), 48px "Shop all {category}" row (13/500), then the category's level-2 links as accordions: 56px headers (13/500 ink + caret 20 muted, single glyph rotating 180deg per l7giL7), cream-40 children (8px vertical padding) of 44px child rows (13/400, 32px left indent). One open at a time, first group open on push; push is 200ms ease-standard, accordions 300ms ease-standard height+opacity, reduced-motion collapses both. Top bar stays owned by level 1 - the brand never reflows between levels.

Level 2 master (MvPsR, 390 - Hair Care, first group open)

Live specimen (main-menu linklist - drill + back + accordion)

Open the drawer, tap "Hair Care" (or any other drill row) to push the level-2 panel; the back row returns to level 1 with its scroll intact.

Article card

Editorial blog card - cover-fit image band with an overlay category badge, stacked info block (title, excerpt, vertical date / read-time meta). Default, no-badge, no-image, no-excerpt and narrow-host render modes plus forced hover / focus specimens. The live default card renders a real store article.

Default card (live store article)

Default (image + badge + excerpt + meta)
Narrow host (342px - mobile board width)

Render modes - no badge, no image, no excerpt

Father’s Day Gift Guide 2026

What do you get for the man who has it all? Something that helps him look and feel his best, of course! From styling essentials...

6 min read
No badge (show_badge: false)
No excerpt (show_excerpt: false)
No image (placeholder band - Keep Reading parity)

State specimens - hover and keyboard focus (forced)

Hover (image scale 1.03 + shadow, title to accent)
Focus-within (same as hover + focus ring)

Featured article

Section - the blog journal featured-article hero (desktop master Cg79j 1440, mobile a9Ktc5 390). Desktop: a 660x460 hairline-stroked cover-fit image with the #374426 category tag overlaid at 0,0 beside the content column (gap 48, vertically centred) on a literal 20px inset under max-width var(--page-width) - the journal boards win over --page-margin 48, same divergence as the hero family. Title 40px/500/-1.5px ink (the H2 master downsized to the h3 step), excerpt 16px/1.2 black, stacked author / date / read-time meta (14px/500/-0.5px; read time in --color-border) and the primary Read-article CTA via the button snippet. Mobile: condensed stacked card - full-bleed 260px image breaking out of the 16px gutters, accent 26px title (mobile board), meta gap 4, CTA offset 8, ending in a 1px hairline bottom rule with 20px bottom padding before the article list. Media, title and CTA all link to the article; category tag = first article tag; 14x9 badge mark omitted (unregistered raster). Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

Desktop hero (>=750px)

Mobile condensed (<750px)

Article hero

Article-page hero (desktop host JmPoM + C4EjJ1 at 1440, mobile B2IGTt + QgUkw at 390) - a centred editorial header block (eyebrow, 60px/500 H1, 17px standfirst, stacked author / date / read-time meta at 14px/500) over a full-bleed featured image (100vw x 620 desktop / 300 mobile, cover-fit, square). Eyebrow is the article's first tag, uppercase, desktop-only per the boards (hidden <750px); the mobile byline wraps its meta in a 1px hairline top rule. Default live-article render plus no-image / no-eyebrow render modes. The article-content gap (no blog-articles stage seeded in the Woo migration) means demo fixtures carry the matrix; the live default card renders only when the store blog has an article.

Default (live store article - desktop breakpoint)

HAIR TRENDS

Father’s Day Gift Guide 2026

Father’s Day Gift Guide 2026
Live article - eyebrow from first tag, full-bleed media

Render modes - no eyebrow

Father’s Day Gift Guide 2026

Father’s Day Gift Guide 2026
show_eyebrow: false (also degrades when the article has no tags)

Desktop state (>=750px) - fixture content

Hair Care

How to repair damaged hair at home

Bond-building treatments, smarter heat habits and the daily routine our stylists swear by for bringing over-processed hair back to life.

Centred 860px column, eyebrow visible, 620px full-bleed media

Mobile state (<750px) - fixture content

How to repair damaged hair at home

Bond-building treatments, smarter heat habits and the daily routine our stylists swear by.

Left-aligned, no eyebrow, hairline byline rule, 300px media

Degraded data - no image, no excerpt, no author role

An article with the minimum of data

No media block, no standfirst - text-only hero

Article body

Section - the article-template long-form body (desktop host wlcB2 at 1440, mobile board kwA7H at 390): a 700px centred content column on a white band (section padding 96/48 desktop, 48/16/64/16 mobile, 48px rhythm desktop / 32px mobile) that hosts the article-hero snippet as its first child, then styles the merchant's RTE `article.content` through element selectors only - plain admin-authored h2 (48/500/1.05/-1.5 desktop, 30 mobile, 16px heading-to-text gap desktop / 12 mobile), h3 (40/500/1.1/-0.5 desktop, 26 mobile), p (17/400/1.25 desktop, 16/1.2/-0.5 mobile), blockquote pull-quote (3px $sg-green left rule, 8/0/8/24 padding, DM Sans 500 italic 24/1.35/-0.5 $sg-green desktop, 18/1.35/-0.3 mobile), ul checklist (accent "—" markers desktop / "•" mobile in $sg-accent, 12px item gaps desktop / 4px mobile, 4px list top padding), and content figures (hairline-bordered image, radius 0, 13px $sg-black caption at 12px below, lh 1.2 desktop / 1.5 mobile). No classes are required in the authored content; images are max-width 100% so wide media never scrolls the body. Static: no JS, no events, no icons. Section atom - the live instance renders inside /blogs/news/<article>?view=qa (a page template carries no article object); this gallery card is the placement anchor in build order.

Desktop master state (>=750px) - fixture RTE content (wlcB2)

Over-processing, heat and time all take their toll — but damaged hair isn't beyond saving. With the right treatments and a few simple habit changes, you can rebuild strength, smooth the cuticle and get your hair looking healthy again. Here's the approach our stylists reach for most.

Start with a bond-building treatment

Chemical services and heat break the internal bonds that give hair its strength. Bond-building treatments — the ones with 'plex' in the name — work inside the strand to relink those broken bonds, so hair feels less brittle and snaps far less easily. Use one weekly as a pre-shampoo treatment and you'll notice the difference within a month.

Rethink your heat routine

You don't have to give up your straightener, but you do need to be smarter about it. Always work on fully dry hair, keep tools below 180°C, and never pass over the same section twice. A good heat protectant is non-negotiable — it puts a crucial buffer between the plate and the strand.

Repair isn't a single product — it's a routine you can actually keep up.

The daily routine

  • Shampoo less and condition more
  • Always use a heat protectant
  • Deep-condition with a weekly mask
  • Swap to a silk pillowcase
  • Trim every 8–10 weeks
A weekly mask keeps mid-lengths supple.

Repairing damaged hair is less about one miracle product and more about consistency. Stick with the bond-builders, protect against heat and keep up the weekly mask, and within a few weeks you'll have hair that looks — and feels — genuinely healthier.

700px centred column, 96/48 section padding, 48px rhythm, em-dash markers

Mobile board state (<750px) - same fixture (kwA7H)

Heat tools, colour and the wear of everyday styling leave even healthy hair looking dull, brittle and prone to snapping. The reassuring news: most damage can be softened — and a good deal of it reversed — with the right products and a little consistency. Here's the approach our stylists rely on to rebuild strength from home.

Start with a bond-building treatment

Chemical services and thermal styling break the internal bonds that keep each strand strong and elastic. A weekly bond-builder works below the cuticle to relink them, so hair feels denser and snaps far less.

Rethink your heat routine

You don't have to give up your straightener — you just have to be smarter about how you use it. Always work on fully dry hair, keep tools below 180°C.

Repair isn't a single product — it's a routine you can actually keep up.

The daily routine

  • Shampoo less and condition more
  • Always use a heat protectant
  • Deep-condition with a weekly mask
  • Swap to a silk pillowcase
  • Trim every 8–10 weeks
A weekly mask keeps mid-lengths supple.

Give it a full month before you judge the results — bonds rebuild gradually, and the difference tends to show in how your hair feels long before it shows in the mirror.

Full column, 48/16/64 padding, 32px rhythm, dot markers, 230px figure

Product card

The repeatable product unit for grids, carousels and search results - a white sharp-cornered card of a contained product image on a white media band (badge overlay top-left, 44x44 quick-add bottom-right, hover-revealed add-to-bag bar) over an info block (brand + rating row, 2-line clamped name, price with compare strike, low-stock meta row reserving height via visibility so grid siblings stay equal). Sale and low-stock are data-driven; hover / focus-within scales the image 1.03 with a shadow lift at 200ms standard ease. Master (VEvUv desktop / FxesC mobile); the drawn detached Add Button hover layer and disabled wishlist heart are normalised per the scope brief, and quick-add publishes the cart events the flyout consumes.

Default cards (live store products)

Default (rating + quick-add + meta row)
Default (second product)

States - badge override, no quick-add, no meta

Best-seller badge override
No quick-add (show_quick_add: false)
No meta row (show_low_stock: false - carousel hosts)

Sale state (data-driven compare-at pricing)

Low stock
Sale (Save n% badge + sale price + compare strike)

Equal-height grid probe (meta row reserves height via visibility)

Narrow host (163px = mobile board width)

163px host (4/5 media aspect under 750px)

Hover / focus specimens (forced)

Hover (image scale 1.03 + shadow)
Focus-within (same as hover - keyboard parity)

Pair it with

PDP upsell rail (masters t87YC hzIKI desktop 380w / rGMtZ fHccb WI5Wd mobile band) - an eyebrow heading over up to three 94px rows: 70x70 cover-fit thumb on a white underlay, uppercase 12px brand line, one-line truncated 14px name linking to the item's PDP, the price snippet's upsell context (14px/500) beside a compact 13px star rating right-aligned on the same line, and a 40x40 green quick-add button (registered plus glyph at 20px white; the board's icon layer is named "Caret" but the glyph is plus-thin). Eyebrow DM Sans 600 #94A73B, 16px desktop / 20px mobile, -0.5px tracking, 32px above the first row; rows carry a 1px hairline bottom rule; add button hovers -1px over 120ms, focuses 2px ink outline offset 2 (note zVXAO). Two states on one component: default "Pair it with", and in-stock-alternatives-on-oos - a sold-out host relabels the eyebrow to "In stock alternatives" at BOTH breakpoints and filters OOS rows (note ddbIH; the desktop board keeping the default label is a recorded omission, caveat 2). Multi-variant items link through to their PDP instead of blind-adding (caveat 3, build-to-convention); the add is a real form POSTing routes.cart_add_url fetch-first, spinner loading state, publishing the shared cart:add event so the flyout opens (note DeGcR - the flyout reuses this snippet at limit 2). Rows render from live complementary-product data - the boards' repeated placeholder row is never reproduced (caveat 1). Empty source renders nothing at all. Masters hzIKI (desktop) / WI5Wd (mobile) + CTByd (OOS); notes ddbIH, DeGcR, zVXAO.

Default rail (live store products, 380px host column)

Desktop 380px column / mobile band (resize under 750px)

In stock alternatives (note ddbIH - eyebrow relabels at BOTH breakpoints)

Sold-out host relabels the rail to the recovery path; OOS rows filter out

Cart-flyout reuse (limit: 2, note DeGcR)

Flyout instance caps at 2 rows; add inserts without closing the flyout

Multi-variant link-through (caveat 3 - build-to-convention)

Multi-variant items link to their PDP instead of blind-adding

Empty (no complementary products - nothing renders at all)

Empty source: no eyebrow, no rows, zero DOM above this line

Category tile

Whole-tile link card - cover-fit image band at a 4/5 aspect and a centred label stack (name + optional count). Products-count (12px uppercase ink) and brands-count (13px muted sentence case) treatments, hide_count boundary, no-collection placeholder band, the label-start host modifier and a 140px mobile grid-width specimen. The master specimen renders a real store collection.

Master specimen (180px grid width - live store collection, count on)

Shampoo Shampoo Default (image + name + products count)
Shampoo Shampoo Brands treatment (13px muted sentence case)

Count boundary - hide_count omits the element entirely

Shampoo Shampoo Count hidden (hide_count: true)

No collection - placeholder band + custom title and link

Shop all No collection (placeholder band, custom title + url)

Host variants - label-start modifier and 140px mobile width

Shampoo Shampoo label-start (left-aligned stack, 16px padding, black name)
Shampoo Shampoo 140px mobile grid width (4/5 band, name wraps max 2 lines)

Brands intro

Brands-page top module - the page H1 ("Brands, from A-Z", 48px/500/1.05/-1px ink desktop, 30px mobile) over a computed "n brands" count (13px/400/1.4 black; brand counts, never product counts, per note MO0PS - the boards' 87 is a 2026-08-09 specimen the build computes, never types) above a 4-up row of category-tile blocks: 4:5 cover image bands (405px at 1344 content width desktop, 188px on the fixed 150px mobile tiles), centred label stacks (name 16px/500/1.2/-0.5px ink over a 12px gap, count 13px/400 muted), 16px desktop / 12px mobile gaps. Mobile is a horizontal swipe row - overflow-x auto, no scroll snap designed, the 636px-wide gy82u frame overflows the 390px board deliberately (recorded caveat, not a defect). Tile counts come from the block's count-override text (the shared brands dataset with brands-directory replaces it); the title-block total comes from the section's brand_count_total setting, computed at build (86 visible brands as at 2026-08-09). No hover state designed; focus-visible rings from the category-tile snippet. Masters Fzm9t + Owwrg (desktop, inside RLQWg) / qB8bC + gy82u (mobile, inside HraQY). Section atom - the live instance renders on /pages/preview-sections-2; this entry is the placement anchor in build order.

Desktop master state (H1 title block + 4-up row, Fzm9t + Owwrg)

Brands, from A–Z

87 brands (design specimen - live renders the computed count)

Mobile master state (30px title, 150px swipe row, qB8bC + gy82u)

Mobile row intentionally overflows its 390px frame - the swipe row is the design (gy82u is 636px wide, flagged partially clipped). Demo counts are the boards' 2026-08-09 specimen values; the live section computes counts from store data.

Brands directory

Section - the A-Z body of the Brands page (desktop body RLQWg: jump uZxeO + rule htdJA + Directory US9YC 1344x1552; mobile HraQY: jump NIhBT + rule Ny4QO + Directory l3ouXS 358x4895; note MO0PS). The az-jump-bar snippet renders first, then a full-width 1px hairline rule, then the directory: desktop four 300px columns at a 48px gap (groups at 48px vertical gap, 32px between jump/rule/columns), mobile one column (32px gaps, letters 18px). Each group is a letter heading (24px/500/1.2/-0.3px ink desktop over a 12px gap), a hairline rule and the 38px brand rows (16px/400/1.4 ink links at 8px vertical padding) - tagged rows carry an 11px/400 uppercase accent (#374426) label 12px after the name (tracking deliberately unset, caveat 5; $sg-font-mono resolves to DM Sans, caveat 6). Status labels: OUR BRAND on Synergy Hair is factual, NEW on Amika and TRENDING on K18 are illustrative and must come from the brand metafields (note MO0PS) - the section's label path is the brand metaobject's status_label enum (none/our-brand/new/trending/hidden); a clean store renders every row untagged. Hidden brands (design: Dyson, the deliberately disabled 87th entry) render nothing and are excluded from groups, live_letters and counts. Column ranges are merchant settings defaulting to the design's A-D / E-K / L-P / R-Z (the designer's judgment call, caveat 2) with a computed contiguous fallback; a letter group never splits across columns. Data: brand metaobjects when the definition exists, else the merchant-bound brands_menu link_list (the seed store's 86 brand collections are the menu contents; /brand/ slugs remap to /collections/ per the redirect map) - Ship links to brand collection URLs (caveat 3, per-brand landing template still open). Group anchors #brands-<letter> are the jump-bar contract with scroll-margin-top 96 above the sticky header; an IntersectionObserver scrollspy in the section owns the jump bar's active-letter state. No search, no category tabs, no product counts (deliberate omissions per MO0PS). The desktop Amika NEW chip (JW87h solid $sg-green 50x28) is a recorded divergence - one mechanism, the inline accent label (caveat 1). Section atom - the live instance renders on /pages/preview-sections-2; this entry is the placement anchor in build order.

Master specimen (uZxeO + htdJA + US9YC desktop, 2026-08-09 dataset)

Mobile shape (l3ouXS - single alphabetical column, 18px letters, 32px group gap)

Status labels shown: K18 TRENDING and Synergy Hair OUR BRAND (the design's factual OUR BRAND + one illustrative label; NEW on Amika ships only from the brand metafield - clean store renders it untagged). The hidden Dyson row is excluded per the design. The desktop Amika solid chip (JW87h) is a recorded board divergence - the build uses the inline accent label everywhere (brief caveat 1).

Campaign tile

Whole-tile campaign link - a cover-fit image band over a translucent cream-40 panel (uppercase eyebrow, heading and the shared text-link primitive) vertically centred in the remaining height. Master specimen at the 250px DS size, the 394px New In desktop column and 187px mobile host proportions (media band retuned via the aspect custom property), plus the no-image placeholder band and the no-url non-link rendering. No tile-level motion by design: hover is the text-link underline slide only. Master (TkJV0); the master's detached Text Link copy (arrow glyph clipped in the .pen) is normalised through the shared text-link snippet.

Master specimen (250px - 4:5 media band, cream-40 panel)

New inCampaign headline Shop the range Default (placeholder band until an image is set)

Host proportions - New In desktop column (394px, media band override) and mobile (187px)

New inThe new season edit Shop the range 394px column (media 405px band via the aspect custom property)
New inThe new season edit Shop the range 187px mobile (media 213.75px band)

Boundaries - no image, no url

EditorialNo image selected Shop the range No image (surface-alt placeholder band)
EditorialNo link set Shop the range
No url (renders as a non-link div)

Service card

Salon service / stylist card - a white clipped card of a cover-fit image band (placeholder band when no image is set) over a body block holding an intro group (title + description above a bottom hairline) and an optional meta row (from price left, underlined book-now text link right). The meta row renders only when a price or a book cue is set - a card with both blank is the meta-disabled stylist variant. Hover lifts the image to scale(1.03) with a shadow at 200ms standard ease and slides the link underline. Master (CzIpL), mobile ref (dJrLi); the drawn detached Button/Hover layer is an unadopted exploration and the arrow defect of the detached text link is normalised through the shared text-link snippet.

Master specimen (380px - price + book cue)

Cut & style

A precision cut and blow-dry finish, tailored to you by our senior stylists.

Default (image placeholder master state + meta row)

Meta-disabled (stylist variant) - meta row absent from the DOM

Senior stylist

Colour correction and balayage specialist with twelve years behind the chair.

No price, no cta (meta row omitted, intro hairline stays)

Mobile width - 273px swipe-row card

Cut & style

A precision cut and blow-dry finish, tailored to you by our senior stylists.

273px host width (meta stacks, media 240px)

Session stylist

Editorial and event styling.

273px stylist card (meta-disabled + no image)

Salon card

The repeatable unit inside an expanded city group on the Salons index - name (the salon detail link), two-line address, tel:/mailto: contact rows in the accent colour and a compact primary Book appointment button (12px/24px padding, 37px box). No card chrome: the surrounding accordion provides the grouping. No photography by explicit design decision. Renders from a live salon metaobject; optional rows are omitted from the DOM when absent. Master (coLxs), desktop ref (jIvSg); the note's muted-body call is superseded by the drawn ink name and black address.

Master specimen (380px)

Bayfair Shopping Centre

Bayfair Shopping Centre 19 Girven Road Mount Maunganui Tauranga 3116
Live salon drop (380px DS board)

Desktop host (358px - two per row in the city accordion)

Bayfair Shopping Centre

Bayfair Shopping Centre 19 Girven Road Mount Maunganui Tauranga 3116
358px host cell

Bayfair Shopping Centre

Bayfair Shopping Centre 19 Girven Road Mount Maunganui Tauranga 3116
Second cell (row pairing)

Mobile host (342px - stacked one per row)

Bayfair Shopping Centre

Bayfair Shopping Centre 19 Girven Road Mount Maunganui Tauranga 3116
342px host width (breakpoint-identical)

Optional-absent (no email passed, no booking url)

Bayfair Shopping Centre

Bayfair Shopping Centre 19 Girven Road Mount Maunganui Tauranga 3116
Email row and actions omitted from the DOM

Cart flyout

Global mini-cart drawer - a right-anchored fixed panel (440px desktop / 340px mobile, 100dvh) with a fixed header ("Bag (n)" + close), a body region that is the only scroll surface (line items with thumb, name, quantity stepper, line price and remove control + the Pair-it-with upsell block capped at 2 rows) and a fixed footer (shipping / discount / total rows, full-width Checkout, returns line). Filled (YDQI0 desktop / Noap1 mobile) and empty (z8jjzP / C080G) masters: the empty state swaps in place with the upsell relabelled Bestsellers and the footer reduced to the returns line. Focus trapped, Esc closes, body scroll locks; bin removes with a 5s undo toast; quantity changes and removals re-render via the Section Rendering API. The undo toast has no board (asserted at runtime only); the flat shipping figure reads the flat_shipping_rate theme setting.

Desktop filled (master YDQI0, 1440)

Desktop empty (master z8jjzP) - empty state, upsell relabelled Bestsellers

Mobile filled (master Noap1, 390 - 340px panel)

Mobile empty (master C080G)

Page toc

"On this page" table of contents for long policy-style pages (desktop fx6TH inside the Generic board's two-column region u9EfXm; mobile l7tXa card above the Ho2TK prose stack). Desktop (>=750px): a 280px sticky left column of plain anchor labels beside the 760px prose column - grey caption (12px, uppercase via CSS text-transform), inactive links 13px/400/1.4 in black, 12px item gaps, hover shifts to accent with an underline at 120ms standard ease. Mobile: an in-flow card above the prose (white surface, 1px hairline border, 24px padding) - black caption, links 13px/400/1.2 in ink at 8px gaps, numbered "1. Introduction" style via CSS counters on the ol (never typed into strings). The list is built from the prose container's h2 headings (ids slugified, handleize-equivalent) so merchant-edited pages stay in sync; works with zero JS once the host slugs anchors, the page-toc custom element owns list build + the IntersectionObserver scrollspy (exactly one active link: accent, weight 500, aria-current="true", 120ms colour transition) and re-syncs on shopify:section:load. Focus draws the 2px accent ring at 2px offset; smooth scrolling honours prefers-reduced-motion. Renders nothing when the prose has fewer than two h2s. Snippet atom - demo here pre-populates the privacy-policy section set; the live instance renders inside the page template's main section beside page.content.

Desktop-anchors specimen (fx6TH - 280px sticky column, first item active)

On this page

    Mobile-numbered card (l7tXa - hairline-bordered, CSS-counter list)

    On this page

      Header

      Global header composite (masters JRYTx desktop 1440x149 / v50HJj mobile 390x98, note the desktop master embeds the 32px announcement band owned by the announcement-bar section) - a desktop main bar (73px: 200x35 logo, flexing 41px cream-40 search field with 14px/300 grey placeholder and a green 20px glass glyph, three 23/24px ink action icons with the bag count overlaid inside the glyph at 10px/600 white) over a 44px category strip (1px hairline top+bottom, 13 linklist-driven items at 14px/500 -0.5px in black with Sale in the sale colour, and the 28px green Book button) and a mobile bar (56px top bar: 24px hamburger left, centred 150x20 logo, account 23 + bag 24 actions at gap 16, no wishlist) plus the full-bleed 42px cream-40 search pill (14px/400 placeholder, green 22px glass). One overlay at a time: the bag click dispatches header:cart:open, the search field/pill header:search:open, the hamburger relies on nav-drawer's own [data-nav-drawer-open] delegate; each overlay's element closes the others. The bag badge subscribes to cart:updated and /cart.js so it tracks every cart mutation. Renders live on /pages/preview-sections as a real section instance.

      Desktop master state (JRYTx, 1440 - excludes the embedded announcement band)

      Mobile master state (v50HJj, 390)

      Bag badge states (manifest state bag-badge-count)

      Empty cart - digit hidden Count 2 - 10px/600 white inside the glyph Two digits - same 14x14 badge frame

      Cart addons

      Cart page quick-add strip (boards rVDkr desktop / pt05t mobile) - a space-between header ("Add to your order:" green eyebrow, sentence case per the checklist, over the right-aligned merchant qualifier label) above up to three product cards: 70x70 desktop / 70x84 mobile white cover-fit thumb (the only link), 12px brand line in border-grey, 14px name (400 desktop / 500 mobile per board), the price snippet's upsell context and a 40x40 green quick-add button (registered plus glyph, 20px white; the board's icon layer is named "Caret" but draws plus-thin). One DOM, two layouts on the single 750px query - desktop 3-up fill row (gap 12), mobile stacked rows (gap 16); hairline bottom on every card; radius 0 throughout. Cards exclude products already in the cart and unavailable products; quick-add POSTs the first available variant and publishes cart:updated only (never cart:add - on the cart template the page IS the cart surface and the flyout must not open, brief section 6); the qualifier renders as a bare bold label (the pill primitive has no text-only variant at these metrics, caveat recorded). Renders live store products (the boards' repeated "Hydrate Condition 350ml / NAK HAIR / $36.95" filler is not reproduced); add controls render real but inert in the demo - the live cart page owns the commerce contract. Snippet atom - the live instance renders inside sections/cart-items after the line rows.

      Default strip (live store products, 3-up desktop row)

      Add to your order:

      Under $25
      • Eleven Miracle Hair Treatment 125ml

        Eleven

        Eleven Miracle Hair Treatment 125ml

        $31.00
      • Ghd Original Hair Straightener

        Ghd

        Ghd Original Hair Straightener

        $267.00
      • K18 Leave-In Molecular Repair Mask 50ml

        K18

        K18 Leave-In Molecular Repair Mask 50ml

        $123.00
      Desktop 3-up row / mobile stacked rows (resize under 750px)

      Limit 2 (caps the row regardless of collection size)

      Add to your order:

      • Eleven Miracle Hair Treatment 125ml

        Eleven

        Eleven Miracle Hair Treatment 125ml

        $31.00
      • Ghd Original Hair Straightener

        Ghd

        Ghd Original Hair Straightener

        $267.00
      Exactly 2 cards render

      Empty (no candidates - nothing renders at all)

      Empty source: no header, no cards, zero DOM above this line

      Write a review

      Review-submission drawer (master Vgflx, host board G3hTIa; note YpZMY) - a right-anchored 480px fixed panel of full viewport height over the shared scrim: 64px header (12px/500 eyebrow title + 24px close), a body that is the only scroll region (48px product row with vendor + name, the 32px 5-star rating radiogroup - the only required field, headline/review/name/email through form-input at the drawer's borderless #F2F1EE 48px treatment, 72px photo dropzone with the 18px camera icon) and a footer (full-width 48px green Submit + 12px fineprint). Opens from the pdp-reviews ratings-block trigger (review:write-open, aria-controls contract) and the #write-a-review deep link (post-purchase email path); Esc / scrim / close close; focus trapped and returned; body scroll locks; single-drawer rule with the flyout/nav drawers; 300ms entrance/exit slide matched to the cart flyout. Submitting / success / error are build-to-convention per YpZMY (spinner + field lock, panel-replacing thank-you, inline field errors); submit posts nowhere until the reviews app lands (data-endpoint override) - the drawer is the skin spec the app gets skinned to. Mobile: same anatomy full-width (no board - drawer convention).

      Open drawer - desktop master (Vgflx, 480px panel)

      480x988 panel: 64px header, product row, 5-star radiogroup (4/5 drawn), four fields, 72px photo zone, footer submit + fineprint

      Mobile convention (390 stage, full-width panel)

      Same anatomy at full width (no mobile board - drawer convention per s.12)

      Interactive specimen (real open/close, scrim, Esc, rating, submit -> success)

      Click the toggle (or load this page with #write-a-review) - scrim, focus trap, Esc, star keyboard arrows, submit without rating (inline error) then with one (submitting -> success swap)

      Hero

      Section - a full-bleed campaign image banner with bottom-anchored copy over a scrim (headline 52px/700 desktop, 42px/700 mobile; primary + secondary CTAs) and a forest USP strip in normal flow beneath (desktop only). Masters uLVnp (desktop 1440x753) / IT5yL (mobile 390x631). Boards' copy defects fixed at build ("Lift" not "Life", "Visit us" not "Visist Us"); secondary CTA border is the measured cream #F2F1EE (the scope brief's #1A1A1A transcription loses to the export); mobile primary CTA fill is the $sg-accent-deep instance override; the 20px board gutter deliberately overrides --page-margin (designer flag open). Renders on /pages/preview-sections as a real section instance.

      Desktop master (660px banner + USP strip)

      Lift Your Hair
      Game with K18.

      Powered by patented peptide technology, K18 works at a molecular level to reverse damage from bleach, colour and heat.

      Shop 70+ professional brands chosen by our stylists, or book your next visit at one of 15 salons nationwide.

      Book appointment

      Mobile master (390px frame, stacked CTAs, no strip)

      Lift Your Hair Game with K18.

      Powered by patented peptide technology, K18 works at a molecular level to reverse damage from bleach, colour and heat.

      Strip hidden (show_usp_strip: false - strip absent from the DOM)

      Lift Your Hair
      Game with K18.

      Powered by patented peptide technology, K18 works at a molecular level to reverse damage from bleach, colour and heat.

      Placeholder image (no image set)

      Lift Your Hair
      Game with K18.

      Powered by patented peptide technology, K18 works at a molecular level to reverse damage from bleach, colour and heat.

      Hero - Split

      Two-panel split hero - a solid brand panel (brand lockup or text fallback, headline, body copy, inverse CTA) beside edge-to-edge media. Panel colour is a six-preset select (background + foreground flip together); desktop splits 480:960 at 720px tall, mobile stacks 1:1 media above the panel. Section atom - the live instances render on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (forest panel, 1:2 split, brand-text fallback)

      MUK HAIRCARE

      Built For Big Hair Days.

      Salon-professional styling built for volume, hold and shine - whatever your hair decides today.

      Shop now

      Demo scope: the section stylesheet is scoped to the section bundle, so this matrix re-declares the identical rules below at demo scale (the demo frame is 720px wide, so the panel reads at half the master's 480:960 split). The real section instances live on /pages/preview-sections.

      Mobile master state (1:1 media above the panel, full-width CTA)

      MUK HAIRCARE

      Built For Big Hair Days.

      Salon-professional styling built for volume, hold and shine - whatever your hair decides today.

      Shop now

      Panel colour presets (background + foreground flip together)

      MUK HAIRCARE

      Big Hair Days.

      Forest deep

      MUK HAIRCARE

      Big Hair Days.

      Forest

      MUK HAIRCARE

      Big Hair Days.

      Blush

      MUK HAIRCARE

      Big Hair Days.

      Taupe deep

      MUK HAIRCARE

      Big Hair Days.

      Hot pink

      MUK HAIRCARE

      Big Hair Days.

      Green

      Brand logo slot (logo renders, text fallback hidden)

      Built For Big Hair Days.

      Salon-professional styling built for volume, hold and shine - whatever your hair decides today.

      Shop now

      Media left variant (panel right)

      MUK HAIRCARE

      Built For Big Hair Days.

      Salon-professional styling built for volume, hold and shine - whatever your hair decides today.

      Shop now

      Shop by category

      An inset section header (eyebrow, heading, optional shop-all text link) above a full-bleed category-tile grid - 4-up with a 4px gap at >=750px, 2x2 with 4px column / 32px row gaps below. Tiles are category-tile blocks (2-8, merchant added and reordered) carrying collection image, name and live product count. Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (4-up full-bleed row, header inset 20px, e52P6)

      Live tiles carry the product count ("n products"); the demo tiles are collection-less, so the count is omitted (placeholder bands, no counts - matching the boards' disabled count layer).

      Mobile master state (2x2 grid, accent eyebrow, no link, lCmyP)

      Shop by brand

      Cream content section - the inset section-header (eyebrow + heading left, Shop All Brands primary button right at 45px) above rows of brand tiles: a cover-fit photograph under a 55% black scrim with the brand's white wordmark centred, the whole tile a link. Desktop 2 rows x 6 tiles of 227px, mobile 3 rows x 2 tiles of 180px, 8px gaps both axes, 1px hairline stroke per tile; hover eases the scrim to 65% (undrawn, build-to-convention). Tiles are brand-tile blocks (2-12) or a collection-list fallback (collection image + title). The marquee layout was removed at QA 2026-09-02 (designer call): it was never enabled on a template and never had visual sign-off, so the grid is now the section's only layout. Masters uFsuS (desktop 1440x754, band j4TvgF) / HS8vs (mobile 390x829); the home bespoke copy GBzYP is retired by the manifest normalise decision. Boards' placeholder artwork (repeated Olaplex/Kerastase pairs, hidden "ghd" texts) is not reproduced. Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (2 rows x 6 tiles, 227px, uFsuS / j4TvgF)

      Salon exclusive

      Our exclusive brands

      • Olaplex
      • Kerastase
      • ghd
      • K18
      • KEVIN.MURPHY
      • Redken
      • Olaplex
      • Kerastase
      • ghd
      • K18
      • Redken
      • Matrix

      Live tiles bind the merchant brand blocks (photo + white wordmark); the demo uses placeholder bands and text fallbacks - the boards' repeated Olaplex/Kerastase artwork and hidden "ghd" text nodes are placeholder noise, not registry assets.

      Mobile master state (3 rows x 2 tiles, 180px, HS8vs)

      Salon exclusive

      Our exclusive brands

      • Olaplex
      • Kerastase
      • ghd
      • K18
      • KEVIN.MURPHY
      • Redken

      Just landed

      Cream content section - the inset section-header (heading-only in the boards, eyebrow and link slot supported but off) above a row of large wide-format campaign tiles: a 4:3 cover image over a label stack (live product count, 24px/600 title, 50%-opacity body, underlined Shop now text-link). Desktop shows two tiles side by side in a 20px-inset row with the master's 0px gutter (board value, flagged to the designer); mobile stacks the tiles at a 16px inset. Tiles are just-landed-tile blocks (this section's own bespoke wide format, not the 4:5 campaign tile); category-tile blocks can mix in (drawn but disabled in the boards). Masters W8XWf9 (desktop 1440x863) / CRTh9 (mobile 390x922). Boards' copy defects fixed at build ("Anika" ships as "Amika", the second tile carries its own body, counts render live from collection.products_count - the boards' "82 PRODUCTS" literals are stale). Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (two-up row, 20px inset, 0 gutter - W8XWf9)

      Demo scope: the section stylesheet is scoped to the section bundle, so this matrix re-declares the identical rules below. The real section instance (with live collection counts and imagery) renders on /pages/preview-sections.

      Mobile master state (stacked tiles, 16px inset - CRTh9)

      Eyebrow + header link on (settings states, both disabled in the boards)

      Eyebrow renders through the section-header caption slot (accent/500 mobile, black/400 desktop - the shop-by-category treatment); the header link renders through the slot's underlined text-link unit.

      Count off + no collection (show_count: false, collection absent)

      With show_count off (or no collection bound) the count element is absent from the DOM; an unbound tile renders the surface-alt placeholder band (matching the category-tile convention).

      Promo offer

      Full-bleed 730px offer banner - top-left reversed offer logo, bottom-left optional 163x221 tile, and a vertically-centred content row at inset 20 (32px/700 white headline left, primary + cream-stroke secondary CTAs right). Mobile (<750px) is a 555px panel with a 20% black scrim, the logo at top and the headline + 46px inverse CTA anchored bottom. CTAs render through the shared button snippet; the cream #F2F1EE secondary stroke is a section-scoped override, never a snippet fork. Eyebrow and subtext are settings-only (designer note OQs7O) and render only when populated. Masters G8ZAwD (desktop 1440x730) / mcYwc (mobile 390x555).

      Default — logo + tile + CTA pair

      A complimentary treatment and travel kit with any order over $99.

      State — secondary CTA off (primary alone, alignment preserved)

      A complimentary treatment and travel kit with any order over $99.

      Reviews (home)

      Homepage social-proof quote carousel - one review visible at a time: the review-count caption (mono 12px uppercase), the big green blockquote (the quote IS the heading, 40px/500 lh 1.05 desktop, 30px mobile), the Shop now text-link (desktop only - the mobile master disables the link layer), and the attribution stack (5 star icons, product name in black, reviewer in grey). A zero-padded 01-10 pagination row (active numeral green, inactive grey) drives the paginated carousel; mobile advances by horizontal swipe; no autoplay. Masters YphOj (desktop 1440) / jVg5s (mobile 390). Stars render through the rating-stars snippet (14px icons) instead of the master's text glyphs; the master's clipped SHOP NOW link renders fully through text-link. Home only - a different component from PDP reviews (FAjXA). Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (YphOj, 1440)

      170,000 customer reviews

      “This MUK straightener has won me over. It glides over my hair effortlessly and straightens with just one swipe.”

      Muk Hair Straightener Original Style Stick

      Debbie

      Mobile master state (jVg5s, 390)

      17,000+ certified reviews

      “This MUK straightener has won me over. It glides over my hair effortlessly and straightens with just one swipe.”

      Muk Hair Straightener Original Style Stick

      Debbie

      Salon services

      Homepage services band - a full-bleed 850px media band (cover image under a flat 65% black overlay, centred over-title, 40px/500 headline and a primary Book a visit CTA) above a white body: a two-column intro (eyebrow + 34px/600 heading left, right-aligned 14px lead + ALL SERVICES text-link right), a 3-up service-card grid (16px gap; mobile a 273px scroll-snap swipe row), and the Visit CTA - a 50/50 image | forest panel band (eyebrow, 40px heading, lead, white primary + cream-stroke secondary buttons; 430px tall desktop, image stacked above panel mobile). The Visit CTA is fully clipped in both masters (dmLJ0 fixed at 821px vs ~1123px content) - it renders at build per the manifest defect (checklist 13); the mobile CTA layout is build-to-convention. Masters W045n (desktop 1440, as instanced at S2Yaug) / klNs8 (mobile 390); cards are service-card blocks (1-6). Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (W045n, 1440)

      The Full Salon Experience

      Book a chair with our senior team. We’ll match you with the right stylist for your hair, your goals and the look you’re after.

      Book a visit

      Salon services

      The full salon experience

      From precision cuts to bespoke colour and restorative treatments, our senior stylists shape every appointment around you.

      All services
      • Cut & style

        A precision cut and blow-dry finish, tailored to you by our senior stylists.

      • Colour

        Expert balayage, foils and gloss using premium, kind-to-hair colour systems.

      • Treatments

        Restorative in-salon treatments to repair, hydrate and strengthen your hair.

      Mobile master state (klNs8, 390)

      The Full Salon Experience

      Book a chair with our senior team. We’ll match you with the right stylist for your hair, your goals and the look you’re after.

      Book a visit

      Salon services

      The full salon experience

      From precision cuts to bespoke colour and restorative treatments, our senior stylists shape every appointment around you.

      All services
      • Cut & style

        A precision cut and blow-dry finish, tailored to you by our senior stylists.

      • Colour

        Expert balayage, foils and gloss using premium, kind-to-hair colour systems.

      Social

      Instagram-feed band - the centred handle heading (@synergyhairnz, rendered through the section-header snippet's title slot) above the post tiles. Desktop: one flex row at a 20px inset - flank labels ("Instagram" accent-deep left, "Follow us" black right, both 178px, trail right-aligned) pinning the row edges around four equal posts (251:362, hairline outline per tile); mobile: full-bleed 2x2 grid (4px gap, 193:257 tiles) with the "Follow us" heading below as a 26px footer heading. Every post is one external anchor (translated "View post N on Instagram" accessible name; images are content-less to AT); both breakpoints draw the headings centred - the scope brief's caveat-2 flush-left reading does not match the live source (host instance yV56V draws the title at a 16px margin with text-align: center; recorded in the dispatch decisions). Posts are section-declared post blocks (image + alt + link, up to 8, 4 drawn); the hover zoom (1.03, 300ms cubic-bezier(0.4, 0, 0.2, 1) inside the overflow-hidden tile) is build-to-convention, undrawn; zero JS. Masters cO1Fu (desktop 1440x595) / Q7M37a (mobile 390x748); note uyx2D; host instances O7BQer / yV56V. Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (flank row, 251:362 posts, cO1Fu)

      Mobile master state (2x2 full-bleed grid, 193:257, Q7M37a)

      Live tiles bind the merchant post blocks (image + Instagram permalink); the demo uses hairline placeholder bands - the masters' Unsplash feed artwork is dynamic content, not a registry asset.

      Salon service strip

      Salons-page service strip - a full-bleed white band of three service tiles directly under the breadcrumb (desktop: 3 equal columns at a 4px gap; mobile: a 273px scroll-snap swipe row at the as-designed 16px gap). Each tile is ONE link of a 16:9 cover image band (477x268 desktop / 273x154 mobile, labels BELOW the image, never overlaid, body row with its own 12px/16px inset) over the name (DM Sans 500 16px ink) and from-price (DM Sans 400 13px black) on one space-between row. Hover/focus underlines the name at 120ms standard ease (build-to-convention, undrawn); the tile anchor draws an inset 2px accent focus ring. Replaces the decorative Salon Banner - "the decoration becomes navigation" (note walft); THREE tiles only (Cut & style / Colour / Treatments - a fourth was tried and cut), prices are placeholder strings by design. Masters STnzc (desktop 1440x314) / YnrcG (mobile 390x200); the shipping spec is the page instance wW7zi whose padding-0 override makes the band full-bleed (the master's default [0,48] inset would break the 16:9 band). Tiles are service-tile blocks (limit 3). Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (STnzc, 1440, full-bleed per wW7zi)

      Mobile master state (YnrcG, 390 - 273px swipe row)

      City accordion

      Salons-page index column - the page h1 ("Salons", 48px/500/1.05/−1px ink desktop, 30px mobile) over a hairline-ruled list of city rows, one per city on the salon metaobjects (sorted alphabetically at render time, counts computed never typed). Every row is one whole-width button (city name 17px/500 + cream-40 count chip 13px + 18px caret) whose panel holds that city's salon cards through the salon-card snippet: 2-up grid (48px row gap, 32px column gap) indented 32px under the city name on desktop, 1-up stacked indented 12px on mobile; rows 74px closed desktop / 58px mobile. All cities collapsed on load, one city open at a time (custom element city-accordion): publishes salon-city:toggle {detail:{city, open}}, subscribes salon-city:open (salon-map pins) and location.hash deep links with scroll-below-sticky-header, 300ms grid-rows panel transition + 200ms caret rotation under prefers-reduced-motion. Data-driven - no blocks; ALL salon content is live metaobject data (placeholder warning AFZvQ: replace from the client's salon list before any client presentation). Desktop the section is the left 780px column of the two-column body beside the salon-map (500px, 64px gap) - that band is template-level layout owned by page.salons. Deliberate omissions per WtTyr: no search, no nearest-to-me, no map filtering, no opening-hours badge. Masters dsh3D (desktop collapsed) / zkNm0 C5n7BH (desktop expanded) / JNkdY (mobile collapsed) / VqxOj t3YQz (mobile expanded). Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop collapsed master (dsh3D, 780w)

      Salons

      Desktop one-city-open state (Auckland expanded, live metaobject cards)

      Mobile collapsed master (JNkdY, 358px list)

      Stylist cards

      Salon-detail stylist band - SPEC-ONLY ATOM, no board exists (verified by whole-document search; the boards end at the detail body). Built from the written spec (mq8Fo: "component/Service Card rendered with the Meta row disabled - the block already supports this, so no new component was created; desktop 3-up grid; mobile a 273px horizontal swipe row matching the mobile Salon Services pattern already signed off") and the Salon data note (mO6yq: "Stylist photography does not exist - worth designing a name-only fallback"). Cards render exclusively through the service-card snippet, meta-disabled (name + bio only, no price, no book cue, nothing focusable - cards are not links). Desktop (wmsHc pattern): 3-equal-column grid, 16px gaps, cards 469px at 1440. Mobile (w8tE0h pattern): a 273px scroll-snap swipe row at a 16px gap (pitch 289), bleeding to the screen edge. Two fallback states from the missing_photo_mode setting: the labelled placeholder band (surface-alt with the centred 11px uppercase muted label - build-to-convention, the master's band is the bare fill; copy to confirm with the client) and the name-only fallback (media band omitted entirely - the contractual state while photography is unconfirmed). Data path: the stylists metaobject_list (name/bio/image fields) plus optional service-card blocks for manual curation. No JS, no events; empty state renders no section output. Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop 3-up grid (wmsHc pattern, 16px gaps)

      • Jess Bennett

        Senior colourist with twelve years behind the chair, specialising in bespoke balayage.

      • Marcus Cole

        Precision cutting and barbering, trained in London and Sydney.

      • Ava Whitfield

        Texture specialist - curls, coils and restorative in-salon treatments.

      Name-only fallback (media omitted - photography unconfirmed)

      • Jess Bennett

        Senior colourist with twelve years behind the chair, specialising in bespoke balayage.

      • Marcus Cole

        Precision cutting and barbering, trained in London and Sydney.

      • Ava Whitfield

        Texture specialist - curls, coils and restorative in-salon treatments.

      Mobile 273px swipe row (w8tE0h pattern)

      • Jess Bennett

        Senior colourist with twelve years behind the chair, specialising in bespoke balayage.

      • Marcus Cole

        Precision cutting and barbering, trained in London and Sydney.

      Salon detail info

      Salon-detail body - the page H1 (salon name, 48px/500/1.05/-1px ink desktop, 30px mobile), Salon Hours (an 11px uppercase mono label over day-range rows, day left in ink, time right-aligned in black, 12px gaps), a contact stack (18px icons: address map-pin in muted with plain-text ink - never a link; phone tel: and email mailto: in accent) and the Book / Price list action pair (one button-snippet primary at the Button/Primary green, one secondary - the mockup's two-greens defect corrected per mq8Fo) beside the salon image (818x540 desktop, 358x220 mobile). Desktop a two-column grid (464px info column, image right, 64px gap) inside the page cap; mobile stacked Title > Image > Hours > Contact > Actions at a 32px rhythm, 48/16/64 padding, buttons splitting the row 50/50. Data path: salon metaobject (name, city, address, phone, hours multi-line parsed into rows, booking_url, image) via the section reference setting, the page's custom.salon metafield, then the first salon; hours_row blocks extend the parsed rows. Price list destination is an open client question - the price_list_type setting ships none/file/link/anchor so the decision is config, not code. Masters Na2jm (desktop, inside byx6X at 1440) / k4Psx h2vOvY (mobile, 390). Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop body (464px info col + 818px image, 64px gap)

      Westfield Riccarton

      Salon hours

      Monday – Wednesday
      9:00am – 6:00pm
      Thursday – Friday
      9:00am – 9:00pm
      Saturday
      9:00am – 6:00pm
      Sunday
      10:00am – 6:00pm

      Mobile body (stacked, image 358x220)

      Westfield Riccarton

      Salon hours

      Monday – Wednesday
      9:00am – 6:00pm
      Sunday
      10:00am – 6:00pm
      • 129 Riccarton Road, Riccarton, Christchurch 8041
      • 03 348 8837

      Demo scope: the section stylesheet is scoped to the section bundle, so this matrix re-declares the identical rules below (demo specimens use the placeholder surface - the metaobject image binds at runtime on the live instance). The real section instance lives on /pages/preview-sections.

      Bestsellers

      Cream content section - the inset section-header ("Best sellers edit", accent-deep/600 at 40px desktop / 30px mobile, -1.5px tracking - the live master's drawn value) over the controls row (desktop: collection tabs left, Shop all button right; mobile: button only - tabs are absent from the mobile master) and the product grid (one 5-up row desktop at an 8px gutter / 2x2 mobile at 4px; cards render through the product-card snippet, ~274px desktop / ~177px mobile at 1440/390). Tabs are anchors (24px/600, comma separators as CSS pseudo-elements outside the accessible name; active = green fill + 1px green underline at 2px pad) that fetch the section in place via the Section Rendering API and swap the grid without navigation - the anchors' collection hrefs are the no-JS fallback. Board tabs (All / Treatments / Styling / Repair & Bond / Tools) ship as opt-in tab blocks only - no store data contract, so none are preset; live tab targets: best-sellers / treatments / styling / hair-bond-repair / hair-tools. Shop all renders through the button snippet (the boards' Button/Hover instances do not hard-code the resting state); max products 2-8, the four layout plus-signs stay out of the schema per note C92WG. Masters I8CkWq (desktop 1440x764) / tStvl (mobile 390x941); note C92WG. Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (tabs + controls row, 5-up grid, I8CkWq)

      Mobile master state (heading + button, 2x2 grid, tStvl)

      Best sellers edit

      show_tabs: false (button-only controls row)

      Best sellers edit

      The boards' MUK card data and Button/Hover CTA fills are placeholder - cards render live store products through the product-card snippet and the CTA through the button snippet (the snippet owns the resting/hover states). Tab anchors in the gallery point at the live collections; the real section instance (schema collection + tabs) renders on /pages/preview-sections.

      New in

      Cream carousel content band - the inset section-header (title + caption at 75% opacity, desktop 34/600/-1px accent-deep over mobile 30/500/-0.5px black per caveat 5) with prev/next 44x44 arrow buttons (matched caret-left/caret-right glyphs normalised from the design's mismatched arrow-left-light/arrow-right-thin weights, caveat 6; icon border at scroll-start, ink enabled; disabled + hidden below 750px) over the body: a 394px linked lead-media column (cover, fills the rail column height - no hard-coded height, caveat 7; full-bleed 390x320 above the cream panel on mobile) beside a horizontally scrolling product rail (300px cards at an 8px gutter desktop / 200px at 4px mobile, scroll-snap, bleeding to the right viewport edge via the left-only 20px inset) with the primary Explore-the-range CTA under the rail (button snippet; the boards' "Explore The Range" title case ships sentence case per caveat 2). Cards render through the product-card snippet (show_low_stock: false - the carousel contract); the optional campaign-tile block renders as the first rail item (enabled:false in every comp - shipped off by default, caveat 3); one custom element new-in-carousel owns arrow state (scroll-position-driven disabled swaps, data-at-start/data-at-end) with a native-scroll :not(:defined) fallback. Masters huPlt (desktop 1440x813) / Y2L3DX (mobile 390x953); note PJoH6. Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (header + arrows, 394px lead media, 5-up rail, CTA, huPlt)

      Synergy Favourites

      From heart to cart: these favourites know the route well.

      Mobile master state (full-bleed media + panel, 200px swipe rail, Y2L3DX)

      Synergy Favourites

      From heart to cart: these favourites know the route well.

      Synergy Favourites

      Campaign tile slide (block added - first rail item, board tile disabled in every comp)

      New in

      • New inThe new season edit Shop the range

      PDP reviews

      Product-page reviews band (FAjXA - PDP only, never a landing-page section): the "Loved by you" heading, a fixed 300px summary column (green 60px average, 14px star row via rating-stars, "Based on N reviews" count, a five-row star histogram with data-driven fills on fixed 176px tracks - 231px mobile - with a 4px minimum for non-zero rows per the BARS FIXED note, and a secondary "Write a review" button), and the review-card list (stars + date, 24px title, 16px body, author + Verified buyer over a 1px divider). Masters R3Nuwg (desktop 1440) / dKlvx (mobile 390). Stars render through the rating-stars snippet (14px icons, ink fill); the master's bespoke outline button normalises to the button snippet's secondary variant. Data-driven values only - no hard-coded score, count or percentages (checklist 17). Schema enabled_on restricts the section to product templates. Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (R3Nuwg, 1440)

      Loved by you

      1. 2 weeks ago

        Holy grail treatment

        My hair has never felt stronger. After a month of weekly use the breakage is basically gone and it looks so much healthier and shinier.

        Sarah M

        Verified buyer

      2. 2 weeks ago

        Worth every cent

        Noticed a difference after the very first use - smoother, glossier and far less frizz. I now use it before every single wash.

        Sarah M

        Verified buyer

      3. 2 weeks ago

        Great for damaged hair

        My over-bleached ends finally feel repaired. It takes a little time to apply and rinse, but the results genuinely speak for themselves.

        Sarah M

        Verified buyer

      Mobile master state (dKlvx, 390)

      Loved by you

      1. 2 weeks ago

        Holy grail treatment

        My hair has never felt stronger. After a month of weekly use the breakage is basically gone and it looks so much healthier and shinier.

        Sarah M

        Verified buyer

      Salon map

      Salons-page NZ map - a surface-alt canvas (540px tall desktop column / 240px tall fluid mobile) carrying the inlined nz-map.svg artwork (decorative, aria-hidden, one normalised forest fill - the masters' two-green mismatch normalised) and five interactive city pins (map-pin icon + label chips, absolutely positioned at merchant-set canvas percentages via pin blocks, limit 8). Pins are real buttons: clicking one dispatches salon-map:city-select {detail:{city}} (the city-accordion owns opening the group and scrolling to it) and inverts the chip to the accent fill (build-to-convention). Revert path: deleting the pin blocks leaves the static artwork. Masters jwSKK (desktop 500x540) / iMxu0 (mobile 390x240); the mobile board places the map BELOW the city accordion. Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (jwSKK, 500x540)

      Desktop - active city state (Auckland, build-to-convention)

      Mobile master state (iMxu0, 390x240)

      Product details

      PDP accordion group (masters NmdKf desktop inside the 380px buy column / cvIZY mobile 390 band with 32px 16px padding) - three rows through the shared accordion-row primitive in its PDP dialect: plus/minus icons, 16px/600 titles, 62px headers over 1px hairlines, Description server-rendered open with paragraphs (14/400/1.25 ink) and attribute chips (pill spans, #F2F1EE fill, 8px gap), How to use with accent number tiles (28x28 #374426, 13px/600 numerals, 17px/1.55 step text at 16px gap, 32px panel pad), Best for closed. Rows are independent native details toggles (no exclusivity - caveat 4); empty data sources drop their rows entirely. The group markup is one product-details snippet - section_product-details instances it and product-hero renders it inside the sticky buy column (note w63wsk). Gift-card variant relabels to How to redeem and omits Best for. Schema enabled_on restricts the section to product templates - the live instance renders through templates/product.preview-product-details.json at /products/<handle>?view=preview-product-details, never on /pages/preview-sections. Section atom - this entry is the placement anchor in build order.

      Desktop master state (NmdKf, 380px buy column)

      Description

      A gentle daily shampoo that cleanses without stripping. Signature peptides and Ectoin barrier boost help hair hold moisture between washes.

      Suits colour-treated and dry hair. Sulphate-free, vegan, and made in Australia.

      • Vegan
      • No Sulfates
      • No Parabens
      How to use
      1. Apply to damp hair from roots to ends.
      2. Massage gently and leave for one minute.
      3. Rinse thoroughly. Follow with conditioner.
      Best for

      All hair types, especially dry and colour-treated hair seeking everyday hydration.

      Mobile band state (cvIZY, 390 - 32px 16px padding on white)

      Description

      A gentle daily shampoo that cleanses without stripping. Signature peptides and Ectoin barrier boost help hair hold moisture between washes.

      Suits colour-treated and dry hair. Sulphate-free, vegan, and made in Australia.

      • Vegan
      • No Sulfates
      • No Parabens
      How to use
      1. Apply to damp hair from roots to ends.
      2. Massage gently and leave for one minute.
      3. Rinse thoroughly. Follow with conditioner.
      Best for

      All hair types, especially dry and colour-treated hair seeking everyday hydration.

      Gift-card variant (rows relabelled, Best for omitted)

      Description

      A gentle daily shampoo that cleanses without stripping. Signature peptides and Ectoin barrier boost help hair hold moisture between washes.

      Suits colour-treated and dry hair. Sulphate-free, vegan, and made in Australia.

      • Vegan
      • No Sulfates
      • No Parabens
      How to redeem

      Your e-gift card arrives by email. At checkout, enter the code in the Gift card field and the balance applies to your order automatically.

      Data omission - empty metafields drop their rows entirely

      Description

      Smooth, shape, define and detangle hair with the Denman Classic Noir Medium Styling Brush 7 Row. Featuring sculpted nylon pins and a half-rounded shape for added grip and control, this brush is ideal for styling hair, detangling and giving definition to curls in wet hair.

      Live product "Denman D3 The Original Styler 7 Row" - no how_to_use / best_for metafields, so only Description renders (checklist 9)

      SEO copy

      Section - the de-emphasised SEO copy band at the foot of collection and brand pages: optional heading (blank falls back to the collection title via closest.collection, the C2JRr instance draws "Hair Care"), grey rich-text body (fill $sg-border #A8AAAC - a drawn WCAG AA fail, caveat 3 - 14px/400/1.2 desktop in TWO CSS columns with a 32px gap inside a 900px copy cap, 13px single column mobile) and the read-more control. The control renders through the shared text-link snippet with the arrow off (the master glyph is a known clipped defect): with a link_url it is a plain anchor; with the toggle on it is a BUTTON styled as the text-link that clamps the body to the configured line count (line-clamp only, text stays in the DOM/a11y tree), measures rendered line boxes so short copy never shows a dead toggle, and swaps to "Read less" with aria-expanded on expand. Masters p2gw8a (desktop 1440x320) / ePCz2 (mobile 390x259); H3 ref Cmxcf overridden 32px/-1 desktop, 26px/-0.5 mobile; mobile heading typo "Premiume" not reproduced. Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (p2gw8a, 1440)

      Mobile master state (ePCz2, 390)

      Short copy - no toggle (clamp under threshold)

      Hair Care

      Salon-only brands, expert advice and free NZ delivery over $75.

      Plain link state (link_url set)

      Hair Care

      Salon-only brands, expert advice and free NZ delivery over $75.

      About the brand

      FAQs

      White content band - the header column (optional uppercase eyebrow "HELP", the 32px/-1px 500 heading wrapping in a 301px column on desktop / 26px mobile, and the view-all link through the shared text-link snippet with the arrow off - the master arrow glyph is a known clipped defect) beside the accordion list: rows render exclusively through the shared accordion-row snippet at the md 16px size (the master's drawn question type; padding 20/0, 63px rows, 1px hairline divider, green + rotated caret when open per master Tf5Fk). Native independent details rows - multiple can be open at once (designer note XLwAK marks allow-multiple-open as proposed; native is the build default per caveat 2) and all rows ship closed (every comp draws closed). Answer voice 14/400/1.65 ink owned by the section; FAQPage JSON-LD ships behind a code constant (proposed, not a merchant setting). Masters yAgiy (desktop 1440x506) / lBk5B (mobile 390x544); note XLwAK; host instances on the sale/brand boards. Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master state (yAgiy, 1440)

      Frequently asked questions

      View all
      Do you stock salon hair products in NZ?

      Yes - every Synergy Hair salon stocks the full professional range, and our stylists can recommend the right products for your hair after your service.

      How can I be sure my hair products are professional grade?

      Yes - every Synergy Hair salon stocks the full professional range, and our stylists can recommend the right products for your hair after your service.

      Do you offer professional hair advice?

      Yes - every Synergy Hair salon stocks the full professional range, and our stylists can recommend the right products for your hair after your service.

      Are Synergy Hair products 100% authentic?

      Yes - every Synergy Hair salon stocks the full professional range, and our stylists can recommend the right products for your hair after your service.

      How do I know which product is right for my hair type?

      Yes - every Synergy Hair salon stocks the full professional range, and our stylists can recommend the right products for your hair after your service.

      How do I book a salon appointment?

      Yes - every Synergy Hair salon stocks the full professional range, and our stylists can recommend the right products for your hair after your service.

      Mobile master state (lBk5B, 390)

      Frequently asked questions

      View all
      Do you stock salon hair products in NZ?

      Yes - every Synergy Hair salon stocks the full professional range, and our stylists can recommend the right products for your hair after your service.

      How can I be sure my hair products are professional grade?

      Yes - every Synergy Hair salon stocks the full professional range, and our stylists can recommend the right products for your hair after your service.

      Do you offer professional hair advice?

      Yes - every Synergy Hair salon stocks the full professional range, and our stylists can recommend the right products for your hair after your service.

      How do I know which product is right for my hair type?

      Yes - every Synergy Hair salon stocks the full professional range, and our stylists can recommend the right products for your hair after your service.

      How do I book a salon appointment?

      Yes - every Synergy Hair salon stocks the full professional range, and our stylists can recommend the right products for your hair after your service.

      Open state (master Tf5Fk - title and caret green, caret rotated, answer visible)

      Frequently asked questions

      View all
      Do you stock salon hair products in NZ?

      Yes - every Synergy Hair salon stocks the full professional range, and our stylists can recommend the right products for your hair after your service.

      How do I book a salon appointment?

      Yes - every Synergy Hair salon stocks the full professional range, and our stylists can recommend the right products for your hair after your service.

      Cart banner

      Cart-page title band - the full-width white strip at the top of the cart page carrying the page's single h1. One component, two server-rendered states on cart.item_count: filled ("Your bag" with the item count as a green superscript numeral - 24px desktop per the disabled oJymu layer, 16px mobile derived, 12px gap) and empty (cart.item_count == 0 swaps to "Your bag is empty.", drops the count, stamps data-cart-empty; no layout change). Optional merchant image behind the copy (object-fit cover, anchored left, clipped to banner height - disabled on every board, ships off) and the 180px global logo slot (show_logo, default off - the board's olaplex.png is placeholder art). Title DM Sans 500, lh 1, -1.5px tracking, ink; 60px desktop / 30px mobile at the 750px breakpoint. Copy pads 32/48 desktop, 48/0 mobile (the mobile bar bleeds into main's top padding), gutters at the --page-margin token - the boards' 20px literal is normalised per caveat 5, and the title fill is plain ink rather than the boards' double-fill composite per caveat 4. One vocabulary across breakpoints ("Your bag" / "Your bag is empty.", sentence case) - the mobile board's "Shopping Bag" wording is not reproduced per caveat 1; the "47 products"/"3 items"/"YOUR CART" disabled layers are excluded noise per caveat 2. Subscribes to the flyout's cart:updated and re-fetches itself via the Section Rendering API so the filled/empty swap happens in place. Masters cqr9e (desktop 1440, filled) / W97ix (mobile 390, filled) / EzBjW (desktop, empty) / t4Q57 (mobile, empty); no designer notes on the cart boards. Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop filled state (cqr9e, 1440)

      Your bag 4 items

      Mobile filled state (W97ix, 390)

      Your bag 3 items

      Desktop empty state (EzBjW)

      Your bag is empty.

      Mobile empty state (t4Q57, normalised to the 30px token)

      Your bag is empty.

      Optional image and 180px logo slots are disabled on every board and ship off; the boards' "Shopping Bag", "YOUR CART" and "47 products"/"3 items" layers are design-source noise and are not reproduced.

      Cart empty

      Empty-cart message block - rendered by the cart-items section when cart.item_count == 0, in place of the line-item form: a 20px/500 body line ("Nothing in here yet - have a look at what our salons are recommending below." - en dash per the scope), the primary Start shopping CTA through the shared button snippet (auto width desktop, full width mobile), and the optional popular-categories nav (24px/600 green label, grey #A8AAAC links comma-separated inline at desktop, wrapped without commas on mobile, from the host's empty_menu link_list; the whole nav is omitted from the DOM when the menu is unset or empty). No h2 - the display-size "Your bag is empty." title belongs to the cart-banner section above. Vertical rhythm 40px between body, CTA and categories; 20px top / 80px bottom desktop, 16px top / 64px mobile; board title-case defects ("Start Shopping", "Popular Categories:") normalised to sentence case; the clipped 678px desktop categories row is a pen defect - the build wraps within the 560px column. Masters: desktop sxTvY (State/Cart/Desktop - Empty > No Results), mobile YnHyY (State/Cart/Mobile - Empty > No Results). Snippet atom - this gallery card renders both states; the live block swaps in on /cart when the last line is removed.

      Desktop state (sxTvY, with categories nav)

      Nothing in here yet - have a look at what our salons are recommending below.

      Start shopping

      No-menu state (nav omitted entirely)

      Nothing in here yet - have a look at what our salons are recommending below.

      Start shopping

      Mobile state (YnHyY - 390px frame, full-width CTA, wrapped links)

      Nothing in here yet - have a look at what our salons are recommending below.

      Start shopping

      The "Your bag is empty." display title belongs to the cart-banner section above (see its gallery card); the Bestsellers merchandising below the block belongs to templates/cart.json. Category links are demo data - the live nav comes from the host's empty_menu link_list. Board grey links (#A8AAAC, ~2.3:1) fail WCAG large-text AA - built per the board pending design sign-off.

      Cart items

      Cart-page main region - the filled state is a single cart form wrapping the desktop column header (Product | Quantity | Total, green 16px/600 labels over a hairline rule, display:none below 750px), the cart-line-item rows list, the cart-addons strip inside the items column, and the 400px order-summary aside (96px column gap; mobile stacks rows - add-ons - full-width summary card at a 48px gap). One form element spans rows + summary so checkout submits it; the cart-items custom element subscribes to cart:updated and re-fetches the section via the Section Rendering API, so quantity/remove/add-on changes and the filled/empty swap all land without a reload (aria-busy marks the rows list in flight). Header labels share the --cart-cols track definition with the line rows so the labels cannot drift from the row tracks. Main pads 32px top / 64px bottom mobile / 80px desktop at the --page-margin gutters (the boards' 20px desktop literal normalises to the token; board column widths 904/400 at 20px become items-flex/400 at 48px, brief caveat 2). The board's "Product:" trailing colon is a recorded copy defect and is not reproduced (checklist 4). Masters ImmXH (desktop Main, items column H01aga, header Ss4Sx, rows vKnqb/duEcO/GQCLq, add-ons rVDkr, summary V8WTb) / aKF74 (mobile Main - cart items vQHUP, summary card ztTQ2); empty states sxTvY / YnHyY render through the cart-empty snippet. Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Cart template

      Template - templates/cart.json wiring the cart page in board order (desktop BoA8d 1440 / mobile aKF74 390; empty PCXsK / aE3Zh): cart-breadcrumb (thin host for the shared breadcrumb snippet - Home / Bag from template context, present on both breakpoints per brief section 9) → cart-banner (the page's single h1, "Your bag" + green item-count numeral from cart.item_count) → cart-items (the one cart form wrapping line rows, add-ons strip and the 400px order-summary aside; the filled/empty swap and every figure re-render in place on cart:updated) → bestsellers ("Best sellers edit", 4 tabs, renders in BOTH states) → usp-banner (3 message blocks). Header + footer chrome stay in the global section groups. Empty state: the banner title swaps to "Your bag is empty." and cart-items swaps to the cart-empty snippet (body copy, Start shopping CTA, popular-categories nav) - items/addons/summary absent from the DOM; breadcrumb, bestsellers and USP stay. Template atom - the live page is /cart (both states QA'd there via /cart/add.js and /cart/change.js); this entry is the placement anchor in build order.

      Product template

      Template - templates/product.json wiring the default PDP in board order (desktop t87YC 1440x4338 / mobile rGMtZ 390x4912; sale A99PWs/dOnt9, OOS v6PkVe/C2KNcQ, multi-variant f7HZY/H9er9i, write-a-review drawer G3hTIa): product-hero (main - breadcrumb bar, gallery, sticky buy column carrying buy-box, product-details accordions and the pair-it-with rail, plus the mobile sticky-add-bar chrome the template owns) -> product-carousel ("Pairs well with", related source with best-sellers collection fallback so first paint carries cards; recommendations take priority via the Product Recommendations API) -> pdp-reviews ("Loved by you" sample data + the write-a-review drawer mounted beside its trigger, id pdp-write-review) -> usp-banner (3 message blocks, board copy). Header + footer chrome stay in the global section groups. Every state is a data condition on this one template - sale / OOS / multi-variant / low-stock never fork template files (only product.bundle and product.gift-card do, briefed separately). Template atom - the live page is /products/<handle> (fixtures: sale nak-hydrate-shampoo-350ml, OOS e-gift-card; the store currently carries no multi-variant or low-stock non-gift products - those states are exercised through the accepted child demos); this entry is the placement anchor in build order.

      Bundle template

      Template - templates/product.bundle.json wiring the fixed-kit bundle PDP (desktop board O39hZy 1440 / mobile HedB9 390, buy box aTZdS / udVXP, What's Inside mTKY7 / P0xaGY; brief docs/03-scoping/components/product.bundle.md): identical section order to the product template (product-hero -> product-carousel -> pdp-reviews -> usp-banner; header/footer chrome from the global section groups) - the delta is behavioural. Template-suffix or custom.bundle_components detection puts snippet_buy-box in its bundle mode: the Size/Options frame is replaced by the whats-inside panel on both breakpoints (fixed kit, no variant axis, no builder - note CPjrb), the price row is permanently the sale treatment (crimson price + compare-at + SAVE pill + savings block; "a bundle saving is a discount and should not invent a second visual language" - CPjrb) with the compare-at derived from the component-product price sum, per-item prices read from the referenced products at render (never typed), each row linking to that product's own PDP, and the optional GWP row rendering "Gift with purchase" (board typo LQ5cV fixed). Savings block stays visible with computed "Valued at" / "You save" values (never the board's inconsistent $54.60). Sold-out bundles keep the OOS treatment (sold-out block + notify-me + sticky Notify me) with What's Inside still rendered. Board ATC label "Add Bundle" renders "Add to bag" per the house sentence-case rule (checklist item 14). No schema (JSON template); template atom - the live page is /products/<bundle-handle>?view=bundle (alternate-template URL; suffix assignment is a store write outside this dispatch); this entry is the placement anchor in build order.

      Template atom - the live page is /products/<bundle-handle>?view=bundle (alternate-template URL; the product.bundle suffix assignment is a store write outside the build dispatch). product-hero → product-carousel → pdp-reviews → usp-banner; What's Inside replaces the options frame, sale treatment permanently on.

      • product-hero (main: gallery, bundle buy box, details, pair-it-with)
      • product-carousel
      • pdp-reviews
      • usp-banner

      Gift card template

      Template - templates/product.gift-card.json wiring the e-gift-card PDP in board order (desktop board x14Rp 1440 / mobile pCE88 390; brief docs/03-scoping/components/product.gift-card.md): identical section skeleton to the product template (product-hero -> product-carousel -> pdp-reviews -> usp-banner; header/footer chrome from the global section groups) with the gift-card deltas: buy-box gift-card mode (template suffix / product.gift_card? detection) drops the vendor / rating / benefits / SAVE-pill / savings / OOS chrome outright (absent from the layer tree, not disabled), renders the price as the range state (price_min – price_max through the price snippet's show_range, never the sale colour), replaces the Size/Options frame with the gift-card-form snippet inside the buy box's own product form (Shopify-native recipient properties: Recipient email / Recipient name / Message / Send on with the __shopify_send_gift_card_to_recipient validation checkbox and __shopify_offset; From as a line-item property; the Other-amount toggle ships on per the mobile board's OTHER AMOUNT ($) row), drops the quantity stepper, and renders the muted 13px voucher-terms line under the purchase row at both breakpoints (board X5MxSy). product-hero auto-switches its product-details group to the gift-card variant (How to redeem + Terms & Conditions, two 69px closed rows) and omits the pair-it-with rail. Mobile sticky-add-bar renders its gift state ("from $25.00" from price_min + add button, board SnLOG). No sale / OOS / low-stock chrome can render on this template. Template atom - the live page is /products/e-gift-card?view=gift-card (alternate-template URL; the suffix assignment to the store's gift-card product is recorded in the build notes); this entry is the placement anchor in build order.

      Template atom - the live page is /products/e-gift-card?view=gift-card (alternate-template URL; the gift-card suffix assignment to the store's e-gift-card product is recorded in the build notes). product-hero (gallery, gift-card buy box, gift-card details accordions, mobile sticky-add-bar gift state) → product-carousel → pdp-reviews → usp-banner; no vendor / rating / benefits / SAVE-pill / savings chrome, price renders the range state.

      • product-hero (main: gallery, gift-card buy box, gift-card details)
      • product-carousel
      • pdp-reviews
      • usp-banner

      E-Gift Card (Online Only)

      $20.53 – $123.17

      Synergy Hair E-Gift Cards are redeemable online only and cannot be redeemed in store. Choose to send this to your own email address, or gift it to someone special! Choose the value on the E-Gift Card, the delivery date (to the specified email address) and choose to write a free message to your special someone.

      Amount:

      0 / 200 characters


      Gift voucher terms & conditions apply. Valid 12 months from purchase.

      Gift-card buy box (live product E-Gift Card (Online Only))

      Article template

      Template - templates/article.json wiring the Journal article page in board order (desktop board AzR9r 1440 / mobile hlpPy 390, editorial host KNJUf): article-body (main - breadcrumb bar Home / Journal / article title via the shared breadcrumb snippet, the article-hero snippet - first-tag eyebrow 12px/500 -1.5 tracking desktop-only, H1 60/38 500 -1.5px ink, 17px standfirst, stacked author / date / live-computed read-time meta, full-bleed featured image 100vw x 620 desktop / 300 mobile - then the 700px RTE prose column with the blockquote pull-quote, checklist and figure treatments) -> bestsellers ("Shop the routine" instance - heading override of the Best sellers edit default, tabs off per the article board, best-sellers collection, 5 products, Shop all) -> keep-reading ("More from the journal" / "Keep reading" header with the Back to blog text-link, 3 related cards from article.blog excluding the current) -> usp-banner (3 message blocks, same wiring as cart/blog/collection). Header + footer chrome stay in the global section groups. No-image articles omit the featured image (hero contract); comments are not rendered (blog contract). Template atom - the live page is /blogs/news/<article> (fixture: best-hair-curlers-in-nz-2026-buyers-guide, 46 seeded articles on /blogs/news); this entry is the placement anchor in build order.

      Template atom - the live page is /blogs/news/<article> (article-body → bestsellers "Shop the routine" → keep-reading; USP banner + footer from the footer group). Fixture: /blogs/news/best-hair-curlers-in-nz-2026-buyers-guide

      Page template

      Template - templates/page.json wiring the generic store page (desktop board UHw1M 1440 / mobile CpYUp 390, brief docs/03-scoping/components/page.md): page (the rebuilt main page-body section - breadcrumb bar Home / page title via the shared breadcrumb snippet, the page-header snippet band (H1 60/38 500 -1.5px ink, optional "Last updated" mono meta from page.metafields.custom.last_updated with published_at fallback, optional intro lede), then the two-column content region: 280px sticky "On this page" anchor rail + 760px prose column at a 64px gap inside the 1104px region cap, 96px block padding - mobile stacks the TOC as a hairline-bordered numbered card above the prose) -> contact-callout (dark accent-deep card + primary button) -> usp-banner (3 message blocks, template level). Header + footer chrome stay in the global section groups. Prose is the merchant's RTE page.content: h2 at the 24px/500/-0.5 board literal at BOTH breakpoints, 17/16px body, em-dash bullets desktop / dot bullets mobile, 1px hairline rules above every h2 after the first (desktop only per the boards). The TOC renders only when the prose carries 2+ h2s - a heading-less page hides it entirely (no empty shell); show_toc / show_last_updated / toc_title / intro are merchant settings on the main section. Template atom - the live page is /pages/<handle> (fixture /pages/privacy-policy; heading-less probe /pages/delivery-returns-warranty); this entry is the placement anchor in build order.

      Template atom - the live page is /pages/<handle> (fixture /pages/privacy-policy; heading-less pages hide the TOC entirely). The main page-body section renders breadcrumb → page-header band → TOC + prose region; contact-callout and the template-level usp-banner follow at template level.

      • page (main: breadcrumb, page-header, TOC + prose)
      • contact-callout
      • usp-banner

      Home template

      Template - templates/index.json wiring the homepage in board order (desktop ubSf0 1440x9094 / mobile VtGH5 390x9243, brief docs/03-scoping/components/index.md): hero-duo (two slots, each taking a colour, image or video, with the K18 copy fixed to slot A; the embedded USP strip was removed at QA 2026-09-02 - the standalone usp-banner is the only USP band; hero / hero-split presets still swap in without a rebuild per sign-off note WY2ZL) -> shop-by-category (4 category tiles) -> new-in (cream band, lead media + product rail off the new-arrivals collection, 5 cards) -> just-landed (ink band, 2 campaign tiles: Amika + Muk) -> a second shop-by-category row for hair type (4 tiles; the shop-by-hair-type section it replaced was retired at QA 2026-09-02, and its Visibility select went with it - one responsive section now serves both breakpoints, with a mobile stacked/scroll choice) -> salon-services (media band + 3 service cards + Visit CTA panel) -> shop-by-brand (grid state, 12 brand tiles) -> reviews-home (10-review quote carousel with 01-10 pagination) -> promo-offer (complimentary-treatment offer band with dual CTAs) -> social (@synergyhairnz, 4 posts) -> usp-banner (3 message blocks, template-level - deliberately NOT in the footer group so page.salons omits it, note Q92ff). Header + footer chrome stay in the global section groups. Sections stack flush - zero inter-section gap; all rhythm is section padding. Template atom - the live page is /; this entry is the placement anchor in build order.

      Template atom - the live page is / (hero-duo → shop-by-category → new-in → just-landed → shop-by-category → salon-services → shop-by-brand → reviews-home → promo-offer → social → usp-banner; announcement + header and footer from the global section groups).

      • hero-duo
      • shop-by-category
      • new-in
      • just-landed
      • salon-services
      • shop-by-brand
      • reviews-home
      • promo-offer
      • social
      • usp-banner

      Salons template

      Template - templates/page.salons.json wiring the Salons index in board order (desktop board k5aVQ 1440, ref BROds chrome + NRhyl breadcrumb; mobile X2zokq 390; expanded state boards zkNm0 desktop Auckland / VqxOj mobile; brief docs/03-scoping/components/page.salons.md): salon-service-strip (the full-bleed three-tile band, wW7zi instance padding-0; mobile keeps the section's 273px swipe-row variant per the two-image banner board h3vXKs) -> city-accordion (the h1 "Salons" + five city rows over the live salon metaobjects, expanded panel 2-up desktop / stacked mobile) beside salon-map (NZ artwork + five interactive pin blocks) - the two-column body band (accordion 780 + map 500, 64px gap desktop; stacked mobile) is the one deliberate build-time composition decision the brief reserves to this template. Header + footer chrome stay in the global section groups; breadcrumb Home / Salons comes from page context through the shared snippet where the host section mounts it. NO usp-banner by design (note Q92ff: the mockup's strip is deliberately absent from the boards pending component registration + redesign decisions; the template keeps the slot so one can be added later without layout change). All cities collapsed on load, one open at a time; pins open their city; #<city-handle> deep links open on load. Template atom - the live page is /pages/salons (page gid 133190582471, template suffix "salons", counts 5/4/2/2/2=15 from the seed store's salon metaobjects; placeholder warning AFZvQ); this entry is the placement anchor in build order.

      Template atom - the live page is /pages/salons (salon-service-strip → city-accordion; header/announcement and footer from the global section groups; NO usp-banner by design, note Q92ff). All city rows and counts render from the live salon metaobjects (placeholder warning AFZvQ).

      • salon-service-strip
      • city-accordion

      Brands template

      Template - templates/page.brands.json wiring the Brands index page in board order (desktop board fJL3s 1440 / mobile blNp8 390; brief docs/03-scoping/components/page.brands.md): brands-intro (the H1 "Brands, from A-Z" over the computed "86 brands" count - never the design's stale 87 - plus four category-tile blocks: Hair care 32 / Styling 19 / Hair colour 21 / Treatments 15, desktop 4-up grid, mobile 150px swipe row) -> brands-directory (az-jump-bar: 26 cells, dead letters I Q U V Y computed from the live dataset; full-width hairline rule; four column-major contiguous ranges A-D / E-K / L-P / R-Z of letter-grouped brand rows) -> template-level usp-banner (note MO0PS: the USP strip IS on the Brands page, unlike page.salons). Header + footer chrome stay in the global section groups. Counts, dead letters and column ranges all recompute from the live brand list at render (brief caveat 1 - brand list drifts). Status labels render only from metafield data (never hard-coded - note MO0PS). Deliberate omissions asserted: no search field, no category tabs, no product counts. Template atom - the live page is /pages/brands (page gid 133190320327, template suffix "brands", 86 brand rows from the brands-directory menu gid 259287908551); this entry is the placement anchor in build order.

      Template atom - the live page is /pages/brands (brands-intro → brands-directory; template-level usp-banner; header/announcement and footer from the global section groups). All 86 brand rows, the live and dead letters (I Q U V Y) and the column ranges render from the brands-directory menu bound on the instance (gid gid://shopify/Menu/259287908551).

      • brands-intro
      • brands-directory
      • usp-banner

      Salon detail template

      Template - templates/page.salon-detail.json wiring the per-salon detail page in board order (desktop board byx6X 1440 / mobile k4Psx 390, breadcrumb N1EIR/ZpWUS, brief docs/03-scoping/components/page.salon-detail.md): salon-detail-breadcrumb (thin host section - Home / Salons / salon name through the shared breadcrumb snippet, current page named per note mq8Fo) -> salon-detail-info (the two-column detail body - H1 salon name, SALON HOURS rows, contact stack, Book appointment primary + Price list secondary beside the salon image; instance price_list_type none while the destination is the open client question) -> stylist-cards (six meta-disabled service-card blocks, name_only fallback while photography is unconfirmed, heading "Meet the team"). Header + footer chrome stay in the global section groups. NO usp-banner by design (manifest state no-usp-banner-by-design; note mq8Fo: "the mockup does not place one on the detail page. Correct as built"). Template atom - the live page is /pages/westfield-riccarton (page record with template suffix salon-detail; per-salon data via the page's custom.salon metafield, first-salon fallback until bound); this entry is the placement anchor in build order.

      Template atom - the live page is /pages/salons?view=qa-salon-detail (salon-detail-breadcrumb → salon-detail-info → stylist-cards; header/announcement and footer from the global section groups; NO usp-banner by design, note mq8Fo). The detail body renders from the live salon metaobjects (placeholder warning AFZvQ); the stylist cards are sample blocks - the live stylist metaobject definition is not yet seeded (store-population plan records stylists as skipped).

      • salon-detail-breadcrumb (Home / Salons / salon name)
      • salon-detail-info
      • stylist-cards

      Contact template

      Template - templates/page.contact.json wiring the Contact page in board order (desktop board aTXC6 1440 / mobile yehUb 390; brief docs/03-scoping/components/page.contact.md): contact-breadcrumb (thin host section - Home / Contact through the shared breadcrumb snippet, the cart-breadcrumb precedent) -> quick-contact (the two whole-tile link cards: envelope "Email us" / map-pin "Find a salon", 44px chip, 18px caret) -> contact-form (the H4-role heading "Send us a message" over the contact form-tag card - name, email, phone optional, reason select, message, consent checkbox, full-width primary submit; the submitted state xkNtR/AGjcw swaps the card for the success panel: accent-10% square badge with check-circle, "Message sent" + reply-time copy, secondary "Send another message" + primary "Back to home", order flipped primary-first on mobile) -> contact-details (surface-alt strip: opening hours dl + head office, hairline divider between the columns on mobile) -> template-level usp-banner (3 house message blocks). Header + footer chrome stay in the global section groups. Template atom - the live page is /pages/contact (page record with template suffix "contact"); hCaptcha on the store may gate headless form POSTs - QA asserts form mechanics and the success-state markup contract, not a completed submission; this entry is the placement anchor in build order.

      Template atom - the live page is /pages/contact (contact-breadcrumb → quick-contact → contact-form → contact-details; template-level usp-banner; header/announcement and footer from the global section groups). The form posts natively to /contact; the submitted state (badge + "Message sent" + two buttons) replaces the form card on posted_successfully. hCaptcha on the store may insert a human check before headless POSTs land - QA asserts form mechanics, not a completed submission.

      • contact-breadcrumb (Home / Contact)
      • quick-contact (Email us / Find a salon tiles)
      • contact-form (heading + form card; success card on post-back)
      • contact-details (opening hours + head office)
      • usp-banner

      Collection sale template

      Template - templates/collection.sale.json, the alternate collection template for the Sale PLP (desktop board d5dB46 1440 / mobile zvNXo 390; brief docs/03-scoping/components/collection.sale.md, a delta on the accepted collection template - the SAME sections, not re-drawn): collection-header in its hero-off state (instance show_image: false - note tNVsq: "a sale page leads with product, not lifestyle"; the header hugs the copy and stays compact, 337 tall vs Haircare 519; six quick_link pills to live sale-relevant handles; title/count render from the collection resource, never hard-coded) -> product-grid (identical instance; cards carry sale badging + sale pricing data-driven from compare_at_price - SAVE pill in $sg-berry #DE0F3F with a computed percent, price in $sg-sale, compare-at struck through in $sg-border per the system-wide ddbIH call) -> product-carousel ("Synergy favourites", hair-care source - included on BOTH breakpoints per caveat 2: the mobile board shows the row, the desktop board omits it; flagged to design) -> faqs ("Frequently asked questions" + view-all, replacing seo-copy's slot per the mobile board order; schema enabled_on widened to collection templates for this host) -> usp-banner (3 house message blocks). No seo-copy section on this template. Header/footer chrome stays in the global section groups. Template atom - the live page is /collections/online-clearance-sale (collection gid gid://shopify/Collection/335817670855; template suffix "sale" assignment is an authorised store write deferred to the wire step; QA route /collections/online-clearance-sale?view=sale); this entry is the placement anchor in build order.

      Template atom - the live page is /collections/online-clearance-sale (collection-header hero-off → product-grid with data-driven sale badging → product-carousel → faqs → usp-banner; announcement + header and footer from the global section groups). Title, T&C description and count render from the collection resource (gid gid://shopify/Collection/335817670855); template suffix "sale" assignment is an authorised store write deferred to the wire step - QA route ?view=sale.

      • collection-header (hero off)
      • product-grid (sale badging)
      • product-carousel
      • faqs
      • usp-banner

      Collection brand template

      Template - templates/collection.brand.json, the alternate collection template for brand pages (desktop board ZolYq 1440 / mobile hemyI 390, spec note t3mwe; brief docs/03-scoping/components/collection.brand.md, a delta on the accepted collection template - the SAME sections, not re-drawn): collection-header in its brand-hero state (show_image true - show_logo true, show_title off per t3mwe "the logo is the brand" - quick links off; hero image left half desktop 720 @1440 / full-bleed 220px mobile band; logo in the copy panel over $sg-bg, never over photography; accessible h1 kept as visually-hidden title or logo alt; text-title fallback when no logo asset; count + sort row right-aligned with the pills absent; breadcrumb Home / Brands / brand via the brands directory parent) -> product-grid (identical instance; brand/vendor facet suppressed on brand templates per t3mwe - "a dead control" - all other groups render) -> brand-story (spec change #2, no board - design-source-visual: off; image + copy + text link out, placed after the grid deliberately so the range leads) -> product-carousel (the shared recently-viewed row) -> faqs ("Frequently asked questions", added before SEO copy matching the mockup's K18 page) -> seo-copy -> usp-banner (3 house message blocks). Header/footer chrome stays in the global section groups. Template atom - the live page is /collections/olaplex (template suffix "brand" assignment is an authorised store write deferred to the wire step; QA route /collections/olaplex?view=brand); this entry is the placement anchor in build order.

      Template atom - the live page is /collections/olaplex (template suffix "brand" assignment is an authorised store write deferred to the wire step - QA route ?view=brand on the dev server), where the template composes collection-header in its brand-hero state (hero image on - brand logo on, title off - quick links off; t3mwe: the logo is the brand, sitting in the copy panel over $sg-bg) → product-grid (brand facet suppressed on brand templates, t3mwe: a dead control) → brand-story (spec-only band, no board - image + copy + text link out, placed after the products deliberately) → product-carousel (recently viewed) → faqs → seo-copy → usp-banner. Announcement + header and footer from the global section groups. One template serves 87 brand collections - swapping the brand is a logo fill, three text nodes and the breadcrumb.

      • collection-header (brand hero: logo on / title off)
      • product-grid (brand facet off)
      • brand-story
      • product-carousel
      • faqs
      • seo-copy
      • usp-banner

      Search template

      Template - templates/search.json wiring the search results page in board order (results: desktop oLxsB 1440x2254 / mobile e4OhW 390x4875; no-results state: COJ4N 1440x2913 / wKHQc 390x3577; brief docs/03-scoping/components/search.md): collection-header in its search state (eyebrow "Search results for", title = the escaped query echo, green count badge, description + quick links off, count + sort row on; the no-results state flips the eyebrow and drops count/sort) -> search-empty (thin host for the no-results snippet: "No results for" eyebrow, query-echo h1, apology message, predictive did-you-mean row, Browse bestsellers + Shop all products CTAs; zero DOM on a results page) -> product-grid (results arm; the search drop paginates search.results - the board's filters rail is NOT built: the accepted product-grid section runs its solo layout on search, flagged) -> product-carousel ("Recently viewed", best-sellers fallback source - mobile board only, included on both breakpoints per caveat 4, flagged for sign-off) -> shop-by-category ("Our range", shop-all link off, 4 live tiles, paddings 40/96) -> bestsellers ("Best sellers edit", tabs OFF per board override PKQ8D, shop-all "View all best sellers", 5-up) -> usp-banner (3 house message blocks). Recovery sections (search-empty, shop-by-category, bestsellers) render only into a performed zero-result search; grid + carousel render only into a performed search with results; a bare /search visit renders the prompt state (header prompt title/body + Browse-all CTA - build-to-convention, no board). Header + footer chrome stay in the global section groups; breadcrumb Home / Search results is frame-level through collection-header. Template atom - the live page is /search?q=repair (results) and /search?q=xkcdzzzqq (no results); this entry is the placement anchor in build order.

      Template atom - the live page is /search (results: /search?q=repair - collection-header search state → product-grid over search.results → product-carousel → usp-banner; no-results: /search?q=xkcdzzzqq - search-empty host rendering the no-results snippet + shop-by-category "Our range" + bestsellers rail + usp-banner; bare /search renders the prompt state). Announcement + header and footer from the global section groups. Query terms, count and suggestions render from the search resource - never hard-coded.

      • collection-header (search / no-results / prompt states)
      • search-empty (no-results host + prompt CTA)
      • product-grid (results arm, search.results)
      • product-carousel (Recently viewed)
      • shop-by-category (recovery, Our range)
      • bestsellers (recovery rail, tabs off)
      • usp-banner

      Gift card receipt

      Template - templates/gift_card.liquid, the gift-card receipt page rendered from a gift-card email (atom template_gift-card-receipt, state no-design-restyle-skeleton-default - no design board exists; the Skeleton default restyled with the theme foundation per brief docs/03-scoping/components/gift_card.md). Standalone document, layout-none: no header/footer/announcement chrome, no cart - html/head/body with css-variables, tokens.css, the DM Sans preload, critical.css, meta-tags and content_for_header in head, and the template CSS in a style tag block (templates cannot use the stylesheet tag). A 600px centred column on white (page-margin padding 48 desktop / 16 mobile, 24px stack rhythm, 32px after the masthead): the logo masthead (settings.logo, max 210px wide, shop.name text fallback), the artwork panel at the Card Front aspect 694/428 (accent #374426 fill with the reversed wordmark until the product.gift-card artwork slot ships - never the Skeleton's generic card.svg), the balance h1 (H1 scale 60/38 desktop/mobile, weight 500 per the H1 master, lh 1, ls -1.5px, ink), the sale-coloured expired notice (conditional), the muted 13px expiry line (conditional, %B %e, %Y), the use-at-checkout instruction, the bordered code box (1px border, radius 0, 16/24px padding, +1.5px tracking, black, format_code), the actions row (Apple Wallet badge when pass_url exists, the print trigger firing window.print(), the continue-shopping text link to routes.root_url in accent with accent-deep hover at 120ms) and the muted shop-name footer (demoted from the Skeleton's h2 - one h1 per document). Print stylesheet hides the actions and forces white/black legibility. Radius 0 everywhere; single 750px media query. Template atom - the live page is /gift_cards/<code>; this gallery card is the placement anchor and the specimen (fixture values, no gift_card object on a page route).

      $100.00

      Expires on August 28, 2027

      Use this gift card at checkout

      XXXX XXXX XXXX XXXX
      Continue shopping

      Synergy Hair

      Template atom - the live page is /gift_cards/<code> (reached from a gift-card email). State matrix: expired cards add the gift-card--expired modifier and the sale-coloured notice; no pass_url renders no wallet badge; the artwork panel carries the product.gift-card artwork asset once that slot ships - the accent fallback panel with the reversed wordmark shows until then.

      Password page

      Template - templates/password.json + layout/password.liquid, the landing a visitor sees while store password protection is active (atom template_password, state no-design-restyle-skeleton-default - no design board exists; the Skeleton default restyled with the theme foundation per brief docs/03-scoping/components/password.md). Footer-less minimal chrome - the password layout ships no header/footer/announcement/nav/cart and the only landmarks are main and the form. A viewport-centred 480px panel on white (100dvh flex centring, page-margin padding 48 desktop / 16 mobile, 32px stack rhythm): the logo wordmark (settings.logo when bound, else the shop.name text fallback, max 210px), the H1 title (H1 scale 60/38 desktop/mobile, weight 500 per the H1 master, lh 1, ls -1.5px, ink), the optional merchant password_message (16/400 body copy, absent from the DOM when blank) and the storefront_password form - a single-field capture row of the form-input primitive (grows, 46px control, uppercase 11px label left-aligned above) beside the primary button primitive (fixed 45px, Enter, submit). Errors render inline under the field through the form-input error contract (sale border, 13px sale message, aria-invalid + aria-describedby) - never the default_errors banner. The form posts to /password natively and is never intercepted; the optional is-loading flip is a password-form custom element toggling the button's loading hook. Radius 0 everywhere; the capture row stacks below 750px - the section's single media query (the H1 and page-margin steps live in tokens.css). Template atom - the live page is /password; this gallery card is the placement anchor and the specimen (non-submit button so the gallery never navigates).

      This shop is private

      Incorrect password - please try again

      Template atom - the live page is /password while store password protection is active. States: default (top row), error (the second row shows the form-input error contract - inline message under the field, sale border, aria-invalid; no default_errors banner), and message-present (shop.password_message non-blank adds the body-copy paragraph - merchant data, not shown here). The live form posts to /password natively; this specimen's button is type button so the gallery never navigates.

      Product grid

      The browse surface of the collection and search templates (desktop main vxsgt - filters rail iNmzh + results column mklZq - and mobile board n52zT; sale variant kINup is structurally identical) - a 264px filters rail beside a results column of product cards at repeat(4,1fr)/8px desktop and repeat(2,1fr)/4px mobile, with the centred pagination block: the interpolated "You've viewed X of Y products" count line, the 320x4 progress track (green fill width = shown/total via the --progress property; the drawn $sg-white track is invisible on the white page per caveat 2, built as specified and flagged to the designer) and the secondary Load more button over a real crawlable ?page=n+1 link. Load More appends via the Section Rendering API holding scroll position and every filter/sort param (note ITaos), replaces the list on facets:changed, focus lands on the first new card, grid:updated publishes {shown,total}. Zero results render the build-to-convention empty state (translated message + Clear all). Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Live browse surface (rail + grid + pagination)

      Filters

      Desktop layout: 264px rail + repeat(4, 1fr) grid, 8px gaps, pagination block

      Solo layout (enable_filtering: false)

      No rail - the results column alone

      Mobile 2-up specimen (390px stage)

      Mobile: repeat(2, 1fr) at 4px, 48px above the pagination

      Empty state (zero results, build-to-convention)

      No products found

      Clear all
      Zero results: grid + pagination hidden, translated message + Clear all

      Article grid

      Section - the Journal (blog) article grid (desktop hosts uybEM + I0o9qO + pagination s9ilBq at 1440, mobile board Egi2b at 390). Desktop: 12 article cards in a 3-column grid (8px column gap, 48px row gap) on a 1400px content container with literal 20px side margins (journal boards win over --page-margin 48 - same divergence as featured-article, flagged). Mobile: single-column list (40px row gap, 16px gutters) under a horizontally scrollable white category filter strip (padding 16 0 16 16, pill gap 8, scrollbar suppressed; hidden >=750px - desktop category filtering belongs to the collection-header pills). Pagination block: centred column, 64px above, 16px gaps - the aria-live "You've viewed X of Y articles" line (16px/500/-0.5 black desktop, 14px mobile), the 320x4 white track with a green fill at width = viewed/total, and the secondary Load more button (a real crawlable ?page=n+1 anchor enhanced by the article-grid custom element - Section Rendering API append, scroll preserved, focus to first new card, fetch failure falls back to native navigation). Section background #F2F1EE (ink-foreground). Cards render through the article-card snippet (internals owned there). Section atom - the live instance renders on /pages/preview-sections; this demo exists for the gallery placement anchor and the mobile filter specimen.

      Live card grid (desktop 3-up, 46 articles)

      1. Is ghd or Cloud Nine Better?

        If you’re in the market for a new high-quality hair straightener – you’re probably weighing up between industry leading brands: ghd and Cloud Nine. Both...

        5 min read
      2. Does Heat Protectant Actually Work?

        We’ve all been there – maybe you’re in a rush in the morning, or cutting a cheeky corner while straightening your hair, and skip the...

        5 min read

      Mobile filter strip (<750px only on the section)

      Keep reading

      Section - the article-template footer rail (desktop host Fx7g2 at 1440, mobile board uAx8g at 390): the shared section-header ("More from the journal" eyebrow over "Keep reading", "Back to blog" text-link in the right slot) above a 3-up row of related article cards from the current article's blog, excluding the current article. Desktop: title left / link right-aligned on one row (flex-end), cards in a 3-column grid with 24px column gaps, top-aligned (heights vary with title wrap by design). Mobile: the header stacks (link below the heading, 16px gap - the mobile board's bespoke stacked header normalised to the section-header snippet per the manifest) and the list is a single column with 32px row gaps. Section padding 80/20 desktop, 64/16 mobile on a --page-width centred container (the journal boards' literal 20px desktop inset wins over the 48px --page-margin, flagged like featured-article/article-grid); header-to-list gap 48. Eyebrow renders sentence case, uppercased by the shared snippet's CSS; the board's 14px link label overrides the snippet's 12px uppercase slot treatment at instance scope. article_count is a 2-4 range (boards show 3). Cards render through the article-card snippet (internals owned there). Static: no JS, no events. Zero related articles render nothing. Section atom - the live instance renders on /blogs/news/<article>?view=qa (a page template carries no article object, so the /pages/preview-sections shell renders nothing by design); this gallery card is the placement anchor and the desktop specimen.

      Desktop master state (header row + 3-up card row, Fx7g2)

      More from the journal

      Keep reading

      Contact callout

      Section - the reassurance band at the foot of the generic page template (desktop master eAwY9 inside UHw1M at 1440, mobile board gX8pT inside TsyAF at 390), placed after the page body and before the USP banner. A white band (padding-block 96 desktop, gutters at --page-margin) carrying one centred dark card (max-width 1104px, $sg-accent-deep #2A3420, radius 0 per the brand's sharp-corner rule): the copy column (h2 at the H4 visual role, 24px/500/-0.5 in $sg-ink-foreground, one body line 17px/400/1.25 in $sg-cream-80) beside the CTA through the shared button snippet's primary variant (green $sg-green, white label). Desktop: flex row, space-between, align-items center, 48px gap, 40px card padding, copy column capped 745px. Mobile (single 750px breakpoint): the card stacks (copy above button, 16px gap), padding 24/16, button full width. One colour scheme built at both breakpoints - accent-deep card with ink-foreground/cream-80 text (deepest contrast); the mobile board's drawn accent/white values are accepted design-source noise per caveat 1. The desktop button ref KNM4A (Primary - Inverse) is overridden on the board to green fill + white label, i.e. visually Primary (caveat 3); label ships as "Contact our privacy team" (one setting - the desktop board's email-address label is the merchant's to type, caveat 2); the 13px button-snippet label size wins at both breakpoints (1px desktop-board delta expected, caveat 4). mailto: resolution via the button_email setting (wins when set) else button_link; blank settings drop their nodes - all-blank renders nothing. Static: no JS, no events. Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master (row card, 1104px, space-between)

      Questions about your privacy?

      If you have any questions about this policy, our team is here to help.

      Contact our privacy team

      Mobile master (stacked card, full-width button)

      Questions about your privacy?

      If you have any questions about this policy, our team is here to help.

      Contact our privacy team

      Demo scope: the section stylesheet is scoped to the section bundle, so this matrix re-declares the identical rules below. The real section instance (settings-driven) lives on /pages/preview-sections.

      Contact details

      Contact-page closing band - a full-width surface-alt band (64/48 padding desktop, 48/16 mobile) carrying a 600px centred wrap of two columns: Opening hours (an 11px mono uppercase caption over a dl of day/time rows - day left in ink, time right-aligned in black, 12px row rhythm) and Head office (the same caption over the company name at weight 500 and address lines at an 8px rhythm). Desktop lays the columns side by side with a 64px gap; mobile stacks them with a 24px gap and a 1px hairline divider that renders only when both columns render (never an orphan) and drops out at desktop. The caption treatment is the canonical 11px mono uppercase pattern cross-referenced by salon detail note mq8Fo; the 11px size is drawn off-token (label token is 12) and recorded as a shared candidate token. Structure is block-driven - hours_group (max 1), repeatable hours_row day/time pairs, office (max 1) - so removing the office block leaves hours full-width without the divider. No links, no interactive states, no JS, zero layout shift. Masters NP60q (desktop, inside aTXC6 at 1440) / vXax8 (mobile, inside yehUb at 390). Section atom - the live instance renders on /pages/preview-sections-2; this entry is the placement anchor in build order.

      Desktop master (600px wrap, two columns, 64px gap)

      Opening hours

      Monday – Friday
      8am – 6pm
      Saturday
      9am – 4pm
      Sunday
      Closed

      Head office

      Synergy Hair Ltd

      PO Box 1234, Auckland 1010 New Zealand

      Mobile master (stacked columns, hairline divider)

      Opening hours

      Monday – Friday
      8am – 6pm
      Saturday
      9am – 4pm
      Sunday
      Closed

      Head office

      Synergy Hair Ltd

      PO Box 1234, Auckland 1010 New Zealand

      Demo scope: the section stylesheet is scoped to the section bundle, so this matrix re-declares the identical rules below. The real section instance (settings-driven) lives on /pages/preview-sections.

      Contact form

      Section - the "Send us a message" form on the contact page (desktop master VDbZi inside aTXC6 at 1440, mobile board musr7 inside yehUb at 390, submitted states xkNtR desktop / AGjcw mobile), placed between quick-contact and contact-details. A white band (padding 8/48/96/48 desktop, 8/16/64/16 mobile) carrying a 600px centred wrap: the H4-role heading (24px/500/-0.3/1.1 desktop, 18px mobile) above the form card (white, 48px padding desktop / 32px mobile, 1px hairline border at BOTH breakpoints per scope caveat 1). Six fields render exclusively through the shared form-input snippet (name, email, phone optional, reason select from the reasons setting, message textarea, consent checkbox); submission is the native contact POST with a full-width primary button (green $sg-green, white 13/500 label). Server-driven states: the submitted success panel replaces heading + card (bordered card, 96/64 desktop / 64/32 mobile, 32px stack gap, accent-10% square badge 72px/64px with the 40px/34px forest check-circle, centred "Message sent" copy, and the 12px-gap action row desktop [secondary, primary] / full-width stacked column mobile with the order swapped via column-reverse). Errors build-to-convention: inline under the offending field in $sg-sale, never a banner; submitting shows the button spinner only (inputs never disable - they would drop out of the native POST). hCaptcha on the store may insert a human check before the POST lands.

      Desktop default (600px wrap, 48px card, six fields)

      Send us a message

      Submitted state (success panel replaces form)

      Message sent

      Thanks for reaching out. We've received your message and will reply within one business day.

      Demo scope: the section stylesheet is scoped to the section bundle, so this matrix re-declares the identical rules below. The real section instance (server-driven states, native contact POST) lives on /pages/preview-sections.

      Quick contact

      Contact-page lead band - a white band (64/48/48/48 padding desktop, 48/16/32/16 mobile) carrying a row of contact tiles: desktop a 2-equal-column grid at a 24px gap, mobile a single column at a 12px gap. Each tile is a whole-surface anchor (660x92 desktop / 358x92 mobile, the entire 92px tile is the tap target): a 44x44 surface-alt chip with a 22px phosphor-thin icon in accent, a text column (title 17/600/1.2 in ink, 16px at mobile via the body-lg token pair - the mobile board's drawn 17px literal is bound to the token per brief caveat 1 - over a 13/400/1.3 value in black) and a trailing 18px caret in muted, space-between so the caret hugs the right edge. Structure is block-driven through content_for 'blocks' (tile blocks, max 4): icon select, title (locale default "Email us" when blank), value, email (mailto wins) and link; a tile with neither renders as a static div, never a dead anchor. Hover/focus are build-to-convention (border darkens to --color-border, caret nudges 2px right over 120ms; 2px accent outline at a 2px offset) - exclude from design-source comparison. Icons render exclusively through the shared icon snippet. Masters k4CGAa (desktop, inside aTXC6 at 1440) / RcWoC (mobile, inside yehUb at 390). Section atom - the live instance renders on /pages/preview-sections; this entry is the placement anchor in build order.

      Desktop master (two tiles, 24px gap)

      Mobile master (stacked tiles, 12px gap)

      Demo scope: the section stylesheet is scoped to the section bundle, so this matrix re-declares the identical rules below. The real section instance (settings-driven) lives on /pages/preview-sections.

      Blocks

      Modular, nested storefront blocks containing structured layouts and settings.

      Blocks represent sub-components or content modules (e.g. product cards, slide entries, grid columns) that are nested inside main layout sections.
      • Modular & Reusable: Encapsulate styling and schema configs inside standalone settings.
      • Dynamic Placement: Can be rearranged, added, or deleted inside parent sections in the Shopify Theme Editor.

      Sections

      Full-width storefront page slices that merchants customise and rearrange.

      Sections form the dynamic layout skeleton of each page template, defining schema settings, storefront spacing controls, and custom block registration.
      • Global Sections: Header, Footer, and Announcement Bar instances rendered across multiple templates.
      • Content Sections: Featured collections, hero banners, rich text, and slideshows tailored for specific page content.
      Copied code variable to clipboard!