fix(storefront): premium UX polish for home, catalog, search
- product-card: normalize hardcoded hex colors to design tokens (--text-primary, --border-color, --primary-color, --bg-tertiary, --bg-secondary); stock bar and stock badge now use semantic --success-color/--warning-color/--error-color instead of near-duplicate literal hex; add-to-cart hover uses --primary-hover and --transition-* tokens; card hover shadow uses --shadow-lg - product-card: add aria-pressed to favorite/compare toggle buttons so their selected state isn't color-only - filters-panel: add aria-pressed to color/size/rating filter chips; add a visible checkmark glyph on selected color swatches plus a focus-style selection ring, so selection isn't conveyed by border color alone - layout-switcher: add aria-pressed to the active layout button - catalog-container: add aria-current to the mobile sort-sheet and grid-sheet option buttons; active sort option gets a checkmark and bold weight instead of color-only highlighting - category-grid: normalize hardcoded border/background/text colors to tokens; align focus ring with the color-mix pattern used elsewhere in catalog - search-results, sorting-control: normalize skeleton/select colors to tokens; sort <select> gets a hover border state - home: convert loading-grid/empty-state spacing to --space-* tokens Build verified green via `npm run build`. Out of scope / skipped: - pages/category and pages/search retain their existing hand-rolled skeleton markup (not app-skeleton) - replacing it is a composition change, not covered by this visual-polish pass - product-card rating-stars color and legacy pages/category, pages/search hex literals left as-is where no exact token match exists, to avoid an unintended visual shift Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 3px solid rgba(73, 118, 113, 0.28);
|
||||
outline: 3px solid color-mix(in srgb, var(--primary-color) 28%, white);
|
||||
outline-offset: 4px;
|
||||
border-radius: var(--radius-lg, 13px);
|
||||
}
|
||||
@@ -27,14 +27,14 @@
|
||||
.catalog-category-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 4 / 3;
|
||||
border: 1px solid #d3dad9;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 13px 13px 0 0;
|
||||
box-shadow: 0 3px 4px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f5f5f5;
|
||||
background: var(--bg-secondary);
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
@@ -49,7 +49,7 @@
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #497671;
|
||||
color: var(--primary-color);
|
||||
font-size: 4rem;
|
||||
font-weight: 800;
|
||||
background: linear-gradient(135deg, #f7f8f8 0%, #e6eceb 100%);
|
||||
@@ -58,18 +58,18 @@
|
||||
.catalog-category-content {
|
||||
min-height: 88px;
|
||||
padding: 13px 16px;
|
||||
border: 1px solid #d3dad9;
|
||||
border: 1px solid var(--border-color);
|
||||
border-top: 0;
|
||||
border-radius: 0 0 13px 13px;
|
||||
box-shadow: 0 3px 4px rgba(0, 0, 0, 0.15);
|
||||
background: #f5f3f9;
|
||||
background: var(--bg-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.catalog-category-title {
|
||||
color: #1e3c38;
|
||||
color: var(--text-primary);
|
||||
font-size: var(--font-size-lg, 1rem);
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
|
||||
@@ -95,9 +95,14 @@
|
||||
type="button"
|
||||
class="color-swatch"
|
||||
[class.active]="isSelected(filter.id, option.value)"
|
||||
[attr.aria-pressed]="isSelected(filter.id, option.value)"
|
||||
[style.background]="option.colorHex ?? '#94a3b8'"
|
||||
[attr.aria-label]="option.label"
|
||||
(click)="toggleOption(filter.id, option.value)"></button>
|
||||
(click)="toggleOption(filter.id, option.value)">
|
||||
@if (isSelected(filter.id, option.value)) {
|
||||
<span class="color-swatch-check" aria-hidden="true">✓</span>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -109,6 +114,7 @@
|
||||
type="button"
|
||||
class="size-chip"
|
||||
[class.active]="isSelected(filter.id, option.value)"
|
||||
[attr.aria-pressed]="isSelected(filter.id, option.value)"
|
||||
(click)="toggleOption(filter.id, option.value)">{{ option.label }}</button>
|
||||
}
|
||||
</div>
|
||||
@@ -121,6 +127,7 @@
|
||||
type="button"
|
||||
class="rating-chip"
|
||||
[class.active]="isSelected(filter.id, option.value)"
|
||||
[attr.aria-pressed]="isSelected(filter.id, option.value)"
|
||||
(click)="toggleOption(filter.id, option.value)">
|
||||
<span aria-hidden="true">★</span>
|
||||
<span>{{ option.label }}</span>
|
||||
|
||||
@@ -185,6 +185,9 @@
|
||||
.color-swatch {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
@@ -193,6 +196,14 @@
|
||||
|
||||
.color-swatch.active {
|
||||
border-color: var(--text-primary);
|
||||
box-shadow: 0 0 0 2px var(--bg-primary), 0 0 0 3px var(--text-primary);
|
||||
}
|
||||
|
||||
.color-swatch-check {
|
||||
color: #fff;
|
||||
font-size: var(--font-size-sm, 0.8125rem);
|
||||
font-weight: var(--font-weight-bold, 700);
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.color-swatch:hover {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
class="layout-btn"
|
||||
[class.active]="layout === active"
|
||||
[attr.aria-label]="labels[layout] | translate"
|
||||
[attr.aria-pressed]="layout === active"
|
||||
(click)="activeChange.emit(layout)">
|
||||
@switch (layout) {
|
||||
@case ('grid') {
|
||||
|
||||
@@ -28,11 +28,11 @@
|
||||
|
||||
.skeleton-card {
|
||||
min-height: 340px;
|
||||
border: 1px solid #d3dad9;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-lg, 13px);
|
||||
background:
|
||||
linear-gradient(90deg, #edf1f1 25%, #e2ebea 50%, #edf1f1 75%) 16px 16px / calc(100% - 32px) calc(100% - 32px) no-repeat,
|
||||
#f5f3f9;
|
||||
var(--bg-secondary);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.2s linear infinite;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0 8px;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.catalog-sorting select:hover {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
|
||||
@@ -262,7 +262,7 @@
|
||||
|
||||
<div class="catalog-sort-sheet-options">
|
||||
@for (option of mobileSortOptions(); track option.id) {
|
||||
<button type="button" class="catalog-sort-option" [class.active]="state().sort === option.id" (click)="selectSortFromSheet(option.id)">
|
||||
<button type="button" class="catalog-sort-option" [class.active]="state().sort === option.id" [attr.aria-current]="state().sort === option.id ? 'true' : null" (click)="selectSortFromSheet(option.id)">
|
||||
{{ option.label }}
|
||||
</button>
|
||||
}
|
||||
@@ -280,7 +280,7 @@
|
||||
|
||||
<div class="catalog-sort-sheet-options">
|
||||
@for (layout of availableLayouts; track layout) {
|
||||
<button type="button" class="catalog-sort-option" [class.active]="state().layout === layout" (click)="selectLayoutFromSheet(layout)">
|
||||
<button type="button" class="catalog-sort-option" [class.active]="state().layout === layout" [attr.aria-current]="state().layout === layout ? 'true' : null" (click)="selectLayoutFromSheet(layout)">
|
||||
{{ layoutLabelKey(layout) | translate }}
|
||||
</button>
|
||||
}
|
||||
|
||||
@@ -320,6 +320,13 @@
|
||||
.catalog-sort-option.active {
|
||||
border-color: var(--primary-color);
|
||||
background: color-mix(in srgb, var(--primary-color) 10%, white);
|
||||
font-weight: var(--font-weight-bold, 700);
|
||||
|
||||
&::after {
|
||||
content: '✓';
|
||||
float: right;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.catalog-sort-option:hover {
|
||||
|
||||
Reference in New Issue
Block a user