feat: enhance theme customization with layout controls and real product images

Add comprehensive layout and styling controls including header layout options (standard, centered, left), content width settings (contained, wide, full), and card shadow levels. Update all theme presets with these new settings. Replace placeholder images with real Unsplash product and category images for more realistic previews. Add announcement bar and sticky header toggle options for enhanced header customization.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jamey Greenwood 2026-01-01 16:16:05 +00:00
parent 1ca703e548
commit a8c0e150c8
16 changed files with 793 additions and 289 deletions

View File

@ -63,7 +63,7 @@
.preview-frame[data-typography="editorial"] {
--t-font-heading: var(--p-font-fraunces);
--t-font-body: var(--p-font-source);
--t-heading-weight: 600;
--t-heading-weight: 700;
--t-heading-tracking: -0.02em;
}
@ -171,9 +171,23 @@
display: flex;
}
/* Standard header - logo left, nav center, cart right */
.preview-frame[data-header="standard"] .shop-header {
justify-content: space-between;
}
/* Centered header - logo on top, nav and cart on same row below */
.preview-frame[data-header="centered"] .shop-header {
flex-direction: column;
gap: 0.75rem;
flex-wrap: wrap;
justify-content: center;
gap: 0.5rem 1.5rem;
padding-top: 1.5rem;
padding-bottom: 1.5rem;
}
.preview-frame[data-header="centered"] .shop-logo {
width: 100%;
justify-content: center;
text-align: center;
}
@ -181,6 +195,126 @@
justify-content: center;
}
.preview-frame[data-header="minimal"] .shop-nav {
display: none !important;
.preview-frame[data-header="centered"] .shop-cart {
/* Cart flows inline with nav, no absolute positioning */
}
/* Left header - logo and nav grouped left, cart right */
.preview-frame[data-header="left"] .shop-header {
justify-content: flex-start;
gap: 2rem;
}
.preview-frame[data-header="left"] .shop-cart {
margin-left: auto;
}
/* Sticky header */
.preview-frame[data-sticky="true"] .shop-header {
position: sticky;
top: 0;
z-index: 50;
}
/* Layout Width */
.preview-frame[data-layout="contained"] .max-w-7xl {
max-width: 1024px;
}
.preview-frame[data-layout="wide"] .max-w-7xl {
max-width: 1280px;
}
.preview-frame[data-layout="full"] .max-w-7xl {
max-width: 100%;
padding-left: 2rem;
padding-right: 2rem;
}
/* Card Shadow */
.preview-frame[data-shadow="none"] .product-card,
.preview-frame[data-shadow="none"] .category-card {
box-shadow: none;
}
.preview-frame[data-shadow="sm"] .product-card,
.preview-frame[data-shadow="sm"] .category-card {
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
.preview-frame[data-shadow="md"] .product-card,
.preview-frame[data-shadow="md"] .category-card {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
}
.preview-frame[data-shadow="lg"] .product-card,
.preview-frame[data-shadow="lg"] .category-card {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
}
/* Product Badges */
.product-badge {
position: absolute;
top: 0.5rem;
left: 0.5rem;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.025em;
border-radius: var(--t-radius-sm, 4px);
z-index: 10;
}
.badge-sale {
background-color: var(--t-sale-color, #dc2626);
color: #ffffff;
}
.badge-new {
background-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
color: var(--t-text-inverse);
}
/* Product Hover Image */
.product-image-container {
position: relative;
}
.product-image-hover {
position: absolute;
inset: 0;
opacity: 0;
transition: opacity 0.3s ease;
}
.product-card:hover .product-image-hover {
opacity: 1;
}
.product-card:hover .product-image-primary {
opacity: 0;
}
/* Social Links */
.social-link:hover {
background-color: var(--t-surface-sunken);
color: var(--t-text-primary);
}
/* Header Icon Buttons */
.header-icon-btn:hover {
background-color: var(--t-surface-sunken);
color: var(--t-text-primary);
}
/* Search Modal Animation */
.search-modal {
opacity: 1;
transition: opacity 0.2s ease;
}
.search-modal-content {
transform: translateY(0);
transition: transform 0.2s ease;
}

View File

@ -3,9 +3,12 @@
Semantic aliases for easy usage
======================================== */
:root {
.preview-frame, .shop-root {
/* Accent color - HSL components set dynamically by CSS generator */
/* Derived accent colors use the dynamic HSL values */
--t-accent-h: 24;
--t-accent-s: 95%;
--t-accent-l: 53%;
--t-accent: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
--t-accent-hover: hsl(var(--t-accent-h) var(--t-accent-s) calc(var(--t-accent-l) - 8%));
--t-accent-subtle: hsl(var(--t-accent-h) 40% 95%);
@ -15,8 +18,9 @@
--t-secondary-accent: #ea580c;
--t-sale-color: #dc2626;
/* Density multiplier */
--t-density: 1;
/* Font size scale */
--t-font-size-scale: 1;
--t-heading-weight: 600;
/* Layout */
--t-layout-max-width: 1400px;
@ -48,6 +52,14 @@
--weight-heading: var(--t-heading-weight);
--tracking-heading: var(--t-heading-tracking);
/* Density-aware spacing */
--space-xs: calc(var(--p-space-2) * var(--t-density));
--space-sm: calc(var(--p-space-3) * var(--t-density));
--space-md: calc(var(--p-space-4) * var(--t-density));
--space-lg: calc(var(--p-space-6) * var(--t-density));
--space-xl: calc(var(--p-space-8) * var(--t-density));
--space-2xl: calc(var(--p-space-12) * var(--t-density));
/* Border radius */
--radius-button: var(--t-radius-button);
--radius-card: var(--t-radius-card);
@ -59,7 +71,7 @@
0 1px 2px hsl(var(--p-shadow-color) / calc(var(--p-shadow-strength) * 0.5)),
0 1px 3px hsl(var(--p-shadow-color) / var(--p-shadow-strength));
--shadow-md:
0 2px 4px hsl(var(--p-shadow-color) / calc(--p-shadow-strength) * 0.5)),
0 2px 4px hsl(var(--p-shadow-color) / calc(var(--p-shadow-strength) * 0.5)),
0 4px 8px hsl(var(--p-shadow-color) / var(--p-shadow-strength)),
0 8px 16px hsl(var(--p-shadow-color) / calc(var(--p-shadow-strength) * 0.5));
@ -68,3 +80,8 @@
--transition-normal: var(--p-duration-normal) var(--p-ease-out);
--transition-bounce: var(--p-duration-normal) var(--p-ease-out-back);
}
/* Dark mode accent-subtle override */
.preview-frame[data-mood="dark"], .shop-root[data-mood="dark"] {
--t-accent-subtle: hsl(var(--t-accent-h) 30% 15%);
}

View File

@ -97,11 +97,13 @@ defmodule SimpleshopTheme.Settings.ThemeSettings do
|> validate_inclusion(:shape, ~w(sharp soft round pill))
|> validate_inclusion(:density, ~w(spacious balanced compact))
|> validate_inclusion(:grid_columns, ~w(2 3 4))
|> validate_inclusion(:header_layout, ~w(standard centered minimal))
|> validate_inclusion(:header_layout, ~w(standard centered left))
|> validate_inclusion(:logo_mode, ~w(text-only logo-text logo-only))
|> validate_number(:logo_size, greater_than_or_equal_to: 24, less_than_or_equal_to: 120)
|> validate_number(:header_zoom, greater_than_or_equal_to: 100, less_than_or_equal_to: 200)
|> validate_number(:header_position_x, greater_than_or_equal_to: 0, less_than_or_equal_to: 100)
|> validate_number(:header_position_y, greater_than_or_equal_to: 0, less_than_or_equal_to: 100)
|> validate_inclusion(:layout_width, ~w(contained wide full))
|> validate_inclusion(:card_shadow, ~w(none sm md lg))
end
end

View File

@ -1,6 +1,6 @@
defmodule SimpleshopTheme.Theme.Presets do
@moduledoc """
Defines the 9 curated theme presets for SimpleShop.
Defines the 8 curated theme presets for SimpleShop.
"""
@presets %{
@ -11,7 +11,10 @@ defmodule SimpleshopTheme.Theme.Presets do
density: "spacious",
grid_columns: "3",
header_layout: "centered",
accent_color: "#e85d04"
accent_color: "#e85d04",
layout_width: "wide",
card_shadow: "sm",
announcement_bar: true
},
studio: %{
mood: "neutral",
@ -20,7 +23,10 @@ defmodule SimpleshopTheme.Theme.Presets do
density: "balanced",
grid_columns: "4",
header_layout: "standard",
accent_color: "#3b82f6"
accent_color: "#3b82f6",
layout_width: "wide",
card_shadow: "sm",
announcement_bar: true
},
boutique: %{
mood: "warm",
@ -28,8 +34,11 @@ defmodule SimpleshopTheme.Theme.Presets do
shape: "soft",
density: "balanced",
grid_columns: "3",
header_layout: "centered",
accent_color: "#b45309"
header_layout: "left",
accent_color: "#b45309",
layout_width: "contained",
card_shadow: "md",
announcement_bar: true
},
bold: %{
mood: "neutral",
@ -38,7 +47,10 @@ defmodule SimpleshopTheme.Theme.Presets do
density: "compact",
grid_columns: "4",
header_layout: "standard",
accent_color: "#dc2626"
accent_color: "#dc2626",
layout_width: "full",
card_shadow: "none",
announcement_bar: true
},
playful: %{
mood: "neutral",
@ -47,16 +59,22 @@ defmodule SimpleshopTheme.Theme.Presets do
density: "balanced",
grid_columns: "4",
header_layout: "standard",
accent_color: "#8b5cf6"
accent_color: "#8b5cf6",
layout_width: "wide",
card_shadow: "md",
announcement_bar: true
},
minimal: %{
mood: "cool",
typography: "minimal",
mood: "neutral",
typography: "impulse",
shape: "sharp",
density: "spacious",
grid_columns: "2",
header_layout: "minimal",
accent_color: "#171717"
header_layout: "standard",
accent_color: "#171717",
layout_width: "full",
card_shadow: "none",
announcement_bar: false
},
night: %{
mood: "dark",
@ -65,7 +83,10 @@ defmodule SimpleshopTheme.Theme.Presets do
density: "balanced",
grid_columns: "4",
header_layout: "standard",
accent_color: "#f97316"
accent_color: "#f97316",
layout_width: "wide",
card_shadow: "lg",
announcement_bar: true
},
classic: %{
mood: "warm",
@ -74,22 +95,10 @@ defmodule SimpleshopTheme.Theme.Presets do
density: "spacious",
grid_columns: "3",
header_layout: "standard",
accent_color: "#166534"
},
impulse: %{
mood: "neutral",
typography: "impulse",
shape: "sharp",
density: "spacious",
grid_columns: "3",
header_layout: "centered",
accent_color: "#000000",
font_size: "medium",
heading_weight: "regular",
layout_width: "full",
button_style: "filled",
card_shadow: "none",
product_text_align: "center"
accent_color: "#166534",
layout_width: "contained",
card_shadow: "sm",
announcement_bar: true
}
}
@ -99,14 +108,13 @@ defmodule SimpleshopTheme.Theme.Presets do
boutique: "Warm & sophisticated",
bold: "High contrast, strong",
playful: "Fun & approachable",
minimal: "Understated & modern",
minimal: "Light & airy",
night: "Dark & dramatic",
classic: "Traditional & refined",
impulse: "Light & airy"
classic: "Traditional & refined"
}
# Core keys used to match presets (excludes branding-specific settings)
@core_keys ~w(mood typography shape density grid_columns header_layout accent_color)a
@core_keys ~w(mood typography shape density grid_columns header_layout accent_color layout_width card_shadow announcement_bar)a
@doc """
Returns all available presets.
@ -141,7 +149,7 @@ defmodule SimpleshopTheme.Theme.Presets do
## Examples
iex> list_names()
[:gallery, :studio, :boutique, :bold, :playful, :minimal, :night, :classic, :impulse]
[:gallery, :studio, :boutique, :bold, :playful, :minimal, :night, :classic]
"""
def list_names do

View File

@ -79,8 +79,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
description: "Soft, breathable cotton tee perfect for everyday wear",
price: 2999,
compare_at_price: nil,
image_url: "https://placehold.co/600x800/e5e5e5/525252?text=Cotton+Tee",
hover_image_url: "https://placehold.co/600x800/d4d4d4/525252?text=Cotton+Tee",
image_url: "https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=600&h=800&fit=crop&q=80",
hover_image_url: "https://images.unsplash.com/photo-1622445275576-721325763afe?w=600&h=800&fit=crop&q=80",
category: "Clothing",
in_stock: true,
on_sale: false
@ -91,8 +91,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
description: "Handcrafted genuine leather bag with adjustable strap",
price: 8999,
compare_at_price: 11999,
image_url: "https://placehold.co/600x800/d4a574/1c1917?text=Leather+Bag",
hover_image_url: "https://placehold.co/600x800/c49563/1c1917?text=Leather+Bag",
image_url: "https://images.unsplash.com/photo-1548036328-c9fa89d128fa?w=600&h=800&fit=crop&q=80",
hover_image_url: "https://images.unsplash.com/photo-1590874103328-eac38a683ce7?w=600&h=800&fit=crop&q=80",
category: "Accessories",
in_stock: true,
on_sale: true
@ -103,8 +103,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
description: "Handmade ceramic mug with unique glaze finish",
price: 2499,
compare_at_price: nil,
image_url: "https://placehold.co/600x800/94a3b8/0f172a?text=Coffee+Mug",
hover_image_url: "https://placehold.co/600x800/64748b/0f172a?text=Coffee+Mug",
image_url: "https://images.unsplash.com/photo-1514228742587-6b1558fcca3d?w=600&h=800&fit=crop&q=80",
hover_image_url: "https://images.unsplash.com/photo-1481833761820-0509d3217039?w=600&h=800&fit=crop&q=80",
category: "Home",
in_stock: true,
on_sale: false
@ -115,8 +115,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
description: "Sleek design with Japanese quartz movement",
price: 12999,
compare_at_price: nil,
image_url: "https://placehold.co/600x800/171717/fafafa?text=Watch",
hover_image_url: "https://placehold.co/600x800/262626/fafafa?text=Watch",
image_url: "https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=600&h=800&fit=crop&q=80",
hover_image_url: "https://images.unsplash.com/photo-1524592094714-0f0654e20314?w=600&h=800&fit=crop&q=80",
category: "Accessories",
in_stock: true,
on_sale: false
@ -127,8 +127,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
description: "Cozy merino wool blanket in herringbone pattern",
price: 7999,
compare_at_price: 9999,
image_url: "https://placehold.co/600x800/a3a3a3/171717?text=Blanket",
hover_image_url: "https://placehold.co/600x800/8a8a8a/171717?text=Blanket",
image_url: "https://images.unsplash.com/photo-1580301762395-21ce84d00bc6?w=600&h=800&fit=crop&q=80",
hover_image_url: "https://images.unsplash.com/photo-1600369671854-2d9f5db4a5e0?w=600&h=800&fit=crop&q=80",
category: "Home",
in_stock: false,
on_sale: true
@ -139,8 +139,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
description: "Natural handmade soaps with essential oils",
price: 3499,
compare_at_price: nil,
image_url: "https://placehold.co/600x800/fdf8f3/1c1917?text=Soap+Set",
hover_image_url: "https://placehold.co/600x800/f5ebe0/1c1917?text=Soap+Set",
image_url: "https://images.unsplash.com/photo-1607006344380-b6775a0824a7?w=600&h=800&fit=crop&q=80",
hover_image_url: "https://images.unsplash.com/photo-1600857544200-b2f666a9a2ec?w=600&h=800&fit=crop&q=80",
category: "Beauty",
in_stock: true,
on_sale: false
@ -151,8 +151,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
description: "Classic cut denim jacket with vintage wash",
price: 8499,
compare_at_price: nil,
image_url: "https://placehold.co/600x800/3b82f6/ffffff?text=Denim+Jacket",
hover_image_url: "https://placehold.co/600x800/2563eb/ffffff?text=Denim+Jacket",
image_url: "https://images.unsplash.com/photo-1576995853123-5a10305d93c0?w=600&h=800&fit=crop&q=80",
hover_image_url: "https://images.unsplash.com/photo-1601333144130-8cbb312386b6?w=600&h=800&fit=crop&q=80",
category: "Clothing",
in_stock: true,
on_sale: false
@ -163,8 +163,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
description: "Durable organic cotton canvas with reinforced handles",
price: 2999,
compare_at_price: nil,
image_url: "https://placehold.co/600x800/f5f5f5/171717?text=Tote+Bag",
hover_image_url: "https://placehold.co/600x800/e5e5e5/171717?text=Tote+Bag",
image_url: "https://images.unsplash.com/photo-1622560480605-d83c853bc5c3?w=600&h=800&fit=crop&q=80",
hover_image_url: "https://images.unsplash.com/photo-1597633125097-5a9ae3cb8a8f?w=600&h=800&fit=crop&q=80",
category: "Accessories",
in_stock: true,
on_sale: false
@ -175,8 +175,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
description: "Soy wax candle with cedar and vanilla notes",
price: 3299,
compare_at_price: 3999,
image_url: "https://placehold.co/600x800/fdf8f3/57534e?text=Candle",
hover_image_url: "https://placehold.co/600x800/f5ebe0/57534e?text=Candle",
image_url: "https://images.unsplash.com/photo-1602028915047-37269d1a73f7?w=600&h=800&fit=crop&q=80",
hover_image_url: "https://images.unsplash.com/photo-1603006905003-be475563bc59?w=600&h=800&fit=crop&q=80",
category: "Home",
in_stock: true,
on_sale: true
@ -187,8 +187,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
description: "Insulated bottle keeps drinks cold for 24 hours",
price: 3999,
compare_at_price: nil,
image_url: "https://placehold.co/600x800/0a0a0a/fafafa?text=Water+Bottle",
hover_image_url: "https://placehold.co/600x800/171717/fafafa?text=Water+Bottle",
image_url: "https://images.unsplash.com/photo-1602143407151-7111542de6e8?w=600&h=800&fit=crop&q=80",
hover_image_url: "https://images.unsplash.com/photo-1570831739435-6601aa3fa4fb?w=600&h=800&fit=crop&q=80",
category: "Accessories",
in_stock: true,
on_sale: false
@ -199,8 +199,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
description: "Comfortable crew socks in solid colors",
price: 1499,
compare_at_price: nil,
image_url: "https://placehold.co/600x800/e5e5e5/525252?text=Socks",
hover_image_url: "https://placehold.co/600x800/d4d4d4/525252?text=Socks",
image_url: "https://images.unsplash.com/photo-1586350977771-b3b0abd50c82?w=600&h=800&fit=crop&q=80",
hover_image_url: "https://images.unsplash.com/photo-1582966772680-860e372bb558?w=600&h=800&fit=crop&q=80",
category: "Clothing",
in_stock: true,
on_sale: false
@ -211,8 +211,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
description: "Sustainable bamboo with juice groove",
price: 4499,
compare_at_price: nil,
image_url: "https://placehold.co/600x800/d4a574/1c1917?text=Cutting+Board",
hover_image_url: "https://placehold.co/600x800/c49563/1c1917?text=Cutting+Board",
image_url: "https://images.unsplash.com/photo-1594226801341-41427b4e5c22?w=600&h=800&fit=crop&q=80",
hover_image_url: "https://images.unsplash.com/photo-1606760227091-3dd870d97f1d?w=600&h=800&fit=crop&q=80",
category: "Kitchen",
in_stock: true,
on_sale: false
@ -296,35 +296,35 @@ defmodule SimpleshopTheme.Theme.PreviewData do
name: "Clothing",
slug: "clothing",
product_count: 3,
image_url: "https://placehold.co/400x300/e5e5e5/525252?text=Clothing"
image_url: "https://images.unsplash.com/photo-1489987707025-afc232f7ea0f?w=400&h=300&fit=crop&q=80"
},
%{
id: "2",
name: "Accessories",
slug: "accessories",
product_count: 4,
image_url: "https://placehold.co/400x300/d4a574/1c1917?text=Accessories"
image_url: "https://images.unsplash.com/photo-1606760227091-3dd870d97f1d?w=400&h=300&fit=crop&q=80"
},
%{
id: "3",
name: "Home",
slug: "home",
product_count: 3,
image_url: "https://placehold.co/400x300/94a3b8/0f172a?text=Home"
image_url: "https://images.unsplash.com/photo-1616046229478-9901c5536a45?w=400&h=300&fit=crop&q=80"
},
%{
id: "4",
name: "Kitchen",
slug: "kitchen",
product_count: 1,
image_url: "https://placehold.co/400x300/fdf8f3/1c1917?text=Kitchen"
image_url: "https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=400&h=300&fit=crop&q=80"
},
%{
id: "5",
name: "Beauty",
slug: "beauty",
product_count: 1,
image_url: "https://placehold.co/400x300/f5f5f5/171717?text=Beauty"
image_url: "https://images.unsplash.com/photo-1596462502278-27bfdc403348?w=400&h=300&fit=crop&q=80"
}
]
end

