34 KiB
SimpleShop Theme - Implementation Plan
Overview
Integrate the complete theme feature from theme-demo-v28.html into the Phoenix LiveView application. The system will provide 9 curated presets and extensive customization options with real-time preview. All data (including uploaded images) will be stored in a single SQLite database file.
Key Concept: This codebase IS the shop (like WordPress is one site). Multiple users can manage it, but there's only one shop/site per deployment.
Core Architectural Decisions
1. Database Storage Strategy
- Images as BLOBs in SQLite - Complete self-containment in single database file
- Application-level settings - NO Shop model, just Settings tables for the site
- Multiple admin users - Any authenticated user can manage theme (no ownership model)
- SVG stored as both BLOB and TEXT - Enable recoloring feature
2. CSS Architecture
- Three-layer CSS system using Custom Properties:
- Layer 1: Primitives (static) - spacing, fonts, radii
- Layer 2: Theme tokens (dynamic) - generated from settings
- Layer 3: Semantic aliases (static) - friendly variable names
- Inline
<style>generation in LiveView for instant preview - ETS cache for storefront CSS (invalidate on save)
3. LiveView Structure
- Single LiveView (
ThemeLive.Index) with nested function components - No iframe - Direct preview in same LiveView using data attributes
- Smart preview data - Mock data initially, switch to real products when available
- Optimistic UI - Update preview immediately, persist on "Save" button
- Idiomatic Phoenix/LiveView - Use Tailwind utilities where possible, CSS variables for dynamic theming
4. Development Workflow
- Semantic git commits - Commit each logical step for easy rollback
- Test-driven - Write Phoenix tests alongside implementation (ExUnit, ConnCase, LiveViewTest)
- Manual verification - Test in browser after each phase
- Progressive enhancement - Each commit should leave app in working state
Database Schema
Philosophy: No Shop model - this app IS the shop. Settings are site-wide, managed by any admin user.
Migration 1: Create Settings Table
File: priv/repo/migrations/YYYYMMDDHHMMSS_create_settings.exs
defmodule SimpleshopTheme.Repo.Migrations.CreateSettings do
use Ecto.Migration
def change do
create table(:settings, primary_key: false) do
add :id, :binary_id, primary_key: true
add :key, :string, null: false # e.g., "site_name", "theme_settings"
add :value, :text, null: false # JSON or plain text
add :value_type, :string, null: false, default: "string" # string, json, integer, boolean
timestamps(type: :utc_datetime)
end
create unique_index(:settings, [:key])
end
end
Migration 2: Create Images Table
File: priv/repo/migrations/YYYYMMDDHHMMSS_create_images.exs
defmodule SimpleshopTheme.Repo.Migrations.CreateImages do
use Ecto.Migration
def change do
create table(:images, primary_key: false) do
add :id, :binary_id, primary_key: true
add :image_type, :string, null: false # "logo" | "header" | "product"
add :filename, :string, null: false
add :content_type, :string, null: false
add :file_size, :integer, null: false
# BLOB storage
add :data, :binary, null: false
# SVG support
add :is_svg, :boolean, default: false
add :svg_content, :text # For recoloring
# Optional: thumbnail for admin UI performance
add :thumbnail_data, :binary
timestamps(type: :utc_datetime)
end
create index(:images, [:image_type])
end
end
File Structure
New Contexts and Schemas
lib/simpleshop_theme/
├── settings/
│ ├── setting.ex # Setting schema (key-value store)
│ └── theme_settings.ex # Embedded schema for theme settings
├── settings.ex # Settings context (site-wide config API)
├── media/
│ ├── image.ex # Image schema (replaces ShopImage)
│ └── svg_recolorer.ex # Recolor SVG logos
├── media.ex # Media context (image uploads/serving)
└── theme/
├── presets.ex # Preset definitions (9 presets)
├── css_generator.ex # Generate CSS from settings
├── css_cache.ex # ETS cache GenServer
└── preview_data.ex # Smart preview (mock → real products)
LiveView Components
lib/simpleshop_theme_web/live/theme_live/
├── index.ex # Main LiveView
├── index.html.heex # Template
├── preset_selector.ex # 9 preset buttons (function component)
├── branding_section.ex # Logo/header uploads
├── customization_panel.ex # Accordion with all options
├── preview_frame.ex # Preview area with page switcher
└── preview_pages/
├── home.html.heex # Preview homepage
├── collection.html.heex # Preview product grid
├── pdp.html.heex # Preview product detail page
├── cart.html.heex # Preview cart
├── about.html.heex # Preview about page
├── contact.html.heex # Preview contact page
└── error.html.heex # Preview 404 page
Controllers
lib/simpleshop_theme_web/controllers/
└── image_controller.ex # Serve images from BLOBs
Static Assets & Tests
priv/static/css/
├── theme-primitives.css # Layer 1: Fixed CSS variables
└── theme-semantic.css # Layer 3: Semantic aliases
assets/css/
└── app.css # Import theme CSS here
test/simpleshop_theme/
├── settings_test.exs # Settings context tests
├── media_test.exs # Media context tests
└── theme/
├── css_generator_test.exs # CSS generation tests
├── presets_test.exs # Preset validation tests
└── preview_data_test.exs # Preview data tests
test/simpleshop_theme_web/
├── controllers/
│ └── image_controller_test.exs
└── live/
└── theme_live_test.exs # LiveView integration tests
Implementation Phases
Phase 1: Database Foundation ✓
Goal: Set up Settings and Media models for site-wide configuration
Steps:
- Create migrations for
settingsandimagestables - Run migrations
- Create
lib/simpleshop_theme/settings/setting.exschema - Create
lib/simpleshop_theme/settings/theme_settings.exembedded schema - Create
lib/simpleshop_theme/settings.excontext with:get_setting/2- Get setting by key with defaultput_setting/3- Set a setting valueget_theme_settings/0- Get theme settings as structupdate_theme_settings/1- Update theme settings
- Create
lib/simpleshop_theme/media/image.exschema - Create
lib/simpleshop_theme/media.excontext with:upload_image/2- Upload image with typeget_image/1- Get image by IDdelete_image/1- Delete image
- Create
lib/simpleshop_theme/theme/presets.exwith all 9 preset definitions - Create seed data in
priv/repo/seeds.exsfor default theme settings - Write tests:
test/simpleshop_theme/settings_test.exstest/simpleshop_theme/media_test.exs
Files to create:
priv/repo/migrations/*_create_settings.exspriv/repo/migrations/*_create_images.exslib/simpleshop_theme/settings/setting.exlib/simpleshop_theme/settings/theme_settings.exlib/simpleshop_theme/settings.exlib/simpleshop_theme/media/image.exlib/simpleshop_theme/media.exlib/simpleshop_theme/theme/presets.extest/simpleshop_theme/settings_test.exstest/simpleshop_theme/media_test.exs
Files to modify:
priv/repo/seeds.exs- Add default theme settings
Git commit: feat: add Settings and Media contexts with theme settings schema
Validation:
- Run tests:
mix test - Run
iex -S mixand test Settings API - Run seeds:
mix run priv/repo/seeds.exs
Phase 2: CSS Architecture ✓
Goal: Set up CSS generation system using Phoenix/Tailwind best practices
Steps:
- Create
priv/static/css/theme-primitives.csswith primitive CSS variables from demo - Create
priv/static/css/theme-semantic.csswith semantic aliases - Update
assets/css/app.cssto import theme CSS files - Create
lib/simpleshop_theme/theme/css_generator.exmodule:generate/1- Takes ThemeSettings, returns CSS custom properties string- Helper functions for each theme token (mood, typography, shape, density)
hex_to_hsl/1for accent color conversion- Use Tailwind-friendly approach (CSS variables that Tailwind can reference)
- Create
lib/simpleshop_theme/theme/css_cache.exGenServer with ETS table - Add
CSSCacheto supervision tree inlib/simpleshop_theme/application.ex - Write tests:
test/simpleshop_theme/theme/css_generator_test.exstest/simpleshop_theme/theme/presets_test.exs
Files to create:
priv/static/css/theme-primitives.csspriv/static/css/theme-semantic.csslib/simpleshop_theme/theme/css_generator.exlib/simpleshop_theme/theme/css_cache.extest/simpleshop_theme/theme/css_generator_test.exstest/simpleshop_theme/theme/presets_test.exs
Files to modify:
assets/css/app.csslib/simpleshop_theme/application.ex
Git commit: feat: add CSS generation system with custom properties and ETS cache
Validation:
- Run tests:
mix test test/simpleshop_theme/theme/ - Generate CSS for each preset in IEx, verify output
- Check CSS variables work with Tailwind
Phase 3: Smart Preview Data ✓
Goal: Create preview data system that uses real data when available, falls back to mock
Steps:
- Create
lib/simpleshop_theme/theme/preview_data.exwith:products/0- Real products if exist, else mock productscart_items/0- Mock cart contentstestimonials/0- Mock testimonials for homepagecategories/0- Mock product categorieshas_real_products?/0- Check if shop has products- Mock data uses placeholder images from
https://placehold.co/
- Write tests:
test/simpleshop_theme/theme/preview_data_test.exs
Files to create:
lib/simpleshop_theme/theme/preview_data.extest/simpleshop_theme/theme/preview_data_test.exs
Git commit: feat: add smart preview data system with mock fallback
Validation:
- Run tests:
mix test test/simpleshop_theme/theme/preview_data_test.exs - Call functions in IEx, verify data structure
- Verify falls back to mock when no products exist
Phase 4: Theme LiveView (Basic) ✓
Goal: Get basic theme editor working with preset switching
Steps:
- Create
lib/simpleshop_theme_web/live/theme_live/index.ex:- Mount: Load theme settings from Settings context
- Assign:
:theme_settings,:preview_page,:generated_css - Event handlers:
apply_preset,change_preview_page,save_theme
- Create
lib/simpleshop_theme_web/live/theme_live/index.html.heex:- Two-column layout (controls sidebar + preview area)
- Render preset buttons using Tailwind/daisyUI components
- Render preview frame with generated CSS in
<style>tag - Use Phoenix.Component for reusable pieces
- Add route to
lib/simpleshop_theme_web/router.ex:live "/admin/theme", ThemeLive.Index(authenticated)
- Add "Theme" link to user menu/navigation
- Write tests:
test/simpleshop_theme_web/live/theme_live_test.exs
Files to create:
lib/simpleshop_theme_web/live/theme_live/index.exlib/simpleshop_theme_web/live/theme_live/index.html.heextest/simpleshop_theme_web/live/theme_live_test.exs
Files to modify:
lib/simpleshop_theme_web/router.exlib/simpleshop_theme_web/components/layouts.ex(add nav link)
Git commit: feat: add Theme LiveView with preset switching
Validation:
- Run tests:
mix test test/simpleshop_theme_web/live/theme_live_test.exs - Visit
/admin/theme, click presets, verify CSS changes - Test authentication requirement
Phase 5: Preview Pages ✓
Goal: Create all 7 preview page templates
Steps:
- Create preview page templates by porting HTML from
theme-demo-v28.html:lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/home.html.heexlib/simpleshop_theme_web/live/theme_studio_live/preview_pages/collection.html.heexlib/simpleshop_theme_web/live/theme_studio_live/preview_pages/pdp.html.heexlib/simpleshop_theme_web/live/theme_studio_live/preview_pages/cart.html.heexlib/simpleshop_theme_web/live/theme_studio_live/preview_pages/about.html.heexlib/simpleshop_theme_web/live/theme_studio_live/preview_pages/contact.html.heexlib/simpleshop_theme_web/live/theme_studio_live/preview_pages/error.html.heex
- Port CSS from demo to theme CSS files (primitives, semantic)
- Wire up mock data in templates
- Implement page switcher buttons in preview frame
- Add data attributes system (
data-mood,data-typography, etc.) to preview container
Files to create:
- 7 preview page
.html.heexfiles inpreview_pages/directory
Files to modify:
lib/simpleshop_theme_web/live/theme_studio_live/index.html.heex- Add page switcher- CSS files - Port all styles from demo
Git commit: feat: add preview page templates with theme styling
Validation:
- Run tests:
mix test test/simpleshop_theme_web/live/theme_live_test.exs - Switch between all 7 pages, verify they render correctly
- Verify preview data shows (mock or real)
Phase 6: Customization Options ✓
Goal: Add all customization controls beyond presets
Steps:
- Create option group components in
index.html.heex:- Mood selector (4 options: neutral, warm, cool, dark)
- Typography selector (7 options: clean, editorial, modern, classic, friendly, minimal, impulse)
- Shape selector (4 options: sharp, soft, round, pill)
- Density selector (3 options: spacious, balanced, compact)
- Grid columns selector (3 options: 2, 3, 4)
- Header layout selector (3 options: standard, centered, minimal)
- Add color pickers:
- Accent color (with live preview)
- Secondary accent color
- Sale color
- Add advanced options accordion:
- Font size (small, medium, large)
- Heading weight (light, regular, bold)
- Layout width (contained, wide, full)
- Button style (filled, outline, soft)
- Card shadow (none, subtle, pronounced)
- Product text align (left, center)
- Image aspect ratio (square, portrait, landscape)
- Add feature toggles:
- Announcement bar
- Sticky header
- Hover image
- Quick add button
- Show prices
- PDP trust badges
- PDP reviews
- PDP related products
- Implement
update_settingevent handler in LiveView - Wire all controls to update theme settings and regenerate CSS
Files to modify:
lib/simpleshop_theme_web/live/theme_live/index.ex- Addupdate_settinghandlerlib/simpleshop_theme_web/live/theme_live/index.html.heex- Add all controlstest/simpleshop_theme_web/live/theme_live_test.exs- Add tests for customization
Git commit: feat: add full customization controls with real-time preview
Validation:
- Run tests:
mix test test/simpleshop_theme_web/live/theme_live_test.exs - Change each option, verify preview updates instantly
- Test all toggles work correctly
Phase 6.5: Fix Theme Visual Application 🔧
Goal: Fix the theme controls so they actually visually affect the preview pages
Problem: Theme controls update the database but don't visually change the preview. The Phoenix implementation is missing the data-attribute-based CSS system that the demo uses.
Root Cause Analysis:
The demo HTML uses data attributes (data-mood="warm", data-typography="editorial", etc.) on the .preview-frame element, and the CSS uses attribute selectors like .preview-frame[data-mood="warm"] { ... } to apply styles. The Phoenix LiveView implementation is missing these data attributes entirely.
Discovered Issues:
| Issue | Status | Root Cause |
|---|---|---|
| Fonts don't change | ❌ Broken | 1. Google Fonts not loaded 2. No data attributes on preview-frame |
| Mood/colors don't work (except dark) | ❌ Broken | No data attributes on preview-frame |
| Shape doesn't work | ❌ Broken | No data attributes on preview-frame |
| Density doesn't work | ❌ Broken | No data attributes on preview-frame |
| Grid columns 4 shows as 2 | ❌ Broken | Tailwind can't process dynamic class interpolation |
| Accent color doesn't update | ⚠️ Partial | CSS generated but may be overridden |
| Header layout has no component | ❌ Missing | No header component in preview pages |
Steps:
-
Add Google Fonts to root layout (
lib/simpleshop_theme_web/components/layouts/root.html.heex):<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&family=Libre+Baskerville:wght@400;700&family=Nunito:wght@400;600;700&family=Nunito+Sans:opsz,wght@6..12,300;6..12,400;6..12,500;6..12,600&family=Outfit:wght@300;400;500;600&family=Source+Sans+3:wght@400;500;600&family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet"> -
Add data attributes to preview frame (
lib/simpleshop_theme_web/live/theme_live/index.html.heex:205):<div class="preview-frame bg-white overflow-auto" 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} style="min-height: 600px; max-height: calc(100vh - 200px);"> -
Copy CSS from demo file (
theme-demo-v28.htmllines 81-217) to createpriv/static/css/theme-layer2-attributes.css:- All the
.preview-frame[data-mood="..."]rules - All the
.preview-frame[data-typography="..."]rules - All the
.preview-frame[data-shape="..."]rules - All the
.preview-frame[data-density="..."]rules - All the
.preview-frame[data-grid="..."]rules - All the
.preview-frame[data-header="..."]rules
- All the
-
Import the new CSS file in
assets/css/app.css:@import "../priv/static/css/theme-primitives.css"; @import "../priv/static/css/theme-layer2-attributes.css"; @import "../priv/static/css/theme-semantic.css"; -
Fix grid columns dynamic classes - Replace string interpolation with conditional logic in all preview pages:
<!-- BEFORE (broken): --> <div class={"grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-#{@theme_settings.grid_columns}"}> <!-- AFTER (working): --> <div class={["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]}> -
Add header component to preview pages - Create a header section that responds to
data-headerattribute:<header class="theme-header" style="background-color: var(--t-surface-raised); border-bottom: 1px solid var(--t-border-subtle);"> <!-- Header content that adapts based on data-header="standard|centered|minimal" --> </header> -
Update CSSGenerator to work alongside attribute-based CSS:
- Keep generating accent color HSL variables (these are used by all themes)
- Remove mood/typography/shape/density generation (now handled by attribute CSS)
- Focus on dynamic values like accent_color, secondary_accent_color, sale_color
-
Test all controls in browser to verify visual changes work
Files to create:
priv/static/css/theme-layer2-attributes.css- Attribute-based theme CSS from demo
Files to modify:
lib/simpleshop_theme_web/components/layouts/root.html.heex- Add Google Fontslib/simpleshop_theme_web/live/theme_live/index.html.heex- Add data attributes to preview-frameassets/css/app.css- Import new CSS filelib/simpleshop_theme/theme/css_generator.ex- Simplify to focus on color variables- All preview page files - Fix grid columns dynamic classes
- All preview page files - Add header component
Git commit: fix: add data attributes and attribute-based CSS to make theme controls work visually
Validation:
- Change mood - verify background colors change
- Change typography - verify fonts change
- Change shape - verify button/card border radius changes
- Change density - verify spacing changes
- Change grid columns - verify all 3 options (2, 3, 4) work correctly
- Change accent color - verify primary buttons change color
- Change header layout - verify header structure changes
Phase 7: File Uploads (Logo & Header) ✓
Goal: Enable logo and header image uploads with database storage
Steps:
- Add dependency to
mix.exs:{:image, "~> 0.54"}for thumbnail generation - Implement SVG detection and text storage in
image.exchangeset - Add Media context functions for logo/header:
- Update
upload_image/2to handle thumbnails - Add
get_logo/0,get_header/0convenience functions
- Update
- Create
lib/simpleshop_theme_web/controllers/image_controller.ex:show/2- Serve image BLOB with proper content-type and cachingrecolored_svg/2- Serve recolored SVG
- Create
lib/simpleshop_theme/media/svg_recolorer.exfor SVG color manipulation - Add routes for image serving in router
- Configure LiveView uploads in
index.ex::logo_upload- Accept PNG, JPG, WebP, SVG (max 2MB):header_upload- Accept PNG, JPG, WebP (max 5MB)
- Add upload controls to branding section in template using daisyUI components:
- Logo mode selector (text-only, logo+text, logo-only, header-image, logo+header)
- Logo upload with preview
- Logo size slider (24-120px)
- SVG recolor toggle + color picker
- Header image upload with preview
- Header zoom slider (100-200%)
- Header position sliders (X and Y: 0-100%)
- Implement upload event handlers
- Display uploaded images in preview
- Write tests:
test/simpleshop_theme_web/controllers/image_controller_test.exstest/simpleshop_theme/media/svg_recolorer_test.exs
Files to create:
lib/simpleshop_theme_web/controllers/image_controller.exlib/simpleshop_theme/media/svg_recolorer.extest/simpleshop_theme_web/controllers/image_controller_test.exstest/simpleshop_theme/media/svg_recolorer_test.exs
Files to modify:
lib/simpleshop_theme/media.ex- Add logo/header helperslib/simpleshop_theme/media/image.ex- SVG handlinglib/simpleshop_theme_web/live/theme_live/index.ex- Upload config and handlerslib/simpleshop_theme_web/live/theme_live/index.html.heex- Branding section UIlib/simpleshop_theme_web/router.ex- Image serving routesmix.exs- Add:imagedependencytest/simpleshop_theme_web/live/theme_live_test.exs- Add upload tests
Git commit: feat: add image uploads with BLOB storage and SVG recoloring
Validation:
- Run tests:
mix test - Upload logo and header images
- Verify BLOB storage in database
- Test SVG recoloring works
- Verify images display in preview
- Test image serving with proper cache headers
Phase 8: Persistence & Polish ✓
Goal: Complete save functionality and UX polish
Steps:
- Implement "Save Theme" button and handler:
- Show loading state during save
- Flash success/error message
- Invalidate CSS cache
- Add "Reset to Preset" functionality
- Implement unsaved changes warning
- Add keyboard shortcuts (Cmd+S to save)
- Debounce color picker inputs (300ms) to avoid excessive renders
- Add loading skeletons for preview
- Handle edge cases:
- File too large error
- Invalid file type error
- Network errors
- Add client-side validation
- Improve mobile responsiveness of editor
- Add tooltips/help text for complex options
Files to modify:
lib/simpleshop_theme_web/live/theme_live/index.ex- Save logic, debouncinglib/simpleshop_theme_web/live/theme_live/index.html.heex- UI polish- JavaScript hooks for keyboard shortcuts (if needed)
test/simpleshop_theme_web/live/theme_live_test.exs- Test persistence
Git commit: feat: add theme persistence with UX polish and validation
Validation:
- Run tests:
mix test - Save theme, reload page, verify settings persist
- Test unsaved changes warning
- Test keyboard shortcuts
- Verify error handling for edge cases
- Test mobile responsiveness
Phase 9: Storefront Integration ✓
Goal: Apply theme to actual public-facing shop
Steps:
- Create public shop routes in router:
get "/:shop_slug", ShopController, :show(shop homepage)- Or use domain-based routing for custom domains
- Create
lib/simpleshop_theme_web/live/shop_live/directory for storefront LiveViews - Create storefront page templates that respect theme settings:
- Use same CSS system as preview
- Load theme CSS from cache
- Render actual products (when they exist) or empty states
- Create
lib/simpleshop_theme_web/plugs/load_shop_theme.ex:- Load shop by domain/slug
- Load theme CSS from cache
- Assign to
conn.assigns.theme_css
- Add theme CSS to root layout for storefront
- Implement proper caching headers for theme CSS
- Handle theme CSS cache warming on app startup
Files to create:
lib/simpleshop_theme_web/live/shop_live/*(storefront pages)lib/simpleshop_theme_web/plugs/load_theme.extest/simpleshop_theme_web/live/shop_live_test.exstest/simpleshop_theme_web/plugs/load_theme_test.exs
Files to modify:
lib/simpleshop_theme_web/router.ex- Public shop routeslib/simpleshop_theme_web/components/layouts.ex- Theme CSS injection- Root layout template
Git commit: feat: integrate theme system with public storefront
Validation:
- Run tests:
mix test - Visit public shop URL, verify theme applies correctly
- Change theme in admin, verify storefront updates
- Test CSS cache invalidation
- Verify performance with caching headers
Technical Implementation Details
CSS Generation Example
# lib/simpleshop_theme/theme/css_generator.ex
defmodule SimpleshopTheme.Theme.CSSGenerator do
alias SimpleshopTheme.Settings.ThemeSettings
def generate(%ThemeSettings{} = settings) do
"""
.preview-frame, .shop-root {
#{generate_accent(settings.accent_color)}
#{generate_mood(settings.mood)}
#{generate_typography(settings.typography)}
#{generate_shape(settings.shape)}
#{generate_density(settings.density)}
}
"""
end
defp generate_accent(hex) do
{h, s, l} = hex_to_hsl(hex)
"""
--t-accent-h: #{h};
--t-accent-s: #{s}%;
--t-accent-l: #{l}%;
--t-accent: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
"""
end
defp hex_to_hsl("#" <> hex), do: hex_to_hsl(hex)
defp hex_to_hsl(hex) when byte_size(hex) == 6 do
{r, ""} = Integer.parse(String.slice(hex, 0..1), 16)
{g, ""} = Integer.parse(String.slice(hex, 2..3), 16)
{b, ""} = Integer.parse(String.slice(hex, 4..5), 16)
r = r / 255
g = g / 255
b = b / 255
max = Enum.max([r, g, b])
min = Enum.min([r, g, b])
l = (max + min) / 2
if max == min do
{0, 0, round(l * 100)}
else
d = max - min
s = if l > 0.5, do: d / (2 - max - min), else: d / (max + min)
h = cond do
max == r -> (g - b) / d + (if g < b, do: 6, else: 0)
max == g -> (b - r) / d + 2
max == b -> (r - g) / d + 4
end
{round(h * 60), round(s * 100), round(l * 100)}
end
end
defp generate_mood("neutral"), do: """
--t-surface-base: #ffffff;
--t-surface-raised: #ffffff;
--t-text-primary: #171717;
--t-border-default: #e5e5e5;
"""
defp generate_mood("warm"), do: """
--t-surface-base: #fdf8f3;
--t-surface-raised: #fffcf8;
--t-text-primary: #1c1917;
--t-border-default: #e7e0d8;
"""
defp generate_mood("cool"), do: """
--t-surface-base: #f4f7fb;
--t-surface-raised: #f8fafc;
--t-text-primary: #0f172a;
--t-border-default: #d4dce8;
"""
defp generate_mood("dark"), do: """
--t-surface-base: #0a0a0a;
--t-surface-raised: #171717;
--t-text-primary: #fafafa;
--t-border-default: #262626;
--p-shadow-strength: 0.25;
"""
# Similar for typography, shape, density...
end
LiveView Upload Configuration
# In mount/3
socket =
socket
|> allow_upload(:logo_upload,
accept: ~w(.png .jpg .jpeg .webp .svg),
max_entries: 1,
max_file_size: 2_000_000,
auto_upload: true
)
|> allow_upload(:header_upload,
accept: ~w(.png .jpg .jpeg .webp),
max_entries: 1,
max_file_size: 5_000_000,
auto_upload: true
)
# Handle upload completion
def handle_event("logo_uploaded", _params, socket) do
uploaded_files =
consume_uploaded_entries(socket, :logo_upload, fn %{path: path}, entry ->
file_binary = File.read!(path)
{:ok, image} = Media.upload_image(%{
image_type: "logo",
filename: entry.client_name,
content_type: entry.client_type,
file_size: entry.client_size,
data: file_binary
})
{:ok, image}
end)
case uploaded_files do
[image | _] ->
# Update theme settings to reference this image
settings = socket.assigns.theme_settings
{:ok, _} = Settings.update_theme_settings(%{settings | logo_image_id: image.id})
{:noreply, assign(socket, :logo_image, image)}
[] ->
{:noreply, socket}
end
end
Image Controller Serving
defmodule SimpleshopThemeWeb.ImageController do
use SimpleshopThemeWeb, :controller
alias SimpleshopTheme.Media
def show(conn, %{"id" => id}) do
case Media.get_image(id) do
nil ->
send_resp(conn, 404, "Image not found")
image ->
conn
|> put_resp_content_type(image.content_type)
|> put_resp_header("cache-control", "public, max-age=31536000, immutable")
|> put_resp_header("etag", "\"#{image.id}\"")
|> send_resp(200, image.data)
end
end
def recolored_svg(conn, %{"id" => id, "color" => color}) do
with %{is_svg: true, svg_content: svg} <- Media.get_image(id),
clean_color <- String.trim_leading(color, "#"),
recolored <- SimpleshopTheme.Media.SVGRecolorer.recolor(svg, "##{clean_color}") do
conn
|> put_resp_content_type("image/svg+xml")
|> put_resp_header("cache-control", "public, max-age=3600")
|> send_resp(200, recolored)
else
_ -> send_resp(conn, 400, "Invalid request")
end
end
end
Key Dependencies to Add
# mix.exs
{:image, "~> 0.54"} # Thumbnail generation (uses libvips)
{:fast_sanitize, "~> 0.2"} # SVG sanitization (security)
Router Updates
# lib/simpleshop_theme_web/router.ex
# Theme editor (authenticated)
scope "/admin", SimpleshopThemeWeb do
pipe_through [:browser, :require_authenticated_user]
live_session :theme_editor,
on_mount: [{SimpleshopThemeWeb.UserAuth, :require_authenticated}] do
live "/theme", ThemeLive.Index, :index
end
end
# Image serving (public)
scope "/", SimpleshopThemeWeb do
pipe_through :browser
get "/images/:id", ImageController, :show
get "/images/:id/recolored/:color", ImageController, :recolored_svg
end
# Public storefront (future - Phase 9)
# scope "/", SimpleshopThemeWeb do
# pipe_through :browser
# live "/", ShopLive.Home, :index
# live "/products/:slug", ShopLive.Product, :show
# end
Critical Files Reference
To Create
lib/simpleshop_theme/settings.ex- Settings contextlib/simpleshop_theme/settings/setting.ex- Setting schema (key-value)lib/simpleshop_theme/settings/theme_settings.ex- Embedded theme settingslib/simpleshop_theme/media.ex- Media contextlib/simpleshop_theme/media/image.ex- Image schemalib/simpleshop_theme/media/svg_recolorer.ex- SVG manipulationlib/simpleshop_theme/theme/presets.ex- 9 preset definitionslib/simpleshop_theme/theme/css_generator.ex- CSS generationlib/simpleshop_theme/theme/css_cache.ex- ETS cachinglib/simpleshop_theme/theme/preview_data.ex- Smart preview datalib/simpleshop_theme_web/live/theme_live/index.ex- Main LiveViewlib/simpleshop_theme_web/controllers/image_controller.ex- Image servingpriv/static/css/theme-primitives.css- CSS Layer 1priv/static/css/theme-semantic.css- CSS Layer 3- Comprehensive test files for all contexts and LiveViews
To Modify
lib/simpleshop_theme/application.ex- Add CSS cache to supervision treelib/simpleshop_theme_web/router.ex- Add theme and image routeslib/simpleshop_theme_web/components/layouts.ex- Add theme nav linkassets/css/app.css- Import theme CSSpriv/repo/seeds.exs- Add default theme settingsmix.exs- Add dependencies
Reference Files
SIMPLESHOP_THEME_STUDIO_SPEC.md- Complete specificationtheme-demo-v28.html- Working prototype (CSS, HTML structure, JavaScript logic)
Success Criteria
✅ User can access theme studio at /admin/theme
✅ All 9 presets work with real-time preview
✅ All customization options update preview instantly
✅ Logo and header images upload and store as BLOBs
✅ SVG recoloring works correctly
✅ Preview shows all 7 mock pages
✅ Settings persist to database
✅ "Save" button works with success feedback
✅ Images served efficiently from database
✅ CSS cached in ETS for performance
✅ Everything self-contained in SQLite file
Next Steps After Implementation
- Add Products Context - So users can add real products
- Build Storefront - Public-facing shop pages using saved theme
- Add Orders/Cart - E-commerce functionality
- Multi-admin Support - Invite additional users to manage shop
- Custom Domains - Allow users to point their own domain
- Theme Export/Import - Share themes between shops
- Advanced Features - Custom CSS, code injection, etc.