ilibras-widget 1.4.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/README.md ADDED
@@ -0,0 +1,713 @@
1
+ # 🎯 iLibras Widget - Widget de Atendimento Embarcável
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
+ [![JavaScript](https://img.shields.io/badge/JavaScript-ES6+-yellow.svg)](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
5
+ [![CSS3](https://img.shields.io/badge/CSS-3-blue.svg)](https://developer.mozilla.org/en-US/docs/Web/CSS)
6
+
7
+ Widget embarcável similar ao WhatsApp para captura de dados (Nome e CPF) com integração via API. Envia dados de forma segura via POST e redireciona para plataforma de atendimento. Ideal para integração em sites, sistemas e plataformas web.
8
+
9
+ ![iLibras Widget](https://via.placeholder.com/800x400/25D366/FFFFFF?text=iLibras+Widget)
10
+
11
+ ## ✨ Características
12
+
13
+ - 🚀 **Fácil integração** - Apenas 2 linhas de código
14
+ - 🎨 **Totalmente personalizável** - Cores, posição, textos e mais
15
+ - 📱 **Responsivo** - Funciona perfeitamente em desktop e mobile
16
+ - ✅ **Validação automática** - CPF validado antes do envio
17
+ - 🔒 **Envio seguro** - Dados enviados via POST para API
18
+ - 🎭 **Máscaras de entrada** - Formatação automática de CPF
19
+ - 🌐 **Universal** - Funciona em qualquer site ou sistema
20
+ - ♿ **Acessível** - Suporte a leitores de tela e navegação por teclado
21
+ - 🌙 **Tema escuro** - Adapta-se automaticamente às preferências do usuário
22
+
23
+ ## 📦 Instalação
24
+
25
+ O widget é um arquivo só: estilo e logo vão embutidos. Não há CSS nem imagem
26
+ para baixar junto, e não há caminho para configurar errado.
27
+
28
+ > ⚠️ Em qualquer uma das opções, troque `SEU_TOKEN_AQUI` pelo token que a
29
+ > equipe iLibras forneceu para o contrato.
30
+
31
+ ### Opção 1: npm
32
+
33
+ Para projetos com bundler — React, Vue, Svelte, Next, Nuxt, Astro, Vite,
34
+ webpack:
35
+
36
+ ```bash
37
+ npm i ilibras-widget
38
+ ```
39
+
40
+ ```js
41
+ import ILibrasWidget from 'ilibras-widget';
42
+
43
+ const widget = new ILibrasWidget({ token: 'SEU_TOKEN_AQUI' });
44
+ ```
45
+
46
+ Importar não cria widget nenhum — quem decide quando instanciar é você. Isso é
47
+ o que permite usar o pacote em projeto com renderização no servidor, onde o
48
+ import acontece onde não existe `document`.
49
+
50
+ O pacote traz tipos TypeScript; não é preciso instalar `@types`.
51
+
52
+ #### React
53
+
54
+ ```jsx
55
+ import { useEffect } from 'react';
56
+ import ILibrasWidget from 'ilibras-widget';
57
+
58
+ export function Acessibilidade() {
59
+ useEffect(() => {
60
+ const widget = new ILibrasWidget({ token: 'SEU_TOKEN_AQUI' });
61
+ // Sem isto, cada montagem deixa um widget para trás e o site acumula
62
+ // botões flutuantes.
63
+ return () => widget.destroy();
64
+ }, []);
65
+
66
+ return null;
67
+ }
68
+ ```
69
+
70
+ #### Vue
71
+
72
+ ```vue
73
+ <script setup>
74
+ import { onMounted, onBeforeUnmount } from 'vue';
75
+ import ILibrasWidget from 'ilibras-widget';
76
+
77
+ let widget;
78
+ onMounted(() => { widget = new ILibrasWidget({ token: 'SEU_TOKEN_AQUI' }); });
79
+ onBeforeUnmount(() => widget?.destroy());
80
+ </script>
81
+ ```
82
+
83
+ ### Opção 2: Via CDN
84
+
85
+ Sem build, sem instalar nada. A versão fica presa na URL — atualizar é trocar
86
+ o número:
87
+
88
+ ```html
89
+ <script src="https://cdn.jsdelivr.net/npm/ilibras-widget@1/dist/ilibras-widget.js"></script>
90
+ <script>
91
+ new ILibrasWidget({
92
+ token: 'SEU_TOKEN_AQUI'
93
+ });
94
+ </script>
95
+ ```
96
+
97
+ Use `@1` para receber correções automaticamente, ou fixe a versão exata
98
+ (`@1.4.0`) se preferir controlar cada atualização.
99
+
100
+ ### Opção 3: Arquivo baixado
101
+
102
+ Para WordPress, HTML solto e qualquer site sem build. Baixe
103
+ [`ilibras-widget.js`](ilibras-widget.js) e hospede junto do seu site:
104
+
105
+ ```html
106
+ <script src="ilibras-widget.js"></script>
107
+ <script>
108
+ new ILibrasWidget({
109
+ token: 'SEU_TOKEN_AQUI'
110
+ });
111
+ </script>
112
+ ```
113
+
114
+ > Instalações antigas incluíam também um `<link rel="stylesheet">` para
115
+ > `ilibras-widget.css`. A partir da v1.4.0 o estilo vai dentro do JavaScript e
116
+ > essa linha virou desnecessária — mas deixá-la não quebra nada.
117
+
118
+ ### Instalação sem escrever JavaScript
119
+
120
+ Em qualquer uma das opções com `<script>`, dá para configurar o widget antes
121
+ de carregá-lo, e ele se cria sozinho:
122
+
123
+ ```html
124
+ <script>
125
+ window.iLibrasWidgetConfig = { token: 'SEU_TOKEN_AQUI' };
126
+ </script>
127
+ <script src="https://cdn.jsdelivr.net/npm/ilibras-widget@1/dist/ilibras-widget.js"></script>
128
+ ```
129
+
130
+ ## 🚀 Uso Rápido
131
+
132
+ ### Configuração Básica
133
+
134
+ ```javascript
135
+ new ILibrasWidget({
136
+ token: 'SEU_TOKEN_AQUI'
137
+ });
138
+ ```
139
+
140
+ > ⚠️ O parâmetro `token` é **obrigatório**. Entre em contato com a equipe iLibras para obter seu token.
141
+
142
+ ### Configuração Personalizada
143
+
144
+ ```javascript
145
+ new ILibrasWidget({
146
+ position: 'bottom-right',
147
+ redirectUrl: 'https://teste.com.br',
148
+ primaryColor: '#4A90E2',
149
+ title: 'iLibras',
150
+ message: 'Olá! Como podemos ajudar?',
151
+ buttonText: 'Iniciar atendimento',
152
+ agendamento: true,
153
+ scheduleButtonText: 'Agendar uma conversa',
154
+ token: 'SEU_TOKEN_AQUI',
155
+ zIndex: 9999
156
+ });
157
+ ```
158
+
159
+ ## ⚙️ Opções de Configuração
160
+
161
+ | Opção | Tipo | Padrão | Descrição |
162
+ |-------|------|--------|-----------|
163
+ | `position` | `string` | `'bottom-right'` | Posição do widget: `'bottom-right'`, `'bottom-left'`, `'top-right'`, `'top-left'` |
164
+ | `redirectUrl` | `string` | `'https://teste.com.br'` | URL para redirecionamento após envio |
165
+ | `primaryColor` | `string` | `'#4A90E2'` | Cor principal do widget (hex) |
166
+ | `title` | `string` | `'iLibras'` | Título exibido no cabeçalho |
167
+ | `message` | `string` | `'Olá, somos a equipe...'` | Mensagem de boas-vindas |
168
+ | `buttonText` | `string` | `'Iniciar atendimento em Libras'` | Texto do botão de envio |
169
+ | `agendamento` | `boolean` | `true` | Mostra o segundo botão, que leva direto à escolha de data e horário em vez da fila ao vivo. Deixe `false` em site que só atende ao vivo |
170
+ | `scheduleButtonText` | `string` | `'Agendar uma conversa'` | Texto do botão de agendamento |
171
+ | `token` | `string` | `''` | **OBRIGATÓRIO** — token de autenticação da API |
172
+ | `zIndex` | `number` | `9999` | Z-index do widget |
173
+ | `apiUrl` | `string` | endpoint de produção | Endpoint de cadastro. Troque apenas para apontar a um ambiente de homologação |
174
+ | `timeoutMs` | `number` | `15000` | Prazo de cada tentativa. Passado isso a requisição é abortada e a pessoa recebe uma mensagem, em vez de ficar no "Enviando..." |
175
+ | `tentativas` | `number` | `2` | Quantas vezes tentar quando a falha é de rede ou do servidor. Erro de validação e limite de uso nunca são repetidos |
176
+ | `fallback` | `object` | `{}` | Canais alternativos exibidos quando o serviço não responde: `{ telefone, email, url, texto }` |
177
+
178
+ ### Plano de contingência (`fallback`)
179
+
180
+ Se a API do iLibras não responder, o widget mostra a falha dentro do próprio
181
+ modal e — se você tiver configurado — oferece outro caminho de atendimento, em
182
+ vez de deixar a pessoa sem saída:
183
+
184
+ ```javascript
185
+ new ILibrasWidget({
186
+ token: 'seu_token_aqui',
187
+ fallback: {
188
+ telefone: '(11) 4002-8922',
189
+ email: 'acessibilidade@suaempresa.com.br',
190
+ url: 'https://suaempresa.com.br/atendimento',
191
+ texto: 'Enquanto isso, você pode falar com a gente por aqui:'
192
+ }
193
+ });
194
+ ```
195
+
196
+ Qualquer combinação dos quatro campos funciona; o bloco só aparece se pelo
197
+ menos um canal estiver preenchido.
198
+
199
+ ## 📊 Integração com API
200
+
201
+ Quando o usuário submete o formulário, os dados são enviados via **POST** (método seguro) para a API configurada:
202
+
203
+ ### Endpoint da API
204
+
205
+ ```
206
+ POST https://sistema.ilibras.com.br/administrativo/api/clientes/cadastrar.php
207
+ Content-Type: multipart/form-data
208
+ ```
209
+
210
+ ### Dados Enviados
211
+
212
+ Os dados são enviados como **FormData** (similar a um formulário HTML tradicional):
213
+
214
+ ```
215
+ nome: "João Silva"
216
+ cpf: "12345678900"
217
+ telefone: "11987654321"
218
+ token: "seu_token_de_autenticacao"
219
+ ```
220
+
221
+ | Campo | Descrição | Formato |
222
+ |-------|-----------|------|
223
+ | `nome` | Nome completo do usuário | String |
224
+ | `cpf` | CPF sem formatação (apenas números) | String numérica (11 dígitos) |
225
+ | `telefone` | Telefone sem formatação (apenas números) | String numérica (10-11 dígitos) |
226
+ | `token` | Token de autenticação fornecido pela equipe | String |
227
+
228
+ ### Resposta Esperada da API
229
+
230
+ A API deve retornar um JSON com um link de redirecionamento:
231
+
232
+ ```json
233
+ {
234
+ "status": "OK",
235
+ "link_fila": "https://sistema.ilibras.com.br/acesso.php?token=eyJp...",
236
+ "codigo": "4c6779a0aadb331ff53047879447809812fc9d5778254525d66466339135da5b",
237
+ "id_atendimento": "717",
238
+ "id_surdo": 417
239
+ }
240
+ ```
241
+
242
+ Campos aceitos para redirecionamento (em ordem de prioridade):
243
+ - `link_fila` ⭐ **Recomendado**
244
+ - `link`
245
+ - `url`
246
+ - `redirect`
247
+
248
+ **Comportamento após envio:**
249
+ 1. ✅ O widget valida e envia os dados
250
+ 2. ✅ A API processa e retorna o link de atendimento
251
+ 3. ✅ O link abre em **nova aba** do navegador
252
+ 4. ✅ O modal do widget fecha automaticamente
253
+ 5. ✅ Usuário permanece na página original enquanto o atendimento abre em outra aba
254
+
255
+ ## 🎯 Exemplos de Uso
256
+
257
+ ### Exemplo 1: Widget Simples
258
+
259
+ ```html
260
+ <!DOCTYPE html>
261
+ <html>
262
+ <head>
263
+ <title>Meu Site</title>
264
+ </head>
265
+ <body>
266
+ <h1>Bem-vindo!</h1>
267
+
268
+ <!-- Widget -->
269
+ <link rel="stylesheet" href="ilibras-widget.css">
270
+ <script src="ilibras-widget.js"></script>
271
+ <script>
272
+ new ILibrasWidget();
273
+ </script>
274
+ </body>
275
+ </html>
276
+ ```
277
+
278
+ ### Exemplo 2: Posição Personalizada
279
+
280
+ ```javascript
281
+ // Widget no canto inferior esquerdo
282
+ new ILibrasWidget({
283
+ position: 'bottom-left'
284
+ });
285
+ ```
286
+
287
+ ### Exemplo 3: Customização Completa
288
+
289
+ ```javascript
290
+ new ILibrasWidget({
291
+ position: 'bottom-right',
292
+ redirectUrl: 'https://minhaplataforma.com.br/atendimento',
293
+ primaryColor: '#0066CC',
294
+ title: 'Suporte Premium',
295
+ message: 'Nossa equipe está pronta para atender você! Preencha os dados abaixo.',
296
+ buttonText: 'Solicitar Atendimento',
297
+ zIndex: 10000
298
+ });
299
+ ```
300
+
301
+ ### Exemplo 4: Auto-inicialização Global
302
+
303
+ ```html
304
+ <!-- Defina a configuração antes de carregar o script -->
305
+ <script>
306
+ window.iLibrasWidgetConfig = {
307
+ position: 'bottom-right',
308
+ redirectUrl: 'https://teste.com.br',
309
+ primaryColor: '#FF5722'
310
+ };
311
+ </script>
312
+ <script src="ilibras-widget.js"></script>
313
+ <!-- Widget será inicializado automaticamente -->
314
+ ```
315
+
316
+ ### Exemplo 5: Controle Programático
317
+
318
+ ```javascript
319
+ // Guarde a instância
320
+ const widget = new ILibrasWidget();
321
+
322
+ // Abrir widget programaticamente
323
+ widget.openWidget();
324
+
325
+ // Fechar widget
326
+ widget.closeWidget();
327
+
328
+ // Alternar (abrir/fechar)
329
+ widget.toggleWidget();
330
+
331
+ // Remover widget da página
332
+ widget.destroy();
333
+ ```
334
+
335
+ ## 🎨 Personalização Avançada
336
+
337
+ ### Alterar Estilos via CSS
338
+
339
+ Você pode sobrescrever os estilos padrão:
340
+
341
+ ```css
342
+ /* Customizar botão flutuante */
343
+ .ilibras-widget-button {
344
+ width: 70px !important;
345
+ height: 70px !important;
346
+ }
347
+
348
+ /* Customizar modal */
349
+ .ilibras-widget-modal {
350
+ border-radius: 20px !important;
351
+ }
352
+
353
+ /* Customizar inputs */
354
+ .ilibras-widget-form-group input {
355
+ border-radius: 12px !important;
356
+ }
357
+ ```
358
+
359
+ ## 🔧 API JavaScript
360
+
361
+ ### Métodos Públicos
362
+
363
+ ```javascript
364
+ const widget = new ILibrasWidget(config);
365
+
366
+ // Abrir o widget
367
+ widget.openWidget();
368
+
369
+ // Fechar o widget
370
+ widget.closeWidget();
371
+
372
+ // Alternar estado (abrir/fechar)
373
+ widget.toggleWidget();
374
+
375
+ // Destruir o widget e remover do DOM
376
+ widget.destroy();
377
+ ```
378
+
379
+ ### Propriedades
380
+
381
+ ```javascript
382
+ // Verificar se o widget está aberto
383
+ console.log(widget.isOpen); // true ou false
384
+
385
+ // Acessar configuração
386
+ console.log(widget.config);
387
+ ```
388
+
389
+ ## 🌐 Compatibilidade
390
+
391
+ ### Navegadores Suportados
392
+
393
+ - ✅ Chrome 90+
394
+ - ✅ Firefox 88+
395
+ - ✅ Safari 14+
396
+ - ✅ Edge 90+
397
+ - ✅ Opera 76+
398
+ - ✅ Mobile Browsers (iOS Safari, Chrome Mobile)
399
+
400
+ ### Frameworks e CMS
401
+
402
+ O widget funciona com:
403
+
404
+ - ✅ HTML estático
405
+ - ✅ WordPress
406
+ - ✅ Joomla
407
+ - ✅ Drupal
408
+ - ✅ React
409
+ - ✅ Vue.js
410
+ - ✅ Angular
411
+ - ✅ Next.js
412
+ - ✅ Qualquer framework JavaScript
413
+
414
+ ## 📱 Responsividade
415
+
416
+ O widget se adapta automaticamente a diferentes tamanhos de tela:
417
+
418
+ - Desktop: Modal de 380px de largura
419
+ - Tablet: Modal responsivo com margens
420
+ - Mobile: Modal em tela cheia com otimizações
421
+
422
+ ## 🔒 Segurança e Validação
423
+
424
+ ### Envio Seguro via POST
425
+
426
+ Todos os dados são enviados via **método POST** (não via URL/GET), garantindo:
427
+ - Dados não ficam expostos na URL
428
+ - Maior segurança no tráfego de informações sensíveis
429
+ - Conformidade com boas práticas de desenvolvimento web
430
+
431
+ ### Validação de CPF
432
+
433
+ O widget valida o CPF antes do envio usando o algoritmo de verificação de dígitos. CPFs inválidos são rejeitados automaticamente.
434
+
435
+ ```javascript
436
+ // CPFs aceitos: números válidos com 11 dígitos
437
+ // CPFs rejeitados:
438
+ // - Números com menos de 11 dígitos
439
+ // - Sequências repetidas (111.111.111-11)
440
+ // - Dígitos verificadores incorretos
441
+ ```
442
+
443
+ ### Validação de Nome
444
+
445
+ - Entre 3 e 120 caracteres
446
+ - Apenas letras, espaços, apóstrofos, hífens e pontos — dígito, emoji e
447
+ pontuação de código são rejeitados
448
+ - Exige nome e sobrenome
449
+
450
+ ### Validação de Telefone
451
+
452
+ - DDD conferido contra a lista de códigos em uso no país
453
+ - Celular com 9 dígitos precisa começar em 9; fixo com 8 dígitos, entre 2 e 5
454
+ - Sequências repetidas (00000000000) são rejeitadas
455
+
456
+ ### Sanitização de Dados
457
+
458
+ - CPF e telefone: apenas dígitos
459
+ - Nome: espaços das pontas removidos e sequências de espaço reduzidas a um
460
+
461
+ > **A validação do widget é conveniência, não barreira.** Ela roda no navegador
462
+ > e qualquer pessoa a desliga pelo console. As mesmas regras valem no servidor,
463
+ > e é lá que elas decidem — o widget as repete só para avisar cedo e em
464
+ > português, sem uma ida à rede.
465
+
466
+ ## 🤖 Proteção contra automação
467
+
468
+ O widget participa de duas checagens simples do lado do servidor:
469
+
470
+ - **campo-armadilha** (`site_url`): existe no formulário, fica fora da tela e
471
+ fora da leitura de tela. Só chega preenchido quando quem respondeu foi um
472
+ programa que preenche todo campo que encontra;
473
+ - **tempo de preenchimento** (`iniciado_em`): o instante em que o modal foi
474
+ aberto. Envio em menos de 3 segundos é recusado.
475
+
476
+ Nenhuma das duas é intransponível, e não é o que se espera delas: quem segura
477
+ volume é o limite de requisições por IP e por contrato, aplicado no servidor.
478
+ Elas existem para que o ataque trivial — um `curl` em laço — não chegue até lá.
479
+
480
+ **Não há CAPTCHA, e isso é deliberado:** o público deste widget é surdo, e
481
+ desafio visual ou de áudio é exatamente a barreira que o produto existe para
482
+ remover.
483
+
484
+ Se o limite for atingido, a API responde `429` e o widget mostra a mensagem do
485
+ servidor (que já diz quanto esperar) sem repetir a requisição.
486
+
487
+ ## ♿ Acessibilidade
488
+
489
+ O widget segue as WCAG 2.1 nível AA nos pontos que dependem dele:
490
+
491
+ - **teclado**: o gatilho é um `<button>` de verdade; o modal é um
492
+ `role="dialog"` com `aria-modal`, foco levado ao primeiro campo ao abrir,
493
+ `Tab` preso dentro da janela, `Esc` para fechar e foco devolvido a quem
494
+ abriu;
495
+ - **leitor de tela**: todo campo tem rótulo associado, `aria-required` e
496
+ `aria-describedby` ligando dica e mensagem de erro; o estado do envio é
497
+ anunciado por uma região `aria-live`; falhas de serviço usam `role="alert"`;
498
+ - **erros**: aparecem junto do campo que os causou, com ícone além da cor, e o
499
+ foco vai para o primeiro campo inválido — nada de `alert()`;
500
+ - **contraste**: textos e mensagens em AA (4.5:1); campo inválido sinalizado por
501
+ borda mais espessa e símbolo, não só por cor;
502
+ - **preferências do sistema**: `prefers-reduced-motion` desliga as animações e
503
+ `forced-colors` (alto contraste do Windows) mantém contornos visíveis;
504
+ - **idioma**: o container declara `lang="pt-BR"`, para o leitor de tela usar a
505
+ voz certa mesmo num site em outro idioma.
506
+
507
+ ## 🎓 Exemplos Práticos
508
+
509
+ ### WordPress
510
+
511
+ ```php
512
+ <?php
513
+ // Adicione ao footer.php ou use wp_footer hook
514
+ function adicionar_ilibras_widget() {
515
+ ?>
516
+ <link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/ilibras-widget.css">
517
+ <script src="<?php echo get_template_directory_uri(); ?>/ilibras-widget.js"></script>
518
+ <script>
519
+ new ILibrasWidget({
520
+ redirectUrl: 'https://meusite.com.br/atendimento'
521
+ });
522
+ </script>
523
+ <?php
524
+ }
525
+ add_action('wp_footer', 'adicionar_ilibras_widget');
526
+ ?>
527
+ ```
528
+
529
+ ### React
530
+
531
+ ```jsx
532
+ import { useEffect } from 'react';
533
+
534
+ function App() {
535
+ useEffect(() => {
536
+ // Carregar CSS
537
+ const link = document.createElement('link');
538
+ link.rel = 'stylesheet';
539
+ link.href = '/ilibras-widget.css';
540
+ document.head.appendChild(link);
541
+
542
+ // Carregar JS
543
+ const script = document.createElement('script');
544
+ script.src = '/ilibras-widget.js';
545
+ script.onload = () => {
546
+ new window.ILibrasWidget({
547
+ position: 'bottom-right'
548
+ });
549
+ };
550
+ document.body.appendChild(script);
551
+
552
+ return () => {
553
+ // Cleanup
554
+ if (window.iLibrasWidgetInstance) {
555
+ window.iLibrasWidgetInstance.destroy();
556
+ }
557
+ };
558
+ }, []);
559
+
560
+ return <div className="App">Meu App</div>;
561
+ }
562
+ ```
563
+
564
+ ### Vue.js
565
+
566
+ ```vue
567
+ <template>
568
+ <div id="app">
569
+ <h1>Meu App Vue</h1>
570
+ </div>
571
+ </template>
572
+
573
+ <script>
574
+ export default {
575
+ name: 'App',
576
+ mounted() {
577
+ // Carregar widget
578
+ const link = document.createElement('link');
579
+ link.rel = 'stylesheet';
580
+ link.href = '/ilibras-widget.css';
581
+ document.head.appendChild(link);
582
+
583
+ const script = document.createElement('script');
584
+ script.src = '/ilibras-widget.js';
585
+ script.onload = () => {
586
+ new window.ILibrasWidget({
587
+ position: 'bottom-right',
588
+ redirectUrl: 'https://teste.com.br'
589
+ });
590
+ };
591
+ document.body.appendChild(script);
592
+ },
593
+ beforeUnmount() {
594
+ if (window.iLibrasWidgetInstance) {
595
+ window.iLibrasWidgetInstance.destroy();
596
+ }
597
+ }
598
+ }
599
+ </script>
600
+ ```
601
+
602
+ ## 📄 Estrutura de Arquivos
603
+
604
+ ```
605
+ Ilibras_widget/
606
+ ├── src/ # A fonte — é aqui que se mexe
607
+ │ ├── ILibrasWidget.js # a classe
608
+ │ ├── index.js # entrada do pacote npm
609
+ │ ├── navegador.js # entrada da build de <script>
610
+ │ └── assets-gerados.js # GERADO: css e logo embutidos
611
+ ├── scripts/
612
+ │ ├── gerar-assets.mjs # css + svg -> src/assets-gerados.js
613
+ │ └── build.mjs # src/ -> dist/ e raiz
614
+ ├── tests/ # npm test
615
+ ├── ilibras-widget.css # Estilo (fonte do embutido)
616
+ ├── ilibras-LOGO.svg # Logo (fonte do embutido)
617
+ ├── ilibras-widget.js # GERADO: build de <script>, no endereço de sempre
618
+ ├── index.d.ts # Tipos TypeScript
619
+ ├── exemplo.html # Página de exemplo e demonstração
620
+ └── README.md # Esta documentação
621
+ ```
622
+
623
+ ## 🛠️ Desenvolvimento
624
+
625
+ ```bash
626
+ npm install # só o esbuild
627
+ npm run build # gera dist/ e o ilibras-widget.js da raiz
628
+ npm test # garante que estilo e logo saíram embutidos nas três builds
629
+ ```
630
+
631
+ > ⚠️ **Não edite `ilibras-widget.js` na raiz nem `src/assets-gerados.js`.** Os
632
+ > dois são gerados e sobrescritos pelo build. A lógica vive em
633
+ > `src/ILibrasWidget.js`; o visual, em `ilibras-widget.css`.
634
+
635
+ O `ilibras-widget.js` da raiz é versionado de propósito, mesmo sendo gerado:
636
+ sites já instalados apontam para esse caminho e o README manda baixá-lo de lá.
637
+ Mudar o endereço quebraria justamente quem não usa npm.
638
+
639
+ ### Publicando no npm
640
+
641
+ `prepublishOnly` roda o build sozinho, então basta:
642
+
643
+ ```bash
644
+ npm version minor # ou patch / major
645
+ npm publish
646
+ ```
647
+
648
+ Confira o conteúdo antes com `npm pack --dry-run`.
649
+
650
+ ## 🐛 Solução de Problemas
651
+
652
+ ### Widget não aparece
653
+
654
+ 1. Confira se o script carregou (aba Network do navegador)
655
+ 2. Veja o console: sem `token`, o widget avisa ao tentar enviar
656
+ 3. Usando bundler? Lembre que `import` não cria o widget — é preciso
657
+ `new ILibrasWidget({ token })`
658
+
659
+ ### Estilos não aplicados
660
+
661
+ A partir da v1.4.0 o estilo vai dentro do JavaScript; não há mais CSS para
662
+ carregar na ordem certa. Se o visual está estranho, é conflito com o CSS do
663
+ site — usar `!important` nas suas regras resolve.
664
+
665
+ ### Aparecem dois widgets
666
+
667
+ Em React, Vue ou Angular, chame `destroy()` na desmontagem do componente. Sem
668
+ isso cada montagem deixa um widget para trás.
669
+
670
+ ### Erro de importação em Next.js ou Nuxt
671
+
672
+ O pacote não toca o DOM ao ser importado, então o import em si é seguro. Mas
673
+ `new ILibrasWidget(...)` precisa do navegador: crie dentro de `useEffect`
674
+ (React) ou `onMounted` (Vue), nunca no corpo do componente.
675
+
676
+ ## 🤝 Contribuindo
677
+
678
+ Contribuições são bem-vindas! Para contribuir:
679
+
680
+ 1. Fork o projeto
681
+ 2. Crie uma branch para sua feature (`git checkout -b feature/NovaFeature`)
682
+ 3. Commit suas mudanças (`git commit -m 'Adiciona nova feature'`)
683
+ 4. Push para a branch (`git push origin feature/NovaFeature`)
684
+ 5. Abra um Pull Request
685
+
686
+ ## 📝 Licença
687
+
688
+ Este projeto está sob a licença MIT. Veja o arquivo [LICENSE](LICENSE) para mais detalhes.
689
+
690
+ ## 👥 Autor
691
+
692
+ **iLibras Team**
693
+
694
+ ## 📧 Suporte
695
+
696
+ Para suporte, envie um email para suporte@ilibras.com.br ou abra uma issue no GitHub.
697
+
698
+ ## 🔄 Changelog
699
+
700
+ ### v1.0.0 - 2026-02-18
701
+ - ✨ Versão inicial
702
+ - 🎨 Widget flutuante com design responsivo
703
+ - ✅ Validação de CPF
704
+ - 📱 Suporte mobile
705
+ - 🎯 Máscara de entrada para CPF
706
+ - 🌐 Múltiplas opções de posicionamento
707
+ - 🎨 Personalização de cores e textos
708
+ - 🔒 Envio seguro via POST para API
709
+ - 📡 Integração com API iLibras (cadastrar_cliente.php)
710
+ - 🔄 Redirecionamento automático baseado na resposta da API
711
+
712
+
713
+ Feito com ❤️ pela equipe iLibras