feat: implement UX pattern improvements for accessibility & performance

Accessibility:
- Add skip link component for keyboard navigation
- Add visible focus rings on all interactive elements
- Add aria-current="page" to navigation active states
- Ensure 44px minimum touch targets on header icons and filter pills

Product Page (PDP):
- Add accordion layout for Description, Size Guide, Shipping & Returns
- Convert Reviews section to accordion format (open by default)
- Make Add to Basket button sticky on mobile, normal on desktop

Product Cards (home & collection):
- Add "Free delivery over £40" shipping badge
- Add loading="lazy" and decoding="async" to images

Cart Drawer:
- Add "Delivery: Calculated at checkout" label
- Add Remove button to each cart item

All Preview Pages:
- Add skip link to all 7 preview pages
- Wrap main content in <main id="main-content">
- Pass active_page to shop_header for nav highlighting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jamey Greenwood 2026-01-15 00:24:25 +00:00
parent 0c43d65a04
commit aa469ffb50
9 changed files with 318 additions and 96 deletions

View File

@ -105,3 +105,77 @@
.preview-frame[data-mood="dark"], .shop-root[data-mood="dark"] { .preview-frame[data-mood="dark"], .shop-root[data-mood="dark"] {
--t-accent-subtle: hsl(var(--t-accent-h) 30% 15%); --t-accent-subtle: hsl(var(--t-accent-h) 30% 15%);
} }
/* ========================================
ACCESSIBILITY - Focus Rings
Visible focus indicators for all interactive elements
======================================== */
/* Base focus ring style */
.shop-container a:focus-visible,
.shop-container button:focus-visible,
.shop-container input:focus-visible,
.shop-container select:focus-visible,
.shop-container textarea:focus-visible,
.shop-container [tabindex]:focus-visible,
.shop-container details summary:focus-visible {
outline: 2px solid hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
outline-offset: 2px;
}
/* Remove default browser outlines when using focus-visible */
.shop-container a:focus:not(:focus-visible),
.shop-container button:focus:not(:focus-visible),
.shop-container input:focus:not(:focus-visible),
.shop-container select:focus:not(:focus-visible),
.shop-container textarea:focus:not(:focus-visible) {
outline: none;
}
/* Skip link for keyboard navigation */
.skip-link {
position: absolute;
top: -100px;
left: 50%;
transform: translateX(-50%);
background: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
color: var(--t-text-inverse);
padding: 0.75rem 1.5rem;
z-index: 9999;
border-radius: var(--t-radius-button);
font-weight: 600;
text-decoration: none;
transition: top 0.2s ease;
}
.skip-link:focus {
top: 1rem;
}
/* Ensure minimum touch target size (44x44px) */
.shop-container .header-icon-btn {
min-width: 44px;
min-height: 44px;
}
.shop-container .filter-pill {
min-height: 44px;
padding-left: 1rem;
padding-right: 1rem;
}
/* Nav link styling with active state indicator */
.shop-nav a {
padding: 0.5rem 0;
border-bottom: 2px solid transparent;
transition: border-color 0.2s ease, color 0.2s ease;
}
.shop-nav a:hover {
color: var(--t-text-primary);
}
.shop-nav a[aria-current="page"] {
color: var(--t-text-primary);
border-bottom-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
}

View File

