diff --git a/.env b/.env new file mode 100644 index 0000000..3dd433a --- /dev/null +++ b/.env @@ -0,0 +1 @@ +set PRINTIFY_API_TOKEN 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIzN2Q0YmQzMDM1ZmUxMWU5YTgwM2FiN2VlYjNjY2M5NyIsImp0aSI6Ijk3ZjY0YzEwZjIyNGRjOGZkYmM1NDgwN2VhNzI2YzY3N2NmYjYzYTZkNmZmMmYzNjExMmVkMzBmYTM1YjdmYTVmZGZiOWRlMDMzMTZiM2I0IiwiaWF0IjoxNzY4NDI4MTk2LjExNjM4OSwibmJmIjoxNzY4NDI4MTk2LjExNjM5MSwiZXhwIjoxNzk5OTY0MTk2LjEwOTY2Mywic3ViIjoiMjU4ODY0NDQiLCJzY29wZXMiOlsic2hvcHMubWFuYWdlIiwic2hvcHMucmVhZCIsImNhdGFsb2cucmVhZCIsIm9yZGVycy5yZWFkIiwib3JkZXJzLndyaXRlIiwicHJvZHVjdHMucmVhZCIsInByb2R1Y3RzLndyaXRlIiwid2ViaG9va3MucmVhZCIsIndlYmhvb2tzLndyaXRlIiwidXBsb2Fkcy5yZWFkIiwidXBsb2Fkcy53cml0ZSIsInByaW50X3Byb3ZpZGVycy5yZWFkIiwidXNlci5pbmZvIl19.CcBAOWMK2xGiTVPaq0B5JP_YBCJQMG9UiwY5hCfQRKBnc33CEyXm8eGJjt5ugy3ENH-0QCiFBtETvZ6f-hc2atVIsAwRPJ2-ey76wTZ1MlBM1LWTMj33jd2aFy3WguI0OMsJil2cEUBCmU47Hhv2Wgsf1Njp6vJEGAuwwfxVk6TArqMad0yHu6ZH4xyKD3F98MDcAk-lroDp8VmkBfGgYJZri7pHKcFZ7BvOGUZUnkO5PiFKZJyFTjIqYXwuCmbzDR1BSnz5LkSBdEe43tYPl_9-gleSWaADOnjQGJYJT0yZRk5oC9N_IgPjZakZcH_XVhvZ1yjRfWMkvmZQArtHrz7FPn1FwM7yDW82x3vHGmZ56l-AvMf85OUWh1pq_J5yvN7aOVkeNi1aDsWZTWkH6xqtWU4vhVjhE_nr5EoqmN5tIj4dy-v6XJ4XpDzOxxmZF01ty57KcmIY0W7uSd2CZGPGjnicWKKOBR7gKChRCBkSawLEEcDrxe5_RJQw9jHxid7Mfwp5MCApmbd_vfQd5NtdpAC5OA_sGAU_tfA9j4qV0PpFW_733lN--CjWjD0lVnGRnU-p2WH-t0qZpjd6UnvJT_C4p2q9vrpd8x29N7UJYfYCwTBLsrYouVu6OILYGV3uMs9oa4U7gay-EM1qBUww8mc_4xhQT3EvtanQrZY' diff --git a/lib/simpleshop_theme/printify/client.ex b/lib/simpleshop_theme/printify/client.ex index 6285a68..8a52459 100644 --- a/lib/simpleshop_theme/printify/client.ex +++ b/lib/simpleshop_theme/printify/client.ex @@ -19,7 +19,7 @@ defmodule SimpleshopTheme.Printify.Client do @doc """ Make a GET request to the Printify API. """ - def get(path, opts \\ []) do + def get(path, _opts \\ []) do url = @base_url <> path case Req.get(url, headers: auth_headers(), receive_timeout: 30_000) do @@ -37,7 +37,7 @@ defmodule SimpleshopTheme.Printify.Client do @doc """ Make a POST request to the Printify API. """ - def post(path, body, opts \\ []) do + def post(path, body, _opts \\ []) do url = @base_url <> path case Req.post(url, json: body, headers: auth_headers(), receive_timeout: 60_000) do @@ -55,7 +55,7 @@ defmodule SimpleshopTheme.Printify.Client do @doc """ Make a DELETE request to the Printify API. """ - def delete(path, opts \\ []) do + def delete(path, _opts \\ []) do url = @base_url <> path case Req.delete(url, headers: auth_headers(), receive_timeout: 30_000) do diff --git a/lib/simpleshop_theme/printify/mockup_generator.ex b/lib/simpleshop_theme/printify/mockup_generator.ex index 5ab5cc6..8f1a794 100644 --- a/lib/simpleshop_theme/printify/mockup_generator.ex +++ b/lib/simpleshop_theme/printify/mockup_generator.ex @@ -361,9 +361,6 @@ defmodule SimpleshopTheme.Printify.MockupGenerator do IO.puts("Using shop ID: #{shop_id}") IO.puts("") - # Track created products for cleanup - created_products = [] - results = product_definitions() |> Enum.map(fn product_def -> diff --git a/lib/simpleshop_theme/theme/css_generator.ex b/lib/simpleshop_theme/theme/css_generator.ex index c5a4540..ce419c9 100644 --- a/lib/simpleshop_theme/theme/css_generator.ex +++ b/lib/simpleshop_theme/theme/css_generator.ex @@ -49,188 +49,6 @@ defmodule SimpleshopTheme.Theme.CSSGenerator do """ end - # Mood variations (color schemes) - defp generate_mood("neutral") do - """ - --t-surface-base: #ffffff; - --t-surface-raised: #ffffff; - --t-surface-sunken: #f5f5f5; - --t-surface-overlay: rgba(255, 255, 255, 0.95); - --t-text-primary: #171717; - --t-text-secondary: #525252; - --t-text-tertiary: #a3a3a3; - --t-text-inverse: #ffffff; - --t-border-default: #e5e5e5; - --t-border-subtle: #f0f0f0; - """ - end - - defp generate_mood("warm") do - """ - --t-surface-base: #fdf8f3; - --t-surface-raised: #fffcf8; - --t-surface-sunken: #f5ebe0; - --t-surface-overlay: rgba(253, 248, 243, 0.95); - --t-text-primary: #1c1917; - --t-text-secondary: #57534e; - --t-text-tertiary: #a8a29e; - --t-text-inverse: #ffffff; - --t-border-default: #e7e0d8; - --t-border-subtle: #f0ebe4; - """ - end - - defp generate_mood("cool") do - """ - --t-surface-base: #f4f7fb; - --t-surface-raised: #f8fafc; - --t-surface-sunken: #e8eff7; - --t-surface-overlay: rgba(244, 247, 251, 0.95); - --t-text-primary: #0f172a; - --t-text-secondary: #475569; - --t-text-tertiary: #94a3b8; - --t-text-inverse: #ffffff; - --t-border-default: #d4dce8; - --t-border-subtle: #e8eff5; - """ - end - - defp generate_mood("dark") do - """ - --t-surface-base: #0a0a0a; - --t-surface-raised: #171717; - --t-surface-sunken: #000000; - --t-surface-overlay: rgba(23, 23, 23, 0.95); - --t-text-primary: #fafafa; - --t-text-secondary: #a3a3a3; - --t-text-tertiary: #737373; - --t-text-inverse: #171717; - --t-border-default: #262626; - --t-border-subtle: #1c1c1c; - --p-shadow-strength: 0.25; - """ - end - - # Typography styles - defp generate_typography("clean") do - """ - --t-font-heading: var(--p-font-inter); - --t-font-body: var(--p-font-inter); - --t-heading-weight: 600; - --t-heading-tracking: -0.025em; - """ - end - - defp generate_typography("editorial") do - """ - --t-font-heading: var(--p-font-fraunces); - --t-font-body: var(--p-font-source); - --t-heading-weight: 600; - --t-heading-tracking: -0.02em; - """ - end - - defp generate_typography("modern") do - """ - --t-font-heading: var(--p-font-space); - --t-font-body: var(--p-font-space); - --t-heading-weight: 500; - --t-heading-tracking: -0.03em; - """ - end - - defp generate_typography("classic") do - """ - --t-font-heading: var(--p-font-baskerville); - --t-font-body: var(--p-font-source); - --t-heading-weight: 400; - --t-heading-tracking: 0; - """ - end - - defp generate_typography("friendly") do - """ - --t-font-heading: var(--p-font-nunito); - --t-font-body: var(--p-font-nunito); - --t-heading-weight: 700; - --t-heading-tracking: -0.01em; - """ - end - - defp generate_typography("minimal") do - """ - --t-font-heading: var(--p-font-outfit); - --t-font-body: var(--p-font-outfit); - --t-heading-weight: 300; - --t-heading-tracking: 0; - """ - end - - defp generate_typography("impulse") do - """ - --t-font-heading: var(--p-font-avenir); - --t-font-body: var(--p-font-avenir); - --t-heading-weight: 300; - --t-heading-tracking: 0.02em; - """ - end - - # Shape variations (border radius) - defp generate_shape("sharp") do - """ - --t-radius-button: 0; - --t-radius-card: 0; - --t-radius-input: 0; - --t-radius-image: 0; - """ - end - - defp generate_shape("soft") do - """ - --t-radius-button: var(--p-radius-md); - --t-radius-card: var(--p-radius-lg); - --t-radius-input: var(--p-radius-md); - --t-radius-image: var(--p-radius-md); - """ - end - - defp generate_shape("round") do - """ - --t-radius-button: var(--p-radius-lg); - --t-radius-card: var(--p-radius-xl); - --t-radius-input: var(--p-radius-lg); - --t-radius-image: var(--p-radius-lg); - """ - end - - defp generate_shape("pill") do - """ - --t-radius-button: var(--p-radius-full); - --t-radius-card: var(--p-radius-xl); - --t-radius-input: var(--p-radius-full); - --t-radius-image: var(--p-radius-lg); - """ - end - - # Density variations (spacing multiplier) - defp generate_density("spacious") do - """ - --t-density: 1.25; - """ - end - - defp generate_density("balanced") do - """ - --t-density: 1; - """ - end - - defp generate_density("compact") do - """ - --t-density: 0.85; - """ - end - # Font size variations # Using 18px as base for better accessibility (WCAG recommends 18px+) # Small: 18px, Medium: 19px, Large: 20px diff --git a/simpleshop-typography.md b/simpleshop-typography.md new file mode 100644 index 0000000..ecb1625 --- /dev/null +++ b/simpleshop-typography.md @@ -0,0 +1,375 @@ +# Professional typography systems for Tailwind e-commerce themes + +A well-designed typography system using modular scales, vertical rhythm, and constrained customization can transform an e-commerce shop from generic to polished. The key insight: **typography accounts for 95% of web design**, yet most theme builders expose the wrong controls—offering granular font size pickers when users actually need curated presets with automatic scaling. The most successful approach combines a **Major Third (1.25) type scale**, **8px baseline grid**, and **2-3 carefully paired Google Fonts** with preset-based theme customization that prevents design chaos while enabling brand expression. + +This report covers the complete technical foundation: mathematical type scales with CSS implementations, curated Google Font pairings across seven style categories, production-ready Tailwind configurations, DaisyUI integration patterns, and research-backed guidelines for building theme editors that balance freedom with quality outcomes. + +--- + +## Type scales create mathematical harmony across all text sizes + +Modular type scales use consistent mathematical ratios to generate font sizes, ensuring visual harmony without arbitrary decisions. Starting with a **16px base** (browser default), each size step multiplies by the chosen ratio. + +| Ratio | Name | Best use case | +|-------|------|--------------| +| 1.125 | Major Second | Dense UIs, dashboards, documentation | +| **1.25** | **Major Third** | **Recommended default for e-commerce** | +| 1.333 | Perfect Fourth | Editorial, content-heavy sites | +| 1.414 | Augmented Fourth | Landing pages, marketing | +| 1.5 | Perfect Fifth | High-contrast headlines | +| 1.618 | Golden Ratio | Luxury brands, creative projects | + +The **Major Third (1.25)** strikes the ideal balance for e-commerce: sufficient hierarchy without oversized mobile headings. A full scale at this ratio: 12.8px → 16px → 20px → 25px → 31.25px → 39px → 48.8px. + +**Implementation principle**: Use larger ratios (1.333–1.5) on desktop and smaller ratios (1.125–1.25) on mobile. Fluid typography with `clamp()` handles this automatically: + +```css +--fs-heading: clamp(1.5rem, 1rem + 2vw, 2.5rem); +``` + +--- + +## Vertical rhythm and baseline grids enforce consistent spacing + +The **8-point grid system** (used by Material Design, iOS, and most design systems) creates visual consistency by constraining all spacing to multiples of 8px. Typography integrates with this grid through line-height alignment. + +**Why 8px works**: Most screen sizes divide evenly by 8, scaling cleanly to @1x, @2x, and @3x resolutions. It reduces decision-making to a finite set of options: 8, 16, 24, 32, 40, 48px. + +**Typography integration**: Base font of 16px with line-height 1.5 yields **24px line-height** (3 × 8), keeping text on the baseline grid. All margins and padding use 8px increments. + +```css +:root { + --rhythm: 0.5rem; /* 8px */ + --line-height-body: 1.5rem; /* 24px = 3 × rhythm */ +} + +body { font-size: 1rem; line-height: 1.5; } +p { margin-bottom: 1rem; } /* 16px = 2 × rhythm */ +h2 { margin-bottom: 1.5rem; } /* 24px = 3 × rhythm */ +``` + +**Practical approach**: Accept line-height-based rhythm rather than true baseline alignment—CSS centers text vertically within line-height, making pixel-perfect baseline grids impractical without complex padding offsets. + +--- + +## Line-height, measure, and letter-spacing have precise optimal values + +These three parameters have research-backed optimal ranges that should rarely need customization in a theme editor. + +**Line-height follows an inverse relationship with font size**—larger text needs tighter leading: + +| Context | Line-height | Example at 16px base | +|---------|-------------|---------------------| +| Large headings (32px+) | 1.1–1.2 | 35–38px | +| Subheadings (20–28px) | 1.2–1.3 | 24–36px | +| **Body text** | **1.5–1.6** | **24–26px** | +| Small text (<14px) | 1.4–1.5 | 20–21px | + +WCAG accessibility requires **minimum 1.5 line-height** for body text. Always use unitless values (`line-height: 1.5`) so they scale proportionally. + +**Measure (line length)** should stay within **45–75 characters** for optimal readability, with 66 being ideal. Implementation: + +```css +.prose { max-width: 65ch; } +/* Or responsive: */ +.content { width: clamp(45ch, 90%, 75ch); } +``` + +**Letter-spacing guidelines**: +- **Body text**: Leave default (font is optimized) +- **ALL CAPS**: Add 5–12% spacing (`letter-spacing: 0.08em`) +- **Large headings (32px+)**: Tighten slightly (`letter-spacing: -0.02em`) +- **Small text (<14px)**: Widen slightly (`letter-spacing: 0.02em`) + +--- + +## Curated Google Font pairings for seven e-commerce styles + +Each pairing includes heading and body fonts with specific weights to load. These combinations feel distinctive while maintaining professional readability. + +### Minimal and modern (tech, sustainable goods, contemporary homeware) + +**Manrope (500, 600) + Inter (400, 500)** — Geometric precision meets exceptional screen readability. Both feature clean lines without quirks. + +**Plus Jakarta Sans (500, 600) + Literata (400, 400i)** — Newer, distinctive sans-serif with variable font support paired with a screen-optimized serif. Fresh but professional. + +**DM Sans (500, 700) + Source Serif Pro (400, 400i)** — Low-contrast geometric sans contrasts elegantly with refined serif. Ideal for design tools, premium stationery. + +### Warm and artisan (handmade goods, craft products, specialty food) + +**Cormorant Garamond (500, 600) + Proza Libre (400, 400i)** — Lavish Garamond revival with strong calligraphic feel pairs with humanist sans optimized for screens. Perfect for artisan ceramics, specialty coffee. + +**Calistoga (400) + IBM Plex Sans (400, 500)** — Rounded serifs with friendly sophistication. Excellent for bakeries, organic goods, candle shops. + +**Josefin Sans Light (300, 400) + Crimson Text (400, 400i, 600)** — Thin letterforms create an airy feel; classic serif adds professional warmth. Suits makeup, jewelry, wellness. + +### Bold and editorial (fashion, lifestyle brands) + +**Playfair Display (500, 700) + Raleway Light (300, 400)** — High-contrast delicate hairlines of Playfair with thin Raleway strokes. Classic fashion magazine aesthetic. + +**Instrument Serif + Instrument Sans (400, 500)** — Same-designer family creates cohesive editorial look with perfect built-in contrast. Art-forward brands, creative agencies. + +**DM Serif Display (400) + Poppins (300, 400)** — Beautiful calligraphic strokes with thin modern geometric sans. Fashion accessories, beauty products. + +### Playful and quirky (creative products, novelty gifts) + +**Fraunces (variable, 400–900) + Work Sans (400, 500)** — Four axes including "wonk" create vintage-inspired whimsy while maintaining professionalism. Variable font with softness, weight, and optical size controls. + +**Syne Bold (700) + Syne Regular (400)** — Footprint expands as weight increases for quirky but readable stand-out headlines. Music merchandise, creative agencies. + +**Dynapuff (variable) + Nunito (400, 600)** — Bold, playful, round font with soft rounded sans. Children's products, candy shops. + +### Luxury and elegant (high-end goods, premium products) + +**Cinzel (400, 500) + Fauna One (400)** — High-contrast Roman inscription inspiration conveys timeless authority. Fine jewelry, luxury watches. + +**Cormorant Garamond (400, 500) + Montserrat Light (300, 400)** — Elegant Garamond with clean urban sans creates sophisticated contrast. High-end cosmetics, luxury home decor. + +**Prata (400) + Manrope Light (300, 400)** — Contrasting thick/thin strokes convey elegance; light Manrope maintains delicacy. Premium skincare, designer accessories. + +### Vintage and retro (heritage brands, nostalgic products) + +**Libre Franklin (500, 600) + Libre Baskerville (400, 400i)** — American Gothic + Transitional pairing used for 100+ years. Evokes established, traditional authenticity. + +**Oswald (500, 600) + EB Garamond (400, 400i)** — Bold condensed sans (Franklin Gothic–inspired) with elegant Garamond. Vintage clothing, retro posters. + +**Rokkitt (400, 700) + Raleway (400, 500)** — Geometric slab serif (Egyptian style) with elegant sans. Heritage stationery, letterpress products. + +### Tech and futuristic (sci-fi prints, gadget accessories) + +**Space Grotesk (500, 700) + IBM Plex Mono (400, 500)** — Distinctive geometric sans with monospace body creates coding/tech aesthetic. + +--- + +## Variable fonts offer performance and flexibility advantages + +Variable fonts contain multiple styles in a single file, reducing HTTP requests and enabling precise weight control (e.g., font-weight: 450). + +| Font | Weight range | Other axes | Best for | +|------|-------------|-----------|----------| +| **Inter** | 100–900 | Optical size 14–32 | All-purpose workhorse | +| **Roboto Flex** | 100–1000 | Width, grade, optical size | Maximum flexibility | +| **Fraunces** | 100–900 | Soft, wonk, optical size | Playful brands | +| **Outfit** | 100–900 | — | Modern headlines | +| **DM Sans** | 100–1000 | Optical size 9–40 | Clean interfaces | + +**Request syntax** for Google Fonts variable fonts: +```html + +``` + +--- + +## Font loading strategies that protect performance + +**Recommended approach**: Use `font-display: swap` for headings (fastest text visibility) and `font-display: optional` for body text (prevents layout shifts if font fails to load). + +**Self-hosting now outperforms Google Fonts CDN** due to Chrome and Safari's cache partitioning (since 2020)—cross-site caching no longer works. Self-hosting reported PageSpeed improvements of 66 → 94 on mobile scores. + +**Performance checklist**: +- Load only weights actually used (e.g., 400, 600 instead of full range) +- Preconnect to font origins: `` +- Use WOFF2 format only (30% smaller than WOFF) +- Limit to 2–3 font families maximum +- Preload critical above-fold fonts: +```html + +``` + +--- + +## Tailwind configuration for production typography systems + +Tailwind's fontSize config supports a tuple syntax for complete typographic definitions: + +```javascript +// tailwind.config.js +module.exports = { + theme: { + fontSize: { + 'xs': ['0.75rem', { lineHeight: '1rem', letterSpacing: '0.025em' }], + 'sm': ['0.875rem', { lineHeight: '1.25rem' }], + 'base': ['1rem', { lineHeight: '1.5rem' }], + 'lg': ['1.125rem', { lineHeight: '1.75rem' }], + 'xl': ['1.25rem', { lineHeight: '1.75rem' }], + '2xl': ['1.5rem', { lineHeight: '2rem', letterSpacing: '-0.01em' }], + '3xl': ['1.875rem', { lineHeight: '2.25rem', letterSpacing: '-0.02em' }], + '4xl': ['2.25rem', { lineHeight: '2.5rem', letterSpacing: '-0.02em' }], + }, + spacing: { + // 8px baseline grid + '1': '0.5rem', // 8px + '2': '1rem', // 16px + '3': '1.5rem', // 24px + '4': '2rem', // 32px + '6': '3rem', // 48px + }, + fontFamily: { + sans: ['Inter', 'ui-sans-serif', 'system-ui'], + display: ['Playfair Display', 'ui-serif', 'Georgia'], + }, + }, +} +``` + +**Fluid typography in Tailwind** uses arbitrary values or custom config: + +```html +

