replace Tailwind in content + collection, remove shop Tailwind entirely (Phase 5c)

- Replace all Tailwind utilities in content.ex and collection.ex with
  semantic CSS classes (content body, contact form, cards, reviews, etc.)
- Delete app-shop.css (Tailwind shop entry point)
- Remove shop Tailwind config from config.exs, dev.exs, mix.exs
- Remove shop Tailwind stylesheet link from shop_root.html.heex
- Add collection filter bar, empty state, and select dropdown styles
- Fix filter pill sizing (use theme font vars instead of hardcoded rem)
- Fix active pill contrast (tinted accent background + dark accent text)
- Fix --t-text-on-accent fallback for pill legibility
- Add padding/font-size to .themed-select

Shop pages now use zero Tailwind. Admin Tailwind remains for Phase 6.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-17 19:07:15 +00:00
parent 04b6ee3f37
commit f5f6374f7b
13 changed files with 530 additions and 309 deletions

View File

@@ -1,59 +0,0 @@
/* 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";
@source "../../lib/simpleshop_theme_web/components/shop_components.ex";
@source "../../lib/simpleshop_theme_web/components/shop_components";
@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

@@ -1,16 +1,16 @@
/* Shop CSS — hand-written, zero-framework stylesheet.
Layered cascade: later layers beat earlier ones, no !important needed.
This file runs alongside app-shop.css during migration (Phases 1-4).
After Phase 5 it replaces app-shop.css entirely. */
Layered cascade: later layers beat earlier ones, no !important needed. */
@layer reset, primitives, tokens, components, layout, utilities, overrides;
@import "./shop/reset.css";
/* Theme CSS stays in the existing files (loaded via app-shop.css).
Primitives and tokens will be wrapped in @layer when
app-shop.css is removed in Phase 5. */
/* Theme CSS — unlayered so it overrides component layers (intentional).
Primitives set CSS custom properties, layer2 sets theme-aware rules,
semantic sets base styles on .themed containers. */
@import "./theme-primitives.css";
@import "./theme-layer2-attributes.css";
@import "./theme-semantic.css";
@import "./shop/components.css";
@import "./shop/layout.css";

View File