@ -3,6 +3,17 @@ defmodule SimpleshopThemeWeb.ThemeLive.PreviewPages do
embed_templates "preview_pages/*" embed_templates "preview_pages/*"
@doc """
Renders the skip link for keyboard navigation accessibility.
"""
def skip_link(assigns) do
~H"""
<a href="#main-content" class="skip-link">
Skip to main content
</a>
"""
end
@doc """ @doc """
Renders the announcement bar. Renders the announcement bar.
""" """
@ -25,6 +36,7 @@ defmodule SimpleshopThemeWeb.ThemeLive.PreviewPages do
attr :theme_settings, :map, required: true attr :theme_settings, :map, required: true
attr :logo_image, :map, default: nil attr :logo_image, :map, default: nil
attr :header_image, :map, default: nil attr :header_image, :map, default: nil
attr :active_page, :string, default: nil
def shop_header(assigns) do def shop_header(assigns) do
~H""" ~H"""
@ -76,10 +88,10 @@ defmodule SimpleshopThemeWeb.ThemeLive.PreviewPages do
</div> </div>
<nav class="shop-nav hidden md:flex" style="gap: 1.5rem; position: relative; z-index: 1;"> <nav class="shop-nav hidden md:flex" style="gap: 1.5rem; position: relative; z-index: 1;">
<a href="#" phx-click="change_preview_page" phx-value-page="home" style="color: var(--t-text-secondary); text-decoration: none; cursor: pointer;">Home</a> <a href="#" phx-click="change_preview_page" phx-value-page="home" aria-current={if @active_page == "home", do: "page"} style="color: var(--t-text-secondary); text-decoration: none; cursor: pointer;">Home</a>
<a href="#" phx-click="change_preview_page" phx-value-page="collection" style="color: var(--t-text-secondary); text-decoration: none; cursor: pointer;">Shop</a> <a href="#" phx-click="change_preview_page" phx-value-page="collection" aria-current={if @active_page in ["collection", "pdp"], do: "page"} style="color: var(--t-text-secondary); text-decoration: none; cursor: pointer;">Shop</a>
<a href="#" phx-click="change_preview_page" phx-value-page="about" style="color: var(--t-text-secondary); text-decoration: none; cursor: pointer;">About</a> <a href="#" phx-click="change_preview_page" phx-value-page="about" aria-current={if @active_page == "about", do: "page"} style="color: var(--t-text-secondary); text-decoration: none; cursor: pointer;">About</a>
<a href="#" phx-click="change_preview_page" phx-value-page="contact" style="color: var(--t-text-secondary); text-decoration: none; cursor: pointer;">Contact</a> <a href="#" phx-click="change_preview_page" phx-value-page="contact" aria-current={if @active_page == "contact", do: "page"} style="color: var(--t-text-secondary); text-decoration: none; cursor: pointer;">Contact</a>
</nav> </nav>
<div class="shop-actions flex items-center gap-1" style="position: relative; z-index: 1;"> <div class="shop-actions flex items-center gap-1" style="position: relative; z-index: 1;">
@ -266,15 +278,24 @@ defmodule SimpleshopThemeWeb.ThemeLive.PreviewPages do
<p style="font-family: var(--t-font-body); font-size: var(--t-text-caption); color: var(--t-text-tertiary); margin: 0;"> <p style="font-family: var(--t-font-body); font-size: var(--t-text-caption); color: var(--t-text-tertiary); margin: 0;">
<%= item.variant %> <%= item.variant %>
</p> </p>
<p class="cart-drawer-item-price" style="color: var(--t-text-primary); font-weight: 500; margin-top: 4px; font-size: var(--t-text-small);"> <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 4px;">
<p class="cart-drawer-item-price" style="color: var(--t-text-primary); font-weight: 500; font-size: var(--t-text-small); margin: 0;">
<%= item.price %> <%= item.price %>
</p> </p>
<button type="button" style="background: none; border: none; padding: 0; cursor: pointer; font-family: var(--t-font-body); font-size: var(--t-text-caption); color: var(--t-text-tertiary); text-decoration: underline;">
Remove
</button>
</div>
</div> </div>
</div> </div>
<% end %> <% end %>
</div> </div>
<div class="cart-drawer-footer" style="padding: 1rem 1.5rem; border-top: 1px solid var(--t-border-default); background: var(--t-surface-sunken);"> <div class="cart-drawer-footer" style="padding: 1rem 1.5rem; border-top: 1px solid var(--t-border-default); background: var(--t-surface-sunken);">
<div style="display: flex; justify-content: space-between; font-family: var(--t-font-body); font-size: var(--t-text-small); color: var(--t-text-secondary); margin-bottom: 0.5rem;">
<span>Delivery</span>
<span>Calculated at checkout</span>
</div>
<div class="cart-drawer-total" style="display: flex; justify-content: space-between; font-family: var(--t-font-body); font-size: var(--t-text-base); font-weight: 600; color: var(--t-text-primary); margin-bottom: 1rem;"> <div class="cart-drawer-total" style="display: flex; justify-content: space-between; font-family: var(--t-font-body); font-size: var(--t-text-base); font-weight: 600; color: var(--t-text-primary); margin-bottom: 1rem;">
<span>Subtotal</span> <span>Subtotal</span>
<span>£72.00</span> <span>£72.00</span>

