Auto — Vehicle Detail
A dealership vehicle-detail page for a certified 2022 Toyota 4Runner, pairing a gradient photo gallery with a sticky pricing rail. A spec section flips between overview, engine and history tabs, a key-features grid lists equipment, and a live payment estimator recomputes the monthly figure from down payment, term and APR sliders. A sticky CTA bar surfaces on scroll, while test-drive and trade-in modals validate input and return a fictional trade valuation.
MCP
Code
:root {
--garage: #141518;
--garage-2: #1f2127;
--steel: #5b6470;
--steel-l: #8a929d;
--orange: #ff6a13;
--orange-d: #e2540a;
--orange-50: #fff0e6;
--ink: #16181c;
--ink-2: #3b4049;
--muted: #737a85;
--bg: #f3f4f6;
--surface: #ffffff;
--line: rgba(20, 21, 24, 0.1);
--line-2: rgba(20, 21, 24, 0.18);
--ok: #2f9e6f;
--warn: #e0962a;
--danger: #d4493e;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 18px;
--shadow: 0 1px 2px rgba(20, 21, 24, 0.06), 0 8px 24px rgba(20, 21, 24, 0.06);
--shadow-lg: 0 12px 40px rgba(20, 21, 24, 0.16);
}
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding-bottom: 92px;
}
.num { font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
a { color: inherit; }
/* ---------- Top bar ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 30;
display: flex;
align-items: center;
gap: 18px;
padding: 12px 22px;
background: var(--garage);
color: #fff;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.brand { display: flex; align-items: center; gap: 11px; }
.brand-mark {
display: grid;
place-items: center;
width: 40px;
height: 40px;
border-radius: 10px;
background: var(--orange);
color: #fff;
font-weight: 800;
font-size: 14px;
letter-spacing: 0.04em;
}
.brand-text { display: flex; flex-direction: column; line-height: 1.25; }
.brand-text strong { font-size: 15px; font-weight: 700; }
.brand-text span { font-size: 11.5px; color: var(--steel-l); }
.topnav { display: flex; gap: 4px; margin-left: auto; }
.topnav a {
text-decoration: none;
font-size: 13px;
font-weight: 500;
color: var(--steel-l);
padding: 7px 12px;
border-radius: var(--r-sm);
transition: color 0.15s, background 0.15s;
}
.topnav a:hover { color: #fff; background: rgba(255, 255, 255, 0.07); }
.btn-call .dot {
width: 7px; height: 7px; border-radius: 50%;
background: var(--ok); display: inline-block;
box-shadow: 0 0 0 3px rgba(47, 158, 111, 0.25);
}
/* ---------- Buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
font-family: inherit;
font-size: 14px;
font-weight: 600;
border: 1px solid transparent;
border-radius: var(--r-sm);
padding: 11px 16px;
cursor: pointer;
transition: transform 0.08s, background 0.15s, border-color 0.15s, box-shadow 0.15s;
white-space: nowrap;
}
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.btn-sm { padding: 9px 14px; font-size: 13px; }
.block { width: 100%; }
.btn-primary { background: var(--orange); color: #fff; box-shadow: 0 4px 12px rgba(255, 106, 19, 0.3); }
.btn-primary:hover { background: var(--orange-d); }
.btn-outline { background: transparent; color: var(--ink); border-color: var(--line-2); }
.btn-outline:hover { border-color: var(--ink-2); background: rgba(20, 21, 24, 0.03); }
.btn-ghost { background: rgba(255, 255, 255, 0.06); color: #fff; border-color: rgba(255, 255, 255, 0.12); font-size: 13px; }
.btn-ghost:hover { background: rgba(255, 255, 255, 0.12); }
.dealer .btn-ghost { background: rgba(20, 21, 24, 0.04); color: var(--ink); border-color: var(--line); flex: 1; }
.dealer .btn-ghost:hover { background: rgba(20, 21, 24, 0.07); }
/* ---------- Layout ---------- */
.wrap { max-width: 1140px; margin: 0 auto; padding: 0 22px; }
.crumbs {
display: flex; gap: 8px; align-items: center;
font-size: 12.5px; color: var(--muted);
padding: 16px 2px 4px;
}
.crumbs a { text-decoration: none; }
.crumbs a:hover { color: var(--ink); }
.crumbs span[aria-current] { color: var(--ink); font-weight: 600; }
.layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 360px;
gap: 24px;
padding: 12px 0 40px;
align-items: start;
}
.col-rail { position: sticky; top: 78px; display: flex; flex-direction: column; gap: 16px; }
/* ---------- Gallery ---------- */
.gallery { margin-bottom: 18px; }
.stage {
position: relative;
aspect-ratio: 16 / 10;
border-radius: var(--r-lg);
overflow: hidden;
background:
radial-gradient(120% 80% at 20% 0%, rgba(255, 255, 255, 0.14), transparent 60%),
linear-gradient(135deg, var(--garage-2), var(--garage));
box-shadow: var(--shadow);
transition: background 0.35s ease;
}
.stage::after {
content: "";
position: absolute; inset: 0;
background: radial-gradient(80% 60% at 70% 100%, rgba(255, 106, 19, 0.14), transparent 70%);
pointer-events: none;
}
.stage-badge {
position: absolute; top: 14px; left: 14px; z-index: 2;
background: var(--ok); color: #fff;
font-size: 11px; font-weight: 700; letter-spacing: 0.03em;
padding: 5px 10px; border-radius: 999px; text-transform: uppercase;
}
.stage-count {
position: absolute; bottom: 14px; right: 14px; z-index: 2;
background: rgba(20, 21, 24, 0.6); color: #fff;
font-size: 12px; font-weight: 600; padding: 5px 11px;
border-radius: 999px; backdrop-filter: blur(4px);
}
.stage-label {
position: absolute; bottom: 14px; left: 14px; z-index: 2;
color: #fff; font-size: 13px; font-weight: 600;
text-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}
.stage-nav {
position: absolute; top: 50%; transform: translateY(-50%);
z-index: 2; width: 40px; height: 40px;
display: grid; place-items: center;
border: none; border-radius: 50%;
background: rgba(255, 255, 255, 0.9); color: var(--ink);
font-size: 24px; line-height: 1; cursor: pointer;
box-shadow: var(--shadow); transition: background 0.15s, transform 0.08s;
}
.stage-nav:hover { background: #fff; }
.stage-nav:active { transform: translateY(-50%) scale(0.94); }
.stage-nav.prev { left: 14px; }
.stage-nav.next { right: 14px; }
.stage-nav:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.thumbs {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 10px;
margin-top: 10px;
}
.thumb {
position: relative;
aspect-ratio: 4 / 3;
border-radius: var(--r-sm);
border: 2px solid transparent;
cursor: pointer;
overflow: hidden;
padding: 0;
transition: border-color 0.15s, transform 0.08s;
}
.thumb::after {
content: ""; position: absolute; inset: 0;
background: linear-gradient(0deg, rgba(20, 21, 24, 0.35), transparent 60%);
}
.thumb span {
position: absolute; bottom: 3px; left: 5px; z-index: 1;
color: #fff; font-size: 9.5px; font-weight: 600;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
.thumb:hover { transform: translateY(-1px); }
.thumb.is-active { border-color: var(--orange); }
.thumb:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
/* ---------- Title ---------- */
.title-block {
display: flex; justify-content: space-between; align-items: flex-start;
gap: 14px; margin: 6px 0 14px;
}
.title-block h1 { margin: 0; font-size: 26px; font-weight: 800; letter-spacing: -0.02em; }
.title-block h1 .trim { color: var(--orange); font-weight: 700; }
.subtitle { margin: 4px 0 0; color: var(--muted); font-size: 14px; }
.like {
flex: none; width: 44px; height: 44px;
display: grid; place-items: center;
border: 1px solid var(--line-2); border-radius: var(--r-md);
background: var(--surface); cursor: pointer;
transition: border-color 0.15s, background 0.15s, transform 0.08s;
}
.like svg { width: 22px; height: 22px; fill: none; stroke: var(--steel); stroke-width: 1.8; transition: fill 0.15s, stroke 0.15s; }
.like:hover { border-color: var(--ink-2); }
.like:active { transform: scale(0.93); }
.like[aria-pressed="true"] svg { fill: var(--danger); stroke: var(--danger); }
.like:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
/* ---------- Quick facts ---------- */
.facts {
list-style: none; margin: 0 0 20px; padding: 0;
display: grid; grid-template-columns: repeat(6, 1fr);
gap: 1px; background: var(--line);
border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden;
}
.facts li {
background: var(--surface);
display: flex; flex-direction: column; gap: 2px;
padding: 12px 13px;
}
.f-k { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.f-v { font-size: 15px; font-weight: 700; }
/* ---------- Panels ---------- */
.panel {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow);
padding: 6px 20px 20px;
margin-bottom: 18px;
}
.panel-h { font-size: 16px; font-weight: 700; margin: 16px 0 12px; }
.tabs {
display: flex; gap: 4px;
border-bottom: 1px solid var(--line);
margin: 0 -20px 16px;
padding: 6px 20px 0;
}
.tab {
font-family: inherit; font-size: 14px; font-weight: 600;
color: var(--muted); background: none; border: none;
padding: 10px 4px; margin-right: 16px; cursor: pointer;
border-bottom: 2px solid transparent; transition: color 0.15s, border-color 0.15s;
}
.tab:hover { color: var(--ink); }
.tab.is-active { color: var(--orange); border-bottom-color: var(--orange); }
.tab:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; border-radius: 4px; }
.tabpane { display: none; animation: fade 0.25s ease; }
.tabpane.is-active { display: block; }
@keyframes fade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.spec-grid {
margin: 0; display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1px; background: var(--line);
border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden;
}
.spec-grid > div { background: var(--surface); padding: 12px 14px; }
.spec-grid dt { font-size: 11.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.03em; }
.spec-grid dd { margin: 2px 0 0; font-size: 14.5px; font-weight: 600; }
.history { list-style: none; margin: 0; padding: 0; display: grid; gap: 11px; }
.history li { display: flex; align-items: center; gap: 10px; font-size: 14px; }
.ok-dot, .warn-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.ok-dot { background: var(--ok); box-shadow: 0 0 0 3px rgba(47, 158, 111, 0.18); }
.warn-dot { background: var(--warn); box-shadow: 0 0 0 3px rgba(224, 150, 42, 0.18); }
.features {
list-style: none; margin: 0; padding: 0;
display: grid; grid-template-columns: repeat(3, 1fr); gap: 9px 14px;
}
.features li {
position: relative; padding-left: 22px; font-size: 13.5px; color: var(--ink-2);
}
.features li::before {
content: ""; position: absolute; left: 0; top: 5px;
width: 13px; height: 13px; border-radius: 4px;
background: var(--orange-50);
box-shadow: inset 0 0 0 1.5px var(--orange);
}
.features li::after {
content: ""; position: absolute; left: 4px; top: 8px;
width: 4px; height: 7px;
border-right: 2px solid var(--orange); border-bottom: 2px solid var(--orange);
transform: rotate(40deg);
}
/* ---------- Price card ---------- */
.price-card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow);
padding: 18px;
}
.price-flag {
display: inline-block;
font-size: 11px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase;
color: var(--ok); background: rgba(47, 158, 111, 0.12);
padding: 4px 9px; border-radius: 999px; margin-bottom: 12px;
}
.price-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 10px; }
.price-k { font-size: 12px; color: var(--muted); display: block; }
.price { font-size: 30px; font-weight: 800; line-height: 1.1; letter-spacing: -0.02em; }
.price-was { color: var(--muted); text-decoration: line-through; font-size: 15px; font-weight: 600; }
.finance-from {
margin: 8px 0 14px; font-size: 13px; color: var(--ink-2);
padding-top: 12px; border-top: 1px dashed var(--line-2);
}
.finance-from strong { color: var(--orange); }
/* ---------- Calculator ---------- */
.calc {
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 14px;
margin-bottom: 16px;
}
.calc-head {
display: flex; justify-content: space-between; align-items: center;
font-size: 12.5px; font-weight: 700; text-transform: uppercase;
letter-spacing: 0.03em; color: var(--ink-2); margin-bottom: 12px;
}
.calc-out { color: var(--orange); font-size: 15px; }
.field { display: block; margin-bottom: 12px; }
.field > span {
display: flex; justify-content: space-between;
font-size: 12.5px; font-weight: 600; color: var(--ink-2); margin-bottom: 6px;
}
.field > span em { font-style: normal; color: var(--orange); font-weight: 700; }
.field input[type="range"] {
-webkit-appearance: none; appearance: none; width: 100%; height: 5px;
background: var(--line-2); border-radius: 999px; cursor: pointer; outline: none;
}
.field input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 18px; height: 18px; border-radius: 50%;
background: var(--orange); border: 3px solid #fff;
box-shadow: 0 1px 4px rgba(20, 21, 24, 0.3); cursor: pointer;
}
.field input[type="range"]::-moz-range-thumb {
width: 18px; height: 18px; border-radius: 50%;
background: var(--orange); border: 3px solid #fff;
box-shadow: 0 1px 4px rgba(20, 21, 24, 0.3); cursor: pointer;
}
.field input[type="range"]:focus-visible { outline: 2px solid var(--orange); outline-offset: 4px; }
.field input[type="text"], .field input[type="tel"], .field input[type="date"],
.field input[type="number"], .field select {
width: 100%; font-family: inherit; font-size: 14px;
padding: 10px 11px; border: 1px solid var(--line-2);
border-radius: var(--r-sm); background: var(--surface); color: var(--ink);
}
.field input:focus, .field select:focus { outline: none; border-color: var(--orange); box-shadow: 0 0 0 3px rgba(255, 106, 19, 0.15); }
.calc-note { margin: 4px 0 0; font-size: 12px; color: var(--muted); }
.cta-stack { display: grid; gap: 9px; }
/* ---------- Dealer ---------- */
.dealer {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow);
padding: 16px 18px;
}
.dealer-head { display: flex; align-items: center; gap: 11px; }
.avatar {
width: 42px; height: 42px; flex: none; border-radius: 50%;
display: grid; place-items: center; color: #fff; font-weight: 700; font-size: 14px;
background: linear-gradient(135deg, var(--steel), var(--garage-2));
}
.dealer-head strong { display: block; font-size: 14.5px; }
.muted { color: var(--muted); font-size: 12.5px; }
.dealer-loc { font-size: 12.5px; color: var(--ink-2); margin: 12px 0; }
.dealer-actions { display: flex; gap: 8px; }
.reassure {
list-style: none; margin: 14px 0 0; padding: 12px 0 0;
border-top: 1px solid var(--line); display: grid; gap: 8px;
}
.reassure li { position: relative; padding-left: 20px; font-size: 12.5px; color: var(--ink-2); }
.reassure li::before {
content: "✓"; position: absolute; left: 0; top: -1px;
color: var(--ok); font-weight: 800;
}
/* ---------- Sticky bar ---------- */
.sticky-bar {
position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
display: flex; align-items: center; justify-content: space-between; gap: 14px;
padding: 12px 22px;
background: rgba(20, 21, 24, 0.97);
color: #fff; backdrop-filter: blur(8px);
border-top: 1px solid rgba(255, 255, 255, 0.08);
transform: translateY(110%);
transition: transform 0.28s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.sticky-bar.show { transform: translateY(0); }
.sticky-info strong { display: block; font-size: 14px; }
.sticky-info span { font-size: 12.5px; color: var(--steel-l); }
.sticky-cta { display: flex; gap: 9px; }
/* ---------- Modal ---------- */
.modal { position: fixed; inset: 0; z-index: 60; display: grid; place-items: center; padding: 18px; }
.modal[hidden] { display: none; }
.modal-backdrop { position: absolute; inset: 0; background: rgba(20, 21, 24, 0.55); backdrop-filter: blur(2px); animation: fade 0.2s ease; }
.modal-card {
position: relative; width: 100%; max-width: 440px;
background: var(--surface); border-radius: var(--r-lg);
box-shadow: var(--shadow-lg); padding: 24px;
animation: pop 0.24s cubic-bezier(0.2, 0.9, 0.3, 1.2);
max-height: 90vh; overflow-y: auto;
}
@keyframes pop { from { opacity: 0; transform: translateY(12px) scale(0.97); } to { opacity: 1; transform: none; } }
.modal-card h2 { margin: 0; font-size: 19px; font-weight: 800; letter-spacing: -0.01em; }
.modal-sub { margin: 5px 0 18px; font-size: 13px; color: var(--muted); }
.modal-x {
position: absolute; top: 14px; right: 14px;
width: 32px; height: 32px; border: none; border-radius: var(--r-sm);
background: var(--bg); color: var(--ink-2); font-size: 22px; line-height: 1;
cursor: pointer; transition: background 0.15s;
}
.modal-x:hover { background: var(--line); }
.modal-form .field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.form-err {
margin: 0 0 12px; font-size: 13px; font-weight: 600; color: var(--danger);
background: rgba(212, 73, 62, 0.1); padding: 9px 12px; border-radius: var(--r-sm);
}
.trade-out {
display: flex; align-items: center; justify-content: space-between;
background: var(--orange-50); border: 1px solid rgba(255, 106, 19, 0.3);
border-radius: var(--r-md); padding: 13px 15px; margin-bottom: 14px;
}
.trade-out span { font-size: 13px; font-weight: 600; color: var(--ink-2); }
.trade-out strong { font-size: 22px; font-weight: 800; color: var(--orange-d); }
/* ---------- Toast ---------- */
.toast {
position: fixed; left: 50%; bottom: 110px; z-index: 80;
transform: translate(-50%, 20px);
background: var(--garage); color: #fff;
font-size: 13.5px; font-weight: 500;
padding: 12px 18px; border-radius: var(--r-md);
box-shadow: var(--shadow-lg);
opacity: 0; pointer-events: none;
transition: opacity 0.2s, transform 0.2s;
max-width: 90vw;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
/* ---------- Responsive ---------- */
@media (max-width: 920px) {
.layout { grid-template-columns: 1fr; }
.col-rail { position: static; }
.features { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
.topnav { display: none; }
.topbar { padding: 10px 16px; gap: 12px; }
.btn-call { margin-left: auto; }
.wrap { padding: 0 16px; }
.title-block h1 { font-size: 21px; }
.facts { grid-template-columns: repeat(2, 1fr); }
.thumbs { grid-template-columns: repeat(3, 1fr); }
.spec-grid { grid-template-columns: 1fr; }
.features { grid-template-columns: 1fr; }
.modal-form .field-row { grid-template-columns: 1fr; }
.sticky-info strong { font-size: 13px; }
.sticky-cta .btn { padding: 9px 12px; }
.price { font-size: 26px; }
}(function () {
"use strict";
var PRICE = 42985;
/* ---------- 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");
}, 2600);
}
function money(n) {
return "$" + Math.round(n).toLocaleString("en-US");
}
/* ---------- Gallery ---------- */
var SHOTS = [
{ label: "Front 3/4", shade: 0, g: "linear-gradient(135deg,#1f2127,#141518)" },
{ label: "Rear 3/4", shade: 1, g: "linear-gradient(135deg,#2a2d34,#16181c)" },
{ label: "Cockpit", shade: 2, g: "linear-gradient(135deg,#3b4049,#1f2127)" },
{ label: "Dashboard", shade: 3, g: "linear-gradient(135deg,#5b6470,#2a2d34)" },
{ label: "2nd Row", shade: 4, g: "linear-gradient(135deg,#2a2d34,#1f2127)" },
{ label: "Cargo", shade: 5, g: "linear-gradient(135deg,#1f2127,#0f1013)" }
];
var stage = document.getElementById("stage");
var stageCount = document.getElementById("stageCount");
var stageLabel = document.getElementById("stageLabel");
var thumbsWrap = document.getElementById("thumbs");
var current = 0;
function paintStage(i) {
var s = SHOTS[i];
stage.style.background =
"radial-gradient(120% 80% at 20% 0%, rgba(255,255,255,0.14), transparent 60%)," +
s.g;
stage.setAttribute("aria-label", "Vehicle photo, " + s.label);
stageCount.textContent = i + 1 + " / " + SHOTS.length;
stageLabel.textContent = s.label;
var ts = thumbsWrap.querySelectorAll(".thumb");
for (var k = 0; k < ts.length; k++) {
ts[k].classList.toggle("is-active", k === i);
ts[k].setAttribute("aria-selected", k === i ? "true" : "false");
}
current = i;
}
SHOTS.forEach(function (s, i) {
var b = document.createElement("button");
b.type = "button";
b.className = "thumb" + (i === 0 ? " is-active" : "");
b.setAttribute("role", "tab");
b.setAttribute("aria-selected", i === 0 ? "true" : "false");
b.setAttribute("aria-label", s.label);
b.style.background = s.g;
var lbl = document.createElement("span");
lbl.textContent = s.label;
b.appendChild(lbl);
b.addEventListener("click", function () { paintStage(i); });
thumbsWrap.appendChild(b);
});
function step(dir) {
paintStage((current + dir + SHOTS.length) % SHOTS.length);
}
document.getElementById("prevShot").addEventListener("click", function () { step(-1); });
document.getElementById("nextShot").addEventListener("click", function () { step(1); });
document.addEventListener("keydown", function (e) {
if (document.querySelector(".modal:not([hidden])")) return;
if (e.key === "ArrowLeft") step(-1);
if (e.key === "ArrowRight") step(1);
});
/* ---------- Spec tabs ---------- */
var tabs = document.querySelectorAll(".tab");
tabs.forEach(function (tab) {
tab.addEventListener("click", function () {
var name = tab.getAttribute("data-tab");
tabs.forEach(function (t) {
var on = t === tab;
t.classList.toggle("is-active", on);
t.setAttribute("aria-selected", on ? "true" : "false");
});
document.querySelectorAll(".tabpane").forEach(function (p) {
var on = p.id === "pane-" + name;
p.classList.toggle("is-active", on);
p.hidden = !on;
});
});
});
/* ---------- Like ---------- */
var likeBtn = document.getElementById("likeBtn");
likeBtn.addEventListener("click", function () {
var on = likeBtn.getAttribute("aria-pressed") === "true";
likeBtn.setAttribute("aria-pressed", on ? "false" : "true");
toast(on ? "Removed from saved vehicles" : "Saved to your garage");
});
/* ---------- Finance calculator ---------- */
var down = document.getElementById("down");
var term = document.getElementById("term");
var apr = document.getElementById("apr");
var downOut = document.getElementById("downOut");
var aprOut = document.getElementById("aprOut");
var calcMonthly = document.getElementById("calcMonthly");
var calcAmount = document.getElementById("calcAmount");
var finFrom = document.getElementById("finFrom");
var stickyPrice = document.getElementById("stickyPrice");
function monthly(principal, ratePct, months) {
if (principal <= 0) return 0;
var r = ratePct / 100 / 12;
if (r === 0) return principal / months;
return (principal * r) / (1 - Math.pow(1 + r, -months));
}
function recalc() {
var d = parseFloat(down.value);
var t = parseInt(term.value, 10);
var a = parseFloat(apr.value);
var financed = Math.max(0, PRICE - d);
var pay = monthly(financed, a, t);
downOut.textContent = money(d);
aprOut.textContent = a.toFixed(1) + "%";
calcMonthly.textContent = money(pay) + "/mo";
calcAmount.textContent = "Financing " + money(financed) + " over " + t + " months";
finFrom.textContent = money(pay);
var fromLine = document.querySelector(".finance-from");
fromLine.innerHTML =
"Est. <strong class=\"num\">" + money(pay) + "</strong>/mo · " + t + " mo @ " + a.toFixed(1) + "% APR";
stickyPrice.textContent = money(PRICE) + " · est. " + money(pay) + "/mo";
}
[down, term, apr].forEach(function (el) {
el.addEventListener("input", recalc);
el.addEventListener("change", recalc);
});
recalc();
/* ---------- Sticky CTA bar ---------- */
var stickyBar = document.getElementById("stickyBar");
var priceCard = document.querySelector(".price-card");
var sentinel = priceCard;
window.addEventListener(
"scroll",
function () {
var rect = sentinel.getBoundingClientRect();
var show = rect.bottom < 80 || rect.top > window.innerHeight;
stickyBar.classList.toggle("show", show);
stickyBar.setAttribute("aria-hidden", show ? "false" : "true");
},
{ passive: true }
);
/* ---------- Generic toast buttons ---------- */
document.querySelectorAll("[data-toast]").forEach(function (b) {
b.addEventListener("click", function () { toast(b.getAttribute("data-toast")); });
});
/* ---------- Modals ---------- */
var driveModal = document.getElementById("driveModal");
var tradeModal = document.getElementById("tradeModal");
var lastFocus = null;
function openModal(m) {
lastFocus = document.activeElement;
m.hidden = false;
var first = m.querySelector("input, select, button");
if (first) setTimeout(function () { first.focus(); }, 30);
}
function closeModal(m) {
m.hidden = true;
if (lastFocus && lastFocus.focus) lastFocus.focus();
}
document.getElementById("openDrive").addEventListener("click", function () { openModal(driveModal); });
document.getElementById("openTrade").addEventListener("click", function () { openModal(tradeModal); });
document.querySelectorAll("[data-open]").forEach(function (b) {
b.addEventListener("click", function () {
openModal(b.getAttribute("data-open") === "drive" ? driveModal : tradeModal);
});
});
document.querySelectorAll("[data-close]").forEach(function (el) {
el.addEventListener("click", function () { closeModal(el.closest(".modal")); });
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") {
var open = document.querySelector(".modal:not([hidden])");
if (open) closeModal(open);
}
});
/* ---------- Test drive form ---------- */
var driveForm = document.getElementById("driveForm");
var driveErr = document.getElementById("driveErr");
driveForm.addEventListener("submit", function (e) {
e.preventDefault();
var name = driveForm.name.value.trim();
var phone = driveForm.phone.value.trim();
var date = driveForm.date.value;
if (!name || !phone || !date) {
driveErr.textContent = "Please add your name, phone and a preferred date.";
driveErr.hidden = false;
return;
}
driveErr.hidden = true;
closeModal(driveModal);
toast("Test drive requested for " + name + " — we'll confirm shortly");
driveForm.reset();
});
/* ---------- Trade-in form ---------- */
var tradeForm = document.getElementById("tradeForm");
var tradeOut = document.getElementById("tradeOut");
var tradeVal = document.getElementById("tradeVal");
tradeForm.addEventListener("submit", function (e) {
e.preventDefault();
var year = parseInt(tradeForm.year.value, 10);
var miles = parseInt(tradeForm.miles.value, 10);
var cond = parseFloat(tradeForm.cond.value);
if (!year || isNaN(miles) || !tradeForm.model.value.trim()) {
toast("Fill in year, mileage and model for an estimate");
return;
}
// Fictional valuation model.
var base = 28000;
var ageHit = Math.max(0, 2026 - year) * 1450;
var mileHit = (miles / 1000) * 105;
var est = Math.max(900, (base - ageHit - mileHit) * cond);
est = Math.round(est / 50) * 50;
tradeVal.textContent = money(est);
tradeOut.hidden = false;
toast("Estimated trade value: " + money(est));
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Westgate Motors — 2022 Toyota 4Runner TRD Off-Road</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>
<header class="topbar">
<div class="brand">
<span class="brand-mark" aria-hidden="true">WM</span>
<div class="brand-text">
<strong>Westgate Motors</strong>
<span>Pre-Owned & Certified · Bay City</span>
</div>
</div>
<nav class="topnav" aria-label="Primary">
<a href="#gallery">Photos</a>
<a href="#specs">Specs</a>
<a href="#finance">Finance</a>
<a href="#contact">Contact</a>
</nav>
<button class="btn btn-ghost btn-call" type="button" data-toast="Calling sales — (555) 018-4420">
<span class="dot" aria-hidden="true"></span> (555) 018-4420
</button>
</header>
<main class="wrap">
<nav class="crumbs" aria-label="Breadcrumb">
<a href="#">Inventory</a><span aria-hidden="true">/</span>
<a href="#">SUV</a><span aria-hidden="true">/</span>
<span aria-current="page">2022 Toyota 4Runner</span>
</nav>
<div class="layout">
<!-- LEFT: gallery + details -->
<section class="col-main">
<!-- GALLERY -->
<div class="gallery" id="gallery">
<div class="stage" id="stage" data-shade="0" role="img" aria-label="Vehicle photo, exterior front three-quarter">
<span class="stage-badge">Certified</span>
<span class="stage-count" id="stageCount">1 / 6</span>
<span class="stage-label" id="stageLabel">Front 3/4</span>
<button class="stage-nav prev" type="button" id="prevShot" aria-label="Previous photo">‹</button>
<button class="stage-nav next" type="button" id="nextShot" aria-label="Next photo">›</button>
</div>
<div class="thumbs" id="thumbs" role="tablist" aria-label="Vehicle photos"></div>
</div>
<!-- TITLE BLOCK -->
<div class="title-block">
<div>
<h1>2022 Toyota 4Runner <span class="trim">TRD Off-Road Premium</span></h1>
<p class="subtitle">4WD · 4.0L V6 · Magnetic Gray Metallic · Stock #WM-4729</p>
</div>
<button class="like" type="button" id="likeBtn" aria-pressed="false" aria-label="Save vehicle">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21s-7.5-4.6-10-9.3C.3 8.4 2 4.7 5.5 4.7c2.1 0 3.5 1.2 4.5 2.6 1-1.4 2.4-2.6 4.5-2.6 3.5 0 5.2 3.7 3.5 7C19.5 16.4 12 21 12 21z"/></svg>
</button>
</div>
<!-- QUICK FACTS -->
<ul class="facts">
<li><span class="f-k">Odometer</span><span class="f-v num">38,412 mi</span></li>
<li><span class="f-k">Drivetrain</span><span class="f-v">4WD</span></li>
<li><span class="f-k">Fuel</span><span class="f-v">Gasoline</span></li>
<li><span class="f-k">Transmission</span><span class="f-v">5-Spd Auto</span></li>
<li><span class="f-k">MPG</span><span class="f-v num">16 / 19</span></li>
<li><span class="f-k">Owners</span><span class="f-v num">1</span></li>
</ul>
<!-- SPEC TABS -->
<div class="panel" id="specs">
<div class="tabs" role="tablist" aria-label="Vehicle specifications">
<button class="tab is-active" role="tab" aria-selected="true" id="tab-overview" data-tab="overview">Overview</button>
<button class="tab" role="tab" aria-selected="false" id="tab-engine" data-tab="engine">Engine</button>
<button class="tab" role="tab" aria-selected="false" id="tab-history" data-tab="history">History</button>
</div>
<div class="tabpane is-active" id="pane-overview" role="tabpanel" aria-labelledby="tab-overview">
<dl class="spec-grid">
<div><dt>Body Style</dt><dd>SUV · 4-Door</dd></div>
<div><dt>Exterior</dt><dd>Magnetic Gray</dd></div>
<div><dt>Interior</dt><dd>Black SofTex</dd></div>
<div><dt>Seating</dt><dd class="num">5 Passengers</dd></div>
<div><dt>VIN</dt><dd class="num">JTEPU5JR9N4029471</dd></div>
<div><dt>Doors</dt><dd class="num">4</dd></div>
</dl>
</div>
<div class="tabpane" id="pane-engine" role="tabpanel" aria-labelledby="tab-engine" hidden>
<dl class="spec-grid">
<div><dt>Engine</dt><dd>4.0L DOHC V6</dd></div>
<div><dt>Horsepower</dt><dd class="num">270 hp</dd></div>
<div><dt>Torque</dt><dd class="num">278 lb-ft</dd></div>
<div><dt>Transmission</dt><dd>5-Speed Automatic</dd></div>
<div><dt>Drivetrain</dt><dd>Part-Time 4WD</dd></div>
<div><dt>Towing</dt><dd class="num">5,000 lbs</dd></div>
</dl>
</div>
<div class="tabpane" id="pane-history" role="tabpanel" aria-labelledby="tab-history" hidden>
<ul class="history">
<li><span class="ok-dot" aria-hidden="true"></span> No accidents or damage reported</li>
<li><span class="ok-dot" aria-hidden="true"></span> 1 previous owner · personal use</li>
<li><span class="ok-dot" aria-hidden="true"></span> 7 service records on file</li>
<li><span class="ok-dot" aria-hidden="true"></span> Open recalls: none</li>
<li><span class="warn-dot" aria-hidden="true"></span> Title: clean · last reported 03/2026</li>
</ul>
</div>
</div>
<!-- FEATURES -->
<div class="panel">
<h2 class="panel-h">Key Features</h2>
<ul class="features">
<li>Apple CarPlay & Android Auto</li>
<li>Heated Front Seats</li>
<li>Multi-Terrain Select</li>
<li>Crawl Control</li>
<li>Blind Spot Monitor</li>
<li>Adaptive Cruise</li>
<li>Roof Rails & Crossbars</li>
<li>Tow Hitch + 7-Pin</li>
<li>Sunroof / Moonroof</li>
<li>Backup Camera</li>
<li>Keyless Entry</li>
<li>17" TRD Alloy Wheels</li>
</ul>
</div>
</section>
<!-- RIGHT: pricing rail -->
<aside class="col-rail">
<div class="price-card">
<span class="price-flag">Certified Pre-Owned</span>
<div class="price-row">
<div>
<span class="price-k">Sale Price</span>
<strong class="price num" id="priceMain">$42,985</strong>
</div>
<span class="price-was num">$45,200</span>
</div>
<p class="finance-from">Est. <strong class="num" id="finFrom">$642</strong>/mo · 72 mo @ 6.9% APR</p>
<!-- FINANCE CALCULATOR -->
<div class="calc" id="finance">
<div class="calc-head">
<span>Payment Estimator</span>
<span class="calc-out num" id="calcMonthly">$642/mo</span>
</div>
<label class="field">
<span>Down payment <em class="num" id="downOut">$3,000</em></span>
<input type="range" id="down" min="0" max="15000" step="500" value="3000" aria-label="Down payment" />
</label>
<label class="field">
<span>Term</span>
<select id="term" aria-label="Loan term">
<option value="36">36 months</option>
<option value="48">48 months</option>
<option value="60">60 months</option>
<option value="72" selected>72 months</option>
<option value="84">84 months</option>
</select>
</label>
<label class="field">
<span>APR <em class="num" id="aprOut">6.9%</em></span>
<input type="range" id="apr" min="2.9" max="14.9" step="0.1" value="6.9" aria-label="Annual percentage rate" />
</label>
<p class="calc-note num" id="calcAmount">Financing $39,985 over 72 months</p>
</div>
<div class="cta-stack">
<button class="btn btn-primary" type="button" id="openDrive">Schedule Test Drive</button>
<button class="btn btn-outline" type="button" id="openTrade">Value Your Trade-In</button>
</div>
</div>
<!-- DEALER CONTACT -->
<div class="dealer" id="contact">
<div class="dealer-head">
<span class="avatar" aria-hidden="true">DA</span>
<div>
<strong>Dana Alvarez</strong>
<span class="muted">Sales Specialist</span>
</div>
</div>
<p class="dealer-loc">Westgate Motors · 4120 Harbor Blvd, Bay City</p>
<div class="dealer-actions">
<button class="btn btn-ghost" type="button" data-toast="Calling Dana — (555) 018-4420">Call</button>
<button class="btn btn-ghost" type="button" data-toast="Message thread opened with Dana">Message</button>
</div>
<ul class="reassure">
<li>7-Day Money-Back Guarantee</li>
<li>165-Point Inspection Passed</li>
<li>Home Delivery Available</li>
</ul>
</div>
</aside>
</div>
</main>
<!-- STICKY CTA BAR -->
<div class="sticky-bar" id="stickyBar" aria-hidden="true">
<div class="sticky-info">
<strong>2022 Toyota 4Runner TRD</strong>
<span class="num" id="stickyPrice">$42,985 · est. $642/mo</span>
</div>
<div class="sticky-cta">
<button class="btn btn-outline btn-sm" type="button" data-open="trade">Trade-In</button>
<button class="btn btn-primary btn-sm" type="button" data-open="drive">Test Drive</button>
</div>
</div>
<!-- TEST DRIVE MODAL -->
<div class="modal" id="driveModal" role="dialog" aria-modal="true" aria-labelledby="driveTitle" hidden>
<div class="modal-backdrop" data-close></div>
<div class="modal-card">
<button class="modal-x" type="button" data-close aria-label="Close">×</button>
<h2 id="driveTitle">Schedule a Test Drive</h2>
<p class="modal-sub">2022 Toyota 4Runner TRD Off-Road · Stock #WM-4729</p>
<form id="driveForm" class="modal-form" novalidate>
<label class="field">
<span>Full name</span>
<input type="text" name="name" required autocomplete="name" placeholder="Jordan Lee" />
</label>
<label class="field">
<span>Phone</span>
<input type="tel" name="phone" required autocomplete="tel" placeholder="(555) 000-0000" />
</label>
<div class="field-row">
<label class="field">
<span>Preferred date</span>
<input type="date" name="date" required />
</label>
<label class="field">
<span>Time</span>
<select name="time">
<option>10:00 AM</option><option>12:00 PM</option>
<option>2:00 PM</option><option>4:00 PM</option><option>6:00 PM</option>
</select>
</label>
</div>
<p class="form-err" id="driveErr" hidden></p>
<button class="btn btn-primary block" type="submit">Confirm Test Drive</button>
</form>
</div>
</div>
<!-- TRADE-IN MODAL -->
<div class="modal" id="tradeModal" role="dialog" aria-modal="true" aria-labelledby="tradeTitle" hidden>
<div class="modal-backdrop" data-close></div>
<div class="modal-card">
<button class="modal-x" type="button" data-close aria-label="Close">×</button>
<h2 id="tradeTitle">Value Your Trade-In</h2>
<p class="modal-sub">Get an instant ballpark estimate toward your purchase.</p>
<form id="tradeForm" class="modal-form" novalidate>
<div class="field-row">
<label class="field"><span>Year</span><input type="number" name="year" min="1990" max="2026" placeholder="2018" required /></label>
<label class="field"><span>Mileage</span><input type="number" name="miles" min="0" placeholder="62000" required /></label>
</div>
<label class="field"><span>Make & model</span><input type="text" name="model" placeholder="Honda CR-V EX" required /></label>
<label class="field">
<span>Condition</span>
<select name="cond">
<option value="1.0">Excellent</option>
<option value="0.85" selected>Good</option>
<option value="0.7">Fair</option>
<option value="0.55">Rough</option>
</select>
</label>
<div class="trade-out" id="tradeOut" hidden>
<span>Estimated value</span>
<strong class="num" id="tradeVal">$0</strong>
</div>
<button class="btn btn-primary block" type="submit">Estimate Trade Value</button>
</form>
</div>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Vehicle Detail
A full vehicle-detail page for Westgate Motors, built around a certified pre-owned 2022 Toyota 4Runner TRD Off-Road. A gradient photo gallery anchors the layout with a large stage, prev/next controls (also driven by the arrow keys), a six-shot thumbnail strip and a certified badge. Below it sit the title block with a save-to-garage heart, a six-cell quick-facts grid of odometer, drivetrain, fuel, transmission, MPG and owner count, and a tabbed spec panel that swaps between Overview, Engine and History views. A key-features grid lists equipment with custom checkmarks.
A sticky pricing rail carries the sale price, struck-through MSRP and a finance-from line. Its payment estimator recomputes the monthly figure live as you drag the down-payment and APR sliders or change the loan term, and the rail also exposes the dealer contact card with reassurance points. Scrolling the price card off-screen reveals a fixed CTA bar at the bottom; both it and the rail open test-drive and trade-in modals. The test-drive form validates name, phone and date, and the trade-in form returns a ballpark valuation from a small fictional pricing model.
Everything is vanilla HTML, CSS and JavaScript with no frameworks, build step or assets beyond the Inter font. Prices, the VIN and the odometer use tabular figures, modals trap initial focus and close on Escape or backdrop click, and the layout collapses the rail beneath the gallery and restacks the grids for screens down to about 360px.
Illustrative UI only — fictional shop/dealership, not a real service system.