@use 'sprucecss/scss/spruce' as *;
$product-spotlight-star: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m12 2 3.1 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.8 21l1.2-6.8-5-4.9 6.9-1z"/></svg>';
.product-spotlight-block {
padding-block: spacer-clamp('xl', 'xxl');
&__inner {
align-items: start;
display: grid;
gap: spacer-clamp('l', 'xl');
@include breakpoint('md') {
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
}
&__media {
border-radius: radius('lg');
overflow: hidden;
position: relative;
@include breakpoint('md') {
inset-block-start: spacer('l');
position: sticky;
}
img {
aspect-ratio: 1;
background-color: color('surface-muted');
inline-size: 100%;
object-fit: cover;
}
}
&__tag {
background-color: color('danger', 'alert');
border-radius: radius('pill');
color: hsl(0deg 0% 100%);
font-weight: 700;
inset-block-start: spacer('s');
inset-inline-start: spacer('s');
padding: spacer('xxs') spacer('s');
position: absolute;
}
&__content {
@include layout-stack('m');
}
&__eyebrow {
color: color('primary');
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
&__eyebrow + &__title {
margin-block-start: spacer('xxs');
}
&__title {
font-size: responsive-font-size(2.75rem, 35, '3vw + 1rem');
}
&__rating {
align-items: center;
display: flex;
flex-wrap: wrap;
font-size: config('font-size-sm', $typography);
gap: spacer('xs');
}
&__stars {
--size: 1.1rem;
background-image: linear-gradient(to right, #{color('warning', 'alert')} calc(var(--rating) / 5 * 100%), #{color('border-strong')} 0);
block-size: var(--size);
display: inline-block;
inline-size: calc(var(--size) * 5);
mask: url('#{svg-escape($product-spotlight-star)}') 0 0 / var(--size) var(--size) repeat-x;
}
&__price {
color: color('heading');
font-size: font-size('h3');
font-weight: 700;
del {
color: color('muted');
font-size: config('font-size-lg', $typography);
font-weight: 400;
}
}
&__text {
font-size: config('font-size-lg', $typography);
}
&__features {
list-style: none;
padding: 0;
@include layout-stack('xxs');
li {
align-items: center;
display: flex;
gap: spacer('xs');
&::before {
color: color('success', 'alert');
content: '✓';
font-weight: 700;
}
}
}
&__buy {
display: flex;
flex-wrap: wrap;
gap: spacer('xs');
.btn:first-child {
flex: 1 1 12rem;
}
}
}
[dir='rtl'] .product-spotlight-block__stars {
background-image: linear-gradient(to left, #{color('warning', 'alert')} calc(var(--rating) / 5 * 100%), #{color('border-strong')} 0);
}
<section class="product-spotlight-block" aria-labelledby="product-spotlight-title">
<div class="container">
<div class="product-spotlight-block__inner">
<div class="product-spotlight-block__media">
<img src="https://images.unsplash.com/photo-1596755094514-f87e34085b2c?auto=format&fit=crop&w=1000&q=75" alt="The sand-colored linen overshirt on a hanger." width="1000" height="1000" loading="lazy">
<span class="product-spotlight-block__tag">Sale</span>
</div>
<div class="product-spotlight-block__content">
<p class="product-spotlight-block__eyebrow">Product of the month</p>
<h2 class="product-spotlight-block__title" id="product-spotlight-title">The Linen Overshirt</h2>
<p class="product-spotlight-block__rating">
<span class="product-spotlight-block__stars" style="--rating: 4.8;" aria-hidden="true"></span>
<a href="#">4.8 out of 5 · 312 reviews</a>
</p>
<p class="product-spotlight-block__price"><span class="sr-only">Sale price: </span>$79 <del><span class="sr-only">Original price: </span>$98</del></p>
<p class="product-spotlight-block__text">Washed European linen that gets softer with every wear. Cut a little loose, with a real collar and corozo buttons.</p>
<ul class="product-spotlight-block__features" role="list">
<li>100% European linen</li>
<li>Garment-washed, pre-shrunk</li>
<li>Made in Portugal</li>
</ul>
<section class="stock-indicator" aria-label="Availability" data-stock-indicator>
<fieldset class="stock-indicator__sizes">
<legend class="stock-indicator__legend">Size</legend>
<label class="stock-indicator__size"><input type="radio" name="stock-size" value="S" data-stock="14"><span>S</span></label>
<label class="stock-indicator__size"><input type="radio" name="stock-size" value="M" data-stock="3" checked><span>M</span></label>
<label class="stock-indicator__size"><input type="radio" name="stock-size" value="L" data-stock="0"><span>L</span></label>
<label class="stock-indicator__size"><input type="radio" name="stock-size" value="XL" data-stock="8"><span>XL</span></label>
</fieldset>
<div class="stock-indicator__status stock-indicator__status--low" aria-live="polite" data-stock-status>
<span class="stock-indicator__level" aria-hidden="true"><span></span><span></span><span></span></span>
<p><strong data-stock-text>Only 3 left</strong> <span data-stock-detail>Order soon. Ships tomorrow.</span></p>
</div>
<form class="stock-indicator__notify" action="#" method="post" hidden data-stock-notify>
<label class="form-label" for="stock-notify-email">Get an email when it’s back</label>
<div class="stock-indicator__notify-row">
<input class="form-control" id="stock-notify-email" name="email" type="email" autocomplete="email" placeholder="you@example.com" required>
<button class="btn btn--secondary" type="submit">Notify me</button>
</div>
</form>
<ul class="stock-indicator__stores" role="list" aria-label="Pick up in a store">
<li><span class="stock-indicator__dot stock-indicator__dot--in" aria-hidden="true"></span><span><strong>Portland, Pearl District</strong> Available today</span></li>
<li><span class="stock-indicator__dot stock-indicator__dot--low" aria-hidden="true"></span><span><strong>Seattle, Capitol Hill</strong> Limited stock</span></li>
<li><span class="stock-indicator__dot stock-indicator__dot--out" aria-hidden="true"></span><span><strong>San Francisco, Mission</strong> Not available</span></li>
</ul>
</section>
<div class="product-spotlight-block__buy">
<button class="btn btn--primary btn--lg" type="button">Add to cart</button>
<a class="btn btn--secondary btn--lg" href="#">View details</a>
</div>
<section class="delivery-estimate" aria-labelledby="delivery-estimate-title" data-delivery-estimate data-cutoff="16">
<h2 class="delivery-estimate__title" id="delivery-estimate-title">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M3 7h11v9H3z"/><path d="M14 10h4l3 3v3h-7z"/><circle cx="7" cy="17.5" r="1.75"/><circle cx="17" cy="17.5" r="1.75"/></svg>
Delivery
</h2>
<p class="delivery-estimate__cutoff" data-delivery-cutoff hidden></p>
<form class="delivery-estimate__form" action="#" method="get" novalidate data-delivery-form>
<label class="form-label" for="delivery-zip">ZIP code</label>
<div class="delivery-estimate__row">
<input class="form-control form-control--sm" id="delivery-zip" name="zip" type="text" inputmode="numeric" autocomplete="postal-code" pattern="[0-9]{5}" maxlength="5" placeholder="97205" aria-describedby="delivery-zip-error">
<button class="btn btn--secondary btn--sm" type="submit">Check</button>
</div>
<p class="delivery-estimate__error" id="delivery-zip-error" hidden data-delivery-error>Enter a 5-digit ZIP code.</p>
</form>
<div aria-live="polite" data-delivery-results-region>
<ul class="delivery-estimate__options" role="list" hidden data-delivery-results>
<li><span><strong>Standard</strong> Free over $50</span> <span data-delivery-days="5">—</span></li>
<li><span><strong>Express</strong> $12.90</span> <span data-delivery-days="2">—</span></li>
<li><span><strong>Pick up in store</strong> Free</span> <span data-delivery-days="0">—</span></li>
</ul>
</div>
<p class="delivery-estimate__returns"><a href="#">Free 30-day returns</a></p>
</section>
</div>
</div>
</div>
</section>