View File

@ -1,14 +1,17 @@
<div class="shop-container min-h-screen" style="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"> <div class="shop-container min-h-screen" style="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
<!-- Skip Link for Accessibility -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.skip_link />
<!-- Announcement Bar --> <!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %> <%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %> <% end %>
<!-- Header --> <!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="about" />
<!-- Content Page --> <!-- Content Page -->
<div class="content-page" style="background-color: var(--t-surface-base);"> <main id="main-content" class="content-page" style="background-color: var(--t-surface-base);">
<!-- Hero Section --> <!-- Hero Section -->
<div class="content-hero text-center" style="padding: var(--space-2xl) var(--space-lg); background-color: var(--t-surface-sunken);"> <div class="content-hero text-center" style="padding: var(--space-2xl) var(--space-lg); background-color: var(--t-surface-sunken);">
<h1 class="text-3xl md:text-4xl mb-3" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);"> <h1 class="text-3xl md:text-4xl mb-3" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);">
@ -60,7 +63,7 @@
</p> </p>
</div> </div>
</div> </div>
</div> </main>
<!-- Footer --> <!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />

View File

@ -1,13 +1,16 @@
<div class="shop-container min-h-screen" style="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"> <div class="shop-container min-h-screen" style="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
<!-- Skip Link for Accessibility -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.skip_link />
<!-- Announcement Bar --> <!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %> <%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %> <% end %>
<!-- Header --> <!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="cart" />
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <main id="main-content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<h1 class="text-3xl md:text-4xl font-bold mb-8" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);"> <h1 class="text-3xl md:text-4xl font-bold mb-8" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);">
Your basket Your basket
</h1> </h1>
@ -113,7 +116,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </main>
<!-- Footer --> <!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />

View File

@ -1,13 +1,17 @@
<div class="shop-container min-h-screen" style="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"> <div class="shop-container min-h-screen" style="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
<!-- Skip Link for Accessibility -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.skip_link />
<!-- Announcement Bar --> <!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %> <%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %> <% end %>
<!-- Header --> <!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="collection" />
<!-- Page Header --> <!-- Page Header -->
<main id="main-content">
<div class="border-b" style="background-color: var(--t-surface-raised); border-color: var(--t-border-default);"> <div class="border-b" style="background-color: var(--t-surface-raised); border-color: var(--t-border-default);">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<h1 class="text-3xl md:text-4xl font-bold mb-2" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);"> <h1 class="text-3xl md:text-4xl font-bold mb-2" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);">
@ -77,6 +81,8 @@
<img <img
src={product.image_url} src={product.image_url}
alt={product.name} alt={product.name}
loading="lazy"
decoding="async"
class="product-image-primary w-full h-full object-cover transition-opacity duration-300" class="product-image-primary w-full h-full object-cover transition-opacity duration-300"
/> />
<!-- Hover Image --> <!-- Hover Image -->
@ -84,6 +90,8 @@
<img <img
src={product.hover_image_url} src={product.hover_image_url}
alt={product.name} alt={product.name}
loading="lazy"
decoding="async"
class="product-image-hover w-full h-full object-cover" class="product-image-hover w-full h-full object-cover"
/> />
<% end %> <% end %>
@ -111,11 +119,15 @@
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
<p class="text-xs mt-1" style="color: var(--t-text-tertiary);">
Free delivery over £40
</p>
</div> </div>
</div> </div>
<% end %> <% end %>
</div> </div>
</div> </div>
</main>
<!-- Footer --> <!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />

View File

@ -1,13 +1,16 @@
<div class="shop-container min-h-screen" style="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"> <div class="shop-container min-h-screen" style="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
<!-- Skip Link for Accessibility -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.skip_link />
<!-- Announcement Bar --> <!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %> <%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %> <% end %>
<!-- Header --> <!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="contact" />
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-16"> <main id="main-content" class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<h1 class="text-4xl md:text-5xl font-bold mb-6 text-center" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);"> <h1 class="text-4xl md:text-5xl font-bold mb-6 text-center" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);">
Contact Us Contact Us
</h1> </h1>
@ -180,7 +183,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </main>
<!-- Footer --> <!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />

View File

