berrypod/lib/simpleshop_theme/theme/presets.ex
Jamey Greenwood 974d91ce33 feat: implement professional typography system with curated font pairings
Typography Presets (research-backed pairings):
- clean: Manrope + Inter (minimal, modern)
- editorial: Playfair Display + Raleway (fashion, lifestyle)
- modern: Space Grotesk + Inter (tech, futuristic)
- classic: Cormorant Garamond + Source Serif 4 (luxury, elegant)
- friendly: Fraunces + Work Sans (playful, quirky)
- minimal: DM Sans + Source Serif 4 (design-forward)
- impulse: Raleway + Inter (wellness, beauty)

Type Scale & Line Heights:
- Major Third (1.25) ratio for mathematical harmony
- H1: line-height 1.1, letter-spacing -0.025em
- H2: line-height 1.15, letter-spacing -0.02em
- H3: line-height 1.2, letter-spacing -0.015em
- Body: line-height 1.5 (WCAG compliant)
- Small text: letter-spacing +0.01em for readability

Fluid Typography:
- Headings use clamp() for smooth mobile→desktop scaling
- Display: 36px→48px, XL: 30px→40px, LG: 24px→32px

Performance:
- Variable font loading where available (Inter, Manrope, etc.)
- Removed unused fonts (Libre Baskerville, Nunito, Source Sans)

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

227 lines
5.0 KiB
Elixir

defmodule SimpleshopTheme.Theme.Presets do
@moduledoc """
Defines the 8 curated theme presets for SimpleShop.
"""
@presets %{
gallery: %{
mood: "warm",
typography: "editorial",
shape: "soft",
density: "spacious",
grid_columns: "3",
header_layout: "centered",
accent_color: "#e85d04",
layout_width: "wide",
card_shadow: "sm",
announcement_bar: true
},
studio: %{
mood: "neutral",
typography: "clean",
shape: "soft",
density: "balanced",
grid_columns: "4",
header_layout: "standard",
accent_color: "#3b82f6",
layout_width: "wide",
card_shadow: "sm",
announcement_bar: true
},
boutique: %{
mood: "warm",
typography: "classic",
shape: "soft",
density: "balanced",
grid_columns: "3",
header_layout: "left",
accent_color: "#b45309",
layout_width: "contained",
card_shadow: "md",
announcement_bar: true
},
bold: %{
mood: "neutral",
typography: "modern",
shape: "sharp",
density: "compact",
grid_columns: "4",
header_layout: "standard",
accent_color: "#dc2626",
layout_width: "full",
card_shadow: "none",
announcement_bar: true
},
playful: %{
mood: "neutral",
typography: "friendly",
shape: "pill",
density: "balanced",
grid_columns: "4",
header_layout: "standard",
accent_color: "#8b5cf6",
layout_width: "wide",
card_shadow: "md",
announcement_bar: true
},
minimal: %{
mood: "neutral",
typography: "impulse",
shape: "sharp",
density: "spacious",
grid_columns: "2",
header_layout: "standard",
accent_color: "#171717",
layout_width: "full",
card_shadow: "none",
announcement_bar: false
},
night: %{
mood: "dark",
typography: "modern",
shape: "soft",
density: "balanced",
grid_columns: "4",
header_layout: "standard",
accent_color: "#f97316",
layout_width: "wide",
card_shadow: "lg",
announcement_bar: true
},
classic: %{
mood: "warm",
typography: "classic",
shape: "soft",
density: "spacious",
grid_columns: "3",
header_layout: "standard",
accent_color: "#166534",
layout_width: "contained",
card_shadow: "sm",
announcement_bar: true
}
}
@descriptions %{
gallery: "Editorial serif headlines",
studio: "Clean modern sans-serif",
boutique: "Elegant classic serif",
bold: "Tech-forward geometric",
playful: "Quirky variable font",
minimal: "Light refined pairing",
night: "Dark tech aesthetic",
classic: "Traditional luxury serif"
}
# Core keys used to match presets (excludes branding-specific settings)
@core_keys ~w(mood typography shape density grid_columns header_layout accent_color layout_width card_shadow announcement_bar)a
@doc """
Returns all available presets.
## Examples
iex> all()
%{gallery: %{...}, studio: %{...}, ...}
"""
def all, do: @presets
@doc """
Gets a preset by name.
## Examples
iex> get(:gallery)
%{mood: "warm", typography: "editorial", ...}
iex> get(:nonexistent)
nil
"""
def get(preset_name) when is_atom(preset_name) do
Map.get(@presets, preset_name)
end
@doc """
Lists all preset names.
## Examples
iex> list_names()
[:gallery, :studio, :boutique, :bold, :playful, :minimal, :night, :classic]
"""
def list_names do
Map.keys(@presets)
end
@doc """
Gets the description for a preset.
## Examples
iex> get_description(:gallery)
"Elegant & editorial"
"""
def get_description(preset_name) when is_atom(preset_name) do
Map.get(@descriptions, preset_name, "")
end
@doc """
Returns all presets with their descriptions.
## Examples
iex> all_with_descriptions()
[{:bold, "High contrast, strong"}, ...]
"""
def all_with_descriptions do
@presets
|> Map.keys()
|> Enum.sort()
|> Enum.map(fn name -> {name, Map.get(@descriptions, name, "")} end)
end
@doc """
Detects which preset matches the current theme settings, if any.
Only compares core theme keys, ignoring branding-specific settings.
## Examples
iex> detect_preset(%ThemeSettings{mood: "warm", typography: "editorial", ...})
:gallery
iex> detect_preset(%ThemeSettings{...customized...})
nil
"""
def detect_preset(theme_settings) do
current_core = extract_core_values(theme_settings)
Enum.find_value(@presets, fn {name, preset} ->
preset_core = Map.take(preset, @core_keys)
if maps_match?(current_core, preset_core) do
name
else
nil
end
end)
end
defp extract_core_values(theme_settings) do
theme_settings
|> Map.from_struct()
|> Map.take(@core_keys)
end
defp maps_match?(map1, map2) do
Enum.all?(@core_keys, fn key ->
Map.get(map1, key) == Map.get(map2, key)
end)
end
end