refactor: extract remaining PDP components to ShopComponents

Add PDP-specific components:
- product_gallery with lightbox
- product_info (title, price, sale badge)
- variant_selector
- quantity_selector
- add_to_cart_button
- product_details accordion
- star_rating
- trust_badges
- reviews_section with review_card

Add page layout components:
- page_title for consistent h1 styling
- cart_layout for cart page structure
- rich_text for structured content blocks
- accordion_item for generic collapsible sections

Update preview pages to be fully component-based:
- PDP: 415 → 48 lines (88% reduction)
- Cart: 47 → 23 lines
- About: 65 → 27 lines

Add preview data functions:
- reviews() for product reviews
- about_content() for rich text blocks

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-17 15:37:58 +00:00
parent 0c15929c19
commit 97981a9884
5 changed files with 813 additions and 447 deletions

View File

@@ -1,64 +1,26 @@
<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 -->
<.skip_link />
<!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %>
<.announcement_bar theme_settings={@theme_settings} />
<% end %>
<!-- Header -->
<.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="about" mode={:preview} cart_count={2} />
<!-- Content Page -->
<main id="main-content" class="content-page" style="background-color: var(--t-surface-base);">
<!-- Hero Section -->
<.hero_section
title="About the studio"
description="Nature photography, printed with care"
background={:sunken}
/>
<!-- Content Body -->
<.content_body image_url="/mockups/night-sky-blanket-3.jpg">
<p class="lead-text text-lg mb-4" style="color: var(--t-text-primary);">
I'm Emma, a nature photographer based in the UK. What started as weekend walks with my camera has grown into something I never expected a little shop where I can share my favourite captures with others.
</p>
<p class="mb-4" style="color: var(--t-text-secondary);">
Every design in this shop comes from my own photography. Whether it's early morning mist over the hills, autumn leaves in the local woods, or the quiet beauty of wildflower meadows, I'm drawn to the peaceful moments that nature offers.
</p>
<p class="mb-4" style="color: var(--t-text-secondary);">
I work with quality print partners to bring these images to life on products you can actually use and enjoy from art prints for your walls to mugs for your morning tea.
</p>
<h2 class="mt-8 mb-3" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); font-size: var(--t-text-xl); color: var(--t-text-primary);">
Quality you can trust
</h2>
<p class="mb-4" style="color: var(--t-text-secondary);">
I've carefully chosen print partners who share my commitment to quality. Every product is made to order using premium materials and printing techniques that ensure vibrant colours and lasting quality.
</p>
<h2 class="mt-8 mb-3" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); font-size: var(--t-text-xl); color: var(--t-text-primary);">
Printed sustainably
</h2>
<p class="mb-4" style="color: var(--t-text-secondary);">
Because each item is printed on demand, there's no waste from unsold stock. My print partners use eco-friendly inks where possible, and products are shipped directly to you to minimise unnecessary handling.
</p>
<p class="mt-8" style="color: var(--t-text-secondary);">
Thank you for visiting. It means a lot that you're here.
</p>
<.rich_text blocks={SimpleshopTheme.Theme.PreviewData.about_content()} />
</.content_body>
</main>
<!-- Footer -->
<.shop_footer theme_settings={@theme_settings} mode={:preview} />
<!-- Search Modal -->
<!-- Cart Drawer -->
<.cart_drawer cart_items={SimpleshopTheme.Theme.PreviewData.cart_drawer_items()} subtotal="£72.00" mode={:preview} />
<.search_modal hint_text={~s(Try searching for "mountain", "forest", or "ocean")} />
</div>

View File

