@fluencypassdevs/cycle 1.10.0 → 1.15.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/bin/mcp.mjs +225 -9
- package/dist/chunk-5HPZCREI.js +55 -0
- package/dist/chunk-5HPZCREI.js.map +1 -0
- package/dist/chunk-6JH4YWWX.js +29 -0
- package/dist/chunk-6JH4YWWX.js.map +1 -0
- package/dist/chunk-7LH7PSQS.js +77 -0
- package/dist/chunk-7LH7PSQS.js.map +1 -0
- package/dist/{chunk-YMWRR7ET.js → chunk-CG7NXMBC.js} +18 -23
- package/dist/chunk-CG7NXMBC.js.map +1 -0
- package/dist/{chunk-27PO7X4G.js → chunk-CWMXYPWK.js} +101 -11
- package/dist/chunk-CWMXYPWK.js.map +1 -0
- package/dist/chunk-EACBYVLK.js +29 -0
- package/dist/chunk-EACBYVLK.js.map +1 -0
- package/dist/chunk-HDCU6QSG.js +24 -0
- package/dist/chunk-HDCU6QSG.js.map +1 -0
- package/dist/{chunk-U4QKU5RB.js → chunk-JGUDRAWA.js} +81 -23
- package/dist/chunk-JGUDRAWA.js.map +1 -0
- package/dist/chunk-K2NUBNSA.js +17 -0
- package/dist/chunk-K2NUBNSA.js.map +1 -0
- package/dist/chunk-NW56QDQM.js +64 -0
- package/dist/chunk-NW56QDQM.js.map +1 -0
- package/dist/chunk-NZXP7BAH.js +33 -0
- package/dist/chunk-NZXP7BAH.js.map +1 -0
- package/dist/chunk-TPRZMMT6.js +57 -0
- package/dist/chunk-TPRZMMT6.js.map +1 -0
- package/dist/chunk-UJ6OER6O.js +49 -0
- package/dist/chunk-UJ6OER6O.js.map +1 -0
- package/dist/chunk-VXWMC2NK.js +24 -0
- package/dist/chunk-VXWMC2NK.js.map +1 -0
- package/dist/chunk-VYHRNO77.js +27 -0
- package/dist/chunk-VYHRNO77.js.map +1 -0
- package/dist/chunk-XHKFKLHD.js +26 -0
- package/dist/chunk-XHKFKLHD.js.map +1 -0
- package/dist/chunk-XQUQDZVA.js +36 -0
- package/dist/chunk-XQUQDZVA.js.map +1 -0
- package/dist/composites/chat-thread.d.ts +27 -2
- package/dist/composites/chat-thread.js +3 -3
- package/dist/hooks/use-intersection.d.ts +34 -0
- package/dist/hooks/use-intersection.js +4 -0
- package/dist/hooks/use-intersection.js.map +1 -0
- package/dist/hooks/use-reduced-motion.d.ts +11 -0
- package/dist/hooks/use-reduced-motion.js +4 -0
- package/dist/hooks/use-reduced-motion.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/marketing/effects/card-dark-glow.d.ts +30 -0
- package/dist/marketing/effects/card-dark-glow.js +5 -0
- package/dist/marketing/effects/card-dark-glow.js.map +1 -0
- package/dist/marketing/effects/circle-glow.d.ts +26 -0
- package/dist/marketing/effects/circle-glow.js +5 -0
- package/dist/marketing/effects/circle-glow.js.map +1 -0
- package/dist/marketing/effects/index.d.ts +4 -0
- package/dist/marketing/effects/index.js +7 -0
- package/dist/marketing/effects/index.js.map +1 -0
- package/dist/marketing/effects/marquee.d.ts +30 -0
- package/dist/marketing/effects/marquee.js +5 -0
- package/dist/marketing/effects/marquee.js.map +1 -0
- package/dist/marketing/layout/index.d.ts +2 -0
- package/dist/marketing/layout/index.js +5 -0
- package/dist/marketing/layout/index.js.map +1 -0
- package/dist/marketing/layout/section.d.ts +36 -0
- package/dist/marketing/layout/section.js +5 -0
- package/dist/marketing/layout/section.js.map +1 -0
- package/dist/marketing/motion/animated-number.d.ts +35 -0
- package/dist/marketing/motion/animated-number.js +7 -0
- package/dist/marketing/motion/animated-number.js.map +1 -0
- package/dist/marketing/motion/index.d.ts +6 -0
- package/dist/marketing/motion/index.js +10 -0
- package/dist/marketing/motion/index.js.map +1 -0
- package/dist/marketing/motion/reveal-on-scroll.d.ts +27 -0
- package/dist/marketing/motion/reveal-on-scroll.js +7 -0
- package/dist/marketing/motion/reveal-on-scroll.js.map +1 -0
- package/dist/marketing/motion/smooth-anchor.d.ts +31 -0
- package/dist/marketing/motion/smooth-anchor.js +4 -0
- package/dist/marketing/motion/smooth-anchor.js.map +1 -0
- package/dist/marketing/motion/stagger-reveal.d.ts +30 -0
- package/dist/marketing/motion/stagger-reveal.js +7 -0
- package/dist/marketing/motion/stagger-reveal.js.map +1 -0
- package/dist/marketing/typography/hero-heading.d.ts +25 -0
- package/dist/marketing/typography/hero-heading.js +5 -0
- package/dist/marketing/typography/hero-heading.js.map +1 -0
- package/dist/marketing/typography/index.d.ts +5 -0
- package/dist/marketing/typography/index.js +8 -0
- package/dist/marketing/typography/index.js.map +1 -0
- package/dist/marketing/typography/section-eyebrow.d.ts +21 -0
- package/dist/marketing/typography/section-eyebrow.js +5 -0
- package/dist/marketing/typography/section-eyebrow.js.map +1 -0
- package/dist/marketing/typography/section-heading.d.ts +37 -0
- package/dist/marketing/typography/section-heading.js +5 -0
- package/dist/marketing/typography/section-heading.js.map +1 -0
- package/dist/marketing/typography/section-subtitle.d.ts +19 -0
- package/dist/marketing/typography/section-subtitle.js +5 -0
- package/dist/marketing/typography/section-subtitle.js.map +1 -0
- package/dist/styles/tokens.css +192 -0
- package/dist/ui/chat-message.d.ts +7 -2
- package/dist/ui/chat-message.js +1 -1
- package/dist/ui/message-bar.d.ts +24 -1
- package/dist/ui/message-bar.js +1 -1
- package/package.json +37 -1
- package/dist/chunk-27PO7X4G.js.map +0 -1
- package/dist/chunk-U4QKU5RB.js.map +0 -1
- package/dist/chunk-YMWRR7ET.js.map +0 -1
package/bin/mcp.mjs
CHANGED
|
@@ -411,11 +411,11 @@ const COMPONENTS = [
|
|
|
411
411
|
{
|
|
412
412
|
name: "ChatMessage",
|
|
413
413
|
import: `import { ChatMessage } from "@fluencypassdevs/cycle"`,
|
|
414
|
-
description: "Mensagem de conversa com IA. Persona ai/user/system, suporta texto + audio inline + like/dislike. Loading state com typing indicator. AI usa icone Ai + bubble transparente. User usa bubble pink coral (theme-brand) + avatar.",
|
|
414
|
+
description: "Mensagem de conversa com IA. Persona ai/user/system, suporta texto + audio inline + like/dislike. Loading state com typing indicator. AI usa icone Ai + bubble transparente. User usa bubble pink coral (theme-brand) + avatar. CONVENCAO DO PRODUTO: (1) toda msg da IA com `audioSrc` deve ter `text` (roteiro/legenda do TTS); (2) toda msg do user com `audioSrc` deve ter `text` (transcricao gerada por IA). Garante acessibilidade (leitura + escuta), busca no historico e suporte a screen readers.",
|
|
415
415
|
props: [
|
|
416
416
|
{ name: "persona", type: '"ai" | "user" | "system"', default: "-" },
|
|
417
|
-
{ name: "text", type: "string", default: "-" },
|
|
418
|
-
{ name: "audioSrc", type: "string", default: "-" },
|
|
417
|
+
{ name: "text", type: "string", default: "-", note: "Em ai com audioSrc, e o roteiro do TTS. Em user com audioSrc, e a transcricao." },
|
|
418
|
+
{ name: "audioSrc", type: "string", default: "-", note: "IA: TTS gerado. User: gravacao do microfone. Em ambos, sempre acompanhe com text." },
|
|
419
419
|
{ name: "avatar", type: "string", default: "-" },
|
|
420
420
|
{ name: "author", type: "string", default: "-" },
|
|
421
421
|
{ name: "showFeedback", type: "boolean", default: "false" },
|
|
@@ -423,8 +423,11 @@ const COMPONENTS = [
|
|
|
423
423
|
{ name: "onFeedbackChange", type: "(val) => void", default: "-" },
|
|
424
424
|
{ name: "loading", type: "boolean", default: "false" },
|
|
425
425
|
],
|
|
426
|
-
example:
|
|
427
|
-
<ChatMessage persona="ai" text="
|
|
426
|
+
example: `{/* IA: sempre com text + audioSrc (convencao) */}
|
|
427
|
+
<ChatMessage persona="ai" text="Como posso ajudar?" audioSrc="/ai-tts.mp3" showFeedback />
|
|
428
|
+
{/* User com audio: SEMPRE inclua text com a transcricao */}
|
|
429
|
+
<ChatMessage persona="user" text="Can you explain present perfect?" audioSrc="/user-rec.mp3" author="Felipe" />
|
|
430
|
+
{/* User so com texto (digitado): audio nao se aplica */}
|
|
428
431
|
<ChatMessage persona="user" text="Oi" avatar="/me.jpg" author="Felipe" />
|
|
429
432
|
<ChatMessage persona="ai" loading />
|
|
430
433
|
<ChatMessage persona="system" text="Conversa iniciada" />`,
|
|
@@ -450,7 +453,7 @@ const COMPONENTS = [
|
|
|
450
453
|
{
|
|
451
454
|
name: "MessageBar",
|
|
452
455
|
import: `import { MessageBar } from "@fluencypassdevs/cycle"`,
|
|
453
|
-
description: "Input bar para chat com IA. 6 estados: default, audio-only, active, disabled, recording, paused. UI-only: consumer implementa MediaRecorder + HTMLAudioElement via callbacks. Live waveform real durante recording (Web Audio API, rolling window estilo WhatsApp). RecordedWaveform real durante paused com peaks + barras tocadas/nao-tocadas + dot verde + click/drag-to-seek. Press-to-record no mobile (touch-only): long-press inicia, drag-up trava em hands-free, drag-left cancela, release < 1s cancela, release >= 1s envia. Vibracao haptica em lock/cancel.",
|
|
456
|
+
description: "Input bar para chat com IA. 6 estados: default, audio-only, active, disabled, recording, paused. UI-only: consumer implementa MediaRecorder + HTMLAudioElement via callbacks. Live waveform real durante recording (Web Audio API, rolling window estilo WhatsApp). RecordedWaveform real durante paused com peaks + barras tocadas/nao-tocadas + dot verde + click/drag-to-seek. Press-to-record no mobile (touch-only): long-press inicia, drag-up trava em hands-free, drag-left cancela, release < 1s cancela, release >= 1s envia. Vibracao haptica em lock/cancel. Suporta limite de duracao via maxRecordingDuration (segundos) com warning visual configuravel nos segundos finais (timer destructive + 'Xs restantes' aria-live) e callback onMaxDurationReached ao atingir o limite.",
|
|
454
457
|
props: [
|
|
455
458
|
{ name: "state", type: '"default" | "audio-only" | "active" | "disabled" | "recording" | "paused"', default: '"default"' },
|
|
456
459
|
{ name: "value", type: "string", default: '""' },
|
|
@@ -468,6 +471,10 @@ const COMPONENTS = [
|
|
|
468
471
|
{ name: "playbackProgress", type: "number (0-1)", default: "0", note: "consumer atualiza via audio.ontimeupdate enquanto toca o preview no estado paused" },
|
|
469
472
|
{ name: "onSeekPlayback", type: "(progress) => void", default: "-", note: "consumer seta audio.currentTime = progress * duration" },
|
|
470
473
|
{ name: "placeholder", type: "string", default: '"Digite sua mensagem..."' },
|
|
474
|
+
{ name: "maxRecordingDuration", type: "number", default: "-", note: "limite em segundos. Quando atingido, dispara onMaxDurationReached uma vez. Sem isso, sem limite." },
|
|
475
|
+
{ name: "warnAtSecondsLeft", type: "number", default: "10", note: "ativa warning visual nos ultimos N segundos (timer destructive + 'Xs restantes' aria-live polite)" },
|
|
476
|
+
{ name: "onMaxDurationReached", type: "() => void", default: "-", note: "disparado uma vez ao atingir o limite. ChatThread usa pra auto-pausar; consumer puro pode usar pra customizar" },
|
|
477
|
+
{ name: "secondsLeftLabel", type: "(s) => string", default: "(s) => `${s}s restantes`", note: "label do warning (pra i18n)" },
|
|
471
478
|
],
|
|
472
479
|
example: `// 1) Captura: passa o MESMO stream pro MessageBar (live waveform).
|
|
473
480
|
// 2) Pause: recorder.stop() gera o Blob; crie um <audio> e atualize playbackProgress via ontimeupdate.
|
|
@@ -475,13 +482,15 @@ const COMPONENTS = [
|
|
|
475
482
|
|
|
476
483
|
<MessageBar value={text} onChange={setText} onSendText={(t) => send(t)} onStartRecording={() => startRec()} />
|
|
477
484
|
<MessageBar state="recording" recordingDuration={4} recordingStream={stream} onPauseRecording={pause} onCancelRecording={cancel} onSendAudio={send} />
|
|
478
|
-
<MessageBar state="paused" recordingDuration={42} isPlaying={isPlaying} playbackProgress={progress} onTogglePlay={togglePlay} onSeekPlayback={seek} onResumeRecording={resume} onCancelRecording={cancel} onSendAudio={send}
|
|
485
|
+
<MessageBar state="paused" recordingDuration={42} isPlaying={isPlaying} playbackProgress={progress} onTogglePlay={togglePlay} onSeekPlayback={seek} onResumeRecording={resume} onCancelRecording={cancel} onSendAudio={send} />
|
|
486
|
+
{/* Com limite de duracao (timer fica vermelho aos ultimos 5s, auto-pausa aos 30s) */}
|
|
487
|
+
<MessageBar state="recording" recordingDuration={duration} recordingStream={stream} maxRecordingDuration={30} warnAtSecondsLeft={5} onMaxDurationReached={() => pause()} onPauseRecording={pause} onSendAudio={send} />`,
|
|
479
488
|
keywords: ["chat", "input", "message", "bar", "audio", "recording", "microphone", "mensagem", "envio", "press-to-record", "waveform", "playback", "whatsapp"],
|
|
480
489
|
},
|
|
481
490
|
{
|
|
482
491
|
name: "ChatThread",
|
|
483
492
|
import: `import { ChatThread, type ChatThreadMessage } from "@fluencypassdevs/cycle"`,
|
|
484
|
-
description: "Composite 'tela de chat pronta' que junta ChatMessage + MessageBar + MessageRating. Gerencia state machine interno do MessageBar (default↔recording↔paused) baseado nos callbacks. Auto-scroll inteligente (preserva posicao se user subiu + botao '↓ nova mensagem'). Scroll infinito via onLoadMore. Banners configuraveis: offline, rateLimitedUntil (countdown), quotaExhausted (AlertDialog). Retry inline em msgs failed do user (B2) e regenerar resposta da IA (B3). Botao 'Parar resposta'
|
|
493
|
+
description: "Composite 'tela de chat pronta' que junta ChatMessage + MessageBar + MessageRating. Gerencia state machine interno do MessageBar (default↔recording↔paused) baseado nos callbacks. Auto-scroll inteligente (preserva posicao se user subiu + botao '↓ nova mensagem'). Scroll infinito via onLoadMore. Banners configuraveis: offline, rateLimitedUntil (countdown), quotaExhausted (AlertDialog). Retry inline em msgs failed do user (B2) e regenerar resposta da IA (B3). Botao 'Parar resposta' durante thinking e OPT-IN via presenca de onStopResponse — sem callback, MessageBar some com animacao de saida e volta quando IA termina. requestRating per-message renderiza MessageRating inline embaixo da bubble da IA — escolha some imediato, callback onRate(messageId, value, label) permite consumer enviar como msg do user. initialThinking pra conversas que comecam com IA falando. Input controlled internamente. Animacoes fade+slide (200ms enter / 150ms leave). Suporta limite de duracao de gravacao via maxRecordingDuration (segundos) com warning visual configuravel e auto-pause ao atingir; override via onMaxDurationReached.",
|
|
485
494
|
props: [
|
|
486
495
|
{ name: "messages", type: "ChatThreadMessage[]", default: "[]", note: "array de mensagens da conversa" },
|
|
487
496
|
{ name: "state", type: '"idle" | "sending" | "thinking" | "error"', default: '"idle"', note: "estado global da conversa (controlled)" },
|
|
@@ -501,7 +510,7 @@ const COMPONENTS = [
|
|
|
501
510
|
{ name: "playbackProgress", type: "number (0-1)", default: "0" },
|
|
502
511
|
{ name: "onRetryMessage", type: "(messageId) => void", default: "-", note: "retry msg do user com status=failed" },
|
|
503
512
|
{ name: "onRegenerateResponse", type: "() => void", default: "-", note: "regerar resposta da IA com erro" },
|
|
504
|
-
{ name: "onStopResponse", type: "() => void", default: "-", note: "
|
|
513
|
+
{ name: "onStopResponse", type: "() => void", default: "-", note: "OPT-IN: passar este callback ativa o botao 'Parar resposta' durante thinking. Sem ele, MessageBar some com animacao e volta quando IA termina." },
|
|
505
514
|
{ name: "onLoadMore", type: "() => Promise<void>", default: "-", note: "scroll infinito ao chegar no topo (G1)" },
|
|
506
515
|
{ name: "offline", type: "boolean", default: "false", note: "banner topo + MessageBar disabled" },
|
|
507
516
|
{ name: "rateLimitedUntil", type: "number | null", default: "null", note: "timestamp epoch ate quando bloquear (banner countdown)" },
|
|
@@ -511,6 +520,12 @@ const COMPONENTS = [
|
|
|
511
520
|
{ name: "ratingLabels", type: "Partial<MessageRatingLabels>", default: "PT-BR defaults", note: "labels customizadas do rating" },
|
|
512
521
|
{ name: "placeholder", type: "string", default: '"Digite sua mensagem..."' },
|
|
513
522
|
{ name: "maxLength", type: "number", default: "-" },
|
|
523
|
+
{ name: "userAvatar", type: "string", default: "-", note: "URL do avatar do user — propagado pra todas as msgs do user automaticamente" },
|
|
524
|
+
{ name: "userName", type: "string", default: "-", note: "Nome do user — usado para iniciais no fallback do avatar e alt da imagem" },
|
|
525
|
+
{ name: "maxRecordingDuration", type: "number", default: "-", note: "limite de duracao da gravacao em segundos. Ao atingir, auto-pausa (vai pro preview) a menos que onMaxDurationReached seja passado" },
|
|
526
|
+
{ name: "warnAtSecondsLeft", type: "number", default: "10", note: "passthrough pro MessageBar — quando ativar o warning visual nos segundos finais" },
|
|
527
|
+
{ name: "onMaxDurationReached", type: "() => void", default: "-", note: "OVERRIDE do auto-pause. Quando passado, consumer assume controle ao atingir o limite" },
|
|
528
|
+
{ name: "secondsLeftLabel", type: "(s) => string", default: "(s) => `${s}s restantes`", note: "passthrough pro MessageBar (pra i18n)" },
|
|
514
529
|
],
|
|
515
530
|
example: `// ChatThreadMessage extra fields:
|
|
516
531
|
// - status?: "sent" | "pending" | "failed"
|
|
@@ -524,6 +539,8 @@ const COMPONENTS = [
|
|
|
524
539
|
messages={messages}
|
|
525
540
|
state={state}
|
|
526
541
|
initialThinking
|
|
542
|
+
userAvatar="/me.jpg"
|
|
543
|
+
userName="Felipe Pereira"
|
|
527
544
|
onSendText={(text) => sendText(text)}
|
|
528
545
|
onSendAudio={() => sendAudio()}
|
|
529
546
|
onStartRecording={() => startRec()}
|
|
@@ -926,6 +943,205 @@ import { Home, Search } from "@fluencypassdevs/cycle/icons/lucide"
|
|
|
926
943
|
<CycleIcon icon={Search} size="md" aria-label="Buscar" />`,
|
|
927
944
|
keywords: ["icone", "icon", "lucide", "svg", "grafico"],
|
|
928
945
|
},
|
|
946
|
+
// ─── Marketing — Camada 1 (motion utilities) ────────────────────────────────
|
|
947
|
+
{
|
|
948
|
+
name: "RevealOnScroll",
|
|
949
|
+
import: `import { RevealOnScroll } from "@fluencypassdevs/cycle/marketing/motion/reveal-on-scroll"`,
|
|
950
|
+
description: "MARKETING (landing pages). Wrapper que aplica fade-up suave (translateY 8px + opacity 0->1, 720ms) quando o elemento entra no viewport. Usa IntersectionObserver. Respeita prefers-reduced-motion (entrega visivel sem transicao). Compoe com qualquer conteudo via children.",
|
|
951
|
+
props: [
|
|
952
|
+
{ name: "as", type: '"div" | "section" | "article" | "header" | "footer" | "main" | "aside"', default: '"div"' },
|
|
953
|
+
{ name: "delay", type: "number", default: "0", note: "delay extra antes da animacao (ms)" },
|
|
954
|
+
{ name: "threshold", type: "number", default: "0.15", note: "fracao do elemento visivel pra disparar (0-1)" },
|
|
955
|
+
{ name: "rootMargin", type: "string", default: '"0px 0px -10% 0px"', note: "margin do viewport (CSS shorthand)" },
|
|
956
|
+
{ name: "repeat", type: "boolean", default: "false", note: "reanima toda vez que reentrar no viewport" },
|
|
957
|
+
],
|
|
958
|
+
example: `<RevealOnScroll>
|
|
959
|
+
<h2>Aparece com fade-up</h2>
|
|
960
|
+
</RevealOnScroll>
|
|
961
|
+
|
|
962
|
+
<RevealOnScroll as="section" delay={200}>
|
|
963
|
+
<Card>Atrasa 200ms a entrada</Card>
|
|
964
|
+
</RevealOnScroll>`,
|
|
965
|
+
keywords: ["marketing", "landing", "motion", "animation", "scroll", "fade", "reveal", "entrada"],
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
name: "StaggerReveal",
|
|
969
|
+
import: `import { StaggerReveal } from "@fluencypassdevs/cycle/marketing/motion/stagger-reveal"`,
|
|
970
|
+
description: "MARKETING (landing pages). Container que anima seus filhos em sequencia (fade-up com delay incremental). Clona cada filho via React.Children.map e injeta --stagger-index como CSS var; animacao acontece via .stagger-child no globals.css. Util pra grids de cards, listas de features, secao de pilares. Respeita prefers-reduced-motion.",
|
|
971
|
+
props: [
|
|
972
|
+
{ name: "step", type: "number", default: "100", note: "delay entre cada filho (ms)" },
|
|
973
|
+
{ name: "threshold", type: "number", default: "0.15", note: "fracao do container visivel pra disparar" },
|
|
974
|
+
{ name: "rootMargin", type: "string", default: '"0px 0px -10% 0px"' },
|
|
975
|
+
],
|
|
976
|
+
example: `<StaggerReveal step={120} className="grid grid-cols-3 gap-3">
|
|
977
|
+
<Card>1</Card>
|
|
978
|
+
<Card>2</Card>
|
|
979
|
+
<Card>3</Card>
|
|
980
|
+
</StaggerReveal>`,
|
|
981
|
+
keywords: ["marketing", "landing", "motion", "animation", "stagger", "sequencia", "cards", "grid"],
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
name: "AnimatedNumber",
|
|
985
|
+
import: `import { AnimatedNumber } from "@fluencypassdevs/cycle/marketing/motion/animated-number"`,
|
|
986
|
+
description: "MARKETING (landing pages). Conta de `from` ate `value` com easing easeOutCubic ao entrar no viewport. Suporta prefix/suffix, decimals, locale (pt-BR ou plain). Aplica tabular-nums automaticamente pra largura estavel. Respeita prefers-reduced-motion (pula pro valor final).",
|
|
987
|
+
props: [
|
|
988
|
+
{ name: "value", type: "number", default: "-", note: "valor final (obrigatorio)" },
|
|
989
|
+
{ name: "from", type: "number", default: "0" },
|
|
990
|
+
{ name: "duration", type: "number", default: "1400", note: "duracao da animacao (ms)" },
|
|
991
|
+
{ name: "prefix", type: "string", default: '""', note: 'ex: "R$ ", "+"' },
|
|
992
|
+
{ name: "suffix", type: "string", default: '""', note: 'ex: "%", "k"' },
|
|
993
|
+
{ name: "decimals", type: "number", default: "0" },
|
|
994
|
+
{ name: "locale", type: '"pt-BR" | "plain"', default: '"pt-BR"' },
|
|
995
|
+
{ name: "threshold", type: "number", default: "0.4" },
|
|
996
|
+
],
|
|
997
|
+
example: `<AnimatedNumber value={1247} suffix="+" />
|
|
998
|
+
<AnimatedNumber prefix="R$ " value={3.5} decimals={1} suffix="M" />
|
|
999
|
+
<AnimatedNumber value={97} suffix="%" />`,
|
|
1000
|
+
keywords: ["marketing", "landing", "motion", "counter", "number", "stats", "kpi", "metric"],
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
name: "SmoothAnchor",
|
|
1004
|
+
import: `import { SmoothAnchor } from "@fluencypassdevs/cycle/marketing/motion/smooth-anchor"`,
|
|
1005
|
+
description: "MARKETING (landing pages). Intercepta cliques em links de hash (<a href=#X>) e faz scroll suave com easing easeInOutCubic em vez do scroll-behavior:smooth nativo. Monta UMA VEZ por app (no RootLayout). Renderiza null (so registra listener global). Pulsa destino apos chegar via .anchor-target-pulse. Respeita prefers-reduced-motion (scroll instantaneo).",
|
|
1006
|
+
props: [
|
|
1007
|
+
{ name: "headerOffset", type: "number", default: "96", note: "offset em px (compensar header sticky)" },
|
|
1008
|
+
{ name: "duration", type: "number", default: "900", note: "duracao do scroll (ms)" },
|
|
1009
|
+
],
|
|
1010
|
+
example: `// app/layout.tsx
|
|
1011
|
+
<body>
|
|
1012
|
+
<SmoothAnchor headerOffset={64} duration={900} />
|
|
1013
|
+
{children}
|
|
1014
|
+
</body>
|
|
1015
|
+
|
|
1016
|
+
// Qualquer link de hash agora usa scroll suave:
|
|
1017
|
+
<a href="#solucao">Ver solucao</a>
|
|
1018
|
+
<section id="solucao" data-anchor-pulse>...</section>`,
|
|
1019
|
+
keywords: ["marketing", "landing", "motion", "scroll", "anchor", "hash", "navegacao", "suave"],
|
|
1020
|
+
},
|
|
1021
|
+
// ─── Marketing — Camada 2a (Foundation visual: layout + typography + effects) ─
|
|
1022
|
+
{
|
|
1023
|
+
name: "Section",
|
|
1024
|
+
import: `import { Section } from "@fluencypassdevs/cycle/marketing/layout/section"`,
|
|
1025
|
+
description: "MARKETING (landing pages) — Camada 2a. Wrapper de dobra. Padroniza padding/altura via variant (hero=min-h-screen flex flex-col, default=py-20 md:py-28) e alterna fundo via tone (dark default, light aplica .light-surface). Generaliza o HomeSection do site corporativo. Pareie com SectionHeading/Eyebrow/Subtitle.",
|
|
1026
|
+
props: [
|
|
1027
|
+
{ name: "as", type: '"section" | "div"', default: '"section"' },
|
|
1028
|
+
{ name: "variant", type: '"hero" | "default"', default: '"default"', note: "hero ocupa min-h-screen; default tem py generoso" },
|
|
1029
|
+
{ name: "tone", type: '"dark" | "light"', default: '"dark"', note: "light aplica .light-surface (override de tokens semanticos)" },
|
|
1030
|
+
{ name: "id", type: "string", default: "-", note: "ID para ancoragem via SmoothAnchor" },
|
|
1031
|
+
],
|
|
1032
|
+
example: `<Section variant="hero">
|
|
1033
|
+
<HeroHeading>Acelere o ingles do seu time</HeroHeading>
|
|
1034
|
+
</Section>
|
|
1035
|
+
|
|
1036
|
+
<Section id="pilares" variant="default" tone="light">
|
|
1037
|
+
<SectionEyebrow align="center">Para empresas</SectionEyebrow>
|
|
1038
|
+
<SectionHeading>3 pilares essenciais</SectionHeading>
|
|
1039
|
+
<div className="grid md:grid-cols-3 gap-6">{cards}</div>
|
|
1040
|
+
</Section>`,
|
|
1041
|
+
keywords: ["marketing", "landing", "layout", "section", "dobra", "wrapper", "hero"],
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
name: "HeroHeading",
|
|
1045
|
+
import: `import { HeroHeading } from "@fluencypassdevs/cycle/marketing/typography/hero-heading"`,
|
|
1046
|
+
description: "MARKETING — Camada 2a. Titulo dramatico para hero (h1 default). Sizes grandes, font-bold, leading apertado (1.07), text-balance, max-w-2xl. Use no primeiro titulo da landing.",
|
|
1047
|
+
props: [
|
|
1048
|
+
{ name: "as", type: '"h1" | "h2" | "div"', default: '"h1"' },
|
|
1049
|
+
{ name: "align", type: '"left" | "center"', default: '"left"' },
|
|
1050
|
+
],
|
|
1051
|
+
example: `<HeroHeading>Acelere o ingles do seu time</HeroHeading>
|
|
1052
|
+
<HeroHeading align="center">Aprenda rapido com IA</HeroHeading>`,
|
|
1053
|
+
keywords: ["marketing", "landing", "typography", "heading", "h1", "hero", "titulo"],
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
name: "SectionHeading",
|
|
1057
|
+
import: `import { SectionHeading } from "@fluencypassdevs/cycle/marketing/typography/section-heading"`,
|
|
1058
|
+
description: "MARKETING — Camada 2a. Titulo de dobra (h2 default). Dois pesos: light (font-light elegante, default) e bold (font-bold impacto). leading 1.15, text-balance. Pareie com SectionEyebrow acima e SectionSubtitle abaixo.",
|
|
1059
|
+
props: [
|
|
1060
|
+
{ name: "as", type: '"h2" | "h3"', default: '"h2"' },
|
|
1061
|
+
{ name: "weight", type: '"light" | "bold"', default: '"light"' },
|
|
1062
|
+
{ name: "align", type: '"left" | "center"', default: '"center"' },
|
|
1063
|
+
{ name: "maxW", type: '"wide" | "prose"', default: '"wide"', note: "wide=max-w-5xl, prose=max-w-3xl" },
|
|
1064
|
+
],
|
|
1065
|
+
example: `<SectionHeading>
|
|
1066
|
+
Conteudo, professores e tecnologia no mesmo lugar
|
|
1067
|
+
</SectionHeading>
|
|
1068
|
+
|
|
1069
|
+
<SectionHeading weight="bold" align="left" maxW="prose">
|
|
1070
|
+
Mude o jeito de aprender ingles
|
|
1071
|
+
</SectionHeading>`,
|
|
1072
|
+
keywords: ["marketing", "landing", "typography", "heading", "h2", "section", "dobra"],
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
name: "SectionEyebrow",
|
|
1076
|
+
import: `import { SectionEyebrow } from "@fluencypassdevs/cycle/marketing/typography/section-eyebrow"`,
|
|
1077
|
+
description: "MARKETING — Camada 2a. Rotulo monospace acima do SectionHeading que categoriza a dobra. text-xs font-mono uppercase tracking-widest text-muted-foreground.",
|
|
1078
|
+
props: [
|
|
1079
|
+
{ name: "as", type: '"p" | "span" | "div"', default: '"p"' },
|
|
1080
|
+
{ name: "align", type: '"left" | "center"', default: '"left"' },
|
|
1081
|
+
],
|
|
1082
|
+
example: `<SectionEyebrow align="center">Para empresas</SectionEyebrow>
|
|
1083
|
+
<SectionHeading>Acelere o ingles do seu time</SectionHeading>`,
|
|
1084
|
+
keywords: ["marketing", "landing", "typography", "eyebrow", "label", "rotulo", "categoria"],
|
|
1085
|
+
},
|
|
1086
|
+
{
|
|
1087
|
+
name: "SectionSubtitle",
|
|
1088
|
+
import: `import { SectionSubtitle } from "@fluencypassdevs/cycle/marketing/typography/section-subtitle"`,
|
|
1089
|
+
description: "MARKETING — Camada 2a. Body de apoio abaixo do SectionHeading. text-base sm:text-lg md:text-xl muted-foreground leading-relaxed text-balance max-w-3xl. align center (default) adiciona mx-auto.",
|
|
1090
|
+
props: [
|
|
1091
|
+
{ name: "align", type: '"left" | "center"', default: '"center"' },
|
|
1092
|
+
],
|
|
1093
|
+
example: `<SectionHeading>Nossa solucao em 3 passos</SectionHeading>
|
|
1094
|
+
<SectionSubtitle>
|
|
1095
|
+
Do diagnostico ao certificado, num caminho desenhado pra resultado.
|
|
1096
|
+
</SectionSubtitle>`,
|
|
1097
|
+
keywords: ["marketing", "landing", "typography", "subtitle", "subtitulo", "body", "lead"],
|
|
1098
|
+
},
|
|
1099
|
+
{
|
|
1100
|
+
name: "CircleGlow",
|
|
1101
|
+
import: `import { CircleGlow } from "@fluencypassdevs/cycle/marketing/effects/circle-glow"`,
|
|
1102
|
+
description: "MARKETING — Camada 2a. Decoracao de elipse radial pulsante (4.5s scale + opacity) posicionada absolute. Container precisa ser relative. Sempre aria-hidden + pointer-events none. Respeita prefers-reduced-motion.",
|
|
1103
|
+
props: [
|
|
1104
|
+
{ name: "size", type: '"sm" | "md" | "lg"', default: '"lg"', note: "controla transform: scale()" },
|
|
1105
|
+
],
|
|
1106
|
+
example: `<div className="relative overflow-hidden">
|
|
1107
|
+
<CircleGlow className="-z-10" />
|
|
1108
|
+
<div className="grid grid-cols-3 gap-6">{cards}</div>
|
|
1109
|
+
</div>`,
|
|
1110
|
+
keywords: ["marketing", "landing", "effects", "glow", "decoration", "background", "rose"],
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
name: "CardDarkGlow",
|
|
1114
|
+
import: `import { CardDarkGlow } from "@fluencypassdevs/cycle/marketing/effects/card-dark-glow"`,
|
|
1115
|
+
description: "MARKETING — Camada 2a. Wrapper de card com inset highlight + glow rose subtil + hover lift opcional (translateY -3px). Prop interactive (default true) habilita o hover. Pareie com CircleGlow no container externo pro efeito 'dark premium'.",
|
|
1116
|
+
props: [
|
|
1117
|
+
{ name: "as", type: '"div" | "article" | "section"', default: '"div"' },
|
|
1118
|
+
{ name: "interactive", type: "boolean", default: "true", note: "habilita hover lift + glow stronger" },
|
|
1119
|
+
],
|
|
1120
|
+
example: `<CardDarkGlow className="rounded-2xl bg-card border p-6">
|
|
1121
|
+
<h3>Titulo</h3>
|
|
1122
|
+
<p>Descricao do card</p>
|
|
1123
|
+
</CardDarkGlow>
|
|
1124
|
+
|
|
1125
|
+
<CardDarkGlow interactive={false}>...sem hover</CardDarkGlow>`,
|
|
1126
|
+
keywords: ["marketing", "landing", "effects", "card", "shadow", "glow", "hover", "lift"],
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
name: "Marquee",
|
|
1130
|
+
import: `import { Marquee } from "@fluencypassdevs/cycle/marketing/effects/marquee"`,
|
|
1131
|
+
description: "MARKETING — Camada 2a. Scroll horizontal infinito (logo cloud, partner band). Duplica children pra loop seamless (segundo bloco aria-hidden). Pausa em hover e focus-within (a11y). Respeita prefers-reduced-motion.",
|
|
1132
|
+
props: [
|
|
1133
|
+
{ name: "speed", type: "number", default: "40", note: "duracao de um ciclo em segundos" },
|
|
1134
|
+
{ name: "direction", type: '"left" | "right"', default: '"left"' },
|
|
1135
|
+
{ name: "pauseOnHover", type: "boolean", default: "true" },
|
|
1136
|
+
{ name: "gap", type: "number", default: "48", note: "espacamento entre items em px" },
|
|
1137
|
+
],
|
|
1138
|
+
example: `<Marquee speed={30} gap={64}>
|
|
1139
|
+
<img src="/logos/logo-a.png" alt="Cliente A" />
|
|
1140
|
+
<img src="/logos/logo-b.png" alt="Cliente B" />
|
|
1141
|
+
<img src="/logos/logo-c.png" alt="Cliente C" />
|
|
1142
|
+
</Marquee>`,
|
|
1143
|
+
keywords: ["marketing", "landing", "effects", "marquee", "scroll", "horizontal", "logo", "cloud", "partner", "band"],
|
|
1144
|
+
},
|
|
929
1145
|
]
|
|
930
1146
|
|
|
931
1147
|
// ─── Design Tokens ─────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { cn } from './chunk-TYCPXAXF.js';
|
|
2
|
+
import { __objRest, __spreadProps, __spreadValues } from './chunk-YINJ5YZ5.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var Marquee = React.forwardRef(function Marquee2(_a, ref) {
|
|
7
|
+
var _b = _a, {
|
|
8
|
+
speed = 40,
|
|
9
|
+
direction = "left",
|
|
10
|
+
pauseOnHover = true,
|
|
11
|
+
gap = 48,
|
|
12
|
+
className,
|
|
13
|
+
children,
|
|
14
|
+
style
|
|
15
|
+
} = _b, props = __objRest(_b, [
|
|
16
|
+
"speed",
|
|
17
|
+
"direction",
|
|
18
|
+
"pauseOnHover",
|
|
19
|
+
"gap",
|
|
20
|
+
"className",
|
|
21
|
+
"children",
|
|
22
|
+
"style"
|
|
23
|
+
]);
|
|
24
|
+
const trackStyle = {
|
|
25
|
+
["--cycle-marquee-duration"]: `${speed}s`,
|
|
26
|
+
["--cycle-marquee-gap"]: `${gap}px`
|
|
27
|
+
};
|
|
28
|
+
return /* @__PURE__ */ jsx(
|
|
29
|
+
"div",
|
|
30
|
+
__spreadProps(__spreadValues({
|
|
31
|
+
ref,
|
|
32
|
+
"data-slot": "marquee",
|
|
33
|
+
"data-pause-on-hover": pauseOnHover ? "true" : "false",
|
|
34
|
+
className: cn("cycle-marquee", className),
|
|
35
|
+
style
|
|
36
|
+
}, props), {
|
|
37
|
+
children: /* @__PURE__ */ jsxs(
|
|
38
|
+
"div",
|
|
39
|
+
{
|
|
40
|
+
className: "cycle-marquee-track",
|
|
41
|
+
"data-direction": direction,
|
|
42
|
+
style: trackStyle,
|
|
43
|
+
children: [
|
|
44
|
+
children,
|
|
45
|
+
/* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "contents", children })
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export { Marquee };
|
|
54
|
+
//# sourceMappingURL=chunk-5HPZCREI.js.map
|
|
55
|
+
//# sourceMappingURL=chunk-5HPZCREI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/marketing/effects/marquee.tsx"],"names":["Marquee"],"mappings":";;;;;AA8BA,IAAM,OAAA,GAAgB,KAAA,CAAA,UAAA,CAAyC,SAASA,QAAAA,CACtE,IAUA,GAAA,EACA;AAXA,EAAA,IAAA,EAAA,GAAA,EAAA,EACE;AAAA,IAAA,KAAA,GAAQ,EAAA;AAAA,IACR,SAAA,GAAY,MAAA;AAAA,IACZ,YAAA,GAAe,IAAA;AAAA,IACf,GAAA,GAAM,EAAA;AAAA,IACN,SAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,GAtCJ,GA+BE,EAAA,EAQK,KAAA,GAAA,SAAA,CARL,EAAA,EAQK;AAAA,IAPH,OAAA;AAAA,IACA,WAAA;AAAA,IACA,cAAA;AAAA,IACA,KAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAMF,EAAA,MAAM,UAAA,GAAa;AAAA,IACjB,CAAC,0BAAoC,GAAG,CAAA,EAAG,KAAK,CAAA,CAAA,CAAA;AAAA,IAChD,CAAC,qBAA+B,GAAG,CAAA,EAAG,GAAG,CAAA,EAAA;AAAA,GAC3C;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA,aAAA,CAAA,cAAA,CAAA;AAAA,MACC,GAAA;AAAA,MACA,WAAA,EAAU,SAAA;AAAA,MACV,qBAAA,EAAqB,eAAe,MAAA,GAAS,OAAA;AAAA,MAC7C,SAAA,EAAW,EAAA,CAAG,eAAA,EAAiB,SAAS,CAAA;AAAA,MACxC;AAAA,KAAA,EACI,KAAA,CAAA,EANL;AAAA,MAQC,QAAA,kBAAA,IAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAU,qBAAA;AAAA,UACV,gBAAA,EAAgB,SAAA;AAAA,UAChB,KAAA,EAAO,UAAA;AAAA,UAEN,QAAA,EAAA;AAAA,YAAA,QAAA;AAAA,gCAEA,KAAA,EAAA,EAAI,aAAA,EAAY,MAAA,EAAO,SAAA,EAAU,YAC/B,QAAA,EACH;AAAA;AAAA;AAAA;AACF,KAAA;AAAA,GACF;AAEJ,CAAC","file":"chunk-5HPZCREI.js","sourcesContent":["import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface MarqueeProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Duracao de um ciclo completo em segundos. Default `40`. */\n speed?: number\n /** Direcao do movimento. Default `\"left\"`. */\n direction?: \"left\" | \"right\"\n /** Pausa em `:hover` e `:focus-within`. Default `true` (acessibilidade). */\n pauseOnHover?: boolean\n /** Espacamento entre cada item em px. Default `48`. */\n gap?: number\n}\n\n/**\n * Scroll horizontal infinito (logo cloud, partner band). Duplica os children\n * internamente pra criar loop seamless. Pausa em hover/focus pra dar tempo\n * de leitura. Respeita `prefers-reduced-motion` (anima zerada).\n *\n * Importante: a duplicacao usa `aria-hidden` no segundo bloco pra evitar\n * leitura repetida por screen readers.\n *\n * @example\n * <Marquee speed={30} gap={64}>\n * <img src=\"/logos/logo-a.png\" alt=\"Cliente A\" />\n * <img src=\"/logos/logo-b.png\" alt=\"Cliente B\" />\n * <img src=\"/logos/logo-c.png\" alt=\"Cliente C\" />\n * </Marquee>\n */\nconst Marquee = React.forwardRef<HTMLDivElement, MarqueeProps>(function Marquee(\n {\n speed = 40,\n direction = \"left\",\n pauseOnHover = true,\n gap = 48,\n className,\n children,\n style,\n ...props\n },\n ref\n) {\n // Style merge: nossas CSS vars + qualquer style passado pelo consumer\n const trackStyle = {\n [\"--cycle-marquee-duration\" as string]: `${speed}s`,\n [\"--cycle-marquee-gap\" as string]: `${gap}px`,\n }\n\n return (\n <div\n ref={ref}\n data-slot=\"marquee\"\n data-pause-on-hover={pauseOnHover ? \"true\" : \"false\"}\n className={cn(\"cycle-marquee\", className)}\n style={style}\n {...props}\n >\n <div\n className=\"cycle-marquee-track\"\n data-direction={direction}\n style={trackStyle}\n >\n {children}\n {/* Duplicacao pro loop seamless — `aria-hidden` pra screen readers ignorarem. */}\n <div aria-hidden=\"true\" className=\"contents\">\n {children}\n </div>\n </div>\n </div>\n )\n})\n\nexport { Marquee }\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { cn } from './chunk-TYCPXAXF.js';
|
|
2
|
+
import { __objRest, __spreadValues } from './chunk-YINJ5YZ5.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var SectionHeading = React.forwardRef(
|
|
7
|
+
function SectionHeading2(_a, ref) {
|
|
8
|
+
var _b = _a, { as = "h2", weight = "light", align = "center", maxW = "wide", className } = _b, props = __objRest(_b, ["as", "weight", "align", "maxW", "className"]);
|
|
9
|
+
const Tag = as;
|
|
10
|
+
return /* @__PURE__ */ jsx(
|
|
11
|
+
Tag,
|
|
12
|
+
__spreadValues({
|
|
13
|
+
ref,
|
|
14
|
+
"data-slot": "section-heading",
|
|
15
|
+
className: cn(
|
|
16
|
+
"text-4xl sm:text-5xl md:text-6xl tracking-tight leading-[1.15] text-balance",
|
|
17
|
+
weight === "light" ? "font-light" : "font-bold",
|
|
18
|
+
maxW === "wide" ? "max-w-5xl" : "max-w-3xl",
|
|
19
|
+
align === "center" ? "text-center mx-auto" : "text-left",
|
|
20
|
+
className
|
|
21
|
+
)
|
|
22
|
+
}, props)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
export { SectionHeading };
|
|
28
|
+
//# sourceMappingURL=chunk-6JH4YWWX.js.map
|
|
29
|
+
//# sourceMappingURL=chunk-6JH4YWWX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/marketing/typography/section-heading.tsx"],"names":["SectionHeading"],"mappings":";;;;;AAuCA,IAAM,cAAA,GAAuB,KAAA,CAAA,UAAA;AAAA,EAC3B,SAASA,eAAAA,CACP,EAAA,EACA,GAAA,EACA;AAFA,IAAA,IAAA,EAAA,GAAA,EAAA,EAAE,OAAK,IAAA,EAAM,MAAA,GAAS,OAAA,EAAS,KAAA,GAAQ,UAAU,IAAA,GAAO,MAAA,EAAQ,SAAA,EAzCpE,GAyCI,IAA8E,KAAA,GAAA,SAAA,CAA9E,EAAA,EAA8E,CAA5E,IAAA,EAAW,QAAA,EAAkB,SAAkB,MAAA,EAAe,WAAA,CAAA,CAAA;AAGhE,IAAA,MAAM,GAAA,GAAM,EAAA;AACZ,IAAA,uBACE,GAAA;AAAA,MAAC,GAAA;AAAA,MAAA,cAAA,CAAA;AAAA,QACC,GAAA;AAAA,QACA,WAAA,EAAU,iBAAA;AAAA,QACV,SAAA,EAAW,EAAA;AAAA,UACT,6EAAA;AAAA,UACA,MAAA,KAAW,UAAU,YAAA,GAAe,WAAA;AAAA,UACpC,IAAA,KAAS,SAAS,WAAA,GAAc,WAAA;AAAA,UAChC,KAAA,KAAU,WAAW,qBAAA,GAAwB,WAAA;AAAA,UAC7C;AAAA;AACF,OAAA,EACI,KAAA;AAAA,KACN;AAAA,EAEJ;AACF","file":"chunk-6JH4YWWX.js","sourcesContent":["import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype SectionHeadingTag = \"h2\" | \"h3\"\n\nexport interface SectionHeadingProps\n extends Omit<React.HTMLAttributes<HTMLHeadingElement>, \"color\"> {\n /** Tag renderizada. Default `\"h2\"`. */\n as?: SectionHeadingTag\n /**\n * Peso/intencao visual:\n * - `\"light\"` (default): `font-light` — elegante, usual em maioria das dobras.\n * - `\"bold\"`: `font-bold` — impacto, usado em momentos de virada (quebra).\n */\n weight?: \"light\" | \"bold\"\n /** Alinhamento. Default `\"center\"`. */\n align?: \"left\" | \"center\"\n /**\n * Largura maxima do bloco de texto:\n * - `\"wide\"` (default): `max-w-5xl` — usual centro/elegante.\n * - `\"prose\"`: `max-w-3xl` — quando o titulo e mais conciso.\n */\n maxW?: \"wide\" | \"prose\"\n}\n\n/**\n * Titulo de dobra de landing. Dois pesos principais (`light` elegante vs `bold`\n * impacto). Pareie com `SectionEyebrow` acima e `SectionSubtitle` abaixo.\n *\n * @example\n * <SectionHeading>\n * Conteudo, professores e <strong>tecnologia</strong> no mesmo lugar\n * </SectionHeading>\n *\n * <SectionHeading weight=\"bold\" align=\"left\" maxW=\"prose\">\n * Mude o jeito de aprender ingles\n * </SectionHeading>\n */\nconst SectionHeading = React.forwardRef<HTMLHeadingElement, SectionHeadingProps>(\n function SectionHeading(\n { as = \"h2\", weight = \"light\", align = \"center\", maxW = \"wide\", className, ...props },\n ref\n ) {\n const Tag = as as React.ElementType\n return (\n <Tag\n ref={ref}\n data-slot=\"section-heading\"\n className={cn(\n \"text-4xl sm:text-5xl md:text-6xl tracking-tight leading-[1.15] text-balance\",\n weight === \"light\" ? \"font-light\" : \"font-bold\",\n maxW === \"wide\" ? \"max-w-5xl\" : \"max-w-3xl\",\n align === \"center\" ? \"text-center mx-auto\" : \"text-left\",\n className\n )}\n {...props}\n />\n )\n }\n)\n\nexport { SectionHeading }\n"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useReducedMotion } from './chunk-K2NUBNSA.js';
|
|
2
|
+
import { useIntersection } from './chunk-XQUQDZVA.js';
|
|
3
|
+
import { cn } from './chunk-TYCPXAXF.js';
|
|
4
|
+
import { __objRest, __spreadProps, __spreadValues } from './chunk-YINJ5YZ5.js';
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { jsxs } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
function easeOutCubic(t) {
|
|
9
|
+
return 1 - Math.pow(1 - t, 3);
|
|
10
|
+
}
|
|
11
|
+
function formatNumber(n, decimals, locale) {
|
|
12
|
+
if (locale === "pt-BR") {
|
|
13
|
+
return n.toLocaleString("pt-BR", {
|
|
14
|
+
minimumFractionDigits: decimals,
|
|
15
|
+
maximumFractionDigits: decimals
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return n.toFixed(decimals);
|
|
19
|
+
}
|
|
20
|
+
function AnimatedNumber(_a) {
|
|
21
|
+
var _b = _a, {
|
|
22
|
+
value,
|
|
23
|
+
from = 0,
|
|
24
|
+
duration = 1400,
|
|
25
|
+
prefix = "",
|
|
26
|
+
suffix = "",
|
|
27
|
+
decimals = 0,
|
|
28
|
+
locale = "pt-BR",
|
|
29
|
+
threshold = 0.4,
|
|
30
|
+
className
|
|
31
|
+
} = _b, props = __objRest(_b, [
|
|
32
|
+
"value",
|
|
33
|
+
"from",
|
|
34
|
+
"duration",
|
|
35
|
+
"prefix",
|
|
36
|
+
"suffix",
|
|
37
|
+
"decimals",
|
|
38
|
+
"locale",
|
|
39
|
+
"threshold",
|
|
40
|
+
"className"
|
|
41
|
+
]);
|
|
42
|
+
const reduced = useReducedMotion();
|
|
43
|
+
const { ref, isVisible } = useIntersection({
|
|
44
|
+
threshold,
|
|
45
|
+
startVisible: reduced
|
|
46
|
+
});
|
|
47
|
+
const [current, setCurrent] = React.useState(reduced ? value : from);
|
|
48
|
+
const startedRef = React.useRef(false);
|
|
49
|
+
React.useEffect(() => {
|
|
50
|
+
if (reduced) {
|
|
51
|
+
setCurrent(value);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!isVisible || startedRef.current) return;
|
|
55
|
+
startedRef.current = true;
|
|
56
|
+
const start = performance.now();
|
|
57
|
+
let frame = 0;
|
|
58
|
+
const tick = (now) => {
|
|
59
|
+
const elapsed = now - start;
|
|
60
|
+
const t = Math.min(elapsed / duration, 1);
|
|
61
|
+
const next = from + (value - from) * easeOutCubic(t);
|
|
62
|
+
setCurrent(next);
|
|
63
|
+
if (t < 1) frame = requestAnimationFrame(tick);
|
|
64
|
+
};
|
|
65
|
+
frame = requestAnimationFrame(tick);
|
|
66
|
+
return () => cancelAnimationFrame(frame);
|
|
67
|
+
}, [isVisible, value, from, duration, reduced]);
|
|
68
|
+
return /* @__PURE__ */ jsxs("span", __spreadProps(__spreadValues({ ref, className: cn("tabular-nums", className) }, props), { children: [
|
|
69
|
+
prefix,
|
|
70
|
+
formatNumber(current, decimals, locale),
|
|
71
|
+
suffix
|
|
72
|
+
] }));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { AnimatedNumber };
|
|
76
|
+
//# sourceMappingURL=chunk-7LH7PSQS.js.map
|
|
77
|
+
//# sourceMappingURL=chunk-7LH7PSQS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/marketing/motion/animated-number.tsx"],"names":[],"mappings":";;;;;;;AA8BA,SAAS,aAAa,CAAA,EAAmB;AACvC,EAAA,OAAO,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,CAAA,GAAI,GAAG,CAAC,CAAA;AAC9B;AAEA,SAAS,YAAA,CAAa,CAAA,EAAW,QAAA,EAAkB,MAAA,EAAmC;AACpF,EAAA,IAAI,WAAW,OAAA,EAAS;AACtB,IAAA,OAAO,CAAA,CAAE,eAAe,OAAA,EAAS;AAAA,MAC/B,qBAAA,EAAuB,QAAA;AAAA,MACvB,qBAAA,EAAuB;AAAA,KACxB,CAAA;AAAA,EACH;AACA,EAAA,OAAO,CAAA,CAAE,QAAQ,QAAQ,CAAA;AAC3B;AAUA,SAAS,eAAe,EAAA,EAWA;AAXA,EAAA,IAAA,EAAA,GAAA,EAAA,EACtB;AAAA,IAAA,KAAA;AAAA,IACA,IAAA,GAAO,CAAA;AAAA,IACP,QAAA,GAAW,IAAA;AAAA,IACX,MAAA,GAAS,EAAA;AAAA,IACT,MAAA,GAAS,EAAA;AAAA,IACT,QAAA,GAAW,CAAA;AAAA,IACX,MAAA,GAAS,OAAA;AAAA,IACT,SAAA,GAAY,GAAA;AAAA,IACZ;AAAA,GA7DF,GAoDwB,EAAA,EAUnB,KAAA,GAAA,SAAA,CAVmB,EAAA,EAUnB;AAAA,IATH,OAAA;AAAA,IACA,MAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAGA,EAAA,MAAM,UAAU,gBAAA,EAAiB;AACjC,EAAA,MAAM,EAAE,GAAA,EAAK,SAAA,EAAU,GAAI,eAAA,CAAiC;AAAA,IAC1D,SAAA;AAAA,IACA,YAAA,EAAc;AAAA,GACf,CAAA;AACD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,IAAU,KAAA,CAAA,QAAA,CAAS,OAAA,GAAU,QAAQ,IAAI,CAAA;AACnE,EAAA,MAAM,UAAA,GAAmB,aAAO,KAAK,CAAA;AAErC,EAAM,gBAAU,MAAM;AACpB,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAC,SAAA,IAAa,UAAA,CAAW,OAAA,EAAS;AACtC,IAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AAErB,IAAA,MAAM,KAAA,GAAQ,YAAY,GAAA,EAAI;AAC9B,IAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,IAAA,MAAM,IAAA,GAAO,CAAC,GAAA,KAAgB;AAC5B,MAAA,MAAM,UAAU,GAAA,GAAM,KAAA;AACtB,MAAA,MAAM,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,OAAA,GAAU,UAAU,CAAC,CAAA;AACxC,MAAA,MAAM,IAAA,GAAO,IAAA,GAAA,CAAQ,KAAA,GAAQ,IAAA,IAAQ,aAAa,CAAC,CAAA;AACnD,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,IAAI,CAAA,GAAI,CAAA,EAAG,KAAA,GAAQ,qBAAA,CAAsB,IAAI,CAAA;AAAA,IAC/C,CAAA;AACA,IAAA,KAAA,GAAQ,sBAAsB,IAAI,CAAA;AAClC,IAAA,OAAO,MAAM,qBAAqB,KAAK,CAAA;AAAA,EACzC,GAAG,CAAC,SAAA,EAAW,OAAO,IAAA,EAAM,QAAA,EAAU,OAAO,CAAC,CAAA;AAE9C,EAAA,uBACE,IAAA,CAAC,uCAAK,GAAA,EAAU,SAAA,EAAW,GAAG,cAAA,EAAgB,SAAS,CAAA,EAAA,EAAO,KAAA,CAAA,EAA7D,EACE,QAAA,EAAA;AAAA,IAAA,MAAA;AAAA,IACA,YAAA,CAAa,OAAA,EAAS,QAAA,EAAU,MAAM,CAAA;AAAA,IACtC;AAAA,GAAA,EAAA,CACH,CAAA;AAEJ","file":"chunk-7LH7PSQS.js","sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useIntersection } from \"@/hooks/use-intersection\"\nimport { useReducedMotion } from \"@/hooks/use-reduced-motion\"\n\nexport interface AnimatedNumberProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Valor final do contador. */\n value: number\n /** Valor inicial. Default `0`. */\n from?: number\n /** Duracao da animacao em ms. Default `1400`. */\n duration?: number\n /** Texto antes do numero (ex: `\"R$ \"` ou `\"+\"`). */\n prefix?: string\n /** Texto depois do numero (ex: `\"%\"` ou `\"k\"`). */\n suffix?: string\n /** Casas decimais. Default `0`. */\n decimals?: number\n /**\n * Formato dos numeros. `\"pt-BR\"` usa separador `.` e decimal `,`;\n * `\"plain\"` usa `toFixed`. Default `\"pt-BR\"`.\n */\n locale?: \"pt-BR\" | \"plain\"\n /** Threshold do IntersectionObserver. Default `0.4`. */\n threshold?: number\n}\n\nfunction easeOutCubic(t: number): number {\n return 1 - Math.pow(1 - t, 3)\n}\n\nfunction formatNumber(n: number, decimals: number, locale: \"pt-BR\" | \"plain\"): string {\n if (locale === \"pt-BR\") {\n return n.toLocaleString(\"pt-BR\", {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n })\n }\n return n.toFixed(decimals)\n}\n\n/**\n * Conta de `from` ate `value` com easing `easeOutCubic` quando o componente\n * entra no viewport. Aplica `tabular-nums` automaticamente pra largura estavel\n * durante a contagem (evita \"saltos\" no layout).\n *\n * Acessibilidade: respeita `prefers-reduced-motion` — pula direto pro valor\n * final, sem animar.\n */\nfunction AnimatedNumber({\n value,\n from = 0,\n duration = 1400,\n prefix = \"\",\n suffix = \"\",\n decimals = 0,\n locale = \"pt-BR\",\n threshold = 0.4,\n className,\n ...props\n}: AnimatedNumberProps) {\n const reduced = useReducedMotion()\n const { ref, isVisible } = useIntersection<HTMLSpanElement>({\n threshold,\n startVisible: reduced,\n })\n const [current, setCurrent] = React.useState(reduced ? value : from)\n const startedRef = React.useRef(false)\n\n React.useEffect(() => {\n if (reduced) {\n setCurrent(value)\n return\n }\n if (!isVisible || startedRef.current) return\n startedRef.current = true\n\n const start = performance.now()\n let frame = 0\n const tick = (now: number) => {\n const elapsed = now - start\n const t = Math.min(elapsed / duration, 1)\n const next = from + (value - from) * easeOutCubic(t)\n setCurrent(next)\n if (t < 1) frame = requestAnimationFrame(tick)\n }\n frame = requestAnimationFrame(tick)\n return () => cancelAnimationFrame(frame)\n }, [isVisible, value, from, duration, reduced])\n\n return (\n <span ref={ref} className={cn(\"tabular-nums\", className)} {...props}>\n {prefix}\n {formatNumber(current, decimals, locale)}\n {suffix}\n </span>\n )\n}\n\nexport { AnimatedNumber }\n"]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { LikeDislike } from './chunk-F2XA2Z75.js';
|
|
2
2
|
import { Ai } from './chunk-JPEDYOV7.js';
|
|
3
|
+
import { CycleIcon } from './chunk-V7M2NHUO.js';
|
|
3
4
|
import { Avatar, AvatarImage, AvatarFallback } from './chunk-MSLQRGSP.js';
|
|
4
5
|
import { cn } from './chunk-TYCPXAXF.js';
|
|
5
6
|
import { __objRest, __spreadProps, __spreadValues } from './chunk-YINJ5YZ5.js';
|
|
6
7
|
import * as React from 'react';
|
|
7
8
|
import { cva } from 'class-variance-authority';
|
|
8
|
-
import { Pause, Play } from 'lucide-react';
|
|
9
|
+
import { Pause, Play, User } from 'lucide-react';
|
|
9
10
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
10
11
|
|
|
11
12
|
var chatMessageVariants = cva("flex w-full text-sm", {
|
|
@@ -143,8 +144,6 @@ var STATIC_FALLBACK_PEAKS = [
|
|
|
143
144
|
].map((h) => h / 32);
|
|
144
145
|
var MIN_BAR_HEIGHT = 2;
|
|
145
146
|
var MAX_BAR_HEIGHT = 32;
|
|
146
|
-
var BAR_WIDTH = 2;
|
|
147
|
-
var BAR_GAP = 2;
|
|
148
147
|
var SPEED_OPTIONS = [1, 1.25, 1.5, 2, 0.75];
|
|
149
148
|
var peaksCache = /* @__PURE__ */ new Map();
|
|
150
149
|
async function calculatePeaks(audioUrl, barCount) {
|
|
@@ -273,13 +272,10 @@ function ChatAudio(_a) {
|
|
|
273
272
|
if (!audio || !waveform2 || !duration) return;
|
|
274
273
|
const rect = waveform2.getBoundingClientRect();
|
|
275
274
|
const x = clientX - rect.left;
|
|
276
|
-
const newProgress = Math.max(
|
|
277
|
-
0,
|
|
278
|
-
Math.min(x / (peaks.length * (BAR_WIDTH + BAR_GAP) - BAR_GAP), 1)
|
|
279
|
-
);
|
|
275
|
+
const newProgress = Math.max(0, Math.min(x / rect.width, 1));
|
|
280
276
|
audio.currentTime = newProgress * duration;
|
|
281
277
|
},
|
|
282
|
-
[duration
|
|
278
|
+
[duration]
|
|
283
279
|
);
|
|
284
280
|
const handlePointerDown = (e) => {
|
|
285
281
|
if (!duration) return;
|
|
@@ -298,12 +294,11 @@ function ChatAudio(_a) {
|
|
|
298
294
|
};
|
|
299
295
|
const progress = duration > 0 ? currentTime / duration : 0;
|
|
300
296
|
const displayTime = formatAudioTime(currentTime);
|
|
301
|
-
const barsLayoutWidth = peaks.length * (BAR_WIDTH + BAR_GAP) - BAR_GAP;
|
|
302
297
|
const waveform = /* @__PURE__ */ jsxs(
|
|
303
298
|
"div",
|
|
304
299
|
{
|
|
305
300
|
ref: waveformRef,
|
|
306
|
-
className: "flex-1 relative h-8 min-w-0
|
|
301
|
+
className: "flex-1 relative h-8 min-w-0 touch-none cursor-pointer select-none",
|
|
307
302
|
onPointerDown: handlePointerDown,
|
|
308
303
|
onPointerMove: handlePointerMove,
|
|
309
304
|
onPointerUp: handlePointerUp,
|
|
@@ -314,7 +309,7 @@ function ChatAudio(_a) {
|
|
|
314
309
|
"aria-valuemax": Math.round(duration),
|
|
315
310
|
"aria-valuenow": Math.round(currentTime),
|
|
316
311
|
children: [
|
|
317
|
-
/* @__PURE__ */ jsx("div", { className: "absolute inset-
|
|
312
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-between", children: peaks.map((peak, i) => {
|
|
318
313
|
const isPlayed = i / peaks.length <= progress;
|
|
319
314
|
const height = Math.max(
|
|
320
315
|
MIN_BAR_HEIGHT,
|
|
@@ -336,7 +331,7 @@ function ChatAudio(_a) {
|
|
|
336
331
|
"div",
|
|
337
332
|
{
|
|
338
333
|
className: "absolute top-1/2 z-10 -translate-y-1/2 -translate-x-1/2 size-3 rounded-full bg-[#098A5E] pointer-events-none shadow-md",
|
|
339
|
-
style: { left: `${progress *
|
|
334
|
+
style: { left: `${progress * 100}%` },
|
|
340
335
|
"aria-hidden": "true"
|
|
341
336
|
}
|
|
342
337
|
)
|
|
@@ -453,11 +448,11 @@ function ChatMessage(_a) {
|
|
|
453
448
|
children: /* @__PURE__ */ jsxs("div", { className: "flex gap-3 max-w-[85%] sm:max-w-[75%] w-full", children: [
|
|
454
449
|
/* @__PURE__ */ jsx("div", { className: "hidden sm:flex shrink-0 size-8 rounded-md bg-primary items-center justify-center theme-brand", children: /* @__PURE__ */ jsx("span", { className: "text-primary-foreground", children: /* @__PURE__ */ jsx(Ai, { size: "sm", decorative: true }) }) }),
|
|
455
450
|
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 min-w-0 flex-1", children: loading ? (
|
|
456
|
-
/* Loading: dots dentro de bubble cinza (speech bubble shape — top-left reto) */
|
|
457
|
-
/* @__PURE__ */ jsx("div", { className: "self-start inline-flex items-center rounded-2xl
|
|
451
|
+
/* Loading: dots dentro de bubble cinza (speech bubble shape — top-left reto em todos os breakpoints) */
|
|
452
|
+
/* @__PURE__ */ jsx("div", { className: "self-start inline-flex items-center rounded-2xl rounded-tl-none bg-muted p-5 text-muted-foreground", children: /* @__PURE__ */ jsx(TypingDots, {}) })
|
|
458
453
|
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
459
|
-
text && /* Text bubble: bg-muted sempre, speech bubble shape (tl-none)
|
|
460
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-2xl bg-muted p-5
|
|
454
|
+
text && /* Text bubble: bg-muted sempre, speech bubble shape (tl-none) em todos os breakpoints */
|
|
455
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-2xl rounded-tl-none bg-muted p-5", children: /* @__PURE__ */ jsx("p", { className: "text-base leading-6 text-neutral-foreground break-words", children: text }) }),
|
|
461
456
|
audioSrc && /* @__PURE__ */ jsx(ChatAudio, { src: audioSrc, variant: "neutral" }),
|
|
462
457
|
showFeedback && /* @__PURE__ */ jsx(
|
|
463
458
|
LikeDislike,
|
|
@@ -481,17 +476,17 @@ function ChatMessage(_a) {
|
|
|
481
476
|
}, props), {
|
|
482
477
|
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 max-w-[85%] sm:max-w-[75%] w-full justify-end", children: [
|
|
483
478
|
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 min-w-0 items-end theme-brand flex-1", children: loading ? (
|
|
484
|
-
/* Loading: dots dentro de bubble pink (speech bubble shape — top-right reto) */
|
|
485
|
-
/* @__PURE__ */ jsx("div", { className: "inline-flex items-center rounded-2xl
|
|
479
|
+
/* Loading: dots dentro de bubble pink (speech bubble shape — top-right reto em todos os breakpoints) */
|
|
480
|
+
/* @__PURE__ */ jsx("div", { className: "inline-flex items-center rounded-2xl rounded-tr-none bg-accent p-5 text-neutral-muted-foreground", children: /* @__PURE__ */ jsx(TypingDots, {}) })
|
|
486
481
|
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
487
|
-
text && /* Text bubble: speech bubble shape (top-right reto)
|
|
482
|
+
text && /* Text bubble: speech bubble shape (top-right reto) em todos os breakpoints.
|
|
488
483
|
text-neutral-foreground = preto neutro fixo, nao muda com theme-brand */
|
|
489
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-2xl
|
|
484
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-2xl rounded-tr-none bg-accent p-5", children: /* @__PURE__ */ jsx("p", { className: "text-base leading-6 text-neutral-foreground break-words", children: text }) }),
|
|
490
485
|
audioSrc && /* @__PURE__ */ jsx(ChatAudio, { src: audioSrc, variant: "brand" })
|
|
491
486
|
] }) }),
|
|
492
487
|
/* @__PURE__ */ jsxs(Avatar, { size: "lg", className: "hidden sm:flex shrink-0 rounded-lg", children: [
|
|
493
488
|
avatar && /* @__PURE__ */ jsx(AvatarImage, { src: avatar, alt: author != null ? author : "User", className: "rounded-lg" }),
|
|
494
|
-
/* @__PURE__ */ jsx(AvatarFallback, { className: "rounded-lg bg-muted text-muted-foreground text-sm", children: getInitials(author) })
|
|
489
|
+
/* @__PURE__ */ jsx(AvatarFallback, { className: "rounded-lg bg-muted text-muted-foreground text-sm", children: author ? getInitials(author) : /* @__PURE__ */ jsx(CycleIcon, { icon: User, size: "sm", decorative: true }) })
|
|
495
490
|
] })
|
|
496
491
|
] })
|
|
497
492
|
})
|
|
@@ -499,5 +494,5 @@ function ChatMessage(_a) {
|
|
|
499
494
|
}
|
|
500
495
|
|
|
501
496
|
export { ChatAudio, ChatMessage, chatMessageVariants };
|
|
502
|
-
//# sourceMappingURL=chunk-
|
|
503
|
-
//# sourceMappingURL=chunk-
|
|
497
|
+
//# sourceMappingURL=chunk-CG7NXMBC.js.map
|
|
498
|
+
//# sourceMappingURL=chunk-CG7NXMBC.js.map
|