Auto — Luxury Dealership Landing
A cinematic single-page landing for Vanderhall Automobiles, a fictional luxury marque, in charcoal with champagne and chrome accents. A reveal-driven hero presents the flagship Etoile GT with headline specs, a filterable six-model lineup grid, a signature-craft section, a bespoke concierge ownership panel with a live service timeline, an interactive finance estimator with an animated monthly figure, and a validating book-a-test-drive form. Built with semantic HTML, CSS variables and vanilla JavaScript only.
MCP
Code
:root {
--charcoal: #16181c;
--charcoal-2: #1e2127;
--charcoal-3: #2a2e36;
--champagne: #d9c7a3;
--champagne-d: #c2ac80;
--champagne-l: #ece1c9;
--chrome: #c9ced6;
--chrome-d: #9aa1ab;
--ink: #16181c;
--ink-2: #3b4049;
--muted: #737a85;
--bg: #f3f1ec;
--surface: #ffffff;
--line: rgba(20, 21, 24, 0.1);
--line-2: rgba(20, 21, 24, 0.18);
--line-light: rgba(217, 199, 163, 0.18);
--ok: #2f9e6f;
--inprogress: #2b7fff;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 18px;
--shadow-sm: 0 1px 2px rgba(20, 21, 24, 0.06), 0 4px 14px rgba(20, 21, 24, 0.06);
--shadow-md: 0 10px 30px rgba(20, 21, 24, 0.1);
--shadow-lg: 0 24px 60px rgba(20, 21, 24, 0.16);
--ease: cubic-bezier(0.22, 1, 0.36, 1);
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.num {
font-variant-numeric: tabular-nums;
letter-spacing: -0.01em;
}
img {
max-width: 100%;
display: block;
}
a {
color: inherit;
text-decoration: none;
}
.skip-link {
position: absolute;
left: -999px;
top: 0;
z-index: 100;
background: var(--charcoal);
color: var(--champagne);
padding: 10px 16px;
border-radius: 0 0 var(--r-sm) 0;
}
.skip-link:focus {
left: 0;
}
:focus-visible {
outline: 2px solid var(--champagne-d);
outline-offset: 2px;
}
/* ============ BUTTONS ============ */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
font: inherit;
font-weight: 600;
font-size: 14px;
letter-spacing: 0.02em;
padding: 11px 22px;
border-radius: var(--r-sm);
border: 1px solid transparent;
cursor: pointer;
transition: transform 0.18s var(--ease), background 0.18s, color 0.18s,
border-color 0.18s, box-shadow 0.18s;
white-space: nowrap;
}
.btn:active {
transform: translateY(1px);
}
.btn--lg {
padding: 15px 30px;
font-size: 15px;
}
.btn--block {
width: 100%;
}
.btn--gold {
background: linear-gradient(180deg, var(--champagne-l), var(--champagne-d));
color: var(--charcoal);
box-shadow: 0 2px 0 rgba(20, 21, 24, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.5);
}
.btn--gold:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md), inset 0 1px 0 rgba(255, 255, 255, 0.5);
}
.btn--ghost {
background: transparent;
color: var(--charcoal);
border-color: var(--line-2);
}
.nav__cta .btn--ghost,
.section--dark .btn--ghost,
.hero .btn--ghost {
color: var(--champagne-l);
border-color: var(--line-light);
}
.btn--ghost:hover {
border-color: var(--champagne-d);
background: rgba(217, 199, 163, 0.08);
}
/* ============ NAV ============ */
.nav {
position: sticky;
top: 0;
z-index: 50;
background: rgba(22, 24, 28, 0.82);
backdrop-filter: saturate(150%) blur(14px);
-webkit-backdrop-filter: saturate(150%) blur(14px);
border-bottom: 1px solid transparent;
transition: border-color 0.3s, background 0.3s;
}
.nav.is-stuck {
border-bottom-color: var(--line-light);
background: rgba(22, 24, 28, 0.94);
}
.nav__inner {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
height: 72px;
display: flex;
align-items: center;
gap: 28px;
}
.brand {
display: flex;
align-items: baseline;
gap: 8px;
color: var(--champagne-l);
}
.brand__mark {
color: var(--champagne);
font-size: 16px;
transform: translateY(1px);
}
.brand__name {
font-weight: 800;
letter-spacing: 0.22em;
font-size: 15px;
}
.brand__sub {
font-size: 9px;
letter-spacing: 0.34em;
color: var(--chrome-d);
font-weight: 600;
}
.nav__links {
display: flex;
gap: 26px;
margin-left: auto;
}
.nav__links a {
color: var(--chrome);
font-size: 13px;
font-weight: 500;
letter-spacing: 0.04em;
position: relative;
padding: 6px 0;
}
.nav__links a::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
height: 1px;
width: 0;
background: var(--champagne);
transition: width 0.25s var(--ease);
}
.nav__links a:hover {
color: var(--champagne-l);
}
.nav__links a:hover::after {
width: 100%;
}
.nav__cta {
display: flex;
gap: 10px;
}
.nav__cta .btn {
padding: 9px 16px;
}
.nav__toggle {
display: none;
flex-direction: column;
gap: 5px;
width: 42px;
height: 38px;
align-items: center;
justify-content: center;
background: transparent;
border: 1px solid var(--line-light);
border-radius: var(--r-sm);
cursor: pointer;
margin-left: auto;
}
.nav__toggle span {
width: 20px;
height: 2px;
background: var(--champagne-l);
transition: transform 0.25s, opacity 0.2s;
}
.nav__toggle[aria-expanded="true"] span:nth-child(1) {
transform: translateY(7px) rotate(45deg);
}
.nav__toggle[aria-expanded="true"] span:nth-child(2) {
opacity: 0;
}
.nav__toggle[aria-expanded="true"] span:nth-child(3) {
transform: translateY(-7px) rotate(-45deg);
}
.mobile-menu {
background: var(--charcoal-2);
border-top: 1px solid var(--line-light);
padding: 12px 24px 22px;
display: flex;
flex-direction: column;
gap: 4px;
}
.mobile-menu a {
color: var(--chrome);
padding: 12px 4px;
font-size: 15px;
font-weight: 500;
border-bottom: 1px solid var(--line-light);
}
.mobile-menu__cta {
margin-top: 14px;
}
/* ============ HERO ============ */
.hero {
position: relative;
overflow: hidden;
background: var(--charcoal);
color: var(--champagne-l);
isolation: isolate;
}
.hero__media {
position: absolute;
inset: 0;
z-index: -1;
background: radial-gradient(
120% 90% at 78% 8%,
rgba(217, 199, 163, 0.22),
transparent 55%
),
radial-gradient(90% 80% at 12% 100%, rgba(201, 206, 214, 0.12), transparent 60%),
linear-gradient(180deg, #0f1114, #1c1f25 60%, #16181c);
}
.hero__sheen {
position: absolute;
inset: -20% -10% auto auto;
width: 70%;
height: 140%;
background: linear-gradient(115deg, transparent 40%, rgba(217, 199, 163, 0.1) 50%, transparent 60%);
transform: translateX(0);
animation: sheen 9s ease-in-out infinite;
}
@keyframes sheen {
0%, 100% { transform: translateX(8%); opacity: 0.5; }
50% { transform: translateX(-8%); opacity: 1; }
}
.hero__inner {
max-width: 1200px;
margin: 0 auto;
padding: 96px 24px 88px;
}
.eyebrow {
text-transform: uppercase;
letter-spacing: 0.28em;
font-size: 11px;
font-weight: 700;
color: var(--champagne);
margin: 0 0 22px;
}
.eyebrow--dark {
color: var(--champagne-d);
}
.hero__title {
font-size: clamp(38px, 6.4vw, 76px);
line-height: 1.04;
font-weight: 800;
letter-spacing: -0.025em;
margin: 0 0 24px;
max-width: 14ch;
}
.hero__title em {
font-style: italic;
font-weight: 400;
color: var(--champagne);
}
.hero__lead {
font-size: clamp(16px, 2.2vw, 20px);
color: var(--chrome);
max-width: 56ch;
margin: 0 0 34px;
}
.hero__lead strong {
color: var(--champagne-l);
font-weight: 600;
}
.hero__actions {
display: flex;
gap: 14px;
flex-wrap: wrap;
margin-bottom: 56px;
}
.hero__specs {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 0;
margin: 0;
border-top: 1px solid var(--line-light);
max-width: 760px;
}
.hero__specs > div {
padding: 22px 22px 4px 0;
border-right: 1px solid var(--line-light);
}
.hero__specs > div:last-child {
border-right: none;
}
.hero__specs dt {
text-transform: uppercase;
letter-spacing: 0.16em;
font-size: 10px;
color: var(--chrome-d);
font-weight: 600;
margin-bottom: 8px;
}
.hero__specs dd {
margin: 0;
font-size: clamp(26px, 4vw, 38px);
font-weight: 700;
color: var(--champagne-l);
}
.hero__specs dd span {
font-size: 15px;
color: var(--champagne-d);
margin-left: 3px;
font-weight: 600;
}
.hero__scroll {
position: absolute;
left: 50%;
bottom: 22px;
transform: translateX(-50%);
width: 26px;
height: 42px;
border: 1px solid var(--line-light);
border-radius: 14px;
background: transparent;
cursor: pointer;
}
.hero__scroll span {
position: absolute;
left: 50%;
top: 8px;
width: 4px;
height: 8px;
margin-left: -2px;
border-radius: 2px;
background: var(--champagne);
animation: scrolldot 1.8s var(--ease) infinite;
}
@keyframes scrolldot {
0% { transform: translateY(0); opacity: 0; }
40% { opacity: 1; }
100% { transform: translateY(14px); opacity: 0; }
}
/* ============ STRIP ============ */
.strip {
background: var(--charcoal-2);
border-top: 1px solid var(--line-light);
border-bottom: 1px solid var(--line-light);
overflow: hidden;
padding: 14px 0;
}
.strip__track {
display: flex;
gap: 38px;
align-items: center;
white-space: nowrap;
width: max-content;
animation: marquee 26s linear infinite;
}
.strip__track span {
color: var(--chrome-d);
font-size: 12px;
letter-spacing: 0.24em;
font-weight: 600;
}
.strip__track span:nth-child(even) {
color: var(--champagne);
}
@keyframes marquee {
to { transform: translateX(-50%); }
}
/* ============ SECTIONS ============ */
.section {
max-width: 1200px;
margin: 0 auto;
padding: 96px 24px;
}
.section--dark {
max-width: none;
background: var(--charcoal);
color: var(--champagne-l);
}
.section--dark > * {
max-width: 1200px;
margin-left: auto;
margin-right: auto;
}
.section--soft {
max-width: none;
background: linear-gradient(180deg, #efece4, var(--bg));
}
.section--soft > * {
max-width: 1200px;
margin-left: auto;
margin-right: auto;
}
.section__head {
max-width: 64ch;
margin-bottom: 46px;
}
.section__title {
font-size: clamp(26px, 4vw, 42px);
line-height: 1.1;
font-weight: 800;
letter-spacing: -0.02em;
margin: 0 0 16px;
}
.section__sub {
font-size: 17px;
color: var(--muted);
margin: 0;
}
.section__head--light .section__sub {
color: var(--chrome);
}
/* ============ FILTERS ============ */
.filters {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 32px;
}
.chip {
font: inherit;
font-size: 13px;
font-weight: 600;
letter-spacing: 0.02em;
padding: 9px 18px;
border-radius: 999px;
border: 1px solid var(--line-2);
background: var(--surface);
color: var(--ink-2);
cursor: pointer;
transition: all 0.2s var(--ease);
}
.chip:hover {
border-color: var(--champagne-d);
}
.chip.is-active {
background: var(--charcoal);
color: var(--champagne-l);
border-color: var(--charcoal);
}
/* ============ MODELS ============ */
.models {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 22px;
}
.model {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
box-shadow: var(--shadow-sm);
transition: transform 0.3s var(--ease), box-shadow 0.3s, border-color 0.3s;
}
.model:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-lg);
border-color: var(--champagne-d);
}
.model.is-hidden {
display: none;
}
.model__photo {
position: relative;
aspect-ratio: 16 / 10;
background: linear-gradient(135deg, #2a2e36, #1a1d22);
}
.model__photo::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(120deg, transparent 45%, rgba(217, 199, 163, 0.16) 52%, transparent 60%);
}
.model__photo--a { background: linear-gradient(135deg, #3a342a, #16181c); }
.model__photo--b { background: linear-gradient(135deg, #2f3a3a, #16181c); }
.model__photo--c { background: linear-gradient(135deg, #2a3340, #16181c); }
.model__photo--d { background: linear-gradient(135deg, #38302c, #16181c); }
.model__photo--e { background: linear-gradient(135deg, #303a31, #16181c); }
.model__photo--f { background: linear-gradient(135deg, #2c3242, #16181c); }
.model__badge {
position: absolute;
top: 12px;
left: 12px;
z-index: 1;
background: var(--champagne);
color: var(--charcoal);
font-size: 10px;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 5px 10px;
border-radius: 999px;
}
.model__badge--ev {
background: var(--inprogress);
color: #fff;
}
.model__body {
padding: 20px 22px 22px;
}
.model__body h3 {
margin: 0 0 4px;
font-size: 20px;
font-weight: 700;
letter-spacing: -0.01em;
}
.model__line {
margin: 0 0 16px;
color: var(--muted);
font-size: 13px;
}
.model__price {
margin: 0 0 18px;
display: flex;
align-items: baseline;
gap: 6px;
}
.model__price .from {
text-transform: uppercase;
font-size: 10px;
letter-spacing: 0.14em;
color: var(--muted);
font-weight: 600;
}
.model__price .num {
font-size: 22px;
font-weight: 700;
}
.model__cta {
font: inherit;
font-weight: 600;
font-size: 13px;
letter-spacing: 0.04em;
padding: 10px 18px;
border-radius: var(--r-sm);
border: 1px solid var(--line-2);
background: transparent;
color: var(--charcoal);
cursor: pointer;
transition: all 0.2s var(--ease);
}
.model__cta:hover {
background: var(--charcoal);
color: var(--champagne-l);
border-color: var(--charcoal);
}
/* ============ CRAFT ============ */
.craft {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 1px;
background: var(--line-light);
border: 1px solid var(--line-light);
border-radius: var(--r-lg);
overflow: hidden;
}
.craft__card {
background: var(--charcoal);
padding: 30px 26px 34px;
transition: background 0.3s;
}
.craft__card:hover {
background: var(--charcoal-2);
}
.craft__no {
display: block;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.2em;
color: var(--champagne);
margin-bottom: 18px;
}
.craft__card h3 {
margin: 0 0 10px;
font-size: 18px;
font-weight: 700;
color: var(--champagne-l);
}
.craft__card p {
margin: 0;
color: var(--chrome-d);
font-size: 14px;
}
/* ============ OWNERSHIP ============ */
.ownership {
display: grid;
grid-template-columns: 1.1fr 0.9fr;
gap: 54px;
align-items: center;
}
.ticks {
list-style: none;
padding: 0;
margin: 26px 0 30px;
display: grid;
gap: 12px;
}
.ticks li {
position: relative;
padding-left: 30px;
font-size: 15px;
color: var(--ink-2);
}
.ticks li::before {
content: "◇";
position: absolute;
left: 0;
top: 0;
color: var(--champagne-d);
font-size: 13px;
}
.ticks--light li {
color: var(--chrome);
}
.service-card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 24px;
box-shadow: var(--shadow-md);
}
.service-card__top {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 14px;
margin-bottom: 16px;
}
.service-card__label {
margin: 0 0 4px;
text-transform: uppercase;
letter-spacing: 0.16em;
font-size: 10px;
font-weight: 700;
color: var(--muted);
}
.service-card__vin {
margin: 0;
font-size: 13px;
color: var(--ink-2);
font-weight: 600;
}
.status {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.04em;
padding: 6px 12px;
border-radius: 999px;
white-space: nowrap;
}
.status--inprogress {
background: rgba(43, 127, 255, 0.12);
color: var(--inprogress);
}
.service-card__veh {
margin: 0 0 18px;
font-size: 14px;
color: var(--muted);
padding-bottom: 16px;
border-bottom: 1px solid var(--line);
}
.timeline {
list-style: none;
padding: 0;
margin: 0 0 20px;
display: grid;
gap: 14px;
}
.timeline li {
position: relative;
padding-left: 26px;
font-size: 13px;
color: var(--muted);
}
.timeline li span {
position: absolute;
left: 0;
top: 3px;
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid var(--line-2);
background: var(--surface);
}
.timeline li.done {
color: var(--ink-2);
}
.timeline li.done span {
background: var(--ok);
border-color: var(--ok);
}
.timeline li.active {
color: var(--ink);
font-weight: 600;
}
.timeline li.active span {
border-color: var(--inprogress);
box-shadow: 0 0 0 4px rgba(43, 127, 255, 0.16);
}
.service-card__btn {
width: 100%;
font: inherit;
font-weight: 600;
font-size: 13px;
padding: 11px;
border-radius: var(--r-sm);
border: 1px solid var(--line-2);
background: var(--charcoal);
color: var(--champagne-l);
cursor: pointer;
transition: transform 0.18s var(--ease), background 0.2s;
}
.service-card__btn:hover {
background: var(--charcoal-2);
transform: translateY(-2px);
}
/* ============ FINANCE ============ */
.finance {
display: grid;
grid-template-columns: 1.2fr 0.8fr;
gap: 28px;
align-items: start;
}
.calc {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 28px;
box-shadow: var(--shadow-sm);
display: grid;
gap: 26px;
}
.calc__row {
display: grid;
gap: 10px;
}
.calc__row label {
display: flex;
justify-content: space-between;
align-items: baseline;
font-size: 13px;
font-weight: 600;
color: var(--ink-2);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.calc__row label .num {
font-size: 15px;
color: var(--champagne-d);
text-transform: none;
letter-spacing: 0;
}
.calc select {
font: inherit;
font-size: 15px;
padding: 12px 14px;
border-radius: var(--r-sm);
border: 1px solid var(--line-2);
background: var(--bg);
color: var(--ink);
cursor: pointer;
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 6px;
border-radius: 999px;
background: var(--line-2);
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 22px;
height: 22px;
border-radius: 50%;
background: linear-gradient(180deg, var(--champagne-l), var(--champagne-d));
border: 2px solid var(--charcoal);
box-shadow: var(--shadow-sm);
}
input[type="range"]::-moz-range-thumb {
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--champagne-d);
border: 2px solid var(--charcoal);
}
.calc__result {
background: var(--charcoal);
color: var(--champagne-l);
border-radius: var(--r-lg);
padding: 28px;
box-shadow: var(--shadow-md);
position: sticky;
top: 90px;
}
.calc__caption {
margin: 0 0 6px;
text-transform: uppercase;
letter-spacing: 0.16em;
font-size: 11px;
color: var(--chrome-d);
font-weight: 600;
}
.calc__amount {
margin: 0 0 22px;
display: flex;
align-items: baseline;
gap: 2px;
}
.calc__amount .cur {
font-size: 22px;
color: var(--champagne);
font-weight: 600;
}
.calc__amount .num {
font-size: 46px;
font-weight: 800;
letter-spacing: -0.02em;
color: var(--champagne-l);
}
.calc__amount .per {
font-size: 16px;
color: var(--chrome-d);
margin-left: 4px;
}
.calc__break {
margin: 0 0 22px;
display: grid;
gap: 12px;
padding-top: 18px;
border-top: 1px solid var(--line-light);
}
.calc__break > div {
display: flex;
justify-content: space-between;
font-size: 14px;
}
.calc__break dt {
color: var(--chrome-d);
}
.calc__break dd {
margin: 0;
font-weight: 700;
color: var(--champagne-l);
}
.calc__cta {
width: 100%;
}
/* ============ TEST DRIVE ============ */
.testdrive {
display: grid;
grid-template-columns: 1fr 0.95fr;
gap: 54px;
align-items: center;
}
.td-form {
background: var(--charcoal-2);
border: 1px solid var(--line-light);
border-radius: var(--r-lg);
padding: 28px;
display: grid;
gap: 16px;
}
.td-form__row {
display: grid;
gap: 7px;
}
.td-form__grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.td-form label {
font-size: 12px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--chrome);
}
.td-form input,
.td-form select {
font: inherit;
font-size: 15px;
padding: 12px 14px;
border-radius: var(--r-sm);
border: 1px solid var(--line-light);
background: var(--charcoal);
color: var(--champagne-l);
}
.td-form input::placeholder {
color: var(--chrome-d);
}
.td-form input:focus,
.td-form select:focus {
border-color: var(--champagne-d);
outline: none;
}
.td-form input.invalid {
border-color: #d4493e;
}
.err {
margin: 0;
font-size: 12px;
color: #e8857c;
min-height: 0;
}
.td-form__fine {
margin: 0;
text-align: center;
font-size: 12px;
color: var(--chrome-d);
}
/* ============ FOOTER ============ */
.footer {
background: var(--charcoal);
color: var(--chrome);
border-top: 1px solid var(--line-light);
}
.footer__inner {
max-width: 1200px;
margin: 0 auto;
padding: 64px 24px 40px;
display: grid;
grid-template-columns: 1.6fr 1fr 1fr 1fr;
gap: 40px;
}
.footer__brand .brand__name {
display: inline-block;
margin-left: 8px;
}
.footer__note {
margin: 16px 0 0;
font-size: 14px;
color: var(--chrome-d);
max-width: 36ch;
}
.footer__col h4 {
margin: 0 0 16px;
font-size: 12px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--champagne);
}
.footer__col a,
.footer__col p {
display: block;
margin: 0 0 10px;
font-size: 14px;
color: var(--chrome-d);
}
.footer__col a:hover {
color: var(--champagne-l);
}
.footer__bar {
max-width: 1200px;
margin: 0 auto;
padding: 20px 24px 30px;
border-top: 1px solid var(--line-light);
display: flex;
justify-content: space-between;
gap: 14px;
flex-wrap: wrap;
font-size: 12px;
color: var(--chrome-d);
}
.footer__legal a:hover {
color: var(--champagne-l);
}
/* ============ TOAST ============ */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 16px);
background: var(--charcoal);
color: var(--champagne-l);
border: 1px solid var(--champagne-d);
padding: 13px 22px;
border-radius: var(--r-md);
box-shadow: var(--shadow-lg);
font-size: 14px;
font-weight: 500;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s, transform 0.3s var(--ease);
z-index: 90;
max-width: calc(100vw - 32px);
}
.toast.show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ============ REVEAL ============ */
[data-reveal] {
opacity: 0;
transform: translateY(22px);
transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}
[data-reveal].is-in {
opacity: 1;
transform: none;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.001ms !important;
transition-duration: 0.001ms !important;
}
[data-reveal] {
opacity: 1;
transform: none;
}
}
/* ============ RESPONSIVE ============ */
@media (max-width: 960px) {
.models {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.craft {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.ownership,
.finance,
.testdrive {
grid-template-columns: 1fr;
gap: 32px;
}
.calc__result {
position: static;
}
.nav__links,
.nav__cta {
display: none;
}
.nav__toggle {
display: flex;
}
}
@media (max-width: 520px) {
.nav__inner {
padding: 0 16px;
height: 64px;
}
.hero__inner {
padding: 64px 18px 64px;
}
.section {
padding: 64px 18px;
}
.hero__specs {
grid-template-columns: 1fr 1fr;
}
.hero__specs > div:nth-child(2) {
border-right: none;
}
.hero__specs > div {
padding: 18px 16px 4px 0;
}
.models,
.craft {
grid-template-columns: 1fr;
}
.td-form__grid {
grid-template-columns: 1fr;
}
.footer__inner {
grid-template-columns: 1fr 1fr;
gap: 28px;
}
.footer__brand {
grid-column: 1 / -1;
}
.hero__actions .btn {
flex: 1;
}
}/* ============================================================
Vanderhall Automobiles — luxury landing interactions
Vanilla JS only. No external libraries.
============================================================ */
(function () {
"use strict";
/* ---------- toast helper ---------- */
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");
}, 3400);
}
/* ---------- sticky nav state ---------- */
var nav = document.getElementById("nav");
function onScroll() {
if (nav) nav.classList.toggle("is-stuck", window.scrollY > 10);
}
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
/* ---------- mobile menu ---------- */
var toggle = document.getElementById("navToggle");
var menu = document.getElementById("mobileMenu");
function setMenu(open) {
if (!toggle || !menu) return;
toggle.setAttribute("aria-expanded", String(open));
toggle.setAttribute("aria-label", open ? "Close menu" : "Open menu");
menu.hidden = !open;
}
if (toggle && menu) {
toggle.addEventListener("click", function () {
setMenu(toggle.getAttribute("aria-expanded") !== "true");
});
menu.addEventListener("click", function (e) {
if (e.target.closest("a")) setMenu(false);
});
}
/* ---------- hero scroll cue ---------- */
var heroScroll = document.getElementById("heroScroll");
if (heroScroll) {
heroScroll.addEventListener("click", function () {
var t = document.getElementById("models");
if (t) t.scrollIntoView({ behavior: "smooth" });
});
}
/* ---------- scroll reveal ---------- */
var revealEls = Array.prototype.slice.call(document.querySelectorAll("[data-reveal]"));
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry, i) {
if (entry.isIntersecting) {
var el = entry.target;
setTimeout(function () {
el.classList.add("is-in");
}, Math.min(i * 60, 240));
io.unobserve(el);
}
});
},
{ threshold: 0.14, rootMargin: "0px 0px -8% 0px" }
);
revealEls.forEach(function (el) {
io.observe(el);
});
} else {
revealEls.forEach(function (el) {
el.classList.add("is-in");
});
}
/* ---------- model filters ---------- */
var filters = document.getElementById("filters");
var models = Array.prototype.slice.call(document.querySelectorAll(".model"));
if (filters) {
filters.addEventListener("click", function (e) {
var btn = e.target.closest(".chip");
if (!btn) return;
var cat = btn.getAttribute("data-filter");
filters.querySelectorAll(".chip").forEach(function (c) {
var active = c === btn;
c.classList.toggle("is-active", active);
c.setAttribute("aria-selected", String(active));
});
var shown = 0;
models.forEach(function (m) {
var match = cat === "all" || m.getAttribute("data-cat") === cat;
m.classList.toggle("is-hidden", !match);
if (match) shown++;
});
toast(
shown +
(shown === 1 ? " model" : " models") +
(cat === "all" ? " in the collection" : " in this silhouette")
);
});
}
/* ---------- configure buttons ---------- */
document.querySelectorAll(".model__cta").forEach(function (b) {
b.addEventListener("click", function () {
toast("Configurator reserved for the " + b.getAttribute("data-model") + " — a specialist will be in touch.");
});
});
/* ---------- track button ---------- */
var trackBtn = document.getElementById("trackBtn");
if (trackBtn) {
trackBtn.addEventListener("click", function () {
toast("Live service tracking opened in the concierge app.");
});
}
/* ---------- finance calculator ---------- */
var fModel = document.getElementById("fModel");
var fDown = document.getElementById("fDown");
var fTerm = document.getElementById("fTerm");
var fApr = document.getElementById("fApr");
var fDownVal = document.getElementById("fDownVal");
var fTermVal = document.getElementById("fTermVal");
var fAprVal = document.getElementById("fAprVal");
var fMonthly = document.getElementById("fMonthly");
var fFinanced = document.getElementById("fFinanced");
var fTotal = document.getElementById("fTotal");
var fInterest = document.getElementById("fInterest");
function money(n) {
return "$" + Math.round(n).toLocaleString("en-US");
}
function recalc() {
if (!fModel) return;
var price = parseFloat(fModel.value);
var down = Math.min(parseFloat(fDown.value), price);
var months = parseInt(fTerm.value, 10);
var apr = parseInt(fApr.value, 10) / 10; // slider stores tenths of a percent
var principal = Math.max(price - down, 0);
var r = apr / 100 / 12;
var monthly;
if (r === 0) {
monthly = principal / months;
} else {
monthly = (principal * r) / (1 - Math.pow(1 + r, -months));
}
var total = monthly * months;
var interest = total - principal;
fDownVal.textContent = money(down);
fTermVal.textContent = months + " mo";
fAprVal.textContent = apr.toFixed(1) + "%";
fFinanced.textContent = money(principal);
fTotal.textContent = money(total + down);
fInterest.textContent = money(interest);
// animate the monthly figure
animateNumber(fMonthly, Math.round(monthly));
}
var animFrame;
function animateNumber(el, target) {
cancelAnimationFrame(animFrame);
var start = parseInt((el.textContent || "0").replace(/[^0-9]/g, ""), 10) || 0;
var t0 = performance.now();
var dur = 420;
function step(now) {
var p = Math.min((now - t0) / dur, 1);
var eased = 1 - Math.pow(1 - p, 3);
var val = Math.round(start + (target - start) * eased);
el.textContent = val.toLocaleString("en-US");
if (p < 1) animFrame = requestAnimationFrame(step);
}
animFrame = requestAnimationFrame(step);
}
[fModel, fDown, fTerm, fApr].forEach(function (input) {
if (input) input.addEventListener("input", recalc);
});
recalc();
/* ---------- test-drive form validation ---------- */
var tdForm = document.getElementById("tdForm");
function setErr(id, msg) {
var field = document.getElementById(id);
var slot = document.querySelector('[data-err="' + id + '"]');
if (field) field.classList.toggle("invalid", !!msg);
if (slot) slot.textContent = msg || "";
return !msg;
}
if (tdForm) {
// sensible min date = today
var dateInput = document.getElementById("tdDate");
if (dateInput) dateInput.min = new Date().toISOString().split("T")[0];
tdForm.addEventListener("submit", function (e) {
e.preventDefault();
var name = document.getElementById("tdName");
var email = document.getElementById("tdEmail");
var date = document.getElementById("tdDate");
var ok = true;
ok = setErr("tdName", name.value.trim().length >= 2 ? "" : "Please enter your full name.") && ok;
ok =
setErr(
"tdEmail",
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value.trim()) ? "" : "Enter a valid email address."
) && ok;
ok = setErr("tdDate", date.value ? "" : "Choose a preferred date.") && ok;
if (!ok) {
toast("Please complete the highlighted fields.");
return;
}
var model = document.getElementById("tdModelSel").value;
toast("Thank you, " + name.value.trim().split(" ")[0] + " — your " + model + " viewing is requested.");
tdForm.reset();
if (dateInput) dateInput.min = new Date().toISOString().split("T")[0];
});
// clear error on input
["tdName", "tdEmail", "tdDate"].forEach(function (id) {
var el = document.getElementById(id);
if (el) el.addEventListener("input", function () { setErr(id, ""); });
});
}
/* ---------- smooth-scroll for in-page anchors ---------- */
document.querySelectorAll('a[href^="#"]').forEach(function (a) {
a.addEventListener("click", function (e) {
var id = a.getAttribute("href");
if (id.length < 2) return;
var target = document.querySelector(id);
if (target) {
e.preventDefault();
target.scrollIntoView({ behavior: "smooth", block: "start" });
}
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Vanderhall Automobiles — Luxury Dealership</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=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<!-- ============ NAV ============ -->
<header class="nav" id="nav">
<div class="nav__inner">
<a class="brand" href="#top" aria-label="Vanderhall Automobiles, home">
<span class="brand__mark" aria-hidden="true">◇</span>
<span class="brand__name">VANDERHALL</span>
<span class="brand__sub">AUTOMOBILES</span>
</a>
<nav class="nav__links" aria-label="Primary">
<a href="#models">Models</a>
<a href="#features">Craft</a>
<a href="#ownership">Ownership</a>
<a href="#finance">Finance</a>
</nav>
<div class="nav__cta">
<a class="btn btn--ghost" href="#models">Explore lineup</a>
<a class="btn btn--gold" href="#testdrive">Book a test drive</a>
</div>
<button
class="nav__toggle"
id="navToggle"
aria-expanded="false"
aria-controls="mobileMenu"
aria-label="Open menu"
>
<span></span><span></span><span></span>
</button>
</div>
<div class="mobile-menu" id="mobileMenu" hidden>
<a href="#models">Models</a>
<a href="#features">Craft</a>
<a href="#ownership">Ownership</a>
<a href="#finance">Finance</a>
<a class="btn btn--gold mobile-menu__cta" href="#testdrive">Book a test drive</a>
</div>
</header>
<main id="main">
<a id="top"></a>
<!-- ============ HERO ============ -->
<section class="hero" id="hero">
<div class="hero__media" aria-hidden="true">
<div class="hero__sheen"></div>
</div>
<div class="hero__inner">
<p class="eyebrow" data-reveal>The 2026 Marque Collection</p>
<h1 class="hero__title" data-reveal>
Engineered for the<br /><em>few</em> who notice everything.
</h1>
<p class="hero__lead" data-reveal>
Introducing the <strong>Vanderhall Étoile GT</strong> — a hand-finished grand tourer
where 612 horsepower meets a cabin trimmed in champagne nappa and milled chrome.
</p>
<div class="hero__actions" data-reveal>
<a class="btn btn--gold btn--lg" href="#testdrive">Reserve your drive</a>
<a class="btn btn--ghost btn--lg" href="#models">View the lineup</a>
</div>
<dl class="hero__specs" data-reveal>
<div><dt>0–60 mph</dt><dd class="num">2.9<span>s</span></dd></div>
<div><dt>Output</dt><dd class="num">612<span>hp</span></dd></div>
<div><dt>Top speed</dt><dd class="num">208<span>mph</span></dd></div>
<div><dt>Bespoke trims</dt><dd class="num">∞</dd></div>
</dl>
</div>
<button class="hero__scroll" id="heroScroll" aria-label="Scroll to lineup">
<span></span>
</button>
</section>
<!-- ============ MARQUEE STRIP ============ -->
<div class="strip" aria-hidden="true">
<div class="strip__track">
<span>HAND-ASSEMBLED IN GENEVA</span><span>◇</span>
<span>LIFETIME CONCIERGE</span><span>◇</span>
<span>CARBON-CERAMIC AS STANDARD</span><span>◇</span>
<span>BESPOKE ATELIER</span><span>◇</span>
<span>HAND-ASSEMBLED IN GENEVA</span><span>◇</span>
<span>LIFETIME CONCIERGE</span><span>◇</span>
<span>CARBON-CERAMIC AS STANDARD</span><span>◇</span>
<span>BESPOKE ATELIER</span><span>◇</span>
</div>
</div>
<!-- ============ MODELS ============ -->
<section class="section" id="models">
<div class="section__head" data-reveal>
<p class="eyebrow eyebrow--dark">Current lineup</p>
<h2 class="section__title">Six expressions of the marque</h2>
<p class="section__sub">
Filter the collection by silhouette. Every figure is indicative MSRP before bespoke
commissioning.
</p>
</div>
<div class="filters" id="filters" role="tablist" aria-label="Filter models">
<button class="chip is-active" role="tab" aria-selected="true" data-filter="all">
All
</button>
<button class="chip" role="tab" aria-selected="false" data-filter="gt">Grand Tourer</button>
<button class="chip" role="tab" aria-selected="false" data-filter="suv">SUV</button>
<button class="chip" role="tab" aria-selected="false" data-filter="ev">Electric</button>
</div>
<div class="models" id="modelsGrid">
<article class="model" data-cat="gt" data-reveal>
<div class="model__photo model__photo--a"><span class="model__badge">Flagship</span></div>
<div class="model__body">
<h3>Étoile GT</h3>
<p class="model__line">V8 · 612 hp · Grand Tourer</p>
<p class="model__price"><span class="from">from</span> <span class="num">$248,500</span></p>
<button class="model__cta" data-model="Étoile GT">Configure</button>
</div>
</article>
<article class="model" data-cat="suv" data-reveal>
<div class="model__photo model__photo--b"></div>
<div class="model__body">
<h3>Aurelia Estate</h3>
<p class="model__line">V6 Hybrid · 510 hp · Luxury SUV</p>
<p class="model__price"><span class="from">from</span> <span class="num">$176,900</span></p>
<button class="model__cta" data-model="Aurelia Estate">Configure</button>
</div>
</article>
<article class="model" data-cat="ev" data-reveal>
<div class="model__photo model__photo--c"><span class="model__badge model__badge--ev">Electric</span></div>
<div class="model__body">
<h3>Lumière EV</h3>
<p class="model__line">Dual-motor · 740 hp · 412 mi range</p>
<p class="model__price"><span class="from">from</span> <span class="num">$199,000</span></p>
<button class="model__cta" data-model="Lumière EV">Configure</button>
</div>
</article>
<article class="model" data-cat="gt" data-reveal>
<div class="model__photo model__photo--d"></div>
<div class="model__body">
<h3>Sovereign Coupé</h3>
<p class="model__line">V12 · 690 hp · 2+2 Coupé</p>
<p class="model__price"><span class="from">from</span> <span class="num">$312,000</span></p>
<button class="model__cta" data-model="Sovereign Coupé">Configure</button>
</div>
</article>
<article class="model" data-cat="suv" data-reveal>
<div class="model__photo model__photo--e"></div>
<div class="model__body">
<h3>Aurelia Sport</h3>
<p class="model__line">V8 · 600 hp · Performance SUV</p>
<p class="model__price"><span class="from">from</span> <span class="num">$214,400</span></p>
<button class="model__cta" data-model="Aurelia Sport">Configure</button>
</div>
</article>
<article class="model" data-cat="ev" data-reveal>
<div class="model__photo model__photo--f"><span class="model__badge model__badge--ev">Electric</span></div>
<div class="model__body">
<h3>Lumière Roadster</h3>
<p class="model__line">Tri-motor · 920 hp · Open-top EV</p>
<p class="model__price"><span class="from">from</span> <span class="num">$268,750</span></p>
<button class="model__cta" data-model="Lumière Roadster">Configure</button>
</div>
</article>
</div>
</section>
<!-- ============ FEATURES / CRAFT ============ -->
<section class="section section--dark" id="features">
<div class="section__head section__head--light" data-reveal>
<p class="eyebrow">Signature craft</p>
<h2 class="section__title">Detail is not a department. It is the whole idea.</h2>
</div>
<div class="craft">
<article class="craft__card" data-reveal>
<span class="craft__no">01</span>
<h3>The Atelier finish</h3>
<p>
Forty-one hours of hand-laid champagne nappa, double-stitched by a single artisan
who signs the sill plate.
</p>
</article>
<article class="craft__card" data-reveal>
<span class="craft__no">02</span>
<h3>Milled-chrome controls</h3>
<p>
Every switch is machined from solid billet, knurled by hand and damped to a precise
tactile weight.
</p>
</article>
<article class="craft__card" data-reveal>
<span class="craft__no">03</span>
<h3>Silent architecture</h3>
<p>
A triple-laminated cabin holds 31 dB at 80 mph — quieter than a recording studio at
rest.
</p>
</article>
<article class="craft__card" data-reveal>
<span class="craft__no">04</span>
<h3>Carbon-ceramic, standard</h3>
<p>
420 mm front rotors fade-free from 208 mph, finished in your choice of caliper
champagne or graphite.
</p>
</article>
</div>
</section>
<!-- ============ OWNERSHIP ============ -->
<section class="section" id="ownership">
<div class="ownership">
<div class="ownership__copy" data-reveal>
<p class="eyebrow eyebrow--dark">Bespoke ownership</p>
<h2 class="section__title">A concierge for the life of the car</h2>
<p class="section__sub">
Ownership begins, not ends, at delivery. Your dedicated marque specialist arranges
collection, valet servicing and seasonal storage — wherever you are.
</p>
<ul class="ticks">
<li>Door-to-door collection and return for every service</li>
<li>Climate-controlled storage and a 90-point seasonal check</li>
<li>24/7 concierge line answered by a named specialist</li>
<li>Complimentary detailing twice each year, for life</li>
</ul>
<a class="btn btn--gold" href="#testdrive">Meet your specialist</a>
</div>
<aside class="service-card" data-reveal aria-label="Service status example">
<header class="service-card__top">
<div>
<p class="service-card__label">Concierge service</p>
<p class="service-card__vin">VIN · WBA<span class="num">7E83090KZ41</span></p>
</div>
<span class="status status--inprogress">In Progress</span>
</header>
<p class="service-card__veh">2026 Étoile GT · Champagne Métal</p>
<ul class="timeline">
<li class="done"><span></span>Collected from residence · 08:10</li>
<li class="done"><span></span>90-point inspection complete · 09:42</li>
<li class="active"><span></span>Detail & software calibration</li>
<li><span></span>Return delivery · est. 16:30</li>
</ul>
<button class="service-card__btn" id="trackBtn">Track in app</button>
</aside>
</div>
</section>
<!-- ============ FINANCE ============ -->
<section class="section section--soft" id="finance">
<div class="section__head" data-reveal>
<p class="eyebrow eyebrow--dark">Finance & offers</p>
<h2 class="section__title">Acquire on your terms</h2>
<p class="section__sub">
Estimate a monthly figure on the model that moves you. Indicative only — your
specialist confirms a bespoke quote.
</p>
</div>
<div class="finance">
<form class="calc" id="calc" aria-label="Finance estimator" novalidate>
<div class="calc__row">
<label for="fModel">Model</label>
<select id="fModel">
<option value="248500">Étoile GT — $248,500</option>
<option value="176900">Aurelia Estate — $176,900</option>
<option value="199000">Lumière EV — $199,000</option>
<option value="312000">Sovereign Coupé — $312,000</option>
</select>
</div>
<div class="calc__row">
<label for="fDown">Deposit <span id="fDownVal" class="num">$50,000</span></label>
<input type="range" id="fDown" min="0" max="120000" step="5000" value="50000" />
</div>
<div class="calc__row">
<label for="fTerm">Term <span id="fTermVal" class="num">48 mo</span></label>
<input type="range" id="fTerm" min="24" max="72" step="12" value="48" />
</div>
<div class="calc__row">
<label for="fApr">APR <span id="fAprVal" class="num">4.9%</span></label>
<input type="range" id="fApr" min="29" max="99" step="2" value="49" />
</div>
</form>
<aside class="calc__result" aria-live="polite">
<p class="calc__caption">Estimated monthly</p>
<p class="calc__amount"><span class="cur">$</span><span class="num" id="fMonthly">0</span><span class="per">/mo</span></p>
<dl class="calc__break">
<div><dt>Amount financed</dt><dd class="num" id="fFinanced">$0</dd></div>
<div><dt>Total payable</dt><dd class="num" id="fTotal">$0</dd></div>
<div><dt>Total interest</dt><dd class="num" id="fInterest">$0</dd></div>
</dl>
<a class="btn btn--gold calc__cta" href="#testdrive">Request a firm quote</a>
</aside>
</div>
</section>
<!-- ============ TEST DRIVE ============ -->
<section class="section section--dark" id="testdrive">
<div class="testdrive">
<div class="testdrive__copy" data-reveal>
<p class="eyebrow">Book a private viewing</p>
<h2 class="section__title">Drive the car before the conversation</h2>
<p class="section__sub">
We bring the model to you, or host you at the Geneva atelier. A specialist confirms
within one business hour.
</p>
<ul class="ticks ticks--light">
<li>Choice of model and route</li>
<li>No obligation, no sales floor</li>
<li>Concierge collection available</li>
</ul>
</div>
<form class="td-form" id="tdForm" data-reveal novalidate>
<div class="td-form__row">
<label for="tdName">Full name</label>
<input id="tdName" name="name" type="text" autocomplete="name" placeholder="Eleanor Beaumont" required />
<p class="err" data-err="tdName"></p>
</div>
<div class="td-form__row">
<label for="tdEmail">Email</label>
<input id="tdEmail" name="email" type="email" autocomplete="email" placeholder="[email protected]" required />
<p class="err" data-err="tdEmail"></p>
</div>
<div class="td-form__grid">
<div class="td-form__row">
<label for="tdModelSel">Model</label>
<select id="tdModelSel" name="model">
<option>Étoile GT</option>
<option>Aurelia Estate</option>
<option>Lumière EV</option>
<option>Sovereign Coupé</option>
</select>
</div>
<div class="td-form__row">
<label for="tdDate">Preferred date</label>
<input id="tdDate" name="date" type="date" required />
<p class="err" data-err="tdDate"></p>
</div>
</div>
<button class="btn btn--gold btn--block" type="submit">Request my test drive</button>
<p class="td-form__fine">A specialist replies within one business hour.</p>
</form>
</div>
</section>
</main>
<!-- ============ FOOTER ============ -->
<footer class="footer">
<div class="footer__inner">
<div class="footer__brand">
<span class="brand__mark" aria-hidden="true">◇</span>
<span class="brand__name">VANDERHALL</span>
<p class="footer__note">
Hand-built grand tourers, SUVs and electric coachwork since 1974. A fictional marque,
built for demonstration.
</p>
</div>
<nav class="footer__col" aria-label="Models">
<h4>Models</h4>
<a href="#models">Étoile GT</a>
<a href="#models">Aurelia Estate</a>
<a href="#models">Lumière EV</a>
<a href="#models">Sovereign Coupé</a>
</nav>
<nav class="footer__col" aria-label="Ownership">
<h4>Ownership</h4>
<a href="#ownership">Concierge service</a>
<a href="#finance">Finance offers</a>
<a href="#testdrive">Book a test drive</a>
<a href="#features">The Atelier</a>
</nav>
<div class="footer__col">
<h4>Geneva atelier</h4>
<p>14 Quai du Mont-Blanc<br />1201 Genève, Suisse</p>
<p><a href="#top">+41 22 555 0142</a></p>
</div>
</div>
<div class="footer__bar">
<span>© 2026 Vanderhall Automobiles — illustrative demo.</span>
<span class="footer__legal"><a href="#top">Privacy</a> · <a href="#top">Terms</a></span>
</div>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Luxury Dealership Landing
A complete marketing landing page for Vanderhall Automobiles, a fictional luxury marque. It opens with a cinematic, charcoal hero — animated champagne sheen, an oversized reveal headline and a tabular spec strip (0–60, output, top speed) — fronted by the flagship Étoile GT. A blur-backed sticky nav gains a subtle border on scroll and collapses into an animated hamburger menu on small screens.
The lineup section presents six models across grand tourer, SUV and electric silhouettes in a responsive card grid, with chip filters that hide and count matching cars and per-model Configure actions. A dark signature-craft band, a bespoke-ownership panel pairing a concierge narrative with a live service timeline (collected → inspected → in progress → return), and a soft finance section drive the page forward. The finance estimator recomputes a true amortized monthly payment from model, deposit, term and APR sliders, animating the headline figure and breaking out amount financed, total payable and total interest.
The closing book-a-test-drive form validates name, email and a future-dated preferred date inline,
surfaces field-level errors, and confirms via a toast. Throughout, an IntersectionObserver
staggers section reveals, in-page anchors scroll smoothly, and a reusable toast(msg) helper
handles all feedback — all in framework-free vanilla JavaScript.
Illustrative UI only — fictional shop/dealership, not a real service system.