@burdenoff/website-sdk 2026.703.2 → 2026.709.2

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 (37) hide show
  1. package/dist/components/electron-download.d.mts +32 -0
  2. package/dist/components/electron-download.d.ts +32 -0
  3. package/dist/components/electron-download.js +131 -0
  4. package/dist/components/electron-download.js.map +1 -0
  5. package/dist/components/electron-download.mjs +108 -0
  6. package/dist/components/electron-download.mjs.map +1 -0
  7. package/dist/components/launch-countdown.d.mts +15 -0
  8. package/dist/components/launch-countdown.d.ts +15 -0
  9. package/dist/components/launch-countdown.js +89 -0
  10. package/dist/components/launch-countdown.js.map +1 -0
  11. package/dist/components/launch-countdown.mjs +87 -0
  12. package/dist/components/launch-countdown.mjs.map +1 -0
  13. package/dist/components/problems-solved.d.mts +37 -0
  14. package/dist/components/problems-solved.d.ts +37 -0
  15. package/dist/components/problems-solved.js +157 -0
  16. package/dist/components/problems-solved.js.map +1 -0
  17. package/dist/components/problems-solved.mjs +135 -0
  18. package/dist/components/problems-solved.mjs.map +1 -0
  19. package/dist/components/website-switcher.d.mts +27 -0
  20. package/dist/components/website-switcher.d.ts +27 -0
  21. package/dist/components/website-switcher.js +168 -0
  22. package/dist/components/website-switcher.js.map +1 -0
  23. package/dist/components/website-switcher.mjs +146 -0
  24. package/dist/components/website-switcher.mjs.map +1 -0
  25. package/dist/content/index.d.mts +24 -1
  26. package/dist/content/index.d.ts +24 -1
  27. package/dist/content/index.js +459 -149
  28. package/dist/content/index.js.map +1 -1
  29. package/dist/content/index.mjs +378 -87
  30. package/dist/content/index.mjs.map +1 -1
  31. package/dist/index.d.mts +15 -2
  32. package/dist/index.d.ts +15 -2
  33. package/dist/index.js +817 -86
  34. package/dist/index.js.map +1 -1
  35. package/dist/index.mjs +812 -88
  36. package/dist/index.mjs.map +1 -1
  37. package/package.json +3 -1
