diff --git a/assets/css/app.css b/assets/css/app.css index 0133acc..80cefc4 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -102,4 +102,10 @@ /* Make LiveView wrapper divs transparent for layout */ [data-phx-session], [data-phx-teleported-src] { display: contents } +/* Theme CSS - Layer 1: Primitives (fixed CSS variables) */ +@import url("/css/theme-primitives.css"); + +/* Theme CSS - Layer 3: Semantic aliases */ +@import url("/css/theme-semantic.css"); + /* This file is for your main application CSS */ diff --git a/lib/simpleshop_theme/application.ex b/lib/simpleshop_theme/application.ex index 0dae44d..d4c8c25 100644 --- a/lib/simpleshop_theme/application.ex +++ b/lib/simpleshop_theme/application.ex @@ -14,8 +14,8 @@ defmodule SimpleshopTheme.Application do repos: Application.fetch_env!(:simpleshop_theme, :ecto_repos), skip: skip_migrations?()}, {DNSCluster, query: Application.get_env(:simpleshop_theme, :dns_cluster_query) || :ignore}, {Phoenix.PubSub, name: SimpleshopTheme.PubSub}, - # Start a worker by calling: SimpleshopTheme.Worker.start_link(arg) - # {SimpleshopTheme.Worker, arg}, + # Theme CSS cache + SimpleshopTheme.Theme.CSSCache, # Start to serve requests, typically the last entry SimpleshopThemeWeb.Endpoint ] diff --git a/lib/simpleshop_theme/theme/css_cache.ex b/lib/simpleshop_theme/theme/css_cache.ex new file mode 100644 index 0000000..e274f84 --- /dev/null +++ b/lib/simpleshop_theme/theme/css_cache.ex @@ -0,0 +1,107 @@ +defmodule SimpleshopTheme.Theme.CSSCache do + @moduledoc """ + GenServer that maintains an ETS table for caching generated theme CSS. + + This provides fast lookups for theme CSS without regenerating it on every request. + The cache is invalidated when theme settings are updated. + """ + + use GenServer + + @table_name :theme_css_cache + + ## Client API + + @doc """ + Starts the CSS cache GenServer. + """ + def start_link(opts) do + GenServer.start_link(__MODULE__, opts, name: __MODULE__) + end + + @doc """ + Gets cached CSS for the site theme. + + Returns `{:ok, css}` if found in cache, or `:miss` if not cached. + + ## Examples + + iex> CSSCache.get() + {:ok, "/* Theme CSS ... */"} + + iex> CSSCache.get() + :miss + + """ + def get do + case :ets.lookup(@table_name, :site_theme) do + [{:site_theme, css}] -> {:ok, css} + [] -> :miss + end + end + + @doc """ + Caches CSS for the site theme. + + ## Examples + + iex> CSSCache.put(css_string) + :ok + + """ + def put(css) when is_binary(css) do + :ets.insert(@table_name, {:site_theme, css}) + :ok + end + + @doc """ + Invalidates the cached CSS, forcing regeneration on next request. + + ## Examples + + iex> CSSCache.invalidate() + :ok + + """ + def invalidate do + :ets.delete(@table_name, :site_theme) + :ok + end + + @doc """ + Warms the cache by generating and storing CSS from current theme settings. + + ## Examples + + iex> CSSCache.warm() + :ok + + """ + def warm do + alias SimpleshopTheme.Settings + alias SimpleshopTheme.Theme.CSSGenerator + + settings = Settings.get_theme_settings() + css = CSSGenerator.generate(settings) + put(css) + :ok + end + + ## Server Callbacks + + @impl true + def init(_opts) do + :ets.new(@table_name, [ + :set, + :public, + :named_table, + read_concurrency: true, + write_concurrency: false + ]) + + # Warm the cache on startup + warm() + + {:ok, %{}} + end +end diff --git a/lib/simpleshop_theme/theme/css_generator.ex b/lib/simpleshop_theme/theme/css_generator.ex new file mode 100644 index 0000000..17f47b2 --- /dev/null +++ b/lib/simpleshop_theme/theme/css_generator.ex @@ -0,0 +1,274 @@ +defmodule SimpleshopTheme.Theme.CSSGenerator do + @moduledoc """ + Generates CSS custom properties (Layer 2: Theme Tokens) from theme settings. + + This module converts ThemeSettings into CSS variables that bridge the gap + between fixed primitives (Layer 1) and semantic aliases (Layer 3). + """ + + alias SimpleshopTheme.Settings.ThemeSettings + + @doc """ + Generates CSS for theme settings. + + Returns a string of CSS custom properties that can be injected into a