berrypod/lib/simpleshop_theme/theme/fonts.ex

215 lines
5.9 KiB
Elixir
Raw Normal View History

defmodule SimpleshopTheme.Theme.Fonts do
@moduledoc """
Centralized font configuration for the theme system.
This module defines all available fonts and their variants, maps typography
presets to font pairs, and generates CSS @font-face declarations.
Font files are expected to be in /priv/static/fonts/ and served at /fonts/.
"""
# Font definitions: family name, file prefix, and available weights
@fonts %{
inter: %{
family: "Inter",
file_prefix: "inter-v20-latin",
weights: [300, 400, 500, 600, 700],
fallback: "system-ui, sans-serif"
},
manrope: %{
family: "Manrope",
file_prefix: "manrope-v20-latin",
weights: [400, 500, 600, 700],
fallback: "system-ui, sans-serif"
},
raleway: %{
family: "Raleway",
file_prefix: "raleway-v37-latin",
weights: [300, 400, 500],
fallback: "system-ui, sans-serif"
},
playfair: %{
family: "Playfair Display",
file_prefix: "playfair-display-v40-latin",
weights: [400, 500, 700],
fallback: "Georgia, serif"
},
space_grotesk: %{
family: "Space Grotesk",
file_prefix: "space-grotesk-v22-latin",
weights: [400, 500, 600],
fallback: "system-ui, sans-serif"
},
cormorant: %{
family: "Cormorant Garamond",
file_prefix: "cormorant-garamond-v21-latin",
weights: [400, 500, 600],
fallback: "Georgia, serif"
},
source_serif: %{
family: "Source Serif 4",
file_prefix: "source-serif-4-v14-latin",
weights: [400, 600],
fallback: "Georgia, serif"
},
fraunces: %{
family: "Fraunces",
file_prefix: "fraunces-v38-latin",
weights: [400, 500, 600, 700],
fallback: "Georgia, serif"
},
work_sans: %{
family: "Work Sans",
file_prefix: "work-sans-v24-latin",
weights: [300, 400, 500, 600],
fallback: "system-ui, sans-serif"
},
dm_sans: %{
family: "DM Sans",
file_prefix: "dm-sans-v17-latin",
weights: [400, 500, 600, 700],
fallback: "system-ui, sans-serif"
}
}
# Typography presets map to heading and body font keys
@typography_fonts %{
"clean" => %{heading: :manrope, body: :inter},
"editorial" => %{heading: :playfair, body: :raleway},
"modern" => %{heading: :space_grotesk, body: :inter},
"classic" => %{heading: :cormorant, body: :source_serif},
"friendly" => %{heading: :fraunces, body: :work_sans},
"minimal" => %{heading: :dm_sans, body: :source_serif},
"impulse" => %{heading: :raleway, body: :inter}
}
@doc """
Returns the font configuration for a given font key.
"""
def get_font(key), do: Map.get(@fonts, key)
@doc """
Returns all font configurations.
"""
def all_fonts, do: @fonts
@doc """
Returns the heading and body font keys for a typography preset.
"""
def fonts_for_typography(typography) do
Map.get(@typography_fonts, typography, @typography_fonts["clean"])
end
@doc """
Returns the CSS font-family declaration (with fallbacks) for a font key.
"""
def font_family(key) do
case get_font(key) do
%{family: family, fallback: fallback} -> "'#{family}', #{fallback}"
nil -> "system-ui, sans-serif"
end
end
@doc """
Generates @font-face CSS for a specific typography preset.
Only includes the fonts needed for that preset.
"""
def generate_font_faces(typography) do
%{heading: heading_key, body: body_key} = fonts_for_typography(typography)
font_keys =
if heading_key == body_key do
[heading_key]
else
[heading_key, body_key]
end
font_keys
|> Enum.map(&generate_font_face_for_font/1)
|> Enum.join("\n")
end
@doc """
Generates @font-face CSS for ALL fonts.
Used in the theme editor where users can switch between typography presets.
"""
def generate_all_font_faces do
@fonts
|> Map.keys()
|> Enum.map(&generate_font_face_for_font/1)
|> Enum.join("\n")
end
@doc """
Generates preload link tags for a specific typography preset.
Returns a list of maps with href, as, type, and crossorigin attributes.
"""
def preload_links(typography) do
%{heading: heading_key, body: body_key} = fonts_for_typography(typography)
# Preload the most commonly used weights
preload_weights = %{
# For headings, preload the typical heading weight (500-600)
heading: [500, 600],
# For body, preload regular and semibold
body: [400, 600]
}
heading_links = generate_preload_links_for_font(heading_key, preload_weights.heading)
body_links = generate_preload_links_for_font(body_key, preload_weights.body)
# Deduplicate in case heading and body use the same font
(heading_links ++ body_links) |> Enum.uniq_by(& &1.href)
end
# Private functions
defp generate_font_face_for_font(key) do
case get_font(key) do
%{family: family, file_prefix: prefix, weights: weights} ->
weights
|> Enum.map(fn weight ->
weight_suffix = if weight == 400, do: "regular", else: to_string(weight)
"""
@font-face {
font-family: '#{family}';
font-style: normal;
font-weight: #{weight};
font-display: swap;
src: url('/fonts/#{prefix}-#{weight_suffix}.woff2') format('woff2');
}
"""
end)
|> Enum.join("")
nil ->
""
end
end
defp generate_preload_links_for_font(key, weights) do
case get_font(key) do
%{file_prefix: prefix, weights: available_weights} ->
weights
|> Enum.filter(&(&1 in available_weights))
|> Enum.map(fn weight ->
weight_suffix = if weight == 400, do: "regular", else: to_string(weight)
%{
href: "/fonts/#{prefix}-#{weight_suffix}.woff2",
as: "font",
type: "font/woff2",
crossorigin: true
}
end)
nil ->
[]
end
end
end