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:
parent
b1635c7313
commit
1b12dc3e7f
58
assets/css/app-shop.css
Normal file
58
assets/css/app-shop.css
Normal 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));
|
||||||
|
}
|
||||||
@ -102,9 +102,6 @@
|
|||||||
/* Make LiveView wrapper divs transparent for layout */
|
/* Make LiveView wrapper divs transparent for layout */
|
||||||
[data-phx-session], [data-phx-teleported-src] { display: contents }
|
[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) */
|
/* Theme CSS - Layer 1: Primitives (fixed CSS variables) */
|
||||||
@import "./theme-primitives.css";
|
@import "./theme-primitives.css";
|
||||||
|
|
||||||
@ -135,3 +132,167 @@
|
|||||||
.pdp-thumbnail-active {
|
.pdp-thumbnail-active {
|
||||||
border-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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');
|
|
||||||
}
|
|
||||||
@ -1,183 +1,12 @@
|
|||||||
/* ========================================
|
/* ========================================
|
||||||
LAYER 2: THEME TOKENS (Attribute-based)
|
LAYER 2: THEME TOKENS (Shared Styles)
|
||||||
========================================
|
========================================
|
||||||
|
|
||||||
ARCHITECTURE:
|
This file contains .themed styles used by both shop and theme editor.
|
||||||
- .themed is the shared class for all themed content (shop + preview)
|
The .preview-frame CSS variable switching rules are in app.css (admin only).
|
||||||
- .preview-frame uses data-attribute selectors for CSS variable switching (editor only)
|
Shop pages get CSS variables inline from CSSGenerator.
|
||||||
- Shop pages get CSS variable values inline from CSSGenerator
|
|
||||||
- All visual/behavioral styles use .themed
|
|
||||||
======================================== */
|
======================================== */
|
||||||
|
|
||||||
/* =============================================
|
|
||||||
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 {
|
.themed {
|
||||||
/* Font size scale */
|
/* Font size scale */
|
||||||
font-size: calc(16px * var(--t-font-size-scale, 1));
|
font-size: calc(16px * var(--t-font-size-scale, 1));
|
||||||
|
|||||||
@ -63,6 +63,13 @@ config :tailwind,
|
|||||||
--output=priv/static/assets/css/app.css
|
--output=priv/static/assets/css/app.css
|
||||||
),
|
),
|
||||||
cd: Path.expand("..", __DIR__)
|
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
|
# Configures Elixir's Logger
|
||||||
|
|||||||
@ -23,7 +23,8 @@ config :simpleshop_theme, SimpleshopThemeWeb.Endpoint,
|
|||||||
secret_key_base: "Jk04sYT/pzfZ0cywS+i0vCURPoQYgqAGa72uS8bv2gydLyusWFc08kJyEnQP4zgT",
|
secret_key_base: "Jk04sYT/pzfZ0cywS+i0vCURPoQYgqAGa72uS8bv2gydLyusWFc08kJyEnQP4zgT",
|
||||||
watchers: [
|
watchers: [
|
||||||
esbuild: {Esbuild, :install_and_run, [:simpleshop_theme, ~w(--sourcemap=inline --watch)]},
|
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
|
# ## SSL Support
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
) do %>
|
) do %>
|
||||||
<link rel="preload" href={preload.href} as="font" type="font/woff2" crossorigin />
|
<link rel="preload" href={preload.href} as="font" type="font/woff2" crossorigin />
|
||||||
<% end %>
|
<% end %>
|
||||||
<link phx-track-static rel="stylesheet" href={~p"/assets/css/app.css"} />
|
<link phx-track-static rel="stylesheet" href={~p"/assets/css/app-shop.css"} />
|
||||||
<script defer phx-track-static src={~p"/assets/js/app.js"}>
|
<script defer phx-track-static src={~p"/assets/js/app.js"}>
|
||||||
</script>
|
</script>
|
||||||
<!-- Generated theme CSS with @font-face declarations -->
|
<!-- Generated theme CSS with @font-face declarations -->
|
||||||
|
|||||||
@ -938,6 +938,11 @@
|
|||||||
data-shadow={@theme_settings.card_shadow}
|
data-shadow={@theme_settings.card_shadow}
|
||||||
data-button-style={@theme_settings.button_style}>
|
data-button-style={@theme_settings.button_style}>
|
||||||
<style>
|
<style>
|
||||||
|
/* All font faces for theme switching */
|
||||||
|
<%= Phoenix.HTML.raw(SimpleshopTheme.Theme.Fonts.generate_all_font_faces(
|
||||||
|
&SimpleshopThemeWeb.Endpoint.static_path/1
|
||||||
|
)) %>
|
||||||
|
/* Generated theme CSS */
|
||||||
<%= Phoenix.HTML.raw(@generated_css) %>
|
<%= Phoenix.HTML.raw(@generated_css) %>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
8
mix.exs
8
mix.exs
@ -86,9 +86,15 @@ defmodule SimpleshopTheme.MixProject do
|
|||||||
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
||||||
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
|
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
|
||||||
"assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
|
"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": [
|
"assets.deploy": [
|
||||||
"tailwind simpleshop_theme --minify",
|
"tailwind simpleshop_theme --minify",
|
||||||
|
"tailwind simpleshop_theme_shop --minify",
|
||||||
"esbuild simpleshop_theme --minify",
|
"esbuild simpleshop_theme --minify",
|
||||||
"phx.digest"
|
"phx.digest"
|
||||||
],
|
],
|
||||||
|
|||||||
@ -5,9 +5,9 @@ defmodule SimpleshopThemeWeb.ThemeCSSConsistencyTest do
|
|||||||
|
|
||||||
Architecture:
|
Architecture:
|
||||||
- Both shop pages and preview use .themed class for shared styles
|
- Both shop pages and preview use .themed class for shared styles
|
||||||
- Theme editor uses .preview-frame[data-*] selectors for live switching
|
- Theme editor uses .preview-frame[data-*] selectors for live switching (in app.css)
|
||||||
- Shop pages get theme values via inline CSS from CSSGenerator
|
- Shop pages get theme values via inline CSS from CSSGenerator (app-shop.css)
|
||||||
- Component styles use .themed for shared styling
|
- Component styles use .themed for shared styling (theme-layer2-attributes.css)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use SimpleshopThemeWeb.ConnCase, async: false
|
use SimpleshopThemeWeb.ConnCase, async: false
|
||||||
@ -88,12 +88,13 @@ defmodule SimpleshopThemeWeb.ThemeCSSConsistencyTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "CSS file structure" do
|
describe "CSS file structure" do
|
||||||
test "theme-layer2-attributes.css has .preview-frame variant selectors for editor" do
|
test "app.css has .preview-frame variant selectors for theme editor" do
|
||||||
css_path = Path.join([File.cwd!(), "assets", "css", "theme-layer2-attributes.css"])
|
css_path = Path.join([File.cwd!(), "assets", "css", "app.css"])
|
||||||
css_content = File.read!(css_path)
|
css_content = File.read!(css_path)
|
||||||
|
|
||||||
# Variant selectors are editor-only (.preview-frame) using CSS nesting
|
# Variant selectors are editor-only (.preview-frame) using CSS nesting
|
||||||
# The file uses &[data-*] syntax inside .preview-frame { }
|
# 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 =~ ".preview-frame"
|
||||||
assert css_content =~ "&[data-mood=\"dark\"]"
|
assert css_content =~ "&[data-mood=\"dark\"]"
|
||||||
assert css_content =~ "&[data-mood=\"warm\"]"
|
assert css_content =~ "&[data-mood=\"warm\"]"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user