Fluid headline

+``` + +Or extend the config: +```javascript +fontSize: { + 'fluid-xl': 'clamp(1.5rem, 1rem + 2vw, 2.5rem)', + 'fluid-display': 'clamp(2.5rem, 1.5rem + 4vw, 5rem)', +} +``` + +**@tailwindcss/typography plugin** handles prose content with sensible defaults: + +```html +
+ {{ markdown }} +
+``` + +Override defaults in config: +```javascript +typography: { + DEFAULT: { + css: { + maxWidth: '65ch', + 'h1, h2, h3': { fontFamily: 'var(--font-display)' }, + a: { color: '#3182ce', '&:hover': { color: '#2c5282' } }, + }, + }, +}, +``` + +--- + +## DaisyUI themes control colors while typography stays at Tailwind level + +**Critical distinction**: DaisyUI themes only control colors, border-radius, and effects—not typography. Font families, sizes, and spacing are configured in Tailwind, applying consistently across all DaisyUI themes. + +```css +/* DaisyUI plugin configuration */ +@import "tailwindcss"; +@plugin "daisyui" { + themes: light --default, dark --prefersdark; +} + +/* Typography at Tailwind level (applies to all themes) */ +@theme { + --font-display: "Playfair Display", serif; + --font-body: "Inter", sans-serif; +} +``` + +**Semantic color usage for text**: DaisyUI provides `base-content` for default text, `primary-content` for text on primary backgrounds, etc. Components auto-apply correct text colors (`btn-primary` uses `primary-content`). + +**Overriding component typography**: +```css +@layer components { + .btn { + text-transform: none; /* Remove default uppercase */ + font-weight: 500; + letter-spacing: 0.01em; + } +} +``` + +--- + +## Theme editors should offer presets, not granular controls + +Research across Shopify, Squarespace, and Webflow reveals a clear pattern: the most successful theme systems use **constrained customization** that prevents design chaos. + +### Parameters to make user-configurable + +| Setting | Implementation | Why expose | +|---------|---------------|-----------| +| **Font pairing preset** | 5–10 curated combinations | Enables brand expression without typography expertise | +| **Heading font family** | Dropdown of paired fonts | Primary brand identity signal | +| **Body font family** | Dropdown of paired fonts | Readability preference | +| **Font size scale** | Slider 90%–120% | Accessibility accommodation | +| **Accent colors** | Color picker with presets | Brand customization | + +### Parameters to lock down (prevent user adjustment) + +| Setting | Locked value | Why restrict | +|---------|-------------|-------------| +| **Line-height ratios** | 1.5 body, 1.2 headings | Users break readability | +| **Letter-spacing** | Preset per size | Technical, easy to ruin | +| **Measure/line length** | 45–75ch | Readability science | +| **Type scale ratio** | 1.25 | Mathematical harmony | +| **Mobile scaling** | Automatic | Complex responsive logic | + +### The Shopify model (maximum constraints) + +Shopify exposes only **two font categories** (headings and body), a **single size slider** (100–150%), and no line-height or letter-spacing controls. Users select from a curated library of ~1,000 fonts. Result: hard to break, accessible to non-designers. + +### The Squarespace model (balanced approach) + +Squarespace offers **60+ Font Packs**—professionally curated heading/body pairs that guarantee aesthetic coherence. Users can also access individual controls (family, weight, size, line-height, letter-spacing) but the preset system handles most needs. + +### Recommended implementation for e-commerce themes + +1. **Offer 5–10 curated font pairing presets** covering different brand aesthetics +2. **Allow 2 font category overrides** (heading + body) from a filtered quality list +3. **Use range sliders with sensible limits** (size 90–120%, never expose line-height) +4. **Provide real-time preview** before saving changes +5. **Include accessibility validation** (contrast ratio warnings) +6. **Group elements semantically** (all headings share one setting) + +--- + +## E-commerce typography patterns that increase conversions + +Research from Baymard Institute (200,000+ hours of UX testing) reveals specific typography patterns that improve shopping experiences. + +### Product page hierarchy + +- **Product title**: Largest text (20–28px), semi-bold to bold (600–700), first in visual hierarchy +- **Price**: High prominence near Add to Cart, **4.5:1 minimum contrast**, consider showing price-per-unit (**74% of sites miss this**) +- **CTA buttons**: Clean sans-serif, 14–16px, action verbs ("Add to Cart" outperforms "Submit") +- **Body descriptions**: 16–18px, 1.5 line-height, structured with subheadings (**78% of sites don't use highlights format**) + +### Trust signals through typography + +Monotype/Neurons research found serif fonts increase **perceived quality by 13%** and **reliability by 9%**. For premium positioning: +- Use serif fonts for headlines (Playfair Display, Cormorant Garamond) +- Generous white space around text elements +- Restrained design—avoid decorative excess +- Limit to 2–3 fonts maximum (**4+ creates "cheap" perception**) + +### Mobile e-commerce requirements + +- **Touch targets**: 44×44px minimum for text links (WCAG AAA), 8px minimum spacing between targets +- **Body text**: 16px minimum (prevents iOS input zoom) +- **Avoid horizontal tabs on mobile**—27% of users overlook them entirely +- Position price and CTA in natural thumb zone (bottom 2/3 of screen) + +### Scanning patterns inform layout + +**F-pattern** (product pages with descriptions): Users scan horizontally across top, then shorter horizontal line below, then vertically down left side. Place critical information (product name, price, CTAs) along these paths. + +**Z-pattern** (landing pages, minimal text): Top left → top right → diagonal to bottom left → bottom right. Position logo top-left, navigation top-right, primary CTA bottom-right. + +--- + +## Conclusion: A complete typography system for Tailwind e-commerce themes + +The optimal system combines: + +1. **Major Third (1.25) type scale** as the default, with automatic mobile scaling using `clamp()` +2. **8px baseline grid** for all spacing, with line-heights aligned to 24px (1.5rem) for body text +3. **2–3 Google Fonts** loaded as variable fonts with `font-display: swap`, self-hosted for performance +4. **Tailwind configuration** with fontSize tuples including line-height and letter-spacing +5. **DaisyUI for color theming** while typography remains at Tailwind level +6. **Preset-based theme customization** offering curated font pairings rather than granular controls + +The research consensus is clear: typography parameters like line-height (1.5 body, 1.2 headings), measure (65ch), and letter-spacing should be **locked to optimal values**. User customization should focus on font pairing presets and size scaling within safe ranges—enabling brand expression without the risk of breaking readability or professional appearance. + +For print-on-demand shops specifically, the **Minimal/Modern** (Manrope + Inter) and **Playful/Quirky** (Fraunces + Work Sans) pairings offer the most versatility across product categories, while **Warm/Artisan** (Cormorant Garamond + Proza Libre) suits handmade goods and **Bold/Editorial** (Playfair Display + Raleway) elevates fashion-forward brands. \ No newline at end of file diff --git a/simpleshop-ux-component-patterns.md b/simpleshop-ux-component-patterns.md new file mode 100644 index 0000000..0866aae --- /dev/null +++ b/simpleshop-ux-component-patterns.md @@ -0,0 +1,276 @@ +# SimpleShop UX-Optimised Component Patterns + +A research-backed guide to component design that avoids common anti-patterns and creates professional, ethical, high-converting e-commerce experiences. + +--- + +## Spacing & Layout System + +Building on the 8px grid from the typography guide: + +**Spacing Tokens**: +```css +--space-1: 0.25rem; /* 4px - tight */ +--space-2: 0.5rem; /* 8px - compact */ +--space-3: 0.75rem; /* 12px */ +--space-4: 1rem; /* 16px - default */ +--space-5: 1.5rem; /* 24px */ +--space-6: 2rem; /* 32px */ +--space-8: 3rem; /* 48px - section gaps */ +--space-10: 4rem; /* 64px */ +--space-12: 6rem; /* 96px - major sections */ +--space-16: 8rem; /* 128px - hero spacing */ +``` + +**Tailwind Config**: +```javascript +module.exports = { + theme: { + spacing: { + '0': '0', + '1': '0.25rem', + '2': '0.5rem', + '3': '0.75rem', + '4': '1rem', + '5': '1.5rem', + '6': '2rem', + '8': '3rem', + '10': '4rem', + '12': '6rem', + '16': '8rem', + } + } +} +``` + +**Section Spacing**: +```html + +
+
+ +
+
+``` + +**Container Widths**: +```css +--container-prose: 65ch; /* Text content */ +--container-content: 80rem; /* 1280px - main content */ +--container-wide: 90rem; /* 1440px - full layouts */ +``` + +--- + +## Component States + +Every interactive component needs these states defined: + +**Button States**: +``` +Default → Base styling +Hover → Subtle lift/colour shift (200ms ease) +Active → Pressed state +Focus → Visible ring (accessibility) +Disabled → Greyed, no pointer +Loading → Spinner, disabled +``` + +**Form Input States**: +``` +Default → Border, background +Focus → Ring, border colour change +Error → Red border, error message below +Success → Green check (optional, for validation) +Disabled → Greyed background +``` + +**Card States**: +``` +Default → Base styling +Hover → Subtle lift (box-shadow increase) +Focus → Visible ring (if interactive) +``` + +--- + +## Accessibility Baseline + +Every SimpleShop theme must meet WCAG 2.1 AA: + +**Colour Contrast**: +- Normal text: 4.5:1 minimum +- Large text (18px+ or 14px bold): 3:1 minimum +- UI components and graphics: 3:1 minimum + +**Focus Indicators**: +- Visible focus ring on all interactive elements +- Never remove outline without replacement +- Ring should have 3:1 contrast with adjacent colours + +**Touch Targets**: +- Minimum 44×44px for touch targets +- 8px minimum spacing between targets + +**Screen Reader**: +- Semantic HTML (button for buttons, not div) +- Alt text on all images +- ARIA labels where needed +- Skip links for keyboard navigation +- Logical heading hierarchy (h1 → h2 → h3) + +--- + +## Performance Patterns + +**Image Loading**: +```html + +Product + + +Hero +``` + +**Font Loading**: +```html + + +``` + +**Critical CSS**: +- Inline above-fold styles +- Defer non-critical CSS +- Remove unused styles + +--- + +## Theme Presets + +Combining typography pairings with component styling: + +### Minimal Modern +- **Fonts**: Manrope + Inter +- **Cards**: No shadows, subtle borders +- **Buttons**: Solid fills, sharp corners (border-radius: 0 or 2px) +- **Spacing**: Generous, airy +- **Colours**: Monochrome with single accent + +### Warm Artisan +- **Fonts**: Cormorant Garamond + Proza Libre +- **Cards**: Subtle shadows, rounded corners +- **Buttons**: Rounded, warm fills +- **Spacing**: Comfortable, not sparse +- **Colours**: Warm neutrals, earthy accents + +### Bold Editorial +- **Fonts**: Playfair Display + Raleway +- **Cards**: Strong contrast, dramatic shadows +- **Buttons**: High contrast, possibly outlined +- **Spacing**: Dramatic, asymmetric +- **Colours**: Black/white with bold accent + +### Playful Quirky +- **Fonts**: Fraunces + Work Sans +- **Cards**: Rounded, possibly with borders +- **Buttons**: Rounded/pill, playful hover states +- **Spacing**: Varied, dynamic +- **Colours**: Bright, fun palette + +### Luxury Elegant +- **Fonts**: Cinzel + Fauna One +- **Cards**: Minimal, refined +- **Buttons**: Thin borders, subtle +- **Spacing**: Generous, refined +- **Colours**: Muted, sophisticated + +--- + +## Implementation Checklist + +Before shipping any SimpleShop theme, verify: + +**Layout**: +- [ ] Mobile-first responsive design +- [ ] No horizontal scroll on any viewport +- [ ] Content readable at 320px minimum +- [ ] Touch targets 44×44px minimum + +**Navigation**: +- [ ] Logo links to homepage +- [ ] Cart always accessible +- [ ] Current page indicated +- [ ] Mobile menu works smoothly + +**Product Cards**: +- [ ] Consistent aspect ratios +- [ ] Price clearly visible +- [ ] Hover state provides value +- [ ] Links are obvious + +**Product Page**: +- [ ] Accordion sections, not horizontal tabs +- [ ] Images zoomable +- [ ] Price prominent +- [ ] Add to Cart sticky on mobile +- [ ] Trust signals visible + +**Checkout**: +- [ ] Guest checkout prominent +- [ ] Progress indicator clear +- [ ] All costs shown upfront +- [ ] Form fields properly labeled +- [ ] Error messages inline +- [ ] Trust signals present + +**Ethical**: +- [ ] No fake urgency/scarcity +- [ ] No confirmshaming +- [ ] No hidden costs +- [ ] No exit-intent popups +- [ ] Clear, neutral options +- [ ] Honest product information + +**Accessibility**: +- [ ] Colour contrast passes WCAG AA +- [ ] Focus rings visible +- [ ] Semantic HTML +- [ ] Alt text on images +- [ ] Keyboard navigable + +**Performance**: +- [ ] Images lazy loaded (except hero) +- [ ] Fonts preloaded +- [ ] LCP under 2.5s +- [ ] No layout shift (CLS < 0.1) + +--- + +## Summary + +SimpleShop's component system prioritises: + +1. **Honesty over manipulation** — No dark patterns, ever +2. **Clarity over cleverness** — Obvious UI beats novel UI +3. **Research over assumptions** — Patterns backed by testing +4. **Consistency over creativity** — Systems over one-offs +5. **Accessibility over aesthetics** — Everyone can use it + +These patterns, combined with the typography and spacing systems, create a foundation for themes that are not just beautiful but genuinely effective—converting browsers into buyers through trust and clarity rather than tricks and pressure. + +The result: shops that sellers are proud of and customers enjoy using. diff --git a/test/simpleshop_theme/theme/css_generator_test.exs b/test/simpleshop_theme/theme/css_generator_test.exs index 7ab4cb6..ca6d33b 100644 --- a/test/simpleshop_theme/theme/css_generator_test.exs +++ b/test/simpleshop_theme/theme/css_generator_test.exs @@ -16,118 +16,6 @@ defmodule SimpleshopTheme.Theme.CSSGeneratorTest do assert css =~ "--t-accent-l:" end - test "generates correct mood variables for neutral" do - settings = %ThemeSettings{mood: "neutral"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-surface-base: #ffffff" - assert css =~ "--t-text-primary: #171717" - assert css =~ "--t-border-default: #e5e5e5" - end - - test "generates correct mood variables for dark" do - settings = %ThemeSettings{mood: "dark"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-surface-base: #0a0a0a" - assert css =~ "--t-text-primary: #fafafa" - assert css =~ "--t-border-default: #262626" - assert css =~ "--p-shadow-strength: 0.25" - end - - test "generates correct mood variables for warm" do - settings = %ThemeSettings{mood: "warm"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-surface-base: #fdf8f3" - assert css =~ "--t-text-primary: #1c1917" - end - - test "generates correct mood variables for cool" do - settings = %ThemeSettings{mood: "cool"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-surface-base: #f4f7fb" - assert css =~ "--t-text-primary: #0f172a" - end - - test "generates correct typography for clean" do - settings = %ThemeSettings{typography: "clean"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-font-heading: var(--p-font-inter)" - assert css =~ "--t-font-body: var(--p-font-inter)" - assert css =~ "--t-heading-weight: 600" - end - - test "generates correct typography for editorial" do - settings = %ThemeSettings{typography: "editorial"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-font-heading: var(--p-font-fraunces)" - assert css =~ "--t-font-body: var(--p-font-source)" - end - - test "generates correct typography for modern" do - settings = %ThemeSettings{typography: "modern"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-font-heading: var(--p-font-space)" - assert css =~ "--t-heading-weight: 500" - end - - test "generates correct shape for sharp" do - settings = %ThemeSettings{shape: "sharp"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-radius-button: 0" - assert css =~ "--t-radius-card: 0" - end - - test "generates correct shape for soft" do - settings = %ThemeSettings{shape: "soft"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-radius-button: var(--p-radius-md)" - assert css =~ "--t-radius-card: var(--p-radius-lg)" - end - - test "generates correct shape for round" do - settings = %ThemeSettings{shape: "round"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-radius-button: var(--p-radius-lg)" - assert css =~ "--t-radius-card: var(--p-radius-xl)" - end - - test "generates correct shape for pill" do - settings = %ThemeSettings{shape: "pill"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-radius-button: var(--p-radius-full)" - end - - test "generates correct density for spacious" do - settings = %ThemeSettings{density: "spacious"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-density: 1.25" - end - - test "generates correct density for balanced" do - settings = %ThemeSettings{density: "balanced"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-density: 1" - end - - test "generates correct density for compact" do - settings = %ThemeSettings{density: "compact"} - css = CSSGenerator.generate(settings) - - assert css =~ "--t-density: 0.85" - end - test "converts hex colors to HSL" do settings = %ThemeSettings{accent_color: "#ff0000"} css = CSSGenerator.generate(settings) @@ -138,6 +26,26 @@ defmodule SimpleshopTheme.Theme.CSSGeneratorTest do assert css =~ "--t-accent-l: 50%" end + test "handles blue accent color" do + settings = %ThemeSettings{accent_color: "#0000ff"} + css = CSSGenerator.generate(settings) + + # Blue should be H=240, S=100%, L=50% + assert css =~ "--t-accent-h: 240" + assert css =~ "--t-accent-s: 100%" + assert css =~ "--t-accent-l: 50%" + end + + test "handles green accent color" do + settings = %ThemeSettings{accent_color: "#00ff00"} + css = CSSGenerator.generate(settings) + + # Green should be H=120, S=100%, L=50% + assert css =~ "--t-accent-h: 120" + assert css =~ "--t-accent-s: 100%" + assert css =~ "--t-accent-l: 50%" + end + test "includes secondary colors" do settings = %ThemeSettings{ secondary_accent_color: "#ea580c", @@ -149,5 +57,103 @@ defmodule SimpleshopTheme.Theme.CSSGeneratorTest do assert css =~ "--t-secondary-accent: #ea580c" assert css =~ "--t-sale-color: #dc2626" end + + test "generates font size scale for small" do + settings = %ThemeSettings{font_size: "small"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-font-size-scale: 1.125" + end + + test "generates font size scale for medium" do + settings = %ThemeSettings{font_size: "medium"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-font-size-scale: 1.1875" + end + + test "generates font size scale for large" do + settings = %ThemeSettings{font_size: "large"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-font-size-scale: 1.25" + end + + test "generates heading weight override for regular" do + settings = %ThemeSettings{heading_weight: "regular"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-heading-weight-override: 400" + end + + test "generates heading weight override for bold" do + settings = %ThemeSettings{heading_weight: "bold"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-heading-weight-override: 700" + end + + test "generates layout width for contained" do + settings = %ThemeSettings{layout_width: "contained"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-layout-max-width: 1100px" + end + + test "generates layout width for wide" do + settings = %ThemeSettings{layout_width: "wide"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-layout-max-width: 1400px" + end + + test "generates layout width for full" do + settings = %ThemeSettings{layout_width: "full"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-layout-max-width: 100%" + end + + test "generates button style for filled" do + settings = %ThemeSettings{button_style: "filled"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-button-style: filled" + end + + test "generates button style for outline" do + settings = %ThemeSettings{button_style: "outline"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-button-style: outline" + end + + test "generates product text alignment" do + settings = %ThemeSettings{product_text_align: "center"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-product-text-align: center" + end + + test "generates image aspect ratio for square" do + settings = %ThemeSettings{image_aspect_ratio: "square"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-image-aspect-ratio: 1 / 1" + end + + test "generates image aspect ratio for portrait" do + settings = %ThemeSettings{image_aspect_ratio: "portrait"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-image-aspect-ratio: 3 / 4" + end + + test "generates image aspect ratio for landscape" do + settings = %ThemeSettings{image_aspect_ratio: "landscape"} + css = CSSGenerator.generate(settings) + + assert css =~ "--t-image-aspect-ratio: 4 / 3" + end end end diff --git a/test/simpleshop_theme/theme/presets_test.exs b/test/simpleshop_theme/theme/presets_test.exs index a3cb723..369d19c 100644 --- a/test/simpleshop_theme/theme/presets_test.exs +++ b/test/simpleshop_theme/theme/presets_test.exs @@ -4,11 +4,11 @@ defmodule SimpleshopTheme.Theme.PresetsTest do alias SimpleshopTheme.Theme.Presets describe "all/0" do - test "returns all 9 presets" do + test "returns all 8 presets" do presets = Presets.all() assert is_map(presets) - assert map_size(presets) == 9 + assert map_size(presets) == 8 assert Map.has_key?(presets, :gallery) assert Map.has_key?(presets, :studio) assert Map.has_key?(presets, :boutique) @@ -17,7 +17,6 @@ defmodule SimpleshopTheme.Theme.PresetsTest do assert Map.has_key?(presets, :minimal) assert Map.has_key?(presets, :night) assert Map.has_key?(presets, :classic) - assert Map.has_key?(presets, :impulse) end end @@ -74,8 +73,8 @@ defmodule SimpleshopTheme.Theme.PresetsTest do test "returns minimal preset" do preset = Presets.get(:minimal) - assert preset.mood == "cool" - assert preset.typography == "minimal" + assert preset.mood == "neutral" + assert preset.typography == "impulse" assert preset.shape == "sharp" assert preset.accent_color == "#171717" end @@ -96,18 +95,6 @@ defmodule SimpleshopTheme.Theme.PresetsTest do assert preset.accent_color == "#166534" end - test "returns impulse preset with extended settings" do - preset = Presets.get(:impulse) - - assert preset.mood == "neutral" - assert preset.typography == "impulse" - assert preset.shape == "sharp" - assert preset.accent_color == "#000000" - assert preset.font_size == "medium" - assert preset.heading_weight == "regular" - assert preset.product_text_align == "center" - end - test "returns nil for nonexistent preset" do assert Presets.get(:nonexistent) == nil end @@ -118,7 +105,7 @@ defmodule SimpleshopTheme.Theme.PresetsTest do names = Presets.list_names() assert is_list(names) - assert length(names) == 9 + assert length(names) == 8 assert :gallery in names assert :studio in names assert :boutique in names @@ -127,7 +114,6 @@ defmodule SimpleshopTheme.Theme.PresetsTest do assert :minimal in names assert :night in names assert :classic in names - assert :impulse in names end end end diff --git a/test/simpleshop_theme/theme/preview_data_test.exs b/test/simpleshop_theme/theme/preview_data_test.exs index ec4036f..6bc8a5d 100644 --- a/test/simpleshop_theme/theme/preview_data_test.exs +++ b/test/simpleshop_theme/theme/preview_data_test.exs @@ -45,11 +45,14 @@ defmodule SimpleshopTheme.Theme.PreviewDataTest do for product <- products do assert is_binary(product.image_url) - assert String.starts_with?(product.image_url, "http") + # Images can be either local paths (starting with /) or full URLs + assert String.starts_with?(product.image_url, "/") or + String.starts_with?(product.image_url, "http") if product.hover_image_url do assert is_binary(product.hover_image_url) - assert String.starts_with?(product.hover_image_url, "http") + assert String.starts_with?(product.hover_image_url, "/") or + String.starts_with?(product.hover_image_url, "http") end end end diff --git a/test/simpleshop_theme_web/live/theme_live_test.exs b/test/simpleshop_theme_web/live/theme_live_test.exs index 0f88c13..1d8ec1b 100644 --- a/test/simpleshop_theme_web/live/theme_live_test.exs +++ b/test/simpleshop_theme_web/live/theme_live_test.exs @@ -29,12 +29,11 @@ defmodule SimpleshopThemeWeb.ThemeLiveTest do test "renders theme editor page", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/admin/theme") - assert html =~ "Theme Editor" - assert html =~ "Save Theme" - assert html =~ "Presets" + assert html =~ "Theme Studio" + assert html =~ "preset" end - test "displays all 9 presets", %{conn: conn} do + test "displays all 8 presets", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/admin/theme") assert html =~ "gallery" @@ -45,7 +44,6 @@ defmodule SimpleshopThemeWeb.ThemeLiveTest do assert html =~ "minimal" assert html =~ "night" assert html =~ "classic" - assert html =~ "impulse" end test "displays current theme settings", %{conn: conn} do @@ -61,10 +59,9 @@ defmodule SimpleshopThemeWeb.ThemeLiveTest do test "displays generated CSS in preview", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/admin/theme") + # CSS generator outputs accent colors and layout variables assert html =~ ".preview-frame, .shop-root" assert html =~ "--t-accent-h:" - assert html =~ "--t-surface-base:" - assert html =~ "--t-font-heading:" end test "applies preset and updates preview", %{conn: conn} do @@ -93,14 +90,18 @@ defmodule SimpleshopThemeWeb.ThemeLiveTest do assert html =~ "All Products" end - test "save theme button works", %{conn: conn} do + test "theme settings are saved when applying a preset", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/theme") + # Apply a preset view - |> element("button", "Save Theme") + |> element("button", "gallery") |> render_click() - assert view |> element("button", "Save Theme") |> has_element?() + # Verify settings were persisted + theme_settings = Settings.get_theme_settings() + assert theme_settings.mood == "warm" + assert theme_settings.typography == "editorial" end test "all preview page buttons are present", %{conn: conn} do @@ -121,7 +122,7 @@ defmodule SimpleshopThemeWeb.ThemeLiveTest do # Click the "dark" mood button html = view - |> element("button", "dark") + |> element("button[phx-value-setting_value='dark']") |> render_click() # Verify the setting was updated @@ -135,7 +136,7 @@ defmodule SimpleshopThemeWeb.ThemeLiveTest do # Click the "round" shape button view - |> element("button", "round") + |> element("button[phx-value-setting_value='round']") |> render_click() # Verify the setting was updated @@ -148,7 +149,7 @@ defmodule SimpleshopThemeWeb.ThemeLiveTest do # Click the "compact" density button view - |> element("button", "compact") + |> element("button[phx-value-setting_value='compact']") |> render_click() # Verify the setting was updated @@ -159,9 +160,9 @@ defmodule SimpleshopThemeWeb.ThemeLiveTest do test "grid columns customization buttons work", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/theme") - # Click the "2" grid columns button + # Click the "2 columns" grid columns button view - |> element("button", "2") + |> element("button", "2 columns") |> render_click() # Verify the setting was updated @@ -169,13 +170,13 @@ defmodule SimpleshopThemeWeb.ThemeLiveTest do assert theme_settings.grid_columns == "2" end - test "typography customization select works", %{conn: conn} do + test "typography customization buttons work", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/theme") - # Change typography via the form + # Click the "modern" typography button view - |> element("form[phx-value-field='typography']") - |> render_change(%{"typography" => "modern"}) + |> element("button[phx-value-setting_value='modern']") + |> render_click() # Verify the setting was updated theme_settings = Settings.get_theme_settings() @@ -187,7 +188,7 @@ defmodule SimpleshopThemeWeb.ThemeLiveTest do # Click the "centered" header layout button view - |> element("button", "centered") + |> element("button[phx-value-setting_value='centered']") |> render_click() # Verify the setting was updated @@ -204,12 +205,12 @@ defmodule SimpleshopThemeWeb.ThemeLiveTest do # Change a setting new_html = view - |> element("button", "dark") + |> element("button[phx-value-setting_value='dark']") |> render_click() - # Verify CSS has changed (dark mode should have different surface colors) + # Verify CSS has changed refute initial_css == new_html - assert new_html =~ "--t-surface-base:" + assert new_html =~ "--t-accent-h:" end end end