keystone-design-bootstrap 1.0.103 → 1.0.105-dev.0

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 (68) hide show
  1. package/README.md +4 -4
  2. package/dist/blog-post-vWzW8yFb.d.ts +50 -0
  3. package/dist/contexts/index.d.ts +13 -0
  4. package/dist/contexts/index.js +173 -0
  5. package/dist/contexts/index.js.map +1 -0
  6. package/dist/design_system/components/DynamicFormFields.d.ts +15 -0
  7. package/dist/design_system/components/DynamicFormFields.js +5176 -0
  8. package/dist/design_system/components/DynamicFormFields.js.map +1 -0
  9. package/dist/design_system/elements/index.d.ts +383 -0
  10. package/dist/design_system/elements/index.js +4007 -0
  11. package/dist/design_system/elements/index.js.map +1 -0
  12. package/dist/design_system/logo/keystone-logo.d.ts +6 -0
  13. package/dist/design_system/logo/keystone-logo.js +145 -0
  14. package/dist/design_system/logo/keystone-logo.js.map +1 -0
  15. package/dist/design_system/sections/index.d.ts +233 -0
  16. package/dist/design_system/sections/index.js +20048 -0
  17. package/dist/design_system/sections/index.js.map +1 -0
  18. package/dist/form-C94A_PX_.d.ts +36 -0
  19. package/dist/index.d.ts +86 -0
  20. package/dist/index.js +20646 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/lib/component-registry.d.ts +13 -0
  23. package/dist/lib/component-registry.js +36 -0
  24. package/dist/lib/component-registry.js.map +1 -0
  25. package/dist/lib/hooks/index.d.ts +83 -0
  26. package/dist/lib/hooks/index.js +182 -0
  27. package/dist/lib/hooks/index.js.map +1 -0
  28. package/dist/lib/server-api.d.ts +67 -0
  29. package/dist/lib/server-api.js +195 -0
  30. package/dist/lib/server-api.js.map +1 -0
  31. package/dist/package-DeHKpQp7.d.ts +121 -0
  32. package/dist/photos-CmBdWiuZ.d.ts +27 -0
  33. package/dist/themes/index.d.ts +17 -0
  34. package/dist/themes/index.js +29 -0
  35. package/dist/themes/index.js.map +1 -0
  36. package/dist/tracking/index.d.ts +254 -0
  37. package/dist/tracking/index.js +587 -0
  38. package/dist/tracking/index.js.map +1 -0
  39. package/dist/types/index.d.ts +313 -0
  40. package/dist/types/index.js +1 -0
  41. package/dist/types/index.js.map +1 -0
  42. package/dist/utils/cx.d.ts +15 -0
  43. package/dist/utils/cx.js +18 -0
  44. package/dist/utils/cx.js.map +1 -0
  45. package/dist/utils/gradient-placeholder.d.ts +7 -0
  46. package/dist/utils/gradient-placeholder.js +59 -0
  47. package/dist/utils/gradient-placeholder.js.map +1 -0
  48. package/dist/utils/is-react-component.d.ts +21 -0
  49. package/dist/utils/is-react-component.js +20 -0
  50. package/dist/utils/is-react-component.js.map +1 -0
  51. package/dist/utils/markdown-toc.d.ts +14 -0
  52. package/dist/utils/markdown-toc.js +29 -0
  53. package/dist/utils/markdown-toc.js.map +1 -0
  54. package/dist/utils/phone-helpers.d.ts +24 -0
  55. package/dist/utils/phone-helpers.js +26 -0
  56. package/dist/utils/phone-helpers.js.map +1 -0
  57. package/dist/utils/photo-helpers.d.ts +38 -0
  58. package/dist/utils/photo-helpers.js +41 -0
  59. package/dist/utils/photo-helpers.js.map +1 -0
  60. package/dist/website-photos-Cl1YqAno.d.ts +21 -0
  61. package/package.json +1 -1
  62. package/src/design_system/components/ChatWidget.tsx +66 -10
  63. package/src/design_system/portal/LoginForm.tsx +1 -24
  64. package/src/lib/chat-backend.ts +36 -0
  65. package/src/lib/consumer-session.ts +3 -1
  66. package/src/next/layouts/root-layout.tsx +5 -0
  67. package/src/next/routes/chat.ts +215 -18
  68. package/src/next/routes/consumer-auth.ts +49 -124
