Salon — Modern Hair Salon Landing
A luxe, editorial landing page for a modern hair salon, built in vanilla HTML, CSS and JavaScript. It pairs a Cormorant serif display with clean Inter UI across a sticky nav, a striking serif hero with a Book CTA, a signature services menu, a stylists strip, an interactive before and after slider with a lookbook, a rotating testimonials carousel, live hours with an open or closed indicator, and a working appointment request form with a live plan total and toasts.
MCP
代码
/* ============================================================
Maison Lumière — Modern Hair Salon Landing
Palette: rose-gold + cream + matte black, subtle gold
Fonts: Cormorant Garamond (display) + Inter (UI)
============================================================ */
:root {
--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;
--sh-sm: 0 1px 2px rgba(28, 24, 20, 0.06), 0 4px 14px rgba(28, 24, 20, 0.05);
--sh-md: 0 6px 22px rgba(28, 24, 20, 0.08), 0 18px 50px rgba(28, 24, 20, 0.07);
--sh-lg: 0 18px 60px rgba(28, 24, 20, 0.14);
--serif: "Cormorant Garamond", Georgia, serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
--nav-h: 74px;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--sans);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4 {
font-family: var(--serif);
font-weight: 600;
line-height: 1.08;
margin: 0;
color: var(--ink);
}
p { margin: 0; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
.skip {
position: absolute;
left: -999px;
top: 0;
background: var(--ink);
color: var(--cream);
padding: 10px 16px;
border-radius: var(--r-sm);
z-index: 200;
}
.skip:focus { left: 12px; top: 12px; }
:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 3px;
border-radius: 4px;
}
.eyebrow {
font-family: var(--sans);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--gold-d);
margin: 0 0 14px;
}
.eyebrow--light { color: var(--rose); }
/* ---------- Buttons ---------- */
.btn {
font-family: var(--sans);
font-weight: 600;
font-size: 14px;
letter-spacing: 0.01em;
padding: 11px 20px;
border-radius: 999px;
border: 1px solid transparent;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: transform 0.18s ease, background 0.2s ease, color 0.2s ease,
border-color 0.2s ease, box-shadow 0.2s ease;
white-space: nowrap;
}
.btn:active { transform: translateY(1px) scale(0.99); }
.btn--solid {
background: var(--ink);
color: var(--cream);
}
.btn--solid:hover {
background: var(--gold-d);
color: var(--white);
box-shadow: var(--sh-md);
}
.btn--ghost {
background: transparent;
color: var(--ink);
border-color: var(--line-2);
}
.btn--ghost:hover { border-color: var(--gold); color: var(--gold-d); }
.btn--line {
background: var(--white);
color: var(--ink);
border-color: var(--line-2);
}
.btn--line:hover { border-color: var(--gold); box-shadow: var(--sh-sm); }
.btn--lg { padding: 15px 28px; font-size: 15px; }
.btn--block { width: 100%; padding: 15px; }
/* ---------- Nav ---------- */
.nav {
position: sticky;
top: 0;
z-index: 100;
background: rgba(250, 246, 239, 0.82);
backdrop-filter: saturate(150%) blur(14px);
-webkit-backdrop-filter: saturate(150%) blur(14px);
border-bottom: 1px solid var(--line);
transition: box-shadow 0.3s ease, background 0.3s ease;
}
.nav.is-scrolled { box-shadow: var(--sh-sm); }
.nav__inner {
max-width: 1200px;
margin: 0 auto;
height: var(--nav-h);
padding: 0 28px;
display: flex;
align-items: center;
gap: 26px;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
margin-right: auto;
}
.brand__mark {
width: 38px;
height: 38px;
border-radius: 50%;
display: grid;
place-items: center;
background: linear-gradient(135deg, var(--gold), var(--gold-d));
color: var(--white);
font-family: var(--serif);
font-weight: 700;
font-size: 15px;
letter-spacing: 0.02em;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25);
}
.brand__name {
font-family: var(--serif);
font-size: 22px;
font-weight: 600;
letter-spacing: 0.01em;
}
.nav__links {
display: flex;
gap: 26px;
}
.nav__links a {
font-size: 14px;
font-weight: 500;
color: var(--ink-2);
position: relative;
padding: 6px 0;
}
.nav__links a::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
height: 1.5px;
width: 0;
background: var(--gold);
transition: width 0.25s ease;
}
.nav__links a:hover { color: var(--ink); }
.nav__links a:hover::after { width: 100%; }
.nav__cta { display: flex; gap: 10px; }
.nav__toggle {
display: none;
flex-direction: column;
justify-content: center;
gap: 5px;
width: 44px;
height: 44px;
background: transparent;
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
cursor: pointer;
}
.nav__toggle span {
height: 1.6px;
width: 20px;
margin: 0 auto;
background: var(--ink);
transition: transform 0.28s ease, opacity 0.2s ease;
}
.nav__toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6.6px) rotate(45deg); }
.nav__toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav__toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6.6px) rotate(-45deg); }
.mobileNav {
display: flex;
flex-direction: column;
gap: 4px;
padding: 12px 22px 22px;
border-top: 1px solid var(--line);
background: var(--bg);
}
.mobileNav a {
font-size: 16px;
font-weight: 500;
padding: 12px 4px;
border-bottom: 1px solid var(--line);
}
.mobileNav .btn { margin-top: 12px; }
/* ---------- Hero ---------- */
.hero {
max-width: 1200px;
margin: 0 auto;
padding: 56px 28px 0;
position: relative;
}
.hero__grid {
display: grid;
grid-template-columns: 1.05fr 0.95fr;
gap: 48px;
align-items: center;
min-height: 64vh;
}
.hero__title {
font-size: clamp(56px, 9vw, 116px);
font-weight: 600;
letter-spacing: -0.01em;
margin: 4px 0 22px;
}
.hero__title em {
font-style: italic;
font-weight: 500;
color: var(--gold-d);
}
.hero__lead {
font-size: 18px;
color: var(--ink-2);
max-width: 46ch;
margin-bottom: 30px;
}
.hero__actions {
display: flex;
gap: 14px;
flex-wrap: wrap;
margin-bottom: 42px;
}
.hero__stats {
display: flex;
gap: 38px;
margin: 0;
flex-wrap: wrap;
}
.hero__stats dt {
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 6px;
}
.hero__stats dd {
margin: 0;
font-family: var(--serif);
font-size: 30px;
font-weight: 600;
color: var(--ink);
}
.hero__art { margin: 0; }
.hero__frame {
position: relative;
border-radius: var(--r-lg);
aspect-ratio: 4 / 5;
overflow: hidden;
background:
radial-gradient(120% 90% at 70% 10%, rgba(255, 255, 255, 0.5), transparent 55%),
linear-gradient(160deg, var(--rose-soft) 0%, var(--rose) 42%, var(--gold) 78%, var(--gold-d) 100%);
box-shadow: var(--sh-lg), inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}
.hero__frame::before {
content: "";
position: absolute;
inset: 14px;
border: 1px solid rgba(255, 255, 255, 0.45);
border-radius: calc(var(--r-lg) - 8px);
pointer-events: none;
}
.hero__shine {
position: absolute;
inset: 0;
background: linear-gradient(115deg, transparent 30%, rgba(255, 255, 255, 0.55) 48%, transparent 62%);
transform: translateX(-30%);
animation: shine 5.5s ease-in-out infinite;
}
@keyframes shine {
0%, 60% { transform: translateX(-60%); }
85%, 100% { transform: translateX(120%); }
}
.hero__tag {
position: absolute;
left: 22px;
bottom: 22px;
background: rgba(28, 24, 20, 0.7);
color: var(--cream);
font-size: 12px;
letter-spacing: 0.14em;
text-transform: uppercase;
padding: 9px 16px;
border-radius: 999px;
backdrop-filter: blur(6px);
}
/* ---------- Marquee ---------- */
.marquee {
margin-top: 56px;
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
overflow: hidden;
padding: 18px 0;
}
.marquee__track {
display: flex;
gap: 26px;
width: max-content;
animation: scroll 30s linear infinite;
font-family: var(--serif);
font-size: 22px;
color: var(--ink-2);
white-space: nowrap;
}
.marquee__track span { color: var(--ink-2); }
.marquee:hover .marquee__track { animation-play-state: paused; }
@keyframes scroll {
to { transform: translateX(-50%); }
}
/* ---------- Sections ---------- */
.section {
max-width: 1200px;
margin: 0 auto;
padding: 96px 28px;
}
.section--tint {
max-width: none;
background: linear-gradient(180deg, var(--cream), var(--bg));
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
}
.section--tint > * {
max-width: 1200px;
margin-left: auto;
margin-right: auto;
}
.section--dark {
max-width: none;
background: var(--ink);
color: var(--cream);
}
.section--dark > * {
max-width: 1000px;
margin-left: auto;
margin-right: auto;
}
.section--dark .section__title { color: var(--cream); }
.section__head { max-width: 640px; margin-bottom: 44px; }
.section--dark .section__head,
.section--tint .section__head { margin-left: auto; margin-right: auto; text-align: center; }
.section__title {
font-size: clamp(34px, 5vw, 54px);
margin-bottom: 16px;
}
.section__sub {
font-size: 16px;
color: var(--muted);
max-width: 56ch;
}
.section--tint .section__sub,
.section--dark .section__head .section__sub { margin: 0 auto; }
.section--dark .section__sub { color: var(--rose); }
/* ---------- Services ---------- */
.services {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.svc {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 26px 24px 22px;
display: flex;
flex-direction: column;
gap: 12px;
transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.22s ease;
}
.svc:hover {
transform: translateY(-4px);
box-shadow: var(--sh-md);
border-color: var(--gold-soft);
}
.svc--feature {
background: linear-gradient(165deg, var(--ink) 0%, var(--ink-2) 100%);
color: var(--cream);
border-color: transparent;
}
.svc--feature h3,
.svc--feature .svc__price { color: var(--white); }
.svc--feature p { color: var(--rose-soft); }
.svc--feature .svc__meta { color: var(--rose); }
.svc--feature .svc__add { border-color: rgba(255, 255, 255, 0.3); color: var(--cream); }
.svc--feature .svc__add:hover { background: var(--gold); border-color: var(--gold); color: var(--white); }
.svc__top {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 12px;
}
.svc__top h3 { font-size: 24px; }
.svc__price {
font-family: var(--sans);
font-size: 13px;
font-weight: 600;
color: var(--gold-d);
white-space: nowrap;
}
.svc p { font-size: 14.5px; color: var(--ink-2); flex: 1; }
.svc__meta {
display: flex;
align-items: center;
gap: 10px;
font-size: 12px;
letter-spacing: 0.04em;
color: var(--muted);
text-transform: uppercase;
}
.dot { width: 4px; height: 4px; border-radius: 50%; background: currentColor; opacity: 0.5; }
.svc__add {
align-self: flex-start;
margin-top: 6px;
font-family: var(--sans);
font-size: 13px;
font-weight: 600;
padding: 9px 16px;
border-radius: 999px;
border: 1px solid var(--line-2);
background: transparent;
color: var(--ink);
cursor: pointer;
transition: all 0.18s ease;
}
.svc__add:hover { background: var(--ink); color: var(--cream); border-color: var(--ink); }
.svc__add.is-added { background: var(--gold-soft); border-color: var(--gold); color: var(--gold-d); }
/* ---------- Stylists ---------- */
.stylists {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 22px;
}
.stylist {
text-align: center;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 24px 18px 22px;
transition: transform 0.22s ease, box-shadow 0.22s ease;
}
.stylist:hover { transform: translateY(-4px); box-shadow: var(--sh-md); }
.stylist__photo {
width: 96px;
height: 96px;
border-radius: 50%;
margin: 0 auto 16px;
background: linear-gradient(140deg, var(--c1), var(--c2));
position: relative;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3), var(--sh-sm);
}
.stylist__photo::after {
content: attr(data-initials);
position: absolute;
inset: 0;
display: grid;
place-items: center;
font-family: var(--serif);
font-size: 30px;
font-weight: 600;
color: rgba(255, 255, 255, 0.92);
}
.stylist h3 { font-size: 22px; }
.stylist__role {
font-size: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--gold-d);
margin: 6px 0 10px;
}
.stylist__bio { font-size: 13.5px; color: var(--ink-2); margin-bottom: 16px; min-height: 38px; }
.link-pill {
font-family: var(--sans);
font-size: 12.5px;
font-weight: 600;
letter-spacing: 0.03em;
padding: 8px 18px;
border-radius: 999px;
border: 1px solid var(--line-2);
background: transparent;
color: var(--ink);
cursor: pointer;
transition: all 0.18s ease;
}
.link-pill:hover { background: var(--gold); border-color: var(--gold); color: var(--white); }
/* ---------- Before / After ---------- */
.ba {
position: relative;
width: 100%;
aspect-ratio: 21 / 9;
border-radius: var(--r-lg);
overflow: hidden;
box-shadow: var(--sh-md);
margin-bottom: 28px;
user-select: none;
touch-action: none;
}
.ba__pane {
position: absolute;
inset: 0;
}
.ba__pane--after {
background:
radial-gradient(80% 120% at 80% 10%, rgba(255, 255, 255, 0.35), transparent 50%),
linear-gradient(120deg, var(--gold-soft), var(--rose) 55%, var(--gold-d));
}
.ba__pane--before {
background:
radial-gradient(80% 120% at 20% 90%, rgba(255, 255, 255, 0.12), transparent 50%),
linear-gradient(120deg, #6f655a, var(--ink-2) 60%, var(--ink));
clip-path: inset(0 50% 0 0);
}
.ba__lbl {
position: absolute;
top: 18px;
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
padding: 7px 14px;
border-radius: 999px;
background: rgba(28, 24, 20, 0.55);
color: var(--cream);
backdrop-filter: blur(4px);
}
.ba__pane--after .ba__lbl { right: 18px; }
.ba__pane--before .ba__lbl { left: 18px; }
.ba__range {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
margin: 0;
opacity: 0;
cursor: ew-resize;
}
.ba__handle {
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 2px;
background: rgba(255, 255, 255, 0.9);
transform: translateX(-1px);
pointer-events: none;
box-shadow: 0 0 0 1px rgba(28, 24, 20, 0.1);
}
.ba__handle span {
position: absolute;
top: 50%;
left: 50%;
width: 44px;
height: 44px;
transform: translate(-50%, -50%);
border-radius: 50%;
background: var(--white);
box-shadow: var(--sh-md);
display: grid;
place-items: center;
}
.ba__handle span::before {
content: "‹ ›";
font-size: 16px;
letter-spacing: 2px;
color: var(--gold-d);
font-weight: 700;
}
/* ---------- Lookbook ---------- */
.lookbook {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.look {
margin: 0;
position: relative;
aspect-ratio: 3 / 4;
border-radius: var(--r-md);
overflow: hidden;
background: linear-gradient(160deg, var(--a), var(--b));
box-shadow: var(--sh-sm);
transition: transform 0.3s ease;
}
.look::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(28, 24, 20, 0.55), transparent 50%);
}
.look:hover { transform: scale(1.02); }
.look figcaption {
position: absolute;
left: 16px;
bottom: 14px;
z-index: 1;
font-family: var(--serif);
font-size: 19px;
color: var(--white);
}
/* ---------- Testimonials ---------- */
.quotes { position: relative; min-height: 200px; }
.quote {
margin: 0;
text-align: center;
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 22px;
opacity: 0;
transform: translateY(14px);
transition: opacity 0.5s ease, transform 0.5s ease;
pointer-events: none;
}
.quote.is-active { opacity: 1; transform: none; pointer-events: auto; }
.quote p {
font-family: var(--serif);
font-size: clamp(22px, 3.4vw, 34px);
font-weight: 500;
line-height: 1.32;
color: var(--cream);
max-width: 22ch;
}
.quote footer {
display: flex;
align-items: center;
gap: 14px;
font-size: 14px;
}
.quote__name { color: var(--rose-soft); font-weight: 500; }
.quote__stars { color: var(--gold); letter-spacing: 2px; }
.quotes__dots {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 34px;
}
.quotes__dots button {
width: 9px;
height: 9px;
border-radius: 50%;
border: none;
background: rgba(247, 241, 232, 0.3);
cursor: pointer;
padding: 0;
transition: background 0.2s ease, transform 0.2s ease;
}
.quotes__dots button:hover { background: rgba(247, 241, 232, 0.6); }
.quotes__dots button.is-active { background: var(--gold); transform: scale(1.2); }
/* ---------- Visit + Book ---------- */
.visit {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 56px;
align-items: start;
}
.hours {
list-style: none;
margin: 22px 0 26px;
padding: 0;
border-top: 1px solid var(--line);
}
.hours li {
display: flex;
justify-content: space-between;
gap: 14px;
padding: 14px 2px;
border-bottom: 1px solid var(--line);
font-size: 15px;
}
.hours li span:first-child { color: var(--ink-2); font-weight: 500; }
.hours li span:last-child { color: var(--ink); font-variant-numeric: tabular-nums; }
.hours--off span:last-child { color: var(--muted); }
.visit__addr {
font-style: normal;
font-size: 15px;
line-height: 1.8;
color: var(--ink-2);
}
.visit__addr a { color: var(--gold-d); font-weight: 500; }
.visit__addr a:hover { text-decoration: underline; }
.status {
display: inline-flex;
align-items: center;
gap: 9px;
margin-top: 20px;
font-size: 13px;
font-weight: 600;
padding: 8px 16px;
border-radius: 999px;
background: var(--white);
border: 1px solid var(--line);
}
.status::before {
content: "";
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--muted);
}
.status.is-open { color: var(--ok); }
.status.is-open::before { background: var(--ok); box-shadow: 0 0 0 3px rgba(95, 138, 107, 0.18); }
.status.is-closed { color: var(--danger); }
.status.is-closed::before { background: var(--danger); }
/* Booking form */
.book {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 32px 30px;
box-shadow: var(--sh-md);
position: sticky;
top: calc(var(--nav-h) + 16px);
}
.book__title { font-size: 30px; margin-bottom: 6px; }
.book__note { font-size: 13.5px; color: var(--muted); margin-bottom: 22px; }
.field { margin-bottom: 16px; }
.field label {
display: block;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ink-2);
margin-bottom: 7px;
}
.field input,
.field select {
width: 100%;
font-family: var(--sans);
font-size: 15px;
color: var(--ink);
padding: 12px 14px;
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
background: var(--bg);
transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
}
.field input:focus,
.field select:focus {
outline: none;
border-color: var(--gold);
background: var(--white);
box-shadow: 0 0 0 3px rgba(176, 141, 87, 0.16);
}
.field input.is-invalid,
.field select.is-invalid {
border-color: var(--danger);
box-shadow: 0 0 0 3px rgba(179, 80, 62, 0.14);
}
.field--row { display: grid; grid-template-columns: 1fr 0.8fr; gap: 14px; }
.book__summary {
margin: 4px 0 18px;
padding: 16px 18px;
border-radius: var(--r-md);
background: var(--cream);
border: 1px dashed var(--gold-soft);
}
.book__summary-label {
font-size: 11px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--gold-d);
font-weight: 600;
}
.book__summary ul {
list-style: none;
margin: 12px 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.book__summary li {
display: flex;
justify-content: space-between;
gap: 12px;
font-size: 14px;
color: var(--ink-2);
}
.book__summary li button {
border: none;
background: transparent;
color: var(--danger);
cursor: pointer;
font-size: 13px;
padding: 0 2px;
}
.book__summary li button:hover { text-decoration: underline; }
.book__total {
display: flex;
justify-content: space-between;
align-items: baseline;
padding-top: 12px;
border-top: 1px solid var(--gold-soft);
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
}
.book__total strong {
font-family: var(--serif);
font-size: 26px;
color: var(--ink);
letter-spacing: 0;
text-transform: none;
}
.book__fine { font-size: 11.5px; color: var(--muted); text-align: center; margin-top: 12px; }
/* ---------- Footer ---------- */
.foot {
background: var(--ink);
color: var(--rose-soft);
padding: 64px 28px 28px;
}
.foot__inner {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: 1.2fr 2fr;
gap: 48px;
padding-bottom: 44px;
border-bottom: 1px solid rgba(247, 241, 232, 0.12);
}
.foot__brand { display: flex; flex-direction: column; gap: 16px; }
.foot__tag {
font-family: var(--serif);
font-size: 22px;
color: var(--cream);
max-width: 16ch;
}
.foot__cols {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 28px;
}
.foot__cols h4 {
font-family: var(--sans);
font-size: 11px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--rose);
margin-bottom: 16px;
}
.foot__cols a {
display: block;
font-size: 14px;
color: var(--rose-soft);
padding: 5px 0;
transition: color 0.18s ease;
}
.foot__cols a:hover { color: var(--gold); }
.foot__base {
max-width: 1200px;
margin: 24px auto 0;
display: flex;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
font-size: 12.5px;
color: var(--muted);
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 130%);
background: var(--ink);
color: var(--cream);
padding: 13px 22px;
border-radius: 999px;
font-size: 14px;
font-weight: 500;
box-shadow: var(--sh-lg);
z-index: 300;
opacity: 0;
transition: transform 0.4s cubic-bezier(0.2, 0.9, 0.3, 1), opacity 0.3s ease;
max-width: calc(100vw - 40px);
text-align: center;
}
.toast.is-show { transform: translate(-50%, 0); opacity: 1; }
.toast::before { content: "✦ "; color: var(--gold); }
/* ---------- Reveal-on-scroll ---------- */
.reveal {
opacity: 0;
transform: translateY(26px);
transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.reveal.is-in { opacity: 1; transform: none; }
/* ---------- Responsive ---------- */
@media (max-width: 960px) {
.hero__grid { grid-template-columns: 1fr; gap: 32px; min-height: auto; }
.hero__art { order: -1; max-width: 420px; }
.services { grid-template-columns: repeat(2, 1fr); }
.stylists { grid-template-columns: repeat(2, 1fr); }
.lookbook { grid-template-columns: repeat(2, 1fr); }
.visit { grid-template-columns: 1fr; gap: 40px; }
.book { position: static; }
.foot__inner { grid-template-columns: 1fr; gap: 32px; }
}
@media (max-width: 860px) {
.nav__links { display: none; }
.nav__cta { display: none; }
.nav__toggle { display: flex; }
}
@media (max-width: 520px) {
.nav__inner { padding: 0 18px; gap: 14px; }
.brand__name { font-size: 19px; }
.hero { padding: 36px 18px 0; }
.hero__title { margin-bottom: 18px; }
.hero__lead { font-size: 16px; }
.hero__actions { gap: 10px; }
.hero__actions .btn { flex: 1; }
.hero__stats { gap: 26px; }
.hero__stats dd { font-size: 24px; }
.marquee__track { font-size: 18px; }
.section { padding: 64px 18px; }
.section--tint, .section--dark { padding-left: 18px; padding-right: 18px; }
.services { grid-template-columns: 1fr; }
.stylists { grid-template-columns: 1fr; }
.lookbook { grid-template-columns: 1fr 1fr; }
.ba { aspect-ratio: 4 / 5; }
.book { padding: 24px 20px; }
.field--row { grid-template-columns: 1fr; }
.foot__cols { grid-template-columns: 1fr 1fr; }
.foot__base { flex-direction: column; gap: 6px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
scroll-behavior: auto !important;
}
.reveal { opacity: 1; transform: none; }
}
/* Visibility guard: honor the [hidden] attribute over base display */
.mobileNav[hidden],
.book__summary[hidden] {
display: none;
}/* ============================================================
Maison Lumière — landing interactions (vanilla JS)
- mobile nav + sticky shadow
- reveal-on-scroll
- animated stat counter
- service "plan" builder with live total
- before/after slider (pointer + range)
- testimonial carousel
- live open/closed status
- booking form validation + toast
============================================================ */
(function () {
"use strict";
/* ---------- Toast helper ---------- */
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");
}, 2800);
}
/* ---------- Mobile nav ---------- */
var toggle = document.getElementById("navToggle");
var mobileNav = document.getElementById("mobileNav");
function closeMobile() {
if (!toggle || !mobileNav) return;
toggle.setAttribute("aria-expanded", "false");
toggle.setAttribute("aria-label", "Open menu");
mobileNav.hidden = true;
}
if (toggle && mobileNav) {
toggle.addEventListener("click", function () {
var open = toggle.getAttribute("aria-expanded") === "true";
toggle.setAttribute("aria-expanded", String(!open));
toggle.setAttribute("aria-label", open ? "Open menu" : "Close menu");
mobileNav.hidden = open;
});
mobileNav.querySelectorAll("a").forEach(function (a) {
a.addEventListener("click", closeMobile);
});
}
/* ---------- Sticky nav shadow ---------- */
var nav = document.querySelector(".nav");
function onScroll() {
if (nav) nav.classList.toggle("is-scrolled", window.scrollY > 8);
}
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
/* ---------- Reveal on scroll ---------- */
var reveals = document.querySelectorAll(".reveal");
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(
function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) {
e.target.classList.add("is-in");
io.unobserve(e.target);
}
});
},
{ threshold: 0.12, rootMargin: "0px 0px -8% 0px" }
);
reveals.forEach(function (el) { io.observe(el); });
} else {
reveals.forEach(function (el) { el.classList.add("is-in"); });
}
/* ---------- Animated counters ---------- */
function animateCount(el) {
var target = parseInt(el.getAttribute("data-count"), 10) || 0;
var dur = 1400;
var start = performance.now();
function tick(now) {
var p = Math.min((now - start) / dur, 1);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = Math.round(target * eased).toLocaleString("en-US") + (p === 1 ? "+" : "");
if (p < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
}
var counters = document.querySelectorAll("[data-count]");
if ("IntersectionObserver" in window) {
var cio = new IntersectionObserver(
function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) {
animateCount(e.target);
cio.unobserve(e.target);
}
});
},
{ threshold: 0.6 }
);
counters.forEach(function (el) { cio.observe(el); });
} else {
counters.forEach(function (el) {
el.textContent = (parseInt(el.getAttribute("data-count"), 10) || 0).toLocaleString("en-US") + "+";
});
}
/* ---------- Service plan builder ---------- */
var plan = []; // { name, price }
var planSummary = document.getElementById("planSummary");
var planList = document.getElementById("planList");
var planTotal = document.getElementById("planTotal");
function renderPlan() {
if (!planList) return;
if (plan.length === 0) {
if (planSummary) planSummary.hidden = true;
return;
}
if (planSummary) planSummary.hidden = false;
planList.innerHTML = "";
var total = 0;
plan.forEach(function (item, idx) {
total += item.price;
var li = document.createElement("li");
var label = document.createElement("span");
label.textContent = item.name + " · from $" + item.price;
var rm = document.createElement("button");
rm.type = "button";
rm.textContent = "remove";
rm.setAttribute("aria-label", "Remove " + item.name + " from plan");
rm.addEventListener("click", function () {
plan.splice(idx, 1);
// reset matching service button
cards.forEach(function (c) {
if (c.getAttribute("data-name") === item.name) resetCardBtn(c);
});
renderPlan();
});
li.appendChild(label);
li.appendChild(rm);
planList.appendChild(li);
});
if (planTotal) planTotal.textContent = "$" + total.toLocaleString("en-US");
}
function resetCardBtn(card) {
var btn = card.querySelector(".svc__add");
if (btn) {
btn.classList.remove("is-added");
btn.textContent = "Add to plan";
}
}
var cards = Array.prototype.slice.call(document.querySelectorAll(".svc"));
cards.forEach(function (card) {
var btn = card.querySelector(".svc__add");
if (!btn) return;
var name = card.getAttribute("data-name");
var price = parseInt(card.getAttribute("data-price"), 10) || 0;
btn.addEventListener("click", function () {
var existing = plan.findIndex(function (p) { return p.name === name; });
if (existing > -1) {
plan.splice(existing, 1);
resetCardBtn(card);
toast(name + " removed from your plan");
} else {
plan.push({ name: name, price: price });
btn.classList.add("is-added");
btn.textContent = "Added ✓";
toast(name + " added — from $" + price);
// preselect in booking dropdown if it's the only one
var sel = document.getElementById("bService");
if (sel && !sel.value) {
for (var i = 0; i < sel.options.length; i++) {
if (sel.options[i].textContent.replace(/&/g, "&").trim() === name) {
sel.selectedIndex = i;
break;
}
}
}
}
renderPlan();
});
});
/* ---------- Stylist quick-book ---------- */
document.querySelectorAll("[data-stylist]").forEach(function (b) {
b.addEventListener("click", function () {
var who = b.getAttribute("data-stylist");
var sel = document.getElementById("bStylist");
if (sel) {
for (var i = 0; i < sel.options.length; i++) {
if (sel.options[i].text === who) { sel.selectedIndex = i; break; }
}
}
toast("Booking with " + who + " — fill in your details");
var book = document.getElementById("book");
if (book) book.scrollIntoView({ behavior: "smooth", block: "start" });
});
});
/* ---------- Before / After slider ---------- */
var baRange = document.getElementById("baRange");
var baBefore = document.getElementById("baBefore");
var baHandle = document.getElementById("baHandle");
function setBA(val) {
var v = Math.max(0, Math.min(100, val));
if (baBefore) baBefore.style.clipPath = "inset(0 " + (100 - v) + "% 0 0)";
if (baHandle) baHandle.style.left = v + "%";
}
if (baRange) {
baRange.addEventListener("input", function () { setBA(parseFloat(baRange.value)); });
setBA(parseFloat(baRange.value));
}
/* ---------- Testimonials carousel ---------- */
var quotes = document.querySelectorAll("#quotes .quote");
var dots = document.querySelectorAll("#quoteDots button");
var qIndex = 0;
var qTimer;
function showQuote(i) {
qIndex = (i + quotes.length) % quotes.length;
quotes.forEach(function (q, n) { q.classList.toggle("is-active", n === qIndex); });
dots.forEach(function (d, n) {
d.classList.toggle("is-active", n === qIndex);
d.setAttribute("aria-selected", String(n === qIndex));
});
}
function startQuotes() {
clearInterval(qTimer);
qTimer = setInterval(function () { showQuote(qIndex + 1); }, 5500);
}
dots.forEach(function (d, n) {
d.addEventListener("click", function () { showQuote(n); startQuotes(); });
});
if (quotes.length) { showQuote(0); startQuotes(); }
/* ---------- Live open / closed status ---------- */
// Tue–Thu 10–19, Fri 9–20, Sat 9–18, Sun/Mon closed. Day: 0=Sun..6=Sat
var SCHEDULE = {
0: null, 1: null,
2: [10, 19], 3: [10, 19], 4: [10, 19],
5: [9, 20], 6: [9, 18]
};
function updateStatus() {
var el = document.getElementById("openStatus");
if (!el) return;
var now = new Date();
var day = now.getDay();
var hour = now.getHours() + now.getMinutes() / 60;
var hrs = SCHEDULE[day];
el.classList.remove("is-open", "is-closed");
if (hrs && hour >= hrs[0] && hour < hrs[1]) {
el.classList.add("is-open");
el.textContent = "Open now · closes at " + hrs[1] + ":00";
} else {
el.classList.add("is-closed");
// find next opening
for (var add = 0; add < 8; add++) {
var d = (day + add) % 7;
var h = SCHEDULE[d];
if (h && !(add === 0 && hour < h[0] ? false : add === 0)) {
if (add === 0 && hour < h[0]) {
el.textContent = "Closed · opens today at " + h[0] + ":00";
return;
}
if (add > 0) {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
var label = add === 1 ? "tomorrow" : names[d];
el.textContent = "Closed · opens " + label + " at " + h[0] + ":00";
return;
}
}
}
el.textContent = "Closed · see hours above";
}
}
updateStatus();
setInterval(updateStatus, 60000);
/* ---------- Booking form ---------- */
var form = document.getElementById("book");
var dateInput = document.getElementById("bDate");
if (dateInput) {
var today = new Date();
dateInput.min = today.toISOString().split("T")[0];
}
if (form) {
form.addEventListener("submit", function (e) {
e.preventDefault();
var required = form.querySelectorAll("[required]");
var firstBad = null;
required.forEach(function (f) {
var bad = !f.value.trim();
f.classList.toggle("is-invalid", bad);
if (bad && !firstBad) firstBad = f;
});
if (firstBad) {
firstBad.focus();
toast("Please complete the highlighted fields");
return;
}
var name = form.querySelector("#bName").value.trim().split(" ")[0];
var service = form.querySelector("#bService").value.replace(/&/g, "&");
var date = form.querySelector("#bDate").value;
var time = form.querySelector("#bTime").value;
toast("Thank you, " + name + " — your " + service + " request for " + date + " at " + time + " is in. We will text to confirm.");
form.reset();
plan = [];
cards.forEach(resetCardBtn);
renderPlan();
if (dateInput) dateInput.min = new Date().toISOString().split("T")[0];
});
form.querySelectorAll("input, select").forEach(function (f) {
f.addEventListener("input", function () { f.classList.remove("is-invalid"); });
f.addEventListener("change", function () { f.classList.remove("is-invalid"); });
});
}
/* ---------- Footer social stubs ---------- */
document.querySelectorAll("[data-soc]").forEach(function (a) {
a.addEventListener("click", function (e) {
e.preventDefault();
toast("Follow @maisonlumiere — link coming soon");
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Home · 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&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip" href="#book">Skip to booking</a>
<!-- ===== NAV ===== -->
<header class="nav" id="top">
<div class="nav__inner">
<a class="brand" href="#top" aria-label="Maison Lumière, home">
<span class="brand__mark" aria-hidden="true">ML</span>
<span class="brand__name">Maison Lumière</span>
</a>
<nav class="nav__links" aria-label="Primary">
<a href="#services">Services</a>
<a href="#stylists">Stylists</a>
<a href="#gallery">Gallery</a>
<a href="#voices">Voices</a>
<a href="#visit">Visit</a>
</nav>
<div class="nav__cta">
<a class="btn btn--ghost" href="tel:+13105550172">Call</a>
<a class="btn btn--solid" href="#book">Book now</a>
</div>
<button class="nav__toggle" id="navToggle" aria-expanded="false" aria-controls="mobileNav" aria-label="Open menu">
<span></span><span></span><span></span>
</button>
</div>
<div class="mobileNav" id="mobileNav" hidden>
<a href="#services">Services</a>
<a href="#stylists">Stylists</a>
<a href="#gallery">Gallery</a>
<a href="#voices">Voices</a>
<a href="#visit">Visit</a>
<a class="btn btn--solid" href="#book">Book now</a>
</div>
</header>
<main>
<!-- ===== HERO ===== -->
<section class="hero">
<div class="hero__grid">
<div class="hero__copy reveal">
<p class="eyebrow">Maison Lumière · Hair Atelier</p>
<h1 class="hero__title">Hair,<br /><em>elevated.</em></h1>
<p class="hero__lead">
A modern atelier in the Arts District where precision cutting,
dimensional colour and quiet luxury meet. Sit, sip, and let our
artists compose hair that moves the way you do.
</p>
<div class="hero__actions">
<a class="btn btn--solid btn--lg" href="#book">Book your visit</a>
<a class="btn btn--line btn--lg" href="#services">Explore services</a>
</div>
<dl class="hero__stats" aria-label="Salon highlights">
<div><dt>Est.</dt><dd>2014</dd></div>
<div><dt>Artists</dt><dd>9</dd></div>
<div><dt>Reviews</dt><dd data-count="2480">0</dd></div>
<div><dt>Rating</dt><dd>4.9★</dd></div>
</dl>
</div>
<figure class="hero__art reveal" aria-hidden="true">
<div class="hero__frame">
<div class="hero__shine"></div>
<span class="hero__tag">Colour · Cut · Care</span>
</div>
</figure>
</div>
<div class="marquee" aria-hidden="true">
<div class="marquee__track">
<span>Balayage</span><span>·</span><span>Precision Cuts</span><span>·</span>
<span>Gloss Treatments</span><span>·</span><span>Bridal</span><span>·</span>
<span>Keratin</span><span>·</span><span>Colour Correction</span><span>·</span>
<span>Balayage</span><span>·</span><span>Precision Cuts</span><span>·</span>
<span>Gloss Treatments</span><span>·</span><span>Bridal</span><span>·</span>
<span>Keratin</span><span>·</span><span>Colour Correction</span><span>·</span>
</div>
</div>
</section>
<!-- ===== SERVICES ===== -->
<section class="section" id="services">
<header class="section__head reveal">
<p class="eyebrow">The menu</p>
<h2 class="section__title">Signature services</h2>
<p class="section__sub">
Every appointment opens with a consultation and a glass of something warm.
Prices are starting points — your artist will quote precisely in chair.
</p>
</header>
<div class="services" id="serviceGrid">
<article class="svc" data-price="65" data-name="Precision Cut">
<div class="svc__top">
<h3>Precision Cut & Style</h3>
<span class="svc__price">from $65</span>
</div>
<p>Architectural cutting tailored to your texture, finished with a polished blow-dry.</p>
<div class="svc__meta"><span>45–60 min</span><span class="dot"></span><span>All textures</span></div>
<button class="svc__add" type="button">Add to plan</button>
</article>
<article class="svc svc--feature" data-price="180" data-name="Signature Balayage">
<div class="svc__top">
<h3>Signature Balayage</h3>
<span class="svc__price">from $180</span>
</div>
<p>Hand-painted, sun-kissed dimension with a bespoke gloss and bond treatment.</p>
<div class="svc__meta"><span>2.5–3 hr</span><span class="dot"></span><span>Most loved</span></div>
<button class="svc__add" type="button">Add to plan</button>
</article>
<article class="svc" data-price="120" data-name="Glaze & Gloss">
<div class="svc__top">
<h3>Glaze & Gloss</h3>
<span class="svc__price">from $120</span>
</div>
<p>A luminous, ammonia-free refresh that revives tone and mirror-like shine.</p>
<div class="svc__meta"><span>60 min</span><span class="dot"></span><span>Shine boost</span></div>
<button class="svc__add" type="button">Add to plan</button>
</article>
<article class="svc" data-price="95" data-name="Keratin Smoothing">
<div class="svc__top">
<h3>Keratin Smoothing</h3>
<span class="svc__price">from $95</span>
</div>
<p>Frizz tamed, body kept — a weightless smoothing ritual for effortless mornings.</p>
<div class="svc__meta"><span>90 min</span><span class="dot"></span><span>Anti-frizz</span></div>
<button class="svc__add" type="button">Add to plan</button>
</article>
<article class="svc" data-price="240" data-name="Colour Correction">
<div class="svc__top">
<h3>Colour Correction</h3>
<span class="svc__price">from $240</span>
</div>
<p>For the bold reset. A staged, bond-safe journey back to the tone you love.</p>
<div class="svc__meta"><span>3–4 hr</span><span class="dot"></span><span>By consult</span></div>
<button class="svc__add" type="button">Add to plan</button>
</article>
<article class="svc" data-price="150" data-name="Bridal & Events">
<div class="svc__top">
<h3>Bridal & Events</h3>
<span class="svc__price">from $150</span>
</div>
<p>Trial, day-of styling and a calm hand for the moments that matter most.</p>
<div class="svc__meta"><span>By appt</span><span class="dot"></span><span>On-site avail.</span></div>
<button class="svc__add" type="button">Add to plan</button>
</article>
</div>
</section>
<!-- ===== STYLISTS ===== -->
<section class="section section--tint" id="stylists">
<header class="section__head reveal">
<p class="eyebrow">The artists</p>
<h2 class="section__title">Meet your stylists</h2>
<p class="section__sub">Nine hands, one obsession with detail. Book a name or let us pair you.</p>
</header>
<div class="stylists">
<article class="stylist reveal">
<div class="stylist__photo" data-initials="AV" style="--c1:#c9a78f;--c2:#b08d57"></div>
<h3>Aria Vance</h3>
<p class="stylist__role">Creative Director · Colour</p>
<p class="stylist__bio">Balayage purist. Editorial colour and lived-in blondes.</p>
<button class="link-pill" type="button" data-stylist="Aria Vance">Book Aria</button>
</article>
<article class="stylist reveal">
<div class="stylist__photo" data-initials="JR" style="--c1:#3d362f;--c2:#8c6d3f"></div>
<h3>Julian Reyes</h3>
<p class="stylist__role">Senior Stylist · Cutting</p>
<p class="stylist__bio">Razor-precise shapes and textured, low-effort cuts.</p>
<button class="link-pill" type="button" data-stylist="Julian Reyes">Book Julian</button>
</article>
<article class="stylist reveal">
<div class="stylist__photo" data-initials="MK" style="--c1:#efe2cf;--c2:#c9a78f"></div>
<h3>Mira Kovač</h3>
<p class="stylist__role">Colourist · Correction</p>
<p class="stylist__bio">The fixer. Complex colour corrections done bond-safe.</p>
<button class="link-pill" type="button" data-stylist="Mira Kovač">Book Mira</button>
</article>
<article class="stylist reveal">
<div class="stylist__photo" data-initials="TN" style="--c1:#b08d57;--c2:#1c1814"></div>
<h3>Theo Nakamura</h3>
<p class="stylist__role">Stylist · Textured Hair</p>
<p class="stylist__bio">Curl specialist and the calm in your chair.</p>
<button class="link-pill" type="button" data-stylist="Theo Nakamura">Book Theo</button>
</article>
</div>
</section>
<!-- ===== GALLERY / BEFORE-AFTER ===== -->
<section class="section" id="gallery">
<header class="section__head reveal">
<p class="eyebrow">The work</p>
<h2 class="section__title">Before & after</h2>
<p class="section__sub">Drag the handle to reveal the transformation, or browse the lookbook below.</p>
</header>
<div class="ba reveal" id="baCompare">
<div class="ba__pane ba__pane--after" aria-hidden="true"><span class="ba__lbl">After</span></div>
<div class="ba__pane ba__pane--before" id="baBefore" aria-hidden="true"><span class="ba__lbl">Before</span></div>
<input class="ba__range" id="baRange" type="range" min="0" max="100" value="50"
aria-label="Reveal before and after, percentage shown of before image" />
<div class="ba__handle" id="baHandle" aria-hidden="true"><span></span></div>
</div>
<div class="lookbook">
<figure class="look reveal" style="--a:#c9a78f;--b:#b08d57"><figcaption>Lived-in Bronde</figcaption></figure>
<figure class="look reveal" style="--a:#3d362f;--b:#8c6d3f"><figcaption>Espresso Gloss</figcaption></figure>
<figure class="look reveal" style="--a:#efe2cf;--b:#c9a78f"><figcaption>Champagne Babylights</figcaption></figure>
<figure class="look reveal" style="--a:#1c1814;--b:#b08d57"><figcaption>Soft Curtain Bang</figcaption></figure>
</div>
</section>
<!-- ===== TESTIMONIALS ===== -->
<section class="section section--dark" id="voices">
<header class="section__head reveal">
<p class="eyebrow eyebrow--light">Voices</p>
<h2 class="section__title">Loved by our guests</h2>
</header>
<div class="quotes" id="quotes">
<blockquote class="quote is-active">
<p>“Aria gave me the most natural balayage I have ever had. Six months on and it still looks intentional. The whole room feels like a sanctuary.”</p>
<footer><span class="quote__name">Eleanor M.</span><span class="quote__stars">★★★★★</span></footer>
</blockquote>
<blockquote class="quote">
<p>“Julian understood my hair in one consultation. The cut grows out beautifully and styles itself. I will not go anywhere else.”</p>
<footer><span class="quote__name">Daniel R.</span><span class="quote__stars">★★★★★</span></footer>
</blockquote>
<blockquote class="quote">
<p>“Mira rescued a box-dye disaster with such patience. Honest, skilled, and kind. Maison Lumière is the real thing.”</p>
<footer><span class="quote__name">Priya S.</span><span class="quote__stars">★★★★★</span></footer>
</blockquote>
</div>
<div class="quotes__dots" id="quoteDots" role="tablist" aria-label="Testimonials">
<button class="is-active" type="button" role="tab" aria-label="Testimonial 1"></button>
<button type="button" role="tab" aria-label="Testimonial 2"></button>
<button type="button" role="tab" aria-label="Testimonial 3"></button>
</div>
</section>
<!-- ===== VISIT + BOOK ===== -->
<section class="section" id="visit">
<div class="visit">
<div class="visit__info reveal">
<p class="eyebrow">Visit us</p>
<h2 class="section__title">Hours & location</h2>
<ul class="hours" aria-label="Opening hours">
<li><span>Tuesday – Thursday</span><span>10:00 – 19:00</span></li>
<li><span>Friday</span><span>09:00 – 20:00</span></li>
<li><span>Saturday</span><span>09:00 – 18:00</span></li>
<li class="hours--off"><span>Sunday – Monday</span><span>Closed</span></li>
</ul>
<address class="visit__addr">
48 Marlowe Lane, Arts District<br />
Los Angeles, CA 90013<br />
<a href="tel:+13105550172">+1 (310) 555-0172</a> · <a href="mailto:[email protected]">[email protected]</a>
</address>
<p class="status" id="openStatus" aria-live="polite">Checking hours…</p>
</div>
<!-- ===== BOOKING ===== -->
<form class="book" id="book" novalidate>
<h3 class="book__title">Request an appointment</h3>
<p class="book__note">No deposit to request — we confirm by text within the hour.</p>
<div class="field">
<label for="bName">Full name</label>
<input id="bName" name="name" type="text" autocomplete="name" required placeholder="Eleanor Marsh" />
</div>
<div class="field">
<label for="bService">Service</label>
<select id="bService" name="service" required>
<option value="">Choose a service…</option>
<option>Precision Cut & Style</option>
<option>Signature Balayage</option>
<option>Glaze & Gloss</option>
<option>Keratin Smoothing</option>
<option>Colour Correction</option>
<option>Bridal & Events</option>
</select>
</div>
<div class="field">
<label for="bStylist">Preferred stylist</label>
<select id="bStylist" name="stylist">
<option value="">No preference</option>
<option>Aria Vance</option>
<option>Julian Reyes</option>
<option>Mira Kovač</option>
<option>Theo Nakamura</option>
</select>
</div>
<div class="field field--row">
<div>
<label for="bDate">Date</label>
<input id="bDate" name="date" type="date" required />
</div>
<div>
<label for="bTime">Time</label>
<select id="bTime" name="time" required>
<option value="">—</option>
<option>10:00</option><option>11:30</option><option>13:00</option>
<option>14:30</option><option>16:00</option><option>17:30</option>
</select>
</div>
</div>
<div class="book__summary" id="planSummary" hidden>
<span class="book__summary-label">Your plan</span>
<ul id="planList"></ul>
<div class="book__total"><span>Estimated from</span><strong id="planTotal">$0</strong></div>
</div>
<button class="btn btn--solid btn--block" type="submit">Request appointment</button>
<p class="book__fine">By requesting you agree to our 24-hour courtesy cancellation policy.</p>
</form>
</div>
</section>
</main>
<!-- ===== FOOTER ===== -->
<footer class="foot">
<div class="foot__inner">
<div class="foot__brand">
<span class="brand__mark" aria-hidden="true">ML</span>
<p class="foot__tag">Maison Lumière — Hair, elevated.</p>
</div>
<nav class="foot__cols" aria-label="Footer">
<div>
<h4>Atelier</h4>
<a href="#services">Services</a>
<a href="#stylists">Stylists</a>
<a href="#gallery">Gallery</a>
</div>
<div>
<h4>Visit</h4>
<a href="#visit">Hours</a>
<a href="#book">Book</a>
<a href="tel:+13105550172">Call us</a>
</div>
<div>
<h4>Social</h4>
<a href="#" data-soc>Instagram</a>
<a href="#" data-soc>Pinterest</a>
<a href="#" data-soc>Journal</a>
</div>
</nav>
</div>
<div class="foot__base">
<span>© 2026 Maison Lumière Salon · 48 Marlowe Lane, LA</span>
<span>Crafted with care · Fictional studio</span>
</div>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Modern Hair Salon Landing
A premium, editorial landing page for Maison Lumière, a fictional modern hair atelier. The design leans into quiet luxury — a Cormorant Garamond display face over clean Inter UI, generous whitespace, thin gold hairlines and a rose-gold, cream and matte-black palette. A sticky, blurred navigation gives way to a full-height serif hero (“Hair, elevated.”) with an animated shine on the portrait frame, a scrolling service marquee, and an animated review counter.
The page is genuinely interactive. Each signature service can be added to a live plan that tallies an estimated total and pre-fills the booking form; stylist cards offer one-tap quick booking; and a draggable before-and-after slider reveals the transformation alongside a hover-zoom lookbook. Testimonials rotate on an accessible dot-controlled carousel, and a live clock compares against the weekly schedule to show an open or closed status with the next opening time.
Everything is vanilla — no frameworks or build step. Sections reveal on scroll via IntersectionObserver, the mobile nav collapses into an accessible hamburger, and the appointment form validates inline before confirming with an elegant toast. It is fully responsive down to ~360px and respects reduced-motion preferences.