@frontlit/page-builder 0.1.0-alpha.1 → 0.1.0-alpha.3

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 (116) hide show
  1. package/README.md +38 -2
  2. package/dist/blocks/index.d.ts +35 -21
  3. package/dist/blocks/index.js +4 -3
  4. package/dist/builder/index.d.ts +8 -11
  5. package/dist/builder/index.js +7 -5
  6. package/dist/{button-C3VrMJkr.d.ts → button-OLKN3wPf.d.ts} +3 -3
  7. package/dist/{chunk-7W6Q4WA7.js → chunk-36QHCIPM.js} +252 -282
  8. package/dist/chunk-36QHCIPM.js.map +1 -0
  9. package/dist/{chunk-GTY75HW2.js → chunk-ATNM5JHI.js} +2 -2
  10. package/dist/chunk-ATNM5JHI.js.map +1 -0
  11. package/dist/chunk-BBI7UHOA.js +56 -0
  12. package/dist/chunk-BBI7UHOA.js.map +1 -0
  13. package/dist/{chunk-MO76KZY2.js → chunk-GIKTOAQS.js} +661 -423
  14. package/dist/chunk-GIKTOAQS.js.map +1 -0
  15. package/dist/chunk-HWOVSHIW.js +4 -0
  16. package/dist/{chunk-RUYOL7JZ.js.map → chunk-HWOVSHIW.js.map} +1 -1
  17. package/dist/{chunk-SSQWYMQR.js → chunk-IU4KATI2.js} +74 -129
  18. package/dist/chunk-IU4KATI2.js.map +1 -0
  19. package/dist/chunk-J7Z4UW45.js +3418 -0
  20. package/dist/chunk-J7Z4UW45.js.map +1 -0
  21. package/dist/{chunk-7KIX4WAO.js → chunk-K4WCLHMS.js} +1435 -1382
  22. package/dist/chunk-K4WCLHMS.js.map +1 -0
  23. package/dist/chunk-XU7ALPGQ.js +22 -0
  24. package/dist/chunk-XU7ALPGQ.js.map +1 -0
  25. package/dist/components/index.d.ts +33 -8
  26. package/dist/components/index.js +43 -2
  27. package/dist/components/index.js.map +1 -1
  28. package/dist/data-slots.d.ts +3 -3
  29. package/dist/data-slots.js +1 -1
  30. package/dist/fonts/archivo/archivo-latin-variable.woff2 +0 -0
  31. package/dist/fonts/hanken-grotesk/hanken-grotesk-latin-variable.woff2 +0 -0
  32. package/dist/fonts/jetbrains-mono/jetbrains-mono-latin-variable.woff2 +0 -0
  33. package/dist/fonts/spline-sans-mono/spline-sans-mono-latin-variable.woff2 +0 -0
  34. package/dist/heading-DQCxQt38.d.ts +20 -0
  35. package/dist/image-Bk8kPXwP.d.ts +52 -0
  36. package/dist/index.d.ts +18 -11
  37. package/dist/index.js +9 -7
  38. package/dist/media-CnLImjVC.d.ts +21 -0
  39. package/dist/models/index.d.ts +6 -6
  40. package/dist/models/index.js +2 -1
  41. package/dist/{page-DfVl_aHX.d.ts → page-B5Gmm-Ys.d.ts} +2 -3
  42. package/dist/primitives/index.d.ts +287 -61
  43. package/dist/primitives/index.js +2 -1
  44. package/dist/renderer/index.d.ts +12 -8
  45. package/dist/renderer/index.js +6 -4
  46. package/dist/{section-background-BgOCp5l3.d.ts → section-background-6V_LCi5e.d.ts} +1 -1
  47. package/dist/server/index.d.ts +103 -0
  48. package/dist/server/index.js +2649 -0
  49. package/dist/server/index.js.map +1 -0
  50. package/dist/server/islands/code-snippet.d.ts +17 -0
  51. package/dist/server/islands/code-snippet.js +72 -0
  52. package/dist/server/islands/code-snippet.js.map +1 -0
  53. package/dist/server/islands/email-form.d.ts +50 -0
  54. package/dist/server/islands/email-form.js +406 -0
  55. package/dist/server/islands/email-form.js.map +1 -0
  56. package/dist/server/islands/header-mobile-menu.d.ts +22 -0
  57. package/dist/server/islands/header-mobile-menu.js +422 -0
  58. package/dist/server/islands/header-mobile-menu.js.map +1 -0
  59. package/dist/server/islands/inline-embed.d.ts +28 -0
  60. package/dist/server/islands/inline-embed.js +84 -0
  61. package/dist/server/islands/inline-embed.js.map +1 -0
  62. package/dist/server/islands/marquee-track.d.ts +18 -0
  63. package/dist/server/islands/marquee-track.js +67 -0
  64. package/dist/server/islands/marquee-track.js.map +1 -0
  65. package/dist/server/islands/nav-link.d.ts +37 -0
  66. package/dist/server/islands/nav-link.js +67 -0
  67. package/dist/server/islands/nav-link.js.map +1 -0
  68. package/dist/server/islands/support-form.d.ts +17 -0
  69. package/dist/server/islands/support-form.js +469 -0
  70. package/dist/server/islands/support-form.js.map +1 -0
  71. package/dist/server/islands/theme-mode-switcher.d.ts +38 -0
  72. package/dist/server/islands/theme-mode-switcher.js +169 -0
  73. package/dist/server/islands/theme-mode-switcher.js.map +1 -0
  74. package/dist/server/islands/video-with-preview.d.ts +13 -0
  75. package/dist/server/islands/video-with-preview.js +269 -0
  76. package/dist/server/islands/video-with-preview.js.map +1 -0
  77. package/dist/server/primitives.d.ts +200 -0
  78. package/dist/server/primitives.js +888 -0
  79. package/dist/server/primitives.js.map +1 -0
  80. package/dist/server/schema.d.ts +11308 -0
  81. package/dist/server/schema.js +6179 -0
  82. package/dist/server/schema.js.map +1 -0
  83. package/dist/server/theme-CF3fIKvw.d.ts +9 -0
  84. package/dist/{theme-style-39nxXQy0.d.ts → server/theme-style-5_AGy-4V.d.ts} +43 -1
  85. package/dist/server/theme-style-BPaMwk88.d.ts +183 -0
  86. package/dist/server/theme.d.ts +28 -0
  87. package/dist/{chunk-RBJBODQP.js → server/theme.js} +903 -1243
  88. package/dist/server/theme.js.map +1 -0
  89. package/dist/server/widget-YH9BrOwY.d.ts +77 -0
  90. package/dist/styles.css +4016 -8317
  91. package/dist/theme-style-BVd5tOsc.d.ts +183 -0
  92. package/dist/theme.css +186 -191
  93. package/dist/{widget--uEOpdq3.d.ts → widget-CpE2HdgU.d.ts} +25 -18
  94. package/package.json +71 -11
  95. package/schema/page.v1.json +3855 -0
  96. package/schema/site.v1.json +3811 -0
  97. package/dist/alignment-dZDzN96G.d.ts +0 -5
  98. package/dist/chunk-7KIX4WAO.js.map +0 -1
  99. package/dist/chunk-7W6Q4WA7.js.map +0 -1
  100. package/dist/chunk-GTY75HW2.js.map +0 -1
  101. package/dist/chunk-MO76KZY2.js.map +0 -1
  102. package/dist/chunk-RBJBODQP.js.map +0 -1
  103. package/dist/chunk-RUYOL7JZ.js +0 -4
  104. package/dist/chunk-SSQWYMQR.js.map +0 -1
  105. package/dist/fonts/montserrat/montserrat-latin-100-normal.woff2 +0 -0
  106. package/dist/fonts/montserrat/montserrat-latin-200-normal.woff2 +0 -0
  107. package/dist/fonts/montserrat/montserrat-latin-300-normal.woff2 +0 -0
  108. package/dist/fonts/montserrat/montserrat-latin-400-normal.woff2 +0 -0
  109. package/dist/fonts/montserrat/montserrat-latin-500-normal.woff2 +0 -0
  110. package/dist/fonts/montserrat/montserrat-latin-600-normal.woff2 +0 -0
  111. package/dist/fonts/montserrat/montserrat-latin-700-normal.woff2 +0 -0
  112. package/dist/fonts/montserrat/montserrat-latin-800-normal.woff2 +0 -0
  113. package/dist/fonts/montserrat/montserrat-latin-900-normal.woff2 +0 -0
  114. package/dist/fonts/source-sans-3/source-sans-3-latin-400-normal.woff2 +0 -0
  115. package/dist/image-D7GlnOio.d.ts +0 -30
  116. package/dist/media-Njv378j7.d.ts +0 -13
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/blocks/support-form/form.tsx","../../../src/primitives/lib/utils.ts","../../../src/primitives/components/ui/button.tsx","../../../src/primitives/button.tsx","../../../src/primitives/input.tsx","../../../src/primitives/components/ui/input.tsx","../../../src/primitives/label.tsx","../../../src/primitives/components/ui/label.tsx","../../../src/primitives/text2.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\nimport type { PageData, ThemeStyle } from \"../../models\";\nimport { Button, Input, Label, Text2 } from \"../../primitives\";\nimport { cn } from \"../../primitives/lib/utils\";\n\ninterface DocumentationSuggestion {\n documentId: string;\n slug: string;\n title: string | null;\n kind?: \"blog\" | \"doc\" | \"changelog\";\n}\n\nfunction suggestionHref(item: DocumentationSuggestion) {\n if (item.kind === \"blog\") return `/blog/${item.slug}`;\n if (item.kind === \"changelog\") return `/changelog`;\n return `/docs/${item.slug}`;\n}\n\n/** Documentation deflection (architecture.md §6.4/§11.2): searches the\n * team's published docs as the visitor types a subject, before they ever\n * submit a ticket. No-ops in isolated previews with no live team/API. */\nasync function searchDocumentation(\n query: string,\n pageData: PageData,\n): Promise<DocumentationSuggestion[]> {\n if (!pageData.teamId || !pageData.publicApiBaseUrl || query.trim().length < 3)\n return [];\n\n try {\n const res = await fetch(\n `${pageData.publicApiBaseUrl}/public/${pageData.teamId}/content/documentation/search?q=${encodeURIComponent(query)}&k=3`,\n );\n if (!res.ok) return [];\n const body = (await res.json()) as {\n items: DocumentationSuggestion[];\n };\n const seen = new Set<string>();\n return body.items.filter((item) =>\n seen.has(item.documentId) ? false : (seen.add(item.documentId), true),\n );\n } catch {\n return [];\n }\n}\n\n/** Raises a support ticket via FrontLit's public, unauthenticated ticket\n * endpoint (architecture.md §11.2) — one of the three submission doors,\n * source: \"form\". Falls back to a local-only success in isolated previews. */\nasync function submitTicket(\n input: { subject: string; message: string; email: string; name?: string },\n pageData: PageData,\n): Promise<{ success: boolean }> {\n if (!pageData.teamId || !pageData.publicApiBaseUrl) {\n await new Promise((resolve) => setTimeout(resolve, 600));\n return { success: true };\n }\n\n try {\n const res = await fetch(\n `${pageData.publicApiBaseUrl}/public/${pageData.teamId}/tickets`,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ ...input, source: \"form\" }),\n },\n );\n return { success: res.ok };\n } catch {\n return { success: false };\n }\n}\n\n/**\n * The interactive part of the support-form block: the ticket form and its\n * success message. The block's heading stays server-rendered around it.\n */\nexport function SupportForm({\n theme: overriddenTheme,\n buttonCaption,\n successMessage,\n pageData,\n editing,\n}: {\n theme: ThemeStyle;\n buttonCaption: string;\n successMessage: string;\n pageData: PageData;\n editing?: boolean;\n}) {\n const [subject, setSubject] = useState(\"\");\n const [message, setMessage] = useState(\"\");\n const [email, setEmail] = useState(\"\");\n const [name, setName] = useState(\"\");\n const [suggestions, setSuggestions] = useState<DocumentationSuggestion[]>([]);\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [success, setSuccess] = useState(false);\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (editing) return;\n if (debounceRef.current) clearTimeout(debounceRef.current);\n debounceRef.current = setTimeout(() => {\n searchDocumentation(subject, pageData).then(setSuggestions);\n }, 400);\n return () => {\n if (debounceRef.current) clearTimeout(debounceRef.current);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [subject]);\n\n const onSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (editing) return;\n\n setIsSubmitting(true);\n try {\n const result = await submitTicket(\n { subject, message, email, name: name || undefined },\n pageData,\n );\n if (result.success) {\n setSuccess(true);\n setSubject(\"\");\n setMessage(\"\");\n setEmail(\"\");\n setName(\"\");\n setSuggestions([]);\n }\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const inputStyles = overriddenTheme.interactives.input;\n const inputTypography = overriddenTheme.typography.input;\n const cardStyles = overriddenTheme.interactives.card;\n\n return success ? (\n <Text2 theme={overriddenTheme} className=\"mt-4\">\n {successMessage}\n </Text2>\n ) : (\n <form onSubmit={onSubmit} className=\"mt-4 flex w-full max-w-lg flex-col gap-3\">\n <div className=\"flex flex-col gap-1 items-start\">\n <Label theme={overriddenTheme} htmlFor=\"subject\">\n Subject\n </Label>\n <Input\n theme={overriddenTheme}\n id=\"subject\"\n value={subject}\n onChange={(e) => setSubject(e.target.value)}\n placeholder=\"What do you need help with?\"\n required\n className=\"w-full\"\n />\n </div>\n\n {suggestions.length > 0 && (\n <div\n className={cn(\n \"rounded-lg border border-border bg-card p-3 text-sm text-card-foreground shadow-sm\",\n cardStyles.border?.width,\n cardStyles.border?.radius,\n cardStyles.border?.style,\n cardStyles.shadow,\n )}\n >\n <p className=\"mb-1 font-medium\">This might help:</p>\n <ul className=\"list-inside list-disc\">\n {suggestions.map((doc) => (\n <li key={doc.documentId}>\n <a\n href={suggestionHref(doc)}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"underline\"\n >\n {doc.title ?? doc.slug}\n </a>\n </li>\n ))}\n </ul>\n </div>\n )}\n\n <div className=\"flex flex-col gap-1 items-start\">\n <Label theme={overriddenTheme} htmlFor=\"message\">\n Message\n </Label>\n <textarea\n id=\"message\"\n value={message}\n onChange={(e) => setMessage(e.target.value)}\n placeholder=\"Describe the issue…\"\n required\n rows={4}\n className={cn(\n \"w-full border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n inputTypography.fontFamily,\n inputTypography.fontSize,\n inputTypography.fontWeight,\n inputTypography.lineHeight,\n inputTypography.letterSpacing,\n inputStyles.border?.width,\n inputStyles.border?.style,\n inputStyles.border?.radius,\n inputStyles.shadow,\n inputStyles.custom,\n )}\n />\n </div>\n\n <div className=\"flex flex-col gap-3 sm:!flex-row\">\n <div className=\"flex flex-1 flex-col gap-1 items-start\">\n <Label theme={overriddenTheme} htmlFor=\"email\">\n Email\n </Label>\n <Input\n theme={overriddenTheme}\n id=\"email\"\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder=\"you@example.com\"\n required\n className=\"w-full\"\n />\n </div>\n <div className=\"flex flex-1 flex-col gap-1 items-start\">\n <Label theme={overriddenTheme} htmlFor=\"name\">\n Name (optional)\n </Label>\n <Input\n theme={overriddenTheme}\n id=\"name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"Your name\"\n className=\"w-full\"\n />\n </div>\n </div>\n\n <Button\n theme={overriddenTheme}\n type=\"submit\"\n disabled={!editing && (isSubmitting || !subject || !message || !email)}\n className=\"self-start\"\n >\n {buttonCaption}\n </Button>\n </form>\n );\n}\n","import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n outline:\n \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-10 px-4 py-2\",\n sm: \"h-9 rounded-md px-3\",\n lg: \"h-11 rounded-md px-8\",\n icon: \"h-10 w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n {...props}\n />\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n","import type React from \"react\";\nimport type { ThemeStyle } from \"../models\";\nimport {\n Button as ShadcnButton,\n type ButtonProps as ShadcnButtonProps,\n} from \"./components/ui/button\";\nimport { cn } from \"./lib/utils\";\n\nexport interface ButtonProps extends ShadcnButtonProps {\n theme?: ThemeStyle;\n}\n\nexport const Button: React.FC<ButtonProps> = ({\n variant = \"default\",\n size = \"default\",\n disabled = false,\n children,\n className = \"\",\n theme,\n ...props\n}) => {\n const buttonStyles = theme?.interactives?.button;\n const typographyStyles = theme?.typography?.button;\n const isIcon = size === \"icon\";\n\n const classes = cn(\n // Base styles\n \"\",\n // Theme typography\n typographyStyles?.fontFamily,\n typographyStyles?.fontSize,\n typographyStyles?.fontWeight,\n typographyStyles?.lineHeight,\n typographyStyles?.letterSpacing,\n typographyStyles?.textTransform,\n typographyStyles?.textDecoration,\n typographyStyles?.textOverflow,\n // Theme interactivity. Icon buttons stay square — CTA padding\n // would stretch them off the size=\"icon\" box.\n !isIcon && buttonStyles?.padding?.x,\n !isIcon && buttonStyles?.padding?.y,\n buttonStyles?.border?.width,\n buttonStyles?.border?.radius,\n buttonStyles?.border?.style,\n // buttonStyles?.border?.color,\n buttonStyles?.shadow,\n // Theme disabled states\n disabled && buttonStyles?.disabled?.opacity,\n disabled && buttonStyles?.disabled?.cursor,\n disabled && buttonStyles?.disabled?.color,\n disabled && buttonStyles?.disabled?.background,\n disabled && buttonStyles?.disabled?.border,\n // User overrides\n buttonStyles?.custom,\n className,\n );\n\n return (\n <ShadcnButton\n variant={variant}\n size={size}\n disabled={disabled}\n className={classes}\n {...props}\n >\n {children}\n </ShadcnButton>\n );\n};\n","import React from \"react\";\nimport type { ThemeStyle } from \"../models\";\nimport { Input as ShadcnInput } from \"./components/ui/input\";\nimport { cn } from \"./lib/utils\";\n\nexport interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {\n theme?: ThemeStyle;\n error?: boolean;\n // style?: React.CSSProperties;\n}\n\nexport const Input = React.forwardRef<HTMLInputElement, InputProps>(\n ({ className = \"\", theme, error = false, /*style = {},*/ ...props }, ref) => {\n const inputStyles = theme?.interactives?.input;\n const typographyStyles = theme?.typography?.input;\n\n const classes = cn(\n // Base styles\n // \"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n // Theme typography\n typographyStyles?.fontFamily,\n typographyStyles?.fontSize,\n typographyStyles?.fontWeight,\n typographyStyles?.lineHeight,\n typographyStyles?.letterSpacing,\n typographyStyles?.textTransform,\n typographyStyles?.textDecoration,\n typographyStyles?.textOverflow,\n // Theme interactivity\n inputStyles?.padding?.x,\n inputStyles?.padding?.y,\n inputStyles?.border?.width,\n inputStyles?.border?.style,\n inputStyles?.border?.radius,\n // inputStyles?.border?.color,\n inputStyles?.shadow,\n // Theme disabled states\n props.disabled && inputStyles?.disabled?.opacity,\n props.disabled && inputStyles?.disabled?.cursor,\n props.disabled && inputStyles?.disabled?.color,\n props.disabled && inputStyles?.disabled?.background,\n props.disabled && inputStyles?.disabled?.border,\n // Error state\n error && \"border-red-300 focus:border-red-500 focus:ring-red-500\",\n // User overrides\n inputStyles?.custom,\n className,\n );\n\n return <ShadcnInput ref={ref} className={classes} {...props} />;\n },\n);\n\nInput.displayName = \"Input\";\n","import * as React from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<\"input\">>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n className,\n )}\n ref={ref}\n {...props}\n />\n );\n },\n);\nInput.displayName = \"Input\";\n\nexport { Input };\n","import React from \"react\";\nimport type { ThemeStyle } from \"../models\";\nimport { Label as ShadcnLabel } from \"./components/ui/label\";\nimport { cn } from \"./lib/utils\";\n\nexport interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {\n theme?: ThemeStyle;\n error?: boolean;\n}\n\nexport const Label = React.forwardRef<HTMLLabelElement, LabelProps>(\n ({ className = \"\", theme, error = false, ...props }, ref) => {\n const typographyStyles = theme?.typography?.text2;\n\n const classes = cn(\n // Theme typography\n typographyStyles?.fontFamily,\n typographyStyles?.fontSize,\n typographyStyles?.fontWeight,\n typographyStyles?.lineHeight,\n typographyStyles?.letterSpacing,\n typographyStyles?.textTransform,\n typographyStyles?.textDecoration,\n typographyStyles?.textOverflow,\n // Error state\n error && \"text-red-500\",\n className,\n );\n\n return <ShadcnLabel ref={ref} className={classes} {...props} />;\n },\n);\n\nLabel.displayName = \"Label\";\n","import * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\nconst labelVariants = cva(\n \"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\",\n);\n\nconst Label = React.forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &\n VariantProps<typeof labelVariants>\n>(({ className, ...props }, ref) => (\n <LabelPrimitive.Root\n ref={ref}\n className={cn(labelVariants(), className)}\n {...props}\n />\n));\nLabel.displayName = LabelPrimitive.Root.displayName;\n\nexport { Label };\n","import type React from \"react\";\nimport { cn } from \"./lib/utils\";\nimport type { TypographyProps } from \"./typography\";\n\nexport const Text2: React.FC<TypographyProps & { component?: \"p\" | \"span\" }> = ({\n children,\n className = \"\",\n theme,\n component = \"p\",\n ...props\n}) => {\n const typographyStyles = theme?.typography?.text2;\n const classes = cn(\n // Base styles\n \"text-sm\",\n // Theme overrides\n typographyStyles?.fontFamily,\n typographyStyles?.fontSize,\n typographyStyles?.fontWeight,\n typographyStyles?.lineHeight,\n typographyStyles?.letterSpacing,\n typographyStyles?.textTransform,\n typographyStyles?.textDecoration,\n typographyStyles?.textOverflow,\n className,\n );\n\n return component === \"p\" ? (\n <p className={classes} {...props}>\n {children}\n </p>\n ) : (\n <span className={classes} {...props}>\n {children}\n </span>\n );\n};\n"],"mappings":"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","names":["jsx","Button","React","React","jsx","jsx","Input","React","React","cva","React","jsx","cva","jsx","Label","React","jsx","jsx","Label","Input","Button"]}
@@ -0,0 +1,38 @@
1
+ import * as React from 'react';
2
+ import React__default from 'react';
3
+ import { T as ThemeStyle } from '../theme-style-5_AGy-4V.js';
4
+ import * as class_variance_authority_types from 'class-variance-authority/types';
5
+ import { VariantProps } from 'class-variance-authority';
6
+
7
+ declare const buttonVariants: (props?: {
8
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
9
+ size?: "default" | "sm" | "lg" | "icon";
10
+ } & class_variance_authority_types.ClassProp) => string;
11
+ interface ButtonProps$1 extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
12
+ asChild?: boolean;
13
+ }
14
+
15
+ interface ButtonProps extends ButtonProps$1 {
16
+ theme?: ThemeStyle;
17
+ }
18
+
19
+ type ResolvedColorMode = "light" | "dark";
20
+
21
+ /**
22
+ * Light / dark toggle. Public sites pass `theme` so the button uses
23
+ * page-builder primitives; the builder chrome omits it and matches the
24
+ * other icon buttons in the top bar.
25
+ *
26
+ * Preference starts as system (OS). The first click pins an explicit
27
+ * light or dark side; later clicks flip between those two.
28
+ */
29
+ declare function ThemeModeSwitcher({ resolved, onToggle, theme, className, size, variant, }: {
30
+ resolved: ResolvedColorMode;
31
+ onToggle: () => void;
32
+ theme?: ThemeStyle;
33
+ className?: string;
34
+ size?: ButtonProps["size"];
35
+ variant?: ButtonProps["variant"];
36
+ }): React__default.JSX.Element;
37
+
38
+ export { ThemeModeSwitcher };
@@ -0,0 +1,169 @@
1
+ "use client";
2
+ "use client";
3
+
4
+ // src/components/theme-mode-switcher.tsx
5
+ import { useEffect, useState } from "react";
6
+ import { Moon, Sun } from "lucide-react";
7
+
8
+ // src/primitives/lib/utils.ts
9
+ import { clsx } from "clsx";
10
+ import { twMerge } from "tailwind-merge";
11
+ function cn(...inputs) {
12
+ return twMerge(clsx(inputs));
13
+ }
14
+
15
+ // src/primitives/components/ui/button.tsx
16
+ import { Slot } from "@radix-ui/react-slot";
17
+ import { cva } from "class-variance-authority";
18
+ import * as React from "react";
19
+ import { jsx } from "react/jsx-runtime";
20
+ var buttonVariants = cva(
21
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
22
+ {
23
+ variants: {
24
+ variant: {
25
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
26
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
27
+ outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
28
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
29
+ ghost: "hover:bg-accent hover:text-accent-foreground",
30
+ link: "text-primary underline-offset-4 hover:underline"
31
+ },
32
+ size: {
33
+ default: "h-10 px-4 py-2",
34
+ sm: "h-9 rounded-md px-3",
35
+ lg: "h-11 rounded-md px-8",
36
+ icon: "h-10 w-10"
37
+ }
38
+ },
39
+ defaultVariants: {
40
+ variant: "default",
41
+ size: "default"
42
+ }
43
+ }
44
+ );
45
+ var Button = React.forwardRef(
46
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
47
+ const Comp = asChild ? Slot : "button";
48
+ return /* @__PURE__ */ jsx(
49
+ Comp,
50
+ {
51
+ className: cn(buttonVariants({ variant, size, className })),
52
+ ref,
53
+ ...props
54
+ }
55
+ );
56
+ }
57
+ );
58
+ Button.displayName = "Button";
59
+
60
+ // src/primitives/button.tsx
61
+ import { jsx as jsx2 } from "react/jsx-runtime";
62
+ var Button2 = ({
63
+ variant = "default",
64
+ size = "default",
65
+ disabled = false,
66
+ children,
67
+ className = "",
68
+ theme,
69
+ ...props
70
+ }) => {
71
+ const buttonStyles = theme?.interactives?.button;
72
+ const typographyStyles = theme?.typography?.button;
73
+ const isIcon = size === "icon";
74
+ const classes = cn(
75
+ // Base styles
76
+ "",
77
+ // Theme typography
78
+ typographyStyles?.fontFamily,
79
+ typographyStyles?.fontSize,
80
+ typographyStyles?.fontWeight,
81
+ typographyStyles?.lineHeight,
82
+ typographyStyles?.letterSpacing,
83
+ typographyStyles?.textTransform,
84
+ typographyStyles?.textDecoration,
85
+ typographyStyles?.textOverflow,
86
+ // Theme interactivity. Icon buttons stay square — CTA padding
87
+ // would stretch them off the size="icon" box.
88
+ !isIcon && buttonStyles?.padding?.x,
89
+ !isIcon && buttonStyles?.padding?.y,
90
+ buttonStyles?.border?.width,
91
+ buttonStyles?.border?.radius,
92
+ buttonStyles?.border?.style,
93
+ // buttonStyles?.border?.color,
94
+ buttonStyles?.shadow,
95
+ // Theme disabled states
96
+ disabled && buttonStyles?.disabled?.opacity,
97
+ disabled && buttonStyles?.disabled?.cursor,
98
+ disabled && buttonStyles?.disabled?.color,
99
+ disabled && buttonStyles?.disabled?.background,
100
+ disabled && buttonStyles?.disabled?.border,
101
+ // User overrides
102
+ buttonStyles?.custom,
103
+ className
104
+ );
105
+ return /* @__PURE__ */ jsx2(
106
+ Button,
107
+ {
108
+ variant,
109
+ size,
110
+ disabled,
111
+ className: classes,
112
+ ...props,
113
+ children
114
+ }
115
+ );
116
+ };
117
+
118
+ // src/components/theme-mode-switcher.tsx
119
+ import { jsx as jsx3 } from "react/jsx-runtime";
120
+ function ThemeModeSwitcher({
121
+ resolved,
122
+ onToggle,
123
+ theme,
124
+ className,
125
+ size,
126
+ variant
127
+ }) {
128
+ const [mounted, setMounted] = useState(false);
129
+ useEffect(() => {
130
+ setMounted(true);
131
+ }, []);
132
+ const painted = mounted ? resolved : "light";
133
+ const next = painted === "dark" ? "light" : "dark";
134
+ const label = next === "dark" ? "Switch to dark mode" : "Switch to light mode";
135
+ const Icon = painted === "dark" ? Sun : Moon;
136
+ if (theme) {
137
+ return /* @__PURE__ */ jsx3(
138
+ Button2,
139
+ {
140
+ type: "button",
141
+ variant: variant ?? "outline",
142
+ size: size ?? "sm",
143
+ theme,
144
+ title: label,
145
+ "aria-label": label,
146
+ className,
147
+ onClick: onToggle,
148
+ children: /* @__PURE__ */ jsx3(Icon, { className: "h-4 w-4" })
149
+ }
150
+ );
151
+ }
152
+ return /* @__PURE__ */ jsx3(
153
+ Button,
154
+ {
155
+ type: "button",
156
+ variant: "outline",
157
+ size: "icon",
158
+ title: label,
159
+ "aria-label": label,
160
+ className,
161
+ onClick: onToggle,
162
+ children: /* @__PURE__ */ jsx3(Icon, { className: "h-4 w-4" })
163
+ }
164
+ );
165
+ }
166
+ export {
167
+ ThemeModeSwitcher
168
+ };
169
+ //# sourceMappingURL=theme-mode-switcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/theme-mode-switcher.tsx","../../../src/primitives/lib/utils.ts","../../../src/primitives/components/ui/button.tsx","../../../src/primitives/button.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useEffect, useState } from \"react\";\nimport { Moon, Sun } from \"lucide-react\";\nimport { Button, type ButtonProps } from \"../primitives\";\nimport { Button as ChromeButton } from \"../primitives/components/ui/button\";\nimport type { ThemeStyle } from \"../models\";\nimport type { ResolvedColorMode } from \"../utils\";\n\n/**\n * Light / dark toggle. Public sites pass `theme` so the button uses\n * page-builder primitives; the builder chrome omits it and matches the\n * other icon buttons in the top bar.\n *\n * Preference starts as system (OS). The first click pins an explicit\n * light or dark side; later clicks flip between those two.\n */\nexport function ThemeModeSwitcher({\n resolved,\n onToggle,\n theme,\n className,\n size,\n variant,\n}: {\n resolved: ResolvedColorMode;\n onToggle: () => void;\n theme?: ThemeStyle;\n className?: string;\n size?: ButtonProps[\"size\"];\n variant?: ButtonProps[\"variant\"];\n}) {\n const [mounted, setMounted] = useState(false);\n useEffect(() => {\n setMounted(true);\n }, []);\n const painted = mounted ? resolved : \"light\";\n const next = painted === \"dark\" ? \"light\" : \"dark\";\n const label = next === \"dark\" ? \"Switch to dark mode\" : \"Switch to light mode\";\n const Icon = painted === \"dark\" ? Sun : Moon;\n\n if (theme) {\n return (\n <Button\n type=\"button\"\n variant={variant ?? \"outline\"}\n size={size ?? \"sm\"}\n theme={theme}\n title={label}\n aria-label={label}\n className={className}\n onClick={onToggle}\n >\n <Icon className=\"h-4 w-4\" />\n </Button>\n );\n }\n\n return (\n <ChromeButton\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n title={label}\n aria-label={label}\n className={className}\n onClick={onToggle}\n >\n <Icon className=\"h-4 w-4\" />\n </ChromeButton>\n );\n}\n","import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n outline:\n \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-10 px-4 py-2\",\n sm: \"h-9 rounded-md px-3\",\n lg: \"h-11 rounded-md px-8\",\n icon: \"h-10 w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n {...props}\n />\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n","import type React from \"react\";\nimport type { ThemeStyle } from \"../models\";\nimport {\n Button as ShadcnButton,\n type ButtonProps as ShadcnButtonProps,\n} from \"./components/ui/button\";\nimport { cn } from \"./lib/utils\";\n\nexport interface ButtonProps extends ShadcnButtonProps {\n theme?: ThemeStyle;\n}\n\nexport const Button: React.FC<ButtonProps> = ({\n variant = \"default\",\n size = \"default\",\n disabled = false,\n children,\n className = \"\",\n theme,\n ...props\n}) => {\n const buttonStyles = theme?.interactives?.button;\n const typographyStyles = theme?.typography?.button;\n const isIcon = size === \"icon\";\n\n const classes = cn(\n // Base styles\n \"\",\n // Theme typography\n typographyStyles?.fontFamily,\n typographyStyles?.fontSize,\n typographyStyles?.fontWeight,\n typographyStyles?.lineHeight,\n typographyStyles?.letterSpacing,\n typographyStyles?.textTransform,\n typographyStyles?.textDecoration,\n typographyStyles?.textOverflow,\n // Theme interactivity. Icon buttons stay square — CTA padding\n // would stretch them off the size=\"icon\" box.\n !isIcon && buttonStyles?.padding?.x,\n !isIcon && buttonStyles?.padding?.y,\n buttonStyles?.border?.width,\n buttonStyles?.border?.radius,\n buttonStyles?.border?.style,\n // buttonStyles?.border?.color,\n buttonStyles?.shadow,\n // Theme disabled states\n disabled && buttonStyles?.disabled?.opacity,\n disabled && buttonStyles?.disabled?.cursor,\n disabled && buttonStyles?.disabled?.color,\n disabled && buttonStyles?.disabled?.background,\n disabled && buttonStyles?.disabled?.border,\n // User overrides\n buttonStyles?.custom,\n className,\n );\n\n return (\n <ShadcnButton\n variant={variant}\n size={size}\n disabled={disabled}\n className={classes}\n {...props}\n >\n {children}\n </ShadcnButton>\n );\n};\n"],"mappings":"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","names":["jsx","Button","jsx","Button"]}
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+
3
+ type AspectRatio = "16/9" | "4/3" | "1/1" | "21/9" | "9/16" | "2/1" | "3/2" | string;
4
+ interface VideoThumbnailProps {
5
+ title?: string;
6
+ thumbnailUrl?: string;
7
+ videoUrl: string;
8
+ aspectRatio?: AspectRatio;
9
+ modal?: boolean;
10
+ }
11
+ declare function VideoWithPreview({ title, thumbnailUrl, videoUrl, aspectRatio, modal, }: VideoThumbnailProps): React.JSX.Element;
12
+
13
+ export { type AspectRatio, type VideoThumbnailProps, VideoWithPreview };
@@ -0,0 +1,269 @@
1
+ "use client";
2
+
3
+ // src/components/video-with-preview.tsx
4
+ import { useState, useEffect } from "react";
5
+ import { Play, X } from "lucide-react";
6
+
7
+ // src/utils/extract-video-id.ts
8
+ var YOUTUBE_HOSTNAMES = /* @__PURE__ */ new Set([
9
+ "youtube.com",
10
+ "www.youtube.com",
11
+ "m.youtube.com",
12
+ "music.youtube.com",
13
+ "youtube-nocookie.com",
14
+ "www.youtube-nocookie.com",
15
+ "youtu.be"
16
+ ]);
17
+ var VIMEO_HOSTNAMES = /* @__PURE__ */ new Set(["vimeo.com", "www.vimeo.com", "player.vimeo.com"]);
18
+ var isNumeric = (value) => /^\d+$/.test(value);
19
+ var extractYouTubeVideoId = (parsedUrl) => {
20
+ const hostname = parsedUrl.hostname.toLowerCase();
21
+ if (!YOUTUBE_HOSTNAMES.has(hostname)) {
22
+ return null;
23
+ }
24
+ if (hostname === "youtu.be") {
25
+ return parsedUrl.pathname.split("/").filter(Boolean)[0] || null;
26
+ }
27
+ if (parsedUrl.pathname === "/watch") {
28
+ return parsedUrl.searchParams.get("v");
29
+ }
30
+ const pathParts = parsedUrl.pathname.split("/").filter(Boolean);
31
+ const firstPart = pathParts[0];
32
+ const secondPart = pathParts[1];
33
+ if ((firstPart === "embed" || firstPart === "v" || firstPart === "shorts" || firstPart === "live") && secondPart) {
34
+ return secondPart;
35
+ }
36
+ return null;
37
+ };
38
+ var extractVimeoVideoId = (parsedUrl) => {
39
+ const hostname = parsedUrl.hostname.toLowerCase();
40
+ if (!VIMEO_HOSTNAMES.has(hostname)) {
41
+ return null;
42
+ }
43
+ const pathParts = parsedUrl.pathname.split("/").filter(Boolean);
44
+ for (let index = pathParts.length - 1; index >= 0; index--) {
45
+ if (isNumeric(pathParts[index])) {
46
+ return pathParts[index];
47
+ }
48
+ }
49
+ return null;
50
+ };
51
+ function extractVideoId(url, platform) {
52
+ if (!url) {
53
+ return null;
54
+ }
55
+ const normalizedUrl = /^[a-z][a-z\d+\-.]*:\/\//i.test(url) ? url : `https://${url}`;
56
+ try {
57
+ const parsedUrl = new URL(normalizedUrl);
58
+ if (platform === "youtube") {
59
+ return extractYouTubeVideoId(parsedUrl);
60
+ }
61
+ if (platform === "vimeo") {
62
+ return extractVimeoVideoId(parsedUrl);
63
+ }
64
+ return extractYouTubeVideoId(parsedUrl) || extractVimeoVideoId(parsedUrl);
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ // src/components/video-with-preview.tsx
71
+ import { jsx, jsxs } from "react/jsx-runtime";
72
+ function VideoWithPreview({
73
+ title = "Video",
74
+ thumbnailUrl,
75
+ videoUrl = "https://www.youtube.com/watch?v=7OP2bU9RWVE",
76
+ aspectRatio = "16/9",
77
+ modal = false
78
+ }) {
79
+ const [isModalOpen, setIsModalOpen] = useState(false);
80
+ const [isPlaying, setIsPlaying] = useState(false);
81
+ const [vimeoThumbnail, setVimeoThumbnail] = useState(null);
82
+ const detectVideoType = () => {
83
+ if (extractVideoId(videoUrl, "youtube")) {
84
+ return "youtube";
85
+ }
86
+ if (extractVideoId(videoUrl, "vimeo")) {
87
+ return "vimeo";
88
+ }
89
+ return "self-hosted";
90
+ };
91
+ const videoType = detectVideoType();
92
+ const getVideoId = () => {
93
+ if (videoType === "youtube") {
94
+ return extractVideoId(videoUrl, "youtube");
95
+ }
96
+ if (videoType === "vimeo") {
97
+ return extractVideoId(videoUrl, "vimeo");
98
+ }
99
+ return null;
100
+ };
101
+ useEffect(() => {
102
+ const fetchVimeoThumbnail = async () => {
103
+ if (thumbnailUrl || videoType !== "vimeo") return;
104
+ const videoId = getVideoId();
105
+ if (!videoId) return;
106
+ try {
107
+ const response = await fetch(
108
+ `https://vimeo.com/api/oembed.json?url=https://vimeo.com/${videoId}`
109
+ );
110
+ if (response.ok) {
111
+ const data = await response.json();
112
+ setVimeoThumbnail(data.thumbnail_url);
113
+ }
114
+ } catch (error) {
115
+ console.error("Error fetching Vimeo thumbnail:", error);
116
+ }
117
+ };
118
+ fetchVimeoThumbnail();
119
+ }, [videoUrl, videoType, thumbnailUrl]);
120
+ const getThumbnailUrl = () => {
121
+ if (thumbnailUrl) return thumbnailUrl;
122
+ if (videoType === "youtube") {
123
+ const videoId = getVideoId();
124
+ if (videoId) {
125
+ return `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;
126
+ }
127
+ }
128
+ if (videoType === "vimeo" && vimeoThumbnail) {
129
+ return vimeoThumbnail;
130
+ }
131
+ return "/placeholder.svg?height=480&width=854";
132
+ };
133
+ useEffect(() => {
134
+ if (isModalOpen) {
135
+ document.body.style.overflow = "hidden";
136
+ } else {
137
+ document.body.style.overflow = "";
138
+ }
139
+ return () => {
140
+ document.body.style.overflow = "";
141
+ };
142
+ }, [isModalOpen]);
143
+ const getEmbedUrl = () => {
144
+ const videoId = getVideoId();
145
+ const shouldAutoplay = isPlaying;
146
+ if (videoType === "youtube") {
147
+ if (videoId) {
148
+ return `https://www.youtube.com/embed/${videoId}${shouldAutoplay ? "?autoplay=1" : ""}`;
149
+ }
150
+ const hasParams = videoUrl.includes("?");
151
+ return videoUrl + (shouldAutoplay && !hasParams ? "?autoplay=1" : shouldAutoplay && hasParams ? "&autoplay=1" : "");
152
+ }
153
+ if (videoType === "vimeo" && videoId) {
154
+ return `https://player.vimeo.com/video/${videoId}${shouldAutoplay ? "?autoplay=1" : ""}`;
155
+ }
156
+ return videoUrl;
157
+ };
158
+ const aspectRatioStyle = () => {
159
+ if (!aspectRatio.includes("/")) return {};
160
+ const [width, height] = aspectRatio.split("/").map(Number);
161
+ if (!width || !height) return {};
162
+ return {
163
+ aspectRatio: `${width}/${height}`
164
+ };
165
+ };
166
+ const handleThumbnailClick = () => {
167
+ if (modal) {
168
+ setIsModalOpen(true);
169
+ } else {
170
+ setIsPlaying(true);
171
+ }
172
+ };
173
+ return /* @__PURE__ */ jsxs("div", { className: aspectRatio === "9/16" ? "max-w-xs mx-auto" : "", children: [
174
+ /* @__PURE__ */ jsx("div", { className: "relative overflow-hidden", style: aspectRatioStyle(), children: !modal && isPlaying ? (
175
+ // In-place video player
176
+ /* @__PURE__ */ jsx("div", { className: "w-full h-full", children: videoType !== "self-hosted" ? /* @__PURE__ */ jsx(
177
+ "iframe",
178
+ {
179
+ src: getEmbedUrl(),
180
+ className: "w-full h-full",
181
+ allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
182
+ allowFullScreen: true
183
+ }
184
+ ) : /* @__PURE__ */ jsx(
185
+ "video",
186
+ {
187
+ src: videoUrl,
188
+ controls: true,
189
+ controlsList: "nodownload",
190
+ onContextMenu: (e) => e.preventDefault(),
191
+ autoPlay: true,
192
+ className: "w-full h-full",
193
+ children: "Your browser does not support the video tag."
194
+ }
195
+ ) })
196
+ ) : (
197
+ // Thumbnail with play button
198
+ /* @__PURE__ */ jsxs(
199
+ "div",
200
+ {
201
+ className: "cursor-pointer group w-full h-full",
202
+ onClick: handleThumbnailClick,
203
+ children: [
204
+ /* @__PURE__ */ jsx(
205
+ "img",
206
+ {
207
+ src: getThumbnailUrl() || "/placeholder.svg",
208
+ alt: `Thumbnail for ${title}`,
209
+ className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
210
+ }
211
+ ),
212
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/30 flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
213
+ /* @__PURE__ */ jsx("span", { className: "absolute -inset-1.5 rounded-full bg-gray-200/50 animate-pulse duration-[3000ms]" }),
214
+ /* @__PURE__ */ jsx("div", { className: "relative z-1 rounded-full bg-primary p-4 text-primary-foreground", children: /* @__PURE__ */ jsx(Play, { className: "h-8 w-8", fill: "currentColor" }) })
215
+ ] }) })
216
+ ]
217
+ }
218
+ )
219
+ ) }),
220
+ modal && isModalOpen && /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 bg-black/95 z-50 flex items-center justify-center p-4 sm:p-6 md:p-10", children: [
221
+ /* @__PURE__ */ jsx(
222
+ "button",
223
+ {
224
+ onClick: () => setIsModalOpen(false),
225
+ className: "absolute top-2 right-2 sm:top-4 sm:right-4 text-white hover:text-gray-300 focus:outline-none z-10",
226
+ "aria-label": "Close",
227
+ children: /* @__PURE__ */ jsx(X, { className: "h-6 w-6 sm:h-8 sm:w-8" })
228
+ }
229
+ ),
230
+ /* @__PURE__ */ jsx(
231
+ "div",
232
+ {
233
+ className: "w-full max-w-xl sm:max-w-2xl md:max-w-3xl",
234
+ style: aspectRatioStyle(),
235
+ children: videoType !== "self-hosted" ? (
236
+ // YouTube or Vimeo video
237
+ /* @__PURE__ */ jsx(
238
+ "iframe",
239
+ {
240
+ src: getEmbedUrl(),
241
+ className: "w-full h-full",
242
+ allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
243
+ allowFullScreen: true
244
+ }
245
+ )
246
+ ) : (
247
+ // Self-hosted video
248
+ /* @__PURE__ */ jsx(
249
+ "video",
250
+ {
251
+ src: videoUrl,
252
+ controls: true,
253
+ controlsList: "nodownload",
254
+ onContextMenu: (e) => e.preventDefault(),
255
+ autoPlay: true,
256
+ className: "w-full h-full",
257
+ children: "Your browser does not support the video tag."
258
+ }
259
+ )
260
+ )
261
+ }
262
+ )
263
+ ] })
264
+ ] });
265
+ }
266
+ export {
267
+ VideoWithPreview
268
+ };
269
+ //# sourceMappingURL=video-with-preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/video-with-preview.tsx","../../../src/utils/extract-video-id.ts"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { Play, X } from \"lucide-react\";\nimport { extractVideoId } from \"../utils\";\n\ntype VideoType = \"youtube\" | \"vimeo\" | \"self-hosted\";\n\nexport type AspectRatio =\n | \"16/9\" // Widescreen\n | \"4/3\" // Standard\n | \"1/1\" // Square\n | \"21/9\" // Ultrawide\n | \"9/16\" // Vertical\n | \"2/1\" // Panoramic\n | \"3/2\" // Classic\n | string; // Allow custom aspect ratios\n\nexport interface VideoThumbnailProps {\n title?: string;\n thumbnailUrl?: string;\n videoUrl: string;\n aspectRatio?: AspectRatio;\n modal?: boolean;\n}\n\nexport function VideoWithPreview({\n title = \"Video\",\n thumbnailUrl,\n videoUrl = \"https://www.youtube.com/watch?v=7OP2bU9RWVE\",\n aspectRatio = \"16/9\",\n modal = false,\n}: VideoThumbnailProps) {\n const [isModalOpen, setIsModalOpen] = useState(false);\n const [isPlaying, setIsPlaying] = useState(false);\n const [vimeoThumbnail, setVimeoThumbnail] = useState<string | null>(null);\n\n const detectVideoType = (): VideoType => {\n if (extractVideoId(videoUrl, \"youtube\")) {\n return \"youtube\";\n }\n\n if (extractVideoId(videoUrl, \"vimeo\")) {\n return \"vimeo\";\n }\n\n return \"self-hosted\";\n };\n\n const videoType = detectVideoType();\n\n const getVideoId = () => {\n if (videoType === \"youtube\") {\n return extractVideoId(videoUrl, \"youtube\");\n }\n\n if (videoType === \"vimeo\") {\n return extractVideoId(videoUrl, \"vimeo\");\n }\n\n return null;\n };\n\n // Fetch Vimeo thumbnail if needed\n useEffect(() => {\n const fetchVimeoThumbnail = async () => {\n // Skip fetching if a thumbnail URL is already provided\n if (thumbnailUrl || videoType !== \"vimeo\") return;\n\n const videoId = getVideoId();\n if (!videoId) return;\n\n try {\n // Use Vimeo's oEmbed API to get thumbnail (no token required)\n const response = await fetch(\n `https://vimeo.com/api/oembed.json?url=https://vimeo.com/${videoId}`,\n );\n\n if (response.ok) {\n const data = await response.json();\n setVimeoThumbnail(data.thumbnail_url);\n }\n } catch (error) {\n console.error(\"Error fetching Vimeo thumbnail:\", error);\n }\n };\n\n fetchVimeoThumbnail();\n }, [videoUrl, videoType, thumbnailUrl]);\n\n // Get the appropriate thumbnail URL\n const getThumbnailUrl = () => {\n // If a thumbnail URL is explicitly provided, use that\n if (thumbnailUrl) return thumbnailUrl;\n\n // For YouTube videos, generate thumbnail URL\n if (videoType === \"youtube\") {\n const videoId = getVideoId();\n if (videoId) {\n return `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;\n }\n }\n\n // For Vimeo videos, use the fetched thumbnail\n if (videoType === \"vimeo\" && vimeoThumbnail) {\n return vimeoThumbnail;\n }\n\n // Fall back to placeholder\n return \"/placeholder.svg?height=480&width=854\";\n };\n\n // Lock body scroll when modal is open\n useEffect(() => {\n if (isModalOpen) {\n document.body.style.overflow = \"hidden\";\n } else {\n document.body.style.overflow = \"\";\n }\n\n return () => {\n document.body.style.overflow = \"\";\n };\n }, [isModalOpen]);\n\n // Get embed URL based on video type\n const getEmbedUrl = () => {\n const videoId = getVideoId();\n // Add autoplay parameter for in-place videos\n const shouldAutoplay = isPlaying;\n\n if (videoType === \"youtube\") {\n if (videoId) {\n return `https://www.youtube.com/embed/${videoId}${shouldAutoplay ? \"?autoplay=1\" : \"\"}`;\n }\n\n // If we couldn't extract the ID but it's a YouTube URL, try to use it directly\n const hasParams = videoUrl.includes(\"?\");\n return (\n videoUrl +\n (shouldAutoplay && !hasParams\n ? \"?autoplay=1\"\n : shouldAutoplay && hasParams\n ? \"&autoplay=1\"\n : \"\")\n );\n }\n\n if (videoType === \"vimeo\" && videoId) {\n return `https://player.vimeo.com/video/${videoId}${shouldAutoplay ? \"?autoplay=1\" : \"\"}`;\n }\n\n // For self-hosted videos, return the URL as is\n return videoUrl;\n };\n\n // Calculate aspect ratio style\n const aspectRatioStyle = () => {\n if (!aspectRatio.includes(\"/\")) return {};\n\n const [width, height] = aspectRatio.split(\"/\").map(Number);\n if (!width || !height) return {};\n\n return {\n aspectRatio: `${width}/${height}`,\n };\n };\n\n // Handle thumbnail click\n const handleThumbnailClick = () => {\n if (modal) {\n setIsModalOpen(true);\n } else {\n setIsPlaying(true);\n }\n };\n\n return (\n <div className={aspectRatio === \"9/16\" ? \"max-w-xs mx-auto\" : \"\"}>\n {/* Thumbnail with play button or in-place video */}\n <div className=\"relative overflow-hidden\" style={aspectRatioStyle()}>\n {!modal && isPlaying ? (\n // In-place video player\n <div className=\"w-full h-full\">\n {videoType !== \"self-hosted\" ? (\n <iframe\n src={getEmbedUrl()}\n className=\"w-full h-full\"\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n allowFullScreen\n ></iframe>\n ) : (\n <video\n src={videoUrl}\n controls\n controlsList=\"nodownload\" // eslint-disable-line react/no-unknown-property\n onContextMenu={(e) => e.preventDefault()}\n autoPlay\n className=\"w-full h-full\"\n >\n Your browser does not support the video tag.\n </video>\n )}\n </div>\n ) : (\n // Thumbnail with play button\n <div\n className=\"cursor-pointer group w-full h-full\"\n onClick={handleThumbnailClick}\n >\n <img\n src={getThumbnailUrl() || \"/placeholder.svg\"}\n alt={`Thumbnail for ${title}`}\n className=\"w-full h-full object-cover transition-transform duration-300 group-hover:scale-105\"\n />\n <div className=\"absolute inset-0 bg-black/30 flex items-center justify-center\">\n <div className=\"relative\">\n {/* Smaller grayish concentric wave animation */}\n <span className=\"absolute -inset-1.5 rounded-full bg-gray-200/50 animate-pulse duration-[3000ms]\"></span>\n\n {/* Play button */}\n <div className=\"relative z-1 rounded-full bg-primary p-4 text-primary-foreground\">\n <Play className=\"h-8 w-8\" fill=\"currentColor\" />\n </div>\n </div>\n </div>\n </div>\n )}\n </div>\n\n {/* Custom Video Modal (only used when modal is true) */}\n {modal && isModalOpen && (\n <div className=\"fixed inset-0 bg-black/95 z-50 flex items-center justify-center p-4 sm:p-6 md:p-10\">\n <button\n onClick={() => setIsModalOpen(false)}\n className=\"absolute top-2 right-2 sm:top-4 sm:right-4 text-white hover:text-gray-300 focus:outline-none z-10\"\n aria-label=\"Close\"\n >\n <X className=\"h-6 w-6 sm:h-8 sm:w-8\" />\n </button>\n\n <div\n className=\"w-full max-w-xl sm:max-w-2xl md:max-w-3xl\"\n style={aspectRatioStyle()}\n >\n {videoType !== \"self-hosted\" ? (\n // YouTube or Vimeo video\n <iframe\n src={getEmbedUrl()}\n className=\"w-full h-full\"\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n allowFullScreen\n ></iframe>\n ) : (\n // Self-hosted video\n <video\n src={videoUrl}\n controls\n controlsList=\"nodownload\" // eslint-disable-line react/no-unknown-property\n onContextMenu={(e) => e.preventDefault()}\n autoPlay\n className=\"w-full h-full\"\n >\n Your browser does not support the video tag.\n </video>\n )}\n </div>\n </div>\n )}\n </div>\n );\n}\n","export type SupportedVideoPlatform = \"youtube\" | \"vimeo\";\n\nconst YOUTUBE_HOSTNAMES = new Set([\n \"youtube.com\",\n \"www.youtube.com\",\n \"m.youtube.com\",\n \"music.youtube.com\",\n \"youtube-nocookie.com\",\n \"www.youtube-nocookie.com\",\n \"youtu.be\",\n]);\n\nconst VIMEO_HOSTNAMES = new Set([\"vimeo.com\", \"www.vimeo.com\", \"player.vimeo.com\"]);\n\nconst isNumeric = (value: string) => /^\\d+$/.test(value);\n\nconst extractYouTubeVideoId = (parsedUrl: URL) => {\n const hostname = parsedUrl.hostname.toLowerCase();\n\n if (!YOUTUBE_HOSTNAMES.has(hostname)) {\n return null;\n }\n\n if (hostname === \"youtu.be\") {\n return parsedUrl.pathname.split(\"/\").filter(Boolean)[0] || null;\n }\n\n if (parsedUrl.pathname === \"/watch\") {\n return parsedUrl.searchParams.get(\"v\");\n }\n\n const pathParts = parsedUrl.pathname.split(\"/\").filter(Boolean);\n const firstPart = pathParts[0];\n const secondPart = pathParts[1];\n\n if (\n (firstPart === \"embed\" ||\n firstPart === \"v\" ||\n firstPart === \"shorts\" ||\n firstPart === \"live\") &&\n secondPart\n ) {\n return secondPart;\n }\n\n return null;\n};\n\nconst extractVimeoVideoId = (parsedUrl: URL) => {\n const hostname = parsedUrl.hostname.toLowerCase();\n\n if (!VIMEO_HOSTNAMES.has(hostname)) {\n return null;\n }\n\n const pathParts = parsedUrl.pathname.split(\"/\").filter(Boolean);\n\n for (let index = pathParts.length - 1; index >= 0; index--) {\n if (isNumeric(pathParts[index])) {\n return pathParts[index];\n }\n }\n\n return null;\n};\n\nexport function extractVideoId(url?: string, platform?: SupportedVideoPlatform) {\n if (!url) {\n return null;\n }\n\n const normalizedUrl = /^[a-z][a-z\\d+\\-.]*:\\/\\//i.test(url) ? url : `https://${url}`;\n\n try {\n const parsedUrl = new URL(normalizedUrl);\n\n if (platform === \"youtube\") {\n return extractYouTubeVideoId(parsedUrl);\n }\n\n if (platform === \"vimeo\") {\n return extractVimeoVideoId(parsedUrl);\n }\n\n return extractYouTubeVideoId(parsedUrl) || extractVimeoVideoId(parsedUrl);\n } catch {\n return null;\n }\n}\n"],"mappings":";;;AAAA,SAAS,UAAU,iBAAiB;AACpC,SAAS,MAAM,SAAS;;;ACCxB,IAAM,oBAAoB,oBAAI,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,kBAAkB,oBAAI,IAAI,CAAC,aAAa,iBAAiB,kBAAkB,CAAC;AAElF,IAAM,YAAY,CAAC,UAAkB,QAAQ,KAAK,KAAK;AAEvD,IAAM,wBAAwB,CAAC,cAAmB;AAChD,QAAM,WAAW,UAAU,SAAS,YAAY;AAEhD,MAAI,CAAC,kBAAkB,IAAI,QAAQ,GAAG;AACpC,WAAO;AAAA,EACT;AAEA,MAAI,aAAa,YAAY;AAC3B,WAAO,UAAU,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,CAAC,KAAK;AAAA,EAC7D;AAEA,MAAI,UAAU,aAAa,UAAU;AACnC,WAAO,UAAU,aAAa,IAAI,GAAG;AAAA,EACvC;AAEA,QAAM,YAAY,UAAU,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AAC9D,QAAM,YAAY,UAAU,CAAC;AAC7B,QAAM,aAAa,UAAU,CAAC;AAE9B,OACG,cAAc,WACb,cAAc,OACd,cAAc,YACd,cAAc,WAChB,YACA;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,IAAM,sBAAsB,CAAC,cAAmB;AAC9C,QAAM,WAAW,UAAU,SAAS,YAAY;AAEhD,MAAI,CAAC,gBAAgB,IAAI,QAAQ,GAAG;AAClC,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,UAAU,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AAE9D,WAAS,QAAQ,UAAU,SAAS,GAAG,SAAS,GAAG,SAAS;AAC1D,QAAI,UAAU,UAAU,KAAK,CAAC,GAAG;AAC/B,aAAO,UAAU,KAAK;AAAA,IACxB;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,eAAe,KAAc,UAAmC;AAC9E,MAAI,CAAC,KAAK;AACR,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,2BAA2B,KAAK,GAAG,IAAI,MAAM,WAAW,GAAG;AAEjF,MAAI;AACF,UAAM,YAAY,IAAI,IAAI,aAAa;AAEvC,QAAI,aAAa,WAAW;AAC1B,aAAO,sBAAsB,SAAS;AAAA,IACxC;AAEA,QAAI,aAAa,SAAS;AACxB,aAAO,oBAAoB,SAAS;AAAA,IACtC;AAEA,WAAO,sBAAsB,SAAS,KAAK,oBAAoB,SAAS;AAAA,EAC1E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AD+Fc,cA+BA,YA/BA;AA/JP,SAAS,iBAAiB;AAAA,EAC/B,QAAQ;AAAA,EACR;AAAA,EACA,WAAW;AAAA,EACX,cAAc;AAAA,EACd,QAAQ;AACV,GAAwB;AACtB,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAwB,IAAI;AAExE,QAAM,kBAAkB,MAAiB;AACvC,QAAI,eAAe,UAAU,SAAS,GAAG;AACvC,aAAO;AAAA,IACT;AAEA,QAAI,eAAe,UAAU,OAAO,GAAG;AACrC,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,gBAAgB;AAElC,QAAM,aAAa,MAAM;AACvB,QAAI,cAAc,WAAW;AAC3B,aAAO,eAAe,UAAU,SAAS;AAAA,IAC3C;AAEA,QAAI,cAAc,SAAS;AACzB,aAAO,eAAe,UAAU,OAAO;AAAA,IACzC;AAEA,WAAO;AAAA,EACT;AAGA,YAAU,MAAM;AACd,UAAM,sBAAsB,YAAY;AAEtC,UAAI,gBAAgB,cAAc,QAAS;AAE3C,YAAM,UAAU,WAAW;AAC3B,UAAI,CAAC,QAAS;AAEd,UAAI;AAEF,cAAM,WAAW,MAAM;AAAA,UACrB,2DAA2D,OAAO;AAAA,QACpE;AAEA,YAAI,SAAS,IAAI;AACf,gBAAM,OAAO,MAAM,SAAS,KAAK;AACjC,4BAAkB,KAAK,aAAa;AAAA,QACtC;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ,MAAM,mCAAmC,KAAK;AAAA,MACxD;AAAA,IACF;AAEA,wBAAoB;AAAA,EACtB,GAAG,CAAC,UAAU,WAAW,YAAY,CAAC;AAGtC,QAAM,kBAAkB,MAAM;AAE5B,QAAI,aAAc,QAAO;AAGzB,QAAI,cAAc,WAAW;AAC3B,YAAM,UAAU,WAAW;AAC3B,UAAI,SAAS;AACX,eAAO,8BAA8B,OAAO;AAAA,MAC9C;AAAA,IACF;AAGA,QAAI,cAAc,WAAW,gBAAgB;AAC3C,aAAO;AAAA,IACT;AAGA,WAAO;AAAA,EACT;AAGA,YAAU,MAAM;AACd,QAAI,aAAa;AACf,eAAS,KAAK,MAAM,WAAW;AAAA,IACjC,OAAO;AACL,eAAS,KAAK,MAAM,WAAW;AAAA,IACjC;AAEA,WAAO,MAAM;AACX,eAAS,KAAK,MAAM,WAAW;AAAA,IACjC;AAAA,EACF,GAAG,CAAC,WAAW,CAAC;AAGhB,QAAM,cAAc,MAAM;AACxB,UAAM,UAAU,WAAW;AAE3B,UAAM,iBAAiB;AAEvB,QAAI,cAAc,WAAW;AAC3B,UAAI,SAAS;AACX,eAAO,iCAAiC,OAAO,GAAG,iBAAiB,gBAAgB,EAAE;AAAA,MACvF;AAGA,YAAM,YAAY,SAAS,SAAS,GAAG;AACvC,aACE,YACC,kBAAkB,CAAC,YAChB,gBACA,kBAAkB,YAChB,gBACA;AAAA,IAEV;AAEA,QAAI,cAAc,WAAW,SAAS;AACpC,aAAO,kCAAkC,OAAO,GAAG,iBAAiB,gBAAgB,EAAE;AAAA,IACxF;AAGA,WAAO;AAAA,EACT;AAGA,QAAM,mBAAmB,MAAM;AAC7B,QAAI,CAAC,YAAY,SAAS,GAAG,EAAG,QAAO,CAAC;AAExC,UAAM,CAAC,OAAO,MAAM,IAAI,YAAY,MAAM,GAAG,EAAE,IAAI,MAAM;AACzD,QAAI,CAAC,SAAS,CAAC,OAAQ,QAAO,CAAC;AAE/B,WAAO;AAAA,MACL,aAAa,GAAG,KAAK,IAAI,MAAM;AAAA,IACjC;AAAA,EACF;AAGA,QAAM,uBAAuB,MAAM;AACjC,QAAI,OAAO;AACT,qBAAe,IAAI;AAAA,IACrB,OAAO;AACL,mBAAa,IAAI;AAAA,IACnB;AAAA,EACF;AAEA,SACE,qBAAC,SAAI,WAAW,gBAAgB,SAAS,qBAAqB,IAE5D;AAAA,wBAAC,SAAI,WAAU,4BAA2B,OAAO,iBAAiB,GAC/D,WAAC,SAAS;AAAA;AAAA,MAET,oBAAC,SAAI,WAAU,iBACZ,wBAAc,gBACb;AAAA,QAAC;AAAA;AAAA,UACC,KAAK,YAAY;AAAA,UACjB,WAAU;AAAA,UACV,OAAM;AAAA,UACN,iBAAe;AAAA;AAAA,MAChB,IAED;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,UAAQ;AAAA,UACR,cAAa;AAAA,UACb,eAAe,CAAC,MAAM,EAAE,eAAe;AAAA,UACvC,UAAQ;AAAA,UACR,WAAU;AAAA,UACX;AAAA;AAAA,MAED,GAEJ;AAAA;AAAA;AAAA,MAGA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,SAAS;AAAA,UAET;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,KAAK,gBAAgB,KAAK;AAAA,gBAC1B,KAAK,iBAAiB,KAAK;AAAA,gBAC3B,WAAU;AAAA;AAAA,YACZ;AAAA,YACA,oBAAC,SAAI,WAAU,iEACb,+BAAC,SAAI,WAAU,YAEb;AAAA,kCAAC,UAAK,WAAU,mFAAkF;AAAA,cAGlG,oBAAC,SAAI,WAAU,oEACb,8BAAC,QAAK,WAAU,WAAU,MAAK,gBAAe,GAChD;AAAA,eACF,GACF;AAAA;AAAA;AAAA,MACF;AAAA,OAEJ;AAAA,IAGC,SAAS,eACR,qBAAC,SAAI,WAAU,sFACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,MAAM,eAAe,KAAK;AAAA,UACnC,WAAU;AAAA,UACV,cAAW;AAAA,UAEX,8BAAC,KAAE,WAAU,yBAAwB;AAAA;AAAA,MACvC;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO,iBAAiB;AAAA,UAEvB,wBAAc;AAAA;AAAA,YAEb;AAAA,cAAC;AAAA;AAAA,gBACC,KAAK,YAAY;AAAA,gBACjB,WAAU;AAAA,gBACV,OAAM;AAAA,gBACN,iBAAe;AAAA;AAAA,YAChB;AAAA;AAAA;AAAA,YAGD;AAAA,cAAC;AAAA;AAAA,gBACC,KAAK;AAAA,gBACL,UAAQ;AAAA,gBACR,cAAa;AAAA,gBACb,eAAe,CAAC,MAAM,EAAE,eAAe;AAAA,gBACvC,UAAQ;AAAA,gBACR,WAAU;AAAA,gBACX;AAAA;AAAA,YAED;AAAA;AAAA;AAAA,MAEJ;AAAA,OACF;AAAA,KAEJ;AAEJ;","names":[]}