Commit Graph

69 Commits

Author SHA1 Message Date
jamey
bb358f890b consolidate image serving and clean up pipeline
Move all image URL logic into ProductImage.url/2 and thumbnail_url/1,
remove dead on-demand generation code from Optimizer, strip controller
routes down to SVG recolor only, fix mockup startup check to verify all
variant formats, and isolate test image cache directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 17:47:41 +00:00
jamey
81e94d0d65 add admin products list and detail pages
Read-mostly admin views for synced products: filterable/sortable list
with inline visibility toggle, and detail page with images grid,
variants table, storefront controls form, and provider edit links.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:48:51 +00:00
jamey
ccc14aa5e1 fix category images, gallery reset, scroll hint and orphan image cleanup
- category_nav pulls first product image per category from DB
- ProductImageScroll JS hook resets to index 0 on updated()
- collection filter bar gets CSS fade gradient scroll hint on mobile
- sync_product_images and delete_product_images now clean up orphaned
  Media.Image records to prevent DB bloat from repeated syncs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:20:55 +00:00
jamey
0fe48baaa8 filter Printify options to enabled variants and order by hero colour
Printify's API returns all blueprint option values (e.g. 21 colours)
even when only a few are enabled as variants. This caused the PDP to
show phantom colour swatches with no images or purchasable variants.

Now filter_options_by_variants strips option values to only those with
enabled variants, ordered by first appearance so the hero/default
colour leads the swatch list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 00:29:20 +00:00
jamey
4e19d4c4a9 fetch catalog color hex codes during Printful sync
Printful sync variants don't include color_code — hex values are only
available from the catalog product endpoint. Fetch catalog colors per
unique product type during sync (cached in process dictionary to avoid
duplicate calls) and store as "colors" array in option values, matching
the Printify format that Product.option_types expects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 00:11:18 +00:00
jamey
daa6d3de71 add per-colour product images and gallery colour filtering
Tag product images with their colour during sync (both Printful and
Printify providers). Printify images are cherry-picked: hero colour
keeps all angles, other colours keep front + back only. Printful
MockupEnricher now generates mockups per colour from the
color_variant_map.

PDP gallery filters by the selected colour, falling back to all
images when the selected colour has none. Fix option name mismatch
(Printify "Colors" vs variant "Color") by singularizing in
Product.option_types.

Generator creates multi-colour apparel products so mock data matches
real sync behaviour.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:21:22 +00:00
jamey
29d8839ac2 put front-view mockup extras first in image gallery
When the enricher adds extra angle images, sort them so front views
come first (position 0) and shift existing images down. This ensures
the product gallery leads with a proper front shot rather than a
handle-side or angled preview.

Also adds Products.update_product_image/2.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 16:58:54 +00:00
jamey
1aceaf9444 add Printful mockup generator and post-sync angle enrichment
New PrintfulGenerator module creates demo products in Printful with
multi-variant support (multiple colours/sizes per product type).
Mix task gets --provider flag to choose between Printify and Printful.

After syncing Printful products, MockupEnricher Oban worker calls the
legacy mockup generator API to produce extra angle images (back, left,
right) and appends them as product images. Jobs are staggered 45s apart
with snooze-based 429 handling. Flat products (canvas, poster) get no
extras — apparel and 3D products get 1-5 extra angles each.

Also fixes:
- cross-provider slug uniqueness (appends -2, -3 suffix)
- category mapping order (Accessories before Canvas Prints)
- image dedup by URL instead of colour (fixes canvas variants)
- artwork URL stored in provider_data for enricher access

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 16:52:53 +00:00
jamey
61cb2b7a87 make admin provider UI support both Printify and Printful
- Provider form accepts ?type= query param (printify/printful)
- Conditional setup instructions per provider (API key steps, login URLs)
- Dynamic labels, titles, and config handling (shop_id vs store_id)
- Provider index shows dropdown with both provider options
- Settings page renamed from @printify to @provider (generic)
- Fix Printful shipping rates: add default state codes for US/CA/AU

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 10:53:15 +00:00
jamey
24d61f7a9e add Printful webhook endpoint with token verification
New POST /webhooks/printful route with VerifyPrintfulWebhook plug
(shared secret token via header or query param). Handles package_shipped,
order_failed, order_canceled, product_updated, product_synced, and
product_deleted events. Webhook registration via Printful v2 API with
token appended to URL. 19 new tests (819 total).

