Shop — Product Detail (PDP)
A complete e-commerce product detail page with a gradient and SVG image gallery, thumbnail switching, color swatches and capacity chips that drive price, image, and stock state, a quantity stepper, working add-to-cart and wishlist toasts, expandable shipping and returns accordions, a rating breakdown with verified reviews, and a horizontal you-may-also-like rail with quick-add buttons.
MCP
Code
:root{
--bg:#ffffff;
--ink:#16181d;
--ink-2:#2b2f3a;
--muted:#6b7280;
--brand:#3457ff;
--brand-d:#2742d6;
--sale:#e0245e;
--ok:#1f9d55;
--warn:#b8730c;
--line:rgba(16,18,29,.1);
--line-2:rgba(16,18,29,.06);
--soft:#f6f7fb;
--soft-2:#eef1f8;
--star:#f5a623;
--radius:14px;
--radius-sm:10px;
--shadow:0 1px 2px rgba(16,18,29,.04), 0 8px 28px rgba(16,18,29,.07);
--shadow-lg:0 18px 50px rgba(16,18,29,.14);
--maxw:1180px;
--tone:#3457ff;
--tone-1:#dfe6ff;
--tone-2:#aebdff;
}
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
margin:0;
font-family:"Inter",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
color:var(--ink);
background:var(--bg);
line-height:1.5;
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
}
img,svg{display:block;max-width:100%}
button{font:inherit;color:inherit;cursor:pointer}
a{color:inherit;text-decoration:none}
h1,h2,h3,p,ul{margin:0}
ul{list-style:none;padding:0}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 22px}
.skip{
position:absolute;left:-9999px;top:0;z-index:50;
background:var(--ink);color:#fff;padding:10px 16px;border-radius:0 0 10px 0;
}
.skip:focus{left:0}
:focus-visible{outline:3px solid color-mix(in srgb,var(--brand) 55%,#fff);outline-offset:2px;border-radius:6px}
/* ---------- TOPBAR ---------- */
.topbar{position:sticky;top:0;z-index:30;background:rgba(255,255,255,.86);backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}
.topbar-inner{display:flex;align-items:center;gap:22px;height:64px}
.brand{display:inline-flex;align-items:center;gap:9px;font-weight:800;letter-spacing:-.02em;font-size:18px}
.brand-mark{display:grid;place-items:center;width:34px;height:34px;border-radius:10px;background:linear-gradient(135deg,var(--brand),#6f86ff);color:#fff}
.topnav{display:flex;gap:6px;margin-left:8px}
.topnav a{padding:8px 12px;border-radius:9px;font-weight:600;font-size:14.5px;color:var(--ink-2);transition:background .15s,color .15s}
.topnav a:hover{background:var(--soft)}
.sale-link{color:var(--sale)}
.topbar-actions{margin-left:auto;display:flex;align-items:center;gap:6px}
.icon-btn{display:grid;place-items:center;width:42px;height:42px;border:1px solid transparent;border-radius:11px;background:transparent;color:var(--ink);transition:background .15s,border-color .15s}
.icon-btn:hover{background:var(--soft);border-color:var(--line)}
.cart-btn{position:relative}
.cart-count{position:absolute;top:4px;right:3px;min-width:18px;height:18px;padding:0 5px;display:grid;place-items:center;font-size:11px;font-weight:700;color:#fff;background:var(--brand);border-radius:999px;border:2px solid #fff;transform:scale(0);transition:transform .2s cubic-bezier(.34,1.56,.64,1)}
.cart-count.show{transform:scale(1)}
.cart-count.pop{animation:pop .35s}
@keyframes pop{50%{transform:scale(1.4)}}
.promo-strip{background:var(--ink);color:#fff;text-align:center;font-size:12.5px;font-weight:500;letter-spacing:.01em;padding:8px 16px}
/* ---------- PDP LAYOUT ---------- */
.pdp{padding-top:18px;padding-bottom:60px}
.crumbs{display:flex;gap:8px;align-items:center;color:var(--muted);font-size:13px;margin-bottom:18px;flex-wrap:wrap}
.crumbs a:hover{color:var(--ink);text-decoration:underline}
.crumbs [aria-current]{color:var(--ink);font-weight:600}
.pdp-grid{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:46px;align-items:start}
/* ---------- GALLERY ---------- */
.gallery{position:sticky;top:88px}
.gallery-main{position:relative;border-radius:var(--radius);overflow:hidden;border:1px solid var(--line)}
.stage{
aspect-ratio:1/1;
background:
radial-gradient(120% 90% at 75% 12%, rgba(255,255,255,.55), transparent 55%),
linear-gradient(155deg, var(--tone-1), #fff 78%);
display:grid;place-items:center;
transition:background .35s ease;
position:relative;
}
.stage::after{
content:"";
width:58%;height:66%;
border-radius:18px 18px 26px 26px;
background:
linear-gradient(180deg, var(--tone) 0%, color-mix(in srgb,var(--tone) 78%,#000) 100%);
box-shadow:
inset 0 8px 22px rgba(255,255,255,.18),
inset 0 -14px 28px rgba(0,0,0,.22),
0 22px 40px color-mix(in srgb,var(--tone) 40%,transparent);
position:relative;
transition:all .35s ease;
}
.stage::before{
content:"";
position:absolute;
width:58%;height:66%;
border-radius:18px 18px 26px 26px;
pointer-events:none;z-index:2;
background:
/* roll-top strap line */
linear-gradient(transparent 14%, rgba(0,0,0,.16) 14.4%, transparent 15%),
/* front pocket */
radial-gradient(60% 30% at 50% 78%, rgba(0,0,0,.18), transparent 60%),
/* zipper sheen */
linear-gradient(115deg, transparent 44%, rgba(255,255,255,.35) 50%, transparent 56%);
}
/* view variants reshape the silhouette */
.stage[data-view="side"]::after{width:42%;border-radius:14px 26px 26px 14px}
.stage[data-view="open"]::after{width:60%;border-radius:8px 8px 22px 22px;background:linear-gradient(180deg, color-mix(in srgb,var(--tone) 60%,#fff) 0%, var(--tone) 100%)}
.stage[data-view="open"]::before{background:repeating-linear-gradient(180deg, transparent 0 16px, rgba(0,0,0,.08) 16px 17px),radial-gradient(70% 40% at 50% 30%, rgba(255,255,255,.4), transparent 60%)}
.stage[data-view="detail"]::after{width:46%;height:46%;border-radius:50px}
.stage[data-view="detail"]::before{width:46%;height:46%;background:radial-gradient(circle at 50% 40%, rgba(255,255,255,.5), transparent 45%),linear-gradient(115deg, transparent 40%, rgba(255,255,255,.4) 50%, transparent 60%)}
.stage-badge{position:absolute;top:14px;left:14px;background:var(--sale);color:#fff;font-weight:700;font-size:13px;padding:5px 11px;border-radius:999px;box-shadow:0 4px 12px color-mix(in srgb,var(--sale) 40%,transparent);z-index:3}
.stage-wish{position:absolute;top:12px;right:12px;width:42px;height:42px;display:grid;place-items:center;border:1px solid var(--line);background:rgba(255,255,255,.9);color:var(--ink);border-radius:50%;z-index:3;transition:transform .15s,color .15s,background .15s}
.stage-wish:hover{transform:scale(1.06)}
.stage-wish[aria-pressed="true"]{color:var(--sale);background:#fff}
.stage-wish[aria-pressed="true"] svg{fill:var(--sale)}
.thumbs{display:flex;gap:11px;margin-top:14px}
.thumb{
flex:1;aspect-ratio:1/1;border-radius:12px;border:2px solid var(--line);background:linear-gradient(155deg,var(--tone-1),#fff);position:relative;overflow:hidden;transition:border-color .15s,transform .15s;
}
.thumb::after{content:"";position:absolute;inset:24%;border-radius:10px;background:var(--tone);box-shadow:inset 0 -8px 14px rgba(0,0,0,.2)}
.thumb[data-view="side"]::after{inset:24% 34%}
.thumb[data-view="open"]::after{background:linear-gradient(180deg,color-mix(in srgb,var(--tone) 55%,#fff),var(--tone))}
.thumb[data-view="detail"]::after{inset:30%;border-radius:50%}
.thumb:hover{transform:translateY(-2px)}
.thumb.is-active{border-color:var(--brand)}
.trust-strip{display:flex;flex-wrap:wrap;gap:6px 18px;margin-top:18px;color:var(--ink-2);font-size:12.5px;font-weight:500}
.trust-strip li{display:inline-flex;align-items:center;gap:6px}
.trust-strip svg{color:var(--brand)}
/* ---------- BUY BOX ---------- */
.eyebrow{color:var(--brand);font-weight:700;font-size:12.5px;letter-spacing:.06em;text-transform:uppercase;margin-bottom:6px}
.title{font-size:clamp(26px,3.4vw,36px);font-weight:800;letter-spacing:-.025em;line-height:1.12}
.title-vol{font-weight:600;color:var(--muted)}
.rating-row{display:flex;align-items:center;gap:8px;margin-top:12px;flex-wrap:wrap;font-size:14px;color:var(--muted)}
.rating-row strong{color:var(--ink)}
.rating-count{text-decoration:underline;text-underline-offset:3px}
.rating-count:hover{color:var(--brand)}
.rating-sep{opacity:.6}
.sold{color:var(--ok);font-weight:600}
.stars{--pct:90%;display:inline-block;width:96px;height:18px;position:relative;background:
linear-gradient(90deg,#dfe2ea 0 100%);
-webkit-mask:repeating-linear-gradient(90deg,#000 0 14.4px,transparent 14.4px 19.2px);
mask:repeating-linear-gradient(90deg,#000 0 14.4px,transparent 14.4px 19.2px);
}
.stars{background:none}
.stars{position:relative}
.stars{width:100px}
.stars{display:inline-grid}
/* star rendering via two layered icon rows */
.stars{background:none;-webkit-mask:none;mask:none;width:auto;height:18px;line-height:0}
.stars{font-size:0}
.stars{
--star-empty:#d6dae3;
width:96px;height:18px;
background:var(--star-empty);
-webkit-mask:var(--starmask);mask:var(--starmask);
-webkit-mask-size:19.2px 18px;mask-size:19.2px 18px;
-webkit-mask-repeat:repeat-x;mask-repeat:repeat-x;
}
.stars{--starmask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 1.5l2.6 5.3 5.9.9-4.2 4.1 1 5.8L10 15l-5.3 2.6 1-5.8L1.5 7.7l5.9-.9z'/%3E%3C/svg%3E")}
.stars-fill{display:block;height:100%;width:var(--pct);background:var(--star);-webkit-mask:inherit;mask:inherit;-webkit-mask-size:inherit;mask-size:inherit;-webkit-mask-repeat:inherit;mask-repeat:inherit}
.stars-lg{width:128px;height:24px;-webkit-mask-size:25.6px 24px;mask-size:25.6px 24px}
.stars-sm{width:80px;height:15px;-webkit-mask-size:16px 15px;mask-size:16px 15px}
.price-row{display:flex;align-items:baseline;gap:12px;margin-top:18px;flex-wrap:wrap}
.price{font-size:30px;font-weight:800;letter-spacing:-.02em}
.price-was{color:var(--muted);text-decoration:line-through;font-size:18px}
.price-save{background:color-mix(in srgb,var(--sale) 12%,#fff);color:var(--sale);font-weight:700;font-size:13px;padding:3px 9px;border-radius:999px}
.stock-row{display:flex;align-items:center;gap:10px;margin-top:12px;flex-wrap:wrap;font-size:13.5px}
.stock-chip{display:inline-flex;align-items:center;gap:6px;font-weight:600;padding:4px 10px;border-radius:999px}
.stock-chip::before{content:"";width:7px;height:7px;border-radius:50%;background:currentColor}
.stock-chip.in-stock{color:var(--ok);background:color-mix(in srgb,var(--ok) 10%,#fff)}
.stock-chip.low{color:var(--warn);background:color-mix(in srgb,var(--warn) 12%,#fff)}
.stock-chip.out{color:var(--sale);background:color-mix(in srgb,var(--sale) 10%,#fff)}
.stock-note{color:var(--muted)}
.variant{margin-top:22px}
.variant-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.variant-label{font-weight:700;font-size:14px}
.variant-value{color:var(--muted);font-size:14px}
.size-guide{font-size:13px;color:var(--brand);text-decoration:underline;text-underline-offset:3px}
.swatches{display:flex;gap:10px}
.swatch{width:38px;height:38px;border-radius:50%;background:var(--sw);border:2px solid var(--line);position:relative;transition:transform .15s}
.swatch::after{content:"";position:absolute;inset:-5px;border-radius:50%;border:2px solid transparent;transition:border-color .15s}
.swatch:hover{transform:scale(1.05)}
.swatch.is-active::after{border-color:var(--ink)}
.chips{display:flex;gap:10px;flex-wrap:wrap}
.chip{position:relative;min-width:56px;padding:10px 14px;border-radius:11px;border:1.5px solid var(--line);background:#fff;font-weight:600;font-size:14px;color:var(--ink);transition:border-color .15s,background .15s,color .15s}
.chip:hover{border-color:var(--ink-2)}
.chip.is-active{border-color:var(--brand);background:color-mix(in srgb,var(--brand) 8%,#fff);color:var(--brand-d)}
.chip.is-disabled{color:var(--muted);background:var(--soft);border-style:dashed;cursor:not-allowed;text-decoration:line-through}
.chip-oos{display:block;font-size:10px;text-decoration:none;color:var(--sale);margin-top:1px;font-weight:600}
.action-row{display:flex;gap:12px;margin-top:26px;align-items:stretch}
.qty{display:inline-flex;align-items:center;border:1.5px solid var(--line);border-radius:12px;overflow:hidden;background:#fff}
.qty-btn{width:46px;align-self:stretch;border:none;background:#fff;font-size:20px;font-weight:600;color:var(--ink-2);transition:background .12s}
.qty-btn:hover{background:var(--soft)}
.qty-btn:disabled{color:#c3c8d2;cursor:not-allowed;background:#fff}
.qty-input{width:44px;text-align:center;border:none;background:transparent;font-weight:700;font-size:15px;color:var(--ink)}
.qty-input:focus{outline:none}
.btn-add{flex:1;border:none;border-radius:12px;background:var(--brand);color:#fff;font-weight:700;font-size:15.5px;padding:0 18px;display:flex;align-items:center;justify-content:center;gap:6px;box-shadow:0 8px 20px color-mix(in srgb,var(--brand) 34%,transparent);transition:background .15s,transform .1s,box-shadow .15s}
.btn-add:hover{background:var(--brand-d)}
.btn-add:active{transform:translateY(1px)}
.btn-add:disabled{background:#c3c8d2;box-shadow:none;cursor:not-allowed}
.btn-add-label{font-weight:600;opacity:.92}
.btn-wish{width:54px;border:1.5px solid var(--line);border-radius:12px;background:#fff;color:var(--ink-2);display:grid;place-items:center;transition:border-color .15s,color .15s,background .15s}
.btn-wish:hover{border-color:var(--ink-2)}
.btn-wish[aria-pressed="true"]{color:var(--sale);border-color:color-mix(in srgb,var(--sale) 40%,#fff);background:color-mix(in srgb,var(--sale) 6%,#fff)}
.btn-wish[aria-pressed="true"] svg{fill:var(--sale)}
.btn-buy{width:100%;margin-top:12px;border:1.5px solid var(--ink);border-radius:12px;background:var(--ink);color:#fff;font-weight:700;font-size:15px;padding:14px;transition:background .15s,opacity .15s}
.btn-buy:hover{background:#000}
.btn-buy:disabled{background:#c3c8d2;border-color:#c3c8d2;cursor:not-allowed}
.reassure{margin-top:20px;display:grid;gap:9px;color:var(--ink-2);font-size:13.5px}
.reassure li{display:flex;align-items:center;gap:9px}
.reassure svg{color:var(--ok);flex:none}
/* ---------- ACCORDION ---------- */
.accordion{margin-top:26px;border-top:1px solid var(--line)}
.acc-item{border-bottom:1px solid var(--line)}
.acc-head{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:16px 2px;background:none;border:none;font-weight:700;font-size:15px;text-align:left}
.acc-ico{position:relative;width:16px;height:16px;flex:none}
.acc-ico::before,.acc-ico::after{content:"";position:absolute;background:var(--ink-2);border-radius:2px;transition:transform .25s,opacity .25s}
.acc-ico::before{top:7px;left:0;width:16px;height:2px}
.acc-ico::after{top:0;left:7px;width:2px;height:16px}
.acc-item.is-open .acc-ico::after{transform:rotate(90deg);opacity:0}
.acc-body{display:grid;grid-template-rows:0fr;transition:grid-template-rows .28s ease}
.acc-item.is-open .acc-body{grid-template-rows:1fr}
.acc-body>*{overflow:hidden}
.acc-body p{color:var(--ink-2);font-size:14px;padding:0 2px 16px}
.spec-list{padding:0 2px 16px;display:grid;gap:8px}
.spec-list li{display:flex;justify-content:space-between;font-size:13.5px;border-bottom:1px dotted var(--line);padding-bottom:7px}
.spec-list li span:first-child{color:var(--muted)}
.spec-list li span:last-child{font-weight:600}
/* ---------- REVIEWS ---------- */
.sec-title{font-size:22px;font-weight:800;letter-spacing:-.02em;margin-bottom:20px}
.reviews{margin-top:54px;padding-top:36px;border-top:1px solid var(--line)}
.reviews-grid{display:grid;grid-template-columns:280px minmax(0,1fr);gap:42px;align-items:start}
.review-summary{background:var(--soft);border:1px solid var(--line);border-radius:var(--radius);padding:22px;text-align:center;position:sticky;top:88px}
.rs-score{font-size:46px;font-weight:800;letter-spacing:-.03em;line-height:1}
.review-summary .stars-lg{margin:8px auto 6px}
.rs-count{color:var(--muted);font-size:13px}
.rs-bars{margin:18px 0;display:grid;gap:8px}
.rs-bars li{display:flex;align-items:center;gap:9px;font-size:12.5px}
.rs-n{width:10px;color:var(--muted);font-weight:600}
.rs-track{flex:1;height:8px;background:#e2e6ef;border-radius:999px;overflow:hidden}
.rs-bar{display:block;height:100%;width:var(--w);background:var(--star);border-radius:999px}
.rs-pct{width:34px;text-align:right;color:var(--muted)}
.btn-ghost{width:100%;border:1.5px solid var(--ink);background:#fff;border-radius:11px;padding:11px;font-weight:700;font-size:14px;transition:background .15s,color .15s}
.btn-ghost:hover{background:var(--ink);color:#fff}
.review-list{display:grid;gap:20px}
.review{border:1px solid var(--line);border-radius:var(--radius);padding:18px 20px;transition:box-shadow .2s}
.review:hover{box-shadow:var(--shadow)}
.rev-head{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:11px;margin-bottom:12px}
.rev-avatar{width:42px;height:42px;border-radius:50%;display:grid;place-items:center;color:#fff;font-weight:700;font-size:14px;background:var(--a)}
.rev-head strong{font-size:14.5px;display:block}
.rev-meta{display:flex;align-items:center;gap:6px;color:var(--muted);font-size:12.5px}
.rev-date{color:var(--muted);font-size:12.5px;align-self:start}
.rev-title{font-weight:700;font-size:15px;margin-bottom:5px}
.review p:not(.rev-meta):not(.rev-title){color:var(--ink-2);font-size:14px}
.rev-help{margin-top:12px;border:1px solid var(--line);background:#fff;border-radius:9px;padding:7px 13px;font-size:13px;font-weight:600;color:var(--ink-2);transition:border-color .15s,background .15s}
.rev-help:hover{border-color:var(--brand);color:var(--brand)}
.rev-help.voted{border-color:var(--brand);color:var(--brand-d);background:color-mix(in srgb,var(--brand) 7%,#fff)}
/* ---------- RAIL ---------- */
.rail{margin-top:54px;padding-top:36px;border-top:1px solid var(--line)}
.rail-track{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(220px,1fr);gap:18px;overflow-x:auto;padding-bottom:8px;scroll-snap-type:x mandatory;scrollbar-width:thin}
.card{scroll-snap-align:start;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;background:#fff;display:flex;flex-direction:column;transition:transform .18s,box-shadow .18s;position:relative}
.card:hover{transform:translateY(-3px);box-shadow:var(--shadow)}
.card-img{aspect-ratio:4/3;position:relative;background:linear-gradient(155deg,var(--tone-1),#fff)}
.card-img::after{content:"";position:absolute;inset:24% 30%;border-radius:14px;background:var(--tone);box-shadow:inset 0 -10px 16px rgba(0,0,0,.2),0 10px 18px color-mix(in srgb,var(--tone) 30%,transparent)}
.card-body{padding:14px 16px 10px;flex:1;display:flex;flex-direction:column;gap:5px}
.card-name{font-weight:700;font-size:14.5px;letter-spacing:-.01em}
.card-name span{color:var(--muted);font-weight:600}
.card-stars{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted)}
.card-price{font-weight:800;font-size:16px;margin-top:auto}
.card-add{margin:0 16px 16px;border:1.5px solid var(--line);background:#fff;border-radius:10px;padding:9px;font-weight:700;font-size:13.5px;color:var(--brand-d);transition:background .15s,border-color .15s,color .15s}
.card-add:hover{background:var(--brand);border-color:var(--brand);color:#fff}
/* per-card tone */
[data-tone="cobalt"]{--tone:#3457ff;--tone-1:#dfe6ff;--tone-2:#aebdff}
[data-tone="moss"]{--tone:#3f6f4a;--tone-1:#e1ece2;--tone-2:#bcd7c0}
[data-tone="clay"]{--tone:#b5613a;--tone-1:#f4e3d9;--tone-2:#e6c2ac}
[data-tone="slate"]{--tone:#414857;--tone-1:#e4e7ee;--tone-2:#c3c9d6}
/* ---------- FOOTER ---------- */
.foot{border-top:1px solid var(--line);background:var(--soft);margin-top:30px}
.foot-inner{padding:28px 22px;display:flex;justify-content:space-between;gap:14px;flex-wrap:wrap;font-size:13px;color:var(--ink-2)}
.foot-fine{color:var(--muted)}
/* ---------- TOAST ---------- */
.toast-wrap{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:60;display:grid;gap:10px;width:min(94vw,380px)}
.toast{display:flex;align-items:center;gap:11px;background:var(--ink);color:#fff;border-radius:12px;padding:13px 15px;box-shadow:var(--shadow-lg);font-size:14px;font-weight:500;transform:translateY(20px);opacity:0;transition:transform .28s cubic-bezier(.34,1.56,.64,1),opacity .28s}
.toast.show{transform:translateY(0);opacity:1}
.toast-ico{display:grid;place-items:center;width:26px;height:26px;border-radius:8px;background:var(--ok);flex:none}
.toast-ico svg{stroke:#fff}
.toast-text strong{display:block;font-weight:700}
.toast-text span{font-size:12.5px;color:rgba(255,255,255,.72)}
/* ---------- RESPONSIVE ---------- */
@media (max-width:920px){
.pdp-grid{grid-template-columns:1fr;gap:34px}
.gallery{position:static}
.reviews-grid{grid-template-columns:1fr;gap:26px}
.review-summary{position:static;display:grid;justify-items:center}
}
@media (max-width:620px){
.topnav{display:none}
.promo-strip{font-size:11.5px}
.stage-badge{font-size:12px}
.price{font-size:26px}
.rail-track{grid-auto-columns:minmax(180px,78%)}
}
@media (max-width:420px){
.wrap{padding:0 16px}
.action-row{flex-wrap:wrap}
.btn-add{order:3;flex-basis:100%}
.reassure{font-size:13px}
}
@media (prefers-reduced-motion:reduce){
*{animation-duration:.001ms!important;transition-duration:.001ms!important}
}(function () {
"use strict";
/* ---------- state ---------- */
var BASE_PRICE = 149; // base price for 32L / cobalt
var COMPARE_AT = 194; // strike-through compare-at
var state = {
color: "Cobalt",
tone: "cobalt",
size: "32L",
sizeDelta: 0,
qty: 1,
wished: false,
cart: 0
};
// per-size stock so variant selection can update the stock chip
var STOCK = { "22L": 28, "32L": 14, "40L": 6, "55L": 0 };
/* ---------- helpers ---------- */
function money(n) {
return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function $(sel, root) { return (root || document).querySelector(sel); }
function $all(sel, root) { return Array.prototype.slice.call((root || document).querySelectorAll(sel)); }
function unitPrice() {
var p = BASE_PRICE + state.sizeDelta;
return p < 0 ? 0 : p;
}
function unitCompare() {
// keep the same discount ratio on the compare-at price
var ratio = COMPARE_AT / BASE_PRICE;
return Math.round(unitPrice() * ratio);
}
/* ---------- toast ---------- */
var toastWrap = $("#toastWrap");
var CHECK = '<span class="toast-ico"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg></span>';
function toast(title, sub) {
var el = document.createElement("div");
el.className = "toast";
el.setAttribute("role", "status");
el.innerHTML = CHECK + '<div class="toast-text"><strong>' + title + "</strong>" +
(sub ? "<span>" + sub + "</span>" : "") + "</div>";
toastWrap.appendChild(el);
requestAnimationFrame(function () { el.classList.add("show"); });
setTimeout(function () {
el.classList.remove("show");
setTimeout(function () { el.remove(); }, 300);
}, 2600);
}
/* ---------- price / stock rendering ---------- */
var elPrice = $("#price");
var elWas = $("#priceWas");
var elSave = $("#priceSave");
var elAddPrice = $("#addPrice");
var elBadge = $("#discountBadge");
var stockChip = $("#stockChip");
var stockNote = $("#stockNote");
var addBtn = $("#addToCart");
var buyBtn = $("#buyNow");
function renderPrice() {
var unit = unitPrice();
var was = unitCompare();
var save = was - unit;
var pct = Math.round((save / was) * 100);
elPrice.textContent = money(unit);
elWas.textContent = money(was);
elSave.textContent = "Save " + money(save);
elAddPrice.textContent = money(unit * state.qty);
elBadge.textContent = "−" + pct + "%";
}
function renderStock() {
var left = STOCK[state.size];
stockChip.classList.remove("in-stock", "low", "out");
var soldOut = left <= 0;
if (soldOut) {
stockChip.classList.add("out");
stockChip.textContent = "Sold out";
stockNote.textContent = "Notify me when " + state.size + " is back";
} else if (left <= 8) {
stockChip.classList.add("low");
stockChip.textContent = "Low stock";
stockNote.textContent = "Ships today · only " + left + " left at this price";
} else {
stockChip.classList.add("in-stock");
stockChip.textContent = "In stock";
stockNote.textContent = "Ships today · " + left + " available";
}
addBtn.disabled = soldOut;
buyBtn.disabled = soldOut;
if (soldOut) {
addBtn.querySelector(".btn-add-label").textContent = "Sold out";
$("#addPrice").textContent = "";
} else {
addBtn.querySelector(".btn-add-label").textContent = "Add to cart ·";
renderPrice();
}
}
/* ---------- gallery ---------- */
var stage = $("#stage");
function setView(view) {
stage.dataset.view = view;
$all(".thumb").forEach(function (t) {
var on = t.dataset.view === view;
t.classList.toggle("is-active", on);
t.setAttribute("aria-selected", on ? "true" : "false");
});
}
$all(".thumb").forEach(function (t) {
t.addEventListener("click", function () { setView(t.dataset.view); });
});
function setTone(tone) {
state.tone = tone;
document.documentElement.style.setProperty("--tone", "");
stage.dataset.tone = tone;
// map tone to its palette by toggling a data-tone on the gallery
var galleryMain = $(".gallery-main");
galleryMain.setAttribute("data-tone", tone);
stage.setAttribute("data-tone", tone);
// thumbs share tone
$all(".thumb").forEach(function (t) { t.setAttribute("data-tone", tone); });
}
/* ---------- color swatches ---------- */
var colorValue = $("#colorValue");
$all(".swatch").forEach(function (sw) {
sw.addEventListener("click", function () { selectColor(sw); });
sw.addEventListener("keydown", arrowNav(".swatch", selectColor));
});
function selectColor(sw) {
$all(".swatch").forEach(function (s) {
var on = s === sw;
s.classList.toggle("is-active", on);
s.setAttribute("aria-checked", on ? "true" : "false");
});
state.color = sw.dataset.color;
colorValue.textContent = state.color;
setTone(sw.dataset.tone);
sw.focus();
}
/* ---------- size chips ---------- */
$all(".chip").forEach(function (chip) {
if (chip.classList.contains("is-disabled")) {
chip.addEventListener("click", function () {
toast("We'll let you know", state.color + " " + chip.dataset.size + " is sold out — tap to be notified.");
});
return;
}
chip.addEventListener("click", function () { selectSize(chip); });
chip.addEventListener("keydown", arrowNav(".chip:not(.is-disabled)", selectSize));
});
function selectSize(chip) {
$all(".chip").forEach(function (c) {
var on = c === chip;
c.classList.toggle("is-active", on);
c.setAttribute("aria-checked", on ? "true" : "false");
});
state.size = chip.dataset.size;
state.sizeDelta = parseInt(chip.dataset.delta, 10) || 0;
renderStock();
chip.focus();
}
/* roving arrow-key navigation for radio groups */
function arrowNav(selector, choose) {
return function (e) {
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft" && e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
e.preventDefault();
var items = $all(selector);
var i = items.indexOf(e.currentTarget);
var fwd = e.key === "ArrowRight" || e.key === "ArrowDown";
var next = items[(i + (fwd ? 1 : -1) + items.length) % items.length];
if (next) choose(next);
};
}
/* ---------- quantity stepper ---------- */
var qtyInput = $("#qtyInput");
var qtyMinus = $("#qtyMinus");
var qtyPlus = $("#qtyPlus");
var MAX_QTY = 12;
function setQty(n) {
n = parseInt(n, 10);
if (isNaN(n) || n < 1) n = 1;
if (n > MAX_QTY) { n = MAX_QTY; toast("Max reached", "Limited to " + MAX_QTY + " per order."); }
state.qty = n;
qtyInput.value = n;
qtyMinus.disabled = n <= 1;
qtyPlus.disabled = n >= MAX_QTY;
renderPrice();
}
qtyMinus.addEventListener("click", function () { setQty(state.qty - 1); });
qtyPlus.addEventListener("click", function () { setQty(state.qty + 1); });
qtyInput.addEventListener("input", function () { qtyInput.value = qtyInput.value.replace(/[^0-9]/g, ""); });
qtyInput.addEventListener("change", function () { setQty(qtyInput.value); });
/* ---------- cart ---------- */
var cartCount = $("#cartCount");
function addToCart(qty, label) {
state.cart += qty;
cartCount.textContent = state.cart;
cartCount.classList.add("show");
cartCount.classList.remove("pop");
void cartCount.offsetWidth; // reflow to restart animation
cartCount.classList.add("pop");
$("#cartButton").setAttribute("aria-label", "Cart, " + state.cart + " item" + (state.cart === 1 ? "" : "s"));
toast("Added to cart", label);
}
addBtn.addEventListener("click", function () {
if (addBtn.disabled) return;
var label = state.qty + " × " + state.color + " Aurora Field Pack " + state.size +
" — " + money(unitPrice() * state.qty);
addToCart(state.qty, label);
});
buyBtn.addEventListener("click", function () {
if (buyBtn.disabled) return;
addToCart(state.qty, "Heading to secure checkout…");
toast("Secure checkout", "Encrypted · this is a demo, no real payment.");
});
/* "you may also like" quick-add */
$all(".card-add").forEach(function (btn) {
btn.addEventListener("click", function () {
var card = btn.closest(".card");
var name = card.getAttribute("data-name");
var price = parseFloat(card.getAttribute("data-price"));
addToCart(1, "1 × " + name + " — " + money(price));
});
});
/* ---------- wishlist ---------- */
var wishBtn = $("#wishBtn");
var stageWish = $("#stageWish");
function toggleWish() {
state.wished = !state.wished;
[wishBtn, stageWish].forEach(function (b) {
b.setAttribute("aria-pressed", state.wished ? "true" : "false");
});
toast(state.wished ? "Saved to wishlist" : "Removed from wishlist",
state.wished ? state.color + " Aurora Field Pack " + state.size : null);
}
wishBtn.addEventListener("click", toggleWish);
stageWish.addEventListener("click", toggleWish);
/* ---------- accordions ---------- */
$all(".acc-head").forEach(function (head) {
head.addEventListener("click", function () {
var item = head.closest(".acc-item");
var open = item.classList.toggle("is-open");
head.setAttribute("aria-expanded", open ? "true" : "false");
});
});
/* ---------- review helpful votes ---------- */
$all(".rev-help").forEach(function (btn) {
btn.addEventListener("click", function () {
var span = btn.querySelector("span");
var voted = btn.classList.toggle("voted");
var n = parseInt(span.textContent.replace(/[^0-9]/g, ""), 10) || 0;
span.textContent = "(" + (voted ? n + 1 : n - 1) + ")";
});
});
$("#writeReview").addEventListener("click", function () {
toast("Thanks!", "Review form would open here in a real store.");
});
/* ---------- init ---------- */
setQty(1);
setView("default");
renderStock();
renderPrice();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Aurora Field Pack — Northwind Supply Co.</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip" href="#pdp">Skip to product</a>
<header class="topbar" role="banner">
<div class="wrap topbar-inner">
<a class="brand" href="#" aria-label="Northwind Supply Co. home">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 4 7v10l8 5 8-5V7z"/><path d="M12 22V12"/><path d="m4 7 8 5 8-5"/></svg>
</span>
<span class="brand-name">Northwind</span>
</a>
<nav class="topnav" aria-label="Primary">
<a href="#">Packs</a>
<a href="#">Apparel</a>
<a href="#">Camp</a>
<a href="#" class="sale-link">Sale</a>
</nav>
<div class="topbar-actions">
<button class="icon-btn" type="button" aria-label="Search"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg></button>
<button class="icon-btn cart-btn" type="button" aria-label="Cart, 0 items" id="cartButton">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.7 13.4a2 2 0 0 0 2 1.6h9.7a2 2 0 0 0 2-1.6L23 6H6"/></svg>
<span class="cart-count" id="cartCount" aria-hidden="true">0</span>
</button>
</div>
</div>
</header>
<div class="promo-strip" role="note">Free 2-day shipping over $75 · 30-day returns · Lifetime repair guarantee</div>
<main id="pdp" class="wrap pdp" role="main">
<nav class="crumbs" aria-label="Breadcrumb">
<a href="#">Home</a><span aria-hidden="true">/</span>
<a href="#">Packs</a><span aria-hidden="true">/</span>
<span aria-current="page">Aurora Field Pack 32L</span>
</nav>
<div class="pdp-grid">
<!-- GALLERY -->
<section class="gallery" aria-label="Product gallery">
<div class="gallery-main">
<div class="stage" id="stage" data-view="default" aria-live="polite"></div>
<span class="stage-badge" id="discountBadge">−23%</span>
<button class="stage-wish" type="button" id="stageWish" aria-pressed="false" aria-label="Save to wishlist">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.7l-1-1.1a5.5 5.5 0 1 0-7.8 7.8l1 1L12 21l7.8-7.6 1-1a5.5 5.5 0 0 0 0-7.8z"/></svg>
</button>
</div>
<div class="thumbs" role="tablist" aria-label="Product views">
<button class="thumb is-active" role="tab" aria-selected="true" data-view="default" data-tone="cobalt" aria-label="Front view"></button>
<button class="thumb" role="tab" aria-selected="false" data-view="side" data-tone="cobalt" aria-label="Side view"></button>
<button class="thumb" role="tab" aria-selected="false" data-view="open" data-tone="cobalt" aria-label="Open compartment"></button>
<button class="thumb" role="tab" aria-selected="false" data-view="detail" data-tone="cobalt" aria-label="Strap detail"></button>
</div>
<ul class="trust-strip" aria-label="Why buy from us">
<li><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>Lifetime guarantee</li>
<li><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12l5 5L20 7"/></svg>Recycled materials</li>
<li><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 7h13v10H3z"/><path d="M16 10h4l1 3v4h-5"/></svg>Carbon-neutral ship</li>
</ul>
</section>
<!-- BUY BOX -->
<section class="buybox" aria-label="Product information">
<p class="eyebrow">Northwind · Trail Series</p>
<h1 class="title" id="prodTitle">Aurora Field Pack <span class="title-vol">32L</span></h1>
<div class="rating-row">
<span class="stars" role="img" aria-label="Rated 4.6 out of 5 stars">
<span class="stars-fill" style="--pct:92%"></span>
</span>
<strong>4.6</strong>
<a href="#reviews" class="rating-count">1,248 reviews</a>
<span class="rating-sep" aria-hidden="true">·</span>
<span class="sold">3.4k sold</span>
</div>
<div class="price-row" aria-live="polite">
<span class="price" id="price">$149.00</span>
<span class="price-was" id="priceWas">$194.00</span>
<span class="price-save" id="priceSave">Save $45.00</span>
</div>
<div class="stock-row" id="stockRow">
<span class="stock-chip in-stock" id="stockChip">In stock</span>
<span class="stock-note" id="stockNote">Ships today · 14 left at this price</span>
</div>
<!-- COLOR VARIANT -->
<div class="variant">
<div class="variant-head">
<span class="variant-label">Color</span>
<span class="variant-value" id="colorValue">Cobalt</span>
</div>
<div class="swatches" role="radiogroup" aria-label="Color">
<button class="swatch is-active" role="radio" aria-checked="true" data-color="Cobalt" data-tone="cobalt" style="--sw:#3457ff" aria-label="Cobalt"></button>
<button class="swatch" role="radio" aria-checked="false" data-color="Moss" data-tone="moss" style="--sw:#3f6f4a" aria-label="Moss"></button>
<button class="swatch" role="radio" aria-checked="false" data-color="Clay" data-tone="clay" style="--sw:#b5613a" aria-label="Clay"></button>
<button class="swatch" role="radio" aria-checked="false" data-color="Slate" data-tone="slate" style="--sw:#414857" aria-label="Slate"></button>
</div>
</div>
<!-- SIZE VARIANT -->
<div class="variant">
<div class="variant-head">
<span class="variant-label">Capacity</span>
<a href="#" class="size-guide">Size guide</a>
</div>
<div class="chips" role="radiogroup" aria-label="Capacity">
<button class="chip" role="radio" aria-checked="false" data-size="22L" data-delta="-30">22L</button>
<button class="chip is-active" role="radio" aria-checked="true" data-size="32L" data-delta="0">32L</button>
<button class="chip" role="radio" aria-checked="false" data-size="40L" data-delta="35">40L</button>
<button class="chip is-disabled" role="radio" aria-checked="false" aria-disabled="true" data-size="55L" data-delta="60">55L<span class="chip-oos">Sold out</span></button>
</div>
</div>
<!-- QTY + ACTIONS -->
<div class="action-row">
<div class="qty" role="group" aria-label="Quantity">
<button class="qty-btn" type="button" id="qtyMinus" aria-label="Decrease quantity">−</button>
<input class="qty-input" id="qtyInput" type="text" inputmode="numeric" value="1" aria-label="Quantity" />
<button class="qty-btn" type="button" id="qtyPlus" aria-label="Increase quantity">+</button>
</div>
<button class="btn-add" type="button" id="addToCart">
<span class="btn-add-label">Add to cart ·</span> <span id="addPrice">$149.00</span>
</button>
<button class="btn-wish" type="button" id="wishBtn" aria-pressed="false" aria-label="Save to wishlist">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.7l-1-1.1a5.5 5.5 0 1 0-7.8 7.8l1 1L12 21l7.8-7.6 1-1a5.5 5.5 0 0 0 0-7.8z"/></svg>
</button>
</div>
<button class="btn-buy" type="button" id="buyNow">Buy it now</button>
<ul class="reassure" aria-label="Purchase assurances">
<li><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="10" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>Secure checkout · encrypted</li>
<li><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 7h13v10H3z"/><path d="M16 10h4l1 3v4h-5"/></svg>Free 2-day shipping over $75</li>
<li><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 9-9"/><path d="M3 4v5h5"/></svg>30-day free returns</li>
</ul>
<!-- ACCORDIONS -->
<div class="accordion" id="accordion">
<div class="acc-item is-open">
<button class="acc-head" type="button" aria-expanded="true">Details & materials<span class="acc-ico" aria-hidden="true"></span></button>
<div class="acc-body">
<p>A weatherproof daily hauler built from 420D recycled ripstop with a waxed-canvas base. Padded laptop sleeve fits up to 16″, roll-top main compartment, magnetic side pockets, and a sternum strap rated to 30 lb.</p>
<ul class="spec-list">
<li><span>Weight</span><span>2.1 lb / 0.95 kg</span></li>
<li><span>Fabric</span><span>420D recycled ripstop</span></li>
<li><span>Water rating</span><span>IPX-4 splash resistant</span></li>
</ul>
</div>
</div>
<div class="acc-item">
<button class="acc-head" type="button" aria-expanded="false">Shipping<span class="acc-ico" aria-hidden="true"></span></button>
<div class="acc-body">
<p>Free carbon-neutral 2-day shipping on orders over $75. Standard delivery 3–5 business days. Orders placed before 2pm ET ship same day.</p>
</div>
</div>
<div class="acc-item">
<button class="acc-head" type="button" aria-expanded="false">Returns & warranty<span class="acc-ico" aria-hidden="true"></span></button>
<div class="acc-body">
<p>30-day no-questions returns, prepaid label included. Every pack carries our lifetime repair guarantee — send it back and we'll fix the wear, on us.</p>
</div>
</div>
</div>
</section>
</div>
<!-- REVIEWS -->
<section class="reviews" id="reviews" aria-label="Customer reviews">
<h2 class="sec-title">Customer reviews</h2>
<div class="reviews-grid">
<aside class="review-summary">
<div class="rs-score">4.6</div>
<span class="stars stars-lg" role="img" aria-label="Average 4.6 of 5">
<span class="stars-fill" style="--pct:92%"></span>
</span>
<p class="rs-count">Based on 1,248 reviews</p>
<ul class="rs-bars">
<li><span class="rs-n">5</span><span class="rs-track"><span class="rs-bar" style="--w:74%"></span></span><span class="rs-pct">74%</span></li>
<li><span class="rs-n">4</span><span class="rs-track"><span class="rs-bar" style="--w:16%"></span></span><span class="rs-pct">16%</span></li>
<li><span class="rs-n">3</span><span class="rs-track"><span class="rs-bar" style="--w:6%"></span></span><span class="rs-pct">6%</span></li>
<li><span class="rs-n">2</span><span class="rs-track"><span class="rs-bar" style="--w:2%"></span></span><span class="rs-pct">2%</span></li>
<li><span class="rs-n">1</span><span class="rs-track"><span class="rs-bar" style="--w:2%"></span></span><span class="rs-pct">2%</span></li>
</ul>
<button class="btn-ghost" type="button" id="writeReview">Write a review</button>
</aside>
<ul class="review-list">
<li class="review">
<div class="rev-head">
<span class="rev-avatar" style="--a:#3457ff" aria-hidden="true">JM</span>
<div>
<strong>Jordan M.</strong>
<span class="rev-meta"><span class="stars stars-sm" role="img" aria-label="5 of 5"><span class="stars-fill" style="--pct:100%"></span></span> · Verified buyer</span>
</div>
<span class="rev-date">2 weeks ago</span>
</div>
<p class="rev-title">Carried it through three countries</p>
<p>Roll-top kept everything dry in a downpour in Lisbon. The laptop sleeve is genuinely padded, not an afterthought. Straps held up to airline gate-checking like a champ.</p>
<button class="rev-help" type="button">Helpful <span>(214)</span></button>
</li>
<li class="review">
<div class="rev-head">
<span class="rev-avatar" style="--a:#3f6f4a" aria-hidden="true">AR</span>
<div>
<strong>Avery R.</strong>
<span class="rev-meta"><span class="stars stars-sm" role="img" aria-label="4 of 5"><span class="stars-fill" style="--pct:80%"></span></span> · Verified buyer</span>
</div>
<span class="rev-date">1 month ago</span>
</div>
<p class="rev-title">Great, wish the 32L were a touch bigger</p>
<p>Build quality is excellent and the magnetic pockets are oddly satisfying. Sized up to the 40L for ski weekends and that's the sweet spot for me.</p>
<button class="rev-help" type="button">Helpful <span>(96)</span></button>
</li>
<li class="review">
<div class="rev-head">
<span class="rev-avatar" style="--a:#b5613a" aria-hidden="true">SK</span>
<div>
<strong>Sam K.</strong>
<span class="rev-meta"><span class="stars stars-sm" role="img" aria-label="5 of 5"><span class="stars-fill" style="--pct:100%"></span></span> · Verified buyer</span>
</div>
<span class="rev-date">1 month ago</span>
</div>
<p class="rev-title">The repair guarantee is real</p>
<p>Frayed a strap after two years of daily commuting. Mailed it in with the prepaid label, got it back stitched and good as new. That's why I keep buying Northwind.</p>
<button class="rev-help" type="button">Helpful <span>(341)</span></button>
</li>
</ul>
</div>
</section>
<!-- YOU MAY ALSO LIKE -->
<section class="rail" aria-label="You may also like">
<h2 class="sec-title">You may also like</h2>
<div class="rail-track" id="rail">
<article class="card" data-tone="moss" data-name="Ridgeline Daypack 18L" data-price="89">
<div class="card-img"></div>
<div class="card-body">
<p class="card-name">Ridgeline Daypack <span>18L</span></p>
<span class="card-stars" role="img" aria-label="4.5 of 5"><span class="stars stars-sm"><span class="stars-fill" style="--pct:90%"></span></span> (612)</span>
<p class="card-price">$89.00</p>
</div>
<button class="card-add" type="button" aria-label="Add Ridgeline Daypack to cart">Add +</button>
</article>
<article class="card" data-tone="clay" data-name="Summit Roll-Top 40L" data-price="184">
<div class="card-img"></div>
<div class="card-body">
<p class="card-name">Summit Roll-Top <span>40L</span></p>
<span class="card-stars" role="img" aria-label="4.8 of 5"><span class="stars stars-sm"><span class="stars-fill" style="--pct:96%"></span></span> (438)</span>
<p class="card-price">$184.00</p>
</div>
<button class="card-add" type="button" aria-label="Add Summit Roll-Top to cart">Add +</button>
</article>
<article class="card" data-tone="slate" data-name="Transit Sling 7L" data-price="64">
<div class="card-img"></div>
<div class="card-body">
<p class="card-name">Transit Sling <span>7L</span></p>
<span class="card-stars" role="img" aria-label="4.4 of 5"><span class="stars stars-sm"><span class="stars-fill" style="--pct:88%"></span></span> (294)</span>
<p class="card-price">$64.00</p>
</div>
<button class="card-add" type="button" aria-label="Add Transit Sling to cart">Add +</button>
</article>
<article class="card" data-tone="cobalt" data-name="Packable Rain Cover" data-price="29">
<div class="card-img"></div>
<div class="card-body">
<p class="card-name">Packable <span>Rain Cover</span></p>
<span class="card-stars" role="img" aria-label="4.7 of 5"><span class="stars stars-sm"><span class="stars-fill" style="--pct:94%"></span></span> (1.1k)</span>
<p class="card-price">$29.00</p>
</div>
<button class="card-add" type="button" aria-label="Add Packable Rain Cover to cart">Add +</button>
</article>
</div>
</section>
</main>
<footer class="foot" role="contentinfo">
<div class="wrap foot-inner">
<p>Northwind Supply Co. — built for the long way around.</p>
<p class="foot-fine">Illustrative storefront UI only · fictional products & prices · no real checkout.</p>
</div>
</footer>
<div class="toast-wrap" id="toastWrap" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>Product Detail (PDP)
A polished, self-contained product detail page for a fictional outdoor-gear storefront. The two-column layout pairs a sticky gallery — a CSS-gradient and inline-SVG product silhouette with four switchable thumbnail views — against a full buy box: eyebrow, title, star rating with review count, a sale price showing compare-at and savings, and a stock chip. Color swatches and capacity chips are real radio groups; choosing one updates the brand tone, the live price, and the per-variant stock state.
The quantity stepper clamps between one and twelve and keeps the add-to-cart button’s total in sync. Add-to-cart, Buy it now, wishlist, and the quick-add buttons on the “you may also like” rail all push animated toasts and bump the header cart badge. Shipping, returns, and details collapse into accessible accordions, and the reviews block renders a rating-distribution breakdown alongside verified customer reviews with toggleable “helpful” votes.
Everything is keyboard-operable — arrow keys rove across swatches and chips, controls expose aria state, and focus-visible rings stay sharp. The layout collapses to a single column on tablets and reflows the action row at phone widths, with a reduced-motion fallback.
Illustrative storefront UI only — fictional products, prices, and reviews. No real checkout.