berrypod/lib/berrypod_web/live/admin/theme/index.html.heex
jamey 65ea11c3a2 replace --color-* with --t-* tokens, delete bridge and .preview-frame
Phase 4: admin components and utilities now reference --t-* theme
tokens directly. Status colour tokens added to theme-semantic.css.
Bridge file (admin/themes.css) deleted.

Phase 5: removed duplicated .preview-frame CSS block (~160 lines).
Admin components and icons wrapped in @layer admin. Layer order
updated in admin_root to include admin layer.

Phase 6: added prefers-reduced-motion support (zeroes all durations
and disables animations). Migrated physical properties to logical
equivalents (text-align start/end, margin-inline, padding-inline,
inset-inline-end) across shop and admin CSS.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 00:13:33 +00:00

1184 lines
49 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div class="min-h-screen bg-base-200">
<div class="flex flex-col lg:flex-row lg:h-screen">
<!-- Controls Sidebar -->
<div
id="theme-sidebar"
class={[
"bg-base-100 border-r border-base-300 lg:h-screen flex-shrink-0 transition-all duration-300",
if(@sidebar_collapsed,
do: "w-12 overflow-hidden",
else: "w-full lg:w-[380px] overflow-y-auto p-6"
)
]}
>
<!-- Collapsed state: just show expand button -->
<%= if @sidebar_collapsed do %>
<div class="h-full flex items-start justify-center pt-4">
<button
type="button"
phx-click="toggle_sidebar"
class="p-2 rounded-lg hover:bg-base-200 transition-colors"
aria-label="Expand sidebar"
aria-expanded="false"
aria-controls="theme-sidebar"
>
<svg
class="w-5 h-5 text-base-content/70"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
aria-hidden="true"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</button>
</div>
<% else %>
<.link
href={~p"/admin"}
class="inline-flex items-center gap-1 text-sm text-base-content/60 hover:text-base-content mb-4"
>
<.icon name="hero-arrow-left-mini" class="size-4" /> Admin
</.link>
<!-- Header -->
<div class="mb-6 flex items-start justify-between gap-3">
<div class="flex-1">
<h1 class="text-xl font-semibold tracking-tight mb-2 text-base-content">
Theme Studio
</h1>
<p class="text-sm text-base-content/60 leading-relaxed">
One theme, infinite possibilities. Every combination is designed to work beautifully.
</p>
</div>
<button
type="button"
phx-click="toggle_sidebar"
class="p-2 rounded-lg hover:bg-base-200 transition-colors flex-shrink-0 -mr-2 -mt-1"
aria-label="Collapse sidebar"
aria-expanded="true"
aria-controls="theme-sidebar"
>
<svg
class="w-5 h-5 text-base-content/70"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
aria-hidden="true"
>
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</button>
</div>
<!-- Site Name -->
<div class="mb-6">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Shop name
</label>
<form phx-change="update_setting" phx-value-field="site_name">
<input
type="text"
name="site_name"
value={@theme_settings.site_name}
placeholder="Your shop name"
class="w-full px-4 py-3 border border-base-300 rounded-lg text-base bg-base-100 text-base-content focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20 transition-all"
/>
</form>
</div>
<!-- Branding Section (styled background box) -->
<div class="bg-base-200 rounded-xl p-4 mb-6">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-4">
Logo & header
</label>
<!-- Logo Mode Radio Cards -->
<div class="flex flex-col gap-2 mb-4">
<%= for {value, title, desc} <- [
{"text-only", "Shop name only", "Your name in the heading font"},
{"logo-text", "Logo + shop name", "Your logo image with name beside it"},
{"logo-only", "Logo only", "Just your logo (with text built in)"}
] do %>
<label class={[
"flex items-center gap-3 p-3 bg-base-100 border-2 rounded-lg cursor-pointer transition-all",
if(@theme_settings.logo_mode == value,
do: "border-base-content",
else: "border-transparent hover:border-base-300"
)
]}>
<input
type="radio"
name="logo_mode"
value={value}
checked={@theme_settings.logo_mode == value}
phx-click="update_setting"
phx-value-field="logo_mode"
phx-value-setting_value={value}
class="hidden"
/>
<span class={[
"w-[18px] h-[18px] rounded-full border-2 flex items-center justify-center flex-shrink-0 transition-all",
if(@theme_settings.logo_mode == value,
do: "border-base-content",
else: "border-base-300"
)
]}>
<span class={[
"w-2 h-2 rounded-full bg-base-content transition-all",
if(@theme_settings.logo_mode == value,
do: "scale-100 opacity-100",
else: "scale-0 opacity-0"
)
]}>
</span>
</span>
<div class="flex-1">
<div class="text-sm font-medium text-base-content">{title}</div>
<div class="text-xs text-base-content/60">{desc}</div>
</div>
</label>
<% end %>
</div>
<!-- Logo Upload (for logo-text and logo-only modes) -->
<%= if @theme_settings.logo_mode in ["logo-text", "logo-only"] do %>
<div class="mt-4 pt-4 border-t border-base-300">
<span class="block text-xs font-medium text-base-content/70 mb-2">
Upload logo (SVG or PNG)
</span>
<div class="flex items-center gap-3">
<form phx-change="noop" phx-submit="noop" class="flex-1">
<label class="flex-1 bg-base-100 border border-dashed border-base-300 rounded-lg p-3 text-sm text-base-content/60 text-center cursor-pointer hover:border-base-content/40 hover:text-base-content/80 transition-all">
<span>Choose file...</span>
<.live_file_input upload={@uploads.logo_upload} class="hidden" />
</label>
</form>
<%= if @logo_image do %>
<div class="relative w-16 h-10 bg-base-100 border border-base-300 rounded-lg flex items-center justify-center overflow-hidden">
<img
src={"/image_cache/#{@logo_image.id}.webp"}
alt="Current logo"
class="max-w-full max-h-full object-contain"
/>
<button
type="button"
phx-click="remove_logo"
class="absolute -top-1.5 -right-1.5 w-[18px] h-[18px] bg-base-content text-base-100 rounded-full text-xs flex items-center justify-center leading-none"
title="Remove logo"
>
×
</button>
</div>
<% end %>
</div>
<%= for entry <- @uploads.logo_upload.entries do %>
<div class="flex items-center gap-2 mt-2">
<div class="flex-1 h-1.5 bg-base-300 rounded-full overflow-hidden">
<div
class="h-full bg-primary transition-all"
style={"width: #{entry.progress}%"}
>
</div>
</div>
<span class="text-xs text-base-content/60">{entry.progress}%</span>
<button
type="button"
phx-click="cancel_upload"
phx-value-ref={entry.ref}
phx-value-upload="logo_upload"
class="text-base-content/40 hover:text-base-content/70"
>
×
</button>
</div>
<%= for err <- upload_errors(@uploads.logo_upload, entry) do %>
<p class="text-error text-xs mt-1">{error_to_string(err)}</p>
<% end %>
<% end %>
<%= for err <- upload_errors(@uploads.logo_upload) do %>
<p class="text-error text-xs mt-1">{error_to_string(err)}</p>
<% end %>
<!-- Logo Size Slider -->
<%= if @logo_image do %>
<form phx-change="update_setting" phx-value-field="logo_size" class="mt-3">
<div class="flex justify-between items-center mb-2">
<span class="text-xs font-medium text-base-content/70">Logo size</span>
<span class="text-xs font-mono text-base-content/60">
{@theme_settings.logo_size}px
</span>
</div>
<input
type="range"
min="24"
max="120"
value={@theme_settings.logo_size}
name="logo_size"
class="admin-range w-full"
/>
</form>
<!-- SVG Recolor Toggle (only for SVG logos) -->
<%= if @logo_image.is_svg do %>
<div class="mt-3">
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={@theme_settings.logo_recolor}
phx-click="update_setting"
phx-value-field="logo_recolor"
phx-value-setting_value={
if @theme_settings.logo_recolor, do: "false", else: "true"
}
class="admin-toggle admin-toggle-sm"
/>
<span class="text-sm text-base-content/70">Recolour logo</span>
</label>
<%= if @theme_settings.logo_recolor do %>
<form
id="logo-color-form"
phx-change="update_color"
phx-value-field="logo_color"
phx-hook="ColorSync"
class="flex items-center gap-3 mt-2"
>
<input
type="color"
name="value"
value={@theme_settings.logo_color}
class="w-9 h-9 rounded-lg cursor-pointer border-0 p-0"
/>
<span class="font-mono text-sm text-base-content/70">
{@theme_settings.logo_color}
</span>
</form>
<% end %>
</div>
<% end %>
<% end %>
</div>
<% end %>
</div>
<!-- Header Background Toggle -->
<div class="mb-6">
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={@theme_settings.header_background_enabled}
phx-click="update_setting"
phx-value-field="header_background_enabled"
phx-value-setting_value={
if @theme_settings.header_background_enabled, do: "false", else: "true"
}
class="admin-toggle admin-toggle-sm"
/>
<span class="text-sm text-base-content/80">Header background image</span>
</label>
</div>
<!-- Header Image Upload (only when enabled) -->
<%= if @theme_settings.header_background_enabled do %>
<div class="bg-base-200 rounded-xl p-4 mb-6">
<span class="block text-xs font-medium text-base-content/70 mb-2">
Upload header image
</span>
<form phx-change="noop" phx-submit="noop">
<label class="block bg-base-100 border border-dashed border-base-300 rounded-lg p-3 text-sm text-base-content/60 text-center cursor-pointer hover:border-base-content/40 hover:text-base-content/80 transition-all">
<span>Choose file...</span>
<.live_file_input upload={@uploads.header_upload} class="hidden" />
</label>
</form>
<%= if @header_image do %>
<div class="relative w-full h-[60px] bg-base-100 border border-base-300 rounded-lg mt-2 overflow-hidden">
<img
src={"/image_cache/#{@header_image.id}.webp"}
alt="Current header background"
class="w-full h-full object-cover"
/>
<button
type="button"
phx-click="remove_header"
class="absolute -top-1.5 -right-1.5 w-[18px] h-[18px] bg-base-content text-base-100 rounded-full text-xs flex items-center justify-center leading-none"
title="Remove header background"
>
×
</button>
</div>
<!-- Header Image Controls -->
<div class="mt-3 flex flex-col gap-3">
<form phx-change="update_setting" phx-value-field="header_zoom">
<div class="flex justify-between items-center mb-2">
<span class="text-xs font-medium text-base-content/70">Zoom</span>
<span class="text-xs font-mono text-base-content/60">
{@theme_settings.header_zoom}%
</span>
</div>
<input
type="range"
min="100"
max="200"
value={@theme_settings.header_zoom}
name="header_zoom"
class="admin-range w-full"
/>
</form>
<form phx-change="update_setting" phx-value-field="header_position_x">
<div class="flex justify-between items-center mb-2">
<span class="text-xs font-medium text-base-content/70">
Horizontal position
</span>
<span class="text-xs font-mono text-base-content/60">
{@theme_settings.header_position_x}%
</span>
</div>
<input
type="range"
min="0"
max="100"
value={@theme_settings.header_position_x}
name="header_position_x"
class="admin-range w-full"
/>
</form>
<form phx-change="update_setting" phx-value-field="header_position_y">
<div class="flex justify-between items-center mb-2">
<span class="text-xs font-medium text-base-content/70">
Vertical position
</span>
<span class="text-xs font-mono text-base-content/60">
{@theme_settings.header_position_y}%
</span>
</div>
<input
type="range"
min="0"
max="100"
value={@theme_settings.header_position_y}
name="header_position_y"
class="admin-range w-full"
/>
</form>
</div>
<% end %>
<%= for entry <- @uploads.header_upload.entries do %>
<div class="flex items-center gap-2 mt-2">
<div class="flex-1 h-1.5 bg-base-300 rounded-full overflow-hidden">
<div
class="h-full bg-primary transition-all"
style={"width: #{entry.progress}%"}
>
</div>
</div>
<span class="text-xs text-base-content/60">{entry.progress}%</span>
<button
type="button"
phx-click="cancel_upload"
phx-value-ref={entry.ref}
phx-value-upload="header_upload"
class="text-base-content/40 hover:text-base-content/70"
>
×
</button>
</div>
<%= for err <- upload_errors(@uploads.header_upload, entry) do %>
<p class="text-error text-xs mt-1">{error_to_string(err)}</p>
<% end %>
<% end %>
<%= for err <- upload_errors(@uploads.header_upload) do %>
<p class="text-error text-xs mt-1">{error_to_string(err)}</p>
<% end %>
</div>
<% end %>
<!-- Presets Section -->
<div class="mb-6">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Start with a preset
</label>
<div class="grid grid-cols-2 gap-2">
<%= for {preset_name, description} <- @presets_with_descriptions do %>
<button
type="button"
phx-click="apply_preset"
phx-value-preset={preset_name}
class={[
"p-3 rounded-lg text-left transition-all border-2",
if(@active_preset == preset_name,
do: "border-base-content bg-base-100",
else: "border-transparent bg-base-200 hover:bg-base-300"
)
]}
>
<div class="font-semibold text-sm capitalize text-base-content">
{preset_name}
</div>
<div class="text-xs text-base-content/60">{description}</div>
</button>
<% end %>
</div>
</div>
<!-- Accent Colors (stays in essentials) -->
<div class="mb-6">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Accent colour
</label>
<form
id="accent-color-form"
phx-change="update_color"
phx-value-field="accent_color"
phx-hook="ColorSync"
>
<div class="flex items-center gap-3">
<input
type="color"
id="accent-color-picker"
name="value"
value={@theme_settings.accent_color}
class="w-12 h-12 rounded-lg cursor-pointer border-0 p-0"
/>
<span class="font-mono text-sm text-base-content/70">
{@theme_settings.accent_color}
</span>
</div>
</form>
</div>
<div class="mb-6">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Hover colour
</label>
<form
id="secondary-accent-color-form"
phx-change="update_color"
phx-value-field="secondary_accent_color"
phx-hook="ColorSync"
>
<div class="flex items-center gap-3">
<input
type="color"
id="secondary-accent-color-picker"
name="value"
value={@theme_settings.secondary_accent_color}
class="w-12 h-12 rounded-lg cursor-pointer border-0 p-0"
/>
<span class="font-mono text-sm text-base-content/70">
{@theme_settings.secondary_accent_color}
</span>
</div>
</form>
</div>
<div class="mb-6">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Sale colour
</label>
<form
id="sale-color-form"
phx-change="update_color"
phx-value-field="sale_color"
phx-hook="ColorSync"
>
<div class="flex items-center gap-3">
<input
type="color"
id="sale-color-picker"
name="value"
value={@theme_settings.sale_color}
class="w-12 h-12 rounded-lg cursor-pointer border-0 p-0"
/>
<span class="font-mono text-sm text-base-content/70">
{@theme_settings.sale_color}
</span>
</div>
</form>
</div>
<!-- Customise Section (collapsible accordion using native details/summary) -->
<details
class="border-t border-base-300 mt-6 pt-4 group"
id="customise-section"
open={@customise_open}
>
<summary
class="flex items-center justify-between w-full py-3 cursor-pointer list-none [&::-webkit-details-marker]:hidden"
phx-click="toggle_customise"
>
<span class="text-sm font-semibold text-base-content/70 group-hover:text-base-content transition-colors">
Customise
</span>
<svg
class="w-5 h-5 text-base-content/50 transition-transform group-open:rotate-180"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</summary>
<div class="pt-4">
<!-- Typography Group -->
<div class="mb-6 pb-6 border-b border-base-200">
<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"
>
<polyline points="4 7 4 4 20 4 20 7"></polyline>
<line x1="9" y1="20" x2="15" y2="20"></line>
<line x1="12" y1="4" x2="12" y2="20"></line>
</svg>
<span class="text-sm font-semibold text-base-content">Typography</span>
</div>
<div class="mb-4">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Font style
</label>
<div class="flex flex-wrap gap-2">
<%= for typo <- ["clean", "editorial", "modern", "classic", "friendly", "minimal"] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="typography"
phx-value-setting_value={typo}
class={[
"px-3 py-2 text-sm rounded-lg border-2 transition-all capitalize",
if(@theme_settings.typography == typo,
do: "border-base-content bg-base-100 text-base-content",
else:
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
)
]}
>
{typo}
</button>
<% end %>
</div>
</div>
<div class="mb-4">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Font size
</label>
<div class="flex flex-wrap gap-2">
<%= for {value, label} <- [{"small", "Small"}, {"medium", "Medium"}, {"large", "Large"}] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="font_size"
phx-value-setting_value={value}
class={[
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
if(@theme_settings.font_size == 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 class="mb-4">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Heading weight
</label>
<div class="flex flex-wrap gap-2">
<%= for {value, label} <- [{"regular", "Regular"}, {"medium", "Medium"}, {"bold", "Bold"}] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="heading_weight"
phx-value-setting_value={value}
class={[
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
if(@theme_settings.heading_weight == 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>
<!-- Colours Group -->
<div class="mb-6 pb-6 border-b border-base-200">
<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"
>
<circle cx="12" cy="12" r="10"></circle>
<circle cx="12" cy="12" r="3"></circle>
</svg>
<span class="text-sm font-semibold text-base-content">Colours</span>
</div>
<div class="mb-4">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Colour mood
</label>
<div class="flex flex-wrap gap-2">
<%= for mood <- ["warm", "neutral", "cool", "dark"] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="mood"
phx-value-setting_value={mood}
class={[
"px-3 py-2 text-sm rounded-lg border-2 transition-all capitalize",
if(@theme_settings.mood == mood,
do: "border-base-content bg-base-100 text-base-content",
else:
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
)
]}
>
{mood}
</button>
<% end %>
</div>
</div>
</div>
<!-- Layout Group -->
<div class="mb-6 pb-6 border-b border-base-200">
<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="18" height="18" rx="2" ry="2"></rect>
<line x1="3" y1="9" x2="21" y2="9"></line>
<line x1="9" y1="21" x2="9" y2="9"></line>
</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">
Product grid
</label>
<div class="flex flex-wrap gap-2">
<%= for cols <- ["2", "3", "4"] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="grid_columns"
phx-value-setting_value={cols}
class={[
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
if(@theme_settings.grid_columns == cols,
do: "border-base-content bg-base-100 text-base-content",
else:
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
)
]}
>
{cols} columns
</button>
<% end %>
</div>
</div>
<div class="mb-4">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Density
</label>
<div class="flex flex-wrap gap-2">
<%= for density <- ["spacious", "balanced", "compact"] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="density"
phx-value-setting_value={density}
class={[
"px-3 py-2 text-sm rounded-lg border-2 transition-all capitalize",
if(@theme_settings.density == density,
do: "border-base-content bg-base-100 text-base-content",
else:
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
)
]}
>
{density}
</button>
<% end %>
</div>
</div>
<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", "left"] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="header_layout"
phx-value-setting_value={layout}
class={[
"px-3 py-2 text-sm rounded-lg border-2 transition-all capitalize",
if(@theme_settings.header_layout == layout,
do: "border-base-content bg-base-100 text-base-content",
else:
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
)
]}
>
{layout}
</button>
<% 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="admin-checkbox admin-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="admin-checkbox admin-checkbox-sm"
/>
<span class="text-sm text-base-content">Sticky header</span>
</label>
</div>
</div>
<!-- Shape 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="18" height="18" rx="2" ry="2"></rect>
</svg>
<span class="text-sm font-semibold text-base-content">Shape</span>
</div>
<div class="mb-4">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Corner style
</label>
<div class="flex flex-wrap gap-2">
<%= for shape <- ["sharp", "soft", "round", "pill"] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="shape"
phx-value-setting_value={shape}
class={[
"px-3 py-2 text-sm rounded-lg border-2 transition-all capitalize",
if(@theme_settings.shape == shape,
do: "border-base-content bg-base-100 text-base-content",
else:
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
)
]}
>
{shape}
</button>
<% 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 class="mb-4">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Button style
</label>
<div class="flex flex-wrap gap-2">
<%= for {value, label} <- [{"filled", "Filled"}, {"outline", "Outline"}, {"soft", "Soft"}] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="button_style"
phx-value-setting_value={value}
class={[
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
if(@theme_settings.button_style == 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>
<!-- Products Group -->
<div class="mb-6 pb-6 border-b border-base-200">
<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">Products</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 class="mb-4">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Image aspect ratio
</label>
<div class="flex flex-wrap gap-2">
<%= for {value, label} <- [{"square", "Square"}, {"portrait", "Portrait"}, {"landscape", "Landscape"}] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="image_aspect_ratio"
phx-value-setting_value={value}
class={[
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
if(@theme_settings.image_aspect_ratio == 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 class="mb-4">
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
Product text alignment
</label>
<div class="flex flex-wrap gap-2">
<%= for {value, label} <- [{"left", "Left"}, {"center", "Centre"}] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="product_text_align"
phx-value-setting_value={value}
class={[
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
if(@theme_settings.product_text_align == 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 class="mb-4">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={@theme_settings.hover_image}
phx-click="toggle_setting"
phx-value-field="hover_image"
class="admin-checkbox admin-checkbox-sm"
/>
<span class="text-sm text-base-content">Second image on hover</span>
</label>
</div>
<div class="mb-4">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={@theme_settings.show_prices}
phx-click="toggle_setting"
phx-value-field="show_prices"
class="admin-checkbox admin-checkbox-sm"
/>
<span class="text-sm text-base-content">Show prices</span>
</label>
</div>
</div>
<!-- Product Page 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="18" height="18" rx="2" ry="2"></rect>
<line x1="3" y1="9" x2="21" y2="9"></line>
</svg>
<span class="text-sm font-semibold text-base-content">Product page</span>
</div>
<div class="mb-4">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={@theme_settings.pdp_trust_badges}
phx-click="toggle_setting"
phx-value-field="pdp_trust_badges"
class="admin-checkbox admin-checkbox-sm"
/>
<span class="text-sm text-base-content">Trust badges</span>
</label>
</div>
<div class="mb-4">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={@theme_settings.pdp_reviews}
phx-click="toggle_setting"
phx-value-field="pdp_reviews"
class="admin-checkbox admin-checkbox-sm"
/>
<span class="text-sm text-base-content">Reviews section</span>
</label>
</div>
<div class="mb-4">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={@theme_settings.pdp_related_products}
phx-click="toggle_setting"
phx-value-field="pdp_related_products"
class="admin-checkbox admin-checkbox-sm"
/>
<span class="text-sm text-base-content">Related products</span>
</label>
</div>
</div>
</div>
</details>
<!-- Current Combination Display -->
<div class="bg-base-200 rounded-xl p-4 mt-6">
<div class="text-xs font-semibold uppercase tracking-wider text-base-content/50 mb-2">
Current combination
</div>
<div class="text-sm text-base-content leading-relaxed">
{String.capitalize(@theme_settings.mood)} · {String.capitalize(
@theme_settings.typography
)} · {String.capitalize(@theme_settings.shape)} · {String.capitalize(
@theme_settings.density
)} · {@theme_settings.grid_columns}-up · {String.capitalize(
@theme_settings.header_layout
)}
</div>
<div class="text-xs text-base-content/40 mt-2">
One of 100,000+ possible combinations
</div>
</div>
<% end %>
</div>
<!-- Preview Area -->
<div class="flex-1 p-6 flex flex-col overflow-hidden bg-base-200">
<div class="max-w-[1200px] mx-auto w-full flex flex-col flex-1 min-h-0 overflow-hidden">
<!-- Preview Page Switcher -->
<div class="flex gap-1 mb-3 bg-base-300 p-1 rounded-lg w-fit flex-shrink-0">
<%= for {page_name, label} <- [
{:home, "Home"},
{:collection, "Collection"},
{:pdp, "Product"},
{:cart, "Cart"},
{:about, "About"},
{:delivery, "Delivery"},
{:privacy, "Privacy"},
{:terms, "Terms"},
{:contact, "Contact"},
{:error, "404"}
] do %>
<button
type="button"
phx-click="change_preview_page"
phx-value-page={page_name}
class={[
"px-4 py-2 text-sm font-medium rounded transition-all",
if(@preview_page == page_name,
do: "bg-base-100 text-base-content shadow-sm",
else: "text-base-content/70 hover:text-base-content"
)
]}
>
{label}
</button>
<% end %>
</div>
<!-- Browser Chrome -->
<div class="flex items-center bg-gradient-to-b from-base-300 to-base-300/80 border border-base-content/20 border-b-base-content/30 rounded-t-[10px] px-[14px] py-[10px] gap-[14px] flex-shrink-0">
<div class="flex gap-2">
<div class="w-3 h-3 rounded-full bg-[#ff5f57] border border-[#e14640]"></div>
<div class="w-3 h-3 rounded-full bg-[#ffbd2e] border border-[#dfa123]"></div>
<div class="w-3 h-3 rounded-full bg-[#28c940] border border-[#1aab29]"></div>
</div>
<div class="flex-1 flex items-center gap-2 bg-base-100 border border-base-content/20 rounded-md px-3 py-[5px]">
<svg
class="w-[14px] h-[14px] text-base-content/50"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
<span class="text-sm text-base-content/60 truncate">
{@theme_settings.site_name |> String.downcase() |> String.replace(" ", "")}.myshopify.com
</span>
</div>
</div>
<!-- Preview Frame -->
<div
class="themed overflow-auto flex-1 rounded-b-lg border border-t-0 border-base-content/20"
data-mood={@theme_settings.mood}
data-typography={@theme_settings.typography}
data-shape={@theme_settings.shape}
data-density={@theme_settings.density}
data-grid={@theme_settings.grid_columns}
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}
data-button-style={@theme_settings.button_style}
>
<style>
/* All font faces for theme switching */
<%= Phoenix.HTML.raw(Berrypod.Theme.Fonts.generate_all_font_faces(
&BerrypodWeb.Endpoint.static_path/1
)) %>
/* Generated theme CSS */
<%= Phoenix.HTML.raw(@generated_css) %>
</style>
<.preview_page
page={@preview_page}
preview_data={@preview_data}
theme_settings={@theme_settings}
logo_image={@logo_image}
header_image={@header_image}
cart_drawer_open={@cart_drawer_open}
/>
</div>
</div>
</div>
</div>
</div>