Product gallery
The top of a product page: a swipeable photo gallery with dots on a phone and thumbnails from tablet up, name, sale price, rating, colour swatches, size chips with one sold out, Add to bag and save.
Brindlecup Rewards
Silver memberPoints balance
1,240 pts
760 points to Gold1,240 / 2,000
Wairimu NjorogeScan at the till to earn and pay4417 2208 9631
Free fruit cup with any drinkEnds Sunday
Double points on coffee3 days left · any size
It is live: hover, focus and its own controls respond. Actions that would change data (approve, send, delete) are for your app to wire. The width is a real 390 px, 768 px or 1280 px box, shrunk to fit your screen.
<!-- Icons: Lucide (lucide.dev), ISC licence -->
<style>
.odl-rewards-card { box-sizing: border-box; inline-size: 100%; max-inline-size: 30rem; margin-inline: auto; container-type: inline-size; color: var(--ds-color-text-primary); font: var(--ds-text-body-md); text-wrap: pretty; }
.odl-rewards-card *, .odl-rewards-card *::before, .odl-rewards-card *::after { box-sizing: border-box; }
.odl-rewards-card .odl-wrap { --odl-tap: var(--ds-size-target-touch); display: grid; gap: var(--ds-space-stack-xl); }
/* The member card: a lightly tinted surface so it reads as a card, not a settings panel. Accent fill only on the progress and the primary action. */
.odl-rewards-card .odl-face { display: grid; gap: var(--ds-space-stack-lg); padding: var(--ds-space-inset-xl); border: var(--ds-border-width-default) solid var(--ds-color-border-subtle); border-radius: var(--ds-radius-container);
background: var(--ds-color-bg-accent-subtle); }
.odl-rewards-card .odl-brand { display: flex; align-items: center; justify-content: space-between; gap: var(--ds-space-inline-sm); }
.odl-rewards-card .odl-brand p { display: flex; align-items: center; gap: var(--ds-space-inline-md); margin: 0; font: var(--ds-text-label-lg); }
.odl-rewards-card .odl-brand svg { inline-size: var(--ds-size-icon-md); block-size: var(--ds-size-icon-md); stroke-width: var(--ds-icon-stroke-md); color: var(--ds-color-text-secondary); }
.odl-rewards-card .odl-tier { display: inline-flex; align-items: center; min-block-size: var(--ds-size-control-sm); padding: 0 var(--ds-space-inset-sm); border-radius: var(--ds-radius-control-sm);
background: var(--ds-color-surface-raised); color: var(--ds-color-text-secondary); font: var(--ds-text-label-md); white-space: nowrap; }
.odl-rewards-card .odl-pts { display: grid; gap: var(--ds-space-stack-xs); }
.odl-rewards-card .odl-pts p { margin: 0; }
.odl-rewards-card .odl-lbl { color: var(--ds-color-text-secondary); font: var(--ds-text-label-md); }
.odl-rewards-card .odl-big { font: var(--ds-text-headline-sm); line-height: 1; font-variant-numeric: tabular-nums; }
.odl-rewards-card .odl-big small { font: var(--ds-text-label-lg); color: var(--ds-color-text-secondary); }
.odl-rewards-card .odl-next { display: grid; gap: var(--ds-space-stack-sm); }
.odl-rewards-card .odl-next p { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--ds-space-inline-sm); margin: 0; font: var(--ds-text-label-md); color: var(--ds-color-text-secondary); font-variant-numeric: tabular-nums; }
.odl-rewards-card .odl-next b { color: var(--ds-color-text-primary); font-weight: var(--ds-font-weight-label); }
.odl-rewards-card .odl-track { position: relative; block-size: var(--ds-space-8); border-radius: var(--ds-radius-full); background: var(--ds-color-surface-raised); }
.odl-rewards-card .odl-track i { position: absolute; inset-block: 0; inset-inline-start: 0; inline-size: 62%; border-radius: var(--ds-radius-full); background: var(--ds-color-bg-accent-bold); }
/* The code: always dark on white so it scans in dark mode too. */
.odl-rewards-card .odl-code { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: var(--ds-space-inline-md); padding: var(--ds-space-inset-md); border: var(--ds-border-width-default) solid var(--ds-color-border-subtle);
border-radius: var(--ds-radius-nested); background: var(--ds-color-white); color: var(--ds-color-black); }
.odl-rewards-card .odl-qr { inline-size: calc(var(--ds-size-control-lg) * 2); aspect-ratio: 1; }
.odl-rewards-card .odl-qr svg { display: block; inline-size: 100%; block-size: 100%; shape-rendering: crispEdges; }
.odl-rewards-card .odl-qr path, .odl-rewards-card .odl-qr .odl-eye { fill: var(--ds-color-black); }
.odl-rewards-card .odl-qr .odl-eye-in { fill: var(--ds-color-white); }
.odl-rewards-card .odl-code p { display: grid; gap: var(--ds-space-stack-xs); margin: 0; min-inline-size: 0; }
.odl-rewards-card .odl-code b { font: var(--ds-text-label-lg); }
.odl-rewards-card .odl-code span { color: var(--ds-color-neutral-light-11); font: var(--ds-text-label-md); }
.odl-rewards-card .odl-num { font: var(--ds-text-code-sm); letter-spacing: var(--ds-text-label-sm-tracking); color: var(--ds-color-neutral-light-12); font-variant-numeric: tabular-nums; }
/* Offers */
.odl-rewards-card .odl-offers { display: grid; gap: var(--ds-space-stack-md); }
.odl-rewards-card h3 { margin: 0; font: var(--ds-text-title-sm); }
.odl-rewards-card ul { display: grid; gap: var(--ds-space-stack-sm); margin: 0; padding: 0; list-style: none; }
.odl-rewards-card li { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--ds-space-inline-md); padding: var(--ds-space-inset-md);
border: var(--ds-border-width-default) solid var(--ds-color-border-subtle); border-radius: var(--ds-radius-container); background: var(--ds-color-surface-raised); }
.odl-rewards-card .odl-ph { display: block; inline-size: calc(var(--ds-size-control-lg) * 1.5); block-size: calc(var(--ds-size-control-lg) * 1.5); border-radius: max(var(--ds-radius-2), calc(var(--ds-radius-container) - var(--ds-space-inset-md) - var(--ds-border-width-default))); object-fit: cover; background: var(--ds-color-bg-neutral-subtle); }
.odl-rewards-card .odl-offer { display: grid; gap: var(--ds-space-stack-xs); margin: 0; min-inline-size: 0; }
.odl-rewards-card .odl-offer b { font: var(--ds-text-label-lg); text-wrap: wrap; }
.odl-rewards-card .odl-offer span { color: var(--ds-color-text-secondary); font: var(--ds-text-label-md); }
.odl-rewards-card .odl-act { display: inline-flex; align-items: center; justify-content: center; gap: var(--ds-space-inline-xs); min-block-size: var(--odl-tap); padding: 0 var(--ds-space-inset-md); border: 0; border-radius: var(--ds-radius-control);
background: var(--ds-color-bg-action-primary); color: var(--ds-color-text-on-action); font: var(--ds-text-label-md); white-space: nowrap; cursor: pointer;
transition: background-color var(--ds-motion-duration-short) var(--ds-motion-easing-standard); }
.odl-rewards-card .odl-act:hover { background: var(--ds-color-bg-action-primary-hover); }
.odl-rewards-card .odl-act svg { display: none; inline-size: var(--ds-size-icon-sm); block-size: var(--ds-size-icon-sm); stroke-width: var(--ds-icon-stroke-md); }
.odl-rewards-card .odl-act[aria-pressed="true"] { background: var(--ds-color-bg-neutral-subtle); color: var(--ds-color-text-primary); }
.odl-rewards-card .odl-act[aria-pressed="true"] svg { display: block; color: var(--ds-color-text-success); }
.odl-rewards-card button:focus-visible { outline: var(--ds-focus-ring-width) solid var(--ds-color-border-focus); outline-offset: var(--ds-focus-ring-offset); }
.odl-rewards-card .odl-sr { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* Phone: a little less padding, and the button goes under the offer text so a title never squeezes beside it. */
@container (max-width: 24rem) { .odl-rewards-card .odl-face { padding: var(--ds-space-inset-lg); } }
@container (max-width: 23rem) { .odl-rewards-card li { grid-template-columns: auto minmax(0, 1fr); row-gap: var(--ds-space-stack-sm); }
.odl-rewards-card .odl-ph { grid-row: span 2; align-self: start; }
.odl-rewards-card .odl-act { grid-column: 2; justify-self: start; } }
@media (prefers-reduced-motion: reduce) { .odl-rewards-card, .odl-rewards-card *, .odl-rewards-card *::before { transition: none !important; animation: none !important; } }
</style>
<section class="odl-rewards-card" aria-label="Brindlecup Rewards card">
<div class="odl-wrap">
<div class="odl-face">
<div class="odl-brand"><p><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 2v2"/><path d="M14 2v2"/><path d="M16 8a1 1 0 0 1 1 1v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1h14a4 4 0 1 1 0 8h-1"/><path d="M6 2v2"/></svg>Brindlecup Rewards</p><span class="odl-tier">Silver member</span></div>
<div class="odl-pts"><p class="odl-lbl">Points balance</p><p class="odl-big">1,240 <small>pts</small></p></div>
<div class="odl-next">
<p><span><b>760 points</b> to Gold</span><span>1,240 / 2,000</span></p>
<div class="odl-track" role="progressbar" aria-label="Progress to Gold" aria-valuemin="0" aria-valuemax="2000" aria-valuenow="1240" aria-valuetext="1,240 of 2,000 points"><i></i></div>
</div>
<div class="odl-code">
<div class="odl-qr" role="img" aria-label="Member code to scan">
<svg viewBox="-1 -1 27 27" aria-hidden="true">
<path d="M8 0h1v1h-1zM9 0h1v1h-1zM10 0h1v1h-1zM11 0h1v1h-1zM12 0h1v1h-1zM14 0h1v1h-1zM15 0h1v1h-1zM8 1h1v1h-1zM9 1h1v1h-1zM10 1h1v1h-1zM12 1h1v1h-1zM13 1h1v1h-1zM15 1h1v1h-1zM16 1h1v1h-1zM11 2h1v1h-1zM15 2h1v1h-1zM9 3h1v1h-1zM10 3h1v1h-1zM11 3h1v1h-1zM13 3h1v1h-1zM14 3h1v1h-1zM8 4h1v1h-1zM15 4h1v1h-1zM8 5h1v1h-1zM9 5h1v1h-1zM12 5h1v1h-1zM13 5h1v1h-1zM14 5h1v1h-1zM15 5h1v1h-1zM16 5h1v1h-1zM12 6h1v1h-1zM13 6h1v1h-1zM14 6h1v1h-1zM10 7h1v1h-1zM14 7h1v1h-1zM15 7h1v1h-1zM16 7h1v1h-1zM0 8h1v1h-1zM5 8h1v1h-1zM6 8h1v1h-1zM8 8h1v1h-1zM11 8h1v1h-1zM12 8h1v1h-1zM15 8h1v1h-1zM16 8h1v1h-1zM17 8h1v1h-1zM20 8h1v1h-1zM24 8h1v1h-1zM1 9h1v1h-1zM2 9h1v1h-1zM7 9h1v1h-1zM10 9h1v1h-1zM11 9h1v1h-1zM12 9h1v1h-1zM13 9h1v1h-1zM15 9h1v1h-1zM16 9h1v1h-1zM17 9h1v1h-1zM19 9h1v1h-1zM20 9h1v1h-1zM23 9h1v1h-1zM3 10h1v1h-1zM4 10h1v1h-1zM5 10h1v1h-1zM6 10h1v1h-1zM8 10h1v1h-1zM9 10h1v1h-1zM12 10h1v1h-1zM16 10h1v1h-1zM18 10h1v1h-1zM19 10h1v1h-1zM0 11h1v1h-1zM5 11h1v1h-1zM7 11h1v1h-1zM12 11h1v1h-1zM16 11h1v1h-1zM17 11h1v1h-1zM18 11h1v1h-1zM19 11h1v1h-1zM20 11h1v1h-1zM21 11h1v1h-1zM0 12h1v1h-1zM3 12h1v1h-1zM6 12h1v1h-1zM8 12h1v1h-1zM12 12h1v1h-1zM13 12h1v1h-1zM15 12h1v1h-1zM16 12h1v1h-1zM17 12h1v1h-1zM19 12h1v1h-1zM21 12h1v1h-1zM22 12h1v1h-1zM23 12h1v1h-1zM1 13h1v1h-1zM5 13h1v1h-1zM6 13h1v1h-1zM7 13h1v1h-1zM9 13h1v1h-1zM10 13h1v1h-1zM11 13h1v1h-1zM14 13h1v1h-1zM15 13h1v1h-1zM19 13h1v1h-1zM20 13h1v1h-1zM22 13h1v1h-1zM23 13h1v1h-1zM2 14h1v1h-1zM4 14h1v1h-1zM6 14h1v1h-1zM7 14h1v1h-1zM10 14h1v1h-1zM11 14h1v1h-1zM13 14h1v1h-1zM14 14h1v1h-1zM15 14h1v1h-1zM16 14h1v1h-1zM17 14h1v1h-1zM18 14h1v1h-1zM19 14h1v1h-1zM21 14h1v1h-1zM23 14h1v1h-1zM24 14h1v1h-1zM0 15h1v1h-1zM1 15h1v1h-1zM2 15h1v1h-1zM5 15h1v1h-1zM6 15h1v1h-1zM8 15h1v1h-1zM10 15h1v1h-1zM13 15h1v1h-1zM17 15h1v1h-1zM21 15h1v1h-1zM22 15h1v1h-1zM23 15h1v1h-1zM24 15h1v1h-1zM0 16h1v1h-1zM2 16h1v1h-1zM3 16h1v1h-1zM4 16h1v1h-1zM8 16h1v1h-1zM9 16h1v1h-1zM10 16h1v1h-1zM11 16h1v1h-1zM12 16h1v1h-1zM13 16h1v1h-1zM14 16h1v1h-1zM18 16h1v1h-1zM20 16h1v1h-1zM21 16h1v1h-1zM22 16h1v1h-1zM23 16h1v1h-1zM24 16h1v1h-1zM8 17h1v1h-1zM9 17h1v1h-1zM10 17h1v1h-1zM17 17h1v1h-1zM18 17h1v1h-1zM20 17h1v1h-1zM23 17h1v1h-1zM14 18h1v1h-1zM15 18h1v1h-1zM16 18h1v1h-1zM17 18h1v1h-1zM18 18h1v1h-1zM8 19h1v1h-1zM9 19h1v1h-1zM10 19h1v1h-1zM12 19h1v1h-1zM16 19h1v1h-1zM17 19h1v1h-1zM22 19h1v1h-1zM23 19h1v1h-1zM24 19h1v1h-1zM9 20h1v1h-1zM11 20h1v1h-1zM12 20h1v1h-1zM13 20h1v1h-1zM15 20h1v1h-1zM18 20h1v1h-1zM19 20h1v1h-1zM22 20h1v1h-1zM23 20h1v1h-1zM24 20h1v1h-1zM11 21h1v1h-1zM14 21h1v1h-1zM15 21h1v1h-1zM17 21h1v1h-1zM18 21h1v1h-1zM19 21h1v1h-1zM20 21h1v1h-1zM21 21h1v1h-1zM23 21h1v1h-1zM10 22h1v1h-1zM11 22h1v1h-1zM12 22h1v1h-1zM13 22h1v1h-1zM14 22h1v1h-1zM16 22h1v1h-1zM18 22h1v1h-1zM19 22h1v1h-1zM21 22h1v1h-1zM22 22h1v1h-1zM8 23h1v1h-1zM11 23h1v1h-1zM12 23h1v1h-1zM14 23h1v1h-1zM15 23h1v1h-1zM16 23h1v1h-1zM17 23h1v1h-1zM20 23h1v1h-1zM22 23h1v1h-1zM24 23h1v1h-1zM8 24h1v1h-1zM9 24h1v1h-1zM10 24h1v1h-1zM11 24h1v1h-1zM12 24h1v1h-1zM13 24h1v1h-1zM14 24h1v1h-1zM15 24h1v1h-1zM19 24h1v1h-1zM20 24h1v1h-1zM22 24h1v1h-1zM23 24h1v1h-1zM24 24h1v1h-1z"/>
<rect class="odl-eye" x="0" y="0" width="7" height="7"/><rect class="odl-eye-in" x="1" y="1" width="5" height="5"/><rect class="odl-eye" x="2" y="2" width="3" height="3"/>
<rect class="odl-eye" x="18" y="0" width="7" height="7"/><rect class="odl-eye-in" x="19" y="1" width="5" height="5"/><rect class="odl-eye" x="20" y="2" width="3" height="3"/>
<rect class="odl-eye" x="0" y="18" width="7" height="7"/><rect class="odl-eye-in" x="1" y="19" width="5" height="5"/><rect class="odl-eye" x="2" y="20" width="3" height="3"/>
</svg>
</div>
<p><b>Wairimu Njoroge</b><span>Scan at the till to earn and pay</span><span class="odl-num">4417 2208 9631</span></p>
</div>
</div>
<div class="odl-offers">
<h3>Offers for you</h3>
<ul>
<li>
<img class="odl-ph" src="https://images.unsplash.com/photo-1490818387583-1baba5e638af?w=144&h=144&fit=crop&crop=focalpoint&fp-x=0.18&fp-y=0.45&fp-z=1.5&auto=format&q=70" alt="Sliced fresh fruit" width="72" height="72" loading="lazy" decoding="async">
<p class="odl-offer"><b>Free fruit cup with any drink</b><span>Ends Sunday</span></p>
<button type="button" class="odl-act" aria-pressed="false"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><span class="odl-l">Activate</span><span class="odl-sr"> free fruit cup offer</span></button>
</li>
<li>
<img class="odl-ph" src="https://images.unsplash.com/photo-1563311977-d285756282dc?w=144&h=144&fit=crop&crop=focalpoint&fp-x=0.8&fp-y=0.72&fp-z=1.4&auto=format&q=70" alt="Cappuccino with latte art" width="72" height="72" loading="lazy" decoding="async">
<p class="odl-offer"><b>Double points on coffee</b><span>3 days left · any size</span></p>
<button type="button" class="odl-act" aria-pressed="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><span class="odl-l">Active</span><span class="odl-sr"> double points offer</span></button>
</li>
</ul>
</div>
</div>
</section>
<script>
document.querySelectorAll(".odl-rewards-card").forEach((root) => {
if (root.dataset.odlReady) return; // wire once, even if the file is pasted twice
root.dataset.odlReady = "1";
root.querySelectorAll(".odl-act").forEach((b) => b.addEventListener("click", () => {
const on = b.getAttribute("aria-pressed") !== "true";
b.setAttribute("aria-pressed", String(on)); b.querySelector(".odl-l").textContent = on ? "Active" : "Activate";
}));
});
</script>
The top of a product page: a swipeable photo gallery with dots on a phone and thumbnails from tablet up, name, sale price, rating, colour swatches, size chips with one sold out, Add to bag and save.
A holiday rental or hotel listing: a swipeable photo carousel with dots, a favourite heart, the place, host and dates, the rating, and the price per night with the total.
Three plans as cards you select, with a monthly or yearly switch that changes the prices and one plan marked as the common choice.