Photography — Pricing Packages
A gallery-dark session pricing grid with three photography tiers — Mini, Standard, and Premium — each listing shoot hours, retouched image counts, and deliverables. A popular badge highlights the recommended tier, a currency toggle recalculates every price live, and book buttons fire a confirmation toast for a polished, image-first studio checkout feel.
MCP
Code
:root {
--ink: #0b0b0c;
--bg: #101012;
--surface: #17171b;
--surface-2: #1e1e23;
--paper: #f6f4ef;
--sand: #c9b79c;
--sand-d: #a8916f;
--muted: #9a9aa2;
--line: rgba(255, 255, 255, 0.10);
--line-2: rgba(255, 255, 255, 0.18);
--white: #fff;
--r-sm: 6px;
--r-md: 12px;
--r-lg: 18px;
}
* {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
background:
radial-gradient(1200px 600px at 80% -10%, rgba(201, 183, 156, 0.08), transparent 60%),
var(--bg);
color: var(--paper);
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
min-height: 100vh;
}
.wrap {
max-width: 1120px;
margin: 0 auto;
padding: clamp(32px, 6vw, 72px) 20px 56px;
}
/* ---------- header ---------- */
.head {
text-align: center;
margin-bottom: clamp(28px, 5vw, 48px);
}
.eyebrow {
margin: 0 0 14px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--sand);
}
.title {
margin: 0 0 12px;
font-family: "Fraunces", Georgia, serif;
font-weight: 400;
font-size: clamp(34px, 6vw, 58px);
letter-spacing: -0.01em;
line-height: 1.05;
}
.lede {
max-width: 52ch;
margin: 0 auto;
color: var(--muted);
font-size: 15.5px;
}
/* ---------- rate switch ---------- */
.rate {
display: inline-flex;
align-items: center;
gap: 14px;
margin-top: 26px;
flex-wrap: wrap;
justify-content: center;
}
.rate__label {
font-size: 12px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--muted);
}
.switch {
display: inline-flex;
padding: 4px;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 999px;
}
.switch__btn {
appearance: none;
border: 0;
background: transparent;
color: var(--muted);
font: inherit;
font-size: 13px;
font-weight: 600;
padding: 8px 18px;
border-radius: 999px;
cursor: pointer;
transition: color 0.2s, background 0.2s;
}
.switch__btn.is-active {
background: var(--sand);
color: var(--ink);
}
.switch__btn:focus-visible {
outline: 2px solid var(--sand);
outline-offset: 2px;
}
/* ---------- grid ---------- */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
align-items: start;
}
.card {
position: relative;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1),
border-color 0.3s, box-shadow 0.3s;
}
.card:hover {
transform: translateY(-6px);
border-color: var(--line-2);
box-shadow: 0 24px 50px -24px rgba(0, 0, 0, 0.7);
}
.card--popular {
border-color: var(--sand-d);
transform: translateY(-10px);
box-shadow: 0 30px 60px -30px rgba(201, 183, 156, 0.28);
}
.card--popular:hover {
transform: translateY(-16px);
}
.badge {
position: absolute;
top: 14px;
right: 14px;
z-index: 2;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--ink);
background: var(--sand);
padding: 6px 12px;
border-radius: 999px;
animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(201, 183, 156, 0.5); }
50% { box-shadow: 0 0 0 8px rgba(201, 183, 156, 0); }
}
.card__media {
height: 150px;
background-image: linear-gradient(180deg, rgba(16,16,18,0.05), rgba(16,16,18,0.85)), var(--img);
background-size: cover;
background-position: center;
filter: grayscale(0.15) contrast(1.05);
transition: filter 0.4s, transform 0.6s;
}
.card:hover .card__media {
filter: grayscale(0) contrast(1.08);
transform: scale(1.03);
}
.card__body {
padding: 22px 22px 24px;
}
.card__label {
margin: 0 0 6px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--sand);
}
.card__name {
margin: 0 0 6px;
font-family: "Fraunces", Georgia, serif;
font-weight: 600;
font-size: 24px;
}
.card__desc {
margin: 0 0 18px;
color: var(--muted);
font-size: 14px;
min-height: 40px;
}
.price {
display: flex;
align-items: baseline;
gap: 3px;
margin: 0 0 20px;
padding-bottom: 18px;
border-bottom: 1px solid var(--line);
}
.price__cur {
font-size: 20px;
color: var(--muted);
font-weight: 600;
}
.price__num {
font-family: "Fraunces", Georgia, serif;
font-size: 44px;
font-weight: 600;
letter-spacing: -0.02em;
transition: opacity 0.2s, transform 0.2s;
}
.price__num.is-bump {
animation: bump 0.35s ease;
}
@keyframes bump {
0% { transform: translateY(6px); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }
}
.price__unit {
margin-left: 4px;
font-size: 13px;
color: var(--muted);
}
.feat {
list-style: none;
margin: 0 0 22px;
padding: 0;
display: grid;
gap: 11px;
}
.feat li {
display: flex;
align-items: flex-start;
gap: 10px;
font-size: 14px;
color: #e6e4de;
}
.feat strong {
color: var(--white);
}
.tick {
flex: none;
width: 18px;
height: 18px;
margin-top: 1px;
display: grid;
place-items: center;
border-radius: 50%;
background: rgba(201, 183, 156, 0.16);
color: var(--sand);
font-size: 11px;
font-weight: 700;
}
.book {
width: 100%;
appearance: none;
border: 1px solid var(--line-2);
background: var(--surface-2);
color: var(--paper);
font: inherit;
font-weight: 600;
font-size: 14px;
padding: 13px;
border-radius: var(--r-md);
cursor: pointer;
transition: background 0.2s, transform 0.15s, border-color 0.2s;
}
.book:hover {
background: #26262c;
border-color: var(--sand-d);
}
.book:active {
transform: scale(0.98);
}
.card--popular .book {
background: var(--sand);
color: var(--ink);
border-color: var(--sand);
}
.card--popular .book:hover {
background: #d7c6ac;
}
.book:focus-visible,
.disclose:focus-visible {
outline: 2px solid var(--sand);
outline-offset: 2px;
}
.disclose {
width: 100%;
margin-top: 12px;
appearance: none;
border: 0;
background: transparent;
color: var(--muted);
font: inherit;
font-size: 13px;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 6px;
cursor: pointer;
transition: color 0.2s;
}
.disclose:hover {
color: var(--paper);
}
.chev {
transition: transform 0.3s;
font-size: 15px;
}
.disclose[aria-expanded="true"] .chev {
transform: rotate(180deg);
}
.more {
margin-top: 10px;
border-top: 1px solid var(--line);
animation: reveal 0.3s ease;
}
@keyframes reveal {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
.more dl {
margin: 0;
padding-top: 14px;
display: grid;
grid-template-columns: 1fr auto;
gap: 10px 12px;
font-size: 13px;
}
.more dt {
color: var(--muted);
}
.more dd {
margin: 0;
text-align: right;
color: #e6e4de;
}
/* ---------- footer ---------- */
.foot {
margin-top: 40px;
text-align: center;
color: var(--muted);
font-size: 13px;
}
/* ---------- toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 20px);
background: var(--paper);
color: var(--ink);
font-size: 14px;
font-weight: 600;
padding: 13px 20px;
border-radius: 999px;
box-shadow: 0 18px 40px -12px rgba(0, 0, 0, 0.6);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s, transform 0.3s;
z-index: 50;
max-width: calc(100vw - 32px);
}
.toast.is-show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- responsive ---------- */
@media (max-width: 900px) {
.grid {
grid-template-columns: 1fr;
max-width: 480px;
margin: 0 auto;
}
.card,
.card--popular {
transform: none;
}
.card:hover,
.card--popular:hover {
transform: translateY(-4px);
}
}
@media (max-width: 520px) {
.wrap {
padding: 40px 16px 44px;
}
.card__body {
padding: 20px 18px 22px;
}
.price__num {
font-size: 38px;
}
.card__desc {
min-height: 0;
}
.rate {
gap: 10px;
}
}(function () {
"use strict";
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
}, 2600);
}
/* ---- rate toggle ---- */
var rateBtns = document.querySelectorAll(".switch__btn");
var priceNums = document.querySelectorAll(".price__num");
var rateHint = document.getElementById("rateHint");
var hints = { weekday: "Weekday rate", weekend: "Weekend rate" };
function setRate(rate) {
rateBtns.forEach(function (b) {
var active = b.getAttribute("data-rate") === rate;
b.classList.toggle("is-active", active);
b.setAttribute("aria-selected", active ? "true" : "false");
});
priceNums.forEach(function (num) {
var next = num.getAttribute("data-" + rate);
if (next && next !== num.textContent) {
num.textContent = next;
num.classList.remove("is-bump");
// reflow to restart animation
void num.offsetWidth;
num.classList.add("is-bump");
}
});
if (rateHint) rateHint.textContent = hints[rate] || "";
}
rateBtns.forEach(function (btn) {
btn.addEventListener("click", function () {
setRate(btn.getAttribute("data-rate"));
});
});
/* ---- disclosure ---- */
document.querySelectorAll(".disclose").forEach(function (btn) {
btn.addEventListener("click", function () {
var target = document.getElementById(btn.getAttribute("aria-controls"));
if (!target) return;
var open = btn.getAttribute("aria-expanded") === "true";
btn.setAttribute("aria-expanded", open ? "false" : "true");
target.hidden = open;
btn.querySelector("span").textContent = open ? "What's included" : "Hide details";
});
});
/* ---- book ---- */
document.querySelectorAll(".book").forEach(function (btn) {
btn.addEventListener("click", function () {
var name = btn.getAttribute("data-name") || "your session";
var activeRate = document.querySelector(".switch__btn.is-active");
var label = activeRate ? activeRate.textContent.trim().toLowerCase() : "weekday";
toast("Reserved · " + name + " (" + label + " rate). Check your email to confirm.");
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Aperture Studio — Session Packages</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=Fraunces:wght@400;600&family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="wrap">
<header class="head">
<p class="eyebrow">Aperture Studio · Portland</p>
<h1 class="title">Session packages</h1>
<p class="lede">
Studio & on-location photography built around your story. Pick a tier,
pick a date — everything below is delivered in a private online gallery.
</p>
<div class="rate" role="group" aria-label="Rate schedule">
<span class="rate__label" id="rateHint">Weekday rate</span>
<div class="switch" role="tablist" aria-label="Choose rate">
<button class="switch__btn is-active" role="tab" aria-selected="true" data-rate="weekday">
Weekday
</button>
<button class="switch__btn" role="tab" aria-selected="false" data-rate="weekend">
Weekend
</button>
</div>
</div>
</header>
<section class="grid" aria-label="Pricing tiers">
<!-- MINI -->
<article class="card" data-tier="mini">
<div class="card__media" style="--img:url('https://images.unsplash.com/photo-1519741497674-611481863552?auto=format&fit=crop&w=900&q=60')"></div>
<div class="card__body">
<p class="card__label">Mini</p>
<h2 class="card__name">The Sitting</h2>
<p class="card__desc">A quick, focused session — perfect for headshots and updates.</p>
<p class="price">
<span class="price__cur">$</span><span class="price__num" data-weekday="180" data-weekend="220">180</span>
<span class="price__unit">/ session</span>
</p>
<ul class="feat">
<li><span class="tick">✓</span> 30-minute shoot, one look</li>
<li><span class="tick">✓</span> 1 location or studio</li>
<li><span class="tick">✓</span> <strong>8</strong> retouched images</li>
<li><span class="tick">✓</span> Online gallery, 72-hour turnaround</li>
</ul>
<button class="book" data-name="The Sitting">Book session</button>
<button class="disclose" aria-expanded="false" aria-controls="more-mini">
<span>What's included</span><span class="chev" aria-hidden="true">⌄</span>
</button>
<div class="more" id="more-mini" hidden>
<dl>
<dt>Second shooter</dt><dd>—</dd>
<dt>Delivery format</dt><dd>Web + print JPEG</dd>
<dt>Print rights</dt><dd>Personal use</dd>
</dl>
</div>
</div>
</article>
<!-- STANDARD -->
<article class="card card--popular" data-tier="standard">
<span class="badge">Most booked</span>
<div class="card__media" style="--img:url('https://images.unsplash.com/photo-1502920917128-1aa500764cbd?auto=format&fit=crop&w=900&q=60')"></div>
<div class="card__body">
<p class="card__label">Standard</p>
<h2 class="card__name">The Feature</h2>
<p class="card__desc">Our signature session — space to move, change, and explore.</p>
<p class="price">
<span class="price__cur">$</span><span class="price__num" data-weekday="420" data-weekend="490">420</span>
<span class="price__unit">/ session</span>
</p>
<ul class="feat">
<li><span class="tick">✓</span> 2-hour shoot, up to 3 looks</li>
<li><span class="tick">✓</span> 2 locations or studio + outdoor</li>
<li><span class="tick">✓</span> <strong>30</strong> retouched images</li>
<li><span class="tick">✓</span> Online gallery, 5-day turnaround</li>
<li><span class="tick">✓</span> Styling & posing guide</li>
</ul>
<button class="book" data-name="The Feature">Book session</button>
<button class="disclose" aria-expanded="false" aria-controls="more-standard">
<span>What's included</span><span class="chev" aria-hidden="true">⌄</span>
</button>
<div class="more" id="more-standard" hidden>
<dl>
<dt>Second shooter</dt><dd>Optional (+$120)</dd>
<dt>Delivery format</dt><dd>Full-res + web JPEG</dd>
<dt>Print rights</dt><dd>Personal + social</dd>
</dl>
</div>
</div>
</article>
<!-- PREMIUM -->
<article class="card" data-tier="premium">
<div class="card__media" style="--img:url('https://images.unsplash.com/photo-1554048612-b6a482bc67e5?auto=format&fit=crop&w=900&q=60')"></div>
<div class="card__body">
<p class="card__label">Premium</p>
<h2 class="card__name">The Editorial</h2>
<p class="card__desc">A full production day — the complete narrative, start to finish.</p>
<p class="price">
<span class="price__cur">$</span><span class="price__num" data-weekday="880" data-weekend="990">880</span>
<span class="price__unit">/ session</span>
</p>
<ul class="feat">
<li><span class="tick">✓</span> 4-hour shoot, unlimited looks</li>
<li><span class="tick">✓</span> Up to 4 locations</li>
<li><span class="tick">✓</span> <strong>75</strong> retouched images</li>
<li><span class="tick">✓</span> Online gallery, 7-day turnaround</li>
<li><span class="tick">✓</span> Second shooter included</li>
<li><span class="tick">✓</span> Hardcover fine-art album</li>
</ul>
<button class="book" data-name="The Editorial">Book session</button>
<button class="disclose" aria-expanded="false" aria-controls="more-premium">
<span>What's included</span><span class="chev" aria-hidden="true">⌄</span>
</button>
<div class="more" id="more-premium" hidden>
<dl>
<dt>Second shooter</dt><dd>Included</dd>
<dt>Delivery format</dt><dd>Full-res RAW + JPEG</dd>
<dt>Print rights</dt><dd>Commercial license</dd>
</dl>
</div>
</div>
</article>
</section>
<footer class="foot">
<p>All packages include travel within 20 mi · A 30% deposit reserves your date.</p>
</footer>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A dark, image-first pricing section for a photography studio. Three session tiers — Mini, Standard, and Premium — sit side by side as full-bleed cards, each with a thin uppercase label, a display-serif price, and a checklist of deliverables covering shoot hours, retouched image counts, print rights, and turnaround. The middle tier wears a “Most booked” badge and stays visually lifted so the recommended choice reads instantly.
The header carries a small billing toggle that switches every card between weekday and weekend rates, animating the price numbers as they recalculate. Each card exposes a Book session button plus a details disclosure that expands to reveal the fine print — location count, second shooter, and delivery format. Hovering a card raises it and warms its border; the popular card pulses its accent.
Interactions are handled in vanilla JS: the currency/rate toggle updates prices from a data model, the disclosure toggles aria-expanded, and booking a tier fires an accessible toast confirming the selection. Everything is keyboard-usable with visible focus rings, and the grid collapses to a single stacked column below 520px.