keystone-design-bootstrap 1.0.55 → 1.0.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/design_system/elements/index.js +8 -3
  2. package/dist/design_system/elements/index.js.map +1 -1
  3. package/dist/design_system/sections/index.js +203 -106
  4. package/dist/design_system/sections/index.js.map +1 -1
  5. package/dist/index.js +303 -247
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/hooks/index.js +72 -0
  8. package/dist/lib/hooks/index.js.map +1 -1
  9. package/dist/lib/server-api.js.map +1 -1
  10. package/dist/utils/phone-helpers.js +26 -0
  11. package/dist/utils/phone-helpers.js.map +1 -0
  12. package/package.json +5 -2
  13. package/src/design_system/components/ChatWidget.tsx +51 -34
  14. package/src/design_system/components/DynamicFormFields.tsx +1 -24
  15. package/src/design_system/elements/modal/modal.tsx +54 -35
  16. package/src/design_system/portal/LoginForm.tsx +339 -0
  17. package/src/design_system/portal/LoginModalController.tsx +63 -0
  18. package/src/design_system/portal/LogoutButton.tsx +23 -0
  19. package/src/design_system/portal/MessageComposer.tsx +84 -0
  20. package/src/design_system/portal/PortalPage.tsx +754 -0
  21. package/src/design_system/portal/RowThumbnail.tsx +76 -0
  22. package/src/design_system/portal/actions.ts +160 -0
  23. package/src/design_system/portal/index.ts +5 -0
  24. package/src/design_system/sections/index.tsx +1 -1
  25. package/src/design_system/sections/service-menu-section.tsx +7 -108
  26. package/src/lib/actions.ts +51 -115
  27. package/src/lib/consumer-session.ts +74 -0
  28. package/src/lib/hooks/index.ts +2 -0
  29. package/src/lib/hooks/use-image-cycle.ts +105 -0
  30. package/src/lib/server-api.ts +7 -6
  31. package/src/next/routes/chat.ts +30 -58
  32. package/src/next/routes/consumer-auth.ts +113 -0
  33. package/src/types/api/consumer.ts +39 -0
  34. package/src/types/api/offer.ts +1 -1
  35. package/src/types/api/package.ts +20 -0
  36. package/src/types/api/service.ts +6 -24
  37. package/src/types/index.ts +2 -0
  38. package/src/utils/phone-helpers.ts +27 -0
  39. package/dist/blog-post-DGjaJ3wf.d.ts +0 -50
  40. package/dist/contexts/index.d.ts +0 -13
  41. package/dist/design_system/elements/index.d.ts +0 -372
  42. package/dist/design_system/logo/keystone-logo.d.ts +0 -6
  43. package/dist/design_system/sections/index.d.ts +0 -237
  44. package/dist/form-CpsCONG5.d.ts +0 -151
  45. package/dist/index.d.ts +0 -76
  46. package/dist/lib/component-registry.d.ts +0 -13
  47. package/dist/lib/hooks/index.d.ts +0 -64
  48. package/dist/lib/server-api.d.ts +0 -43
  49. package/dist/themes/index.d.ts +0 -16
  50. package/dist/types/index.d.ts +0 -264
  51. package/dist/utils/cx.d.ts +0 -15
  52. package/dist/utils/gradient-placeholder.d.ts +0 -8
  53. package/dist/utils/is-react-component.d.ts +0 -21
  54. package/dist/utils/markdown-toc.d.ts +0 -14
  55. package/dist/utils/photo-helpers.d.ts +0 -37
  56. package/dist/website-photos-Bm-CBK9g.d.ts +0 -47
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ var __objRest = (source, exclude) => {
37
37
  };
38
38
 
39
39
  // src/design_system/sections/index.tsx
40
- import React61 from "react";
40
+ import React62 from "react";
41
41
 
42
42
  // src/lib/component-registry.ts
43
43
  var registry = /* @__PURE__ */ new Map();
