Law Firm — Contact & Offices
A trust-first law-firm contact section pairing a validated enquiry form with name email phone matter-type and message fields, three office cards showing address phone and live open-or-closed hours, and a stylized static map placeholder rendered without any libraries.
MCP
Code
:root {
--navy: #1b2a4a;
--navy-d: #101b33;
--gold: #b08d57;
--gold-soft: #efe3cf;
--ink: #1a1f2b;
--muted: #5b6577;
--bg: #f5f3ee;
--white: #ffffff;
--line: #e3ddd1;
--ok: #2e6b4f;
--ok-soft: #e3f0e8;
--closed: #9a3b3b;
--closed-soft: #f4e2e0;
--serif: "Georgia", "Times New Roman", serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
--radius: 12px;
--shadow: 0 10px 30px rgba(16, 27, 51, 0.08);
}
* {
box-sizing: border-box;
margin: 0;
}
body {
font-family: var(--sans);
color: var(--ink);
background: var(--bg);
line-height: 1.55;
-webkit-font-smoothing: antialiased;
padding: 40px 20px;
}
.wrap {
max-width: 1080px;
margin: 0 auto;
}
/* Masthead */
.masthead {
text-align: center;
margin-bottom: 36px;
}
.eyebrow {
font-family: var(--sans);
text-transform: uppercase;
letter-spacing: 0.22em;
font-size: 0.72rem;
font-weight: 600;
color: var(--gold);
}
.masthead h1 {
font-family: var(--serif);
font-weight: 600;
font-size: 2.4rem;
color: var(--navy);
margin: 8px 0 12px;
}
.lede {
max-width: 620px;
margin: 0 auto;
color: var(--muted);
font-size: 1.02rem;
}
.rule {
display: block;
width: 64px;
height: 2px;
background: var(--gold);
margin: 24px auto 0;
}
/* Layout */
.grid {
display: grid;
grid-template-columns: 1.1fr 1fr;
gap: 28px;
align-items: start;
}
.card-title {
font-family: var(--serif);
font-size: 1.4rem;
font-weight: 600;
color: var(--navy);
}
/* Form card */
.card {
background: var(--white);
border: 1px solid var(--line);
border-top: 3px solid var(--gold);
border-radius: var(--radius);
padding: 28px;
box-shadow: var(--shadow);
}
.card-sub {
color: var(--muted);
font-size: 0.88rem;
margin: 6px 0 22px;
}
.req {
color: var(--gold);
font-weight: 700;
}
.field {
margin-bottom: 18px;
}
.field-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
label {
display: block;
font-size: 0.82rem;
font-weight: 600;
color: var(--navy);
margin-bottom: 6px;
}
input,
select,
textarea {
width: 100%;
font-family: var(--sans);
font-size: 0.95rem;
color: var(--ink);
background: var(--bg);
border: 1px solid var(--line);
border-radius: 8px;
padding: 11px 13px;
transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
textarea {
resize: vertical;
min-height: 96px;
}
input::placeholder,
textarea::placeholder {
color: #9aa2b1;
}
input:focus,
select:focus,
textarea:focus {
outline: none;
border-color: var(--gold);
background: var(--white);
box-shadow: 0 0 0 3px rgba(176, 141, 87, 0.18);
}
.field.invalid input,
.field.invalid select,
.field.invalid textarea {
border-color: var(--closed);
box-shadow: 0 0 0 3px rgba(154, 59, 59, 0.12);
}
.hint {
font-size: 0.76rem;
color: var(--muted);
margin-top: 6px;
}
.error {
font-size: 0.78rem;
color: var(--closed);
margin-top: 6px;
min-height: 0;
display: none;
}
.field.invalid .error {
display: block;
}
.submit {
width: 100%;
font-family: var(--sans);
font-size: 0.95rem;
font-weight: 600;
letter-spacing: 0.02em;
color: var(--white);
background: var(--navy);
border: none;
border-radius: 8px;
padding: 13px;
cursor: pointer;
transition: background 0.15s ease, transform 0.08s ease;
}
.submit:hover {
background: var(--navy-d);
}
.submit:active {
transform: translateY(1px);
}
.submit:focus-visible {
outline: 3px solid rgba(176, 141, 87, 0.5);
outline-offset: 2px;
}
.form-status {
margin-top: 14px;
font-size: 0.9rem;
font-weight: 600;
color: var(--ok);
text-align: center;
min-height: 1.2em;
}
/* Offices */
.offices {
display: flex;
flex-direction: column;
gap: 18px;
}
/* Map placeholder */
.map {
position: relative;
height: 220px;
border-radius: var(--radius);
overflow: hidden;
background: linear-gradient(160deg, #e9ede6 0%, #dfe6dd 100%);
border: 1px solid var(--line);
box-shadow: var(--shadow);
}
.map-grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(27, 42, 74, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(27, 42, 74, 0.05) 1px, transparent 1px);
background-size: 28px 28px;
}
.map-road {
position: absolute;
background: var(--white);
}
.map-road--h {
left: 0;
right: 0;
height: 8px;
}
.map-road--v {
top: 0;
bottom: 0;
width: 8px;
}
.map-water {
position: absolute;
right: -40px;
bottom: -40px;
width: 160px;
height: 160px;
background: #cfe0e6;
border-radius: 50%;
opacity: 0.8;
}
.pin {
position: absolute;
transform: translate(-50%, -100%);
font-size: 0.66rem;
font-weight: 700;
letter-spacing: 0.04em;
color: var(--navy);
background: var(--white);
padding: 3px 7px 3px 6px;
border-radius: 999px;
border: 1px solid var(--gold-soft);
box-shadow: 0 4px 10px rgba(16, 27, 51, 0.18);
display: inline-flex;
align-items: center;
gap: 5px;
white-space: nowrap;
}
.pin-dot {
width: 9px;
height: 9px;
border-radius: 50%;
background: var(--gold);
box-shadow: 0 0 0 3px rgba(176, 141, 87, 0.25);
}
/* Office cards */
.office-list {
list-style: none;
padding: 0;
display: flex;
flex-direction: column;
gap: 14px;
}
.office {
background: var(--white);
border: 1px solid var(--line);
border-left: 3px solid var(--gold);
border-radius: var(--radius);
padding: 18px 20px;
box-shadow: var(--shadow);
}
.office-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.office h3 {
font-family: var(--serif);
font-size: 1.12rem;
font-weight: 600;
color: var(--navy);
}
.status {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
padding: 3px 9px;
border-radius: 999px;
background: #eee;
color: var(--muted);
}
.status.is-open {
background: var(--ok-soft);
color: var(--ok);
}
.status.is-closed {
background: var(--closed-soft);
color: var(--closed);
}
address {
font-style: normal;
color: var(--ink);
font-size: 0.9rem;
margin: 10px 0 8px;
}
.phone {
display: inline-block;
font-weight: 600;
font-size: 0.9rem;
color: var(--navy);
text-decoration: none;
border-bottom: 1px solid var(--gold-soft);
transition: color 0.15s ease, border-color 0.15s ease;
}
.phone:hover {
color: var(--gold);
border-color: var(--gold);
}
.hours {
font-size: 0.82rem;
color: var(--muted);
margin-top: 8px;
}
/* Responsive */
@media (max-width: 520px) {
body {
padding: 28px 14px;
}
.masthead h1 {
font-size: 1.85rem;
}
.grid {
grid-template-columns: 1fr;
gap: 22px;
}
.field-row {
grid-template-columns: 1fr;
gap: 0;
}
.card {
padding: 22px 18px;
}
.map {
height: 180px;
}
}(function () {
"use strict";
/* ---------- Contact form validation ---------- */
var form = document.getElementById("contactForm");
var status = document.getElementById("formStatus");
var validators = {
name: function (v) {
if (!v.trim()) return "Please enter your full name.";
if (v.trim().length < 2) return "That name looks too short.";
return "";
},
email: function (v) {
if (!v.trim()) return "Please enter your email address.";
if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v.trim())) return "Enter a valid email address.";
return "";
},
phone: function (v) {
if (!v.trim()) return "Please enter a phone number.";
var digits = v.replace(/\D/g, "");
if (digits.length < 7) return "Enter a valid phone number.";
return "";
},
matter: function (v) {
if (!v) return "Please choose a matter type.";
return "";
},
message: function (v) {
if (!v.trim()) return "Please tell us how we can help.";
if (v.trim().length < 10) return "Please add a little more detail.";
return "";
},
};
function fieldWrap(input) {
return input.closest(".field");
}
function errorEl(name) {
return form.querySelector('[data-error-for="' + name + '"]');
}
function validateField(input) {
var name = input.name;
if (!validators[name]) return true;
var msg = validators[name](input.value);
var wrap = fieldWrap(input);
var err = errorEl(name);
if (msg) {
wrap.classList.add("invalid");
if (err) err.textContent = msg;
input.setAttribute("aria-invalid", "true");
return false;
}
wrap.classList.remove("invalid");
if (err) err.textContent = "";
input.removeAttribute("aria-invalid");
return true;
}
if (form) {
// Re-validate on input once a field has been touched/marked invalid.
Array.prototype.forEach.call(
form.querySelectorAll("input, select, textarea"),
function (el) {
el.addEventListener("blur", function () {
validateField(el);
});
el.addEventListener("input", function () {
if (fieldWrap(el).classList.contains("invalid")) validateField(el);
});
}
);
form.addEventListener("submit", function (e) {
e.preventDefault();
status.textContent = "";
var fields = ["name", "email", "phone", "matter", "message"];
var firstInvalid = null;
var ok = true;
fields.forEach(function (name) {
var input = form.elements[name];
if (!validateField(input)) {
ok = false;
if (!firstInvalid) firstInvalid = input;
}
});
if (!ok) {
if (firstInvalid) firstInvalid.focus();
status.style.color = "var(--closed)";
status.textContent = "Please correct the highlighted fields.";
return;
}
var name = form.elements.name.value.trim().split(" ")[0];
form.reset();
status.style.color = "var(--ok)";
status.textContent =
"Thank you, " + name + ". Your enquiry has been received — we'll respond within one business day.";
});
}
/* ---------- Live open/closed office status ---------- */
function hourInZone(tz) {
try {
var parts = new Intl.DateTimeFormat("en-US", {
timeZone: tz,
hour: "numeric",
hour12: false,
weekday: "short",
}).formatToParts(new Date());
var hour = 0;
var weekday = "";
parts.forEach(function (p) {
if (p.type === "hour") hour = parseInt(p.value, 10) % 24;
if (p.type === "weekday") weekday = p.value;
});
return { hour: hour, weekday: weekday };
} catch (err) {
var d = new Date();
return { hour: d.getHours(), weekday: d.toLocaleDateString("en-US", { weekday: "short" }) };
}
}
function refreshOffices() {
var offices = document.querySelectorAll(".office");
Array.prototype.forEach.call(offices, function (office) {
var tz = office.getAttribute("data-tz");
var open = parseInt(office.getAttribute("data-open"), 10);
var close = parseInt(office.getAttribute("data-close"), 10);
var badge = office.querySelector("[data-status]");
if (!badge) return;
var now = hourInZone(tz);
var isWeekday = ["Sat", "Sun"].indexOf(now.weekday) === -1;
var isOpen = isWeekday && now.hour >= open && now.hour < close;
badge.classList.remove("is-open", "is-closed");
if (isOpen) {
badge.classList.add("is-open");
badge.textContent = "Open now";
} else {
badge.classList.add("is-closed");
badge.textContent = "Closed";
}
});
}
refreshOffices();
// Re-check every minute so the badge stays accurate near opening/closing.
setInterval(refreshOffices, 60 * 1000);
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sterling & Vance LLP — Contact & Offices</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&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="wrap">
<header class="masthead">
<p class="eyebrow">Sterling & Vance LLP</p>
<h1>Contact & Offices</h1>
<p class="lede">
Speak with our team about your matter. Submit an enquiry below or reach an office directly —
we typically respond within one business day.
</p>
<span class="rule" aria-hidden="true"></span>
</header>
<div class="grid">
<!-- Enquiry form -->
<section class="card form-card" aria-labelledby="form-heading">
<h2 id="form-heading" class="card-title">Send an enquiry</h2>
<p class="card-sub">All fields marked with <span class="req">*</span> are required.</p>
<form id="contactForm" novalidate>
<div class="field">
<label for="name">Full name <span class="req">*</span></label>
<input type="text" id="name" name="name" autocomplete="name" placeholder="e.g. Marcus Delaney" />
<p class="error" data-error-for="name" role="alert"></p>
</div>
<div class="field-row">
<div class="field">
<label for="email">Email <span class="req">*</span></label>
<input type="email" id="email" name="email" autocomplete="email" placeholder="[email protected]" />
<p class="error" data-error-for="email" role="alert"></p>
</div>
<div class="field">
<label for="phone">Phone <span class="req">*</span></label>
<input type="tel" id="phone" name="phone" autocomplete="tel" placeholder="(212) 555-0148" />
<p class="error" data-error-for="phone" role="alert"></p>
</div>
</div>
<div class="field">
<label for="matter">Matter type <span class="req">*</span></label>
<select id="matter" name="matter">
<option value="">Select a practice area…</option>
<option>Corporate & M&A</option>
<option>Commercial Litigation</option>
<option>Real Estate</option>
<option>Intellectual Property</option>
<option>Employment & Labor</option>
<option>Estate Planning</option>
<option>Other</option>
</select>
<p class="error" data-error-for="matter" role="alert"></p>
</div>
<div class="field">
<label for="message">How can we help? <span class="req">*</span></label>
<textarea id="message" name="message" rows="4" placeholder="Briefly describe your matter. Do not include confidential details."></textarea>
<p class="hint">Please don't share confidential information until we've confirmed there's no conflict.</p>
<p class="error" data-error-for="message" role="alert"></p>
</div>
<button type="submit" class="submit">Submit enquiry</button>
<p class="form-status" id="formStatus" role="status" aria-live="polite"></p>
</form>
</section>
<!-- Offices + map -->
<section class="offices" aria-labelledby="offices-heading">
<h2 id="offices-heading" class="card-title">Our offices</h2>
<div class="map" role="img" aria-label="Stylized map showing three office locations">
<span class="map-grid" aria-hidden="true"></span>
<span class="map-road map-road--h" style="top:34%"></span>
<span class="map-road map-road--h" style="top:68%"></span>
<span class="map-road map-road--v" style="left:40%"></span>
<span class="map-road map-road--v" style="left:74%"></span>
<span class="map-water" aria-hidden="true"></span>
<span class="pin" style="top:30%;left:38%"><span class="pin-dot"></span>NYC</span>
<span class="pin" style="top:62%;left:22%"><span class="pin-dot"></span>CHI</span>
<span class="pin" style="top:46%;left:78%"><span class="pin-dot"></span>LDN</span>
</div>
<ul class="office-list" id="officeList">
<li class="office" data-tz="America/New_York" data-open="9" data-close="18">
<div class="office-head">
<h3>Manhattan</h3>
<span class="status" data-status></span>
</div>
<address>
412 Park Avenue, Suite 2200<br />
New York, NY 10022
</address>
<a class="phone" href="tel:+12125550148">(212) 555-0148</a>
<p class="hours" data-hours>Mon–Fri, 9:00 AM – 6:00 PM</p>
</li>
<li class="office" data-tz="America/Chicago" data-open="8" data-close="17">
<div class="office-head">
<h3>Chicago</h3>
<span class="status" data-status></span>
</div>
<address>
155 N. Wacker Drive, Floor 31<br />
Chicago, IL 60606
</address>
<a class="phone" href="tel:+13125550193">(312) 555-0193</a>
<p class="hours" data-hours>Mon–Fri, 8:00 AM – 5:00 PM</p>
</li>
<li class="office" data-tz="Europe/London" data-open="9" data-close="17">
<div class="office-head">
<h3>London</h3>
<span class="status" data-status></span>
</div>
<address>
28 King William Street<br />
London, EC4R 9AT, UK
</address>
<a class="phone" href="tel:+442075550172">+44 20 7555 0172</a>
<p class="hours" data-hours>Mon–Fri, 9:00 AM – 5:00 PM</p>
</li>
</ul>
</section>
</div>
</main>
<script src="script.js"></script>
</body>
</html>Contact & Offices
An authoritative, trust-first contact section for a law firm. A gold-ruled enquiry form gathers the prospect’s name, email, phone, the matter type and a short message, validating each field inline — required entries, a real email format and a sensible phone — and moving focus to the first problem on a failed submit before showing a calm confirmation.
Alongside the form sit three office cards for Manhattan, Chicago and London, each with a full street address, a click-to-call phone line and posted opening hours. A small vanilla-JS clock reads each office’s weekday schedule against the current time and stamps a live Open now or Closed badge so visitors know who they can reach right away.
A stylized static map placeholder anchors the section with gold pins for every location, drawn purely in CSS and markup so there are no external tiles or API keys to wire up.
Illustrative UI only — not legal advice and not a real firm.