The correspondence desk

A useful correction is always welcome.

If a tent has changed its rules or a route no longer works, tell me what you checked and when. A link to the organiser helps more than a confident guess.

Write to the editor

Prepare an email

This form checks your details in this browser and creates an email draft. It does not send a message or store it on our server. You will need to open your email application and press Send there.

20–1,500 characters. Include the guide name, relevant date and a source link if you have one. Please omit sensitive personal information.

Read our Privacy policy before sending. We cannot make reservations or provide urgent travel assistance.

Before you write

Need a starting point rather than a reservation?

The five guides compare formats, costs and travel choices. For live availability, use the organiser links inside each guide.

Return to the festival index
===ENDFILE=== ===FILE: styles.css=== :root { --paper: #f8f4ea; --canvas: #fffdf7; --ink: #272923; --muted: #595c51; --wine: #722b35; --wine-dark: #511c25; --frame: #e9dcc3; --rule: #b6ac99; --sage: #e5e7d9; --space-1: 4px; --space-2: 8px; --space-3: 16px; --space-4: 24px; --space-5: 40px; --space-6: 64px; --serif: Georgia, "Times New Roman", serif; --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif; } * { box-sizing: border-box; } html { scroll-behavior: smooth; scroll-padding-block: 24px; } body { margin: 0; background: var(--paper); color: var(--ink); font-family: var(--sans); font-size: 16px; line-height: 1.7; } [hidden] { display: none !important; } a { color: var(--wine); text-underline-offset: 4px; text-decoration-thickness: 1px; } a:hover { color: var(--wine-dark); text-decoration-thickness: 2px; } a, button, input, textarea, select { -webkit-tap-highlight-color: #e9dcc3; } :focus-visible { outline: 3px solid var(--wine); outline-offset: 4px; } a, p, li, dd, td, th, address, h1, h2, h3 { overflow-wrap: anywhere; } button, input, select, textarea { font: inherit; } button, .button, .header-cta { display: inline-flex; justify-content: center; align-items: center; gap: 16px; border: 1px solid var(--wine); border-radius: 2px; padding: 8px 16px; min-height: 44px; background: var(--wine); color: var(--canvas); cursor: pointer; font-size: .9375rem; font-weight: 650; line-height: 1.5; text-decoration: none; text-align: center; transition: background-color 160ms ease, color 160ms ease; } button:hover, .button:hover, .header-cta:hover { background: var(--wine-dark); color: var(--canvas); text-decoration: none; } .button-secondary { background: var(--canvas); color: var(--wine); } .button-secondary:hover { background: var(--frame); color: var(--wine-dark); } h1, h2, h3 { font-family: var(--serif); font-weight: 400; line-height: 1.12; } h1 { margin: 0; font-size: clamp(2.5rem, 5.7vw, 4.75rem); letter-spacing: -.045em; } h2 { margin: 0 0 24px; font-size: clamp(1.9rem, 3.5vw, 2.75rem); letter-spacing: -.025em; } h3 { margin: 0; font-size: clamp(1.65rem, 2.8vw, 2.2rem); letter-spacing: -.02em; } p { margin: 0 0 24px; } p:last-child { margin-bottom: 0; } svg { display: block; max-width: 100%; height: auto; } .container { width: min(100%, 1168px); margin-inline: auto; padding-inline: 24px; } main > section { margin-block: 64px; } .eyebrow { margin: 0 0 16px; color: var(--wine); font-size: .75rem; font-weight: 750; letter-spacing: .13em; line-height: 1.6; text-transform: uppercase; } .standfirst { font-family: var(--serif); font-size: clamp(1.25rem, 2.1vw, 1.55rem); line-height: 1.5; } .skip-link { position: absolute; z-index: 100; top: 8px; left: 16px; padding: 16px; background: var(--wine); color: var(--canvas); transform: translateY(-180%); } .skip-link:focus { transform: translateY(0); } .skip-link:hover { background: var(--wine-dark); color: var(--canvas); } .site-header { height: 60px; border-bottom: 1px solid var(--rule); position: relative; z-index: 30; } .header-inner { max-width: 1440px; height: 100%; margin-inline: auto; padding-inline: 24px; display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 24px; } .brand { display: inline-flex; align-items: center; gap: 8px; width: fit-content; color: var(--ink); font-size: 1rem; font-weight: 750; text-decoration: none; letter-spacing: -.035em; } .barley-logo-mark { width: 24px; flex: 0 0 24px; stroke: var(--wine); stroke-width: 2.5; fill: none; } .main-navigation { display: flex; align-items: center; gap: 40px; } .main-navigation a { color: var(--ink); font-size: .875rem; text-decoration: none; } .main-navigation a:hover, .main-navigation a[aria-current="page"] { color: var(--wine); text-decoration: underline; } .header-cta { justify-self: end; min-height: 36px; font-size: .8125rem; padding: 4px 16px; } .menu-toggle { display: none; } .hero { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: 64px; align-items: center; } .hero-copy h1 { margin-bottom: 24px; } .hero-copy > p:not(.eyebrow) { max-width: 56ch; } .hero-copy .button { margin-top: 16px; } .festival-artwork { min-width: 0; } .european-festival-route-illustration { width: 100%; } .art-caption { padding: 16px; color: var(--muted); font-size: .8125rem; border-bottom: 1px solid var(--rule); } .section-heading { padding-top: 24px; border-top: 2px solid var(--wine); display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 40px; align-items: end; margin-bottom: 40px; } .section-heading h2 { margin-bottom: 0; } .section-heading > p { max-width: 48ch; margin-bottom: 0; color: var(--muted); } .guide-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 40px; padding: 0 8px 8px 0; } .guide-card { min-width: 0; } .framed-card { position: relative; isolation: isolate; min-width: 0; } .framed-card::before { content: ""; position: absolute; inset: 8px -8px -8px 8px; border: 1px solid var(--rule); background: var(--frame); color: var(--ink); z-index: -1; } .card-surface { position: relative; height: 100%; display: flex; flex-direction: column; gap: 24px; border: 1px solid var(--rule); padding: 24px; background: var(--canvas); color: var(--ink); text-decoration: none; transition: transform 180ms ease, border-color 180ms ease; } .card-surface > * { margin: 0; } a.card-surface:hover { background: var(--canvas); color: var(--ink); border-color: var(--wine); text-decoration: none; transform: translate(-4px, -4px); } a.card-surface:focus-visible { transform: translate(-4px, -4px); } .card-surface .eyebrow { color: var(--wine); } .card-surface h3 { max-width: 25ch; } .card-meta { display: flex; flex-wrap: wrap; gap: 16px 24px; border-top: 1px solid var(--rule); padding-top: 24px; color: var(--muted); font-size: .8125rem; } .card-surface > .text-link { margin-top: auto; } .text-link { color: var(--wine); font-weight: 650; text-decoration: underline; text-underline-offset: 4px; } .guide-grid > .framed-card:last-child { grid-column: 1 / -1; } .guide-grid > .framed-card:last-child h3 { max-width: 30ch; } .guide-grid > .framed-card:last-child .card-surface > p:not(.eyebrow) { max-width: 78ch; } .supporting-section { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 64px; border-top: 1px solid var(--rule); padding-top: 40px; } .supporting-copy { column-count: 1; } .article-hero { max-width: 1000px; padding-bottom: 40px; border-bottom: 1px solid var(--rule); } .article-hero h1 { margin-bottom: 24px; max-width: 19ch; } .article-hero .standfirst { max-width: 60ch; } .breadcrumb { display: inline-block; margin-bottom: 24px; font-size: .875rem; } .editorial-note { color: var(--muted); font-size: .8125rem; } .article-layout { display: grid; grid-template-columns: minmax(0, 2.15fr) minmax(0, 1fr); gap: 64px; padding-right: 8px; } .article-body { min-width: 0; } .article-body section + section, .legal-body section + section { margin-top: 64px; } .article-body h2, .legal-body h2 { font-size: clamp(1.65rem, 2.6vw, 2.1rem); } .article-body p, .legal-body p { max-width: 72ch; } .article-sidebar { min-width: 0; } .article-sidebar h2 { font-size: 1.8rem; } .article-sidebar p { font-size: .9375rem; } .stats { display: grid; gap: 24px; } .stats > div { display: flex; flex-direction: column; gap: 12px; } .stats dt { order: 2; color: var(--muted); font-size: .75rem; text-transform: uppercase; letter-spacing: .07em; } .stats dd { margin: 0; font-family: var(--serif); font-size: 1.55rem; line-height: 1.25; } .stats > div:last-child dd { font-family: var(--sans); font-size: .9375rem; line-height: 1.5; } .next-guide { border-top: 2px solid var(--wine); padding-top: 40px; } .next-guide h2 { max-width: 26ch; } .next-guide > p:not(.eyebrow) { max-width: 65ch; } .legal-main { max-width: 1016px; } .legal-body { max-width: 800px; margin-bottom: 64px; } .table-wrap { width: 100%; overflow-x: auto; border: 1px solid var(--rule); margin-top: 24px; } table { width: 100%; border-collapse: collapse; font-size: .875rem; table-layout: fixed; } caption { text-align: left; padding: 16px; font-weight: 700; background: var(--sage); color: var(--ink); } th, td { text-align: left; vertical-align: top; padding: 16px; border-bottom: 1px solid var(--rule); } thead { background: var(--frame); color: var(--ink); } tbody tr:last-child > * { border-bottom: 0; } code { font-size: .8125rem; overflow-wrap: anywhere; } .contact-grid { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: 64px; padding-right: 8px; } .contact-form { display: grid; gap: 24px; } .form-field { display: grid; gap: 8px; } .form-field label { font-weight: 650; font-size: .9375rem; } .form-field label span { color: var(--muted); font-weight: 400; } input, textarea, select { display: block; width: 100%; min-width: 0; max-width: 100%; padding: 16px; border: 1px solid #817b6e; border-radius: 2px; background: var(--canvas); color: var(--ink); } textarea { resize: vertical; min-height: 160px; } input[type="checkbox"] { display: inline-block; width: 20px; height: 20px; min-width: 20px; margin: 0; accent-color: var(--wine); } input:disabled { opacity: 1; cursor: not-allowed; } .is-invalid { border: 2px solid var(--wine); background: #fff2ef; color: var(--ink); } .field-error { margin: 0; color: var(--wine-dark); font-size: .875rem; font-weight: 650; } .field-hint, .form-notice { margin: 0; color: var(--muted); font-size: .8125rem; } .contact-form > button { justify-self: start; } .form-status { margin: 0; font-weight: 650; color: var(--wine); } .form-status:empty { display: none; } .form-result { padding: 24px; border: 1px solid #747b5c; background: var(--sage); color: var(--ink); } .form-result h3 { margin-bottom: 24px; font-size: 1.5rem; } .contact-details { display: grid; align-content: start; gap: 64px; min-width: 0; } .contact-details h2 { font-size: 2rem; } address { font-style: normal; margin-bottom: 24px; } .static-map { margin: 0; border: 1px solid var(--rule); } .edinburgh-office-location-illustration { width: 100%; } .static-map figcaption { border-top: 1px solid var(--rule); padding: 16px; font-size: .8125rem; color: var(--muted); } .identity-disclosure { padding-top: 24px; border-top: 1px solid var(--rule); } .identity-disclosure h3 { font-size: 1.5rem; margin-bottom: 24px; } .identity-disclosure p { font-size: .875rem; color: var(--muted); } .site-footer { margin-top: 64px; border-top: 1px solid var(--rule); padding-block: 40px 24px; background: var(--frame); color: var(--ink); } .footer-grid { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr) minmax(0, 1fr); gap: 40px; } .footer-grid > div { display: flex; flex-direction: column; align-items: flex-start; gap: 16px; min-width: 0; } .footer-grid h2 { font-family: var(--sans); font-size: .8125rem; font-weight: 750; text-transform: uppercase; letter-spacing: .06em; margin: 0; } .footer-grid p, .footer-grid a, .footer-button { font-size: .875rem; margin: 0; } .footer-grid .footer-brand { font-size: 1.15rem; color: var(--ink); font-weight: 750; letter-spacing: -.03em; text-decoration: none; } .footer-button { border: 0; border-radius: 0; padding: 0; min-height: 0; background: transparent; color: var(--wine); font-weight: 400; text-decoration: underline; text-underline-offset: 4px; } .footer-button:hover { background: transparent; color: var(--wine-dark); text-decoration: underline; } .footer-bottom { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 16px 40px; margin-top: 40px; padding-top: 24px; position: relative; } .footer-bottom::before { content: ""; position: absolute; height: 1px; top: 0; left: 24px; right: 24px; background: var(--rule); color: var(--ink); } .footer-bottom p { margin: 0; font-size: .75rem; color: var(--muted); } .cookie-banner { position: fixed; z-index: 60; left: 16px; right: 16px; bottom: 16px; max-width: 1136px; max-height: 75vh; max-height: 75dvh; overflow-y: auto; margin-inline: auto; padding: 24px; border: 1px solid var(--wine); box-shadow: 0 8px 32px #27292324; background: var(--canvas); color: var(--ink); } .cookie-intro h2 { font-size: 1.5rem; margin-bottom: 8px; } .cookie-intro p { margin: 0; font-size: .875rem; } .cookie-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; } .cookie-actions button { font-size: .8125rem; } .cookie-options { margin-top: 24px; padding-top: 24px; border-top: 1px solid var(--rule); } .cookie-options h3 { font-size: 1.5rem; margin-bottom: 24px; } .cookie-options label { display: flex; gap: 16px; align-items: center; margin-bottom: 16px; font-size: .9375rem; } .cookie-options p { font-size: .875rem; } body.is-menu-open { overflow-y: hidden; } @media (min-width: 768px) and (max-width: 1000px) { .hero { gap: 40px; } .article-layout { gap: 40px; grid-template-columns: minmax(0, 1.8fr) minmax(0, 1fr); } .contact-grid { gap: 40px; } .header-inner { gap: 16px; } .main-navigation { gap: 24px; } } @media (max-width: 767px) { .container { padding-inline: 16px; } .site-header { height: 52px; } .header-inner { padding-inline: 16px; grid-template-columns: minmax(0, 1fr) auto auto; gap: 8px; } .brand { font-size: .875rem; } .barley-logo-mark { width: 20px; flex-basis: 20px; } .header-cta { min-height: 36px; padding: 4px 16px; } .menu-toggle { display: flex; flex-direction: column; gap: 4px; padding: 8px; width: 40px; min-height: 40px; border-color: transparent; background: transparent; color: var(--ink); } .menu-toggle:hover { background: var(--frame); color: var(--ink); } .menu-toggle span { width: 20px; height: 2px; background: currentColor; color: inherit; transition: transform 160ms ease, opacity 160ms ease; } .menu-toggle[aria-expanded="true"] span:first-child { transform: translateY(6px) rotate(45deg); } .menu-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; } .menu-toggle[aria-expanded="true"] span:last-child { transform: translateY(-6px) rotate(-45deg); } .main-navigation { display: flex; position: fixed; top: 52px; left: 0; right: 0; bottom: 0; height: calc(100vh - 52px); height: calc(100dvh - 52px); padding: 40px 24px; flex-direction: column; align-items: stretch; justify-content: flex-start; gap: 24px; overflow-y: auto; visibility: hidden; pointer-events: none; background: var(--paper); color: var(--ink); } .main-navigation.is-visible { visibility: visible; pointer-events: auto; } .main-navigation a { padding: 16px; border-bottom: 1px solid var(--rule); font-family: var(--serif); font-size: 2rem; } .hero, .supporting-section, .article-layout, .contact-grid { grid-template-columns: minmax(0, 1fr); gap: 64px; } .hero h1 { max-width: 15ch; } .festival-artwork { max-width: 520px; width: 100%; justify-self: center; } .section-heading { grid-template-columns: minmax(0, 1fr); gap: 24px; } .guide-grid { grid-template-columns: minmax(0, 1fr); } .guide-grid > .framed-card:last-child { grid-column: auto; } .article-hero h1 { max-width: 20ch; } .article-layout { padding-right: 8px; } .article-sidebar { margin-top: 0; } .contact-grid { padding-right: 8px; } .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 40px 24px; } .footer-grid > div:first-child { grid-column: 1 / -1; } .footer-bottom::before { left: 16px; right: 16px; } .cookie-banner { max-height: 65vh; max-height: 65dvh; padding: 16px; } .cookie-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); } .cookie-actions button:last-child { grid-column: 1 / -1; } .cookie-actions button { padding-inline: 8px; } table { table-layout: auto; } thead { display: none; } tbody, tr, td, th { display: block; } tbody tr { padding: 16px; border-bottom: 1px solid var(--rule); } tbody tr:last-child { border-bottom: 0; } td, tbody th { padding: 0; border: 0; margin-bottom: 16px; } tbody td:last-child { margin-bottom: 0; } tbody td:nth-child(2)::before { content: "Name / provider: "; font-weight: 700; } tbody td:nth-child(3)::before { content: "Purpose: "; font-weight: 700; } tbody td:nth-child(4)::before { content: "Duration: "; font-weight: 700; } } @media (max-width: 399px) { .brand { font-size: .75rem; gap: 4px; } .barley-logo-mark { width: 16px; flex-basis: 16px; } .header-cta { padding-inline: 8px; } .menu-toggle { width: 36px; } h1 { font-size: 2.45rem; } .card-surface { padding: 16px; gap: 24px; } .card-meta { flex-direction: column; gap: 24px; } .cookie-actions { grid-template-columns: minmax(0, 1fr); } .cookie-actions button:last-child { grid-column: auto; } .footer-grid { grid-template-columns: minmax(0, 1fr); } .footer-grid > div:first-child { grid-column: auto; } .form-result { padding: 16px; } .button { width: 100%; } } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition: none !important; animation: none !important; } html { scroll-behavior: auto; } } @media print { .site-header, .site-footer, .cookie-banner, .skip-link, .menu-toggle { display: none !important; } body { background: #fff; color: #000; font-size: 11pt; } .container { max-width: none; padding: 0; } .article-layout { display: block; } .article-sidebar { margin-top: 40px; } .framed-card::before { display: none; } .card-surface { background: #fff; color: #000; } a { color: #000; } main > section, .article-body section + section, .legal-body section + section { margin-block: 24px; } } ===ENDFILE=== ===FILE: script.js=== (() => { "use strict"; const menuToggle = document.querySelector("[data-menu-toggle]"); const navigation = document.querySelector("[data-navigation]"); const cookieBanner = document.querySelector("[data-cookie-banner]"); const cookieOptions = document.querySelector("[data-cookie-options]"); const cookieSave = document.querySelector("[data-cookie-save]"); const cookieChoiceButtons = document.querySelectorAll("[data-cookie-choice]"); const mobileViewport = window.matchMedia("(max-width: 767px)"); function setMenu(open, restoreFocus = false) { menuToggle.setAttribute("aria-expanded", String(open)); menuToggle.setAttribute("aria-label", open ? "Close navigation" : "Open navigation"); navigation.classList.toggle("is-visible", open); document.body.classList.toggle("is-menu-open", open); if (restoreFocus) menuToggle.focus(); } menuToggle.addEventListener("click", () => { const open = menuToggle.getAttribute("aria-expanded") !== "true"; setMenu(open); if (open) navigation.querySelector("a").focus(); }); navigation.addEventListener("click", event => { if (event.target.closest("a")) setMenu(false); }); document.addEventListener("keydown", event => { if (menuToggle.getAttribute("aria-expanded") !== "true") return; if (event.key === "Escape") { event.preventDefault(); setMenu(false, true); return; } if (event.key === "Tab" && mobileViewport.matches) { const links = Array.from(navigation.querySelectorAll("a")); const focusable = [menuToggle, ...links]; const first = focusable[0]; const last = focusable[focusable.length - 1]; if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } else if (!focusable.includes(document.activeElement)) { event.preventDefault(); first.focus(); } } }); function handleViewportChange() { if (!mobileViewport.matches) setMenu(false); } if (mobileViewport.addEventListener) { mobileViewport.addEventListener("change", handleViewportChange); } else { mobileViewport.addListener(handleViewportChange); } const storageKey = "ssp-consent"; const policyVersion = 1; const maxAge = 180 * 24 * 60 * 60 * 1000; let preferenceReturnTarget = null; function readConsent() { try { const raw = localStorage.getItem(storageKey); if (!raw) return null; const stored = JSON.parse(raw); const age = Date.now() - stored.savedAt; const valid = stored.version === policyVersion && ["accept", "reject", "custom"].includes(stored.choice) && stored.essential === true && stored.analytics === false && stored.functional === false && typeof stored.savedAt === "number" && Number.isFinite(age) && age >= 0 && age < maxAge; if (valid) return stored; localStorage.removeItem(storageKey); } catch { // A blocked or unavailable storage API must not prevent reading the site. } return null; } function closeCookieBanner() { const hadFocus = cookieBanner.contains(document.activeElement); cookieBanner.hidden = true; cookieOptions.hidden = true; if (hadFocus) { const fallback = Array.from(cookieChoiceButtons).find(button => button.dataset.cookieChoice === "preferences" && !cookieBanner.contains(button) ); const target = preferenceReturnTarget && preferenceReturnTarget.isConnected ? preferenceReturnTarget : fallback; if (target) target.focus({ preventScroll: true }); } preferenceReturnTarget = null; } function saveConsent(choice) { const consent = { version: policyVersion, choice, essential: true, analytics: false, functional: false, savedAt: Date.now() }; try { localStorage.setItem(storageKey, JSON.stringify(consent)); } catch { // The current page can still dismiss the banner without persistence. } closeCookieBanner(); } cookieChoiceButtons.forEach(button => { button.addEventListener("click", () => { const choice = button.dataset.cookieChoice; if (choice === "preferences") { if (!cookieBanner.contains(button)) preferenceReturnTarget = button; setMenu(false); cookieBanner.hidden = false; cookieOptions.hidden = false; cookieSave.focus({ preventScroll: true }); cookieSave.scrollIntoView({ block: "nearest", behavior: "auto" }); } else if (choice === "accept" || choice === "reject") { saveConsent(choice); } }); }); cookieSave.addEventListener("click", () => saveConsent("custom")); cookieBanner.hidden = Boolean(readConsent()); window.addEventListener("storage", event => { if (event.key !== storageKey && event.key !== null) return; if (readConsent()) closeCookieBanner(); else cookieBanner.hidden = false; }); const contactForm = document.querySelector("[data-contact-form]"); if (!contactForm) return; // Retain browser validation for the no-JavaScript mailto fallback. contactForm.noValidate = true; const fields = Array.from(contactForm.querySelectorAll("[data-field]")); const errors = new Map( Array.from(contactForm.querySelectorAll("[data-error-for]")).map(element => [ element.dataset.errorFor, element ]) ); const subject = contactForm.querySelector("[data-contact-subject]"); const status = contactForm.querySelector("[data-form-status]"); const result = contactForm.querySelector("[data-contact-result]"); const draftLink = contactForm.querySelector("[data-mail-draft]"); let attemptedSubmission = false; function errorMessage(field) { const value = field.value.trim(); if (field.dataset.field === "name") { if (value.length < 2) return "Please enter a name of at least 2 characters."; if (value.length > 80) return "Please keep your name to 80 characters."; if (/[\r\n]/.test(value)) return "Please enter your name on one line."; } if (field.dataset.field === "email") { if (!value || field.validity.typeMismatch || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) { return "Please enter a complete email address, such as [email protected]."; } if (value.length > 254) return "Please use an email address under 255 characters."; } if (field.dataset.field === "message") { if (value.length < 20) return "Please add a little more detail: at least 20 characters."; if (value.length > 1500) return "Please shorten your message to 1,500 characters."; } return ""; } function validateField(field) { const message = errorMessage(field); const error = errors.get(field.dataset.field); 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", () => { if (attemptedSubmission || field.value.length > 0) validateField(field); }); }); contactForm.addEventListener("input", event => { result.hidden = true; draftLink.setAttribute("href", "mailto:[email protected]"); status.textContent = ""; if (attemptedSubmission && event.target.matches("[data-field]")) { validateField(event.target); } }); subject.addEventListener("change", () => { result.hidden = true; draftLink.setAttribute("href", "mailto:[email protected]"); status.textContent = ""; }); contactForm.addEventListener("submit", event => { event.preventDefault(); attemptedSubmission = true; fields.forEach(field => { field.value = field.value.trim(); }); const invalidFields = fields.filter(field => !validateField(field)); if (invalidFields.length > 0) { result.hidden = true; status.textContent = `Please correct ${invalidFields.length === 1 ? "the highlighted field" : "the highlighted fields"} before preparing your draft.`; invalidFields[0].focus(); return; } const values = Object.fromEntries( fields.map(field => [field.dataset.field, field.value]) ); const body = [ `Name: ${values.name}`, `Reply email: ${values.email}`, "", values.message, "", "Prepared using scoutshelfstorypuls.mom" ].join("\r\n"); const draft = `mailto:[email protected]?subject=${encodeURIComponent(subject.value)}&body=${encodeURIComponent(body)}`; draftLink.setAttribute("href", draft); result.hidden = false; status.textContent = "Your email draft is ready. Nothing has been sent."; draftLink.focus(); }); })(); ===ENDFILE===