feat: add automated Printify mockup generation & POD sample content
Printify Integration: - Add Printify API client module with full HTTP wrapper - Add mockup generator with dynamic "cover" scaling algorithm - Create Mix task (mix generate_mockups) for automated mockup generation - Support --cleanup flag to delete products after downloading mockups - Calculate optimal scale factor based on artwork/placeholder aspect ratios - Handle landscape artwork on portrait print areas without white space Sample Content (16 products across 5 POD categories): - Art Prints: Mountain Sunrise, Ocean Waves, Wildflower Meadow, Geometric Abstract, Botanical Illustration - Apparel: Forest Silhouette T-Shirt, Forest Light Hoodie - Tote Bags: Wildflower Meadow, Sunset Gradient - Homewares: Fern Leaf Mug, Ocean Waves Cushion, Night Sky Blanket - Stationery: Autumn Leaves Notebook, Monstera Leaf Notebook - Accessories: Monstera Leaf Phone Case, Blue Waves Laptop Sleeve Preview Data Updates: - Replace generic e-commerce products with POD-focused items - Update categories to POD-relevant: Art Prints, Apparel, Homewares, Stationery, Accessories - Update cart drawer items to match new product range - Refresh testimonials with POD-appropriate reviews Theme Content Updates: - Update hero section for "Wildprint Studio" brand identity - Rewrite about page narrative with humble, British, personal tone - Update search hints to nature/POD relevant terms - Add mockups directory to static paths 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@ -228,28 +228,70 @@ POD-relevant reviews:
|
|||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
### Phase 1: Mockup Generation (Manual - You)
|
### Phase 1: Automated Mockup Generation (via Printify API)
|
||||||
1. Download high-res versions of each artwork from Unsplash (use the download button)
|
|
||||||
2. Create free Printify account (if not already done)
|
|
||||||
3. For each product in the mapping table:
|
|
||||||
- Create the product in Printify
|
|
||||||
- Upload the artwork
|
|
||||||
- Position/scale appropriately
|
|
||||||
- Download the mockup image
|
|
||||||
4. Share mockup image URLs or files with me
|
|
||||||
|
|
||||||
### Phase 2: Code Updates (Me)
|
We'll create an Elixir module + Mix task to automate mockup generation.
|
||||||
Once mockup images are available:
|
|
||||||
1. Update `preview_data.ex` with new products, categories, testimonials
|
#### Implementation Plan
|
||||||
|
|
||||||
|
**New files to create:**
|
||||||
|
1. `lib/simpleshop_theme/printify/client.ex` - HTTP client wrapper for Printify API
|
||||||
|
2. `lib/simpleshop_theme/printify/mockup_generator.ex` - Core logic module
|
||||||
|
3. `lib/mix/tasks/generate_mockups.ex` - Mix task entry point
|
||||||
|
|
||||||
|
**API Workflow:**
|
||||||
|
1. Fetch shop ID via `GET /v1/shops.json`
|
||||||
|
2. For each product type, look up blueprint_id via `GET /v1/catalog/blueprints.json`
|
||||||
|
3. Get print provider and placeholder dimensions via `GET /v1/catalog/blueprints/{id}/print_providers/{id}/variants.json`
|
||||||
|
4. Download Unsplash image at highest resolution
|
||||||
|
5. Upload image to Printify via `POST /v1/uploads/images.json` (using URL)
|
||||||
|
6. Create product via `POST /v1/shops/{shop_id}/products.json` with centered artwork
|
||||||
|
7. Extract mockup URLs from response `images` array
|
||||||
|
8. Download mockups to `priv/static/mockups/` directory
|
||||||
|
|
||||||
|
**Image Handling:**
|
||||||
|
- Printify API returns placeholder dimensions (width x height in pixels) for each product
|
||||||
|
- We'll calculate scale factor to fit artwork within print area while maintaining aspect ratio
|
||||||
|
- Center position: x=0.5, y=0.5 with appropriate scale
|
||||||
|
- Unsplash provides high-res downloads (typically 4000+ pixels) - should exceed 300 DPI requirements
|
||||||
|
- Printify auto-enhances low-res images, but we'll use highest quality available
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- API token stored in environment variable `PRINTIFY_API_TOKEN`
|
||||||
|
- Product definitions in the module (blueprint IDs, provider IDs)
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
# Generate mockups (keeps products in Printify)
|
||||||
|
mix generate_mockups
|
||||||
|
|
||||||
|
# Generate mockups and delete products from Printify afterwards
|
||||||
|
mix generate_mockups --cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
**Blueprint IDs to look up:**
|
||||||
|
- Premium Matte Poster
|
||||||
|
- Bella+Canvas 3001 Unisex Tee
|
||||||
|
- Gildan 18500 Heavy Blend Hoodie
|
||||||
|
- Cotton Canvas Tote Bag
|
||||||
|
- White Glossy Mug 11oz
|
||||||
|
- Spun Polyester Pillow
|
||||||
|
- Sherpa Fleece Blanket
|
||||||
|
- Hardcover Journal
|
||||||
|
- iPhone Tough Case
|
||||||
|
- Laptop Sleeve
|
||||||
|
|
||||||
|
### Phase 2: Code Updates
|
||||||
|
Once mockup images are generated:
|
||||||
|
1. Update `preview_data.ex` with new products pointing to local mockup images
|
||||||
2. Update preview page templates (home, about, pdp, etc.)
|
2. Update preview page templates (home, about, pdp, etc.)
|
||||||
3. Update shared components (footer, announcement bar, search hints)
|
3. Update shared components (footer, announcement bar, search hints)
|
||||||
4. Test all preview pages work correctly
|
4. Test all preview pages work correctly
|
||||||
|
|
||||||
### Phase 3: About Page Image
|
### Phase 3: About Page Image
|
||||||
We also need an "about" page image (currently picsum.photos/seed/studio). Options:
|
Download directly from Unsplash (no Printify needed):
|
||||||
- A creative workspace photo from Unsplash
|
- Forest Clearing: https://unsplash.com/photos/sunlight-filters-through-a-peaceful-forest-clearing-FwVkxITt8Bg
|
||||||
- A hands-at-work photo (packing orders, etc.)
|
- Save to `priv/static/images/about-hero.jpg`
|
||||||
- Leave as placeholder for now
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
201
lib/mix/tasks/generate_mockups.ex
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
defmodule Mix.Tasks.GenerateMockups do
|
||||||
|
@moduledoc """
|
||||||
|
Generates product mockups using the Printify API.
|
||||||
|
|
||||||
|
This task automates the creation of product mockups for the SimpleshopTheme
|
||||||
|
sample content. It downloads artwork from Unsplash, uploads it to Printify,
|
||||||
|
creates products, and downloads the generated mockups.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- A Printify account with API access
|
||||||
|
- The PRINTIFY_API_TOKEN environment variable must be set
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
# Generate mockups (keeps products in Printify)
|
||||||
|
mix generate_mockups
|
||||||
|
|
||||||
|
# Generate mockups and delete products afterwards
|
||||||
|
mix generate_mockups --cleanup
|
||||||
|
|
||||||
|
# Search for available blueprints
|
||||||
|
mix generate_mockups --search "poster"
|
||||||
|
|
||||||
|
# List all blueprints
|
||||||
|
mix generate_mockups --list-blueprints
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Mockup images are saved to: priv/static/mockups/
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
alias SimpleshopTheme.Printify.MockupGenerator
|
||||||
|
|
||||||
|
@shortdoc "Generates product mockups using Printify API"
|
||||||
|
|
||||||
|
@impl Mix.Task
|
||||||
|
def run(args) do
|
||||||
|
# Start required applications
|
||||||
|
Mix.Task.run("app.start")
|
||||||
|
|
||||||
|
{opts, _, _} =
|
||||||
|
OptionParser.parse(args,
|
||||||
|
switches: [
|
||||||
|
cleanup: :boolean,
|
||||||
|
search: :string,
|
||||||
|
list_blueprints: :boolean,
|
||||||
|
help: :boolean
|
||||||
|
],
|
||||||
|
aliases: [
|
||||||
|
c: :cleanup,
|
||||||
|
s: :search,
|
||||||
|
l: :list_blueprints,
|
||||||
|
h: :help
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
cond do
|
||||||
|
opts[:help] ->
|
||||||
|
print_help()
|
||||||
|
|
||||||
|
opts[:list_blueprints] ->
|
||||||
|
list_blueprints()
|
||||||
|
|
||||||
|
opts[:search] ->
|
||||||
|
search_blueprints(opts[:search])
|
||||||
|
|
||||||
|
true ->
|
||||||
|
generate_mockups(opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp print_help do
|
||||||
|
Mix.shell().info("""
|
||||||
|
|
||||||
|
Printify Mockup Generator
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Generates product mockups using the Printify API.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
mix generate_mockups [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--cleanup, -c Delete created products from Printify after downloading mockups
|
||||||
|
--search, -s TERM Search for blueprints by name
|
||||||
|
--list-blueprints List all available blueprint IDs and names
|
||||||
|
--help, -h Show this help message
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
PRINTIFY_API_TOKEN Required. Your Printify API token.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
# Generate all mockups
|
||||||
|
export PRINTIFY_API_TOKEN="your-token"
|
||||||
|
mix generate_mockups
|
||||||
|
|
||||||
|
# Generate and cleanup
|
||||||
|
mix generate_mockups --cleanup
|
||||||
|
|
||||||
|
# Find blueprint IDs
|
||||||
|
mix generate_mockups --search "poster"
|
||||||
|
""")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp list_blueprints do
|
||||||
|
Mix.shell().info("Fetching blueprints from Printify...")
|
||||||
|
|
||||||
|
case MockupGenerator.list_blueprints() do
|
||||||
|
blueprints when is_list(blueprints) ->
|
||||||
|
Mix.shell().info("\nAvailable Blueprints:\n")
|
||||||
|
|
||||||
|
blueprints
|
||||||
|
|> Enum.each(fn {id, title} ->
|
||||||
|
Mix.shell().info(" #{id}: #{title}")
|
||||||
|
end)
|
||||||
|
|
||||||
|
Mix.shell().info("\nTotal: #{length(blueprints)} blueprints")
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
Mix.shell().error("Error fetching blueprints: #{inspect(reason)}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp search_blueprints(term) do
|
||||||
|
Mix.shell().info("Searching for blueprints matching '#{term}'...")
|
||||||
|
|
||||||
|
case MockupGenerator.search_blueprints(term) do
|
||||||
|
results when is_list(results) ->
|
||||||
|
if length(results) == 0 do
|
||||||
|
Mix.shell().info("No blueprints found matching '#{term}'")
|
||||||
|
else
|
||||||
|
Mix.shell().info("\nMatching Blueprints:\n")
|
||||||
|
|
||||||
|
results
|
||||||
|
|> Enum.each(fn {id, title} ->
|
||||||
|
Mix.shell().info(" #{id}: #{title}")
|
||||||
|
end)
|
||||||
|
|
||||||
|
Mix.shell().info("\nFound: #{length(results)} blueprints")
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
Mix.shell().error("Error searching blueprints: #{inspect(reason)}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp generate_mockups(opts) do
|
||||||
|
cleanup = Keyword.get(opts, :cleanup, false)
|
||||||
|
|
||||||
|
Mix.shell().info("""
|
||||||
|
|
||||||
|
╔═══════════════════════════════════════════╗
|
||||||
|
║ Printify Mockup Generator ║
|
||||||
|
╠═══════════════════════════════════════════╣
|
||||||
|
║ Cleanup mode: #{if cleanup, do: "ON ", else: "OFF"} ║
|
||||||
|
╚═══════════════════════════════════════════╝
|
||||||
|
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Verify API token is set
|
||||||
|
case System.get_env("PRINTIFY_API_TOKEN") do
|
||||||
|
nil ->
|
||||||
|
Mix.shell().error("""
|
||||||
|
Error: PRINTIFY_API_TOKEN environment variable is not set.
|
||||||
|
|
||||||
|
To get your API token:
|
||||||
|
1. Log in to Printify
|
||||||
|
2. Go to Settings > API tokens
|
||||||
|
3. Create a new token with required permissions
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
export PRINTIFY_API_TOKEN="your-token"
|
||||||
|
mix generate_mockups
|
||||||
|
""")
|
||||||
|
|
||||||
|
_token ->
|
||||||
|
results = MockupGenerator.generate_all(cleanup: cleanup)
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
successful = Enum.count(results, &match?({:ok, _, _, _}, &1))
|
||||||
|
failed = Enum.count(results, &match?({:error, _, _}, &1))
|
||||||
|
|
||||||
|
Mix.shell().info("""
|
||||||
|
|
||||||
|
═══════════════════════════════════════════
|
||||||
|
Summary
|
||||||
|
═══════════════════════════════════════════
|
||||||
|
Successful: #{successful}
|
||||||
|
Failed: #{failed}
|
||||||
|
═══════════════════════════════════════════
|
||||||
|
""")
|
||||||
|
|
||||||
|
if failed > 0 do
|
||||||
|
Mix.shell().error("Some products failed to generate. Check the output above for details.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
182
lib/simpleshop_theme/printify/client.ex
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
defmodule SimpleshopTheme.Printify.Client do
|
||||||
|
@moduledoc """
|
||||||
|
HTTP client for the Printify API.
|
||||||
|
|
||||||
|
Handles authentication and provides low-level API access.
|
||||||
|
Requires PRINTIFY_API_TOKEN environment variable to be set.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@base_url "https://api.printify.com/v1"
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get the API token from environment.
|
||||||
|
"""
|
||||||
|
def api_token do
|
||||||
|
System.get_env("PRINTIFY_API_TOKEN") ||
|
||||||
|
raise "PRINTIFY_API_TOKEN environment variable is not set"
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Make a GET request to the Printify API.
|
||||||
|
"""
|
||||||
|
def get(path, opts \\ []) do
|
||||||
|
url = @base_url <> path
|
||||||
|
|
||||||
|
case Req.get(url, headers: auth_headers(), receive_timeout: 30_000) do
|
||||||
|
{:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
|
||||||
|
{:ok, body}
|
||||||
|
|
||||||
|
{:ok, %Req.Response{status: status, body: body}} ->
|
||||||
|
{:error, {status, body}}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{:error, reason}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Make a POST request to the Printify API.
|
||||||
|
"""
|
||||||
|
def post(path, body, opts \\ []) do
|
||||||
|
url = @base_url <> path
|
||||||
|
|
||||||
|
case Req.post(url, json: body, headers: auth_headers(), receive_timeout: 60_000) do
|
||||||
|
{:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
|
||||||
|
{:ok, body}
|
||||||
|
|
||||||
|
{:ok, %Req.Response{status: status, body: body}} ->
|
||||||
|
{:error, {status, body}}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{:error, reason}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Make a DELETE request to the Printify API.
|
||||||
|
"""
|
||||||
|
def delete(path, opts \\ []) do
|
||||||
|
url = @base_url <> path
|
||||||
|
|
||||||
|
case Req.delete(url, headers: auth_headers(), receive_timeout: 30_000) do
|
||||||
|
{:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
|
||||||
|
{:ok, body}
|
||||||
|
|
||||||
|
{:ok, %Req.Response{status: status}} when status == 204 ->
|
||||||
|
{:ok, nil}
|
||||||
|
|
||||||
|
{:ok, %Req.Response{status: status, body: body}} ->
|
||||||
|
{:error, {status, body}}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{:error, reason}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get all shops for the authenticated account.
|
||||||
|
"""
|
||||||
|
def get_shops do
|
||||||
|
get("/shops.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get the first shop ID for the account.
|
||||||
|
"""
|
||||||
|
def get_shop_id do
|
||||||
|
case get_shops() do
|
||||||
|
{:ok, shops} when is_list(shops) and length(shops) > 0 ->
|
||||||
|
{:ok, hd(shops)["id"]}
|
||||||
|
|
||||||
|
{:ok, []} ->
|
||||||
|
{:error, :no_shops}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get all blueprints (product types) from the catalog.
|
||||||
|
"""
|
||||||
|
def get_blueprints do
|
||||||
|
get("/catalog/blueprints.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get print providers for a specific blueprint.
|
||||||
|
"""
|
||||||
|
def get_print_providers(blueprint_id) do
|
||||||
|
get("/catalog/blueprints/#{blueprint_id}/print_providers.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get variants for a specific blueprint and print provider.
|
||||||
|
"""
|
||||||
|
def get_variants(blueprint_id, print_provider_id) do
|
||||||
|
get("/catalog/blueprints/#{blueprint_id}/print_providers/#{print_provider_id}/variants.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get shipping information for a blueprint/provider combination.
|
||||||
|
"""
|
||||||
|
def get_shipping(blueprint_id, print_provider_id) do
|
||||||
|
get("/catalog/blueprints/#{blueprint_id}/print_providers/#{print_provider_id}/shipping.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Upload an image to Printify via URL.
|
||||||
|
"""
|
||||||
|
def upload_image(file_name, url) do
|
||||||
|
post("/uploads/images.json", %{
|
||||||
|
file_name: file_name,
|
||||||
|
url: url
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Create a product in a shop.
|
||||||
|
"""
|
||||||
|
def create_product(shop_id, product_data) do
|
||||||
|
post("/shops/#{shop_id}/products.json", product_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get a product by ID.
|
||||||
|
"""
|
||||||
|
def get_product(shop_id, product_id) do
|
||||||
|
get("/shops/#{shop_id}/products/#{product_id}.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Delete a product from a shop.
|
||||||
|
"""
|
||||||
|
def delete_product(shop_id, product_id) do
|
||||||
|
delete("/shops/#{shop_id}/products/#{product_id}.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Download a file from a URL to a local path.
|
||||||
|
"""
|
||||||
|
def download_file(url, output_path) do
|
||||||
|
case Req.get(url, into: File.stream!(output_path), receive_timeout: 60_000) do
|
||||||
|
{:ok, %Req.Response{status: status}} when status in 200..299 ->
|
||||||
|
{:ok, output_path}
|
||||||
|
|
||||||
|
{:ok, %Req.Response{status: status}} ->
|
||||||
|
File.rm(output_path)
|
||||||
|
{:error, {:http_error, status}}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
File.rm(output_path)
|
||||||
|
{:error, reason}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp auth_headers do
|
||||||
|
[
|
||||||
|
{"Authorization", "Bearer #{api_token()}"},
|
||||||
|
{"Content-Type", "application/json"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
476
lib/simpleshop_theme/printify/mockup_generator.ex
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
defmodule SimpleshopTheme.Printify.MockupGenerator do
|
||||||
|
@moduledoc """
|
||||||
|
Generates product mockups using the Printify API.
|
||||||
|
|
||||||
|
This module handles the end-to-end process of:
|
||||||
|
1. Looking up product blueprints and variants
|
||||||
|
2. Downloading artwork from Unsplash
|
||||||
|
3. Uploading artwork to Printify
|
||||||
|
4. Creating products with the artwork
|
||||||
|
5. Downloading generated mockup images
|
||||||
|
6. Optionally cleaning up created products
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias SimpleshopTheme.Printify.Client
|
||||||
|
|
||||||
|
@output_dir "priv/static/mockups"
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Product definitions with their artwork URLs and Printify product types.
|
||||||
|
"""
|
||||||
|
def product_definitions do
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
name: "Mountain Sunrise Art Print",
|
||||||
|
slug: "mountain-sunrise-print",
|
||||||
|
category: "Art Prints",
|
||||||
|
artwork_url: unsplash_download_url("UweNcthlmDc"),
|
||||||
|
product_type: :poster,
|
||||||
|
price: 2400
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Ocean Waves Art Print",
|
||||||
|
slug: "ocean-waves-print",
|
||||||
|
category: "Art Prints",
|
||||||
|
artwork_url: unsplash_download_url("XRhUTUVuXAE"),
|
||||||
|
product_type: :poster,
|
||||||
|
price: 2400
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Wildflower Meadow Art Print",
|
||||||
|
slug: "wildflower-meadow-print",
|
||||||
|
category: "Art Prints",
|
||||||
|
artwork_url: unsplash_download_url("QvjL4y7SF9k"),
|
||||||
|
product_type: :poster,
|
||||||
|
price: 2400
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Geometric Abstract Art Print",
|
||||||
|
slug: "geometric-abstract-print",
|
||||||
|
category: "Art Prints",
|
||||||
|
artwork_url: unsplash_download_url("-6GvTDpkkPU"),
|
||||||
|
product_type: :poster,
|
||||||
|
price: 2800
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Botanical Illustration Print",
|
||||||
|
slug: "botanical-illustration-print",
|
||||||
|
category: "Art Prints",
|
||||||
|
artwork_url: unsplash_download_url("FNtNIDQWUZY"),
|
||||||
|
product_type: :poster,
|
||||||
|
price: 2400
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Forest Silhouette T-Shirt",
|
||||||
|
slug: "forest-silhouette-tshirt",
|
||||||
|
category: "Apparel",
|
||||||
|
artwork_url: unsplash_download_url("EhvMzMRO4_o"),
|
||||||
|
product_type: :tshirt,
|
||||||
|
price: 2999
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Forest Light Hoodie",
|
||||||
|
slug: "forest-light-hoodie",
|
||||||
|
category: "Apparel",
|
||||||
|
artwork_url: unsplash_download_url("FwVkxITt8Bg"),
|
||||||
|
product_type: :hoodie,
|
||||||
|
price: 4499
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Wildflower Meadow Tote Bag",
|
||||||
|
slug: "wildflower-meadow-tote",
|
||||||
|
category: "Apparel",
|
||||||
|
artwork_url: unsplash_download_url("QvjL4y7SF9k"),
|
||||||
|
product_type: :tote,
|
||||||
|
price: 1999
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Sunset Gradient Tote Bag",
|
||||||
|
slug: "sunset-gradient-tote",
|
||||||
|
category: "Apparel",
|
||||||
|
artwork_url: unsplash_download_url("XRhUTUVuXAE"),
|
||||||
|
product_type: :tote,
|
||||||
|
price: 1999
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Fern Leaf Mug",
|
||||||
|
slug: "fern-leaf-mug",
|
||||||
|
category: "Homewares",
|
||||||
|
artwork_url: unsplash_download_url("bYiJojtkHnc"),
|
||||||
|
product_type: :mug,
|
||||||
|
price: 1499
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Ocean Waves Cushion",
|
||||||
|
slug: "ocean-waves-cushion",
|
||||||
|
category: "Homewares",
|
||||||
|
artwork_url: unsplash_download_url("XRhUTUVuXAE"),
|
||||||
|
product_type: :cushion,
|
||||||
|
price: 2999
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Night Sky Blanket",
|
||||||
|
slug: "night-sky-blanket",
|
||||||
|
category: "Homewares",
|
||||||
|
artwork_url: unsplash_download_url("oQR1B87HsNs"),
|
||||||
|
product_type: :blanket,
|
||||||
|
price: 5999
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Autumn Leaves Notebook",
|
||||||
|
slug: "autumn-leaves-notebook",
|
||||||
|
category: "Stationery",
|
||||||
|
artwork_url: unsplash_download_url("Aa3ALtIxEGY"),
|
||||||
|
product_type: :notebook,
|
||||||
|
price: 1999
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Monstera Leaf Notebook",
|
||||||
|
slug: "monstera-leaf-notebook",
|
||||||
|
category: "Stationery",
|
||||||
|
artwork_url: unsplash_download_url("hETU8_b2IM0"),
|
||||||
|
product_type: :notebook,
|
||||||
|
price: 1999
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Monstera Leaf Phone Case",
|
||||||
|
slug: "monstera-leaf-phone-case",
|
||||||
|
category: "Accessories",
|
||||||
|
artwork_url: unsplash_download_url("hETU8_b2IM0"),
|
||||||
|
product_type: :phone_case,
|
||||||
|
price: 2499
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
name: "Blue Waves Laptop Sleeve",
|
||||||
|
slug: "blue-waves-laptop-sleeve",
|
||||||
|
category: "Accessories",
|
||||||
|
artwork_url: unsplash_download_url("dYksH3vHorc"),
|
||||||
|
product_type: :laptop_sleeve,
|
||||||
|
price: 3499
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Blueprint configurations for each product type.
|
||||||
|
These IDs need to be looked up from the Printify catalog.
|
||||||
|
"""
|
||||||
|
def blueprint_config do
|
||||||
|
%{
|
||||||
|
# Search terms matched to Printify's actual blueprint titles (partial match)
|
||||||
|
poster: %{blueprint_id: nil, print_provider_id: nil, search_term: "Matte Posters"},
|
||||||
|
tshirt: %{blueprint_id: nil, print_provider_id: nil, search_term: "Softstyle T-Shirt"},
|
||||||
|
hoodie: %{blueprint_id: nil, print_provider_id: nil, search_term: "Pullover Hoodie"},
|
||||||
|
tote: %{blueprint_id: nil, print_provider_id: nil, search_term: "Cotton Tote Bag"},
|
||||||
|
mug: %{blueprint_id: nil, print_provider_id: nil, search_term: "Mug 11oz"},
|
||||||
|
cushion: %{blueprint_id: nil, print_provider_id: nil, search_term: "Spun Polyester Square Pillow"},
|
||||||
|
blanket: %{blueprint_id: nil, print_provider_id: nil, search_term: "Sherpa Fleece Blanket"},
|
||||||
|
notebook: %{blueprint_id: nil, print_provider_id: nil, search_term: "Hardcover Journal Matte"},
|
||||||
|
phone_case: %{blueprint_id: nil, print_provider_id: nil, search_term: "Tough Phone Cases"},
|
||||||
|
laptop_sleeve: %{blueprint_id: nil, print_provider_id: nil, search_term: "Laptop Sleeve"}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate Unsplash download URL from photo ID.
|
||||||
|
Uses the Unsplash download API which provides high-quality images.
|
||||||
|
"""
|
||||||
|
def unsplash_download_url(photo_id) do
|
||||||
|
"https://unsplash.com/photos/#{photo_id}/download?force=true"
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Search for a blueprint by name/term.
|
||||||
|
"""
|
||||||
|
def find_blueprint(search_term) do
|
||||||
|
case Client.get_blueprints() do
|
||||||
|
{:ok, blueprints} ->
|
||||||
|
found =
|
||||||
|
Enum.find(blueprints, fn bp ->
|
||||||
|
String.contains?(String.downcase(bp["title"] || ""), String.downcase(search_term))
|
||||||
|
end)
|
||||||
|
|
||||||
|
case found do
|
||||||
|
nil -> {:error, {:blueprint_not_found, search_term}}
|
||||||
|
bp -> {:ok, bp}
|
||||||
|
end
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Find a suitable print provider for a blueprint.
|
||||||
|
Prefers providers with good ratings and reasonable pricing.
|
||||||
|
"""
|
||||||
|
def find_print_provider(blueprint_id) do
|
||||||
|
case Client.get_print_providers(blueprint_id) do
|
||||||
|
{:ok, providers} when is_list(providers) and length(providers) > 0 ->
|
||||||
|
# Just pick the first provider for simplicity
|
||||||
|
{:ok, hd(providers)}
|
||||||
|
|
||||||
|
{:ok, []} ->
|
||||||
|
{:error, :no_providers}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get variant and placeholder information for a blueprint/provider combination.
|
||||||
|
"""
|
||||||
|
def get_variant_info(blueprint_id, print_provider_id) do
|
||||||
|
case Client.get_variants(blueprint_id, print_provider_id) do
|
||||||
|
{:ok, %{"variants" => variants}} when is_list(variants) ->
|
||||||
|
{:ok, variants}
|
||||||
|
|
||||||
|
{:ok, variants} when is_list(variants) ->
|
||||||
|
{:ok, variants}
|
||||||
|
|
||||||
|
{:ok, response} when is_map(response) ->
|
||||||
|
# Handle case where variants might be nested differently
|
||||||
|
{:ok, response["variants"] || []}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Upload artwork to Printify from a URL.
|
||||||
|
"""
|
||||||
|
def upload_artwork(name, url) do
|
||||||
|
file_name = "#{name}.jpg"
|
||||||
|
Client.upload_image(file_name, url)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Calculate scale factor for "cover" behavior.
|
||||||
|
Image will fill entire placeholder, cropping edges if necessary.
|
||||||
|
Printify scale is relative to placeholder width (1.0 = artwork width matches placeholder width).
|
||||||
|
"""
|
||||||
|
def calculate_cover_scale(artwork_width, artwork_height, placeholder_width, placeholder_height)
|
||||||
|
when is_number(artwork_width) and is_number(artwork_height) and
|
||||||
|
is_number(placeholder_width) and is_number(placeholder_height) and
|
||||||
|
artwork_width > 0 and artwork_height > 0 and
|
||||||
|
placeholder_width > 0 and placeholder_height > 0 do
|
||||||
|
# For cover: use the larger scale to ensure full coverage
|
||||||
|
width_scale = 1.0
|
||||||
|
height_scale = (placeholder_height * artwork_width) / (artwork_height * placeholder_width)
|
||||||
|
max(width_scale, height_scale)
|
||||||
|
end
|
||||||
|
|
||||||
|
def calculate_cover_scale(_, _, _, _), do: 1.0
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Create a product with the uploaded artwork.
|
||||||
|
"""
|
||||||
|
def create_product(shop_id, product_def, image_id, image_width, image_height, blueprint_id, print_provider_id, variants) do
|
||||||
|
# Get the first variant for simplicity (typically a standard size/color)
|
||||||
|
variant = hd(variants)
|
||||||
|
variant_id = variant["id"]
|
||||||
|
|
||||||
|
# Get placeholder info
|
||||||
|
placeholders = variant["placeholders"] || []
|
||||||
|
front_placeholder = Enum.find(placeholders, fn p -> p["position"] == "front" end) || hd(placeholders)
|
||||||
|
|
||||||
|
# Extract placeholder dimensions and calculate cover scale
|
||||||
|
placeholder_width = front_placeholder["width"]
|
||||||
|
placeholder_height = front_placeholder["height"]
|
||||||
|
scale = calculate_cover_scale(image_width, image_height, placeholder_width, placeholder_height)
|
||||||
|
|
||||||
|
IO.puts(" Scale calculation: artwork #{image_width}x#{image_height}, placeholder #{placeholder_width}x#{placeholder_height} -> scale #{Float.round(scale, 3)}")
|
||||||
|
|
||||||
|
product_data = %{
|
||||||
|
title: product_def.name,
|
||||||
|
description: "#{product_def.name} - Nature-inspired design from Wildprint Studio",
|
||||||
|
blueprint_id: blueprint_id,
|
||||||
|
print_provider_id: print_provider_id,
|
||||||
|
variants: [
|
||||||
|
%{
|
||||||
|
id: variant_id,
|
||||||
|
price: product_def.price,
|
||||||
|
is_enabled: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
print_areas: [
|
||||||
|
%{
|
||||||
|
variant_ids: [variant_id],
|
||||||
|
placeholders: [
|
||||||
|
%{
|
||||||
|
position: front_placeholder["position"] || "front",
|
||||||
|
images: [
|
||||||
|
%{
|
||||||
|
id: image_id,
|
||||||
|
x: 0.5,
|
||||||
|
y: 0.5,
|
||||||
|
scale: scale,
|
||||||
|
angle: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.create_product(shop_id, product_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Extract mockup image URLs from a created product.
|
||||||
|
"""
|
||||||
|
def extract_mockup_urls(product) do
|
||||||
|
images = product["images"] || []
|
||||||
|
Enum.map(images, fn img -> img["src"] end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Download mockup images to the output directory.
|
||||||
|
"""
|
||||||
|
def download_mockups(product_slug, mockup_urls) do
|
||||||
|
File.mkdir_p!(@output_dir)
|
||||||
|
|
||||||
|
mockup_urls
|
||||||
|
|> Enum.with_index(1)
|
||||||
|
|> Enum.map(fn {url, index} ->
|
||||||
|
output_path = Path.join(@output_dir, "#{product_slug}-#{index}.jpg")
|
||||||
|
IO.puts(" Downloading mockup #{index} to #{output_path}...")
|
||||||
|
|
||||||
|
case Client.download_file(url, output_path) do
|
||||||
|
{:ok, path} -> {:ok, path}
|
||||||
|
{:error, reason} -> {:error, {url, reason}}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate mockups for all products.
|
||||||
|
"""
|
||||||
|
def generate_all(opts \\ []) do
|
||||||
|
cleanup = Keyword.get(opts, :cleanup, false)
|
||||||
|
|
||||||
|
IO.puts("Starting mockup generation...")
|
||||||
|
IO.puts("")
|
||||||
|
|
||||||
|
# Get shop ID
|
||||||
|
IO.puts("Fetching shop ID...")
|
||||||
|
{:ok, shop_id} = Client.get_shop_id()
|
||||||
|
IO.puts("Using shop ID: #{shop_id}")
|
||||||
|
IO.puts("")
|
||||||
|
|
||||||
|
# Track created products for cleanup
|
||||||
|
created_products = []
|
||||||
|
|
||||||
|
results =
|
||||||
|
product_definitions()
|
||||||
|
|> Enum.map(fn product_def ->
|
||||||
|
IO.puts("Processing: #{product_def.name}")
|
||||||
|
result = generate_single(shop_id, product_def)
|
||||||
|
|
||||||
|
case result do
|
||||||
|
{:ok, product_id, mockup_paths} ->
|
||||||
|
IO.puts(" ✓ Generated #{length(mockup_paths)} mockups")
|
||||||
|
{:ok, product_def.slug, product_id, mockup_paths}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
IO.puts(" ✗ Error: #{inspect(reason)}")
|
||||||
|
{:error, product_def.slug, reason}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
# Cleanup if requested
|
||||||
|
if cleanup do
|
||||||
|
IO.puts("")
|
||||||
|
IO.puts("Cleaning up created products...")
|
||||||
|
|
||||||
|
results
|
||||||
|
|> Enum.filter(fn
|
||||||
|
{:ok, _, _, _} -> true
|
||||||
|
_ -> false
|
||||||
|
end)
|
||||||
|
|> Enum.each(fn {:ok, slug, product_id, _} ->
|
||||||
|
IO.puts(" Deleting #{slug}...")
|
||||||
|
Client.delete_product(shop_id, product_id)
|
||||||
|
end)
|
||||||
|
|
||||||
|
IO.puts("Cleanup complete.")
|
||||||
|
end
|
||||||
|
|
||||||
|
IO.puts("")
|
||||||
|
IO.puts("Mockup generation complete!")
|
||||||
|
IO.puts("Output directory: #{@output_dir}")
|
||||||
|
|
||||||
|
results
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate mockups for a single product.
|
||||||
|
"""
|
||||||
|
def generate_single(shop_id, product_def) do
|
||||||
|
config = blueprint_config()[product_def.product_type]
|
||||||
|
|
||||||
|
with {:ok, blueprint} <- find_blueprint(config.search_term),
|
||||||
|
blueprint_id = blueprint["id"],
|
||||||
|
_ = IO.puts(" Found blueprint: #{blueprint["title"]} (#{blueprint_id})"),
|
||||||
|
{:ok, provider} <- find_print_provider(blueprint_id),
|
||||||
|
provider_id = provider["id"],
|
||||||
|
_ = IO.puts(" Using provider: #{provider["title"]} (#{provider_id})"),
|
||||||
|
{:ok, variants} <- get_variant_info(blueprint_id, provider_id),
|
||||||
|
_ = IO.puts(" Found #{length(variants)} variants"),
|
||||||
|
_ = IO.puts(" Uploading artwork..."),
|
||||||
|
{:ok, upload} <- upload_artwork(product_def.slug, product_def.artwork_url),
|
||||||
|
image_id = upload["id"],
|
||||||
|
image_width = upload["width"],
|
||||||
|
image_height = upload["height"],
|
||||||
|
_ = IO.puts(" Artwork uploaded (ID: #{image_id}, #{image_width}x#{image_height})"),
|
||||||
|
_ = IO.puts(" Creating product..."),
|
||||||
|
{:ok, product} <- create_product(shop_id, product_def, image_id, image_width, image_height, blueprint_id, provider_id, variants),
|
||||||
|
product_id = product["id"],
|
||||||
|
mockup_urls = extract_mockup_urls(product),
|
||||||
|
_ = IO.puts(" Product created (ID: #{product_id})"),
|
||||||
|
_ = IO.puts(" Downloading #{length(mockup_urls)} mockups..."),
|
||||||
|
download_results <- download_mockups(product_def.slug, mockup_urls) do
|
||||||
|
successful_downloads =
|
||||||
|
download_results
|
||||||
|
|> Enum.filter(&match?({:ok, _}, &1))
|
||||||
|
|> Enum.map(fn {:ok, path} -> path end)
|
||||||
|
|
||||||
|
{:ok, product_id, successful_downloads}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
List all available blueprints (for discovery).
|
||||||
|
"""
|
||||||
|
def list_blueprints do
|
||||||
|
case Client.get_blueprints() do
|
||||||
|
{:ok, blueprints} ->
|
||||||
|
blueprints
|
||||||
|
|> Enum.map(fn bp -> {bp["id"], bp["title"]} end)
|
||||||
|
|> Enum.sort_by(fn {_, title} -> title end)
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Search blueprints by keyword.
|
||||||
|
"""
|
||||||
|
def search_blueprints(keyword) do
|
||||||
|
case Client.get_blueprints() do
|
||||||
|
{:ok, blueprints} ->
|
||||||
|
blueprints
|
||||||
|
|> Enum.filter(fn bp ->
|
||||||
|
String.contains?(String.downcase(bp["title"] || ""), String.downcase(keyword))
|
||||||
|
end)
|
||||||
|
|> Enum.map(fn bp -> {bp["id"], bp["title"]} end)
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -34,16 +34,16 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
def cart_drawer_items do
|
def cart_drawer_items do
|
||||||
[
|
[
|
||||||
%{
|
%{
|
||||||
name: "Autumn Fern",
|
name: "Mountain Sunrise Art Print",
|
||||||
variant: "A4 / Unframed",
|
variant: "12″ x 18″ / Matte",
|
||||||
price: "£24.00",
|
price: "£24.00",
|
||||||
image: "https://picsum.photos/seed/fern/120/120"
|
image: "/mockups/mountain-sunrise-print-1.jpg"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
name: "Wild Roses",
|
name: "Fern Leaf Mug",
|
||||||
variant: "A3 / Oak frame",
|
variant: "11oz / White",
|
||||||
price: "£48.00",
|
price: "£14.99",
|
||||||
image: "https://picsum.photos/seed/roses/120/120"
|
image: "/mockups/fern-leaf-mug-1.jpg"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
@ -93,147 +93,200 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
|
|
||||||
defp mock_products do
|
defp mock_products do
|
||||||
[
|
[
|
||||||
|
# Art Prints
|
||||||
%{
|
%{
|
||||||
id: "1",
|
id: "1",
|
||||||
name: "Classic Cotton T-Shirt",
|
name: "Mountain Sunrise Art Print",
|
||||||
description: "Soft, breathable cotton tee perfect for everyday wear",
|
description: "Capture the magic of dawn with this stunning mountain landscape print",
|
||||||
price: 2999,
|
price: 2400,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=600&h=800&fit=crop&q=80",
|
image_url: "/mockups/mountain-sunrise-print-1.jpg",
|
||||||
hover_image_url: "https://images.unsplash.com/photo-1622445275576-721325763afe?w=600&h=800&fit=crop&q=80",
|
hover_image_url: "/mockups/mountain-sunrise-print-2.jpg",
|
||||||
category: "Clothing",
|
category: "Art Prints",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "2",
|
id: "2",
|
||||||
name: "Leather Crossbody Bag",
|
name: "Ocean Waves Art Print",
|
||||||
description: "Handcrafted genuine leather bag with adjustable strap",
|
description: "A calming sunset over ocean waves to bring peace to any room",
|
||||||
price: 8999,
|
price: 2400,
|
||||||
compare_at_price: 11999,
|
compare_at_price: nil,
|
||||||
image_url: "https://images.unsplash.com/photo-1548036328-c9fa89d128fa?w=600&h=800&fit=crop&q=80",
|
image_url: "/mockups/ocean-waves-print-1.jpg",
|
||||||
hover_image_url: "https://images.unsplash.com/photo-1590874103328-eac38a683ce7?w=600&h=800&fit=crop&q=80",
|
hover_image_url: "/mockups/ocean-waves-print-2.jpg",
|
||||||
category: "Accessories",
|
category: "Art Prints",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: true
|
on_sale: false
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "3",
|
id: "3",
|
||||||
name: "Ceramic Coffee Mug",
|
name: "Wildflower Meadow Art Print",
|
||||||
description: "Handmade ceramic mug with unique glaze finish",
|
description: "Beautiful wildflower meadow captured in the summer sunshine",
|
||||||
price: 2499,
|
price: 2400,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "https://images.unsplash.com/photo-1514228742587-6b1558fcca3d?w=600&h=800&fit=crop&q=80",
|
image_url: "/mockups/wildflower-meadow-print-1.jpg",
|
||||||
hover_image_url: "https://images.unsplash.com/photo-1481833761820-0509d3217039?w=600&h=800&fit=crop&q=80",
|
hover_image_url: "/mockups/wildflower-meadow-print-2.jpg",
|
||||||
category: "Home",
|
category: "Art Prints",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "4",
|
id: "4",
|
||||||
name: "Minimalist Watch",
|
name: "Geometric Abstract Art Print",
|
||||||
description: "Sleek design with Japanese quartz movement",
|
description: "Modern minimalist design with bold geometric shapes",
|
||||||
price: 12999,
|
price: 2800,
|
||||||
compare_at_price: nil,
|
compare_at_price: 3200,
|
||||||
image_url: "https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=600&h=800&fit=crop&q=80",
|
image_url: "/mockups/geometric-abstract-print-1.jpg",
|
||||||
hover_image_url: "https://images.unsplash.com/photo-1524592094714-0f0654e20314?w=600&h=800&fit=crop&q=80",
|
hover_image_url: "/mockups/geometric-abstract-print-2.jpg",
|
||||||
category: "Accessories",
|
category: "Art Prints",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
id: "5",
|
|
||||||
name: "Wool Throw Blanket",
|
|
||||||
description: "Cozy merino wool blanket in herringbone pattern",
|
|
||||||
price: 7999,
|
|
||||||
compare_at_price: 9999,
|
|
||||||
image_url: "https://images.unsplash.com/photo-1580301762395-21ce84d00bc6?w=600&h=800&fit=crop&q=80",
|
|
||||||
hover_image_url: "https://images.unsplash.com/photo-1600369671854-2d9f5db4a5e0?w=600&h=800&fit=crop&q=80",
|
|
||||||
category: "Home",
|
|
||||||
in_stock: false,
|
|
||||||
on_sale: true
|
on_sale: true
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "6",
|
id: "5",
|
||||||
name: "Artisan Soap Set",
|
name: "Botanical Illustration Print",
|
||||||
description: "Natural handmade soaps with essential oils",
|
description: "Vintage-inspired botanical drawing with intricate detail",
|
||||||
price: 3499,
|
price: 2400,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "https://images.unsplash.com/photo-1607006344380-b6775a0824a7?w=600&h=800&fit=crop&q=80",
|
image_url: "/mockups/botanical-illustration-print-1.jpg",
|
||||||
hover_image_url: "https://images.unsplash.com/photo-1600857544200-b2f666a9a2ec?w=600&h=800&fit=crop&q=80",
|
hover_image_url: "/mockups/botanical-illustration-print-2.jpg",
|
||||||
category: "Beauty",
|
category: "Art Prints",
|
||||||
|
in_stock: true,
|
||||||
|
on_sale: false
|
||||||
|
},
|
||||||
|
# Apparel
|
||||||
|
%{
|
||||||
|
id: "6",
|
||||||
|
name: "Forest Silhouette T-Shirt",
|
||||||
|
description: "Soft cotton tee featuring a peaceful forest silhouette design",
|
||||||
|
price: 2999,
|
||||||
|
compare_at_price: nil,
|
||||||
|
image_url: "/mockups/forest-silhouette-tshirt-1.jpg",
|
||||||
|
hover_image_url: "/mockups/forest-silhouette-tshirt-2.jpg",
|
||||||
|
category: "Apparel",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "7",
|
id: "7",
|
||||||
name: "Denim Jacket",
|
name: "Forest Light Hoodie",
|
||||||
description: "Classic cut denim jacket with vintage wash",
|
description: "Cosy fleece hoodie with stunning forest light photography",
|
||||||
price: 8499,
|
price: 4499,
|
||||||
compare_at_price: nil,
|
compare_at_price: 4999,
|
||||||
image_url: "https://images.unsplash.com/photo-1576995853123-5a10305d93c0?w=600&h=800&fit=crop&q=80",
|
image_url: "/mockups/forest-light-hoodie-1.jpg",
|
||||||
hover_image_url: "https://images.unsplash.com/photo-1601333144130-8cbb312386b6?w=600&h=800&fit=crop&q=80",
|
hover_image_url: "/mockups/forest-light-hoodie-2.jpg",
|
||||||
category: "Clothing",
|
category: "Apparel",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: true
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "8",
|
id: "8",
|
||||||
name: "Canvas Tote Bag",
|
name: "Wildflower Meadow Tote Bag",
|
||||||
description: "Durable organic cotton canvas with reinforced handles",
|
description: "Sturdy cotton tote bag with vibrant wildflower design",
|
||||||
price: 2999,
|
price: 1999,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "https://images.unsplash.com/photo-1622560480605-d83c853bc5c3?w=600&h=800&fit=crop&q=80",
|
image_url: "/mockups/wildflower-meadow-tote-1.jpg",
|
||||||
hover_image_url: "https://images.unsplash.com/photo-1597633125097-5a9ae3cb8a8f?w=600&h=800&fit=crop&q=80",
|
hover_image_url: "/mockups/wildflower-meadow-tote-2.jpg",
|
||||||
category: "Accessories",
|
category: "Apparel",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "9",
|
id: "9",
|
||||||
name: "Scented Candle",
|
name: "Sunset Gradient Tote Bag",
|
||||||
description: "Soy wax candle with cedar and vanilla notes",
|
description: "Beautiful ocean sunset printed on durable canvas tote",
|
||||||
price: 3299,
|
price: 1999,
|
||||||
compare_at_price: 3999,
|
compare_at_price: nil,
|
||||||
image_url: "https://images.unsplash.com/photo-1602028915047-37269d1a73f7?w=600&h=800&fit=crop&q=80",
|
image_url: "/mockups/sunset-gradient-tote-1.jpg",
|
||||||
hover_image_url: "https://images.unsplash.com/photo-1603006905003-be475563bc59?w=600&h=800&fit=crop&q=80",
|
hover_image_url: "/mockups/sunset-gradient-tote-2.jpg",
|
||||||
category: "Home",
|
category: "Apparel",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: true
|
on_sale: false
|
||||||
},
|
},
|
||||||
|
# Homewares
|
||||||
%{
|
%{
|
||||||
id: "10",
|
id: "10",
|
||||||
name: "Stainless Steel Water Bottle",
|
name: "Fern Leaf Mug",
|
||||||
description: "Insulated bottle keeps drinks cold for 24 hours",
|
description: "Start your morning right with this nature-inspired ceramic mug",
|
||||||
price: 3999,
|
price: 1499,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "https://images.unsplash.com/photo-1602143407151-7111542de6e8?w=600&h=800&fit=crop&q=80",
|
image_url: "/mockups/fern-leaf-mug-1.jpg",
|
||||||
hover_image_url: "https://images.unsplash.com/photo-1570831739435-6601aa3fa4fb?w=600&h=800&fit=crop&q=80",
|
hover_image_url: "/mockups/fern-leaf-mug-2.jpg",
|
||||||
category: "Accessories",
|
category: "Homewares",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "11",
|
id: "11",
|
||||||
name: "Organic Cotton Socks",
|
name: "Ocean Waves Cushion",
|
||||||
description: "Comfortable crew socks in solid colors",
|
description: "Soft polyester cushion featuring a stunning ocean sunset",
|
||||||
price: 1499,
|
price: 2999,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "https://images.unsplash.com/photo-1586350977771-b3b0abd50c82?w=600&h=800&fit=crop&q=80",
|
image_url: "/mockups/ocean-waves-cushion-1.jpg",
|
||||||
hover_image_url: "https://images.unsplash.com/photo-1582966772680-860e372bb558?w=600&h=800&fit=crop&q=80",
|
hover_image_url: "/mockups/ocean-waves-cushion-2.jpg",
|
||||||
category: "Clothing",
|
category: "Homewares",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "12",
|
id: "12",
|
||||||
name: "Bamboo Cutting Board",
|
name: "Night Sky Blanket",
|
||||||
description: "Sustainable bamboo with juice groove",
|
description: "Cosy sherpa fleece blanket with mesmerising milky way print",
|
||||||
price: 4499,
|
price: 5999,
|
||||||
|
compare_at_price: 6999,
|
||||||
|
image_url: "/mockups/night-sky-blanket-1.jpg",
|
||||||
|
hover_image_url: "/mockups/night-sky-blanket-2.jpg",
|
||||||
|
category: "Homewares",
|
||||||
|
in_stock: true,
|
||||||
|
on_sale: true
|
||||||
|
},
|
||||||
|
# Stationery
|
||||||
|
%{
|
||||||
|
id: "13",
|
||||||
|
name: "Autumn Leaves Notebook",
|
||||||
|
description: "Hardcover journal with beautiful autumn foliage design",
|
||||||
|
price: 1999,
|
||||||
compare_at_price: nil,
|
compare_at_price: nil,
|
||||||
image_url: "https://images.unsplash.com/photo-1594226801341-41427b4e5c22?w=600&h=800&fit=crop&q=80",
|
image_url: "/mockups/autumn-leaves-notebook-1.jpg",
|
||||||
hover_image_url: "https://images.unsplash.com/photo-1606760227091-3dd870d97f1d?w=600&h=800&fit=crop&q=80",
|
hover_image_url: "/mockups/autumn-leaves-notebook-2.jpg",
|
||||||
category: "Kitchen",
|
category: "Stationery",
|
||||||
|
in_stock: true,
|
||||||
|
on_sale: false
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
id: "14",
|
||||||
|
name: "Monstera Leaf Notebook",
|
||||||
|
description: "Tropical-inspired hardcover journal for your thoughts",
|
||||||
|
price: 1999,
|
||||||
|
compare_at_price: nil,
|
||||||
|
image_url: "/mockups/monstera-leaf-notebook-1.jpg",
|
||||||
|
hover_image_url: "/mockups/monstera-leaf-notebook-2.jpg",
|
||||||
|
category: "Stationery",
|
||||||
|
in_stock: true,
|
||||||
|
on_sale: false
|
||||||
|
},
|
||||||
|
# Accessories
|
||||||
|
%{
|
||||||
|
id: "15",
|
||||||
|
name: "Monstera Leaf Phone Case",
|
||||||
|
description: "Tough phone case with stunning monstera leaf photography",
|
||||||
|
price: 2499,
|
||||||
|
compare_at_price: nil,
|
||||||
|
image_url: "/mockups/monstera-leaf-phone-case-1.jpg",
|
||||||
|
hover_image_url: "/mockups/monstera-leaf-phone-case-2.jpg",
|
||||||
|
category: "Accessories",
|
||||||
|
in_stock: true,
|
||||||
|
on_sale: false
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
id: "16",
|
||||||
|
name: "Blue Waves Laptop Sleeve",
|
||||||
|
description: "Protective laptop sleeve with abstract blue gradient design",
|
||||||
|
price: 3499,
|
||||||
|
compare_at_price: nil,
|
||||||
|
image_url: "/mockups/blue-waves-laptop-sleeve-1.jpg",
|
||||||
|
hover_image_url: "/mockups/blue-waves-laptop-sleeve-2.jpg",
|
||||||
|
category: "Accessories",
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
on_sale: false
|
on_sale: false
|
||||||
}
|
}
|
||||||
@ -246,16 +299,16 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
[
|
[
|
||||||
%{
|
%{
|
||||||
product: Enum.at(products, 0),
|
product: Enum.at(products, 0),
|
||||||
quantity: 2,
|
|
||||||
variant: "Medium / Navy"
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
product: Enum.at(products, 2),
|
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
variant: "One Size"
|
variant: "12″ x 18″ / Matte"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
product: Enum.at(products, 6),
|
product: Enum.at(products, 9),
|
||||||
|
quantity: 2,
|
||||||
|
variant: "11oz / White"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
product: Enum.at(products, 5),
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
variant: "Large / Black"
|
variant: "Large / Black"
|
||||||
}
|
}
|
||||||
@ -267,44 +320,44 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
%{
|
%{
|
||||||
id: "1",
|
id: "1",
|
||||||
author: "Sarah M.",
|
author: "Sarah M.",
|
||||||
content: "Absolutely love the quality! The attention to detail is incredible.",
|
content: "The print quality is absolutely stunning - colours are exactly as shown online. My living room looks so much better now!",
|
||||||
rating: 5,
|
rating: 5,
|
||||||
date: "2024-01-15"
|
date: "2025-01-15"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "2",
|
id: "2",
|
||||||
author: "James L.",
|
author: "James L.",
|
||||||
content: "Fast shipping and beautiful packaging. Will definitely order again.",
|
content: "Bought the forest hoodie as a gift. The packaging was lovely and the quality exceeded expectations. Will order again!",
|
||||||
rating: 5,
|
rating: 5,
|
||||||
date: "2024-01-10"
|
date: "2025-01-10"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "3",
|
id: "3",
|
||||||
author: "Emily R.",
|
author: "Emily R.",
|
||||||
content: "These products have become my everyday favorites. Highly recommend!",
|
content: "My new favourite mug! I love sipping my morning tea while looking at that beautiful fern design.",
|
||||||
rating: 5,
|
rating: 5,
|
||||||
date: "2024-01-05"
|
date: "2025-01-05"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "4",
|
id: "4",
|
||||||
author: "Michael T.",
|
author: "Michael T.",
|
||||||
content: "Great customer service and even better products. Worth every penny.",
|
content: "The tote bag is so sturdy - I use it for everything now. Great print quality that hasn't faded at all.",
|
||||||
rating: 5,
|
rating: 5,
|
||||||
date: "2023-12-28"
|
date: "2024-12-28"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "5",
|
id: "5",
|
||||||
author: "Lisa K.",
|
author: "Lisa K.",
|
||||||
content: "The craftsmanship is outstanding. You can tell these are made with care.",
|
content: "The night sky blanket is gorgeous and so cosy. Perfect for film nights on the sofa. Highly recommend!",
|
||||||
rating: 5,
|
rating: 5,
|
||||||
date: "2023-12-20"
|
date: "2024-12-20"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "6",
|
id: "6",
|
||||||
author: "David P.",
|
author: "David P.",
|
||||||
content: "Perfect gift idea! My friends loved what I got them from here.",
|
content: "Ordered several prints for my new flat. They arrived well packaged and look amazing on the wall.",
|
||||||
rating: 5,
|
rating: 5,
|
||||||
date: "2023-12-15"
|
date: "2024-12-15"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
@ -313,38 +366,38 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
|||||||
[
|
[
|
||||||
%{
|
%{
|
||||||
id: "1",
|
id: "1",
|
||||||
name: "Clothing",
|
name: "Art Prints",
|
||||||
slug: "clothing",
|
slug: "art-prints",
|
||||||
product_count: 3,
|
product_count: 5,
|
||||||
image_url: "https://images.unsplash.com/photo-1489987707025-afc232f7ea0f?w=400&h=300&fit=crop&q=80"
|
image_url: "/mockups/mountain-sunrise-print-2.jpg"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "2",
|
id: "2",
|
||||||
name: "Accessories",
|
name: "Apparel",
|
||||||
slug: "accessories",
|
slug: "apparel",
|
||||||
product_count: 4,
|
product_count: 4,
|
||||||
image_url: "https://images.unsplash.com/photo-1606760227091-3dd870d97f1d?w=400&h=300&fit=crop&q=80"
|
image_url: "/mockups/forest-silhouette-tshirt-1.jpg"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "3",
|
id: "3",
|
||||||
name: "Home",
|
name: "Homewares",
|
||||||
slug: "home",
|
slug: "homewares",
|
||||||
product_count: 3,
|
product_count: 3,
|
||||||
image_url: "https://images.unsplash.com/photo-1616046229478-9901c5536a45?w=400&h=300&fit=crop&q=80"
|
image_url: "/mockups/fern-leaf-mug-1.jpg"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "4",
|
id: "4",
|
||||||
name: "Kitchen",
|
name: "Stationery",
|
||||||
slug: "kitchen",
|
slug: "stationery",
|
||||||
product_count: 1,
|
product_count: 2,
|
||||||
image_url: "https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=400&h=300&fit=crop&q=80"
|
image_url: "/mockups/autumn-leaves-notebook-1.jpg"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
id: "5",
|
id: "5",
|
||||||
name: "Beauty",
|
name: "Accessories",
|
||||||
slug: "beauty",
|
slug: "accessories",
|
||||||
product_count: 1,
|
product_count: 2,
|
||||||
image_url: "https://images.unsplash.com/photo-1596462502278-27bfdc403348?w=400&h=300&fit=crop&q=80"
|
image_url: "/mockups/monstera-leaf-phone-case-1.jpg"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|||||||
@ -17,7 +17,7 @@ defmodule SimpleshopThemeWeb do
|
|||||||
those modules here.
|
those modules here.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def static_paths, do: ~w(assets css fonts images favicon.ico robots.txt demo.html)
|
def static_paths, do: ~w(assets css fonts images mockups favicon.ico robots.txt demo.html)
|
||||||
|
|
||||||
def router do
|
def router do
|
||||||
quote do
|
quote do
|
||||||
|
|||||||
@ -142,10 +142,10 @@ defmodule SimpleshopThemeWeb.ThemeLive.PreviewPages do
|
|||||||
<!-- Newsletter -->
|
<!-- Newsletter -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-xl font-bold mb-2" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);">
|
<h3 class="text-xl font-bold mb-2" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);">
|
||||||
Join the studio
|
Stay in touch
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mb-4 text-sm" style="color: var(--t-text-secondary);">
|
<p class="mb-4 text-sm" style="color: var(--t-text-secondary);">
|
||||||
Get 10% off your first order and be the first to know about new prints.
|
Get 10% off your first order and be the first to know about new designs.
|
||||||
</p>
|
</p>
|
||||||
<form class="flex flex-wrap gap-2">
|
<form class="flex flex-wrap gap-2">
|
||||||
<input
|
<input
|
||||||
@ -364,7 +364,7 @@ defmodule SimpleshopThemeWeb.ThemeLive.PreviewPages do
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6" style="color: var(--t-text-tertiary);">
|
<div class="p-6" style="color: var(--t-text-tertiary);">
|
||||||
<p class="text-sm">Try searching for "fern", "roses", or "botanical"</p>
|
<p class="text-sm">Try searching for "mountain", "forest", or "ocean"</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
About the studio
|
About the studio
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-lg" style="color: var(--t-text-secondary);">
|
<p class="text-lg" style="color: var(--t-text-secondary);">
|
||||||
Nature-inspired art, made with care
|
Nature photography, printed with care
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -24,35 +24,39 @@
|
|||||||
<!-- About Image -->
|
<!-- About Image -->
|
||||||
<div
|
<div
|
||||||
class="content-image about-image"
|
class="content-image about-image"
|
||||||
style="width: 100%; height: 300px; border-radius: var(--t-radius-image); margin-bottom: var(--space-lg); background-size: cover; background-position: center; background-image: url('https://picsum.photos/seed/studio/800/400');"
|
style="width: 100%; height: 300px; border-radius: var(--t-radius-image); margin-bottom: var(--space-lg); background-size: cover; background-position: center; background-image: url('/mockups/night-sky-blanket-3.jpg');"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<!-- Content Text -->
|
<!-- Content Text -->
|
||||||
<div class="content-text" style="line-height: 1.7;">
|
<div class="content-text" style="line-height: 1.7;">
|
||||||
<p class="lead-text text-lg mb-4" style="color: var(--t-text-primary);">
|
<p class="lead-text text-lg mb-4" style="color: var(--t-text-primary);">
|
||||||
Botanical Studio was born from a love of the natural world and a desire to bring its beauty indoors.
|
I'm Emma, a nature photographer based in the UK. What started as weekend walks with my camera has grown into something I never expected – a little shop where I can share my favourite captures with others.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="mb-4" style="color: var(--t-text-secondary);">
|
<p class="mb-4" style="color: var(--t-text-secondary);">
|
||||||
Every illustration starts as a pencil sketch, inspired by the plants and flowers found in British woodlands, meadows, and gardens. These sketches are then refined and printed on museum-quality archival paper using pigment-based inks that will last a lifetime.
|
Every design in this shop comes from my own photography. Whether it's early morning mist over the hills, autumn leaves in the local woods, or the quiet beauty of wildflower meadows, I'm drawn to the peaceful moments that nature offers.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="mb-4" style="color: var(--t-text-secondary);">
|
<p class="mb-4" style="color: var(--t-text-secondary);">
|
||||||
Based in the Yorkshire countryside, I work from a small garden studio surrounded by the very nature that inspires each piece. Every print is checked by hand before being carefully packaged and sent on its way.
|
I work with quality print partners to bring these images to life on products you can actually use and enjoy – from art prints for your walls to mugs for your morning tea.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 class="mt-8 mb-3" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); font-size: var(--t-text-xl); color: var(--t-text-primary);">
|
<h2 class="mt-8 mb-3" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); font-size: var(--t-text-xl); color: var(--t-text-primary);">
|
||||||
Sustainability
|
Quality you can trust
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mb-4" style="color: var(--t-text-secondary);">
|
<p class="mb-4" style="color: var(--t-text-secondary);">
|
||||||
I believe beautiful art shouldn't cost the earth. All prints are produced on FSC-certified paper, shipped in plastic-free packaging, and printed locally to reduce transport emissions.
|
I've carefully chosen print partners who share my commitment to quality. Every product is made to order using premium materials and printing techniques that ensure vibrant colours and lasting quality.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 class="mt-8 mb-3" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); font-size: var(--t-text-xl); color: var(--t-text-primary);">
|
<h2 class="mt-8 mb-3" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); font-size: var(--t-text-xl); color: var(--t-text-primary);">
|
||||||
The process
|
Printed sustainably
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mb-4" style="color: var(--t-text-secondary);">
|
<p class="mb-4" style="color: var(--t-text-secondary);">
|
||||||
Each botanical illustration takes between 20-40 hours to complete, from initial field sketches through to the final digital refinement. I work primarily in graphite and watercolour, which are then scanned and carefully colour-corrected to ensure the prints match the original artwork.
|
Because each item is printed on demand, there's no waste from unsold stock. My print partners use eco-friendly inks where possible, and products are shipped directly to you to minimise unnecessary handling.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="mt-8" style="color: var(--t-text-secondary);">
|
||||||
|
Thank you for visiting. It means a lot that you're here.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,10 +10,10 @@
|
|||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<section class="text-center" style="padding: var(--space-2xl) var(--space-lg); background-color: var(--t-surface-base);">
|
<section class="text-center" style="padding: var(--space-2xl) var(--space-lg); background-color: var(--t-surface-base);">
|
||||||
<h1 class="text-3xl md:text-4xl mb-4" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);">
|
<h1 class="text-3xl md:text-4xl mb-4" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);">
|
||||||
Nature-inspired art prints
|
Original designs, printed on demand
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-lg max-w-lg mx-auto mb-8" style="color: var(--t-text-secondary); line-height: 1.6;">
|
<p class="text-lg max-w-lg mx-auto mb-8" style="color: var(--t-text-secondary); line-height: 1.6;">
|
||||||
Original botanical illustrations, printed on premium archival paper. Each piece brings a little bit of the outside, inside.
|
From art prints to apparel – unique products created by independent artists and delivered straight to your door.
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
@ -45,7 +45,7 @@
|
|||||||
<!-- Featured Products -->
|
<!-- Featured Products -->
|
||||||
<section style="padding: var(--space-xl) var(--space-lg); background-color: var(--t-surface-sunken);">
|
<section style="padding: var(--space-xl) var(--space-lg); background-color: var(--t-surface-sunken);">
|
||||||
<h2 class="text-2xl mb-6" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);">
|
<h2 class="text-2xl mb-6" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);">
|
||||||
Featured prints
|
Featured products
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class={[
|
<div class={[
|
||||||
@ -117,14 +117,14 @@
|
|||||||
<section class="grid grid-cols-1 md:grid-cols-2 gap-12 items-center" style="padding: var(--space-2xl) var(--space-lg); background-color: var(--t-surface-base);">
|
<section class="grid grid-cols-1 md:grid-cols-2 gap-12 items-center" style="padding: var(--space-2xl) var(--space-lg); background-color: var(--t-surface-base);">
|
||||||
<div
|
<div
|
||||||
class="h-72 rounded-lg bg-cover bg-center"
|
class="h-72 rounded-lg bg-cover bg-center"
|
||||||
style="background-image: url('https://picsum.photos/seed/studio/600/400'); border-radius: var(--t-radius-image);"
|
style="background-image: url('/mockups/mountain-sunrise-print-3.jpg'); border-radius: var(--t-radius-image);"
|
||||||
></div>
|
></div>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-2xl mb-4" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);">
|
<h2 class="text-2xl mb-4" style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);">
|
||||||
Made with care in Yorkshire
|
Made with passion, printed with care
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-base mb-4" style="color: var(--t-text-secondary); line-height: 1.7;">
|
<p class="text-base mb-4" style="color: var(--t-text-secondary); line-height: 1.7;">
|
||||||
Every illustration starts as a pencil sketch, inspired by the plants and flowers found in British woodlands, meadows, and gardens. Printed on museum-quality archival paper using pigment-based inks that will last a lifetime.
|
Every design starts with an idea. We work with quality print partners to bring those ideas to life on premium products – from gallery-quality art prints to everyday essentials.
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
|||||||
BIN
priv/static/mockups/autumn-leaves-notebook-1.jpg
Normal file
|
After Width: | Height: | Size: 351 KiB |
BIN
priv/static/mockups/autumn-leaves-notebook-2.jpg
Normal file
|
After Width: | Height: | Size: 313 KiB |
BIN
priv/static/mockups/autumn-leaves-notebook-3.jpg
Normal file
|
After Width: | Height: | Size: 329 KiB |
BIN
priv/static/mockups/autumn-leaves-notebook-4.jpg
Normal file
|
After Width: | Height: | Size: 420 KiB |
BIN
priv/static/mockups/blue-waves-laptop-sleeve-1.jpg
Normal file
|
After Width: | Height: | Size: 258 KiB |
BIN
priv/static/mockups/blue-waves-laptop-sleeve-2.jpg
Normal file
|
After Width: | Height: | Size: 262 KiB |
BIN
priv/static/mockups/blue-waves-laptop-sleeve-3.jpg
Normal file
|
After Width: | Height: | Size: 512 KiB |
BIN
priv/static/mockups/botanical-illustration-print-1.jpg
Normal file
|
After Width: | Height: | Size: 359 KiB |
BIN
priv/static/mockups/botanical-illustration-print-2.jpg
Normal file
|
After Width: | Height: | Size: 507 KiB |
BIN
priv/static/mockups/botanical-illustration-print-3.jpg
Normal file
|
After Width: | Height: | Size: 883 KiB |
BIN
priv/static/mockups/fern-leaf-mug-1.jpg
Normal file
|
After Width: | Height: | Size: 274 KiB |
BIN
priv/static/mockups/fern-leaf-mug-2.jpg
Normal file
|
After Width: | Height: | Size: 268 KiB |
BIN
priv/static/mockups/fern-leaf-mug-3.jpg
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
priv/static/mockups/fern-leaf-mug-4.jpg
Normal file
|
After Width: | Height: | Size: 914 KiB |
BIN
priv/static/mockups/forest-light-hoodie-1.jpg
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
priv/static/mockups/forest-light-hoodie-2.jpg
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
priv/static/mockups/forest-silhouette-tshirt-1.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
priv/static/mockups/forest-silhouette-tshirt-2.jpg
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
priv/static/mockups/forest-silhouette-tshirt-3.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
priv/static/mockups/forest-silhouette-tshirt-4.jpg
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
priv/static/mockups/geometric-abstract-print-1.jpg
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
priv/static/mockups/geometric-abstract-print-2.jpg
Normal file
|
After Width: | Height: | Size: 413 KiB |
BIN
priv/static/mockups/geometric-abstract-print-3.jpg
Normal file
|
After Width: | Height: | Size: 860 KiB |
BIN
priv/static/mockups/monstera-leaf-notebook-1.jpg
Normal file
|
After Width: | Height: | Size: 399 KiB |
BIN
priv/static/mockups/monstera-leaf-notebook-2.jpg
Normal file
|
After Width: | Height: | Size: 386 KiB |
BIN
priv/static/mockups/monstera-leaf-notebook-3.jpg
Normal file
|
After Width: | Height: | Size: 376 KiB |
BIN
priv/static/mockups/monstera-leaf-notebook-4.jpg
Normal file
|
After Width: | Height: | Size: 418 KiB |
BIN
priv/static/mockups/monstera-leaf-phone-case-1.jpg
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
priv/static/mockups/monstera-leaf-phone-case-2.jpg
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
priv/static/mockups/monstera-leaf-phone-case-3.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
priv/static/mockups/monstera-leaf-phone-case-4.jpg
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
priv/static/mockups/mountain-sunrise-print-1.jpg
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
priv/static/mockups/mountain-sunrise-print-2.jpg
Normal file
|
After Width: | Height: | Size: 422 KiB |
BIN
priv/static/mockups/mountain-sunrise-print-3.jpg
Normal file
|
After Width: | Height: | Size: 864 KiB |
BIN
priv/static/mockups/night-sky-blanket-1.jpg
Normal file
|
After Width: | Height: | Size: 520 KiB |
BIN
priv/static/mockups/night-sky-blanket-2.jpg
Normal file
|
After Width: | Height: | Size: 417 KiB |
BIN
priv/static/mockups/night-sky-blanket-3.jpg
Normal file
|
After Width: | Height: | Size: 919 KiB |
BIN
priv/static/mockups/night-sky-blanket-4.jpg
Normal file
|
After Width: | Height: | Size: 654 KiB |
BIN
priv/static/mockups/ocean-waves-cushion-1.jpg
Normal file
|
After Width: | Height: | Size: 165 KiB |
BIN
priv/static/mockups/ocean-waves-cushion-2.jpg
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
priv/static/mockups/ocean-waves-cushion-3.jpg
Normal file
|
After Width: | Height: | Size: 442 KiB |
BIN
priv/static/mockups/ocean-waves-print-1.jpg
Normal file
|
After Width: | Height: | Size: 191 KiB |
BIN
priv/static/mockups/ocean-waves-print-2.jpg
Normal file
|
After Width: | Height: | Size: 436 KiB |
BIN
priv/static/mockups/ocean-waves-print-3.jpg
Normal file
|
After Width: | Height: | Size: 869 KiB |
BIN
priv/static/mockups/sunset-gradient-tote-1.jpg
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
priv/static/mockups/sunset-gradient-tote-2.jpg
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
priv/static/mockups/sunset-gradient-tote-3.jpg
Normal file
|
After Width: | Height: | Size: 428 KiB |
BIN
priv/static/mockups/sunset-gradient-tote-4.jpg
Normal file
|
After Width: | Height: | Size: 407 KiB |
BIN
priv/static/mockups/wildflower-meadow-print-1.jpg
Normal file
|
After Width: | Height: | Size: 316 KiB |
BIN
priv/static/mockups/wildflower-meadow-print-2.jpg
Normal file
|
After Width: | Height: | Size: 500 KiB |
BIN
priv/static/mockups/wildflower-meadow-print-3.jpg
Normal file
|
After Width: | Height: | Size: 884 KiB |
BIN
priv/static/mockups/wildflower-meadow-tote-1.jpg
Normal file
|
After Width: | Height: | Size: 266 KiB |
BIN
priv/static/mockups/wildflower-meadow-tote-2.jpg
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
priv/static/mockups/wildflower-meadow-tote-3.jpg
Normal file
|
After Width: | Height: | Size: 468 KiB |
BIN
priv/static/mockups/wildflower-meadow-tote-4.jpg
Normal file
|
After Width: | Height: | Size: 447 KiB |