@@ -2,45 +2,21 @@
subtotal = Enum.reduce(@preview_data.cart_items, 0, fn item, acc -> acc + item.product.price * item.quantity end)
%>
<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 -->
<.skip_link />
<!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %>
<.announcement_bar theme_settings={@theme_settings} />
<% end %>
<!-- Header -->
<.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="cart" mode={:preview} cart_count={2} />
<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);">
Your basket
</h1>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Cart Items -->
<div class="lg:col-span-2">
<div class="space-y-4">
<%= for item <- @preview_data.cart_items do %>
<.cart_item item={item} currency="$" />
<% end %>
</div>
</div>
<!-- Order Summary -->
<div>
<.order_summary subtotal={subtotal} mode={:preview} />
</div>
</div>
<.page_title text="Your basket" />
<.cart_layout items={@preview_data.cart_items} subtotal={subtotal} mode={:preview} />
</main>
<!-- Footer -->
<.shop_footer theme_settings={@theme_settings} mode={:preview} />
<!-- Search Modal -->
<!-- Cart Drawer -->
<.cart_drawer cart_items={SimpleshopTheme.Theme.PreviewData.cart_drawer_items()} subtotal="£72.00" mode={:preview} />
<.search_modal hint_text={~s(Try searching for "mountain", "forest", or "ocean")} />
</div>

View File