@@ -0,0 +1,32 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type DetectedPlatform = "win" | "mac" | "linux" | "unknown";
4
+ interface ElectronDownloadLinkProps {
5
+ /** Base URL where artifacts are hosted (with or without trailing slash). */
6
+ downloadBaseUrl: string;
7
+ /** Artifact file name; may include a `{platform}` token. */
8
+ artifactName: string;
9
+ /** When true, the element is not rendered. Defaults to true (hidden). */
10
+ hidden?: boolean;
11
+ /** Link/button text. */
12
+ label?: string;
13
+ /** Accessible label for the download action. */
14
+ downloadLabel?: string;
15
+ /** Optional mapping from detected platform to artifact platform key. */
16
+ platformMap?: Partial<Record<DetectedPlatform, string>>;
17
+ /** Additional CSS class for the link. */
18
+ className?: string;
19
+ /** Variant for the rendered button. */
20
+ variant?: "default" | "outline" | "ghost" | "link";
21
+ /** Size for the rendered button. */
22
+ size?: "default" | "sm" | "lg" | "icon";
23
+ }
24
+ /**
25
+ * Detect the visitor's desktop operating system from the user agent string.
26
+ * Returns `win`, `mac`, `linux`, or `unknown`. This is intended for choosing
27
+ * a download artifact; it intentionally does not distinguish mobile OSes.
28
+ */
29
+ declare function detectElectronPlatform(userAgent?: string): DetectedPlatform;
30
+ declare function ElectronDownloadLink({ downloadBaseUrl, artifactName, hidden, label, downloadLabel, platformMap, className, variant, size, }: ElectronDownloadLinkProps): react_jsx_runtime.JSX.Element | null;
31
+
32
+ export { type DetectedPlatform, ElectronDownloadLink, type ElectronDownloadLinkProps, detectElectronPlatform };
@@ -0,0 +1,32 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type DetectedPlatform = "win" | "mac" | "linux" | "unknown";
4
+ interface ElectronDownloadLinkProps {
5
+ /** Base URL where artifacts are hosted (with or without trailing slash). */
6
+ downloadBaseUrl: string;
7
+ /** Artifact file name; may include a `{platform}` token. */
8
+ artifactName: string;
9
+ /** When true, the element is not rendered. Defaults to true (hidden). */
10
+ hidden?: boolean;
11
+ /** Link/button text. */
12
+ label?: string;
13
+ /** Accessible label for the download action. */
14
+ downloadLabel?: string;
15
+ /** Optional mapping from detected platform to artifact platform key. */
16
+ platformMap?: Partial<Record<DetectedPlatform, string>>;
17
+ /** Additional CSS class for the link. */
18
+ className?: string;
19
+ /** Variant for the rendered button. */
20
+ variant?: "default" | "outline" | "ghost" | "link";
21
+ /** Size for the rendered button. */
22
+ size?: "default" | "sm" | "lg" | "icon";
23
+ }
24
+ /**
25
+ * Detect the visitor's desktop operating system from the user agent string.
26
+ * Returns `win`, `mac`, `linux`, or `unknown`. This is intended for choosing
27
+ * a download artifact; it intentionally does not distinguish mobile OSes.
28
+ */
29
+ declare function detectElectronPlatform(userAgent?: string): DetectedPlatform;
30
+ declare function ElectronDownloadLink({ downloadBaseUrl, artifactName, hidden, label, downloadLabel, platformMap, className, variant, size, }: ElectronDownloadLinkProps): react_jsx_runtime.JSX.Element | null;
31
+
32
+ export { type DetectedPlatform, ElectronDownloadLink, type ElectronDownloadLinkProps, detectElectronPlatform };
@@ -0,0 +1,131 @@
1
+ 'use strict';
2
+
3
+ var lucideReact = require('lucide-react');
4
+ var React = require('react');
5
+ var reactSlot = require('@radix-ui/react-slot');
6
+ var classVarianceAuthority = require('class-variance-authority');
7
+ var clsx = require('clsx');
8
+ var tailwindMerge = require('tailwind-merge');
9
+ var jsxRuntime = require('react/jsx-runtime');
10
+
11
+ function _interopNamespace(e) {
12
+ if (e && e.__esModule) return e;
13
+ var n = Object.create(null);
14
+ if (e) {
15
+ Object.keys(e).forEach(function (k) {
16
+ if (k !== 'default') {
17
+ var d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: function () { return e[k]; }
21
+ });
22
+ }
23
+ });
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+
29
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
30
+
31
+ function cn(...inputs) {
32
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
33
+ }
34
+ var buttonVariants = classVarianceAuthority.cva(
35
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
36
+ {
37
+ variants: {
38
+ variant: {
39
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
40
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
41
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
42
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
43
+ ghost: "hover:bg-accent hover:text-accent-foreground",
44
+ link: "text-primary underline-offset-4 hover:underline"
45
+ },
46
+ size: {
47
+ default: "h-9 px-4 py-2",
48
+ sm: "h-8 rounded-md px-3 text-xs",
49
+ lg: "h-10 rounded-md px-8",
50
+ icon: "h-9 w-9"
51
+ }
52
+ },
53
+ defaultVariants: {
54
+ variant: "default",
55
+ size: "default"
56
+ }
57
+ }
58
+ );
59
+ var Button = React__namespace.forwardRef(
60
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
61
+ const Comp = asChild ? reactSlot.Slot : "button";
62
+ return /* @__PURE__ */ jsxRuntime.jsx(
63
+ Comp,
64
+ {
65
+ className: cn(buttonVariants({ variant, size, className })),
66
+ ref,
67
+ ...props
68
+ }
69
+ );
70
+ }
71
+ );
72
+ Button.displayName = "Button";
73
+ function detectElectronPlatform(userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "") {
74
+ const ua = userAgent.toLowerCase();
75
+ if (ua.includes("win")) return "win";
76
+ if (ua.includes("mac")) return "mac";
77
+ if (ua.includes("linux")) return "linux";
78
+ return "unknown";
79
+ }
80
+ function buildDownloadUrl(base, artifactName, platform, platformMap) {
81
+ const normalizedBase = base.replace(/\/$/, "");
82
+ const platformKey = platformMap[platform] ?? platform;
83
+ const fileName = artifactName.includes("{platform}") ? artifactName.replace(/\{platform\}/g, platformKey) : artifactName;
84
+ return `${normalizedBase}/${fileName}`;
85
+ }
86
+ function ElectronDownloadLink({
87
+ downloadBaseUrl,
88
+ artifactName,
89
+ hidden = true,
90
+ label = "Download desktop app",
91
+ downloadLabel = label,
92
+ platformMap = {},
93
+ className,
94
+ variant = "default",
95
+ size = "default"
96
+ }) {
97
+ if (hidden) return null;
98
+ const platform = detectElectronPlatform();
99
+ const href = buildDownloadUrl(
100
+ downloadBaseUrl,
101
+ artifactName,
102
+ platform,
103
+ platformMap
104
+ );
105
+ return /* @__PURE__ */ jsxRuntime.jsx(
106
+ Button,
107
+ {
108
+ variant,
109
+ size,
110
+ className: cn("inline-flex items-center gap-2", className),
111
+ asChild: true,
112
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
113
+ "a",
114
+ {
115
+ href,
116
+ download: true,
117
+ "aria-label": `${downloadLabel}${platform !== "unknown" ? ` for ${platform}` : ""}`,
118
+ children: [
119
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4", "aria-hidden": "true" }),
120
+ label
121
+ ]
122
+ }
123
+ )
124
+ }
125
+ );
126
+ }
127
+
128
+ exports.ElectronDownloadLink = ElectronDownloadLink;
129
+ exports.detectElectronPlatform = detectElectronPlatform;
130
+ //# sourceMappingURL=electron-download.js.map
131
+ //# sourceMappingURL=electron-download.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/cn.ts","../../src/ui/button.tsx","../../src/components/electron-download.tsx"],"names":["twMerge","clsx","cva","React","Slot","jsx","jsxs","Download"],"mappings":"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","file":"electron-download.js","sourcesContent":["/**\n * Utility function to merge Tailwind CSS classes\n * Combines clsx and tailwind-merge for optimal class handling\n */\n\nimport { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge multiple class names intelligently\n * - Handles conditional classes via clsx\n * - Deduplicates and resolves Tailwind conflicts via tailwind-merge\n *\n * @example\n * ```ts\n * cn('px-4 py-2', 'bg-blue-500', { 'text-white': isActive })\n * // => 'px-4 py-2 bg-blue-500 text-white' (if isActive is true)\n * ```\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"../utils/cn\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90\",\n outline:\n \"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-sm 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-9 px-4 py-2\",\n sm: \"h-8 rounded-md px-3 text-xs\",\n lg: \"h-10 rounded-md px-8\",\n icon: \"h-9 w-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends\n 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","\"use client\";\n\n/**\n * ElectronDownloadLink Component\n *\n * A download link that auto-detects the visitor's operating system and points\n * to the matching Electron artifact. Hidden by default via the `hidden` prop so\n * it can be staged in pages and revealed once a desktop build is ready.\n *\n * The `artifactName` prop may contain a `{platform}` token which is replaced\n * with the detected platform key (`win`, `mac`, or `linux`).\n *\n * @example\n * ```tsx\n * import { ElectronDownloadLink } from '@burdenoff/website-sdk'\n *\n * <ElectronDownloadLink\n * downloadBaseUrl=\"https://releases.example.com/desktop\"\n * artifactName=\"MyApp-{platform}.zip\"\n * />\n * ```\n */\n\nimport { Download } from \"lucide-react\";\n\nimport { Button } from \"../ui/button\";\nimport { cn } from \"../utils/cn\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type DetectedPlatform = \"win\" | \"mac\" | \"linux\" | \"unknown\";\n\nexport interface ElectronDownloadLinkProps {\n /** Base URL where artifacts are hosted (with or without trailing slash). */\n downloadBaseUrl: string;\n /** Artifact file name; may include a `{platform}` token. */\n artifactName: string;\n /** When true, the element is not rendered. Defaults to true (hidden). */\n hidden?: boolean;\n /** Link/button text. */\n label?: string;\n /** Accessible label for the download action. */\n downloadLabel?: string;\n /** Optional mapping from detected platform to artifact platform key. */\n platformMap?: Partial<Record<DetectedPlatform, string>>;\n /** Additional CSS class for the link. */\n className?: string;\n /** Variant for the rendered button. */\n variant?: \"default\" | \"outline\" | \"ghost\" | \"link\";\n /** Size for the rendered button. */\n size?: \"default\" | \"sm\" | \"lg\" | \"icon\";\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Detect the visitor's desktop operating system from the user agent string.\n * Returns `win`, `mac`, `linux`, or `unknown`. This is intended for choosing\n * a download artifact; it intentionally does not distinguish mobile OSes.\n */\nexport function detectElectronPlatform(\n userAgent: string = typeof navigator !== \"undefined\"\n ? navigator.userAgent\n : \"\",\n): DetectedPlatform {\n const ua = userAgent.toLowerCase();\n\n if (ua.includes(\"win\")) return \"win\";\n if (ua.includes(\"mac\")) return \"mac\";\n if (ua.includes(\"linux\")) return \"linux\";\n\n return \"unknown\";\n}\n\nfunction buildDownloadUrl(\n base: string,\n artifactName: string,\n platform: DetectedPlatform,\n platformMap: Partial<Record<DetectedPlatform, string>>,\n): string {\n const normalizedBase = base.replace(/\\/$/, \"\");\n const platformKey = platformMap[platform] ?? platform;\n const fileName = artifactName.includes(\"{platform}\")\n ? artifactName.replace(/\\{platform\\}/g, platformKey)\n : artifactName;\n return `${normalizedBase}/${fileName}`;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function ElectronDownloadLink({\n downloadBaseUrl,\n artifactName,\n hidden = true,\n label = \"Download desktop app\",\n downloadLabel = label,\n platformMap = {},\n className,\n variant = \"default\",\n size = \"default\",\n}: ElectronDownloadLinkProps) {\n if (hidden) return null;\n\n const platform = detectElectronPlatform();\n const href = buildDownloadUrl(\n downloadBaseUrl,\n artifactName,\n platform,\n platformMap,\n );\n\n return (\n <Button\n variant={variant}\n size={size}\n className={cn(\"inline-flex items-center gap-2\", className)}\n asChild\n >\n <a\n href={href}\n download\n aria-label={`${downloadLabel}${platform !== \"unknown\" ? ` for ${platform}` : \"\"}`}\n >\n <Download className=\"h-4 w-4\" aria-hidden=\"true\" />\n {label}\n </a>\n </Button>\n );\n}\n"]}
@@ -0,0 +1,108 @@
1
+ import { Download } from 'lucide-react';
2
+ import * as React from 'react';
3
+ import { Slot } from '@radix-ui/react-slot';
4
+ import { cva } from 'class-variance-authority';
5
+ import { clsx } from 'clsx';
6
+ import { twMerge } from 'tailwind-merge';
7
+ import { jsx, jsxs } from 'react/jsx-runtime';
8
+
9
+ function cn(...inputs) {
10
+ return twMerge(clsx(inputs));
11
+ }
12
+ var buttonVariants = cva(
13
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
14
+ {
15
+ variants: {
16
+ variant: {
17
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
18
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
19
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
20
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
21
+ ghost: "hover:bg-accent hover:text-accent-foreground",
22
+ link: "text-primary underline-offset-4 hover:underline"
23
+ },
24
+ size: {
25
+ default: "h-9 px-4 py-2",
26
+ sm: "h-8 rounded-md px-3 text-xs",
27
+ lg: "h-10 rounded-md px-8",
28
+ icon: "h-9 w-9"
29
+ }
30
+ },
31
+ defaultVariants: {
32
+ variant: "default",
33
+ size: "default"
34
+ }
35
+ }
36
+ );
37
+ var Button = React.forwardRef(
38
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
39
+ const Comp = asChild ? Slot : "button";
40
+ return /* @__PURE__ */ jsx(
41
+ Comp,
42
+ {
43
+ className: cn(buttonVariants({ variant, size, className })),
44
+ ref,
45
+ ...props
46
+ }
47
+ );
48
+ }
49
+ );
50
+ Button.displayName = "Button";
51
+ function detectElectronPlatform(userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "") {
52
+ const ua = userAgent.toLowerCase();
53
+ if (ua.includes("win")) return "win";
54
+ if (ua.includes("mac")) return "mac";
55
+ if (ua.includes("linux")) return "linux";
56
+ return "unknown";
57
+ }
58
+ function buildDownloadUrl(base, artifactName, platform, platformMap) {
59
+ const normalizedBase = base.replace(/\/$/, "");
60
+ const platformKey = platformMap[platform] ?? platform;
61
+ const fileName = artifactName.includes("{platform}") ? artifactName.replace(/\{platform\}/g, platformKey) : artifactName;
62
+ return `${normalizedBase}/${fileName}`;
63
+ }
64
+ function ElectronDownloadLink({
65
+ downloadBaseUrl,
66
+ artifactName,
67
+ hidden = true,
68
+ label = "Download desktop app",
69
+ downloadLabel = label,
70
+ platformMap = {},
71
+ className,
72
+ variant = "default",
73
+ size = "default"
74
+ }) {
75
+ if (hidden) return null;
76
+ const platform = detectElectronPlatform();
77
+ const href = buildDownloadUrl(
78
+ downloadBaseUrl,
79
+ artifactName,
80
+ platform,
81
+ platformMap
82
+ );
83
+ return /* @__PURE__ */ jsx(
84
+ Button,
85
+ {
86
+ variant,
87
+ size,
88
+ className: cn("inline-flex items-center gap-2", className),
89
+ asChild: true,
90
+ children: /* @__PURE__ */ jsxs(
91
+ "a",
92
+ {
93
+ href,
94
+ download: true,
95
+ "aria-label": `${downloadLabel}${platform !== "unknown" ? ` for ${platform}` : ""}`,
96
+ children: [
97
+ /* @__PURE__ */ jsx(Download, { className: "h-4 w-4", "aria-hidden": "true" }),
98
+ label
99
+ ]
100
+ }
101
+ )
102
+ }
103
+ );
104
+ }
105
+
106
+ export { ElectronDownloadLink, detectElectronPlatform };
107
+ //# sourceMappingURL=electron-download.mjs.map
108
+ //# sourceMappingURL=electron-download.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/cn.ts","../../src/ui/button.tsx","../../src/components/electron-download.tsx"],"names":["jsx"],"mappings":"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","file":"electron-download.mjs","sourcesContent":["/**\n * Utility function to merge Tailwind CSS classes\n * Combines clsx and tailwind-merge for optimal class handling\n */\n\nimport { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge multiple class names intelligently\n * - Handles conditional classes via clsx\n * - Deduplicates and resolves Tailwind conflicts via tailwind-merge\n *\n * @example\n * ```ts\n * cn('px-4 py-2', 'bg-blue-500', { 'text-white': isActive })\n * // => 'px-4 py-2 bg-blue-500 text-white' (if isActive is true)\n * ```\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"../utils/cn\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90\",\n outline:\n \"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-sm 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-9 px-4 py-2\",\n sm: \"h-8 rounded-md px-3 text-xs\",\n lg: \"h-10 rounded-md px-8\",\n icon: \"h-9 w-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends\n 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","\"use client\";\n\n/**\n * ElectronDownloadLink Component\n *\n * A download link that auto-detects the visitor's operating system and points\n * to the matching Electron artifact. Hidden by default via the `hidden` prop so\n * it can be staged in pages and revealed once a desktop build is ready.\n *\n * The `artifactName` prop may contain a `{platform}` token which is replaced\n * with the detected platform key (`win`, `mac`, or `linux`).\n *\n * @example\n * ```tsx\n * import { ElectronDownloadLink } from '@burdenoff/website-sdk'\n *\n * <ElectronDownloadLink\n * downloadBaseUrl=\"https://releases.example.com/desktop\"\n * artifactName=\"MyApp-{platform}.zip\"\n * />\n * ```\n */\n\nimport { Download } from \"lucide-react\";\n\nimport { Button } from \"../ui/button\";\nimport { cn } from \"../utils/cn\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type DetectedPlatform = \"win\" | \"mac\" | \"linux\" | \"unknown\";\n\nexport interface ElectronDownloadLinkProps {\n /** Base URL where artifacts are hosted (with or without trailing slash). */\n downloadBaseUrl: string;\n /** Artifact file name; may include a `{platform}` token. */\n artifactName: string;\n /** When true, the element is not rendered. Defaults to true (hidden). */\n hidden?: boolean;\n /** Link/button text. */\n label?: string;\n /** Accessible label for the download action. */\n downloadLabel?: string;\n /** Optional mapping from detected platform to artifact platform key. */\n platformMap?: Partial<Record<DetectedPlatform, string>>;\n /** Additional CSS class for the link. */\n className?: string;\n /** Variant for the rendered button. */\n variant?: \"default\" | \"outline\" | \"ghost\" | \"link\";\n /** Size for the rendered button. */\n size?: \"default\" | \"sm\" | \"lg\" | \"icon\";\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Detect the visitor's desktop operating system from the user agent string.\n * Returns `win`, `mac`, `linux`, or `unknown`. This is intended for choosing\n * a download artifact; it intentionally does not distinguish mobile OSes.\n */\nexport function detectElectronPlatform(\n userAgent: string = typeof navigator !== \"undefined\"\n ? navigator.userAgent\n : \"\",\n): DetectedPlatform {\n const ua = userAgent.toLowerCase();\n\n if (ua.includes(\"win\")) return \"win\";\n if (ua.includes(\"mac\")) return \"mac\";\n if (ua.includes(\"linux\")) return \"linux\";\n\n return \"unknown\";\n}\n\nfunction buildDownloadUrl(\n base: string,\n artifactName: string,\n platform: DetectedPlatform,\n platformMap: Partial<Record<DetectedPlatform, string>>,\n): string {\n const normalizedBase = base.replace(/\\/$/, \"\");\n const platformKey = platformMap[platform] ?? platform;\n const fileName = artifactName.includes(\"{platform}\")\n ? artifactName.replace(/\\{platform\\}/g, platformKey)\n : artifactName;\n return `${normalizedBase}/${fileName}`;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function ElectronDownloadLink({\n downloadBaseUrl,\n artifactName,\n hidden = true,\n label = \"Download desktop app\",\n downloadLabel = label,\n platformMap = {},\n className,\n variant = \"default\",\n size = \"default\",\n}: ElectronDownloadLinkProps) {\n if (hidden) return null;\n\n const platform = detectElectronPlatform();\n const href = buildDownloadUrl(\n downloadBaseUrl,\n artifactName,\n platform,\n platformMap,\n );\n\n return (\n <Button\n variant={variant}\n size={size}\n className={cn(\"inline-flex items-center gap-2\", className)}\n asChild\n >\n <a\n href={href}\n download\n aria-label={`${downloadLabel}${platform !== \"unknown\" ? ` for ${platform}` : \"\"}`}\n >\n <Download className=\"h-4 w-4\" aria-hidden=\"true\" />\n {label}\n </a>\n </Button>\n );\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface LaunchCountdownProps {
4
+ /** Launch moment as epoch milliseconds. */
5
+ launchDate: number;
6
+ /** Optional accessible label describing the launch moment. */
7
+ launchLabel?: string;
8
+ /** Message shown once the countdown reaches zero. */
9
+ launchedMessage?: string;
10
+ /** Optional additional CSS class for the outer container. */
11
+ className?: string;
12
+ }
13
+ declare function LaunchCountdown({ launchDate, launchLabel, launchedMessage, className, }: LaunchCountdownProps): react_jsx_runtime.JSX.Element;
14
+
15
+ export { LaunchCountdown, type LaunchCountdownProps };
@@ -0,0 +1,15 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface LaunchCountdownProps {
4
+ /** Launch moment as epoch milliseconds. */
5
+ launchDate: number;
6
+ /** Optional accessible label describing the launch moment. */
7
+ launchLabel?: string;
8
+ /** Message shown once the countdown reaches zero. */
9
+ launchedMessage?: string;
10
+ /** Optional additional CSS class for the outer container. */
11
+ className?: string;
12
+ }
13
+ declare function LaunchCountdown({ launchDate, launchLabel, launchedMessage, className, }: LaunchCountdownProps): react_jsx_runtime.JSX.Element;
14
+
15
+ export { LaunchCountdown, type LaunchCountdownProps };
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var lucideReact = require('lucide-react');
5
+ var clsx = require('clsx');
6
+ var tailwindMerge = require('tailwind-merge');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+
9
+ function cn(...inputs) {
10
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
11
+ }
12
+ function splitRemaining(ms) {
13
+ const clamped = Math.max(0, ms);
14
+ const totalSeconds = Math.floor(clamped / 1e3);
15
+ return {
16
+ days: Math.floor(totalSeconds / 86400),
17
+ hours: Math.floor(totalSeconds % 86400 / 3600),
18
+ minutes: Math.floor(totalSeconds % 3600 / 60),
19
+ seconds: totalSeconds % 60
20
+ };
21
+ }
22
+ function padTwo(n) {
23
+ return String(n).padStart(2, "0");
24
+ }
25
+ function LaunchCountdown({
26
+ launchDate,
27
+ launchLabel,
28
+ launchedMessage = "We have launched!",
29
+ className
30
+ }) {
31
+ const [now, setNow] = react.useState(() => Date.now());
32
+ const remainingMs = launchDate - now;
33
+ const hasLaunched = remainingMs <= 0;
34
+ react.useEffect(() => {
35
+ if (hasLaunched) return;
36
+ const interval = setInterval(() => {
37
+ setNow(Date.now());
38
+ }, 1e3);
39
+ return () => clearInterval(interval);
40
+ }, [hasLaunched]);
41
+ const { days, hours, minutes, seconds } = splitRemaining(remainingMs);
42
+ const cells = [
43
+ { label: "Days", value: padTwo(Math.min(days, 99)) },
44
+ { label: "Hours", value: padTwo(hours) },
45
+ { label: "Minutes", value: padTwo(minutes) },
46
+ { label: "Seconds", value: padTwo(seconds) }
47
+ ];
48
+ return /* @__PURE__ */ jsxRuntime.jsxs(
49
+ "div",
50
+ {
51
+ className: cn("inline-flex flex-col gap-3", className),
52
+ role: "timer",
53
+ "aria-label": launchLabel ?? (hasLaunched ? launchedMessage : `Countdown to launch`),
54
+ children: [
55
+ !hasLaunched && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono uppercase tracking-wider text-muted-foreground", children: "Launching in" }),
56
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2 sm:gap-3", children: hasLaunched ? /* @__PURE__ */ jsxRuntime.jsxs(
57
+ "div",
58
+ {
59
+ className: cn(
60
+ "flex min-w-[15rem] items-center justify-center gap-3 rounded-lg border border-primary/30 bg-primary/10 px-6 py-4",
61
+ "text-center"
62
+ ),
63
+ children: [
64
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Rocket, { className: "h-5 w-5 text-primary", "aria-hidden": "true" }),
65
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-lg font-semibold text-foreground", children: launchedMessage })
66
+ ]
67
+ }
68
+ ) : cells.map((cell) => /* @__PURE__ */ jsxRuntime.jsxs(
69
+ "div",
70
+ {
71
+ className: cn(
72
+ "flex min-w-[3.75rem] flex-col items-center rounded-lg border border-border/60 bg-card px-3 py-2 sm:min-w-[4.5rem] sm:px-4 sm:py-3"
73
+ ),
74
+ children: [
75
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-2xl font-bold tabular-nums text-foreground sm:text-3xl", children: cell.value }),
76
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground sm:text-xs", children: cell.label })
77
+ ]
78
+ },
79
+ cell.label
80
+ )) }),
81
+ !hasLaunched && launchLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: launchLabel })
82
+ ]
83
+ }
84
+ );
85
+ }
86
+
87
+ exports.LaunchCountdown = LaunchCountdown;
88
+ //# sourceMappingURL=launch-countdown.js.map
89
+ //# sourceMappingURL=launch-countdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/cn.ts","../../src/components/launch-countdown.tsx"],"names":["twMerge","clsx","useState","useEffect","jsxs","jsx","Rocket"],"mappings":"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","file":"launch-countdown.js","sourcesContent":["/**\n * Utility function to merge Tailwind CSS classes\n * Combines clsx and tailwind-merge for optimal class handling\n */\n\nimport { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge multiple class names intelligently\n * - Handles conditional classes via clsx\n * - Deduplicates and resolves Tailwind conflicts via tailwind-merge\n *\n * @example\n * ```ts\n * cn('px-4 py-2', 'bg-blue-500', { 'text-white': isActive })\n * // => 'px-4 py-2 bg-blue-500 text-white' (if isActive is true)\n * ```\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\n/**\n * LaunchCountdown Component\n *\n * Reusable countdown timer counting down to a `launchDate` (epoch ms). Renders\n * a fixed-size grid of days / hours / minutes / seconds and flips to a\n * \"Launched\" message once the moment passes. Uses semantic theme tokens so it\n * works in light, dark, and high-contrast themes.\n *\n * @example\n * ```tsx\n * import { LaunchCountdown } from '@burdenoff/website-sdk'\n *\n * <LaunchCountdown launchDate={Date.UTC(2026, 7, 15, 0, 0, 0)} />\n * ```\n */\n\nimport { useEffect, useState } from \"react\";\n\nimport { Rocket } from \"lucide-react\";\n\nimport { cn } from \"../utils/cn\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface LaunchCountdownProps {\n /** Launch moment as epoch milliseconds. */\n launchDate: number;\n /** Optional accessible label describing the launch moment. */\n launchLabel?: string;\n /** Message shown once the countdown reaches zero. */\n launchedMessage?: string;\n /** Optional additional CSS class for the outer container. */\n className?: string;\n}\n\ninterface Remaining {\n days: number;\n hours: number;\n minutes: number;\n seconds: number;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction splitRemaining(ms: number): Remaining {\n const clamped = Math.max(0, ms);\n const totalSeconds = Math.floor(clamped / 1000);\n return {\n days: Math.floor(totalSeconds / 86400),\n hours: Math.floor((totalSeconds % 86400) / 3600),\n minutes: Math.floor((totalSeconds % 3600) / 60),\n seconds: totalSeconds % 60,\n };\n}\n\nfunction padTwo(n: number): string {\n return String(n).padStart(2, \"0\");\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function LaunchCountdown({\n launchDate,\n launchLabel,\n launchedMessage = \"We have launched!\",\n className,\n}: LaunchCountdownProps) {\n const [now, setNow] = useState(() => Date.now());\n const remainingMs = launchDate - now;\n const hasLaunched = remainingMs <= 0;\n\n useEffect(() => {\n if (hasLaunched) return;\n\n const interval = setInterval(() => {\n setNow(Date.now());\n }, 1000);\n\n return () => clearInterval(interval);\n }, [hasLaunched]);\n\n const { days, hours, minutes, seconds } = splitRemaining(remainingMs);\n\n const cells: Array<{ label: string; value: string }> = [\n { label: \"Days\", value: padTwo(Math.min(days, 99)) },\n { label: \"Hours\", value: padTwo(hours) },\n { label: \"Minutes\", value: padTwo(minutes) },\n { label: \"Seconds\", value: padTwo(seconds) },\n ];\n\n return (\n <div\n className={cn(\"inline-flex flex-col gap-3\", className)}\n role=\"timer\"\n aria-label={\n launchLabel ?? (hasLaunched ? launchedMessage : `Countdown to launch`)\n }\n >\n {!hasLaunched && (\n <span className=\"text-xs font-mono uppercase tracking-wider text-muted-foreground\">\n Launching in\n </span>\n )}\n\n <div className=\"flex gap-2 sm:gap-3\">\n {hasLaunched ? (\n <div\n className={cn(\n \"flex min-w-[15rem] items-center justify-center gap-3 rounded-lg border border-primary/30 bg-primary/10 px-6 py-4\",\n \"text-center\",\n )}\n >\n <Rocket className=\"h-5 w-5 text-primary\" aria-hidden=\"true\" />\n <span className=\"text-lg font-semibold text-foreground\">\n {launchedMessage}\n </span>\n </div>\n ) : (\n cells.map((cell) => (\n <div\n key={cell.label}\n className={cn(\n \"flex min-w-[3.75rem] flex-col items-center rounded-lg border border-border/60 bg-card px-3 py-2 sm:min-w-[4.5rem] sm:px-4 sm:py-3\",\n )}\n >\n <span className=\"font-mono text-2xl font-bold tabular-nums text-foreground sm:text-3xl\">\n {cell.value}\n </span>\n <span className=\"mt-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground sm:text-xs\">\n {cell.label}\n </span>\n </div>\n ))\n )}\n </div>\n\n {!hasLaunched && launchLabel && (\n <span className=\"text-sm text-muted-foreground\">{launchLabel}</span>\n )}\n </div>\n );\n}\n"]}
@@ -0,0 +1,87 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { Rocket } from 'lucide-react';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ function cn(...inputs) {
8
+ return twMerge(clsx(inputs));
9
+ }
10
+ function splitRemaining(ms) {
11
+ const clamped = Math.max(0, ms);
12
+ const totalSeconds = Math.floor(clamped / 1e3);
13
+ return {
14
+ days: Math.floor(totalSeconds / 86400),
15
+ hours: Math.floor(totalSeconds % 86400 / 3600),
16
+ minutes: Math.floor(totalSeconds % 3600 / 60),
17
+ seconds: totalSeconds % 60
18
+ };
19
+ }
20
+ function padTwo(n) {
21
+ return String(n).padStart(2, "0");
22
+ }
23
+ function LaunchCountdown({
24
+ launchDate,
25
+ launchLabel,
26
+ launchedMessage = "We have launched!",
27
+ className
28
+ }) {
29
+ const [now, setNow] = useState(() => Date.now());
30
+ const remainingMs = launchDate - now;
31
+ const hasLaunched = remainingMs <= 0;
32
+ useEffect(() => {
33
+ if (hasLaunched) return;
34
+ const interval = setInterval(() => {
35
+ setNow(Date.now());
36
+ }, 1e3);
37
+ return () => clearInterval(interval);
38
+ }, [hasLaunched]);
39
+ const { days, hours, minutes, seconds } = splitRemaining(remainingMs);
40
+ const cells = [
41
+ { label: "Days", value: padTwo(Math.min(days, 99)) },
42
+ { label: "Hours", value: padTwo(hours) },
43
+ { label: "Minutes", value: padTwo(minutes) },
44
+ { label: "Seconds", value: padTwo(seconds) }
45
+ ];
46
+ return /* @__PURE__ */ jsxs(
47
+ "div",
48
+ {
49
+ className: cn("inline-flex flex-col gap-3", className),
50
+ role: "timer",
51
+ "aria-label": launchLabel ?? (hasLaunched ? launchedMessage : `Countdown to launch`),
52
+ children: [
53
+ !hasLaunched && /* @__PURE__ */ jsx("span", { className: "text-xs font-mono uppercase tracking-wider text-muted-foreground", children: "Launching in" }),
54
+ /* @__PURE__ */ jsx("div", { className: "flex gap-2 sm:gap-3", children: hasLaunched ? /* @__PURE__ */ jsxs(
55
+ "div",
56
+ {
57
+ className: cn(
58
+ "flex min-w-[15rem] items-center justify-center gap-3 rounded-lg border border-primary/30 bg-primary/10 px-6 py-4",
59
+ "text-center"
60
+ ),
61
+ children: [
62
+ /* @__PURE__ */ jsx(Rocket, { className: "h-5 w-5 text-primary", "aria-hidden": "true" }),
63
+ /* @__PURE__ */ jsx("span", { className: "text-lg font-semibold text-foreground", children: launchedMessage })
64
+ ]
65
+ }
66
+ ) : cells.map((cell) => /* @__PURE__ */ jsxs(
67
+ "div",
68
+ {
69
+ className: cn(
70
+ "flex min-w-[3.75rem] flex-col items-center rounded-lg border border-border/60 bg-card px-3 py-2 sm:min-w-[4.5rem] sm:px-4 sm:py-3"
71
+ ),
72
+ children: [
73
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-2xl font-bold tabular-nums text-foreground sm:text-3xl", children: cell.value }),
74
+ /* @__PURE__ */ jsx("span", { className: "mt-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground sm:text-xs", children: cell.label })
75
+ ]
76
+ },
77
+ cell.label
78
+ )) }),
79
+ !hasLaunched && launchLabel && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: launchLabel })
80
+ ]
81
+ }
82
+ );
83
+ }
84
+
85
+ export { LaunchCountdown };
86
+ //# sourceMappingURL=launch-countdown.mjs.map
87
+ //# sourceMappingURL=launch-countdown.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/cn.ts","../../src/components/launch-countdown.tsx"],"names":[],"mappings":";;;;;;AAmBO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;AC6BA,SAAS,eAAe,EAAA,EAAuB;AAC7C,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,EAAE,CAAA;AAC9B,EAAA,MAAM,YAAA,GAAe,IAAA,CAAK,KAAA,CAAM,OAAA,GAAU,GAAI,CAAA;AAC9C,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,YAAA,GAAe,KAAK,CAAA;AAAA,IACrC,KAAA,EAAO,IAAA,CAAK,KAAA,CAAO,YAAA,GAAe,QAAS,IAAI,CAAA;AAAA,IAC/C,OAAA,EAAS,IAAA,CAAK,KAAA,CAAO,YAAA,GAAe,OAAQ,EAAE,CAAA;AAAA,IAC9C,SAAS,YAAA,GAAe;AAAA,GAC1B;AACF;AAEA,SAAS,OAAO,CAAA,EAAmB;AACjC,EAAA,OAAO,MAAA,CAAO,CAAC,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAClC;AAMO,SAAS,eAAA,CAAgB;AAAA,EAC9B,UAAA;AAAA,EACA,WAAA;AAAA,EACA,eAAA,GAAkB,mBAAA;AAAA,EAClB;AACF,CAAA,EAAyB;AACvB,EAAA,MAAM,CAAC,KAAK,MAAM,CAAA,GAAI,SAAS,MAAM,IAAA,CAAK,KAAK,CAAA;AAC/C,EAAA,MAAM,cAAc,UAAA,GAAa,GAAA;AACjC,EAAA,MAAM,cAAc,WAAA,IAAe,CAAA;AAEnC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,WAAA,EAAa;AAEjB,IAAA,MAAM,QAAA,GAAW,YAAY,MAAM;AACjC,MAAA,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA;AAAA,IACnB,GAAG,GAAI,CAAA;AAEP,IAAA,OAAO,MAAM,cAAc,QAAQ,CAAA;AAAA,EACrC,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,EAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,OAAA,EAAQ,GAAI,eAAe,WAAW,CAAA;AAEpE,EAAA,MAAM,KAAA,GAAiD;AAAA,IACrD,EAAE,KAAA,EAAO,MAAA,EAAQ,KAAA,EAAO,MAAA,CAAO,KAAK,GAAA,CAAI,IAAA,EAAM,EAAE,CAAC,CAAA,EAAE;AAAA,IACnD,EAAE,KAAA,EAAO,OAAA,EAAS,KAAA,EAAO,MAAA,CAAO,KAAK,CAAA,EAAE;AAAA,IACvC,EAAE,KAAA,EAAO,SAAA,EAAW,KAAA,EAAO,MAAA,CAAO,OAAO,CAAA,EAAE;AAAA,IAC3C,EAAE,KAAA,EAAO,SAAA,EAAW,KAAA,EAAO,MAAA,CAAO,OAAO,CAAA;AAAE,GAC7C;AAEA,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,EAAA,CAAG,4BAAA,EAA8B,SAAS,CAAA;AAAA,MACrD,IAAA,EAAK,OAAA;AAAA,MACL,YAAA,EACE,WAAA,KAAgB,WAAA,GAAc,eAAA,GAAkB,CAAA,mBAAA,CAAA,CAAA;AAAA,MAGjD,QAAA,EAAA;AAAA,QAAA,CAAC,WAAA,oBACA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,oEAAmE,QAAA,EAAA,cAAA,EAEnF,CAAA;AAAA,wBAGF,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qBAAA,EACZ,QAAA,EAAA,WAAA,mBACC,IAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAW,EAAA;AAAA,cACT,kHAAA;AAAA,cACA;AAAA,aACF;AAAA,YAEA,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,MAAA,EAAA,EAAO,SAAA,EAAU,sBAAA,EAAuB,aAAA,EAAY,MAAA,EAAO,CAAA;AAAA,8BAC5D,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uCAAA,EACb,QAAA,EAAA,eAAA,EACH;AAAA;AAAA;AAAA,SACF,GAEA,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,qBACT,IAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YAEC,SAAA,EAAW,EAAA;AAAA,cACT;AAAA,aACF;AAAA,YAEA,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uEAAA,EACb,QAAA,EAAA,IAAA,CAAK,KAAA,EACR,CAAA;AAAA,8BACA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uFAAA,EACb,eAAK,KAAA,EACR;AAAA;AAAA,WAAA;AAAA,UAVK,IAAA,CAAK;AAAA,SAYb,CAAA,EAEL,CAAA;AAAA,QAEC,CAAC,WAAA,IAAe,WAAA,wBACd,MAAA,EAAA,EAAK,SAAA,EAAU,iCAAiC,QAAA,EAAA,WAAA,EAAY;AAAA;AAAA;AAAA,GAEjE;AAEJ","file":"launch-countdown.mjs","sourcesContent":["/**\n * Utility function to merge Tailwind CSS classes\n * Combines clsx and tailwind-merge for optimal class handling\n */\n\nimport { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge multiple class names intelligently\n * - Handles conditional classes via clsx\n * - Deduplicates and resolves Tailwind conflicts via tailwind-merge\n *\n * @example\n * ```ts\n * cn('px-4 py-2', 'bg-blue-500', { 'text-white': isActive })\n * // => 'px-4 py-2 bg-blue-500 text-white' (if isActive is true)\n * ```\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\n/**\n * LaunchCountdown Component\n *\n * Reusable countdown timer counting down to a `launchDate` (epoch ms). Renders\n * a fixed-size grid of days / hours / minutes / seconds and flips to a\n * \"Launched\" message once the moment passes. Uses semantic theme tokens so it\n * works in light, dark, and high-contrast themes.\n *\n * @example\n * ```tsx\n * import { LaunchCountdown } from '@burdenoff/website-sdk'\n *\n * <LaunchCountdown launchDate={Date.UTC(2026, 7, 15, 0, 0, 0)} />\n * ```\n */\n\nimport { useEffect, useState } from \"react\";\n\nimport { Rocket } from \"lucide-react\";\n\nimport { cn } from \"../utils/cn\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface LaunchCountdownProps {\n /** Launch moment as epoch milliseconds. */\n launchDate: number;\n /** Optional accessible label describing the launch moment. */\n launchLabel?: string;\n /** Message shown once the countdown reaches zero. */\n launchedMessage?: string;\n /** Optional additional CSS class for the outer container. */\n className?: string;\n}\n\ninterface Remaining {\n days: number;\n hours: number;\n minutes: number;\n seconds: number;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction splitRemaining(ms: number): Remaining {\n const clamped = Math.max(0, ms);\n const totalSeconds = Math.floor(clamped / 1000);\n return {\n days: Math.floor(totalSeconds / 86400),\n hours: Math.floor((totalSeconds % 86400) / 3600),\n minutes: Math.floor((totalSeconds % 3600) / 60),\n seconds: totalSeconds % 60,\n };\n}\n\nfunction padTwo(n: number): string {\n return String(n).padStart(2, \"0\");\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function LaunchCountdown({\n launchDate,\n launchLabel,\n launchedMessage = \"We have launched!\",\n className,\n}: LaunchCountdownProps) {\n const [now, setNow] = useState(() => Date.now());\n const remainingMs = launchDate - now;\n const hasLaunched = remainingMs <= 0;\n\n useEffect(() => {\n if (hasLaunched) return;\n\n const interval = setInterval(() => {\n setNow(Date.now());\n }, 1000);\n\n return () => clearInterval(interval);\n }, [hasLaunched]);\n\n const { days, hours, minutes, seconds } = splitRemaining(remainingMs);\n\n const cells: Array<{ label: string; value: string }> = [\n { label: \"Days\", value: padTwo(Math.min(days, 99)) },\n { label: \"Hours\", value: padTwo(hours) },\n { label: \"Minutes\", value: padTwo(minutes) },\n { label: \"Seconds\", value: padTwo(seconds) },\n ];\n\n return (\n <div\n className={cn(\"inline-flex flex-col gap-3\", className)}\n role=\"timer\"\n aria-label={\n launchLabel ?? (hasLaunched ? launchedMessage : `Countdown to launch`)\n }\n >\n {!hasLaunched && (\n <span className=\"text-xs font-mono uppercase tracking-wider text-muted-foreground\">\n Launching in\n </span>\n )}\n\n <div className=\"flex gap-2 sm:gap-3\">\n {hasLaunched ? (\n <div\n className={cn(\n \"flex min-w-[15rem] items-center justify-center gap-3 rounded-lg border border-primary/30 bg-primary/10 px-6 py-4\",\n \"text-center\",\n )}\n >\n <Rocket className=\"h-5 w-5 text-primary\" aria-hidden=\"true\" />\n <span className=\"text-lg font-semibold text-foreground\">\n {launchedMessage}\n </span>\n </div>\n ) : (\n cells.map((cell) => (\n <div\n key={cell.label}\n className={cn(\n \"flex min-w-[3.75rem] flex-col items-center rounded-lg border border-border/60 bg-card px-3 py-2 sm:min-w-[4.5rem] sm:px-4 sm:py-3\",\n )}\n >\n <span className=\"font-mono text-2xl font-bold tabular-nums text-foreground sm:text-3xl\">\n {cell.value}\n </span>\n <span className=\"mt-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground sm:text-xs\">\n {cell.label}\n </span>\n </div>\n ))\n )}\n </div>\n\n {!hasLaunched && launchLabel && (\n <span className=\"text-sm text-muted-foreground\">{launchLabel}</span>\n )}\n </div>\n );\n}\n"]}
@@ -0,0 +1,37 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ComponentType, ReactNode } from 'react';
3
+
4
+ interface ProblemSolved {
5
+ /** The pain point or before-state. */
6
+ problem: string;
7
+ /** How the product addresses the pain point. */
8
+ solution: string;
9
+ /** Lucide-style icon component. */
10
+ icon: ComponentType<{
11
+ className?: string;
12
+ "aria-hidden"?: boolean;
13
+ }>;
14
+ /** Optional use-case story slug for linking to /use-cases/:slug. */
15
+ storySlug?: string;
16
+ }
17
+ interface ProblemsSolvedSectionProps {
18
+ /** Section eyebrow text. */
19
+ eyebrow?: string;
20
+ /** Section heading. */
21
+ title?: string;
22
+ /** Section subtitle. */
23
+ subtitle?: string;
24
+ /** Problem/solution pairs to render. */
25
+ problems: ProblemSolved[];
26
+ /** Base path for use-case story links. */
27
+ storyBasePath?: string;
28
+ /** Label for story links. */
29
+ storyLabel?: string;
30
+ /** Optional additional CSS class for the outer section. */
31
+ className?: string;
32
+ /** Optional child element rendered at the bottom of the section. */
33
+ children?: ReactNode;
34
+ }
35
+ declare function ProblemsSolvedSection({ eyebrow, title, subtitle, problems, storyBasePath, storyLabel, className, children, }: ProblemsSolvedSectionProps): react_jsx_runtime.JSX.Element;
36
+
37
+ export { type ProblemSolved, ProblemsSolvedSection, type ProblemsSolvedSectionProps };