perf: use responsive images for theme preview mockups
Update theme preview to use optimized responsive images with modern format support (AVIF/WebP with JPEG fallback). - Change mockup URLs from .jpg to base paths for srcset generation - Add source_width to preview products for proper variant selection - Add responsive_image component with <picture> element - Update image_text_section to use optimized 800px WebP variant This ensures the theme preview loads optimal image formats and sizes, matching the production responsive image behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0ade34d994
commit
2c3d8f5647
@ -163,6 +163,9 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Default source width for mockup variants (max generated size)
|
||||||
|
@mockup_source_width 1200
|
||||||
|
|
||||||
defp mock_products do
|
defp mock_products do
|
||||||
[
|
[
|
||||||
# Art Prints
|
# Art Prints
|
||||||
@ -172,8 +175,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Capture the magic of dawn with this stunning mountain landscape print",
|
description: "Capture the magic of dawn with this stunning mountain landscape print",
|
||||||
price: 2400,
|
price: 2400,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "/mockups/mountain-sunrise-print-1.jpg",
|
image_url: "/mockups/mountain-sunrise-print-1",
|
||||||
hover_image_url: "/mockups/mountain-sunrise-print-2.jpg",
|
hover_image_url: "/mockups/mountain-sunrise-print-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Art Prints",
|
category: "Art Prints",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
@ -184,8 +189,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "A calming sunset over ocean waves to bring peace to any room",
|
description: "A calming sunset over ocean waves to bring peace to any room",
|
||||||
price: 2400,
|
price: 2400,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "/mockups/ocean-waves-print-1.jpg",
|
image_url: "/mockups/ocean-waves-print-1",
|
||||||
hover_image_url: "/mockups/ocean-waves-print-2.jpg",
|
hover_image_url: "/mockups/ocean-waves-print-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Art Prints",
|
category: "Art Prints",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
@ -196,8 +203,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Beautiful wildflower meadow captured in the summer sunshine",
|
description: "Beautiful wildflower meadow captured in the summer sunshine",
|
||||||
price: 2400,
|
price: 2400,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "/mockups/wildflower-meadow-print-1.jpg",
|
image_url: "/mockups/wildflower-meadow-print-1",
|
||||||
hover_image_url: "/mockups/wildflower-meadow-print-2.jpg",
|
hover_image_url: "/mockups/wildflower-meadow-print-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Art Prints",
|
category: "Art Prints",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
@ -208,8 +217,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Modern minimalist design with bold geometric shapes",
|
description: "Modern minimalist design with bold geometric shapes",
|
||||||
price: 2800,
|
price: 2800,
|
||||||
compare_at_price: 3200,
|
compare_at_price: 3200,
|
||||||
image_url: "/mockups/geometric-abstract-print-1.jpg",
|
image_url: "/mockups/geometric-abstract-print-1",
|
||||||
hover_image_url: "/mockups/geometric-abstract-print-2.jpg",
|
hover_image_url: "/mockups/geometric-abstract-print-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Art Prints",
|
category: "Art Prints",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: true
|
on_sale: true
|
||||||
@ -220,8 +231,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Vintage-inspired botanical drawing with intricate detail",
|
description: "Vintage-inspired botanical drawing with intricate detail",
|
||||||
price: 2400,
|
price: 2400,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "/mockups/botanical-illustration-print-1.jpg",
|
image_url: "/mockups/botanical-illustration-print-1",
|
||||||
hover_image_url: "/mockups/botanical-illustration-print-2.jpg",
|
hover_image_url: "/mockups/botanical-illustration-print-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Art Prints",
|
category: "Art Prints",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
@ -233,8 +246,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Soft cotton tee featuring a peaceful forest silhouette design",
|
description: "Soft cotton tee featuring a peaceful forest silhouette design",
|
||||||
price: 2999,
|
price: 2999,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "/mockups/forest-silhouette-tshirt-1.jpg",
|
image_url: "/mockups/forest-silhouette-tshirt-1",
|
||||||
hover_image_url: "/mockups/forest-silhouette-tshirt-2.jpg",
|
hover_image_url: "/mockups/forest-silhouette-tshirt-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Apparel",
|
category: "Apparel",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
@ -245,8 +260,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Cosy fleece hoodie with stunning forest light photography",
|
description: "Cosy fleece hoodie with stunning forest light photography",
|
||||||
price: 4499,
|
price: 4499,
|
||||||
compare_at_price: 4999,
|
compare_at_price: 4999,
|
||||||
image_url: "/mockups/forest-light-hoodie-1.jpg",
|
image_url: "/mockups/forest-light-hoodie-1",
|
||||||
hover_image_url: "/mockups/forest-light-hoodie-2.jpg",
|
hover_image_url: "/mockups/forest-light-hoodie-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Apparel",
|
category: "Apparel",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: true
|
on_sale: true
|
||||||
@ -257,8 +274,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Sturdy cotton tote bag with vibrant wildflower design",
|
description: "Sturdy cotton tote bag with vibrant wildflower design",
|
||||||
price: 1999,
|
price: 1999,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "/mockups/wildflower-meadow-tote-1.jpg",
|
image_url: "/mockups/wildflower-meadow-tote-1",
|
||||||
hover_image_url: "/mockups/wildflower-meadow-tote-2.jpg",
|
hover_image_url: "/mockups/wildflower-meadow-tote-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Apparel",
|
category: "Apparel",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
@ -269,8 +288,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Beautiful ocean sunset printed on durable canvas tote",
|
description: "Beautiful ocean sunset printed on durable canvas tote",
|
||||||
price: 1999,
|
price: 1999,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "/mockups/sunset-gradient-tote-1.jpg",
|
image_url: "/mockups/sunset-gradient-tote-1",
|
||||||
hover_image_url: "/mockups/sunset-gradient-tote-2.jpg",
|
hover_image_url: "/mockups/sunset-gradient-tote-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Apparel",
|
category: "Apparel",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
@ -282,8 +303,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Start your morning right with this nature-inspired ceramic mug",
|
description: "Start your morning right with this nature-inspired ceramic mug",
|
||||||
price: 1499,
|
price: 1499,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "/mockups/fern-leaf-mug-1.jpg",
|
image_url: "/mockups/fern-leaf-mug-1",
|
||||||
hover_image_url: "/mockups/fern-leaf-mug-2.jpg",
|
hover_image_url: "/mockups/fern-leaf-mug-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Homewares",
|
category: "Homewares",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
@ -294,8 +317,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Soft polyester cushion featuring a stunning ocean sunset",
|
description: "Soft polyester cushion featuring a stunning ocean sunset",
|
||||||
price: 2999,
|
price: 2999,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "/mockups/ocean-waves-cushion-1.jpg",
|
image_url: "/mockups/ocean-waves-cushion-1",
|
||||||
hover_image_url: "/mockups/ocean-waves-cushion-2.jpg",
|
hover_image_url: "/mockups/ocean-waves-cushion-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Homewares",
|
category: "Homewares",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
@ -306,8 +331,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Cosy sherpa fleece blanket with mesmerising milky way print",
|
description: "Cosy sherpa fleece blanket with mesmerising milky way print",
|
||||||
price: 5999,
|
price: 5999,
|
||||||
compare_at_price: 6999,
|
compare_at_price: 6999,
|
||||||
image_url: "/mockups/night-sky-blanket-1.jpg",
|
image_url: "/mockups/night-sky-blanket-1",
|
||||||
hover_image_url: "/mockups/night-sky-blanket-2.jpg",
|
hover_image_url: "/mockups/night-sky-blanket-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Homewares",
|
category: "Homewares",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: true
|
on_sale: true
|
||||||
@ -319,8 +346,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Hardcover journal with beautiful autumn foliage design",
|
description: "Hardcover journal with beautiful autumn foliage design",
|
||||||
price: 1999,
|
price: 1999,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "/mockups/autumn-leaves-notebook-1.jpg",
|
image_url: "/mockups/autumn-leaves-notebook-1",
|
||||||
hover_image_url: "/mockups/autumn-leaves-notebook-2.jpg",
|
hover_image_url: "/mockups/autumn-leaves-notebook-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Stationery",
|
category: "Stationery",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
@ -331,8 +360,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Tropical-inspired hardcover journal for your thoughts",
|
description: "Tropical-inspired hardcover journal for your thoughts",
|
||||||
price: 1999,
|
price: 1999,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "/mockups/monstera-leaf-notebook-1.jpg",
|
image_url: "/mockups/monstera-leaf-notebook-1",
|
||||||
hover_image_url: "/mockups/monstera-leaf-notebook-2.jpg",
|
hover_image_url: "/mockups/monstera-leaf-notebook-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Stationery",
|
category: "Stationery",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
@ -344,8 +375,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Tough phone case with stunning monstera leaf photography",
|
description: "Tough phone case with stunning monstera leaf photography",
|
||||||
price: 2499,
|
price: 2499,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "/mockups/monstera-leaf-phone-case-1.jpg",
|
image_url: "/mockups/monstera-leaf-phone-case-1",
|
||||||
hover_image_url: "/mockups/monstera-leaf-phone-case-2.jpg",
|
hover_image_url: "/mockups/monstera-leaf-phone-case-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Accessories",
|
category: "Accessories",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
@ -356,8 +389,10 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
description: "Protective laptop sleeve with abstract blue gradient design",
|
description: "Protective laptop sleeve with abstract blue gradient design",
|
||||||
price: 3499,
|
price: 3499,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "/mockups/blue-waves-laptop-sleeve-1.jpg",
|
image_url: "/mockups/blue-waves-laptop-sleeve-1",
|
||||||
hover_image_url: "/mockups/blue-waves-laptop-sleeve-2.jpg",
|
hover_image_url: "/mockups/blue-waves-laptop-sleeve-2",
|
||||||
|
source_width: @mockup_source_width,
|
||||||
|
hover_source_width: @mockup_source_width,
|
||||||
category: "Accessories",
|
category: "Accessories",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
@ -441,35 +476,35 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
name: "Art Prints",
|
name: "Art Prints",
|
||||||
slug: "art-prints",
|
slug: "art-prints",
|
||||||
product_count: 5,
|
product_count: 5,
|
||||||
image_url: "/mockups/mountain-sunrise-print-2.jpg"
|
image_url: "/mockups/mountain-sunrise-print-2-thumb.jpg"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "2",
|
id: "2",
|
||||||
name: "Apparel",
|
name: "Apparel",
|
||||||
slug: "apparel",
|
slug: "apparel",
|
||||||
product_count: 4,
|
product_count: 4,
|
||||||
image_url: "/mockups/forest-silhouette-tshirt-1.jpg"
|
image_url: "/mockups/forest-silhouette-tshirt-1-thumb.jpg"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "3",
|
id: "3",
|
||||||
name: "Homewares",
|
name: "Homewares",
|
||||||
slug: "homewares",
|
slug: "homewares",
|
||||||
product_count: 3,
|
product_count: 3,
|
||||||
image_url: "/mockups/fern-leaf-mug-1.jpg"
|
image_url: "/mockups/fern-leaf-mug-1-thumb.jpg"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "4",
|
id: "4",
|
||||||
name: "Stationery",
|
name: "Stationery",
|
||||||
slug: "stationery",
|
slug: "stationery",
|
||||||
product_count: 2,
|
product_count: 2,
|
||||||
image_url: "/mockups/autumn-leaves-notebook-1.jpg"
|
image_url: "/mockups/autumn-leaves-notebook-1-thumb.jpg"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "5",
|
id: "5",
|
||||||
name: "Accessories",
|
name: "Accessories",
|
||||||
slug: "accessories",
|
slug: "accessories",
|
||||||
product_count: 2,
|
product_count: 2,
|
||||||
image_url: "/mockups/monstera-leaf-phone-case-1.jpg"
|
image_url: "/mockups/monstera-leaf-phone-case-1-thumb.jpg"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
<.image_text_section
|
<.image_text_section
|
||||||
title="Made with passion, printed with care"
|
title="Made with passion, printed with care"
|
||||||
description="Every design starts with an idea. We work with quality print partners to bring those ideas to life on premium products – from gallery-quality art prints to everyday essentials."
|
description="Every design starts with an idea. We work with quality print partners to bring those ideas to life on premium products – from gallery-quality art prints to everyday essentials."
|
||||||
image_url="/mockups/mountain-sunrise-print-3.jpg"
|
image_url="/mockups/mountain-sunrise-print-3-800.webp"
|
||||||
link_text="Learn more about the studio →"
|
link_text="Learn more about the studio →"
|
||||||
link_page="about"
|
link_page="about"
|
||||||
mode={@mode}
|
mode={@mode}
|
||||||
|
|||||||
@ -32,7 +32,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
|||||||
~H"""
|
~H"""
|
||||||
<div
|
<div
|
||||||
class="announcement-bar"
|
class="announcement-bar"
|
||||||
style="background-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); color: var(--t-text-inverse); text-align: center; padding: 0.5rem 1rem; font-size: var(--t-text-small);"
|
style="background-color: var(--t-accent-button, hsl(var(--t-accent-h) var(--t-accent-s) 42%)); color: var(--t-text-inverse); text-align: center; padding: 0.5rem 1rem; font-size: var(--t-text-small);"
|
||||||
>
|
>
|
||||||
<p style="margin: 0;">{@message}</p>
|
<p style="margin: 0;">{@message}</p>
|
||||||
</div>
|
</div>
|
||||||
@ -143,7 +143,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
|||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
phx-value-page={@page}
|
phx-value-page={@page}
|
||||||
class="flex flex-col items-center justify-center gap-1 py-2 mx-1 rounded-lg min-h-[56px]"
|
class="flex flex-col items-center justify-center gap-1 py-2 mx-1 rounded-lg min-h-[56px]"
|
||||||
style={"color: #{if @is_current, do: "hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l))", else: "var(--t-text-secondary)"}; text-decoration: none; font-weight: #{if @is_current, do: "600", else: "500"}; background-color: #{if @is_current, do: "hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l) / 0.1)", else: "transparent"};"}
|
style={"color: #{if @is_current, do: "hsl(var(--t-accent-h) var(--t-accent-s) calc(var(--t-accent-l) - 15%))", else: "var(--t-text-secondary)"}; text-decoration: none; font-weight: #{if @is_current, do: "600", else: "500"}; background-color: #{if @is_current, do: "hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l) / 0.1)", else: "transparent"};"}
|
||||||
aria-current={if @is_current, do: "page", else: nil}
|
aria-current={if @is_current, do: "page", else: nil}
|
||||||
>
|
>
|
||||||
<.nav_icon icon={@icon} size={if @is_current, do: "w-6 h-6", else: "w-5 h-5"} />
|
<.nav_icon icon={@icon} size={if @is_current, do: "w-6 h-6", else: "w-5 h-5"} />
|
||||||
@ -153,7 +153,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
|||||||
<a
|
<a
|
||||||
href={@href}
|
href={@href}
|
||||||
class="flex flex-col items-center justify-center gap-1 py-2 mx-1 rounded-lg min-h-[56px]"
|
class="flex flex-col items-center justify-center gap-1 py-2 mx-1 rounded-lg min-h-[56px]"
|
||||||
style={"color: #{if @is_current, do: "hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l))", else: "var(--t-text-secondary)"}; text-decoration: none; font-weight: #{if @is_current, do: "600", else: "500"}; background-color: #{if @is_current, do: "hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l) / 0.1)", else: "transparent"};"}
|
style={"color: #{if @is_current, do: "hsl(var(--t-accent-h) var(--t-accent-s) calc(var(--t-accent-l) - 15%))", else: "var(--t-text-secondary)"}; text-decoration: none; font-weight: #{if @is_current, do: "600", else: "500"}; background-color: #{if @is_current, do: "hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l) / 0.1)", else: "transparent"};"}
|
||||||
aria-current={if @is_current, do: "page", else: nil}
|
aria-current={if @is_current, do: "page", else: nil}
|
||||||
>
|
>
|
||||||
<.nav_icon icon={@icon} size={if @is_current, do: "w-6 h-6", else: "w-5 h-5"} />
|
<.nav_icon icon={@icon} size={if @is_current, do: "w-6 h-6", else: "w-5 h-5"} />
|
||||||
@ -691,7 +691,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="cart-drawer-checkout w-full mb-2"
|
class="cart-drawer-checkout w-full mb-2"
|
||||||
style="width: 100%; padding: 0.75rem 1.5rem; font-weight: 600; transition-all; background: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); color: var(--t-text-inverse); border-radius: var(--t-radius-button); border: none; cursor: pointer; font-family: var(--t-font-body);"
|
style="width: 100%; padding: 0.75rem 1.5rem; font-weight: 600; transition: all 0.2s ease; background: var(--t-accent-button, hsl(var(--t-accent-h) var(--t-accent-s) 42%)); color: var(--t-text-inverse); border-radius: var(--t-radius-button); border: none; cursor: pointer; font-family: var(--t-font-body);"
|
||||||
>
|
>
|
||||||
Checkout
|
Checkout
|
||||||
</button>
|
</button>
|
||||||
@ -726,15 +726,6 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
|
||||||
.cart-drawer.open {
|
|
||||||
right: 0 !important;
|
|
||||||
}
|
|
||||||
.cart-drawer-overlay.open {
|
|
||||||
opacity: 1 !important;
|
|
||||||
visibility: visible !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -857,23 +848,16 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
|||||||
<%= if @show_badges do %>
|
<%= if @show_badges do %>
|
||||||
<.product_badge product={@product} />
|
<.product_badge product={@product} />
|
||||||
<% end %>
|
<% end %>
|
||||||
<img
|
<.product_card_image
|
||||||
src={@product.image_url}
|
product={@product}
|
||||||
alt={@product.name}
|
variant={@variant}
|
||||||
width="600"
|
|
||||||
height="600"
|
|
||||||
loading={if @variant == :minimal, do: nil, else: "lazy"}
|
|
||||||
decoding={if @variant == :minimal, do: nil, else: "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
|
<.product_card_image
|
||||||
src={@product.hover_image_url}
|
product={@product}
|
||||||
alt={@product.name}
|
variant={@variant}
|
||||||
width="600"
|
image_key={:hover}
|
||||||
height="600"
|
|
||||||
loading={if @variant == :minimal, do: nil, else: "lazy"}
|
|
||||||
decoding={if @variant == :minimal, do: nil, else: "async"}
|
|
||||||
class="product-image-hover w-full h-full object-cover"
|
class="product-image-hover w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -899,6 +883,66 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
|||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Helper to render product images with responsive variants.
|
||||||
|
# Works for both mockups (static files) and database images.
|
||||||
|
# Requires source_width to be set for responsive image support.
|
||||||
|
attr :product, :map, required: true
|
||||||
|
attr :variant, :atom, required: true
|
||||||
|
attr :class, :string, default: ""
|
||||||
|
attr :image_key, :atom, default: :primary
|
||||||
|
|
||||||
|
defp product_card_image(assigns) do
|
||||||
|
# Determine which image fields to use based on primary vs hover
|
||||||
|
{image_id_field, image_url_field, source_width_field} =
|
||||||
|
case assigns.image_key do
|
||||||
|
:hover -> {:hover_image_id, :hover_image_url, :hover_source_width}
|
||||||
|
_ -> {:image_id, :image_url, :source_width}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Build the base image path:
|
||||||
|
# - Database images: /images/{id}/variant
|
||||||
|
# - Mockup images: {image_url} (e.g., /mockups/product-1)
|
||||||
|
image_id = assigns.product[image_id_field]
|
||||||
|
image_url = assigns.product[image_url_field]
|
||||||
|
|
||||||
|
src =
|
||||||
|
if image_id do
|
||||||
|
"/images/#{image_id}/variant"
|
||||||
|
else
|
||||||
|
image_url
|
||||||
|
end
|
||||||
|
|
||||||
|
assigns =
|
||||||
|
assigns
|
||||||
|
|> assign(:src, src)
|
||||||
|
|> assign(:source_width, assigns.product[source_width_field])
|
||||||
|
|
||||||
|
~H"""
|
||||||
|
<%= if @source_width do %>
|
||||||
|
<.responsive_image
|
||||||
|
src={@src}
|
||||||
|
alt={@product.name}
|
||||||
|
source_width={@source_width}
|
||||||
|
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 400px"
|
||||||
|
class={@class}
|
||||||
|
width={600}
|
||||||
|
height={600}
|
||||||
|
priority={@variant == :minimal}
|
||||||
|
/>
|
||||||
|
<% else %>
|
||||||
|
<img
|
||||||
|
src={@src}
|
||||||
|
alt={@product.name}
|
||||||
|
width="600"
|
||||||
|
height="600"
|
||||||
|
loading={if @variant == :minimal, do: nil, else: "lazy"}
|
||||||
|
decoding={if @variant == :minimal, do: nil, else: "async"}
|
||||||
|
class={@class}
|
||||||
|
/>
|
||||||
|
<% end %>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
attr :product, :map, required: true
|
attr :product, :map, required: true
|
||||||
|
|
||||||
defp product_badge(assigns) do
|
defp product_badge(assigns) do
|
||||||
@ -1290,6 +1334,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
|||||||
def category_nav(assigns) do
|
def category_nav(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<section style="padding: var(--space-xl) var(--space-lg); background-color: var(--t-surface-base);">
|
<section style="padding: var(--space-xl) var(--space-lg); background-color: var(--t-surface-base);">
|
||||||
|
<h2 class="sr-only">Shop by Category</h2>
|
||||||
<nav class="grid grid-cols-3 gap-4 max-w-3xl mx-auto" aria-label="Product categories">
|
<nav class="grid grid-cols-3 gap-4 max-w-3xl mx-auto" aria-label="Product categories">
|
||||||
<%= for category <- Enum.take(@categories, @limit) do %>
|
<%= for category <- Enum.take(@categories, @limit) do %>
|
||||||
<%= if @mode == :preview do %>
|
<%= if @mode == :preview do %>
|
||||||
@ -1475,7 +1520,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
|||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
phx-value-page={@link_page}
|
phx-value-page={@link_page}
|
||||||
class="text-sm font-medium transition-colors"
|
class="text-sm font-medium transition-colors"
|
||||||
style="color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); text-decoration: none; cursor: pointer;"
|
style="color: var(--t-accent-text, hsl(var(--t-accent-h) var(--t-accent-s) 38%)); text-decoration: none; cursor: pointer;"
|
||||||
>
|
>
|
||||||
<%= @link_text %>
|
<%= @link_text %>
|
||||||
</a>
|
</a>
|
||||||
@ -1483,7 +1528,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
|||||||
<a
|
<a
|
||||||
href={@link_href || "/"}
|
href={@link_href || "/"}
|
||||||
class="text-sm font-medium transition-colors"
|
class="text-sm font-medium transition-colors"
|
||||||
style="color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); text-decoration: none;"
|
style="color: var(--t-accent-text, hsl(var(--t-accent-h) var(--t-accent-s) 38%)); text-decoration: none;"
|
||||||
>
|
>
|
||||||
<%= @link_text %>
|
<%= @link_text %>
|
||||||
</a>
|
</a>
|
||||||
@ -1980,28 +2025,26 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
|||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
# Renders a social media icon for the given platform.
|
||||||
Renders a social media icon for the given platform.
|
#
|
||||||
|
# All icons are from Simple Icons (simpleicons.org), MIT licensed.
|
||||||
All icons are from Simple Icons (simpleicons.org), MIT licensed.
|
#
|
||||||
|
# ## Supported platforms
|
||||||
## Supported platforms
|
#
|
||||||
|
# **Commercial/Creative:**
|
||||||
**Commercial/Creative:**
|
# :instagram, :pinterest, :tiktok, :facebook, :twitter, :youtube, :patreon, :kofi, :etsy, :gumroad, :bandcamp
|
||||||
:instagram, :pinterest, :tiktok, :facebook, :twitter, :youtube, :patreon, :kofi, :etsy, :gumroad, :bandcamp
|
#
|
||||||
|
# **Open Web/Federated:**
|
||||||
**Open Web/Federated:**
|
# :mastodon, :pixelfed, :bluesky, :peertube, :lemmy, :matrix
|
||||||
:mastodon, :pixelfed, :bluesky, :peertube, :lemmy, :matrix
|
#
|
||||||
|
# **Developer/Hacker:**
|
||||||
**Developer/Hacker:**
|
# :github, :gitlab, :codeberg, :sourcehut
|
||||||
:github, :gitlab, :codeberg, :sourcehut
|
#
|
||||||
|
# **Communication:**
|
||||||
**Communication:**
|
# :discord, :telegram, :signal
|
||||||
:discord, :telegram, :signal
|
#
|
||||||
|
# **Other:**
|
||||||
**Other:**
|
# :substack, :rss, :website
|
||||||
:substack, :rss, :website
|
|
||||||
"""
|
|
||||||
attr :platform, :atom, required: true
|
attr :platform, :atom, required: true
|
||||||
|
|
||||||
# Commercial/Creative platforms
|
# Commercial/Creative platforms
|
||||||
@ -2738,15 +2781,6 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
|||||||
</button>
|
</button>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</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>
|
|
||||||
|
|
||||||
<.product_lightbox images={@images} product_name={@product_name} id_prefix={@id_prefix} />
|
<.product_lightbox images={@images} product_name={@product_name} id_prefix={@id_prefix} />
|
||||||
</div>
|
</div>
|
||||||
@ -3233,4 +3267,99 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
|||||||
</p>
|
</p>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Renders a responsive `<picture>` element with AVIF, WebP, and JPEG sources.
|
||||||
|
|
||||||
|
Computes available widths from `source_width` to avoid upscaling - only generates
|
||||||
|
srcset entries for sizes smaller than or equal to the original image dimensions.
|
||||||
|
|
||||||
|
The component renders:
|
||||||
|
- `<source>` for AVIF (best compression, modern browsers)
|
||||||
|
- `<source>` for WebP (good compression, broad support)
|
||||||
|
- `<img>` with JPEG srcset (fallback for legacy browsers)
|
||||||
|
|
||||||
|
## Attributes
|
||||||
|
|
||||||
|
* `src` - Required. Base path to the image variants (without size/extension).
|
||||||
|
* `alt` - Required. Alt text for accessibility.
|
||||||
|
* `source_width` - Required. Original image width in pixels.
|
||||||
|
* `sizes` - Optional. Responsive sizes attribute. Defaults to "100vw".
|
||||||
|
* `class` - Optional. CSS classes to apply to the `<img>` element.
|
||||||
|
* `width` - Optional. Explicit width attribute.
|
||||||
|
* `height` - Optional. Explicit height attribute.
|
||||||
|
* `priority` - Optional. If true, sets eager loading and high fetchpriority.
|
||||||
|
Defaults to false (lazy loading).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
<.responsive_image
|
||||||
|
src="/image_cache/abc123"
|
||||||
|
source_width={1200}
|
||||||
|
alt="Product image"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<.responsive_image
|
||||||
|
src="/image_cache/abc123"
|
||||||
|
source_width={1200}
|
||||||
|
alt="Hero banner"
|
||||||
|
priority={true}
|
||||||
|
sizes="(max-width: 768px) 100vw, 50vw"
|
||||||
|
/>
|
||||||
|
"""
|
||||||
|
attr :src, :string, required: true, doc: "Base path without size/extension"
|
||||||
|
attr :alt, :string, required: true
|
||||||
|
attr :source_width, :integer, required: true, doc: "Original image width"
|
||||||
|
attr :sizes, :string, default: "100vw"
|
||||||
|
attr :class, :string, default: ""
|
||||||
|
attr :width, :integer, default: nil
|
||||||
|
attr :height, :integer, default: nil
|
||||||
|
attr :priority, :boolean, default: false
|
||||||
|
|
||||||
|
def responsive_image(assigns) do
|
||||||
|
alias SimpleshopTheme.Images.Optimizer
|
||||||
|
|
||||||
|
# Compute available widths from source dimensions (no upscaling)
|
||||||
|
available = Optimizer.applicable_widths(assigns.source_width)
|
||||||
|
default_width = Enum.max(available)
|
||||||
|
|
||||||
|
assigns =
|
||||||
|
assigns
|
||||||
|
|> assign(:available_widths, available)
|
||||||
|
|> assign(:default_width, default_width)
|
||||||
|
|
||||||
|
~H"""
|
||||||
|
<picture>
|
||||||
|
<source
|
||||||
|
type="image/avif"
|
||||||
|
srcset={build_srcset(@src, @available_widths, "avif")}
|
||||||
|
sizes={@sizes}
|
||||||
|
/>
|
||||||
|
<source
|
||||||
|
type="image/webp"
|
||||||
|
srcset={build_srcset(@src, @available_widths, "webp")}
|
||||||
|
sizes={@sizes}
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={"#{@src}-#{@default_width}.jpg"}
|
||||||
|
srcset={build_srcset(@src, @available_widths, "jpg")}
|
||||||
|
sizes={@sizes}
|
||||||
|
alt={@alt}
|
||||||
|
width={@width}
|
||||||
|
height={@height}
|
||||||
|
loading={if @priority, do: "eager", else: "lazy"}
|
||||||
|
decoding={if @priority, do: "sync", else: "async"}
|
||||||
|
fetchpriority={if @priority, do: "high", else: nil}
|
||||||
|
class={@class}
|
||||||
|
/>
|
||||||
|
</picture>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_srcset(base, widths, format) do
|
||||||
|
widths
|
||||||
|
|> Enum.sort()
|
||||||
|
|> Enum.map(&"#{base}-#{&1}.#{format} #{&1}w")
|
||||||
|
|> Enum.join(", ")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user