site_name and site_description are shop identity, not theme concerns. They now live in the Settings table as first-class settings with their own assigns (@site_name, @site_description) piped through hooks and plugs. The setup wizard writes site_name on account creation, and the theme editor reads/writes via Settings.put_setting. Removed the "configure your shop" checklist item since currency/country aren't built yet. Also adds shop name field to setup wizard step 1. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1225 lines
43 KiB
Plaintext
1225 lines
43 KiB
Plaintext
<div class="theme-layout">
|
||
<!-- Controls Sidebar -->
|
||
<div
|
||
id="theme-sidebar"
|
||
class={[
|
||
"theme-sidebar",
|
||
if(@sidebar_collapsed,
|
||
do: "theme-sidebar-collapsed",
|
||
else: "theme-sidebar-expanded"
|
||
)
|
||
]}
|
||
>
|
||
<!-- Collapsed state: just show expand button -->
|
||
<%= if @sidebar_collapsed do %>
|
||
<div class="theme-sidebar-collapsed-inner">
|
||
<button
|
||
type="button"
|
||
phx-click="toggle_sidebar"
|
||
class="theme-collapse-btn"
|
||
aria-label="Expand sidebar"
|
||
aria-expanded="false"
|
||
aria-controls="theme-sidebar"
|
||
>
|
||
<svg
|
||
class="theme-collapse-icon"
|
||
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="theme-back-link">
|
||
<.icon name="hero-arrow-left-mini" class="size-4" /> Admin
|
||
</.link>
|
||
|
||
<!-- Header -->
|
||
<div class="theme-header">
|
||
<div class="admin-fill">
|
||
<h1 class="theme-title">Theme Studio</h1>
|
||
<p class="theme-subtitle">
|
||
One theme, infinite possibilities. Every combination is designed to work beautifully.
|
||
</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
phx-click="toggle_sidebar"
|
||
class="theme-collapse-btn"
|
||
aria-label="Collapse sidebar"
|
||
aria-expanded="true"
|
||
aria-controls="theme-sidebar"
|
||
>
|
||
<svg
|
||
class="theme-collapse-icon"
|
||
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="theme-section">
|
||
<label class="theme-section-label">Shop name</label>
|
||
<form phx-change="update_setting" phx-value-field="site_name">
|
||
<input
|
||
type="text"
|
||
name="site_name"
|
||
value={@site_name}
|
||
placeholder="Your shop name"
|
||
class="admin-input admin-input-lg"
|
||
/>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Branding Section -->
|
||
<div class="theme-panel">
|
||
<label class="theme-section-label">Logo & header</label>
|
||
|
||
<!-- Logo Mode Radio Cards -->
|
||
<div class="admin-stack admin-stack-sm theme-field">
|
||
<%= 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={[
|
||
"theme-radio-card",
|
||
if(@theme_settings.logo_mode == value,
|
||
do: "theme-radio-card-active",
|
||
else: ""
|
||
)
|
||
]}>
|
||
<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={[
|
||
"theme-radio-dot",
|
||
@theme_settings.logo_mode == value && "theme-radio-dot-active"
|
||
]}>
|
||
<span class={[
|
||
"theme-radio-dot-inner",
|
||
if(@theme_settings.logo_mode == value,
|
||
do: "theme-radio-dot-inner-active",
|
||
else: ""
|
||
)
|
||
]}>
|
||
</span>
|
||
</span>
|
||
<div class="admin-fill">
|
||
<div class="theme-radio-title">{title}</div>
|
||
<div class="admin-text-secondary">{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="theme-subsection">
|
||
<span class="theme-slider-label theme-block-label">
|
||
Upload logo (SVG or PNG)
|
||
</span>
|
||
<div class="admin-row admin-row-lg">
|
||
<form phx-change="noop" phx-submit="noop" class="admin-fill">
|
||
<label class="theme-upload-label">
|
||
<span>Choose file...</span>
|
||
<.live_file_input upload={@uploads.logo_upload} class="hidden" />
|
||
</label>
|
||
</form>
|
||
<%= if @logo_image do %>
|
||
<div class="theme-thumb theme-thumb-logo">
|
||
<img
|
||
src={"/image_cache/#{@logo_image.id}.webp"}
|
||
alt="Current logo"
|
||
/>
|
||
<button
|
||
type="button"
|
||
phx-click="remove_logo"
|
||
class="theme-remove-btn"
|
||
title="Remove logo"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
<form
|
||
phx-change="update_image_alt"
|
||
phx-value-image-id={@logo_image.id}
|
||
class="theme-subfield-sm"
|
||
>
|
||
<label class="admin-row">
|
||
<span class="admin-text-secondary shrink-0">Alt text</span>
|
||
<input
|
||
type="text"
|
||
name="alt"
|
||
value={@logo_image.alt || ""}
|
||
placeholder="Describe this image"
|
||
class="admin-input admin-input-sm admin-fill"
|
||
phx-debounce="blur"
|
||
/>
|
||
</label>
|
||
<p
|
||
:if={!@logo_image.alt || @logo_image.alt == ""}
|
||
class="theme-alt-warning"
|
||
>
|
||
Missing alt text — add a description for accessibility
|
||
</p>
|
||
</form>
|
||
<% end %>
|
||
</div>
|
||
|
||
<%= for entry <- @uploads.logo_upload.entries do %>
|
||
<div class="theme-progress">
|
||
<div class="theme-progress-bar">
|
||
<div
|
||
class="theme-progress-fill"
|
||
style={"width: #{entry.progress}%"}
|
||
>
|
||
</div>
|
||
</div>
|
||
<span class="admin-text-secondary">{entry.progress}%</span>
|
||
<button
|
||
type="button"
|
||
phx-click="cancel_upload"
|
||
phx-value-ref={entry.ref}
|
||
phx-value-upload="logo_upload"
|
||
class="theme-upload-cancel"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
<%= for err <- upload_errors(@uploads.logo_upload, entry) do %>
|
||
<p class="theme-error-text">{error_to_string(err)}</p>
|
||
<% end %>
|
||
<% end %>
|
||
|
||
<%= for err <- upload_errors(@uploads.logo_upload) do %>
|
||
<p class="theme-error-text">{error_to_string(err)}</p>
|
||
<% end %>
|
||
|
||
<!-- Logo Size Slider -->
|
||
<%= if @logo_image do %>
|
||
<form
|
||
phx-change="update_setting"
|
||
phx-value-field="logo_size"
|
||
class="theme-subfield"
|
||
>
|
||
<div class="theme-slider-header">
|
||
<span class="theme-slider-label">Logo size</span>
|
||
<span class="theme-slider-value">{@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"
|
||
/>
|
||
</form>
|
||
|
||
<!-- SVG Recolor Toggle (only for SVG logos) -->
|
||
<%= if @logo_image.is_svg do %>
|
||
<div class="theme-subfield">
|
||
<label class="admin-toggle-label">
|
||
<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="theme-slider-label">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="theme-color-row theme-subfield-sm"
|
||
>
|
||
<input
|
||
type="color"
|
||
name="value"
|
||
value={@theme_settings.logo_color}
|
||
class="theme-color-swatch theme-color-swatch-sm"
|
||
/>
|
||
<span class="theme-color-value">{@theme_settings.logo_color}</span>
|
||
</form>
|
||
<% end %>
|
||
</div>
|
||
<% end %>
|
||
<% end %>
|
||
</div>
|
||
<% end %>
|
||
</div>
|
||
|
||
<!-- Site Icon / Favicon -->
|
||
<div class="theme-panel">
|
||
<label class="theme-section-label">Site icon</label>
|
||
<p class="admin-text-tertiary theme-field">
|
||
Your icon appears in browser tabs and on home screens.
|
||
</p>
|
||
|
||
<!-- Use logo as icon toggle -->
|
||
<label class="admin-toggle-label theme-field">
|
||
<input
|
||
type="checkbox"
|
||
checked={@theme_settings.use_logo_as_icon}
|
||
phx-click="toggle_setting"
|
||
phx-value-field="use_logo_as_icon"
|
||
class="admin-toggle admin-toggle-sm"
|
||
/>
|
||
<span class="theme-slider-label">Use logo as favicon</span>
|
||
</label>
|
||
|
||
<!-- Icon upload (only when not using logo) -->
|
||
<%= if !@theme_settings.use_logo_as_icon do %>
|
||
<div class="admin-separator">
|
||
<span class="theme-slider-label theme-block-label">
|
||
Upload icon (PNG or SVG, 512×512+)
|
||
</span>
|
||
<div class="admin-row admin-row-lg">
|
||
<form phx-change="noop" phx-submit="noop" class="admin-fill">
|
||
<label class="theme-upload-label">
|
||
<span>Choose file...</span>
|
||
<.live_file_input upload={@uploads.icon_upload} class="hidden" />
|
||
</label>
|
||
</form>
|
||
<%= if @icon_image do %>
|
||
<div class="theme-thumb theme-thumb-icon">
|
||
<%= if @icon_image.is_svg do %>
|
||
<img
|
||
src={"/images/#{@icon_image.id}/recolored/000000"}
|
||
alt="Current icon"
|
||
/>
|
||
<% else %>
|
||
<img
|
||
src={"/image_cache/#{@icon_image.id}.webp"}
|
||
alt="Current icon"
|
||
/>
|
||
<% end %>
|
||
<button
|
||
type="button"
|
||
phx-click="remove_icon"
|
||
class="theme-remove-btn"
|
||
title="Remove icon"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
<% end %>
|
||
</div>
|
||
|
||
<%= for entry <- @uploads.icon_upload.entries do %>
|
||
<div class="theme-progress">
|
||
<div class="theme-progress-bar">
|
||
<div
|
||
class="theme-progress-fill"
|
||
style={"width: #{entry.progress}%"}
|
||
>
|
||
</div>
|
||
</div>
|
||
<span class="admin-text-secondary">{entry.progress}%</span>
|
||
<button
|
||
type="button"
|
||
phx-click="cancel_upload"
|
||
phx-value-ref={entry.ref}
|
||
phx-value-upload="icon_upload"
|
||
class="theme-upload-cancel"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
<%= for err <- upload_errors(@uploads.icon_upload, entry) do %>
|
||
<p class="theme-error-text">{error_to_string(err)}</p>
|
||
<% end %>
|
||
<% end %>
|
||
|
||
<%= for err <- upload_errors(@uploads.icon_upload) do %>
|
||
<p class="theme-error-text">{error_to_string(err)}</p>
|
||
<% end %>
|
||
</div>
|
||
<% end %>
|
||
|
||
<!-- Short name -->
|
||
<div class="theme-subfield">
|
||
<form phx-change="update_setting" phx-value-field="favicon_short_name">
|
||
<div class="theme-slider-header">
|
||
<span class="theme-slider-label">Short name</span>
|
||
<span class="admin-text-tertiary">Home screen label</span>
|
||
</div>
|
||
<input
|
||
type="text"
|
||
name="favicon_short_name"
|
||
value={@theme_settings.favicon_short_name}
|
||
placeholder={String.slice(@site_name, 0, 12)}
|
||
maxlength="12"
|
||
class="admin-input admin-input-sm"
|
||
/>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Icon background colour -->
|
||
<div class="theme-subfield">
|
||
<form
|
||
id="icon-bg-color-form"
|
||
phx-change="update_color"
|
||
phx-value-field="icon_background_color"
|
||
phx-hook="ColorSync"
|
||
class="theme-color-row"
|
||
>
|
||
<input
|
||
type="color"
|
||
name="value"
|
||
value={@theme_settings.icon_background_color}
|
||
class="theme-color-swatch theme-color-swatch-sm"
|
||
/>
|
||
<div>
|
||
<span class="theme-slider-label theme-block-label">Icon background</span>
|
||
<span class="theme-slider-value">
|
||
{@theme_settings.icon_background_color}
|
||
</span>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Header Background Toggle -->
|
||
<div class="theme-section">
|
||
<label class="admin-toggle-label">
|
||
<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="theme-check-text">
|
||
Header background image
|
||
</span>
|
||
</label>
|
||
</div>
|
||
|
||
<!-- Header Image Upload (only when enabled) -->
|
||
<%= if @theme_settings.header_background_enabled do %>
|
||
<div class="theme-panel">
|
||
<span class="theme-slider-label theme-block-label">
|
||
Upload header image
|
||
</span>
|
||
<form phx-change="noop" phx-submit="noop">
|
||
<label class="theme-upload-label">
|
||
<span>Choose file...</span>
|
||
<.live_file_input upload={@uploads.header_upload} class="hidden" />
|
||
</label>
|
||
</form>
|
||
|
||
<%= if @header_image do %>
|
||
<div class="theme-thumb theme-thumb-cover theme-thumb-header">
|
||
<img
|
||
src={"/image_cache/#{@header_image.id}.webp"}
|
||
alt="Current header background"
|
||
/>
|
||
<button
|
||
type="button"
|
||
phx-click="remove_header"
|
||
class="theme-remove-btn"
|
||
title="Remove header background"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
<form
|
||
phx-change="update_image_alt"
|
||
phx-value-image-id={@header_image.id}
|
||
class="theme-subfield-sm"
|
||
>
|
||
<label class="admin-row">
|
||
<span class="admin-text-secondary shrink-0">Alt text</span>
|
||
<input
|
||
type="text"
|
||
name="alt"
|
||
value={@header_image.alt || ""}
|
||
placeholder="Describe this image"
|
||
class="admin-input admin-input-sm admin-fill"
|
||
phx-debounce="blur"
|
||
/>
|
||
</label>
|
||
<p
|
||
:if={!@header_image.alt || @header_image.alt == ""}
|
||
class="theme-alt-warning"
|
||
>
|
||
Missing alt text — add a description for accessibility
|
||
</p>
|
||
</form>
|
||
|
||
<!-- Header Image Controls -->
|
||
<div class="admin-stack admin-stack-md theme-subfield">
|
||
<form phx-change="update_setting" phx-value-field="header_zoom">
|
||
<div class="theme-slider-header">
|
||
<span class="theme-slider-label">Zoom</span>
|
||
<span class="theme-slider-value">{@theme_settings.header_zoom}%</span>
|
||
</div>
|
||
<input
|
||
type="range"
|
||
min="100"
|
||
max="200"
|
||
value={@theme_settings.header_zoom}
|
||
name="header_zoom"
|
||
class="admin-range"
|
||
/>
|
||
</form>
|
||
<form phx-change="update_setting" phx-value-field="header_position_x">
|
||
<div class="theme-slider-header">
|
||
<span class="theme-slider-label">Horizontal position</span>
|
||
<span class="theme-slider-value">{@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"
|
||
/>
|
||
</form>
|
||
<form phx-change="update_setting" phx-value-field="header_position_y">
|
||
<div class="theme-slider-header">
|
||
<span class="theme-slider-label">Vertical position</span>
|
||
<span class="theme-slider-value">{@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"
|
||
/>
|
||
</form>
|
||
</div>
|
||
<% end %>
|
||
|
||
<%= for entry <- @uploads.header_upload.entries do %>
|
||
<div class="theme-progress">
|
||
<div class="theme-progress-bar">
|
||
<div
|
||
class="theme-progress-fill"
|
||
style={"width: #{entry.progress}%"}
|
||
>
|
||
</div>
|
||
</div>
|
||
<span class="admin-text-secondary">{entry.progress}%</span>
|
||
<button
|
||
type="button"
|
||
phx-click="cancel_upload"
|
||
phx-value-ref={entry.ref}
|
||
phx-value-upload="header_upload"
|
||
class="theme-upload-cancel"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
<%= for err <- upload_errors(@uploads.header_upload, entry) do %>
|
||
<p class="theme-error-text">{error_to_string(err)}</p>
|
||
<% end %>
|
||
<% end %>
|
||
|
||
<%= for err <- upload_errors(@uploads.header_upload) do %>
|
||
<p class="theme-error-text">{error_to_string(err)}</p>
|
||
<% end %>
|
||
</div>
|
||
<% end %>
|
||
|
||
<!-- Presets Section -->
|
||
<div class="theme-section">
|
||
<label class="theme-section-label">Start with a preset</label>
|
||
<div class="theme-presets">
|
||
<%= for {preset_name, description} <- @presets_with_descriptions do %>
|
||
<button
|
||
type="button"
|
||
phx-click="apply_preset"
|
||
phx-value-preset={preset_name}
|
||
class={[
|
||
"theme-preset",
|
||
@active_preset == preset_name && "theme-preset-active"
|
||
]}
|
||
>
|
||
<div class="theme-preset-name">{preset_name}</div>
|
||
<div class="theme-preset-desc">{description}</div>
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Accent Colors -->
|
||
<div class="theme-section">
|
||
<label class="theme-section-label">Accent colour</label>
|
||
<form
|
||
id="accent-color-form"
|
||
phx-change="update_color"
|
||
phx-value-field="accent_color"
|
||
phx-hook="ColorSync"
|
||
>
|
||
<div class="theme-color-row">
|
||
<input
|
||
type="color"
|
||
id="accent-color-picker"
|
||
name="value"
|
||
value={@theme_settings.accent_color}
|
||
class="theme-color-swatch"
|
||
/>
|
||
<span class="theme-color-value">{@theme_settings.accent_color}</span>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="theme-section">
|
||
<label class="theme-section-label">Hover colour</label>
|
||
<form
|
||
id="secondary-accent-color-form"
|
||
phx-change="update_color"
|
||
phx-value-field="secondary_accent_color"
|
||
phx-hook="ColorSync"
|
||
>
|
||
<div class="theme-color-row">
|
||
<input
|
||
type="color"
|
||
id="secondary-accent-color-picker"
|
||
name="value"
|
||
value={@theme_settings.secondary_accent_color}
|
||
class="theme-color-swatch"
|
||
/>
|
||
<span class="theme-color-value">{@theme_settings.secondary_accent_color}</span>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="theme-section">
|
||
<label class="theme-section-label">Sale colour</label>
|
||
<form
|
||
id="sale-color-form"
|
||
phx-change="update_color"
|
||
phx-value-field="sale_color"
|
||
phx-hook="ColorSync"
|
||
>
|
||
<div class="theme-color-row">
|
||
<input
|
||
type="color"
|
||
id="sale-color-picker"
|
||
name="value"
|
||
value={@theme_settings.sale_color}
|
||
class="theme-color-swatch"
|
||
/>
|
||
<span class="theme-color-value">{@theme_settings.sale_color}</span>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Customise Section -->
|
||
<details
|
||
class="theme-customise"
|
||
id="customise-section"
|
||
open={@customise_open}
|
||
>
|
||
<summary class="theme-customise-summary" phx-click="toggle_customise">
|
||
<span class="theme-customise-label">Customise</span>
|
||
<svg
|
||
class="theme-customise-chevron"
|
||
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="theme-customise-body">
|
||
<!-- Typography Group -->
|
||
<div class="theme-group">
|
||
<div class="theme-group-header">
|
||
<svg
|
||
class="theme-group-icon"
|
||
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="theme-group-title">Typography</span>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="theme-section-label">Font style</label>
|
||
<div class="theme-chips">
|
||
<%= 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={[
|
||
"theme-chip",
|
||
@theme_settings.typography == typo && "theme-chip-active"
|
||
]}
|
||
>
|
||
{typo}
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="theme-section-label">Font size</label>
|
||
<div class="theme-chips">
|
||
<%= 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={[
|
||
"theme-chip",
|
||
@theme_settings.font_size == value && "theme-chip-active"
|
||
]}
|
||
>
|
||
{label}
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="theme-section-label">Heading weight</label>
|
||
<div class="theme-chips">
|
||
<%= 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={[
|
||
"theme-chip",
|
||
@theme_settings.heading_weight == value && "theme-chip-active"
|
||
]}
|
||
>
|
||
{label}
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Colours Group -->
|
||
<div class="theme-group">
|
||
<div class="theme-group-header">
|
||
<svg
|
||
class="theme-group-icon"
|
||
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="theme-group-title">Colours</span>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="theme-section-label">Colour mood</label>
|
||
<div class="theme-chips">
|
||
<%= for mood <- ["warm", "neutral", "cool", "dark"] do %>
|
||
<button
|
||
type="button"
|
||
phx-click="update_setting"
|
||
phx-value-field="mood"
|
||
phx-value-setting_value={mood}
|
||
class={["theme-chip", @theme_settings.mood == mood && "theme-chip-active"]}
|
||
>
|
||
{mood}
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Layout Group -->
|
||
<div class="theme-group">
|
||
<div class="theme-group-header">
|
||
<svg
|
||
class="theme-group-icon"
|
||
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="theme-group-title">Layout</span>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="theme-section-label">Product grid</label>
|
||
<div class="theme-chips">
|
||
<%= for cols <- ["2", "3", "4"] do %>
|
||
<button
|
||
type="button"
|
||
phx-click="update_setting"
|
||
phx-value-field="grid_columns"
|
||
phx-value-setting_value={cols}
|
||
class={[
|
||
"theme-chip",
|
||
@theme_settings.grid_columns == cols && "theme-chip-active"
|
||
]}
|
||
>
|
||
{cols} columns
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="theme-section-label">Density</label>
|
||
<div class="theme-chips">
|
||
<%= for density <- ["spacious", "balanced", "compact"] do %>
|
||
<button
|
||
type="button"
|
||
phx-click="update_setting"
|
||
phx-value-field="density"
|
||
phx-value-setting_value={density}
|
||
class={[
|
||
"theme-chip",
|
||
@theme_settings.density == density && "theme-chip-active"
|
||
]}
|
||
>
|
||
{density}
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="theme-section-label">Header layout</label>
|
||
<div class="theme-chips">
|
||
<%= for layout <- ["standard", "centered", "left"] do %>
|
||
<button
|
||
type="button"
|
||
phx-click="update_setting"
|
||
phx-value-field="header_layout"
|
||
phx-value-setting_value={layout}
|
||
class={[
|
||
"theme-chip",
|
||
@theme_settings.header_layout == layout && "theme-chip-active"
|
||
]}
|
||
>
|
||
{layout}
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="admin-check-label">
|
||
<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="theme-check-text">Announcement bar</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="admin-check-label">
|
||
<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="theme-check-text">Sticky header</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Shape Group -->
|
||
<div class="theme-group-flush">
|
||
<div class="theme-group-header">
|
||
<svg
|
||
class="theme-group-icon"
|
||
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="theme-group-title">Shape</span>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="theme-section-label">Corner style</label>
|
||
<div class="theme-chips">
|
||
<%= for shape <- ["sharp", "soft", "round", "pill"] do %>
|
||
<button
|
||
type="button"
|
||
phx-click="update_setting"
|
||
phx-value-field="shape"
|
||
phx-value-setting_value={shape}
|
||
class={["theme-chip", @theme_settings.shape == shape && "theme-chip-active"]}
|
||
>
|
||
{shape}
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="theme-section-label">Card shadow</label>
|
||
<div class="theme-chips">
|
||
<%= 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={[
|
||
"theme-chip",
|
||
@theme_settings.card_shadow == value && "theme-chip-active"
|
||
]}
|
||
>
|
||
{label}
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="theme-section-label">Button style</label>
|
||
<div class="theme-chips">
|
||
<%= 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={[
|
||
"theme-chip",
|
||
@theme_settings.button_style == value && "theme-chip-active"
|
||
]}
|
||
>
|
||
{label}
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Products Group -->
|
||
<div class="theme-group">
|
||
<div class="theme-group-header">
|
||
<svg
|
||
class="theme-group-icon"
|
||
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="theme-group-title">Products</span>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="theme-section-label">Content width</label>
|
||
<div class="theme-chips">
|
||
<%= for width <- ["contained", "wide", "full"] do %>
|
||
<button
|
||
type="button"
|
||
phx-click="update_setting"
|
||
phx-value-field="layout_width"
|
||
phx-value-setting_value={width}
|
||
class={[
|
||
"theme-chip",
|
||
@theme_settings.layout_width == width && "theme-chip-active"
|
||
]}
|
||
>
|
||
{width}
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="theme-section-label">Image aspect ratio</label>
|
||
<div class="theme-chips">
|
||
<%= 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={[
|
||
"theme-chip",
|
||
@theme_settings.image_aspect_ratio == value && "theme-chip-active"
|
||
]}
|
||
>
|
||
{label}
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="theme-section-label">Product text alignment</label>
|
||
<div class="theme-chips">
|
||
<%= 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={[
|
||
"theme-chip",
|
||
@theme_settings.product_text_align == value && "theme-chip-active"
|
||
]}
|
||
>
|
||
{label}
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="admin-check-label">
|
||
<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="theme-check-text">Second image on hover</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="admin-check-label">
|
||
<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="theme-check-text">Show prices</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Product Page Group -->
|
||
<div class="theme-group-flush">
|
||
<div class="theme-group-header">
|
||
<svg
|
||
class="theme-group-icon"
|
||
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="theme-group-title">Product page</span>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="admin-check-label">
|
||
<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="theme-check-text">Trust badges</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="admin-check-label">
|
||
<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="theme-check-text">Reviews section</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="theme-field">
|
||
<label class="admin-check-label">
|
||
<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="theme-check-text">Related products</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
<!-- Current Combination Display -->
|
||
<div class="theme-combination">
|
||
<div class="theme-combination-label">Current combination</div>
|
||
<div class="theme-combination-value">
|
||
{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="theme-combination-footnote">
|
||
One of 100,000+ possible combinations
|
||
</div>
|
||
</div>
|
||
<% end %>
|
||
</div>
|
||
|
||
<!-- Preview Area -->
|
||
<div class="theme-preview-area">
|
||
<div class="theme-preview-container">
|
||
<!-- Preview Page Switcher -->
|
||
<div class="theme-preview-tabs">
|
||
<%= 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={[
|
||
"theme-preview-tab",
|
||
@preview_page == page_name && "theme-preview-tab-active"
|
||
]}
|
||
>
|
||
{label}
|
||
</button>
|
||
<% end %>
|
||
</div>
|
||
|
||
<!-- Browser Chrome -->
|
||
<div class="theme-browser-chrome">
|
||
<div class="theme-browser-dots">
|
||
<div class="theme-browser-dot theme-browser-dot-close"></div>
|
||
<div class="theme-browser-dot theme-browser-dot-min"></div>
|
||
<div class="theme-browser-dot theme-browser-dot-max"></div>
|
||
</div>
|
||
<div class="theme-browser-url">
|
||
<svg
|
||
class="theme-browser-url-icon"
|
||
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="theme-browser-url-text truncate">
|
||
{@site_name |> String.downcase() |> String.replace(" ", "")}.myshopify.com
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Preview Frame -->
|
||
<div
|
||
class="themed theme-preview-frame"
|
||
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}
|
||
site_name={@site_name}
|
||
logo_image={@logo_image}
|
||
header_image={@header_image}
|
||
cart_drawer_open={@cart_drawer_open}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|