Also marks task #28 as done — Printful sync products already include
preview mockup images handled by the existing ImageDownloadWorker
pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 09:32:14 +00:00
jamey
0cfcb2448e wire Printful shipping rates into cart calculation
Add blueprint_id and print_provider_id to Printful provider_data so the
generic shipping calculator can look up rates. Fix v2 API request format
(order_items key) and response field names. Fetch one representative
variant per product to get accurate per-item rates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 09:15:47 +00:00
jamey
3c788bff78 add Printful provider integration with HTTP client and order routing
Printful HTTP client (v2 + v1 for sync products), Provider behaviour
implementation with all callbacks (test_connection, fetch_products,
submit_order, get_order_status, fetch_shipping_rates), and multi-provider
order routing that looks up the provider connection from the order's
product instead of hardcoding "printify".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 09:01:05 +00:00
jamey
af207d7a35 switch mockup generator to UK print providers and add --replace flag
- Prefer Print Clever (72) for canvas, Monster Digital (29) for
  apparel and mugs where available, fall back to default providers
- Rename Art Print products to Canvas (new Satin Canvas blueprint)
- Add Canvas Prints category in Printify tag extraction
- Add --replace/-r flag to purge existing Printify products before
  generating (with interactive confirmation)
- Add purge_all_products/1 to generator module
- Remove old art print mockups, add new canvas + extra provider mockups

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 13:00:59 +00:00
jamey
5c2f70ce44 add shipping costs with live exchange rates and country detection
Shipping rates fetched from Printify during product sync, converted to
GBP at sync time using frankfurter.app ECB exchange rates with 5%
buffer. Cached in shipping_rates table per blueprint/provider/country.

Cart page shows shipping estimate with country selector (detected from
Accept-Language header, persisted in cookie). Stripe Checkout includes
shipping_options for UK domestic and international delivery. Order
shipping_cost extracted from Stripe on payment.

ScheduledSyncWorker runs every 6 hours via Oban cron to keep rates
and exchange rates fresh. REST_OF_THE_WORLD fallback covers unlisted
countries. 780 tests, 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 10:48:00 +00:00
jamey
edcbc596e3 add LIKE substring fallback to search and update plan statuses
FTS5 prefix matching misses mid-word substrings (e.g. "ebook" in
"notebook"). When FTS5 returns zero results, fall back to LIKE
query on title and category with proper wildcard escaping. 4 new
tests, 757 total.

Also marks completed plan files (search, admin-redesign,
setup-wizard, products-context) with correct status.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 09:09:10 +00:00
jamey
57c3ba0e28 wire shop LiveViews to DB queries and improve search UX
Replace PreviewData indirection in all shop LiveViews with direct
Products context queries. Home, collection, product detail and error
pages now query the database. Categories loaded once in ThemeHook.
Cart hydration no longer falls back to mock data. PreviewData kept
only for the theme editor.

Search modal gains keyboard navigation (arrow keys, Enter, Escape),
Cmd+K/Ctrl+K shortcut, full ARIA combobox pattern, LiveView navigate
links, and 150ms debounce. SearchModal JS hook manages selection
state and highlight. search.ex gets transaction safety on reindex
and a public remove_product/1. 10 new integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 08:27:26 +00:00
jamey
037cd168cd add FTS5 full-text product search
Adds SQLite FTS5 search index with BM25 ranking across product title,
category, variant attributes, and description. Search modal now has
live results with thumbnails, prices, and click-to-navigate. Index
rebuilds automatically after each provider sync.

