Redesign forms to multi-step; add sponsor section and content updates

- 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 <noreply@anthropic.com>
This commit is contained in:
Calum Muir 2026-05-23 17:03:37 +02:00
parent 7c3e8b5ef0
commit ea25680363
11 changed files with 3572 additions and 1523 deletions

View file

@ -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<string, unknown>, 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<string, string[]>();
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<string, string> = {
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<Env> = 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<Env> = 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,
);
}
};

41
package-lock.json generated
View file

@ -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"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View file

@ -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, TueFri" },
{ number: "TueFri", label: "Morning sessions" },
];
@ -117,7 +117,7 @@ const testimonials = [
Enriching lives through horses in the Highlands since 1975.
</h1>
<p style="margin-top: 22px; font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: clamp(15px, 1.5vw, 18px); color: rgba(246,241,232,0.85); line-height: 1.65; max-width: 580px;">
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.
</p>
</div>
</div>
@ -152,10 +152,10 @@ const testimonials = [
<div>
<p style="font-family: 'Public Sans', sans-serif; font-weight: 700; font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase; color: rgba(0,0,0,0.45); margin-bottom: 14px;">What we do</p>
<h2 style="font-family: 'Merriweather', serif; font-weight: 700; font-size: clamp(22px, 2.5vw, 32px); color: #000; line-height: 1.15; margin-bottom: 20px;">
Riding, carriage driving, and so much more.
Riding and so much more.
</h2>
<p style="font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: 16px; color: rgba(0,0,0,0.7); line-height: 1.7; margin-bottom: 16px;">
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.
</p>
<p style="font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: 16px; color: rgba(0,0,0,0.7); line-height: 1.7;">
Sessions run Tuesday to Friday mornings from our base at Sandycroft. Each session is supported by trained volunteers and supervised by qualified coaches.

View file

@ -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%" },
];
---
<BaseLayout {...pageSeo}>
<div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]">
<style>
#site-nav {
transition: background 0.4s ease, backdrop-filter 0.4s ease;
}
#site-nav.scrolled {
background: rgba(0,0,0,0.82);
backdrop-filter: blur(8px);
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(18px); }
to { opacity: 1; transform: translateY(0); }
}
.anim-fade-up { animation: fadeUp 0.9s ease both; }
.fb-card {
background: #fff;
border-radius: 8px;
border: 1px solid rgba(0,0,0,0.08);
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
overflow: hidden;
}
.fb-action {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
flex: 1;
padding: 10px 0;
cursor: pointer;
border-radius: 6px;
transition: background 0.15s;
color: #65676b;
font-family: 'Public Sans', sans-serif;
font-weight: 600;
font-size: 14px;
border: none;
background: transparent;
text-decoration: none;
}
.fb-action:hover { background: #f2f2f2; }
.fb-action[data-liked="true"] { color: #1877F2; font-weight: 700; }
.feed-grid {
display: grid;
grid-template-columns: 1fr 320px;
gap: 32px;
align-items: flex-start;
}
.sidebar { position: sticky; top: 88px; }
@media (max-width: 900px) {
.feed-grid { grid-template-columns: 1fr; }
.sidebar { position: static; }
}
@media (max-width: 767px) {
.page-header-info { flex-wrap: wrap; }
.page-header-buttons { padding-top: 12px !important; }
}
</style>
<!-- Fixed nav -->
<div id="site-nav" class="fixed top-0 left-0 right-0 z-50 px-4 sm:px-10">
<SiteHeader navigationItems={navigationItems} theme="dark" />
</div>
<div class="flex flex-col min-h-screen bg-[#e2e2e2]">
<main class="w-full">
<section class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0">
<div
class="relative w-full sm:rounded-[7px] overflow-hidden bg-cover bg-[50%_35%]"
style="background-image: url(/320711306_865749364475187_6152104707200224701_n-1d6b6ac8-1920w-1.webp);"
>
<div class="absolute inset-0 bg-black/45"></div>
<SiteHeader
navigationItems={navigationItems}
theme="dark"
className="px-6 sm:px-4"
facebookHref={facebookPageUrl}
<!-- ── HERO ── -->
<section class="relative w-full overflow-hidden" style="min-height: 48vh;">
<img
class="absolute inset-0 w-full h-full object-cover"
style="object-position: 50% 30%;"
src="/Vols5-2880w-1024x768.avif"
alt=""
loading="eager"
fetchpriority="high"
decoding="async"
/>
<div
class="relative px-6 sm:px-8 pb-32 pt-6 sm:pb-24 sm:pt-8"
>
class="absolute inset-0"
style="background: linear-gradient(160deg, rgba(0,0,0,0.78) 0%, rgba(0,0,0,0.4) 60%, rgba(0,0,0,0.5) 100%);"
/>
<div
class="max-w-none sm:max-w-[760px] rounded-[7px] bg-black/45 px-5 py-5 sm:px-6 sm:py-6 backdrop-blur-sm"
>
<p
class="[font-family:'Public_Sans',Helvetica] font-semibold text-xs sm:text-sm uppercase tracking-[0.2em] text-white/90"
class="relative flex flex-col justify-end"
style="min-height: 48vh; padding: 100px clamp(24px, 5vw, 72px) 60px;"
>
<div class="anim-fade-up" style="max-width: 680px;">
<p style="font-family: 'Public Sans', sans-serif; font-weight: 700; font-size: 12px; letter-spacing: 0.25em; text-transform: uppercase; color: rgba(255,255,255,0.7); margin-bottom: 14px;">
Social
</p>
<div class="mt-3 h-[2px] w-10 bg-white/70"></div>
<h1
class="mt-4 [font-family:'Merriweather',Helvetica] font-bold text-white text-[34px] sm:text-[44px] leading-[1.15]"
>
Facebook updates
<div style="width: 48px; height: 2px; background: #7DA371; margin-bottom: 20px;" />
<h1 style="font-family: 'Merriweather', serif; font-weight: 900; font-size: clamp(32px, 4.5vw, 52px); color: #F6F1E8; line-height: 1.1; margin-bottom: 18px;">
Follow us on Facebook.
</h1>
<p
class="mt-4 [font-family:'Public_Sans',Helvetica] font-semibold text-white text-base sm:text-lg leading-[1.6] max-w-[720px]"
>
Follow our latest news, events, and photos on
Facebook.
<p style="font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: clamp(15px, 1.5vw, 18px); color: rgba(246,241,232,0.82); line-height: 1.65;">
The latest from Sandycroft — meet the ponies, see our riders in action, and follow our sessions, events, and fundraising.
</p>
</div>
</div>
<a
class="absolute bottom-4 left-4 sm:bottom-6 sm:left-6 w-[190px] sm:w-[240px] h-[48px] sm:h-[50px]"
href="https://www.justgiving.com/charity/highlandgrouprda"
target="_blank"
rel="noreferrer"
>
<img
class="w-full h-full object-contain"
alt="Donate via JustGiving"
src="/Button.webp"
/>
</a>
</div>
</section>
<section
class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0 pb-0"
>
<div class="bg-[#d6d6d6] px-6 sm:px-8 py-6 sm:py-8">
<div class="mt-6 w-full max-w-[500px] mx-auto">
<iframe
title="Highland Group RDA Facebook feed"
src={facebookEmbedUrl}
width="500"
height="900"
class="w-full h-[700px] sm:h-[900px] border-0 rounded-[7px] bg-white"
allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"
loading="lazy"></iframe>
<!-- ── PAGE HEADER CARD ── -->
<section style="background: #e2e2e2; padding: clamp(32px, 5vw, 56px) clamp(24px, 5vw, 72px) 0;">
<div style="max-width: 1100px; margin: 0 auto;">
<div class="fb-card">
<!-- Cover photo -->
<div style="position: relative; height: 200px; overflow: hidden;">
<img
src="/Samantha-and-Connolly-1280.webp"
alt=""
style="width: 100%; height: 100%; object-fit: cover; object-position: 50% 35%;"
loading="lazy"
/>
</div>
<!-- Info row -->
<div class="page-header-info" style="padding: 0 32px 28px; display: flex; align-items: flex-end; gap: 24px; margin-top: -50px; flex-wrap: wrap;">
<div style="width: 120px; height: 120px; border-radius: 9999px; background: #fff; padding: 5px; flex-shrink: 0; box-shadow: 0 4px 12px rgba(0,0,0,0.1);">
<div style="width: 100%; height: 100%; border-radius: 9999px; background: #1e2e1a; display: flex; align-items: center; justify-content: center;">
<img src="/figmaAssets/rdalogo-1.svg" alt="" style="height: 64px; filter: brightness(0) invert(1);" />
</div>
</div>
<div style="flex: 1; padding-top: 50px; min-width: 200px;">
<h2 style="font-family: 'Merriweather', serif; font-weight: 700; font-size: clamp(20px, 2.2vw, 28px); color: #000; line-height: 1.2;">
Highland Group RDA
</h2>
<p style="margin-top: 6px; font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: 14px; color: #65676b;">
Nonprofit organisation · Riding for the Disabled
</p>
</div>
<div class="page-header-buttons" style="display: flex; gap: 10px; padding-top: 50px; flex-wrap: wrap;">
<a
class="mt-6 inline-flex [font-family:'Public_Sans',Helvetica] font-semibold text-black text-base sm:text-lg underline"
href={facebookPageUrl}
target="_blank"
rel="noreferrer"
style="display: inline-flex; align-items: center; gap: 8px; background: #1877F2; color: #fff; border-radius: 6px; padding: 10px 20px; font-family: 'Public Sans', sans-serif; font-weight: 700; font-size: 14px; text-decoration: none;"
>
Visit our Facebook page
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"/></svg>
Follow
</a>
<a
href={facebookPageUrl}
target="_blank"
rel="noreferrer"
style="display: inline-flex; align-items: center; gap: 8px; background: #e4e6eb; color: #000; border-radius: 6px; padding: 10px 20px; font-family: 'Public Sans', sans-serif; font-weight: 700; font-size: 14px; text-decoration: none;"
>
Visit page
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 3h7v7M13 3L4 12"/></svg>
</a>
</div>
</section>
<SiteFooter fullBleedOnMobile />
</main>
</div>
</div>
</div>
</section>
<!-- ── FEED + SIDEBAR ── -->
<section style="background: #e2e2e2; padding: clamp(24px, 4vw, 40px) clamp(24px, 5vw, 72px) clamp(56px, 8vw, 88px);">
<div style="max-width: 1100px; margin: 0 auto;" class="feed-grid">
<!-- Feed column -->
<div style="display: flex; flex-direction: column; gap: 16px;">
<div style="display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 8px; gap: 12px; flex-wrap: wrap;">
<h2 style="font-family: 'Merriweather', serif; font-weight: 700; font-size: clamp(20px, 2.2vw, 28px); color: #000;">Recent posts</h2>
<a
href={facebookPageUrl}
target="_blank"
rel="noreferrer"
style="font-family: 'Public Sans', sans-serif; font-weight: 800; font-size: 13px; color: #7DA371; letter-spacing: 0.05em; text-transform: uppercase; text-decoration: underline;"
>
See all on Facebook
</a>
</div>
{posts.map((post) => (
<div class="fb-card" data-post-id={post.id}>
<div style="padding: 14px 16px; display: flex; align-items: center; gap: 12px;">
<div style="width: 40px; height: 40px; border-radius: 9999px; background: #1e2e1a; display: flex; align-items: center; justify-content: center; flex-shrink: 0;">
<img src="/figmaAssets/rdalogo-1.svg" alt="" style="height: 24px; filter: brightness(0) invert(1);" />
</div>
<div>
<p style="font-family: 'Public Sans', sans-serif; font-weight: 700; font-size: 14px; color: #000;">Highland Group RDA</p>
<p style="font-family: 'Public Sans', sans-serif; font-weight: 500; font-size: 12px; color: #65676b;">
{post.date} · 🌍
</p>
</div>
</div>
<div style="padding: 0 16px 14px;">
<p style="font-family: 'Public Sans', sans-serif; font-weight: 500; font-size: 15px; color: #000; line-height: 1.55; white-space: pre-line;">
{post.body}
</p>
</div>
{post.img && (
<div style="position: relative; width: 100%; max-height: 520px; overflow: hidden; background: #f0f2f5;">
<img
src={post.img}
alt=""
style={`width: 100%; max-height: 520px; object-fit: cover; object-position: ${post.imgPos}; display: block;`}
loading="lazy"
decoding="async"
/>
</div>
)}
<div style="padding: 10px 16px 6px; display: flex; align-items: center; justify-content: space-between; font-family: 'Public Sans', sans-serif; font-weight: 500; font-size: 13px; color: #65676b;">
<div style="display: flex; align-items: center; gap: 6px;">
<div style="display: flex;">
<span style="width: 18px; height: 18px; border-radius: 9999px; background: #1877F2; display: inline-flex; align-items: center; justify-content: center; border: 2px solid #fff; position: relative; z-index: 2;">
<svg width="9" height="9" viewBox="0 0 24 24" fill="white" aria-hidden="true"><path d="M2 21h4V9H2zm20-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L13.17 1 7.58 6.59C7.22 6.95 7 7.45 7 8v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73z"/></svg>
</span>
<span style="width: 18px; height: 18px; border-radius: 9999px; background: #f33e58; display: inline-flex; align-items: center; justify-content: center; border: 2px solid #fff; margin-left: -5px; position: relative; z-index: 1;">
<svg width="9" height="9" viewBox="0 0 24 24" fill="white" aria-hidden="true"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>
</span>
</div>
<span data-like-count={post.likes}>{post.likes}</span>
</div>
<div style="display: flex; gap: 14px;">
<span>{post.comments} comments</span>
<span>{post.shares} shares</span>
</div>
</div>
<div style="margin: 0 12px; border-top: 1px solid rgba(0,0,0,0.1); padding: 4px 0; display: flex;">
<button class="fb-action" data-like-btn aria-label="Like" aria-pressed="false">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M7 11v9H3v-9zm14-1l-2 9c-.2.8-.9 1-1.5 1H10c-.6 0-1-.4-1-1V10c0-.2.1-.5.3-.7L15 4l1 1c.3.3.4.7.3 1.1L15 10h5c.6 0 1.1.4 1 1z"/>
</svg>
Like
</button>
<a href={facebookPageUrl} target="_blank" rel="noreferrer" class="fb-action">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M21 11.5a8.4 8.4 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.4 8.4 0 0 1-3.8-.9L3 21l1.9-5.7a8.4 8.4 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.4 8.4 0 0 1 3.8-.9h.5a8.5 8.5 0 0 1 8 8z"/>
</svg>
Comment
</a>
<a href={facebookPageUrl} target="_blank" rel="noreferrer" class="fb-action">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8M16 6l-4-4-4 4M12 2v13"/>
</svg>
Share
</a>
</div>
</div>
))}
<div style="text-align: center; margin-top: 8px;">
<a
href={facebookPageUrl}
target="_blank"
rel="noreferrer"
style="display: inline-flex; align-items: center; gap: 10px; background: #1877F2; color: #fff; border-radius: 9999px; padding: 14px 32px; font-family: 'Public Sans', sans-serif; font-weight: 800; font-size: 13px; letter-spacing: 0.06em; text-transform: uppercase; text-decoration: none;"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"/></svg>
See more on Facebook
</a>
</div>
</div>
<!-- Sidebar -->
<aside class="sidebar" style="display: flex; flex-direction: column; gap: 16px;">
<!-- About card -->
<div class="fb-card" style="padding: 20px 22px;">
<p style="font-family: 'Public Sans', sans-serif; font-weight: 800; font-size: 12px; color: #65676b; letter-spacing: 0.08em; text-transform: uppercase; margin-bottom: 14px;">About</p>
<p style="font-family: 'Public Sans', sans-serif; font-weight: 500; font-size: 14px; color: #000; line-height: 1.6; margin-bottom: 18px;">
Highland Group RDA provides therapeutic riding for people with disabilities in the Highlands. Based at Sandycroft, Reelig, Kirkhill.
</p>
<div style="display: flex; flex-direction: column; gap: 10px; font-family: 'Public Sans', sans-serif; font-size: 13px; color: #65676b;">
<div style="display: flex; gap: 10px; align-items: flex-start;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink: 0; margin-top: 2px;" aria-hidden="true">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/>
</svg>
<span>Sandycroft, Reelig, Kirkhill IV5 7PP</span>
</div>
<div style="display: flex; gap: 10px; align-items: center;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink: 0;" aria-hidden="true">
<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M22 6l-10 7L2 6"/>
</svg>
<a href="mailto:info@highlandgrouprda.org.uk" style="color: #65676b;">info@highlandgrouprda.org.uk</a>
</div>
<div style="display: flex; gap: 10px; align-items: center;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink: 0;" aria-hidden="true">
<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>
</svg>
<span>Tue Fri mornings</span>
</div>
</div>
</div>
<!-- Photos grid -->
<div class="fb-card" style="padding: 20px 22px;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px;">
<p style="font-family: 'Public Sans', sans-serif; font-weight: 800; font-size: 12px; color: #65676b; letter-spacing: 0.08em; text-transform: uppercase;">Photos</p>
<a href={facebookPageUrl} target="_blank" rel="noreferrer" style="font-family: 'Public Sans', sans-serif; font-weight: 700; font-size: 12px; color: #1877F2; text-decoration: none;">See all</a>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px;">
{photosGrid.map((photo) => (
<div style="aspect-ratio: 1/1; overflow: hidden; border-radius: 4px;">
<img
src={photo.src}
alt=""
style={`width: 100%; height: 100%; object-fit: cover; object-position: ${photo.pos};`}
loading="lazy"
decoding="async"
/>
</div>
))}
</div>
</div>
<!-- Donate CTA -->
<div class="fb-card" style="padding: 22px; background: #1e2e1a;">
<p style="font-family: 'Merriweather', serif; font-weight: 700; font-size: 18px; color: #F6F1E8; line-height: 1.3; margin-bottom: 8px;">
Like what you see?
</p>
<p style="font-family: 'Public Sans', sans-serif; font-weight: 500; font-size: 14px; color: rgba(246,241,232,0.7); line-height: 1.6; margin-bottom: 18px;">
Donate today to help us keep going.
</p>
<a href="https://www.justgiving.com/charity/highlandgrouprda" target="_blank" rel="noreferrer">
<img src="/Button.webp" alt="Donate via JustGiving" style="height: 44px; object-fit: contain; display: block;" loading="lazy" />
</a>
</div>
</aside>
</div>
</section>
<!-- ── EMBED NOTE ── -->
<section style="background: #d6d6d6; padding: 24px clamp(24px, 5vw, 72px); border-top: 1px solid rgba(0,0,0,0.1);">
<div style="max-width: 1100px; margin: 0 auto; display: flex; align-items: center; gap: 16px; flex-wrap: wrap;">
<div style="flex-shrink: 0; width: 32px; height: 32px; border-radius: 9999px; background: rgba(125,163,113,0.15); display: flex; align-items: center; justify-content: center;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#7DA371" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/>
</svg>
</div>
<p style="flex: 1; font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: 13px; color: rgba(0,0,0,0.6); line-height: 1.6; min-width: 200px;">
The posts above are a curated preview of our Facebook activity. For the full feed, visit us directly on <a href={facebookPageUrl} target="_blank" rel="noreferrer" style="color: #1877F2; text-decoration: underline;">Facebook</a>.
</p>
</div>
</section>
</main>
<SiteFooter fullBleedOnMobile />
</div>
<script>
const nav = document.getElementById("site-nav");
if (nav) {
window.addEventListener("scroll", () => {
nav.classList.toggle("scrolled", window.scrollY > 40);
}, { passive: true });
}
document.querySelectorAll("[data-like-btn]").forEach((btn) => {
btn.addEventListener("click", () => {
const card = btn.closest("[data-post-id]");
if (!card) return;
const liked = btn.getAttribute("data-liked") === "true";
const countEl = card.querySelector("[data-like-count]");
if (countEl) {
const base = parseInt(countEl.getAttribute("data-like-count") ?? "0", 10);
countEl.textContent = String(base + (liked ? 0 : 1));
}
btn.setAttribute("data-liked", liked ? "false" : "true");
btn.setAttribute("aria-pressed", liked ? "false" : "true");
});
});
</script>
</BaseLayout>

View file

@ -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?
</h2>
<p style="font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: 16px; color: rgba(246,241,232,0.75); line-height: 1.65; max-width: 680px; margin-bottom: 40px;">
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.
</p>
<div class="video-embed" style="max-width: 860px; margin: 0 auto; box-shadow: 0 20px 60px rgba(0,0,0,0.5);">
<iframe
@ -309,10 +319,69 @@ const ctaCards = [
<p style="font-family: 'Merriweather', serif; font-weight: 700; font-size: clamp(20px, 2.5vw, 30px); color: #F6F1E8; text-align: center; margin-bottom: 48px; line-height: 1.2;">
We are grateful for the generous support of our sponsors.
</p>
<div style="display: flex; flex-wrap: wrap; gap: 24px; justify-content: center; align-items: center;">
{["Sponsor 1", "Sponsor 2", "Sponsor 3", "Sponsor 4", "Sponsor 5", "Sponsor 6"].map((s) => (
<div class="sponsor-logo">{s}</div>
))}
<!-- Current sponsors -->
<div style="display: flex; flex-wrap: wrap; gap: 40px; justify-content: center; align-items: center; margin-bottom: 64px;">
<div class="sponsor-logo">
<img src="/sponsors/ffordes_logo.png" alt="Ffordes" loading="lazy" decoding="async" />
</div>
</div>
<!-- Corporate sponsorship package -->
<div style="border-top: 1px solid rgba(255,255,255,0.12); padding-top: 56px;">
<p style="font-family: 'Public Sans', sans-serif; font-weight: 700; font-size: 11px; letter-spacing: 0.25em; text-transform: uppercase; color: #7DA371; text-align: center; margin-bottom: 12px;">
Corporate Sponsorship
</p>
<h3 style="font-family: 'Merriweather', serif; font-weight: 700; font-size: clamp(20px, 2.5vw, 30px); color: #F6F1E8; text-align: center; margin-bottom: 12px; line-height: 1.2;">
Sponsor one of our horses — £750 per year
</h3>
<p style="font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: 16px; color: rgba(246,241,232,0.7); text-align: center; max-width: 620px; margin: 0 auto 48px; line-height: 1.65;">
Your sponsorship covers the annual livery costs of one of our horses, making a direct and lasting difference to the lives of our participants.
</p>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px; max-width: 960px; margin: 0 auto 48px;">
<div style="background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; padding: 24px 22px;">
<div style="width: 32px; height: 32px; background: #7DA371; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 14px;">
<svg width="16" height="16" viewBox="0 0 16 16" fill="white" aria-hidden="true"><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zm3.5 6.5l-4 4-2-2-1 1 3 3 5-5-1-1z"/></svg>
</div>
<p style="font-family: 'Merriweather', serif; font-weight: 700; font-size: 15px; color: #F6F1E8; margin-bottom: 8px; line-height: 1.3;">Social media recognition</p>
<p style="font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: 14px; color: rgba(246,241,232,0.65); line-height: 1.6;">Your company featured in our posts on Facebook and Instagram, and in any press coverage we receive.</p>
</div>
<div style="background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; padding: 24px 22px;">
<div style="width: 32px; height: 32px; background: #7DA371; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 14px;">
<svg width="16" height="16" viewBox="0 0 16 16" fill="white" aria-hidden="true"><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zm3.5 6.5l-4 4-2-2-1 1 3 3 5-5-1-1z"/></svg>
</div>
<p style="font-family: 'Merriweather', serif; font-weight: 700; font-size: 15px; color: #F6F1E8; margin-bottom: 8px; line-height: 1.3;">Branded banner at Sandycroft</p>
<p style="font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: 14px; color: rgba(246,241,232,0.65); line-height: 1.6;">A 1m × 1.8m printed banner with your company logo displayed at our Sandycroft base, seen by volunteers, participants, and the public.</p>
</div>
<div style="background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; padding: 24px 22px;">
<div style="width: 32px; height: 32px; background: #7DA371; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 14px;">
<svg width="16" height="16" viewBox="0 0 16 16" fill="white" aria-hidden="true"><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zm3.5 6.5l-4 4-2-2-1 1 3 3 5-5-1-1z"/></svg>
</div>
<p style="font-family: 'Merriweather', serif; font-weight: 700; font-size: 15px; color: #F6F1E8; margin-bottom: 8px; line-height: 1.3;">Logo on our website</p>
<p style="font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: 14px; color: rgba(246,241,232,0.65); line-height: 1.6;">Your company logo displayed as a sponsor banner on the Highland Group RDA website.</p>
</div>
<div style="background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; padding: 24px 22px;">
<div style="width: 32px; height: 32px; background: #7DA371; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 14px;">
<svg width="16" height="16" viewBox="0 0 16 16" fill="white" aria-hidden="true"><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zm3.5 6.5l-4 4-2-2-1 1 3 3 5-5-1-1z"/></svg>
</div>
<p style="font-family: 'Merriweather', serif; font-weight: 700; font-size: 15px; color: #F6F1E8; margin-bottom: 8px; line-height: 1.3;">Event invitations &amp; pony updates</p>
<p style="font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: 14px; color: rgba(246,241,232,0.65); line-height: 1.6;">Invitations to Highland Group RDA events, plus three-monthly updates on the specific pony you are sponsoring.</p>
</div>
</div>
<div style="text-align: center;">
<p style="font-family: 'Public Sans', sans-serif; font-weight: 700; font-size: 15px; color: rgba(246,241,232,0.8); margin-bottom: 20px;">
Interested in becoming a corporate sponsor?
</p>
<a href="/contact" style="display: inline-block; font-family: 'Public Sans', sans-serif; font-weight: 800; font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase; color: #1e2e1a; background: #7DA371; border-radius: 999px; padding: 12px 28px; text-decoration: none; transition: opacity 0.2s ease;">
Get in touch
</a>
<span style="display: inline-block; margin: 0 12px; color: rgba(246,241,232,0.4); font-size: 14px;">or</span>
<a href="/support-us" style="display: inline-block; font-family: 'Public Sans', sans-serif; font-weight: 800; font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase; color: rgba(246,241,232,0.85); border: 1px solid rgba(246,241,232,0.3); border-radius: 999px; padding: 12px 28px; text-decoration: none;">
Support us
</a>
</div>
</div>
</div>
</section>

File diff suppressed because it is too large Load diff

View file

@ -52,7 +52,7 @@ const pageSeo = {
class="mt-3 [font-family:'Public_Sans',Helvetica] font-semibold text-black text-base sm:text-lg leading-[1.6]"
>
Highland Group RDA is a registered charity providing
riding and carriage-driving activities for disabled
riding activities for disabled
children and adults.
</p>
<ul

View file

@ -7,7 +7,7 @@ import { navigationItems } from "../lib/navigation";
const pageSeo = {
title: "Support Us",
description:
"Support Highland Group RDA through donations, volunteering, or fundraising. Help us provide free, life-changing horse riding sessions in the Highlands.",
"Support Highland Group RDA through donations, volunteering, or fundraising. Help us provide life-changing horse riding sessions in the Highlands.",
keywords:
"Support Highland RDA, donate to RDA, RDA fundraising, volunteer with RDA, charity donations Scotland, equestrian therapy support",
canonicalPath: "/support-us",

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,704 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import SiteHeader from "../components/SiteHeader.astro";
import SiteFooter from "../components/SiteFooter.astro";
import { navigationItems } from "../lib/navigation";
const pageSeo = {
title: "Volunteer Reference Form",
description:
"Volunteer reference form for Highland Group RDA. Complete this form if you have been asked to provide a reference for a volunteer applicant.",
keywords: "",
canonicalPath: "/volunteer-reference",
};
const turnstileSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY ?? "";
---
<BaseLayout {...pageSeo}>
{turnstileSiteKey && (
<Fragment slot="head">
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</Fragment>
)}
<Fragment slot="head">
<meta name="robots" content="noindex, nofollow" />
</Fragment>
<style>
/* ── Nav ── */
#site-nav { transition: background 0.4s ease, backdrop-filter 0.4s ease; }
#site-nav.scrolled { background: rgba(0,0,0,0.82); backdrop-filter: blur(8px); }
/* ── Animations ── */
@keyframes fadeUp { from { opacity:0; transform:translateY(14px); } to { opacity:1; transform:translateY(0); } }
@keyframes slideIn { from { opacity:0; transform:translateX(18px); } to { opacity:1; transform:translateX(0); } }
.anim-fade-up { animation: fadeUp 0.9s ease both; }
.step-anim { animation: slideIn 0.35s ease both; }
/* ── Fields ── */
.field-input {
width: 100%;
border: 1.5px solid rgba(0,0,0,0.18);
border-radius: 6px;
padding: 11px 14px;
font-family: 'Public Sans', sans-serif;
font-size: 15px; font-weight: 600;
color: #000; background: #fff;
outline: none;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
appearance: none;
box-sizing: border-box;
}
.field-input:focus { border-color: #7DA371; box-shadow: 0 0 0 3px rgba(125,163,113,0.18); }
.field-input::placeholder { color: rgba(0,0,0,0.35); font-weight: 500; }
textarea.field-input { resize: vertical; line-height: 1.6; min-height: 110px; }
/* ── Choice cards ── */
.choice {
display: flex; align-items: flex-start; gap: 12px;
cursor: pointer; padding: 13px 15px; border-radius: 6px;
border: 1.5px solid rgba(0,0,0,0.15); background: #fff;
transition: border-color 0.2s ease, background 0.2s ease;
user-select: none;
}
.choice:hover { border-color: #7DA371; }
.choice.selected { border-color: #7DA371; background: rgba(125,163,113,0.07); }
.choice-mark {
width: 18px; height: 18px; flex-shrink: 0; margin-top: 2px;
border: 2px solid rgba(0,0,0,0.25);
display: flex; align-items: center; justify-content: center;
transition: all 0.2s;
}
.choice-mark.radio { border-radius: 9999px; }
.choice-mark.check { border-radius: 4px; }
.choice.selected .choice-mark { border-color: #7DA371; background: #7DA371; }
/* ── Buttons ── */
.btn-primary {
display: inline-flex; align-items: center; gap: 10px;
background: #7DA371; color: #fff; border: none; border-radius: 9999px;
padding: 13px 28px;
font-family: 'Public Sans', sans-serif; font-weight: 800; font-size: 13px;
letter-spacing: 0.06em; text-transform: uppercase;
cursor: pointer; transition: opacity 0.2s ease;
}
.btn-primary:hover:not(:disabled) { opacity: 0.88; }
.btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-secondary {
display: inline-flex; align-items: center; gap: 10px;
background: transparent; color: #000;
border: 2px solid rgba(0,0,0,0.2); border-radius: 9999px;
padding: 11px 24px;
font-family: 'Public Sans', sans-serif; font-weight: 800; font-size: 13px;
letter-spacing: 0.06em; text-transform: uppercase;
cursor: pointer; transition: border-color 0.2s ease;
}
.btn-secondary:hover:not(:disabled) { border-color: #7DA371; }
.btn-secondary:disabled { opacity: 0.35; cursor: not-allowed; }
/* ── Progress ── */
.progress-pill {
display: inline-flex; align-items: center; gap: 9px;
border-radius: 9999px; padding: 8px 15px;
font-family: 'Public Sans', sans-serif; font-weight: 700; font-size: 12px;
letter-spacing: 0.04em; white-space: nowrap;
cursor: default; border: 1.5px solid transparent;
transition: all 0.25s ease;
}
.progress-pill.active { background: #7DA371; color: #fff; border-color: #7DA371; cursor: default; }
.progress-pill.done { background: rgba(125,163,113,0.12); color: #7DA371; border-color: rgba(125,163,113,0.35); cursor: pointer; }
.progress-pill.upcoming { background: transparent; color: rgba(0,0,0,0.4); border-color: rgba(0,0,0,0.12); cursor: default; }
.pill-num {
width: 20px; height: 20px; border-radius: 9999px;
display: inline-flex; align-items: center; justify-content: center;
font-size: 11px; font-weight: 800;
}
.progress-pill.active .pill-num { background: rgba(255,255,255,0.25); color: #fff; }
.progress-pill.done .pill-num { background: #7DA371; color: #fff; }
.progress-pill.upcoming .pill-num { background: rgba(0,0,0,0.08); color: rgba(0,0,0,0.4); }
/* ── Field label ── */
.field-label {
font-family: 'Public Sans', sans-serif;
font-weight: 700; font-size: 13px;
color: rgba(0,0,0,0.7); letter-spacing: 0.04em;
text-transform: uppercase; margin-bottom: 8px; display: block;
}
.field-label .req { color: #7DA371; margin-left: 3px; }
.field-hint { margin-top: 4px; font-family: 'Public Sans', sans-serif; font-weight: 500; font-size: 13px; color: rgba(0,0,0,0.5); }
.field-error { font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: 13px; color: #c0392b; margin-top: 6px; display: none; }
.field-error.visible { display: block; }
/* ── Steps ── */
.step-panel { display: none; }
.step-panel.active { display: block; }
/* ── Success ── */
#success-panel { display: none; }
@media (max-width: 767px) {
.grid-2 { grid-template-columns: 1fr !important; }
.grid-3 { grid-template-columns: 1fr !important; }
.progress-wrap { gap: 4px !important; }
.progress-pill { font-size: 0; padding: 8px 10px; }
.progress-pill .pill-label { display: none; }
}
</style>
<!-- Fixed nav -->
<div id="site-nav" class="fixed top-0 left-0 right-0 z-50 px-4 sm:px-10">
<SiteHeader navigationItems={navigationItems} theme="dark" />
</div>
<div class="flex flex-col min-h-screen bg-[#e2e2e2]">
<main class="w-full">
<!-- ── HERO ── -->
<section class="relative w-full overflow-hidden" style="min-height: 46vh;">
<img
class="absolute inset-0 w-full h-full object-cover"
style="object-position: 50% 35%;"
src="/274864973_7295359163837841_5927911936073070889_n-1920w.webp"
alt=""
loading="eager" fetchpriority="high" decoding="async"
/>
<div class="absolute inset-0" style="background: linear-gradient(160deg,rgba(0,0,0,0.78) 0%,rgba(0,0,0,0.42) 60%,rgba(0,0,0,0.55) 100%);" />
<div class="relative flex flex-col justify-end" style="min-height: 46vh; padding: 100px clamp(24px,5vw,72px) 60px;">
<div class="anim-fade-up" style="max-width: 680px;">
<p style="font-family:'Public Sans',sans-serif;font-weight:700;font-size:12px;letter-spacing:0.25em;text-transform:uppercase;color:rgba(255,255,255,0.7);margin-bottom:14px;">Volunteer reference</p>
<div style="width:48px;height:2px;background:#7DA371;margin-bottom:20px;"></div>
<h1 style="font-family:'Merriweather',serif;font-weight:900;font-size:clamp(30px,4.5vw,50px);color:#F6F1E8;line-height:1.1;margin-bottom:16px;">Volunteer Reference Form</h1>
<p style="font-family:'Public Sans',sans-serif;font-weight:600;font-size:clamp(15px,1.5vw,18px);color:rgba(246,241,232,0.82);line-height:1.65;">You've been asked to provide a reference for someone applying to volunteer with us. All information is treated in the strictest confidence and used only for assessing the application.</p>
</div>
</div>
</section>
<!-- ── FORM SECTION ── -->
<section style="background:#d6d6d6;padding:clamp(48px,8vw,72px) clamp(24px,5vw,72px) clamp(64px,10vw,96px);">
<div style="max-width:820px;margin:0 auto;" id="form-root">
<!-- ── PROGRESS ── -->
<div id="progress-wrapper" style="margin-bottom:36px;">
<div class="progress-wrap" style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;" id="progress-pills"></div>
<div style="height:4px;background:rgba(0,0,0,0.1);border-radius:9999px;overflow:hidden;margin-top:20px;">
<div id="progress-bar" style="height:100%;background:#7DA371;border-radius:9999px;transition:width 0.4s ease;width:20%;"></div>
</div>
</div>
<!-- ── FORM CARD ── -->
<div style="background:#e2e2e2;border-radius:8px;padding:clamp(28px,5vw,48px) clamp(24px,5vw,52px);">
<!-- SUCCESS -->
<div id="success-panel" style="text-align:center;padding:48px 0;">
<div style="width:72px;height:72px;border-radius:9999px;background:#7DA371;display:flex;align-items:center;justify-content:center;margin:0 auto 24px;">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
</div>
<h2 style="font-family:'Merriweather',serif;font-weight:700;font-size:clamp(22px,2.5vw,30px);color:#000;margin-bottom:14px;">Thank you for your reference!</h2>
<p style="font-family:'Public Sans',sans-serif;font-weight:600;font-size:16px;color:rgba(0,0,0,0.65);line-height:1.7;max-width:460px;margin:0 auto 32px;">
Your reference has been received by Highland Group RDA and will be treated in the strictest confidence. The applicant's coordinator will be in touch if further information is needed.
</p>
<a href="/" style="display:inline-flex;align-items:center;gap:10px;background:#7DA371;color:#fff;border-radius:9999px;padding:13px 28px;font-family:'Public Sans',sans-serif;font-weight:800;font-size:13px;letter-spacing:0.06em;text-transform:uppercase;text-decoration:none;">Back to homepage</a>
</div>
<!-- ── STEP 0: THE APPLICANT ── -->
<div class="step-panel active step-anim" data-step="0">
<h2 style="font-family:'Merriweather',serif;font-weight:700;font-size:clamp(22px,2.2vw,28px);color:#000;margin-bottom:8px;">The applicant</h2>
<p style="font-family:'Public Sans',sans-serif;font-weight:600;font-size:15px;color:rgba(0,0,0,0.6);line-height:1.6;margin-bottom:32px;">Please enter the name of the person you are providing a reference for. If you're unsure of the exact spelling, check with the person who sent you this link.</p>
<div class="grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
<div>
<label class="field-label" for="f-app-first">Applicant's first name(s) <span class="req">*</span></label>
<input class="field-input" id="f-app-first" name="applicant_first_names" type="text" placeholder="Jane" autocomplete="off" />
<p class="field-error" id="err-app-first">Please enter the applicant's first name.</p>
</div>
<div>
<label class="field-label" for="f-app-last">Applicant's last name <span class="req">*</span></label>
<input class="field-input" id="f-app-last" name="applicant_last_name" type="text" placeholder="Smith" autocomplete="off" />
<p class="field-error" id="err-app-last">Please enter the applicant's last name.</p>
</div>
</div>
</div>
<!-- ── STEP 1: YOUR DETAILS ── -->
<div class="step-panel step-anim" data-step="1">
<h2 style="font-family:'Merriweather',serif;font-weight:700;font-size:clamp(22px,2.2vw,28px);color:#000;margin-bottom:8px;">Your details</h2>
<p style="font-family:'Public Sans',sans-serif;font-weight:600;font-size:15px;color:rgba(0,0,0,0.6);line-height:1.6;margin-bottom:32px;">Your contact details as the person providing this reference.</p>
<div class="grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
<div>
<label class="field-label" for="f-ref-name">Your full name <span class="req">*</span></label>
<input class="field-input" id="f-ref-name" name="referee_name" type="text" placeholder="Alex Mackenzie" autocomplete="name" />
<p class="field-error" id="err-ref-name">Please enter your full name.</p>
</div>
<div>
<label class="field-label" for="f-ref-email">Your email address <span class="req">*</span></label>
<input class="field-input" id="f-ref-email" name="referee_email" type="email" placeholder="alex@example.com" autocomplete="email" />
<p class="field-error" id="err-ref-email">Please enter a valid email address.</p>
</div>
<div>
<label class="field-label" for="f-ref-job">Job title or role</label>
<input class="field-input" id="f-ref-job" name="referee_job_title" type="text" placeholder="e.g. Teacher" />
</div>
<div>
<label class="field-label" for="f-ref-tel">Telephone</label>
<input class="field-input" id="f-ref-tel" name="referee_telephone" type="tel" placeholder="07700 900000" autocomplete="tel" />
</div>
<div style="grid-column:1/-1;">
<label class="field-label" for="f-ref-org">Organisation (if applicable)</label>
<input class="field-input" id="f-ref-org" name="referee_organisation" type="text" placeholder="e.g. Inverness Academy" />
</div>
<div style="grid-column:1/-1;">
<label class="field-label" for="f-ref-addr">Address</label>
<textarea class="field-input" id="f-ref-addr" name="referee_address" placeholder="Street, Town, Postcode" style="min-height:80px;"></textarea>
</div>
</div>
</div>
<!-- ── STEP 2: YOUR RELATIONSHIP ── -->
<div class="step-panel step-anim" data-step="2">
<h2 style="font-family:'Merriweather',serif;font-weight:700;font-size:clamp(22px,2.2vw,28px);color:#000;margin-bottom:8px;">Your relationship</h2>
<p style="font-family:'Public Sans',sans-serif;font-weight:600;font-size:15px;color:rgba(0,0,0,0.6);line-height:1.6;margin-bottom:32px;">Please tell us how you know the applicant and for how long.</p>
<div style="display:flex;flex-direction:column;gap:28px;">
<div>
<label class="field-label">In what capacity do you know the applicant? <span class="req">*</span></label>
<div class="grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:4px;">
{["Employer / line manager", "Colleague", "Teacher / tutor", "Community or faith leader", "Friend", "Other"].map((opt) => (
<div class="choice" data-group="relationship_capacity" data-val={opt}>
<div class="choice-mark radio">
<div class="choice-dot" style="display:none;width:7px;height:7px;border-radius:9999px;background:#fff;"></div>
</div>
<span style="font-family:'Public Sans',sans-serif;font-size:14px;font-weight:600;color:#000;">{opt}</span>
</div>
))}
</div>
<input type="hidden" id="hid-capacity" />
<p class="field-error" id="err-capacity">Please select your relationship to the applicant.</p>
</div>
<div>
<label class="field-label" for="f-rel-other">If you selected "Other", please describe your relationship</label>
<input class="field-input" id="f-rel-other" name="relationship_other" type="text" placeholder="e.g. Neighbour, club leader…" />
</div>
<div>
<label class="field-label" for="f-known-duration">How long have you known the applicant? <span class="req">*</span></label>
<input class="field-input" id="f-known-duration" name="known_duration" type="text" placeholder="e.g. 3 years" />
<p class="field-error" id="err-known-duration">Please enter how long you have known the applicant.</p>
</div>
<div>
<label class="field-label">Are you related to the applicant?</label>
<p class="field-hint" style="margin-bottom:10px;">RDA requires references from people who are not related to the applicant.</p>
<div style="display:flex;gap:10px;flex-wrap:wrap;">
{["Yes", "No"].map((opt) => (
<div class="choice" data-group="related_to_applicant" data-val={opt} style="flex:0 0 auto;min-width:100px;">
<div class="choice-mark radio">
<div class="choice-dot" style="display:none;width:7px;height:7px;border-radius:9999px;background:#fff;"></div>
</div>
<span style="font-family:'Public Sans',sans-serif;font-size:14px;font-weight:600;color:#000;">{opt}</span>
</div>
))}
</div>
<input type="hidden" id="hid-related" />
</div>
</div>
</div>
<!-- ── STEP 3: ASSESSMENT ── -->
<div class="step-panel step-anim" data-step="3">
<h2 style="font-family:'Merriweather',serif;font-weight:700;font-size:clamp(22px,2.2vw,28px);color:#000;margin-bottom:8px;">Your assessment</h2>
<p style="font-family:'Public Sans',sans-serif;font-weight:600;font-size:15px;color:rgba(0,0,0,0.6);line-height:1.6;margin-bottom:32px;">Please give your honest assessment. These questions help us ensure the safety and wellbeing of our participants, who include children and vulnerable adults.</p>
<div style="display:flex;flex-direction:column;gap:28px;">
<div>
<label class="field-label" for="f-character">How would you describe the applicant's character and personal qualities? <span class="req">*</span></label>
<textarea class="field-input" id="f-character" name="character_assessment" placeholder="Please share your honest impressions of their personality, values, and how they interact with others…"></textarea>
<p class="field-error" id="err-character">Please provide a character assessment.</p>
</div>
<div>
<label class="field-label">Do you consider them reliable and punctual?</label>
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:4px;">
{["Yes", "No", "Not sure"].map((opt) => (
<div class="choice" data-group="reliable_punctual" data-val={opt} style="flex:0 0 auto;min-width:100px;">
<div class="choice-mark radio">
<div class="choice-dot" style="display:none;width:7px;height:7px;border-radius:9999px;background:#fff;"></div>
</div>
<span style="font-family:'Public Sans',sans-serif;font-size:14px;font-weight:600;color:#000;">{opt}</span>
</div>
))}
</div>
<input type="hidden" id="hid-reliable" />
</div>
<div>
<label class="field-label">Do you consider them suitable to work with people with disabilities, including children and vulnerable adults? <span class="req">*</span></label>
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:4px;">
{["Yes", "No", "Not sure"].map((opt) => (
<div class="choice" data-group="suitable_for_role" data-val={opt} style="flex:0 0 auto;min-width:100px;">
<div class="choice-mark radio">
<div class="choice-dot" style="display:none;width:7px;height:7px;border-radius:9999px;background:#fff;"></div>
</div>
<span style="font-family:'Public Sans',sans-serif;font-size:14px;font-weight:600;color:#000;">{opt}</span>
</div>
))}
</div>
<input type="hidden" id="hid-suitable" />
<p class="field-error" id="err-suitable">Please indicate whether you consider the applicant suitable for this role.</p>
</div>
<div>
<label class="field-label" for="f-concerns">Is there anything we should be aware of regarding the applicant's suitability to work with vulnerable people?</label>
<textarea class="field-input" id="f-concerns" name="suitability_concerns" placeholder="If yes, please describe. If not, you can leave this blank."></textarea>
</div>
<div>
<label class="field-label" for="f-additional">Any other comments you would like to add?</label>
<textarea class="field-input" id="f-additional" name="additional_comments" placeholder="Optional — any further information that might be helpful."></textarea>
</div>
</div>
</div>
<!-- ── STEP 4: DECLARATION ── -->
<div class="step-panel step-anim" data-step="4">
<h2 style="font-family:'Merriweather',serif;font-weight:700;font-size:clamp(22px,2.2vw,28px);color:#000;margin-bottom:8px;">Declaration</h2>
<p style="font-family:'Public Sans',sans-serif;font-weight:600;font-size:15px;color:rgba(0,0,0,0.6);line-height:1.6;margin-bottom:32px;">Please confirm the following before submitting your reference.</p>
<div style="display:flex;flex-direction:column;gap:28px;">
<div>
<label class="field-label">Please read and confirm <span class="req">*</span></label>
<div style="display:flex;flex-direction:column;gap:10px;margin-top:4px;">
<div class="choice" data-group="declaration_truth" data-val="Yes" data-multi="true">
<div class="choice-mark check">
<svg class="choice-tick" width="11" height="11" viewBox="0 0 12 12" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="display:none;"><path d="M10 3L4.5 8.5 2 6"/></svg>
</div>
<span style="font-family:'Public Sans',sans-serif;font-size:14px;font-weight:600;color:#000;line-height:1.55;">I confirm that the information provided in this reference is true and accurate to the best of my knowledge. <span style="color:#7DA371;">*</span></span>
</div>
<div class="choice" data-group="declaration_consent" data-val="Yes" data-multi="true">
<div class="choice-mark check">
<svg class="choice-tick" width="11" height="11" viewBox="0 0 12 12" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="display:none;"><path d="M10 3L4.5 8.5 2 6"/></svg>
</div>
<span style="font-family:'Public Sans',sans-serif;font-size:14px;font-weight:600;color:#000;line-height:1.55;">I understand that this reference will be treated as confidential and used only to assess the above applicant's suitability to volunteer with Highland Group RDA, in line with the Data Protection Act 2018.</span>
</div>
</div>
<p class="field-error" id="err-declaration">Please confirm the declaration of truth to submit.</p>
</div>
<div class="grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
<div>
<label class="field-label" for="f-sig">Signature name</label>
<input class="field-input" id="f-sig" name="declaration_signature" type="text" placeholder="Your full name" autocomplete="name" />
</div>
<div>
<label class="field-label" for="f-date">Date</label>
<input class="field-input" id="f-date" name="declaration_date" type="date" />
</div>
</div>
{turnstileSiteKey && (
<div>
<div class="cf-turnstile" data-sitekey={turnstileSiteKey}></div>
</div>
)}
<p class="field-error" id="err-submit" style="margin-top:4px;"></p>
</div>
</div>
<!-- ── NAV BUTTONS ── -->
<div id="form-nav" style="margin-top:36px;padding-top:28px;border-top:1px solid rgba(0,0,0,0.12);display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;">
<button class="btn-secondary" id="btn-back" disabled>
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13 8H3M7 4L3 8l4 4"/></svg>
Back
</button>
<p id="step-counter" style="font-family:'Public Sans',sans-serif;font-weight:600;font-size:13px;color:rgba(0,0,0,0.5);">Step 1 of 5</p>
<button class="btn-primary" id="btn-next">
Continue
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4"/></svg>
</button>
</div>
</div>
<p style="margin-top:20px;font-family:'Public Sans',sans-serif;font-weight:600;font-size:13px;color:rgba(0,0,0,0.5);text-align:center;line-height:1.6;" id="help-text">
Need help? <a href="/contact" style="text-decoration:underline;color:#7DA371;">Contact us</a> and we'll be happy to assist.
</p>
</div>
</section>
</main>
<SiteFooter fullBleedOnMobile />
</div>
<script>
(() => {
const STEPS = [
{ label: 'The applicant' },
{ label: 'Your details' },
{ label: 'Relationship' },
{ label: 'Assessment' },
{ label: 'Declaration' },
];
let currentStep = 0;
const completed = new Set();
const multiState = {
declaration_truth: [],
declaration_consent: [],
};
// ── Pill rendering ──
function renderPills() {
const wrap = document.getElementById('progress-pills');
wrap.innerHTML = '';
STEPS.forEach((s, i) => {
const isDone = completed.has(i);
const isActive = i === currentStep;
const pill = document.createElement('button');
pill.type = 'button';
pill.className = 'progress-pill ' + (isActive ? 'active' : isDone ? 'done' : 'upcoming');
pill.disabled = !(isDone || isActive);
pill.innerHTML = `
<span class="pill-num">${isDone
? '<svg width="11" height="11" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L4.5 8.5 2 6"/></svg>'
: (i + 1)
}</span>
<span class="pill-label">${s.label}</span>`;
if (isDone) pill.addEventListener('click', () => goTo(i));
if (i < STEPS.length - 1) {
wrap.appendChild(pill);
const sep = document.createElement('div');
sep.style.cssText = 'width:14px;height:1.5px;background:rgba(0,0,0,0.15);flex-shrink:0;';
wrap.appendChild(sep);
} else {
wrap.appendChild(pill);
}
});
const bar = document.getElementById('progress-bar');
bar.style.width = `${((currentStep + 1) / STEPS.length) * 100}%`;
document.getElementById('step-counter').textContent = `Step ${currentStep + 1} of ${STEPS.length}`;
}
// ── Step visibility ──
function showStep(n) {
document.querySelectorAll('.step-panel').forEach(p => {
p.classList.remove('active');
p.classList.remove('step-anim');
});
const panel = document.querySelector(`.step-panel[data-step="${n}"]`);
if (panel) {
panel.classList.add('active');
void panel.offsetWidth;
panel.classList.add('step-anim');
}
document.getElementById('btn-back').disabled = n === 0;
const btnNext = document.getElementById('btn-next');
if (n === STEPS.length - 1) {
btnNext.innerHTML = `Submit reference <svg width="14" height="14" viewBox="0 0 12 12" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L4.5 8.5 2 6"/></svg>`;
} else {
btnNext.innerHTML = `Continue <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8h10M9 4l4 4-4 4"/></svg>`;
}
document.getElementById('form-root').scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function goTo(n) {
currentStep = n;
renderPills();
showStep(n);
}
// ── Choice cards ──
document.querySelectorAll('.choice').forEach(card => {
card.addEventListener('click', () => {
const group = card.dataset.group;
const cardVal = card.dataset.val;
const multi = card.dataset.multi === 'true';
if (multi) {
const arr = multiState[group] || [];
const idx = arr.indexOf(cardVal);
if (idx === -1) arr.push(cardVal); else arr.splice(idx, 1);
multiState[group] = arr;
document.querySelectorAll(`.choice[data-group="${group}"]`).forEach(c => {
const isSelected = arr.includes(c.dataset.val);
c.classList.toggle('selected', isSelected);
const tick = c.querySelector('.choice-tick');
if (tick) tick.style.display = isSelected ? 'block' : 'none';
});
} else {
// Radio: deselect all in group
document.querySelectorAll(`.choice[data-group="${group}"]`).forEach(c => {
c.classList.remove('selected');
const dot = c.querySelector('.choice-dot');
if (dot) dot.style.display = 'none';
});
card.classList.add('selected');
const dot = card.querySelector('.choice-dot');
if (dot) dot.style.display = 'block';
// Update hidden input
const hiddenMap = {
relationship_capacity: 'hid-capacity',
related_to_applicant: 'hid-related',
reliable_punctual: 'hid-reliable',
suitable_for_role: 'hid-suitable',
};
const hidId = hiddenMap[group];
if (hidId) document.getElementById(hidId).value = cardVal;
}
// Clear relevant errors
const errMap = {
relationship_capacity: 'err-capacity',
suitable_for_role: 'err-suitable',
declaration_truth: 'err-declaration',
};
const errId = errMap[group];
if (errId) document.getElementById(errId)?.classList.remove('visible');
});
});
// ── Validation ──
function showErr(id, show) {
const el = document.getElementById(id);
if (el) el.classList.toggle('visible', show);
return show;
}
function fieldVal(id) {
return (document.getElementById(id)?.value || '').trim();
}
function validateStep(s) {
let ok = true;
if (s === 0) {
if (!fieldVal('f-app-first')) { showErr('err-app-first', true); ok = false; } else showErr('err-app-first', false);
if (!fieldVal('f-app-last')) { showErr('err-app-last', true); ok = false; } else showErr('err-app-last', false);
}
if (s === 1) {
if (!fieldVal('f-ref-name')) { showErr('err-ref-name', true); ok = false; } else showErr('err-ref-name', false);
const email = fieldVal('f-ref-email');
if (!email || !/\S+@\S+\.\S+/.test(email)) { showErr('err-ref-email', true); ok = false; } else showErr('err-ref-email', false);
}
if (s === 2) {
if (!fieldVal('hid-capacity')) { showErr('err-capacity', true); ok = false; } else showErr('err-capacity', false);
if (!fieldVal('f-known-duration')) { showErr('err-known-duration', true); ok = false; } else showErr('err-known-duration', false);
}
if (s === 3) {
if (!fieldVal('f-character')) { showErr('err-character', true); ok = false; } else showErr('err-character', false);
if (!fieldVal('hid-suitable')) { showErr('err-suitable', true); ok = false; } else showErr('err-suitable', false);
}
if (s === 4) {
if (!multiState.declaration_truth.includes('Yes')) { showErr('err-declaration', true); ok = false; } else showErr('err-declaration', false);
}
return ok;
}
// ── Submit ──
async function submitForm() {
const btnNext = document.getElementById('btn-next');
const errEl = document.getElementById('err-submit');
btnNext.disabled = true;
btnNext.textContent = 'Submitting…';
errEl.classList.remove('visible');
const body = new URLSearchParams();
// All named text fields
const fields = [
['applicant_first_names', 'f-app-first'],
['applicant_last_name', 'f-app-last'],
['referee_name', 'f-ref-name'],
['referee_email', 'f-ref-email'],
['referee_job_title', 'f-ref-job'],
['referee_telephone', 'f-ref-tel'],
['referee_organisation', 'f-ref-org'],
['referee_address', 'f-ref-addr'],
['relationship_other', 'f-rel-other'],
['known_duration', 'f-known-duration'],
['character_assessment', 'f-character'],
['suitability_concerns', 'f-concerns'],
['additional_comments', 'f-additional'],
['declaration_signature', 'f-sig'],
['declaration_date', 'f-date'],
// radio hidden inputs
['relationship_capacity', 'hid-capacity'],
['related_to_applicant', 'hid-related'],
['reliable_punctual', 'hid-reliable'],
['suitable_for_role', 'hid-suitable'],
];
fields.forEach(([name, id]) => {
const el = document.getElementById(id);
if (el && el.value.trim()) body.append(name, el.value.trim());
});
// Checkbox declarations
if (multiState.declaration_truth.includes('Yes')) body.append('declaration_truth', 'Yes');
if (multiState.declaration_consent.includes('Yes')) body.append('declaration_consent', 'Yes');
// Turnstile
const tsInput = document.querySelector('[name="cf-turnstile-response"]');
if (tsInput) body.append('cf-turnstile-response', tsInput.value);
try {
const res = await fetch('/api/volunteer-reference', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body,
});
const json = await res.json().catch(() => ({}));
if (!res.ok) {
throw new Error(json.details ? `${json.error} (${json.details})` : (json.error || `Error ${res.status}`));
}
// Show success
document.getElementById('progress-wrapper').style.display = 'none';
document.getElementById('form-nav').style.display = 'none';
document.getElementById('help-text').style.display = 'none';
document.querySelectorAll('.step-panel').forEach(p => p.classList.remove('active'));
document.getElementById('success-panel').style.display = 'block';
window.scrollTo({ top: 0, behavior: 'smooth' });
} catch (err) {
errEl.textContent = err instanceof Error ? err.message : 'Sorry, something went wrong. Please try again.';
errEl.classList.add('visible');
btnNext.disabled = false;
btnNext.innerHTML = `Submit reference <svg width="14" height="14" viewBox="0 0 12 12" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L4.5 8.5 2 6"/></svg>`;
}
}
// ── Navigation buttons ──
document.getElementById('btn-next').addEventListener('click', () => {
if (currentStep === STEPS.length - 1) {
if (!validateStep(currentStep)) return;
submitForm();
return;
}
if (!validateStep(currentStep)) return;
completed.add(currentStep);
goTo(currentStep + 1);
});
document.getElementById('btn-back').addEventListener('click', () => {
if (currentStep > 0) goTo(currentStep - 1);
});
// ── Scroll-aware nav ──
const nav = document.getElementById('site-nav');
if (nav) {
window.addEventListener('scroll', () => {
nav.classList.toggle('scrolled', window.scrollY > 40);
}, { passive: true });
}
// ── Init ──
renderPills();
showStep(0);
})();
</script>
</BaseLayout>