@@ -293,7 +293,7 @@
.category-nav {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1rem;
max-width: 48rem;
margin-inline: auto;
@@ -317,7 +317,8 @@
.category-image {
width: 6rem;
height: 6rem;
max-width: 100%;
aspect-ratio: 1;
border-radius: 9999px;
background-color: #e5e7eb;
background-size: cover;
@@ -435,7 +436,7 @@
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 1rem;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
@@ -445,6 +446,21 @@
overflow-x: auto;
}
/* ── Collection empty state ── */
.collection-empty {
text-align: center;
padding-block: 4rem;
color: var(--t-text-secondary);
}
.collection-empty-link {
display: inline-block;
margin-top: 1rem;
color: var(--t-text-accent, hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)));
text-decoration: underline;
}
/* ── Breadcrumb ── */
.breadcrumb {
@@ -501,6 +517,10 @@
object-fit: cover;
}
.pdp-thumbnail-active {
border: 2px solid hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
}
/* ── Variant selector ── */
.variant-selector {
@@ -827,6 +847,7 @@
justify-content: center;
position: relative;
color: var(--t-text-secondary);
text-decoration: none;
border-radius: var(--t-radius-button);
transition: all 0.2s ease;
@@ -1124,6 +1145,7 @@
.footer-link {
color: var(--t-text-secondary);
text-decoration: none;
cursor: pointer;
transition: opacity 0.15s ease;
@@ -1158,6 +1180,11 @@
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
&.open {
opacity: 1;
visibility: visible;
}
}
.cart-drawer {
@@ -1174,6 +1201,10 @@
flex-direction: column;
transition: right 0.3s ease;
box-shadow: -4px 0 20px rgba(0, 0, 0, 0.15);
&.open {
right: 0;
}
}
.cart-drawer-header {
@@ -1265,6 +1296,7 @@
.cart-item-image {
display: block;
text-decoration: none;
border-radius: var(--t-radius-card);
background-size: cover;
background-position: center;
@@ -1589,6 +1621,12 @@
margin-bottom: var(--space-lg);
border-radius: var(--t-radius-image);
overflow: hidden;
& img {
width: 100%;
height: 300px;
object-fit: cover;
}
}
.content-text {
@@ -1601,14 +1639,56 @@
/* ── Contact form ── */
.contact-form-card {
padding: 2rem;
}
.contact-form-heading {
font-family: var(--t-font-heading);
font-weight: 700;
font-size: var(--t-text-xl);
color: var(--t-text-primary);
margin-bottom: 0.5rem;
}
.contact-form-meta {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-bottom: 1.5rem;
font-size: var(--t-text-small);
color: var(--t-text-secondary);
}
.contact-form-spacer {
margin-bottom: 1rem;
}
.contact-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.contact-form-label {
display: block;
font-weight: 500;
margin-bottom: 0.5rem;
color: var(--t-text-primary);
}
.contact-form .shop-input,
.contact-form .shop-textarea {
width: 100%;
padding: 0.5rem 1rem;
}
.contact-form-submit {
width: 100%;
padding: 0.75rem 1.5rem;
font-weight: 600;
}
/* ── Accent email link ── */
.accent-email {
@@ -1617,20 +1697,61 @@
/* ── Card shared styles (info, tracking, newsletter, social cards) ── */
.card-section {
padding: 1.5rem;
}
.card-heading {
font-weight: 700;
margin-bottom: 0.75rem;
color: var(--t-text-primary);
}
.card-text {
font-size: var(--t-text-small);
color: var(--t-text-secondary);
}
.card-text--spaced {
margin-bottom: 1rem;
}
.card-inline-form {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
& .themed-input {
flex: 1;
min-width: 0;
padding: 0.5rem 0.75rem;
font-size: var(--t-text-small);
}
& .themed-button {
padding: 0.5rem 1rem;
font-size: var(--t-text-small);
font-weight: 500;
white-space: nowrap;
}
}
/* ── Info card ── */
.info-card-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
font-size: var(--t-text-small);
color: var(--t-text-secondary);
}
.info-card-item {
display: flex;
align-items: flex-start;
gap: 0.5rem;
}
.info-card-bullet {
color: var(--t-text-tertiary);
}
@@ -1642,52 +1763,150 @@
/* ── Contact info card ── */
.contact-info-email {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
text-decoration: none;
& svg {
width: 1rem;
height: 1rem;
}
}
/* ── Email input min-width ── */
/* ── Newsletter card ── */
.email-input {
min-width: 150px;
.newsletter-heading {
font-family: var(--t-font-heading);
font-weight: 700;
font-size: var(--t-text-xl);
color: var(--t-text-primary);
margin-bottom: 0.5rem;
}
/* ── Social links card ── */
.social-link-card-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.social-link-card-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
font-size: var(--t-text-small);
text-decoration: none;
transition: opacity 0.15s ease;
&:hover {
opacity: 0.8;
}
}
.social-link-card-icon {
color: var(--t-text-secondary);
& svg {
width: 1.25rem;
height: 1.25rem;
}
}
/* ── Social links (footer) ── */
.social-links {
display: flex;
gap: 1rem;
justify-content: center;
}
.social-link {
width: 2.25rem;
height: 2.25rem;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
transition: opacity 0.15s ease;
color: var(--t-text-secondary);
border-radius: var(--t-radius-button);
& svg {
width: 1.25rem;
height: 1.25rem;
}
}
/* ── Star rating ── */
.star-rating {
display: flex;
gap: 0.125rem;
& svg {
width: 1rem;
height: 1rem;
}
&[data-size="md"] svg {
width: 1.25rem;
height: 1.25rem;
}
}
/* ── Trust badges ── */
.trust-badges {
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 1rem;
background-color: var(--t-surface-sunken);
border-radius: var(--t-radius-card);
}
.trust-badge-item {
display: flex;
align-items: flex-start;
gap: 0.75rem;
}
.trust-badge-icon {
flex-shrink: 0;
margin-top: 0.125rem;
color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
& svg {
width: 1.25rem;
height: 1.25rem;
}
}
.trust-badge-title {
font-weight: 600;
color: var(--t-text-primary);
}
.trust-badge-text {
font-size: var(--t-text-small);
color: var(--t-text-secondary);
}
/* ── Page title ── */
.page-title {
font-family: var(--t-font-heading);
font-weight: var(--t-heading-weight);
font-size: var(--t-heading-lg);
color: var(--t-text-primary);
margin-bottom: 2rem;
}
/* ── Reviews section ── */
.pdp-reviews {
@@ -1695,37 +1914,121 @@
}
.reviews-summary {
display: flex;
justify-content: space-between;
align-items: center;
padding-block: 1.5rem;
cursor: pointer;
list-style: none;
color: var(--t-text-primary);
&::-webkit-details-marker {
display: none;
}
}
.reviews-header-left {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.reviews-heading {
font-family: var(--t-font-heading);
font-weight: 700;
font-size: var(--t-text-2xl);
color: var(--t-text-primary);
}
.reviews-rating-group {
display: flex;
align-items: center;
gap: 0.5rem;
}
.reviews-count {
font-size: var(--t-text-small);
color: var(--t-text-secondary);
}
.reviews-chevron {
width: 1.25rem;
height: 1.25rem;
flex-shrink: 0;
transition: transform 0.2s ease;
}
details[open] > .reviews-summary .reviews-chevron {
transform: rotate(180deg);
}
.reviews-body {
padding-bottom: 2rem;
}
.reviews-list {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.reviews-load-more {
display: block;
margin-top: 1.5rem;
margin-inline: auto;
padding: 0.5rem 1.5rem;
font-size: var(--t-text-small);
font-weight: 500;
}
/* ── Review card ── */
.review-card {
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--t-border-subtle);
}
.review-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.review-date {
font-size: var(--t-text-caption);
color: var(--t-text-tertiary);
}
.review-title {
font-weight: 600;
margin-bottom: 0.25rem;
color: var(--t-text-primary);
}
.review-body {
font-size: var(--t-text-small);
margin-bottom: 0.75rem;
color: var(--t-text-secondary);
line-height: 1.6;
}
.review-footer {
display: flex;
align-items: center;
gap: 0.5rem;
}
.review-author {
font-size: var(--t-text-small);
font-weight: 500;
color: var(--t-text-primary);
}
.review-verified {
font-size: var(--t-text-caption);
padding: 0.125rem 0.5rem;
border-radius: var(--t-radius-sm, 4px);
background-color: var(--t-surface-sunken);
color: var(--t-text-tertiary);
}
@@ -1737,10 +2040,13 @@
}
.rich-text-lead {
font-size: var(--t-text-large);
margin-bottom: 1rem;
color: var(--t-text-primary);
}
.rich-text-paragraph {
margin-bottom: 1rem;
color: var(--t-text-secondary);
}
@@ -1749,17 +2055,54 @@
font-weight: var(--t-heading-weight);
font-size: var(--t-text-xl);
color: var(--t-text-primary);
margin-top: 2rem;
margin-bottom: 0.75rem;
}
.rich-text-closing {
margin-top: 2rem;
color: var(--t-text-secondary);
}
.rich-text-list {
margin-bottom: 1rem;
margin-left: 1.5rem;
list-style: disc;
color: var(--t-text-secondary);
& li {
margin-bottom: 0.25rem;
}
}
/* ── Flash messages ── */
.shop-flash-group {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 200;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.shop-flash {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: var(--t-radius-card);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
max-width: 24rem;
animation: flash-in 0.3s ease-out;
background-color: var(--t-surface-raised, #fff);
color: var(--t-text-primary);
& p {
font-size: var(--t-text-small);
margin: 0;
}
}
.shop-flash--info {
@@ -1770,6 +2113,12 @@
border: 1px solid hsl(0 70% 50% / 0.3);
}
.shop-flash-icon {
flex-shrink: 0;
width: 1.25rem;
height: 1.25rem;
}
.shop-flash-icon--info {
color: var(--t-accent);
}
@@ -1778,6 +2127,16 @@
color: hsl(0 70% 50%);
}
@keyframes flash-in {
from { opacity: 0; transform: translateX(1rem); }
to { opacity: 1; transform: translateX(0); }
}
/* Transition classes for JS.hide flash dismiss */
.fade-out { transition: opacity 200ms ease-out; }
.fade-out-from { opacity: 1; }
.fade-out-to { opacity: 0; }
/* ── Link buttons (make <a> links styled as buttons) ── */
.themed-button:where(a),
@@ -2067,20 +2426,6 @@
margin-inline: auto;
}
/* ── Screen reader only ── */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* ── Responsive breakpoints ── */
@media (min-width: 640px) {
@@ -2093,9 +2438,11 @@
grid-template-columns: repeat(2, 1fr);
}
.hero-cta-group { flex-direction: row; }
.reviews-header-left { flex-direction: row; align-items: center; gap: 1rem; }
}
@media (min-width: 768px) {
.page-title { font-size: var(--t-heading-xl); }
.shop-container[data-bottom-nav] { padding-bottom: 0; }
.shop-header { padding: 1rem 2rem; }
.shop-nav { display: flex; gap: 1.5rem; }

View File

@@ -263,6 +263,9 @@
color: var(--t-text-primary);
border: 1px solid var(--t-border-default);
border-radius: var(--t-radius-input);
padding: 0.375rem 0.75rem;
font-size: var(--t-text-small);
font-family: var(--t-font-body);
&:focus {
outline: none;
@@ -293,8 +296,7 @@
}
}
/* Shop nav display - hidden on mobile, flex on md+ (via Tailwind classes in component) */
/* Note: Removed explicit display:flex here as it overrides Tailwind's hidden class */
/* Shop nav display - handled by components.css (hidden on mobile, flex on md+) */
/* =============================================
STANDALONE COMPONENTS (Context-independent)

View File

@@ -210,23 +210,30 @@
.collection-filter-pills {
display: flex;
flex-wrap: wrap;
gap: 0.375rem;
gap: 0.5rem;
}
.collection-filter-pill {
display: inline-block;
padding: 0.375rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-size: var(--t-text-small);
white-space: nowrap;
text-decoration: none;
transition: opacity 0.15s;
background-color: var(--t-surface-raised);
color: var(--t-text-primary);
border: 1px solid var(--t-border-default);
&:hover:not(.active) {
opacity: 0.8;
}
&.active {
background-color: var(--t-accent);
color: var(--t-text-on-accent);
font-weight: 500;
background-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l) / 0.12);
color: hsl(var(--t-accent-h) var(--t-accent-s) calc(var(--t-accent-l) - 15%));
border-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l) / 0.25);
font-weight: 600;
}
}
@@ -248,15 +255,6 @@
}
@media (min-width: 640px) {
.collection-filter-pills {
gap: 0.5rem;
}
.collection-filter-pill {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
/* Desktop always wraps — no scroll needed */
.collection-filters.is-scrollable {
overflow-x: visible;