@@ -0,0 +1,13 @@
1
+ import React__default from 'react';
2
+ import { Theme } from '../themes/index.js';
3
+
4
+ /**
5
+ * Component Registry
6
+ * Runtime registry for component theme variants
7
+ */
8
+
9
+ declare function registerThemeVariant<P = unknown>(componentName: string, theme: string, component: React__default.ComponentType<P>): void;
10
+ declare function getThemedComponent(componentName: string, theme?: Theme): React__default.ComponentType<unknown>;
11
+ declare function getRegistry(): Record<string, string[]>;
12
+
13
+ export { getRegistry, getThemedComponent, registerThemeVariant };
@@ -0,0 +1,36 @@
1
+ "use client";
2
+
3
+ // src/lib/component-registry.ts
4
+ var registry = /* @__PURE__ */ new Map();
5
+ function registerThemeVariant(componentName, theme, component) {
6
+ if (!registry.has(componentName)) {
7
+ registry.set(componentName, /* @__PURE__ */ new Map());
8
+ }
9
+ registry.get(componentName).set(theme, component);
10
+ }
11
+ function getThemedComponent(componentName, theme = "classic") {
12
+ const variants = registry.get(componentName);
13
+ if (!variants || variants.size === 0) {
14
+ throw new Error(`No theme variants registered for "${componentName}"`);
15
+ }
16
+ if (variants.has(theme)) {
17
+ return variants.get(theme);
18
+ }
19
+ if (theme === "classic") {
20
+ throw new Error(`No classic variant for "${componentName}" - use base component`);
21
+ }
22
+ throw new Error(`No variant available for "${componentName}.${theme}"`);
23
+ }
24
+ function getRegistry() {
25
+ const result = {};
26
+ registry.forEach((variants, name) => {
27
+ result[name] = Array.from(variants.keys());
28
+ });
29
+ return result;
30
+ }
31
+ export {
32
+ getRegistry,
33
+ getThemedComponent,
34
+ registerThemeVariant
35
+ };
36
+ //# sourceMappingURL=component-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/component-registry.ts"],"sourcesContent":["/**\n * Component Registry\n * Runtime registry for component theme variants\n */\n\n'use client';\n\nimport React from 'react';\nimport { Theme } from '../themes';\n\n// The registry stores components with different prop types\n// We use React.ComponentType (without generics) to store components dynamically\n// This is type-safe because the generic registerThemeVariant function ensures correct types at registration\n// and components are retrieved and used dynamically at runtime\nconst registry = new Map<string, Map<string, React.ComponentType<unknown>>>();\n\n// Generic function accepts any component type without requiring type assertions at call sites\n// The component is stored with type erasure internally, but callers have full type safety\nexport function registerThemeVariant<P = unknown>(\n componentName: string,\n theme: string,\n component: React.ComponentType<P>\n) {\n if (!registry.has(componentName)) {\n registry.set(componentName, new Map());\n }\n registry.get(componentName)!.set(theme, component as React.ComponentType<unknown>);\n}\n\nexport function getThemedComponent(\n componentName: string,\n theme: Theme = 'classic'\n): React.ComponentType<unknown> {\n const variants = registry.get(componentName);\n \n if (!variants || variants.size === 0) {\n throw new Error(`No theme variants registered for \"${componentName}\"`);\n }\n \n if (variants.has(theme)) {\n return variants.get(theme)!;\n }\n \n if (theme === 'classic') {\n throw new Error(`No classic variant for \"${componentName}\" - use base component`);\n }\n \n throw new Error(`No variant available for \"${componentName}.${theme}\"`);\n}\n\nexport function getRegistry() {\n const result: Record<string, string[]> = {};\n registry.forEach((variants, name) => {\n result[name] = Array.from(variants.keys());\n });\n return result;\n}\n"],"mappings":";;;AAcA,IAAM,WAAW,oBAAI,IAAuD;AAIrE,SAAS,qBACd,eACA,OACA,WACA;AACA,MAAI,CAAC,SAAS,IAAI,aAAa,GAAG;AAChC,aAAS,IAAI,eAAe,oBAAI,IAAI,CAAC;AAAA,EACvC;AACA,WAAS,IAAI,aAAa,EAAG,IAAI,OAAO,SAAyC;AACnF;AAEO,SAAS,mBACd,eACA,QAAe,WACe;AAC9B,QAAM,WAAW,SAAS,IAAI,aAAa;AAE3C,MAAI,CAAC,YAAY,SAAS,SAAS,GAAG;AACpC,UAAM,IAAI,MAAM,qCAAqC,aAAa,GAAG;AAAA,EACvE;AAEA,MAAI,SAAS,IAAI,KAAK,GAAG;AACvB,WAAO,SAAS,IAAI,KAAK;AAAA,EAC3B;AAEA,MAAI,UAAU,WAAW;AACvB,UAAM,IAAI,MAAM,2BAA2B,aAAa,wBAAwB;AAAA,EAClF;AAEA,QAAM,IAAI,MAAM,6BAA6B,aAAa,IAAI,KAAK,GAAG;AACxE;AAEO,SAAS,cAAc;AAC5B,QAAM,SAAmC,CAAC;AAC1C,WAAS,QAAQ,CAAC,UAAU,SAAS;AACnC,WAAO,IAAI,IAAI,MAAM,KAAK,SAAS,KAAK,CAAC;AAAA,EAC3C,CAAC;AACD,SAAO;AACT;","names":[]}
@@ -0,0 +1,83 @@
1
+ import { RefObject } from '@react-types/shared';
2
+ import { P as PhotoAttachment } from '../../photos-CmBdWiuZ.js';
3
+
4
+ /**
5
+ * Checks whether a particular Tailwind CSS viewport size applies.
6
+ *
7
+ * @param size The size to check, which must either be included in Tailwind CSS's
8
+ * list of default screen sizes, or added to the Tailwind CSS config file.
9
+ *
10
+ * @returns A boolean indicating whether the viewport size applies.
11
+ */
12
+ declare const useBreakpoint: (size: "sm" | "md" | "lg" | "xl" | "2xl") => boolean;
13
+
14
+ type UseClipboardReturnType = {
15
+ /**
16
+ * The state indicating whether the text has been copied.
17
+ * If a string is provided, it will be used as the identifier for the copied state.
18
+ */
19
+ copied: string | boolean;
20
+ /**
21
+ * Function to copy text to the clipboard using the modern clipboard API.
22
+ * Falls back to the fallback function if the modern API fails.
23
+ *
24
+ * @param {string} text - The text to be copied.
25
+ * @param {string} [id] - Optional identifier to set the copied state.
26
+ * @returns {Promise<Object>} - A promise that resolves to an object containing:
27
+ * - `success` (boolean): Whether the copy operation was successful.
28
+ * - `error` (Error | undefined): The error object if the copy operation failed.
29
+ */
30
+ copy: (text: string, id?: string) => Promise<{
31
+ success: boolean;
32
+ error?: Error;
33
+ }>;
34
+ };
35
+ /**
36
+ * Custom hook to copy text to the clipboard.
37
+ *
38
+ * @returns {UseClipboardReturnType} - An object containing the copied state and the copy function.
39
+ */
40
+ declare const useClipboard: () => UseClipboardReturnType;
41
+
42
+ /**
43
+ * The options for the useResizeObserver hook.
44
+ */
45
+ type useResizeObserverOptionsType<T> = {
46
+ /**
47
+ * The ref to the element to observe.
48
+ */
49
+ ref: RefObject<T | undefined | null> | undefined;
50
+ /**
51
+ * The box to observe.
52
+ */
53
+ box?: ResizeObserverBoxOptions;
54
+ /**
55
+ * The callback function to call when the size changes.
56
+ */
57
+ onResize: () => void;
58
+ };
59
+ /**
60
+ * A hook that observes the size of an element and calls a callback function when the size changes.
61
+ * @param options - The options for the hook.
62
+ */
63
+ declare function useResizeObserver<T extends Element>(options: useResizeObserverOptionsType<T>): void;
64
+
65
+ interface CycledImage {
66
+ url: string;
67
+ alt: string;
68
+ }
69
+ interface UseImageCycleResult {
70
+ list: CycledImage[];
71
+ currentIndex: number;
72
+ nextIndex: number;
73
+ transitioning: boolean;
74
+ }
75
+ /**
76
+ * Cycles through a list of photo attachments with a seeded shuffle and crossfade transitions.
77
+ *
78
+ * Each card uses a unique seed so intervals are staggered — cards don't all transition in sync.
79
+ * The shuffle is deterministic (SSR-safe) and the timer only activates when there are 2+ images.
80
+ */
81
+ declare function useImageCycle(photoAttachments: PhotoAttachment[] | undefined, seed: string): UseImageCycleResult;
82
+
83
+ export { type CycledImage, type UseImageCycleResult, useBreakpoint, useClipboard, useImageCycle, useResizeObserver };
@@ -0,0 +1,182 @@
1
+ // src/lib/hooks/use-breakpoint.ts
2
+ import { useSyncExternalStore } from "react";
3
+ var screens = {
4
+ sm: "640px",
5
+ md: "768px",
6
+ lg: "1024px",
7
+ xl: "1280px",
8
+ "2xl": "1536px"
9
+ };
10
+ var useBreakpoint = (size) => {
11
+ return useSyncExternalStore(
12
+ (onStoreChange) => {
13
+ if (typeof window === "undefined") {
14
+ return () => {
15
+ };
16
+ }
17
+ const breakpoint = window.matchMedia(`(min-width: ${screens[size]})`);
18
+ breakpoint.addEventListener("change", onStoreChange);
19
+ return () => breakpoint.removeEventListener("change", onStoreChange);
20
+ },
21
+ () => {
22
+ if (typeof window === "undefined") {
23
+ return true;
24
+ }
25
+ return window.matchMedia(`(min-width: ${screens[size]})`).matches;
26
+ },
27
+ () => true
28
+ );
29
+ };
30
+
31
+ // src/lib/hooks/use-clipboard.ts
32
+ import { useCallback, useState } from "react";
33
+ var DEFAULT_TIMEOUT = 2e3;
34
+ var useClipboard = () => {
35
+ const [copied, setCopied] = useState(false);
36
+ const fallback = (text, id) => {
37
+ try {
38
+ const textArea = document.createElement("textarea");
39
+ textArea.value = text;
40
+ textArea.style.position = "absolute";
41
+ textArea.style.left = "-99999px";
42
+ document.body.appendChild(textArea);
43
+ textArea.select();
44
+ const success = document.execCommand("copy");
45
+ textArea.remove();
46
+ setCopied(id || true);
47
+ setTimeout(() => setCopied(false), DEFAULT_TIMEOUT);
48
+ return success ? { success: true } : { success: false, error: new Error("execCommand returned false") };
49
+ } catch (err) {
50
+ return {
51
+ success: false,
52
+ error: err instanceof Error ? err : new Error("Fallback copy failed")
53
+ };
54
+ }
55
+ };
56
+ const copy = useCallback(async (text, id) => {
57
+ if (navigator.clipboard && window.isSecureContext) {
58
+ try {
59
+ await navigator.clipboard.writeText(text);
60
+ setCopied(id || true);
61
+ setTimeout(() => setCopied(false), DEFAULT_TIMEOUT);
62
+ return { success: true };
63
+ } catch (e) {
64
+ return fallback(text, id);
65
+ }
66
+ }
67
+ return fallback(text);
68
+ }, []);
69
+ return { copied, copy };
70
+ };
71
+
72
+ // src/lib/hooks/use-resize-observer.ts
73
+ import { useEffect } from "react";
74
+ function hasResizeObserver() {
75
+ return typeof window.ResizeObserver !== "undefined";
76
+ }
77
+ function useResizeObserver(options) {
78
+ const { ref, box, onResize } = options;
79
+ useEffect(() => {
80
+ const element = ref == null ? void 0 : ref.current;
81
+ if (!element) {
82
+ return;
83
+ }
84
+ if (!hasResizeObserver()) {
85
+ window.addEventListener("resize", onResize, false);
86
+ return () => {
87
+ window.removeEventListener("resize", onResize, false);
88
+ };
89
+ } else {
90
+ const resizeObserverInstance = new window.ResizeObserver((entries) => {
91
+ if (!entries.length) {
92
+ return;
93
+ }
94
+ onResize();
95
+ });
96
+ resizeObserverInstance.observe(element, { box });
97
+ return () => {
98
+ if (element) {
99
+ resizeObserverInstance.unobserve(element);
100
+ }
101
+ };
102
+ }
103
+ }, [onResize, ref, box]);
104
+ }
105
+
106
+ // src/lib/hooks/use-image-cycle.ts
107
+ import { useState as useState2, useEffect as useEffect2, useMemo } from "react";
108
+ var CYCLE_INTERVAL_MIN_MS = 6e3;
109
+ var CYCLE_INTERVAL_MAX_MS = 8e3;
110
+ var CROSSFADE_DURATION_MS = 600;
111
+ function seedToUnit(seed) {
112
+ let h = 2166136261 >>> 0;
113
+ for (let i = 0; i < seed.length; i++) {
114
+ h ^= seed.charCodeAt(i);
115
+ h = Math.imul(h, 16777619) >>> 0 >>> 0;
116
+ }
117
+ return (h >>> 0) / 4294967296;
118
+ }
119
+ function shuffleWithSeed(array, seed) {
120
+ if (array.length <= 1) return array;
121
+ const arr = [...array];
122
+ let h = 2166136261 >>> 0;
123
+ for (let i = 0; i < seed.length; i++) {
124
+ h ^= seed.charCodeAt(i);
125
+ h = Math.imul(h, 16777619) >>> 0 >>> 0;
126
+ }
127
+ const next = (step) => {
128
+ h = Math.imul(1664525, h + step >>> 0) + 1013904223 >>> 0;
129
+ return (h >>> 0) / 4294967296;
130
+ };
131
+ for (let i = arr.length - 1; i > 0; i--) {
132
+ const j = Math.floor(next(i) * (i + 1));
133
+ [arr[i], arr[j]] = [arr[j], arr[i]];
134
+ }
135
+ return arr;
136
+ }
137
+ function photoUrlFromAttachment(pa) {
138
+ var _a, _b, _c;
139
+ 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);
140
+ }
141
+ function photoAltFromAttachment(pa) {
142
+ var _a, _b;
143
+ return ((_a = pa.photo) == null ? void 0 : _a.alt_text) || ((_b = pa.photo) == null ? void 0 : _b.title) || "";
144
+ }
145
+ function useImageCycle(photoAttachments, seed) {
146
+ const list = useMemo(() => {
147
+ const arr = Array.isArray(photoAttachments) && photoAttachments.length > 0 ? photoAttachments : [];
148
+ if (arr.length === 0) return [];
149
+ return shuffleWithSeed(arr, seed).map((pa) => {
150
+ var _a;
151
+ return { url: (_a = photoUrlFromAttachment(pa)) != null ? _a : "", alt: photoAltFromAttachment(pa) };
152
+ }).filter((x) => x.url);
153
+ }, [photoAttachments, seed]);
154
+ const [currentIndex, setCurrentIndex] = useState2(0);
155
+ const [transitioning, setTransitioning] = useState2(false);
156
+ const intervalMs = useMemo(
157
+ () => CYCLE_INTERVAL_MIN_MS + Math.floor(seedToUnit(seed) * (CYCLE_INTERVAL_MAX_MS - CYCLE_INTERVAL_MIN_MS + 1)),
158
+ [seed]
159
+ );
160
+ useEffect2(() => {
161
+ if (list.length <= 1) return;
162
+ const id = setInterval(() => setTransitioning(true), intervalMs);
163
+ return () => clearInterval(id);
164
+ }, [list.length, intervalMs]);
165
+ useEffect2(() => {
166
+ if (!transitioning || list.length <= 1) return;
167
+ const t = setTimeout(() => {
168
+ setCurrentIndex((i) => (i + 1) % list.length);
169
+ setTransitioning(false);
170
+ }, CROSSFADE_DURATION_MS);
171
+ return () => clearTimeout(t);
172
+ }, [transitioning, list.length]);
173
+ const nextIndex = list.length > 1 ? (currentIndex + 1) % list.length : 0;
174
+ return { list, currentIndex, nextIndex, transitioning };
175
+ }
176
+ export {
177
+ useBreakpoint,
178
+ useClipboard,
179
+ useImageCycle,
180
+ useResizeObserver
181
+ };
182
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/hooks/use-breakpoint.ts","../../../src/lib/hooks/use-clipboard.ts","../../../src/lib/hooks/use-resize-observer.ts","../../../src/lib/hooks/use-image-cycle.ts"],"sourcesContent":["\"use client\";\n\nimport { useSyncExternalStore } from \"react\";\n\nconst screens = {\n sm: \"640px\",\n md: \"768px\",\n lg: \"1024px\",\n xl: \"1280px\",\n \"2xl\": \"1536px\",\n};\n\n/**\n * Checks whether a particular Tailwind CSS viewport size applies.\n *\n * @param size The size to check, which must either be included in Tailwind CSS's\n * list of default screen sizes, or added to the Tailwind CSS config file.\n *\n * @returns A boolean indicating whether the viewport size applies.\n */\nexport const useBreakpoint = (size: \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\") => {\n return useSyncExternalStore(\n (onStoreChange) => {\n if (typeof window === \"undefined\") {\n return () => {};\n }\n const breakpoint = window.matchMedia(`(min-width: ${screens[size]})`);\n breakpoint.addEventListener(\"change\", onStoreChange);\n return () => breakpoint.removeEventListener(\"change\", onStoreChange);\n },\n () => {\n if (typeof window === \"undefined\") {\n return true;\n }\n return window.matchMedia(`(min-width: ${screens[size]})`).matches;\n },\n () => true\n );\n};\n\n","\"use client\";\n\nimport { useCallback, useState } from \"react\";\n\nconst DEFAULT_TIMEOUT = 2000;\n\ntype UseClipboardReturnType = {\n /**\n * The state indicating whether the text has been copied.\n * If a string is provided, it will be used as the identifier for the copied state.\n */\n copied: string | boolean;\n /**\n * Function to copy text to the clipboard using the modern clipboard API.\n * Falls back to the fallback function if the modern API fails.\n *\n * @param {string} text - The text to be copied.\n * @param {string} [id] - Optional identifier to set the copied state.\n * @returns {Promise<Object>} - A promise that resolves to an object containing:\n * - `success` (boolean): Whether the copy operation was successful.\n * - `error` (Error | undefined): The error object if the copy operation failed.\n */\n copy: (text: string, id?: string) => Promise<{ success: boolean; error?: Error }>;\n};\n\n/**\n * Custom hook to copy text to the clipboard.\n *\n * @returns {UseClipboardReturnType} - An object containing the copied state and the copy function.\n */\nexport const useClipboard = (): UseClipboardReturnType => {\n const [copied, setCopied] = useState<string | boolean>(false);\n\n // Fallback function for older browsers\n const fallback = (text: string, id?: string) => {\n try {\n // Textarea to copy the text to the clipboard\n const textArea = document.createElement(\"textarea\");\n textArea.value = text;\n textArea.style.position = \"absolute\";\n textArea.style.left = \"-99999px\";\n\n document.body.appendChild(textArea);\n textArea.select();\n\n const success = document.execCommand(\"copy\");\n textArea.remove();\n\n setCopied(id || true);\n setTimeout(() => setCopied(false), DEFAULT_TIMEOUT);\n\n return success ? { success: true } : { success: false, error: new Error(\"execCommand returned false\") };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err : new Error(\"Fallback copy failed\"),\n };\n }\n };\n\n const copy = useCallback(async (text: string, id?: string) => {\n if (navigator.clipboard && window.isSecureContext) {\n try {\n await navigator.clipboard.writeText(text);\n\n setCopied(id || true);\n setTimeout(() => setCopied(false), DEFAULT_TIMEOUT);\n\n return { success: true };\n } catch {\n // If modern method fails, try fallback\n return fallback(text, id);\n }\n }\n return fallback(text);\n }, []);\n\n return { copied, copy };\n};\n","import { useEffect } from \"react\";\nimport type { RefObject } from \"@react-types/shared\";\n\n/**\n * Checks if the ResizeObserver API is supported.\n * @returns True if the ResizeObserver API is supported, false otherwise.\n */\nfunction hasResizeObserver() {\n return typeof window.ResizeObserver !== \"undefined\";\n}\n\n/**\n * The options for the useResizeObserver hook.\n */\ntype useResizeObserverOptionsType<T> = {\n /**\n * The ref to the element to observe.\n */\n ref: RefObject<T | undefined | null> | undefined;\n /**\n * The box to observe.\n */\n box?: ResizeObserverBoxOptions;\n /**\n * The callback function to call when the size changes.\n */\n onResize: () => void;\n};\n\n/**\n * A hook that observes the size of an element and calls a callback function when the size changes.\n * @param options - The options for the hook.\n */\nexport function useResizeObserver<T extends Element>(options: useResizeObserverOptionsType<T>) {\n const { ref, box, onResize } = options;\n\n useEffect(() => {\n const element = ref?.current;\n if (!element) {\n return;\n }\n\n if (!hasResizeObserver()) {\n window.addEventListener(\"resize\", onResize, false);\n\n return () => {\n window.removeEventListener(\"resize\", onResize, false);\n };\n } else {\n const resizeObserverInstance = new window.ResizeObserver((entries) => {\n if (!entries.length) {\n return;\n }\n\n onResize();\n });\n\n resizeObserverInstance.observe(element, { box });\n\n return () => {\n if (element) {\n resizeObserverInstance.unobserve(element);\n }\n };\n }\n }, [onResize, ref, box]);\n}\n\n","'use client';\n\nimport { useState, useEffect, useMemo } from 'react';\nimport type { PhotoAttachment } from '../../types/api/photos';\n\nconst CYCLE_INTERVAL_MIN_MS = 6000;\nconst CYCLE_INTERVAL_MAX_MS = 8000;\nexport const CROSSFADE_DURATION_MS = 600;\n\nexport interface CycledImage {\n url: string;\n alt: string;\n}\n\nexport interface UseImageCycleResult {\n list: CycledImage[];\n currentIndex: number;\n nextIndex: number;\n transitioning: boolean;\n}\n\n/** Stable value in [0, 1) derived from seed string (FNV-1a hash). Same seed always produces same value. */\nfunction seedToUnit(seed: string): number {\n let h = 2166136261 >>> 0;\n for (let i = 0; i < seed.length; i++) {\n h ^= seed.charCodeAt(i);\n h = (Math.imul(h, 16777619) >>> 0) >>> 0;\n }\n return (h >>> 0) / 4294967296;\n}\n\n/** Seeded Fisher-Yates shuffle. Same seed produces same order (SSR-safe). */\nfunction shuffleWithSeed<T>(array: T[], seed: string): T[] {\n if (array.length <= 1) return array;\n const arr = [...array];\n let h = 2166136261 >>> 0;\n for (let i = 0; i < seed.length; i++) {\n h ^= seed.charCodeAt(i);\n h = (Math.imul(h, 16777619) >>> 0) >>> 0;\n }\n const next = (step: number) => {\n h = (Math.imul(1664525, (h + step) >>> 0) + 1013904223) >>> 0;\n return (h >>> 0) / 4294967296;\n };\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(next(i) * (i + 1));\n [arr[i], arr[j]] = [arr[j], arr[i]];\n }\n return arr;\n}\n\nfunction photoUrlFromAttachment(pa: PhotoAttachment): string | undefined {\n return pa.photo?.large_url || pa.photo?.medium_url || pa.photo?.thumbnail_url;\n}\n\nfunction photoAltFromAttachment(pa: PhotoAttachment): string {\n return pa.photo?.alt_text || pa.photo?.title || '';\n}\n\n/**\n * Cycles through a list of photo attachments with a seeded shuffle and crossfade transitions.\n *\n * Each card uses a unique seed so intervals are staggered — cards don't all transition in sync.\n * The shuffle is deterministic (SSR-safe) and the timer only activates when there are 2+ images.\n */\nexport function useImageCycle(\n photoAttachments: PhotoAttachment[] | undefined,\n seed: string\n): UseImageCycleResult {\n const list = useMemo<CycledImage[]>(() => {\n const arr = Array.isArray(photoAttachments) && photoAttachments.length > 0 ? photoAttachments : [];\n if (arr.length === 0) return [];\n return shuffleWithSeed(arr, seed)\n .map((pa) => ({ url: photoUrlFromAttachment(pa) ?? '', alt: photoAltFromAttachment(pa) }))\n .filter((x) => x.url);\n }, [photoAttachments, seed]);\n\n const [currentIndex, setCurrentIndex] = useState(0);\n const [transitioning, setTransitioning] = useState(false);\n\n // Per-card random interval (6–8 s) so cards don't all transition in sync\n const intervalMs = useMemo(\n () => CYCLE_INTERVAL_MIN_MS + Math.floor(seedToUnit(seed) * (CYCLE_INTERVAL_MAX_MS - CYCLE_INTERVAL_MIN_MS + 1)),\n [seed]\n );\n\n useEffect(() => {\n if (list.length <= 1) return;\n const id = setInterval(() => setTransitioning(true), intervalMs);\n return () => clearInterval(id);\n }, [list.length, intervalMs]);\n\n useEffect(() => {\n if (!transitioning || list.length <= 1) return;\n const t = setTimeout(() => {\n setCurrentIndex((i) => (i + 1) % list.length);\n setTransitioning(false);\n }, CROSSFADE_DURATION_MS);\n return () => clearTimeout(t);\n }, [transitioning, list.length]);\n\n const nextIndex = list.length > 1 ? (currentIndex + 1) % list.length : 0;\n\n return { list, currentIndex, nextIndex, transitioning };\n}\n"],"mappings":"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","names":["useState","useEffect"]}
@@ -0,0 +1,67 @@
1
+ import { F as FormDefinition } from '../form-C94A_PX_.js';
2
+ import { C as CompanyInformation, P as Package, S as Service } from '../package-DeHKpQp7.js';
3
+ import { W as WebsitePhotos } from '../website-photos-Cl1YqAno.js';
4
+ import '../photos-CmBdWiuZ.js';
5
+
6
+ interface FetchOptions {
7
+ cache?: RequestCache;
8
+ revalidate?: number;
9
+ }
10
+ /**
11
+ * Generic serverApi object for flexible endpoint access
12
+ */
13
+ declare const serverApi: {
14
+ get: <T = unknown>(endpoint: string, options?: FetchOptions) => Promise<T | null>;
15
+ };
16
+ declare function getCompanyInformation(): Promise<CompanyInformation | null>;
17
+ /** Ads config (e.g. Meta Pixel). Returns { meta_pixel_id?: string } or {}. Only present when account has connected Meta Ads. */
18
+ declare function getAdsConfig(): Promise<{
19
+ meta_pixel_id?: string;
20
+ } | null>;
21
+ /** Extract Meta Pixel ID from ads config for use with <MetaPixel pixelId={...} />. Only returns a value when present and valid (numeric). */
22
+ declare function getMetaPixelId(adsConfig: {
23
+ meta_pixel_id?: string;
24
+ } | null | undefined): string | null;
25
+ type AnalyticsConfig = {
26
+ /** PostHog project API key — platform-wide, from POSTHOG_API_KEY env var on the Rails server. */
27
+ posthog_api_key?: string;
28
+ /** Per-site GTM container ID (e.g. GTM-ABC123), provisioned by Keystone. */
29
+ gtm_container_public_id?: string;
30
+ /**
31
+ * Environment identifier from KEYSTONE_ENV on the Rails server (e.g. "production", "staging",
32
+ * "development"). Registered as a PostHog super property on every event so the sync job can
33
+ * filter by environment and avoid cross-environment data contamination.
34
+ */
35
+ environment?: string;
36
+ };
37
+ /** Analytics config for customer sites. Returns platform-wide analytics keys (e.g. PostHog). */
38
+ declare function getAnalyticsConfig(): Promise<AnalyticsConfig | null>;
39
+ /** Extract PostHog API key from analytics config for use with <PostHogProvider apiKey={...} />. */
40
+ declare function getPostHogApiKey(analyticsConfig: AnalyticsConfig | null | undefined): string | null;
41
+ /** Extract GTM container ID from analytics config for use with <GoogleTagManager containerId={...} />. */
42
+ declare function getGtmContainerPublicId(analyticsConfig: AnalyticsConfig | null | undefined): string | null;
43
+ /** Extract environment string from analytics config for use with <PostHogProvider environment={...} />. */
44
+ declare function getKeystoneEnvironment(analyticsConfig: AnalyticsConfig | null | undefined): string;
45
+ declare function getServices(): Promise<Service[] | null>;
46
+ declare function getService(slug: string): Promise<Service | null>;
47
+ declare function getLocations(): Promise<unknown>;
48
+ declare function getLocation(slug: string): Promise<unknown>;
49
+ declare function getReviews(): Promise<unknown>;
50
+ declare function getFAQs(): Promise<unknown>;
51
+ declare function getBlogPosts(): Promise<unknown>;
52
+ declare function getBlogPost(slug: string): Promise<unknown>;
53
+ declare function getTeamMembers(): Promise<unknown>;
54
+ declare function getWebsitePhotos(): Promise<WebsitePhotos | null>;
55
+ declare function getJobPostings(): Promise<unknown>;
56
+ declare function getJobPosting(slug: string): Promise<unknown>;
57
+ declare function getSocialPosts(): Promise<unknown>;
58
+ /** Packages (bundles of service items). */
59
+ declare function getPackages(): Promise<Package[] | null>;
60
+ declare function getPackage(slug: string): Promise<Package | null>;
61
+ declare function getTestimonials(): Promise<unknown>;
62
+ /** Form definition for dynamic form rendering, fetched by form_type. */
63
+ declare function getForm(formType: string): Promise<FormDefinition | null>;
64
+ /** Form definition for dynamic form rendering, fetched by numeric ID. */
65
+ declare function getFormById(id: number | string): Promise<FormDefinition | null>;
66
+
67
+ export { type AnalyticsConfig, getAdsConfig, getAnalyticsConfig, getBlogPost, getBlogPosts, getCompanyInformation, getFAQs, getForm, getFormById, getGtmContainerPublicId, getJobPosting, getJobPostings, getKeystoneEnvironment, getLocation, getLocations, getMetaPixelId, getPackage, getPackages, getPostHogApiKey, getReviews, getService, getServices, getSocialPosts, getTeamMembers, getTestimonials, getWebsitePhotos, serverApi };
@@ -0,0 +1,195 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
17
+
18
+ // src/lib/server-log.ts
19
+ function normalizeError(error) {
20
+ if (error instanceof Error) {
21
+ return {
22
+ message: error.message,
23
+ stack: error.stack || "",
24
+ name: error.name
25
+ };
26
+ }
27
+ return { message: String(error) };
28
+ }
29
+ function writeServerLog(level, event, detail) {
30
+ const payload = {
31
+ level,
32
+ event,
33
+ detail: detail != null ? detail : {},
34
+ at: (/* @__PURE__ */ new Date()).toISOString()
35
+ };
36
+ const line = `[ks-server] ${JSON.stringify(payload)}
37
+ `;
38
+ const stream = level === "ERROR" ? process.stderr : process.stdout;
39
+ stream.write(line);
40
+ }
41
+ function serverError(event, error, detail) {
42
+ writeServerLog("ERROR", event, __spreadValues(__spreadValues({}, detail != null ? detail : {}), error !== void 0 ? { error: normalizeError(error) } : {}));
43
+ }
44
+
45
+ // src/lib/server-api.ts
46
+ var API_URL = process.env.API_URL || "http://localhost:3000/api/v1";
47
+ var API_KEY = process.env.API_KEY || "";
48
+ async function serverFetch(endpoint, options = {}) {
49
+ var _a;
50
+ const url = `${API_URL}${endpoint}`;
51
+ try {
52
+ const fetchOptions = {
53
+ headers: {
54
+ "X-API-Key": API_KEY,
55
+ "Content-Type": "application/json"
56
+ },
57
+ cache: options.cache
58
+ };
59
+ if (options.revalidate) {
60
+ fetchOptions.next = { revalidate: options.revalidate };
61
+ }
62
+ const response = await fetch(url, fetchOptions);
63
+ if (!response.ok) {
64
+ serverError("SERVER_API_NON_OK_RESPONSE", void 0, {
65
+ endpoint,
66
+ status: response.status
67
+ });
68
+ return null;
69
+ }
70
+ const json = await response.json();
71
+ return (_a = json.data) != null ? _a : json;
72
+ } catch (error) {
73
+ serverError("SERVER_API_FETCH_FAILED", error, { endpoint });
74
+ return null;
75
+ }
76
+ }
77
+ var serverApi = {
78
+ get: (endpoint, options) => {
79
+ return serverFetch(endpoint, options || { revalidate: 60 });
80
+ }
81
+ };
82
+ var defaultOptions = { revalidate: 60 };
83
+ async function getCompanyInformation() {
84
+ return serverFetch("/public/company_information", defaultOptions);
85
+ }
86
+ async function getAdsConfig() {
87
+ const data = await serverFetch("/public/ads_config", defaultOptions);
88
+ return data != null ? data : null;
89
+ }
90
+ function getMetaPixelId(adsConfig) {
91
+ const id = adsConfig && typeof adsConfig === "object" && "meta_pixel_id" in adsConfig && adsConfig.meta_pixel_id;
92
+ const str = id != null && id !== "" ? String(id).trim() : "";
93
+ return str !== "" && str !== "null" && /^\d+$/.test(str) ? str : null;
94
+ }
95
+ async function getAnalyticsConfig() {
96
+ const data = await serverFetch("/public/analytics_config", defaultOptions);
97
+ return data != null ? data : null;
98
+ }
99
+ function getPostHogApiKey(analyticsConfig) {
100
+ const key = analyticsConfig == null ? void 0 : analyticsConfig.posthog_api_key;
101
+ const str = key != null && key !== "" ? String(key).trim() : "";
102
+ return str !== "" && str !== "null" ? str : null;
103
+ }
104
+ function getGtmContainerPublicId(analyticsConfig) {
105
+ const value = analyticsConfig == null ? void 0 : analyticsConfig.gtm_container_public_id;
106
+ const str = value != null && value !== "" ? String(value).trim() : "";
107
+ return str !== "" && str !== "null" && /^GTM-[A-Z0-9]+$/i.test(str) ? str : null;
108
+ }
109
+ function getKeystoneEnvironment(analyticsConfig) {
110
+ var _a;
111
+ return ((_a = analyticsConfig == null ? void 0 : analyticsConfig.environment) == null ? void 0 : _a.trim()) || "development";
112
+ }
113
+ async function getServices() {
114
+ return serverFetch("/public/services", defaultOptions);
115
+ }
116
+ async function getService(slug) {
117
+ return serverFetch(`/public/services/by_slug/${slug}`, defaultOptions);
118
+ }
119
+ async function getLocations() {
120
+ return serverFetch("/public/locations", defaultOptions);
121
+ }
122
+ async function getLocation(slug) {
123
+ return serverFetch(`/public/locations/by_slug/${slug}`, defaultOptions);
124
+ }
125
+ async function getReviews() {
126
+ return serverFetch("/public/reviews", defaultOptions);
127
+ }
128
+ async function getFAQs() {
129
+ return serverFetch("/public/faq_questions", defaultOptions);
130
+ }
131
+ async function getBlogPosts() {
132
+ return serverFetch("/public/blog_posts", defaultOptions);
133
+ }
134
+ async function getBlogPost(slug) {
135
+ return serverFetch(`/public/blog_posts/by_slug/${slug}`, defaultOptions);
136
+ }
137
+ async function getTeamMembers() {
138
+ return serverFetch("/public/team_members", defaultOptions);
139
+ }
140
+ async function getWebsitePhotos() {
141
+ return serverFetch("/public/website_photos", defaultOptions);
142
+ }
143
+ async function getJobPostings() {
144
+ return serverFetch("/public/job_postings", defaultOptions);
145
+ }
146
+ async function getJobPosting(slug) {
147
+ return serverFetch(`/public/job_postings/by_slug/${slug}`, defaultOptions);
148
+ }
149
+ async function getSocialPosts() {
150
+ return serverFetch("/public/social_posts", defaultOptions);
151
+ }
152
+ async function getPackages() {
153
+ return serverFetch("/public/packages", defaultOptions);
154
+ }
155
+ async function getPackage(slug) {
156
+ return serverFetch(`/public/packages/by_slug/${encodeURIComponent(slug)}`, defaultOptions);
157
+ }
158
+ async function getTestimonials() {
159
+ return getReviews();
160
+ }
161
+ async function getForm(formType) {
162
+ return serverFetch(`/public/forms/${encodeURIComponent(formType)}`, defaultOptions);
163
+ }
164
+ async function getFormById(id) {
165
+ return serverFetch(`/public/form_by_id/${encodeURIComponent(id)}`, defaultOptions);
166
+ }
167
+ export {
168
+ getAdsConfig,
169
+ getAnalyticsConfig,
170
+ getBlogPost,
171
+ getBlogPosts,
172
+ getCompanyInformation,
173
+ getFAQs,
174
+ getForm,
175
+ getFormById,
176
+ getGtmContainerPublicId,
177
+ getJobPosting,
178
+ getJobPostings,
179
+ getKeystoneEnvironment,
180
+ getLocation,
181
+ getLocations,
182
+ getMetaPixelId,
183
+ getPackage,
184
+ getPackages,
185
+ getPostHogApiKey,
186
+ getReviews,
187
+ getService,
188
+ getServices,
189
+ getSocialPosts,
190
+ getTeamMembers,
191
+ getTestimonials,
192
+ getWebsitePhotos,
193
+ serverApi
194
+ };
195
+ //# sourceMappingURL=server-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/server-log.ts","../../src/lib/server-api.ts"],"sourcesContent":["type ServerLogLevel = 'INFO' | 'WARN' | 'ERROR';\n\nfunction normalizeError(error: unknown): Record<string, string> {\n if (error instanceof Error) {\n return {\n message: error.message,\n stack: error.stack || '',\n name: error.name,\n };\n }\n return { message: String(error) };\n}\n\nfunction writeServerLog(level: ServerLogLevel, event: string, detail?: Record<string, unknown>): void {\n const payload = {\n level,\n event,\n detail: detail ?? {},\n at: new Date().toISOString(),\n };\n const line = `[ks-server] ${JSON.stringify(payload)}\\n`;\n const stream = level === 'ERROR' ? process.stderr : process.stdout;\n stream.write(line);\n}\n\nexport function serverLog(event: string, detail?: Record<string, unknown>): void {\n writeServerLog('INFO', event, detail);\n}\n\nexport function serverWarn(event: string, detail?: Record<string, unknown>): void {\n writeServerLog('WARN', event, detail);\n}\n\nexport function serverError(event: string, error?: unknown, detail?: Record<string, unknown>): void {\n writeServerLog('ERROR', event, {\n ...(detail ?? {}),\n ...(error !== undefined ? { error: normalizeError(error) } : {}),\n });\n}\n","/**\n * Server-side API client for SSR\n * API key is stored securely on the server - never exposed to browser\n */\n\nimport type { CompanyInformation } from '../types/api/company-information';\nimport type { Service } from '../types/api/service';\nimport type { Package } from '../types/api/package';\nimport type { WebsitePhotos } from '../types/api/website-photos';\nimport { serverError } from './server-log';\n\nconst API_URL = process.env.API_URL || 'http://localhost:3000/api/v1';\nconst API_KEY = process.env.API_KEY || '';\n\ninterface FetchOptions {\n cache?: RequestCache;\n revalidate?: number;\n}\n\nasync function serverFetch<T>(endpoint: string, options: FetchOptions = {}): Promise<T | null> {\n const url = `${API_URL}${endpoint}`;\n \n try {\n const fetchOptions: RequestInit & { next?: { revalidate?: number } } = {\n headers: {\n 'X-API-Key': API_KEY,\n 'Content-Type': 'application/json',\n },\n cache: options.cache,\n };\n \n if (options.revalidate) {\n fetchOptions.next = { revalidate: options.revalidate };\n }\n \n const response = await fetch(url, fetchOptions);\n\n if (!response.ok) {\n serverError('SERVER_API_NON_OK_RESPONSE', undefined, {\n endpoint,\n status: response.status,\n });\n return null;\n }\n\n const json = await response.json();\n \n // Rails API returns { data: [...], meta: {...} }\n return json.data ?? json;\n } catch (error) {\n serverError('SERVER_API_FETCH_FAILED', error, { endpoint });\n return null;\n }\n}\n\n/**\n * Generic serverApi object for flexible endpoint access\n */\nexport const serverApi = {\n get: <T = unknown>(endpoint: string, options?: FetchOptions): Promise<T | null> => {\n return serverFetch<T>(endpoint, options || { revalidate: 60 });\n }\n};\n\n// Revalidate data every 60 seconds (ISR)\nconst defaultOptions: FetchOptions = { revalidate: 60 };\n\nexport async function getCompanyInformation(): Promise<CompanyInformation | null> {\n return serverFetch<CompanyInformation>('/public/company_information', defaultOptions);\n}\n\n/** Ads config (e.g. Meta Pixel). Returns { meta_pixel_id?: string } or {}. Only present when account has connected Meta Ads. */\nexport async function getAdsConfig(): Promise<{ meta_pixel_id?: string } | null> {\n const data = await serverFetch<{ meta_pixel_id?: string }>('/public/ads_config', defaultOptions);\n return data ?? null;\n}\n\n/** Extract Meta Pixel ID from ads config for use with <MetaPixel pixelId={...} />. Only returns a value when present and valid (numeric). */\nexport function getMetaPixelId(adsConfig: { meta_pixel_id?: string } | null | undefined): string | null {\n const id = adsConfig && typeof adsConfig === 'object' && 'meta_pixel_id' in adsConfig && adsConfig.meta_pixel_id;\n const str = id != null && id !== '' ? String(id).trim() : '';\n return str !== '' && str !== 'null' && /^\\d+$/.test(str) ? str : null;\n}\n\nexport type AnalyticsConfig = {\n /** PostHog project API key — platform-wide, from POSTHOG_API_KEY env var on the Rails server. */\n posthog_api_key?: string;\n /** Per-site GTM container ID (e.g. GTM-ABC123), provisioned by Keystone. */\n gtm_container_public_id?: string;\n /**\n * Environment identifier from KEYSTONE_ENV on the Rails server (e.g. \"production\", \"staging\",\n * \"development\"). Registered as a PostHog super property on every event so the sync job can\n * filter by environment and avoid cross-environment data contamination.\n */\n environment?: string;\n};\n\n/** Analytics config for customer sites. Returns platform-wide analytics keys (e.g. PostHog). */\nexport async function getAnalyticsConfig(): Promise<AnalyticsConfig | null> {\n const data = await serverFetch<AnalyticsConfig>('/public/analytics_config', defaultOptions);\n return data ?? null;\n}\n\n/** Extract PostHog API key from analytics config for use with <PostHogProvider apiKey={...} />. */\nexport function getPostHogApiKey(analyticsConfig: AnalyticsConfig | null | undefined): string | null {\n const key = analyticsConfig?.posthog_api_key;\n const str = key != null && key !== '' ? String(key).trim() : '';\n return str !== '' && str !== 'null' ? str : null;\n}\n\n/** Extract GTM container ID from analytics config for use with <GoogleTagManager containerId={...} />. */\nexport function getGtmContainerPublicId(analyticsConfig: AnalyticsConfig | null | undefined): string | null {\n const value = analyticsConfig?.gtm_container_public_id;\n const str = value != null && value !== '' ? String(value).trim() : '';\n return str !== '' && str !== 'null' && /^GTM-[A-Z0-9]+$/i.test(str) ? str : null;\n}\n\n/** Extract environment string from analytics config for use with <PostHogProvider environment={...} />. */\nexport function getKeystoneEnvironment(analyticsConfig: AnalyticsConfig | null | undefined): string {\n return analyticsConfig?.environment?.trim() || 'development';\n}\n\nexport async function getServices(): Promise<Service[] | null> {\n return serverFetch<Service[]>('/public/services', defaultOptions);\n}\n\nexport async function getService(slug: string): Promise<Service | null> {\n return serverFetch<Service>(`/public/services/by_slug/${slug}`, defaultOptions);\n}\n\nexport async function getLocations() {\n return serverFetch('/public/locations', defaultOptions);\n}\n\nexport async function getLocation(slug: string) {\n return serverFetch(`/public/locations/by_slug/${slug}`, defaultOptions);\n}\n\nexport async function getReviews() {\n return serverFetch('/public/reviews', defaultOptions);\n}\n\nexport async function getFAQs() {\n return serverFetch('/public/faq_questions', defaultOptions);\n}\n\nexport async function getBlogPosts() {\n return serverFetch('/public/blog_posts', defaultOptions);\n}\n\nexport async function getBlogPost(slug: string) {\n return serverFetch(`/public/blog_posts/by_slug/${slug}`, defaultOptions);\n}\n\nexport async function getTeamMembers() {\n return serverFetch('/public/team_members', defaultOptions);\n}\n\nexport async function getWebsitePhotos(): Promise<WebsitePhotos | null> {\n return serverFetch<WebsitePhotos>('/public/website_photos', defaultOptions);\n}\n\nexport async function getJobPostings() {\n return serverFetch('/public/job_postings', defaultOptions);\n}\n\nexport async function getJobPosting(slug: string) {\n return serverFetch(`/public/job_postings/by_slug/${slug}`, defaultOptions);\n}\n\nexport async function getSocialPosts() {\n return serverFetch('/public/social_posts', defaultOptions);\n}\n\n/** Packages (bundles of service items). */\nexport async function getPackages(): Promise<Package[] | null> {\n return serverFetch<Package[]>('/public/packages', defaultOptions);\n}\n\nexport async function getPackage(slug: string): Promise<Package | null> {\n return serverFetch<Package>(`/public/packages/by_slug/${encodeURIComponent(slug)}`, defaultOptions);\n}\n\n// Alias for testimonials (API uses \"reviews\")\nexport async function getTestimonials() {\n return getReviews();\n}\n\n/** Form definition for dynamic form rendering, fetched by form_type. */\nexport async function getForm(formType: string) {\n type FormDefinition = import('../types/api/form').FormDefinition;\n return serverFetch<FormDefinition>(`/public/forms/${encodeURIComponent(formType)}`, defaultOptions);\n}\n\n/** Form definition for dynamic form rendering, fetched by numeric ID. */\nexport async function getFormById(id: number | string) {\n type FormDefinition = import('../types/api/form').FormDefinition;\n return serverFetch<FormDefinition>(`/public/form_by_id/${encodeURIComponent(id)}`, defaultOptions);\n}\n\n"],"mappings":"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","names":[]}