View File

@ -17,7 +17,7 @@ defmodule SimpleshopThemeWeb do
those modules here.
"""
def static_paths, do: ~w(assets css fonts images favicon.ico robots.txt)
def static_paths, do: ~w(assets css fonts images favicon.ico robots.txt demo.html)
def router do
quote do

View File

@ -201,6 +201,30 @@ defmodule SimpleshopThemeWeb.ThemeLive.Index do
end
end
@impl true
def handle_event("toggle_setting", %{"field" => field}, socket) do
field_atom = String.to_existing_atom(field)
current_value = Map.get(socket.assigns.theme_settings, field_atom)
attrs = %{field_atom => !current_value}
case Settings.update_theme_settings(attrs) do
{:ok, theme_settings} ->
generated_css = CSSGenerator.generate(theme_settings)
active_preset = Presets.detect_preset(theme_settings)
socket =
socket
|> assign(:theme_settings, theme_settings)
|> assign(:generated_css, generated_css)
|> assign(:active_preset, active_preset)
{:noreply, socket}
{:error, _} ->
{:noreply, socket}
end
end
@impl true
def handle_event("save_theme", _params, socket) do
socket = put_flash(socket, :info, "Theme saved successfully")

View File

@ -466,7 +466,7 @@
<div class="mb-4">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Header layout</label>
<div class="flex flex-wrap gap-2">
<%= for layout <- ["standard", "centered", "minimal"] do %>
<%= for layout <- ["standard", "centered", "left"] do %>
<button
type="button"
phx-click="update_setting"
@ -485,6 +485,32 @@
<% end %>
</div>
</div>
<div class="mb-4">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={@theme_settings.announcement_bar}
phx-click="toggle_setting"
phx-value-field="announcement_bar"
class="checkbox checkbox-sm"
/>
<span class="text-sm text-base-content">Announcement bar</span>
</label>
</div>
<div class="mb-4">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={@theme_settings.sticky_header}
phx-click="toggle_setting"
phx-value-field="sticky_header"
class="checkbox checkbox-sm"
/>
<span class="text-sm text-base-content">Sticky header</span>
</label>
</div>
</div>
<!-- Shape Group -->
@ -518,6 +544,65 @@
<% end %>
</div>
</div>
<div class="mb-4">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Card shadow</label>
<div class="flex flex-wrap gap-2">
<%= for {value, label} <- [{"none", "None"}, {"sm", "Subtle"}, {"md", "Medium"}, {"lg", "Strong"}] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="card_shadow"
phx-value-setting_value={value}
class={[
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
if(@theme_settings.card_shadow == value,
do: "border-base-content bg-base-100 text-base-content",
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
)
]}
>
<%= label %>
</button>
<% end %>
</div>
</div>
</div>
<!-- Layout Group -->
<div class="mb-4">
<div class="flex items-center gap-2 mb-4">
<svg class="w-4 h-4 text-base-content/50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7"></rect>
<rect x="14" y="3" width="7" height="7"></rect>
<rect x="14" y="14" width="7" height="7"></rect>
<rect x="3" y="14" width="7" height="7"></rect>
</svg>
<span class="text-sm font-semibold text-base-content">Layout</span>
</div>
<div class="mb-4">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Content width</label>
<div class="flex flex-wrap gap-2">
<%= for width <- ["contained", "wide", "full"] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="layout_width"
phx-value-setting_value={width}
class={[
"px-3 py-2 text-sm rounded-lg border-2 transition-all capitalize",
if(@theme_settings.layout_width == width,
do: "border-base-content bg-base-100 text-base-content",
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
)
]}
>
<%= width %>
</button>
<% end %>
</div>
</div>
</div>
</div>
</details>
@ -586,7 +671,10 @@
data-shape={@theme_settings.shape}
data-density={@theme_settings.density}
data-grid={@theme_settings.grid_columns}
data-header={@theme_settings.header_layout}>
data-header={@theme_settings.header_layout}
data-sticky={to_string(@theme_settings.sticky_header)}
data-layout={@theme_settings.layout_width}
data-shadow={@theme_settings.card_shadow}>
<style>
<%= Phoenix.HTML.raw(@generated_css) %>
</style>

View File

@ -3,6 +3,22 @@ defmodule SimpleshopThemeWeb.ThemeLive.PreviewPages do
embed_templates "preview_pages/*"
@doc """
Renders the announcement bar.
"""
attr :theme_settings, :map, required: true
def announcement_bar(assigns) do
~H"""
<div
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: 0.875rem;"
>
<p style="margin: 0;">Free shipping on orders over $50</p>
</div>
"""
end
@doc """
Renders the shop header with logo based on logo_mode setting.
"""
@ -14,19 +30,12 @@ defmodule SimpleshopThemeWeb.ThemeLive.PreviewPages do
~H"""
<header
class="shop-header"
style={"position: relative; background-color: var(--t-surface-raised); border-bottom: 1px solid var(--t-border-default); padding: 1rem 2rem; display: flex; align-items: center; #{header_justify(@theme_settings.header_layout)};"}
style="background-color: var(--t-surface-raised); border-bottom: 1px solid var(--t-border-default); padding: 1rem 2rem; display: flex; align-items: center;"
>
<%= if @theme_settings.header_background_enabled && @header_image do %>
<div style={header_background_style(@theme_settings, @header_image)} />
<% end %>
<%= if @theme_settings.header_layout == "centered" do %>
<nav class="shop-nav hidden md:flex" style="gap: 1.5rem; position: relative; z-index: 1;">
<a href="#" style="color: var(--t-text-secondary); text-decoration: none;">Home</a>
<a href="#" style="color: var(--t-text-secondary); text-decoration: none;">Shop</a>
</nav>
<% end %>
<div class="shop-logo" style="display: flex; align-items: center; position: relative; z-index: 1;">
<%= case @theme_settings.logo_mode do %>
<% "text-only" -> %>
@ -66,28 +75,44 @@ defmodule SimpleshopThemeWeb.ThemeLive.PreviewPages do
<% end %>
</div>
<%= if @theme_settings.header_layout != "minimal" do %>
<nav class={"shop-nav #{if @theme_settings.header_layout == "centered", do: "hidden md:flex", else: "hidden md:flex"}"} style="gap: 1.5rem; position: relative; z-index: 1;">
<%= unless @theme_settings.header_layout == "centered" do %>
<a href="#" style="color: var(--t-text-secondary); text-decoration: none;">Home</a>
<a href="#" style="color: var(--t-text-secondary); text-decoration: none;">Shop</a>
<% end %>
<a href="#" style="color: var(--t-text-secondary); text-decoration: none;">About</a>
<a href="#" style="color: var(--t-text-secondary); text-decoration: none;">Contact</a>
</nav>
<% end %>
<nav class="shop-nav hidden md:flex" style="gap: 1.5rem; position: relative; z-index: 1;">
<a href="#" style="color: var(--t-text-secondary); text-decoration: none;">Home</a>
<a href="#" style="color: var(--t-text-secondary); text-decoration: none;">Shop</a>
<a href="#" style="color: var(--t-text-secondary); text-decoration: none;">About</a>
<a href="#" style="color: var(--t-text-secondary); text-decoration: none;">Contact</a>
</nav>
<div class="shop-cart" style="position: relative; z-index: 1;">
<button style="color: var(--t-text-primary); background: none; border: none; cursor: pointer;">Cart (0)</button>
<div class="shop-actions flex items-center gap-1" style="position: relative; z-index: 1;">
<button
type="button"
class="header-icon-btn w-9 h-9 flex items-center justify-center transition-all"
style="color: var(--t-text-secondary); background: none; border: none; cursor: pointer; border-radius: var(--t-radius-button);"
phx-click={Phoenix.LiveView.JS.show(to: "#search-modal", display: "flex") |> Phoenix.LiveView.JS.focus(to: "#search-input")}
aria-label="Search"
>
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<path d="M21 21l-4.35-4.35"></path>
</svg>
</button>
<button
type="button"
class="header-icon-btn w-9 h-9 flex items-center justify-center transition-all"
style="color: var(--t-text-secondary); background: none; border: none; cursor: pointer; border-radius: var(--t-radius-button);"
aria-label="Cart"
>
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"></path>
<line x1="3" y1="6" x2="21" y2="6"></line>
<path d="M16 10a4 4 0 01-8 0"></path>
</svg>
<span class="sr-only">Cart (0)</span>
</button>
</div>
</header>
"""
end
defp header_justify("centered"), do: "justify-content: center; gap: 2rem;"
defp header_justify("minimal"), do: "justify-content: space-between;"
defp header_justify(_), do: "justify-content: space-between;"
defp logo_url(logo_image, %{logo_recolor: true, logo_color: color}) when logo_image.is_svg do
clean_color = String.trim_leading(color, "#")
"/images/#{logo_image.id}/recolored/#{clean_color}"
@ -101,4 +126,154 @@ defmodule SimpleshopThemeWeb.ThemeLive.PreviewPages do
"background-position: #{settings.header_position_x}% #{settings.header_position_y}%; " <>
"background-repeat: no-repeat; z-index: 0;"
end
@doc """
Renders the shop footer with newsletter and links.
"""
attr :theme_settings, :map, required: true
def shop_footer(assigns) do
~H"""
<footer style="background-color: var(--t-surface-raised); border-top: 1px solid var(--t-border-default);">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div class="grid grid-cols-1 md:grid-cols-2 gap-12">
<!-- Newsletter -->
<div>
<h3 class="text-xl font-bold mb-2" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);">
Join the studio
</h3>
<p class="mb-4 text-sm" style="color: var(--t-text-secondary);">
Get 10% off your first order and be the first to know about new prints.
</p>
<form class="flex gap-2">
<input
type="email"
placeholder="your@email.com"
class="flex-1 px-4 py-2 text-sm"
style="background-color: var(--t-surface-base); color: var(--t-text-primary); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-input);"
/>
<button
type="submit"
class="px-6 py-2 font-medium transition-all text-sm"
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);"
>
Subscribe
</button>
</form>
</div>
<!-- Links -->
<div class="grid grid-cols-2 gap-8">
<div>
<h4 class="font-semibold mb-4 text-sm" style="font-family: var(--t-font-heading); color: var(--t-text-primary);">
Shop
</h4>
<ul class="space-y-2 text-sm">
<li><a href="#" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary);">All products</a></li>
<li><a href="#" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary);">New arrivals</a></li>
<li><a href="#" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary);">Best sellers</a></li>
</ul>
</div>
<div>
<h4 class="font-semibold mb-4 text-sm" style="font-family: var(--t-font-heading); color: var(--t-text-primary);">
Help
</h4>
<ul class="space-y-2 text-sm">
<li><a href="#" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary);">Shipping</a></li>
<li><a href="#" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary);">Returns</a></li>
<li><a href="#" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary);">Contact</a></li>
</ul>
</div>
</div>
</div>
<!-- Bottom Bar -->
<div class="mt-12 pt-8 flex flex-col md:flex-row justify-between items-center gap-4" style="border-top: 1px solid var(--t-border-subtle);">
<p class="text-xs" style="color: var(--t-text-tertiary);">
© 2025 <%= @theme_settings.site_name %>
</p>
<div class="flex gap-2">
<a
href="#"
class="social-link w-9 h-9 flex items-center justify-center transition-all"
style="color: var(--t-text-secondary); border-radius: var(--t-radius-button);"
aria-label="Instagram"
>
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="2" width="20" height="20" rx="5" ry="5"></rect>
<path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path>
<line x1="17.5" y1="6.5" x2="17.51" y2="6.5"></line>
</svg>
</a>
<a
href="#"
class="social-link w-9 h-9 flex items-center justify-center transition-all"
style="color: var(--t-text-secondary); border-radius: var(--t-radius-button);"
aria-label="Pinterest"
>
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<path d="M8 12c0-2.2 1.8-4 4-4s4 1.8 4 4-1.8 4-4 4"></path>
<line x1="12" y1="16" x2="9" y2="21"></line>
</svg>
</a>
</div>
</div>
</div>
</footer>
"""
end
@doc """
Renders the search modal overlay.
"""
def search_modal(assigns) do
~H"""
<div
id="search-modal"
class="search-modal"
style="position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1001; display: none; align-items: flex-start; justify-content: center; padding-top: 10vh;"
phx-click={Phoenix.LiveView.JS.hide(to: "#search-modal")}
>
<div
class="search-modal-content w-full max-w-xl mx-4"
style="background: var(--t-surface-raised); border-radius: var(--t-radius-card); overflow: hidden; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);"
phx-click-away={Phoenix.LiveView.JS.hide(to: "#search-modal")}
>
<div
class="flex items-center gap-3 p-4"
style="border-bottom: 1px solid var(--t-border-default);"
>
<svg class="w-5 h-5 flex-shrink-0" style="color: var(--t-text-tertiary);" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<path d="M21 21l-4.35-4.35"></path>
</svg>
<input
type="text"
id="search-input"
class="flex-1 text-lg bg-transparent border-none outline-none"
style="font-family: var(--t-font-body); color: var(--t-text-primary);"
placeholder="Search products..."
phx-click={Phoenix.LiveView.JS.dispatch("stop-propagation")}
/>
<button
type="button"
class="w-8 h-8 flex items-center justify-center transition-all"
style="color: var(--t-text-tertiary); background: none; border: none; cursor: pointer; border-radius: var(--t-radius-button);"
phx-click={Phoenix.LiveView.JS.hide(to: "#search-modal")}
aria-label="Close search"
>
<svg class="w-5 h-5" 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>
</div>
<div class="p-6" style="color: var(--t-text-tertiary);">
<p class="text-sm">Try searching for "fern", "roses", or "botanical"</p>
</div>
</div>
</div>
"""
end
end

View File

@ -1,70 +1,66 @@
<div class="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="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
<!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %>
<!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} />
<div 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);">
About Us
</h1>
<p class="text-lg mb-12 text-center max-w-2xl mx-auto" style="color: var(--t-text-secondary);">
We're passionate about bringing you the finest products, handpicked with care and attention to detail.
</p>
<div class="aspect-video bg-gray-200 mb-12 overflow-hidden" style="border-radius: var(--t-radius-card);">
<img
src="https://placehold.co/1200x675/e5e5e5/525252?text=Our+Story"
alt="Our Story"
class="w-full h-full object-cover"
/>
<!-- Content Page -->
<div class="content-page" style="background-color: var(--t-surface-base);">
<!-- Hero Section -->
<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);">
About the studio
</h1>
<p class="text-lg" style="color: var(--t-text-secondary);">
Nature-inspired art, made with care
</p>
</div>
<div class="prose max-w-none mb-16">
<h2 class="text-2xl font-bold mb-4" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);">
Our Story
</h2>
<p class="mb-4 leading-relaxed" style="color: var(--t-text-secondary);">
Founded in 2020, our journey began with a simple mission: to curate and deliver exceptional products that enhance everyday life. We believe that quality shouldn't be compromised, and that's why every item in our collection is carefully selected for its craftsmanship, sustainability, and timeless appeal.
</p>
<p class="mb-4 leading-relaxed" style="color: var(--t-text-secondary);">
What started as a small passion project has grown into a community of like-minded individuals who appreciate the finer things in life. We work directly with artisans and makers who share our values of quality, authenticity, and ethical production.
</p>
<!-- Content Body -->
<div class="content-body" style="padding: var(--space-xl) var(--space-lg); max-width: 800px; margin: 0 auto;">
<!-- About Image -->
<div
class="content-image about-image"
style="width: 100%; height: 300px; border-radius: var(--t-radius-image); margin-bottom: var(--space-lg); background-size: cover; background-position: center; background-image: url('https://picsum.photos/seed/studio/800/400');"
></div>
<h2 class="text-2xl font-bold mb-4 mt-8" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);">
Our Values
</h2>
<div class="grid gap-6 md:grid-cols-3 mb-8">
<div class="p-6" style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);">
<h3 class="font-bold mb-2" style="color: var(--t-text-primary);">Quality First</h3>
<p class="text-sm" style="color: var(--t-text-secondary);">
Every product is vetted for exceptional quality and durability.
</p>
</div>
<div class="p-6" style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);">
<h3 class="font-bold mb-2" style="color: var(--t-text-primary);">Sustainability</h3>
<p class="text-sm" style="color: var(--t-text-secondary);">
We prioritize eco-friendly materials and ethical production methods.
</p>
</div>
<div class="p-6" style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);">
<h3 class="font-bold mb-2" style="color: var(--t-text-primary);">Community</h3>
<p class="text-sm" style="color: var(--t-text-secondary);">
Supporting local artisans and building lasting relationships.
</p>
</div>
<!-- Content Text -->
<div class="content-text" style="line-height: 1.7;">
<p class="lead-text text-lg mb-4" style="color: var(--t-text-primary);">
Botanical Studio was born from a love of the natural world and a desire to bring its beauty indoors.
</p>
<p class="mb-4" style="color: var(--t-text-secondary);">
Every illustration starts as a pencil sketch, inspired by the plants and flowers found in British woodlands, meadows, and gardens. These sketches are then refined and printed on museum-quality archival paper using pigment-based inks that will last a lifetime.
</p>
<p class="mb-4" style="color: var(--t-text-secondary);">
Based in the Yorkshire countryside, I work from a small garden studio surrounded by the very nature that inspires each piece. Every print is checked by hand before being carefully packaged and sent on its way.
</p>
<h2 class="mt-8 mb-3" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); font-size: var(--p-text-xl); color: var(--t-text-primary);">
Sustainability
</h2>
<p class="mb-4" style="color: var(--t-text-secondary);">
I believe beautiful art shouldn't cost the earth. All prints are produced on FSC-certified paper, shipped in plastic-free packaging, and printed locally to reduce transport emissions.
</p>
<h2 class="mt-8 mb-3" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); font-size: var(--p-text-xl); color: var(--t-text-primary);">
The process
</h2>
<p class="mb-4" style="color: var(--t-text-secondary);">
Each botanical illustration takes between 20-40 hours to complete, from initial field sketches through to the final digital refinement. I work primarily in graphite and watercolour, which are then scanned and carefully colour-corrected to ensure the prints match the original artwork.
</p>
</div>
</div>
<div class="text-center">
<h2 class="text-2xl font-bold mb-6" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);">
Ready to Explore?
</h2>
<button
class="px-8 py-3 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);"
>
Shop Our Collection
</button>
</div>
</div>
<!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />
<!-- Search Modal -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.search_modal />
</div>

View File

@ -1,4 +1,9 @@
<div class="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="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
<!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %>
<!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} />
@ -109,4 +114,10 @@
</div>
</div>
</div>
<!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />
<!-- Search Modal -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.search_modal />
</div>

View File

@ -1,4 +1,9 @@
<div class="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="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
<!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %>
<!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} />
@ -109,25 +114,33 @@
]}>
<%= for product <- @preview_data.products do %>
<div
class="group overflow-hidden transition-all"
class="product-card group overflow-hidden transition-all"
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);"
>
<div class="aspect-square bg-gray-200 overflow-hidden relative">
<div class="product-image-container aspect-square bg-gray-200 overflow-hidden relative">
<!-- Product Badge -->
<%= if product.on_sale do %>
<div class="absolute top-2 right-2 px-2 py-1 text-xs font-bold text-white rounded" style="background-color: var(--t-sale-color);">
SALE
</div>
<span class="product-badge badge-sale">Sale</span>
<% end %>
<%= if not product.in_stock do %>
<div class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center z-20">
<span class="text-white font-semibold">Out of Stock</span>
</div>
<% end %>
<!-- Primary Image -->
<img
src={product.image_url}
alt={product.name}
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
class="product-image-primary w-full h-full object-cover transition-opacity duration-300"
/>
<!-- Hover Image -->
<%= if product[:hover_image_url] do %>
<img
src={product.hover_image_url}
alt={product.name}
class="product-image-hover w-full h-full object-cover"
/>
<% end %>
</div>
<div class="p-4">
<p class="text-xs mb-1" style="color: var(--t-text-tertiary);">
@ -153,10 +166,10 @@
</div>
<%= if product.in_stock do %>
<button
class="px-3 py-1.5 text-sm font-medium transition-all"
class="quick-add-btn px-3 py-1.5 text-sm font-medium 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);"
>
Add
Quick add
</button>
<% end %>
</div>
@ -167,4 +180,10 @@
</div>
</div>
</div>
<!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />
<!-- Search Modal -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.search_modal />
</div>

View File

@ -1,4 +1,9 @@
<div class="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="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
<!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %>
<!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} />
@ -124,4 +129,10 @@
</div>
</div>
</div>
<!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />
<!-- Search Modal -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.search_modal />
</div>

View File

@ -1,4 +1,9 @@
<div class="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="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
<!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %>
<!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} />
@ -35,7 +40,7 @@
<div class="mt-12 grid gap-4 grid-cols-2 md:grid-cols-4 max-w-xl mx-auto">
<%= for product <- Enum.take(@preview_data.products, 4) do %>
<div
class="group overflow-hidden"
class="product-card group overflow-hidden"
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-subtle); border-radius: var(--t-radius-card);"
>
<div class="aspect-square bg-gray-200 overflow-hidden">
@ -58,4 +63,10 @@
</div>
</div>
</div>
<!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />
<!-- Search Modal -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.search_modal />
</div>

View File

@ -1,145 +1,142 @@
<div class="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);">
<!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %>
<!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} />
<!-- Hero Section -->
<div class="relative" style="background-color: var(--t-surface-raised);">
<div class="max-w-7xl mx-auto" style="padding: var(--space-2xl) var(--space-md);">
<div class="text-center">
<h1 class="text-4xl md:text-6xl font-bold mb-6" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);">
Welcome to Our Store
</h1>
<p class="text-lg md:text-xl mb-8 max-w-2xl mx-auto" style="color: var(--t-text-secondary);">
Discover our curated collection of handpicked products crafted with care
</p>
<button
class="px-8 py-3 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);"
>
Shop Now
</button>
</div>
</div>
</div>
<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);">
Nature-inspired art prints
</h1>
<p class="text-lg max-w-lg mx-auto mb-8" style="color: var(--t-text-secondary); line-height: 1.6;">
Original botanical illustrations, printed on premium archival paper. Each piece brings a little bit of the outside, inside.
</p>
<button
class="px-6 py-3 font-medium 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);"
>
Shop the collection
</button>
</section>
<!-- Categories -->
<section style="padding: var(--space-xl) var(--space-lg); background-color: var(--t-surface-base);">
<nav class="grid grid-cols-3 gap-4 max-w-3xl mx-auto" aria-label="Product categories">
<%= for category <- Enum.take(@preview_data.categories, 3) do %>
<a href="#" class="flex flex-col items-center gap-3 p-4 rounded-lg transition-colors hover:bg-black/5" style="text-decoration: none;">
<div
class="w-24 h-24 rounded-full bg-gray-200 bg-cover bg-center transition-transform hover:scale-105"
style={"background-image: url('#{category.image_url}');"}
></div>
<span class="text-sm font-medium" style="font-family: var(--t-font-body); color: var(--t-text-primary);">
<%= category.name %>
</span>
</a>
<% end %>
</nav>
</section>
<!-- Featured Products -->
<div class="max-w-7xl mx-auto" style="padding: var(--space-2xl) var(--space-md);">
<h2 class="text-3xl font-bold mb-8" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);">
Featured Products
<section style="padding: var(--space-xl) var(--space-lg); background-color: var(--t-surface-sunken);">
<h2 class="text-2xl mb-6" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);">
Featured prints
</h2>
<div class={[
"grid grid-cols-1 sm:grid-cols-2",
"grid gap-6 grid-cols-1 sm:grid-cols-2",
case @theme_settings.grid_columns do
"2" -> "lg:grid-cols-2"
"3" -> "lg:grid-cols-3"
"4" -> "lg:grid-cols-4"
_ -> "lg:grid-cols-3"
end
]} style="gap: var(--space-lg);">
<%= for product <- Enum.take(@preview_data.products, 6) do %>
]}>
<%= for product <- Enum.take(@preview_data.products, 4) do %>
<div
class="group overflow-hidden transition-all"
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);"
class="product-card group overflow-hidden transition-all hover:-translate-y-1"
style="background-color: var(--t-surface-raised); border-radius: var(--t-radius-card);"
>
<div class="aspect-square bg-gray-200 overflow-hidden">
<div class="product-image-container aspect-square bg-gray-200 overflow-hidden relative">
<%= if product[:is_new] do %>
<span class="product-badge badge-new">New</span>
<% end %>
<%= if product.on_sale do %>
<span class="product-badge badge-sale">Sale</span>
<% end %>
<img
src={product.image_url}
alt={product.name}
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
class="product-image-primary w-full h-full object-cover transition-opacity duration-300"
/>
<%= if product[:hover_image_url] do %>
<img
src={product.hover_image_url}
alt={product.name}
class="product-image-hover w-full h-full object-cover"
/>
<% end %>
<button
class="absolute bottom-2 left-2 right-2 px-3 py-2 text-sm font-medium opacity-0 translate-y-2 group-hover:opacity-100 group-hover:translate-y-0 transition-all"
style="background-color: var(--t-surface-raised); color: var(--t-text-primary); border-radius: var(--t-radius-button);"
>
Quick add
</button>
</div>
<div style="padding: var(--space-md);">
<h3 class="font-semibold" style="font-family: var(--t-font-heading); color: var(--t-text-primary); margin-bottom: var(--space-xs);">
<h3 class="text-sm font-medium mb-1" style="color: var(--t-text-primary);">
<%= product.name %>
</h3>
<p class="text-sm mb-3" style="color: var(--t-text-secondary);">
<%= product.description %>
</p>
<div class="flex items-center justify-between">
<div>
<%= if product.on_sale do %>
<span class="text-lg font-bold" style="color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));">
$<%= product.price / 100 %>
</span>
<span class="text-sm line-through ml-2" style="color: var(--t-text-tertiary);">
$<%= product.compare_at_price / 100 %>
</span>
<% else %>
<span class="text-lg font-bold" style="color: var(--t-text-primary);">
$<%= product.price / 100 %>
</span>
<% end %>
</div>
<button
class="px-4 py-2 text-sm font-medium 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);"
>
Add to Cart
</button>
</div>
</div>
</div>
<% end %>
</div>
</div>
<!-- Testimonials -->
<div class="py-16" style="background-color: var(--t-surface-sunken);">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 class="text-3xl font-bold mb-12 text-center" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);">
What Our Customers Say
</h2>
<div class="grid gap-6 grid-cols-1 md:grid-cols-3">
<%= for testimonial <- Enum.take(@preview_data.testimonials, 3) do %>
<div class="p-6" style="background-color: var(--t-surface-base); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);">
<div class="flex mb-3">
<%= for _ <- 1..testimonial.rating do %>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" style="color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
<p class="text-sm" style="color: var(--t-text-secondary);">
<%= if product.on_sale do %>
<span class="line-through mr-1" style="color: var(--t-text-tertiary);">$<%= product.compare_at_price / 100 %></span>
<% end %>
</div>
<p class="mb-4" style="color: var(--t-text-primary);">
"<%= testimonial.content %>"
</p>
<p class="font-semibold" style="color: var(--t-text-secondary);">
— <%= testimonial.author %>
$<%= product.price / 100 %>
</p>
</div>
<% end %>
</div>
</div>
</div>
<!-- Categories -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<h2 class="text-3xl font-bold mb-8" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);">
Shop by Category
</h2>
<div class="grid gap-6 grid-cols-2 md:grid-cols-3 lg:grid-cols-5">
<%= for category <- @preview_data.categories do %>
<div
class="group cursor-pointer overflow-hidden"
style="border-radius: var(--t-radius-card);"
>
<div class="aspect-square bg-gray-200 overflow-hidden mb-3">
<img
src={category.image_url}
alt={category.name}
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
/>
</div>
<h3 class="font-semibold text-center" style="font-family: var(--t-font-heading); color: var(--t-text-primary);">
<%= category.name %>
</h3>
<p class="text-sm text-center" style="color: var(--t-text-tertiary);">
<%= category.product_count %> products
</p>
</div>
<% end %>
</div>
</div>
<div class="text-center mt-8">
<button
class="px-6 py-3 font-medium transition-all"
style="background-color: transparent; color: var(--t-text-primary); border: 1px solid var(--t-text-primary); border-radius: var(--t-radius-button);"
>
View all products
</button>
</div>
</section>
<!-- About Section -->
<section class="grid grid-cols-1 md:grid-cols-2 gap-12 items-center" style="padding: var(--space-2xl) var(--space-lg); background-color: var(--t-surface-base);">
<div
class="h-72 rounded-lg bg-cover bg-center"
style="background-image: url('https://picsum.photos/seed/studio/600/400'); border-radius: var(--t-radius-image);"
></div>
<div>
<h2 class="text-2xl 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);">
Made with care in Yorkshire
</h2>
<p class="text-base mb-4" style="color: var(--t-text-secondary); line-height: 1.7;">
Every illustration starts as a pencil sketch, inspired by the plants and flowers found in British woodlands, meadows, and gardens. Printed on museum-quality archival paper using pigment-based inks that will last a lifetime.
</p>
<a
href="#"
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;"
>
Learn more about the studio →
</a>
</div>
</section>
<!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />
<!-- Search Modal -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.search_modal />
</div>

View File

@ -1,7 +1,12 @@
<%
product = List.first(@preview_data.products)
%>
<div class="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="position: relative; background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
<!-- Announcement Bar -->
<%= if @theme_settings.announcement_bar do %>
<SimpleshopThemeWeb.ThemeLive.PreviewPages.announcement_bar theme_settings={@theme_settings} />
<% end %>
<!-- Header -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} />
@ -145,7 +150,7 @@
<div class="grid gap-6 grid-cols-2 md:grid-cols-4">
<%= for related_product <- Enum.slice(@preview_data.products, 1, 4) do %>
<div
class="group overflow-hidden"
class="product-card group overflow-hidden"
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);"
>
<div class="aspect-square bg-gray-200 overflow-hidden">
@ -168,4 +173,10 @@
</div>
</div>
</div>
<!-- Footer -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.shop_footer theme_settings={@theme_settings} />
<!-- Search Modal -->
<SimpleshopThemeWeb.ThemeLive.PreviewPages.search_modal />
</div>