@open-ai-school/ai-ui-library 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,482 @@
1
+ import { createContext, forwardRef, useState, useEffect, useContext, useRef, useCallback } from 'react';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import Link2 from 'next/link';
4
+ import { motion } from 'framer-motion';
5
+
6
+ // src/components/ThemeProvider.tsx
7
+ var ThemeContext = createContext({
8
+ theme: "system",
9
+ resolvedTheme: "light",
10
+ setTheme: () => {
11
+ }
12
+ });
13
+ function ThemeProvider({ children }) {
14
+ const [theme, setThemeState] = useState("system");
15
+ const [resolvedTheme, setResolvedTheme] = useState("light");
16
+ function applyTheme(t) {
17
+ const root = document.documentElement;
18
+ let resolved;
19
+ if (t === "system") {
20
+ resolved = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
21
+ } else {
22
+ resolved = t;
23
+ }
24
+ root.classList.remove("light", "dark");
25
+ root.classList.add(resolved);
26
+ setResolvedTheme(resolved);
27
+ }
28
+ function setTheme(t) {
29
+ setThemeState(t);
30
+ localStorage.setItem("theme", t);
31
+ applyTheme(t);
32
+ }
33
+ useEffect(() => {
34
+ const stored = localStorage.getItem("theme");
35
+ const initial = stored || "system";
36
+ setThemeState(initial);
37
+ applyTheme(initial);
38
+ }, []);
39
+ useEffect(() => {
40
+ if (theme !== "system") return;
41
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
42
+ const handler = () => applyTheme("system");
43
+ mq.addEventListener("change", handler);
44
+ return () => mq.removeEventListener("change", handler);
45
+ }, [theme]);
46
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: { theme, resolvedTheme, setTheme }, children });
47
+ }
48
+ var useTheme = () => useContext(ThemeContext);
49
+ function ThemeToggle() {
50
+ const { resolvedTheme, setTheme } = useTheme();
51
+ return /* @__PURE__ */ jsxs(
52
+ "button",
53
+ {
54
+ onClick: () => setTheme(resolvedTheme === "dark" ? "light" : "dark"),
55
+ className: "relative w-9 h-9 flex items-center justify-center rounded-xl border border-[var(--color-border)] hover:bg-[var(--color-bg-card)] transition-all duration-200 active:scale-95",
56
+ "aria-label": "Toggle theme",
57
+ children: [
58
+ /* @__PURE__ */ jsx(
59
+ "svg",
60
+ {
61
+ className: `w-4 h-4 absolute transition-all duration-300 ${resolvedTheme === "dark" ? "opacity-0 rotate-90 scale-0" : "opacity-100 rotate-0 scale-100"}`,
62
+ fill: "none",
63
+ stroke: "currentColor",
64
+ viewBox: "0 0 24 24",
65
+ children: /* @__PURE__ */ jsx(
66
+ "path",
67
+ {
68
+ strokeLinecap: "round",
69
+ strokeLinejoin: "round",
70
+ strokeWidth: 2,
71
+ d: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
72
+ }
73
+ )
74
+ }
75
+ ),
76
+ /* @__PURE__ */ jsx(
77
+ "svg",
78
+ {
79
+ className: `w-4 h-4 absolute transition-all duration-300 ${resolvedTheme === "dark" ? "opacity-100 rotate-0 scale-100" : "opacity-0 -rotate-90 scale-0"}`,
80
+ fill: "none",
81
+ stroke: "currentColor",
82
+ viewBox: "0 0 24 24",
83
+ children: /* @__PURE__ */ jsx(
84
+ "path",
85
+ {
86
+ strokeLinecap: "round",
87
+ strokeLinejoin: "round",
88
+ strokeWidth: 2,
89
+ d: "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
90
+ }
91
+ )
92
+ }
93
+ )
94
+ ]
95
+ }
96
+ );
97
+ }
98
+ function ScrollReveal({
99
+ children,
100
+ animation = "fade-up",
101
+ delay = 0,
102
+ className = "",
103
+ stagger = false
104
+ }) {
105
+ const ref = useRef(null);
106
+ const [isVisible, setIsVisible] = useState(false);
107
+ useEffect(() => {
108
+ const el = ref.current;
109
+ if (!el) return;
110
+ const observer = new IntersectionObserver(
111
+ ([entry]) => {
112
+ if (entry.isIntersecting) {
113
+ setIsVisible(true);
114
+ observer.unobserve(el);
115
+ }
116
+ },
117
+ { threshold: 0.1, rootMargin: "0px 0px -40px 0px" }
118
+ );
119
+ observer.observe(el);
120
+ return () => observer.disconnect();
121
+ }, []);
122
+ return /* @__PURE__ */ jsx(
123
+ "div",
124
+ {
125
+ ref,
126
+ className: `${className} ${stagger ? "stagger-children" : ""}`,
127
+ style: {
128
+ opacity: isVisible ? 1 : 0,
129
+ animation: isVisible ? `${animation} 0.7s cubic-bezier(0.22, 1, 0.36, 1) ${delay}ms both` : "none"
130
+ },
131
+ children
132
+ }
133
+ );
134
+ }
135
+ function FloatingParticles() {
136
+ const canvasRef = useRef(null);
137
+ useEffect(() => {
138
+ const canvas = canvasRef.current;
139
+ if (!canvas) return;
140
+ const ctx = canvas.getContext("2d");
141
+ if (!ctx) return;
142
+ let animId;
143
+ const particles = [];
144
+ const resize = () => {
145
+ canvas.width = canvas.offsetWidth;
146
+ canvas.height = canvas.offsetHeight;
147
+ };
148
+ resize();
149
+ window.addEventListener("resize", resize);
150
+ for (let i = 0; i < 40; i++) {
151
+ particles.push({
152
+ x: Math.random() * canvas.width,
153
+ y: Math.random() * canvas.height,
154
+ r: Math.random() * 2 + 0.5,
155
+ dx: (Math.random() - 0.5) * 0.3,
156
+ dy: (Math.random() - 0.5) * 0.3,
157
+ o: Math.random() * 0.3 + 0.05
158
+ });
159
+ }
160
+ const draw = () => {
161
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
162
+ const style = getComputedStyle(document.documentElement);
163
+ const primary = style.getPropertyValue("--color-primary").trim() || "#0071e3";
164
+ for (const p of particles) {
165
+ p.x += p.dx;
166
+ p.y += p.dy;
167
+ if (p.x < 0) p.x = canvas.width;
168
+ if (p.x > canvas.width) p.x = 0;
169
+ if (p.y < 0) p.y = canvas.height;
170
+ if (p.y > canvas.height) p.y = 0;
171
+ ctx.beginPath();
172
+ ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
173
+ ctx.fillStyle = primary;
174
+ ctx.globalAlpha = p.o;
175
+ ctx.fill();
176
+ }
177
+ ctx.globalAlpha = 0.04;
178
+ ctx.strokeStyle = primary;
179
+ ctx.lineWidth = 0.5;
180
+ for (let i = 0; i < particles.length; i++) {
181
+ for (let j = i + 1; j < particles.length; j++) {
182
+ const dx = particles[i].x - particles[j].x;
183
+ const dy = particles[i].y - particles[j].y;
184
+ const dist = Math.sqrt(dx * dx + dy * dy);
185
+ if (dist < 120) {
186
+ ctx.beginPath();
187
+ ctx.moveTo(particles[i].x, particles[i].y);
188
+ ctx.lineTo(particles[j].x, particles[j].y);
189
+ ctx.stroke();
190
+ }
191
+ }
192
+ }
193
+ ctx.globalAlpha = 1;
194
+ animId = requestAnimationFrame(draw);
195
+ };
196
+ draw();
197
+ return () => {
198
+ cancelAnimationFrame(animId);
199
+ window.removeEventListener("resize", resize);
200
+ };
201
+ }, []);
202
+ return /* @__PURE__ */ jsx(
203
+ "canvas",
204
+ {
205
+ ref: canvasRef,
206
+ className: "absolute inset-0 w-full h-full pointer-events-none",
207
+ "aria-hidden": "true"
208
+ }
209
+ );
210
+ }
211
+ var STORAGE_KEY = "open-ai-school-progress";
212
+ function emptyProgram() {
213
+ return { completed: [], timestamps: {} };
214
+ }
215
+ function migrateOldFormat(stored) {
216
+ try {
217
+ const parsed = JSON.parse(stored);
218
+ if (Array.isArray(parsed)) {
219
+ return { "ai-seeds": { completed: parsed, timestamps: {} } };
220
+ }
221
+ if (parsed.completed && Array.isArray(parsed.completed)) {
222
+ return { "ai-seeds": parsed };
223
+ }
224
+ return parsed;
225
+ } catch {
226
+ return {};
227
+ }
228
+ }
229
+ function useProgress(programSlug) {
230
+ const [data, setData] = useState({});
231
+ useEffect(() => {
232
+ const stored = localStorage.getItem(STORAGE_KEY);
233
+ if (stored) {
234
+ setData(migrateOldFormat(stored));
235
+ }
236
+ }, []);
237
+ const getProgram = useCallback(
238
+ (slug) => data[slug] || emptyProgram(),
239
+ [data]
240
+ );
241
+ const markComplete = useCallback((lessonKey) => {
242
+ setData((prev) => {
243
+ const parts = lessonKey.split("/");
244
+ const pSlug = parts.length > 1 ? parts[0] : programSlug || "ai-seeds";
245
+ const lSlug = parts.length > 1 ? parts.slice(1).join("/") : parts[0];
246
+ const prog2 = prev[pSlug] || emptyProgram();
247
+ if (prog2.completed.includes(lSlug)) return prev;
248
+ const next = {
249
+ ...prev,
250
+ [pSlug]: {
251
+ completed: [...prog2.completed, lSlug],
252
+ timestamps: { ...prog2.timestamps, [lSlug]: (/* @__PURE__ */ new Date()).toISOString() }
253
+ }
254
+ };
255
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
256
+ return next;
257
+ });
258
+ }, [programSlug]);
259
+ const isCompleted = useCallback(
260
+ (lessonKey) => {
261
+ const parts = lessonKey.split("/");
262
+ const pSlug = parts.length > 1 ? parts[0] : programSlug || "ai-seeds";
263
+ const lSlug = parts.length > 1 ? parts.slice(1).join("/") : parts[0];
264
+ return (data[pSlug]?.completed || []).includes(lSlug);
265
+ },
266
+ [data, programSlug]
267
+ );
268
+ const getCompletedAt = useCallback(
269
+ (lessonKey) => {
270
+ const parts = lessonKey.split("/");
271
+ const pSlug = parts.length > 1 ? parts[0] : programSlug || "ai-seeds";
272
+ const lSlug = parts.length > 1 ? parts.slice(1).join("/") : parts[0];
273
+ return data[pSlug]?.timestamps[lSlug] || null;
274
+ },
275
+ [data, programSlug]
276
+ );
277
+ const reset = useCallback(() => {
278
+ setData({});
279
+ localStorage.removeItem(STORAGE_KEY);
280
+ }, []);
281
+ const prog = programSlug ? getProgram(programSlug) : null;
282
+ const completed = prog ? prog.completed : Object.values(data).flatMap((p) => p.completed);
283
+ const completedCount = completed.length;
284
+ const totalCompleted = Object.values(data).reduce((sum, p) => sum + p.completed.length, 0);
285
+ return {
286
+ completed,
287
+ completedCount,
288
+ totalCompleted,
289
+ markComplete,
290
+ isCompleted,
291
+ getCompletedAt,
292
+ getProgram,
293
+ allData: data,
294
+ reset
295
+ };
296
+ }
297
+ function CourseProgress({
298
+ totalLessons,
299
+ basePath,
300
+ programSlug
301
+ }) {
302
+ const { completedCount } = useProgress(programSlug);
303
+ if (completedCount === 0) return null;
304
+ const percentage = Math.round(completedCount / totalLessons * 100);
305
+ return /* @__PURE__ */ jsxs("div", { className: "max-w-md mx-auto mt-10 p-5 rounded-2xl bg-[var(--color-bg-card)] border border-[var(--color-border)]", children: [
306
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm mb-3", children: [
307
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "\u{1F4DA} Your Progress" }),
308
+ /* @__PURE__ */ jsxs("span", { className: "text-[var(--color-text-muted)]", children: [
309
+ completedCount,
310
+ "/",
311
+ totalLessons,
312
+ " lessons (",
313
+ percentage,
314
+ "%)"
315
+ ] })
316
+ ] }),
317
+ /* @__PURE__ */ jsx("div", { className: "progress-bar mb-4", children: /* @__PURE__ */ jsx(
318
+ "div",
319
+ {
320
+ className: "progress-bar-fill",
321
+ style: { width: `${percentage}%` }
322
+ }
323
+ ) }),
324
+ completedCount === totalLessons ? /* @__PURE__ */ jsx("p", { className: "text-center text-sm font-semibold text-[var(--color-accent)]", children: "\u{1F3C6} All lessons completed!" }) : /* @__PURE__ */ jsx(
325
+ Link2,
326
+ {
327
+ href: basePath,
328
+ className: "block text-center text-sm font-medium text-[var(--color-primary)] hover:underline",
329
+ children: "Continue learning \u2192"
330
+ }
331
+ )
332
+ ] });
333
+ }
334
+ var GuestProfileContext = createContext({
335
+ profile: null,
336
+ saveProfile: () => {
337
+ },
338
+ clearProfile: () => {
339
+ },
340
+ isSignedIn: false
341
+ });
342
+ function useGuestProfile() {
343
+ return useContext(GuestProfileContext);
344
+ }
345
+ var ALL_SLUGS = ["what-is-ai", "how-machines-learn", "your-first-ai-model"];
346
+ function WelcomeBanner({ basePath }) {
347
+ const { profile, isSignedIn } = useGuestProfile();
348
+ const { completed, completedCount } = useProgress();
349
+ if (!isSignedIn) return null;
350
+ const nextSlug = ALL_SLUGS.find((s) => !completed.includes(s)) || ALL_SLUGS[0];
351
+ const percentage = Math.round(completedCount / ALL_SLUGS.length * 100);
352
+ return /* @__PURE__ */ jsx("div", { className: "max-w-2xl mx-auto mt-10 animate-fade-up", style: { animationDelay: "600ms" }, children: /* @__PURE__ */ jsxs("div", { className: "p-6 rounded-2xl glass-card", children: [
353
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 mb-4", children: [
354
+ /* @__PURE__ */ jsx("span", { className: "text-3xl", children: profile?.avatar }),
355
+ /* @__PURE__ */ jsxs("div", { children: [
356
+ /* @__PURE__ */ jsxs("p", { className: "font-semibold", children: [
357
+ "Welcome back, ",
358
+ profile?.name,
359
+ "! \u{1F44B}"
360
+ ] }),
361
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-[var(--color-text-muted)]", children: completedCount === ALL_SLUGS.length ? "You've completed all available lessons! \u{1F389}" : `${completedCount} of ${ALL_SLUGS.length} lessons completed` })
362
+ ] })
363
+ ] }),
364
+ /* @__PURE__ */ jsx("div", { className: "h-2 rounded-full bg-[var(--color-border)] overflow-hidden mb-4", children: /* @__PURE__ */ jsx(
365
+ "div",
366
+ {
367
+ className: "h-full rounded-full bg-gradient-to-r from-[var(--color-primary)] via-purple-500 to-[var(--color-accent)] transition-all duration-1000",
368
+ style: { width: `${percentage}%` }
369
+ }
370
+ ) }),
371
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
372
+ /* @__PURE__ */ jsx(
373
+ Link2,
374
+ {
375
+ href: `${basePath}/dashboard`,
376
+ className: "text-sm text-[var(--color-text-muted)] hover:text-[var(--color-primary)] transition-colors",
377
+ children: "View Dashboard \u2192"
378
+ }
379
+ ),
380
+ completedCount < ALL_SLUGS.length && /* @__PURE__ */ jsx(
381
+ Link2,
382
+ {
383
+ href: `${basePath}/lessons/${nextSlug}`,
384
+ className: "px-4 py-2 bg-[var(--color-primary)] text-white rounded-xl text-sm font-medium hover:brightness-110 transition-all active:scale-95",
385
+ children: "Continue Learning \u2192"
386
+ }
387
+ )
388
+ ] })
389
+ ] }) });
390
+ }
391
+ var variantStyles = {
392
+ primary: "bg-[var(--color-primary)] text-white hover:brightness-110 active:brightness-95",
393
+ secondary: "bg-[var(--color-bg-card)] text-[var(--color-text)] border border-[var(--color-border)] hover:bg-[var(--color-bg-secondary)]",
394
+ ghost: "text-[var(--color-text-muted)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)]",
395
+ outline: "border border-[var(--color-primary)] text-[var(--color-primary)] hover:bg-[var(--color-primary)] hover:text-white",
396
+ accent: "bg-[var(--color-accent)] text-white hover:brightness-110 active:brightness-95"
397
+ };
398
+ var sizeStyles = {
399
+ sm: "px-3 py-1.5 text-sm rounded-lg",
400
+ md: "px-5 py-2.5 text-base rounded-xl",
401
+ lg: "px-7 py-3.5 text-lg rounded-2xl"
402
+ };
403
+ var Button = forwardRef(
404
+ ({ variant = "primary", size = "md", loading, className = "", children, disabled, ...props }, ref) => {
405
+ return /* @__PURE__ */ jsxs(
406
+ motion.button,
407
+ {
408
+ ref,
409
+ whileTap: { scale: 0.97 },
410
+ whileHover: { scale: 1.02 },
411
+ transition: { type: "spring", stiffness: 400, damping: 17 },
412
+ className: `inline-flex items-center justify-center font-semibold transition-all cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed ${variantStyles[variant]} ${sizeStyles[size]} ${className}`,
413
+ disabled: disabled || loading,
414
+ ...props,
415
+ children: [
416
+ loading && /* @__PURE__ */ jsxs("svg", { className: "animate-spin -ml-1 mr-2 h-4 w-4", fill: "none", viewBox: "0 0 24 24", children: [
417
+ /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
418
+ /* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })
419
+ ] }),
420
+ children
421
+ ]
422
+ }
423
+ );
424
+ }
425
+ );
426
+ Button.displayName = "Button";
427
+ var variantStyles2 = {
428
+ default: "bg-[var(--color-bg-card)] border border-[var(--color-border)]",
429
+ elevated: "bg-[var(--color-bg-card)] border border-[var(--color-border)] shadow-lg",
430
+ glass: "bg-[var(--color-bg-card)]/80 backdrop-blur-xl border border-[var(--color-border)]/50",
431
+ outline: "bg-transparent border-2 border-[var(--color-border)]"
432
+ };
433
+ function Card({
434
+ children,
435
+ variant = "default",
436
+ hover = false,
437
+ className = "",
438
+ onClick
439
+ }) {
440
+ return /* @__PURE__ */ jsx(
441
+ motion.div,
442
+ {
443
+ whileHover: hover ? { y: -4, scale: 1.01 } : void 0,
444
+ transition: { type: "spring", stiffness: 300, damping: 20 },
445
+ className: `rounded-2xl p-6 transition-all ${variantStyles2[variant]} ${hover ? "cursor-pointer" : ""} ${className}`,
446
+ onClick,
447
+ children
448
+ }
449
+ );
450
+ }
451
+ var variantStyles3 = {
452
+ default: "bg-[var(--color-bg-secondary)] text-[var(--color-text-muted)]",
453
+ success: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400",
454
+ warning: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400",
455
+ error: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400",
456
+ info: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
457
+ };
458
+ var sizeStyles2 = {
459
+ sm: "px-2 py-0.5 text-xs",
460
+ md: "px-2.5 py-1 text-sm"
461
+ };
462
+ function Badge({
463
+ children,
464
+ variant = "default",
465
+ size = "sm",
466
+ color,
467
+ className = ""
468
+ }) {
469
+ const customStyle = color ? { backgroundColor: `${color}20`, color } : void 0;
470
+ return /* @__PURE__ */ jsx(
471
+ "span",
472
+ {
473
+ className: `inline-flex items-center font-medium rounded-full ${color ? "" : variantStyles3[variant]} ${sizeStyles2[size]} ${className}`,
474
+ style: customStyle,
475
+ children
476
+ }
477
+ );
478
+ }
479
+
480
+ export { Badge, Button, Card, CourseProgress, FloatingParticles, ScrollReveal, ThemeProvider, ThemeToggle, WelcomeBanner, useGuestProfile, useProgress, useTheme };
481
+ //# sourceMappingURL=index.mjs.map
482
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/ThemeProvider.tsx","../src/components/ThemeToggle.tsx","../src/components/ScrollReveal.tsx","../src/components/FloatingParticles.tsx","../src/hooks/useProgress.ts","../src/components/CourseProgress.tsx","../src/hooks/useGuestProfile.ts","../src/components/WelcomeBanner.tsx","../src/components/Button.tsx","../src/components/Card.tsx","../src/components/Badge.tsx"],"names":["jsx","useState","useEffect","useRef","prog","jsxs","Link","createContext","useContext","variantStyles","motion","sizeStyles"],"mappings":"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","file":"index.mjs","sourcesContent":["\"use client\";\n\nimport { createContext, useContext, useEffect, useState } from \"react\";\n\ntype Theme = \"light\" | \"dark\" | \"system\";\n\nconst ThemeContext = createContext<{\n theme: Theme;\n resolvedTheme: \"light\" | \"dark\";\n setTheme: (theme: Theme) => void;\n}>({\n theme: \"system\",\n resolvedTheme: \"light\",\n setTheme: () => {},\n});\n\nexport function ThemeProvider({ children }: { children: React.ReactNode }) {\n const [theme, setThemeState] = useState<Theme>(\"system\");\n const [resolvedTheme, setResolvedTheme] = useState<\"light\" | \"dark\">(\"light\");\n\n function applyTheme(t: Theme) {\n const root = document.documentElement;\n let resolved: \"light\" | \"dark\";\n\n if (t === \"system\") {\n resolved = window.matchMedia(\"(prefers-color-scheme: dark)\").matches\n ? \"dark\"\n : \"light\";\n } else {\n resolved = t;\n }\n\n root.classList.remove(\"light\", \"dark\");\n root.classList.add(resolved);\n setResolvedTheme(resolved);\n }\n\n function setTheme(t: Theme) {\n setThemeState(t);\n localStorage.setItem(\"theme\", t);\n applyTheme(t);\n }\n\n useEffect(() => {\n const stored = localStorage.getItem(\"theme\") as Theme | null;\n const initial = stored || \"system\";\n setThemeState(initial);\n applyTheme(initial);\n }, []);\n\n // Listen for system theme changes when in \"system\" mode\n useEffect(() => {\n if (theme !== \"system\") return;\n const mq = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const handler = () => applyTheme(\"system\");\n mq.addEventListener(\"change\", handler);\n return () => mq.removeEventListener(\"change\", handler);\n }, [theme]);\n\n return (\n <ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>\n {children}\n </ThemeContext.Provider>\n );\n}\n\nexport const useTheme = () => useContext(ThemeContext);\n","\"use client\";\n\nimport { useTheme } from \"./ThemeProvider\";\n\nexport function ThemeToggle() {\n const { resolvedTheme, setTheme } = useTheme();\n\n return (\n <button\n onClick={() => setTheme(resolvedTheme === \"dark\" ? \"light\" : \"dark\")}\n className=\"relative w-9 h-9 flex items-center justify-center rounded-xl border border-[var(--color-border)] hover:bg-[var(--color-bg-card)] transition-all duration-200 active:scale-95\"\n aria-label=\"Toggle theme\"\n >\n {/* Sun icon */}\n <svg\n className={`w-4 h-4 absolute transition-all duration-300 ${\n resolvedTheme === \"dark\"\n ? \"opacity-0 rotate-90 scale-0\"\n : \"opacity-100 rotate-0 scale-100\"\n }`}\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z\"\n />\n </svg>\n {/* Moon icon */}\n <svg\n className={`w-4 h-4 absolute transition-all duration-300 ${\n resolvedTheme === \"dark\"\n ? \"opacity-100 rotate-0 scale-100\"\n : \"opacity-0 -rotate-90 scale-0\"\n }`}\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z\"\n />\n </svg>\n </button>\n );\n}\n","\"use client\";\n\nimport { useEffect, useRef, useState, type ReactNode } from \"react\";\n\ntype Animation = \"fade-up\" | \"fade-in\" | \"scale-in\" | \"slide-left\" | \"slide-right\";\n\ninterface ScrollRevealProps {\n children: ReactNode;\n animation?: Animation;\n delay?: number;\n className?: string;\n stagger?: boolean;\n}\n\nexport function ScrollReveal({\n children,\n animation = \"fade-up\",\n delay = 0,\n className = \"\",\n stagger = false,\n}: ScrollRevealProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [isVisible, setIsVisible] = useState(false);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setIsVisible(true);\n observer.unobserve(el);\n }\n },\n { threshold: 0.1, rootMargin: \"0px 0px -40px 0px\" }\n );\n\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n return (\n <div\n ref={ref}\n className={`${className} ${stagger ? \"stagger-children\" : \"\"}`}\n style={{\n opacity: isVisible ? 1 : 0,\n animation: isVisible\n ? `${animation} 0.7s cubic-bezier(0.22, 1, 0.36, 1) ${delay}ms both`\n : \"none\",\n }}\n >\n {children}\n </div>\n );\n}\n","\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\nexport function FloatingParticles() {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n let animId: number;\n const particles: { x: number; y: number; r: number; dx: number; dy: number; o: number }[] = [];\n\n const resize = () => {\n canvas.width = canvas.offsetWidth;\n canvas.height = canvas.offsetHeight;\n };\n resize();\n window.addEventListener(\"resize\", resize);\n\n // Create subtle floating dots\n for (let i = 0; i < 40; i++) {\n particles.push({\n x: Math.random() * canvas.width,\n y: Math.random() * canvas.height,\n r: Math.random() * 2 + 0.5,\n dx: (Math.random() - 0.5) * 0.3,\n dy: (Math.random() - 0.5) * 0.3,\n o: Math.random() * 0.3 + 0.05,\n });\n }\n\n const draw = () => {\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n // Get theme from CSS var\n const style = getComputedStyle(document.documentElement);\n const primary = style.getPropertyValue(\"--color-primary\").trim() || \"#0071e3\";\n\n for (const p of particles) {\n p.x += p.dx;\n p.y += p.dy;\n\n // Wrap around\n if (p.x < 0) p.x = canvas.width;\n if (p.x > canvas.width) p.x = 0;\n if (p.y < 0) p.y = canvas.height;\n if (p.y > canvas.height) p.y = 0;\n\n ctx.beginPath();\n ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);\n ctx.fillStyle = primary;\n ctx.globalAlpha = p.o;\n ctx.fill();\n }\n\n // Draw faint connecting lines between nearby particles\n ctx.globalAlpha = 0.04;\n ctx.strokeStyle = primary;\n ctx.lineWidth = 0.5;\n for (let i = 0; i < particles.length; i++) {\n for (let j = i + 1; j < particles.length; j++) {\n const dx = particles[i].x - particles[j].x;\n const dy = particles[i].y - particles[j].y;\n const dist = Math.sqrt(dx * dx + dy * dy);\n if (dist < 120) {\n ctx.beginPath();\n ctx.moveTo(particles[i].x, particles[i].y);\n ctx.lineTo(particles[j].x, particles[j].y);\n ctx.stroke();\n }\n }\n }\n\n ctx.globalAlpha = 1;\n animId = requestAnimationFrame(draw);\n };\n\n draw();\n\n return () => {\n cancelAnimationFrame(animId);\n window.removeEventListener(\"resize\", resize);\n };\n }, []);\n\n return (\n <canvas\n ref={canvasRef}\n className=\"absolute inset-0 w-full h-full pointer-events-none\"\n aria-hidden=\"true\"\n />\n );\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useState } from \"react\";\n\nconst STORAGE_KEY = \"open-ai-school-progress\";\n\ninterface ProgramProgress {\n completed: string[];\n timestamps: Record<string, string>;\n}\n\ntype ProgressData = Record<string, ProgramProgress>;\n\nfunction emptyProgram(): ProgramProgress {\n return { completed: [], timestamps: {} };\n}\n\nfunction migrateOldFormat(stored: string): ProgressData {\n try {\n const parsed = JSON.parse(stored);\n // Old format: plain array of slugs\n if (Array.isArray(parsed)) {\n return { \"ai-seeds\": { completed: parsed, timestamps: {} } };\n }\n // Old format: { completed: [...], timestamps: {...} }\n if (parsed.completed && Array.isArray(parsed.completed)) {\n return { \"ai-seeds\": parsed as ProgramProgress };\n }\n // New format: already namespaced\n return parsed as ProgressData;\n } catch {\n return {};\n }\n}\n\nexport function useProgress(programSlug?: string) {\n const [data, setData] = useState<ProgressData>({});\n\n useEffect(() => {\n const stored = localStorage.getItem(STORAGE_KEY);\n if (stored) {\n setData(migrateOldFormat(stored));\n }\n }, []);\n\n const getProgram = useCallback(\n (slug: string): ProgramProgress => data[slug] || emptyProgram(),\n [data]\n );\n\n const markComplete = useCallback((lessonKey: string) => {\n setData((prev) => {\n // lessonKey can be \"programSlug/lessonSlug\" or just \"lessonSlug\" (backward compat)\n const parts = lessonKey.split(\"/\");\n const pSlug = parts.length > 1 ? parts[0] : (programSlug || \"ai-seeds\");\n const lSlug = parts.length > 1 ? parts.slice(1).join(\"/\") : parts[0];\n\n const prog = prev[pSlug] || emptyProgram();\n if (prog.completed.includes(lSlug)) return prev;\n\n const next: ProgressData = {\n ...prev,\n [pSlug]: {\n completed: [...prog.completed, lSlug],\n timestamps: { ...prog.timestamps, [lSlug]: new Date().toISOString() },\n },\n };\n localStorage.setItem(STORAGE_KEY, JSON.stringify(next));\n return next;\n });\n }, [programSlug]);\n\n const isCompleted = useCallback(\n (lessonKey: string) => {\n const parts = lessonKey.split(\"/\");\n const pSlug = parts.length > 1 ? parts[0] : (programSlug || \"ai-seeds\");\n const lSlug = parts.length > 1 ? parts.slice(1).join(\"/\") : parts[0];\n return (data[pSlug]?.completed || []).includes(lSlug);\n },\n [data, programSlug]\n );\n\n const getCompletedAt = useCallback(\n (lessonKey: string) => {\n const parts = lessonKey.split(\"/\");\n const pSlug = parts.length > 1 ? parts[0] : (programSlug || \"ai-seeds\");\n const lSlug = parts.length > 1 ? parts.slice(1).join(\"/\") : parts[0];\n return data[pSlug]?.timestamps[lSlug] || null;\n },\n [data, programSlug]\n );\n\n const reset = useCallback(() => {\n setData({});\n localStorage.removeItem(STORAGE_KEY);\n }, []);\n\n // Per-program counts\n const prog = programSlug ? getProgram(programSlug) : null;\n const completed = prog ? prog.completed : Object.values(data).flatMap((p) => p.completed);\n const completedCount = completed.length;\n\n // Total across all programs\n const totalCompleted = Object.values(data).reduce((sum, p) => sum + p.completed.length, 0);\n\n return {\n completed,\n completedCount,\n totalCompleted,\n markComplete,\n isCompleted,\n getCompletedAt,\n getProgram,\n allData: data,\n reset,\n };\n}\n","\"use client\";\n\nimport { useProgress } from \"../hooks/useProgress\";\nimport Link from \"next/link\";\n\nexport function CourseProgress({\n totalLessons,\n basePath,\n programSlug,\n}: {\n totalLessons: number;\n basePath: string;\n programSlug?: string;\n}) {\n const { completedCount } = useProgress(programSlug);\n\n if (completedCount === 0) return null;\n\n const percentage = Math.round((completedCount / totalLessons) * 100);\n\n return (\n <div className=\"max-w-md mx-auto mt-10 p-5 rounded-2xl bg-[var(--color-bg-card)] border border-[var(--color-border)]\">\n <div className=\"flex items-center justify-between text-sm mb-3\">\n <span className=\"font-medium\">📚 Your Progress</span>\n <span className=\"text-[var(--color-text-muted)]\">\n {completedCount}/{totalLessons} lessons ({percentage}%)\n </span>\n </div>\n <div className=\"progress-bar mb-4\">\n <div\n className=\"progress-bar-fill\"\n style={{ width: `${percentage}%` }}\n />\n </div>\n {completedCount === totalLessons ? (\n <p className=\"text-center text-sm font-semibold text-[var(--color-accent)]\">\n 🏆 All lessons completed!\n </p>\n ) : (\n <Link\n href={basePath}\n className=\"block text-center text-sm font-medium text-[var(--color-primary)] hover:underline\"\n >\n Continue learning →\n </Link>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { createContext, useContext, useCallback, useState, useEffect } from \"react\";\n\nconst PROFILE_KEY = \"open-ai-school-profile\";\n\nexport interface GuestProfile {\n name: string;\n avatar: string;\n joinedAt: string;\n}\n\ninterface GuestProfileContextValue {\n profile: GuestProfile | null;\n saveProfile: (name: string) => void;\n clearProfile: () => void;\n isSignedIn: boolean;\n}\n\nconst avatars = [\"🧑‍🎓\", \"👨‍💻\", \"👩‍💻\", \"🧑‍🔬\", \"👨‍🔬\", \"👩‍🔬\", \"🧑‍🏫\", \"👨‍🏫\", \"👩‍🏫\", \"🦊\", \"🐱\", \"🐼\", \"🦉\", \"🐬\", \"🦋\", \"🌻\", \"🍄\", \"🌈\"];\n\nfunction pickAvatar(name: string): string {\n let hash = 0;\n for (let i = 0; i < name.length; i++) {\n hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0;\n }\n return avatars[Math.abs(hash) % avatars.length];\n}\n\nexport function useGuestProfileState(): GuestProfileContextValue {\n const [profile, setProfile] = useState<GuestProfile | null>(null);\n\n useEffect(() => {\n try {\n const stored = localStorage.getItem(PROFILE_KEY);\n if (stored) setProfile(JSON.parse(stored));\n } catch {\n // ignore\n }\n }, []);\n\n const saveProfile = useCallback((name: string) => {\n const newProfile: GuestProfile = {\n name: name.trim(),\n avatar: pickAvatar(name),\n joinedAt: new Date().toISOString(),\n };\n localStorage.setItem(PROFILE_KEY, JSON.stringify(newProfile));\n setProfile(newProfile);\n }, []);\n\n const clearProfile = useCallback(() => {\n localStorage.removeItem(PROFILE_KEY);\n setProfile(null);\n }, []);\n\n return {\n profile,\n saveProfile,\n clearProfile,\n isSignedIn: !!profile,\n };\n}\n\nexport const GuestProfileContext = createContext<GuestProfileContextValue>({\n profile: null,\n saveProfile: () => {},\n clearProfile: () => {},\n isSignedIn: false,\n});\n\nexport function useGuestProfile() {\n return useContext(GuestProfileContext);\n}\n","\"use client\";\n\nimport Link from \"next/link\";\nimport { useGuestProfile } from \"../hooks/useGuestProfile\";\nimport { useProgress } from \"../hooks/useProgress\";\n\nconst ALL_SLUGS = [\"what-is-ai\", \"how-machines-learn\", \"your-first-ai-model\"];\n\nexport function WelcomeBanner({ basePath }: { basePath: string }) {\n const { profile, isSignedIn } = useGuestProfile();\n const { completed, completedCount } = useProgress();\n\n if (!isSignedIn) return null;\n\n const nextSlug = ALL_SLUGS.find((s) => !completed.includes(s)) || ALL_SLUGS[0];\n const percentage = Math.round((completedCount / ALL_SLUGS.length) * 100);\n\n return (\n <div className=\"max-w-2xl mx-auto mt-10 animate-fade-up\" style={{ animationDelay: \"600ms\" }}>\n <div className=\"p-6 rounded-2xl glass-card\">\n <div className=\"flex items-center gap-4 mb-4\">\n <span className=\"text-3xl\">{profile?.avatar}</span>\n <div>\n <p className=\"font-semibold\">\n Welcome back, {profile?.name}! 👋\n </p>\n <p className=\"text-sm text-[var(--color-text-muted)]\">\n {completedCount === ALL_SLUGS.length\n ? \"You've completed all available lessons! 🎉\"\n : `${completedCount} of ${ALL_SLUGS.length} lessons completed`}\n </p>\n </div>\n </div>\n {/* Progress bar */}\n <div className=\"h-2 rounded-full bg-[var(--color-border)] overflow-hidden mb-4\">\n <div\n className=\"h-full rounded-full bg-gradient-to-r from-[var(--color-primary)] via-purple-500 to-[var(--color-accent)] transition-all duration-1000\"\n style={{ width: `${percentage}%` }}\n />\n </div>\n <div className=\"flex items-center justify-between\">\n <Link\n href={`${basePath}/dashboard`}\n className=\"text-sm text-[var(--color-text-muted)] hover:text-[var(--color-primary)] transition-colors\"\n >\n View Dashboard →\n </Link>\n {completedCount < ALL_SLUGS.length && (\n <Link\n href={`${basePath}/lessons/${nextSlug}`}\n className=\"px-4 py-2 bg-[var(--color-primary)] text-white rounded-xl text-sm font-medium hover:brightness-110 transition-all active:scale-95\"\n >\n Continue Learning →\n </Link>\n )}\n </div>\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { forwardRef, type ButtonHTMLAttributes } from \"react\";\nimport { motion, type HTMLMotionProps } from \"framer-motion\";\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: \"primary\" | \"secondary\" | \"ghost\" | \"outline\" | \"accent\";\n size?: \"sm\" | \"md\" | \"lg\";\n loading?: boolean;\n}\n\nconst variantStyles: Record<string, string> = {\n primary:\n \"bg-[var(--color-primary)] text-white hover:brightness-110 active:brightness-95\",\n secondary:\n \"bg-[var(--color-bg-card)] text-[var(--color-text)] border border-[var(--color-border)] hover:bg-[var(--color-bg-secondary)]\",\n ghost:\n \"text-[var(--color-text-muted)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)]\",\n outline:\n \"border border-[var(--color-primary)] text-[var(--color-primary)] hover:bg-[var(--color-primary)] hover:text-white\",\n accent:\n \"bg-[var(--color-accent)] text-white hover:brightness-110 active:brightness-95\",\n};\n\nconst sizeStyles: Record<string, string> = {\n sm: \"px-3 py-1.5 text-sm rounded-lg\",\n md: \"px-5 py-2.5 text-base rounded-xl\",\n lg: \"px-7 py-3.5 text-lg rounded-2xl\",\n};\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n ({ variant = \"primary\", size = \"md\", loading, className = \"\", children, disabled, ...props }, ref) => {\n return (\n <motion.button\n ref={ref}\n whileTap={{ scale: 0.97 }}\n whileHover={{ scale: 1.02 }}\n transition={{ type: \"spring\", stiffness: 400, damping: 17 }}\n className={`inline-flex items-center justify-center font-semibold transition-all cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed ${variantStyles[variant]} ${sizeStyles[size]} ${className}`}\n disabled={disabled || loading}\n {...(props as HTMLMotionProps<\"button\">)}\n >\n {loading && (\n <svg className=\"animate-spin -ml-1 mr-2 h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\n <path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\" />\n </svg>\n )}\n {children}\n </motion.button>\n );\n }\n);\nButton.displayName = \"Button\";\n","\"use client\";\n\nimport { type ReactNode } from \"react\";\nimport { motion } from \"framer-motion\";\n\nexport interface CardProps {\n children: ReactNode;\n variant?: \"default\" | \"elevated\" | \"glass\" | \"outline\";\n hover?: boolean;\n className?: string;\n onClick?: () => void;\n}\n\nconst variantStyles: Record<string, string> = {\n default:\n \"bg-[var(--color-bg-card)] border border-[var(--color-border)]\",\n elevated:\n \"bg-[var(--color-bg-card)] border border-[var(--color-border)] shadow-lg\",\n glass:\n \"bg-[var(--color-bg-card)]/80 backdrop-blur-xl border border-[var(--color-border)]/50\",\n outline:\n \"bg-transparent border-2 border-[var(--color-border)]\",\n};\n\nexport function Card({\n children,\n variant = \"default\",\n hover = false,\n className = \"\",\n onClick,\n}: CardProps) {\n return (\n <motion.div\n whileHover={hover ? { y: -4, scale: 1.01 } : undefined}\n transition={{ type: \"spring\", stiffness: 300, damping: 20 }}\n className={`rounded-2xl p-6 transition-all ${variantStyles[variant]} ${\n hover ? \"cursor-pointer\" : \"\"\n } ${className}`}\n onClick={onClick}\n >\n {children}\n </motion.div>\n );\n}\n","import { type ReactNode } from \"react\";\n\nexport interface BadgeProps {\n children: ReactNode;\n variant?: \"default\" | \"success\" | \"warning\" | \"error\" | \"info\";\n size?: \"sm\" | \"md\";\n color?: string;\n className?: string;\n}\n\nconst variantStyles: Record<string, string> = {\n default: \"bg-[var(--color-bg-secondary)] text-[var(--color-text-muted)]\",\n success: \"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400\",\n warning: \"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400\",\n error: \"bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400\",\n info: \"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400\",\n};\n\nconst sizeStyles: Record<string, string> = {\n sm: \"px-2 py-0.5 text-xs\",\n md: \"px-2.5 py-1 text-sm\",\n};\n\nexport function Badge({\n children,\n variant = \"default\",\n size = \"sm\",\n color,\n className = \"\",\n}: BadgeProps) {\n const customStyle = color\n ? { backgroundColor: `${color}20`, color }\n : undefined;\n\n return (\n <span\n className={`inline-flex items-center font-medium rounded-full ${\n color ? \"\" : variantStyles[variant]\n } ${sizeStyles[size]} ${className}`}\n style={customStyle}\n >\n {children}\n </span>\n );\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@open-ai-school/ai-ui-library",
3
+ "version": "0.1.0",
4
+ "description": "Shared UI component library for Open AI School — React, TypeScript, Tailwind CSS, Framer Motion",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./styles.css": "./dist/styles.css"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": [
20
+ "**/*.css"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "lint": "eslint src/",
26
+ "storybook": "storybook dev -p 6006",
27
+ "build-storybook": "storybook build",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/open-ai-school/ai-ui-library"
33
+ },
34
+ "keywords": [
35
+ "react",
36
+ "ui",
37
+ "components",
38
+ "tailwindcss",
39
+ "education",
40
+ "open-ai-school",
41
+ "design-system"
42
+ ],
43
+ "author": "Ramesh Reddy Adutla <rameshreddy.adutla@gmail.com>",
44
+ "license": "MIT",
45
+ "bugs": {
46
+ "url": "https://github.com/open-ai-school/ai-ui-library/issues"
47
+ },
48
+ "homepage": "https://github.com/open-ai-school/ai-ui-library#readme",
49
+ "peerDependencies": {
50
+ "next": ">=14.0.0",
51
+ "react": ">=18.0.0",
52
+ "react-dom": ">=18.0.0"
53
+ },
54
+ "devDependencies": {
55
+ "@storybook/addon-essentials": "^8.6.14",
56
+ "@storybook/addon-interactions": "^8.6.14",
57
+ "@storybook/addon-themes": "^8.6.14",
58
+ "@storybook/react": "^8.6.14",
59
+ "@storybook/react-vite": "^8.6.14",
60
+ "@types/react": "^19.0.0",
61
+ "@types/react-dom": "^19.0.0",
62
+ "eslint": "^9.39.3",
63
+ "next": "^16.1.6",
64
+ "react": "^19.2.4",
65
+ "react-dom": "^19.2.4",
66
+ "storybook": "^8.6.14",
67
+ "tsup": "^8.5.0",
68
+ "typescript": "^5.9.3"
69
+ },
70
+ "dependencies": {
71
+ "framer-motion": "^12.15.0",
72
+ "lucide-react": "^0.525.0",
73
+ "next-themes": "^0.4.6"
74
+ }
75
+ }