perf: split CSS bundles for shop and admin pages

Create separate CSS bundles to reduce shop page load times:
- app-shop.css (45KB/7.8KB gzip): Shop pages only, no daisyUI
- app.css (139KB): Admin pages with daisyUI and theme editor

Key changes:
- Add app-shop.css with targeted @source paths for shop files only
- Move .preview-frame rules from theme-layer2-attributes.css to app.css
- Delete fonts.css (fonts now generated inline by CSSGenerator)
- Add inline all-fonts generation in theme editor for typography switching
- Configure separate Tailwind profiles and watchers for both bundles

Shop pages now load 54% less CSS by excluding:
- daisyUI components (admin only)
- .preview-frame theme switching rules (editor only)
- Admin-specific Tailwind utilities

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-25 11:36:20 +00:00
parent b1635c7313
commit 1b12dc3e7f
10 changed files with 254 additions and 455 deletions

58
assets/css/app-shop.css Normal file
View File

@@ -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));
}

View File

@@ -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;
}
}

View File

@@ -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');
}

View File

@@ -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));