@alkimi.org/ui-kit 0.1.13 → 0.1.14

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.
package/README.npm.md CHANGED
@@ -10,95 +10,59 @@ npm install @alkimi.org/ui-kit
10
10
 
11
11
  ## Setup
12
12
 
13
- ### 1. Install Tailwind CSS
13
+ ### 1. Install Tailwind CSS v4
14
14
 
15
- If you haven't already, install Tailwind CSS in your project:
15
+ If you haven't already, install Tailwind CSS v4 and its dependencies in your project:
16
16
 
17
17
  ```bash
18
- npm install -D tailwindcss postcss autoprefixer
19
- npx tailwindcss init -p
18
+ npm install -D tailwindcss@next @tailwindcss/postcss@next postcss autoprefixer
20
19
  ```
21
20
 
22
- ### 2. Configure Tailwind
21
+ ### 2. Configure PostCSS
23
22
 
24
- Update your `tailwind.config.js` to include the library's content:
23
+ Create or update your `postcss.config.js`:
24
+
25
+ ```js
26
+ module.exports = {
27
+ plugins: {
28
+ "@tailwindcss/postcss": {},
29
+ autoprefixer: {},
30
+ },
31
+ }
32
+ ```
33
+
34
+ ### 3. Configure Tailwind
35
+
36
+ Create a `tailwind.config.js` to specify your content paths:
25
37
 
26
38
  ```js
27
39
  /** @type {import('tailwindcss').Config} */
28
40
  module.exports = {
29
- darkMode: ["class"],
30
41
  content: [
42
+ // Scans YOUR project files for Tailwind classes
31
43
  "./src/**/*.{js,jsx,ts,tsx}",
44
+ "./app/**/*.{js,jsx,ts,tsx}", // If using Next.js App Router
45
+ "./pages/**/*.{js,jsx,ts,tsx}", // If using Next.js Pages Router
46
+ "./components/**/*.{js,jsx,ts,tsx}",
47
+
48
+ // Scans the UI Kit's components for their Tailwind classes
49
+ // (Required for library components to be styled correctly)
32
50
  "./node_modules/@alkimi.org/ui-kit/dist/**/*.{js,mjs}",
33
51
  ],
34
- theme: {
35
- fontSize: {
36
- xs: ['0.75rem', '1rem'],
37
- sm: ['12px', '1.25rem'],
38
- base: ['14px', '1.5rem'],
39
- lg: ['1.125rem', '1.75rem'],
40
- xl: ['1.25rem', '1.75rem'],
41
- '2xl': ['1.5rem', '2rem'],
42
- '3xl': ['1.875rem', '2.25rem'],
43
- '4xl': ['2.25rem', '2.5rem'],
44
- '5xl': ['3rem', '1'],
45
- '6xl': ['3.75rem', '1'],
46
- '7xl': ['4.5rem', '1'],
47
- '8xl': ['6rem', '1'],
48
- '9xl': ['8rem', '1'],
49
- },
50
- extend: {
51
- colors: {
52
- border: "hsl(var(--border))",
53
- input: "hsl(var(--input))",
54
- ring: "hsl(var(--ring))",
55
- background: "hsl(var(--background))",
56
- foreground: "hsl(var(--foreground))",
57
- primary: {
58
- DEFAULT: "hsl(var(--primary))",
59
- foreground: "hsl(var(--primary-foreground))",
60
- },
61
- secondary: {
62
- DEFAULT: "hsl(var(--secondary))",
63
- foreground: "hsl(var(--secondary-foreground))",
64
- },
65
- destructive: {
66
- DEFAULT: "hsl(var(--destructive))",
67
- foreground: "hsl(var(--destructive-foreground))",
68
- },
69
- muted: {
70
- DEFAULT: "hsl(var(--muted))",
71
- foreground: "hsl(var(--muted-foreground))",
72
- },
73
- accent: {
74
- DEFAULT: "hsl(var(--accent))",
75
- foreground: "hsl(var(--accent-foreground))",
76
- },
77
- popover: {
78
- DEFAULT: "hsl(var(--popover))",
79
- foreground: "hsl(var(--popover-foreground))",
80
- },
81
- card: {
82
- DEFAULT: "hsl(var(--card))",
83
- foreground: "hsl(var(--card-foreground))",
84
- },
85
- },
86
- borderRadius: {
87
- DEFAULT: "var(--radius)",
88
- lg: "calc(var(--radius) + 2px)",
89
- md: "var(--radius)",
90
- sm: "calc(var(--radius) - 2px)",
91
- xl: "calc(var(--radius) + 4px)",
92
- "2xl": "calc(var(--radius) + 6px)",
93
- "3xl": "3.75rem",
94
- },
95
- },
96
- },
97
- plugins: [require("tailwindcss-animate")],
98
52
  }
99
53
  ```
100
54
 
101
- ### 3. Import Styles
55
+ **Important**: The second path (`node_modules/@alkimi.org/ui-kit/dist/...`) is **required** for the library components to display correctly. It tells Tailwind to scan the library's compiled files and generate CSS for the utility classes used inside the components (like `bg-primary`, `rounded-3xl`, etc.).
56
+
57
+ ### 4. Import Tailwind in Your CSS
58
+
59
+ Create or update your main CSS file (e.g., `app.css` or `globals.css`):
60
+
61
+ ```css
62
+ @import "tailwindcss";
63
+ ```
64
+
65
+ ### 5. Import Styles
102
66
 
103
67
  Import the library's CSS in your main application file (e.g., `App.tsx` or `index.tsx`):
104
68
 
@@ -106,40 +70,100 @@ Import the library's CSS in your main application file (e.g., `App.tsx` or `inde
106
70
  import "@alkimi.org/ui-kit/styles.css"
107
71
  ```
108
72
 
109
- Or if you prefer, add the CSS variables to your own CSS file:
73
+ ## Customizing Colors
74
+
75
+ The library uses CSS variables for colors, making it easy to customize. You have two approaches:
76
+
77
+ ### Option A: Override CSS Variables (Recommended)
78
+
79
+ Add custom color values to your CSS file **after** importing the library styles:
110
80
 
111
81
  ```css
