Salon — Services & Prices
An editorial services and prices page for Maison Lumière Salon, presented like a printed atelier menu. Five grouped sections span hair, colour, treatments, nails and spa across roughly twenty realistic services, each set with a serif heading, duration, dotted leader line and optional from pricing. A sticky section nav scroll-spies as you read, tapping any row adds it to a live running estimate, and a luxe booking band plus gold-flecked toasts complete the boutique feel.
MCP
コード
:root {
--serif: "Cormorant Garamond", Georgia, serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
--gold: #b08d57;
--gold-d: #8c6d3f;
--gold-soft: #efe2cf;
--rose: #c9a78f;
--rose-soft: #f3e6dc;
--ink: #1c1814;
--ink-2: #3d362f;
--muted: #8a7d70;
--cream: #f7f1e8;
--bg: #faf6ef;
--white: #ffffff;
--line: rgba(28, 24, 20, 0.1);
--line-2: rgba(28, 24, 20, 0.18);
--ok: #5f8a6b;
--warn: #c08a3e;
--danger: #b3503e;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 22px;
--shadow-sm: 0 1px 2px rgba(28, 24, 20, 0.05), 0 6px 18px rgba(28, 24, 20, 0.06);
--shadow-md: 0 10px 30px rgba(28, 24, 20, 0.1);
--shadow-lg: 0 24px 60px rgba(28, 24, 20, 0.16);
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
scroll-padding-top: 84px;
}
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--sans);
font-size: 16px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1,
h2,
h3 {
font-family: var(--serif);
font-weight: 600;
margin: 0;
}
em {
font-style: italic;
}
.page {
max-width: 920px;
margin: 0 auto;
padding: 0 24px 64px;
}
.eyebrow {
font-size: 0.72rem;
letter-spacing: 0.32em;
text-transform: uppercase;
color: var(--gold-d);
font-weight: 600;
margin: 0;
}
.eyebrow--light {
color: var(--gold-soft);
}
.dot {
color: var(--muted);
margin: 0 0.25rem;
}
/* ── Buttons ───────────────────────────── */
.btn {
font-family: var(--sans);
font-size: 0.85rem;
font-weight: 600;
letter-spacing: 0.02em;
border: 1px solid transparent;
border-radius: 999px;
padding: 0.62rem 1.25rem;
cursor: pointer;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
transition: transform 0.18s ease, background 0.18s ease, color 0.18s ease,
border-color 0.18s ease, box-shadow 0.18s ease;
white-space: nowrap;
}
.btn:active {
transform: translateY(1px);
}
.btn--ghost {
background: transparent;
border-color: var(--line-2);
color: var(--ink);
}
.btn--ghost:hover {
border-color: var(--gold);
color: var(--gold-d);
background: var(--white);
}
.btn--solid {
background: var(--ink);
color: var(--cream);
}
.btn--solid:hover {
background: var(--gold-d);
box-shadow: var(--shadow-md);
}
.btn--lg {
padding: 0.85rem 1.8rem;
font-size: 0.9rem;
}
.btn--on-dark {
color: var(--cream);
border-color: rgba(247, 241, 232, 0.3);
}
.btn--on-dark:hover {
background: rgba(247, 241, 232, 0.08);
border-color: var(--gold);
color: var(--white);
}
:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 3px;
border-radius: 4px;
}
/* ── Masthead ──────────────────────────── */
.masthead {
display: flex;
align-items: center;
gap: 1rem;
padding: 26px 0 22px;
}
.brand {
display: flex;
align-items: center;
gap: 0.7rem;
margin-right: auto;
}
.brand__mark {
width: 40px;
height: 40px;
display: grid;
place-items: center;
border-radius: 50%;
border: 1px solid var(--gold);
color: var(--gold-d);
font-family: var(--serif);
font-weight: 700;
font-size: 0.95rem;
letter-spacing: 0.04em;
}
.brand__name {
font-family: var(--serif);
font-size: 1.4rem;
font-weight: 600;
letter-spacing: 0.01em;
}
.masthead__nav {
display: flex;
gap: 1.4rem;
}
.masthead__nav a {
color: var(--ink-2);
text-decoration: none;
font-size: 0.86rem;
font-weight: 500;
position: relative;
padding-bottom: 2px;
}
.masthead__nav a::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 0;
height: 1px;
background: var(--gold);
transition: width 0.2s ease;
}
.masthead__nav a:hover {
color: var(--ink);
}
.masthead__nav a:hover::after {
width: 100%;
}
/* ── Hero ──────────────────────────────── */
.hero {
text-align: center;
padding: 30px 0 36px;
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
position: relative;
}
.hero::before,
.hero::after {
content: "";
position: absolute;
left: 50%;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--gold);
transform: translateX(-50%) rotate(45deg);
}
.hero::before {
top: -3px;
}
.hero::after {
bottom: -3px;
}
.hero__title {
font-size: clamp(2.6rem, 7vw, 4rem);
line-height: 1.04;
margin: 0.5rem 0 0.7rem;
letter-spacing: -0.01em;
}
.hero__lede {
max-width: 540px;
margin: 0 auto;
color: var(--ink-2);
font-size: 1rem;
}
.hero__lede em {
color: var(--gold-d);
font-weight: 500;
}
.hero__meta {
margin-top: 1.1rem;
font-size: 0.82rem;
color: var(--muted);
letter-spacing: 0.02em;
}
.hero__meta strong {
color: var(--ink-2);
font-weight: 600;
}
/* ── Section nav (sticky) ──────────────── */
.sectionnav {
position: sticky;
top: 0;
z-index: 20;
background: color-mix(in srgb, var(--bg) 88%, transparent);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
margin: 0 -24px 8px;
padding: 10px 24px;
border-bottom: 1px solid var(--line);
}
.sectionnav__list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
gap: 0.5rem;
justify-content: center;
flex-wrap: wrap;
}
.sectionnav__btn {
font-family: var(--sans);
background: transparent;
border: 1px solid var(--line-2);
border-radius: 999px;
padding: 0.42rem 1rem;
font-size: 0.78rem;
font-weight: 600;
letter-spacing: 0.04em;
color: var(--ink-2);
cursor: pointer;
transition: all 0.18s ease;
}
.sectionnav__btn:hover {
border-color: var(--gold);
color: var(--gold-d);
}
.sectionnav__btn.is-active {
background: var(--ink);
border-color: var(--ink);
color: var(--cream);
}
/* ── Menu groups ───────────────────────── */
.menu {
padding-top: 18px;
}
.group {
padding: 30px 0;
border-bottom: 1px solid var(--line);
scroll-margin-top: 80px;
}
.group:last-child {
border-bottom: none;
}
.group__head {
display: flex;
align-items: baseline;
gap: 1rem;
margin-bottom: 1.1rem;
}
.group__title {
font-size: clamp(1.7rem, 4vw, 2.2rem);
letter-spacing: 0.01em;
position: relative;
padding-left: 1.1rem;
}
.group__title::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 1.2em;
border-radius: 2px;
background: linear-gradient(var(--gold), var(--gold-d));
}
.group__note {
margin: 0;
font-size: 0.8rem;
color: var(--muted);
font-style: italic;
}
.rows {
list-style: none;
margin: 0;
padding: 0;
}
/* ── Service row ───────────────────────── */
.row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
grid-template-areas: "main time price";
align-items: baseline;
column-gap: 0.9rem;
padding: 0.85rem 0.7rem 0.85rem 0.7rem;
border-radius: var(--r-sm);
cursor: pointer;
transition: background 0.16s ease, box-shadow 0.16s ease;
position: relative;
}
.row:hover {
background: var(--white);
box-shadow: var(--shadow-sm);
}
.row.is-selected {
background: var(--rose-soft);
}
.row.is-selected::before {
content: "✓";
position: absolute;
left: -10px;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
display: grid;
place-items: center;
border-radius: 50%;
background: var(--gold-d);
color: var(--white);
font-size: 0.7rem;
font-weight: 700;
}
.row__main {
grid-area: main;
display: flex;
flex-direction: column;
gap: 0.15rem;
min-width: 0;
}
.row__name {
font-family: var(--serif);
font-size: 1.32rem;
font-weight: 600;
color: var(--ink);
line-height: 1.15;
}
.row__desc {
font-size: 0.82rem;
color: var(--muted);
line-height: 1.35;
}
/* dotted leader between name block and price */
.row__leader {
grid-area: main;
align-self: flex-end;
height: 0;
margin: 0 0.5rem 0.35rem 0.5rem;
border-bottom: 1.5px dotted var(--line-2);
flex: 1;
display: none;
}
.row__time {
grid-area: time;
font-size: 0.74rem;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--muted);
font-weight: 600;
white-space: nowrap;
}
.row__price {
grid-area: price;
font-family: var(--serif);
font-size: 1.3rem;
font-weight: 600;
color: var(--gold-d);
white-space: nowrap;
min-width: 78px;
text-align: right;
}
.row__price em {
font-size: 0.72rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
font-family: var(--sans);
font-weight: 600;
margin-right: 0.2rem;
}
/* leader line via pseudo on the name row, full-width version */
.row__main {
position: relative;
}
.row__name {
display: inline;
position: relative;
}
/* ── Estimate bar ──────────────────────── */
.estimate {
position: sticky;
bottom: 16px;
z-index: 25;
margin: 24px 0 0;
}
.estimate__inner {
display: flex;
align-items: center;
gap: 1rem;
background: var(--ink);
color: var(--cream);
border-radius: var(--r-lg);
padding: 0.85rem 0.85rem 0.85rem 1.4rem;
box-shadow: var(--shadow-lg);
border: 1px solid rgba(247, 241, 232, 0.08);
}
.estimate__copy {
display: flex;
flex-direction: column;
margin-right: auto;
min-width: 0;
}
.estimate__label {
font-size: 0.68rem;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--gold-soft);
font-weight: 600;
}
.estimate__count {
font-size: 0.9rem;
font-weight: 500;
color: var(--white);
}
.estimate__total {
font-family: var(--serif);
font-size: 1.8rem;
font-weight: 600;
color: var(--gold-soft);
white-space: nowrap;
}
.estimate__total .from {
font-size: 0.7rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--rose);
font-family: var(--sans);
margin-right: 0.25rem;
}
.estimate__clear {
background: transparent;
border: none;
color: var(--rose);
font-family: var(--sans);
font-size: 0.78rem;
font-weight: 600;
cursor: pointer;
padding: 0.4rem 0.3rem;
text-decoration: underline;
text-underline-offset: 3px;
}
.estimate__clear:hover {
color: var(--gold-soft);
}
/* ── CTA band ──────────────────────────── */
.cta {
margin: 48px -24px 0;
padding: 56px 24px;
background: radial-gradient(
120% 100% at 50% 0%,
#2a231b 0%,
var(--ink) 60%
);
color: var(--cream);
text-align: center;
border-radius: var(--r-lg);
position: relative;
overflow: hidden;
}
.cta__rule {
width: 64px;
height: 1px;
background: var(--gold);
margin: 0 auto 1.4rem;
}
.cta__title {
font-size: clamp(2rem, 6vw, 3rem);
margin: 0.5rem 0 0.7rem;
color: var(--white);
}
.cta__lede {
max-width: 460px;
margin: 0 auto 1.8rem;
color: var(--gold-soft);
font-size: 0.95rem;
}
.cta__actions {
display: flex;
gap: 0.8rem;
justify-content: center;
flex-wrap: wrap;
}
/* ── Footer ────────────────────────────── */
.footer {
margin-top: 32px;
text-align: center;
font-size: 0.78rem;
color: var(--muted);
letter-spacing: 0.03em;
}
/* ── Toast ─────────────────────────────── */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 16px);
background: var(--ink);
color: var(--cream);
padding: 0.85rem 1.3rem;
border-radius: 999px;
font-size: 0.86rem;
font-weight: 500;
box-shadow: var(--shadow-lg);
border: 1px solid rgba(176, 141, 87, 0.4);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 60;
max-width: calc(100vw - 32px);
text-align: center;
}
.toast::before {
content: "✦";
color: var(--gold);
margin-right: 0.5rem;
}
.toast.is-visible {
opacity: 1;
transform: translate(-50%, 0);
pointer-events: auto;
}
/* ── Responsive ────────────────────────── */
@media (max-width: 520px) {
.page {
padding: 0 16px 48px;
}
.masthead {
flex-wrap: wrap;
gap: 0.7rem;
}
.masthead__nav {
order: 3;
width: 100%;
justify-content: space-between;
gap: 0.6rem;
overflow-x: auto;
}
.sectionnav {
margin: 0 -16px 8px;
padding: 10px 16px;
}
.sectionnav__list {
justify-content: flex-start;
flex-wrap: nowrap;
overflow-x: auto;
scrollbar-width: none;
}
.sectionnav__list::-webkit-scrollbar {
display: none;
}
.group__head {
flex-direction: column;
gap: 0.2rem;
}
.row {
grid-template-columns: minmax(0, 1fr) auto;
grid-template-areas:
"main price"
"time price";
column-gap: 0.7rem;
row-gap: 0.1rem;
padding-left: 0.4rem;
padding-right: 0.4rem;
}
.row__name {
font-size: 1.18rem;
}
.row__price {
font-size: 1.15rem;
min-width: 64px;
}
.estimate__inner {
flex-wrap: wrap;
padding: 0.9rem 1rem;
gap: 0.7rem;
}
.estimate__total {
order: 2;
}
.estimate__copy {
order: 1;
}
#estimate-book {
order: 3;
flex: 1;
}
.estimate__clear {
order: 4;
}
.cta {
margin: 36px -16px 0;
padding: 44px 18px;
}
.btn--lg {
width: 100%;
}
}
@media (prefers-reduced-motion: reduce) {
* {
scroll-behavior: auto !important;
transition-duration: 0.01ms !important;
}
}(function () {
"use strict";
const groups = Array.from(document.querySelectorAll(".group"));
const rows = Array.from(document.querySelectorAll(".row"));
const navList = document.getElementById("section-nav");
const estimate = document.getElementById("estimate");
const estCount = document.getElementById("estimate-count");
const estTotal = document.getElementById("estimate-total");
const estBook = document.getElementById("estimate-book");
const estClear = document.getElementById("estimate-clear");
/* ── Toast helper ──────────────────────────── */
const toastEl = document.getElementById("toast");
let toastTimer = null;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-visible");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => {
toastEl.classList.remove("is-visible");
}, 2600);
}
/* ── Build sticky section nav from groups ──── */
groups.forEach((g) => {
const li = document.createElement("li");
const btn = document.createElement("button");
btn.type = "button";
btn.className = "sectionnav__btn";
btn.textContent = g.dataset.group;
btn.dataset.target = g.id;
btn.addEventListener("click", () => {
const target = document.getElementById(g.id);
target.scrollIntoView({ behavior: "smooth", block: "start" });
target.focus({ preventScroll: true });
});
li.appendChild(btn);
navList.appendChild(li);
});
const navBtns = Array.from(navList.querySelectorAll(".sectionnav__btn"));
/* ── Scroll-spy: highlight active section ──── */
const spy = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const id = entry.target.id;
navBtns.forEach((b) =>
b.classList.toggle("is-active", b.dataset.target === id)
);
}
});
},
{ rootMargin: "-45% 0px -50% 0px", threshold: 0 }
);
groups.forEach((g) => spy.observe(g));
/* ── Selection / running estimate ──────────── */
const selected = new Map(); // name -> { price, from }
function fmt(n) {
return "$" + n.toLocaleString("en-US");
}
function renderEstimate() {
const count = selected.size;
if (count === 0) {
estimate.hidden = true;
return;
}
estimate.hidden = false;
let total = 0;
let hasFrom = false;
selected.forEach((v) => {
total += v.price;
if (v.from) hasFrom = true;
});
estCount.textContent =
count + (count === 1 ? " service selected" : " services selected");
estTotal.innerHTML = hasFrom
? '<span class="from">from</span>' + fmt(total)
: fmt(total);
}
function toggleRow(row) {
const name = row.dataset.name.replace(/&/g, "&");
const price = parseInt(row.dataset.price, 10) || 0;
const from = row.dataset.from === "true";
if (selected.has(name)) {
selected.delete(name);
row.classList.remove("is-selected");
row.setAttribute("aria-pressed", "false");
} else {
selected.set(name, { price, from });
row.classList.add("is-selected");
row.setAttribute("aria-pressed", "true");
toast(name + " added to your visit");
}
renderEstimate();
}
rows.forEach((row) => {
row.setAttribute("role", "button");
row.setAttribute("tabindex", "0");
row.setAttribute("aria-pressed", "false");
const label = row.querySelector(".row__name").textContent.trim();
const price = row.querySelector(".row__price").textContent.trim();
row.setAttribute("aria-label", label + ", " + price + ". Add to visit.");
row.addEventListener("click", () => toggleRow(row));
row.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
toggleRow(row);
}
});
});
estClear.addEventListener("click", () => {
selected.clear();
rows.forEach((r) => {
r.classList.remove("is-selected");
r.setAttribute("aria-pressed", "false");
});
renderEstimate();
toast("Selection cleared");
});
estBook.addEventListener("click", () => {
const count = selected.size;
if (!count) return;
toast(
"Reserved " +
count +
(count === 1 ? " service" : " services") +
" with Aria Vance — see you soon"
);
selected.clear();
rows.forEach((r) => {
r.classList.remove("is-selected");
r.setAttribute("aria-pressed", "false");
});
renderEstimate();
});
/* ── Generic booking buttons ───────────────── */
document.querySelectorAll("[data-book]").forEach((el) => {
el.addEventListener("click", () => {
toast("Booking request sent for " + el.dataset.book + " — we'll confirm shortly");
});
});
renderEstimate();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Services & Prices · Maison Lumière Salon</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=Cormorant+Garamond:wght@500;600;700&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="page">
<header class="masthead">
<div class="brand">
<span class="brand__mark" aria-hidden="true">ML</span>
<span class="brand__name">Maison Lumière</span>
</div>
<nav class="masthead__nav" aria-label="Primary">
<a href="#hair">Services</a>
<a href="#">Stylists</a>
<a href="#">Journal</a>
<a href="#">Contact</a>
</nav>
<button class="btn btn--ghost" type="button" data-book="Maison Lumière">
Book a visit
</button>
</header>
<section class="hero" aria-labelledby="hero-title">
<p class="eyebrow">The Price List · 2026</p>
<h1 class="hero__title" id="hero-title">Services & Prices</h1>
<p class="hero__lede">
A considered menu of hair, colour, treatment, nail and spa rituals —
each performed by our atelier of stylists. Prices marked
<em>from</em> vary with length, density and the artistry your hair asks for.
</p>
<div class="hero__meta">
<span><strong>27</strong> Avenue Lumière, Paris 8e</span>
<span class="dot" aria-hidden="true">·</span>
<span>Tue–Sat · 9am–7pm</span>
</div>
</section>
<nav class="sectionnav" aria-label="Service sections">
<ul class="sectionnav__list" id="section-nav"></ul>
</nav>
<main class="menu" id="menu">
<!-- HAIR -->
<section class="group" id="hair" data-group="Hair" tabindex="-1">
<header class="group__head">
<h2 class="group__title">Hair</h2>
<p class="group__note">Cutting, finishing & styling by appointment.</p>
</header>
<ul class="rows">
<li class="row" data-name="The Lumière Cut" data-price="95">
<div class="row__main">
<span class="row__name">The Lumière Cut</span>
<span class="row__desc">Consultation, bespoke cut & signature blow-dry.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">75 min</span>
<span class="row__price">$95</span>
</li>
<li class="row" data-name="Gentlemen's Cut" data-price="58">
<div class="row__main">
<span class="row__name">Gentlemen’s Cut</span>
<span class="row__desc">Precision scissor cut, neaten & finish.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">45 min</span>
<span class="row__price">$58</span>
</li>
<li class="row" data-name="Restyle Consultation" data-price="120">
<div class="row__main">
<span class="row__name">Restyle & Direction</span>
<span class="row__desc">A full reimagining with our senior creative team.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">90 min</span>
<span class="row__price">$120</span>
</li>
<li class="row" data-name="Blow-Dry & Style" data-price="48" data-from="true">
<div class="row__main">
<span class="row__name">Blow-Dry & Style</span>
<span class="row__desc">Smooth, wave or volume — finished by hand.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">40 min</span>
<span class="row__price"><em>from</em> $48</span>
</li>
<li class="row" data-name="Occasion Styling" data-price="85" data-from="true">
<div class="row__main">
<span class="row__name">Occasion Styling</span>
<span class="row__desc">Upstyles & bridal-adjacent dressing.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">60 min</span>
<span class="row__price"><em>from</em> $85</span>
</li>
</ul>
</section>
<!-- COLOUR -->
<section class="group" id="color" data-group="Colour" tabindex="-1">
<header class="group__head">
<h2 class="group__title">Colour</h2>
<p class="group__note">A skin test is required 48 hours before first colour.</p>
</header>
<ul class="rows">
<li class="row" data-name="Full Head Tint" data-price="110" data-from="true">
<div class="row__main">
<span class="row__name">Full Head Tint</span>
<span class="row__desc">Edge-to-edge permanent colour & gloss.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">120 min</span>
<span class="row__price"><em>from</em> $110</span>
</li>
<li class="row" data-name="Signature Balayage" data-price="185" data-from="true">
<div class="row__main">
<span class="row__name">Signature Balayage</span>
<span class="row__desc">Hand-painted dimension, sun-kissed ends.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">180 min</span>
<span class="row__price"><em>from</em> $185</span>
</li>
<li class="row" data-name="Full Highlights" data-price="160" data-from="true">
<div class="row__main">
<span class="row__name">Full Highlights</span>
<span class="row__desc">Foiled lightening throughout, toned to finish.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">165 min</span>
<span class="row__price"><em>from</em> $160</span>
</li>
<li class="row" data-name="Root Refresh" data-price="72">
<div class="row__main">
<span class="row__name">Root Refresh</span>
<span class="row__desc">Regrowth tint to your existing shade.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">60 min</span>
<span class="row__price">$72</span>
</li>
<li class="row" data-name="Gloss & Tone" data-price="55">
<div class="row__main">
<span class="row__name">Gloss & Tone</span>
<span class="row__desc">Shine-boosting demi to refine brassiness.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">45 min</span>
<span class="row__price">$55</span>
</li>
</ul>
</section>
<!-- TREATMENTS -->
<section class="group" id="treatments" data-group="Treatments" tabindex="-1">
<header class="group__head">
<h2 class="group__title">Treatments</h2>
<p class="group__note">Best paired with a cut or colour for lasting effect.</p>
</header>
<ul class="rows">
<li class="row" data-name="Lumière Bond Repair" data-price="40">
<div class="row__main">
<span class="row__name">Lumière Bond Repair</span>
<span class="row__desc">In-salon bond rebuild for compromised hair.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">30 min</span>
<span class="row__price">$40</span>
</li>
<li class="row" data-name="Deep Hydration Mask" data-price="35">
<div class="row__main">
<span class="row__name">Deep Hydration Mask</span>
<span class="row__desc">Steam-infused moisture & scalp massage.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">35 min</span>
<span class="row__price">$35</span>
</li>
<li class="row" data-name="Keratin Smoothing" data-price="220" data-from="true">
<div class="row__main">
<span class="row__name">Keratin Smoothing</span>
<span class="row__desc">Frizz-taming treatment, lasts up to 16 weeks.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">150 min</span>
<span class="row__price"><em>from</em> $220</span>
</li>
<li class="row" data-name="Scalp Detox Ritual" data-price="48">
<div class="row__main">
<span class="row__name">Scalp Detox Ritual</span>
<span class="row__desc">Clarifying exfoliation & pressure-point massage.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">40 min</span>
<span class="row__price">$48</span>
</li>
</ul>
</section>
<!-- NAILS -->
<section class="group" id="nails" data-group="Nails" tabindex="-1">
<header class="group__head">
<h2 class="group__title">Nails</h2>
<p class="group__note">Our lacquers are 10-free and cruelty-free.</p>
</header>
<ul class="rows">
<li class="row" data-name="Classic Manicure" data-price="42">
<div class="row__main">
<span class="row__name">Classic Manicure</span>
<span class="row__desc">Shape, cuticle care & polish of choice.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">45 min</span>
<span class="row__price">$42</span>
</li>
<li class="row" data-name="Gel Manicure" data-price="58">
<div class="row__main">
<span class="row__name">Gel Manicure</span>
<span class="row__desc">Long-wear gel finish, cured to a high gloss.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">55 min</span>
<span class="row__price">$58</span>
</li>
<li class="row" data-name="Luxe Pedicure" data-price="68">
<div class="row__main">
<span class="row__name">Luxe Pedicure</span>
<span class="row__desc">Soak, exfoliation, mask & mineral polish.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">60 min</span>
<span class="row__price">$68</span>
</li>
</ul>
</section>
<!-- SPA -->
<section class="group" id="spa" data-group="Spa" tabindex="-1">
<header class="group__head">
<h2 class="group__title">Spa</h2>
<p class="group__note">Arrive ten minutes early to settle into the lounge.</p>
</header>
<ul class="rows">
<li class="row" data-name="Express Facial" data-price="75">
<div class="row__main">
<span class="row__name">Express Facial</span>
<span class="row__desc">Cleanse, exfoliate & hydrate in a half-hour.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">30 min</span>
<span class="row__price">$75</span>
</li>
<li class="row" data-name="Lumière Glow Facial" data-price="135">
<div class="row__main">
<span class="row__name">Lumière Glow Facial</span>
<span class="row__desc">Resurfacing ritual with massage & LED.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">75 min</span>
<span class="row__price">$135</span>
</li>
<li class="row" data-name="Aromatherapy Massage" data-price="110" data-from="true">
<div class="row__main">
<span class="row__name">Aromatherapy Massage</span>
<span class="row__desc">Full-body unwind with bespoke oil blend.</span>
</div>
<span class="row__leader" aria-hidden="true"></span>
<span class="row__time">60 min</span>
<span class="row__price"><em>from</em> $110</span>
</li>
</ul>
</section>
</main>
<aside class="estimate" id="estimate" aria-live="polite" hidden>
<div class="estimate__inner">
<div class="estimate__copy">
<span class="estimate__label">Your selection</span>
<span class="estimate__count" id="estimate-count">0 services</span>
</div>
<span class="estimate__total" id="estimate-total">$0</span>
<button class="btn btn--solid" type="button" id="estimate-book">
Book selection
</button>
<button class="estimate__clear" type="button" id="estimate-clear" aria-label="Clear selection">
Clear
</button>
</div>
</aside>
<section class="cta" aria-labelledby="cta-title">
<div class="cta__rule" aria-hidden="true"></div>
<p class="eyebrow eyebrow--light">Ready when you are</p>
<h2 class="cta__title" id="cta-title">Reserve your chair</h2>
<p class="cta__lede">
Tap any price to add it to your visit, or book a consultation and
we’ll tailor the perfect appointment with stylist Aria Vance.
</p>
<div class="cta__actions">
<button class="btn btn--solid btn--lg" type="button" data-book="a consultation">
Book a consultation
</button>
<a class="btn btn--ghost btn--lg btn--on-dark" href="tel:+33100000000">
Call +33 1 00 00 00 00
</a>
</div>
</section>
<footer class="footer">
<span>Maison Lumière Salon</span>
<span class="dot" aria-hidden="true">·</span>
<span>Prices in USD, incl. tax</span>
<span class="dot" aria-hidden="true">·</span>
<span>Gratuity at your discretion</span>
</footer>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Services & Prices
A services and prices page for the fictional Maison Lumière Salon, composed like a printed menu rather than a list. A centered serif masthead, thin gold hairlines, dotted price leaders and small-caps durations give every entry the feel of a curated atelier card. Around twenty realistic services are grouped into Hair, Colour, Treatments, Nails and Spa, each with a short description, a duration, and a price that reads either as a fixed figure or as from pricing for variable work.
A sticky section nav sits beneath the hero and scroll-spies as you move through the menu, flipping the active chip to a matte-black pill; tapping a chip glides you to that section. Every service row is keyboard-usable and selectable — choosing one slips it into a running estimate bar that totals the visit live, marks the figure as from when any chosen service has variable pricing, and lets you book the whole selection in a tap.
A radial matte-black booking band anchors the page, pairing a consultation CTA with a call link, and every action — adding a service, clearing the selection, or requesting a booking — surfaces a gold-flecked toast confirmation. The screen is pure HTML, CSS and JavaScript with no frameworks, respects reduced-motion preferences, keeps visible focus rings, and reflows gracefully down to a single column at 360px.