From ea25680363e7d3e73b098522fd9bd46e3d051808 Mon Sep 17 00:00:00 2001 From: Calum Muir Date: Sat, 23 May 2026 17:03:37 +0200 Subject: [PATCH] Redesign forms to multi-step; add sponsor section and content updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rewrite volunteer-application.astro: 5-step multi-step form with progress pills, choice cards, per-step validation, review step, and fetch-based submission; matches new site design system - Rewrite participant-application.astro: 5-step multi-step form (Participant, Health & support, Activities, Consent, Review); retains all original field names; 27 support question checkboxes as interactive cards; callout box; guardian section on step 1 - Add volunteer-reference.astro: new non-public 5-step reference form (noindex/nofollow); steps: The applicant, Your details, Relationship, Assessment, Declaration; posts to /api/volunteer-reference - Add functions/api/volunteer-reference.ts: Cloudflare Pages Function mirroring volunteer-application pattern with Turnstile + Resend - Add Ffordes sponsor logo to homepage; replace placeholder sponsor boxes with full corporate sponsorship section (£750/annum) with four benefit cards and CTAs - Move ffordes_logo.png to public/sponsors/ - Remove all references to 'carriage driving' and 'free sessions' across about.astro, index.astro, privacy.astro, support-us.astro, and participant-application.astro - facebook.astro: styled mock-post feed with like toggle, page header card, sidebar, and embed note banner Co-Authored-By: Claude Sonnet 4.6 --- functions/api/volunteer-reference.ts | 348 ++++ package-lock.json | 41 +- public/sponsors/ffordes_logo.png | Bin 0 -> 29765 bytes src/pages/about.astro | 8 +- src/pages/facebook.astro | 490 +++++- src/pages/index.astro | 97 +- src/pages/participant-application.astro | 1944 +++++++++++++---------- src/pages/privacy.astro | 2 +- src/pages/support-us.astro | 2 +- src/pages/volunteer-application.astro | 1459 ++++++++++------- src/pages/volunteer-reference.astro | 704 ++++++++ 11 files changed, 3572 insertions(+), 1523 deletions(-) create mode 100644 functions/api/volunteer-reference.ts create mode 100644 public/sponsors/ffordes_logo.png create mode 100644 src/pages/volunteer-reference.astro diff --git a/functions/api/volunteer-reference.ts b/functions/api/volunteer-reference.ts new file mode 100644 index 0000000..a691e7a --- /dev/null +++ b/functions/api/volunteer-reference.ts @@ -0,0 +1,348 @@ +type Env = { + TURNSTILE_SECRET_KEY?: string; + RESEND_API_KEY?: string; + CONTACT_FROM_EMAIL?: string; + CONTACT_TO_EMAIL?: string; +}; + +const jsonResponse = (data: Record, status = 200) => + new Response(JSON.stringify(data), { + status, + headers: { "content-type": "application/json" }, + }); + +const fetchWithTimeout = async ( + input: RequestInfo | URL, + init: RequestInit, + timeoutMs: number, + label: string, +) => { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), timeoutMs); + try { + return await fetch(input, { ...init, signal: controller.signal }); + } catch (error) { + const message = + error instanceof Error ? error.message : "Unknown error"; + throw new Error(`${label} request failed: ${message}`); + } finally { + clearTimeout(timeout); + } +}; + +const readTextField = (value: string | null) => (value ?? "").trim(); +const toTitleCase = (value: string) => + value + .replace(/_/g, " ") + .replace(/\b\w/g, (char) => char.toUpperCase()); + +const parseFormFields = async (request: Request) => { + const contentType = request.headers.get("content-type") ?? ""; + const data = new Map(); + + const appendValue = (key: string, value: string | null) => { + const normalized = readTextField(value); + if (!normalized) return; + data.set(key, [...(data.get(key) ?? []), normalized]); + }; + + if (contentType.includes("application/x-www-form-urlencoded")) { + const bodyText = await request.text(); + const params = new URLSearchParams(bodyText); + for (const [key, value] of params.entries()) { + appendValue(key, value); + } + return data; + } + + const formData = await request.formData(); + for (const [key, value] of formData.entries()) { + appendValue(key, typeof value === "string" ? value : value.name); + } + return data; +}; + +const formatValue = (values: string[] | undefined) => (values ?? []).join(", "); + +const labelMap: Record = { + applicant_first_names: "Applicant first name(s)", + applicant_last_name: "Applicant last name", + referee_name: "Referee full name", + referee_job_title: "Referee job title or role", + referee_organisation: "Referee organisation", + referee_address: "Referee address", + referee_telephone: "Referee telephone", + referee_email: "Referee email", + relationship_capacity: "Capacity in which referee knows the applicant", + relationship_other: "Relationship (if Other)", + known_duration: "How long referee has known the applicant", + related_to_applicant: "Related to the applicant", + character_assessment: "Character and personal qualities", + reliable_punctual: "Reliable and punctual", + suitable_for_role: "Suitable to work with people with disabilities", + suitability_concerns: "Concerns regarding suitability", + additional_comments: "Additional comments", + declaration_truth: "Declaration of truth and accuracy", + declaration_consent: "Declaration of confidentiality understanding", + declaration_signature: "Signature name", + declaration_date: "Date", +}; + +const requiredFields = [ + "applicant_first_names", + "applicant_last_name", + "referee_name", + "referee_email", + "known_duration", + "character_assessment", + "suitable_for_role", + "declaration_truth", +]; + +const sectionOrder = [ + { + title: "Applicant", + fields: ["applicant_first_names", "applicant_last_name"], + }, + { + title: "Referee Details", + fields: [ + "referee_name", + "referee_job_title", + "referee_organisation", + "referee_address", + "referee_telephone", + "referee_email", + ], + }, + { + title: "Relationship to Applicant", + fields: [ + "relationship_capacity", + "relationship_other", + "known_duration", + "related_to_applicant", + ], + }, + { + title: "Assessment", + fields: [ + "character_assessment", + "reliable_punctual", + "suitable_for_role", + "suitability_concerns", + "additional_comments", + ], + }, + { + title: "Declaration", + fields: [ + "declaration_truth", + "declaration_consent", + "declaration_signature", + "declaration_date", + ], + }, +]; + +const handlePost: PagesFunction = async ({ request, env }) => { + const fields = await parseFormFields(request); + + const missingRequired = requiredFields.filter( + (field) => !formatValue(fields.get(field)), + ); + if (missingRequired.length > 0) { + return jsonResponse( + { + ok: false, + error: "Missing required fields.", + details: missingRequired.map((field) => labelMap[field] ?? field).join(", "), + }, + 400, + ); + } + + if (!env.TURNSTILE_SECRET_KEY) { + return jsonResponse( + { ok: false, error: "Turnstile secret key not configured." }, + 500, + ); + } + + const turnstileToken = formatValue(fields.get("cf-turnstile-response")); + if (!turnstileToken) { + return jsonResponse( + { ok: false, error: "Turnstile verification failed." }, + 400, + ); + } + + const verifyBody = new URLSearchParams({ + secret: env.TURNSTILE_SECRET_KEY, + response: turnstileToken, + remoteip: request.headers.get("CF-Connecting-IP") ?? "", + }); + + const verifyResponse = await fetchWithTimeout( + "https://challenges.cloudflare.com/turnstile/v0/siteverify", + { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, + body: verifyBody, + }, + 8000, + "Turnstile", + ); + const verifyResult = (await verifyResponse.json()) as { + success?: boolean; + "error-codes"?: string[]; + messages?: string[]; + hostname?: string; + action?: string; + }; + + if (!verifyResult.success) { + const details = [ + verifyResult["error-codes"]?.length + ? `codes=${verifyResult["error-codes"].join(",")}` + : "", + verifyResult.messages?.length + ? `messages=${verifyResult.messages.join(",")}` + : "", + verifyResult.hostname ? `hostname=${verifyResult.hostname}` : "", + verifyResult.action ? `action=${verifyResult.action}` : "", + ] + .filter(Boolean) + .join(" | ") + .slice(0, 500); + return jsonResponse( + { + ok: false, + error: "Turnstile verification failed.", + details: details || undefined, + }, + 400, + ); + } + + if (!env.RESEND_API_KEY) { + return jsonResponse( + { ok: false, error: "Email provider not configured." }, + 500, + ); + } + + const fromEmail = env.CONTACT_FROM_EMAIL; + if (!fromEmail) { + return jsonResponse( + { ok: false, error: "Sender email not configured." }, + 500, + ); + } + + const toEmail = env.CONTACT_TO_EMAIL ?? "calum@muir.in"; + const applicantName = [ + formatValue(fields.get("applicant_first_names")), + formatValue(fields.get("applicant_last_name")), + ] + .filter(Boolean) + .join(" "); + const refereeName = formatValue(fields.get("referee_name")); + + const bodyText = sectionOrder + .map(({ title, fields: sectionFields }) => { + const lines = sectionFields + .map((field) => { + const value = formatValue(fields.get(field)); + if (!value) return null; + return `${labelMap[field] ?? toTitleCase(field)}: ${value}`; + }) + .filter(Boolean) + .join("\n"); + + return lines ? `${title}\n${lines}` : ""; + }) + .filter(Boolean) + .join("\n\n"); + + const extraFields = [...fields.keys()].filter( + (field) => + field !== "cf-turnstile-response" && + !sectionOrder.some((section) => section.fields.includes(field)), + ); + const extraText = extraFields + .map((field) => { + const value = formatValue(fields.get(field)); + return value ? `${labelMap[field] ?? toTitleCase(field)}: ${value}` : ""; + }) + .filter(Boolean) + .join("\n"); + + const emailPayload = { + from: fromEmail, + to: [toEmail], + reply_to: formatValue(fields.get("referee_email")) || undefined, + subject: `New volunteer reference${applicantName ? ` for ${applicantName}` : ""}${refereeName ? ` from ${refereeName}` : ""}`, + text: [bodyText, extraText ? `Additional Fields\n${extraText}` : ""] + .filter(Boolean) + .join("\n\n"), + }; + + const response = await fetchWithTimeout( + "https://api.resend.com/emails", + { + method: "POST", + headers: { + Authorization: `Bearer ${env.RESEND_API_KEY}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(emailPayload), + }, + 8000, + "Resend", + ); + if (!response.ok) { + const contentType = response.headers.get("content-type") ?? ""; + const errorText = contentType.includes("application/json") + ? JSON.stringify(await response.json().catch(() => ({}))) + : await response.text(); + const errorCode = response.headers.get("x-ms-error-code"); + const details = [ + `${response.status} ${response.statusText}`.trim(), + errorCode ? `code=${errorCode}` : "", + errorText.trim(), + ] + .filter(Boolean) + .join(" | ") + .slice(0, 500); + return jsonResponse( + { + ok: false, + error: "Failed to send email.", + details, + }, + 502, + ); + } + + return jsonResponse({ ok: true }); +}; + +export const onRequest: PagesFunction = async (context) => { + try { + if (context.request.method !== "POST") { + return jsonResponse( + { ok: false, error: "Method not allowed." }, + 405, + ); + } + return await handlePost(context); + } catch (error) { + const message = + error instanceof Error ? error.message : "Unknown error"; + return jsonResponse( + { ok: false, error: "Unhandled exception.", details: message }, + 500, + ); + } +}; diff --git a/package-lock.json b/package-lock.json index 3d8f511..e0bea88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -236,6 +236,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -1950,6 +1951,7 @@ "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.10.4.tgz", "integrity": "sha512-2nZuh3VUO9voBauuh+IGYRhGU/MskWHt1IuZvHcJw6GLjDgtqj/KViKo7SIrLdGLdot7vFbiRRw+BgEy3wT9HA==", "license": "MIT", + "peer": true, "dependencies": { "@types/pg": "8.11.6" } @@ -4335,6 +4337,7 @@ "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -4346,6 +4349,7 @@ "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/react": "*" } @@ -4704,6 +4708,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5064,6 +5069,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5085,20 +5091,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bufferutil": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", - "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -5671,6 +5663,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" @@ -5938,6 +5931,7 @@ "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.39.1.tgz", "integrity": "sha512-2bDHlzTY31IDmrYn8i+ZZrxK8IyBD4mPZ7JmZdVDQj2tpBZXs/gxB/1kK5pSvkjxPUMNOVsTnoGkSltgjuJwcA==", "license": "Apache-2.0", + "peer": true, "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", @@ -6099,7 +6093,8 @@ "node_modules/embla-carousel": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", - "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==" + "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", + "peer": true }, "node_modules/embla-carousel-react": { "version": "8.6.0", @@ -6194,6 +6189,7 @@ "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -7254,6 +7250,7 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -7307,6 +7304,7 @@ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==", "devOptional": true, + "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -9030,6 +9028,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz", "integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.7.0", "pg-pool": "^3.7.0", @@ -9241,6 +9240,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -9564,6 +9564,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -9589,6 +9590,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -9601,6 +9603,7 @@ "version": "7.55.0", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.55.0.tgz", "integrity": "sha512-XRnjsH3GVMQz1moZTW53MxfoWN7aDpUg/GpVNc4A3eXRVNdGXfbzJ4vM4aLQ8g6XCUh1nIbx70aaNCl7kxnjog==", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -10082,6 +10085,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -10668,6 +10672,7 @@ "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -10797,6 +10802,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10883,6 +10889,7 @@ "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -10935,6 +10942,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11478,6 +11486,7 @@ "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -12206,6 +12215,7 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -12266,6 +12276,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/public/sponsors/ffordes_logo.png b/public/sponsors/ffordes_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4f16ce52294cc38d0b211aceeba7551a0ad2831d GIT binary patch literal 29765 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 literal 0 HcmV?d00001 diff --git a/src/pages/about.astro b/src/pages/about.astro index bc8a8fa..99de298 100644 --- a/src/pages/about.astro +++ b/src/pages/about.astro @@ -16,7 +16,7 @@ const pageSeo = { const stats = [ { number: "1975", label: "Founded" }, { number: "50+", label: "Years serving the Highlands" }, - { number: "Free", label: "Sessions for all participants" }, + { number: "4", label: "Days a week, Tue–Fri" }, { number: "Tue–Fri", label: "Morning sessions" }, ]; @@ -117,7 +117,7 @@ const testimonials = [ Enriching lives through horses in the Highlands since 1975.

- We are a voluntary organisation based at Sandycroft, Reelig, Kirkhill — providing free therapeutic and recreational riding for people with disabilities across the Highlands. + We are a voluntary organisation based at Sandycroft, Reelig, Kirkhill — providing therapeutic and recreational riding for people with disabilities across the Highlands.

@@ -152,10 +152,10 @@ const testimonials = [

What we do

- Riding, carriage driving, and so much more. + Riding and so much more.

- We provide free riding and carriage driving sessions for participants with a wide range of disabilities. Working with horses improves balance, coordination, confidence, and provides a uniquely meaningful interaction with animals. + We provide riding sessions for participants with a wide range of disabilities. Working with horses improves balance, coordination, confidence, and provides a uniquely meaningful interaction with animals.

Sessions run Tuesday to Friday mornings from our base at Sandycroft. Each session is supported by trained volunteers and supervised by qualified coaches. diff --git a/src/pages/facebook.astro b/src/pages/facebook.astro index e8b614b..88951f2 100644 --- a/src/pages/facebook.astro +++ b/src/pages/facebook.astro @@ -5,9 +5,7 @@ import SiteFooter from "../components/SiteFooter.astro"; import { navigationItems } from "../lib/navigation"; const facebookPageUrl = - "https://www.facebook.com/profile.php?id=100064729054822"; -const facebookEmbedUrl = - "https://www.facebook.com/plugins/page.php?href=https%3A%2F%2Fwww.facebook.com%2Fprofile.php%3Fid%3D100064729054822&tabs=timeline&width=500&height=900&small_header=false&adapt_container_width=true&hide_cover=false&show_facepile=true"; + "https://www.facebook.com/people/Highland-Group-RDA/100064729054822/"; const pageSeo = { title: "Facebook", @@ -17,88 +15,436 @@ const pageSeo = { "Highland Group RDA Facebook, RDA social media, Highland RDA updates, RDA events Facebook, therapeutic riding community", canonicalPath: "/facebook", }; + +const posts = [ + { + id: 1, + date: "3 days ago", + body: "What a glorious morning at Sandycroft! All four ponies were out, the sun was shining, and our riders had a brilliant session. Huge thanks to our amazing volunteer team who make every Tuesday possible. 🐴☀️", + img: "/Samantha-and-Connolly-1280.webp", + imgPos: "50% 38%", + likes: 47, + comments: 6, + shares: 3, + }, + { + id: 2, + date: "1 week ago", + body: "We are thrilled to announce that three of our team have just completed their RDA Coach training! Welcome to the qualified ranks — and thank you for the hours of study, practice, and dedication. The Highlands are lucky to have you. 🎉", + img: "/Vols5-2880w-1024x768.avif", + imgPos: "50% 28%", + likes: 132, + comments: 24, + shares: 11, + }, + { + id: 3, + date: "2 weeks ago", + body: "MEET THE PONIES — Today we'd like to introduce you to Breagh, one of our most willing and independent ponies. Breagh has been a firm favourite with our riders for years. She loves a good back-scratch. 💚", + img: "/Breagh-bc8e70e8-2880w.webp", + imgPos: "50% 45%", + likes: 218, + comments: 41, + shares: 15, + }, + { + id: 4, + date: "3 weeks ago", + body: "Open Day reminder — Sunday 10 May, 11am to 3pm at Sandycroft. Come and meet the ponies, chat to our coaches, and find out how you can get involved. Tea, coffee, and home baking on offer! All welcome.", + img: "/about-960.webp", + imgPos: "50% 35%", + likes: 89, + comments: 18, + shares: 27, + }, + { + id: 5, + date: "1 month ago", + body: "Incredible news — our sponsored ride raised over £1,200! 🙏 That's enough to cover hoof trimming and hay for the whole summer. To everyone who donated, sponsored, walked, or cheered us on: thank you. You make this work possible.", + img: null, + imgPos: "", + likes: 165, + comments: 32, + shares: 9, + }, +]; + +const photosGrid = [ + { src: "/Samantha-and-Connolly-1280.webp", pos: "50% 38%" }, + { src: "/alineofponies-1920w.webp", pos: "50% 50%" }, + { src: "/Vols5-2880w-1024x768.avif", pos: "50% 28%" }, + { src: "/about-960.webp", pos: "50% 42%" }, + { src: "/contact-960.webp", pos: "50% 40%" }, + { src: "/Breagh-bc8e70e8-2880w.webp", pos: "50% 50%" }, + { src: "/Harley-6ece9eca-2880w.webp", pos: "50% 50%" }, + { src: "/puzzle-5de6e325-2880w.webp", pos: "50% 50%" }, + { src: "/Wispa-2880w.webp", pos: "50% 20%" }, +]; --- -

+ + + + + +
-
+ + +
+
+
-
- -
-
-

- Social -

-
-

- Facebook updates -

-

- Follow our latest news, events, and photos on - Facebook. -

-
+
+

+ Social +

+
+

+ Follow us on Facebook. +

+

+ The latest from Sandycroft — meet the ponies, see our riders in action, and follow our sessions, events, and fundraising. +

- - Donate via JustGiving -
-
-
-
- + +
+
+
+ +
+ +
+ +
+
+
+ +
+
+
+

+ Highland Group RDA +

+

+ Nonprofit organisation · Riding for the Disabled +

+
+ +
- - Visit our Facebook page -
- + + +
+
+ + +
+
+

Recent posts

+ + See all on Facebook + +
+ + {posts.map((post) => ( +
+
+
+ +
+
+

Highland Group RDA

+

+ {post.date} · 🌍 +

+
+
+
+

+ {post.body} +

+
+ {post.img && ( +
+ +
+ )} +
+
+
+ + + + + + +
+ {post.likes} +
+
+ {post.comments} comments + {post.shares} shares +
+
+ +
+ ))} + + +
+ + + +
+
+ + +
+
+
+ +
+

+ The posts above are a curated preview of our Facebook activity. For the full feed, visit us directly on Facebook. +

+
+
+
+
+ + diff --git a/src/pages/index.astro b/src/pages/index.astro index 58496a5..b966dfc 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -120,19 +120,29 @@ const ctaCards = [ } .sponsor-logo { - width: 140px; height: 64px; - border: 2px solid rgba(255, 255, 255, 0.3); - border-radius: 6px; display: flex; align-items: center; justify-content: center; - font-family: 'Public Sans', sans-serif; - font-weight: 700; - font-size: 13px; - color: rgba(255, 255, 255, 0.5); - letter-spacing: 0.08em; - text-transform: uppercase; + } + .sponsor-logo img { + max-height: 64px; + max-width: 200px; + object-fit: contain; + filter: brightness(0) invert(1); + opacity: 0.85; + transition: opacity 0.2s ease; + } + .sponsor-logo img:hover { + opacity: 1; + } + .sponsor-cta { + border: 2px dashed rgba(255, 255, 255, 0.25); + border-radius: 8px; + padding: 20px 28px; + text-align: center; + max-width: 380px; + margin: 0 auto; } @media (max-width: 767px) { @@ -233,7 +243,7 @@ const ctaCards = [ What is Riding for the Disabled?

- Across the Highlands, we provide free riding and carriage driving sessions to people with a wide range of physical and mental disabilities — connecting them with our ponies in a way that's therapeutic, joyful, and life-changing. + Across the Highlands, we provide riding sessions for people with a wide range of physical and mental disabilities — connecting them with our ponies in a way that's therapeutic, joyful, and life-changing.