Also fixes Access syntax on Product/ProductImage structs (Map.get
instead of bracket notation) which was causing crashes when real
products were loaded from the database.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 07:29:19 +00:00
jamey
35e0386abb add denormalized product fields and use Product structs throughout
Adds cheapest_price, compare_at_price, in_stock, on_sale columns to
products table (recomputed from variants after each sync). Shop
components now work with Product structs directly instead of plain
maps from PreviewData. Renames .name to .title, adds Product display
helpers (primary_image, hover_image, option_types) and ProductImage
helpers (display_url, direct_url, source_width). Adds Products context
query functions for storefront use (list_visible_products,
get_visible_product, list_categories with DB-level sort/filter).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 01:26:39 +00:00
jamey
2fb88df853 replace setup checklist with interactive stepper
3-step vertical stepper with inline forms for Printify and Stripe,
real-time sync progress via PubSub, and celebration state on go-live.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 22:55:29 +00:00
jamey
0dac93ec0b add admin dashboard with setup checklist and stats
Dashboard at /admin shows setup progress (when not live), stat cards
(orders, revenue, products), and recent paid orders table. Replaces
the old AdminController redirect. Add Dashboard to sidebar nav as
first item, update admin bar and theme editor links to /admin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 14:17:38 +00:00
jamey
e64bf40a71 add setup foundations: site gate, registration lockdown, coming soon page
- Settings.site_live?/0 and set_site_live/1 for shop visibility control
- Accounts.has_admin?/0 to detect single-tenant admin existence
- Registration lockdown: /users/register redirects when admin exists
- Setup.setup_status/0 aggregates provider, product, and stripe checks
- Coming soon page at /coming-soon with themed styling
- ThemeHook :require_site_live gate on all public shop routes
  - Site live → everyone through
  - Authenticated → admin preview through
  - No admin → fresh install demo through
  - Otherwise → redirect to coming soon
