Pages Hard
E-commerce Shop
Mini e-commerce experience with filterable product grid, FLIP animations, cart interactions, and View Transition detail pages.
Open in Lab
MCP
gsap flip lenis view-transitions-api scrolltrigger
Targets: JS HTML
Code
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
E-commerce Shop โ styles.css
Light-themed mini shop with product grid, detail view, and cart
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
/* โโ Tokens โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
:root {
--page-bg: #fafaf8;
--page-surface: #ffffff;
--page-border: #e5e5e0;
--page-text: #1a1a1a;
--page-muted: #6b6b65;
--page-accent: #e85d3a;
--page-accent-dark: #c04828;
--page-accent-light: #fff0ec;
--page-cart: #2a2a2a;
--page-success: #22c55e;
--page-badge: #ef4444;
--font: "SF Pro Display", "Inter", system-ui, -apple-system, sans-serif;
--radius: 12px;
--radius-sm: 8px;
--radius-pill: 999px;
--header-h: 120px;
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.12);
}
/* โโ Reset โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font-family: var(--font);
background: var(--page-bg);
color: var(--page-text);
line-height: 1.6;
overflow-x: hidden;
min-height: 100vh;
}
button {
font-family: inherit;
cursor: pointer;
border: none;
background: none;
}
a {
text-decoration: none;
color: inherit;
}
/* โโ Demo Shell Overrides (light theme) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.demo-shell-back {
background: rgba(255, 255, 255, 0.85) !important;
border-color: rgba(0, 0, 0, 0.12) !important;
color: var(--page-accent) !important;
}
.demo-shell-info {
background: rgba(250, 250, 248, 0.95) !important;
border-top-color: rgba(0, 0, 0, 0.08) !important;
color: var(--page-muted) !important;
}
.demo-shell-info .demo-title {
color: var(--page-text) !important;
}
.demo-shell-info .demo-tag {
background: rgba(232, 93, 58, 0.1) !important;
border-color: rgba(232, 93, 58, 0.2) !important;
color: var(--page-accent) !important;
}
.motion-toggle {
background: rgba(255, 255, 255, 0.85) !important;
border-color: rgba(0, 0, 0, 0.12) !important;
color: var(--page-accent) !important;
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STORE HEADER
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.store-header {
position: sticky;
top: 0;
z-index: 100;
background: rgba(250, 250, 248, 0.92);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border-bottom: 1px solid var(--page-border);
}
.header-inner {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1200px;
margin: 0 auto;
padding: 16px 24px;
}
.brand {
font-size: 1.5rem;
font-weight: 700;
letter-spacing: -0.03em;
color: var(--page-text);
}
/* Cart trigger */
.cart-trigger {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
border-radius: var(--radius-sm);
color: var(--page-text);
transition: background 0.2s, color 0.2s;
}
.cart-trigger:hover {
background: var(--page-accent-light);
color: var(--page-accent);
}
.cart-badge {
position: absolute;
top: 4px;
right: 2px;
min-width: 18px;
height: 18px;
border-radius: 9px;
background: var(--page-badge);
color: #fff;
font-size: 0.65rem;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
pointer-events: none;
opacity: 0;
transform: scale(0);
transition: opacity 0.2s, transform 0.2s;
}
.cart-badge.visible {
opacity: 1;
transform: scale(1);
}
/* Filter Nav */
.filter-nav {
display: flex;
gap: 8px;
max-width: 1200px;
margin: 0 auto;
padding: 0 24px 14px;
flex-wrap: wrap;
}
.filter-pill {
padding: 7px 18px;
border-radius: var(--radius-pill);
font-size: 0.82rem;
font-weight: 500;
color: var(--page-muted);
border: 1px solid var(--page-border);
background: var(--page-surface);
transition: all 0.25s ease;
white-space: nowrap;
}
.filter-pill:hover {
border-color: var(--page-accent);
color: var(--page-accent);
}
.filter-pill.active {
background: var(--page-accent);
border-color: var(--page-accent);
color: #fff;
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
HERO SECTION
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.hero-section {
max-width: 1200px;
margin: 0 auto;
padding: 56px 24px 24px;
}
.hero-headline {
font-size: clamp(2.2rem, 5vw, 3.8rem);
font-weight: 700;
letter-spacing: -0.04em;
line-height: 1.1;
color: var(--page-text);
}
.hero-headline .word {
display: inline-block;
opacity: 0;
transform: translateY(20px);
}
.hero-sub {
margin-top: 12px;
font-size: 1.05rem;
color: var(--page-muted);
font-weight: 400;
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
PRODUCT GRID
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.grid-section {
max-width: 1200px;
margin: 0 auto;
padding: 32px 24px 80px;
}
.product-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
/* โโ Product Card โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.product-card {
background: var(--page-surface);
border-radius: var(--radius);
border: 1px solid var(--page-border);
overflow: hidden;
display: flex;
flex-direction: column;
transition: box-shadow 0.3s ease, transform 0.3s ease;
cursor: pointer;
/* for GSAP Flip */
position: relative;
}
.product-card:hover {
box-shadow: var(--shadow-md);
transform: translateY(-4px);
}
/* Card image area */
.card-image {
aspect-ratio: 1 / 1;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.card-image .shape-inner {
position: absolute;
width: 50%;
height: 50%;
opacity: 0.35;
}
/* Shape variations */
.shape-circle .shape-inner {
border-radius: 50%;
background: rgba(255, 255, 255, 0.5);
box-shadow: 0 0 40px rgba(255, 255, 255, 0.3);
}
.shape-rounded-sq .shape-inner {
border-radius: 20%;
background: rgba(255, 255, 255, 0.45);
transform: rotate(15deg);
box-shadow: 0 0 40px rgba(255, 255, 255, 0.25);
}
.shape-abstract-a .shape-inner {
clip-path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%);
background: rgba(255, 255, 255, 0.45);
}
.shape-abstract-b .shape-inner {
clip-path: polygon(25% 0%, 100% 0%, 75% 100%, 0% 100%);
background: rgba(255, 255, 255, 0.45);
transform: rotate(-5deg);
}
/* Product gradients */
.grad-merino {
background: linear-gradient(145deg, #d4b896, #7a5c3e);
}
.grad-canvas {
background: linear-gradient(145deg, #8b9a6b, #2d4a2e);
}
.grad-ceramic {
background: linear-gradient(145deg, #f5e6d3, #c4623a);
}
.grad-linen {
background: linear-gradient(145deg, #9bb5cb, #2b3f5c);
}
.grad-leather {
background: linear-gradient(145deg, #c9a87c, #4a2c12);
}
.grad-stoneware {
background: linear-gradient(145deg, #a8a8a0, #3a3a38);
}
.grad-organic {
background: linear-gradient(145deg, #9ab896, #2a4f28);
}
.grad-brass {
background: linear-gradient(145deg, #d4b04a, #7a5f1e);
}
.grad-runner {
background: linear-gradient(145deg, #d6ccb8, #a89272);
}
/* Additional inner shapes using ::after for more visual interest */
.card-image::after {
content: "";
position: absolute;
opacity: 0.12;
pointer-events: none;
}
.shape-circle::after {
width: 30%;
height: 30%;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.6);
bottom: 20%;
right: 18%;
}
.shape-rounded-sq::after {
width: 25%;
height: 25%;
border-radius: 8px;
background: rgba(255, 255, 255, 0.4);
top: 15%;
left: 15%;
transform: rotate(-10deg);
}
.shape-abstract-a::after {
width: 20%;
height: 20%;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.5);
top: 20%;
right: 22%;
}
.shape-abstract-b::after {
width: 35%;
height: 2px;
background: rgba(255, 255, 255, 0.5);
bottom: 30%;
left: 15%;
transform: rotate(-15deg);
}
/* Card body */
.card-body {
padding: 16px 16px 8px;
display: flex;
justify-content: space-between;
align-items: baseline;
}
.card-title {
font-size: 0.95rem;
font-weight: 600;
color: var(--page-text);
letter-spacing: -0.01em;
}
.card-price {
font-size: 0.92rem;
font-weight: 600;
color: var(--page-muted);
}
/* Add to cart button */
.btn-add {
margin: 8px 16px 16px;
padding: 10px 0;
border-radius: var(--radius-sm);
border: 1.5px solid var(--page-accent);
color: var(--page-accent);
font-size: 0.82rem;
font-weight: 600;
letter-spacing: 0.02em;
text-transform: uppercase;
transition: background 0.25s, color 0.25s;
}
.btn-add:hover {
background: var(--page-accent);
color: #fff;
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
PRODUCT DETAIL OVERLAY
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.detail-overlay {
position: fixed;
inset: 0;
z-index: 200;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
opacity: 0;
visibility: hidden;
transition: opacity 0.35s ease, visibility 0.35s ease;
}
.detail-overlay.open {
pointer-events: auto;
opacity: 1;
visibility: visible;
}
.detail-backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
.detail-panel {
position: relative;
width: 90%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
background: var(--page-surface);
border-radius: var(--radius);
box-shadow: var(--shadow-lg);
display: grid;
grid-template-columns: 1fr 1fr;
z-index: 1;
}
.detail-close {
position: absolute;
top: 16px;
right: 16px;
z-index: 5;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--page-bg);
border: 1px solid var(--page-border);
color: var(--page-text);
transition: background 0.2s, color 0.2s;
}
.detail-close:hover {
background: var(--page-accent-light);
color: var(--page-accent);
}
/* Detail image */
.detail-image {
aspect-ratio: 4 / 3;
position: relative;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius) 0 0 var(--radius);
overflow: hidden;
min-height: 300px;
}
.detail-image .shape-inner {
position: absolute;
width: 40%;
height: 40%;
opacity: 0.35;
}
/* Detail info */
.detail-info {
padding: 40px 32px;
display: flex;
flex-direction: column;
gap: 12px;
}
.detail-title {
font-size: 1.6rem;
font-weight: 700;
letter-spacing: -0.03em;
line-height: 1.2;
color: var(--page-text);
view-transition-name: product-title;
}
.detail-category {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 600;
color: var(--page-accent);
}
.detail-desc {
font-size: 0.92rem;
color: var(--page-muted);
line-height: 1.7;
}
.detail-price {
font-size: 1.4rem;
font-weight: 700;
color: var(--page-text);
margin-top: 4px;
}
/* Quantity selector */
.qty-selector {
display: flex;
align-items: center;
gap: 0;
margin-top: 8px;
width: fit-content;
border: 1px solid var(--page-border);
border-radius: var(--radius-sm);
overflow: hidden;
}
.qty-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.1rem;
font-weight: 500;
color: var(--page-text);
background: var(--page-bg);
transition: background 0.2s, color 0.2s;
}
.qty-btn:hover {
background: var(--page-accent-light);
color: var(--page-accent);
}
.qty-value {
width: 44px;
text-align: center;
font-size: 0.95rem;
font-weight: 600;
color: var(--page-text);
border-left: 1px solid var(--page-border);
border-right: 1px solid var(--page-border);
line-height: 40px;
}
/* Large add button */
.btn-add-large {
margin-top: 12px;
padding: 14px 0;
border-radius: var(--radius-sm);
background: var(--page-accent);
color: #fff;
font-size: 0.88rem;
font-weight: 600;
letter-spacing: 0.03em;
text-transform: uppercase;
transition: background 0.25s;
}
.btn-add-large:hover {
background: var(--page-accent-dark);
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
CART SIDEBAR
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.cart-sidebar {
position: fixed;
inset: 0;
z-index: 300;
pointer-events: none;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.cart-sidebar.open {
pointer-events: auto;
opacity: 1;
visibility: visible;
}
.cart-sidebar-backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.35);
}
.cart-panel {
position: absolute;
top: 0;
right: 0;
width: 380px;
height: 100%;
background: var(--page-surface);
box-shadow: -8px 0 30px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.cart-sidebar.open .cart-panel {
transform: translateX(0);
}
.cart-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border-bottom: 1px solid var(--page-border);
}
.cart-heading {
font-size: 1.1rem;
font-weight: 700;
letter-spacing: -0.02em;
}
.cart-close {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
color: var(--page-text);
transition: background 0.2s;
}
.cart-close:hover {
background: var(--page-bg);
}
/* Cart items */
.cart-items {
flex: 1;
overflow-y: auto;
padding: 16px 24px;
}
.cart-empty {
color: var(--page-muted);
font-size: 0.9rem;
text-align: center;
padding-top: 40px;
}
.cart-item {
display: flex;
align-items: center;
gap: 14px;
padding: 14px 0;
border-bottom: 1px solid var(--page-border);
}
.cart-item-swatch {
width: 48px;
height: 48px;
border-radius: var(--radius-sm);
flex-shrink: 0;
}
.cart-item-details {
flex: 1;
min-width: 0;
}
.cart-item-name {
font-size: 0.85rem;
font-weight: 600;
color: var(--page-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.cart-item-meta {
font-size: 0.78rem;
color: var(--page-muted);
margin-top: 2px;
}
.cart-item-price {
font-size: 0.88rem;
font-weight: 600;
color: var(--page-text);
flex-shrink: 0;
}
.cart-item-remove {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 0.75rem;
color: var(--page-muted);
flex-shrink: 0;
transition: background 0.2s, color 0.2s;
}
.cart-item-remove:hover {
background: rgba(239, 68, 68, 0.1);
color: var(--page-badge);
}
/* Cart footer */
.cart-footer {
padding: 20px 24px;
border-top: 1px solid var(--page-border);
}
.cart-total {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 16px;
font-size: 1rem;
font-weight: 700;
color: var(--page-text);
}
.btn-checkout {
width: 100%;
padding: 14px 0;
border-radius: var(--radius-sm);
background: var(--page-cart);
color: #fff;
font-size: 0.88rem;
font-weight: 600;
letter-spacing: 0.03em;
text-transform: uppercase;
transition: background 0.25s;
}
.btn-checkout:hover {
background: #111;
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
VIEW TRANSITION NAMES
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.product-card.vt-active .card-image {
view-transition-name: product-hero;
}
.product-card.vt-active .card-title {
view-transition-name: product-title;
}
.detail-overlay.open .detail-image {
view-transition-name: product-hero;
}
/* View Transition animation tuning */
::view-transition-old(product-hero),
::view-transition-new(product-hero) {
animation-duration: 0.4s;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
::view-transition-old(product-title),
::view-transition-new(product-title) {
animation-duration: 0.35s;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
REDUCED MOTION
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.reduced-motion .product-card {
opacity: 1 !important;
transform: none !important;
}
.reduced-motion .hero-headline .word {
opacity: 1 !important;
transform: none !important;
}
.reduced-motion .product-card:hover {
transform: none;
}
.reduced-motion .cart-panel {
transition: none;
}
.reduced-motion .detail-overlay {
transition: none;
}
.reduced-motion .cart-sidebar {
transition: none;
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
RESPONSIVE
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
@media (max-width: 900px) {
.product-grid {
grid-template-columns: repeat(2, 1fr);
gap: 18px;
}
}
@media (max-width: 600px) {
.product-grid {
grid-template-columns: 1fr;
gap: 16px;
}
.hero-section {
padding: 40px 16px 16px;
}
.grid-section {
padding: 24px 16px 60px;
}
.header-inner {
padding: 14px 16px;
}
.filter-nav {
padding: 0 16px 12px;
gap: 6px;
}
.filter-pill {
padding: 6px 14px;
font-size: 0.78rem;
}
/* Detail panel - stacked layout on mobile */
.detail-panel {
grid-template-columns: 1fr;
width: 95%;
max-height: 85vh;
}
.detail-image {
border-radius: var(--radius) var(--radius) 0 0;
aspect-ratio: 16 / 10;
min-height: 200px;
}
.detail-info {
padding: 24px 20px 32px;
}
/* Cart panel full width on mobile */
.cart-panel {
width: 100%;
}
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
ENTRANCE ANIMATION HELPERS
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.product-card {
opacity: 0;
transform: translateY(30px);
}
.product-card.in-view {
opacity: 1;
transform: translateY(0);
}if (!window.MotionPreference) {
const __mql = window.matchMedia("(prefers-reduced-motion: reduce)");
const __listeners = new Set();
const MotionPreference = {
prefersReducedMotion() {
return __mql.matches;
},
setOverride(value) {
const reduced = Boolean(value);
document.documentElement.classList.toggle("reduced-motion", reduced);
window.dispatchEvent(new CustomEvent("motion-preference", { detail: { reduced } }));
for (const listener of __listeners) {
try {
listener({ reduced, override: reduced, systemReduced: __mql.matches });
} catch {}
}
},
onChange(listener) {
__listeners.add(listener);
try {
listener({
reduced: __mql.matches,
override: null,
systemReduced: __mql.matches,
});
} catch {}
return () => __listeners.delete(listener);
},
getState() {
return { reduced: __mql.matches, override: null, systemReduced: __mql.matches };
},
};
window.MotionPreference = MotionPreference;
}
function prefersReducedMotion() {
return window.MotionPreference.prefersReducedMotion();
}
function initDemoShell() {
// No-op shim in imported standalone snippets.
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
E-commerce Shop โ main.js
Filterable product grid, FLIP animations, cart, View Transitions
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { SplitText } from "gsap/SplitText";
import { Flip } from "gsap/Flip";
import Lenis from "lenis";
gsap.registerPlugin(ScrollTrigger, SplitText, Flip);
initDemoShell({
title: "E-commerce Shop",
category: "pages",
tech: ["gsap", "flip", "lenis", "view-transitions-api"],
});
const lenis = new Lenis({ lerp: 0.1, smoothWheel: true });
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((time) => lenis.raf(time * 1000));
gsap.ticker.lagSmoothing(0);
let reduced = prefersReducedMotion();
if (reduced) document.documentElement.classList.add("reduced-motion");
window.addEventListener("motion-preference", (e) => {
reduced = e.detail.reduced;
document.documentElement.classList.toggle("reduced-motion", reduced);
ScrollTrigger.refresh();
});
const dur = (d) => (reduced ? 0 : d);
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
PRODUCT DATA
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
const products = [
{
id: 1,
name: "Merino Crew Neck",
category: "apparel",
price: 89,
gradient: "grad-merino",
shape: "shape-circle",
description:
"Crafted from ultra-fine 18-micron merino wool, this crew neck strikes the perfect balance between warmth and breathability. The relaxed fit drapes naturally over any frame, making it an effortless everyday layer.",
},
{
id: 2,
name: "Canvas Weekender",
category: "accessories",
price: 145,
gradient: "grad-canvas",
shape: "shape-rounded-sq",
description:
"Heavy-duty waxed canvas meets vegetable-tanned leather handles in a bag built for spontaneous getaways. The reinforced base and brass hardware ensure it ages beautifully over years of use.",
},
{
id: 3,
name: "Ceramic Pour-Over",
category: "homeware",
price: 42,
gradient: "grad-ceramic",
shape: "shape-abstract-a",
description:
"Hand-thrown stoneware dripper with a precision-cut spiral channel that controls water flow for consistent extraction. Each piece is kiln-fired with a reactive glaze that makes it one of a kind.",
},
{
id: 4,
name: "Linen Overshirt",
category: "apparel",
price: 128,
gradient: "grad-linen",
shape: "shape-rounded-sq",
description:
"Stonewashed European linen in a boxy silhouette that works as a light jacket or a structured shirt. The fabric softens with every wash while retaining its natural texture and drape.",
},
{
id: 5,
name: "Leather Card Wallet",
category: "accessories",
price: 55,
gradient: "grad-leather",
shape: "shape-abstract-b",
description:
"Slim enough to slip into a front pocket, this full-grain leather wallet holds up to six cards with a center cash slot. The burnished edges are hand-finished for a clean, modern profile.",
},
{
id: 6,
name: "Stoneware Mug Set",
category: "homeware",
price: 38,
gradient: "grad-stoneware",
shape: "shape-circle",
description:
"A set of two generously sized mugs in a matte volcanic glaze with a smooth interior. The thick walls retain heat longer, and the weighted base keeps them grounded on any surface.",
},
{
id: 7,
name: "Organic Cotton Tee",
category: "apparel",
price: 48,
gradient: "grad-organic",
shape: "shape-abstract-a",
description:
"GOTS-certified organic cotton in a medium weight that drapes without clinging. Pre-shrunk and garment-dyed for a broken-in feel straight out of the box with minimal environmental impact.",
},
{
id: 8,
name: "Brass Key Ring",
category: "accessories",
price: 28,
gradient: "grad-brass",
shape: "shape-circle",
description:
"Solid brass loop with a spring-loaded gate clip, designed to develop a rich patina with daily carry. The compact profile attaches cleanly to belt loops and bag hardware alike.",
},
{
id: 9,
name: "Linen Table Runner",
category: "homeware",
price: 65,
gradient: "grad-runner",
shape: "shape-rounded-sq",
description:
"Woven from raw Belgian flax with naturally frayed edges, this runner brings understated warmth to any table. The oatmeal tone pairs effortlessly with both ceramic and wooden tableware.",
},
];
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
HERO โ SplitText word reveal
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
const headlineEl = document.getElementById("hero-headline");
if (headlineEl) {
const split = new SplitText(headlineEl, { type: "words", wordsClass: "word" });
if (!reduced) {
gsap.fromTo(
split.words,
{ opacity: 0, y: 20 },
{
opacity: 1,
y: 0,
duration: 0.7,
ease: "expo.out",
stagger: 0.08,
delay: 0.15,
}
);
} else {
gsap.set(split.words, { opacity: 1, y: 0 });
}
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
CARD ENTRANCE โ ScrollTrigger stagger
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
const cards = gsap.utils.toArray(".product-card");
if (!reduced) {
gsap.fromTo(
cards,
{ opacity: 0, y: 30 },
{
opacity: 1,
y: 0,
duration: 0.6,
ease: "expo.out",
stagger: 0.06,
scrollTrigger: {
trigger: "#product-grid",
start: "top 85%",
},
}
);
} else {
gsap.set(cards, { opacity: 1, y: 0 });
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
FILTER โ GSAP Flip
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
const filterNav = document.getElementById("filter-nav");
let activeFilter = "all";
function filterProducts(category) {
if (category === activeFilter) return;
activeFilter = category;
// Update pill UI
filterNav.querySelectorAll(".filter-pill").forEach((pill) => {
pill.classList.toggle("active", pill.dataset.filter === category);
});
const state = Flip.getState(".product-card");
document.querySelectorAll(".product-card").forEach((card) => {
const show = category === "all" || card.dataset.category === category;
card.style.display = show ? "" : "none";
});
if (!reduced) {
Flip.from(state, {
duration: 0.6,
ease: "expo.inOut",
stagger: 0.03,
absolute: true,
onComplete: () => ScrollTrigger.refresh(),
});
} else {
ScrollTrigger.refresh();
}
}
filterNav.addEventListener("click", (e) => {
const pill = e.target.closest(".filter-pill");
if (!pill) return;
filterProducts(pill.dataset.filter);
});
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
PRODUCT DETAIL โ View Transitions API
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
const supportsVT = typeof document.startViewTransition === "function";
const detailOverlay = document.getElementById("detail-overlay");
const detailImage = document.getElementById("detail-image");
const detailTitle = document.getElementById("detail-title");
const detailCategory = document.getElementById("detail-category");
const detailDesc = document.getElementById("detail-desc");
const detailPrice = document.getElementById("detail-price");
const detailAdd = document.getElementById("detail-add");
const detailClose = document.getElementById("detail-close");
const detailBackdrop = document.getElementById("detail-backdrop");
const qtyValue = document.getElementById("qty-value");
const qtyMinus = document.getElementById("qty-minus");
const qtyPlus = document.getElementById("qty-plus");
let activeProductId = null;
let activeCard = null;
let detailQty = 1;
function getProduct(id) {
return products.find((p) => p.id === id);
}
function populateDetail(product) {
// Set image gradient + shape class
detailImage.className = "detail-image";
detailImage.classList.add(product.gradient, product.shape);
detailTitle.textContent = product.name;
detailCategory.textContent = product.category;
detailDesc.textContent = product.description;
detailPrice.textContent = `$${product.price}`;
detailQty = 1;
qtyValue.textContent = detailQty;
activeProductId = product.id;
}
function openDetail(card, product) {
activeCard = card;
populateDetail(product);
const performOpen = () => {
// Set view-transition-name on the active card only
card.classList.add("vt-active");
detailOverlay.classList.add("open");
detailOverlay.setAttribute("aria-hidden", "false");
document.body.style.overflow = "hidden";
lenis.stop();
};
if (supportsVT && !reduced) {
document.startViewTransition(() => performOpen());
} else {
performOpen();
}
}
function closeDetail() {
const performClose = () => {
detailOverlay.classList.remove("open");
detailOverlay.setAttribute("aria-hidden", "true");
document.body.style.overflow = "";
lenis.start();
// Remove vt-active after transition completes
if (activeCard) {
activeCard.classList.remove("vt-active");
activeCard = null;
}
activeProductId = null;
};
if (supportsVT && !reduced) {
document.startViewTransition(() => performClose());
} else {
performClose();
}
}
// Open on card click (but not on the Add to Cart button)
document.getElementById("product-grid").addEventListener("click", (e) => {
const addBtn = e.target.closest(".btn-add");
if (addBtn) {
e.stopPropagation();
addToCart(Number(addBtn.dataset.id));
return;
}
const card = e.target.closest(".product-card");
if (!card) return;
const product = getProduct(Number(card.dataset.id));
if (product) openDetail(card, product);
});
// Close triggers
detailClose.addEventListener("click", closeDetail);
detailBackdrop.addEventListener("click", closeDetail);
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
if (detailOverlay.classList.contains("open")) {
closeDetail();
} else if (cartSidebar.classList.contains("open")) {
closeCart();
}
}
});
// Quantity controls
qtyMinus.addEventListener("click", () => {
if (detailQty > 1) {
detailQty--;
qtyValue.textContent = detailQty;
}
});
qtyPlus.addEventListener("click", () => {
if (detailQty < 99) {
detailQty++;
qtyValue.textContent = detailQty;
}
});
// Add from detail view
detailAdd.addEventListener("click", () => {
if (activeProductId) {
for (let i = 0; i < detailQty; i++) {
addToCart(activeProductId, true);
}
closeDetail();
}
});
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
CART STATE & UI
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
let cart = [];
const cartSidebar = document.getElementById("cart-sidebar");
const cartPanel = document.getElementById("cart-panel");
const cartItems = document.getElementById("cart-items");
const cartEmpty = document.getElementById("cart-empty");
const cartBadge = document.getElementById("cart-badge");
const cartTotalValue = document.getElementById("cart-total-value");
const cartTrigger = document.getElementById("cart-trigger");
const cartClose = document.getElementById("cart-close");
const cartSidebarBackdrop = document.getElementById("cart-sidebar-backdrop");
function addToCart(productId, skipBounce = false) {
const existing = cart.find((item) => item.id === productId);
if (existing) {
existing.qty++;
} else {
cart.push({ id: productId, qty: 1 });
}
updateCartUI();
// Badge bounce
if (!reduced && !skipBounce) {
gsap.fromTo(cartBadge, { scale: 1.4 }, { scale: 1, duration: 0.3, ease: "back.out(2)" });
}
}
function removeFromCart(productId) {
cart = cart.filter((item) => item.id !== productId);
updateCartUI();
}
function updateCartUI() {
const totalItems = cart.reduce((sum, item) => sum + item.qty, 0);
const totalPrice = cart.reduce((sum, item) => {
const product = getProduct(item.id);
return sum + (product ? product.price * item.qty : 0);
}, 0);
// Badge
cartBadge.textContent = totalItems;
cartBadge.classList.toggle("visible", totalItems > 0);
// Total
cartTotalValue.textContent = `$${totalPrice}`;
// Items list
if (cart.length === 0) {
cartEmpty.style.display = "";
// Clear all cart items
cartItems.querySelectorAll(".cart-item").forEach((el) => el.remove());
return;
}
cartEmpty.style.display = "none";
// Rebuild items
const existingItems = cartItems.querySelectorAll(".cart-item");
existingItems.forEach((el) => el.remove());
cart.forEach((item) => {
const product = getProduct(item.id);
if (!product) return;
const row = document.createElement("div");
row.className = "cart-item";
row.innerHTML = `
<div class="cart-item-swatch ${product.gradient}"></div>
<div class="cart-item-details">
<div class="cart-item-name">${product.name}</div>
<div class="cart-item-meta">Qty: ${item.qty}</div>
</div>
<div class="cart-item-price">$${product.price * item.qty}</div>
<button class="cart-item-remove" data-remove-id="${product.id}" aria-label="Remove ${product.name}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
`;
cartItems.appendChild(row);
});
}
// Cart item removal (delegated)
cartItems.addEventListener("click", (e) => {
const removeBtn = e.target.closest(".cart-item-remove");
if (!removeBtn) return;
removeFromCart(Number(removeBtn.dataset.removeId));
});
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
CART SIDEBAR OPEN / CLOSE
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
function openCart() {
cartSidebar.classList.add("open");
cartSidebar.setAttribute("aria-hidden", "false");
document.body.style.overflow = "hidden";
lenis.stop();
}
function closeCart() {
cartSidebar.classList.remove("open");
cartSidebar.setAttribute("aria-hidden", "true");
document.body.style.overflow = "";
lenis.start();
}
cartTrigger.addEventListener("click", openCart);
cartClose.addEventListener("click", closeCart);
cartSidebarBackdrop.addEventListener("click", closeCart);<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>E-commerce Shop โ stealthisdesign</title>
<link rel="stylesheet" href="style.css">
<script type="importmap">{"imports":{"gsap":"https://esm.sh/gsap@3.13.0","gsap/ScrollTrigger":"https://esm.sh/gsap@3.13.0/ScrollTrigger","gsap/SplitText":"https://esm.sh/gsap@3.13.0/SplitText","gsap/Flip":"https://esm.sh/gsap@3.13.0/Flip","gsap/ScrambleTextPlugin":"https://esm.sh/gsap@3.13.0/ScrambleTextPlugin","gsap/TextPlugin":"https://esm.sh/gsap@3.13.0/TextPlugin","gsap/all":"https://esm.sh/gsap@3.13.0/all","gsap/":"https://esm.sh/gsap@3.13.0/","lenis":"https://esm.sh/lenis@1.1.13/dist/lenis.mjs","three":"https://esm.sh/three@0.171.0","three/addons/":"https://esm.sh/three@0.171.0/examples/jsm/"}}</script>
<style>html.lenis,
html.lenis body {
height: auto;
}
.lenis:not(.lenis-autoToggle).lenis-stopped {
overflow: clip;
}
.lenis [data-lenis-prevent],
.lenis [data-lenis-prevent-wheel],
.lenis [data-lenis-prevent-touch] {
overscroll-behavior: contain;
}
.lenis.lenis-smooth iframe {
pointer-events: none;
}
.lenis.lenis-autoToggle {
transition-property: overflow;
transition-duration: 1ms;
transition-behavior: allow-discrete;
}</style>
</head>
<body>
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<!-- STORE HEADER -->
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<header class="store-header" id="store-header">
<div class="header-inner">
<a class="brand" href="#">Forma</a>
<button class="cart-trigger" id="cart-trigger" aria-label="Open cart">
<svg class="cart-icon" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/>
<line x1="3" y1="6" x2="21" y2="6"/>
<path d="M16 10a4 4 0 01-8 0"/>
</svg>
<span class="cart-badge" id="cart-badge" aria-live="polite">0</span>
</button>
</div>
<nav class="filter-nav" id="filter-nav">
<button class="filter-pill active" data-filter="all">All</button>
<button class="filter-pill" data-filter="apparel">Apparel</button>
<button class="filter-pill" data-filter="accessories">Accessories</button>
<button class="filter-pill" data-filter="homeware">Homeware</button>
</nav>
</header>
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<!-- HERO HEADLINE -->
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<section class="hero-section" id="hero">
<h1 class="hero-headline" id="hero-headline">Curated Essentials</h1>
<p class="hero-sub">Thoughtfully designed goods for everyday living.</p>
</section>
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<!-- PRODUCT GRID -->
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<section class="grid-section" id="grid-section">
<div class="product-grid" id="product-grid">
<article class="product-card" data-id="1" data-category="apparel">
<div class="card-image shape-circle grad-merino" aria-hidden="true">
<div class="shape-inner"></div>
</div>
<div class="card-body">
<h3 class="card-title">Merino Crew Neck</h3>
<span class="card-price">$89</span>
</div>
<button class="btn-add" data-id="1">Add to Cart</button>
</article>
<article class="product-card" data-id="2" data-category="accessories">
<div class="card-image shape-rounded-sq grad-canvas" aria-hidden="true">
<div class="shape-inner"></div>
</div>
<div class="card-body">
<h3 class="card-title">Canvas Weekender</h3>
<span class="card-price">$145</span>
</div>
<button class="btn-add" data-id="2">Add to Cart</button>
</article>
<article class="product-card" data-id="3" data-category="homeware">
<div class="card-image shape-abstract-a grad-ceramic" aria-hidden="true">
<div class="shape-inner"></div>
</div>
<div class="card-body">
<h3 class="card-title">Ceramic Pour-Over</h3>
<span class="card-price">$42</span>
</div>
<button class="btn-add" data-id="3">Add to Cart</button>
</article>
<article class="product-card" data-id="4" data-category="apparel">
<div class="card-image shape-rounded-sq grad-linen" aria-hidden="true">
<div class="shape-inner"></div>
</div>
<div class="card-body">
<h3 class="card-title">Linen Overshirt</h3>
<span class="card-price">$128</span>
</div>
<button class="btn-add" data-id="4">Add to Cart</button>
</article>
<article class="product-card" data-id="5" data-category="accessories">
<div class="card-image shape-abstract-b grad-leather" aria-hidden="true">
<div class="shape-inner"></div>
</div>
<div class="card-body">
<h3 class="card-title">Leather Card Wallet</h3>
<span class="card-price">$55</span>
</div>
<button class="btn-add" data-id="5">Add to Cart</button>
</article>
<article class="product-card" data-id="6" data-category="homeware">
<div class="card-image shape-circle grad-stoneware" aria-hidden="true">
<div class="shape-inner"></div>
</div>
<div class="card-body">
<h3 class="card-title">Stoneware Mug Set</h3>
<span class="card-price">$38</span>
</div>
<button class="btn-add" data-id="6">Add to Cart</button>
</article>
<article class="product-card" data-id="7" data-category="apparel">
<div class="card-image shape-abstract-a grad-organic" aria-hidden="true">
<div class="shape-inner"></div>
</div>
<div class="card-body">
<h3 class="card-title">Organic Cotton Tee</h3>
<span class="card-price">$48</span>
</div>
<button class="btn-add" data-id="7">Add to Cart</button>
</article>
<article class="product-card" data-id="8" data-category="accessories">
<div class="card-image shape-circle grad-brass" aria-hidden="true">
<div class="shape-inner"></div>
</div>
<div class="card-body">
<h3 class="card-title">Brass Key Ring</h3>
<span class="card-price">$28</span>
</div>
<button class="btn-add" data-id="8">Add to Cart</button>
</article>
<article class="product-card" data-id="9" data-category="homeware">
<div class="card-image shape-rounded-sq grad-runner" aria-hidden="true">
<div class="shape-inner"></div>
</div>
<div class="card-body">
<h3 class="card-title">Linen Table Runner</h3>
<span class="card-price">$65</span>
</div>
<button class="btn-add" data-id="9">Add to Cart</button>
</article>
</div>
</section>
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<!-- PRODUCT DETAIL OVERLAY -->
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<div class="detail-overlay" id="detail-overlay" aria-hidden="true">
<div class="detail-backdrop" id="detail-backdrop"></div>
<div class="detail-panel" id="detail-panel">
<button class="detail-close" id="detail-close" aria-label="Close detail view">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
<div class="detail-image" id="detail-image" aria-hidden="true">
<div class="shape-inner"></div>
</div>
<div class="detail-info">
<h2 class="detail-title" id="detail-title"></h2>
<p class="detail-category" id="detail-category"></p>
<p class="detail-desc" id="detail-desc"></p>
<span class="detail-price" id="detail-price"></span>
<div class="qty-selector">
<button class="qty-btn" id="qty-minus" aria-label="Decrease quantity">-</button>
<span class="qty-value" id="qty-value">1</span>
<button class="qty-btn" id="qty-plus" aria-label="Increase quantity">+</button>
</div>
<button class="btn-add-large" id="detail-add">Add to Cart</button>
</div>
</div>
</div>
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<!-- CART SIDEBAR -->
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<aside class="cart-sidebar" id="cart-sidebar" aria-hidden="true">
<div class="cart-sidebar-backdrop" id="cart-sidebar-backdrop"></div>
<div class="cart-panel" id="cart-panel">
<div class="cart-header">
<h2 class="cart-heading">Your Cart</h2>
<button class="cart-close" id="cart-close" aria-label="Close cart">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="cart-items" id="cart-items">
<p class="cart-empty" id="cart-empty">Your cart is empty.</p>
</div>
<div class="cart-footer" id="cart-footer">
<div class="cart-total">
<span>Total</span>
<span id="cart-total-value">$0</span>
</div>
<button class="btn-checkout">Checkout</button>
</div>
</div>
</aside>
<script type="module" src="script.js"></script>
</body>
</html>E-commerce Shop
Mini e-commerce experience with filterable product grid, FLIP animations, cart interactions, and View Transition detail pages.
Source
- Repository:
libs-genclaude - Original demo id:
29-ecommerce-grid
Notes
Mini e-commerce experience with filterable product grid, FLIP animations, cart interactions, and View Transition detail pages.