@@ -1,20 +1,17 @@
<%
product = List.first(@preview_data.products)
gallery_images = [product.image_url, product.hover_image_url, product.image_url, product.hover_image_url]
%>
<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 -->
<.skip_link />
<!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %>
<.announcement_bar theme_settings={@theme_settings} />
<% end %>
<!-- Header -->
<.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="pdp" mode={:preview} cart_count={2} />
<main id="main-content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Breadcrumb -->
<.breadcrumb items={[
%{label: "Home", page: "home", href: "/"},
%{label: product.category, page: "collection", href: "/products"},
@@ -22,393 +19,30 @@
]} mode={:preview} />
<div class="grid grid-cols-1 md:grid-cols-2 gap-12 mb-16">
<!-- Product Images -->
<.product_gallery images={gallery_images} product_name={product.name} />
<div>
<% gallery_images = [product.image_url, product.hover_image_url, product.image_url, product.hover_image_url] %>
<div
class="pdp-main-image-container aspect-square bg-gray-200 mb-4 overflow-hidden relative"
style="border-radius: var(--t-radius-image);"
phx-click={Phoenix.LiveView.JS.exec("data-show", to: "#pdp-lightbox")}
>
<img
id="pdp-main-image"
src={product.image_url}
alt={product.name}
class="w-full h-full object-cover"
/>
<!-- Zoom icon overlay -->
<div class="absolute inset-0 flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity bg-black/10">
<div class="w-12 h-12 bg-white/90 rounded-full flex items-center justify-center shadow-lg">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7" />
</svg>
</div>
</div>
</div>
<div class="grid grid-cols-4 gap-4">
<%= for {img_url, idx} <- Enum.with_index(gallery_images) do %>
<button
type="button"
class={"aspect-square bg-gray-200 cursor-pointer overflow-hidden pdp-thumbnail#{if idx == 0, do: " pdp-thumbnail-active", else: ""}"}
style="border-radius: var(--t-radius-image);"
data-index={idx}
phx-click={Phoenix.LiveView.JS.set_attribute({"src", img_url}, to: "#pdp-main-image")
|> Phoenix.LiveView.JS.set_attribute({"data-current-index", to_string(idx)}, to: "#pdp-lightbox")
|> Phoenix.LiveView.JS.remove_class("pdp-thumbnail-active", to: ".pdp-thumbnail")
|> Phoenix.LiveView.JS.add_class("pdp-thumbnail-active")}
>
<img
src={img_url}
alt={product.name}
class="w-full h-full object-cover"
/>
</button>
<% end %>
</div>
<style>
.pdp-thumbnail {
border: 2px solid var(--t-border-default);
transition: border-color 0.15s ease;
}
.pdp-thumbnail-active {
border-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
}
</style>
<!-- Image Lightbox -->
<dialog
class="lightbox"
id="pdp-lightbox"
aria-label="Product image gallery"
data-current-index="0"
data-images={Jason.encode!(gallery_images)}
data-show={Phoenix.LiveView.JS.exec("phx-show-lightbox", to: "#pdp-lightbox")}
phx-show-lightbox={Phoenix.LiveView.JS.dispatch("pdp:open-lightbox", to: "#pdp-lightbox")}
phx-hook="Lightbox"
>
<div class="lightbox-content">
<button
type="button"
class="lightbox-close"
aria-label="Close gallery"
phx-click={Phoenix.LiveView.JS.dispatch("pdp:close-lightbox", to: "#pdp-lightbox")}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<button
type="button"
class="lightbox-nav lightbox-prev"
aria-label="Previous image"
phx-click={Phoenix.LiveView.JS.dispatch("pdp:prev-image", to: "#pdp-lightbox")}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</button>
<figure class="lightbox-figure">
<div class="lightbox-image-container">
<img
class="lightbox-image"
id="lightbox-image"
src={product.image_url}
alt={product.name}
/>
</div>
<figcaption class="lightbox-caption"><%= product.name %></figcaption>
</figure>
<button
type="button"
class="lightbox-nav lightbox-next"
aria-label="Next image"
phx-click={Phoenix.LiveView.JS.dispatch("pdp:next-image", to: "#pdp-lightbox")}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</button>
<div class="lightbox-counter" id="lightbox-counter">1 / <%= length(gallery_images) %></div>
</div>
</dialog>
</div>
<!-- Product Info -->
<div>
<h1 class="text-3xl md:text-4xl font-bold mb-4" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);">
<%= product.name %>
</h1>
<div class="flex items-center gap-4 mb-6">
<%= if product.on_sale do %>
<span class="text-3xl font-bold" style="color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));">
£<%= product.price / 100 %>
</span>
<span class="text-xl line-through" style="color: var(--t-text-tertiary);">
£<%= product.compare_at_price / 100 %>
</span>
<span class="px-2 py-1 text-sm font-bold text-white rounded" style="background-color: var(--t-sale-color);">
SAVE <%= round((product.compare_at_price - product.price) / product.compare_at_price * 100) %>%
</span>
<% else %>
<span class="text-3xl font-bold" style="color: var(--t-text-primary);">
£<%= product.price / 100 %>
</span>
<% end %>
</div>
<!-- Variant Options -->
<div class="mb-6">
<label class="block font-semibold mb-2" style="color: var(--t-text-primary);">
Size
</label>
<div class="flex gap-2">
<%= for size <- ["S", "M", "L", "XL"] do %>
<button
class="px-4 py-2 font-medium transition-all"
style="border: 2px solid var(--t-border-default); border-radius: var(--t-radius-button); color: var(--t-text-primary);"
>
<%= size %>
</button>
<% end %>
</div>
</div>
<!-- Quantity -->
<div class="mb-8">
<label class="block font-semibold mb-2" style="color: var(--t-text-primary);">
Quantity
</label>
<div class="flex items-center gap-4">
<div class="flex items-center" style="border: 2px solid var(--t-border-default); border-radius: var(--t-radius-input);">
<button class="px-4 py-2" style="color: var(--t-text-primary);"></button>
<span class="px-4 py-2 border-x-2" style="border-color: var(--t-border-default); color: var(--t-text-primary);">
1
</span>
<button class="px-4 py-2" style="color: var(--t-text-primary);">+</button>
</div>
<%= if product.in_stock do %>
<span class="text-sm" style="color: var(--t-text-tertiary);">In stock</span>
<% else %>
<span class="text-sm font-semibold" style="color: var(--t-sale-color);">Out of stock</span>
<% end %>
</div>
</div>
<!-- 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
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"
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
</button>
</div>
<!-- Features / Trust Badges -->
<%= if @theme_settings.pdp_trust_badges do %>
<div class="p-4 space-y-3" style="background-color: var(--t-surface-sunken); border-radius: var(--t-radius-card);">
<div class="flex items-start gap-3">
<svg class="w-5 h-5 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
<div>
<p class="font-semibold" style="color: var(--t-text-primary);">Free Delivery</p>
<p class="text-sm" style="color: var(--t-text-secondary);">On orders over £40</p>
</div>
</div>
<div class="flex items-start gap-3">
<svg class="w-5 h-5 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div>
<p class="font-semibold" style="color: var(--t-text-primary);">Easy Returns</p>
<p class="text-sm" style="color: var(--t-text-secondary);">30-day return policy</p>
</div>
</div>
</div>
<% 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>
<.product_info product={product} />
<.variant_selector label="Size" options={["S", "M", "L", "XL"]} />
<.quantity_selector quantity={1} in_stock={product.in_stock} />
<.add_to_cart_button />
<.trust_badges :if={@theme_settings.pdp_trust_badges} />
<.product_details product={product} />
</div>
</div>
<!-- Reviews Section - Accordion format, open by default for social proof -->
<%= if @theme_settings.pdp_reviews do %>
<details open class="pdp-reviews group" style="border-top: 1px solid var(--t-border-default);">
<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);">
Customer reviews
</h2>
<div class="flex items-center gap-2">
<div class="flex gap-0.5">
<%= for _i <- 1..5 do %>
<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"/>
</svg>
<% end %>
</div>
<span class="text-sm" style="color: var(--t-text-secondary);">(24)</span>
</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>
<.reviews_section :if={@theme_settings.pdp_reviews} reviews={SimpleshopTheme.Theme.PreviewData.reviews()} average_rating={5} total_count={24} />
<div class="pb-8">
<div class="space-y-6">
<!-- Review 1 -->
<article class="pb-6" style="border-bottom: 1px solid var(--t-border-subtle);">
<div class="flex items-center justify-between mb-2">
<div class="flex gap-0.5">
<%= for _i <- 1..5 do %>
<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"/>
</svg>
<% end %>
</div>
<span class="text-xs" style="color: var(--t-text-tertiary);">2 weeks ago</span>
</div>
<h3 class="font-semibold mb-1" style="color: var(--t-text-primary);">Absolutely beautiful</h3>
<p class="text-sm mb-3" style="color: var(--t-text-secondary); line-height: 1.6;">
The quality exceeded my expectations. The colours are vibrant and the paper feels premium. It's now pride of place in my living room.
</p>
<div class="flex items-center gap-2">
<span class="text-sm font-medium" style="color: var(--t-text-primary);">Sarah M.</span>
<span class="text-xs px-2 py-0.5 rounded" style="background-color: var(--t-surface-sunken); color: var(--t-text-tertiary);">Verified purchase</span>
</div>
</article>
<!-- Review 2 -->
<article class="pb-6" style="border-bottom: 1px solid var(--t-border-subtle);">
<div class="flex items-center justify-between mb-2">
<div class="flex gap-0.5">
<%= for i <- 1..5 do %>
<svg class="w-4 h-4" viewBox="0 0 20 20" style={"color: #{if i <= 4, do: "#f59e0b", else: "var(--t-border-default)"};"}>
<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>
<% end %>
</div>
<span class="text-xs" style="color: var(--t-text-tertiary);">1 month ago</span>
</div>
<h3 class="font-semibold mb-1" style="color: var(--t-text-primary);">Great gift</h3>
<p class="text-sm mb-3" style="color: var(--t-text-secondary); line-height: 1.6;">
Bought this as a gift and it arrived beautifully packaged. Fast shipping too. Would definitely order again.
</p>
<div class="flex items-center gap-2">
<span class="text-sm font-medium" style="color: var(--t-text-primary);">James T.</span>
<span class="text-xs px-2 py-0.5 rounded" style="background-color: var(--t-surface-sunken); color: var(--t-text-tertiary);">Verified purchase</span>
</div>
</article>
</div>
<button
class="mt-6 px-6 py-2 text-sm font-medium transition-all mx-auto block"
style="background-color: transparent; color: var(--t-text-primary); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-button);"
>
Load more reviews
</button>
</div>
</details>
<% end %>
<!-- Related Products -->
<%= if @theme_settings.pdp_related_products do %>
<.related_products_section
products={Enum.slice(@preview_data.products, 1, 4)}
theme_settings={@theme_settings}
mode={:preview}
/>
<% end %>
<.related_products_section
:if={@theme_settings.pdp_related_products}
products={Enum.slice(@preview_data.products, 1, 4)}
theme_settings={@theme_settings}
mode={:preview}
/>
</main>
<!-- Footer -->
<.shop_footer theme_settings={@theme_settings} mode={:preview} />
<!-- Search Modal -->
<!-- Cart Drawer -->
<.cart_drawer cart_items={SimpleshopTheme.Theme.PreviewData.cart_drawer_items()} subtotal="£72.00" mode={:preview} />
<.search_modal hint_text={~s(Try searching for "mountain", "forest", or "ocean")} />
</div>