- Go live / take offline toggle on /admin/settings
- 648 tests, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 22:58:58 +00:00
jamey
19b4a5bd59 add variants to all mock products and fix CSSCache race condition
All 16 mock products now have at least one variant so add-to-cart works
in demo mode. CSSCache.invalidate/0 rescues ArgumentError when the ETS
table doesn't exist yet (seed_defaults runs before CSSCache starts).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 18:12:57 +00:00
jamey
90b0242a06 fix cart hydration for demo mode with mock products
Cart.hydrate/1 now falls back to PreviewData mock products when variant
IDs aren't found in the database, so add-to-cart works on fresh deploys
without synced Printify data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 08:20:59 +00:00
jamey
865e3563b6 fix production deployment: CSS, images and theme seeding
- Add @source for shop_components/ directory in app-shop.css (Tailwind
  wasn't scanning sub-modules after the refactor, dropping ~73 utilities)
- Remove overly aggressive .dockerignore rules that excluded mockup
  image variants needed by the responsive_image component
- Seed default theme settings on first boot via Release.seed_defaults/0
  in the supervision tree (seeds.exs doesn't run in releases)
- Fix PDP gallery images for mock data by appending -1200.webp to
  bare mockup base paths
- Update fly.toml format from fly launch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 23:42:56 +00:00
jamey
1ee37c853d add Docker deployment with Alpine image, release config and health check
- Alpine multi-stage Dockerfile (131 MB image)
- Release overlays (bin/server, bin/migrate), env.sh, Release module
- Health check endpoint at GET /health
- Fly.io config with SQLite volume mount
- Fix hardcoded paths in optimizer.ex and variant_cache.ex to use
  Application.app_dir/2 (breaks in releases where Plug.Static serves
  from a different directory than CWD)
- strip_beams: true in release config
- Optimised .dockerignore and .gitignore for mockup variants

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 16:21:05 +00:00
jamey
eaa4bbb3fa add CI pipeline with credo and dialyzer
mix ci alias: compile --warning-as-errors, format --check-formatted,
credo, dialyzer, test. Credo configured with sensible defaults.
Dialyzer ignore file for false positives (Stripe types, Mix tasks,
ExUnit internals). Credo fixes: map_join, filter consolidation,
nesting extraction, cond→if simplification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 15:19:42 +00:00
jamey
3b8d5faf3b refactor: consolidate settings lookups and secrets loading
- Extract fetch_setting/1 in Settings (4 callsites → 1 repo lookup)
- Replace hardcoded load_stripe_config with registry-driven load_all
- Adding new secrets is now a one-line @secret_registry entry
- Mark DRY refactor plan as complete (all 8 items done)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 14:42:13 +00:00
jamey
cb4698bec8 refactor: extract Cart.build_state/1 as single source of truth for cart state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 12:24:39 +00:00
jamey
5a43cfc761 feat: add default content pages for delivery, privacy and terms
Replace one-off ShopLive.About with generic ShopLive.Content that
handles all static content pages via live_action. Add delivery &
returns, privacy policy, and terms of service pages with sample
content. Update footer help links and theme editor preview.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 10:47:54 +00:00
jamey
0af8997623 feat: add transactional emails for order confirmation and shipping
Plain text emails via Swoosh OrderNotifier module. Order confirmation
triggered from Stripe webhook after payment, shipping notification
from Printify shipment webhook with polling fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 10:17:19 +00:00
jamey
3e19887499 feat: add Printify order submission and fulfilment tracking
Submit paid orders to Printify via provider API with idempotent
guards, Stripe address mapping, and error handling. Track fulfilment
status through submitted → processing → shipped → delivered via
webhook-driven updates (primary) and Oban Cron polling fallback.

- 9 fulfilment fields on orders (status, provider IDs, tracking, timestamps)
- OrderSubmissionWorker with retry logic, auto-enqueued after Stripe payment
- FulfilmentStatusWorker polls every 30 mins for missed webhook events
- Printify order webhook handlers (sent-to-production, shipment, delivered)
- Admin UI: fulfilment column in table, fulfilment card with tracking info,
  submit/retry and refresh buttons on order detail
- Mox provider mocking for test isolation (Provider.for_type configurable)
- 33 new tests (555 total), verified against real Printify API

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 09:51:51 +00:00
jamey
02cdc810f2 feat: add order management admin with list and detail views
Admin UI at /admin/orders to view, filter, and inspect orders.
Adds list_orders/1 and count_orders_by_status/0 to the Orders
context, status filter tabs, clickable order table with streams,
and a detail page showing items, totals, and shipping address.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 21:59:14 +00:00
jamey
eede9bb517 feat: add encrypted settings, guided Stripe setup, and admin credentials page
Store API keys and secrets encrypted in the SQLite database via the
existing Vault module (AES-256-GCM). The only external dependency is
SECRET_KEY_BASE — everything else lives in the portable DB file.

- Add encrypted_value column to settings table with new "encrypted" type
- Add put_secret/get_secret/delete_setting/secret_hint to Settings context
- Add Secrets module to load encrypted config into Application env at startup
- Add Stripe.Setup module with connect/disconnect/verify_api_key flow
  - Auto-creates webhook endpoints via Stripe API in production
  - Detects localhost and shows Stripe CLI instructions for dev
- Add admin credentials page at /admin/settings with guided setup:
  - Not configured: single Secret key input with dashboard link
  - Connected (production): status display, webhook info, disconnect
  - Connected (dev): Stripe CLI instructions, manual signing secret input
- Remove Stripe env vars from dev.exs and runtime.exs
- Fix CSSCache test startup crash (handle_continue instead of init)
- Add nav link for Credentials page

507 tests, 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 17:12:53 +00:00
jamey
ff1bc483b9 feat: add Stripe checkout, order persistence, and webhook handling
Stripe-hosted Checkout integration with full order lifecycle:
- stripity_stripe ~> 3.2 with sandbox/prod config via env vars
- Order and OrderItem schemas with price snapshots at purchase time
- CheckoutController creates pending order then redirects to Stripe
- StripeWebhookController verifies signatures and confirms payment
- Success page with real-time PubSub updates from webhook
- Shop flash messages for checkout error feedback
- Cart cleared after successful payment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 08:30:17 +00:00
jamey
cff21703f1 fix: update demo content, fix broken links, and add cart item product links
- Replace all placeholder text with demo-aware copy that signals "replace me"
- Update USPs for POD accuracy (made to order, quality materials)
- Fix broken footer links (/delivery, /returns → /contact)
- Add real platform URLs to social icons with target="_blank"
- Make cart item images and names link to product pages
- Switch about page image to responsive_image component
- Add missing cart_status to collection page cart drawer
- Unify search hint text across all page templates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 23:33:22 +00:00
jamey
1bc08bfb23 feat: add cart page, cart drawer, and shared cart infrastructure
- Cart context with pure functions for add/remove/update/hydrate
- Price formatting via ex_money (replaces all float division)
- CartHook on_mount with attach_hook for shared event handlers
  (open/close drawer, remove item, PubSub sync)
- Accessible cart drawer with focus trap, scroll lock, aria-live
- Cart page with increment/decrement quantity controls
- Preview mode cart drawer support in theme editor
- Cart persistence to session via JS hook + API endpoint
- 19 tests covering all Cart pure functions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 19:39:37 +00:00
jamey
880e7a2888 feat: add dynamic variant selector with color swatches
- Fix Printify options parsing (Color/Size were swapped)
- Add extract_option_types/1 for frontend display with hex colors
- Filter option types to only published variants (not full catalog)
- Track selected variant in LiveView with price updates
- Color swatches for color-type options, text buttons for size
- Disable unavailable combinations
- Add startup recovery for stale sync status

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 22:17:48 +00:00
jamey
1b49b470f2 feat: add product image download pipeline for PageSpeed 100%
Downloads Printify CDN images via ImageDownloadWorker, processes
through Media pipeline (WebP conversion, AVIF/WebP variant generation),
and links to ProductImage via new image_id FK.

- Add image_id to product_images table
- ImageDownloadWorker downloads and processes external images
- sync_product_images preserves image_id when URL unchanged
- PreviewData uses local images for responsive <picture> elements
- VariantCache enqueues pending downloads on startup
- mix simpleshop.download_images backfill task

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 00:26:19 +00:00
jamey
c818d0399c feat: wire shop LiveViews to real product data
PreviewData now queries the Products context when real products exist,
falling back to mock data otherwise. Shop pages automatically display
synced Printify products.

Fixes:
- Printify image position was string ("front"), now uses index
- Category extraction improved to match more Printify tags
- ProductShow finds products by slug for real data

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 23:07:37 +00:00
jamey
a9c15ea6ae feat: add Printify webhook endpoint for real-time product updates
- Add /webhooks/printify endpoint with HMAC-SHA256 signature verification
- Add Webhooks context to handle product:updated, product:deleted events
- Add ProductDeleteWorker for async product deletion
- Add webhook API methods to Printify client (create, list, delete)
- Add register_webhooks/2 to Printify provider
- Add mix register_webhooks task for one-time webhook registration
- Cache raw request body in endpoint for signature verification

Usage:
1. Generate webhook secret: openssl rand -hex 20
2. Add to provider connection config as "webhook_secret"
3. Register with Printify: mix register_webhooks https://yourshop.com/webhooks/printify

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 22:41:15 +00:00
jamey
5b736b99fd feat: add admin provider setup UI with improved product sync
- Add /admin/providers LiveView for connecting and managing POD providers
- Implement pagination for Printify API (handles all products, not just first page)
- Add parallel processing (5 concurrent) for faster product sync
- Add slug-based fallback matching when provider_product_id changes
- Add error recovery with try/rescue to prevent stuck sync status
- Add checksum-based change detection to skip unchanged products
- Add upsert tests covering race conditions and slug matching
- Add Printify provider tests
- Document Printify integration research (product identity, order risks,
  open source vs managed hosting implications)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 22:08:34 +00:00
jamey
336b2bb81d chore: apply mix format to codebase
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 14:24:58 +00:00
c5c06d9979 feat: add Products context with provider integration (Phase 1)
Implement the schema foundation for syncing products from POD providers
like Printify. This includes encrypted credential storage, product/variant
schemas, and an Oban worker for background sync.

New modules:
- Vault: AES-256-GCM encryption for API keys
- Products context: CRUD and sync operations for products
- Provider behaviour: abstraction for POD provider implementations
- ProductSyncWorker: Oban job for async product sync

Schemas: ProviderConnection, Product, ProductImage, ProductVariant

Also reorganizes Printify client to lib/simpleshop_theme/clients/ and
mockup generator to lib/simpleshop_theme/mockups/ for better structure.

134 tests added covering all new functionality.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 20:32:20 +00:00
9783199691 perf: use digested font paths in CSS and preloads
Add path_resolver parameter to font generation functions so both
font preloads and CSS @font-face declarations use the same digested
paths in production. This prevents duplicate font downloads when
preloads were using digested paths but CSS used non-digested paths.

- Add path_resolver parameter to Fonts.generate_font_faces/2,
  Fonts.preload_links/2, and Fonts.generate_all_font_faces/1
- Update CSSGenerator.generate/2 to accept path_resolver
- Update CSSCache.warm/0 to use Endpoint.static_path/1
- Move CSSCache to start after Endpoint in supervision tree
- Add 1-year cache headers for static assets

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 09:32:06 +00:00
364ac8fa0e fix: improve accent color contrast for WCAG AA compliance
Add WCAG AA compliant accent color variants and update default accent
to meet 4.5:1 contrast ratio requirements.

- Add --t-accent-text (darker for text on light backgrounds)
- Add --t-accent-button (darker for button backgrounds with white text)
- Change default accent from #3b82f6 to #2563eb (better contrast)
- Update presets and tests for new default

These changes ensure accent colors meet accessibility standards while
maintaining visual consistency with the brand palette.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 00:33:52 +00:00
2c3d8f5647 perf: use responsive images for theme preview mockups
Update theme preview to use optimized responsive images with modern
format support (AVIF/WebP with JPEG fallback).

- Change mockup URLs from .jpg to base paths for srcset generation
- Add source_width to preview products for proper variant selection
- Add responsive_image component with <picture> element
- Update image_text_section to use optimized 800px WebP variant

This ensures the theme preview loads optimal image formats and sizes,
matching the production responsive image behavior.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 00:33:38 +00:00
0ade34d994 feat: add centralized fonts module for dynamic font loading
Create a DRY Fonts module that centralizes all font definitions and
generates @font-face declarations dynamically based on typography preset.

- Add Fonts module with typography font mappings
- Generate @font-face CSS in CSSGenerator based on active preset
- Add font preload links in shop_root layout for performance
- Remove hardcoded font-face declarations from CSS

This improves font loading performance by only loading fonts for the
active typography preset and preloading critical fonts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 00:33:24 +00:00
2bc05097b9 feat: enhance image optimization with on-demand JPEG fallbacks
Improve the image optimization pipeline with better compression and
smarter variant generation:

- Change to_lossless_webp → to_optimized_webp (lossy, quality 90)
- Auto-resize uploads larger than 2000px to save storage
- Skip pre-generating JPEG variants (~50% disk savings)
- Add on-demand JPEG generation for legacy browsers (<5% of users)
- Add /images/:id/variant/:width route for dynamic serving
- Add VariantCache to supervision tree for startup validation
- Add image_cache to static paths for disk-based serving

The pipeline now stores smaller WebP sources and generates AVIF/WebP
variants upfront, with JPEG generated only when legacy browsers request it.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 00:33:09 +00:00
252ca2268a feat: optimize mockup images with WebP and auto-regeneration
Convert mockup source images from JPG to WebP format for 76% size
reduction (20MB → 4.7MB). Variants are now auto-generated on startup
via Oban, keeping the same DRY approach as database images.

Changes:
- Add OptimizeWorker.enqueue_mockup/1 for filesystem images
- Extend VariantCache to check mockup sources on startup
- Update MockupGenerator to save source as optimized WebP
- Update .gitignore to ignore generated variants
- Convert 55 source mockups from JPG to WebP

The mockup pipeline now uses the same code paths as database images:
- Optimizer.to_optimized_webp/1 for source conversion
- Optimizer.process_file/3 for variant generation
- OptimizeWorker for Oban background processing
- VariantCache for startup cache validation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 00:30:42 +00:00
2b5b749a69 feat: add image optimizer module
- Add Optimizer module with lossless WebP conversion
- Generate responsive variants at [400, 800, 1200] widths
- Only create sizes <= source dimensions (no upscaling)
- Support AVIF, WebP, and JPEG output formats
- Add disk cache at priv/static/image_cache/
- Add comprehensive test suite (12 tests)
- Add image fixtures helper for testing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 22:16:21 +00:00