82
+ /* In your app.css or globals.css */
83
+ @import "tailwindcss";
84
+ @import "@alkimi.org/ui-kit/styles.css";
85
+
86
+ /* 👇 Override the library's default colors */
87
+ @layer base {
88
+ :root {
89
+ /* Your custom colors (HSL format: Hue Saturation% Lightness%) */
90
+ --primary: 220 100% 50%; /* Custom blue primary color */
91
+ --primary-foreground: 0 0% 100%; /* White text on primary */
92
+ --secondary: 280 60% 60%; /* Custom purple secondary */
93
+ --background: 0 0% 100%; /* White background */
94
+ --foreground: 0 0% 0%; /* Black text */
95
+
96
+ /* You can override any of these variables:
97
+ --card, --card-foreground
98
+ --popover, --popover-foreground
99
+ --muted, --muted-foreground
100
+ --accent, --accent-foreground
101
+ --destructive, --destructive-foreground
102
+ --border, --input, --ring
103
+ --radius (border radius)
104
+ */
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### Option B: Don't Import Library CSS (Manual Setup)
110
+
111
+ If you want complete control, skip importing the library CSS and define all variables yourself:
112
+
113
+ ```css
114
+ /* In your app.css or globals.css */
115
+ @import "tailwindcss";
116
+
112
117
  @layer base {
113
118
  :root {
114
- /* Dark Mode Theme (Default) */
115
- --background: 240 10% 4%;
116
- --foreground: 144 100% 97%;
117
- --card: 0 0% 3.5%;
118
- --card-foreground: 140 100% 97.1%;
119
- --popover: 0 0% 3.5%;
120
- --popover-foreground: 140 100% 97.1%;
121
- --primary: 140 100% 97.1%;
122
- --primary-foreground: 240 6% 10%;
123
- --secondary: 240 4% 16%;
124
- --secondary-foreground: 140 100% 97.1%;
125
- --muted: 240 4% 16%;
126
- --muted-foreground: 144 4.3% 54.9%;
127
- --accent: 0 0% 15.3%;
128
- --accent-foreground: 140 100% 97.1%;
129
- --destructive: 0 62.8% 30.6%;
130
- --destructive-foreground: 140 100% 97.1%;
131
- --border: 240 3.7% 27.6%;
132
- --input: 240 3.7% 27.6%;
133
- --ring: 140 100% 97.1%;
134
- --radius: 0.625rem;
119
+ /* Define ALL color variables used by the library */
120
+ --background: 0 0% 100%;
121
+ --foreground: 0 0% 0%;
122
+ --card: 0 0% 100%;
123
+ --card-foreground: 0 0% 0%;
124
+ --popover: 0 0% 100%;
125
+ --popover-foreground: 0 0% 0%;
126
+ --primary: 220 100% 50%;
127
+ --primary-foreground: 0 0% 100%;
128
+ --secondary: 280 60% 60%;
129
+ --secondary-foreground: 0 0% 100%;
130
+ --muted: 0 0% 96%;
131
+ --muted-foreground: 0 0% 45%;
132
+ --accent: 0 0% 96%;
133
+ --accent-foreground: 0 0% 0%;
134
+ --destructive: 0 84% 60%;
135
+ --destructive-foreground: 0 0% 100%;
136
+ --border: 0 0% 90%;
137
+ --input: 0 0% 90%;
138
+ --ring: 220 100% 50%;
139
+ --radius: 0.5rem;
135
140
  }
136
141
 
137
142
  body {
138
- font-size: 14px;
143
+ @apply bg-background text-foreground;
139
144
  }
140
145
  }
141
146
  ```
142
147
 
148
+ **Note**: When using Option B, don't import `@alkimi.org/ui-kit/styles.css` in your code.
149
+
150
+ ## Typography & Sizing
151
+
152
+ The library uses **rem-based sizing** with a base font size of **1rem (16px)**. All font sizes use rem units for better accessibility and scalability:
153
+
154
+ - **Base text**: 1rem (16px)
155
+ - **Small text**: 0.875rem (14px) - Used in small buttons, captions, etc.
156
+ - **Large text**: 1.125rem (18px) and above
157
+
158
+ ### Button Sizes
159
+
160
+ Buttons have the following font sizes by default:
161
+ - **Small** (`size="sm"`): 0.875rem (14px)
162
+ - **Default**: 1rem (16px)
163
+ - **Large** (`size="lg"`): 1rem (16px)
164
+
165
+ You can override these sizes using Tailwind's text utility classes (e.g., `className="text-lg"`) on any component.
166
+
143
167
  ## Usage
144
168
 
145
169
  ### Button Component
@@ -255,19 +255,19 @@ var TextDecoder = ({ children, className, delay = 0 }) => {
255
255
  {
256
256
  ref: elementRef,
257
257
  "aria-label": ariaLabel,
258
- className: cn("inline-grid", className),
258
+ className: cn("inline-grid *:col-start-1 *:row-start-1", className),
259
259
  children: [
260
260
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
261
261
  "span",
262
262
  {
263
263
  className: cn(
264
- "transition-opacity duration-300 col-start-1 row-start-1",
264
+ "transition-opacity duration-300",
265
265
  hasAnimated ? "opacity-100" : "opacity-0"
266
266
  ),
267
267
  children
268
268
  }
269
269
  ),
270
- showDecoded && displayContent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "transition-opacity duration-300 col-start-1 row-start-1", children: displayContent })
270
+ showDecoded && displayContent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "transition-opacity duration-300", children: displayContent })
271
271
  ]
272
272
  }