@@ -3740,6 +3740,9 @@ function Modal({
3740
3740
  title,
3741
3741
  titleId = "modal-title",
3742
3742
  children,
3743
+ footer,
3744
+ hideHeader = false,
3745
+ ariaLabel,
3743
3746
  overlayClassName,
3744
3747
  panelClassName,
3745
3748
  maxWidth = "md"
@@ -3771,7 +3774,8 @@ function Modal({
3771
3774
  className: overlayClassName != null ? overlayClassName : "fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/50",
3772
3775
  role: "dialog",
3773
3776
  "aria-modal": "true",
3774
- "aria-labelledby": title ? titleId : void 0,
3777
+ "aria-label": ariaLabel,
3778
+ "aria-labelledby": !ariaLabel && title && !hideHeader ? titleId : void 0,
3775
3779
  onClick: (e) => e.target === overlayRef.current && onClose()
3776
3780
  },
3777
3781
  /* @__PURE__ */ React13.createElement(
@@ -3780,7 +3784,7 @@ function Modal({
3780
3784
  className: panelClassName != null ? panelClassName : `bg-primary border border-secondary rounded-lg shadow-xl w-full overflow-hidden ${maxWidthClass} max-h-[90vh] flex flex-col`,
3781
3785
  onClick: (e) => e.stopPropagation()
3782
3786
  },
3783
- /* @__PURE__ */ React13.createElement("div", { className: "flex items-start justify-between gap-4 p-4 md:p-6 border-b border-secondary flex-shrink-0" }, title ? /* @__PURE__ */ React13.createElement(
3787
+ !hideHeader && /* @__PURE__ */ React13.createElement("div", { className: "flex items-start justify-between gap-4 p-4 md:p-6 border-b border-secondary flex-shrink-0" }, title ? /* @__PURE__ */ React13.createElement(
3784
3788
  "h2",
3785
3789
  {
3786
3790
  id: titleId,
@@ -3816,7 +3820,8 @@ function Modal({
3816
3820
  )
3817
3821
  )
3818
3822
  )),
3819
- /* @__PURE__ */ React13.createElement("div", { className: "overflow-y-auto flex-1 p-4 md:p-6" }, children)
3823
+ /* @__PURE__ */ React13.createElement("div", { className: "overflow-y-auto flex-1 p-4 md:p-6" }, children),
3824
+ footer && /* @__PURE__ */ React13.createElement("div", { className: "flex-shrink-0 border-t border-secondary" }, footer)
3820
3825
  )
3821
3826
  );
3822
3827
  }
@@ -5977,8 +5982,7 @@ var countriesOptions = countries.map((country) => ({
5977
5982
  }));
5978
5983
  var countries_default = countries;
5979
5984
 
5980
- // src/design_system/components/DynamicFormFields.tsx
5981
- var INPUT_TYPES = ["text", "email", "tel"];
5985
+ // src/utils/phone-helpers.ts
5982
5986
  function getNationalMask(country) {
5983
5987
  if (!(country == null ? void 0 : country.phoneMask)) return "";
5984
5988
  const code = country.phoneCode.startsWith("+") ? country.phoneCode : `+${country.phoneCode}`;
@@ -5999,6 +6003,9 @@ function formatDigitsToMask(digits, mask) {
5999
6003
  }
6000
6004
  return out;
6001
6005
  }
6006
+
6007
+ // src/design_system/components/DynamicFormFields.tsx
6008
+ var INPUT_TYPES = ["text", "email", "tel"];
6002
6009
  function allFieldsFlat(fields) {
6003
6010
  const out = [];
6004
6011
  for (const item of fields) {
@@ -6194,14 +6201,20 @@ function DynamicFormFields({ form, jobSlug, privacyPolicyUrl, termsOfServiceUrl
6194
6201
  function trackMetaLead(eventId) {
6195
6202
  if (typeof window === "undefined" || !eventId) return;
6196
6203
  const fbq = window.fbq;
6197
- if (fbq) fbq("track", "Lead", {}, { eventID: eventId });
6204
+ if (!fbq) {
6205
+ console.debug("[MetaPixel] Lead skipped \u2014 fbq not loaded", { eventId });
6206
+ return;
6207
+ }
6208
+ console.debug("[MetaPixel] Lead", { eventId });
6209
+ fbq("track", "Lead", {}, { eventID: eventId });
6198
6210
  }
6199
6211
 
6200
6212
  // src/next/contexts/form-definitions.tsx
6201
6213
  import React19, { createContext as createContext9, useContext as useContext10 } from "react";
6202
6214
  var FormDefinitionsContext = createContext9({
6203
6215
  leadFormDefinition: null,
6204
- jobApplicationFormDefinition: null
6216
+ jobApplicationFormDefinition: null,
6217
+ marketingListSignupFormDefinition: null
6205
6218
  });
6206
6219
  function useFormDefinitions() {
6207
6220
  return useContext10(FormDefinitionsContext);
@@ -8536,8 +8549,9 @@ function HeaderNavigation2({
8536
8549
  React36.useEffect(() => {
8537
8550
  const handleScroll = () => {
8538
8551
  setIsScrolled(window.scrollY > 10);
8552
+ setActiveDropdown(null);
8539
8553
  };
8540
- window.addEventListener("scroll", handleScroll);
8554
+ window.addEventListener("scroll", handleScroll, { passive: true });
8541
8555
  return () => window.removeEventListener("scroll", handleScroll);
8542
8556
  }, []);
8543
8557
  React36.useEffect(() => {
@@ -8569,6 +8583,8 @@ function HeaderNavigation2({
8569
8583
  setDropdownTop(rect.bottom);
8570
8584
  }
8571
8585
  setActiveDropdown(item.label);
8586
+ } else {
8587
+ setActiveDropdown(null);
8572
8588
  }
8573
8589
  }, [cancelCloseTimeout]);
8574
8590
  const handleMouseLeave = useCallback5(() => {
@@ -17646,6 +17662,8 @@ function HeaderNavigation3({
17646
17662
  cancelCloseTimeout();
17647
17663
  if (item.children && item.children.length > 0) {
17648
17664
  setActiveDropdown(item.label);
17665
+ } else {
17666
+ setActiveDropdown(null);
17649
17667
  }
17650
17668
  }, [cancelCloseTimeout]);
17651
17669
  const handleMouseLeave = useCallback8(() => {
@@ -18436,6 +18454,8 @@ function HeaderNavigation4({
18436
18454
  cancelCloseTimeout();
18437
18455
  if (item.children && item.children.length > 0) {
18438
18456
  setActiveDropdown(item.label);
18457
+ } else {
18458
+ setActiveDropdown(null);
18439
18459
  }
18440
18460
  }, [cancelCloseTimeout]);
18441
18461
  const handleMouseLeave = useCallback9(() => {
@@ -19138,12 +19158,90 @@ var FeatureTabHorizontal = ({ title, subtitle, footer, isCurrent }) => /* @__PUR
19138
19158
  footer
19139
19159
  );
19140
19160
 
19161
+ // src/design_system/sections/email-signup-section.tsx
19162
+ import React60, { useRef as useRef18, useState as useState30 } from "react";
19163
+ var EmailSignupSection = ({
19164
+ title = "Stay in the loop",
19165
+ subtitle = "Subscribe to our newsletter for updates, tips, and exclusive offers.",
19166
+ buttonText = "Subscribe",
19167
+ successMessage = "You're subscribed! Thank you.",
19168
+ formDefinition
19169
+ }) => {
19170
+ const { marketingListSignupFormDefinition } = useFormDefinitions();
19171
+ const resolvedFormDefinition = formDefinition != null ? formDefinition : marketingListSignupFormDefinition;
19172
+ const [isSubmitting, setIsSubmitting] = useState30(false);
19173
+ const [submitStatus, setSubmitStatus] = useState30("idle");
19174
+ const [statusMessage, setStatusMessage] = useState30("");
19175
+ const formRef = useRef18(null);
19176
+ const handleSubmit = async (e) => {
19177
+ var _a;
19178
+ e.preventDefault();
19179
+ setIsSubmitting(true);
19180
+ setSubmitStatus("idle");
19181
+ setStatusMessage("");
19182
+ const formData = new FormData(e.currentTarget);
19183
+ const data = { formType: "marketing_list_signup" };
19184
+ formData.forEach((value, key) => {
19185
+ if (key.endsWith("_prefix")) return;
19186
+ if (typeof value === "string") data[key] = value;
19187
+ });
19188
+ try {
19189
+ const response = await fetch("/api/form/", {
19190
+ method: "POST",
19191
+ headers: { "Content-Type": "application/json" },
19192
+ body: JSON.stringify(data)
19193
+ });
19194
+ const result = await response.json();
19195
+ if (result.success) {
19196
+ setSubmitStatus("success");
19197
+ setStatusMessage(result.message || successMessage);
19198
+ (_a = formRef.current) == null ? void 0 : _a.reset();
19199
+ setTimeout(() => setSubmitStatus("idle"), 6e3);
19200
+ } else {
19201
+ setSubmitStatus("error");
19202
+ setStatusMessage(result.error || "Something went wrong. Please try again.");
19203
+ }
19204
+ } catch (e2) {
19205
+ setSubmitStatus("error");
19206
+ setStatusMessage("Network error. Please try again later.");
19207
+ }
19208
+ setIsSubmitting(false);
19209
+ };
19210
+ if (!resolvedFormDefinition) return null;
19211
+ return /* @__PURE__ */ React60.createElement("section", { className: "bg-secondary py-16 md:py-20" }, /* @__PURE__ */ React60.createElement("div", { className: "mx-auto max-w-container px-4 md:px-8" }, /* @__PURE__ */ React60.createElement("div", { className: "mx-auto max-w-xl text-center" }, /* @__PURE__ */ React60.createElement("h2", { className: "font-display text-display-sm font-semibold text-primary md:text-display-md" }, title), subtitle && /* @__PURE__ */ React60.createElement("p", { className: "mt-4 font-body text-lg text-tertiary" }, subtitle), /* @__PURE__ */ React60.createElement(
19212
+ Form2,
19213
+ {
19214
+ ref: formRef,
19215
+ onSubmit: handleSubmit,
19216
+ className: "mt-8 flex flex-col gap-6 text-left"
19217
+ },
19218
+ /* @__PURE__ */ React60.createElement(DynamicFormFields, { form: resolvedFormDefinition }),
19219
+ submitStatus === "success" && /* @__PURE__ */ React60.createElement("div", { className: "rounded-lg bg-success-50 p-4 text-success-700" }, statusMessage || successMessage),
19220
+ submitStatus === "error" && /* @__PURE__ */ React60.createElement("div", { className: "rounded-lg bg-error-50 p-4 text-error-700" }, statusMessage),
19221
+ /* @__PURE__ */ React60.createElement(
19222
+ Button2,
19223
+ {
19224
+ type: "submit",
19225
+ color: "primary",
19226
+ size: "xl",
19227
+ isDisabled: isSubmitting,
19228
+ isLoading: isSubmitting,
19229
+ className: "w-full"
19230
+ },
19231
+ isSubmitting ? "Subscribing..." : buttonText
19232
+ )
19233
+ ))));
19234
+ };
19235
+
19141
19236
  // src/design_system/sections/service-menu-section.tsx
19142
- import React60, { useState as useState30, useEffect as useEffect11, useCallback as useCallback10, useMemo as useMemo8 } from "react";
19237
+ import React61, { useState as useState32, useEffect as useEffect12, useCallback as useCallback10 } from "react";
19143
19238
  import { createPortal } from "react-dom";
19144
- var SERVICE_MENU_MODAL_ROOT_ID = "service-menu-modal-root";
19239
+
19240
+ // src/lib/hooks/use-image-cycle.ts
19241
+ import { useState as useState31, useEffect as useEffect11, useMemo as useMemo8 } from "react";
19145
19242
  var CYCLE_INTERVAL_MIN_MS = 6e3;
19146
19243
  var CYCLE_INTERVAL_MAX_MS = 8e3;
19244
+ var CROSSFADE_DURATION_MS = 600;
19147
19245
  function seedToUnit(seed) {
19148
19246
  let h = 2166136261 >>> 0;
19149
19247
  for (let i = 0; i < seed.length; i++) {
@@ -19152,18 +19250,6 @@ function seedToUnit(seed) {
19152
19250
  }
19153
19251
  return (h >>> 0) / 4294967296;
19154
19252
  }
19155
- function getActivePublicOffers(offers) {
19156
- if (!Array.isArray(offers) || offers.length === 0) return [];
19157
- return offers.filter((o) => o.active !== false && o.expired !== true);
19158
- }
19159
- function photoAttachmentDisplayUrl(pa) {
19160
- var _a, _b;
19161
- return ((_a = pa.photo) == null ? void 0 : _a.large_url) || ((_b = pa.photo) == null ? void 0 : _b.medium_url);
19162
- }
19163
- function photoAttachmentAlt(pa) {
19164
- var _a, _b;
19165
- return ((_a = pa.photo) == null ? void 0 : _a.alt_text) || ((_b = pa.photo) == null ? void 0 : _b.title) || "";
19166
- }
19167
19253
  function shuffleWithSeed(array, seed) {
19168
19254
  if (array.length <= 1) return array;
19169
19255
  const arr = [...array];
@@ -19182,39 +19268,25 @@ function shuffleWithSeed(array, seed) {
19182
19268
  }
19183
19269
  return arr;
19184
19270
  }
19185
- var CROSSFADE_DURATION_MS = 600;
19186
- function useCycledPhotoList(photoAttachments, seed) {
19187
- return useMemo8(
19188
- () => {
19189
- const arr = Array.isArray(photoAttachments) && photoAttachments.length > 0 ? photoAttachments : [];
19190
- if (arr.length === 0) return [];
19191
- const shuffled = shuffleWithSeed(arr, seed);
19192
- return shuffled.map((pa) => {
19193
- var _a;
19194
- return { url: (_a = photoAttachmentDisplayUrl(pa)) != null ? _a : "", alt: photoAttachmentAlt(pa) };
19195
- }).filter((x) => x.url);
19196
- },
19197
- [photoAttachments, seed]
19198
- );
19271
+ function photoUrlFromAttachment(pa) {
19272
+ var _a, _b, _c;
19273
+ return ((_a = pa.photo) == null ? void 0 : _a.large_url) || ((_b = pa.photo) == null ? void 0 : _b.medium_url) || ((_c = pa.photo) == null ? void 0 : _c.thumbnail_url);
19199
19274
  }
19200
- function GridCardWithImage({
19201
- photoAttachments,
19202
- fallbackId,
19203
- fallbackAlt,
19204
- title,
19205
- subtitle,
19206
- children,
19207
- onClick,
19208
- websitePhotos,
19209
- companyInformation,
19210
- cycleSeed,
19211
- hasSpecial
19212
- }) {
19213
- var _a, _b, _c, _d, _e, _f, _g;
19214
- const seed = cycleSeed != null ? cycleSeed : String(fallbackId);
19215
- const list = useCycledPhotoList(photoAttachments, seed);
19216
- const [currentIndex, setCurrentIndex] = useState30(0);
19217
- const [transitioning, setTransitioning] = useState30(false);
19275
+ function photoAltFromAttachment(pa) {
19276
+ var _a, _b;
19277
+ return ((_a = pa.photo) == null ? void 0 : _a.alt_text) || ((_b = pa.photo) == null ? void 0 : _b.title) || "";
19278
+ }
19279
+ function useImageCycle(photoAttachments, seed) {
19280
+ const list = useMemo8(() => {
19281
+ const arr = Array.isArray(photoAttachments) && photoAttachments.length > 0 ? photoAttachments : [];
19282
+ if (arr.length === 0) return [];
19283
+ return shuffleWithSeed(arr, seed).map((pa) => {
19284
+ var _a;
19285
+ return { url: (_a = photoUrlFromAttachment(pa)) != null ? _a : "", alt: photoAltFromAttachment(pa) };
19286
+ }).filter((x) => x.url);
19287
+ }, [photoAttachments, seed]);
19288
+ const [currentIndex, setCurrentIndex] = useState31(0);
19289
+ const [transitioning, setTransitioning] = useState31(false);
19218
19290
  const intervalMs = useMemo8(
19219
19291
  () => CYCLE_INTERVAL_MIN_MS + Math.floor(seedToUnit(seed) * (CYCLE_INTERVAL_MAX_MS - CYCLE_INTERVAL_MIN_MS + 1)),
19220
19292
  [seed]
@@ -19233,24 +19305,48 @@ function GridCardWithImage({
19233
19305
  return () => clearTimeout(t);
19234
19306
  }, [transitioning, list.length]);
19235
19307
  const nextIndex = list.length > 1 ? (currentIndex + 1) % list.length : 0;
19236
- const currentItem = list[currentIndex];
19237
- const displayAlt = (currentItem == null ? void 0 : currentItem.alt) || fallbackAlt;
19238
- const singleUrl = (_a = list[0]) == null ? void 0 : _a.url;
19239
- return /* @__PURE__ */ React60.createElement(
19308
+ return { list, currentIndex, nextIndex, transitioning };
19309
+ }
19310
+
19311
+ // src/design_system/sections/service-menu-section.tsx
19312
+ var SERVICE_MENU_MODAL_ROOT_ID = "service-menu-modal-root";
19313
+ function getActivePublicOffers(offers) {
19314
+ if (!Array.isArray(offers) || offers.length === 0) return [];
19315
+ return offers.filter((o) => o.active !== false && o.expired !== true);
19316
+ }
19317
+ function GridCardWithImage({
19318
+ photoAttachments,
19319
+ fallbackId,
19320
+ fallbackAlt,
19321
+ title,
19322
+ subtitle,
19323
+ children,
19324
+ onClick,
19325
+ websitePhotos,
19326
+ companyInformation,
19327
+ cycleSeed,
19328
+ hasSpecial
19329
+ }) {
19330
+ var _a, _b, _c, _d, _e, _f, _g, _h;
19331
+ const seed = cycleSeed != null ? cycleSeed : String(fallbackId);
19332
+ const { list, currentIndex, nextIndex, transitioning } = useImageCycle(photoAttachments, seed);
19333
+ const displayAlt = ((_a = list[currentIndex]) == null ? void 0 : _a.alt) || fallbackAlt;
19334
+ const singleUrl = (_b = list[0]) == null ? void 0 : _b.url;
19335
+ return /* @__PURE__ */ React61.createElement(
19240
19336
  "button",
19241
19337
  {
19242
19338
  type: "button",
19243
19339
  onClick,
19244
19340
  className: "flex flex-col h-full w-full text-left group block rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-brand-accent focus-visible:ring-offset-2"
19245
19341
  },
19246
- /* @__PURE__ */ React60.createElement("div", { className: "w-full h-36 overflow-hidden rounded-lg mb-3 relative" }, hasSpecial ? /* @__PURE__ */ React60.createElement(
19342
+ /* @__PURE__ */ React61.createElement("div", { className: "w-full h-36 overflow-hidden rounded-lg mb-3 relative" }, hasSpecial ? /* @__PURE__ */ React61.createElement(
19247
19343
  "span",
19248
19344
  {
19249
19345
  className: "absolute top-2 right-2 z-10 rounded-full border border-brand-accent/40 bg-secondary/95 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-brand-accent shadow-sm backdrop-blur-sm",
19250
19346
  "aria-label": "Has special offer"
19251
19347
  },
19252
19348
  "Special"
19253
- ) : null, list.length === 0 ? /* @__PURE__ */ React60.createElement(
19349
+ ) : null, list.length === 0 ? /* @__PURE__ */ React61.createElement(
19254
19350
  PhotoWithFallback2,
19255
19351
  {
19256
19352
  item: void 0,
@@ -19262,7 +19358,7 @@ function GridCardWithImage({
19262
19358
  }
19263
19359
  ) : list.length === 1 && singleUrl ? (
19264
19360
  // eslint-disable-next-line @next/next/no-img-element -- dynamic API URLs; next/image not configured for external host
19265
- /* @__PURE__ */ React60.createElement(
19361
+ /* @__PURE__ */ React61.createElement(
19266
19362
  "img",
19267
19363
  {
19268
19364
  src: singleUrl,
@@ -19270,37 +19366,37 @@ function GridCardWithImage({
19270
19366
  className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
19271
19367
  }
19272
19368
  )
19273
- ) : /* @__PURE__ */ React60.createElement(React60.Fragment, null, /* @__PURE__ */ React60.createElement(
19369
+ ) : /* @__PURE__ */ React61.createElement(React61.Fragment, null, /* @__PURE__ */ React61.createElement(
19274
19370
  "div",
19275
19371
  {
19276
19372
  className: `absolute inset-0 ${transitioning ? "transition-opacity duration-[600ms] ease-in-out" : "transition-none"}`,
19277
19373
  style: { opacity: transitioning ? 0 : 1 }
19278
19374
  },
19279
- /* @__PURE__ */ React60.createElement(
19375
+ /* @__PURE__ */ React61.createElement(
19280
19376
  "img",
19281
19377
  {
19282
- src: (_b = list[currentIndex]) == null ? void 0 : _b.url,
19283
- alt: (_d = (_c = list[currentIndex]) == null ? void 0 : _c.alt) != null ? _d : displayAlt,
19378
+ src: (_c = list[currentIndex]) == null ? void 0 : _c.url,
19379
+ alt: (_e = (_d = list[currentIndex]) == null ? void 0 : _d.alt) != null ? _e : displayAlt,
19284
19380
  className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
19285
19381
  }
19286
19382
  )
19287
- ), /* @__PURE__ */ React60.createElement(
19383
+ ), /* @__PURE__ */ React61.createElement(
19288
19384
  "div",
19289
19385
  {
19290
19386
  className: `absolute inset-0 ${transitioning ? "transition-opacity duration-[600ms] ease-in-out" : "transition-none"}`,
19291
19387
  style: { opacity: transitioning ? 1 : 0 }
19292
19388
  },
19293
- /* @__PURE__ */ React60.createElement(
19389
+ /* @__PURE__ */ React61.createElement(
19294
19390
  "img",
19295
19391
  {
19296
- src: (_e = list[nextIndex]) == null ? void 0 : _e.url,
19297
- alt: (_g = (_f = list[nextIndex]) == null ? void 0 : _f.alt) != null ? _g : displayAlt,
19392
+ src: (_f = list[nextIndex]) == null ? void 0 : _f.url,
19393
+ alt: (_h = (_g = list[nextIndex]) == null ? void 0 : _g.alt) != null ? _h : displayAlt,
19298
19394
  className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
19299
19395
  }
19300
19396
  )
19301
19397
  ))),
19302
- subtitle && /* @__PURE__ */ React60.createElement("p", { className: "text-xs font-medium text-fg-secondary uppercase tracking-wide line-clamp-1" }, subtitle),
19303
- /* @__PURE__ */ React60.createElement("h4", { className: "font-display text-base font-normal text-fg-primary mt-1 group-hover:underline line-clamp-2" }, title),
19398
+ subtitle && /* @__PURE__ */ React61.createElement("p", { className: "text-xs font-medium text-fg-secondary uppercase tracking-wide line-clamp-1" }, subtitle),
19399
+ /* @__PURE__ */ React61.createElement("h4", { className: "font-display text-base font-normal text-fg-primary mt-1 group-hover:underline line-clamp-2" }, title),
19304
19400
  children
19305
19401
  );
19306
19402
  }
@@ -19310,9 +19406,9 @@ function CarouselRow({
19310
19406
  renderItem
19311
19407
  }) {
19312
19408
  if (!(items == null ? void 0 : items.length)) return null;
19313
- return /* @__PURE__ */ React60.createElement("div", { className: "mb-12 last:mb-0" }, /* @__PURE__ */ React60.createElement(Carousel.Root, { opts: { align: "start", loop: true } }, /* @__PURE__ */ React60.createElement("div", { className: "flex items-center justify-between gap-4 mb-2" }, /* @__PURE__ */ React60.createElement("h3", { className: "font-display text-2xl font-normal text-fg-primary md:text-3xl" }, rowTitle), /* @__PURE__ */ React60.createElement("div", { className: "flex gap-2 flex-shrink-0" }, /* @__PURE__ */ React60.createElement(Carousel.PrevTrigger, { className: "rounded-full p-1.5 border border-secondary hover:bg-primary_hover transition-colors disabled:opacity-30 disabled:cursor-not-allowed" }, /* @__PURE__ */ React60.createElement("svg", { className: "w-5 h-5 text-fg-primary", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": true }, /* @__PURE__ */ React60.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }))), /* @__PURE__ */ React60.createElement(Carousel.NextTrigger, { className: "rounded-full p-1.5 border border-secondary hover:bg-primary_hover transition-colors disabled:opacity-30 disabled:cursor-not-allowed" }, /* @__PURE__ */ React60.createElement("svg", { className: "w-5 h-5 text-fg-primary", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": true }, /* @__PURE__ */ React60.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }))))), /* @__PURE__ */ React60.createElement(Carousel.Content, { className: "-ml-3" }, items.map((item, index) => {
19409
+ return /* @__PURE__ */ React61.createElement("div", { className: "mb-12 last:mb-0" }, /* @__PURE__ */ React61.createElement(Carousel.Root, { opts: { align: "start", loop: true } }, /* @__PURE__ */ React61.createElement("div", { className: "flex items-center justify-between gap-4 mb-2" }, /* @__PURE__ */ React61.createElement("h3", { className: "font-display text-2xl font-normal text-fg-primary md:text-3xl" }, rowTitle), /* @__PURE__ */ React61.createElement("div", { className: "flex gap-2 flex-shrink-0" }, /* @__PURE__ */ React61.createElement(Carousel.PrevTrigger, { className: "rounded-full p-1.5 border border-secondary hover:bg-primary_hover transition-colors disabled:opacity-30 disabled:cursor-not-allowed" }, /* @__PURE__ */ React61.createElement("svg", { className: "w-5 h-5 text-fg-primary", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": true }, /* @__PURE__ */ React61.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }))), /* @__PURE__ */ React61.createElement(Carousel.NextTrigger, { className: "rounded-full p-1.5 border border-secondary hover:bg-primary_hover transition-colors disabled:opacity-30 disabled:cursor-not-allowed" }, /* @__PURE__ */ React61.createElement("svg", { className: "w-5 h-5 text-fg-primary", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": true }, /* @__PURE__ */ React61.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }))))), /* @__PURE__ */ React61.createElement(Carousel.Content, { className: "-ml-3" }, items.map((item, index) => {
19314
19410
  var _a;
19315
- return /* @__PURE__ */ React60.createElement(Carousel.Item, { key: (_a = item.id) != null ? _a : index, className: "pl-3 basis-[70%] sm:basis-1/2 md:basis-1/3 lg:basis-1/4" }, renderItem(item, index));
19411
+ return /* @__PURE__ */ React61.createElement(Carousel.Item, { key: (_a = item.id) != null ? _a : index, className: "pl-3 basis-[70%] sm:basis-1/2 md:basis-1/3 lg:basis-1/4" }, renderItem(item, index));
19316
19412
  }))));
19317
19413
  }
19318
19414
  function GridRow({
@@ -19321,9 +19417,9 @@ function GridRow({
19321
19417
  renderItem
19322
19418
  }) {
19323
19419
  if (!(items == null ? void 0 : items.length)) return null;
19324
- return /* @__PURE__ */ React60.createElement("div", { className: "mb-12 last:mb-0" }, /* @__PURE__ */ React60.createElement("h3", { className: "font-display text-2xl font-normal text-fg-primary mb-3 md:text-3xl" }, rowTitle), /* @__PURE__ */ React60.createElement("div", { className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4" }, items.map((item, index) => {
19420
+ return /* @__PURE__ */ React61.createElement("div", { className: "mb-12 last:mb-0" }, /* @__PURE__ */ React61.createElement("h3", { className: "font-display text-2xl font-normal text-fg-primary mb-3 md:text-3xl" }, rowTitle), /* @__PURE__ */ React61.createElement("div", { className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4" }, items.map((item, index) => {
19325
19421
  var _a;
19326
- return /* @__PURE__ */ React60.createElement("div", { key: (_a = item.id) != null ? _a : index, className: "min-w-0 flex" }, renderItem(item, index));
19422
+ return /* @__PURE__ */ React61.createElement("div", { key: (_a = item.id) != null ? _a : index, className: "min-w-0 flex" }, renderItem(item, index));
19327
19423
  })));
19328
19424
  }
19329
19425
  function MenuBlock({
@@ -19333,27 +19429,27 @@ function MenuBlock({
19333
19429
  layout: layout2
19334
19430
  }) {
19335
19431
  if (!(items == null ? void 0 : items.length)) return null;
19336
- return layout2 === "carousel" ? /* @__PURE__ */ React60.createElement(CarouselRow, { rowTitle, items, renderItem }) : /* @__PURE__ */ React60.createElement(GridRow, { rowTitle, items, renderItem });
19432
+ return layout2 === "carousel" ? /* @__PURE__ */ React61.createElement(CarouselRow, { rowTitle, items, renderItem }) : /* @__PURE__ */ React61.createElement(GridRow, { rowTitle, items, renderItem });
19337
19433
  }
19338
19434
  function formatPriceCents(cents) {
19339
19435
  if (cents == null) return null;
19340
19436
  return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(cents / 100);
19341
19437
  }
19342
19438
  function ModalSection({ title, children }) {
19343
- return /* @__PURE__ */ React60.createElement("div", null, /* @__PURE__ */ React60.createElement("h3", { className: "font-display text-sm font-semibold text-fg-primary uppercase tracking-wide mb-2" }, title), children);
19439
+ return /* @__PURE__ */ React61.createElement("div", null, /* @__PURE__ */ React61.createElement("h3", { className: "font-display text-sm font-semibold text-fg-primary uppercase tracking-wide mb-2" }, title), children);
19344
19440
  }
19345
19441
  function ActiveOffersCallout({ offers }) {
19346
19442
  if (!offers.length) return null;
19347
- return /* @__PURE__ */ React60.createElement("div", { className: "rounded-xl border border-secondary bg-secondary/25 p-4 md:p-5 space-y-3 ring-1 ring-brand-accent/25" }, /* @__PURE__ */ React60.createElement("p", { className: "text-xs font-semibold uppercase tracking-wide text-brand-accent" }, "Current specials"), /* @__PURE__ */ React60.createElement("ul", { className: "space-y-3 list-none m-0 p-0" }, offers.map((o) => /* @__PURE__ */ React60.createElement(
19443
+ return /* @__PURE__ */ React61.createElement("div", { className: "rounded-xl border border-secondary bg-secondary/25 p-4 md:p-5 space-y-3 ring-1 ring-brand-accent/25" }, /* @__PURE__ */ React61.createElement("p", { className: "text-xs font-semibold uppercase tracking-wide text-brand-accent" }, "Current specials"), /* @__PURE__ */ React61.createElement("ul", { className: "space-y-3 list-none m-0 p-0" }, offers.map((o) => /* @__PURE__ */ React61.createElement(
19348
19444
  "li",
19349
19445
  {
19350
19446
  key: o.id,
19351
19447
  className: "rounded-lg border border-secondary border-l-[3px] border-l-brand-accent bg-primary_hover/30 p-4"
19352
19448
  },
19353
- /* @__PURE__ */ React60.createElement("p", { className: "font-display text-base font-medium text-fg-primary" }, o.name),
19354
- o.value_terms ? /* @__PURE__ */ React60.createElement("p", { className: "font-body text-sm text-brand-accent mt-1 font-medium" }, o.value_terms) : null,
19355
- o.description ? /* @__PURE__ */ React60.createElement("p", { className: "font-body text-sm text-fg-secondary mt-2 leading-relaxed" }, o.description) : null,
19356
- o.expires_at ? /* @__PURE__ */ React60.createElement("p", { className: "text-xs text-tertiary mt-2" }, "Ends ", new Date(o.expires_at).toLocaleDateString(void 0, { dateStyle: "medium" })) : null
19449
+ /* @__PURE__ */ React61.createElement("p", { className: "font-display text-base font-medium text-fg-primary" }, o.name),
19450
+ o.value_terms ? /* @__PURE__ */ React61.createElement("p", { className: "font-body text-sm text-brand-accent mt-1 font-medium" }, o.value_terms) : null,
19451
+ o.description ? /* @__PURE__ */ React61.createElement("p", { className: "font-body text-sm text-fg-secondary mt-2 leading-relaxed" }, o.description) : null,
19452
+ o.expires_at ? /* @__PURE__ */ React61.createElement("p", { className: "text-xs text-tertiary mt-2" }, "Ends ", new Date(o.expires_at).toLocaleDateString(void 0, { dateStyle: "medium" })) : null
19357
19453
  ))));
19358
19454
  }
19359
19455
  function DetailModalContent({
@@ -19366,20 +19462,20 @@ function DetailModalContent({
19366
19462
  const categoryLine = ((_a = p.category_names) == null ? void 0 : _a.length) ? p.category_names.join(" | ") : null;
19367
19463
  const descriptionMarkdown2 = p.description_markdown || p.summary || p.first_service_description_markdown;
19368
19464
  const packageSpecials = getActivePublicOffers(p.offers);
19369
- return /* @__PURE__ */ React60.createElement("div", { className: "space-y-6" }, categoryLine && /* @__PURE__ */ React60.createElement("p", { className: "text-sm font-medium text-fg-secondary uppercase tracking-wide" }, categoryLine), /* @__PURE__ */ React60.createElement(ActiveOffersCallout, { offers: packageSpecials }), descriptionMarkdown2 && /* @__PURE__ */ React60.createElement(ModalSection, { title: "Package details" }, /* @__PURE__ */ React60.createElement("div", { className: "prose prose-sm font-body text-fg-primary max-w-none" }, /* @__PURE__ */ React60.createElement(MarkdownRenderer2, { content: descriptionMarkdown2 }))), p.package_items && p.package_items.length > 0 && /* @__PURE__ */ React60.createElement(ModalSection, { title: "What's included" }, /* @__PURE__ */ React60.createElement("ul", { className: "space-y-4" }, p.package_items.map((pi, i) => {
19465
+ return /* @__PURE__ */ React61.createElement("div", { className: "space-y-6" }, categoryLine && /* @__PURE__ */ React61.createElement("p", { className: "text-sm font-medium text-fg-secondary uppercase tracking-wide" }, categoryLine), /* @__PURE__ */ React61.createElement(ActiveOffersCallout, { offers: packageSpecials }), descriptionMarkdown2 && /* @__PURE__ */ React61.createElement(ModalSection, { title: "Package details" }, /* @__PURE__ */ React61.createElement("div", { className: "prose prose-sm font-body text-fg-primary max-w-none" }, /* @__PURE__ */ React61.createElement(MarkdownRenderer2, { content: descriptionMarkdown2 }))), p.package_items && p.package_items.length > 0 && /* @__PURE__ */ React61.createElement(ModalSection, { title: "What's included" }, /* @__PURE__ */ React61.createElement("ul", { className: "space-y-4" }, p.package_items.map((pi, i) => {
19370
19466
  var _a2, _b, _c, _d, _e, _f, _g, _h, _i;
19371
19467
  const fullItem = ((_a2 = pi.service_item) == null ? void 0 : _a2.id) != null ? serviceItems.find((si2) => si2.id === pi.service_item.id) : null;
19372
19468
  const name = (_d = (_c = fullItem == null ? void 0 : fullItem.name) != null ? _c : (_b = pi.service_item) == null ? void 0 : _b.name) != null ? _d : "Item";
19373
19469
  const desc = (_g = (_e = fullItem == null ? void 0 : fullItem.summary) != null ? _e : fullItem == null ? void 0 : fullItem.description_markdown) != null ? _g : (_f = pi.service_item) == null ? void 0 : _f.summary;
19374
- return /* @__PURE__ */ React60.createElement("li", { key: (_i = (_h = pi.service_item) == null ? void 0 : _h.id) != null ? _i : i, className: "border border-secondary rounded-lg p-4 bg-secondary/20" }, /* @__PURE__ */ React60.createElement("p", { className: "font-display font-medium text-fg-primary" }, pi.quantity > 1 && `${pi.quantity}\xD7 `, name), desc && /* @__PURE__ */ React60.createElement("div", { className: "mt-2 prose prose-sm font-body text-fg-secondary max-w-none" }, typeof desc === "string" && !desc.includes("\n") && !desc.match(/[#*\[\]]/) ? /* @__PURE__ */ React60.createElement("p", null, desc) : /* @__PURE__ */ React60.createElement(MarkdownRenderer2, { content: desc })));
19375
- }))), p.pricing_info && /* @__PURE__ */ React60.createElement(ModalSection, { title: "Pricing" }, /* @__PURE__ */ React60.createElement("div", { className: "rounded-lg border border-secondary bg-secondary/40 p-4" }, /* @__PURE__ */ React60.createElement("div", { className: "prose prose-sm font-body text-fg-primary max-w-none" }, /* @__PURE__ */ React60.createElement(MarkdownRenderer2, { content: p.pricing_info })))));
19470
+ return /* @__PURE__ */ React61.createElement("li", { key: (_i = (_h = pi.service_item) == null ? void 0 : _h.id) != null ? _i : i, className: "border border-secondary rounded-lg p-4 bg-secondary/20" }, /* @__PURE__ */ React61.createElement("p", { className: "font-display font-medium text-fg-primary" }, pi.quantity > 1 && `${pi.quantity}\xD7 `, name), desc && /* @__PURE__ */ React61.createElement("div", { className: "mt-2 prose prose-sm font-body text-fg-secondary max-w-none" }, typeof desc === "string" && !desc.includes("\n") && !desc.match(/[#*\[\]]/) ? /* @__PURE__ */ React61.createElement("p", null, desc) : /* @__PURE__ */ React61.createElement(MarkdownRenderer2, { content: desc })));
19471
+ }))), p.pricing_info && /* @__PURE__ */ React61.createElement(ModalSection, { title: "Pricing" }, /* @__PURE__ */ React61.createElement("div", { className: "rounded-lg border border-secondary bg-secondary/40 p-4" }, /* @__PURE__ */ React61.createElement("div", { className: "prose prose-sm font-body text-fg-primary max-w-none" }, /* @__PURE__ */ React61.createElement(MarkdownRenderer2, { content: p.pricing_info })))));
19376
19472
  }
19377
19473
  const si = detail.item;
19378
19474
  const priceStr = formatPriceCents(si.price_cents);
19379
19475
  const summary = si.summary || si.service_summary;
19380
19476
  const descriptionMarkdown = si.description_markdown || si.service_description_markdown;
19381
19477
  const itemSpecials = getActivePublicOffers(si.offers);
19382
- return /* @__PURE__ */ React60.createElement("div", { className: "space-y-6" }, si.service_name && /* @__PURE__ */ React60.createElement("p", { className: "text-sm font-medium text-fg-secondary uppercase tracking-wide" }, si.service_name), /* @__PURE__ */ React60.createElement(ActiveOffersCallout, { offers: itemSpecials }), priceStr && /* @__PURE__ */ React60.createElement(ModalSection, { title: "Price" }, /* @__PURE__ */ React60.createElement("p", { className: "font-display text-lg font-normal text-fg-primary" }, priceStr), si.duration_minutes != null && si.duration_minutes > 0 && /* @__PURE__ */ React60.createElement("p", { className: "font-body text-sm text-fg-secondary mt-1" }, "Duration: ", si.duration_minutes, " min")), summary && /* @__PURE__ */ React60.createElement(ModalSection, { title: "Overview" }, /* @__PURE__ */ React60.createElement("p", { className: "font-body text-fg-primary" }, summary)), descriptionMarkdown && /* @__PURE__ */ React60.createElement(ModalSection, { title: "Full description" }, /* @__PURE__ */ React60.createElement("div", { className: "prose prose-sm font-body text-fg-primary max-w-none" }, /* @__PURE__ */ React60.createElement(MarkdownRenderer2, { content: descriptionMarkdown }))), si.pricing_info && /* @__PURE__ */ React60.createElement(ModalSection, { title: "Pricing details" }, /* @__PURE__ */ React60.createElement("div", { className: "rounded-lg border border-secondary bg-secondary/40 p-4" }, /* @__PURE__ */ React60.createElement("div", { className: "prose prose-sm font-body text-fg-primary max-w-none" }, /* @__PURE__ */ React60.createElement(MarkdownRenderer2, { content: si.pricing_info })))));
19478
+ return /* @__PURE__ */ React61.createElement("div", { className: "space-y-6" }, si.service_name && /* @__PURE__ */ React61.createElement("p", { className: "text-sm font-medium text-fg-secondary uppercase tracking-wide" }, si.service_name), /* @__PURE__ */ React61.createElement(ActiveOffersCallout, { offers: itemSpecials }), priceStr && /* @__PURE__ */ React61.createElement(ModalSection, { title: "Price" }, /* @__PURE__ */ React61.createElement("p", { className: "font-display text-lg font-normal text-fg-primary" }, priceStr), si.duration_minutes != null && si.duration_minutes > 0 && /* @__PURE__ */ React61.createElement("p", { className: "font-body text-sm text-fg-secondary mt-1" }, "Duration: ", si.duration_minutes, " min")), summary && /* @__PURE__ */ React61.createElement(ModalSection, { title: "Overview" }, /* @__PURE__ */ React61.createElement("p", { className: "font-body text-fg-primary" }, summary)), descriptionMarkdown && /* @__PURE__ */ React61.createElement(ModalSection, { title: "Full description" }, /* @__PURE__ */ React61.createElement("div", { className: "prose prose-sm font-body text-fg-primary max-w-none" }, /* @__PURE__ */ React61.createElement(MarkdownRenderer2, { content: descriptionMarkdown }))), si.pricing_info && /* @__PURE__ */ React61.createElement(ModalSection, { title: "Pricing details" }, /* @__PURE__ */ React61.createElement("div", { className: "rounded-lg border border-secondary bg-secondary/40 p-4" }, /* @__PURE__ */ React61.createElement("div", { className: "prose prose-sm font-body text-fg-primary max-w-none" }, /* @__PURE__ */ React61.createElement(MarkdownRenderer2, { content: si.pricing_info })))));
19383
19479
  }
19384
19480
  function ServiceMenuSection({
19385
19481
  title = "Service Menu",
@@ -19393,20 +19489,20 @@ function ServiceMenuSection({
19393
19489
  servicesRowTitle = "Treatments",
19394
19490
  variant = "section"
19395
19491
  }) {
19396
- const packageList = React60.useMemo(
19492
+ const packageList = React61.useMemo(
19397
19493
  () => Array.isArray(packages) ? packages : [],
19398
19494
  [packages]
19399
19495
  );
19400
- const serviceList = React60.useMemo(
19496
+ const serviceList = React61.useMemo(
19401
19497
  () => Array.isArray(services) ? services : [],
19402
19498
  [services]
19403
19499
  );
19404
- const serviceItemIdToService = React60.useMemo(() => {
19500
+ const serviceItemIdToService = React61.useMemo(() => {
19405
19501
  const m = /* @__PURE__ */ new Map();
19406
19502
  serviceList.forEach((s) => (s.service_items || []).forEach((si) => m.set(si.id, s)));
19407
19503
  return m;
19408
19504
  }, [serviceList]);
19409
- const packagesForMenu = React60.useMemo(
19505
+ const packagesForMenu = React61.useMemo(
19410
19506
  () => packageList.map((pkg) => {
19411
19507
  var _a, _b, _c;
19412
19508
  const category_names = [
@@ -19436,9 +19532,9 @@ function ServiceMenuSection({
19436
19532
  const isPage = variant === "page";
19437
19533
  const packagesLayout = isPage ? "grid" : packagesForMenu.length >= CAROUSEL_MIN ? "carousel" : "grid";
19438
19534
  const serviceItemsLayout = isPage ? "grid" : serviceItemsForMenu.length >= CAROUSEL_MIN ? "carousel" : "grid";
19439
- const [detailItem, setDetailItem] = useState30(null);
19440
- const [portalTarget, setPortalTarget] = useState30(null);
19441
- useEffect11(() => {
19535
+ const [detailItem, setDetailItem] = useState32(null);
19536
+ const [portalTarget, setPortalTarget] = useState32(null);
19537
+ useEffect12(() => {
19442
19538
  let el = document.getElementById(SERVICE_MENU_MODAL_ROOT_ID);
19443
19539
  if (!el) {
19444
19540
  el = document.createElement("div");
@@ -19459,7 +19555,7 @@ function ServiceMenuSection({
19459
19555
  const plainDesc = cardDesc ? cardDesc.replace(/[#*`\[\]]/g, "").replace(/\n+/g, " ").trim() : "";
19460
19556
  const descText = plainDesc.length > 120 ? plainDesc.slice(0, 120) + "\u2026" : plainDesc;
19461
19557
  const hasSpecial = getActivePublicOffers(pkg.offers).length > 0;
19462
- return /* @__PURE__ */ React60.createElement(
19558
+ return /* @__PURE__ */ React61.createElement(
19463
19559
  GridCardWithImage,
19464
19560
  {
19465
19561
  photoAttachments: pkg.photo_attachments,
@@ -19473,7 +19569,7 @@ function ServiceMenuSection({
19473
19569
  cycleSeed: `pkg-${pkg.id}-${index}`,
19474
19570
  hasSpecial
19475
19571
  },
19476
- descText && /* @__PURE__ */ React60.createElement("p", { className: "font-body text-sm text-tertiary mt-1 line-clamp-2" }, descText)
19572
+ descText && /* @__PURE__ */ React61.createElement("p", { className: "font-body text-sm text-tertiary mt-1 line-clamp-2" }, descText)
19477
19573
  );
19478
19574
  },
19479
19575
  [websitePhotos, companyInformation]
@@ -19481,7 +19577,7 @@ function ServiceMenuSection({
19481
19577
  const renderServiceItemCard = useCallback10(
19482
19578
  (si, index) => {
19483
19579
  var _a, _b, _c, _d;
19484
- return /* @__PURE__ */ React60.createElement(
19580
+ return /* @__PURE__ */ React61.createElement(
19485
19581
  GridCardWithImage,
19486
19582
  {
19487
19583
  photoAttachments: si.photo_attachments,
@@ -19495,13 +19591,13 @@ function ServiceMenuSection({
19495
19591
  cycleSeed: `service-item-${si.id}-${index}`,
19496
19592
  hasSpecial: getActivePublicOffers(si.offers).length > 0
19497
19593
  },
19498
- formatPriceCents(si.price_cents) && /* @__PURE__ */ React60.createElement("p", { className: "font-body text-sm font-medium text-fg-primary mt-1" }, formatPriceCents(si.price_cents)),
19594
+ formatPriceCents(si.price_cents) && /* @__PURE__ */ React61.createElement("p", { className: "font-body text-sm font-medium text-fg-primary mt-1" }, formatPriceCents(si.price_cents)),
19499
19595
  (() => {
19500
19596
  const desc = si.summary || si.description_markdown || si.service_summary || si.service_description_markdown;
19501
19597
  if (!desc) return null;
19502
19598
  const plain = desc.replace(/[#*`\[\]]/g, "").replace(/\n+/g, " ").trim();
19503
19599
  const text = plain.length > 120 ? plain.slice(0, 120) + "\u2026" : plain;
19504
- return /* @__PURE__ */ React60.createElement("p", { className: "font-body text-sm text-tertiary mt-1 line-clamp-2" }, text);
19600
+ return /* @__PURE__ */ React61.createElement("p", { className: "font-body text-sm text-tertiary mt-1 line-clamp-2" }, text);
19505
19601
  })()
19506
19602
  );
19507
19603
  },
@@ -19509,7 +19605,7 @@ function ServiceMenuSection({
19509
19605
  );
19510
19606
  if (!hasAny) return null;
19511
19607
  const modalTitle = detailItem ? detailItem.item.name : "";
19512
- return /* @__PURE__ */ React60.createElement("section", { className: variant === "page" ? "py-12 md:py-20" : "py-12 md:py-16" }, /* @__PURE__ */ React60.createElement("div", { className: "mx-auto max-w-container px-4 md:px-8" }, variant === "section" && /* @__PURE__ */ React60.createElement("div", { className: "mx-auto max-w-3xl text-center mb-12 md:mb-16" }, title && /* @__PURE__ */ React60.createElement("h2", { className: "font-display text-4xl font-normal text-fg-primary md:text-5xl" }, title), subtitle && /* @__PURE__ */ React60.createElement("p", { className: "mt-4 font-display text-lg leading-relaxed text-tertiary md:text-xl max-w-3xl mx-auto" }, subtitle)), /* @__PURE__ */ React60.createElement(
19608
+ return /* @__PURE__ */ React61.createElement("section", { className: variant === "page" ? "py-12 md:py-20" : "py-12 md:py-16" }, /* @__PURE__ */ React61.createElement("div", { className: "mx-auto max-w-container px-4 md:px-8" }, variant === "section" && /* @__PURE__ */ React61.createElement("div", { className: "mx-auto max-w-3xl text-center mb-12 md:mb-16" }, title && /* @__PURE__ */ React61.createElement("h2", { className: "font-display text-4xl font-normal text-fg-primary md:text-5xl" }, title), subtitle && /* @__PURE__ */ React61.createElement("p", { className: "mt-4 font-display text-lg leading-relaxed text-tertiary md:text-xl max-w-3xl mx-auto" }, subtitle)), /* @__PURE__ */ React61.createElement(
19513
19609
  MenuBlock,
19514
19610
  {
19515
19611
  rowTitle: "Packages",
@@ -19517,7 +19613,7 @@ function ServiceMenuSection({
19517
19613
  layout: packagesLayout,
19518
19614
  renderItem: renderPackageCard
19519
19615
  }
19520
- ), /* @__PURE__ */ React60.createElement(
19616
+ ), /* @__PURE__ */ React61.createElement(
19521
19617
  MenuBlock,
19522
19618
  {
19523
19619
  rowTitle: servicesRowTitle,
@@ -19525,8 +19621,8 @@ function ServiceMenuSection({
19525
19621
  layout: serviceItemsLayout,
19526
19622
  renderItem: renderServiceItemCard
19527
19623
  }
19528
- ), variant === "section" && viewAllHref && viewAllText && /* @__PURE__ */ React60.createElement("div", { className: "mt-12 text-center" }, /* @__PURE__ */ React60.createElement(Button2, { href: viewAllHref, color: "primary", size: "md" }, viewAllText))), portalTarget && detailItem && createPortal(
19529
- /* @__PURE__ */ React60.createElement(
19624
+ ), variant === "section" && viewAllHref && viewAllText && /* @__PURE__ */ React61.createElement("div", { className: "mt-12 text-center" }, /* @__PURE__ */ React61.createElement(Button2, { href: viewAllHref, color: "primary", size: "md" }, viewAllText))), portalTarget && detailItem && createPortal(
19625
+ /* @__PURE__ */ React61.createElement(
19530
19626
  Modal,
19531
19627
  {
19532
19628
  isOpen: true,
@@ -19534,7 +19630,7 @@ function ServiceMenuSection({
19534
19630
  title: modalTitle,
19535
19631
  maxWidth: "2xl"
19536
19632
  },
19537
- /* @__PURE__ */ React60.createElement(
19633
+ /* @__PURE__ */ React61.createElement(
19538
19634
  DetailModalContent,
19539
19635
  {
19540
19636
  detail: detailItem,
@@ -19554,9 +19650,9 @@ function createThemedExport2(componentName, BaseComponent) {
19554
19650
  const { theme } = useTheme();
19555
19651
  try {
19556
19652
  const Component2 = getThemedComponent(componentName, theme);
19557
- return React61.createElement(Component2, props);
19653
+ return React62.createElement(Component2, props);
19558
19654
  } catch (e) {
19559
- return React61.createElement(BaseComponent, props);
19655
+ return React62.createElement(BaseComponent, props);
19560
19656
  }
19561
19657
  };
19562
19658
  }
@@ -19599,108 +19695,65 @@ var HomeHeroComponent2 = createThemedExport2("home-hero-component", HomeHeroComp
19599
19695
  // src/lib/actions.ts
19600
19696
  var API_URL = process.env.API_URL || "http://localhost:3000/api/v1";
19601
19697
  var API_KEY = process.env.API_KEY || "";
19602
- async function submitContactFormAction(formData) {
19698
+ function extractFormFields(formData) {
19603
19699
  var _a, _b, _c, _d, _e;
19700
+ return {
19701
+ firstName: ((_a = formData.get("firstName")) == null ? void 0 : _a.toString().trim()) || "",
19702
+ lastName: ((_b = formData.get("lastName")) == null ? void 0 : _b.toString().trim()) || "",
19703
+ email: ((_c = formData.get("email")) == null ? void 0 : _c.toString().trim()) || "",
19704
+ phone: ((_d = formData.get("phone")) == null ? void 0 : _d.toString().trim()) || "",
19705
+ message: ((_e = formData.get("message")) == null ? void 0 : _e.toString().trim()) || ""
19706
+ };
19707
+ }
19708
+ async function postFormSubmission(payload) {
19709
+ return fetch(`${API_URL}/public/form_submissions`, {
19710
+ method: "POST",
19711
+ headers: {
19712
+ "Content-Type": "application/json",
19713
+ "X-API-Key": API_KEY
19714
+ },
19715
+ body: JSON.stringify(payload)
19716
+ });
19717
+ }
19718
+ async function submitContactFormAction(formData) {
19719
+ const { firstName, lastName, email, phone, message } = extractFormFields(formData);
19720
+ if (!firstName || !lastName || !email || !message) {
19721
+ return { success: false, error: "Please fill in all required fields." };
19722
+ }
19604
19723
  try {
19605
- const firstName = ((_a = formData.get("firstName")) == null ? void 0 : _a.toString().trim()) || "";
19606
- const lastName = ((_b = formData.get("lastName")) == null ? void 0 : _b.toString().trim()) || "";
19607
- const email = ((_c = formData.get("email")) == null ? void 0 : _c.toString().trim()) || "";
19608
- const phone = ((_d = formData.get("phone")) == null ? void 0 : _d.toString().trim()) || "";
19609
- const message = ((_e = formData.get("message")) == null ? void 0 : _e.toString().trim()) || "";
19610
- if (!firstName || !lastName || !email || !message) {
19611
- return {
19612
- success: false,
19613
- error: "Please fill in all required fields."
19614
- };
19615
- }
19616
- const name = `${firstName} ${lastName}`;
19617
- const payload = {
19618
- name,
19724
+ const res = await postFormSubmission({
19725
+ name: `${firstName} ${lastName}`,
19619
19726
  email,
19620
19727
  phone,
19621
19728
  message,
19622
19729
  source: "contact_form"
19623
- };
19624
- const response = await fetch(`${API_URL}/public/form_submissions`, {
19625
- method: "POST",
19626
- headers: {
19627
- "Content-Type": "application/json",
19628
- "X-API-Key": API_KEY
19629
- },
19630
- body: JSON.stringify(payload)
19631
19730
  });
19632
- const data = await response.json();
19633
- if (!response.ok) {
19634
- return {
19635
- success: false,
19636
- error: data.error || "Failed to submit contact form. Please try again."
19637
- };
19638
- }
19639
- return {
19640
- success: true,
19641
- message: data.message || "Thank you for contacting us! We'll get back to you soon."
19642
- };
19643
- } catch (error) {
19644
- console.error("Contact form submission error:", error);
19645
- return {
19646
- success: false,
19647
- error: "Network error. Please try again later."
19648
- };
19731
+ const data = await res.json();
19732
+ if (!res.ok) return { success: false, error: data.error || "Failed to submit. Please try again." };
19733
+ return { success: true, message: data.message || "Thank you for contacting us! We'll get back to you soon." };
19734
+ } catch (e) {
19735
+ return { success: false, error: "Network error. Please try again later." };
19649
19736
  }
19650
19737
  }
19651
19738
  async function submitLeadFormAction(formData) {
19652
- var _a, _b, _c, _d, _e, _f, _g;
19739
+ var _a, _b;
19740
+ const { firstName, lastName, email, phone, message } = extractFormFields(formData);
19741
+ if (!firstName || !lastName || !email || !message) {
19742
+ return { success: false, error: "Please fill in all required fields." };
19743
+ }
19653
19744
  try {
19654
- const firstName = ((_a = formData.get("firstName")) == null ? void 0 : _a.toString().trim()) || "";
19655
- const lastName = ((_b = formData.get("lastName")) == null ? void 0 : _b.toString().trim()) || "";
19656
- const email = ((_c = formData.get("email")) == null ? void 0 : _c.toString().trim()) || "";
19657
- const phone = ((_d = formData.get("phone")) == null ? void 0 : _d.toString().trim()) || "";
19658
- const message = ((_e = formData.get("message")) == null ? void 0 : _e.toString().trim()) || "";
19659
- if (!firstName || !lastName || !email || !message) {
19660
- return {
19661
- success: false,
19662
- error: "Please fill in all required fields."
19663
- };
19664
- }
19665
- const payload = {
19666
- formType: "lead",
19667
- firstName,
19668
- lastName,
19669
- email,
19670
- phone,
19671
- message
19672
- };
19673
- const response = await fetch(`${API_URL}/public/form_submissions`, {
19674
- method: "POST",
19675
- headers: {
19676
- "Content-Type": "application/json",
19677
- "X-API-Key": API_KEY
19678
- },
19679
- body: JSON.stringify(payload)
19680
- });
19681
- const data = await response.json();
19682
- if (!response.ok) {
19683
- return {
19684
- success: false,
19685
- error: data.error || "Failed to submit lead form. Please try again."
19686
- };
19687
- }
19688
- const eventId = (_g = (_f = data.data) == null ? void 0 : _f.event_id) != null ? _g : void 0;
19689
- return __spreadValues({
19690
- success: true,
19691
- message: data.message || "Thank you! We'll be in touch soon."
19692
- }, eventId && { eventId });
19693
- } catch (error) {
19694
- console.error("Lead form submission error:", error);
19695
- return {
19696
- success: false,
19697
- error: "Network error. Please try again later."
19698
- };
19745
+ const res = await postFormSubmission({ formType: "lead", firstName, lastName, email, phone, message });
19746
+ const data = await res.json();
19747
+ if (!res.ok) return { success: false, error: data.error || "Failed to submit. Please try again." };
19748
+ const eventId = (_b = (_a = data.data) == null ? void 0 : _a.event_id) != null ? _b : void 0;
19749
+ return __spreadValues({ success: true, message: data.message || "Thank you! We'll be in touch soon." }, eventId && { eventId });
19750
+ } catch (e) {
19751
+ return { success: false, error: "Network error. Please try again later." };
19699
19752
  }
19700
19753
  }
19701
19754
 
19702
19755
  // src/design_system/components/ChatWidget.tsx
19703
- import React62, { useState as useState31, useEffect as useEffect12, useRef as useRef18, useCallback as useCallback11 } from "react";
19756
+ import React63, { useState as useState33, useEffect as useEffect13, useRef as useRef19, useCallback as useCallback11 } from "react";
19704
19757
  import { X, MessageChatSquare } from "@untitledui/icons";
19705
19758
  var formatTime = (isoString) => {
19706
19759
  const date = new Date(isoString);
@@ -19721,17 +19774,19 @@ function ChatWidget({
19721
19774
  position = "bottom-right",
19722
19775
  primaryColor,
19723
19776
  sessionId: providedSessionId,
19777
+ contactId,
19724
19778
  displayName: providedDisplayName,
19725
19779
  teamMembers = []
19726
19780
  }) {
19727
- const [isOpen, setIsOpen] = useState31(false);
19728
- const [messages, setMessages] = useState31([]);
19729
- const [inputValue, setInputValue] = useState31("");
19730
- const [isLoading, setIsLoading] = useState31(false);
19731
- const [sessionId, setSessionId] = useState31("");
19732
- const [waitingForReply, setWaitingForReply] = useState31(false);
19733
- const messagesEndRef = useRef18(null);
19734
- useEffect12(() => {
19781
+ const [isOpen, setIsOpen] = useState33(false);
19782
+ const [messages, setMessages] = useState33([]);
19783
+ const [inputValue, setInputValue] = useState33("");
19784
+ const [isLoading, setIsLoading] = useState33(false);
19785
+ const [sessionId, setSessionId] = useState33("");
19786
+ const [waitingForReply, setWaitingForReply] = useState33(false);
19787
+ const messagesEndRef = useRef19(null);
19788
+ useEffect13(() => {
19789
+ if (contactId) return;
19735
19790
  if (providedSessionId) {
19736
19791
  setSessionId(providedSessionId);
19737
19792
  } else {
@@ -19739,15 +19794,17 @@ function ChatWidget({
19739
19794
  if (stored) {
19740
19795
  setSessionId(stored);
19741
19796
  } else {
19742
- const newId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
19797
+ const newId = `session_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
19743
19798
  localStorage.setItem("keystone_chat_session_id", newId);
19744
19799
  setSessionId(newId);
19745
19800
  }
19746
19801
  }
19747
- }, [providedSessionId]);
19802
+ }, [contactId, providedSessionId]);
19748
19803
  const loadMessages = useCallback11(async () => {
19804
+ if (!contactId && !sessionId) return [];
19749
19805
  try {
19750
- const response = await fetch(`/api/chat/?identifier=${encodeURIComponent(sessionId)}`);
19806
+ const query = contactId ? `contact_id=${encodeURIComponent(contactId)}` : `identifier=${encodeURIComponent(sessionId)}`;
19807
+ const response = await fetch(`/api/chat/?${query}`);
19751
19808
  if (response.ok) {
19752
19809
  const result = await response.json();
19753
19810
  const newMessages = result.data || [];
@@ -19758,34 +19815,37 @@ function ChatWidget({
19758
19815
  console.error("Failed to load messages:", error);
19759
19816
  }
19760
19817
  return [];
19761
- }, [sessionId]);
19762
- useEffect12(() => {
19763
- if (isOpen && sessionId) {
19818
+ }, [contactId, sessionId]);
19819
+ useEffect13(() => {
19820
+ if (isOpen && (contactId || sessionId)) {
19764
19821
  loadMessages();
19765
19822
  }
19766
- }, [isOpen, sessionId, loadMessages]);
19767
- useEffect12(() => {
19823
+ }, [isOpen, contactId, sessionId, loadMessages]);
19824
+ useEffect13(() => {
19768
19825
  var _a;
19769
19826
  (_a = messagesEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
19770
19827
  }, [messages]);
19828
+ const pollIntervalRef = useRef19(null);
19829
+ useEffect13(() => {
19830
+ return () => {
19831
+ if (pollIntervalRef.current !== null) {
19832
+ clearInterval(pollIntervalRef.current);
19833
+ }
19834
+ };
19835
+ }, []);
19771
19836
  const pollForAgentReply = () => {
19772
19837
  setWaitingForReply(true);
19773
19838
  let attempts = 0;
19774
19839
  const maxAttempts = 30;
19775
- const pollInterval = setInterval(async () => {
19840
+ pollIntervalRef.current = setInterval(async () => {
19776
19841
  attempts++;
19777
19842
  try {
19778
19843
  const newMessages = await loadMessages();
19779
19844
  const latest = newMessages[newMessages.length - 1];
19780
19845
  const hasAgentReplyWithBody = (latest == null ? void 0 : latest.sender_type) === "agent" && (latest == null ? void 0 : latest.body) != null && String(latest.body).trim() !== "";
19781
- if (hasAgentReplyWithBody) {
19782
- clearInterval(pollInterval);
19783
- setWaitingForReply(false);
19784
- setIsLoading(false);
19785
- return;
19786
- }
19787
- if (attempts >= maxAttempts) {
19788
- clearInterval(pollInterval);
19846
+ if (hasAgentReplyWithBody || attempts >= maxAttempts) {
19847
+ clearInterval(pollIntervalRef.current);
19848
+ pollIntervalRef.current = null;
19789
19849
  setWaitingForReply(false);
19790
19850
  setIsLoading(false);
19791
19851
  }
@@ -19796,7 +19856,7 @@ function ChatWidget({
19796
19856
  };
19797
19857
  const sendMessage = async () => {
19798
19858
  var _a, _b, _c;
19799
- if (!inputValue.trim() || !sessionId) return;
19859
+ if (!inputValue.trim() || !contactId && !sessionId) return;
19800
19860
  const messageText = inputValue.trim();
19801
19861
  setInputValue("");
19802
19862
  setIsLoading(true);
@@ -19811,15 +19871,10 @@ function ChatWidget({
19811
19871
  try {
19812
19872
  const response = await fetch("/api/chat/", {
19813
19873
  method: "POST",
19814
- headers: {
19815
- "Content-Type": "application/json"
19816
- },
19817
- body: JSON.stringify({
19818
- identifier: sessionId,
19819
- body: messageText,
19820
- display_name: providedDisplayName,
19821
- page_url: window.location.href
19822
- })
19874
+ headers: { "Content-Type": "application/json" },
19875
+ body: JSON.stringify(
19876
+ contactId ? { contact_id: contactId, body: messageText, display_name: providedDisplayName, page_url: window.location.href } : { identifier: sessionId, body: messageText, display_name: providedDisplayName, page_url: window.location.href }
19877
+ )
19823
19878
  });
19824
19879
  if (response.ok) {
19825
19880
  const result = await response.json();
@@ -19842,7 +19897,7 @@ function ChatWidget({
19842
19897
  setIsLoading(false);
19843
19898
  }
19844
19899
  };
19845
- const handleKeyPress = (e) => {
19900
+ const handleKeyDown = (e) => {
19846
19901
  if (e.key === "Enter" && !e.shiftKey) {
19847
19902
  e.preventDefault();
19848
19903
  sendMessage();
@@ -19854,7 +19909,7 @@ function ChatWidget({
19854
19909
  } : void 0;
19855
19910
  const widgetBrandClass = primaryColor ? "bg-[var(--widget-primary)]" : "bg-brand-solid";
19856
19911
  const widgetBrandHoverClass = primaryColor ? "hover:bg-[var(--widget-primary-hover)]" : "hover:bg-brand-solid_hover";
19857
- return /* @__PURE__ */ React62.createElement(
19912
+ return /* @__PURE__ */ React63.createElement(
19858
19913
  "div",
19859
19914
  {
19860
19915
  className: cx(
@@ -19863,7 +19918,7 @@ function ChatWidget({
19863
19918
  ),
19864
19919
  style: customColorVars
19865
19920
  },
19866
- !isOpen && /* @__PURE__ */ React62.createElement(
19921
+ !isOpen && /* @__PURE__ */ React63.createElement(
19867
19922
  "button",
19868
19923
  {
19869
19924
  onClick: () => setIsOpen(true),
@@ -19873,15 +19928,15 @@ function ChatWidget({
19873
19928
  ),
19874
19929
  "aria-label": "Open chat"
19875
19930
  },
19876
- /* @__PURE__ */ React62.createElement(MessageChatSquare, { className: "size-6" })
19931
+ /* @__PURE__ */ React63.createElement(MessageChatSquare, { className: "size-6" })
19877
19932
  ),
19878
- isOpen && /* @__PURE__ */ React62.createElement("div", { className: "flex h-[500px] w-[380px] flex-col overflow-hidden rounded-xl bg-primary shadow-2xl ring-1 ring-secondary ring-inset" }, /* @__PURE__ */ React62.createElement("div", { className: cx(
19933
+ isOpen && /* @__PURE__ */ React63.createElement("div", { className: "flex h-[500px] w-[380px] flex-col overflow-hidden rounded-xl bg-primary shadow-2xl ring-1 ring-secondary ring-inset" }, /* @__PURE__ */ React63.createElement("div", { className: cx(
19879
19934
  "flex items-center justify-between gap-3 p-4 text-white",
19880
19935
  widgetBrandClass
19881
- ) }, /* @__PURE__ */ React62.createElement("div", { className: "flex min-w-0 flex-1 flex-col gap-2" }, /* @__PURE__ */ React62.createElement("h3", { className: "m-0 text-lg font-semibold leading-tight" }, "Chat with us"), (() => {
19936
+ ) }, /* @__PURE__ */ React63.createElement("div", { className: "flex min-w-0 flex-1 flex-col gap-2" }, /* @__PURE__ */ React63.createElement("h3", { className: "m-0 text-lg font-semibold leading-tight" }, "Chat with us"), (() => {
19882
19937
  const membersWithPhotos = teamMembers.filter((m) => m.photo_url);
19883
19938
  if (membersWithPhotos.length === 0) return null;
19884
- return /* @__PURE__ */ React62.createElement("div", { className: "flex -space-x-2" }, membersWithPhotos.slice(0, 5).map((member) => /* @__PURE__ */ React62.createElement(
19939
+ return /* @__PURE__ */ React63.createElement("div", { className: "flex -space-x-2" }, membersWithPhotos.slice(0, 5).map((member) => /* @__PURE__ */ React63.createElement(
19885
19940
  Avatar,
19886
19941
  {
19887
19942
  key: member.id,
@@ -19890,28 +19945,28 @@ function ChatWidget({
19890
19945
  alt: member.name,
19891
19946
  className: "bg-white ring-2 ring-white"
19892
19947
  }
19893
- )), membersWithPhotos.length > 5 && /* @__PURE__ */ React62.createElement(
19948
+ )), membersWithPhotos.length > 5 && /* @__PURE__ */ React63.createElement(
19894
19949
  Avatar,
19895
19950
  {
19896
19951
  size: "xs",
19897
19952
  className: "bg-white ring-2 ring-white",
19898
- placeholder: /* @__PURE__ */ React62.createElement("span", { className: "flex items-center justify-center text-xs font-semibold text-tertiary" }, "+", membersWithPhotos.length - 5)
19953
+ placeholder: /* @__PURE__ */ React63.createElement("span", { className: "flex items-center justify-center text-xs font-semibold text-tertiary" }, "+", membersWithPhotos.length - 5)
19899
19954
  }
19900
19955
  ));
19901
- })()), /* @__PURE__ */ React62.createElement(
19956
+ })()), /* @__PURE__ */ React63.createElement(
19902
19957
  "button",
19903
19958
  {
19904
19959
  onClick: () => setIsOpen(false),
19905
19960
  className: "cursor-pointer rounded-md p-1 text-white outline-none transition-colors hover:bg-white/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white",
19906
19961
  "aria-label": "Close chat"
19907
19962
  },
19908
- /* @__PURE__ */ React62.createElement(X, { className: "size-5" })
19909
- )), /* @__PURE__ */ React62.createElement("ul", { className: "flex flex-1 flex-col gap-4 overflow-y-auto px-4 py-4" }, messages.length === 0 && /* @__PURE__ */ React62.createElement("li", { className: "mt-5 text-center text-sm text-quaternary" }, "Start a conversation! We're here to help."), messages.map((message, index) => {
19963
+ /* @__PURE__ */ React63.createElement(X, { className: "size-5" })
19964
+ )), /* @__PURE__ */ React63.createElement("ul", { className: "flex flex-1 flex-col gap-4 overflow-y-auto px-4 py-4" }, messages.length === 0 && /* @__PURE__ */ React63.createElement("li", { className: "mt-5 text-center text-sm text-quaternary" }, "Start a conversation! We're here to help."), messages.map((message, index) => {
19910
19965
  var _a, _b, _c, _d;
19911
19966
  const isUser = message.sender_type === "contact";
19912
19967
  const prevMessage = index > 0 ? messages[index - 1] : null;
19913
19968
  const showAvatar = !isUser && (!prevMessage || prevMessage.sender_type === "contact");
19914
- return /* @__PURE__ */ React62.createElement(
19969
+ return /* @__PURE__ */ React63.createElement(
19915
19970
  "li",
19916
19971
  {
19917
19972
  key: message.id,
@@ -19920,7 +19975,7 @@ function ChatWidget({
19920
19975
  isUser ? "self-end pl-10" : "pr-8"
19921
19976
  )
19922
19977
  },
19923
- !isUser && showAvatar && /* @__PURE__ */ React62.createElement(
19978
+ !isUser && showAvatar && /* @__PURE__ */ React63.createElement(
19924
19979
  Avatar,
19925
19980
  {
19926
19981
  size: "sm",
@@ -19930,8 +19985,8 @@ function ChatWidget({
19930
19985
  initials: ((_c = teamMembers[0]) == null ? void 0 : _c.photo_url) ? void 0 : (((_d = teamMembers[0]) == null ? void 0 : _d.name) || message.sender_display_name).split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2)
19931
19986
  }
19932
19987
  ),
19933
- !isUser && !showAvatar && /* @__PURE__ */ React62.createElement("div", { className: "size-8 shrink-0" }),
19934
- /* @__PURE__ */ React62.createElement("article", { className: "flex min-w-0 flex-1 flex-col gap-1" }, /* @__PURE__ */ React62.createElement("header", { className: "flex items-center gap-2 px-0.5" }, /* @__PURE__ */ React62.createElement("cite", { className: "flex-1 truncate text-xs font-medium text-tertiary not-italic" }, isUser ? providedDisplayName || "You" : message.sender_display_name), /* @__PURE__ */ React62.createElement("time", { className: "text-xs text-quaternary", dateTime: message.created_at }, formatTime(message.created_at))), /* @__PURE__ */ React62.createElement(
19988
+ !isUser && !showAvatar && /* @__PURE__ */ React63.createElement("div", { className: "size-8 shrink-0" }),
19989
+ /* @__PURE__ */ React63.createElement("article", { className: "flex min-w-0 flex-1 flex-col gap-1" }, /* @__PURE__ */ React63.createElement("header", { className: "flex items-center gap-2 px-0.5" }, /* @__PURE__ */ React63.createElement("cite", { className: "flex-1 truncate text-xs font-medium text-tertiary not-italic" }, isUser ? providedDisplayName || "You" : message.sender_display_name), /* @__PURE__ */ React63.createElement("time", { className: "text-xs text-quaternary", dateTime: message.created_at }, formatTime(message.created_at))), /* @__PURE__ */ React63.createElement(
19935
19990
  "div",
19936
19991
  {
19937
19992
  className: cx(
@@ -19945,18 +20000,18 @@ function ChatWidget({
19945
20000
  message.body
19946
20001
  ))
19947
20002
  );
19948
- }), waitingForReply && /* @__PURE__ */ React62.createElement("li", { className: "relative flex items-start gap-3 pr-8" }, /* @__PURE__ */ React62.createElement(Avatar, { size: "sm", className: "shrink-0" }), /* @__PURE__ */ React62.createElement("article", { className: "flex min-w-0 flex-1 flex-col gap-1" }, /* @__PURE__ */ React62.createElement("div", { className: "flex h-9 w-16 items-center justify-center gap-1 rounded-xl rounded-tl-none bg-secondary ring-1 ring-inset ring-secondary" }, /* @__PURE__ */ React62.createElement("div", { className: "size-1.5 animate-bounce rounded-full bg-fg-tertiary [animation-delay:-0.3s]" }), /* @__PURE__ */ React62.createElement("div", { className: "size-1.5 animate-bounce rounded-full bg-fg-quaternary [animation-delay:-0.15s]" }), /* @__PURE__ */ React62.createElement("div", { className: "size-1.5 animate-bounce rounded-full bg-fg-tertiary" })))), /* @__PURE__ */ React62.createElement("div", { ref: messagesEndRef })), /* @__PURE__ */ React62.createElement("div", { className: "flex gap-2 border-t border-secondary p-3" }, /* @__PURE__ */ React62.createElement(
20003
+ }), waitingForReply && /* @__PURE__ */ React63.createElement("li", { className: "relative flex items-start gap-3 pr-8" }, /* @__PURE__ */ React63.createElement(Avatar, { size: "sm", className: "shrink-0" }), /* @__PURE__ */ React63.createElement("article", { className: "flex min-w-0 flex-1 flex-col gap-1" }, /* @__PURE__ */ React63.createElement("div", { className: "flex h-9 w-16 items-center justify-center gap-1 rounded-xl rounded-tl-none bg-secondary ring-1 ring-inset ring-secondary" }, /* @__PURE__ */ React63.createElement("div", { className: "size-1.5 animate-bounce rounded-full bg-fg-tertiary [animation-delay:-0.3s]" }), /* @__PURE__ */ React63.createElement("div", { className: "size-1.5 animate-bounce rounded-full bg-fg-quaternary [animation-delay:-0.15s]" }), /* @__PURE__ */ React63.createElement("div", { className: "size-1.5 animate-bounce rounded-full bg-fg-tertiary" })))), /* @__PURE__ */ React63.createElement("div", { ref: messagesEndRef })), /* @__PURE__ */ React63.createElement("div", { className: "flex gap-2 border-t border-secondary p-3" }, /* @__PURE__ */ React63.createElement(
19949
20004
  "input",
19950
20005
  {
19951
20006
  type: "text",
19952
20007
  value: inputValue,
19953
20008
  onChange: (e) => setInputValue(e.target.value),
19954
- onKeyPress: handleKeyPress,
20009
+ onKeyDown: handleKeyDown,
19955
20010
  placeholder: "Type a message...",
19956
20011
  disabled: isLoading,
19957
20012
  className: "flex-1 rounded-lg border border-secondary bg-primary px-3 py-2.5 text-sm text-primary outline-none ring-brand transition-colors placeholder:text-placeholder focus:border-brand focus:ring-1 disabled:cursor-not-allowed disabled:bg-disabled"
19958
20013
  }
19959
- ), /* @__PURE__ */ React62.createElement(
20014
+ ), /* @__PURE__ */ React63.createElement(
19960
20015
  "button",
19961
20016
  {
19962
20017
  onClick: sendMessage,
@@ -20009,6 +20064,7 @@ export {
20009
20064
  ComboBox,
20010
20065
  ContactHome2 as ContactHome,
20011
20066
  ContactSection5 as ContactSection,
20067
+ EmailSignupSection,
20012
20068
  FAQGrid3 as FAQGrid,
20013
20069
  FAQHero3 as FAQHero,
20014
20070
  FAQHome4 as FAQHome,