diff --git a/assets/css/app-shop.css b/assets/css/app-shop.css
new file mode 100644
index 0000000..1b91dae
--- /dev/null
+++ b/assets/css/app-shop.css
@@ -0,0 +1,58 @@
+/* Shop CSS - Tailwind without daisyUI
+ This is the CSS bundle for public shop pages (localhost:4001).
+ It excludes daisyUI which is only needed for admin pages.
+ See app.css for the full admin version. */
+
+@import "tailwindcss" source(none);
+@source "../css";
+@source "../js";
+/* Only scan shop-specific files, not admin pages */
+@source "../../lib/simpleshop_theme_web/live/shop_live";
+@source "../../lib/simpleshop_theme_web/components/shop_components.ex";
+@source "../../lib/simpleshop_theme_web/components/page_templates";
+@source "../../lib/simpleshop_theme_web/components/layouts/shop.html.heex";
+@source "../../lib/simpleshop_theme_web/components/layouts/shop_root.html.heex";
+
+/* Heroicons plugin */
+@plugin "../vendor/heroicons";
+
+/* NO daisyUI - shop pages use the custom .themed system instead */
+
+/* Add variants based on LiveView classes */
+@custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &);
+@custom-variant phx-submit-loading (.phx-submit-loading&, .phx-submit-loading &);
+@custom-variant phx-change-loading (.phx-change-loading&, .phx-change-loading &);
+
+/* 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 "./theme-primitives.css";
+
+/* Theme CSS - Layer 2: Shared styles only (.themed selectors)
+ Note: .preview-frame rules are still included but unused on shop pages.
+ This is acceptable as it's only ~5KB and splitting adds complexity. */
+@import "./theme-layer2-attributes.css";
+
+/* Theme CSS - Layer 3: Semantic aliases */
+@import "./theme-semantic.css";
+
+/* Cart drawer open state styles */
+.cart-drawer.open {
+ right: 0 !important;
+}
+
+.cart-drawer-overlay.open {
+ opacity: 1 !important;
+ visibility: visible !important;
+}
+
+/* Product gallery thumbnail styles */
+.pdp-thumbnail {
+ border: 2px solid var(--t-border-default);
+ transition: border-color 0.15s ease;
+}
+
+.pdp-thumbnail-active {
+ border-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
+}
diff --git a/assets/css/app.css b/assets/css/app.css
index b0cffbf..ab400ce 100644
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -102,9 +102,6 @@
/* Make LiveView wrapper divs transparent for layout */
[data-phx-session], [data-phx-teleported-src] { display: contents }
-/* Self-hosted fonts - all font-face declarations */
-@import "./fonts.css";
-
/* Theme CSS - Layer 1: Primitives (fixed CSS variables) */
@import "./theme-primitives.css";
@@ -135,3 +132,167 @@
.pdp-thumbnail-active {
border-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
}
+
+/* =============================================
+ THEME EDITOR ONLY: .preview-frame CSS variable switching
+ These rules enable live theme switching in the editor.
+ Shop pages get CSS variables inline from CSSGenerator.
+ ============================================= */
+
+.preview-frame {
+ /* Mood - Default (Neutral) */
+ --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;
+
+ /* Typography - Default (Clean) */
+ --t-font-heading: var(--p-font-manrope);
+ --t-font-body: var(--p-font-inter);
+ --t-heading-weight: 600;
+ --t-heading-tracking: -0.02em;
+
+ /* Shape - Default (Soft) */
+ --t-radius-sm: var(--p-radius-sm);
+ --t-radius-md: var(--p-radius-md);
+ --t-radius-lg: var(--p-radius-lg);
+ --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);
+
+ /* Density - Default (Balanced) */
+ --t-density: 1;
+ --space-xs: calc(var(--p-space-2) * var(--t-density));
+ --space-sm: calc(var(--p-space-3) * var(--t-density));
+ --space-md: calc(var(--p-space-4) * var(--t-density));
+ --space-lg: calc(var(--p-space-6) * var(--t-density));
+ --space-xl: calc(var(--p-space-8) * var(--t-density));
+ --space-2xl: calc(var(--p-space-12) * var(--t-density));
+
+ /* Mood Variants */
+ &[data-mood="warm"] {
+ --t-surface-base: #fdf8f3;
+ --t-surface-raised: #fffcf8;
+ --t-surface-sunken: #f5ebe0;
+ --t-text-primary: #1c1917;
+ --t-text-secondary: #57534e;
+ --t-text-tertiary: #a8a29e;
+ --t-border-default: #e7e0d8;
+ --t-border-subtle: #f0ebe4;
+ }
+
+ &[data-mood="cool"] {
+ --t-surface-base: #f4f7fb;
+ --t-surface-raised: #f8fafc;
+ --t-surface-sunken: #e8eff7;
+ --t-text-primary: #0f172a;
+ --t-text-secondary: #475569;
+ --t-text-tertiary: #94a3b8;
+ --t-border-default: #d4dce8;
+ --t-border-subtle: #e8eff5;
+ }
+
+ &[data-mood="dark"] {
+ --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;
+ }
+
+ /* Typography Variants */
+ &[data-typography="editorial"] {
+ --t-font-heading: var(--p-font-playfair);
+ --t-font-body: var(--p-font-raleway);
+ --t-heading-weight: 500;
+ --t-heading-tracking: -0.01em;
+ }
+
+ &[data-typography="modern"] {
+ --t-font-heading: var(--p-font-space);
+ --t-font-body: var(--p-font-inter);
+ --t-heading-weight: 500;
+ --t-heading-tracking: -0.03em;
+ }
+
+ &[data-typography="classic"] {
+ --t-font-heading: var(--p-font-cormorant);
+ --t-font-body: var(--p-font-source-serif);
+ --t-heading-weight: 500;
+ --t-heading-tracking: 0;
+ }
+
+ &[data-typography="friendly"] {
+ --t-font-heading: var(--p-font-fraunces);
+ --t-font-body: var(--p-font-work-sans);
+ --t-heading-weight: 600;
+ --t-heading-tracking: -0.01em;
+ }
+
+ &[data-typography="minimal"] {
+ --t-font-heading: var(--p-font-dm-sans);
+ --t-font-body: var(--p-font-source-serif);
+ --t-heading-weight: 500;
+ --t-heading-tracking: 0;
+ }
+
+ &[data-typography="impulse"] {
+ --t-font-heading: var(--p-font-raleway);
+ --t-font-body: var(--p-font-inter);
+ --t-heading-weight: 300;
+ --t-heading-tracking: 0.02em;
+ }
+
+ /* Shape Variants */
+ &[data-shape="sharp"] {
+ --t-radius-sm: 0;
+ --t-radius-md: 0;
+ --t-radius-lg: 0;
+ --t-radius-button: 0;
+ --t-radius-card: 0;
+ --t-radius-input: 0;
+ --t-radius-image: 0;
+ }
+
+ &[data-shape="round"] {
+ --t-radius-sm: var(--p-radius-md);
+ --t-radius-md: var(--p-radius-lg);
+ --t-radius-lg: var(--p-radius-xl);
+ --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);
+ }
+
+ &[data-shape="pill"] {
+ --t-radius-sm: var(--p-radius-full);
+ --t-radius-md: var(--p-radius-full);
+ --t-radius-lg: var(--p-radius-xl);
+ --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);
+ }
+
+ /* Density Variants */
+ &[data-density="spacious"] {
+ --t-density: 1.25;
+ }
+
+ &[data-density="compact"] {
+ --t-density: 0.85;
+ }
+}
diff --git a/assets/css/fonts.css b/assets/css/fonts.css
deleted file mode 100644
index ad0d959..0000000
--- a/assets/css/fonts.css
+++ /dev/null
@@ -1,269 +0,0 @@
-/* Self-hosted Google Fonts
- * All fonts loaded locally for privacy and performance.
- * Browsers only download fonts actually used on the page.
- */
-
-/* Inter - Clean, Modern, Impulse presets (body) */
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 300;
- font-display: swap;
- src: url('/fonts/inter-v20-latin-300.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url('/fonts/inter-v20-latin-regular.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 500;
- font-display: swap;
- src: url('/fonts/inter-v20-latin-500.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: url('/fonts/inter-v20-latin-600.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Inter';
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url('/fonts/inter-v20-latin-700.woff2') format('woff2');
-}
-
-/* Manrope - Clean preset (heading) */
-@font-face {
- font-family: 'Manrope';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url('/fonts/manrope-v20-latin-regular.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Manrope';
- font-style: normal;
- font-weight: 500;
- font-display: swap;
- src: url('/fonts/manrope-v20-latin-500.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Manrope';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: url('/fonts/manrope-v20-latin-600.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Manrope';
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url('/fonts/manrope-v20-latin-700.woff2') format('woff2');
-}
-
-/* Work Sans - Friendly preset (body) */
-@font-face {
- font-family: 'Work Sans';
- font-style: normal;
- font-weight: 300;
- font-display: swap;
- src: url('/fonts/work-sans-v24-latin-300.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Work Sans';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url('/fonts/work-sans-v24-latin-regular.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Work Sans';
- font-style: normal;
- font-weight: 500;
- font-display: swap;
- src: url('/fonts/work-sans-v24-latin-500.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Work Sans';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: url('/fonts/work-sans-v24-latin-600.woff2') format('woff2');
-}
-
-/* DM Sans - Minimal preset (heading) */
-@font-face {
- font-family: 'DM Sans';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url('/fonts/dm-sans-v17-latin-regular.woff2') format('woff2');
-}
-@font-face {
- font-family: 'DM Sans';
- font-style: normal;
- font-weight: 500;
- font-display: swap;
- src: url('/fonts/dm-sans-v17-latin-500.woff2') format('woff2');
-}
-@font-face {
- font-family: 'DM Sans';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: url('/fonts/dm-sans-v17-latin-600.woff2') format('woff2');
-}
-@font-face {
- font-family: 'DM Sans';
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url('/fonts/dm-sans-v17-latin-700.woff2') format('woff2');
-}
-
-/* Raleway - Editorial, Impulse presets (body/heading) */
-@font-face {
- font-family: 'Raleway';
- font-style: normal;
- font-weight: 300;
- font-display: swap;
- src: url('/fonts/raleway-v37-latin-300.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Raleway';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url('/fonts/raleway-v37-latin-regular.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Raleway';
- font-style: normal;
- font-weight: 500;
- font-display: swap;
- src: url('/fonts/raleway-v37-latin-500.woff2') format('woff2');
-}
-
-/* Space Grotesk - Modern preset (heading) */
-@font-face {
- font-family: 'Space Grotesk';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url('/fonts/space-grotesk-v22-latin-regular.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Space Grotesk';
- font-style: normal;
- font-weight: 500;
- font-display: swap;
- src: url('/fonts/space-grotesk-v22-latin-500.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Space Grotesk';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: url('/fonts/space-grotesk-v22-latin-600.woff2') format('woff2');
-}
-
-/* Playfair Display - Editorial preset (heading) */
-@font-face {
- font-family: 'Playfair Display';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url('/fonts/playfair-display-v40-latin-regular.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Playfair Display';
- font-style: normal;
- font-weight: 500;
- font-display: swap;
- src: url('/fonts/playfair-display-v40-latin-500.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Playfair Display';
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url('/fonts/playfair-display-v40-latin-700.woff2') format('woff2');
-}
-
-/* Cormorant Garamond - Classic preset (heading) */
-@font-face {
- font-family: 'Cormorant Garamond';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url('/fonts/cormorant-garamond-v21-latin-regular.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Cormorant Garamond';
- font-style: normal;
- font-weight: 500;
- font-display: swap;
- src: url('/fonts/cormorant-garamond-v21-latin-500.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Cormorant Garamond';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: url('/fonts/cormorant-garamond-v21-latin-600.woff2') format('woff2');
-}
-
-/* Source Serif 4 - Classic, Minimal presets (body) */
-@font-face {
- font-family: 'Source Serif 4';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url('/fonts/source-serif-4-v14-latin-regular.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Source Serif 4';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: url('/fonts/source-serif-4-v14-latin-600.woff2') format('woff2');
-}
-
-/* Fraunces - Friendly preset (heading) */
-@font-face {
- font-family: 'Fraunces';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url('/fonts/fraunces-v38-latin-regular.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Fraunces';
- font-style: normal;
- font-weight: 500;
- font-display: swap;
- src: url('/fonts/fraunces-v38-latin-500.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Fraunces';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: url('/fonts/fraunces-v38-latin-600.woff2') format('woff2');
-}
-@font-face {
- font-family: 'Fraunces';
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url('/fonts/fraunces-v38-latin-700.woff2') format('woff2');
-}
diff --git a/assets/css/theme-layer2-attributes.css b/assets/css/theme-layer2-attributes.css
index fd9e732..da67fe2 100644
--- a/assets/css/theme-layer2-attributes.css
+++ b/assets/css/theme-layer2-attributes.css
@@ -1,183 +1,12 @@
/* ========================================
- LAYER 2: THEME TOKENS (Attribute-based)
+ LAYER 2: THEME TOKENS (Shared Styles)
========================================
- ARCHITECTURE:
- - .themed is the shared class for all themed content (shop + preview)
- - .preview-frame uses data-attribute selectors for CSS variable switching (editor only)
- - Shop pages get CSS variable values inline from CSSGenerator
- - All visual/behavioral styles use .themed
+ This file contains .themed styles used by both shop and theme editor.
+ The .preview-frame CSS variable switching rules are in app.css (admin only).
+ Shop pages get CSS variables inline from CSSGenerator.
======================================== */
-/* =============================================
- CSS VARIABLE SWITCHING (Editor-only)
- These set CSS variables based on data attributes.
- Shop pages get these values inline from CSSGenerator.
- ============================================= */
-
-.preview-frame {
- /* Mood - Default (Neutral) */
- --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;
-
- /* Typography - Default (Clean) */
- --t-font-heading: var(--p-font-manrope);
- --t-font-body: var(--p-font-inter);
- --t-heading-weight: 600;
- --t-heading-tracking: -0.02em;
-
- /* Shape - Default (Soft) */
- --t-radius-sm: var(--p-radius-sm);
- --t-radius-md: var(--p-radius-md);
- --t-radius-lg: var(--p-radius-lg);
- --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);
-
- /* Density - Default (Balanced) */
- --t-density: 1;
- --space-xs: calc(var(--p-space-2) * var(--t-density));
- --space-sm: calc(var(--p-space-3) * var(--t-density));
- --space-md: calc(var(--p-space-4) * var(--t-density));
- --space-lg: calc(var(--p-space-6) * var(--t-density));
- --space-xl: calc(var(--p-space-8) * var(--t-density));
- --space-2xl: calc(var(--p-space-12) * var(--t-density));
-
- /* Mood Variants */
- &[data-mood="warm"] {
- --t-surface-base: #fdf8f3;
- --t-surface-raised: #fffcf8;
- --t-surface-sunken: #f5ebe0;
- --t-text-primary: #1c1917;
- --t-text-secondary: #57534e;
- --t-text-tertiary: #a8a29e;
- --t-border-default: #e7e0d8;
- --t-border-subtle: #f0ebe4;
- }
-
- &[data-mood="cool"] {
- --t-surface-base: #f4f7fb;
- --t-surface-raised: #f8fafc;
- --t-surface-sunken: #e8eff7;
- --t-text-primary: #0f172a;
- --t-text-secondary: #475569;
- --t-text-tertiary: #94a3b8;
- --t-border-default: #d4dce8;
- --t-border-subtle: #e8eff5;
- }
-
- &[data-mood="dark"] {
- --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;
- }
-
- /* Typography Variants */
- &[data-typography="editorial"] {
- --t-font-heading: var(--p-font-playfair);
- --t-font-body: var(--p-font-raleway);
- --t-heading-weight: 500;
- --t-heading-tracking: -0.01em;
- }
-
- &[data-typography="modern"] {
- --t-font-heading: var(--p-font-space);
- --t-font-body: var(--p-font-inter);
- --t-heading-weight: 500;
- --t-heading-tracking: -0.03em;
- }
-
- &[data-typography="classic"] {
- --t-font-heading: var(--p-font-cormorant);
- --t-font-body: var(--p-font-source-serif);
- --t-heading-weight: 500;
- --t-heading-tracking: 0;
- }
-
- &[data-typography="friendly"] {
- --t-font-heading: var(--p-font-fraunces);
- --t-font-body: var(--p-font-work-sans);
- --t-heading-weight: 600;
- --t-heading-tracking: -0.01em;
- }
-
- &[data-typography="minimal"] {
- --t-font-heading: var(--p-font-dm-sans);
- --t-font-body: var(--p-font-source-serif);
- --t-heading-weight: 500;
- --t-heading-tracking: 0;
- }
-
- &[data-typography="impulse"] {
- --t-font-heading: var(--p-font-raleway);
- --t-font-body: var(--p-font-inter);
- --t-heading-weight: 300;
- --t-heading-tracking: 0.02em;
- }
-
- /* Shape Variants */
- &[data-shape="sharp"] {
- --t-radius-sm: 0;
- --t-radius-md: 0;
- --t-radius-lg: 0;
- --t-radius-button: 0;
- --t-radius-card: 0;
- --t-radius-input: 0;
- --t-radius-image: 0;
- }
-
- &[data-shape="round"] {
- --t-radius-sm: var(--p-radius-md);
- --t-radius-md: var(--p-radius-lg);
- --t-radius-lg: var(--p-radius-xl);
- --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);
- }
-
- &[data-shape="pill"] {
- --t-radius-sm: var(--p-radius-full);
- --t-radius-md: var(--p-radius-full);
- --t-radius-lg: var(--p-radius-xl);
- --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);
- }
-
- /* Density Variants */
- &[data-density="spacious"] {
- --t-density: 1.25;
- }
-
- &[data-density="compact"] {
- --t-density: 0.85;
- }
-}
-
-/* =============================================
- SHARED STYLES (Both Editor and Shop)
- All visual/behavioral styles use .themed
- ============================================= */
-
.themed {
/* Font size scale */
font-size: calc(16px * var(--t-font-size-scale, 1));
diff --git a/config/config.exs b/config/config.exs
index 00eb410..72f8eef 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -63,6 +63,13 @@ config :tailwind,
--output=priv/static/assets/css/app.css
),
cd: Path.expand("..", __DIR__)
+ ],
+ simpleshop_theme_shop: [
+ args: ~w(
+ --input=assets/css/app-shop.css
+ --output=priv/static/assets/css/app-shop.css
+ ),
+ cd: Path.expand("..", __DIR__)
]
# Configures Elixir's Logger
diff --git a/config/dev.exs b/config/dev.exs
index 5f7f679..341d2b6 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -23,7 +23,8 @@ config :simpleshop_theme, SimpleshopThemeWeb.Endpoint,
secret_key_base: "Jk04sYT/pzfZ0cywS+i0vCURPoQYgqAGa72uS8bv2gydLyusWFc08kJyEnQP4zgT",
watchers: [
esbuild: {Esbuild, :install_and_run, [:simpleshop_theme, ~w(--sourcemap=inline --watch)]},
- tailwind: {Tailwind, :install_and_run, [:simpleshop_theme, ~w(--watch)]}
+ tailwind: {Tailwind, :install_and_run, [:simpleshop_theme, ~w(--watch)]},
+ tailwind_shop: {Tailwind, :install_and_run, [:simpleshop_theme_shop, ~w(--watch)]}
]
# ## SSL Support
diff --git a/lib/simpleshop_theme_web/components/layouts/shop_root.html.heex b/lib/simpleshop_theme_web/components/layouts/shop_root.html.heex
index 9b8ecd4..d74eb4e 100644
--- a/lib/simpleshop_theme_web/components/layouts/shop_root.html.heex
+++ b/lib/simpleshop_theme_web/components/layouts/shop_root.html.heex
@@ -13,7 +13,7 @@
) do %>
<% end %>
-
+
diff --git a/lib/simpleshop_theme_web/live/theme_live/index.html.heex b/lib/simpleshop_theme_web/live/theme_live/index.html.heex
index 72b0902..1217650 100644
--- a/lib/simpleshop_theme_web/live/theme_live/index.html.heex
+++ b/lib/simpleshop_theme_web/live/theme_live/index.html.heex
@@ -938,6 +938,11 @@
data-shadow={@theme_settings.card_shadow}
data-button-style={@theme_settings.button_style}>
diff --git a/mix.exs b/mix.exs
index 677030d..07a82ba 100644
--- a/mix.exs
+++ b/mix.exs
@@ -86,9 +86,15 @@ defmodule SimpleshopTheme.MixProject do
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
"assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
- "assets.build": ["compile", "tailwind simpleshop_theme", "esbuild simpleshop_theme"],
+ "assets.build": [
+ "compile",
+ "tailwind simpleshop_theme",
+ "tailwind simpleshop_theme_shop",
+ "esbuild simpleshop_theme"
+ ],
"assets.deploy": [
"tailwind simpleshop_theme --minify",
+ "tailwind simpleshop_theme_shop --minify",
"esbuild simpleshop_theme --minify",
"phx.digest"
],
diff --git a/test/simpleshop_theme_web/live/theme_css_consistency_test.exs b/test/simpleshop_theme_web/live/theme_css_consistency_test.exs
index 72976a5..6054816 100644
--- a/test/simpleshop_theme_web/live/theme_css_consistency_test.exs
+++ b/test/simpleshop_theme_web/live/theme_css_consistency_test.exs
@@ -5,9 +5,9 @@ defmodule SimpleshopThemeWeb.ThemeCSSConsistencyTest do
Architecture:
- Both shop pages and preview use .themed class for shared styles
- - Theme editor uses .preview-frame[data-*] selectors for live switching
- - Shop pages get theme values via inline CSS from CSSGenerator
- - Component styles use .themed for shared styling
+ - Theme editor uses .preview-frame[data-*] selectors for live switching (in app.css)
+ - Shop pages get theme values via inline CSS from CSSGenerator (app-shop.css)
+ - Component styles use .themed for shared styling (theme-layer2-attributes.css)
"""
use SimpleshopThemeWeb.ConnCase, async: false
@@ -88,12 +88,13 @@ defmodule SimpleshopThemeWeb.ThemeCSSConsistencyTest do
end
describe "CSS file structure" do
- test "theme-layer2-attributes.css has .preview-frame variant selectors for editor" do
- css_path = Path.join([File.cwd!(), "assets", "css", "theme-layer2-attributes.css"])
+ test "app.css has .preview-frame variant selectors for theme editor" do
+ css_path = Path.join([File.cwd!(), "assets", "css", "app.css"])
css_content = File.read!(css_path)
# Variant selectors are editor-only (.preview-frame) using CSS nesting
# The file uses &[data-*] syntax inside .preview-frame { }
+ # These rules are in app.css (admin only), not app-shop.css
assert css_content =~ ".preview-frame"
assert css_content =~ "&[data-mood=\"dark\"]"
assert css_content =~ "&[data-mood=\"warm\"]"