@ -1,13 +1,16 @@
<div class="shop-container min-h-screen" style="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"> <div class="shop-container min-h-screen" style="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
<!-- Skip Link for Accessibility -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.skip_link />
<!-- Announcement Bar --> <!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %> <%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %> <% end %>
<!-- Header --> <!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="error" />
<div class="flex items-center justify-center" style="min-height: calc(100vh - 4rem);"> <main id="main-content" class="flex items-center justify-center" style="min-height: calc(100vh - 4rem);">
<div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-16 text-center"> <div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-16 text-center">
<h1 class="text-8xl md:text-9xl font-bold mb-4" style="font-family: var(--t-font-heading); color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); font-weight: var(--t-heading-weight);"> <h1 class="text-8xl md:text-9xl font-bold mb-4" style="font-family: var(--t-font-heading); color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); font-weight: var(--t-heading-weight);">
404 404
@ -71,7 +74,7 @@
<% end %> <% end %>
</div> </div>
</div> </div>
</div> </main>
<!-- Footer --> <!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />

View File

@ -1,13 +1,17 @@
<div class="shop-container min-h-screen" style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"> <div class="shop-container min-h-screen" style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
<!-- Skip Link for Accessibility -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.skip_link />
<!-- Announcement Bar --> <!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %> <%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %> <% end %>
<!-- Header --> <!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="home" />
<!-- Hero Section --> <!-- Hero Section -->
<main id="main-content">
<section class="text-center" style="padding: var(--space-2xl) var(--space-lg); background-color: var(--t-surface-base);"> <section class="text-center" style="padding: var(--space-2xl) var(--space-lg); background-color: var(--t-surface-base);">
<h1 class="text-3xl md:text-4xl mb-4" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);"> <h1 class="text-3xl md:text-4xl mb-4" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);">
Original designs, printed on demand Original designs, printed on demand
@ -74,12 +78,16 @@
<img <img
src={product.image_url} src={product.image_url}
alt={product.name} alt={product.name}
loading="lazy"
decoding="async"
class="product-image-primary w-full h-full object-cover transition-opacity duration-300" class="product-image-primary w-full h-full object-cover transition-opacity duration-300"
/> />
<%= if @theme_settings.hover_image && product[:hover_image_url] do %> <%= if @theme_settings.hover_image && product[:hover_image_url] do %>
<img <img
src={product.hover_image_url} src={product.hover_image_url}
alt={product.name} alt={product.name}
loading="lazy"
decoding="async"
class="product-image-hover w-full h-full object-cover" class="product-image-hover w-full h-full object-cover"
/> />
<% end %> <% end %>
@ -96,6 +104,9 @@
£<%= product.price / 100 %> £<%= product.price / 100 %>
</p> </p>
<% end %> <% end %>
<p class="text-xs mt-1" style="color: var(--t-text-tertiary);">
Free delivery over £40
</p>
</div> </div>
</div> </div>
<% end %> <% end %>
@ -137,6 +148,7 @@
</a> </a>
</div> </div>
</section> </section>
</main>
<!-- Footer --> <!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />

View File

