Shop — Luxury Boutique Landing
A cinematic luxury boutique landing for a fictional Paris haute-joaillerie house, built in black, champagne and ivory with a refined serif. Features a restrained hero, a single signature piece with metal and size selectors and live pricing, an editorial collection grid, an atelier section with animated craft counters, a private-salon cart drawer, an appointment form, and slow elegant scroll reveals — all pure HTML, CSS and vanilla JavaScript.
MCP
Code
:root {
--ivory: #f6f3ec;
--ivory-2: #efe9dc;
--ink: #14110d;
--ink-soft: #3a342a;
--noir: #0c0a07;
--champ: #c9a44c;
--champ-l: #e6cf8f;
--champ-d: #8a6a1f;
--muted: #8a8174;
--line: rgba(20, 17, 13, .14);
--line-gold: rgba(201, 164, 76, .42);
--ok: #4e7c5a;
--shadow: 0 30px 70px -36px rgba(12, 10, 7, .55);
--r: 2px;
--ease: cubic-bezier(.22, .61, .36, 1);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: "Jost", system-ui, -apple-system, "Segoe UI", sans-serif;
color: var(--ink);
background: var(--ivory);
line-height: 1.6;
font-weight: 300;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
letter-spacing: .01em;
}
h1, h2, h3 {
font-family: "Cormorant Garamond", Georgia, serif;
font-weight: 500;
line-height: 1.08;
letter-spacing: .002em;
}
em { font-style: italic; }
a { color: inherit; text-decoration: none; }
img, svg { display: block; max-width: 100%; }
button { font: inherit; color: inherit; cursor: pointer; background: none; border: none; }
.wrap { width: min(1180px, 92vw); margin-inline: auto; }
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.eyebrow {
font-size: .72rem; letter-spacing: .34em; text-transform: uppercase;
color: var(--champ-d); font-weight: 400;
}
:focus-visible {
outline: 2px solid var(--champ-d);
outline-offset: 3px;
border-radius: 1px;
}
/* ---------- Announcement ---------- */
.annc {
background: var(--noir); color: var(--ivory);
text-align: center; padding: .55rem 1rem;
font-size: .68rem; letter-spacing: .26em; text-transform: uppercase;
}
.annc p { color: rgba(246, 243, 236, .82); }
/* ---------- Header ---------- */
.site-head {
position: sticky; top: 0; z-index: 40;
background: rgba(246, 243, 236, .86);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--line);
}
.head-wrap {
display: flex; align-items: center; gap: 1rem;
height: 76px;
}
.brand { display: flex; align-items: center; gap: .7rem; margin-right: auto; }
.brand-mark {
display: grid; place-items: center;
width: 38px; height: 38px; flex: none;
font-family: "Cormorant Garamond", serif; font-size: 1.5rem; font-weight: 600;
color: var(--noir);
border: 1px solid var(--champ);
background: linear-gradient(135deg, var(--champ-l), var(--champ));
border-radius: 50%;
}
.brand-name {
font-family: "Cormorant Garamond", serif;
font-size: 1.35rem; font-weight: 500; letter-spacing: .14em;
}
.primary-nav { display: flex; gap: 2rem; }
.primary-nav a {
font-size: .76rem; letter-spacing: .2em; text-transform: uppercase;
color: var(--ink-soft); position: relative; padding: .3rem 0;
transition: color .3s var(--ease);
}
.primary-nav a::after {
content: ""; position: absolute; left: 0; bottom: -2px; height: 1px;
width: 0; background: var(--champ-d); transition: width .35s var(--ease);
}
.primary-nav a:hover, .primary-nav a:focus-visible { color: var(--ink); }
.primary-nav a:hover::after, .primary-nav a.active::after { width: 100%; }
.primary-nav a.active { color: var(--ink); }
.head-actions { display: flex; align-items: center; gap: .4rem; }
.icon-btn {
position: relative; display: grid; place-items: center;
width: 42px; height: 42px; border-radius: 50%;
color: var(--ink); transition: background .25s, color .25s;
}
.icon-btn:hover { background: var(--ivory-2); }
.cart-count {
position: absolute; top: 4px; right: 2px;
min-width: 17px; height: 17px; padding: 0 3px;
display: grid; place-items: center;
font-size: .62rem; font-weight: 500; letter-spacing: 0;
color: var(--noir); background: var(--champ);
border-radius: 9px; transform: scale(0);
transition: transform .3s var(--ease);
}
.cart-count.show { transform: scale(1); }
.nav-toggle { display: none; flex-direction: column; gap: 4px; width: 30px; padding: 6px 2px; }
.nav-toggle span { height: 1.5px; background: var(--ink); transition: transform .3s, opacity .3s; }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }
/* ---------- Reveal ---------- */
.reveal { opacity: 0; transform: translateY(26px); transition: opacity 1.1s var(--ease), transform 1.1s var(--ease); }
.reveal.in { opacity: 1; transform: none; }
/* ---------- Hero ---------- */
.hero {
width: min(1180px, 92vw); margin-inline: auto;
display: grid; grid-template-columns: 1.15fr .85fr; align-items: center; gap: 3rem;
min-height: clamp(560px, 78vh, 760px); padding: 4.5rem 0 5rem;
}
.hero-frame { max-width: 34ch; }
.hero-title {
font-size: clamp(2.9rem, 7vw, 5.1rem);
margin: 1.4rem 0 1.6rem; letter-spacing: -.01em;
}
.hero-title em { color: var(--champ-d); }
.hero-sub { color: var(--ink-soft); font-size: 1.04rem; max-width: 40ch; }
.hero-cta { display: flex; flex-wrap: wrap; gap: 1rem; margin-top: 2.4rem; }
.hero-art { position: relative; display: grid; place-items: center; min-height: 360px; }
.hero-orb {
position: absolute; inset: 6% 12%;
background: radial-gradient(circle at 38% 32%, rgba(230, 207, 143, .55), rgba(201, 164, 76, .12) 55%, transparent 72%);
filter: blur(6px);
}
.hero-svg { width: clamp(180px, 30vw, 260px); position: relative; animation: floaty 7s ease-in-out infinite; }
@keyframes floaty { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-14px); } }
/* ---------- Buttons ---------- */
.btn {
display: inline-flex; align-items: center; justify-content: center;
padding: .92rem 2.1rem; border-radius: var(--r);
font-size: .74rem; letter-spacing: .2em; text-transform: uppercase; font-weight: 400;
transition: background .3s var(--ease), color .3s var(--ease), border-color .3s, transform .15s, box-shadow .3s;
}
.btn:active { transform: translateY(1px); }
.btn-solid { background: var(--noir); color: var(--ivory); border: 1px solid var(--noir); }
.btn-solid:hover { background: var(--champ-d); border-color: var(--champ-d); box-shadow: var(--shadow); }
.btn-ghost { background: transparent; color: var(--ink); border: 1px solid var(--line-gold); }
.btn-ghost:hover { border-color: var(--champ-d); color: var(--champ-d); }
.btn-block { width: 100%; }
/* ---------- Rule heading ---------- */
.rule {
display: flex; align-items: center; gap: 1.4rem;
margin: 0 0 3rem; color: var(--champ-d);
}
.rule::before, .rule::after { content: ""; height: 1px; flex: 1; background: var(--line-gold); }
.rule span {
font-size: .72rem; letter-spacing: .38em; text-transform: uppercase; white-space: nowrap;
}
/* ---------- Piece ---------- */
.piece { width: min(1180px, 92vw); margin-inline: auto; padding: 4rem 0 5rem; border-top: 1px solid var(--line); }
.piece-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4rem; align-items: center; }
.piece-stage {
position: relative; display: grid; place-items: center;
aspect-ratio: 1; border: 1px solid var(--line-gold);
background:
linear-gradient(160deg, var(--ivory) 0%, var(--ivory-2) 100%);
}
.piece-stage::before {
content: ""; position: absolute; inset: 14px; border: 1px solid var(--line-gold); opacity: .55;
}
.stage-glow {
position: absolute; width: 62%; aspect-ratio: 1; border-radius: 50%;
background: radial-gradient(circle, rgba(230, 207, 143, .5), transparent 68%);
filter: blur(8px);
}
.piece-svg { width: 72%; position: relative; }
.sparkle { animation: twinkle 2.6s ease-in-out infinite; transform-origin: center; }
@keyframes twinkle { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }
.piece-name { font-size: clamp(2.2rem, 4vw, 3rem); margin: .6rem 0 .8rem; }
.rate { display: flex; align-items: center; gap: .7rem; margin-bottom: 1.3rem; }
.stars { color: var(--champ); letter-spacing: .12em; font-size: 1rem; }
.rate-meta { font-size: .82rem; color: var(--muted); letter-spacing: .04em; }
.piece-copy { color: var(--ink-soft); max-width: 42ch; margin-bottom: 2rem; }
.opts { display: flex; flex-direction: column; gap: 1.6rem; margin-bottom: 2.2rem; }
.opt-label { display: block; font-size: .68rem; letter-spacing: .26em; text-transform: uppercase; color: var(--muted); margin-bottom: .7rem; }
.swatches, .sizes { display: flex; flex-wrap: wrap; gap: .6rem; }
.sw {
padding: .55rem 1.1rem; border: 1px solid var(--line); border-radius: var(--r);
font-size: .74rem; letter-spacing: .1em; color: var(--ink-soft);
display: inline-flex; align-items: center; gap: .55rem;
transition: border-color .25s, color .25s, background .25s;
}
.sw::before {
content: ""; width: 13px; height: 13px; border-radius: 50%;
border: 1px solid rgba(0,0,0,.18);
}
.sw-champ::before { background: linear-gradient(135deg, #e6cf8f, #c9a44c); }
.sw-rose::before { background: linear-gradient(135deg, #ecc7b6, #c98e74); }
.sw-noir::before { background: linear-gradient(135deg, #4a4a52, #16161a); }
.sw:hover { border-color: var(--champ-d); }
.sw.is-on { border-color: var(--champ-d); color: var(--ink); background: rgba(201,164,76,.08); }
.size {
width: 46px; height: 46px; border: 1px solid var(--line); border-radius: var(--r);
font-size: .82rem; letter-spacing: .04em; color: var(--ink-soft);
transition: border-color .25s, color .25s, background .25s;
}
.size:hover { border-color: var(--champ-d); }
.size.is-on { border-color: var(--noir); background: var(--noir); color: var(--ivory); }
.buy { display: flex; flex-wrap: wrap; align-items: center; gap: 1.4rem 2rem; margin-bottom: 1.8rem; }
.price { font-family: "Cormorant Garamond", serif; font-size: 2rem; font-weight: 500; letter-spacing: .01em; }
.price-from { font-family: "Jost", sans-serif; font-size: .68rem; letter-spacing: .24em; text-transform: uppercase; color: var(--muted); margin-right: .2rem; }
.btn-buy { flex: 1 1 220px; }
.trust { list-style: none; display: flex; flex-wrap: wrap; gap: 1.2rem 1.8rem; }
.trust li { display: flex; align-items: center; gap: .5rem; font-size: .76rem; color: var(--ink-soft); letter-spacing: .03em; }
.trust svg { color: var(--champ-d); flex: none; }
/* ---------- Collection ---------- */
.collection { width: min(1180px, 92vw); margin-inline: auto; padding: 4rem 0 5rem; border-top: 1px solid var(--line); }
.coll-head { max-width: 52ch; margin-bottom: 3rem; }
.coll-head h2 { font-size: clamp(1.9rem, 3.4vw, 2.7rem); margin-bottom: 1rem; }
.coll-head p { color: var(--ink-soft); }
.coll-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.6rem; }
.coll-card {
position: relative; text-align: left;
border: 1px solid var(--line); background: var(--bg, var(--ivory));
padding: 0; overflow: hidden; border-radius: var(--r);
transition: border-color .35s var(--ease), transform .4s var(--ease), box-shadow .4s var(--ease);
display: flex; flex-direction: column;
}
.coll-card:hover { transform: translateY(-6px); border-color: var(--line-gold); box-shadow: var(--shadow); }
.coll-card.is-saved { border-color: var(--champ-d); }
.card-stage {
position: relative; aspect-ratio: 4 / 3; display: grid; place-items: center;
overflow: hidden;
}
.card-stage svg { width: 52%; transition: transform .6s var(--ease); }
.coll-card:hover .card-stage svg { transform: scale(1.07) rotate(-3deg); }
.card-tag {
position: absolute; top: .8rem; left: .8rem;
font-size: .58rem; letter-spacing: .22em; text-transform: uppercase;
background: rgba(12,10,7,.78); color: var(--ivory); padding: .3rem .6rem; border-radius: 1px;
}
.card-body { padding: 1.3rem 1.4rem 1.5rem; display: flex; flex-direction: column; gap: .5rem; flex: 1; }
.card-name { font-family: "Cormorant Garamond", serif; font-size: 1.5rem; line-height: 1.1; }
.card-meta { font-size: .76rem; color: var(--muted); letter-spacing: .04em; }
.card-foot { margin-top: auto; display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding-top: .8rem; }
.card-price { font-family: "Cormorant Garamond", serif; font-size: 1.3rem; font-weight: 500; }
.card-add {
font-size: .66rem; letter-spacing: .18em; text-transform: uppercase;
border: 1px solid var(--line-gold); padding: .5rem .9rem; border-radius: var(--r);
color: var(--ink-soft); transition: background .25s, color .25s, border-color .25s;
}
.card-add:hover { border-color: var(--champ-d); color: var(--champ-d); }
.coll-card.is-saved .card-add { background: var(--champ-d); color: var(--ivory); border-color: var(--champ-d); }
/* ---------- Atelier ---------- */
.atelier {
background: var(--noir); color: var(--ivory);
display: grid; grid-template-columns: .85fr 1.15fr; gap: 0; align-items: stretch;
}
.atelier-art {
position: relative; display: grid; place-items: center; min-height: 420px;
background:
radial-gradient(circle at 50% 40%, rgba(201,164,76,.18), transparent 60%),
repeating-linear-gradient(135deg, rgba(201,164,76,.05) 0 2px, transparent 2px 22px);
border-right: 1px solid rgba(201,164,76,.2);
}
.craft-tile { width: clamp(180px, 24vw, 260px); animation: spinSlow 28s linear infinite; }
@keyframes spinSlow { to { transform: rotate(360deg); } }
.atelier-copy { padding: clamp(3rem, 6vw, 5.5rem) clamp(2rem, 5vw, 5rem); align-self: center; }
.atelier-copy .eyebrow { color: var(--champ-l); }
.atelier-copy h2 { font-size: clamp(2rem, 3.6vw, 3rem); margin: 1.2rem 0 1.6rem; color: var(--ivory); }
.atelier-copy > p { color: rgba(246,243,236,.74); max-width: 48ch; }
.craft-stats {
display: grid; grid-template-columns: repeat(3, auto); gap: 2rem 3rem;
margin: 2.6rem 0 2rem;
}
.craft-stats dt {
font-family: "Cormorant Garamond", serif; font-size: clamp(2.4rem, 5vw, 3.4rem);
color: var(--champ-l); line-height: 1;
}
.craft-stats dd { font-size: .68rem; letter-spacing: .2em; text-transform: uppercase; color: rgba(246,243,236,.6); margin-top: .5rem; }
.atelier-sign { font-family: "Cormorant Garamond", serif; font-style: italic; font-size: 1.2rem; color: var(--champ-l); }
/* ---------- CTA ---------- */
.cta { padding: clamp(5rem, 10vw, 8rem) 0; text-align: center; }
.cta-inner { width: min(620px, 90vw); margin-inline: auto; }
.cta h2 { font-size: clamp(2.2rem, 5vw, 3.6rem); margin: 1.2rem 0 1.2rem; }
.cta-inner > p { color: var(--ink-soft); margin-bottom: 2.4rem; }
.cta-form { display: flex; gap: .8rem; flex-wrap: wrap; justify-content: center; }
.cta-form input {
flex: 1 1 260px; min-width: 0; padding: .95rem 1.2rem;
border: 1px solid var(--line); border-radius: var(--r); background: var(--bg, #fff);
font: inherit; color: var(--ink); letter-spacing: .04em;
transition: border-color .25s;
}
.cta-form input::placeholder { color: var(--muted); }
.cta-form input:focus-visible { border-color: var(--champ-d); outline-offset: 0; }
.cta-form input.invalid { border-color: var(--sale, #b23a4e); }
.cta-note { margin-top: 1.2rem; font-size: .78rem; color: var(--muted); letter-spacing: .05em; }
.cta-note.ok { color: var(--ok); }
/* ---------- Footer ---------- */
.site-foot { border-top: 1px solid var(--line); padding: 3.5rem 0; background: var(--ivory-2); }
.foot-wrap { display: grid; gap: 1.8rem; }
.foot-brand { display: flex; align-items: center; gap: 1rem; }
.foot-brand p { font-size: .82rem; color: var(--ink-soft); max-width: 46ch; }
.foot-nav { display: flex; flex-wrap: wrap; gap: 1.6rem; }
.foot-nav a { font-size: .72rem; letter-spacing: .18em; text-transform: uppercase; color: var(--ink-soft); transition: color .25s; }
.foot-nav a:hover { color: var(--champ-d); }
.foot-fine { font-size: .72rem; color: var(--muted); letter-spacing: .03em; }
/* ---------- Salon drawer ---------- */
.salon {
position: fixed; inset: 0; z-index: 80; visibility: hidden;
}
.salon.open { visibility: visible; }
.salon-scrim {
position: absolute; inset: 0; background: rgba(12,10,7,.5);
opacity: 0; transition: opacity .4s var(--ease); backdrop-filter: blur(2px);
}
.salon.open .salon-scrim { opacity: 1; }
.salon-panel {
position: absolute; top: 0; right: 0; height: 100%;
width: min(420px, 92vw); background: var(--ivory);
display: flex; flex-direction: column;
transform: translateX(100%); transition: transform .45s var(--ease);
box-shadow: -30px 0 70px -40px rgba(0,0,0,.6);
}
.salon.open .salon-panel { transform: none; }
.salon-head {
display: flex; align-items: center; justify-content: space-between;
padding: 1.4rem 1.6rem; border-bottom: 1px solid var(--line);
}
.salon-head h2 { font-size: 1.5rem; }
.salon-body { flex: 1; overflow-y: auto; padding: 1.2rem 1.6rem; }
.salon-empty { text-align: center; color: var(--muted); padding: 3rem 1rem; }
.salon-empty span { display: block; font-family: "Cormorant Garamond", serif; font-size: 2.4rem; color: var(--champ); margin-bottom: .6rem; }
.salon-item { display: grid; grid-template-columns: 56px 1fr auto; gap: .9rem; align-items: center; padding: 1rem 0; border-bottom: 1px solid var(--line); }
.salon-thumb { width: 56px; height: 56px; display: grid; place-items: center; border: 1px solid var(--line-gold); }
.salon-thumb svg { width: 60%; }
.salon-item h3 { font-family: "Cormorant Garamond", serif; font-size: 1.15rem; font-weight: 500; }
.salon-item p { font-size: .72rem; color: var(--muted); letter-spacing: .04em; }
.salon-item-price { font-family: "Cormorant Garamond", serif; font-size: 1.1rem; }
.salon-remove { font-size: .64rem; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); margin-top: .3rem; transition: color .2s; }
.salon-remove:hover { color: var(--sale, #b23a4e); }
.salon-foot { padding: 1.4rem 1.6rem; border-top: 1px solid var(--line); background: var(--ivory-2); }
.salon-total { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 1rem; }
.salon-total span { font-size: .7rem; letter-spacing: .22em; text-transform: uppercase; color: var(--muted); }
.salon-total strong { font-family: "Cormorant Garamond", serif; font-size: 1.7rem; font-weight: 500; }
/* ---------- Toast ---------- */
.toast {
position: fixed; left: 50%; bottom: 28px; transform: translate(-50%, 130%);
background: var(--noir); color: var(--ivory);
padding: .85rem 1.4rem; border-radius: var(--r);
font-size: .76rem; letter-spacing: .12em; text-transform: uppercase;
border: 1px solid var(--champ-d);
box-shadow: var(--shadow); z-index: 100; pointer-events: none;
transition: transform .45s var(--ease); max-width: 88vw; text-align: center;
}
.toast.show { transform: translate(-50%, 0); }
/* ---------- Responsive ---------- */
@media (max-width: 920px) {
.nav-toggle { display: flex; }
.primary-nav {
position: absolute; top: 100%; left: 0; right: 0;
flex-direction: column; gap: 0;
background: var(--ivory); border-bottom: 1px solid var(--line);
padding: .5rem 6vw 1.4rem;
transform: translateY(-12px); opacity: 0; pointer-events: none;
transition: opacity .3s var(--ease), transform .3s var(--ease);
}
.primary-nav.open { transform: none; opacity: 1; pointer-events: auto; }
.primary-nav a { padding: .95rem 0; border-bottom: 1px solid var(--line); }
.primary-nav a::after { display: none; }
.hero { grid-template-columns: 1fr; gap: 1rem; padding: 3rem 0 3.5rem; min-height: 0; }
.hero-art { order: -1; min-height: 240px; }
.hero-frame { max-width: none; }
.piece-grid { grid-template-columns: 1fr; gap: 2.4rem; }
.coll-grid { grid-template-columns: repeat(2, 1fr); }
.atelier { grid-template-columns: 1fr; }
.atelier-art { min-height: 280px; border-right: none; border-bottom: 1px solid rgba(201,164,76,.2); }
}
@media (max-width: 560px) {
.head-wrap { height: 64px; }
.brand-name { font-size: 1.15rem; }
.coll-grid { grid-template-columns: 1fr; }
.craft-stats { grid-template-columns: repeat(3, auto); gap: 1.4rem; }
.craft-stats dt { font-size: 2rem; }
.buy { gap: 1rem; }
.btn-buy { flex-basis: 100%; }
.trust { gap: .8rem 1.2rem; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; scroll-behavior: auto !important; }
.reveal { opacity: 1; transform: none; }
}/* Maison Aurelle — luxury boutique landing
Vanilla JS. No external libraries. Every interaction works. */
(function () {
"use strict";
var fmt = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
});
var money = function (n) { return fmt.format(n); };
/* ---------- Toast ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2600);
}
/* ---------- Reveal on scroll ---------- */
var reveals = Array.prototype.slice.call(document.querySelectorAll(".reveal"));
if ("IntersectionObserver" in window && reveals.length) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) {
e.target.classList.add("in");
io.unobserve(e.target);
}
});
}, { threshold: 0.16 });
reveals.forEach(function (el) { io.observe(el); });
} else {
reveals.forEach(function (el) { el.classList.add("in"); });
}
/* ---------- Mobile nav ---------- */
var navToggle = document.getElementById("navToggle");
var primaryNav = document.getElementById("primaryNav");
if (navToggle && primaryNav) {
navToggle.addEventListener("click", function () {
var open = primaryNav.classList.toggle("open");
navToggle.setAttribute("aria-expanded", String(open));
});
primaryNav.addEventListener("click", function (e) {
if (e.target.closest("a")) {
primaryNav.classList.remove("open");
navToggle.setAttribute("aria-expanded", "false");
}
});
}
/* ---------- Active-section nav highlight ---------- */
var navLinks = Array.prototype.slice.call(
document.querySelectorAll(".primary-nav a[href^='#']")
);
var sectionMap = navLinks
.map(function (a) {
var sec = document.querySelector(a.getAttribute("href"));
return sec ? { link: a, sec: sec } : null;
})
.filter(Boolean);
if ("IntersectionObserver" in window && sectionMap.length) {
var secIo = new IntersectionObserver(
function (entries) {
entries.forEach(function (e) {
var item = sectionMap.find(function (m) { return m.sec === e.target; });
if (item && e.isIntersecting) {
navLinks.forEach(function (l) { l.classList.remove("active"); });
item.link.classList.add("active");
}
});
},
{ rootMargin: "-45% 0px -50% 0px", threshold: 0 }
);
sectionMap.forEach(function (m) { secIo.observe(m.sec); });
}
/* ---------- Radiogroups: metal + size ---------- */
function wireRadioGroup(selector, onChange) {
var groups = document.querySelectorAll(selector);
Array.prototype.forEach.call(groups, function (group) {
var radios = Array.prototype.slice.call(group.querySelectorAll('[role="radio"]'));
function select(idx) {
radios.forEach(function (r, i) {
var on = i === idx;
r.classList.toggle("is-on", on);
r.setAttribute("aria-checked", String(on));
r.tabIndex = on ? 0 : -1;
});
if (onChange) onChange(radios[idx]);
}
// init tabindex from current state
var initial = radios.findIndex(function (r) { return r.classList.contains("is-on"); });
if (initial < 0) initial = 0;
select(initial);
radios.forEach(function (r, i) {
r.addEventListener("click", function () { select(i); });
r.addEventListener("keydown", function (e) {
var dir = 0;
if (e.key === "ArrowRight" || e.key === "ArrowDown") dir = 1;
else if (e.key === "ArrowLeft" || e.key === "ArrowUp") dir = -1;
else return;
e.preventDefault();
var next = (i + dir + radios.length) % radios.length;
select(next);
radios[next].focus();
});
});
});
}
var selectedMetal = "Champagne gold";
var selectedSize = "49";
wireRadioGroup(".swatches", function (el) {
selectedMetal = el.getAttribute("data-name") || el.textContent.trim();
});
wireRadioGroup(".sizes", function (el) {
selectedSize = el.textContent.trim();
});
/* ---------- Private salon (cart) ---------- */
var BASE_PIECE_PRICE = 48000;
var cart = []; // { id, name, meta, price, art }
var salon = document.getElementById("salon");
var salonBody = document.getElementById("salonBody");
var salonTotal = document.getElementById("salonTotal");
var cartBtn = document.getElementById("cartBtn");
var cartCount = document.getElementById("cartCount");
var salonClose = document.getElementById("salonClose");
var salonScrim = document.getElementById("salonScrim");
var lastFocus = null;
function ringArt() {
return (
'<svg viewBox="0 0 80 80" aria-hidden="true">' +
'<ellipse cx="40" cy="56" rx="20" ry="17" fill="none" stroke="#caa54d" stroke-width="4"/>' +
'<path d="M40 18 28 30 40 44 52 30Z" fill="#e6cf8f"/>' +
"</svg>"
);
}
function openSalon() {
if (!salon) return;
lastFocus = document.activeElement;
salon.classList.add("open");
salon.setAttribute("aria-hidden", "false");
document.body.style.overflow = "hidden";
if (salonClose) salonClose.focus();
}
function closeSalon() {
if (!salon) return;
salon.classList.remove("open");
salon.setAttribute("aria-hidden", "true");
document.body.style.overflow = "";
if (lastFocus && lastFocus.focus) lastFocus.focus();
}
function renderSalon() {
if (!salonBody) return;
if (!cart.length) {
salonBody.innerHTML =
'<div class="salon-empty"><span>✦</span>Your salon is empty.<br>Add a piece to begin a consultation.</div>';
} else {
salonBody.innerHTML = cart
.map(function (item, i) {
return (
'<div class="salon-item">' +
'<div class="salon-thumb">' + (item.art || ringArt()) + "</div>" +
"<div>" +
"<h3>" + item.name + "</h3>" +
"<p>" + item.meta + "</p>" +
'<button class="salon-remove" data-remove="' + i + '">Remove</button>' +
"</div>" +
'<div class="salon-item-price">' + money(item.price) + "</div>" +
"</div>"
);
})
.join("");
}
var total = cart.reduce(function (s, it) { return s + it.price; }, 0);
if (salonTotal) salonTotal.textContent = money(total);
if (cartCount) {
cartCount.textContent = String(cart.length);
cartCount.classList.toggle("show", cart.length > 0);
}
if (cartBtn) {
cartBtn.setAttribute(
"aria-label",
"Open private salon, " + cart.length + (cart.length === 1 ? " item" : " items")
);
}
}
function addToCart(item) {
cart.push(item);
renderSalon();
}
if (salonBody) {
salonBody.addEventListener("click", function (e) {
var btn = e.target.closest("[data-remove]");
if (!btn) return;
var idx = parseInt(btn.getAttribute("data-remove"), 10);
if (!isNaN(idx)) {
var removed = cart.splice(idx, 1)[0];
renderSalon();
if (removed) toast(removed.name + " removed");
}
});
}
if (cartBtn) cartBtn.addEventListener("click", openSalon);
if (salonClose) salonClose.addEventListener("click", closeSalon);
if (salonScrim) salonScrim.addEventListener("click", closeSalon);
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && salon && salon.classList.contains("open")) closeSalon();
});
var salonCheckout = document.getElementById("salonCheckout");
if (salonCheckout) {
salonCheckout.addEventListener("click", function () {
if (!cart.length) {
toast("Add a piece to begin");
return;
}
toast("A salon director will be in touch");
closeSalon();
});
}
/* ---------- Reserve the hero piece ---------- */
var reserveBtn = document.getElementById("reserveBtn");
var priceVal = document.getElementById("priceVal");
if (reserveBtn) {
reserveBtn.addEventListener("click", function () {
addToCart({
name: "The Aurelle Solitaire",
meta: selectedMetal + " · size " + selectedSize,
price: BASE_PIECE_PRICE,
art: ringArt(),
});
toast("Added to your private salon");
openSalon();
});
}
if (priceVal) priceVal.textContent = money(BASE_PIECE_PRICE);
/* ---------- Collection (editorial) ---------- */
var pieces = [
{ name: "Étoile Pendant", meta: "0.9ct · 18k champagne", price: 12400, tag: "1 of 1", tint: "linear-gradient(155deg,#fbf6ea,#efe3c6)" },
{ name: "Lueur Eternity Band", meta: "Pavé · 1.4ct total", price: 18900, tag: "Édition Hiver", tint: "linear-gradient(155deg,#f7efe2,#e7d6b0)" },
{ name: "Onde Drop Earrings", meta: "Briolette · rose gold", price: 9600, tag: "1 of 1", tint: "linear-gradient(155deg,#f9efe9,#ecd6c9)" },
{ name: "Sillage Cuff", meta: "Hand-engraved · noir", price: 21500, tag: "Retired soon", tint: "linear-gradient(155deg,#efece6,#d9d2c6)" },
{ name: "Aube Solitaire", meta: "1.6ct cushion", price: 32800, tag: "Édition Hiver", tint: "linear-gradient(155deg,#fbf6ea,#f0e2c2)" },
{ name: "Volute Tennis Bracelet", meta: "3.2ct line · champagne", price: 27400, tag: "1 of 1", tint: "linear-gradient(155deg,#f6f0e6,#e4d4ab)" },
];
var miniArt = [
'<svg viewBox="0 0 80 80" aria-hidden="true"><path d="M40 14 26 30 40 66 54 30Z" fill="#e6cf8f"/><path d="M26 30 54 30 40 14Z" fill="#fffaf0" opacity=".85"/><path d="M26 30 40 66 40 30Z" fill="#caa54d" opacity=".7"/></svg>',
'<svg viewBox="0 0 80 80" aria-hidden="true"><ellipse cx="40" cy="40" rx="26" ry="20" fill="none" stroke="#caa54d" stroke-width="6"/><circle cx="40" cy="20" r="5" fill="#e6cf8f"/></svg>',
'<svg viewBox="0 0 80 80" aria-hidden="true"><circle cx="30" cy="26" r="8" fill="#e6cf8f"/><circle cx="50" cy="26" r="8" fill="#e6cf8f"/><path d="M30 34 30 56M50 34 50 56" stroke="#caa54d" stroke-width="3"/></svg>',
'<svg viewBox="0 0 80 80" aria-hidden="true"><path d="M18 30 Q40 20 62 30 L58 50 Q40 60 22 50Z" fill="none" stroke="#caa54d" stroke-width="4"/><circle cx="40" cy="40" r="4" fill="#e6cf8f"/></svg>',
'<svg viewBox="0 0 80 80" aria-hidden="true"><ellipse cx="40" cy="52" rx="18" ry="15" fill="none" stroke="#caa54d" stroke-width="5"/><path d="M40 16 30 30 40 42 50 30Z" fill="#e6cf8f"/></svg>',
'<svg viewBox="0 0 80 80" aria-hidden="true"><g fill="#e6cf8f"><circle cx="24" cy="44" r="6"/><circle cx="40" cy="40" r="6"/><circle cx="56" cy="44" r="6"/></g><path d="M18 44 H62" stroke="#caa54d" stroke-width="2"/></svg>',
];
var collGrid = document.getElementById("collGrid");
if (collGrid) {
collGrid.innerHTML = pieces
.map(function (p, i) {
return (
'<article class="coll-card">' +
'<div class="card-stage" style="background:' + p.tint + '">' +
'<span class="card-tag">' + p.tag + "</span>" +
(miniArt[i % miniArt.length]) +
"</div>" +
'<div class="card-body">' +
'<h3 class="card-name">' + p.name + "</h3>" +
'<p class="card-meta">' + p.meta + "</p>" +
'<div class="card-foot">' +
'<span class="card-price">' + money(p.price) + "</span>" +
'<button class="card-add" data-add="' + i + '" aria-pressed="false">Add</button>' +
"</div></div></article>"
);
})
.join("");
collGrid.addEventListener("click", function (e) {
var btn = e.target.closest("[data-add]");
if (!btn) return;
var idx = parseInt(btn.getAttribute("data-add"), 10);
var p = pieces[idx];
if (!p) return;
var card = btn.closest(".coll-card");
if (card) card.classList.add("is-saved");
btn.textContent = "Saved";
btn.setAttribute("aria-pressed", "true");
addToCart({
name: p.name,
meta: p.meta,
price: p.price,
art: miniArt[idx % miniArt.length],
});
toast(p.name + " added to your salon");
});
}
/* ---------- Atelier stat counters ---------- */
var statEls = Array.prototype.slice.call(document.querySelectorAll(".craft-stats dt[data-to]"));
var statsDone = false;
function runStats() {
if (statsDone) return;
statsDone = true;
statEls.forEach(function (el) {
var to = parseInt(el.getAttribute("data-to"), 10) || 0;
var start = null;
var dur = 1400;
function step(ts) {
if (start === null) start = ts;
var p = Math.min((ts - start) / dur, 1);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = Math.round(to * eased);
if (p < 1) requestAnimationFrame(step);
else el.textContent = String(to);
}
requestAnimationFrame(step);
});
}
var atelier = document.getElementById("atelier");
if (atelier && "IntersectionObserver" in window) {
var statIo = new IntersectionObserver(
function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) { runStats(); statIo.disconnect(); }
});
},
{ threshold: 0.4 }
);
statIo.observe(atelier);
} else {
runStats();
}
/* ---------- Appointment form ---------- */
var apptForm = document.getElementById("apptForm");
var apptEmail = document.getElementById("apptEmail");
var apptNote = document.getElementById("apptNote");
if (apptForm && apptEmail && apptNote) {
var emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
apptForm.addEventListener("submit", function (e) {
e.preventDefault();
var val = apptEmail.value.trim();
if (!emailRe.test(val)) {
apptEmail.classList.add("invalid");
apptEmail.focus();
apptNote.textContent = "Please enter a valid email address.";
apptNote.classList.remove("ok");
return;
}
apptEmail.classList.remove("invalid");
apptEmail.value = "";
apptNote.textContent = "Thank you — our salon director will write within two days.";
apptNote.classList.add("ok");
toast("Appointment requested");
});
apptEmail.addEventListener("input", function () {
apptEmail.classList.remove("invalid");
});
}
renderSalon();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Maison Aurelle — Atelier de Haute Joaillerie</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:ital,wght@0,400;0,500;0,600;1,400&family=Jost:wght@300;400;500&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- Announcement -->
<div class="annc" role="region" aria-label="Announcement">
<p>Complimentary engraving & insured worldwide delivery on every commission</p>
</div>
<!-- Header -->
<header class="site-head" id="top">
<div class="wrap head-wrap">
<button class="nav-toggle" id="navToggle" aria-expanded="false" aria-controls="primaryNav" aria-label="Open menu">
<span></span><span></span><span></span>
</button>
<a class="brand" href="#top" aria-label="Maison Aurelle, home">
<span class="brand-mark" aria-hidden="true">A</span>
<span class="brand-name">Maison Aurelle</span>
</a>
<nav class="primary-nav" id="primaryNav" aria-label="Primary">
<a href="#piece">The Piece</a>
<a href="#collection">The Collection</a>
<a href="#atelier">The Atelier</a>
<a href="#appointment">Appointment</a>
</nav>
<div class="head-actions">
<button class="icon-btn" id="cartBtn" aria-label="Open private salon, 0 items">
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.3"><path d="M6 7h12l-1 13H7L6 7Z"/><path d="M9 7a3 3 0 0 1 6 0"/></svg>
<span class="cart-count" id="cartCount" aria-hidden="true">0</span>
</button>
</div>
</div>
</header>
<main>
<!-- Hero -->
<section class="hero reveal" aria-labelledby="heroTitle">
<div class="hero-frame">
<p class="eyebrow">Maison Aurelle · Paris · est. 1908</p>
<h1 id="heroTitle" class="hero-title">The art of the<br /><em>unrepeatable</em> object</h1>
<p class="hero-sub">A single atelier. A handful of pieces each season. Every commission cut, set and signed by one master jeweller — yours alone, and made to be inherited.</p>
<div class="hero-cta">
<a href="#piece" class="btn btn-solid">Discover the piece</a>
<a href="#appointment" class="btn btn-ghost">Request an appointment</a>
</div>
</div>
<div class="hero-art" aria-hidden="true">
<div class="hero-orb"></div>
<svg viewBox="0 0 220 320" class="hero-svg" role="img">
<defs>
<linearGradient id="gold" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#f6e7bd"/><stop offset=".5" stop-color="#c9a44c"/><stop offset="1" stop-color="#8a6a1f"/>
</linearGradient>
<radialGradient id="stone" cx="40%" cy="32%" r="75%">
<stop offset="0" stop-color="#fbf7ef"/><stop offset=".4" stop-color="#efe2c4"/><stop offset="1" stop-color="#b89240"/>
</radialGradient>
</defs>
<ellipse cx="110" cy="276" rx="70" ry="12" fill="rgba(0,0,0,.35)"/>
<path d="M110 70 70 120 110 250 150 120Z" fill="url(#gold)" opacity=".9"/>
<path d="M110 70 70 120 110 138 150 120Z" fill="url(#stone)"/>
<path d="M70 120 110 138 110 250Z" fill="#d8b765" opacity=".7"/>
<path d="M150 120 110 138 110 250Z" fill="#a07c2a" opacity=".8"/>
<circle cx="110" cy="44" r="6" fill="url(#gold)"/>
</svg>
</div>
</section>
<!-- Hero product -->
<section class="piece reveal" id="piece" aria-labelledby="pieceTitle">
<div class="rule"><span>The Piece</span></div>
<div class="piece-grid">
<figure class="piece-stage">
<div class="stage-glow" aria-hidden="true"></div>
<svg viewBox="0 0 320 320" class="piece-svg" role="img" aria-label="Aurelle Solitaire ring rendered in gold and champagne stone">
<defs>
<linearGradient id="band" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#f7e8be"/><stop offset=".5" stop-color="#caa54d"/><stop offset="1" stop-color="#7e5f1a"/>
</linearGradient>
<radialGradient id="gem" cx="38%" cy="30%" r="80%">
<stop offset="0" stop-color="#fffaf0"/><stop offset=".35" stop-color="#f3e6c8"/><stop offset="1" stop-color="#c39a3f"/>
</radialGradient>
</defs>
<ellipse cx="160" cy="232" rx="74" ry="64" fill="none" stroke="url(#band)" stroke-width="13"/>
<path d="M160 96 124 132 160 178 196 132Z" fill="url(#gem)"/>
<path d="M124 132 160 178 160 96Z" fill="#e7d29a" opacity=".75"/>
<path d="M196 132 160 178 160 96Z" fill="#b78f37" opacity=".8"/>
<path d="M124 132 196 132 160 96Z" fill="#fffaf0" opacity=".85"/>
<g stroke="#9a7726" stroke-width="1" opacity=".5">
<path d="M160 96 160 178M124 132 196 132"/>
</g>
<circle class="sparkle" cx="148" cy="120" r="3" fill="#fff"/>
</svg>
</figure>
<div class="piece-info">
<p class="eyebrow">Signature Solitaire · No. 14</p>
<h2 id="pieceTitle" class="piece-name">The Aurelle Solitaire</h2>
<div class="rate" aria-label="Rated 5 out of 5 from 38 commissions">
<span class="stars" aria-hidden="true">★★★★★</span>
<span class="rate-meta">5.0 · 38 private commissions</span>
</div>
<p class="piece-copy">A 2.1-carat cushion stone of rare warmth, raised on a hand-forged band of 18k champagne gold. Forty hours at the bench. One signature beneath the bezel.</p>
<div class="opts">
<div class="opt">
<span class="opt-label">Métal</span>
<div class="swatches" role="radiogroup" aria-label="Choose metal">
<button class="sw sw-champ is-on" role="radio" aria-checked="true" data-name="Champagne gold">Champagne</button>
<button class="sw sw-rose" role="radio" aria-checked="false" data-name="Rose gold">Rose</button>
<button class="sw sw-noir" role="radio" aria-checked="false" data-name="Noir platinum">Noir</button>
</div>
</div>
<div class="opt">
<span class="opt-label">Taille</span>
<div class="sizes" role="radiogroup" aria-label="Choose ring size">
<button class="size is-on" role="radio" aria-checked="true">49</button>
<button class="size" role="radio" aria-checked="false">51</button>
<button class="size" role="radio" aria-checked="false">53</button>
<button class="size" role="radio" aria-checked="false">55</button>
</div>
</div>
</div>
<div class="buy">
<p class="price"><span class="price-from">from</span> <span id="priceVal">$48,000.00</span></p>
<button class="btn btn-solid btn-buy" id="reserveBtn">Reserve this piece</button>
</div>
<ul class="trust" aria-label="Service guarantees">
<li><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true"><path d="M12 3l7 3v5c0 4.5-3 7.5-7 9-4-1.5-7-4.5-7-9V6l7-3Z"/><path d="M9 12l2 2 4-4"/></svg> Lifetime guarantee</li>
<li><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true"><path d="M3 8h13l5 5v3h-3M3 8v8h2"/><circle cx="7" cy="18" r="2"/><circle cx="17" cy="18" r="2"/></svg> Insured delivery</li>
<li><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true"><path d="M4 7h16v12H4Z"/><path d="M4 7l8 6 8-6"/></svg> Private consultation</li>
</ul>
</div>
</div>
</section>
<!-- The collection (editorial) -->
<section class="collection reveal" id="collection" aria-labelledby="collTitle">
<div class="rule"><span>The Collection</span></div>
<header class="coll-head">
<h2 id="collTitle">Édition Hiver — twelve studies in light</h2>
<p>Drawn from the winter archive. Each study is offered once, then retired. Press a piece to add it to your private salon.</p>
</header>
<div class="coll-grid" id="collGrid">
<!-- cards injected by script -->
</div>
</section>
<!-- The Atelier -->
<section class="atelier reveal" id="atelier" aria-labelledby="atelierTitle">
<div class="atelier-art" aria-hidden="true">
<div class="craft-tile">
<svg viewBox="0 0 200 200" role="img">
<defs>
<linearGradient id="bench" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#caa54d"/><stop offset="1" stop-color="#7e5f1a"/>
</linearGradient>
</defs>
<circle cx="100" cy="100" r="78" fill="none" stroke="url(#bench)" stroke-width="2" opacity=".55"/>
<circle cx="100" cy="100" r="56" fill="none" stroke="url(#bench)" stroke-width="1" opacity=".4"/>
<path d="M100 30 86 60 100 84 114 60Z" fill="url(#bench)"/>
<circle cx="100" cy="100" r="4" fill="#f6e7bd"/>
</svg>
</div>
</div>
<div class="atelier-copy">
<p class="eyebrow">The Atelier</p>
<h2 id="atelierTitle">One bench. One pair of hands.<br />A century of patience.</h2>
<p>Since 1908 every Aurelle piece has been finished at a single bench on the Rue de la Paix. No assembly line, no outsourced setting — only one master and the apprentice who will one day take the chair.</p>
<dl class="craft-stats">
<div><dt id="s1" data-to="116">0</dt><dd>years at the bench</dd></div>
<div><dt id="s2" data-to="40">0</dt><dd>hours per commission</dd></div>
<div><dt id="s3" data-to="9">0</dt><dd>pieces per season</dd></div>
</dl>
<p class="atelier-sign">— Élise Aurelle, fourth-generation maître joaillier</p>
</div>
</section>
<!-- CTA / appointment -->
<section class="cta reveal" id="appointment" aria-labelledby="ctaTitle">
<div class="cta-inner">
<p class="eyebrow">By appointment</p>
<h2 id="ctaTitle">A quiet hour, reserved for you</h2>
<p>We receive a single guest at a time. Share where to reach you and our salon director will propose a private viewing within two days.</p>
<form class="cta-form" id="apptForm" novalidate>
<label class="sr-only" for="apptEmail">Email address</label>
<input id="apptEmail" name="email" type="email" inputmode="email" placeholder="your@email" autocomplete="email" required />
<button class="btn btn-solid" type="submit">Request appointment</button>
</form>
<p class="cta-note" id="apptNote" aria-live="polite">Discreet. We never share your details.</p>
</div>
</section>
</main>
<footer class="site-foot">
<div class="wrap foot-wrap">
<div class="foot-brand">
<span class="brand-mark" aria-hidden="true">A</span>
<p>Maison Aurelle — Haute joaillerie depuis 1908. 14 Rue de la Paix, Paris.</p>
</div>
<nav class="foot-nav" aria-label="Footer">
<a href="#piece">The Piece</a>
<a href="#collection">Collection</a>
<a href="#atelier">Atelier</a>
<a href="#appointment">Appointment</a>
</nav>
<p class="foot-fine">Illustrative storefront — fictional pieces, prices & reviews. No real checkout.</p>
</div>
</footer>
<!-- Private salon drawer -->
<aside class="salon" id="salon" aria-hidden="true" aria-label="Private salon">
<div class="salon-panel" role="dialog" aria-modal="true" aria-labelledby="salonTitle">
<header class="salon-head">
<h2 id="salonTitle">Your private salon</h2>
<button class="icon-btn" id="salonClose" aria-label="Close salon">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg>
</button>
</header>
<div class="salon-body" id="salonBody"></div>
<footer class="salon-foot">
<div class="salon-total"><span>Estimate</span><strong id="salonTotal">$0.00</strong></div>
<button class="btn btn-solid btn-block" id="salonCheckout">Begin consultation</button>
</footer>
</div>
<div class="salon-scrim" id="salonScrim"></div>
</aside>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Luxury Boutique Landing
A storefront landing for Maison Aurelle, a fictional Paris haute-joaillerie house. The page leans on immense whitespace, thin gold rules, an ivory-and-noir palette and a Cormorant Garamond serif to evoke quiet opulence. A cinematic hero opens onto a single signature piece — the Aurelle Solitaire — rendered as a layered inline-SVG ring on a tinted stage, with thin gold framing and a slow floating glint. No external images are used anywhere; every product, gem and craft motif is drawn with CSS gradients and inline SVG.
Interactions are restrained but real. The metal and ring-size selectors are keyboard-accessible
radiogroups (arrow keys move the selection) that feed the chosen options straight into the cart.
“Reserve this piece” and every collection-card “Add” button push items into a sliding private
salon drawer with running totals, per-item removal and a focus-trapped, Escape-closable dialog.
The atelier section animates its craft statistics with a count-up when scrolled into view, the
appointment form validates the email inline, and the whole page fades in section by section through
slow IntersectionObserver reveals. Prices are formatted with Intl.NumberFormat, and a
prefers-reduced-motion block disables the cinematics for users who ask for stillness.
Illustrative storefront UI only — fictional products, prices, and reviews. No real checkout.