analytica-frontend-lib 1.1.4 → 1.1.6

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.
@@ -0,0 +1,550 @@
1
+ // src/components/VideoPlayer/VideoPlayer.tsx
2
+ import { useRef, useState, useEffect, useCallback } from "react";
3
+ import {
4
+ Play,
5
+ Pause,
6
+ SpeakerHigh,
7
+ SpeakerSlash,
8
+ ArrowsOutSimple,
9
+ ArrowsInSimple,
10
+ ClosedCaptioning,
11
+ DotsThreeVertical
12
+ } from "phosphor-react";
13
+
14
+ // src/utils/utils.ts
15
+ import { clsx } from "clsx";
16
+ import { twMerge } from "tailwind-merge";
17
+ function cn(...inputs) {
18
+ return twMerge(clsx(inputs));
19
+ }
20
+
21
+ // src/components/IconButton/IconButton.tsx
22
+ import { forwardRef } from "react";
23
+ import { jsx } from "react/jsx-runtime";
24
+ var IconButton = forwardRef(
25
+ ({ icon, size = "md", active = false, className = "", disabled, ...props }, ref) => {
26
+ const baseClasses = [
27
+ "inline-flex",
28
+ "items-center",
29
+ "justify-center",
30
+ "rounded-lg",
31
+ "font-medium",
32
+ "bg-transparent",
33
+ "text-text-950",
34
+ "cursor-pointer",
35
+ "hover:bg-primary-600",
36
+ "hover:text-text",
37
+ "focus-visible:outline-none",
38
+ "focus-visible:ring-2",
39
+ "focus-visible:ring-offset-0",
40
+ "focus-visible:ring-indicator-info",
41
+ "disabled:opacity-50",
42
+ "disabled:cursor-not-allowed",
43
+ "disabled:pointer-events-none"
44
+ ];
45
+ const sizeClasses = {
46
+ sm: ["w-6", "h-6", "text-sm"],
47
+ md: ["w-10", "h-10", "text-base"]
48
+ };
49
+ const activeClasses = active ? ["!bg-primary-50", "!text-primary-950", "hover:!bg-primary-100"] : [];
50
+ const allClasses = [
51
+ ...baseClasses,
52
+ ...sizeClasses[size],
53
+ ...activeClasses
54
+ ].join(" ");
55
+ const ariaLabel = props["aria-label"] ?? "Bot\xE3o de a\xE7\xE3o";
56
+ return /* @__PURE__ */ jsx(
57
+ "button",
58
+ {
59
+ ref,
60
+ type: "button",
61
+ className: cn(allClasses, className),
62
+ disabled,
63
+ "aria-pressed": active,
64
+ "aria-label": ariaLabel,
65
+ ...props,
66
+ children: /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center", children: icon })
67
+ }
68
+ );
69
+ }
70
+ );
71
+ IconButton.displayName = "IconButton";
72
+ var IconButton_default = IconButton;
73
+
74
+ // src/components/Text/Text.tsx
75
+ import { jsx as jsx2 } from "react/jsx-runtime";
76
+ var Text = ({
77
+ children,
78
+ size = "md",
79
+ weight = "normal",
80
+ color = "text-text-950",
81
+ as,
82
+ className = "",
83
+ ...props
84
+ }) => {
85
+ let sizeClasses = "";
86
+ let weightClasses = "";
87
+ const sizeClassMap = {
88
+ "2xs": "text-2xs",
89
+ xs: "text-xs",
90
+ sm: "text-sm",
91
+ md: "text-md",
92
+ lg: "text-lg",
93
+ xl: "text-xl",
94
+ "2xl": "text-2xl",
95
+ "3xl": "text-3xl",
96
+ "4xl": "text-4xl",
97
+ "5xl": "text-5xl",
98
+ "6xl": "text-6xl"
99
+ };
100
+ sizeClasses = sizeClassMap[size] ?? sizeClassMap.md;
101
+ const weightClassMap = {
102
+ hairline: "font-hairline",
103
+ light: "font-light",
104
+ normal: "font-normal",
105
+ medium: "font-medium",
106
+ semibold: "font-semibold",
107
+ bold: "font-bold",
108
+ extrabold: "font-extrabold",
109
+ black: "font-black"
110
+ };
111
+ weightClasses = weightClassMap[weight] ?? weightClassMap.normal;
112
+ const baseClasses = "font-primary";
113
+ const Component = as ?? "p";
114
+ return /* @__PURE__ */ jsx2(
115
+ Component,
116
+ {
117
+ className: cn(baseClasses, sizeClasses, weightClasses, color, className),
118
+ ...props,
119
+ children
120
+ }
121
+ );
122
+ };
123
+ var Text_default = Text;
124
+
125
+ // src/components/VideoPlayer/VideoPlayer.tsx
126
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
127
+ var formatTime = (seconds) => {
128
+ if (!seconds || isNaN(seconds)) return "0:00";
129
+ const mins = Math.floor(seconds / 60);
130
+ const secs = Math.floor(seconds % 60);
131
+ return `${mins}:${secs.toString().padStart(2, "0")}`;
132
+ };
133
+ var VideoPlayer = ({
134
+ src,
135
+ poster,
136
+ subtitles,
137
+ title,
138
+ subtitle: subtitleText,
139
+ initialTime = 0,
140
+ onTimeUpdate,
141
+ onProgress,
142
+ onVideoComplete,
143
+ className,
144
+ autoSave = true,
145
+ storageKey = "video-progress"
146
+ }) => {
147
+ const videoRef = useRef(null);
148
+ const [isPlaying, setIsPlaying] = useState(false);
149
+ const [currentTime, setCurrentTime] = useState(0);
150
+ const [duration, setDuration] = useState(0);
151
+ const [isMuted, setIsMuted] = useState(false);
152
+ const [volume, setVolume] = useState(1);
153
+ const [isFullscreen, setIsFullscreen] = useState(false);
154
+ const [showControls, setShowControls] = useState(true);
155
+ const [hasCompleted, setHasCompleted] = useState(false);
156
+ const [showCaptions, setShowCaptions] = useState(false);
157
+ const [playbackRate, setPlaybackRate] = useState(1);
158
+ const [showSpeedMenu, setShowSpeedMenu] = useState(false);
159
+ const lastSaveTimeRef = useRef(0);
160
+ const trackRef = useRef(null);
161
+ useEffect(() => {
162
+ if (videoRef.current) {
163
+ videoRef.current.volume = volume;
164
+ videoRef.current.muted = isMuted;
165
+ }
166
+ }, [volume, isMuted]);
167
+ useEffect(() => {
168
+ if (!autoSave || !storageKey) return;
169
+ const raw = localStorage.getItem(`${storageKey}-${src}`);
170
+ const saved = raw !== null ? Number(raw) : NaN;
171
+ const hasValidSaved = Number.isFinite(saved) && saved >= 0;
172
+ const hasValidInitial = Number.isFinite(initialTime) && initialTime >= 0;
173
+ let start;
174
+ if (hasValidInitial) {
175
+ start = initialTime;
176
+ } else if (hasValidSaved) {
177
+ start = saved;
178
+ } else {
179
+ start = void 0;
180
+ }
181
+ if (start !== void 0 && videoRef.current) {
182
+ videoRef.current.currentTime = start;
183
+ setCurrentTime(start);
184
+ }
185
+ }, [src, storageKey, autoSave, initialTime]);
186
+ const saveProgress = useCallback(() => {
187
+ if (!autoSave || !storageKey) return;
188
+ const now = Date.now();
189
+ if (now - lastSaveTimeRef.current > 5e3) {
190
+ localStorage.setItem(`${storageKey}-${src}`, currentTime.toString());
191
+ lastSaveTimeRef.current = now;
192
+ }
193
+ }, [autoSave, storageKey, src, currentTime]);
194
+ const togglePlayPause = useCallback(() => {
195
+ if (videoRef.current) {
196
+ if (isPlaying) {
197
+ videoRef.current.pause();
198
+ } else {
199
+ videoRef.current.play();
200
+ }
201
+ setIsPlaying(!isPlaying);
202
+ }
203
+ }, [isPlaying]);
204
+ const handleVolumeChange = useCallback(
205
+ (newVolume) => {
206
+ if (videoRef.current) {
207
+ const volumeValue = newVolume / 100;
208
+ videoRef.current.volume = volumeValue;
209
+ setVolume(volumeValue);
210
+ if (volumeValue === 0) {
211
+ videoRef.current.muted = true;
212
+ setIsMuted(true);
213
+ } else if (isMuted) {
214
+ videoRef.current.muted = false;
215
+ setIsMuted(false);
216
+ }
217
+ }
218
+ },
219
+ [isMuted]
220
+ );
221
+ const toggleMute = useCallback(() => {
222
+ if (videoRef.current) {
223
+ if (isMuted) {
224
+ const restoreVolume = volume > 0 ? volume : 0.5;
225
+ videoRef.current.volume = restoreVolume;
226
+ videoRef.current.muted = false;
227
+ setVolume(restoreVolume);
228
+ setIsMuted(false);
229
+ } else {
230
+ videoRef.current.muted = true;
231
+ setIsMuted(true);
232
+ }
233
+ }
234
+ }, [isMuted, volume]);
235
+ const toggleFullscreen = useCallback(() => {
236
+ const container = videoRef.current?.parentElement;
237
+ if (!container) return;
238
+ if (!isFullscreen) {
239
+ if (container.requestFullscreen) {
240
+ container.requestFullscreen();
241
+ }
242
+ } else if (document.exitFullscreen) {
243
+ document.exitFullscreen();
244
+ }
245
+ setIsFullscreen(!isFullscreen);
246
+ }, [isFullscreen]);
247
+ const handleSpeedChange = useCallback((speed) => {
248
+ if (videoRef.current) {
249
+ videoRef.current.playbackRate = speed;
250
+ setPlaybackRate(speed);
251
+ setShowSpeedMenu(false);
252
+ }
253
+ }, []);
254
+ const toggleSpeedMenu = useCallback(() => {
255
+ setShowSpeedMenu(!showSpeedMenu);
256
+ }, [showSpeedMenu]);
257
+ const toggleCaptions = useCallback(() => {
258
+ if (!trackRef.current?.track) return;
259
+ const newShowCaptions = !showCaptions;
260
+ setShowCaptions(newShowCaptions);
261
+ trackRef.current.track.mode = newShowCaptions ? "showing" : "hidden";
262
+ }, [showCaptions]);
263
+ const handleTimeUpdate = useCallback(() => {
264
+ if (videoRef.current) {
265
+ const current = videoRef.current.currentTime;
266
+ setCurrentTime(current);
267
+ saveProgress();
268
+ onTimeUpdate?.(current);
269
+ if (duration > 0) {
270
+ const progressPercent = current / duration * 100;
271
+ onProgress?.(progressPercent);
272
+ if (progressPercent >= 95 && !hasCompleted) {
273
+ setHasCompleted(true);
274
+ onVideoComplete?.();
275
+ }
276
+ }
277
+ }
278
+ }, [
279
+ duration,
280
+ saveProgress,
281
+ onTimeUpdate,
282
+ onProgress,
283
+ onVideoComplete,
284
+ hasCompleted
285
+ ]);
286
+ const handleLoadedMetadata = useCallback(() => {
287
+ if (videoRef.current) {
288
+ setDuration(videoRef.current.duration);
289
+ }
290
+ }, []);
291
+ useEffect(() => {
292
+ const handleVisibilityChange = () => {
293
+ if (document.hidden && isPlaying && videoRef.current) {
294
+ videoRef.current.pause();
295
+ setIsPlaying(false);
296
+ }
297
+ };
298
+ const handleBlur = () => {
299
+ if (isPlaying && videoRef.current) {
300
+ videoRef.current.pause();
301
+ setIsPlaying(false);
302
+ }
303
+ };
304
+ document.addEventListener("visibilitychange", handleVisibilityChange);
305
+ window.addEventListener("blur", handleBlur);
306
+ return () => {
307
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
308
+ window.removeEventListener("blur", handleBlur);
309
+ };
310
+ }, [isPlaying]);
311
+ const progressPercentage = duration > 0 ? currentTime / duration * 100 : 0;
312
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", className), children: [
313
+ (title || subtitleText) && /* @__PURE__ */ jsx3("div", { className: "bg-subject-1 rounded-t-xl px-8 py-4 flex items-end justify-between min-h-20", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
314
+ title && /* @__PURE__ */ jsx3(
315
+ Text_default,
316
+ {
317
+ as: "h2",
318
+ size: "lg",
319
+ weight: "bold",
320
+ color: "text-text-900",
321
+ className: "leading-5 tracking-wide",
322
+ children: title
323
+ }
324
+ ),
325
+ subtitleText && /* @__PURE__ */ jsx3(
326
+ Text_default,
327
+ {
328
+ as: "p",
329
+ size: "sm",
330
+ weight: "normal",
331
+ color: "text-text-600",
332
+ className: "leading-5",
333
+ children: subtitleText
334
+ }
335
+ )
336
+ ] }) }),
337
+ /* @__PURE__ */ jsxs(
338
+ "div",
339
+ {
340
+ className: cn(
341
+ "relative w-full bg-background overflow-hidden group",
342
+ title || subtitleText ? "rounded-b-xl" : "rounded-xl"
343
+ ),
344
+ children: [
345
+ /* @__PURE__ */ jsx3(
346
+ "video",
347
+ {
348
+ ref: videoRef,
349
+ src,
350
+ poster,
351
+ className: "w-full h-full object-contain",
352
+ controlsList: "nodownload",
353
+ onTimeUpdate: handleTimeUpdate,
354
+ onLoadedMetadata: handleLoadedMetadata,
355
+ onClick: togglePlayPause,
356
+ onKeyDown: (e) => {
357
+ if (e.key) {
358
+ setShowControls(true);
359
+ }
360
+ if (e.key === " " || e.key === "Enter") {
361
+ e.preventDefault();
362
+ togglePlayPause();
363
+ }
364
+ if (e.key === "ArrowLeft" && videoRef.current) {
365
+ e.preventDefault();
366
+ videoRef.current.currentTime -= 10;
367
+ }
368
+ if (e.key === "ArrowRight" && videoRef.current) {
369
+ e.preventDefault();
370
+ videoRef.current.currentTime += 10;
371
+ }
372
+ if (e.key === "ArrowUp") {
373
+ e.preventDefault();
374
+ handleVolumeChange(Math.min(100, volume * 100 + 10));
375
+ }
376
+ if (e.key === "ArrowDown") {
377
+ e.preventDefault();
378
+ handleVolumeChange(Math.max(0, volume * 100 - 10));
379
+ }
380
+ if (e.key === "m" || e.key === "M") {
381
+ e.preventDefault();
382
+ toggleMute();
383
+ }
384
+ if (e.key === "f" || e.key === "F") {
385
+ e.preventDefault();
386
+ toggleFullscreen();
387
+ }
388
+ },
389
+ tabIndex: 0,
390
+ "aria-label": title ? `Video: ${title}` : "Video player",
391
+ children: /* @__PURE__ */ jsx3(
392
+ "track",
393
+ {
394
+ ref: trackRef,
395
+ kind: "captions",
396
+ src: subtitles || "data:text/vtt;charset=utf-8,WEBVTT%0A%0ANOTE%20No%20captions%20available",
397
+ srcLang: "en",
398
+ label: subtitles ? "Subtitles" : "No captions available",
399
+ default: false
400
+ }
401
+ )
402
+ }
403
+ ),
404
+ !isPlaying && /* @__PURE__ */ jsx3("div", { className: "absolute inset-0 flex items-center justify-center bg-black/30 transition-opacity", children: /* @__PURE__ */ jsx3(
405
+ IconButton_default,
406
+ {
407
+ icon: /* @__PURE__ */ jsx3(Play, { size: 32, weight: "regular", className: "ml-1" }),
408
+ onClick: togglePlayPause,
409
+ "aria-label": "Play video",
410
+ className: "!bg-transparent !text-white !w-auto !h-auto hover:!bg-transparent hover:!text-gray-200"
411
+ }
412
+ ) }),
413
+ /* @__PURE__ */ jsx3(
414
+ "div",
415
+ {
416
+ className: cn(
417
+ "absolute top-0 left-0 right-0 p-4 bg-gradient-to-b from-black/70 to-transparent transition-opacity",
418
+ !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100"
419
+ ),
420
+ children: /* @__PURE__ */ jsx3("div", { className: "ml-auto block", children: /* @__PURE__ */ jsx3(
421
+ IconButton_default,
422
+ {
423
+ icon: isFullscreen ? /* @__PURE__ */ jsx3(ArrowsInSimple, { size: 24 }) : /* @__PURE__ */ jsx3(ArrowsOutSimple, { size: 24 }),
424
+ onClick: toggleFullscreen,
425
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
426
+ className: "!bg-transparent !text-white hover:!bg-white/20"
427
+ }
428
+ ) })
429
+ }
430
+ ),
431
+ /* @__PURE__ */ jsxs(
432
+ "div",
433
+ {
434
+ className: cn(
435
+ "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent transition-opacity",
436
+ !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100"
437
+ ),
438
+ children: [
439
+ /* @__PURE__ */ jsx3("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsx3(
440
+ "input",
441
+ {
442
+ type: "range",
443
+ min: 0,
444
+ max: duration || 100,
445
+ value: currentTime,
446
+ onChange: (e) => {
447
+ const newTime = parseFloat(e.target.value);
448
+ if (videoRef.current) {
449
+ videoRef.current.currentTime = newTime;
450
+ }
451
+ },
452
+ className: "w-full h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer slider:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500",
453
+ "aria-label": "Video progress",
454
+ style: {
455
+ background: `linear-gradient(to right, #2271C4 ${progressPercentage}%, #D5D4D4 ${progressPercentage}%)`
456
+ }
457
+ }
458
+ ) }),
459
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 pb-4", children: [
460
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
461
+ /* @__PURE__ */ jsx3(
462
+ IconButton_default,
463
+ {
464
+ icon: isPlaying ? /* @__PURE__ */ jsx3(Pause, { size: 24 }) : /* @__PURE__ */ jsx3(Play, { size: 24 }),
465
+ onClick: togglePlayPause,
466
+ "aria-label": isPlaying ? "Pause" : "Play",
467
+ className: "!bg-transparent !text-white hover:!bg-white/20"
468
+ }
469
+ ),
470
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
471
+ /* @__PURE__ */ jsx3(
472
+ IconButton_default,
473
+ {
474
+ icon: isMuted ? /* @__PURE__ */ jsx3(SpeakerSlash, { size: 24 }) : /* @__PURE__ */ jsx3(SpeakerHigh, { size: 24 }),
475
+ onClick: toggleMute,
476
+ "aria-label": isMuted ? "Unmute" : "Mute",
477
+ className: "!bg-transparent !text-white hover:!bg-white/20"
478
+ }
479
+ ),
480
+ /* @__PURE__ */ jsx3(
481
+ "input",
482
+ {
483
+ type: "range",
484
+ min: 0,
485
+ max: 100,
486
+ value: Math.round(volume * 100),
487
+ onChange: (e) => handleVolumeChange(parseInt(e.target.value)),
488
+ className: "w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500",
489
+ "aria-label": "Volume control",
490
+ style: {
491
+ background: `linear-gradient(to right, #2271C4 ${volume * 100}%, #D5D4D4 ${volume * 100}%)`
492
+ }
493
+ }
494
+ )
495
+ ] }),
496
+ subtitles && /* @__PURE__ */ jsx3(
497
+ IconButton_default,
498
+ {
499
+ icon: /* @__PURE__ */ jsx3(ClosedCaptioning, { size: 24 }),
500
+ onClick: toggleCaptions,
501
+ "aria-label": showCaptions ? "Hide captions" : "Show captions",
502
+ className: cn(
503
+ "!bg-transparent hover:!bg-white/20",
504
+ showCaptions ? "!text-primary-400" : "!text-white"
505
+ )
506
+ }
507
+ ),
508
+ /* @__PURE__ */ jsxs(Text_default, { size: "sm", weight: "medium", color: "text-white", children: [
509
+ formatTime(currentTime),
510
+ " / ",
511
+ formatTime(duration)
512
+ ] })
513
+ ] }),
514
+ /* @__PURE__ */ jsx3("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
515
+ /* @__PURE__ */ jsx3(
516
+ IconButton_default,
517
+ {
518
+ icon: /* @__PURE__ */ jsx3(DotsThreeVertical, { size: 24 }),
519
+ onClick: toggleSpeedMenu,
520
+ "aria-label": "Playback speed",
521
+ className: "!bg-transparent !text-white hover:!bg-white/20"
522
+ }
523
+ ),
524
+ showSpeedMenu && /* @__PURE__ */ jsx3("div", { className: "absolute bottom-12 right-0 bg-black/90 rounded-lg p-2 min-w-20", children: [0.5, 0.75, 1, 1.25, 1.5, 2].map((speed) => /* @__PURE__ */ jsxs(
525
+ "button",
526
+ {
527
+ onClick: () => handleSpeedChange(speed),
528
+ className: `block w-full text-left px-3 py-1 text-sm rounded hover:bg-white/20 transition-colors ${playbackRate === speed ? "text-primary-400" : "text-white"}`,
529
+ children: [
530
+ speed,
531
+ "x"
532
+ ]
533
+ },
534
+ speed
535
+ )) })
536
+ ] }) })
537
+ ] })
538
+ ]
539
+ }
540
+ )
541
+ ]
542
+ }
543
+ )
544
+ ] });
545
+ };
546
+ var VideoPlayer_default = VideoPlayer;
547
+ export {
548
+ VideoPlayer_default as default
549
+ };
550
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/VideoPlayer/VideoPlayer.tsx","../../src/utils/utils.ts","../../src/components/IconButton/IconButton.tsx","../../src/components/Text/Text.tsx"],"sourcesContent":["import { useRef, useState, useEffect, useCallback } from 'react';\nimport {\n Play,\n Pause,\n SpeakerHigh,\n SpeakerSlash,\n ArrowsOutSimple,\n ArrowsInSimple,\n ClosedCaptioning,\n DotsThreeVertical,\n} from 'phosphor-react';\nimport { cn } from '../../utils/utils';\nimport IconButton from '../IconButton/IconButton';\nimport Text from '../Text/Text';\n\n/**\n * VideoPlayer component props interface\n */\ninterface VideoPlayerProps {\n /** Video source URL */\n src: string;\n /** Video poster/thumbnail URL */\n poster?: string;\n /** Subtitles URL */\n subtitles?: string;\n /** Video title */\n title?: string;\n /** Video subtitle/description */\n subtitle?: string;\n /** Initial playback time in seconds */\n initialTime?: number;\n /** Callback fired when video time updates (seconds) */\n onTimeUpdate?: (seconds: number) => void;\n /** Callback fired with progress percentage (0-100) */\n onProgress?: (progress: number) => void;\n /** Callback fired when video completes (>95% watched) */\n onVideoComplete?: () => void;\n /** Additional CSS classes */\n className?: string;\n /** Auto-save progress to localStorage */\n autoSave?: boolean;\n /** localStorage key for saving progress */\n storageKey?: string;\n}\n\n/**\n * Format seconds to MM:SS display format\n * @param seconds - Time in seconds\n * @returns Formatted time string\n */\nconst formatTime = (seconds: number): string => {\n if (!seconds || isNaN(seconds)) return '0:00';\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n};\n\n/**\n * Video player component with controls and progress tracking\n * Integrates with backend lesson progress system\n *\n * @param props - VideoPlayer component props\n * @returns Video player element with controls\n */\nconst VideoPlayer = ({\n src,\n poster,\n subtitles,\n title,\n subtitle: subtitleText,\n initialTime = 0,\n onTimeUpdate,\n onProgress,\n onVideoComplete,\n className,\n autoSave = true,\n storageKey = 'video-progress',\n}: VideoPlayerProps) => {\n const videoRef = useRef<HTMLVideoElement>(null);\n const [isPlaying, setIsPlaying] = useState(false);\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState(0);\n const [isMuted, setIsMuted] = useState(false);\n const [volume, setVolume] = useState(1);\n const [isFullscreen, setIsFullscreen] = useState(false);\n const [showControls, setShowControls] = useState(true);\n const [hasCompleted, setHasCompleted] = useState(false);\n const [showCaptions, setShowCaptions] = useState(false);\n const [playbackRate, setPlaybackRate] = useState(1);\n const [showSpeedMenu, setShowSpeedMenu] = useState(false);\n const lastSaveTimeRef = useRef(0);\n const trackRef = useRef<HTMLTrackElement>(null);\n\n /**\n * Initialize video element properties\n */\n useEffect(() => {\n // Set initial volume\n if (videoRef.current) {\n videoRef.current.volume = volume;\n videoRef.current.muted = isMuted;\n }\n }, [volume, isMuted]);\n\n /**\n * Load saved progress from localStorage\n */\n useEffect(() => {\n if (!autoSave || !storageKey) return;\n\n const raw = localStorage.getItem(`${storageKey}-${src}`);\n const saved = raw !== null ? Number(raw) : NaN;\n const hasValidSaved = Number.isFinite(saved) && saved >= 0;\n const hasValidInitial = Number.isFinite(initialTime) && initialTime >= 0;\n\n let start: number | undefined;\n if (hasValidInitial) {\n start = initialTime;\n } else if (hasValidSaved) {\n start = saved;\n } else {\n start = undefined;\n }\n if (start !== undefined && videoRef.current) {\n videoRef.current.currentTime = start;\n setCurrentTime(start);\n }\n }, [src, storageKey, autoSave, initialTime]);\n\n /**\n * Save progress to localStorage periodically\n */\n const saveProgress = useCallback(() => {\n if (!autoSave || !storageKey) return;\n\n const now = Date.now();\n if (now - lastSaveTimeRef.current > 5000) {\n localStorage.setItem(`${storageKey}-${src}`, currentTime.toString());\n lastSaveTimeRef.current = now;\n }\n }, [autoSave, storageKey, src, currentTime]);\n\n /**\n * Handle play/pause toggle\n */\n const togglePlayPause = useCallback(() => {\n if (videoRef.current) {\n if (isPlaying) {\n videoRef.current.pause();\n } else {\n videoRef.current.play();\n }\n setIsPlaying(!isPlaying);\n }\n }, [isPlaying]);\n\n /**\n * Handle volume change\n */\n const handleVolumeChange = useCallback(\n (newVolume: number) => {\n if (videoRef.current) {\n const volumeValue = newVolume / 100; // Convert 0-100 to 0-1\n videoRef.current.volume = volumeValue;\n setVolume(volumeValue);\n\n // Auto mute/unmute based on volume\n if (volumeValue === 0) {\n videoRef.current.muted = true;\n setIsMuted(true);\n } else if (isMuted) {\n videoRef.current.muted = false;\n setIsMuted(false);\n }\n }\n },\n [isMuted]\n );\n\n /**\n * Handle mute toggle\n */\n const toggleMute = useCallback(() => {\n if (videoRef.current) {\n if (isMuted) {\n // Unmute: restore volume or set to 50% if it was 0\n const restoreVolume = volume > 0 ? volume : 0.5;\n videoRef.current.volume = restoreVolume;\n videoRef.current.muted = false;\n setVolume(restoreVolume);\n setIsMuted(false);\n } else {\n // Mute: set volume to 0 and mute\n videoRef.current.muted = true;\n setIsMuted(true);\n }\n }\n }, [isMuted, volume]);\n\n /**\n * Handle fullscreen toggle\n */\n const toggleFullscreen = useCallback(() => {\n const container = videoRef.current?.parentElement;\n if (!container) return;\n\n if (!isFullscreen) {\n if (container.requestFullscreen) {\n container.requestFullscreen();\n }\n } else if (document.exitFullscreen) {\n document.exitFullscreen();\n }\n setIsFullscreen(!isFullscreen);\n }, [isFullscreen]);\n\n /**\n * Handle playback speed change\n */\n const handleSpeedChange = useCallback((speed: number) => {\n if (videoRef.current) {\n videoRef.current.playbackRate = speed;\n setPlaybackRate(speed);\n setShowSpeedMenu(false);\n }\n }, []);\n\n /**\n * Toggle speed menu visibility\n */\n const toggleSpeedMenu = useCallback(() => {\n setShowSpeedMenu(!showSpeedMenu);\n }, [showSpeedMenu]);\n\n /**\n * Toggle captions visibility\n */\n const toggleCaptions = useCallback(() => {\n if (!trackRef.current?.track) return;\n\n const newShowCaptions = !showCaptions;\n setShowCaptions(newShowCaptions);\n\n // Control track mode programmatically\n trackRef.current.track.mode = newShowCaptions ? 'showing' : 'hidden';\n }, [showCaptions]);\n\n /**\n * Handle time update\n */\n const handleTimeUpdate = useCallback(() => {\n if (videoRef.current) {\n const current = videoRef.current.currentTime;\n setCurrentTime(current);\n\n // Save progress periodically\n saveProgress();\n\n // Fire callbacks\n onTimeUpdate?.(current);\n\n if (duration > 0) {\n const progressPercent = (current / duration) * 100;\n onProgress?.(progressPercent);\n\n // Check for completion (>95% watched)\n if (progressPercent >= 95 && !hasCompleted) {\n setHasCompleted(true);\n onVideoComplete?.();\n }\n }\n }\n }, [\n duration,\n saveProgress,\n onTimeUpdate,\n onProgress,\n onVideoComplete,\n hasCompleted,\n ]);\n\n /**\n * Handle loaded metadata\n */\n const handleLoadedMetadata = useCallback(() => {\n if (videoRef.current) {\n setDuration(videoRef.current.duration);\n }\n }, []);\n\n /**\n * Handle visibility change and blur to pause video when losing focus\n */\n useEffect(() => {\n const handleVisibilityChange = () => {\n if (document.hidden && isPlaying && videoRef.current) {\n videoRef.current.pause();\n setIsPlaying(false);\n }\n };\n\n const handleBlur = () => {\n if (isPlaying && videoRef.current) {\n videoRef.current.pause();\n setIsPlaying(false);\n }\n };\n\n document.addEventListener('visibilitychange', handleVisibilityChange);\n window.addEventListener('blur', handleBlur);\n\n return () => {\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n window.removeEventListener('blur', handleBlur);\n };\n }, [isPlaying]);\n\n const progressPercentage = duration > 0 ? (currentTime / duration) * 100 : 0;\n\n return (\n <div className={cn('flex flex-col', className)}>\n {/* Integrated Header */}\n {(title || subtitleText) && (\n <div className=\"bg-subject-1 rounded-t-xl px-8 py-4 flex items-end justify-between min-h-20\">\n <div className=\"flex flex-col gap-1\">\n {title && (\n <Text\n as=\"h2\"\n size=\"lg\"\n weight=\"bold\"\n color=\"text-text-900\"\n className=\"leading-5 tracking-wide\"\n >\n {title}\n </Text>\n )}\n {subtitleText && (\n <Text\n as=\"p\"\n size=\"sm\"\n weight=\"normal\"\n color=\"text-text-600\"\n className=\"leading-5\"\n >\n {subtitleText}\n </Text>\n )}\n </div>\n </div>\n )}\n\n {/* Video Container */}\n <div\n className={cn(\n 'relative w-full bg-background overflow-hidden group',\n title || subtitleText ? 'rounded-b-xl' : 'rounded-xl'\n )}\n >\n {/* Video Element */}\n <video\n ref={videoRef}\n src={src}\n poster={poster}\n className=\"w-full h-full object-contain\"\n controlsList=\"nodownload\"\n onTimeUpdate={handleTimeUpdate}\n onLoadedMetadata={handleLoadedMetadata}\n onClick={togglePlayPause}\n onKeyDown={(e) => {\n // Show controls when any key is pressed\n if (e.key) {\n setShowControls(true);\n }\n // Space or Enter to play/pause\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n togglePlayPause();\n }\n // Arrow keys for seeking\n if (e.key === 'ArrowLeft' && videoRef.current) {\n e.preventDefault();\n videoRef.current.currentTime -= 10;\n }\n if (e.key === 'ArrowRight' && videoRef.current) {\n e.preventDefault();\n videoRef.current.currentTime += 10;\n }\n // Arrow keys for volume\n if (e.key === 'ArrowUp') {\n e.preventDefault();\n handleVolumeChange(Math.min(100, volume * 100 + 10));\n }\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n handleVolumeChange(Math.max(0, volume * 100 - 10));\n }\n // M for mute\n if (e.key === 'm' || e.key === 'M') {\n e.preventDefault();\n toggleMute();\n }\n // F for fullscreen\n if (e.key === 'f' || e.key === 'F') {\n e.preventDefault();\n toggleFullscreen();\n }\n }}\n tabIndex={0}\n aria-label={title ? `Video: ${title}` : 'Video player'}\n >\n <track\n ref={trackRef}\n kind=\"captions\"\n src={\n subtitles ||\n 'data:text/vtt;charset=utf-8,WEBVTT%0A%0ANOTE%20No%20captions%20available'\n }\n srcLang=\"en\"\n label={subtitles ? 'Subtitles' : 'No captions available'}\n default={false}\n />\n </video>\n\n {/* Center Play Button */}\n {!isPlaying && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/30 transition-opacity\">\n <IconButton\n icon={<Play size={32} weight=\"regular\" className=\"ml-1\" />}\n onClick={togglePlayPause}\n aria-label=\"Play video\"\n className=\"!bg-transparent !text-white !w-auto !h-auto hover:!bg-transparent hover:!text-gray-200\"\n />\n </div>\n )}\n\n {/* Top Controls */}\n <div\n className={cn(\n 'absolute top-0 left-0 right-0 p-4 bg-gradient-to-b from-black/70 to-transparent transition-opacity',\n !isPlaying || showControls\n ? 'opacity-100'\n : 'opacity-0 group-hover:opacity-100'\n )}\n >\n <div className=\"ml-auto block\">\n <IconButton\n icon={\n isFullscreen ? (\n <ArrowsInSimple size={24} />\n ) : (\n <ArrowsOutSimple size={24} />\n )\n }\n onClick={toggleFullscreen}\n aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}\n className=\"!bg-transparent !text-white hover:!bg-white/20\"\n />\n </div>\n </div>\n\n {/* Bottom Controls */}\n <div\n className={cn(\n 'absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent transition-opacity',\n !isPlaying || showControls\n ? 'opacity-100'\n : 'opacity-0 group-hover:opacity-100'\n )}\n >\n {/* Progress Bar */}\n <div className=\"px-4 pb-2\">\n <input\n type=\"range\"\n min={0}\n max={duration || 100}\n value={currentTime}\n onChange={(e) => {\n const newTime = parseFloat(e.target.value);\n if (videoRef.current) {\n videoRef.current.currentTime = newTime;\n }\n }}\n className=\"w-full h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer slider:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500\"\n aria-label=\"Video progress\"\n style={{\n background: `linear-gradient(to right, #2271C4 ${progressPercentage}%, #D5D4D4 ${progressPercentage}%)`,\n }}\n />\n </div>\n\n {/* Control Buttons */}\n <div className=\"flex items-center justify-between px-4 pb-4\">\n {/* Left Controls */}\n <div className=\"flex items-center gap-4\">\n {/* Play/Pause */}\n <IconButton\n icon={isPlaying ? <Pause size={24} /> : <Play size={24} />}\n onClick={togglePlayPause}\n aria-label={isPlaying ? 'Pause' : 'Play'}\n className=\"!bg-transparent !text-white hover:!bg-white/20\"\n />\n\n {/* Volume */}\n <div className=\"flex items-center gap-2\">\n <IconButton\n icon={\n isMuted ? (\n <SpeakerSlash size={24} />\n ) : (\n <SpeakerHigh size={24} />\n )\n }\n onClick={toggleMute}\n aria-label={isMuted ? 'Unmute' : 'Mute'}\n className=\"!bg-transparent !text-white hover:!bg-white/20\"\n />\n\n {/* Volume Slider */}\n <input\n type=\"range\"\n min={0}\n max={100}\n value={Math.round(volume * 100)}\n onChange={(e) => handleVolumeChange(parseInt(e.target.value))}\n className=\"w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500\"\n aria-label=\"Volume control\"\n style={{\n background: `linear-gradient(to right, #2271C4 ${volume * 100}%, #D5D4D4 ${volume * 100}%)`,\n }}\n />\n </div>\n\n {/* Captions */}\n {subtitles && (\n <IconButton\n icon={<ClosedCaptioning size={24} />}\n onClick={toggleCaptions}\n aria-label={showCaptions ? 'Hide captions' : 'Show captions'}\n className={cn(\n '!bg-transparent hover:!bg-white/20',\n showCaptions ? '!text-primary-400' : '!text-white'\n )}\n />\n )}\n\n {/* Time Display */}\n <Text size=\"sm\" weight=\"medium\" color=\"text-white\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </Text>\n </div>\n\n {/* Right Controls */}\n <div className=\"flex items-center gap-4\">\n {/* Speed Control */}\n <div className=\"relative\">\n <IconButton\n icon={<DotsThreeVertical size={24} />}\n onClick={toggleSpeedMenu}\n aria-label=\"Playback speed\"\n className=\"!bg-transparent !text-white hover:!bg-white/20\"\n />\n {showSpeedMenu && (\n <div className=\"absolute bottom-12 right-0 bg-black/90 rounded-lg p-2 min-w-20\">\n {[0.5, 0.75, 1, 1.25, 1.5, 2].map((speed) => (\n <button\n key={speed}\n onClick={() => handleSpeedChange(speed)}\n className={`block w-full text-left px-3 py-1 text-sm rounded hover:bg-white/20 transition-colors ${\n playbackRate === speed\n ? 'text-primary-400'\n : 'text-white'\n }`}\n >\n {speed}x\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n};\n\nexport default VideoPlayer;\n","import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import { ButtonHTMLAttributes, ReactNode, forwardRef } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * IconButton component props interface\n */\nexport type IconButtonProps = {\n /** Ícone a ser exibido no botão */\n icon: ReactNode;\n /** Tamanho do botão */\n size?: 'sm' | 'md';\n /** Estado de seleção/ativo do botão - permanece ativo até ser clicado novamente ou outro botão ser ativado */\n active?: boolean;\n /** Additional CSS classes to apply */\n className?: string;\n} & ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * IconButton component for Analytica Ensino platforms\n *\n * Um botão compacto apenas com ícone, ideal para menus dropdown,\n * barras de ferramentas e ações secundárias.\n * Oferece dois tamanhos com estilo consistente.\n * Estado ativo permanece até ser clicado novamente ou outro botão ser ativado.\n * Suporta forwardRef para acesso programático ao elemento DOM.\n *\n * @param icon - O ícone a ser exibido no botão\n * @param size - Tamanho do botão (sm, md)\n * @param active - Estado ativo/selecionado do botão\n * @param className - Classes CSS adicionais\n * @param props - Todos os outros atributos HTML padrão de button\n * @returns Um elemento button compacto estilizado apenas com ícone\n *\n * @example\n * ```tsx\n * <IconButton\n * icon={<MoreVerticalIcon />}\n * size=\"sm\"\n * onClick={() => openMenu()}\n * />\n * ```\n *\n * @example\n * ```tsx\n * // Botão ativo em uma barra de ferramentas - permanece ativo até outro clique\n * <IconButton\n * icon={<BoldIcon />}\n * active={isBold}\n * onClick={toggleBold}\n * />\n * ```\n *\n * @example\n * ```tsx\n * // Usando ref para controle programático\n * const buttonRef = useRef<HTMLButtonElement>(null);\n *\n * <IconButton\n * ref={buttonRef}\n * icon={<EditIcon />}\n * size=\"md\"\n * onClick={() => startEditing()}\n * />\n * ```\n */\nconst IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(\n (\n { icon, size = 'md', active = false, className = '', disabled, ...props },\n ref\n ) => {\n // Classes base para todos os estados\n const baseClasses = [\n 'inline-flex',\n 'items-center',\n 'justify-center',\n 'rounded-lg',\n 'font-medium',\n 'bg-transparent',\n 'text-text-950',\n 'cursor-pointer',\n 'hover:bg-primary-600',\n 'hover:text-text',\n 'focus-visible:outline-none',\n 'focus-visible:ring-2',\n 'focus-visible:ring-offset-0',\n 'focus-visible:ring-indicator-info',\n 'disabled:opacity-50',\n 'disabled:cursor-not-allowed',\n 'disabled:pointer-events-none',\n ];\n\n // Classes de tamanho\n const sizeClasses = {\n sm: ['w-6', 'h-6', 'text-sm'],\n md: ['w-10', 'h-10', 'text-base'],\n };\n\n // Classes de estado ativo\n const activeClasses = active\n ? ['!bg-primary-50', '!text-primary-950', 'hover:!bg-primary-100']\n : [];\n\n const allClasses = [\n ...baseClasses,\n ...sizeClasses[size],\n ...activeClasses,\n ].join(' ');\n\n // Garantir acessibilidade com aria-label padrão\n const ariaLabel = props['aria-label'] ?? 'Botão de ação';\n\n return (\n <button\n ref={ref}\n type=\"button\"\n className={cn(allClasses, className)}\n disabled={disabled}\n aria-pressed={active}\n aria-label={ariaLabel}\n {...props}\n >\n <span className=\"flex items-center justify-center\">{icon}</span>\n </button>\n );\n }\n);\n\nIconButton.displayName = 'IconButton';\n\nexport default IconButton;\n","import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Base text component props\n */\ntype BaseTextProps = {\n /** Content to be displayed */\n children?: ReactNode;\n /** Text size variant */\n size?:\n | '2xs'\n | 'xs'\n | 'sm'\n | 'md'\n | 'lg'\n | 'xl'\n | '2xl'\n | '3xl'\n | '4xl'\n | '5xl'\n | '6xl';\n /** Font weight variant */\n weight?:\n | 'hairline'\n | 'light'\n | 'normal'\n | 'medium'\n | 'semibold'\n | 'bold'\n | 'extrabold'\n | 'black';\n /** Color variant - white for light backgrounds, black for dark backgrounds */\n color?: string;\n /** Additional CSS classes to apply */\n className?: string;\n};\n\n/**\n * Polymorphic text component props that ensures type safety based on the 'as' prop\n */\ntype TextProps<T extends ElementType = 'p'> = BaseTextProps & {\n /** HTML tag to render */\n as?: T;\n} & Omit<ComponentPropsWithoutRef<T>, keyof BaseTextProps>;\n\n/**\n * Text component for Analytica Ensino platforms\n *\n * A flexible polymorphic text component with multiple sizes, weights, and colors.\n * Automatically adapts to dark and light themes with full type safety.\n *\n * @param children - The content to display\n * @param size - The text size variant (2xs, xs, sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl)\n * @param weight - The font weight variant (hairline, light, normal, medium, semibold, bold, extrabold, black)\n * @param color - The color variant - adapts to theme\n * @param as - The HTML tag to render - determines allowed attributes via TypeScript\n * @param className - Additional CSS classes\n * @param props - HTML attributes valid for the chosen tag only\n * @returns A styled text element with type-safe attributes\n *\n * @example\n * ```tsx\n * <Text size=\"lg\" weight=\"bold\" color=\"text-info-800\">\n * This is a large, bold text\n * </Text>\n *\n * <Text as=\"a\" href=\"/link\" target=\"_blank\">\n * Link with type-safe anchor attributes\n * </Text>\n *\n * <Text as=\"button\" onClick={handleClick} disabled>\n * Button with type-safe button attributes\n * </Text>\n * ```\n */\nconst Text = <T extends ElementType = 'p'>({\n children,\n size = 'md',\n weight = 'normal',\n color = 'text-text-950',\n as,\n className = '',\n ...props\n}: TextProps<T>) => {\n let sizeClasses = '';\n let weightClasses = '';\n\n // Text size classes mapping\n const sizeClassMap = {\n '2xs': 'text-2xs',\n xs: 'text-xs',\n sm: 'text-sm',\n md: 'text-md',\n lg: 'text-lg',\n xl: 'text-xl',\n '2xl': 'text-2xl',\n '3xl': 'text-3xl',\n '4xl': 'text-4xl',\n '5xl': 'text-5xl',\n '6xl': 'text-6xl',\n } as const;\n\n sizeClasses = sizeClassMap[size] ?? sizeClassMap.md;\n\n // Font weight classes mapping\n const weightClassMap = {\n hairline: 'font-hairline',\n light: 'font-light',\n normal: 'font-normal',\n medium: 'font-medium',\n semibold: 'font-semibold',\n bold: 'font-bold',\n extrabold: 'font-extrabold',\n black: 'font-black',\n } as const;\n\n weightClasses = weightClassMap[weight] ?? weightClassMap.normal;\n\n const baseClasses = 'font-primary';\n const Component = as ?? ('p' as ElementType);\n\n return (\n <Component\n className={cn(baseClasses, sizeClasses, weightClasses, color, className)}\n {...props}\n >\n {children}\n </Component>\n );\n};\n\nexport default Text;\n"],"mappings":"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","names":["jsx","jsx"]}