replace Tailwind utilities in product + cart components with CSS (Phase 5b)
Remove ~140 Tailwind utility classes from product.ex and cart.ex, replacing with semantic CSS classes in components.css. Delete helper functions that generated Tailwind class strings (card_classes, image_container_classes, content_padding_class, title_classes, hero_cta_classes, grid_classes). Use data-* attributes for variant styling, grid columns, and sticky positioning. Update theme-layer2 selectors for renamed classes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fc9c33ab0c
commit
04b6ee3f37
@ -18,6 +18,8 @@
|
|||||||
.product-card {
|
.product-card {
|
||||||
background-color: var(--t-surface-raised);
|
background-color: var(--t-surface-raised);
|
||||||
border-radius: var(--t-radius-card);
|
border-radius: var(--t-radius-card);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&[data-variant="default"],
|
&[data-variant="default"],
|
||||||
&[data-variant="compact"] {
|
&[data-variant="compact"] {
|
||||||
@ -33,6 +35,10 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[data-variant="featured"]:hover {
|
||||||
|
transform: translateY(-0.25rem);
|
||||||
|
}
|
||||||
|
|
||||||
&[data-clickable] {
|
&[data-clickable] {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@ -45,10 +51,32 @@
|
|||||||
|
|
||||||
.product-card-image-wrap {
|
.product-card-image-wrap {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card-image-wrap img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image-primary {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card-placeholder {
|
.product-card-placeholder {
|
||||||
color: var(--t-text-tertiary);
|
color: var(--t-text-tertiary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-icon {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card-category {
|
.product-card-category {
|
||||||
@ -56,10 +84,19 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
font-size: var(--t-text-caption);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card-category:where(a):hover {
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card-title {
|
.product-card-title {
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card[data-variant="default"] .product-card-title,
|
.product-card[data-variant="default"] .product-card-title,
|
||||||
@ -67,8 +104,28 @@
|
|||||||
font-family: var(--t-font-heading);
|
font-family: var(--t-font-heading);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.product-card[data-variant="featured"] .product-card-title {
|
||||||
|
font-size: var(--t-text-small);
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card[data-variant="compact"] .product-card-title {
|
||||||
|
font-size: var(--t-text-small);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card[data-variant="minimal"] .product-card-title {
|
||||||
|
font-size: var(--t-text-caption);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.product-card-delivery {
|
.product-card-delivery {
|
||||||
color: var(--t-text-tertiary);
|
color: var(--t-text-tertiary);
|
||||||
|
font-size: var(--t-text-caption);
|
||||||
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Product prices (shared between cards and PDP) ── */
|
/* ── Product prices (shared between cards and PDP) ── */
|
||||||
@ -79,6 +136,7 @@
|
|||||||
|
|
||||||
.product-price--compare {
|
.product-price--compare {
|
||||||
color: var(--t-text-tertiary);
|
color: var(--t-text-tertiary);
|
||||||
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-price--regular {
|
.product-price--regular {
|
||||||
@ -89,14 +147,66 @@
|
|||||||
color: var(--t-text-secondary);
|
color: var(--t-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Card-variant price sizing */
|
||||||
|
.product-card[data-variant="default"] .product-price--sale,
|
||||||
|
.product-card[data-variant="default"] .product-price--regular {
|
||||||
|
font-size: var(--t-text-large);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card[data-variant="default"] .product-price--compare {
|
||||||
|
font-size: var(--t-text-small);
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card[data-variant="featured"] .product-price--secondary {
|
||||||
|
font-size: var(--t-text-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card[data-variant="featured"] .product-price--compare {
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card[data-variant="compact"] .product-price--regular {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card[data-variant="minimal"] .product-price--secondary {
|
||||||
|
font-size: var(--t-text-caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PDP price sizing */
|
||||||
|
.pdp-price-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdp-price-row .product-price--sale,
|
||||||
|
.pdp-price-row .product-price--regular {
|
||||||
|
font-size: var(--t-heading-lg);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdp-price-row .product-price--compare {
|
||||||
|
font-size: var(--t-text-xl);
|
||||||
|
}
|
||||||
|
|
||||||
.sale-badge {
|
.sale-badge {
|
||||||
background-color: var(--t-sale-color);
|
background-color: var(--t-sale-color);
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: var(--t-text-small);
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: var(--t-radius-sm, 4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Hero section ── */
|
/* ── Hero section ── */
|
||||||
|
|
||||||
.hero-section {
|
.hero-section {
|
||||||
padding: var(--space-2xl) var(--space-lg);
|
padding: var(--space-2xl) var(--space-lg);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
&[data-background="base"] {
|
&[data-background="base"] {
|
||||||
background-color: var(--t-surface-base);
|
background-color: var(--t-surface-base);
|
||||||
@ -105,21 +215,73 @@
|
|||||||
&[data-background="sunken"] {
|
&[data-background="sunken"] {
|
||||||
background-color: var(--t-surface-sunken);
|
background-color: var(--t-surface-sunken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& .t-heading {
|
||||||
|
font-size: var(--t-heading-lg);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-section--page {
|
.hero-section--page {
|
||||||
padding-top: var(--space-2xl);
|
padding-top: var(--space-2xl);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
& .t-heading {
|
||||||
|
font-size: var(--t-heading-xl);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .hero-description {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
max-width: 42rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-error {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
& .t-heading {
|
||||||
|
font-size: var(--t-heading-lg);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .hero-description {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
max-width: 28rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-pre-title {
|
.hero-pre-title {
|
||||||
font-family: var(--t-font-heading);
|
font-family: var(--t-font-heading);
|
||||||
font-weight: var(--t-heading-weight);
|
font-weight: var(--t-heading-weight);
|
||||||
color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
|
color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
|
||||||
|
font-size: var(--t-heading-display);
|
||||||
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-description {
|
.hero-description {
|
||||||
color: var(--t-text-secondary);
|
color: var(--t-text-secondary);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
font-size: var(--t-text-large);
|
||||||
|
max-width: 32rem;
|
||||||
|
margin-inline: auto;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-cta-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-cta {
|
||||||
|
padding: 0.75rem 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Category nav ── */
|
/* ── Category nav ── */
|
||||||
@ -129,14 +291,49 @@
|
|||||||
background-color: var(--t-surface-base);
|
background-color: var(--t-surface-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.category-nav {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 1rem;
|
||||||
|
max-width: 48rem;
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.category-card {
|
.category-card {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-card:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-image {
|
||||||
|
width: 6rem;
|
||||||
|
height: 6rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-card:hover .category-image {
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-name {
|
.category-name {
|
||||||
font-family: var(--t-font-body);
|
font-family: var(--t-font-body);
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
|
font-size: var(--t-text-small);
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Featured products section ── */
|
/* ── Featured products section ── */
|
||||||
@ -144,6 +341,16 @@
|
|||||||
.featured-section {
|
.featured-section {
|
||||||
padding: var(--space-xl) var(--space-lg);
|
padding: var(--space-xl) var(--space-lg);
|
||||||
background-color: var(--t-surface-sunken);
|
background-color: var(--t-surface-sunken);
|
||||||
|
|
||||||
|
& .t-heading {
|
||||||
|
font-size: var(--t-text-2xl);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-cta-wrap {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.outline-button {
|
.outline-button {
|
||||||
@ -153,6 +360,10 @@
|
|||||||
border-radius: var(--t-radius-button);
|
border-radius: var(--t-radius-button);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Image + text section ── */
|
/* ── Image + text section ── */
|
||||||
@ -160,21 +371,38 @@
|
|||||||
.image-text-section {
|
.image-text-section {
|
||||||
padding: var(--space-2xl) var(--space-lg);
|
padding: var(--space-2xl) var(--space-lg);
|
||||||
background-color: var(--t-surface-base);
|
background-color: var(--t-surface-base);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 3rem;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
& .t-heading {
|
||||||
|
font-size: var(--t-text-2xl);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-text-image {
|
.image-text-image {
|
||||||
border-radius: var(--t-radius-image);
|
border-radius: var(--t-radius-image);
|
||||||
|
height: 18rem;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-text-body {
|
.image-text-body {
|
||||||
color: var(--t-text-secondary);
|
color: var(--t-text-secondary);
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
|
font-size: var(--t-text-base);
|
||||||
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accent-link {
|
.accent-link {
|
||||||
color: var(--t-accent-text, hsl(var(--t-accent-h) var(--t-accent-s) 38%));
|
color: var(--t-accent-text, hsl(var(--t-accent-h) var(--t-accent-s) 38%));
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-size: var(--t-text-small);
|
||||||
|
font-weight: 500;
|
||||||
|
transition: color 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Collection header ── */
|
/* ── Collection header ── */
|
||||||
@ -182,12 +410,41 @@
|
|||||||
.collection-header-wrap {
|
.collection-header-wrap {
|
||||||
background-color: var(--t-surface-raised);
|
background-color: var(--t-surface-raised);
|
||||||
border-color: var(--t-border-default);
|
border-color: var(--t-border-default);
|
||||||
|
border-bottom: 1px solid var(--t-border-default);
|
||||||
|
|
||||||
|
& .t-heading {
|
||||||
|
font-size: var(--t-heading-lg);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-header-inner {
|
||||||
|
max-width: 80rem;
|
||||||
|
margin-inline: auto;
|
||||||
|
padding: 2rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collection-header-meta {
|
.collection-header-meta {
|
||||||
color: var(--t-text-secondary);
|
color: var(--t-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Filter bar ── */
|
||||||
|
|
||||||
|
.filter-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-pills-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Breadcrumb ── */
|
/* ── Breadcrumb ── */
|
||||||
|
|
||||||
.breadcrumb {
|
.breadcrumb {
|
||||||
@ -196,12 +453,22 @@
|
|||||||
& [aria-current="page"] {
|
& [aria-current="page"] {
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Related products ── */
|
/* ── Related products ── */
|
||||||
|
|
||||||
.related-section {
|
.related-section {
|
||||||
border-top: 1px solid var(--t-border-default);
|
border-top: 1px solid var(--t-border-default);
|
||||||
|
padding-block: 3rem;
|
||||||
|
|
||||||
|
& .t-heading {
|
||||||
|
font-size: var(--t-text-2xl);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── PDP gallery ── */
|
/* ── PDP gallery ── */
|
||||||
@ -209,16 +476,42 @@
|
|||||||
.pdp-gallery-frame {
|
.pdp-gallery-frame {
|
||||||
border-radius: var(--t-radius-image);
|
border-radius: var(--t-radius-image);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdp-gallery-single img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pdp-thumbnail {
|
.pdp-thumbnail {
|
||||||
border-radius: var(--t-radius-image);
|
border-radius: var(--t-radius-image);
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
overflow: hidden;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdp-thumbnail img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Variant selector ── */
|
/* ── Variant selector ── */
|
||||||
|
|
||||||
|
.variant-selector {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.variant-label {
|
.variant-label {
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
|
display: block;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.variant-label-value {
|
.variant-label-value {
|
||||||
@ -226,8 +519,21 @@
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.variant-options {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.color-swatch {
|
.color-swatch {
|
||||||
border-color: var(--t-border-default);
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
border: 2px solid var(--t-border-default);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
&[aria-pressed="true"] {
|
&[aria-pressed="true"] {
|
||||||
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));
|
||||||
@ -240,6 +546,10 @@
|
|||||||
border-radius: var(--t-radius-button);
|
border-radius: var(--t-radius-button);
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&[aria-pressed="true"] {
|
&[aria-pressed="true"] {
|
||||||
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));
|
||||||
@ -249,30 +559,60 @@
|
|||||||
|
|
||||||
/* ── Quantity selector ── */
|
/* ── Quantity selector ── */
|
||||||
|
|
||||||
|
.quantity-selector {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.qty-label {
|
.qty-label {
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
|
display: block;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qty-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qty-group {
|
.qty-group {
|
||||||
border: 2px solid var(--t-border-default);
|
border: 2px solid var(--t-border-default);
|
||||||
border-radius: var(--t-radius-input);
|
border-radius: var(--t-radius-input);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qty-btn {
|
.qty-btn {
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.qty-display {
|
.qty-display {
|
||||||
border-color: var(--t-border-default);
|
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-inline: 2px solid var(--t-border-default);
|
||||||
|
min-width: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stock-in {
|
.stock-in {
|
||||||
color: var(--t-text-tertiary);
|
color: var(--t-text-tertiary);
|
||||||
|
font-size: var(--t-text-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stock-out {
|
.stock-out {
|
||||||
color: var(--t-sale-color);
|
color: var(--t-sale-color);
|
||||||
|
font-size: var(--t-text-small);
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Add to cart ── */
|
/* ── Add to cart ── */
|
||||||
@ -280,6 +620,17 @@
|
|||||||
.atc-wrap {
|
.atc-wrap {
|
||||||
background-color: var(--t-surface-base);
|
background-color: var(--t-surface-base);
|
||||||
border-color: var(--t-border-subtle);
|
border-color: var(--t-border-subtle);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
&[data-sticky="true"] {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
padding-block: 0.75rem;
|
||||||
|
margin-inline: -1rem;
|
||||||
|
padding-inline: 1rem;
|
||||||
|
border-top: 1px solid var(--t-border-subtle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.atc-btn {
|
.atc-btn {
|
||||||
@ -288,6 +639,11 @@
|
|||||||
border-radius: var(--t-radius-button);
|
border-radius: var(--t-radius-button);
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: var(--t-text-large);
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
background-color: var(--t-border-default);
|
background-color: var(--t-border-default);
|
||||||
@ -299,10 +655,35 @@
|
|||||||
|
|
||||||
.accordion-summary {
|
.accordion-summary {
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-block: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
&::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-icon {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
details[open] > .accordion-summary .accordion-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion-body {
|
.accordion-body {
|
||||||
color: var(--t-text-secondary);
|
color: var(--t-text-secondary);
|
||||||
|
padding-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Product details ── */
|
/* ── Product details ── */
|
||||||
@ -311,6 +692,20 @@
|
|||||||
border-top: 1px solid var(--t-border-subtle);
|
border-top: 1px solid var(--t-border-subtle);
|
||||||
border-bottom: 1px solid var(--t-border-subtle);
|
border-bottom: 1px solid var(--t-border-subtle);
|
||||||
border-color: var(--t-border-subtle);
|
border-color: var(--t-border-subtle);
|
||||||
|
margin-top: 2rem;
|
||||||
|
|
||||||
|
& > * + * {
|
||||||
|
border-top: 1px solid var(--t-border-subtle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-description {
|
||||||
|
line-height: 1.625;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-table {
|
||||||
|
width: 100%;
|
||||||
|
font-size: var(--t-text-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
.details-table-row {
|
.details-table-row {
|
||||||
@ -319,10 +714,29 @@
|
|||||||
|
|
||||||
.details-th {
|
.details-th {
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
|
text-align: left;
|
||||||
|
padding-block: 0.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-td {
|
||||||
|
padding-block: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details-subheading {
|
.details-subheading {
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-shipping {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-shipping-text {
|
||||||
|
font-size: var(--t-text-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Announcement bar ── */
|
/* ── Announcement bar ── */
|
||||||
@ -784,6 +1198,11 @@
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--t-text-secondary);
|
color: var(--t-text-secondary);
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-drawer-items {
|
.cart-drawer-items {
|
||||||
@ -829,6 +1248,7 @@
|
|||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: var(--t-font-body);
|
font-family: var(--t-font-body);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Cart item row ── */
|
/* ── Cart item row ── */
|
||||||
@ -910,6 +1330,8 @@
|
|||||||
.cart-qty-group {
|
.cart-qty-group {
|
||||||
border: 1px solid var(--t-border-default);
|
border: 1px solid var(--t-border-default);
|
||||||
border-radius: var(--t-radius-input);
|
border-radius: var(--t-radius-input);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-qty-btn {
|
.cart-qty-btn {
|
||||||
@ -917,13 +1339,15 @@
|
|||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-qty-display {
|
.cart-qty-display {
|
||||||
border-color: var(--t-border-default);
|
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
min-width: 2rem;
|
min-width: 2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-inline: 1px solid var(--t-border-default);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-qty-text {
|
.cart-qty-text {
|
||||||
@ -933,6 +1357,7 @@
|
|||||||
|
|
||||||
.cart-item-price-col {
|
.cart-item-price-col {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item-price {
|
.cart-item-price {
|
||||||
@ -961,10 +1386,20 @@
|
|||||||
|
|
||||||
.cart-empty {
|
.cart-empty {
|
||||||
color: var(--t-text-secondary);
|
color: var(--t-text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
padding-block: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-empty-icon {
|
.cart-empty-icon {
|
||||||
color: var(--t-text-tertiary);
|
color: var(--t-text-tertiary);
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
margin-inline: auto;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-empty p {
|
||||||
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-continue-link {
|
.cart-continue-link {
|
||||||
@ -980,25 +1415,66 @@
|
|||||||
|
|
||||||
/* ── Cart page item (full size) ── */
|
/* ── Cart page item (full size) ── */
|
||||||
|
|
||||||
|
.cart-page-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.cart-page-image {
|
.cart-page-image {
|
||||||
border-radius: var(--t-radius-image);
|
border-radius: var(--t-radius-image);
|
||||||
|
width: 6rem;
|
||||||
|
height: 6rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
& img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-page-item-info {
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-page-item-name {
|
.cart-page-item-name {
|
||||||
font-family: var(--t-font-heading);
|
font-family: var(--t-font-heading);
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-page-item-variant {
|
.cart-page-item-variant {
|
||||||
color: var(--t-text-secondary);
|
color: var(--t-text-secondary);
|
||||||
|
font-size: var(--t-text-small);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-page-item-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-page-item-remove {
|
.cart-page-item-remove {
|
||||||
color: var(--t-text-tertiary);
|
color: var(--t-text-tertiary);
|
||||||
|
font-size: var(--t-text-small);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-page-item-price-col {
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-page-item-price {
|
.cart-page-item-price {
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: var(--t-text-large);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Delivery line ── */
|
/* ── Delivery line ── */
|
||||||
@ -1007,12 +1483,21 @@
|
|||||||
font-family: var(--t-font-body);
|
font-family: var(--t-font-body);
|
||||||
font-size: var(--t-text-small);
|
font-size: var(--t-text-small);
|
||||||
color: var(--t-text-secondary);
|
color: var(--t-text-secondary);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
& form {
|
& form {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delivery-line-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.delivery-select {
|
.delivery-select {
|
||||||
appearance: auto;
|
appearance: auto;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@ -1027,9 +1512,30 @@
|
|||||||
|
|
||||||
/* ── Order summary ── */
|
/* ── Order summary ── */
|
||||||
|
|
||||||
|
.order-summary-card {
|
||||||
|
padding: 1.5rem;
|
||||||
|
position: sticky;
|
||||||
|
top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.order-summary-heading {
|
.order-summary-heading {
|
||||||
font-family: var(--t-font-heading);
|
font-family: var(--t-font-heading);
|
||||||
color: var(--t-text-primary);
|
color: var(--t-text-primary);
|
||||||
|
font-size: var(--t-text-xl);
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-summary-lines {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-summary-line {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-summary-label {
|
.order-summary-label {
|
||||||
@ -1041,7 +1547,34 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.order-summary-divider {
|
.order-summary-divider {
|
||||||
border-color: var(--t-border-default);
|
border-top: 1px solid var(--t-border-default);
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-summary-total {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: var(--t-text-large);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-summary-checkout {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-summary-checkout-form {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-summary-continue {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Content body ── */
|
/* ── Content body ── */
|
||||||
@ -1498,6 +2031,42 @@
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Product grid ── */
|
||||||
|
|
||||||
|
.product-grid {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid[data-columns="fixed-4"] {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid:not([data-columns="fixed-4"]) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Cart layout (cart page) ── */
|
||||||
|
|
||||||
|
.cart-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-items-stack {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Error page product grid ── */
|
||||||
|
|
||||||
|
.error-container .product-grid {
|
||||||
|
margin-top: 3rem;
|
||||||
|
max-width: 36rem;
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Screen reader only ── */
|
/* ── Screen reader only ── */
|
||||||
|
|
||||||
.sr-only {
|
.sr-only {
|
||||||
@ -1516,9 +2085,14 @@
|
|||||||
|
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
.page-container { padding-inline: 1.5rem; }
|
.page-container { padding-inline: 1.5rem; }
|
||||||
|
.collection-header-inner { padding-inline: 1.5rem; }
|
||||||
.shop-header { padding: 0.75rem 1rem; }
|
.shop-header { padding: 0.75rem 1rem; }
|
||||||
.shop-footer-inner { padding-inline: 1.5rem; }
|
.shop-footer-inner { padding-inline: 1.5rem; }
|
||||||
.search-kbd { display: flex; }
|
.search-kbd { display: flex; }
|
||||||
|
.product-grid:not([data-columns="fixed-4"]) {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
.hero-cta-group { flex-direction: row; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
@ -1530,11 +2104,33 @@
|
|||||||
.footer-bottom { flex-direction: row; }
|
.footer-bottom { flex-direction: row; }
|
||||||
.pdp-grid { grid-template-columns: repeat(2, 1fr); }
|
.pdp-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
.contact-grid { grid-template-columns: repeat(2, 1fr); }
|
.contact-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
.product-grid[data-columns="fixed-4"] {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
.image-text-section { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
.collection-header-wrap .t-heading { font-size: var(--t-heading-xl); }
|
||||||
|
.hero-section .t-heading { font-size: var(--t-heading-xl); }
|
||||||
|
.hero-section--page .t-heading { font-size: var(--t-heading-display); }
|
||||||
|
.hero-error .t-heading { font-size: var(--t-heading-xl); }
|
||||||
|
.pdp-price-row .product-price--sale,
|
||||||
|
.pdp-price-row .product-price--regular { font-size: var(--t-heading-xl); }
|
||||||
|
.atc-wrap[data-sticky="true"] {
|
||||||
|
position: relative;
|
||||||
|
padding-block: 0;
|
||||||
|
margin-inline: 0;
|
||||||
|
padding-inline: 0;
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
.page-container { padding-inline: 2rem; }
|
.page-container { padding-inline: 2rem; }
|
||||||
|
.collection-header-inner { padding-inline: 2rem; }
|
||||||
.shop-footer-inner { padding-inline: 2rem; }
|
.shop-footer-inner { padding-inline: 2rem; }
|
||||||
.cart-grid { grid-template-columns: 2fr 1fr; }
|
.cart-grid { grid-template-columns: 2fr 1fr; }
|
||||||
|
.cart-layout { grid-template-columns: 2fr 1fr; }
|
||||||
|
.product-grid[data-columns="2"] { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
.product-grid[data-columns="3"] { grid-template-columns: repeat(3, 1fr); }
|
||||||
|
.product-grid[data-columns="4"] { grid-template-columns: repeat(4, 1fr); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,15 +85,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Layout Width */
|
/* Layout Width */
|
||||||
&[data-layout="contained"] .max-w-7xl {
|
&[data-layout="contained"] :is(.max-w-7xl, .page-container, .collection-header-inner) {
|
||||||
max-width: var(--t-layout-max-width, 1100px);
|
max-width: var(--t-layout-max-width, 1100px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-layout="wide"] .max-w-7xl {
|
&[data-layout="wide"] :is(.max-w-7xl, .page-container, .collection-header-inner) {
|
||||||
max-width: var(--t-layout-max-width, 1400px);
|
max-width: var(--t-layout-max-width, 1400px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-layout="full"] .max-w-7xl {
|
&[data-layout="full"] :is(.max-w-7xl, .page-container, .collection-header-inner) {
|
||||||
max-width: var(--t-layout-max-width, 100%);
|
max-width: var(--t-layout-max-width, 100%);
|
||||||
padding-left: 2rem;
|
padding-left: 2rem;
|
||||||
padding-right: 2rem;
|
padding-right: 2rem;
|
||||||
@ -175,7 +175,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Image Aspect Ratio */
|
/* Image Aspect Ratio */
|
||||||
& .product-card .product-image-container {
|
& .product-card .product-card-image-wrap {
|
||||||
aspect-ratio: var(--t-image-aspect-ratio, 1 / 1);
|
aspect-ratio: var(--t-image-aspect-ratio, 1 / 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,7 +330,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Product Card Images — mobile: swipe, desktop: hover crossfade */
|
/* Product Card Images — mobile: swipe, desktop: hover crossfade */
|
||||||
.product-image-container {
|
.product-card-image-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
mode={@mode}
|
mode={@mode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<.product_grid columns={:fixed_4} gap="gap-4" class="mt-12 max-w-xl mx-auto">
|
<.product_grid columns={:fixed_4}>
|
||||||
<%= for product <- Enum.take(assigns[:products] || [], 4) do %>
|
<%= for product <- Enum.take(assigns[:products] || [], 4) do %>
|
||||||
<.product_card
|
<.product_card
|
||||||
product={product}
|
product={product}
|
||||||
|
|||||||
@ -92,7 +92,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
|
|||||||
aria-label="Close cart"
|
aria-label="Close cart"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="w-5 h-5"
|
class="cart-drawer-close-icon"
|
||||||
width="20"
|
width="20"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@ -134,7 +134,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
|
|||||||
<%= if @mode == :preview do %>
|
<%= if @mode == :preview do %>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="cart-drawer-checkout w-full mb-2"
|
class="cart-drawer-checkout"
|
||||||
>
|
>
|
||||||
Checkout
|
Checkout
|
||||||
</button>
|
</button>
|
||||||
@ -143,7 +143,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
|
|||||||
<input type="hidden" name="_csrf_token" value={Phoenix.Controller.get_csrf_token()} />
|
<input type="hidden" name="_csrf_token" value={Phoenix.Controller.get_csrf_token()} />
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="cart-drawer-checkout w-full mb-2"
|
class="cart-drawer-checkout"
|
||||||
>
|
>
|
||||||
Checkout
|
Checkout
|
||||||
</button>
|
</button>
|
||||||
@ -213,24 +213,24 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
|
|||||||
|
|
||||||
<div class="cart-item-actions">
|
<div class="cart-item-actions">
|
||||||
<%= if @show_quantity_controls do %>
|
<%= if @show_quantity_controls do %>
|
||||||
<div class="cart-qty-group flex items-center">
|
<div class="cart-qty-group">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="decrement"
|
phx-click="decrement"
|
||||||
phx-value-id={@item.variant_id}
|
phx-value-id={@item.variant_id}
|
||||||
class="cart-qty-btn px-3 py-1"
|
class="cart-qty-btn"
|
||||||
aria-label={"Decrease quantity of #{@item.name}"}
|
aria-label={"Decrease quantity of #{@item.name}"}
|
||||||
>
|
>
|
||||||
−
|
−
|
||||||
</button>
|
</button>
|
||||||
<span class="cart-qty-display px-3 py-1 border-x">
|
<span class="cart-qty-display">
|
||||||
{@item.quantity}
|
{@item.quantity}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="increment"
|
phx-click="increment"
|
||||||
phx-value-id={@item.variant_id}
|
phx-value-id={@item.variant_id}
|
||||||
class="cart-qty-btn px-3 py-1"
|
class="cart-qty-btn"
|
||||||
aria-label={"Increase quantity of #{@item.name}"}
|
aria-label={"Increase quantity of #{@item.name}"}
|
||||||
>
|
>
|
||||||
+
|
+
|
||||||
@ -246,7 +246,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cart-item-price-col text-right">
|
<div class="cart-item-price-col">
|
||||||
<p class="cart-item-price" data-size={if @size == :compact, do: "compact"}>
|
<p class="cart-item-price" data-size={if @size == :compact, do: "compact"}>
|
||||||
{SimpleshopTheme.Cart.format_price(@item.price * @item.quantity)}
|
{SimpleshopTheme.Cart.format_price(@item.price * @item.quantity)}
|
||||||
</p>
|
</p>
|
||||||
@ -262,9 +262,9 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
|
|||||||
|
|
||||||
def cart_empty_state(assigns) do
|
def cart_empty_state(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="cart-empty text-center py-8">
|
<div class="cart-empty">
|
||||||
<svg
|
<svg
|
||||||
class="cart-empty-icon w-16 h-16 mx-auto mb-4"
|
class="cart-empty-icon"
|
||||||
width="64"
|
width="64"
|
||||||
height="64"
|
height="64"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@ -276,7 +276,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
|
|||||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||||
<path d="M16 10a4 4 0 01-8 0"></path>
|
<path d="M16 10a4 4 0 01-8 0"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<p class="mb-4">Your basket is empty</p>
|
<p>Your basket is empty</p>
|
||||||
<%= if @mode == :preview do %>
|
<%= if @mode == :preview do %>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -334,43 +334,42 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
|
|||||||
|
|
||||||
def cart_item(assigns) do
|
def cart_item(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<.shop_card class="flex gap-4 p-4">
|
<.shop_card class="cart-page-item">
|
||||||
<div class="cart-page-image w-24 h-24 flex-shrink-0 bg-gray-200 overflow-hidden">
|
<div class="cart-page-image">
|
||||||
<img
|
<img
|
||||||
src={cart_item_image(@item.product)}
|
src={cart_item_image(@item.product)}
|
||||||
alt={@item.product.title}
|
alt={@item.product.title}
|
||||||
width="96"
|
width="96"
|
||||||
height="96"
|
height="96"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
class="w-full h-full object-cover"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1">
|
<div class="cart-page-item-info">
|
||||||
<h3 class="cart-page-item-name font-semibold mb-1">
|
<h3 class="cart-page-item-name">
|
||||||
{@item.product.title}
|
{@item.product.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="cart-page-item-variant text-sm mb-2">
|
<p class="cart-page-item-variant">
|
||||||
{@item.variant}
|
{@item.variant}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="flex items-center gap-4">
|
<div class="cart-page-item-actions">
|
||||||
<div class="cart-qty-group flex items-center">
|
<div class="cart-qty-group">
|
||||||
<button class="cart-qty-btn px-3 py-1">−</button>
|
<button class="cart-qty-btn">−</button>
|
||||||
<span class="cart-qty-display px-3 py-1 border-x">
|
<span class="cart-qty-display">
|
||||||
{@item.quantity}
|
{@item.quantity}
|
||||||
</span>
|
</span>
|
||||||
<button class="cart-qty-btn px-3 py-1">+</button>
|
<button class="cart-qty-btn">+</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="cart-page-item-remove text-sm">
|
<button class="cart-page-item-remove">
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-right">
|
<div class="cart-page-item-price-col">
|
||||||
<p class="cart-page-item-price font-bold text-lg">
|
<p class="cart-page-item-price">
|
||||||
{SimpleshopTheme.Cart.format_price(@item.product.cheapest_price * @item.quantity)}
|
{SimpleshopTheme.Cart.format_price(@item.product.cheapest_price * @item.quantity)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -391,8 +390,8 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
|
|||||||
|
|
||||||
defp delivery_line(assigns) do
|
defp delivery_line(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="delivery-line flex justify-between items-center">
|
<div class="delivery-line">
|
||||||
<span class="flex items-center gap-1">
|
<span class="delivery-line-label">
|
||||||
Delivery to
|
Delivery to
|
||||||
<%= if @available_countries != [] and @mode != :preview do %>
|
<%= if @available_countries != [] and @mode != :preview do %>
|
||||||
<form phx-change="change_country">
|
<form phx-change="change_country">
|
||||||
@ -445,15 +444,15 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
|
|||||||
assign(assigns, :estimated_total, assigns.subtotal + (assigns.shipping_estimate || 0))
|
assign(assigns, :estimated_total, assigns.subtotal + (assigns.shipping_estimate || 0))
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<.shop_card class="p-6 sticky top-4">
|
<.shop_card class="order-summary-card">
|
||||||
<h2 class="order-summary-heading text-xl font-bold mb-6">
|
<h2 class="order-summary-heading">
|
||||||
Order summary
|
Order summary
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="flex flex-col gap-3 mb-6">
|
<div class="order-summary-lines">
|
||||||
<div class="flex justify-between">
|
<div class="order-summary-line">
|
||||||
<span class="order-summary-label">Subtotal</span>
|
<span>Subtotal</span>
|
||||||
<span class="order-summary-value">
|
<span>
|
||||||
{SimpleshopTheme.Cart.format_price(@subtotal)}
|
{SimpleshopTheme.Cart.format_price(@subtotal)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -463,12 +462,12 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
|
|||||||
available_countries={@available_countries}
|
available_countries={@available_countries}
|
||||||
mode={@mode}
|
mode={@mode}
|
||||||
/>
|
/>
|
||||||
<div class="order-summary-divider border-t pt-3">
|
<div class="order-summary-divider">
|
||||||
<div class="flex justify-between text-lg">
|
<div class="order-summary-total">
|
||||||
<span class="order-summary-value font-semibold">
|
<span>
|
||||||
{if @shipping_estimate, do: "Estimated total", else: "Subtotal"}
|
{if @shipping_estimate, do: "Estimated total", else: "Subtotal"}
|
||||||
</span>
|
</span>
|
||||||
<span class="order-summary-value font-bold">
|
<span>
|
||||||
{SimpleshopTheme.Cart.format_price(@estimated_total)}
|
{SimpleshopTheme.Cart.format_price(@estimated_total)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -476,26 +475,26 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @mode == :preview do %>
|
<%= if @mode == :preview do %>
|
||||||
<.shop_button class="w-full px-6 py-3 font-semibold transition-all mb-3">
|
<.shop_button class="order-summary-checkout">
|
||||||
Checkout
|
Checkout
|
||||||
</.shop_button>
|
</.shop_button>
|
||||||
<.shop_button_outline
|
<.shop_button_outline
|
||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
phx-value-page="collection"
|
phx-value-page="collection"
|
||||||
class="w-full px-6 py-3 font-semibold transition-all"
|
class="order-summary-continue"
|
||||||
>
|
>
|
||||||
Continue shopping
|
Continue shopping
|
||||||
</.shop_button_outline>
|
</.shop_button_outline>
|
||||||
<% else %>
|
<% else %>
|
||||||
<form action="/checkout" method="post" class="mb-3">
|
<form action="/checkout" method="post" class="order-summary-checkout-form">
|
||||||
<input type="hidden" name="_csrf_token" value={Phoenix.Controller.get_csrf_token()} />
|
<input type="hidden" name="_csrf_token" value={Phoenix.Controller.get_csrf_token()} />
|
||||||
<.shop_button type="submit" class="w-full px-6 py-3 font-semibold transition-all">
|
<.shop_button type="submit" class="order-summary-checkout">
|
||||||
Checkout
|
Checkout
|
||||||
</.shop_button>
|
</.shop_button>
|
||||||
</form>
|
</form>
|
||||||
<.shop_link_outline
|
<.shop_link_outline
|
||||||
href="/collections/all"
|
href="/collections/all"
|
||||||
class="block w-full px-6 py-3 font-semibold transition-all text-center"
|
class="order-summary-continue"
|
||||||
>
|
>
|
||||||
Continue shopping
|
Continue shopping
|
||||||
</.shop_link_outline>
|
</.shop_link_outline>
|
||||||
@ -524,13 +523,11 @@ defmodule SimpleshopThemeWeb.ShopComponents.Cart do
|
|||||||
|
|
||||||
def cart_layout(assigns) do
|
def cart_layout(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
<div class="cart-layout">
|
||||||
<div class="lg:col-span-2">
|
<div class="cart-items-stack">
|
||||||
<div class="flex flex-col gap-4">
|
<%= for item <- @items do %>
|
||||||
<%= for item <- @items do %>
|
<.cart_item item={item} />
|
||||||
<.cart_item item={item} />
|
<% end %>
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -62,7 +62,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<article
|
<article
|
||||||
class={card_classes(@variant)}
|
class="product-card"
|
||||||
data-variant={@variant}
|
data-variant={@variant}
|
||||||
data-clickable={@clickable_resolved || nil}
|
data-clickable={@clickable_resolved || nil}
|
||||||
>
|
>
|
||||||
@ -103,7 +103,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|> assign(:has_hover_image, assigns.theme_settings.hover_image && hover_image != nil)
|
|> assign(:has_hover_image, assigns.theme_settings.hover_image && hover_image != nil)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class={["product-card-image-wrap", image_container_classes(@variant)]}>
|
<div class="product-card-image-wrap">
|
||||||
<%= if @show_badges do %>
|
<%= if @show_badges do %>
|
||||||
<.product_badge product={@product} />
|
<.product_badge product={@product} />
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -118,13 +118,13 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
alt={@product.title}
|
alt={@product.title}
|
||||||
variant={@variant}
|
variant={@variant}
|
||||||
priority={@priority}
|
priority={@priority}
|
||||||
class="product-image-primary w-full h-full object-cover transition-opacity duration-300"
|
class="product-image-primary"
|
||||||
/>
|
/>
|
||||||
<.product_card_image
|
<.product_card_image
|
||||||
image={@hover_image}
|
image={@hover_image}
|
||||||
alt={@product.title}
|
alt={@product.title}
|
||||||
variant={@variant}
|
variant={@variant}
|
||||||
class="product-image-hover w-full h-full object-cover"
|
class="product-image-hover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
@ -133,7 +133,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
alt={@product.title}
|
alt={@product.title}
|
||||||
variant={@variant}
|
variant={@variant}
|
||||||
priority={@priority}
|
priority={@priority}
|
||||||
class="product-image-primary w-full h-full object-cover"
|
class="product-image-primary"
|
||||||
/>
|
/>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @has_hover_image do %>
|
<%= if @has_hover_image do %>
|
||||||
@ -143,22 +143,22 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class={content_padding_class(@variant)}>
|
<div class="product-card-content">
|
||||||
<%= if @show_category && Map.get(@product, :category) do %>
|
<%= if @show_category && Map.get(@product, :category) do %>
|
||||||
<%= if @mode == :preview do %>
|
<%= if @mode == :preview do %>
|
||||||
<p class="product-card-category text-xs mb-1">
|
<p class="product-card-category">
|
||||||
{@product.category}
|
{@product.category}
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<.link
|
<.link
|
||||||
navigate={"/collections/#{Slug.slugify(@product.category)}"}
|
navigate={"/collections/#{Slug.slugify(@product.category)}"}
|
||||||
class="product-card-category text-xs mb-1 block hover:underline"
|
class="product-card-category"
|
||||||
>
|
>
|
||||||
{@product.category}
|
{@product.category}
|
||||||
</.link>
|
</.link>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<h3 class={["product-card-title", title_classes(@variant)]}>
|
<h3 class="product-card-title">
|
||||||
<%= if @clickable do %>
|
<%= if @clickable do %>
|
||||||
<%= if @mode == :preview do %>
|
<%= if @mode == :preview do %>
|
||||||
<a
|
<a
|
||||||
@ -185,7 +185,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<.product_price product={@product} variant={@variant} />
|
<.product_price product={@product} variant={@variant} />
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @show_delivery_text do %>
|
<%= if @show_delivery_text do %>
|
||||||
<p class="product-card-delivery text-xs mt-1">
|
<p class="product-card-delivery">
|
||||||
Made to order
|
Made to order
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -226,7 +226,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<%= cond do %>
|
<%= cond do %>
|
||||||
<% is_nil(@src) -> %>
|
<% is_nil(@src) -> %>
|
||||||
<div
|
<div
|
||||||
class={[@class, "product-card-placeholder flex items-center justify-center"]}
|
class={[@class, "product-card-placeholder"]}
|
||||||
role="img"
|
role="img"
|
||||||
aria-label={@alt}
|
aria-label={@alt}
|
||||||
>
|
>
|
||||||
@ -238,7 +238,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
width="48"
|
width="48"
|
||||||
height="48"
|
height="48"
|
||||||
class="size-12 opacity-40"
|
class="placeholder-icon"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
@ -297,33 +297,33 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<% :default -> %>
|
<% :default -> %>
|
||||||
<div>
|
<div>
|
||||||
<%= if @product.on_sale do %>
|
<%= if @product.on_sale do %>
|
||||||
<span class="product-price--sale text-lg font-bold">
|
<span class="product-price--sale">
|
||||||
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
||||||
</span>
|
</span>
|
||||||
<span class="product-price--compare text-sm line-through ml-2">
|
<span class="product-price--compare">
|
||||||
{SimpleshopTheme.Cart.format_price(@product.compare_at_price)}
|
{SimpleshopTheme.Cart.format_price(@product.compare_at_price)}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="product-price--regular text-lg font-bold">
|
<span class="product-price--regular">
|
||||||
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% :featured -> %>
|
<% :featured -> %>
|
||||||
<p class="product-price--secondary text-sm">
|
<p class="product-price--secondary">
|
||||||
<%= if @product.on_sale do %>
|
<%= if @product.on_sale do %>
|
||||||
<span class="product-price--compare line-through mr-1">
|
<span class="product-price--compare">
|
||||||
{SimpleshopTheme.Cart.format_price(@product.compare_at_price)}
|
{SimpleshopTheme.Cart.format_price(@product.compare_at_price)}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
||||||
</p>
|
</p>
|
||||||
<% :compact -> %>
|
<% :compact -> %>
|
||||||
<p class="product-price--regular font-bold">
|
<p class="product-price--regular">
|
||||||
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
||||||
</p>
|
</p>
|
||||||
<% :minimal -> %>
|
<% :minimal -> %>
|
||||||
<p class="product-price--secondary text-xs">
|
<p class="product-price--secondary">
|
||||||
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
{SimpleshopTheme.Cart.format_price(@product.cheapest_price)}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -342,32 +342,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
defp variant_defaults(:minimal),
|
defp variant_defaults(:minimal),
|
||||||
do: %{show_category: false, show_badges: false, show_delivery_text: false, clickable: false}
|
do: %{show_category: false, show_badges: false, show_delivery_text: false, clickable: false}
|
||||||
|
|
||||||
defp card_classes(:default), do: "product-card group overflow-hidden transition-all"
|
|
||||||
|
|
||||||
defp card_classes(:featured),
|
|
||||||
do: "product-card group overflow-hidden transition-all hover:-translate-y-1"
|
|
||||||
|
|
||||||
defp card_classes(:compact), do: "product-card group overflow-hidden cursor-pointer"
|
|
||||||
defp card_classes(:minimal), do: "product-card group overflow-hidden"
|
|
||||||
|
|
||||||
defp image_container_classes(:compact),
|
|
||||||
do: "product-image-container aspect-square bg-gray-200 overflow-hidden relative"
|
|
||||||
|
|
||||||
defp image_container_classes(:minimal),
|
|
||||||
do: "product-image-container aspect-square bg-gray-200 overflow-hidden relative"
|
|
||||||
|
|
||||||
defp image_container_classes(_),
|
|
||||||
do: "product-image-container bg-gray-200 overflow-hidden relative"
|
|
||||||
|
|
||||||
defp content_padding_class(:compact), do: "p-3"
|
|
||||||
defp content_padding_class(:minimal), do: "p-2"
|
|
||||||
defp content_padding_class(_), do: ""
|
|
||||||
|
|
||||||
defp title_classes(:default), do: "font-semibold mb-2"
|
|
||||||
defp title_classes(:featured), do: "text-sm font-medium mb-1"
|
|
||||||
defp title_classes(:compact), do: "font-semibold text-sm mb-1"
|
|
||||||
defp title_classes(:minimal), do: "text-xs font-semibold truncate"
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders a responsive product grid container.
|
Renders a responsive product grid container.
|
||||||
|
|
||||||
@ -394,57 +368,33 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<% end %>
|
<% end %>
|
||||||
</.product_grid>
|
</.product_grid>
|
||||||
|
|
||||||
<.product_grid columns={:fixed_4} gap="gap-6">
|
<.product_grid columns={:fixed_4}>
|
||||||
...
|
...
|
||||||
</.product_grid>
|
</.product_grid>
|
||||||
"""
|
"""
|
||||||
attr :theme_settings, :map, default: nil
|
attr :theme_settings, :map, default: nil
|
||||||
attr :columns, :atom, default: nil
|
attr :columns, :atom, default: nil
|
||||||
attr :gap, :string, default: nil
|
|
||||||
attr :class, :string, default: nil
|
attr :class, :string, default: nil
|
||||||
|
|
||||||
slot :inner_block, required: true
|
slot :inner_block, required: true
|
||||||
|
|
||||||
def product_grid(assigns) do
|
def product_grid(assigns) do
|
||||||
|
cols =
|
||||||
|
cond do
|
||||||
|
assigns.columns == :fixed_4 -> "fixed-4"
|
||||||
|
assigns.theme_settings != nil -> assigns.theme_settings.grid_columns || "3"
|
||||||
|
true -> "3"
|
||||||
|
end
|
||||||
|
|
||||||
|
assigns = assign(assigns, :data_columns, cols)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class={grid_classes(@theme_settings, @columns, @gap, @class)}>
|
<div class={["product-grid", @class]} data-columns={@data_columns}>
|
||||||
{render_slot(@inner_block)}
|
{render_slot(@inner_block)}
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
defp grid_classes(theme_settings, columns, gap, extra_class) do
|
|
||||||
base = "product-grid grid"
|
|
||||||
|
|
||||||
cols =
|
|
||||||
cond do
|
|
||||||
columns == :fixed_4 ->
|
|
||||||
"grid-cols-2 md:grid-cols-4"
|
|
||||||
|
|
||||||
theme_settings != nil ->
|
|
||||||
responsive_cols = "grid-cols-1 sm:grid-cols-2"
|
|
||||||
|
|
||||||
lg_cols =
|
|
||||||
case theme_settings.grid_columns do
|
|
||||||
"2" -> "lg:grid-cols-2"
|
|
||||||
"3" -> "lg:grid-cols-3"
|
|
||||||
"4" -> "lg:grid-cols-4"
|
|
||||||
_ -> "lg:grid-cols-3"
|
|
||||||
end
|
|
||||||
|
|
||||||
"#{responsive_cols} #{lg_cols}"
|
|
||||||
|
|
||||||
true ->
|
|
||||||
"grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"
|
|
||||||
end
|
|
||||||
|
|
||||||
gap_class = gap || ""
|
|
||||||
|
|
||||||
[base, cols, gap_class, extra_class]
|
|
||||||
|> Enum.reject(&(is_nil(&1) or &1 == ""))
|
|
||||||
|> Enum.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders a centered hero section with title, description, and optional CTAs.
|
Renders a centered hero section with title, description, and optional CTAs.
|
||||||
|
|
||||||
@ -509,11 +459,11 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
~H"""
|
~H"""
|
||||||
<%= case @variant do %>
|
<%= case @variant do %>
|
||||||
<% :default -> %>
|
<% :default -> %>
|
||||||
<section class="hero-section text-center" data-background={@background}>
|
<section class="hero-section" data-background={@background}>
|
||||||
<h1 class="t-heading text-3xl md:text-4xl mb-4">
|
<h1 class="t-heading">
|
||||||
{@title}
|
{@title}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="hero-description text-lg max-w-lg mx-auto mb-8">
|
<p class="hero-description">
|
||||||
{@description}
|
{@description}
|
||||||
</p>
|
</p>
|
||||||
<.hero_cta
|
<.hero_cta
|
||||||
@ -526,29 +476,29 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
<% :page -> %>
|
<% :page -> %>
|
||||||
<div class="hero-section--page text-center">
|
<div class="hero-section--page">
|
||||||
<h1 class="t-heading text-4xl md:text-5xl mb-6">
|
<h1 class="t-heading">
|
||||||
{@title}
|
{@title}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="hero-description text-lg mb-12 max-w-2xl mx-auto">
|
<p class="hero-description">
|
||||||
{@description}
|
{@description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% :error -> %>
|
<% :error -> %>
|
||||||
<div class="text-center">
|
<div class="hero-error">
|
||||||
<%= if @pre_title do %>
|
<%= if @pre_title do %>
|
||||||
<h1 class="hero-pre-title text-8xl md:text-9xl mb-4">
|
<h1 class="hero-pre-title">
|
||||||
{@pre_title}
|
{@pre_title}
|
||||||
</h1>
|
</h1>
|
||||||
<% end %>
|
<% end %>
|
||||||
<h2 class="t-heading text-3xl md:text-4xl mb-6">
|
<h2 class="t-heading">
|
||||||
{@title}
|
{@title}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="hero-description text-lg mb-8 max-w-md mx-auto">
|
<p class="hero-description">
|
||||||
{@description}
|
{@description}
|
||||||
</p>
|
</p>
|
||||||
<%= if @cta_text || @secondary_cta_text do %>
|
<%= if @cta_text || @secondary_cta_text do %>
|
||||||
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
<div class="hero-cta-group">
|
||||||
<.hero_cta
|
<.hero_cta
|
||||||
:if={@cta_text}
|
:if={@cta_text}
|
||||||
text={@cta_text}
|
text={@cta_text}
|
||||||
@ -579,19 +529,27 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
attr :variant, :atom, required: true
|
attr :variant, :atom, required: true
|
||||||
|
|
||||||
defp hero_cta(assigns) do
|
defp hero_cta(assigns) do
|
||||||
|
base_class =
|
||||||
|
case assigns.variant do
|
||||||
|
:primary -> "themed-button hero-cta"
|
||||||
|
:secondary -> "themed-button-outline hero-cta"
|
||||||
|
end
|
||||||
|
|
||||||
|
assigns = assign(assigns, :cta_class, base_class)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<%= if @mode == :preview do %>
|
<%= if @mode == :preview do %>
|
||||||
<button
|
<button
|
||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
phx-value-page={@page}
|
phx-value-page={@page}
|
||||||
class={hero_cta_classes(@variant)}
|
class={@cta_class}
|
||||||
>
|
>
|
||||||
{@text}
|
{@text}
|
||||||
</button>
|
</button>
|
||||||
<% else %>
|
<% else %>
|
||||||
<.link
|
<.link
|
||||||
navigate={@href || "/"}
|
navigate={@href || "/"}
|
||||||
class={["inline-block", hero_cta_classes(@variant)]}
|
class={@cta_class}
|
||||||
>
|
>
|
||||||
{@text}
|
{@text}
|
||||||
</.link>
|
</.link>
|
||||||
@ -599,11 +557,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
defp hero_cta_classes(:primary), do: "themed-button px-8 py-3 font-semibold transition-all"
|
|
||||||
|
|
||||||
defp hero_cta_classes(:secondary),
|
|
||||||
do: "themed-button-outline px-8 py-3 font-semibold transition-all"
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders a row of category circles for navigation.
|
Renders a row of category circles for navigation.
|
||||||
|
|
||||||
@ -626,17 +579,17 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
~H"""
|
~H"""
|
||||||
<section class="category-nav-section">
|
<section class="category-nav-section">
|
||||||
<h2 class="sr-only">Shop by Category</h2>
|
<h2 class="sr-only">Shop by Category</h2>
|
||||||
<nav class="grid grid-cols-3 gap-4 max-w-3xl mx-auto" aria-label="Product categories">
|
<nav class="category-nav" aria-label="Product categories">
|
||||||
<%= for category <- Enum.take(@categories, @limit) do %>
|
<%= for category <- Enum.take(@categories, @limit) do %>
|
||||||
<%= if @mode == :preview do %>
|
<%= if @mode == :preview do %>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
phx-value-page="collection"
|
phx-value-page="collection"
|
||||||
class="category-card flex flex-col items-center gap-3 p-4 rounded-lg transition-colors hover:bg-black/5"
|
class="category-card"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="w-24 h-24 rounded-full bg-gray-200 bg-cover bg-center transition-transform hover:scale-105"
|
class="category-image"
|
||||||
style={
|
style={
|
||||||
if(category[:image_url],
|
if(category[:image_url],
|
||||||
do: "background-image: url('#{category.image_url}');",
|
do: "background-image: url('#{category.image_url}');",
|
||||||
@ -645,17 +598,17 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<span class="category-name text-sm font-medium">
|
<span class="category-name">
|
||||||
{category.name}
|
{category.name}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<% else %>
|
<% else %>
|
||||||
<.link
|
<.link
|
||||||
navigate={"/collections/#{category.slug}"}
|
navigate={"/collections/#{category.slug}"}
|
||||||
class="category-card flex flex-col items-center gap-3 p-4 rounded-lg transition-colors hover:bg-black/5"
|
class="category-card"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="w-24 h-24 rounded-full bg-gray-200 bg-cover bg-center transition-transform hover:scale-105"
|
class="category-image"
|
||||||
style={
|
style={
|
||||||
if(category[:image_url],
|
if(category[:image_url],
|
||||||
do: "background-image: url('#{category.image_url}');",
|
do: "background-image: url('#{category.image_url}');",
|
||||||
@ -664,7 +617,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<span class="category-name text-sm font-medium">
|
<span class="category-name">
|
||||||
{category.name}
|
{category.name}
|
||||||
</span>
|
</span>
|
||||||
</.link>
|
</.link>
|
||||||
@ -710,7 +663,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
def featured_products_section(assigns) do
|
def featured_products_section(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<section class="featured-section">
|
<section class="featured-section">
|
||||||
<h2 class="t-heading text-2xl mb-6">
|
<h2 class="t-heading">
|
||||||
{@title}
|
{@title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
@ -726,19 +679,19 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<% end %>
|
<% end %>
|
||||||
</.product_grid>
|
</.product_grid>
|
||||||
|
|
||||||
<div class="text-center mt-8">
|
<div class="featured-cta-wrap">
|
||||||
<%= if @mode == :preview do %>
|
<%= if @mode == :preview do %>
|
||||||
<button
|
<button
|
||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
phx-value-page={@cta_page}
|
phx-value-page={@cta_page}
|
||||||
class="outline-button px-6 py-3 font-medium transition-all"
|
class="outline-button"
|
||||||
>
|
>
|
||||||
{@cta_text}
|
{@cta_text}
|
||||||
</button>
|
</button>
|
||||||
<% else %>
|
<% else %>
|
||||||
<.link
|
<.link
|
||||||
navigate={@cta_href}
|
navigate={@cta_href}
|
||||||
class="outline-button inline-block px-6 py-3 font-medium transition-all"
|
class="outline-button"
|
||||||
>
|
>
|
||||||
{@cta_text}
|
{@cta_text}
|
||||||
</.link>
|
</.link>
|
||||||
@ -784,7 +737,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def image_text_section(assigns) do
|
def image_text_section(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<section class="image-text-section grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
<section class="image-text-section">
|
||||||
<%= if @image_position == :left do %>
|
<%= if @image_position == :left do %>
|
||||||
<.image_text_image image_url={@image_url} />
|
<.image_text_image image_url={@image_url} />
|
||||||
<.image_text_content
|
<.image_text_content
|
||||||
@ -815,7 +768,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
defp image_text_image(assigns) do
|
defp image_text_image(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div
|
<div
|
||||||
class="image-text-image h-72 bg-cover bg-center"
|
class="image-text-image"
|
||||||
style={"background-image: url('#{@image_url}');"}
|
style={"background-image: url('#{@image_url}');"}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@ -832,10 +785,10 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
defp image_text_content(assigns) do
|
defp image_text_content(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div>
|
<div>
|
||||||
<h2 class="t-heading text-2xl mb-4">
|
<h2 class="t-heading">
|
||||||
{@title}
|
{@title}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="image-text-body text-base mb-4">
|
<p class="image-text-body">
|
||||||
{@description}
|
{@description}
|
||||||
</p>
|
</p>
|
||||||
<%= if @link_text do %>
|
<%= if @link_text do %>
|
||||||
@ -844,14 +797,14 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
href="#"
|
href="#"
|
||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
phx-value-page={@link_page}
|
phx-value-page={@link_page}
|
||||||
class="accent-link text-sm font-medium transition-colors"
|
class="accent-link"
|
||||||
>
|
>
|
||||||
{@link_text}
|
{@link_text}
|
||||||
</a>
|
</a>
|
||||||
<% else %>
|
<% else %>
|
||||||
<.link
|
<.link
|
||||||
navigate={@link_href || "/"}
|
navigate={@link_href || "/"}
|
||||||
class="accent-link text-sm font-medium transition-colors"
|
class="accent-link"
|
||||||
>
|
>
|
||||||
{@link_text}
|
{@link_text}
|
||||||
</.link>
|
</.link>
|
||||||
@ -880,9 +833,9 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def collection_header(assigns) do
|
def collection_header(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="collection-header-wrap border-b">
|
<div class="collection-header-wrap">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div class="collection-header-inner">
|
||||||
<h1 class="t-heading text-3xl md:text-4xl mb-2">
|
<h1 class="t-heading">
|
||||||
{@title}
|
{@title}
|
||||||
</h1>
|
</h1>
|
||||||
<%= if @subtitle do %>
|
<%= if @subtitle do %>
|
||||||
@ -924,9 +877,9 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def filter_bar(assigns) do
|
def filter_bar(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="flex flex-wrap items-center justify-between gap-4 mb-6">
|
<div class="filter-bar">
|
||||||
<!-- Category Pills -->
|
<!-- Category Pills -->
|
||||||
<div class="filter-pills-container flex gap-2 overflow-x-auto">
|
<div class="filter-pills-container">
|
||||||
<button class={"filter-pill#{if @active_category == "All", do: " filter-pill-active", else: ""}"}>
|
<button class={"filter-pill#{if @active_category == "All", do: " filter-pill-active", else: ""}"}>
|
||||||
All
|
All
|
||||||
</button>
|
</button>
|
||||||
@ -938,7 +891,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sort Dropdown -->
|
<!-- Sort Dropdown -->
|
||||||
<.shop_select options={@sort_options} class="px-4 py-2" />
|
<.shop_select options={@sort_options} />
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
@ -980,12 +933,11 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
href="#"
|
href="#"
|
||||||
phx-click="change_preview_page"
|
phx-click="change_preview_page"
|
||||||
phx-value-page={item.page}
|
phx-value-page={item.page}
|
||||||
class="hover:underline"
|
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</a>
|
</a>
|
||||||
<% else %>
|
<% else %>
|
||||||
<.link navigate={item.href || "/"} class="hover:underline">{item.label}</.link>
|
<.link navigate={item.href || "/"}>{item.label}</.link>
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -1022,12 +974,12 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def related_products_section(assigns) do
|
def related_products_section(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="related-section py-12">
|
<div class="related-section">
|
||||||
<h2 class="t-heading text-2xl mb-6">
|
<h2 class="t-heading">
|
||||||
{@title}
|
{@title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<.product_grid columns={:fixed_4} gap="gap-6">
|
<.product_grid columns={:fixed_4}>
|
||||||
<%= for product <- Enum.take(@products, @limit) do %>
|
<%= for product <- Enum.take(@products, @limit) do %>
|
||||||
<.product_card
|
<.product_card
|
||||||
product={product}
|
product={product}
|
||||||
@ -1062,7 +1014,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
~H"""
|
~H"""
|
||||||
<div class="pdp-gallery">
|
<div class="pdp-gallery">
|
||||||
<%!-- Image area (relative container for dots + desktop nav) --%>
|
<%!-- Image area (relative container for dots + desktop nav) --%>
|
||||||
<div class="pdp-gallery-frame relative">
|
<div class="pdp-gallery-frame">
|
||||||
<%!-- Scroll-snap carousel (2+ images) or single image --%>
|
<%!-- Scroll-snap carousel (2+ images) or single image --%>
|
||||||
<%= if length(@images) > 1 do %>
|
<%= if length(@images) > 1 do %>
|
||||||
<div
|
<div
|
||||||
@ -1129,7 +1081,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<div class="pdp-gallery-single">
|
<div class="pdp-gallery-single">
|
||||||
<%= if @images == [] do %>
|
<%= if @images == [] do %>
|
||||||
<div
|
<div
|
||||||
class="product-card-placeholder w-full h-full flex items-center justify-center"
|
class="product-card-placeholder"
|
||||||
role="img"
|
role="img"
|
||||||
aria-label={@product_name}
|
aria-label={@product_name}
|
||||||
>
|
>
|
||||||
@ -1141,7 +1093,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
width="48"
|
width="48"
|
||||||
height="48"
|
height="48"
|
||||||
class="size-12 opacity-40"
|
class="placeholder-icon"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
@ -1156,7 +1108,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
alt={@product_name}
|
alt={@product_name}
|
||||||
width="600"
|
width="600"
|
||||||
height="600"
|
height="600"
|
||||||
class="w-full h-full object-cover"
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="pdp-lightbox-click"
|
class="pdp-lightbox-click"
|
||||||
@ -1184,7 +1135,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<%= for {url, idx} <- Enum.with_index(@images) do %>
|
<%= for {url, idx} <- Enum.with_index(@images) do %>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={"aspect-square bg-gray-200 overflow-hidden pdp-thumbnail#{if idx == 0, do: " pdp-thumbnail-active", else: ""}"}
|
class={"pdp-thumbnail#{if idx == 0, do: " pdp-thumbnail-active", else: ""}"}
|
||||||
aria-label={"View image #{idx + 1} of #{length(@images)}"}
|
aria-label={"View image #{idx + 1} of #{length(@images)}"}
|
||||||
phx-click={
|
phx-click={
|
||||||
Phoenix.LiveView.JS.dispatch("pdp:scroll-to",
|
Phoenix.LiveView.JS.dispatch("pdp:scroll-to",
|
||||||
@ -1207,7 +1158,6 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
width="150"
|
width="150"
|
||||||
height="150"
|
height="150"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
class="w-full h-full object-cover"
|
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -1337,23 +1287,23 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div>
|
<div>
|
||||||
<h1 class="t-heading text-3xl md:text-4xl mb-4">
|
<h1 class="t-heading pdp-title">
|
||||||
{@product.title}
|
{@product.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="flex items-center gap-4 mb-6">
|
<div class="pdp-price-row">
|
||||||
<%= if @product.on_sale do %>
|
<%= if @product.on_sale do %>
|
||||||
<span class="product-price--sale text-3xl font-bold">
|
<span class="product-price--sale">
|
||||||
{SimpleshopTheme.Cart.format_price(@price)}
|
{SimpleshopTheme.Cart.format_price(@price)}
|
||||||
</span>
|
</span>
|
||||||
<span class="product-price--compare text-xl line-through">
|
<span class="product-price--compare">
|
||||||
{SimpleshopTheme.Cart.format_price(@product.compare_at_price)}
|
{SimpleshopTheme.Cart.format_price(@product.compare_at_price)}
|
||||||
</span>
|
</span>
|
||||||
<span class="sale-badge px-2 py-1 text-sm font-bold text-white rounded">
|
<span class="sale-badge">
|
||||||
SAVE {round((@product.compare_at_price - @price) / @product.compare_at_price * 100)}%
|
SAVE {round((@product.compare_at_price - @price) / @product.compare_at_price * 100)}%
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="product-price--regular text-3xl font-bold">
|
<span class="product-price--regular">
|
||||||
{SimpleshopTheme.Cart.format_price(@price)}
|
{SimpleshopTheme.Cart.format_price(@price)}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -1390,14 +1340,14 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def variant_selector(assigns) do
|
def variant_selector(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mb-6">
|
<div class="variant-selector">
|
||||||
<div class="variant-label block font-semibold mb-2">
|
<div class="variant-label">
|
||||||
{@option_type.name}<span
|
{@option_type.name}<span
|
||||||
:if={@selected}
|
:if={@selected}
|
||||||
class="variant-label-value"
|
class="variant-label-value"
|
||||||
>: {@selected}</span>
|
>: {@selected}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="variant-options">
|
||||||
<%= if @option_type.type == :color do %>
|
<%= if @option_type.type == :color do %>
|
||||||
<.color_swatch
|
<.color_swatch
|
||||||
:for={value <- @option_type.values}
|
:for={value <- @option_type.values}
|
||||||
@ -1437,10 +1387,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
phx-click={if @mode == :shop, do: "select_option"}
|
phx-click={if @mode == :shop, do: "select_option"}
|
||||||
phx-value-option={@option_name}
|
phx-value-option={@option_name}
|
||||||
phx-value-selected={@title}
|
phx-value-selected={@title}
|
||||||
class={[
|
class="color-swatch"
|
||||||
"color-swatch w-10 h-10 rounded-full border-2 transition-all relative",
|
|
||||||
@selected && "ring-2 ring-offset-2"
|
|
||||||
]}
|
|
||||||
style={"background-color: #{@hex};"}
|
style={"background-color: #{@hex};"}
|
||||||
title={@title}
|
title={@title}
|
||||||
aria-label={"Select #{@title}"}
|
aria-label={"Select #{@title}"}
|
||||||
@ -1463,7 +1410,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
phx-click={if @mode == :shop, do: "select_option"}
|
phx-click={if @mode == :shop, do: "select_option"}
|
||||||
phx-value-option={@option_name}
|
phx-value-option={@option_name}
|
||||||
phx-value-selected={@title}
|
phx-value-selected={@title}
|
||||||
class="size-btn px-4 py-2 font-medium transition-all"
|
class="size-btn"
|
||||||
aria-pressed={to_string(@selected)}
|
aria-pressed={to_string(@selected)}
|
||||||
>
|
>
|
||||||
{@title}
|
{@title}
|
||||||
@ -1493,22 +1440,22 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def quantity_selector(assigns) do
|
def quantity_selector(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mb-8">
|
<div class="quantity-selector">
|
||||||
<label class="qty-label block font-semibold mb-2">
|
<label class="qty-label">
|
||||||
Quantity
|
Quantity
|
||||||
</label>
|
</label>
|
||||||
<div class="flex items-center gap-4">
|
<div class="qty-row">
|
||||||
<div class="qty-group flex items-center">
|
<div class="qty-group">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="decrement_quantity"
|
phx-click="decrement_quantity"
|
||||||
disabled={@quantity <= @min}
|
disabled={@quantity <= @min}
|
||||||
aria-label="Decrease quantity"
|
aria-label="Decrease quantity"
|
||||||
class="qty-btn px-4 py-2 disabled:opacity-30"
|
class="qty-btn"
|
||||||
>
|
>
|
||||||
−
|
−
|
||||||
</button>
|
</button>
|
||||||
<span class="qty-display px-4 py-2 border-x-2 min-w-12 text-center tabular-nums">
|
<span class="qty-display">
|
||||||
{@quantity}
|
{@quantity}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
@ -1516,15 +1463,15 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
phx-click="increment_quantity"
|
phx-click="increment_quantity"
|
||||||
disabled={@quantity >= @max}
|
disabled={@quantity >= @max}
|
||||||
aria-label="Increase quantity"
|
aria-label="Increase quantity"
|
||||||
class="qty-btn px-4 py-2 disabled:opacity-30"
|
class="qty-btn"
|
||||||
>
|
>
|
||||||
+
|
+
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<%= if @in_stock do %>
|
<%= if @in_stock do %>
|
||||||
<span class="stock-in text-sm">In stock</span>
|
<span class="stock-in">In stock</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="stock-out text-sm font-semibold">Out of stock</span>
|
<span class="stock-out">Out of stock</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1553,16 +1500,12 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def add_to_cart_button(assigns) do
|
def add_to_cart_button(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class={[
|
<div class="atc-wrap" data-sticky={to_string(@sticky)}>
|
||||||
"atc-wrap mb-4",
|
|
||||||
@sticky &&
|
|
||||||
"sticky bottom-0 z-10 py-3 md:relative md:py-0 -mx-4 px-4 md:mx-0 md:px-0 border-t md:border-0"
|
|
||||||
]}>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click={if @mode == :preview, do: open_cart_drawer_js(), else: "add_to_cart"}
|
phx-click={if @mode == :preview, do: open_cart_drawer_js(), else: "add_to_cart"}
|
||||||
disabled={@disabled}
|
disabled={@disabled}
|
||||||
class="atc-btn w-full px-6 py-4 text-lg font-semibold transition-all"
|
class="atc-btn"
|
||||||
>
|
>
|
||||||
{@text}
|
{@text}
|
||||||
</button>
|
</button>
|
||||||
@ -1599,11 +1542,11 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
|
|
||||||
def accordion_item(assigns) do
|
def accordion_item(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<details open={@open} class="group">
|
<details open={@open}>
|
||||||
<summary class="accordion-summary flex justify-between items-center py-4 cursor-pointer list-none [&::-webkit-details-marker]:hidden">
|
<summary class="accordion-summary">
|
||||||
<span class="font-semibold">{@title}</span>
|
<span class="accordion-title">{@title}</span>
|
||||||
<svg
|
<svg
|
||||||
class="w-5 h-5 transition-transform duration-200 group-open:rotate-180"
|
class="accordion-icon"
|
||||||
width="20"
|
width="20"
|
||||||
height="20"
|
height="20"
|
||||||
fill="none"
|
fill="none"
|
||||||
@ -1613,7 +1556,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
</summary>
|
</summary>
|
||||||
<div class="accordion-body pb-4">
|
<div class="accordion-body">
|
||||||
{render_slot(@inner_block)}
|
{render_slot(@inner_block)}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
@ -1651,25 +1594,25 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="details-wrap mt-8 divide-y">
|
<div class="details-wrap">
|
||||||
<.accordion_item title="Description" open={true}>
|
<.accordion_item title="Description" open={true}>
|
||||||
<p class="leading-relaxed">
|
<p class="details-description">
|
||||||
{@product.description}. Crafted with attention to detail and quality materials, this product is designed to last. Perfect for everyday use or special occasions.
|
{@product.description}. Crafted with attention to detail and quality materials, this product is designed to last. Perfect for everyday use or special occasions.
|
||||||
</p>
|
</p>
|
||||||
</.accordion_item>
|
</.accordion_item>
|
||||||
|
|
||||||
<%= if @show_size_guide do %>
|
<%= if @show_size_guide do %>
|
||||||
<.accordion_item title="Size Guide">
|
<.accordion_item title="Size Guide">
|
||||||
<table class="w-full text-sm">
|
<table class="details-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="details-table-row">
|
<tr class="details-table-row">
|
||||||
<th class="details-th text-left py-2 font-semibold">
|
<th class="details-th">
|
||||||
Size
|
Size
|
||||||
</th>
|
</th>
|
||||||
<th class="details-th text-left py-2 font-semibold">
|
<th class="details-th">
|
||||||
Chest (cm)
|
Chest (cm)
|
||||||
</th>
|
</th>
|
||||||
<th class="details-th text-left py-2 font-semibold">
|
<th class="details-th">
|
||||||
Length (cm)
|
Length (cm)
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -1677,9 +1620,9 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<tbody>
|
<tbody>
|
||||||
<%= for {size_row, idx} <- Enum.with_index(@sizes) do %>
|
<%= for {size_row, idx} <- Enum.with_index(@sizes) do %>
|
||||||
<tr class={idx < length(@sizes) - 1 && "details-table-row"}>
|
<tr class={idx < length(@sizes) - 1 && "details-table-row"}>
|
||||||
<td class="py-2">{size_row.size}</td>
|
<td class="details-td">{size_row.size}</td>
|
||||||
<td class="py-2">{size_row.chest}</td>
|
<td class="details-td">{size_row.chest}</td>
|
||||||
<td class="py-2">{size_row.length}</td>
|
<td class="details-td">{size_row.length}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -1688,16 +1631,16 @@ defmodule SimpleshopThemeWeb.ShopComponents.Product do
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<.accordion_item title="Shipping & Returns">
|
<.accordion_item title="Shipping & Returns">
|
||||||
<div class="flex flex-col gap-3">
|
<div class="details-shipping">
|
||||||
<div>
|
<div>
|
||||||
<p class="details-subheading font-semibold mb-1">Delivery</p>
|
<p class="details-subheading">Delivery</p>
|
||||||
<p class="text-sm">
|
<p class="details-shipping-text">
|
||||||
Free UK delivery on orders over £40. Standard delivery 3-5 working days. Express delivery available at checkout.
|
Free UK delivery on orders over £40. Standard delivery 3-5 working days. Express delivery available at checkout.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="details-subheading font-semibold mb-1">Returns</p>
|
<p class="details-subheading">Returns</p>
|
||||||
<p class="text-sm">
|
<p class="details-shipping-text">
|
||||||
We offer a 30-day return policy. Items must be unused and in original packaging. Please contact us to arrange a return.
|
We offer a 30-day return policy. Items must be unused and in original packaging. Please contact us to arrange a return.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user