PageSourceSearch

https://www.phillyperforms.com/components/build/Registration.js

js phillyperforms.com collected 2026-10-09 23:49:12 UTC 29,056 bytes, 471 lines download raw bytes

1// GENERATED FILE -- DO NOT EDIT.
2// Source: components/Registration.jsx. Rebuild with: npm run build:jsx
3const SIG_LIB_URL = "https://cdn.jsdelivr.net/npm/signature_pad@4/dist/signature_pad.umd.min.js";
4function loadSigPad() {
5  if (window.SignaturePad) return Promise.resolve(window.SignaturePad);
6  return new Promise((resolve, reject) => {
7    const s = document.createElement("script");
8    s.src = SIG_LIB_URL;
9    s.async = true;
10    s.onload = () => resolve(window.SignaturePad);
11    s.onerror = () => reject(new Error("signature_pad_failed_to_load"));
12    document.head.appendChild(s);
13  });
14}
15const FACILITIES = [
16  "",
17  "Athletic Recreation Center",
18  "Hawthorne Cultural Center",
19  "Holmesburg Recreation Center",
20  "Kingsessing Recreation Center",
21  "Kendrick Recreation Center",
22  "Venice Island Performing Arts Center",
23  "Other"
24];
25const PROGRAMS = ["", "Dance@Athletic", "Other"];
26const PROGRAM_LINKS = { "dance-athletic": "Dance@Athletic" };
27const GRADES = ["", "K", "1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "11th", "12th"];
28const SHIRT_OPTS = [
29  ["", "\u2014"],
30  ["cs", "Child's small"],
31  ["cm", "Child's medium"],
32  ["cl", "Child's large"],
33  ["as", "Adult small"],
34  ["am", "Adult medium"],
35  ["al", "Adult large"],
36  ["axl", "Adult XL"],
37  ["other", "Other (specify)"]
38];
39const RACES = [
40  ["ai_an", "American Indian or Alaska Native"],
41  ["asian", "Asian"],
42  ["black", "Black or African American"],
43  ["hispanic", "Hispanic or Latino"],
44  ["mena", "Middle Eastern or North African"],
45  ["nh_pi", "Native Hawaiian or Pacific Islander"],
46  ["white", "White"]
47];
48const WAIVERS = [
49  { key: "dismissal", title: "Dismissal", required: true, body: "By signing below, I acknowledge that it is my and my child's responsibility to arrange transport home." },
50  { key: "release", title: "Release & Assumption of Risk", required: true, body: "I grant permission for my child/ward to attend the program and participate in any and all of its activities, including fieldtrips. I release the City of Philadelphia from all liabilities in case of an injury or illness suffered by the above child resulting from participating in the Program. I voluntarily assume all risks of loss, damage and all injuries that may be sustained by myself and/or my minor child/ward." },
51  { key: "emergency", title: "Emergency Clause", required: false, body: "In the event that my minor child suffers an injury or illness that requires immediate medical attention, I/my minor child will be taken to the nearest hospital. No employee, official, agent or representative of the City will be held responsible for injuries or damages arising from such emergency medical treatment." },
52  { key: "media", title: "Media Release", required: false, body: "I authorize, without compensation, the use by the City of my or my minor child/ward's image and/or voice recordings during the Program. This includes permission to reproduce, publicize, broadcast or display visual images or voice recordings, with or without my name, throughout the world, in perpetuity." }
53];
54function linkedProgram() {
55  const q = (window.location.hash || "").split("?")[1] || "";
56  return PROGRAM_LINKS[new URLSearchParams(q).get("program")] || "";
57}
58function linkedCamp() {
59  const q = (window.location.hash || "").split("?")[1] || "";
60  return new URLSearchParams(q).get("camp") || "";
61}
62const CAMP_ERRORS = {
63  registration_not_open: "Camp registration isn't open right now.",
64  registration_closed: "That site is full. Pick another site.",
65  site_required: "Please choose a camp site.",
66  unavailable: "We couldn't reach the registration system. Try again in a minute."
67};
68function cmsGet(key, fallback) {
69  return (window.CMS || { get: (_k, fb) => fb }).get(key, fallback);
70}
71function ParticipantRegistrationPage() {
72  const [query, setQuery] = React.useState(() => (window.location.hash || "").split("?")[1] || "");
73  React.useEffect(() => {
74    const on = () => setQuery((window.location.hash || "").split("?")[1] || "");
75    window.addEventListener("hashchange", on);
76    return () => window.removeEventListener("hashchange", on);
77  }, []);
78  return /* @__PURE__ */ React.createElement(ParticipantRegistrationForm, { key: query });
79}
80function ParticipantRegistrationForm() {
81  const campSlug = linkedCamp();
82  const campMode = !!campSlug;
83  const linked = campMode ? "" : linkedProgram();
84  const blank = {
85    facility: linked === "Dance@Athletic" ? "Athletic Recreation Center" : "",
86    facility_other: "",
87    program_choice: linked,
88    program: "",
89    season_year: "Fall 2026",
90    participant_name: "",
91    date_of_birth: "",
92    age: "",
93    address: "",
94    city: "Philadelphia",
95    state: "PA",
96    zip: "",
97    primary_language: "English",
98    gender: "",
99    race: {},
100    home_phone: "",
101    cell_phone: "",
102    email: "",
103    school: "",
104    grade: "",
105    shirt_size: "",
106    shirt_size_other: "",
107    contacts: [{ name: "", relationship: "", email: "", phone: "", caregiver: true, emergency_contact: false, authorized_pickup: false }],
108    staff_alerts: "",
109    site_id: campSlug,
110    returning: false
111  };
112  const [data, setData] = React.useState(blank);
113  const [, bumpCms] = React.useState(0);
114  React.useEffect(() => {
115    if (!campMode || window.CMS && window.CMS._ready) return;
116    const on = () => bumpCms((n) => n + 1);
117    window.addEventListener("cms:ready", on);
118    return () => window.removeEventListener("cms:ready", on);
119  }, [campMode]);
120  const [campSites, setCampSites] = React.useState(() => window.CAMP_SITES || []);
121  React.useEffect(() => {
122    if (!campMode) return;
123    let cancelled = false;
124    fetch("/api/camps").then((r) => r.ok ? r.json() : null).then((j) => {
125      if (cancelled || !j || !j.ok || !Array.isArray(j.rows) || !j.rows.length) return;
126      setCampSites(j.rows.map((r) => ({
127        id: r.slug,
128        name: r.name,
129        type: r.type,
130        ageRange: r.age_range,
131        registrationClosed: !!r.registration_closed
132      })));
133    }).catch(() => {
134    });
135    return () => {
136      cancelled = true;
137    };
138  }, [campMode]);
139  const campOpenRaw = cmsGet("camps.registration_open", false);
140  const campOpen = campOpenRaw === true || campOpenRaw === "true";
141  const campYear = String(cmsGet("camps.season_year", "2027"));
142  const feeRaw = String(cmsGet("camps.registration_fee_dollars", "")).trim();
143  const campFee = /^\d+(\.\d{1,2})?$/.test(feeRaw) && Number(feeRaw) > 0 ? `$${feeRaw}` : "";
144  const [submitted, setSubmitted] = React.useState(null);
145  const [submitting, setSubmitting] = React.useState(false);
146  const [error, setError] = React.useState("");
147  const canvasRefs = React.useRef({});
148  const padsRef = React.useRef({});
149  const [sigMeta, setSigMeta] = React.useState(
150    () => Object.fromEntries(WAIVERS.map((w) => [w.key, { relationship: "", date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) }]))
151  );
152  React.useEffect(() => {
153    let cancelled = false;
154    loadSigPad().then((SignaturePad) => {
155      if (cancelled) return;
156      WAIVERS.forEach((w) => {
157        const canvas = canvasRefs.current[w.key];
158        if (!canvas || padsRef.current[w.key]) return;
159        const ratio = Math.max(window.devicePixelRatio || 1, 1);
160        canvas.width = canvas.offsetWidth * ratio;
161        canvas.height = canvas.offsetHeight * ratio;
162        canvas.getContext("2d").scale(ratio, ratio);
163        padsRef.current[w.key] = new SignaturePad(canvas, {
164          backgroundColor: "rgba(255,255,255,0)",
165          penColor: "#2A1820"
166        });
167      });
168    }).catch(() => setError("Couldn't load signature pad \u2014 please refresh and try again."));
169    return () => {
170      cancelled = true;
171    };
172  }, []);
173  const update = (k, v) => setData((d) => ({ ...d, [k]: v }));
174  const pickProgram = (v) => setData((d) => ({
175    ...d,
176    program_choice: v,
177    facility: v === "Dance@Athletic" && !d.facility ? "Athletic Recreation Center" : d.facility
178  }));
179  const updateRace = (k, v) => setData((d) => ({ ...d, race: { ...d.race, [k]: v } }));
180  const updateContact = (i, k, v) => setData((d) => {
181    const next = d.contacts.slice();
182    next[i] = { ...next[i], [k]: v };
183    return { ...d, contacts: next };
184  });
185  const addContact = () => setData((d) => ({
186    ...d,
187    contacts: d.contacts.length >= 4 ? d.contacts : [...d.contacts, {
188      name: "",
189      relationship: "",
190      email: "",
191      phone: "",
192      caregiver: false,
193      emergency_contact: false,
194      authorized_pickup: false
195    }]
196  }));
197  const clearSig = (key) => padsRef.current[key]?.clear();
198  const onSubmit = async (e) => {
199    e.preventDefault();
200    setError("");
201    const nameTokens = String(data.participant_name || "").trim().split(/\s+/).filter(Boolean);
202    if (nameTokens.length < 2) {
203      setError("Please enter both a first and last name for the participant.");
204      return;
205    }
206    let age = data.age;
207    if (!age && data.date_of_birth) {
208      const dob = new Date(data.date_of_birth);
209      if (!isNaN(dob)) {
210        const today = /* @__PURE__ */ new Date();
211        let a = today.getFullYear() - dob.getFullYear();
212        if (today.getMonth() < dob.getMonth() || today.getMonth() === dob.getMonth() && today.getDate() < dob.getDate()) a -= 1;
213        if (a >= 0) age = a;
214      }
215    }
216    const signatures = {};
217    for (const w of WAIVERS) {
218      const pad = padsRef.current[w.key];
219      const empty = !pad || pad.isEmpty();
220      if (empty && w.required) {
221        setError(`Please sign the ${w.title} waiver before submitting.`);
222        return;
223      }
224      signatures[w.key] = empty ? "" : pad.toDataURL("image/png");
225    }
226    if (campMode && !data.site_id) {
227      setError("Please choose a camp site.");
228      return;
229    }
230    if (!campMode && !data.program_choice) {
231      setError("Please choose a program.");
232      return;
233    }
234    const program = data.program_choice === "Other" ? data.program : data.program_choice;
235    setSubmitting(true);
236    try {
237      const res = await fetch(campMode ? "/api/register-camp" : "/api/registration", {
238        method: "POST",
239        headers: { "Content-Type": "application/json" },
240        body: JSON.stringify({ ...data, program, age, signatures, signature_meta: sigMeta })
241      });
242      const json = await res.json().catch(() => ({}));
243      if (!res.ok || !json.ok) {
244        const known = campMode && CAMP_ERRORS[json.error];
245        const e2 = new Error(known || json.details && json.details.join(", ") || json.error || `error_${res.status}`);
246        e2.known = !!known;
247        throw e2;
248      }
249      setSubmitted({ ref: json.ref, emailStatus: json.email_status, pdfBase64: json.pdf_base64, pdfFilename: json.pdf_filename });
250    } catch (err) {
251      setError(err.known ? err.message : `Couldn't submit: ${err.message}. Try again, or email [email protected].`);
252    } finally {
253      setSubmitting(false);
254    }
255  };
256  if (submitted) {
257    const downloadPdf = () => {
258      if (!submitted.pdfBase64) return;
259      try {
260        const bytes = Uint8Array.from(atob(submitted.pdfBase64), (c) =>
260 c.charCodeAt(0));
261        const blob = new Blob([bytes], { type: "application/pdf" });
262        const url = URL.createObjectURL(blob);
263        const a = document.createElement("a");
264        a.href = url;
265        a.download = submitted.pdfFilename || `participant-registration-${submitted.ref}.pdf`;
266        document.body.appendChild(a);
267        a.click();
268        a.remove();
269        setTimeout(() => URL.revokeObjectURL(url), 1e4);
270      } catch (_) {
271      }
272    };
273    if (campMode) {
274      return /* @__PURE__ */ React.createElement(
275        ConfirmationView,
276        {
277          title: "Registration received.",
278          body: `Reference: ${submitted.ref}. Your registration is pending until we receive the ${campFee ? `${campFee} ` : ""}registration fee. The spot is not held until it is paid. We've emailed you a copy of the signed form and how to reach the office to pay.`,
279          ctaLabel: "Register another camper",
280          onCta: () => {
281            setSubmitted(null);
282            setData(blank);
283            padsRef.current = {};
284          },
285          secondary: submitted.pdfBase64 ? { label: "Download for your records", onClick: downloadPdf } : null
286        }
287      );
288    }
289    return /* @__PURE__ */ React.createElement(
290      ConfirmationView,
291      {
292        title: "Registration submitted.",
293        body: `Reference: ${submitted.ref}. A signed copy of the form has been emailed to the program coordinator. Save this reference for your records.`,
294        ctaLabel: "Submit another registration",
295        onCta: () => {
296          setSubmitted(null);
297          setData(blank);
298          padsRef.current = {};
299        },
300        secondary: submitted.pdfBase64 ? { label: "Download for your records \u2193", onClick: downloadPdf } : null
301      }
302    );
303  }
304  const todayIso = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
305  const dobMin = (() => {
306    const d = /* @__PURE__ */ new Date();
307    d.setFullYear(d.getFullYear() - 100);
308    return d.toISOString().slice(0, 10);
309  })();
310  const dobMax = (() => {
311    const d = /* @__PURE__ */ new Date();
312    d.setFullYear(d.getFullYear() - 1);
313    return d.toISOString().slice(0, 10);
314  })();
315  if (campMode && !campOpen && window.CMS && window.CMS._ready) {
316    return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
317      FormHero,
318      {
319        eyebrow: `Summer Camp ${campYear}`,
320        title: /* @__PURE__ */ React.createElement(React.Fragment, null, "Registration", /* @__PURE__ */ React.createElement("br", null), "opens soon."),
321        body: `Camp registration for ${campYear} isn't open yet. The camp page has the sites, dates and a sign-up to hear when it opens.`,
322        bg: "var(--c-cream-deep)"
323      }
324    ), /* @__PURE__ */ React.createElement("section", { className: "section" }, /* @__PURE__ */ React.createElement("div", { className: "container", style: { maxWidth: 920 } }, /* @__PURE__ */ React.createElement("a", { href: "#camps", className: "btn" }, "Back to summer camp"))));
325  }
326  const siteOptions = campSites.filter((c) => !c.registrationClosed || c.id === data.site_id);
327  return /* @__PURE__ */ React.createElement("div", null, campMode ? /* @__PURE__ */ React.createElement(
328    FormHero,
329    {
330      eyebrow: `Summer Camp ${campYear}`,
331      title: /* @__PURE__ */ React.createElement(React.Fragment, null, "Register for", /* @__PURE__ */ React.createElement("br", null), "summer camp."),
332      body: `Fill out and sign the City of Philadelphia Parks & Recreation registration form below. Your registration stays pending until the ${campFee ? `${campFee} ` : ""}registration fee is paid to the Performing Arts office.`,
333      bg: "var(--c-cream-deep)"
334    }
335  ) : /* @__PURE__ */ React.createElement(
336    FormHero,
337    {
338      eyebrow: "Participant Registration",
339      title: /* @__PURE__ */ React.createElement(React.Fragment, null, "Register for", /* @__PURE__ */ React.createElement("br", null), "a program."),
340      body: "Fill out and sign the City of Philadelphia Parks & Recreation participant registration form below. A signed copy is emailed to the program coordinator on submission.",
341      bg: "var(--c-cream-deep)"
342    }
343  ), /* @__PURE__ */ React.createElement("section", { className: "section" }, /* @__PURE__ */ React.createElement("div", { className: "container", style: { maxWidth: 920 } }, /* @__PURE__ */ React.createElement("form", { onSubmit }, campMode ? /* @__PURE__ */ React.createElement(FormSection, { number: "01", title: "Camp" }, /* @__PURE__ */ React.createElement(
344    Field,
345    {
346      label: "Camp site",
347      type: "select",
348      options: ["", ...siteOptions.map((c) => c.id)],
349      optionLabels: ["Choose a site", ...siteOptions.map((c) => `${c.name} (${c.type === "YPTC" ? "Young Performers Theatre Camp" : "Intro to Performing Arts"}${c.ageRange ? `, ${c.ageRange}` : ""})`)],
350      value: data.site_id,
351      onChange: (v) => update("site_id", v),
352      required: true
353    }
354  ), /* @__PURE__ */ React.createElement(Checkbox, { label: "Returning camper (attended a PPR camp before)", checked: data.returning, onChange: (v) => update("returning", v) })) : /* @__PURE__ */ React.createElement(FormSection, { number: "01", title: "Program & enrollment" }, data.facility === "Other" ? (
355    /* CR #225: when Other is picked, the "Please specify"
356       box should appear ABOVE the Program name box. Break
357       the row apart so the order reads facility ->
358       please-specify -> program. */
359    /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Field, { label: "Facility", type: "select", options: FACILITIES, value: data.facility, onChange: (v) => update("facility", v), required: true }), /* @__PURE__ */ React.createElement(
360      Field,
361      {
362        label: "Please specify the recreation center or playground",
363        value: data.facility_other,
364        onChange: (v) => update("facility_other", v),
365        required: true
366      }
367    ), /* @__PURE__ */ React.createElement(Field, { label: "Program", type: "select", options: PROGRAMS, value: data.program_choice, onChange: pickProgram, required: true }))
368  ) : /* @__PURE__ */ React.createElement(Row, null, /* @__PURE__ */ React.createElement(Field, { label: "Facility", type: "select", options: FACILITIES, value: data.facility, onChange: (v) => update("facility", v), required: true }), /* @__PURE__ */ React.createElement(Field, { label: "Program", type: "select", options: PROGRAMS, value: data.program_choice, onChange: pickProgram, required: true })), data.program_choice === "Other" && /* @__PURE__ */ React.createElement(Field, { label: "Program name", value: data.program, onChange: (v) => update("program", v), required: true }), /* @__PURE__ */ React.createElement(Field, { label: "Season / Year", value: data.season_year, onChange: (v) => update("season_year", v) })), /* @__PURE__ */ React.createElement(FormSection, { number: "02", title: "Participant" }, /* @__PURE__ */ React.createElement(Row, null, /* @__PURE__ */ React.createElement(
369    Field,
370    {
371      label: "First and last name",
372      value: data.participant_name,
373      onChange: (v) => update("participant_name", v),
374      required: true,
375      pattern: "^\\s*\\S+\\s+\\S.*$",
376      title: "Please enter both a first and last name."
377    }
378  ), /* @__PURE__ */ React.createElement(Field, { label: "Date of birth", type: "date", value: data.date_of_birth, onChange: (v) => update("date_of_birth", v), required: true, min: dobMin, max: dobMax, title: `Must be between ${dobMin} and ${dobMax}` })), /* @__PURE__ */ React.createElement(Row, null, /* @__PURE__ */ React.createElement(Field, { label: "Residential address", value: data.address, onChange: (v) => update("address", v), required: true }), /* @__PURE__ */ React.createElement(Field, { label: "City", value: data.city, onChange: (v) => update("city", v) })), /* @__PURE__ */ React.createElement(Row, null, /* @__PURE__ */ React.createElement(Field, { label: "State", value: data.state, onChange: (v) => update("state", v) }
378), /* @__PURE__ */ React.createElement(Field, { label: "ZIP code", value: data.zip, onChange: (v) => update("zip", v), required: true, inputMode: "numeric", maxLength: 5, pattern: "\\d{5}", title: "5-digit ZIP code", placeholder: "19102" })), /* @__PURE__ */ React.createElement(Row, null, /* @__PURE__ */ React.createElement(Field, { label: "Primary language at home", value: data.primary_language, onChange: (v) => update("primary_language", v) }), /* @__PURE__ */ React.createElement(Field, { label: "Gender", value: data.gender, onChange: (v) => update("gender", v) })), /* @__PURE__ */ React.createElement(CheckboxGrid, { label: "Race (check all that apply)", items: RACES, state: data.race, onToggle: (k, v) => updateRace(k, v) }), /* @__PURE__ */ React.createElement(Row, null, /* @__PURE__ */ React.createElement(
379    Field,
380    {
381      label: "Home phone",
382      type: "tel",
383      value: data.home_phone,
384      onChange: (v) => update("home_phone", v),
385      placeholder: "215-555-0100",
386      inputMode: "tel",
387      maxLength: 12,
388      pattern: "\\d{3}-\\d{3}-\\d{4}",
389      title: "Use XXX-XXX-XXXX format",
390      formatter: window.formatPhone
391    }
392  ), /* @__PURE__ */ React.createElement(
393    Field,
394    {
395      label: "Cell phone",
396      type: "tel",
397      value: data.cell_phone,
398      onChange: (v) => update("cell_phone", v),
399      placeholder: "215-555-0100",
400      inputMode: "tel",
401      maxLength: 12,
402      pattern: "\\d{3}-\\d{3}-\\d{4}",
403      title: "Use XXX-XXX-XXXX format",
404      formatter: window.formatPhone
405    }
406  )), /* @__PURE__ */ React.createElement(Field, { label: "Email", type: "email", value: data.email, onChange: (v) => update("email", v) }), /* @__PURE__ */ React.createElement(Row, null, /* @__PURE__ */ React.createElement(Field, { label: "School name", value: data.school, onChange: (v) => update("school", v) }), /* @__PURE__ */ React.createElement(Field, { label: "Grade", type: "select", options: GRADES, value: data.grade, onChange: (v) => update("grade", v) })), /* @__PURE__ */ React.createElement(Row, null, /* @__PURE__ */ React.createElement(
407    Field,
408    {
409      label: "Shirt size",
410      type: "select",
411      options: SHIRT_OPTS.map((o) => o[0]),
412      optionLabels: SHIRT_OPTS.map((o) => o[1]),
413      value: data.shirt_size,
414      onChange: (v) => update("shirt_size", v)
415    }
416  ), data.shirt_size === "other" && /* @__PURE__ */ React.createElement(Field, { label: "Other shirt size", value: data.shirt_size_other, onChange: (v) => update("shirt_size_other", v) }))), /* @__PURE__ */ React.createElement(FormSection, { number: "03", title: "Parent / guardian / emergency contacts" }, data.contacts.map((c, i) => /* @__PURE__ */ React.createElement("div", { key: i, style: { border: "2px solid var(--c-ink-soft)", borderRadius: 12, padding: 18, background: "var(--c-paper)" } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--f-mono)", fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--c-ink-soft)", marginBottom: 10 } }, "Contact ", i + 1, i === 0 ? " \xB7 primary" : ""), /* @__PURE__ */ React.createElement(Row, null, /* @__PURE__ */ React.createElement(Field, { label: "First and last name", value: c.name, onChange: (v) => updateContact(i, "name", v), required: i === 0 }
416), /* @__PURE__ */ React.createElement(Field, { label: "Relationship", value: c.relationship, onChange: (v) => updateContact(i, "relationship", v), required: i === 0 })), /* @__PURE__ */ React.createElement(Row, null, /* @__PURE__ */ React.createElement(Field, { label: "Email", type: "email", value: c.email, onChange: (v) => updateContact(i, "email", v), required: i === 0 }), /* @__PURE__ */ React.createElement(
417    Field,
418    {
419      label: "Phone",
420      type: "tel",
421      value: c.phone,
422      onChange: (v) => updateContact(i, "phone", v),
423      required: i === 0,
424      placeholder: "215-555-0100",
425      inputMode: "tel",
426      maxLength: 12,
427      pattern: "\\d{3}-\\d{3}-\\d{4}",
428      title: "Use XXX-XXX-XXXX format",
429      formatter: window.formatPhone
430    }
431  )), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 18, flexWrap: "wrap", marginTop: 8 } }, /* @__PURE__ */ React.createElement(Checkbox, { label: "Caregiver", checked: c.caregiver, onChange: (v) => updateContact(i, "caregiver", v) }
431), /* @__PURE__ */ React.createElement(Checkbox, { label: "Emergency contact", checked: c.emergency_contact, onChange: (v) => updateContact(i, "emergency_contact", v) }), /* @__PURE__ */ React.createElement(Checkbox, { label: "Authorized for pick up", checked: c.authorized_pickup, onChange: (v) => updateContact(i, "authorized_pickup", v) })))), data.contacts.length < 4 && /* @__PURE__ */ React.createElement("button", { type: "button", onClick: addContact, className: "btn small ghost", style: { alignSelf: "flex-start" } }, "+ Add another contact")), /* @__PURE__ */ React.createElement(FormSection, { number: "04", title: "Waivers & signatures" }, /* @__PURE__ */ React.createElement("p", { style: { fontSize: 14, color: "var(--c-ink-soft)", marginTop: -4, lineHeight: 1.55 } }, "A parent or legal guardian signs the Dismissal and Release waivers below. Emergency Clause and Media Release are optional \u2014 sign them only if you agree. Use a finger or stylus on touch devices, or click and drag with a mouse."), WAIVERS.map((w) => /* @__PURE__ */ React.createElement("div", { key: w.key, style: { border: "2px solid var(--c-ink)", borderRadius: 14, padding: 20, background: "var(--c-cream-deep)" } }, /* @__PURE__ */ React.createElement("div", { className: "display", style: { fontSize: 22, marginBottom: 8 } }
431, w.title, !w.required && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--f-mono)", fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--c-ink-soft)", marginLeft: 12, fontWeight: 400 } }, "Optional")), /* @__PURE__ */ React.createElement("p", { style: { fontSize: 13, color: "var(--c-ink)", lineHeight: 1.55, marginBottom: 14 } }, w.body), /* @__PURE__ */ React.createElement(Row, null, /* @__PURE__ */ React.createElement(
432    Field,
433    {
434      label: "Relationship to participant",
435      value: sigMeta[w.key].relationship,
436      onChange: (v) => setSigMeta((s) => ({ ...s, [w.key]: { ...s[w.key], relationship: v } }))
437    }
438  ), /* @__PURE__ */ React.createElement(
439    Field,
440    {
441      label: "Date",
442      type: "date",
443      value: sigMeta[w.key].date,
444      onChange: (v) => setSigMeta((s) => ({ ...s, [w.key]: { ...s[w.key], date: v } }))
445    }
446  )), /* @__PURE__ */ React.createElement("div", { style: { marginTop: 12 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--f-mono)", fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--c-ink-soft)", marginBottom: 6 } }, "Signature of parent or legal guardian"), /* @__PURE__ */ React.createElement(
447    "canvas",
448    {
449      ref: (el) => {
450        canvasRefs.current[w.key] = el;
451      },
452      style: { width: "100%", height: 140, display: "block", border: "2px solid var(--c-ink)", borderRadius: 10, background: "var(--c-paper)", touchAction: "none" }
453    }
454  ), /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => clearSig(w.key), className: "btn small ghost", style: { marginTop: 8 } }, "Clear signature"))))), /* @__PURE__ */ React.createElement(FormSection, { number: "05", title: "Anything staff should know?" }, /* @__PURE__ */ React.createElement(
455    Field,
456    {
457      label: "Behavioral, dietary, medical, or transit-assistance notes (optional)",
458      type: "textarea",
459      value: data.staff_alerts,
460      onChange: (v) => update("staff_alerts", v)
461    }
462  )), error && /* @__PURE__ */ React.createElement("div", { style: { marginTop: 24, padding: 14, background: "#FBE4DC", border: "2px solid var(--c-primary)", borderRadius: 12, fontSize: 14, color: "var(--c-ink)" } }, error), /* @__PURE__ */ React.createElement("div", { className: "card", style: { background: "var(--c-cream-deep)", marginTop: 32, display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 14 } }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "display", style: { fontSize: 20 } }, "Submit registration"), /* @__PURE__ */ React.createElement("div", { style: { fontSize: 13, color: "var(--c-ink-soft)" } }, campMode ? "A signed copy is emailed to you and the office. Pending until the registration fee is paid." : "A signed copy is emailed to the program coordinator on submit.")), /* @__PURE__ */ React.createElement("button", { type: "submit", className: "btn", disabled: submitting }, submitting ? "Submitting\u2026" : "Submit registration"))))));
463}
464function CheckboxGrid({ label, items, state, onToggle }) {
465  return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--f-mono)", fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--c-ink-soft)", marginBottom: 8 } }, label), /* @__PURE__ */ React.createElement("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))", gap: 8 } }, items.map(([k, lbl]) => /* @__PURE__ */ React.createElement(Checkbox, { key: k, label: lbl, checked: !!state[k], onChange: (v) => onToggle(k, v) }))));
466}
467function Checkbox({ label, checked, onChange }) {
468  return /* @__PURE__ */ React.createElement("label", { style: { display: "inline-flex", alignItems: "center", gap: 8, fontSize: 14, cursor: "pointer", userSelect: "none" } }, /* @__PURE__ */ React.createElement("input", { type: "checkbox", checked: !!checked, onChange: (e) => onChange(e.target.checked), style: { width: 18, height: 18, accentColor: "var(--c-primary)" } }), label);
469}
470Object.assign(window, { ParticipantRegistrationPage });
471//# sourceMappingURL=Registration.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.