Prepare your message

This form creates an email draft link. It does not send a message or upload your details. You’ll review and send through your own email application.

20–1,200 characters. For a correction, include the page title and the detail you’re questioning. Please omit sensitive personal information.

Your privacy preferences

Essential storage remembers this choice. Analytics and advertising are not installed.

Optional preferences expire after 180 days. You can change your mind in the footer.

===ENDFILE=== ===FILE: styles.css=== /* Editorial aesthetic + framed-canvas cards, shared across all ten pages. */ :root { --cream: #f7f1e5; --paper: #fffaf1; --sand: #e8ddc6; --terracotta: #b96240; --red: #9d3529; --deep-red: #78261e; --blue: #245c73; --ink: #302d27; --muted: #625b50; --line: #c9bca7; --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-bottom: 240px; } body { margin: 0; background: var(--cream); color: var(--ink); font-family: Arial, Helvetica, sans-serif; font-size: 16px; line-height: 1.65; } body, h1, h2, h3, p, a, nav, button, label, address, td, th, code, output { overflow-wrap: anywhere; } div, section, article, aside, nav, main, form, figure, a, span { min-width: 0; } h1, h2, h3, p, figure { margin: 0; } h1, h2, h3 { font-family: Georgia, "Times New Roman", serif; font-weight: 400; line-height: 1.08; letter-spacing: -.035em; } h1 { font-size: clamp(2.75rem, 5.7vw, 5.5rem); } h2 { font-size: clamp(2rem, 3.2vw, 3.25rem); } h3 { font-size: 1.75rem; } em { color: var(--red); font-weight: 400; } a { color: var(--blue); text-underline-offset: 4px; text-decoration-thickness: 1px; } a:hover { color: var(--deep-red); } button, input, select, textarea { font: inherit; min-width: 0; max-width: 100%; } button { cursor: pointer; } button, a, input, select, textarea { -webkit-tap-highlight-color: transparent; } :focus-visible { outline: 3px solid var(--blue); outline-offset: 4px; } .site-footer :focus-visible { outline-color: #f2dba9; } svg { display: block; max-width: 100%; height: auto; } [hidden] { display: none !important; } .shell { width: min(1200px, calc(100% - 80px)); margin-inline: auto; } .section { margin-block: 64px; } .skip-link { position: fixed; top: 8px; left: 16px; z-index: 100; padding: 16px 24px; background: var(--paper); color: var(--ink); transform: translateY(-160%); } .skip-link:focus { transform: translateY(0); } .site-header { height: 64px; padding-inline: 40px; display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 24px; border-bottom: 1px solid var(--line); } .brand { display: flex; align-items: center; gap: 8px; color: var(--red); text-decoration: none; font-size: .91rem; font-weight: 700; line-height: 1.1; letter-spacing: -.03em; } .brand svg { width: 32px; height: 32px; flex: 0 0 32px; } .brand-caption { display: block; margin-top: 4px; font-size: .55rem; letter-spacing: .15em; color: var(--muted); } .main-nav { display: flex; align-items: center; gap: 24px; } .main-nav a { font-size: .85rem; color: var(--ink); text-decoration: none; } .main-nav a:hover, .main-nav a[aria-current="page"] { color: var(--red); text-decoration: underline; } .header-cta { justify-self: end; } .button { display: inline-flex; align-items: center; justify-content: center; gap: 16px; padding: 16px 24px; border: 1px solid var(--red); border-radius: 8px; background: var(--red); color: #fffaf1; font-size: .94rem; font-weight: 700; text-decoration: none; line-height: 1.4; text-align: center; transition: background-color .2s ease, box-shadow .2s ease; } .button:hover { background: var(--deep-red); color: #fffaf1; border-color: var(--deep-red); box-shadow: 0 4px 12px #302d2714; } .button-small { padding: 8px 16px; font-size: .8rem; } .button-outline { background: transparent; color: var(--red); border-color: var(--red); } .button-outline:hover { background: var(--sand); color: var(--deep-red); } .text-link { font-weight: 700; color: var(--red); text-decoration: none; display: inline-block; } .text-link:hover { text-decoration: underline; } .text-button { border: 0; padding: 8px 0; background: transparent; color: var(--red); font-weight: 700; text-decoration: underline; text-underline-offset: 4px; } .text-button:hover { color: var(--deep-red); } .button-row { display: flex; align-items: center; flex-wrap: wrap; gap: 24px; } .menu-toggle { display: none; border: 1px solid var(--line); border-radius: 4px; padding: 8px; background: var(--paper); color: var(--ink); font-size: .8rem; } .hero { display: grid; grid-template-columns: 1fr 1fr; align-items: center; gap: 64px; padding-block: 64px; } .hero-copy { display: flex; flex-direction: column; gap: 24px; } .hero-copy h1 { max-width: 10ch; } .eyebrow { font-size: .7rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--red); } .lede { font-size: 1.12rem; line-height: 1.7; color: var(--muted); max-width: 65ch; } .small-note { font-size: .78rem; line-height: 1.6; color: var(--muted); } .framed-art { margin: 0; padding: 0 8px 8px 0; position: relative; isolation: isolate; } .framed-art::before { content: ""; position: absolute; inset: 8px 0 0 8px; z-index: -1; border-radius: 16px; background: var(--blue); color: var(--paper); } .framed-art > svg { width: 100%; border-radius: 16px; } .hero-art { transform: rotate(1deg); } .art-caption { position: relative; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px; padding: 16px; background: var(--blue); color: var(--paper); font-size: .62rem; font-weight: 700; letter-spacing: .08em; border-radius: 0 0 16px 16px; } .hero-art > svg { border-radius: 16px 16px 0 0; } .intro-band { background: var(--sand); color: var(--ink); padding-block: 40px; border-block: 1px solid var(--line); } .intro-inner { display: grid; grid-template-columns: 1fr 1.3fr; align-items: center; gap: 64px; } .intro-inner > p { font-family: Georgia, serif; font-size: clamp(2rem, 3.4vw, 3.25rem); line-height: 1.12; } .intro-inner h2 { font-family: Arial, sans-serif; font-size: 1.15rem; font-weight: 700; letter-spacing: -.02em; margin-bottom: 16px; } .intro-inner div p { color: var(--muted); max-width: 60ch; } .section-heading { display: flex; align-items: end; justify-content: space-between; gap: 24px; margin-bottom: 40px; } .section-heading h2 { margin-top: 16px; } .section-heading > p { font-size: .9rem; color: var(--muted); } .collection { display: grid; grid-auto-flow: column; grid-auto-columns: min(86%, 360px); gap: 24px; overflow-x: auto; scroll-snap-type: x proximity; padding: 8px 0 24px; scrollbar-color: var(--red) var(--sand); scrollbar-width: thin; } .frame-card { position: relative; isolation: isolate; padding: 0 8px 8px 0; scroll-snap-align: start; } .frame-card::before { content: ""; position: absolute; inset: 8px 0 0 8px; z-index: -1; background: var(--terracotta); color: var(--ink); border-radius: 16px; } .card-surface { height: 100%; border: 1px solid var(--line); border-radius: 16px; background: var(--paper); color: var(--ink); transition: transform .25s ease, box-shadow .25s ease; } .frame-card:hover .card-surface { transform: translateY(-4px); box-shadow: 0 8px 16px #302d2712; } .card-art { width: 100%; border-radius: 15px 15px 0 0; } .card-body { padding: 24px; display: flex; flex-direction: column; gap: 24px; } .card-body p:not(.eyebrow) { color: var(--muted); font-size: .94rem; } .card-body h3 { min-height: 3.1em; } .card-body .text-link { margin-top: auto; } .route-section { display: grid; grid-template-columns: 1fr 1fr; align-items: center; gap: 64px; } .route-section h2 { margin-block: 16px 24px; } .route-section p:not(.eyebrow) { color: var(--muted); } .route-controls { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 24px; } .route-button { border: 1px solid var(--line); padding: 8px 16px; border-radius: 24px; background: var(--paper); color: var(--ink); font-size: .78rem; } .route-button:hover { border-color: var(--blue); } .route-button[aria-pressed="true"] { background: var(--blue); color: var(--paper); border-color: var(--blue); } .route-detail { min-height: 6em; padding-block: 24px; font-size: .9rem; } .article-heading { padding-block: 64px; border-bottom: 1px solid var(--line); } .breadcrumb { display: inline-block; margin-bottom: 40px; font-size: .82rem; } .article-heading h1 { margin-block: 16px 24px; max-width: 20ch; } .article-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 24px; margin-top: 24px; font-size: .8rem; color: var(--muted); } .article-tools .text-button.is-saved { color: var(--blue); } .article-heading > .small-note:empty { display: none; } .article-layout { display: grid; grid-template-columns: minmax(0, 240px) minmax(0, 1fr); gap: 64px; padding-block: 64px; } .article-aside nav { display: flex; flex-direction: column; gap: 16px; margin-top: 24px; font-size: .9rem; } .article-aside nav a { text-decoration: none; } .article-aside nav a:hover { text-decoration: underline; } .aside-note { border-top: 1px solid var(--line); margin-top: 40px; padding-top: 24px; font-size: .85rem; } .aside-note p { margin-top: 16px; color: var(--muted); } .article-copy { max-width: 760px; } .article-copy > section + section { margin-top: 64px; } .article-copy section h2 { font-size: clamp(1.8rem, 3vw, 2.5rem); margin-bottom: 24px; } .article-copy section h3 { font-size: 1.35rem; margin-block: 24px 16px; } .article-copy p + p { margin-top: 16px; } .article-copy p { line-height: 1.85; } .next-guide { border-top: 1px solid var(--line); margin-top: 64px; padding-top: 40px; display: flex; align-items: flex-start; flex-direction: column; gap: 24px; } .next-guide h2 { font-size: 2rem; } .price-tag { display: inline-block; padding: 8px 16px; margin-bottom: 24px; border-radius: 4px; background: var(--red); color: var(--paper); font-size: .8rem; font-weight: 700; letter-spacing: .1em; transform: rotate(-3deg); animation: tag-sway 5s ease-in-out infinite; } .price-tag-blue { background: var(--blue); color: var(--paper); } @keyframes tag-sway { 0%, 100% { transform: rotate(-3deg); } 50% { transform: rotate(2deg); } } .budget-section { border-top: 1px solid var(--line); padding-top: 40px; } .budget-section .eyebrow { margin-bottom: 16px; } .budget-form { margin-top: 24px; display: grid; grid-template-columns: 1fr 1fr; gap: 24px; } .budget-form .button, .budget-result { grid-column: 1 / -1; } .budget-result { display: block; padding: 24px; background: var(--sand); color: var(--ink); border-radius: 8px; } .legal-page, .contact-page { padding-block: 64px; } .legal-heading { margin-bottom: 64px; } .legal-heading h1 { margin-block: 16px 24px; } .legal-heading .small-note { margin-top: 24px; } .legal-copy { max-width: 850px; } .legal-copy section + section { margin-top: 64px; } .legal-copy h2 { font-size: 1.9rem; margin-bottom: 24px; } .legal-copy p + p { margin-top: 16px; } .legal-copy p { line-height: 1.85; } .legal-copy section > .button { margin-top: 24px; } .table-scroll { width: 100%; overflow-x: auto; margin-top: 24px; border: 1px solid var(--line); border-radius: 8px; } table { width: 100%; border-collapse: collapse; font-size: .86rem; text-align: left; } caption { text-align: left; padding: 16px; color: var(--muted); } th, td { padding: 16px; vertical-align: top; border-top: 1px solid var(--line); } thead th { background: var(--sand); color: var(--ink); } code { font-size: .82rem; white-space: normal; } .contact-layout { display: grid; grid-template-columns: 1.2fr 1fr; gap: 64px; align-items: start; } .contact-surface { padding: 40px; display: flex; flex-direction: column; gap: 24px; } .contact-surface h2 { font-size: 2rem; } .contact-frame:hover .card-surface { transform: translateY(0); } .contact-form { display: flex; flex-direction: column; gap: 24px; } .form-field { display: flex; flex-direction: column; gap: 8px; } .form-field > label { font-weight: 700; font-size: .9rem; } .form-field label span { font-weight: 400; } input, textarea, select { width: 100%; border: 1px solid #8b8070; border-radius: 4px; padding: 16px; background: #fffdf8; color: var(--ink); } textarea { resize: vertical; } input[type="checkbox"] { width: 20px; height: 20px; margin: 4px 0 0; padding: 0; flex: 0 0 20px; accent-color: var(--red); } .check-row { display: flex; align-items: flex-start; gap: 16px; font-size: .9rem; } .is-invalid { border-color: var(--red); box-shadow: 0 0 0 1px var(--red); } .field-error { color: var(--red); font-size: .8rem; } .form-status { font-size: .9rem; color: var(--blue); } .form-status:empty { display: none; } .contact-details section + section, .contact-map { margin-top: 64px; } .contact-details h2 { font-size: 1.8rem; margin-block: 16px 24px; } .contact-details p + p { margin-top: 24px; } address { font-style: normal; } address + p { margin-top: 24px; } .contact-map figcaption { padding: 24px; background: var(--paper); color: var(--muted); border: 1px solid var(--line); border-radius: 0 0 16px 16px; font-size: .8rem; } .contact-map > svg { border-radius: 16px 16px 0 0; } .site-footer { margin-top: 64px; padding-block: 64px 24px; background: #342f29; color: #f7f1e5; } .footer-grid { display: grid; grid-template-columns: 1.7fr 1fr 1fr; gap: 64px; } .site-footer a { color: #f7f1e5; } .site-footer a:hover { color: #f1cda4; } .footer-brand { font-family: Georgia, serif; font-size: 1.5rem; text-decoration: none; } .footer-grid h2 { font-family: Arial, sans-serif; font-weight: 700; font-size: .85rem; letter-spacing: .04em; margin-bottom: 24px; } .footer-grid > div > a:not(.footer-brand) { display: block; font-size: .85rem; margin-bottom: 8px; } .footer-grid p { margin-block: 16px; font-size: .9rem; } .site-footer .small-note { color: #d6cbbb; font-size: .75rem; } .footer-preferences { border: 0; padding: 8px 0; background: transparent; color: #f7f1e5; text-decoration: underline; text-underline-offset: 4px; font-size: .85rem; text-align: left; } .footer-preferences:hover { color: #f1cda4; } .footer-bottom { border-top: 1px solid #6d6255; padding-top: 24px; margin-top: 40px; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 16px; font-size: .73rem; color: #d6cbbb; } .cookie-banner { position: fixed; z-index: 30; bottom: 16px; left: 24px; right: 24px; padding: 24px; display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 16px 24px; max-height: 65dvh; overflow-y: auto; border: 1px solid var(--line); border-radius: 16px; background: var(--paper); color: var(--ink); box-shadow: 0 8px 40px #302d2729; } .cookie-banner strong { font-family: Georgia, serif; font-size: 1.2rem; font-weight: 400; } .cookie-banner p { margin-top: 8px; max-width: 75ch; font-size: .8rem; } .cookie-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; } .cookie-actions .text-button { font-size: .8rem; } .cookie-banner > .small-note { grid-column: 1 / -1; margin: 0; } .cookie-banner > .small-note:empty { display: none; } .preferences-dialog { width: min(560px, calc(100% - 32px)); max-height: calc(100dvh - 32px); padding: 40px; border: 1px solid var(--line); border-radius: 16px; background: var(--paper); color: var(--ink); box-shadow: 0 8px 40px #302d2733; } .preferences-dialog::backdrop { background: #201d19a6; color: var(--paper); } .preferences-dialog h2 { font-size: 2rem; } .preferences-dialog > * + * { margin-top: 24px; } .js-reveal { opacity: 0; transform: translateY(16px); transition: opacity .65s ease, transform .65s ease; } .js-reveal.is-visible { opacity: 1; transform: translateY(0); } @media (min-width: 768px) and (max-width: 1050px) { .site-header { padding-inline: 24px; grid-template-columns: minmax(0, 1.2fr) auto minmax(0, .7fr); gap: 16px; } .brand { font-size: .77rem; } .main-nav { gap: 16px; } .header-cta { font-size: .7rem; padding-inline: 8px; } .hero, .route-section, .contact-layout { gap: 40px; } .cookie-banner { grid-template-columns: 1fr; } } @media (max-width: 767px) { .shell { width: calc(100% - 48px); } .site-header { height: 52px; padding-inline: 24px; gap: 8px; grid-template-columns: minmax(0, 1fr) auto auto; position: relative; z-index: 40; } .brand { font-size: .72rem; } .brand svg { width: 24px; height: 24px; flex-basis: 24px; } .brand-caption { font-size: .48rem; letter-spacing: .08em; } .header-cta { padding: 8px; font-size: .66rem; max-width: 80px; } .menu-toggle { display: inline-flex; gap: 8px; align-items: center; } .main-nav { display: none; position: fixed; inset: 52px 0 0; background: var(--cream); color: var(--ink); padding: 40px 24px; align-items: stretch; flex-direction: column; gap: 24px; overflow-y: auto; } .main-nav.is-open { display: flex; } .main-nav a { font-family: Georgia, serif; font-size: 2rem; border-bottom: 1px solid var(--line); padding-bottom: 24px; } .hero { grid-template-columns: 1fr; gap: 40px; } .hero-copy h1 { max-width: 13ch; } .hero-art { transform: rotate(0); } .framed-art, .frame-card { padding: 0; } .framed-art::before, .frame-card::before { inset: 0; } .intro-inner { grid-template-columns: 1fr; gap: 24px; } .section-heading { align-items: flex-start; flex-direction: column; } .route-section { grid-template-columns: 1fr; gap: 40px; } .route-detail { min-height: 0; } .article-layout { grid-template-columns: 1fr; gap: 64px; } .article-aside nav { flex-direction: row; flex-wrap: wrap; gap: 16px 24px; } .aside-note { margin-top: 24px; } .article-heading h1 { max-width: 18ch; } .contact-layout { grid-template-columns: 1fr; gap: 64px; } .contact-surface { padding: 24px; } .footer-grid { grid-template-columns: 1fr 1fr; gap: 40px 24px; } .footer-grid > div:first-child { grid-column: 1 / -1; } .cookie-banner { left: 16px; right: 16px; bottom: 8px; padding: 16px; grid-template-columns: 1fr; gap: 16px; } .cookie-actions { gap: 8px 16px; } .cookie-actions .button { flex: 1 1 140px; } .preferences-dialog { padding: 24px; } .budget-form { grid-template-columns: 1fr; } .table-scroll table { font-size: .78rem; } } @media (max-width: 399px) { .shell { width: calc(100% - 32px); } .site-header { padding-inline: 16px; gap: 4px; } .brand { font-size: .65rem; gap: 4px; } .brand svg { width: 20px; height: 20px; flex-basis: 20px; } .brand-caption { font-size: .43rem; } .header-cta { max-width: 64px; padding: 4px 8px; font-size: .61rem; } .menu-toggle { gap: 4px; font-size: .68rem; padding: 8px 4px; } h1 { font-size: 2.7rem; } .button-row { align-items: stretch; flex-direction: column; } .card-body { padding: 24px; } .footer-grid { grid-template-columns: 1fr; } .footer-grid > div:first-child { grid-column: auto; } .cookie-banner strong { font-size: 1.05rem; } .cookie-banner p { font-size: .75rem; } .cookie-actions .button { flex-basis: 100%; } .preferences-dialog { padding: 24px 16px; } .contact-surface { padding: 24px 16px; } th, td { padding: 16px; } } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } } ===ENDFILE=== ===FILE: script.js=== (() => { "use strict"; const consentKey = "rsg-consent.v1"; const savedKey = "rsg-saved-guides.v1"; const duration = 180 * 24 * 60 * 60 * 1000; const knownGuides = new Set([ "market-guide", "spanish-paintings", "vintage-posters", "ceramics-photography", "affordable-finds" ]); function readStorage(key) { try { const raw = localStorage.getItem(key); return raw ? JSON.parse(raw) : null; } catch { return null; } } function writeStorage(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); return true; } catch { return false; } } function removeStorage(key) { try { localStorage.removeItem(key); return true; } catch { return false; } } function validConsent(value) { return value && value.version === 1 && typeof value.functional === "boolean" && ["accept", "reject", "custom"].includes(value.choice) && Number.isFinite(value.expires) && value.expires > Date.now(); } let consent = readStorage(consentKey); if (!validConsent(consent)) { consent = null; removeStorage(consentKey); removeStorage(savedKey); } if (consent && !consent.functional) removeStorage(savedKey); const banner = document.querySelector("[data-cookie-banner]"); const cookieMessage = document.querySelector("[data-cookie-message]"); const dialog = document.querySelector("[data-cookie-dialog]"); const functionalChoice = document.querySelector("[data-functional-choice]"); const savePreferences = document.querySelector("[data-save-preferences]"); const closePreferences = document.querySelector("[data-close-preferences]"); const saveGuide = document.querySelector("[data-save-guide]"); const saveStatus = document.querySelector("[data-save-status]"); let preferenceTrigger = null; function getSavedGuides() { if (!consent || !consent.functional || consent.expires <= Date.now()) { removeStorage(savedKey); return []; } const saved = readStorage(savedKey); if (!saved || !Array.isArray(saved.guides) || !Number.isFinite(saved.expires) || saved.expires <= Date.now()) { removeStorage(savedKey); return []; } return [...new Set(saved.guides.filter(id => knownGuides.has(id)))]; } function updateSavedButton() { if (!saveGuide) return; const saved = getSavedGuides().includes(saveGuide.dataset.saveGuide); saveGuide.classList.toggle("is-saved", saved); saveGuide.textContent = saved ? "Saved · remove guide" : "Save this guide"; saveGuide.setAttribute("aria-pressed", String(saved)); } function openPreferences(trigger) { preferenceTrigger = trigger || document.activeElement; functionalChoice.checked = Boolean(consent && consent.functional); if (!dialog.open) dialog.showModal(); } function closePreferenceDialog() { if (dialog.open) dialog.close(); } dialog.addEventListener("close", () => { if (preferenceTrigger && document.contains(preferenceTrigger)) { preferenceTrigger.focus(); } }); function applyConsent(choice, functional) { consent = { version: 1, choice, functional, analytics: false, expires: Date.now() + duration }; const persisted = writeStorage(consentKey, consent); const removed = functional || removeStorage(savedKey); closePreferenceDialog(); banner.hidden = persisted && removed; if (!persisted || !removed) { cookieMessage.textContent = !persisted ? "Your choice applies on this page, but browser storage is unavailable. It may be requested again after navigation." : "Optional features are off. Your browser prevented removal of existing data; clear this site’s data in browser settings."; } else { cookieMessage.textContent = ""; } if (saveStatus) { saveStatus.textContent = functional ? "Functional storage is enabled. Use “Save this guide” to remember this page." : "Functional storage is off. Saved guides have been cleared where browser permissions allow."; } updateSavedButton(); } banner.hidden = Boolean(consent); document.querySelectorAll("[data-cookie-choice]").forEach(button => { button.addEventListener("click", () => { const choice = button.dataset.cookieChoice; if (choice === "preferences") { openPreferences(button); } else { applyConsent(choice, choice === "accept"); } }); }); savePreferences.addEventListener("click", () => { applyConsent("custom", functionalChoice.checked); }); closePreferences.addEventListener("click", closePreferenceDialog); if (saveGuide) { updateSavedButton(); saveGuide.addEventListener("click", () => { if (!consent || consent.expires <= Date.now() || !consent.functional) { saveStatus.textContent = "Allow optional saved-guide storage in Preferences, then select Save again."; openPreferences(saveGuide); return; } const id = saveGuide.dataset.saveGuide; const guides = getSavedGuides(); const alreadySaved = guides.includes(id); const nextGuides = alreadySaved ? guides.filter(item => item !== id) : [...guides, id]; const successful = writeStorage(savedKey, { guides: nextGuides, expires: Math.min(Date.now() + duration, consent.expires) }); saveStatus.textContent = successful ? (alreadySaved ? "Guide removed from this browser’s saved collection." : "Guide saved on this device. Return to this page to remove it.") : "This browser is blocking storage. The guide was not saved; use your browser’s bookmark feature instead."; updateSavedButton(); }); } window.addEventListener("storage", event => { if (event.key !== consentKey && event.key !== savedKey && event.key !== null) return; const next = readStorage(consentKey); consent = validConsent(next) ? next : null; if (!consent || !consent.functional) removeStorage(savedKey); banner.hidden = Boolean(consent); functionalChoice.checked = Boolean(consent && consent.functional); updateSavedButton(); }); const menuToggle = document.querySelector("[data-menu-toggle]"); const menu = document.querySelector("[data-menu]"); const mobileQuery = window.matchMedia("(max-width: 767px)"); function setMenu(open, focusFirst = false) { menu.classList.toggle("is-open", open); menuToggle.setAttribute("aria-expanded", String(open)); menuToggle.setAttribute("aria-label", open ? "Close navigation" : "Open navigation"); if (focusFirst && open) menu.querySelector("a").focus(); } menuToggle.addEventListener("click", () => { setMenu(menuToggle.getAttribute("aria-expanded") !== "true", true); }); menu.addEventListener("click", event => { if (event.target.closest("a") && mobileQuery.matches) setMenu(false); }); document.addEventListener("keydown", event => { if (!mobileQuery.matches || !menu.classList.contains("is-open") || dialog.open) return; if (event.key === "Escape") { setMenu(false); menuToggle.focus(); } if (event.key === "Tab") { const links = Array.from(menu.querySelectorAll("a")); const items = [menuToggle, ...links]; const current = items.indexOf(document.activeElement); if (event.shiftKey && (current === 0 || current === -1)) { event.preventDefault(); items[items.length - 1].focus(); } else if (!event.shiftKey && (current === items.length - 1 || current === -1)) { event.preventDefault(); items[0].focus(); } } }); mobileQuery.addEventListener("change", () => setMenu(false)); const routeDetail = document.querySelector("[data-route-detail]"); const routeDescriptions = { start: "Begin around Plaza de Cascorro. Orient yourself before browsing, and keep valuables secure in the busiest stretches.", browse: "Follow Ribera de Curtidores and compare objects before buying. Ask permission to inspect backs, bases and any labels.", pause: "Pause in the wider La Latina neighbourhood, then revisit your shortlist. Check the complete cost before returning to a seller." }; document.querySelectorAll("[data-route-stop]").forEach(button => { button.addEventListener("click", () => { document.querySelectorAll("[data-route-stop]").forEach(stop => { stop.setAttribute("aria-pressed", String(stop === button)); }); routeDetail.textContent = routeDescriptions[button.dataset.routeStop]; }); }); const budgetForm = document.querySelector("[data-budget-form]"); if (budgetForm) { const total = document.querySelector("[data-budget-total]"); const extras = document.querySelector("[data-budget-extras]"); const result = document.querySelector("[data-budget-result]"); const currency = new Intl.NumberFormat("en-IE", { style: "currency", currency: "EUR" }); budgetForm.addEventListener("submit", event => { event.preventDefault(); if (!budgetForm.reportValidity()) return; const balance = Number(total.value) - Number(extras.value); result.textContent = balance < 0 ? `Your additional costs exceed the budget by ${currency.format(Math.abs(balance))}. Increase the ceiling or reduce those costs before buying.` : `${currency.format(balance)} remains for the object. This is a personal spending ceiling, not an appraisal.`; }); } const contactForm = document.querySelector("[data-contact-form]"); if (contactForm) { const fields = Array.from(contactForm.querySelectorAll("[data-contact-field]")); const status = document.querySelector("[data-contact-status]"); const draft = document.querySelector("[data-email-draft]"); const errors = Object.fromEntries( Array.from(contactForm.querySelectorAll("[data-error-for]")) .map(element => [element.dataset.errorFor, element]) ); function validateField(field) { const key = field.dataset.contactField; const value = field.value.trim(); let message = ""; if (key === "name" && (value.length < 2 || value.length > 80)) { message = "Please enter a name between 2 and 80 characters."; } else if (key === "email" && (!value || field.validity.typeMismatch || value.length > 120)) { message = "Please enter a valid email address."; } else if (key === "topic" && !value) { message = "Please choose a topic."; } else if (key === "message" && (value.length < 20 || value.length > 1200)) { message = "Please write between 20 and 1,200 characters."; } else if (key === "acknowledgement" && !field.checked) { message = "Please confirm that you understand how the email draft works."; } field.classList.toggle("is-invalid", Boolean(message)); field.setAttribute("aria-invalid", String(Boolean(message))); errors[key].textContent = message; errors[key].hidden = !message; return !message; } fields.forEach(field => { field.addEventListener("blur", () => validateField(field)); field.addEventListener("input", () => { draft.hidden = true; draft.href = "mailto:[email protected]"; status.textContent = ""; if (field.getAttribute("aria-invalid") === "true") validateField(field); }); }); contactForm.addEventListener("submit", event => { event.preventDefault(); const results = fields.map(validateField); const firstInvalid = results.indexOf(false); if (firstInvalid !== -1) { draft.hidden = true; status.textContent = "Please correct the marked fields. No message has been sent."; fields[firstInvalid].focus(); return; } const values = Object.fromEntries(fields.map(field => [ field.dataset.contactField, field.value.trim() ])); const subject = `Reelpockscenegarden — ${values.topic}`; const body = `Name: ${values.name}\nReply email: ${values.email}\nTopic: ${values.topic}\n\n${values.message}`; draft.href = `mailto:[email protected]?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; draft.hidden = false; status.textContent = "Your draft link is ready. Nothing has been sent. Open it in your email app, check the recipient and send only if you wish. If no email app opens, copy your message into your usual mail service."; draft.focus(); }); } const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); if ("IntersectionObserver" in window && !reducedMotion.matches) { const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add("is-visible"); observer.unobserve(entry.target); } }); }, { threshold: 0.08 }); document.querySelectorAll("[data-reveal]").forEach(element => { element.classList.add("js-reveal"); observer.observe(element); }); } })(); ===ENDFILE===