@ -2,15 +2,18 @@
product = List.first(@preview_data.products) product = List.first(@preview_data.products)
%> %>
<div class="shop-container min-h-screen" style="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"> <div class="shop-container min-h-screen" style="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
<!-- Skip Link for Accessibility -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.skip_link />
<!-- Announcement Bar --> <!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %> <%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %> <% end %>
<!-- Header --> <!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="pdp" />
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <main id="main-content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Breadcrumb --> <!-- Breadcrumb -->
<nav class="mb-8 flex items-center gap-2 text-sm" style="color: var(--t-text-secondary);"> <nav class="mb-8 flex items-center gap-2 text-sm" style="color: var(--t-text-secondary);">
<a href="#" phx-click="change_preview_page" phx-value-page="home" class="hover:underline">Home</a> <a href="#" phx-click="change_preview_page" phx-value-page="home" class="hover:underline">Home</a>
@ -157,10 +160,6 @@
<% end %> <% end %>
</div> </div>
<p class="mb-8 leading-relaxed" style="color: var(--t-text-secondary);">
<%= product.description %>. Crafted with attention to detail and quality materials, this product is designed to last. Perfect for everyday use or special occasions.
</p>
<!-- Variant Options --> <!-- Variant Options -->
<div class="mb-6"> <div class="mb-6">
<label class="block font-semibold mb-2" style="color: var(--t-text-primary);"> <label class="block font-semibold mb-2" style="color: var(--t-text-primary);">
@ -199,14 +198,16 @@
</div> </div>
</div> </div>
<!-- Add to Cart --> <!-- Add to Cart - Sticky on mobile -->
<div class="sticky bottom-0 z-10 py-3 md:relative md:py-0 -mx-4 px-4 md:mx-0 md:px-0 border-t md:border-0 mb-4" style="background-color: var(--t-surface-base); border-color: var(--t-border-subtle);">
<button <button
phx-click={Phoenix.LiveView.JS.add_class("open", to: "#cart-drawer") |> Phoenix.LiveView.JS.add_class("open", to: "#cart-drawer-overlay")} phx-click={Phoenix.LiveView.JS.add_class("open", to: "#cart-drawer") |> Phoenix.LiveView.JS.add_class("open", to: "#cart-drawer-overlay")}
class="w-full px-6 py-4 text-lg font-semibold transition-all mb-4" class="w-full px-6 py-4 text-lg font-semibold transition-all"
style="background-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); color: var(--t-text-inverse); border-radius: var(--t-radius-button); cursor: pointer; border: none;" style="background-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); color: var(--t-text-inverse); border-radius: var(--t-radius-button); cursor: pointer; border: none;"
> >
Add to basket Add to basket
</button> </button>
</div>
<!-- Features / Trust Badges --> <!-- Features / Trust Badges -->
<%= if @theme_settings.pdp_trust_badges do %> <%= if @theme_settings.pdp_trust_badges do %>
@ -231,28 +232,113 @@
</div> </div>
</div> </div>
<% end %> <% end %>
<!-- Product Info Accordion -->
<div class="mt-8 divide-y" style="border-top: 1px solid var(--t-border-subtle); border-bottom: 1px solid var(--t-border-subtle); border-color: var(--t-border-subtle);">
<!-- Description - open by default -->
<details open class="group">
<summary class="flex justify-between items-center py-4 cursor-pointer list-none [&::-webkit-details-marker]:hidden" style="color: var(--t-text-primary);">
<span class="font-semibold">Description</span>
<svg class="w-5 h-5 transition-transform duration-200 group-open:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</summary>
<div class="pb-4 leading-relaxed" style="color: var(--t-text-secondary);">
<p><%= product.description %>. Crafted with attention to detail and quality materials, this product is designed to last. Perfect for everyday use or special occasions.</p>
</div>
</details>
<!-- Size Guide - collapsed by default -->
<details class="group">
<summary class="flex justify-between items-center py-4 cursor-pointer list-none [&::-webkit-details-marker]:hidden" style="color: var(--t-text-primary);">
<span class="font-semibold">Size Guide</span>
<svg class="w-5 h-5 transition-transform duration-200 group-open:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</summary>
<div class="pb-4" style="color: var(--t-text-secondary);">
<table class="w-full text-sm">
<thead>
<tr style="border-bottom: 1px solid var(--t-border-subtle);">
<th class="text-left py-2 font-semibold" style="color: var(--t-text-primary);">Size</th>
<th class="text-left py-2 font-semibold" style="color: var(--t-text-primary);">Chest (cm)</th>
<th class="text-left py-2 font-semibold" style="color: var(--t-text-primary);">Length (cm)</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom: 1px solid var(--t-border-subtle);">
<td class="py-2">S</td>
<td class="py-2">86-91</td>
<td class="py-2">71</td>
</tr>
<tr style="border-bottom: 1px solid var(--t-border-subtle);">
<td class="py-2">M</td>
<td class="py-2">91-96</td>
<td class="py-2">73</td>
</tr>
<tr style="border-bottom: 1px solid var(--t-border-subtle);">
<td class="py-2">L</td>
<td class="py-2">96-101</td>
<td class="py-2">75</td>
</tr>
<tr>
<td class="py-2">XL</td>
<td class="py-2">101-106</td>
<td class="py-2">77</td>
</tr>
</tbody>
</table>
</div>
</details>
<!-- Shipping & Returns - collapsed by default -->
<details class="group">
<summary class="flex justify-between items-center py-4 cursor-pointer list-none [&::-webkit-details-marker]:hidden" style="color: var(--t-text-primary);">
<span class="font-semibold">Shipping & Returns</span>
<svg class="w-5 h-5 transition-transform duration-200 group-open:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</summary>
<div class="pb-4 space-y-3" style="color: var(--t-text-secondary);">
<div>
<p class="font-semibold mb-1" style="color: var(--t-text-primary);">Delivery</p>
<p class="text-sm">Free UK delivery on orders over £40. Standard delivery 3-5 working days. Express delivery available at checkout.</p>
</div>
<div>
<p class="font-semibold mb-1" style="color: var(--t-text-primary);">Returns</p>
<p class="text-sm">We offer a 30-day return policy. Items must be unused and in original packaging. Please contact us to arrange a return.</p>
</div>
</div>
</details>
</div>
</div> </div>
</div> </div>
<!-- Reviews Section --> <!-- Reviews Section - Accordion format, open by default for social proof -->
<%= if @theme_settings.pdp_reviews do %> <%= if @theme_settings.pdp_reviews do %>
<div class="pdp-reviews py-12" style="border-top: 1px solid var(--t-border-default);"> <details open class="pdp-reviews group" style="border-top: 1px solid var(--t-border-default);">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8"> <summary class="flex justify-between items-center py-6 cursor-pointer list-none [&::-webkit-details-marker]:hidden" style="color: var(--t-text-primary);">
<div class="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
<h2 class="text-2xl font-bold" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);"> <h2 class="text-2xl font-bold" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);">
Customer reviews Customer reviews
</h2> </h2>
<div class="flex items-center gap-3"> <div class="flex items-center gap-2">
<div class="flex gap-0.5"> <div class="flex gap-0.5">
<%= for _i <- 1..5 do %> <%= for _i <- 1..5 do %>
<svg class="w-5 h-5" viewBox="0 0 20 20" style="color: #f59e0b;"> <svg class="w-4 h-4" viewBox="0 0 20 20" style="color: #f59e0b;">
<path d="M10 15l-5.878 3.09 1.123-6.545L.489 6.91l6.572-.955L10 0l2.939 5.955 6.572.955-4.756 4.635 1.123 6.545z" fill="currentColor"/> <path d="M10 15l-5.878 3.09 1.123-6.545L.489 6.91l6.572-.955L10 0l2.939 5.955 6.572.955-4.756 4.635 1.123 6.545z" fill="currentColor"/>
</svg> </svg>
<% end %> <% end %>
</div> </div>
<span class="text-sm" style="color: var(--t-text-secondary);">Based on 24 reviews</span> <span class="text-sm" style="color: var(--t-text-secondary);">(24)</span>
</div> </div>
</div> </div>
<svg class="w-5 h-5 transition-transform duration-200 group-open:rotate-180 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</summary>
<div class="pb-8">
<div class="space-y-6"> <div class="space-y-6">
<!-- Review 1 --> <!-- Review 1 -->
<article class="pb-6" style="border-bottom: 1px solid var(--t-border-subtle);"> <article class="pb-6" style="border-bottom: 1px solid var(--t-border-subtle);">
@ -306,6 +392,7 @@
Load more reviews Load more reviews
</button> </button>
</div> </div>
</details>
<% end %> <% end %>
<!-- Related Products --> <!-- Related Products -->
@ -328,12 +415,16 @@
<img <img
src={related_product.image_url} src={related_product.image_url}
alt={related_product.name} alt={related_product.name}
loading="lazy"
decoding="async"
class="product-image-primary w-full h-full object-cover transition-opacity duration-300" class="product-image-primary w-full h-full object-cover transition-opacity duration-300"
/> />
<%= if @theme_settings.hover_image && related_product[:hover_image_url] do %> <%= if @theme_settings.hover_image && related_product[:hover_image_url] do %>
<img <img
src={related_product.hover_image_url} src={related_product.hover_image_url}
alt={related_product.name} alt={related_product.name}
loading="lazy"
decoding="async"
class="product-image-hover w-full h-full object-cover" class="product-image-hover w-full h-full object-cover"
/> />
<% end %> <% end %>
@ -353,7 +444,7 @@
</div> </div>
</div> </div>
<% end %> <% end %>
</div> </main>
<!-- Footer --> <!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} /> <SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />