A conversation, thoughtfully started

Let’s talk
about your space.

Have a material question, an editorial suggestion or a correction? Start with a few considered details.

Write to the editorial desk

This form prepares a draft in your email application. It does not send messages through this website. Review and send the draft yourself; a configured email app is required.

20–1,200 characters. Tell us about the room, the material or the guide in question. Please leave out sensitive personal information.

Before you write

Our guides might hold your answer.

For finish comparisons, layout starting points and specification questions, begin with the five-part collection. For structural, electrical or site-specific work, consult a qualified professional.

Return to the collection

Cookie preferences

Essential preference storage is always on. Optional permissions do not activate tracking: no optional services are installed.

===ENDFILE=== ===FILE: styles.css=== :root { --cream: #f5f0e7; --paper: #fcf9f3; --espresso: #32251f; --muted: #695a4e; --terracotta: #884a35; --gold: #806635; --line: #cfc1ad; --soft: #e9dfce; --space-1: 4px; --space-2: 8px; --space-3: 16px; --space-4: 24px; --space-5: 40px; --space-6: 64px; } * { box-sizing: border-box; } html { scroll-behavior: smooth; scroll-padding-top: 24px; } body { margin: 0; background: var(--cream); color: var(--espresso); font-family: Arial, Helvetica, sans-serif; font-size: 16px; line-height: 1.7; -webkit-text-size-adjust: 100%; } [hidden] { display: none !important; } h1, h2, h3, p, figure { margin: 0; } h1, h2, h3, nav, a, button, label, address, code, td, th, p, span { overflow-wrap: anywhere; } h1, h2, h3 { font-family: Georgia, "Times New Roman", serif; font-weight: 400; line-height: 1.08; letter-spacing: -.035em; } h1 { font-size: clamp(44px, 5.8vw, 80px); } h2 { font-size: clamp(32px, 3.6vw, 48px); } h3 { font-size: clamp(29px, 3vw, 38px); } h1 em, h2 em { color: var(--terracotta); font-weight: 400; } p + p { margin-top: 24px; } a { color: inherit; text-underline-offset: 5px; text-decoration-thickness: 1px; } a:hover { color: var(--terracotta); } button, input, select, textarea { font: inherit; } button, a, input, select, textarea { -webkit-tap-highlight-color: transparent; } button { cursor: pointer; } button:disabled { cursor: not-allowed; opacity: .65; } button, input, select, textarea { max-width: 100%; } :focus-visible { outline: 3px solid var(--terracotta); outline-offset: 5px; } svg { display: block; max-width: 100%; } img { display: block; max-width: 100%; height: auto; } ul { padding-left: 24px; } li + li { margin-top: 16px; } address { font-style: normal; } .shell { width: min(100%, 1248px); margin-inline: auto; padding-inline: 24px; } .header-inner > *, .hero > *, .section-heading > *, .guide-grid > *, .card-top > *, .philosophy-grid > *, .footer-grid > *, .cookie-banner > *, .cookie-actions > *, .dialog-actions > *, .form-row > *, .contact-layout > *, .check-field > *, .hero-art figcaption > *, .static-map figcaption > * { min-width: 0; } .skip-link { position: fixed; left: 16px; top: 8px; z-index: 300; padding: 16px 24px; max-width: calc(100% - 32px); background: var(--espresso); color: var(--cream); transform: translateY(-160%); } .skip-link:focus { transform: translateY(0); color: var(--cream); } .site-header { height: 64px; border-bottom: 1px solid var(--line); position: relative; z-index: 50; } .header-inner { height: 100%; max-width: 1440px; padding-inline: 24px; margin-inline: auto; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 24px; } .brand { display: inline-flex; gap: 8px; align-items: center; text-decoration: none; font-family: Georgia, "Times New Roman", serif; font-size: 20px; letter-spacing: -.025em; justify-self: start; } .brand svg { width: 28px; height: 32px; flex: 0 0 28px; fill: none; stroke: currentColor; stroke-width: 1.7; } .brand span { min-width: 0; } .primary-nav { display: flex; align-items: center; gap: 24px; font-size: 13px; } .primary-nav a { text-decoration: none; } .primary-nav a[aria-current="page"] { text-decoration: underline; } .header-cta { justify-self: end; text-decoration: none; font-size: 12px; border-bottom: 1px solid var(--gold); padding-block: 4px; } .header-cta span { padding-left: 8px; } .menu-toggle { display: none; border: 0; background: var(--cream); color: var(--espresso); width: 40px; height: 40px; padding: 8px; } .menu-toggle span { display: block; height: 1px; background: currentColor; color: inherit; margin-block: 8px; transition: transform .2s ease; } .menu-toggle[aria-expanded="true"] span:first-child { transform: translateY(4.5px) rotate(45deg); } .menu-toggle[aria-expanded="true"] span:last-child { transform: translateY(-4.5px) rotate(-45deg); } .eyebrow, .small-label { font-size: 11px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase; line-height: 1.7; color: var(--muted); } .eyebrow { margin-bottom: 24px; } .intro { font-size: 18px; line-height: 1.75; color: var(--muted); } .button { display: inline-flex; align-items: center; justify-content: center; gap: 24px; padding: 16px 24px; border: 1px solid var(--espresso); background: var(--espresso); color: var(--cream); text-decoration: none; font-size: 13px; font-weight: 600; line-height: 1.5; min-height: 48px; transition: background-color .2s ease, color .2s ease; } .button:hover { background: var(--terracotta); color: var(--paper); border-color: var(--terracotta); } .button-secondary { background: transparent; color: var(--espresso); border-color: var(--muted); } .button-secondary:hover { background: var(--soft); color: var(--espresso); border-color: var(--espresso); } .text-link { display: inline-flex; gap: 24px; align-items: center; font-size: 13px; font-weight: 600; width: fit-content; } .text-button { padding: 16px; border: 0; background: transparent; color: var(--espresso); text-decoration: underline; text-underline-offset: 5px; font-size: 13px; } .text-button:hover { color: var(--terracotta); } .hero { display: grid; grid-template-columns: 1fr 1fr; gap: 64px; align-items: center; padding-top: 64px; padding-bottom: 64px; } .hero-copy .intro { margin-top: 24px; max-width: 440px; } .hero-copy > .button { margin-top: 40px; } .hero-caption { border-top: 1px solid var(--line); padding-top: 24px; margin-top: 64px; } .hero-caption p { margin-top: 16px; color: var(--muted); font-size: 13px; } .hero-art svg { width: 100%; height: auto; } .hero-art figcaption { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px 24px; margin-top: 16px; font-size: 10px; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); } .guides-section { padding-top: 64px; padding-bottom: 64px; } .section-heading { display: grid; grid-template-columns: 1.3fr 1fr; gap: 64px; align-items: end; margin-bottom: 64px; } .section-heading > p { max-width: 400px; color: var(--muted); } .guide-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 40px; } .guide-card { display: flex; flex-direction: column; align-items: flex-start; gap: 24px; padding: 24px; border-top: 2px solid var(--terracotta); } .guide-card p { color: var(--muted); max-width: 660px; } .guide-card .text-link { margin-top: auto; } .card-top { display: flex; justify-content: space-between; align-items: center; gap: 24px; width: 100%; } .entry-number { font: italic 32px/1 Georgia, serif; color: var(--terracotta); } .tags { display: flex; flex-wrap: wrap; gap: 8px 24px; color: var(--muted); font-size: 11px; } .guide-card-wide { grid-column: 1 / -1; } .philosophy-section { margin-top: 64px; padding-block: 64px; background: var(--soft); color: var(--espresso); } .philosophy-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 64px; } .philosophy-grid .text-link { margin-top: 40px; } .site-footer { margin-top: 64px; padding-block: 64px; border-top: 1px solid var(--line); } .footer-grid { display: grid; grid-template-columns: 1.15fr .8fr .8fr 1.3fr; gap: 40px; } .footer-brand { font-family: Georgia, serif; font-size: 23px; text-decoration: none; line-height: 1.2; display: inline-block; } .footer-heading { font-family: Arial, Helvetica, sans-serif; font-size: 11px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; margin-bottom: 24px; line-height: 1.5; } .footer-grid p, .footer-grid > div > a:not(.footer-brand), .footer-preferences { font-size: 12px; } .footer-grid p { margin-top: 24px; color: var(--muted); } .footer-grid > div > a:not(.footer-brand) { display: block; margin-top: 8px; text-decoration: none; } .footer-grid > div > a:hover { text-decoration: underline; } .fine-print { font-size: 11px; line-height: 1.7; color: var(--muted); } .footer-grid .fine-print { font-size: 11px; } .footer-preferences { margin-top: 8px; padding: 0; text-align: left; border: 0; background: transparent; color: var(--espresso); line-height: 1.7; } .footer-preferences:hover { color: var(--terracotta); text-decoration: underline; } .article-header { padding-top: 64px; padding-bottom: 64px; } .breadcrumb { display: inline-block; font-size: 12px; margin-bottom: 40px; color: var(--muted); } .article-header h1 { max-width: 960px; } .article-header .intro { max-width: 680px; margin-top: 24px; } .material-landscape { height: clamp(144px, 21vw, 264px); margin-top: 64px; } .stone-landscape { background: linear-gradient(119deg, transparent 22%, #b7a992 22.4%, transparent 23.4%, transparent 39%, #c4b6a1 40%, transparent 42%), linear-gradient(90deg, #e9e1d2 0 50%, #c4b293 50% 78%, #503b2c 78%); color: var(--espresso); } .wood-landscape { background: repeating-linear-gradient(92deg, transparent 0 8px, #21160d26 9px 11px, transparent 12px 24px), linear-gradient(90deg, #ad8054 0 30%, #67412a 30% 72%, #3d281e 72%); color: var(--paper); } .furniture-landscape { background: radial-gradient(ellipse at 25% 100%, #e9ddc8 0 29%, transparent 29.5%), radial-gradient(ellipse at 75% 75%, #ad7155 0 25%, transparent 25.5%), #473126; color: var(--paper); } .lighting-landscape { background: radial-gradient(ellipse at 30% 0%, #f1d59a 0, #bc9963 9%, transparent 47%), radial-gradient(ellipse at 76% 0%, #edcc8c 0, #97764c 5%, transparent 37%), #382a22; color: var(--paper); } .architecture-landscape { background: linear-gradient(90deg, #e2d5be 0 25%, #a66349 25% 29%, #eddfc8 29% 68%, #563a2a 68% 85%, #b29b79 85%); color: var(--espresso); } .article-body { width: min(100%, 808px); margin-inline: auto; padding-inline: 24px; } .article-body > section + section { margin-top: 64px; } .article-body h2 { font-size: clamp(27px, 3vw, 36px); margin-bottom: 24px; } .article-body p, .article-body li { color: #51453b; } .article-body ul { margin-block: 24px; } .article-note { margin-top: 64px; border-top: 2px solid var(--terracotta); padding: 24px; display: flex; flex-direction: column; gap: 24px; } .article-note .eyebrow, .article-note h2 { margin: 0; } .article-note h2 { font-size: 32px; } .article-note p + p { margin: 0; } .legal-header { max-width: 1000px; } .legal-body { width: min(100%, 856px); } .legal-body code { font-size: 13px; } .legal-body section > .button { margin-top: 24px; } .table-wrap { overflow-x: auto; margin-top: 24px; border-top: 1px solid var(--line); } table { width: 100%; border-collapse: collapse; font-size: 13px; line-height: 1.6; } caption { text-align: left; padding: 24px 16px; font-weight: 600; } th, td { text-align: left; vertical-align: top; padding: 16px; border-bottom: 1px solid var(--line); } thead { background: var(--soft); color: var(--espresso); } tbody th { font-weight: 600; } .contact-layout { display: grid; grid-template-columns: 1.35fr 1fr; gap: 64px; } .contact-layout h2 { font-size: 32px; margin-bottom: 24px; } .contact-layout > div > p { color: var(--muted); } .contact-form { margin-top: 40px; display: flex; flex-direction: column; align-items: stretch; gap: 24px; } .form-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; } .field { min-width: 0; } .field > label { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 600; } .field > label span { font-weight: 400; } input:not([type="checkbox"]), select, textarea { width: 100%; min-width: 0; padding: 16px; border: 1px solid #8e7e6b; border-radius: 0; background: var(--paper); color: var(--espresso); line-height: 1.5; } textarea { resize: vertical; } input[type="checkbox"] { width: 18px; height: 18px; flex: 0 0 18px; margin: 4px 0 0; accent-color: var(--terracotta); } .check-field, .field > .check-field { display: flex; align-items: flex-start; gap: 16px; font-weight: 400; line-height: 1.7; } .field-help { margin-top: 8px; font-size: 12px; color: var(--muted); } .field-error, p.field-error { margin-top: 8px; font-size: 12px; color: #9b2c20; } .is-invalid { border-color: #9b2c20 !important; box-shadow: 0 0 0 1px #9b2c20; } input[type="checkbox"].is-invalid { outline: 2px solid #9b2c20; outline-offset: 3px; box-shadow: none; } input[type="checkbox"].is-invalid:focus-visible { outline: 3px solid var(--terracotta); outline-offset: 5px; } .contact-form > .button { align-self: flex-start; } .form-status { padding: 16px; border-left: 2px solid var(--terracotta); background: var(--soft); color: var(--espresso); font-size: 13px; } .detail-section { border-top: 2px solid var(--terracotta); padding: 24px; } .detail-section + .detail-section { margin-top: 64px; } .detail-section p { color: var(--muted); } .contact-email { font-size: 16px; } .static-map { margin-top: 64px; } .static-map svg { width: 100%; height: auto; } .static-map figcaption { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px 24px; padding: 16px; font-size: 11px; border-bottom: 1px solid var(--line); } .static-map figcaption strong { font-weight: 600; } .contact-followup { margin-top: 64px; padding-top: 64px; border-top: 1px solid var(--line); } .contact-followup h2 { margin-bottom: 24px; } .contact-followup p:not(.eyebrow) { max-width: 720px; color: var(--muted); } .contact-followup .text-link { margin-top: 24px; } .cookie-banner { position: fixed; z-index: 90; bottom: 0; left: 0; right: 0; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 16px 40px; align-items: center; padding: 24px; border-top: 1px solid #a38b70; background: var(--paper); color: var(--espresso); box-shadow: 0 -4px 24px #32251f12; max-height: 75vh; max-height: 75dvh; overflow-y: auto; } .cookie-banner h2 { font-size: 24px; margin-bottom: 8px; } .cookie-banner p { font-size: 12px; line-height: 1.6; max-width: 680px; } .cookie-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; } .cookie-actions .button { padding: 16px; font-size: 12px; } .cookie-banner > .fine-print { grid-column: 1 / -1; } .cookie-banner > .fine-print:empty { display: none; } .preferences-dialog { width: min(560px, calc(100% - 32px)); max-width: calc(100% - 32px); max-height: calc(100vh - 32px); max-height: calc(100dvh - 32px); overflow-y: auto; padding: 24px; border: 1px solid var(--line); background: var(--paper); color: var(--espresso); } .preferences-dialog::backdrop { background: #231b16a6; color: var(--cream); } .preferences-dialog h2 { font-size: 32px; margin-bottom: 24px; } .preferences-dialog > p { font-size: 14px; margin-bottom: 24px; } .preferences-dialog .check-field { margin-top: 24px; font-size: 14px; } .dialog-actions { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 40px; } @media (max-width: 1050px) { .hero { gap: 40px; } .hero h1 { font-size: clamp(44px, 6vw, 64px); } .brand { font-size: 17px; } .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } .cookie-banner { grid-template-columns: minmax(0, 1fr); } .contact-layout { gap: 40px; } } @media (max-width: 767px) { .site-header { height: 52px; } .header-inner { grid-template-columns: minmax(0, 1fr) auto auto; gap: 16px; padding-inline: 16px; } .brand { font-size: 15px; line-height: 1.15; gap: 8px; } .brand svg { width: 22px; height: 28px; flex-basis: 22px; } .header-cta { font-size: 10px; max-width: 80px; line-height: 1.4; } .header-cta span { padding-left: 4px; } .menu-toggle { display: block; } .primary-nav { position: fixed; top: 52px; left: 0; right: 0; padding: 24px; background: var(--paper); color: var(--espresso); border-bottom: 1px solid var(--line); box-shadow: 0 16px 24px #32251f18; display: flex; flex-direction: column; align-items: stretch; gap: 24px; font-size: 24px; visibility: hidden; pointer-events: none; opacity: 0; transition: opacity .2s ease, visibility .2s ease; } .primary-nav.is-open { visibility: visible; pointer-events: auto; opacity: 1; } .primary-nav a { padding: 16px; } .hero { grid-template-columns: minmax(0, 1fr); gap: 64px; } .hero h1 { font-size: clamp(44px, 9vw, 72px); } .hero-caption { margin-top: 40px; } .hero-art { max-width: 560px; width: 100%; margin-inline: auto; } .section-heading, .philosophy-grid { grid-template-columns: minmax(0, 1fr); gap: 40px; } .guide-grid { grid-template-columns: minmax(0, 1fr); } .guide-card-wide { grid-column: auto; } .section-heading > p { max-width: none; } .contact-layout { grid-template-columns: minmax(0, 1fr); gap: 64px; } .footer-grid { gap: 40px 24px; } .cookie-banner { padding: 16px; gap: 16px; } .cookie-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); } .cookie-actions .text-button { grid-column: 1 / -1; padding: 8px 16px; } .cookie-actions .button { height: 100%; padding: 16px; } table { font-size: 12px; } th, td { padding: 16px; } } @media (max-width: 540px) { .footer-grid, .form-row { grid-template-columns: minmax(0, 1fr); } .footer-grid { gap: 40px; } .intro { font-size: 16px; } .guide-card { padding: 24px 16px; } .card-top { align-items: flex-start; } .card-top .small-label { text-align: right; } .article-note, .detail-section { padding: 24px 16px; } .article-body h2 { line-height: 1.2; } .dialog-actions { flex-direction: column; align-items: stretch; } } @media (max-width: 399px) { .shell, .article-body { padding-inline: 16px; } .header-inner { gap: 8px; } .brand { font-size: 13px; } .brand svg { width: 20px; flex-basis: 20px; } .header-cta { max-width: 58px; font-size: 10px; } .menu-toggle { width: 32px; padding: 4px; } h1, .hero h1 { font-size: 42px; } h2 { font-size: 32px; } .eyebrow { font-size: 10px; } .button { padding: 16px; gap: 16px; } .hero-art figcaption { font-size: 9px; } .cookie-banner h2 { font-size: 21px; } .cookie-actions .button { font-size: 11px; } .preferences-dialog { padding: 24px 16px; } } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } } ===ENDFILE=== ===FILE: script.js=== (() => { "use strict"; const menuButton = document.querySelector("[data-menu-toggle]"); const navigation = document.querySelector("[data-navigation]"); const mobileQuery = window.matchMedia("(max-width: 767px)"); function setMenu(open, restoreFocus = false) { navigation.classList.toggle("is-open", open); menuButton.setAttribute("aria-expanded", String(open)); menuButton.setAttribute("aria-label", open ? "Close navigation" : "Open navigation"); if (restoreFocus) menuButton.focus(); } menuButton.addEventListener("click", () => { setMenu(menuButton.getAttribute("aria-expanded") !== "true"); }); navigation.addEventListener("click", (event) => { if (event.target.closest("a")) setMenu(false); }); document.addEventListener("click", (event) => { if (!navigation.contains(event.target) && !menuButton.contains(event.target)) { setMenu(false); } }); document.addEventListener("keydown", (event) => { if (event.key === "Escape" && menuButton.getAttribute("aria-expanded") === "true") { setMenu(false, true); } }); document.addEventListener("focusin", (event) => { if ( mobileQuery.matches && menuButton.getAttribute("aria-expanded") === "true" && !navigation.contains(event.target) && !menuButton.contains(event.target) ) { setMenu(false); } }); const handleViewport = () => setMenu(false); if (mobileQuery.addEventListener) { mobileQuery.addEventListener("change", handleViewport); } else { mobileQuery.addListener(handleViewport); } const storageKey = "clipcottage-cookie-consent"; const consentVersion = 1; const maximumAge = 180 * 24 * 60 * 60 * 1000; const banner = document.querySelector("[data-cookie-banner]"); const dialog = document.querySelector("[data-cookie-dialog]"); const analyticsInput = document.querySelector("[data-cookie-analytics]"); const functionalInput = document.querySelector("[data-cookie-functional]"); const cookieNotice = document.querySelector("[data-cookie-notice]"); const saveButton = document.querySelector("[data-cookie-save]"); const closeButton = document.querySelector("[data-cookie-close]"); let lastPreferenceTrigger = null; let memoryConsent = null; function readConsent() { try { const raw = localStorage.getItem(storageKey); if (!raw) return null; const value = JSON.parse(raw); const now = Date.now(); if ( !value || value.version !== consentVersion || !["accept", "reject", "custom"].includes(value.choice) || typeof value.analytics !== "boolean" || typeof value.functional !== "boolean" || !Number.isFinite(value.timestamp) || value.timestamp > now || now - value.timestamp >= maximumAge ) { localStorage.removeItem(storageKey); return null; } return value; } catch { return null; } } function closePreferences() { if (dialog.open) dialog.close(); } function openPreferences(trigger) { lastPreferenceTrigger = trigger; const current = memoryConsent || readConsent(); analyticsInput.checked = Boolean(current && current.analytics); functionalInput.checked = Boolean(current && current.functional); setMenu(false); if (!dialog.open) dialog.showModal(); } function storeConsent(choice, analytics, functional) { memoryConsent = { version: consentVersion, choice, analytics, functional, timestamp: Date.now() }; let stored = false; try { localStorage.setItem(storageKey, JSON.stringify(memoryConsent)); stored = true; } catch { stored = false; } closePreferences(); if (stored) { const focusWasInBanner = banner.contains(document.activeElement); banner.hidden = true; cookieNotice.textContent = ""; if (focusWasInBanner) { const footerPreference = document.querySelector(".site-footer [data-cookie-choice='preferences']"); footerPreference.focus({ preventScroll: true }); } } else { banner.hidden = false; cookieNotice.textContent = "Your choice applies to this page, but browser storage is unavailable. It cannot be remembered after navigation. No optional tracking is running."; } // Optional categories are intentionally inert. No tracking scripts are installed. } document.querySelectorAll("[data-cookie-choice]").forEach((button) => { button.addEventListener("click", () => { const choice = button.dataset.cookieChoice; if (choice === "preferences") { openPreferences(button); } else if (choice === "accept") { storeConsent("accept", true, true); } else if (choice === "reject") { storeConsent("reject", false, false); } }); }); saveButton.addEventListener("click", () => { storeConsent("custom", analyticsInput.checked, functionalInput.checked); }); closeButton.addEventListener("click", closePreferences); dialog.addEventListener("close", () => { if (lastPreferenceTrigger && !lastPreferenceTrigger.closest("[hidden]")) { lastPreferenceTrigger.focus({ preventScroll: true }); } else { document.querySelector(".site-footer [data-cookie-choice='preferences']").focus({ preventScroll: true }); } }); window.addEventListener("storage", (event) => { if (event.key === storageKey || event.key === null) { memoryConsent = readConsent(); banner.hidden = Boolean(memoryConsent); if (dialog.open) { analyticsInput.checked = Boolean(memoryConsent && memoryConsent.analytics); functionalInput.checked = Boolean(memoryConsent && memoryConsent.functional); } } }); memoryConsent = readConsent(); banner.hidden = Boolean(memoryConsent); const contactForm = document.querySelector("[data-contact-form]"); if (!contactForm) return; const fields = Array.from(contactForm.querySelectorAll("[data-contact-field]")); const status = document.querySelector("[data-form-status]"); const submitButton = document.querySelector("[data-contact-submit]"); const errors = new Map( Array.from(contactForm.querySelectorAll("[data-field-error]")).map((element) => [ element.dataset.fieldError, element ]) ); contactForm.noValidate = true; submitButton.disabled = false; function fieldMessage(field) { const value = field.type === "checkbox" ? "" : field.value.trim(); switch (field.name) { case "name": if (!value) return "Please enter your name."; if (value.length > 100) return "Please keep your name to 100 characters or fewer."; return ""; case "email": if (!value) return "Please enter your email address."; if (field.validity.typeMismatch || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) { return "Enter a complete email address, such as [email protected]."; } if (value.length > 200) return "Please use an email address under 201 characters."; return ""; case "subject": return value ? "" : "Please choose a topic."; case "message": if (value.length < 20) return "Please write at least 20 characters so your question has some context."; if (value.length > 1200) return "Please keep your message to 1,200 characters or fewer."; return ""; case "consent": return field.checked ? "" : "Please confirm that you understand how the email draft works."; default: return ""; } } function validateField(field) { const message = fieldMessage(field); const error = errors.get(field.name); field.classList.toggle("is-invalid", Boolean(message)); field.setAttribute("aria-invalid", String(Boolean(message))); error.textContent = message; error.hidden = !message; return !message; } fields.forEach((field) => { field.addEventListener("blur", () => validateField(field)); const update = () => { if (field.getAttribute("aria-invalid") === "true") validateField(field); status.hidden = true; }; field.addEventListener("input", update); field.addEventListener("change", update); }); contactForm.addEventListener("submit", (event) => { event.preventDefault(); let firstInvalid = null; fields.forEach((field) => { if (!validateField(field) && !firstInvalid) firstInvalid = field; }); status.hidden = false; if (firstInvalid) { status.textContent = "Please correct the highlighted fields before opening your email draft."; firstInvalid.focus(); return; } const data = new FormData(contactForm); const name = String(data.get("name")).trim(); const email = String(data.get("email")).trim(); const subject = String(data.get("subject")).trim(); const message = String(data.get("message")).trim(); const body = `Name: ${name}\nReply email: ${email}\nTopic: ${subject}\n\n${message}`; status.textContent = "An email draft has been requested in your mail application. Nothing has been sent by this website. Review the draft and send it there. If no app opens, copy your message and email [email protected] directly."; window.location.href = `mailto:[email protected]?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; }); })(); ===ENDFILE===