273
273
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/TextDecoder.tsx","../../src/lib/utils.ts"],"sourcesContent":["\"use client\"\nimport { cn } from \"@/lib/utils\"\nimport React, {\n useState,\n useEffect,\n useMemo,\n useRef,\n Suspense,\n ReactNode,\n} from \"react\"\n\n// The symbols to use for the scrambling effect (moved outside to avoid recreation)\nconst SYMBOLS = [\"\\\\\", \"-\", \"?\", \"/\", \"#\", \"!\", \"_\", \"+\", \"<\", \">\"]\nconst MAX_ITERATIONS = 3 // How many times to scramble\nconst SPEED = 400 // Speed in milliseconds between scrambles\n\n// Helper function moved outside component to avoid recreation\nconst scrambleText = (input: string): string => {\n if (!input) return \"\"\n\n return input\n .split(\" \")\n .map((word: string) => {\n if (!word) return \"\"\n const randomSymbol = SYMBOLS[Math.floor(Math.random() * SYMBOLS.length)]\n return randomSymbol.repeat(word.length)\n })\n .join(\" \")\n}\n\n// Extract text content from React nodes recursively\n// Also checks common text props like 'text', 'label', 'title', etc.\nconst extractText = (node: ReactNode): string => {\n if (typeof node === \"string\" || typeof node === \"number\") {\n return String(node)\n }\n\n if (Array.isArray(node)) {\n return node.map(extractText).join(\"\")\n }\n\n if (React.isValidElement(node)) {\n const props = node.props as {\n children?: ReactNode\n text?: string\n label?: string\n title?: string\n [key: string]: unknown\n }\n\n let result = \"\"\n\n // Extract text from props first (in order: text, label, title)\n if (typeof props.text === \"string\") {\n result += props.text\n }\n if (typeof props.label === \"string\") {\n result += props.label\n }\n if (typeof props.title === \"string\") {\n result += props.title\n }\n\n // Then extract from children\n if (props.children) {\n result += extractText(props.children)\n }\n\n return result\n }\n\n return \"\"\n}\n\n// Clone React nodes and replace text content with scrambled text\n// This function maps scrambled text back to the original structure\nconst scrambleReactNode = (\n node: ReactNode,\n scrambledText: string,\n textStartIndex: { current: number }\n): ReactNode => {\n if (typeof node === \"string\" || typeof node === \"number\") {\n const text = String(node)\n // For each character in the original text, take the corresponding scrambled character\n // This preserves the length and structure\n let scrambled = \"\"\n for (let i = 0; i < text.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n // Fallback if scrambled text is shorter (shouldn't happen, but safety check)\n scrambled += text[i]\n }\n }\n return scrambled\n }\n\n if (Array.isArray(node)) {\n return node.map((child, index) => {\n const scrambledChild = scrambleReactNode(\n child,\n scrambledText,\n textStartIndex\n )\n // Preserve original key if it exists, otherwise add one for React elements\n if (React.isValidElement(scrambledChild)) {\n const originalKey = React.isValidElement(child) ? child.key : null\n if (scrambledChild.key == null && originalKey == null) {\n return React.cloneElement(scrambledChild, { key: `decoded-${index}` })\n }\n // If original had a key but scrambled doesn't, preserve it\n if (scrambledChild.key == null && originalKey != null) {\n return React.cloneElement(scrambledChild, { key: originalKey })\n }\n }\n return scrambledChild\n })\n }\n\n if (React.isValidElement(node)) {\n // Regular element handling - works for all components\n const props = node.props as {\n children?: ReactNode\n text?: string\n label?: string\n title?: string\n [key: string]: unknown\n }\n const newProps = { ...props }\n\n // Handle text props in the same order as extraction (text, label, title)\n if (typeof props.text === \"string\") {\n const text = props.text\n let scrambled = \"\"\n for (let i = 0; i < text.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n scrambled += text[i]\n }\n }\n newProps.text = scrambled\n }\n\n if (typeof props.label === \"string\") {\n const label = props.label\n let scrambled = \"\"\n for (let i = 0; i < label.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n scrambled += label[i]\n }\n }\n newProps.label = scrambled\n }\n\n if (typeof props.title === \"string\") {\n const title = props.title\n let scrambled = \"\"\n for (let i = 0; i < title.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n scrambled += title[i]\n }\n }\n newProps.title = scrambled\n }\n\n // Handle children after props\n if (props.children) {\n newProps.children = scrambleReactNode(\n props.children,\n scrambledText,\n textStartIndex\n )\n }\n return React.cloneElement(node, newProps)\n }\n\n return node\n}\n\nexport interface TextDecoderProps {\n children: ReactNode\n className?: string\n delay?: number // Delay in milliseconds before starting animation\n}\n\nconst TextDecoder = ({ children, className, delay = 0 }: TextDecoderProps) => {\n const [displayContent, setDisplayContent] = useState<ReactNode>(null)\n const [hasAnimated, setHasAnimated] = useState(false)\n const [showDecoded, setShowDecoded] = useState(true)\n const elementRef = useRef<HTMLSpanElement>(null)\n\n // Extract full text for scrambling\n const fullText = useMemo(() => extractText(children), [children])\n\n // Memoize initial scrambled text\n const initialScrambled = useMemo(() => scrambleText(fullText), [fullText])\n\n // Initialize with scrambled content to prevent layout shift\n useEffect(() => {\n if (!hasAnimated && fullText) {\n const textStartIndex = { current: 0 }\n const scrambledContent = scrambleReactNode(\n children,\n initialScrambled,\n textStartIndex\n )\n setDisplayContent(scrambledContent)\n setShowDecoded(true)\n }\n }, [children, initialScrambled, hasAnimated, fullText])\n\n useEffect(() => {\n if (!fullText) {\n setDisplayContent(null)\n setShowDecoded(false)\n return\n }\n\n // If already animated, hide decoded and show children\n if (hasAnimated) {\n setShowDecoded(false)\n return\n }\n\n const element = elementRef.current\n if (!element) return\n\n let iteration = 0\n let intervalId: NodeJS.Timeout | null = null\n let delayTimeoutId: NodeJS.Timeout | null = null\n\n // Intersection Observer to detect when element is visible\n const observer = new IntersectionObserver(\n (entries) => {\n const entry = entries[0]\n if (entry.isIntersecting && !hasAnimated) {\n // Start animation after delay\n delayTimeoutId = setTimeout(() => {\n // 1. Initial scramble\n const textStartIndex = { current: 0 }\n const scrambledContent = scrambleReactNode(\n children,\n initialScrambled,\n textStartIndex\n )\n setDisplayContent(scrambledContent)\n setShowDecoded(true)\n\n // 2. Set up the interval\n intervalId = setInterval(() => {\n iteration++\n\n if (iteration >= MAX_ITERATIONS) {\n // Stop scrambling, fade out decoded and fade in children\n setHasAnimated(true)\n setShowDecoded(false)\n if (intervalId) clearInterval(intervalId)\n } else {\n // Continue scrambling\n const newScrambled = scrambleText(fullText)\n const textStartIndex = { current: 0 }\n const scrambledContent = scrambleReactNode(\n children,\n newScrambled,\n textStartIndex\n )\n setDisplayContent(scrambledContent)\n }\n }, SPEED)\n }, delay)\n\n // Disconnect observer once animation starts\n observer.disconnect()\n }\n },\n {\n threshold: 0.1, // Trigger when 10% of the element is visible\n }\n )\n\n observer.observe(element)\n\n // Cleanup function\n return () => {\n observer.disconnect()\n if (intervalId) clearInterval(intervalId)\n if (delayTimeoutId) clearTimeout(delayTimeoutId)\n }\n }, [children, fullText, initialScrambled, hasAnimated, delay])\n\n const ariaLabel = useMemo(() => extractText(children), [children])\n\n return (\n <span\n ref={elementRef}\n aria-label={ariaLabel} // Accessibility: Screen readers read the real text\n className={cn(\"inline-grid\", className)}\n >\n {/* Both elements in the same grid cell - prevents width changes */}\n <span\n className={cn(\n \"transition-opacity duration-300 col-start-1 row-start-1\",\n hasAnimated ? \"opacity-100\" : \"opacity-0\"\n )}\n >\n {children}\n </span>\n {/* Decoded animation overlay in the same grid cell */}\n {showDecoded && displayContent && (\n <span className=\"transition-opacity duration-300 col-start-1 row-start-1\">\n {displayContent}\n </span>\n )}\n </span>\n )\n}\n\nconst SuspenseDecodedText = (props: TextDecoderProps) => {\n return (\n <Suspense\n fallback={\n <span className={cn(\"opacity-0\", props.className)}>\n {props.children}\n </span>\n }\n >\n <TextDecoder {...props} />\n </Suspense>\n )\n}\n\nexport default SuspenseDecodedText\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAAsC;AACtC,4BAAwB;AAEjB,SAAS,MAAM,QAAsB;AAC1C,aAAO,mCAAQ,kBAAK,MAAM,CAAC;AAC7B;;;ADHA,mBAOO;AAqSH;AAlSJ,IAAM,UAAU,CAAC,MAAM,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAClE,IAAM,iBAAiB;AACvB,IAAM,QAAQ;AAGd,IAAM,eAAe,CAAC,UAA0B;AAC9C,MAAI,CAAC,MAAO,QAAO;AAEnB,SAAO,MACJ,MAAM,GAAG,EACT,IAAI,CAAC,SAAiB;AACrB,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,eAAe,QAAQ,KAAK,MAAM,KAAK,OAAO,IAAI,QAAQ,MAAM,CAAC;AACvE,WAAO,aAAa,OAAO,KAAK,MAAM;AAAA,EACxC,CAAC,EACA,KAAK,GAAG;AACb;AAIA,IAAM,cAAc,CAAC,SAA4B;AAC/C,MAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU;AACxD,WAAO,OAAO,IAAI;AAAA,EACpB;AAEA,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAO,KAAK,IAAI,WAAW,EAAE,KAAK,EAAE;AAAA,EACtC;AAEA,MAAI,aAAAA,QAAM,eAAe,IAAI,GAAG;AAC9B,UAAM,QAAQ,KAAK;AAQnB,QAAI,SAAS;AAGb,QAAI,OAAO,MAAM,SAAS,UAAU;AAClC,gBAAU,MAAM;AAAA,IAClB;AACA,QAAI,OAAO,MAAM,UAAU,UAAU;AACnC,gBAAU,MAAM;AAAA,IAClB;AACA,QAAI,OAAO,MAAM,UAAU,UAAU;AACnC,gBAAU,MAAM;AAAA,IAClB;AAGA,QAAI,MAAM,UAAU;AAClB,gBAAU,YAAY,MAAM,QAAQ;AAAA,IACtC;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAIA,IAAM,oBAAoB,CACxB,MACA,eACA,mBACc;AACd,MAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU;AACxD,UAAM,OAAO,OAAO,IAAI;AAGxB,QAAI,YAAY;AAChB,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAI,eAAe,UAAU,cAAc,QAAQ;AACjD,qBAAa,cAAc,eAAe,OAAO;AACjD,uBAAe;AAAA,MACjB,OAAO;AAEL,qBAAa,KAAK,CAAC;AAAA,MACrB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAO,KAAK,IAAI,CAAC,OAAO,UAAU;AAChC,YAAM,iBAAiB;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,UAAI,aAAAA,QAAM,eAAe,cAAc,GAAG;AACxC,cAAM,cAAc,aAAAA,QAAM,eAAe,KAAK,IAAI,MAAM,MAAM;AAC9D,YAAI,eAAe,OAAO,QAAQ,eAAe,MAAM;AACrD,iBAAO,aAAAA,QAAM,aAAa,gBAAgB,EAAE,KAAK,WAAW,KAAK,GAAG,CAAC;AAAA,QACvE;AAEA,YAAI,eAAe,OAAO,QAAQ,eAAe,MAAM;AACrD,iBAAO,aAAAA,QAAM,aAAa,gBAAgB,EAAE,KAAK,YAAY,CAAC;AAAA,QAChE;AAAA,MACF;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,MAAI,aAAAA,QAAM,eAAe,IAAI,GAAG;AAE9B,UAAM,QAAQ,KAAK;AAOnB,UAAM,WAAW,EAAE,GAAG,MAAM;AAG5B,QAAI,OAAO,MAAM,SAAS,UAAU;AAClC,YAAM,OAAO,MAAM;AACnB,UAAI,YAAY;AAChB,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,YAAI,eAAe,UAAU,cAAc,QAAQ;AACjD,uBAAa,cAAc,eAAe,OAAO;AACjD,yBAAe;AAAA,QACjB,OAAO;AACL,uBAAa,KAAK,CAAC;AAAA,QACrB;AAAA,MACF;AACA,eAAS,OAAO;AAAA,IAClB;AAEA,QAAI,OAAO,MAAM,UAAU,UAAU;AACnC,YAAM,QAAQ,MAAM;AACpB,UAAI,YAAY;AAChB,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAI,eAAe,UAAU,cAAc,QAAQ;AACjD,uBAAa,cAAc,eAAe,OAAO;AACjD,yBAAe;AAAA,QACjB,OAAO;AACL,uBAAa,MAAM,CAAC;AAAA,QACtB;AAAA,MACF;AACA,eAAS,QAAQ;AAAA,IACnB;AAEA,QAAI,OAAO,MAAM,UAAU,UAAU;AACnC,YAAM,QAAQ,MAAM;AACpB,UAAI,YAAY;AAChB,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAI,eAAe,UAAU,cAAc,QAAQ;AACjD,uBAAa,cAAc,eAAe,OAAO;AACjD,yBAAe;AAAA,QACjB,OAAO;AACL,uBAAa,MAAM,CAAC;AAAA,QACtB;AAAA,MACF;AACA,eAAS,QAAQ;AAAA,IACnB;AAGA,QAAI,MAAM,UAAU;AAClB,eAAS,WAAW;AAAA,QAClB,MAAM;AAAA,QACN;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,WAAO,aAAAA,QAAM,aAAa,MAAM,QAAQ;AAAA,EAC1C;AAEA,SAAO;AACT;AAQA,IAAM,cAAc,CAAC,EAAE,UAAU,WAAW,QAAQ,EAAE,MAAwB;AAC5E,QAAM,CAAC,gBAAgB,iBAAiB,QAAI,uBAAoB,IAAI;AACpE,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAS,KAAK;AACpD,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAS,IAAI;AACnD,QAAM,iBAAa,qBAAwB,IAAI;AAG/C,QAAM,eAAW,sBAAQ,MAAM,YAAY,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAGhE,QAAM,uBAAmB,sBAAQ,MAAM,aAAa,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAGzE,8BAAU,MAAM;AACd,QAAI,CAAC,eAAe,UAAU;AAC5B,YAAM,iBAAiB,EAAE,SAAS,EAAE;AACpC,YAAM,mBAAmB;AAAA,QACvB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,wBAAkB,gBAAgB;AAClC,qBAAe,IAAI;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,UAAU,kBAAkB,aAAa,QAAQ,CAAC;AAEtD,8BAAU,MAAM;AACd,QAAI,CAAC,UAAU;AACb,wBAAkB,IAAI;AACtB,qBAAe,KAAK;AACpB;AAAA,IACF;AAGA,QAAI,aAAa;AACf,qBAAe,KAAK;AACpB;AAAA,IACF;AAEA,UAAM,UAAU,WAAW;AAC3B,QAAI,CAAC,QAAS;AAEd,QAAI,YAAY;AAChB,QAAI,aAAoC;AACxC,QAAI,iBAAwC;AAG5C,UAAM,WAAW,IAAI;AAAA,MACnB,CAAC,YAAY;AACX,cAAM,QAAQ,QAAQ,CAAC;AACvB,YAAI,MAAM,kBAAkB,CAAC,aAAa;AAExC,2BAAiB,WAAW,MAAM;AAEhC,kBAAM,iBAAiB,EAAE,SAAS,EAAE;AACpC,kBAAM,mBAAmB;AAAA,cACvB;AAAA,cACA;AAAA,cACA;AAAA,YACF;AACA,8BAAkB,gBAAgB;AAClC,2BAAe,IAAI;AAGnB,yBAAa,YAAY,MAAM;AAC7B;AAEA,kBAAI,aAAa,gBAAgB;AAE/B,+BAAe,IAAI;AACnB,+BAAe,KAAK;AACpB,oBAAI,WAAY,eAAc,UAAU;AAAA,cAC1C,OAAO;AAEL,sBAAM,eAAe,aAAa,QAAQ;AAC1C,sBAAMC,kBAAiB,EAAE,SAAS,EAAE;AACpC,sBAAMC,oBAAmB;AAAA,kBACvB;AAAA,kBACA;AAAA,kBACAD;AAAA,gBACF;AACA,kCAAkBC,iBAAgB;AAAA,cACpC;AAAA,YACF,GAAG,KAAK;AAAA,UACV,GAAG,KAAK;AAGR,mBAAS,WAAW;AAAA,QACtB;AAAA,MACF;AAAA,MACA;AAAA,QACE,WAAW;AAAA;AAAA,MACb;AAAA,IACF;AAEA,aAAS,QAAQ,OAAO;AAGxB,WAAO,MAAM;AACX,eAAS,WAAW;AACpB,UAAI,WAAY,eAAc,UAAU;AACxC,UAAI,eAAgB,cAAa,cAAc;AAAA,IACjD;AAAA,EACF,GAAG,CAAC,UAAU,UAAU,kBAAkB,aAAa,KAAK,CAAC;AAE7D,QAAM,gBAAY,sBAAQ,MAAM,YAAY,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAEjE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,cAAY;AAAA,MACZ,WAAW,GAAG,eAAe,SAAS;AAAA,MAGtC;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,cAAc,gBAAgB;AAAA,YAChC;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QAEC,eAAe,kBACd,4CAAC,UAAK,WAAU,2DACb,0BACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAEA,IAAM,sBAAsB,CAAC,UAA4B;AACvD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UACE,4CAAC,UAAK,WAAW,GAAG,aAAa,MAAM,SAAS,GAC7C,gBAAM,UACT;AAAA,MAGF,sDAAC,eAAa,GAAG,OAAO;AAAA;AAAA,EAC1B;AAEJ;AAEA,IAAO,sBAAQ;","names":["React","textStartIndex","scrambledContent"]}
1
+ {"version":3,"sources":["../../src/components/TextDecoder.tsx","../../src/lib/utils.ts"],"sourcesContent":["\"use client\"\nimport { cn } from \"@/lib/utils\"\nimport React, {\n useState,\n useEffect,\n useMemo,\n useRef,\n Suspense,\n ReactNode,\n} from \"react\"\n\n// The symbols to use for the scrambling effect (moved outside to avoid recreation)\nconst SYMBOLS = [\"\\\\\", \"-\", \"?\", \"/\", \"#\", \"!\", \"_\", \"+\", \"<\", \">\"]\nconst MAX_ITERATIONS = 3 // How many times to scramble\nconst SPEED = 400 // Speed in milliseconds between scrambles\n\n// Helper function moved outside component to avoid recreation\nconst scrambleText = (input: string): string => {\n if (!input) return \"\"\n\n return input\n .split(\" \")\n .map((word: string) => {\n if (!word) return \"\"\n const randomSymbol = SYMBOLS[Math.floor(Math.random() * SYMBOLS.length)]\n return randomSymbol.repeat(word.length)\n })\n .join(\" \")\n}\n\n// Extract text content from React nodes recursively\n// Also checks common text props like 'text', 'label', 'title', etc.\nconst extractText = (node: ReactNode): string => {\n if (typeof node === \"string\" || typeof node === \"number\") {\n return String(node)\n }\n\n if (Array.isArray(node)) {\n return node.map(extractText).join(\"\")\n }\n\n if (React.isValidElement(node)) {\n const props = node.props as {\n children?: ReactNode\n text?: string\n label?: string\n title?: string\n [key: string]: unknown\n }\n\n let result = \"\"\n\n // Extract text from props first (in order: text, label, title)\n if (typeof props.text === \"string\") {\n result += props.text\n }\n if (typeof props.label === \"string\") {\n result += props.label\n }\n if (typeof props.title === \"string\") {\n result += props.title\n }\n\n // Then extract from children\n if (props.children) {\n result += extractText(props.children)\n }\n\n return result\n }\n\n return \"\"\n}\n\n// Clone React nodes and replace text content with scrambled text\n// This function maps scrambled text back to the original structure\nconst scrambleReactNode = (\n node: ReactNode,\n scrambledText: string,\n textStartIndex: { current: number }\n): ReactNode => {\n if (typeof node === \"string\" || typeof node === \"number\") {\n const text = String(node)\n // For each character in the original text, take the corresponding scrambled character\n // This preserves the length and structure\n let scrambled = \"\"\n for (let i = 0; i < text.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n // Fallback if scrambled text is shorter (shouldn't happen, but safety check)\n scrambled += text[i]\n }\n }\n return scrambled\n }\n\n if (Array.isArray(node)) {\n return node.map((child, index) => {\n const scrambledChild = scrambleReactNode(\n child,\n scrambledText,\n textStartIndex\n )\n // Preserve original key if it exists, otherwise add one for React elements\n if (React.isValidElement(scrambledChild)) {\n const originalKey = React.isValidElement(child) ? child.key : null\n if (scrambledChild.key == null && originalKey == null) {\n return React.cloneElement(scrambledChild, { key: `decoded-${index}` })\n }\n // If original had a key but scrambled doesn't, preserve it\n if (scrambledChild.key == null && originalKey != null) {\n return React.cloneElement(scrambledChild, { key: originalKey })\n }\n }\n return scrambledChild\n })\n }\n\n if (React.isValidElement(node)) {\n // Regular element handling - works for all components\n const props = node.props as {\n children?: ReactNode\n text?: string\n label?: string\n title?: string\n [key: string]: unknown\n }\n const newProps = { ...props }\n\n // Handle text props in the same order as extraction (text, label, title)\n if (typeof props.text === \"string\") {\n const text = props.text\n let scrambled = \"\"\n for (let i = 0; i < text.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n scrambled += text[i]\n }\n }\n newProps.text = scrambled\n }\n\n if (typeof props.label === \"string\") {\n const label = props.label\n let scrambled = \"\"\n for (let i = 0; i < label.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n scrambled += label[i]\n }\n }\n newProps.label = scrambled\n }\n\n if (typeof props.title === \"string\") {\n const title = props.title\n let scrambled = \"\"\n for (let i = 0; i < title.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n scrambled += title[i]\n }\n }\n newProps.title = scrambled\n }\n\n // Handle children after props\n if (props.children) {\n newProps.children = scrambleReactNode(\n props.children,\n scrambledText,\n textStartIndex\n )\n }\n return React.cloneElement(node, newProps)\n }\n\n return node\n}\n\nexport interface TextDecoderProps {\n children: ReactNode\n className?: string\n delay?: number // Delay in milliseconds before starting animation\n}\n\nconst TextDecoder = ({ children, className, delay = 0 }: TextDecoderProps) => {\n const [displayContent, setDisplayContent] = useState<ReactNode>(null)\n const [hasAnimated, setHasAnimated] = useState(false)\n const [showDecoded, setShowDecoded] = useState(true)\n const elementRef = useRef<HTMLSpanElement>(null)\n\n // Extract full text for scrambling\n const fullText = useMemo(() => extractText(children), [children])\n\n // Memoize initial scrambled text\n const initialScrambled = useMemo(() => scrambleText(fullText), [fullText])\n\n // Initialize with scrambled content to prevent layout shift\n useEffect(() => {\n if (!hasAnimated && fullText) {\n const textStartIndex = { current: 0 }\n const scrambledContent = scrambleReactNode(\n children,\n initialScrambled,\n textStartIndex\n )\n setDisplayContent(scrambledContent)\n setShowDecoded(true)\n }\n }, [children, initialScrambled, hasAnimated, fullText])\n\n useEffect(() => {\n if (!fullText) {\n setDisplayContent(null)\n setShowDecoded(false)\n return\n }\n\n // If already animated, hide decoded and show children\n if (hasAnimated) {\n setShowDecoded(false)\n return\n }\n\n const element = elementRef.current\n if (!element) return\n\n let iteration = 0\n let intervalId: NodeJS.Timeout | null = null\n let delayTimeoutId: NodeJS.Timeout | null = null\n\n // Intersection Observer to detect when element is visible\n const observer = new IntersectionObserver(\n (entries) => {\n const entry = entries[0]\n if (entry.isIntersecting && !hasAnimated) {\n // Start animation after delay\n delayTimeoutId = setTimeout(() => {\n // 1. Initial scramble\n const textStartIndex = { current: 0 }\n const scrambledContent = scrambleReactNode(\n children,\n initialScrambled,\n textStartIndex\n )\n setDisplayContent(scrambledContent)\n setShowDecoded(true)\n\n // 2. Set up the interval\n intervalId = setInterval(() => {\n iteration++\n\n if (iteration >= MAX_ITERATIONS) {\n // Stop scrambling, fade out decoded and fade in children\n setHasAnimated(true)\n setShowDecoded(false)\n if (intervalId) clearInterval(intervalId)\n } else {\n // Continue scrambling\n const newScrambled = scrambleText(fullText)\n const textStartIndex = { current: 0 }\n const scrambledContent = scrambleReactNode(\n children,\n newScrambled,\n textStartIndex\n )\n setDisplayContent(scrambledContent)\n }\n }, SPEED)\n }, delay)\n\n // Disconnect observer once animation starts\n observer.disconnect()\n }\n },\n {\n threshold: 0.1, // Trigger when 10% of the element is visible\n }\n )\n\n observer.observe(element)\n\n // Cleanup function\n return () => {\n observer.disconnect()\n if (intervalId) clearInterval(intervalId)\n if (delayTimeoutId) clearTimeout(delayTimeoutId)\n }\n }, [children, fullText, initialScrambled, hasAnimated, delay])\n\n const ariaLabel = useMemo(() => extractText(children), [children])\n\n return (\n <span\n ref={elementRef}\n aria-label={ariaLabel} // Accessibility: Screen readers read the real text\n className={cn(\"inline-grid *:col-start-1 *:row-start-1\", className)}\n >\n {/* Both elements in the same grid cell - prevents width changes */}\n <span\n className={cn(\n \"transition-opacity duration-300\",\n hasAnimated ? \"opacity-100\" : \"opacity-0\"\n )}\n >\n {children}\n </span>\n {/* Decoded animation overlay in the same grid cell */}\n {showDecoded && displayContent && (\n <span className=\"transition-opacity duration-300\">\n {displayContent}\n </span>\n )}\n </span>\n )\n}\n\nconst SuspenseDecodedText = (props: TextDecoderProps) => {\n return (\n <Suspense\n fallback={\n <span className={cn(\"opacity-0\", props.className)}>\n {props.children}\n </span>\n }\n >\n <TextDecoder {...props} />\n </Suspense>\n )\n}\n\nexport default SuspenseDecodedText\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"],"mappings":"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","names":["React","textStartIndex","scrambledContent"]}
@@ -226,19 +226,19 @@ var TextDecoder = ({ children, className, delay = 0 }) => {
226
226
  {
227
227
  ref: elementRef,
228
228
  "aria-label": ariaLabel,
229
- className: cn("inline-grid", className),
229
+ className: cn("inline-grid *:col-start-1 *:row-start-1", className),
230
230
  children: [
231
231
  /* @__PURE__ */ jsx(
232
232
  "span",
233
233
  {
234
234
  className: cn(
235
- "transition-opacity duration-300 col-start-1 row-start-1",
235
+ "transition-opacity duration-300",
236
236
  hasAnimated ? "opacity-100" : "opacity-0"
237
237
  ),
238
238
  children
239
239
  }
240
240
  ),
241
- showDecoded && displayContent && /* @__PURE__ */ jsx("span", { className: "transition-opacity duration-300 col-start-1 row-start-1", children: displayContent })
241
+ showDecoded && displayContent && /* @__PURE__ */ jsx("span", { className: "transition-opacity duration-300", children: displayContent })
242
242
  ]
243
243
  }
244
244
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/utils.ts","../../src/components/TextDecoder.tsx"],"sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","\"use client\"\nimport { cn } from \"@/lib/utils\"\nimport React, {\n useState,\n useEffect,\n useMemo,\n useRef,\n Suspense,\n ReactNode,\n} from \"react\"\n\n// The symbols to use for the scrambling effect (moved outside to avoid recreation)\nconst SYMBOLS = [\"\\\\\", \"-\", \"?\", \"/\", \"#\", \"!\", \"_\", \"+\", \"<\", \">\"]\nconst MAX_ITERATIONS = 3 // How many times to scramble\nconst SPEED = 400 // Speed in milliseconds between scrambles\n\n// Helper function moved outside component to avoid recreation\nconst scrambleText = (input: string): string => {\n if (!input) return \"\"\n\n return input\n .split(\" \")\n .map((word: string) => {\n if (!word) return \"\"\n const randomSymbol = SYMBOLS[Math.floor(Math.random() * SYMBOLS.length)]\n return randomSymbol.repeat(word.length)\n })\n .join(\" \")\n}\n\n// Extract text content from React nodes recursively\n// Also checks common text props like 'text', 'label', 'title', etc.\nconst extractText = (node: ReactNode): string => {\n if (typeof node === \"string\" || typeof node === \"number\") {\n return String(node)\n }\n\n if (Array.isArray(node)) {\n return node.map(extractText).join(\"\")\n }\n\n if (React.isValidElement(node)) {\n const props = node.props as {\n children?: ReactNode\n text?: string\n label?: string\n title?: string\n [key: string]: unknown\n }\n\n let result = \"\"\n\n // Extract text from props first (in order: text, label, title)\n if (typeof props.text === \"string\") {\n result += props.text\n }\n if (typeof props.label === \"string\") {\n result += props.label\n }\n if (typeof props.title === \"string\") {\n result += props.title\n }\n\n // Then extract from children\n if (props.children) {\n result += extractText(props.children)\n }\n\n return result\n }\n\n return \"\"\n}\n\n// Clone React nodes and replace text content with scrambled text\n// This function maps scrambled text back to the original structure\nconst scrambleReactNode = (\n node: ReactNode,\n scrambledText: string,\n textStartIndex: { current: number }\n): ReactNode => {\n if (typeof node === \"string\" || typeof node === \"number\") {\n const text = String(node)\n // For each character in the original text, take the corresponding scrambled character\n // This preserves the length and structure\n let scrambled = \"\"\n for (let i = 0; i < text.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n // Fallback if scrambled text is shorter (shouldn't happen, but safety check)\n scrambled += text[i]\n }\n }\n return scrambled\n }\n\n if (Array.isArray(node)) {\n return node.map((child, index) => {\n const scrambledChild = scrambleReactNode(\n child,\n scrambledText,\n textStartIndex\n )\n // Preserve original key if it exists, otherwise add one for React elements\n if (React.isValidElement(scrambledChild)) {\n const originalKey = React.isValidElement(child) ? child.key : null\n if (scrambledChild.key == null && originalKey == null) {\n return React.cloneElement(scrambledChild, { key: `decoded-${index}` })\n }\n // If original had a key but scrambled doesn't, preserve it\n if (scrambledChild.key == null && originalKey != null) {\n return React.cloneElement(scrambledChild, { key: originalKey })\n }\n }\n return scrambledChild\n })\n }\n\n if (React.isValidElement(node)) {\n // Regular element handling - works for all components\n const props = node.props as {\n children?: ReactNode\n text?: string\n label?: string\n title?: string\n [key: string]: unknown\n }\n const newProps = { ...props }\n\n // Handle text props in the same order as extraction (text, label, title)\n if (typeof props.text === \"string\") {\n const text = props.text\n let scrambled = \"\"\n for (let i = 0; i < text.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n scrambled += text[i]\n }\n }\n newProps.text = scrambled\n }\n\n if (typeof props.label === \"string\") {\n const label = props.label\n let scrambled = \"\"\n for (let i = 0; i < label.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n scrambled += label[i]\n }\n }\n newProps.label = scrambled\n }\n\n if (typeof props.title === \"string\") {\n const title = props.title\n let scrambled = \"\"\n for (let i = 0; i < title.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n scrambled += title[i]\n }\n }\n newProps.title = scrambled\n }\n\n // Handle children after props\n if (props.children) {\n newProps.children = scrambleReactNode(\n props.children,\n scrambledText,\n textStartIndex\n )\n }\n return React.cloneElement(node, newProps)\n }\n\n return node\n}\n\nexport interface TextDecoderProps {\n children: ReactNode\n className?: string\n delay?: number // Delay in milliseconds before starting animation\n}\n\nconst TextDecoder = ({ children, className, delay = 0 }: TextDecoderProps) => {\n const [displayContent, setDisplayContent] = useState<ReactNode>(null)\n const [hasAnimated, setHasAnimated] = useState(false)\n const [showDecoded, setShowDecoded] = useState(true)\n const elementRef = useRef<HTMLSpanElement>(null)\n\n // Extract full text for scrambling\n const fullText = useMemo(() => extractText(children), [children])\n\n // Memoize initial scrambled text\n const initialScrambled = useMemo(() => scrambleText(fullText), [fullText])\n\n // Initialize with scrambled content to prevent layout shift\n useEffect(() => {\n if (!hasAnimated && fullText) {\n const textStartIndex = { current: 0 }\n const scrambledContent = scrambleReactNode(\n children,\n initialScrambled,\n textStartIndex\n )\n setDisplayContent(scrambledContent)\n setShowDecoded(true)\n }\n }, [children, initialScrambled, hasAnimated, fullText])\n\n useEffect(() => {\n if (!fullText) {\n setDisplayContent(null)\n setShowDecoded(false)\n return\n }\n\n // If already animated, hide decoded and show children\n if (hasAnimated) {\n setShowDecoded(false)\n return\n }\n\n const element = elementRef.current\n if (!element) return\n\n let iteration = 0\n let intervalId: NodeJS.Timeout | null = null\n let delayTimeoutId: NodeJS.Timeout | null = null\n\n // Intersection Observer to detect when element is visible\n const observer = new IntersectionObserver(\n (entries) => {\n const entry = entries[0]\n if (entry.isIntersecting && !hasAnimated) {\n // Start animation after delay\n delayTimeoutId = setTimeout(() => {\n // 1. Initial scramble\n const textStartIndex = { current: 0 }\n const scrambledContent = scrambleReactNode(\n children,\n initialScrambled,\n textStartIndex\n )\n setDisplayContent(scrambledContent)\n setShowDecoded(true)\n\n // 2. Set up the interval\n intervalId = setInterval(() => {\n iteration++\n\n if (iteration >= MAX_ITERATIONS) {\n // Stop scrambling, fade out decoded and fade in children\n setHasAnimated(true)\n setShowDecoded(false)\n if (intervalId) clearInterval(intervalId)\n } else {\n // Continue scrambling\n const newScrambled = scrambleText(fullText)\n const textStartIndex = { current: 0 }\n const scrambledContent = scrambleReactNode(\n children,\n newScrambled,\n textStartIndex\n )\n setDisplayContent(scrambledContent)\n }\n }, SPEED)\n }, delay)\n\n // Disconnect observer once animation starts\n observer.disconnect()\n }\n },\n {\n threshold: 0.1, // Trigger when 10% of the element is visible\n }\n )\n\n observer.observe(element)\n\n // Cleanup function\n return () => {\n observer.disconnect()\n if (intervalId) clearInterval(intervalId)\n if (delayTimeoutId) clearTimeout(delayTimeoutId)\n }\n }, [children, fullText, initialScrambled, hasAnimated, delay])\n\n const ariaLabel = useMemo(() => extractText(children), [children])\n\n return (\n <span\n ref={elementRef}\n aria-label={ariaLabel} // Accessibility: Screen readers read the real text\n className={cn(\"inline-grid\", className)}\n >\n {/* Both elements in the same grid cell - prevents width changes */}\n <span\n className={cn(\n \"transition-opacity duration-300 col-start-1 row-start-1\",\n hasAnimated ? \"opacity-100\" : \"opacity-0\"\n )}\n >\n {children}\n </span>\n {/* Decoded animation overlay in the same grid cell */}\n {showDecoded && displayContent && (\n <span className=\"transition-opacity duration-300 col-start-1 row-start-1\">\n {displayContent}\n </span>\n )}\n </span>\n )\n}\n\nconst SuspenseDecodedText = (props: TextDecoderProps) => {\n return (\n <Suspense\n fallback={\n <span className={cn(\"opacity-0\", props.className)}>\n {props.children}\n </span>\n }\n >\n <TextDecoder {...props} />\n </Suspense>\n )\n}\n\nexport default SuspenseDecodedText\n"],"mappings":"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","names":["textStartIndex","scrambledContent"]}
1
+ {"version":3,"sources":["../../src/lib/utils.ts","../../src/components/TextDecoder.tsx"],"sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","\"use client\"\nimport { cn } from \"@/lib/utils\"\nimport React, {\n useState,\n useEffect,\n useMemo,\n useRef,\n Suspense,\n ReactNode,\n} from \"react\"\n\n// The symbols to use for the scrambling effect (moved outside to avoid recreation)\nconst SYMBOLS = [\"\\\\\", \"-\", \"?\", \"/\", \"#\", \"!\", \"_\", \"+\", \"<\", \">\"]\nconst MAX_ITERATIONS = 3 // How many times to scramble\nconst SPEED = 400 // Speed in milliseconds between scrambles\n\n// Helper function moved outside component to avoid recreation\nconst scrambleText = (input: string): string => {\n if (!input) return \"\"\n\n return input\n .split(\" \")\n .map((word: string) => {\n if (!word) return \"\"\n const randomSymbol = SYMBOLS[Math.floor(Math.random() * SYMBOLS.length)]\n return randomSymbol.repeat(word.length)\n })\n .join(\" \")\n}\n\n// Extract text content from React nodes recursively\n// Also checks common text props like 'text', 'label', 'title', etc.\nconst extractText = (node: ReactNode): string => {\n if (typeof node === \"string\" || typeof node === \"number\") {\n return String(node)\n }\n\n if (Array.isArray(node)) {\n return node.map(extractText).join(\"\")\n }\n\n if (React.isValidElement(node)) {\n const props = node.props as {\n children?: ReactNode\n text?: string\n label?: string\n title?: string\n [key: string]: unknown\n }\n\n let result = \"\"\n\n // Extract text from props first (in order: text, label, title)\n if (typeof props.text === \"string\") {\n result += props.text\n }\n if (typeof props.label === \"string\") {\n result += props.label\n }\n if (typeof props.title === \"string\") {\n result += props.title\n }\n\n // Then extract from children\n if (props.children) {\n result += extractText(props.children)\n }\n\n return result\n }\n\n return \"\"\n}\n\n// Clone React nodes and replace text content with scrambled text\n// This function maps scrambled text back to the original structure\nconst scrambleReactNode = (\n node: ReactNode,\n scrambledText: string,\n textStartIndex: { current: number }\n): ReactNode => {\n if (typeof node === \"string\" || typeof node === \"number\") {\n const text = String(node)\n // For each character in the original text, take the corresponding scrambled character\n // This preserves the length and structure\n let scrambled = \"\"\n for (let i = 0; i < text.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n // Fallback if scrambled text is shorter (shouldn't happen, but safety check)\n scrambled += text[i]\n }\n }\n return scrambled\n }\n\n if (Array.isArray(node)) {\n return node.map((child, index) => {\n const scrambledChild = scrambleReactNode(\n child,\n scrambledText,\n textStartIndex\n )\n // Preserve original key if it exists, otherwise add one for React elements\n if (React.isValidElement(scrambledChild)) {\n const originalKey = React.isValidElement(child) ? child.key : null\n if (scrambledChild.key == null && originalKey == null) {\n return React.cloneElement(scrambledChild, { key: `decoded-${index}` })\n }\n // If original had a key but scrambled doesn't, preserve it\n if (scrambledChild.key == null && originalKey != null) {\n return React.cloneElement(scrambledChild, { key: originalKey })\n }\n }\n return scrambledChild\n })\n }\n\n if (React.isValidElement(node)) {\n // Regular element handling - works for all components\n const props = node.props as {\n children?: ReactNode\n text?: string\n label?: string\n title?: string\n [key: string]: unknown\n }\n const newProps = { ...props }\n\n // Handle text props in the same order as extraction (text, label, title)\n if (typeof props.text === \"string\") {\n const text = props.text\n let scrambled = \"\"\n for (let i = 0; i < text.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n scrambled += text[i]\n }\n }\n newProps.text = scrambled\n }\n\n if (typeof props.label === \"string\") {\n const label = props.label\n let scrambled = \"\"\n for (let i = 0; i < label.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n scrambled += label[i]\n }\n }\n newProps.label = scrambled\n }\n\n if (typeof props.title === \"string\") {\n const title = props.title\n let scrambled = \"\"\n for (let i = 0; i < title.length; i++) {\n if (textStartIndex.current < scrambledText.length) {\n scrambled += scrambledText[textStartIndex.current]\n textStartIndex.current++\n } else {\n scrambled += title[i]\n }\n }\n newProps.title = scrambled\n }\n\n // Handle children after props\n if (props.children) {\n newProps.children = scrambleReactNode(\n props.children,\n scrambledText,\n textStartIndex\n )\n }\n return React.cloneElement(node, newProps)\n }\n\n return node\n}\n\nexport interface TextDecoderProps {\n children: ReactNode\n className?: string\n delay?: number // Delay in milliseconds before starting animation\n}\n\nconst TextDecoder = ({ children, className, delay = 0 }: TextDecoderProps) => {\n const [displayContent, setDisplayContent] = useState<ReactNode>(null)\n const [hasAnimated, setHasAnimated] = useState(false)\n const [showDecoded, setShowDecoded] = useState(true)\n const elementRef = useRef<HTMLSpanElement>(null)\n\n // Extract full text for scrambling\n const fullText = useMemo(() => extractText(children), [children])\n\n // Memoize initial scrambled text\n const initialScrambled = useMemo(() => scrambleText(fullText), [fullText])\n\n // Initialize with scrambled content to prevent layout shift\n useEffect(() => {\n if (!hasAnimated && fullText) {\n const textStartIndex = { current: 0 }\n const scrambledContent = scrambleReactNode(\n children,\n initialScrambled,\n textStartIndex\n )\n setDisplayContent(scrambledContent)\n setShowDecoded(true)\n }\n }, [children, initialScrambled, hasAnimated, fullText])\n\n useEffect(() => {\n if (!fullText) {\n setDisplayContent(null)\n setShowDecoded(false)\n return\n }\n\n // If already animated, hide decoded and show children\n if (hasAnimated) {\n setShowDecoded(false)\n return\n }\n\n const element = elementRef.current\n if (!element) return\n\n let iteration = 0\n let intervalId: NodeJS.Timeout | null = null\n let delayTimeoutId: NodeJS.Timeout | null = null\n\n // Intersection Observer to detect when element is visible\n const observer = new IntersectionObserver(\n (entries) => {\n const entry = entries[0]\n if (entry.isIntersecting && !hasAnimated) {\n // Start animation after delay\n delayTimeoutId = setTimeout(() => {\n // 1. Initial scramble\n const textStartIndex = { current: 0 }\n const scrambledContent = scrambleReactNode(\n children,\n initialScrambled,\n textStartIndex\n )\n setDisplayContent(scrambledContent)\n setShowDecoded(true)\n\n // 2. Set up the interval\n intervalId = setInterval(() => {\n iteration++\n\n if (iteration >= MAX_ITERATIONS) {\n // Stop scrambling, fade out decoded and fade in children\n setHasAnimated(true)\n setShowDecoded(false)\n if (intervalId) clearInterval(intervalId)\n } else {\n // Continue scrambling\n const newScrambled = scrambleText(fullText)\n const textStartIndex = { current: 0 }\n const scrambledContent = scrambleReactNode(\n children,\n newScrambled,\n textStartIndex\n )\n setDisplayContent(scrambledContent)\n }\n }, SPEED)\n }, delay)\n\n // Disconnect observer once animation starts\n observer.disconnect()\n }\n },\n {\n threshold: 0.1, // Trigger when 10% of the element is visible\n }\n )\n\n observer.observe(element)\n\n // Cleanup function\n return () => {\n observer.disconnect()\n if (intervalId) clearInterval(intervalId)\n if (delayTimeoutId) clearTimeout(delayTimeoutId)\n }\n }, [children, fullText, initialScrambled, hasAnimated, delay])\n\n const ariaLabel = useMemo(() => extractText(children), [children])\n\n return (\n <span\n ref={elementRef}\n aria-label={ariaLabel} // Accessibility: Screen readers read the real text\n className={cn(\"inline-grid *:col-start-1 *:row-start-1\", className)}\n >\n {/* Both elements in the same grid cell - prevents width changes */}\n <span\n className={cn(\n \"transition-opacity duration-300\",\n hasAnimated ? \"opacity-100\" : \"opacity-0\"\n )}\n >\n {children}\n </span>\n {/* Decoded animation overlay in the same grid cell */}\n {showDecoded && displayContent && (\n <span className=\"transition-opacity duration-300\">\n {displayContent}\n </span>\n )}\n </span>\n )\n}\n\nconst SuspenseDecodedText = (props: TextDecoderProps) => {\n return (\n <Suspense\n fallback={\n <span className={cn(\"opacity-0\", props.className)}>\n {props.children}\n </span>\n }\n >\n <TextDecoder {...props} />\n </Suspense>\n )\n}\n\nexport default SuspenseDecodedText\n"],"mappings":"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","names":["textStartIndex","scrambledContent"]}
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const buttonVariants: (props?: ({
6
- variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
6
+ variant?: "default" | "link" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
7
7
  size?: "default" | "sm" | "lg" | "icon" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const buttonVariants: (props?: ({
6
- variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
6
+ variant?: "default" | "link" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
7
7
  size?: "default" | "sm" | "lg" | "icon" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
@@ -49,7 +49,7 @@ function cn(...inputs) {
49
49
  // src/components/button.tsx
50
50
  var import_jsx_runtime = require("react/jsx-runtime");
51
51
  var buttonVariants = (0, import_class_variance_authority.cva)(
52
- "inline-flex items-center justify-center whitespace-nowrap rounded-3xl 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",
52
+ "inline-flex items-center cursor-pointer justify-center whitespace-nowrap rounded-3xl 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",
53
53
  {
54
54
  variants: {
55
55
  variant: {
@@ -61,9 +61,9 @@ var buttonVariants = (0, import_class_variance_authority.cva)(
61
61
  link: "text-primary underline-offset-4 hover:underline"
62
62
  },
63
63
  size: {
64
- default: "h-9 px-4 py-2",
64
+ default: "h-9 px-4 py-2 text-base",
65
65
  sm: "h-8 text-sm px-3 py-2",
66
- lg: "h-10 px-8 py-2",
66
+ lg: "h-10 px-8 py-2 text-base",
67
67
  icon: "h-9 w-9"
68
68
  }
69
69
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/button.tsx","../../src/lib/utils.ts"],"sourcesContent":["import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center whitespace-nowrap rounded-3xl 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\",\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:\n \"bg-secondary/80 text-secondary-foreground hover:bg-secondary\",\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 text-sm px-3 py-2\",\n lg: \"h-10 px-8 py-2\",\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 React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean\n loading?: boolean\n icon?: React.ReactNode\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n className,\n variant,\n size,\n asChild = false,\n loading = false,\n icon,\n ...props\n },\n ref\n ) => {\n const Comp = asChild ? Slot : \"button\"\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n disabled={loading || props.disabled}\n {...props}\n >\n {loading && (\n <div className=\"mr-2 h-4 w-4 animate-spin rounded-full border border-primary-foreground border-t-transparent\" />\n )}\n {!loading && icon && <span className=\"mr-2\">{icon}</span>}\n {props.children}\n </Comp>\n )\n }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAAuB;AACvB,wBAAqB;AACrB,sCAAuC;;;ACFvC,kBAAsC;AACtC,4BAAwB;AAEjB,SAAS,MAAM,QAAsB;AAC1C,aAAO,mCAAQ,kBAAK,MAAM,CAAC;AAC7B;;;ADqDM;AApDN,IAAM,qBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,aACE;AAAA,QACF,SACE;AAAA,QACF,WACE;AAAA,QACF,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAUA,IAAM,SAAe;AAAA,EACnB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EACL,GACA,QACG;AACH,UAAM,OAAO,UAAU,yBAAO;AAC9B,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,UAAU,CAAC,CAAC;AAAA,QAC1D;AAAA,QACA,UAAU,WAAW,MAAM;AAAA,QAC1B,GAAG;AAAA,QAEH;AAAA,qBACC,4CAAC,SAAI,WAAU,gGAA+F;AAAA,UAE/G,CAAC,WAAW,QAAQ,4CAAC,UAAK,WAAU,QAAQ,gBAAK;AAAA,UACjD,MAAM;AAAA;AAAA;AAAA,IACT;AAAA,EAEJ;AACF;AACA,OAAO,cAAc;","names":[]}
1
+ {"version":3,"sources":["../../src/components/button.tsx","../../src/lib/utils.ts"],"sourcesContent":["import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n \"inline-flex items-center cursor-pointer justify-center whitespace-nowrap rounded-3xl 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\",\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:\n \"bg-secondary/80 text-secondary-foreground hover:bg-secondary\",\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 text-base\",\n sm: \"h-8 text-sm px-3 py-2\",\n lg: \"h-10 px-8 py-2 text-base\",\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 React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean\n loading?: boolean\n icon?: React.ReactNode\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n className,\n variant,\n size,\n asChild = false,\n loading = false,\n icon,\n ...props\n },\n ref\n ) => {\n const Comp = asChild ? Slot : \"button\"\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n disabled={loading || props.disabled}\n {...props}\n >\n {loading && (\n <div className=\"mr-2 h-4 w-4 animate-spin rounded-full border border-primary-foreground border-t-transparent\" />\n )}\n {!loading && icon && <span className=\"mr-2\">{icon}</span>}\n {props.children}\n </Comp>\n )\n }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAAuB;AACvB,wBAAqB;AACrB,sCAAuC;;;ACFvC,kBAAsC;AACtC,4BAAwB;AAEjB,SAAS,MAAM,QAAsB;AAC1C,aAAO,mCAAQ,kBAAK,MAAM,CAAC;AAC7B;;;ADqDM;AApDN,IAAM,qBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,aACE;AAAA,QACF,SACE;AAAA,QACF,WACE;AAAA,QACF,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAUA,IAAM,SAAe;AAAA,EACnB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EACL,GACA,QACG;AACH,UAAM,OAAO,UAAU,yBAAO;AAC9B,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,UAAU,CAAC,CAAC;AAAA,QAC1D;AAAA,QACA,UAAU,WAAW,MAAM;AAAA,QAC1B,GAAG;AAAA,QAEH;AAAA,qBACC,4CAAC,SAAI,WAAU,gGAA+F;AAAA,UAE/G,CAAC,WAAW,QAAQ,4CAAC,UAAK,WAAU,QAAQ,gBAAK;AAAA,UACjD,MAAM;AAAA;AAAA;AAAA,IACT;AAAA,EAEJ;AACF;AACA,OAAO,cAAc;","names":[]}
@@ -15,7 +15,7 @@ function cn(...inputs) {
15
15
  // src/components/button.tsx
16
16
  import { jsx, jsxs } from "react/jsx-runtime";
17
17
  var buttonVariants = cva(
18
- "inline-flex items-center justify-center whitespace-nowrap rounded-3xl 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",
18
+ "inline-flex items-center cursor-pointer justify-center whitespace-nowrap rounded-3xl 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",
19
19
  {
20
20
  variants: {
21
21
  variant: {
@@ -27,9 +27,9 @@ var buttonVariants = cva(
27
27
  link: "text-primary underline-offset-4 hover:underline"
28
28
  },
29
29
  size: {
30
- default: "h-9 px-4 py-2",
30
+ default: "h-9 px-4 py-2 text-base",
31
31
  sm: "h-8 text-sm px-3 py-2",
32
- lg: "h-10 px-8 py-2",
32
+ lg: "h-10 px-8 py-2 text-base",
33
33
  icon: "h-9 w-9"
34
34
  }
35
35
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/button.tsx","../../src/lib/utils.ts"],"sourcesContent":["import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center whitespace-nowrap rounded-3xl 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\",\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:\n \"bg-secondary/80 text-secondary-foreground hover:bg-secondary\",\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 text-sm px-3 py-2\",\n lg: \"h-10 px-8 py-2\",\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 React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean\n loading?: boolean\n icon?: React.ReactNode\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n className,\n variant,\n size,\n asChild = false,\n loading = false,\n icon,\n ...props\n },\n ref\n ) => {\n const Comp = asChild ? Slot : \"button\"\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n disabled={loading || props.disabled}\n {...props}\n >\n {loading && (\n <div className=\"mr-2 h-4 w-4 animate-spin rounded-full border border-primary-foreground border-t-transparent\" />\n )}\n {!loading && icon && <span className=\"mr-2\">{icon}</span>}\n {props.children}\n </Comp>\n )\n }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\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"],"mappings":";;;AAAA,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,WAA8B;;;ACFvC,SAA0B,YAAY;AACtC,SAAS,eAAe;AAEjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;ADqDM,SAOI,KAPJ;AApDN,IAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,aACE;AAAA,QACF,SACE;AAAA,QACF,WACE;AAAA,QACF,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAUA,IAAM,SAAe;AAAA,EACnB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EACL,GACA,QACG;AACH,UAAM,OAAO,UAAU,OAAO;AAC9B,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,UAAU,CAAC,CAAC;AAAA,QAC1D;AAAA,QACA,UAAU,WAAW,MAAM;AAAA,QAC1B,GAAG;AAAA,QAEH;AAAA,qBACC,oBAAC,SAAI,WAAU,gGAA+F;AAAA,UAE/G,CAAC,WAAW,QAAQ,oBAAC,UAAK,WAAU,QAAQ,gBAAK;AAAA,UACjD,MAAM;AAAA;AAAA;AAAA,IACT;AAAA,EAEJ;AACF;AACA,OAAO,cAAc;","names":[]}
1
+ {"version":3,"sources":["../../src/components/button.tsx","../../src/lib/utils.ts"],"sourcesContent":["import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n \"inline-flex items-center cursor-pointer justify-center whitespace-nowrap rounded-3xl 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\",\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:\n \"bg-secondary/80 text-secondary-foreground hover:bg-secondary\",\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 text-base\",\n sm: \"h-8 text-sm px-3 py-2\",\n lg: \"h-10 px-8 py-2 text-base\",\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 React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean\n loading?: boolean\n icon?: React.ReactNode\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n className,\n variant,\n size,\n asChild = false,\n loading = false,\n icon,\n ...props\n },\n ref\n ) => {\n const Comp = asChild ? Slot : \"button\"\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n disabled={loading || props.disabled}\n {...props}\n >\n {loading && (\n <div className=\"mr-2 h-4 w-4 animate-spin rounded-full border border-primary-foreground border-t-transparent\" />\n )}\n {!loading && icon && <span className=\"mr-2\">{icon}</span>}\n {props.children}\n </Comp>\n )\n }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\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"],"mappings":";;;AAAA,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,WAA8B;;;ACFvC,SAA0B,YAAY;AACtC,SAAS,eAAe;AAEjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;ADqDM,SAOI,KAPJ;AApDN,IAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,aACE;AAAA,QACF,SACE;AAAA,QACF,WACE;AAAA,QACF,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAUA,IAAM,SAAe;AAAA,EACnB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,GAAG;AAAA,EACL,GACA,QACG;AACH,UAAM,OAAO,UAAU,OAAO;AAC9B,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,UAAU,CAAC,CAAC;AAAA,QAC1D;AAAA,QACA,UAAU,WAAW,MAAM;AAAA,QAC1B,GAAG;AAAA,QAEH;AAAA,qBACC,oBAAC,SAAI,WAAU,gGAA+F;AAAA,UAE/G,CAAC,WAAW,QAAQ,oBAAC,UAAK,WAAU,QAAQ,gBAAK;AAAA,UACjD,MAAM;AAAA;AAAA;AAAA,IACT;AAAA,EAEJ;AACF;AACA,OAAO,cAAc;","names":[]}