diff --git a/assets/css/theme-layer2-attributes.css b/assets/css/theme-layer2-attributes.css index 9953e5f..190d949 100644 --- a/assets/css/theme-layer2-attributes.css +++ b/assets/css/theme-layer2-attributes.css @@ -1,10 +1,22 @@ /* ======================================== LAYER 2: THEME TOKENS (Attribute-based) + ======================================== + + 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 ======================================== */ -/* Mood - Default (Neutral) */ -.preview-frame, -.shop-root { +/* ============================================= + 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; @@ -15,121 +27,14 @@ --t-text-inverse: #ffffff; --t-border-default: #e5e5e5; --t-border-subtle: #f0f0f0; -} -.preview-frame[data-mood="warm"], -.shop-root[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; -} - -.preview-frame[data-mood="cool"], -.shop-root[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; -} - -.preview-frame[data-mood="dark"], -.shop-root[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 Presets - Curated font pairings based on e-commerce research */ - -/* Default (Clean) - Minimal & Modern: Manrope + Inter */ -.preview-frame, -.shop-root { + /* 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; -} -/* Editorial - Bold & Editorial: Playfair Display + Raleway */ -/* Best for: fashion, lifestyle brands */ -.preview-frame[data-typography="editorial"], -.shop-root[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; -} - -/* Modern - Tech & Futuristic: Space Grotesk + Inter */ -/* Best for: tech products, gadget accessories */ -.preview-frame[data-typography="modern"], -.shop-root[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; -} - -/* Classic - Luxury & Elegant: Cormorant Garamond + Source Serif 4 */ -/* Best for: high-end goods, premium products */ -.preview-frame[data-typography="classic"], -.shop-root[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; -} - -/* Friendly - Playful & Quirky: Fraunces + Work Sans */ -/* Best for: creative products, novelty gifts */ -.preview-frame[data-typography="friendly"], -.shop-root[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; -} - -/* Minimal - Modern Sans-Serif: DM Sans + Source Serif 4 */ -/* Best for: design-forward, contemporary homeware */ -.preview-frame[data-typography="minimal"], -.shop-root[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; -} - -/* Impulse - Light & Airy: Raleway + Inter */ -/* Best for: wellness, beauty, sustainable goods */ -.preview-frame[data-typography="impulse"], -.shop-root[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 - Default (Soft) */ -.preview-frame, -.shop-root { + /* Shape - Default (Soft) */ --t-radius-sm: var(--p-radius-sm); --t-radius-md: var(--p-radius-md); --t-radius-lg: var(--p-radius-lg); @@ -137,276 +42,386 @@ --t-radius-card: var(--p-radius-lg); --t-radius-input: var(--p-radius-md); --t-radius-image: var(--p-radius-md); -} -.preview-frame[data-shape="sharp"], -.shop-root[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; -} - -.preview-frame[data-shape="round"], -.shop-root[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); -} - -.preview-frame[data-shape="pill"], -.shop-root[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 - Default (Balanced) */ -.preview-frame, -.shop-root { + /* Density - Default (Balanced) */ --t-density: 1; -} - -.preview-frame[data-density="spacious"], -.shop-root[data-density="spacious"] { - --t-density: 1.25; -} - -.preview-frame[data-density="compact"], -.shop-root[data-density="compact"] { - --t-density: 0.85; -} - -/* Density-aware spacing variables */ -.preview-frame, -.shop-root { --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; + } } -/* Header Layout */ +/* ============================================= + 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)); + + /* Heading Weight Override */ + & h1, & h2, & h3, & h4, & h5, & h6 { + font-weight: var(--t-heading-weight-override, var(--t-heading-weight, 600)) !important; + } + + /* Type Scale Utility Classes */ + & .t-caption { font-size: var(--t-text-caption); } + & .t-small { font-size: var(--t-text-small); } + & .t-base { font-size: var(--t-text-base); } + & .t-large { font-size: var(--t-text-large); } + & .t-xl { font-size: var(--t-text-xl); } + & .t-2xl { font-size: var(--t-text-2xl); } + & .t-heading-sm { font-size: var(--t-heading-sm); } + & .t-heading-md { font-size: var(--t-heading-md); } + & .t-heading-lg { font-size: var(--t-heading-lg); } + & .t-heading-xl { font-size: var(--t-heading-xl); } + & .t-heading-display { font-size: var(--t-heading-display); } + + /* Override Tailwind text-* classes */ + & .text-xs { font-size: var(--t-text-caption) !important; } + & .text-sm { font-size: var(--t-text-small) !important; } + & .text-base { font-size: var(--t-text-base) !important; } + & .text-lg { font-size: var(--t-text-large) !important; } + & .text-xl { font-size: var(--t-text-xl) !important; } + & .text-2xl { font-size: var(--t-text-2xl) !important; } + & .text-3xl { font-size: var(--t-heading-lg) !important; } + & .text-4xl { font-size: var(--t-heading-xl) !important; } + & .text-5xl, & .text-6xl, & .text-7xl, & .text-8xl, & .text-9xl { + font-size: var(--t-heading-display) !important; + } + + /* Header Layout */ + &[data-header="standard"] .shop-header { + justify-content: space-between; + } + + &[data-header="centered"] { + & .shop-header { + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem 1.5rem; + padding-top: 1.5rem; + padding-bottom: 1.5rem; + } + + & .shop-logo { + width: 100%; + justify-content: center; + text-align: center; + } + + & .shop-nav { + justify-content: center; + } + } + + &[data-header="left"] { + & .shop-header { + justify-content: flex-start; + gap: 2rem; + } + + & .shop-cart { + margin-left: auto; + } + } + + &[data-sticky="true"] .shop-header { + position: sticky; + top: 0; + z-index: 50; + } + + /* Layout Width */ + &[data-layout="contained"] .max-w-7xl { + max-width: var(--t-layout-max-width, 1100px); + } + + &[data-layout="wide"] .max-w-7xl { + max-width: var(--t-layout-max-width, 1400px); + } + + &[data-layout="full"] .max-w-7xl { + max-width: var(--t-layout-max-width, 100%); + padding-left: 2rem; + padding-right: 2rem; + } + + /* Card Shadow */ + &[data-shadow="none"] { + & .product-card, + & .category-card { + box-shadow: none; + } + } + + &[data-shadow="sm"] { + & .product-card, + & .category-card { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + } + + &[data-shadow="md"] { + & .product-card, + & .category-card { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1); + } + } + + &[data-shadow="lg"] { + & .product-card, + & .category-card { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); + } + } + + /* Product Grid */ + & .product-grid { + gap: var(--space-lg, 1.5rem); + } + + &[data-density="spacious"] .product-grid { + gap: calc(var(--space-lg, 1.5rem) * 1.25); + } + + &[data-density="compact"] .product-grid { + gap: calc(var(--space-lg, 1.5rem) * 0.75); + } + + /* Product Card Padding */ + & .product-card > div:last-child { + padding: var(--space-md, 1rem); + text-align: var(--t-product-text-align, left); + } + + &[data-density="spacious"] .product-card > div:last-child { + padding: calc(var(--space-md, 1rem) * 1.25); + } + + &[data-density="compact"] .product-card > div:last-child { + padding: calc(var(--space-md, 1rem) * 0.75); + } + + /* Button Style Variants */ + &[data-button-style="outline"] { + & button[style*="background-color: hsl(var(--t-accent"], + & a[style*="background-color: hsl(var(--t-accent"] { + background-color: transparent !important; + color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)) !important; + border: 2px solid hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)) !important; + } + } + + &[data-button-style="soft"] { + & button[style*="background-color: hsl(var(--t-accent"], + & a[style*="background-color: hsl(var(--t-accent"] { + background-color: hsl(var(--t-accent-h) var(--t-accent-s) 90%) !important; + color: hsl(var(--t-accent-h) var(--t-accent-s) 30%) !important; + border: 2px solid transparent !important; + } + } + + /* Image Aspect Ratio */ + & .product-card .product-image-container { + aspect-ratio: var(--t-image-aspect-ratio, 1 / 1); + } + + /* Link Hover */ + & a:not([class*="btn"]):not([class*="button"]):not(.product-card):not(.nav-link):hover { + color: var(--t-secondary-accent, var(--t-text-primary)); + } + + /* Product Card Hover */ + & .product-card:hover { + border-color: var(--t-secondary-accent, var(--t-border-default)) !important; + } + + /* Button Hover */ + & button:hover, + & [role="button"]:hover { + filter: brightness(0.95); + } + + /* Nav Link Hover */ + & .nav-link:hover, + & nav a:hover { + color: var(--t-secondary-accent, var(--t-text-primary)); + } + + /* Filter Pills */ + & .filter-pills-container { + scrollbar-width: none; + -webkit-overflow-scrolling: touch; + + &::-webkit-scrollbar { + display: none; + } + } + + & .filter-pill { + flex-shrink: 0; + padding: 0.5rem 1rem; + font-size: var(--t-text-small); + font-weight: 500; + border-radius: var(--t-radius-button); + border: 1px solid var(--t-border-default); + background-color: var(--t-surface-base); + color: var(--t-text-secondary); + cursor: pointer; + transition: all 0.15s ease; + white-space: nowrap; + + &:hover { + background-color: var(--t-surface-sunken); + color: var(--t-text-primary); + } + } + + & .filter-pill-active { + background-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); + color: var(--t-text-inverse); + border-color: transparent; + + &:hover { + background-color: hsl(var(--t-accent-h) var(--t-accent-s) calc(var(--t-accent-l) - 5%)); + color: var(--t-text-inverse); + } + } +} + +/* Shop nav display */ .shop-nav { display: flex; } -/* Standard header - logo left, nav center, cart right */ -.preview-frame[data-header="standard"] .shop-header, -.shop-root[data-header="standard"] .shop-header { - justify-content: space-between; -} - -/* Centered header - logo on top, nav and cart on same row below */ -.preview-frame[data-header="centered"] .shop-header, -.shop-root[data-header="centered"] .shop-header { - flex-wrap: wrap; - justify-content: center; - gap: 0.5rem 1.5rem; - padding-top: 1.5rem; - padding-bottom: 1.5rem; -} - -.preview-frame[data-header="centered"] .shop-logo, -.shop-root[data-header="centered"] .shop-logo { - width: 100%; - justify-content: center; - text-align: center; -} - -.preview-frame[data-header="centered"] .shop-nav, -.shop-root[data-header="centered"] .shop-nav { - justify-content: center; -} - -.preview-frame[data-header="centered"] .shop-cart, -.shop-root[data-header="centered"] .shop-cart { - /* Cart flows inline with nav, no absolute positioning */ -} - -/* Left header - logo and nav grouped left, cart right */ -.preview-frame[data-header="left"] .shop-header, -.shop-root[data-header="left"] .shop-header { - justify-content: flex-start; - gap: 2rem; -} - -.preview-frame[data-header="left"] .shop-cart, -.shop-root[data-header="left"] .shop-cart { - margin-left: auto; -} - -/* Sticky header */ -.preview-frame[data-sticky="true"] .shop-header, -.shop-root[data-sticky="true"] .shop-header { - position: sticky; - top: 0; - z-index: 50; -} - -/* Layout Width */ -.preview-frame[data-layout="contained"] .max-w-7xl, -.shop-root[data-layout="contained"] .max-w-7xl { - max-width: 1024px; -} - -.preview-frame[data-layout="wide"] .max-w-7xl, -.shop-root[data-layout="wide"] .max-w-7xl { - max-width: 1280px; -} - -.preview-frame[data-layout="full"] .max-w-7xl, -.shop-root[data-layout="full"] .max-w-7xl { - max-width: 100%; - padding-left: 2rem; - padding-right: 2rem; -} - -/* Card Shadow */ -.preview-frame[data-shadow="none"] .product-card, -.shop-root[data-shadow="none"] .product-card, -.preview-frame[data-shadow="none"] .category-card, -.shop-root[data-shadow="none"] .category-card { - box-shadow: none; -} - -.preview-frame[data-shadow="sm"] .product-card, -.shop-root[data-shadow="sm"] .product-card, -.preview-frame[data-shadow="sm"] .category-card, -.shop-root[data-shadow="sm"] .category-card { - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); -} - -.preview-frame[data-shadow="md"] .product-card, -.shop-root[data-shadow="md"] .product-card, -.preview-frame[data-shadow="md"] .category-card, -.shop-root[data-shadow="md"] .category-card { - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1); -} - -.preview-frame[data-shadow="lg"] .product-card, -.shop-root[data-shadow="lg"] .product-card, -.preview-frame[data-shadow="lg"] .category-card, -.shop-root[data-shadow="lg"] .category-card { - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); -} - /* ============================================= - Dynamic Theme Settings (consume CSS variables) + STANDALONE COMPONENTS (Context-independent) ============================================= */ -/* Density - apply to product grids */ -.preview-frame .product-grid, -.shop-root .product-grid { - gap: var(--space-lg, 1.5rem); -} - -.preview-frame[data-density="spacious"] .product-grid, -.shop-root[data-density="spacious"] .product-grid { - gap: calc(var(--space-lg, 1.5rem) * 1.25); -} - -.preview-frame[data-density="compact"] .product-grid, -.shop-root[data-density="compact"] .product-grid { - gap: calc(var(--space-lg, 1.5rem) * 0.75); -} - -/* Density also affects card padding */ -.preview-frame .product-card > div:last-child, -.shop-root .product-card > div:last-child { - padding: var(--space-md, 1rem); -} - -.preview-frame[data-density="spacious"] .product-card > div:last-child, -.shop-root[data-density="spacious"] .product-card > div:last-child { - padding: calc(var(--space-md, 1rem) * 1.25); -} - -.preview-frame[data-density="compact"] .product-card > div:last-child, -.shop-root[data-density="compact"] .product-card > div:last-child { - padding: calc(var(--space-md, 1rem) * 0.75); -} - -/* Product Text Alignment - targets the product info area inside cards */ -.preview-frame .product-card > div:last-child, -.shop-root .product-card > div:last-child { - text-align: var(--t-product-text-align, left); -} - -/* Image Aspect Ratio - targets the image container inside product cards */ -.preview-frame .product-card .product-image-container, -.shop-root .product-card .product-image-container { - aspect-ratio: var(--t-image-aspect-ratio, 1 / 1); -} - -/* Font Size Scale - applied to base font (16px is accessible minimum) */ -.preview-frame, -.shop-root { - font-size: calc(16px * var(--t-font-size-scale, 1)); -} - -/* Heading Weight Override - takes precedence over typography preset */ -.preview-frame h1, -.shop-root h1, -.preview-frame h2, -.shop-root h2, -.preview-frame h3, -.shop-root h3, -.preview-frame h4, -.shop-root h4, -.preview-frame h5, -.shop-root h5, -.preview-frame h6, -.shop-root h6 { - font-weight: var(--t-heading-weight-override, var(--t-heading-weight, 600)) !important; -} - -/* Layout Max Width - applied via data attribute for better specificity */ -.preview-frame[data-layout="contained"] .max-w-7xl, -.shop-root[data-layout="contained"] .max-w-7xl { - max-width: var(--t-layout-max-width, 1100px); -} - -.preview-frame[data-layout="wide"] .max-w-7xl, -.shop-root[data-layout="wide"] .max-w-7xl { - max-width: var(--t-layout-max-width, 1400px); -} - -.preview-frame[data-layout="full"] .max-w-7xl, -.shop-root[data-layout="full"] .max-w-7xl { - max-width: var(--t-layout-max-width, 100%); -} - -/* Button Style - using data attribute approach */ -/* Outline button style */ -.preview-frame[data-button-style="outline"] button[style*="background-color: hsl(var(--t-accent"], -.shop-root[data-button-style="outline"] button[style*="background-color: hsl(var(--t-accent"] { - background-color: transparent !important; - color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)) !important; - border: 2px solid hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)) !important; -} - -/* Soft button style */ -.preview-frame[data-button-style="soft"] button[style*="background-color: hsl(var(--t-accent"], -.shop-root[data-button-style="soft"] button[style*="background-color: hsl(var(--t-accent"] { - background-color: hsl(var(--t-accent-h) var(--t-accent-s) 90%) !important; - color: hsl(var(--t-accent-h) var(--t-accent-s) 30%) !important; - border: 2px solid transparent !important; -} - /* Product Badges */ .product-badge { position: absolute; @@ -452,42 +467,10 @@ opacity: 1; } -/* Only hide primary image on hover when a hover image sibling exists */ .product-card:hover .product-image-primary:has(+ .product-image-hover) { opacity: 0; } -/* Secondary Accent (Hover Colour) Usage */ -/* Applied to interactive elements on hover for visual feedback */ - -/* Links in body text */ -.preview-frame a:not([class*="btn"]):not([class*="button"]):not(.product-card):not(.nav-link):hover, -.shop-root a:not([class*="btn"]):not([class*="button"]):not(.product-card):not(.nav-link):hover { - color: var(--t-secondary-accent, var(--t-text-primary)); -} - -/* Product card hover effect - subtle accent border */ -.preview-frame .product-card:hover, -.shop-root .product-card:hover { - border-color: var(--t-secondary-accent, var(--t-border-default)) !important; -} - -/* Button hover states - darken or use secondary accent */ -.preview-frame button:hover, -.shop-root button:hover, -.preview-frame [role="button"]:hover, -.shop-root [role="button"]:hover { - filter: brightness(0.95); -} - -/* Nav links hover */ -.preview-frame .nav-link:hover, -.shop-root .nav-link:hover, -.preview-frame nav a:hover, -.shop-root nav a:hover { - color: var(--t-secondary-accent, var(--t-text-primary)); -} - /* Social Links */ .social-link:hover { background-color: var(--t-surface-sunken); @@ -511,7 +494,7 @@ transition: transform 0.2s ease; } -/* Lightbox - using native dialog */ +/* Lightbox */ .lightbox { position: fixed; inset: 0; @@ -526,14 +509,14 @@ display: flex; align-items: center; justify-content: center; -} -.lightbox::backdrop { - background: rgba(0, 0, 0, 0.95); -} + &::backdrop { + background: rgba(0, 0, 0, 0.95); + } -.lightbox:not([open]) { - display: none; + &:not([open]) { + display: none; + } } .lightbox-content { @@ -561,15 +544,15 @@ cursor: pointer; transition: background 0.15s ease; z-index: 1; -} -.lightbox-close:hover { - background: rgba(255, 255, 255, 0.1); -} + &:hover { + background: rgba(255, 255, 255, 0.1); + } -.lightbox-close svg { - width: 24px; - height: 24px; + & svg { + width: 24px; + height: 24px; + } } .lightbox-nav { @@ -587,15 +570,15 @@ color: white; cursor: pointer; transition: background 0.15s ease; -} -.lightbox-nav:hover { - background: rgba(255, 255, 255, 0.2); -} + &:hover { + background: rgba(255, 255, 255, 0.2); + } -.lightbox-nav svg { - width: 24px; - height: 24px; + & svg { + width: 24px; + height: 24px; + } } .lightbox-prev { @@ -652,93 +635,3 @@ .pdp-main-image-container { cursor: zoom-in; } - -/* ============================================= - Type Scale Utility Classes - These scale with the font-size setting - ============================================= */ - -/* Body text sizes */ -.preview-frame .t-caption { font-size: var(--t-text-caption); } -.preview-frame .t-small { font-size: var(--t-text-small); } -.preview-frame .t-base { font-size: var(--t-text-base); } -.preview-frame .t-large { font-size: var(--t-text-large); } -.preview-frame .t-xl { font-size: var(--t-text-xl); } -.preview-frame .t-2xl { font-size: var(--t-text-2xl); } - -/* Heading sizes */ -.preview-frame .t-heading-sm { font-size: var(--t-heading-sm); } -.preview-frame .t-heading-md { font-size: var(--t-heading-md); } -.preview-frame .t-heading-lg { font-size: var(--t-heading-lg); } -.preview-frame .t-heading-xl { font-size: var(--t-heading-xl); } -.preview-frame .t-heading-display { font-size: var(--t-heading-display); } - -/* Override Tailwind text-* classes within preview to use our scale */ -.preview-frame .text-xs { font-size: var(--t-text-caption) !important; } -.preview-frame .text-sm { font-size: var(--t-text-small) !important; } -.preview-frame .text-base { font-size: var(--t-text-base) !important; } -.preview-frame .text-lg { font-size: var(--t-text-large) !important; } -.preview-frame .text-xl { font-size: var(--t-text-xl) !important; } -.preview-frame .text-2xl { font-size: var(--t-text-2xl) !important; } - -/* Map larger Tailwind sizes to our heading scale */ -.preview-frame .text-3xl { font-size: var(--t-heading-lg) !important; } -.preview-frame .text-4xl { font-size: var(--t-heading-xl) !important; } -.preview-frame .text-5xl, -.shop-root .text-5xl, -.preview-frame .text-6xl, -.shop-root .text-6xl, -.preview-frame .text-7xl, -.shop-root .text-7xl, -.preview-frame .text-8xl, -.shop-root .text-8xl, -.preview-frame .text-9xl { font-size: var(--t-heading-display) !important; } - -/* ============================================= - Filter Pills (Collection Page) - ============================================= */ - -.preview-frame .filter-pills-container, -.shop-root .filter-pills-container { - scrollbar-width: none; - -webkit-overflow-scrolling: touch; -} - -.preview-frame .filter-pills-container::-webkit-scrollbar, -.shop-root .filter-pills-container::-webkit-scrollbar { - display: none; -} - -.preview-frame .filter-pill, -.shop-root .filter-pill { - flex-shrink: 0; - padding: 0.5rem 1rem; - font-size: var(--t-text-small); - font-weight: 500; - border-radius: var(--t-radius-button); - border: 1px solid var(--t-border-default); - background-color: var(--t-surface-base); - color: var(--t-text-secondary); - cursor: pointer; - transition: all 0.15s ease; - white-space: nowrap; -} - -.preview-frame .filter-pill:hover, -.shop-root .filter-pill:hover { - background-color: var(--t-surface-sunken); - color: var(--t-text-primary); -} - -.preview-frame .filter-pill-active, -.shop-root .filter-pill-active { - background-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); - color: var(--t-text-inverse); - border-color: transparent; -} - -.preview-frame .filter-pill-active:hover, -.shop-root .filter-pill-active:hover { - background-color: hsl(var(--t-accent-h) var(--t-accent-s) calc(var(--t-accent-l) - 5%)); - color: var(--t-text-inverse); -} diff --git a/assets/css/theme-semantic.css b/assets/css/theme-semantic.css index 63f6d8e..91de7cd 100644 --- a/assets/css/theme-semantic.css +++ b/assets/css/theme-semantic.css @@ -1,9 +1,18 @@ /* ======================================== THEME SEMANTIC - Layer 3 Semantic aliases for easy usage + + Uses .themed class as the common selector for both: + - .preview-frame (theme editor) + - .shop-root (public shop pages) ======================================== */ -.preview-frame, .shop-root { +.themed { + /* Apply base theme colors */ + background-color: var(--t-surface-base); + color: var(--t-text-primary); + font-family: var(--t-font-body); + /* Accent color - HSL components set dynamically by CSS generator */ --t-accent-h: 24; --t-accent-s: 95%; @@ -28,19 +37,19 @@ * Body: caption, small, base, large * Display: xl, 2xl (for headings and hero text) */ - --t-text-caption: 0.75em; /* ~12px at 16px base, ~14px at 18px base */ - --t-text-small: 0.875em; /* ~14px at 16px base, ~16px at 18px base */ - --t-text-base: 1em; /* matches base font size setting */ - --t-text-large: 1.125em; /* ~18px at 16px base, ~20px at 18px base */ - --t-text-xl: 1.25em; /* ~20px at 16px base, ~22px at 18px base */ - --t-text-2xl: 1.5em; /* ~24px at 16px base, ~27px at 18px base */ + --t-text-caption: 0.75em; + --t-text-small: 0.875em; + --t-text-base: 1em; + --t-text-large: 1.125em; + --t-text-xl: 1.25em; + --t-text-2xl: 1.5em; /* Fluid heading sizes - scale smoothly between mobile and desktop */ - --t-heading-sm: clamp(1.125rem, 1rem + 0.5vw, 1.25rem); /* 18-20px */ - --t-heading-md: clamp(1.25rem, 1rem + 1vw, 1.5rem); /* 20-24px */ - --t-heading-lg: clamp(1.5rem, 1rem + 2vw, 2rem); /* 24-32px */ - --t-heading-xl: clamp(1.875rem, 1.25rem + 2.5vw, 2.5rem); /* 30-40px */ - --t-heading-display: clamp(2.25rem, 1.5rem + 3vw, 3rem); /* 36-48px */ + --t-heading-sm: clamp(1.125rem, 1rem + 0.5vw, 1.25rem); + --t-heading-md: clamp(1.25rem, 1rem + 1vw, 1.5rem); + --t-heading-lg: clamp(1.5rem, 1rem + 2vw, 2rem); + --t-heading-xl: clamp(1.875rem, 1.25rem + 2.5vw, 2.5rem); + --t-heading-display: clamp(2.25rem, 1.5rem + 3vw, 3rem); /* Layout */ --t-layout-max-width: 1400px; @@ -99,11 +108,11 @@ --transition-fast: var(--p-duration-fast) var(--p-ease-out); --transition-normal: var(--p-duration-normal) var(--p-ease-out); --transition-bounce: var(--p-duration-normal) var(--p-ease-out-back); -} -/* Dark mode accent-subtle override */ -.preview-frame[data-mood="dark"], .shop-root[data-mood="dark"] { - --t-accent-subtle: hsl(var(--t-accent-h) 30% 15%); + /* Dark mode accent-subtle override */ + &[data-mood="dark"] { + --t-accent-subtle: hsl(var(--t-accent-h) 30% 15%); + } } /* ======================================== @@ -111,25 +120,84 @@ Visible focus indicators for all interactive elements ======================================== */ -/* Base focus ring style */ -.shop-container a:focus-visible, -.shop-container button:focus-visible, -.shop-container input:focus-visible, -.shop-container select:focus-visible, -.shop-container textarea:focus-visible, -.shop-container [tabindex]:focus-visible, -.shop-container details summary:focus-visible { - outline: 2px solid hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); - outline-offset: 2px; -} +.shop-container { + line-height: 1.5; -/* Remove default browser outlines when using focus-visible */ -.shop-container a:focus:not(:focus-visible), -.shop-container button:focus:not(:focus-visible), -.shop-container input:focus:not(:focus-visible), -.shop-container select:focus:not(:focus-visible), -.shop-container textarea:focus:not(:focus-visible) { - outline: none; + /* Base focus ring style */ + & a:focus-visible, + & button:focus-visible, + & input:focus-visible, + & select:focus-visible, + & textarea:focus-visible, + & [tabindex]:focus-visible, + & details summary:focus-visible { + outline: 2px solid hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); + outline-offset: 2px; + } + + /* Remove default browser outlines when using focus-visible */ + & a:focus:not(:focus-visible), + & button:focus:not(:focus-visible), + & input:focus:not(:focus-visible), + & select:focus:not(:focus-visible), + & textarea:focus:not(:focus-visible) { + outline: none; + } + + /* Minimum touch target size (44x44px) */ + & .header-icon-btn { + min-width: 44px; + min-height: 44px; + } + + & .filter-pill { + min-height: 44px; + padding-left: 1rem; + padding-right: 1rem; + } + + /* Heading line-heights - tighter for large text */ + & h1 { + line-height: 1.1; + letter-spacing: -0.025em; + } + + & h2 { + line-height: 1.15; + letter-spacing: -0.02em; + } + + & h3 { + line-height: 1.2; + letter-spacing: -0.015em; + } + + & h4, + & h5, + & h6 { + line-height: 1.25; + letter-spacing: -0.01em; + } + + /* Small text - slightly wider tracking for readability */ + & .text-xs, + & .text-sm, + & .t-caption, + & .t-small { + letter-spacing: 0.01em; + } + + /* Prose content - ideal 65ch for comfortable reading */ + & .prose, + & .product-description, + & .about-content { + max-width: 65ch; + } + + /* Slightly wider for collection/category descriptions */ + & .collection-description { + max-width: 75ch; + } } /* Skip link for keyboard navigation */ @@ -146,22 +214,10 @@ font-weight: 600; text-decoration: none; transition: top 0.2s ease; -} -.skip-link:focus { - top: 1rem; -} - -/* Ensure minimum touch target size (44x44px) */ -.shop-container .header-icon-btn { - min-width: 44px; - min-height: 44px; -} - -.shop-container .filter-pill { - min-height: 44px; - padding-left: 1rem; - padding-right: 1rem; + &:focus { + top: 1rem; + } } /* Nav link styling with active state indicator */ @@ -169,71 +225,13 @@ padding: 0.5rem 0; border-bottom: 2px solid transparent; transition: border-color 0.2s ease, color 0.2s ease; -} - -.shop-nav a:hover { - color: var(--t-text-primary); -} - -.shop-nav a[aria-current="page"] { - color: var(--t-text-primary); - border-bottom-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); -} - -/* ======================================== - TYPOGRAPHY - Line Heights & Letter Spacing - Research-backed values for optimal readability - ======================================== */ - -/* Body text line-height - WCAG 1.5 minimum */ -.shop-container { - line-height: 1.5; -} - -/* Heading line-heights - tighter for large text (inverse relationship) */ -.shop-container h1 { - line-height: 1.1; - letter-spacing: -0.025em; -} - -.shop-container h2 { - line-height: 1.15; - letter-spacing: -0.02em; -} - -.shop-container h3 { - line-height: 1.2; - letter-spacing: -0.015em; -} - -.shop-container h4, -.shop-container h5, -.shop-container h6 { - line-height: 1.25; - letter-spacing: -0.01em; -} - -/* Small text - slightly wider tracking for readability */ -.shop-container .text-xs, -.shop-container .text-sm, -.shop-container .t-caption, -.shop-container .t-small { - letter-spacing: 0.01em; -} - -/* ======================================== - TYPOGRAPHY - Measure (Line Length) - Optimal readability: 45-75 characters - ======================================== */ - -/* Prose content - ideal 65ch for comfortable reading */ -.shop-container .prose, -.shop-container .product-description, -.shop-container .about-content { - max-width: 65ch; -} - -/* Slightly wider for collection/category descriptions */ -.shop-container .collection-description { - max-width: 75ch; + + &:hover { + color: var(--t-text-primary); + } + + &[aria-current="page"] { + color: var(--t-text-primary); + border-bottom-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); + } } diff --git a/lib/simpleshop_theme/theme/css_generator.ex b/lib/simpleshop_theme/theme/css_generator.ex index ce419c9..6259419 100644 --- a/lib/simpleshop_theme/theme/css_generator.ex +++ b/lib/simpleshop_theme/theme/css_generator.ex @@ -4,6 +4,10 @@ defmodule SimpleshopTheme.Theme.CSSGenerator do This module converts ThemeSettings into CSS variables that bridge the gap between fixed primitives (Layer 1) and semantic aliases (Layer 3). + + For the shop (public pages), this generates ALL theme tokens inline, so the + shop doesn't need the attribute-based selectors in theme-layer2-attributes.css. + The theme editor still uses those selectors for live preview switching. """ alias SimpleshopTheme.Settings.ThemeSettings @@ -12,11 +16,26 @@ defmodule SimpleshopTheme.Theme.CSSGenerator do Generates CSS for theme settings. Returns a string of CSS custom properties that can be injected into a