@dotrino/support 0.4.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 seyacat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # @dotrino/support
2
+
3
+ Web Component (`<dotrino-support>`) con un **modal de soporte/donaciones**
4
+ reutilizable por **cualquier app del ecosistema Dotrino**: las hechas en
5
+ Vue 3 y las vanilla (HTML/JS), porque es un *custom element* estándar.
6
+
7
+ ## Filosofía (importante)
8
+
9
+ Cumple la regla de Dotrino: **no carga JS de terceros ni setea cookies**.
10
+ El modal es 100 % autohosteado (Shadow DOM). Lo único externo es el **enlace de
11
+ salida** (Ko-fi, PayPal, etc.) que el usuario abre explícitamente en una pestaña
12
+ nueva. No hay widget de terceros, ni pixel, ni tracking.
13
+
14
+ ## Comportamiento por defecto
15
+
16
+ El trigger por defecto es la **moneda dorada del ecosistema** (embebida como
17
+ data-URI en el propio módulo — no hay que copiar ningún archivo). Es un
18
+ **elemento integrado** en el flujo (no flotante): **ubicalo vos arriba a la
19
+ derecha**, dentro de la barra/header de la app (así no se monta sobre los menús).
20
+ Para apps sin barra superior, el atributo `floating` lo fija arriba a la derecha.
21
+ Con `inline` se usa un botón de texto en lugar de la moneda.
22
+
23
+ La moneda es una **tarjeta 3D que rota en loop infinito**: muestra la cara de la
24
+ moneda (donar) ~2 s, gira mostrando el **icono de compartir** ~2 s y vuelve a
25
+ girar, indefinidamente. El hover pausa la rotación. (Respeta
26
+ `prefers-reduced-motion`.)
27
+
28
+ El modal incluye, además de los enlaces de donación, una sección **Compartir**
29
+ con botones para **WhatsApp, X, Facebook e Instagram** que comparten la **URL
30
+ actual de la página** (incluido el `#fragment`, que nunca llega al servidor).
31
+ Como Instagram no admite compartir una URL por web, su botón **copia el enlace
32
+ al portapapeles**. Personalizá la URL con `share-url` o el texto con `share-text`;
33
+ desactivá la sección con `no-share`.
34
+
35
+ Al cargar la página aparece sola una **burbuja de diálogo "Donar/Compartir"**
36
+ debajo de la moneda (extendida a la izquierda, apuntando a ella). Se oculta a los
37
+ ~6 s, al abrir el modal, o cuando la ventana pierde el foco (`blur`). Además,
38
+ **el hover sobre la moneda vuelve a mostrar la burbuja** (en vez de un tooltip
39
+ nativo). Se desactiva con `no-bubble`.
40
+
41
+ El texto de la moneda y la burbuja es **bilingüe es/en** automático (detecta
42
+ `<html lang>` o el idioma del navegador; o pasale `lang="es"|"en"`).
43
+
44
+ ## Instalación
45
+
46
+ ```bash
47
+ npm install @dotrino/support
48
+ ```
49
+
50
+ ## Uso
51
+
52
+ ### Vanilla (HTML/JS — qrshare, ecuavoley, padel…)
53
+
54
+ ```html
55
+ <script type="module" src="/node_modules/@dotrino/support/src/index.js"></script>
56
+
57
+ <dotrino-support href="https://ko-fi.com/tuusuario"></dotrino-support>
58
+ ```
59
+
60
+ ### Vue 3 (dotrino, messenger, mundial, chat, chess, gridgame, favicon)
61
+
62
+ ```js
63
+ // en main.ts
64
+ import '@dotrino/support'
65
+ ```
66
+
67
+ ```vue
68
+ <dotrino-support href="https://ko-fi.com/tuusuario" lang="auto" />
69
+ ```
70
+
71
+ > Si tu build de Vue se queja del tag desconocido, marcá el custom element en
72
+ > `vite.config.ts`:
73
+ > ```ts
74
+ > vue({ template: { compilerOptions: { isCustomElement: (t) => t === 'dotrino-support' } } })
75
+ > ```
76
+
77
+ ## API
78
+
79
+ ### Atributos
80
+
81
+ | Atributo | Descripción |
82
+ |--------------|-------------|
83
+ | `href` | URL de soporte única (p. ej. `https://ko-fi.com/usuario`). |
84
+ | `links` | JSON con uno o varios enlaces: `'[{"label":"Ko-fi","href":"…"},{"label":"PayPal","href":"…"}]'`. Tiene prioridad sobre `href`. |
85
+ | `floating` | Fija la moneda arriba a la derecha (`position:fixed`). Para apps sin barra superior. Por defecto es integrada en el flujo. |
86
+ | `inline` | Si está presente, usa botón de texto en el flujo en vez de la moneda. |
87
+ | `hint` | Texto del tooltip/`title` del trigger y de la burbuja. Default `"Donar/Compartir"` / `"Donate/Share"`. |
88
+ | `share-url` | URL a compartir. Default: `window.location.href` (incluye el `#fragment`). |
89
+ | `share-text` | Texto que acompaña al enlace al compartir. Default según idioma. |
90
+ | `no-share` | Desactiva la sección "Compartir" del modal. |
91
+ | `repo` | `usuario/repo` o URL del repo. Añade una cara de "bug" al flipper y una sección "Reporta un error" en el modal con enlace directo a los issues. |
92
+ | `bug-href` | URL completa a la página de issues (alternativa a `repo` si no sigue el patrón `…/issues`). |
93
+ | `discord` | URL de invitación a Discord (`https://discord.gg/…`). Añade una sección de comunidad al modal con enlace a Discord. |
94
+ | `coin` | URL de imagen para reemplazar la moneda por defecto. |
95
+ | `no-bubble` | Desactiva la burbuja de diálogo automática. |
96
+ | `bubble-timeout` | Ms que la burbuja queda visible antes de ocultarse sola (default `6000`). |
97
+ | `cta` | Texto del botón disparador (solo modo `inline`). Default según idioma. |
98
+ | `no-trigger` | Si está presente, no renderiza trigger; abrís el modal con `el.open()`. |
99
+ | `heading` | Título del modal (override). |
100
+ | `message` | Cuerpo del modal (override). |
101
+ | `lang` | `es` \| `en` \| `auto` (default `auto`: usa `<html lang>` o el idioma del navegador). |
102
+ | `variant` | `solid` (default) \| `ghost` — estilo del botón en modo `inline`. |
103
+
104
+ Si no pasás `label` en un enlace, se deriva del dominio (Ko-fi, PayPal, Patreon,
105
+ GitHub Sponsors) o cae al texto por defecto del idioma.
106
+
107
+ ### Métodos
108
+
109
+ - `el.open()` — abre el modal.
110
+ - `el.close()` — lo cierra.
111
+
112
+ ### Eventos
113
+
114
+ - `cc-support-open` y `cc-support-close` (`bubbles`, `composed`).
115
+
116
+ ### Disparar desde tu propio botón
117
+
118
+ ```html
119
+ <dotrino-support id="sup" no-trigger href="https://ko-fi.com/tuusuario"></dotrino-support>
120
+ <button onclick="document.getElementById('sup').open()">☕ Invitame un café</button>
121
+ ```
122
+
123
+ ### Estilos
124
+
125
+ El modal vive en Shadow DOM (aislado). Para retoques expone `part`s:
126
+ `::part(trigger)`, `::part(modal)`, `::part(bubble)`, `::part(share)`,
127
+ `::part(share-btn)` y `::part(share-copied)`.
128
+
129
+ ## Licencia
130
+
131
+ MIT
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@dotrino/support",
3
+ "version": "0.4.1",
4
+ "description": "Web Component (custom element) de modal de soporte/donaciones reutilizable por cualquier app del ecosistema Dotrino. Autohosteado, sin JS de terceros ni cookies.",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "module": "src/index.js",
8
+ "types": "src/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./src/index.d.ts",
12
+ "import": "./src/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "src",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "keywords": [
21
+ "dotrino",
22
+ "web-component",
23
+ "custom-element",
24
+ "support",
25
+ "donations",
26
+ "ko-fi",
27
+ "cross-app"
28
+ ],
29
+ "author": "seyacat",
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "@dotrino/store": "^0.4.0"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/imdotrino/dotrino-support.git"
37
+ }
38
+ }
package/src/coin.js ADDED
@@ -0,0 +1,19 @@
1
+ // Moneda por defecto: PNG (fondo dorado, src/monedabg.png) + logo SVG inline
2
+ // (src/monedalogo.svg) compuestos como data URI. Sin assets externos en runtime.
3
+ // La app consumidora puede sobreescribir con el atributo `coin`.
4
+
5
+ const BG_B64 = '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'
6
+
7
+ const SVG = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 200 200">
8
+ <defs>
9
+ <filter id="ls" x="-30%" y="-30%" width="160%" height="160%"><feGaussianBlur in="SourceAlpha" stdDeviation="1.6"/><feOffset dx="0" dy="1.4"/><feComponentTransfer><feFuncA type="linear" slope=".6"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge></filter>
10
+ </defs>
11
+ <image href="data:image/png;base64,${BG_B64}" x="0" y="0" width="200" height="200"/>
12
+ <g filter="url(#ls)"><svg x="24.5" y="24.5" width="151" height="151" viewBox="0 0 270.93332 270.93332"><g transform="translate(28.947702,-41.883531)">
13
+ <g transform="matrix(0.67188176,1.0488761,-1.0422716,0.67466777,217.54163,-47.421796)">
14
+ <path fill="#ff3310" d="m 187.73621,172.15946 -73.61353,-47.36097 0.17119,2.24896 c 0.0942,1.23693 0.4026,4.21349 0.68542,6.61458 0.28281,2.40109 0.76072,6.68734 1.06202,9.525 0.72258,6.80541 2.10929,19.36133 2.1834,19.76942 2.74744,3.18095 19.34208,9.09542 19.34208,9.09542 0,0 -19.16069,7.52806 -19.41581,9.60752 0,0.41996 -0.35443,3.93103 -0.78762,7.80237 -0.77006,6.88189 -2.75747,25.31726 -3.06921,28.47006 l -0.15697,1.5875 z"/>
15
+ <path fill="#ff3310" d="m 18.872668,172.15946 73.613488,-47.36097 -0.17119,2.24896 c -0.0942,1.23693 -0.4026,4.21349 -0.68542,6.61458 -0.28281,2.40109 -0.76072,6.68734 -1.06202,9.525 -0.72258,6.80541 -2.109287,19.36133 -2.183397,19.76942 -2.74744,3.18095 -19.342071,9.09542 -19.342071,9.09542 0,0 19.160681,7.52806 19.415801,9.60752 0,0.41996 0.35443,3.93103 0.78762,7.80237 0.770057,6.88189 2.757467,25.31726 3.069207,28.47006 l 0.15697,1.5875 z"/>
16
+ </g></g></svg></g>
17
+ </svg>`
18
+
19
+ export const COIN_DATA_URI = 'data:image/svg+xml;utf8,' + encodeURIComponent(SVG)
package/src/coin.svg ADDED
@@ -0,0 +1,23 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 200 200">
2
+ <defs>
3
+ <filter id="ls" x="-30%" y="-30%" width="160%" height="160%">
4
+ <feGaussianBlur in="SourceAlpha" stdDeviation="1.6"/>
5
+ <feOffset dx="0" dy="1.4"/>
6
+ <feComponentTransfer><feFuncA type="linear" slope=".6"/></feComponentTransfer>
7
+ <feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
8
+ </filter>
9
+ </defs>
10
+
11
+ <image href="data:image/png;base64,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" x="0" y="0" width="200" height="200"/>
12
+
13
+ <g filter="url(#ls)">
14
+ <svg x="24.5" y="24.5" width="151" height="151" viewBox="0 0 270.93332 270.93332">
15
+ <g transform="translate(28.947702,-41.883531)">
16
+ <g transform="matrix(0.67188176,1.0488761,-1.0422716,0.67466777,217.54163,-47.421796)">
17
+ <path fill="#ff3310" d="m 187.73621,172.15946 -73.61353,-47.36097 0.17119,2.24896 c 0.0942,1.23693 0.4026,4.21349 0.68542,6.61458 0.28281,2.40109 0.76072,6.68734 1.06202,9.525 0.72258,6.80541 2.10929,19.36133 2.1834,19.76942 2.74744,3.18095 19.34208,9.09542 19.34208,9.09542 0,0 -19.16069,7.52806 -19.41581,9.60752 0,0.41996 -0.35443,3.93103 -0.78762,7.80237 -0.77006,6.88189 -2.75747,25.31726 -3.06921,28.47006 l -0.15697,1.5875 z"/>
18
+ <path fill="#ff3310" d="m 18.872668,172.15946 73.613488,-47.36097 -0.17119,2.24896 c -0.0942,1.23693 -0.4026,4.21349 -0.68542,6.61458 -0.28281,2.40109 -0.76072,6.68734 -1.06202,9.525 -0.72258,6.80541 -2.109287,19.36133 -2.183397,19.76942 -2.74744,3.18095 -19.342071,9.09542 -19.342071,9.09542 0,0 19.160681,7.52806 19.415801,9.60752 0,0.41996 0.35443,3.93103 0.78762,7.80237 0.770057,6.88189 2.757467,25.31726 3.069207,28.47006 l 0.15697,1.5875 z"/>
19
+ </g>
20
+ </g>
21
+ </svg>
22
+ </g>
23
+ </svg>
package/src/index.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ export interface SupportLink {
2
+ label?: string
3
+ href: string
4
+ }
5
+
6
+ /**
7
+ * Custom element `<dotrino-support>`: modal de soporte autohosteado,
8
+ * reutilizable por cualquier app del ecosistema Dotrino. Sin JS de
9
+ * terceros ni cookies.
10
+ *
11
+ * Atributos para "Reporta un error" (opcional): `repo` ("usuario/repo" o URL
12
+ * completa del repo) o `bug-href` (URL completa a los issues). Si alguno está
13
+ * presente, se añade una cara de bug al flipper y una sección al modal con un
14
+ * enlace directo a la página de issues del repositorio.
15
+ *
16
+ * Atributo `discord` (opcional): URL de invitación a Discord (p. ej.
17
+ * "https://discord.gg/xxxx"). Si está presente, el modal muestra una sección
18
+ * de comunidad con un enlace a Discord.
19
+ *
20
+ * Contador de aperturas (cross-app): al montarse registra la apertura de la app
21
+ * en el store compartido (`store.dotrino.com`), que el hub `dotrino.com` usa
22
+ * para su tab "Recientes". 100% local al navegador, sin servidor ni terceros.
23
+ * - `app`: id de la app a contar (default: `location.hostname`).
24
+ * - `no-count`: si está presente, no registra la apertura.
25
+ * El registro es best-effort vía import dinámico del store: si no hay bundler
26
+ * (carga vanilla por CDN) o el store no está disponible, no cuenta pero tampoco
27
+ * rompe la UI de soporte.
28
+ */
29
+ export declare class DotrinoSupport extends HTMLElement {
30
+ /** Abre el modal y emite 'cc-support-open'. */
31
+ open(): void
32
+ /** Cierra el modal y emite 'cc-support-close'. */
33
+ close(): void
34
+ }
35
+
36
+ declare global {
37
+ interface HTMLElementTagNameMap {
38
+ 'dotrino-support': DotrinoSupport
39
+ }
40
+ }
41
+
42
+ export default DotrinoSupport
package/src/index.js ADDED
@@ -0,0 +1,860 @@
1
+ /**
2
+ * @dotrino/support
3
+ *
4
+ * Web Component (custom element) `<dotrino-support>` reutilizable por
5
+ * CUALQUIER app del ecosistema Dotrino (Vue o vanilla), que muestra un
6
+ * modal de "apoya el proyecto" con enlaces configurables.
7
+ *
8
+ * Filosofía Dotrino: NO carga JS de terceros ni setea cookies. El modal
9
+ * es 100% autohosteado (Shadow DOM); lo único externo es el enlace de salida
10
+ * que abre el usuario explícitamente en una pestaña nueva.
11
+ *
12
+ * Uso vanilla:
13
+ * <script type="module" src=".../dotrino-support/src/index.js"></script>
14
+ * <dotrino-support href="https://ko-fi.com/usuario"></dotrino-support>
15
+ *
16
+ * Uso Vue 3 (el tag funciona tal cual; opcionalmente configurá
17
+ * `isCustomElement` en compilerOptions si tu setup se queja):
18
+ * import '@dotrino/support'
19
+ * <dotrino-support href="https://ko-fi.com/usuario" lang="auto" />
20
+ *
21
+ * API:
22
+ * Atributos:
23
+ * href URL de soporte única (p. ej. https://ko-fi.com/usuario)
24
+ * links JSON: [{ "label": "Ko-fi", "href": "..." }, ...] (uno o varios)
25
+ * cta Texto del botón disparador (default según idioma)
26
+ * no-trigger Si está, no renderiza botón; abrí con el método open()
27
+ * heading Título del modal (override)
28
+ * message Cuerpo del modal (override)
29
+ * lang 'es' | 'en' | 'auto' (default 'auto')
30
+ * variant 'solid' (default) | 'ghost' — estilo del disparador
31
+ * Métodos: el.open(), el.close()
32
+ * Eventos: 'cc-support-open', 'cc-support-close' (bubbles, composed)
33
+ */
34
+
35
+ import { COIN_DATA_URI } from './coin.js'
36
+
37
+ const I18N = {
38
+ es: {
39
+ cta: 'Apoyar',
40
+ heading: 'Apoya al proyecto',
41
+ message:
42
+ 'Si esta herramienta te resulta útil, puedes dar una pequeña contribución. Las apps del ecosistema Dotrino son gratuitas y autohosteadas. ¡Gracias!',
43
+ close: 'Cerrar',
44
+ defaultLink: 'Donar',
45
+ hint: 'Donar/Compartir',
46
+ shareHeading: 'Compartir',
47
+ shareText: '¡Mira esto!',
48
+ copied: '¡Enlace copiado!',
49
+ reportBug: 'Reporta un error',
50
+ discord: 'Canal de Soporte',
51
+ share: {
52
+ whatsapp: 'WhatsApp',
53
+ x: 'X',
54
+ facebook: 'Facebook',
55
+ instagram: 'Instagram',
56
+ },
57
+ },
58
+ en: {
59
+ cta: 'Support',
60
+ heading: 'Support the project',
61
+ message:
62
+ 'If you find this tool useful, you can make a small contribution. The Dotrino ecosystem apps are free and self-hosted. Thank you!',
63
+ close: 'Close',
64
+ defaultLink: 'Donate',
65
+ hint: 'Donate/Share',
66
+ shareHeading: 'Share',
67
+ shareText: 'Check this out!',
68
+ copied: 'Link copied!',
69
+ reportBug: 'Report a bug',
70
+ discord: 'Support channel',
71
+ share: {
72
+ whatsapp: 'WhatsApp',
73
+ x: 'X',
74
+ facebook: 'Facebook',
75
+ instagram: 'Instagram',
76
+ },
77
+ },
78
+ }
79
+
80
+ // Iconos de marca (SVG inline, sin JS de terceros). Heredan color con fill.
81
+ const SHARE_ICONS = {
82
+ share:
83
+ '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6"/><path d="m21 3-9 9"/><path d="M15 3h6v6"/></g></svg>',
84
+ whatsapp:
85
+ '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M.057 24l1.687-6.163a11.867 11.867 0 0 1-1.587-5.946C.16 5.335 5.495 0 12.05 0a11.817 11.817 0 0 1 8.413 3.488 11.824 11.824 0 0 1 3.48 8.414c-.003 6.557-5.338 11.892-11.893 11.892a11.9 11.9 0 0 1-5.688-1.448L.057 24zm6.597-3.807c1.676.995 3.276 1.591 5.392 1.592 5.448 0 9.886-4.434 9.889-9.885.002-5.462-4.415-9.89-9.881-9.892-5.452 0-9.887 4.434-9.889 9.884a9.86 9.86 0 0 0 1.519 5.26l-.999 3.648 3.969-1.018zm11.387-5.464c-.074-.124-.272-.198-.57-.347-.297-.149-1.758-.868-2.031-.967-.272-.099-.47-.149-.669.149-.198.297-.768.967-.941 1.165-.173.198-.347.223-.644.074-.297-.149-1.255-.462-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.297-.347.446-.521.151-.172.2-.296.3-.495.099-.198.05-.372-.025-.521-.075-.148-.669-1.611-.916-2.206-.242-.579-.487-.501-.669-.51l-.57-.01c-.198 0-.52.074-.792.372s-1.04 1.016-1.04 2.479 1.065 2.876 1.213 3.074c.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.626.712.226 1.36.194 1.872.118.571-.085 1.758-.719 2.006-1.413.248-.695.248-1.29.173-1.414z"/></svg>',
86
+ x: '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>',
87
+ facebook:
88
+ '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>',
89
+ instagram:
90
+ '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.406-11.845a1.44 1.44 0 1 0 0 2.881 1.44 1.44 0 0 0 0-2.881z"/></svg>',
91
+ }
92
+
93
+ // Icono de bug (SVG inline, sin JS de terceros). Hereda color con fill.
94
+ const BUG_ICON =
95
+ '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5c-.49 0-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8zm-6 8h-4v-2h4v2zm0-4h-4v-2h4v2z"/></svg>'
96
+
97
+ // Icono de Discord (SVG inline, sin JS de terceros). Hereda color con fill.
98
+ const DISCORD_ICON =
99
+ '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>'
100
+
101
+ const SHARE_COLORS = {
102
+ whatsapp: '#25D366',
103
+ x: '#000000',
104
+ facebook: '#1877F2',
105
+ instagram: '#E4405F',
106
+ }
107
+
108
+ const STYLE = `
109
+ :host {
110
+ all: initial;
111
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
112
+ display: inline-block;
113
+ position: relative;
114
+ vertical-align: middle;
115
+ line-height: 0;
116
+ }
117
+ /* Modo flotante opcional (apps sin barra superior): :host([floating]) */
118
+ :host([floating]) {
119
+ position: fixed;
120
+ top: 14px;
121
+ right: 14px;
122
+ z-index: 2147483000;
123
+ }
124
+ * { box-sizing: border-box; }
125
+
126
+ .trigger {
127
+ font: inherit;
128
+ font-weight: 600;
129
+ font-size: 0.9rem;
130
+ padding: 0.5rem 1.1rem;
131
+ border-radius: 50px;
132
+ cursor: pointer;
133
+ transition: all 0.25s ease;
134
+ white-space: nowrap;
135
+ border: 1px solid transparent;
136
+ }
137
+ .trigger.solid {
138
+ background: #3498db;
139
+ color: #fff;
140
+ box-shadow: 0 2px 10px rgba(52, 152, 219, 0.35);
141
+ }
142
+ .trigger.solid:hover { background: #2980b9; transform: translateY(-1px); }
143
+ .trigger.ghost {
144
+ background: transparent;
145
+ color: currentColor;
146
+ border-color: rgba(127, 127, 127, 0.5);
147
+ }
148
+ .trigger.ghost:hover { border-color: currentColor; }
149
+
150
+ /* Moneda: elemento integrado (no flotante). La app la ubica arriba a la derecha.
151
+ Tarjeta 3D que rota mostrando dos caras: moneda (donar) e icono de compartir. */
152
+ .trigger.coin {
153
+ display: block;
154
+ padding: 0;
155
+ width: 38px;
156
+ height: 38px;
157
+ border-radius: 50%;
158
+ background: transparent;
159
+ border: none;
160
+ line-height: 0;
161
+ perspective: 600px;
162
+ transition: transform 0.2s ease;
163
+ }
164
+ .trigger.coin:hover { transform: translateY(-1px) scale(1.08); }
165
+ .trigger.coin:hover .flipper { animation-play-state: paused; }
166
+
167
+ .flipper {
168
+ display: block;
169
+ position: relative;
170
+ width: 100%;
171
+ height: 100%;
172
+ transform-style: preserve-3d;
173
+ animation: cc-flip 7.2s ease-in-out infinite;
174
+ }
175
+ @keyframes cc-flip {
176
+ 0% { transform: rotateY(0deg); }
177
+ 43% { transform: rotateY(0deg); }
178
+ 50% { transform: rotateY(180deg); }
179
+ 93% { transform: rotateY(180deg); }
180
+ 100% { transform: rotateY(360deg); }
181
+ }
182
+ /* Con cara de bug habilitada: 3 caras (moneda → compartir → bug) repartidas
183
+ a 120° en el mismo plano; la animación se detiene en cada una. */
184
+ .flipper.three { animation-name: cc-flip3; }
185
+ @keyframes cc-flip3 {
186
+ 0% { transform: rotateY(0deg); }
187
+ 27% { transform: rotateY(0deg); }
188
+ 36% { transform: rotateY(-120deg); }
189
+ 60% { transform: rotateY(-120deg); }
190
+ 69% { transform: rotateY(-240deg); }
191
+ 93% { transform: rotateY(-240deg); }
192
+ 100% { transform: rotateY(-360deg); }
193
+ }
194
+ @media (prefers-reduced-motion: reduce) {
195
+ .flipper { animation: none; }
196
+ }
197
+
198
+ .face {
199
+ position: absolute;
200
+ inset: 0;
201
+ width: 100%;
202
+ height: 100%;
203
+ border-radius: 50%;
204
+ overflow: hidden;
205
+ backface-visibility: hidden;
206
+ -webkit-backface-visibility: hidden;
207
+ box-shadow: 0 3px 10px rgba(0, 0, 0, 0.35);
208
+ }
209
+ .face.front img {
210
+ width: 100%;
211
+ height: 100%;
212
+ display: block;
213
+ object-fit: contain;
214
+ pointer-events: none;
215
+ }
216
+ /* Caras con icono (compartir / bug). El transform de rotación va inline
217
+ porque depende de si hay 2 o 3 caras. */
218
+ .face.icon {
219
+ background: #3498db;
220
+ display: flex;
221
+ align-items: center;
222
+ justify-content: center;
223
+ }
224
+ .face.icon svg {
225
+ width: 56%;
226
+ height: 56%;
227
+ color: #fff;
228
+ pointer-events: none;
229
+ }
230
+ @media (max-width: 480px) {
231
+ .trigger.coin { width: 32px; height: 32px; }
232
+ }
233
+
234
+ /* Burbuja: posicionada respecto a la moneda (debajo, extendida a la izquierda) */
235
+ .bubble {
236
+ position: absolute;
237
+ top: calc(100% + 8px);
238
+ right: 0;
239
+ z-index: 2147483000;
240
+ background: #3498db;
241
+ color: #fff;
242
+ padding: 0.5rem 0.85rem;
243
+ border-radius: 10px;
244
+ font-size: 0.85rem;
245
+ line-height: 1.2;
246
+ font-weight: 600;
247
+ white-space: nowrap;
248
+ box-shadow: 0 3px 12px rgba(0, 0, 0, 0.35);
249
+ opacity: 0;
250
+ transform: translateY(-8px) scale(0.96);
251
+ transform-origin: top right;
252
+ transition: opacity 0.3s ease, transform 0.3s ease;
253
+ pointer-events: none;
254
+ cursor: pointer;
255
+ }
256
+ .bubble.show { opacity: 1; transform: none; pointer-events: auto; }
257
+ .bubble::after {
258
+ content: '';
259
+ position: absolute;
260
+ top: -6px;
261
+ right: 13px;
262
+ border: 6px solid transparent;
263
+ border-top: 0;
264
+ border-bottom-color: #3498db;
265
+ }
266
+ @media (max-width: 480px) {
267
+ .bubble { font-size: 0.8rem; padding: 0.4rem 0.7rem; }
268
+ .bubble::after { right: 10px; }
269
+ }
270
+
271
+ /* <dialog> con showModal(): se renderiza en el top layer, así el modal se
272
+ centra respecto a la página y NO queda contenido por el header/ancestros
273
+ con transform/filter donde esté integrada la moneda. */
274
+ .overlay {
275
+ border: none;
276
+ padding: 1rem;
277
+ background: transparent;
278
+ max-width: 100vw;
279
+ max-height: 100vh;
280
+ overflow: visible;
281
+ color: #fff;
282
+ }
283
+ .overlay::backdrop { background: rgba(0, 0, 0, 0.55); backdrop-filter: blur(3px); }
284
+
285
+ .modal {
286
+ background: #1f2c3a;
287
+ color: #fff;
288
+ border: 1px solid rgba(255, 255, 255, 0.12);
289
+ border-radius: 16px;
290
+ max-width: 420px;
291
+ width: 100%;
292
+ padding: 2rem 1.75rem 1.75rem;
293
+ text-align: center;
294
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
295
+ position: relative;
296
+ }
297
+ .overlay[open] .modal { animation: cc-pop 0.2s ease; }
298
+ @keyframes cc-pop { from { transform: translateY(12px) scale(0.98); opacity: 0.4; } to { transform: none; opacity: 1; } }
299
+
300
+ .close {
301
+ position: absolute;
302
+ top: 0.6rem;
303
+ right: 0.6rem;
304
+ width: 2rem;
305
+ height: 2rem;
306
+ border: none;
307
+ background: transparent;
308
+ color: rgba(255, 255, 255, 0.7);
309
+ font-size: 1.4rem;
310
+ line-height: 1;
311
+ cursor: pointer;
312
+ border-radius: 50%;
313
+ transition: all 0.2s ease;
314
+ }
315
+ .close:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
316
+
317
+ .heading { font-size: 1.4rem; font-weight: 700; margin: 0 0 1.1rem; color: #3498db; }
318
+ .message { font-size: 0.98rem; line-height: 1.6; margin: 0 0 1.5rem; opacity: 0.92; }
319
+
320
+ .links { display: flex; flex-direction: column; align-items: center; gap: 0.6rem; }
321
+ .link {
322
+ display: inline-flex;
323
+ align-items: center;
324
+ justify-content: center;
325
+ gap: 0.6rem;
326
+ min-width: 220px;
327
+ text-decoration: none;
328
+ font-weight: 600;
329
+ font-size: 0.95rem;
330
+ padding: 0.7rem 1.4rem;
331
+ border-radius: 50px;
332
+ background: #3498db;
333
+ color: #fff;
334
+ transition: all 0.25s ease;
335
+ box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
336
+ }
337
+ .link:hover { background: #2980b9; transform: translateY(-2px); box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4); }
338
+ .link-ico { display: inline-flex; width: 1.4rem; height: 1.4rem; }
339
+ .link-ico img { width: 100%; height: 100%; display: block; object-fit: contain; }
340
+
341
+ /* Sección de compartir */
342
+ .share {
343
+ margin-top: 1.5rem;
344
+ padding-top: 1.25rem;
345
+ border-top: 1px solid rgba(255, 255, 255, 0.12);
346
+ }
347
+ .share-heading {
348
+ font-size: 0.95rem;
349
+ font-weight: 700;
350
+ margin: 0 0 0.9rem;
351
+ opacity: 0.92;
352
+ }
353
+ .share-list {
354
+ display: flex;
355
+ justify-content: center;
356
+ gap: 0.9rem;
357
+ flex-wrap: wrap;
358
+ }
359
+ .share-btn {
360
+ width: 46px;
361
+ height: 46px;
362
+ border-radius: 50%;
363
+ border: none;
364
+ cursor: pointer;
365
+ display: inline-flex;
366
+ align-items: center;
367
+ justify-content: center;
368
+ color: #fff;
369
+ text-decoration: none;
370
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
371
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
372
+ padding: 0;
373
+ }
374
+ .share-btn:hover { transform: translateY(-3px); box-shadow: 0 7px 18px rgba(0, 0, 0, 0.4); }
375
+ .share-btn svg { width: 50%; height: 50%; }
376
+ .share-copied {
377
+ margin: 0.85rem 0 0;
378
+ font-size: 0.85rem;
379
+ font-weight: 600;
380
+ color: #2ecc71;
381
+ height: 1.1rem;
382
+ opacity: 0;
383
+ transition: opacity 0.25s ease;
384
+ }
385
+ .share-copied.show { opacity: 1; }
386
+
387
+ /* Sección "Reporta un error": enlace discreto a los issues del repo */
388
+ .bug {
389
+ margin-top: 1.25rem;
390
+ padding-top: 1.1rem;
391
+ border-top: 1px solid rgba(255, 255, 255, 0.12);
392
+ }
393
+ .bug-link {
394
+ display: inline-flex;
395
+ align-items: center;
396
+ gap: 0.5rem;
397
+ color: rgba(255, 255, 255, 0.7);
398
+ text-decoration: none;
399
+ font-size: 0.9rem;
400
+ font-weight: 600;
401
+ transition: color 0.2s ease;
402
+ }
403
+ .bug-link:hover { color: #fff; }
404
+ .bug-ico {
405
+ display: inline-flex;
406
+ width: 1.15rem;
407
+ height: 1.15rem;
408
+ }
409
+ .bug-ico svg { width: 100%; height: 100%; display: block; }
410
+
411
+ /* Sección de comunidad/soporte: enlace a Discord (marca #5865F2) */
412
+ .discord {
413
+ margin-top: 1.25rem;
414
+ padding-top: 1.1rem;
415
+ border-top: 1px solid rgba(255, 255, 255, 0.12);
416
+ }
417
+ .discord-link {
418
+ display: inline-flex;
419
+ align-items: center;
420
+ justify-content: center;
421
+ gap: 0.6rem;
422
+ min-width: 220px;
423
+ text-decoration: none;
424
+ font-weight: 600;
425
+ font-size: 0.95rem;
426
+ padding: 0.7rem 1.4rem;
427
+ border-radius: 50px;
428
+ background: #5865f2;
429
+ color: #fff;
430
+ transition: all 0.25s ease;
431
+ box-shadow: 0 4px 15px rgba(88, 101, 242, 0.35);
432
+ }
433
+ .discord-link:hover {
434
+ background: #4752c4;
435
+ transform: translateY(-2px);
436
+ box-shadow: 0 6px 20px rgba(88, 101, 242, 0.45);
437
+ }
438
+ .discord-ico { display: inline-flex; width: 1.4rem; height: 1.4rem; }
439
+ .discord-ico svg { width: 100%; height: 100%; display: block; }
440
+ `
441
+
442
+ /* ─── Contador de aperturas (cross-app, vía store.dotrino.com) ───────────────
443
+ Como <dotrino-support> está montado en CUALQUIER app del ecosistema, al
444
+ cargar registra su propia apertura en el store COMPARTIDO; el hub dotrino.com
445
+ lee el agregado para su tab "Recientes". Así el conteo funciona incluso con
446
+ acceso directo al subdominio (no solo al abrir desde el hub). 100% local al
447
+ navegador: sin servidor, sin terceros, sin cookies.
448
+
449
+ - appId = atributo `app` o, por defecto, el hostname (p. ej. "mundial.dotrino.com").
450
+ - `no-count` desactiva el registro en esa instancia.
451
+ - Carga del store con import DINÁMICO: en apps con bundler (Vite, la norma)
452
+ resuelve el especificador del paquete; en apps que cargan support por CDN
453
+ (jsDelivr) el especificador desnudo no resuelve, así que cae al store por
454
+ jsDelivr (ver _loadStore). Si el store no está disponible (offline…) degrada
455
+ en silencio SIN romper la UI. Una sola vez por appId por carga de página. */
456
+ const _openRecorded = new Set()
457
+ // Fallback CDN para cargar el store cuando NO hay bundler (apps que cargan este
458
+ // componente por jsDelivr): el especificador desnudo no resuelve en el navegador,
459
+ // así que caemos al mismo paquete servido por jsDelivr. En apps con bundler el
460
+ // primer import resuelve y nunca se usa el fallback (@vite-ignore evita que el
461
+ // bundler intente analizar la URL).
462
+ const _STORE_CDN = 'https://cdn.jsdelivr.net/npm/@dotrino/store@0.4/src/index.js'
463
+ async function _loadStore() {
464
+ try { return await import('@dotrino/store') }
465
+ catch { return await import(/* @vite-ignore */ _STORE_CDN) }
466
+ }
467
+ function recordAppOpen(appId) {
468
+ if (!appId || _openRecorded.has(appId)) return
469
+ _openRecorded.add(appId)
470
+ _loadStore()
471
+ .then((mod) => mod.Store.connect())
472
+ .then((store) => store.recordOpen(appId))
473
+ .catch(() => { /* store no disponible (offline, bloqueado…): best-effort */ })
474
+ }
475
+
476
+ class DotrinoSupport extends HTMLElement {
477
+ static get observedAttributes() {
478
+ return ['href', 'links', 'cta', 'no-trigger', 'heading', 'message', 'lang', 'variant', 'inline', 'hint', 'coin', 'no-bubble', 'bubble-timeout', 'share-url', 'share-text', 'no-share', 'repo', 'bug-href', 'discord', 'app', 'no-count']
479
+ }
480
+
481
+ constructor() {
482
+ super()
483
+ this.attachShadow({ mode: 'open' })
484
+ this._onKeydown = this._onKeydown.bind(this)
485
+ this._onBlur = () => this._hideBubble()
486
+ this._onModalBlur = () => this.close()
487
+ this._bubbleTimers = []
488
+ this._bubbleAutoShown = false
489
+ this._bubbleVisible = false
490
+ }
491
+
492
+ connectedCallback() {
493
+ this._render()
494
+ // Registra la apertura de esta app en el store compartido (best-effort).
495
+ if (!this.hasAttribute('no-count')) {
496
+ recordAppOpen(this.getAttribute('app') || location.hostname)
497
+ }
498
+ }
499
+
500
+ disconnectedCallback() {
501
+ document.removeEventListener('keydown', this._onKeydown)
502
+ window.removeEventListener('blur', this._onBlur)
503
+ window.removeEventListener('blur', this._onModalBlur)
504
+ this._bubbleTimers.forEach(clearTimeout)
505
+ this._bubbleTimers = []
506
+ clearTimeout(this._hoverHideTimer)
507
+ clearTimeout(this._copiedTimer)
508
+ }
509
+
510
+ attributeChangedCallback() {
511
+ if (this.shadowRoot.childElementCount) this._render()
512
+ }
513
+
514
+ /* ---- idioma ---- */
515
+ get _lang() {
516
+ const attr = (this.getAttribute('lang') || 'auto').toLowerCase()
517
+ if (attr === 'es' || attr === 'en') return attr
518
+ const doc = (document.documentElement.lang || '').toLowerCase()
519
+ const nav = (navigator.language || '').toLowerCase()
520
+ return (doc || nav).startsWith('en') ? 'en' : 'es'
521
+ }
522
+
523
+ get _t() {
524
+ return I18N[this._lang]
525
+ }
526
+
527
+ /* ---- enlaces ---- */
528
+ get _links() {
529
+ const raw = this.getAttribute('links')
530
+ if (raw) {
531
+ try {
532
+ const arr = JSON.parse(raw)
533
+ if (Array.isArray(arr)) {
534
+ return arr
535
+ .filter((x) => x && x.href)
536
+ .map((x) => ({ href: String(x.href), label: x.label ? String(x.label) : this._labelFor(x.href) }))
537
+ }
538
+ } catch {
539
+ /* atributo links inválido: se ignora */
540
+ }
541
+ }
542
+ const href = this.getAttribute('href')
543
+ return href ? [{ href, label: this._labelFor(href) }] : []
544
+ }
545
+
546
+ _labelFor(href) {
547
+ try {
548
+ const host = new URL(href).hostname.replace(/^www\./, '')
549
+ if (host.includes('ko-fi')) return this._t.defaultLink
550
+ if (host.includes('paypal')) return 'PayPal'
551
+ if (host.includes('patreon')) return 'Patreon'
552
+ if (host.includes('github')) return 'GitHub Sponsors'
553
+ return this._t.defaultLink
554
+ } catch {
555
+ return this._t.defaultLink
556
+ }
557
+ }
558
+
559
+ /* ---- compartir ---- */
560
+ // URL a compartir: por defecto la URL completa de la página actual (incluye el
561
+ // #fragment con el contenido del usuario, que nunca llega al servidor).
562
+ get _shareUrl() {
563
+ const attr = this.getAttribute('share-url')
564
+ if (attr) return attr
565
+ try {
566
+ return window.location.href
567
+ } catch {
568
+ return ''
569
+ }
570
+ }
571
+
572
+ /* ---- reportar error ---- */
573
+ // URL a los issues del repositorio correspondiente. Se configura con:
574
+ // bug-href="https://github.com/usuario/repo/issues" (URL completa), o
575
+ // repo="usuario/repo" (atajo GitHub) o repo="https://github.com/usuario/repo".
576
+ get _bugHref() {
577
+ const explicit = this.getAttribute('bug-href')
578
+ if (explicit) return explicit.trim()
579
+ const repo = (this.getAttribute('repo') || '').trim()
580
+ if (!repo) return ''
581
+ if (/^https?:\/\//i.test(repo)) return repo.replace(/\/+$/, '') + '/issues'
582
+ return `https://github.com/${repo.replace(/^\/+|\/+$/g, '')}/issues`
583
+ }
584
+
585
+ /* ---- comunidad ---- */
586
+ // Enlace a Discord (u otra comunidad). Se configura con discord="https://discord.gg/...".
587
+ get _discordHref() {
588
+ return (this.getAttribute('discord') || '').trim()
589
+ }
590
+
591
+ get _shareText() {
592
+ return this.getAttribute('share-text') || this._t.shareText
593
+ }
594
+
595
+ // Construye los enlaces de compartir para cada red. Instagram no admite
596
+ // compartir una URL por web, así que su acción copia el enlace al portapapeles.
597
+ _shareTargets() {
598
+ const url = this._shareUrl
599
+ const u = encodeURIComponent(url)
600
+ const text = encodeURIComponent(this._shareText)
601
+ return [
602
+ { key: 'whatsapp', href: `https://wa.me/?text=${text}%20${u}` },
603
+ { key: 'x', href: `https://twitter.com/intent/tweet?url=${u}&text=${text}` },
604
+ { key: 'facebook', href: `https://www.facebook.com/sharer/sharer.php?u=${u}` },
605
+ { key: 'instagram', href: null }, // copia al portapapeles
606
+ ]
607
+ }
608
+
609
+ _copyShareUrl() {
610
+ const url = this._shareUrl
611
+ const done = () => this._flashCopied()
612
+ try {
613
+ if (navigator.clipboard && navigator.clipboard.writeText) {
614
+ navigator.clipboard.writeText(url).then(done, () => this._copyFallback(url, done))
615
+ } else {
616
+ this._copyFallback(url, done)
617
+ }
618
+ } catch {
619
+ this._copyFallback(url, done)
620
+ }
621
+ }
622
+
623
+ _copyFallback(text, done) {
624
+ try {
625
+ const ta = document.createElement('textarea')
626
+ ta.value = text
627
+ ta.style.position = 'fixed'
628
+ ta.style.opacity = '0'
629
+ document.body.appendChild(ta)
630
+ ta.select()
631
+ document.execCommand('copy')
632
+ document.body.removeChild(ta)
633
+ done()
634
+ } catch {
635
+ /* sin portapapeles disponible */
636
+ }
637
+ }
638
+
639
+ _flashCopied() {
640
+ const el = this.shadowRoot.querySelector('.share-copied')
641
+ if (!el) return
642
+ el.classList.add('show')
643
+ clearTimeout(this._copiedTimer)
644
+ this._copiedTimer = setTimeout(() => el.classList.remove('show'), 2000)
645
+ }
646
+
647
+ /* ---- API pública ---- */
648
+ open() {
649
+ const overlay = this.shadowRoot.querySelector('.overlay')
650
+ if (!overlay) return
651
+ this._bubbleTimers.forEach(clearTimeout)
652
+ this._bubbleTimers = []
653
+ this._hideBubble()
654
+ if (typeof overlay.showModal === 'function') {
655
+ if (!overlay.open) overlay.showModal()
656
+ } else {
657
+ overlay.setAttribute('open', '') // fallback sin <dialog>
658
+ }
659
+ document.addEventListener('keydown', this._onKeydown)
660
+ window.addEventListener('blur', this._onModalBlur)
661
+ this.dispatchEvent(new CustomEvent('cc-support-open', { bubbles: true, composed: true }))
662
+ }
663
+
664
+ close() {
665
+ const overlay = this.shadowRoot.querySelector('.overlay')
666
+ if (!overlay) return
667
+ document.removeEventListener('keydown', this._onKeydown)
668
+ window.removeEventListener('blur', this._onModalBlur)
669
+ if (typeof overlay.close === 'function') {
670
+ if (overlay.open) overlay.close() // dispara 'close' -> emite cc-support-close
671
+ } else {
672
+ overlay.removeAttribute('open')
673
+ this.dispatchEvent(new CustomEvent('cc-support-close', { bubbles: true, composed: true }))
674
+ }
675
+ }
676
+
677
+ _onKeydown(e) {
678
+ if (e.key === 'Escape') this.close()
679
+ }
680
+
681
+ /* ---- render ---- */
682
+ _render() {
683
+ const t = this._t
684
+ const heading = this.getAttribute('heading') || t.heading
685
+ const message = this.getAttribute('message') || t.message
686
+ const cta = this.getAttribute('cta') || t.cta
687
+ const variant = this.getAttribute('variant') === 'ghost' ? 'ghost' : 'solid'
688
+ const hint = this.getAttribute('hint') || t.hint
689
+ const coinSrc = this.getAttribute('coin') || COIN_DATA_URI
690
+ const inline = this.hasAttribute('inline')
691
+ const hasTrigger = !this.hasAttribute('no-trigger')
692
+ const links = this._links
693
+ const bugHref = this._bugHref
694
+ const discordHref = this._discordHref
695
+
696
+ // Trigger por defecto: moneda flotante arriba a la derecha (hint "Apoya el Proyecto").
697
+ // Con `inline` se usa un botón de texto (cta/variant) en el flujo del documento.
698
+ // La burbuja solo acompaña a la moneda flotante (no al botón inline).
699
+ const wantsBubble = hasTrigger && !inline && !this.hasAttribute('no-bubble')
700
+
701
+ // Caras del flipper: moneda (front) + compartir, y bug si está configurado.
702
+ // Con 3 caras se reparten a 120°; con 2, la cara de compartir va a 180°.
703
+ const iconFaces = bugHref
704
+ ? `<span class="face icon" style="transform: rotateY(120deg)">${SHARE_ICONS.share}</span>` +
705
+ `<span class="face icon" style="transform: rotateY(240deg)">${BUG_ICON}</span>`
706
+ : `<span class="face icon" style="transform: rotateY(180deg)">${SHARE_ICONS.share}</span>`
707
+ const flipperClass = bugHref ? 'flipper three' : 'flipper'
708
+
709
+ const triggerHtml = !hasTrigger
710
+ ? ''
711
+ : inline
712
+ ? `<button type="button" class="trigger ${variant}" part="trigger" title="${escapeAttr(hint)}" aria-label="${escapeAttr(hint)}">${escapeHtml(cta)}</button>`
713
+ : `<button type="button" class="trigger coin" part="trigger" aria-label="${escapeAttr(hint)}"><span class="${flipperClass}"><span class="face front"><img src="${escapeAttr(coinSrc)}" alt="" /></span>${iconFaces}</span></button>`
714
+
715
+ const bubbleHtml = wantsBubble
716
+ ? `<div class="bubble" part="bubble" role="status">${escapeHtml(hint)}</div>`
717
+ : ''
718
+
719
+ const linksHtml = links
720
+ .map(
721
+ (l) =>
722
+ `<a class="link" href="${escapeAttr(l.href)}" target="_blank" rel="noopener noreferrer"><span class="link-ico"><img src="${escapeAttr(
723
+ coinSrc,
724
+ )}" alt="" /></span>${escapeHtml(l.label)}</a>`,
725
+ )
726
+ .join('')
727
+
728
+ const wantsShare = !this.hasAttribute('no-share')
729
+ const shareHtml = wantsShare
730
+ ? `<div class="share" part="share">
731
+ <p class="share-heading">${escapeHtml(t.shareHeading)}</p>
732
+ <div class="share-list">${this._shareTargets()
733
+ .map((s) =>
734
+ s.href
735
+ ? `<a class="share-btn" part="share-btn" href="${escapeAttr(s.href)}" target="_blank" rel="noopener noreferrer" style="background:${SHARE_COLORS[s.key]}" aria-label="${escapeAttr(t.share[s.key])}" title="${escapeAttr(t.share[s.key])}">${SHARE_ICONS[s.key]}</a>`
736
+ : `<button type="button" class="share-btn" part="share-btn" data-share="${s.key}" style="background:${SHARE_COLORS[s.key]}" aria-label="${escapeAttr(t.share[s.key])}" title="${escapeAttr(t.share[s.key])}">${SHARE_ICONS[s.key]}</button>`,
737
+ )
738
+ .join('')}</div>
739
+ <p class="share-copied" part="share-copied" role="status">${escapeHtml(t.copied)}</p>
740
+ </div>`
741
+ : ''
742
+
743
+ const discordHtml = discordHref
744
+ ? `<div class="discord" part="discord">
745
+ <a class="discord-link" part="discord-link" href="${escapeAttr(discordHref)}" target="_blank" rel="noopener noreferrer">
746
+ <span class="discord-ico">${DISCORD_ICON}</span>${escapeHtml(t.discord)}
747
+ </a>
748
+ </div>`
749
+ : ''
750
+
751
+ const bugHtml = bugHref
752
+ ? `<div class="bug" part="bug">
753
+ <a class="bug-link" part="bug-link" href="${escapeAttr(bugHref)}" target="_blank" rel="noopener noreferrer">
754
+ <span class="bug-ico">${BUG_ICON}</span>${escapeHtml(t.reportBug)}
755
+ </a>
756
+ </div>`
757
+ : ''
758
+
759
+ this.shadowRoot.innerHTML = `
760
+ <style>${STYLE}</style>
761
+ ${triggerHtml}
762
+ ${bubbleHtml}
763
+ <dialog class="overlay" part="overlay" aria-label="${escapeAttr(heading)}">
764
+ <div class="modal" part="modal">
765
+ <button type="button" class="close" aria-label="${escapeAttr(t.close)}">&times;</button>
766
+ <h2 class="heading">${escapeHtml(heading)}</h2>
767
+ <p class="message">${escapeHtml(message)}</p>
768
+ <div class="links">${linksHtml}</div>
769
+ ${shareHtml}
770
+ ${discordHtml}
771
+ ${bugHtml}
772
+ </div>
773
+ </dialog>
774
+ `
775
+
776
+ const trigger = this.shadowRoot.querySelector('.trigger')
777
+ if (trigger) trigger.addEventListener('click', () => this.open())
778
+
779
+ const overlay = this.shadowRoot.querySelector('.overlay')
780
+ // Clic en el backdrop (el propio <dialog>, fuera del .modal) cierra.
781
+ overlay.addEventListener('click', (e) => {
782
+ if (e.target === overlay) this.close()
783
+ })
784
+ overlay.addEventListener('close', () =>
785
+ this.dispatchEvent(new CustomEvent('cc-support-close', { bubbles: true, composed: true })),
786
+ )
787
+ this.shadowRoot.querySelector('.close').addEventListener('click', () => this.close())
788
+
789
+ // Compartir: el botón sin href (Instagram) copia el enlace al portapapeles.
790
+ this.shadowRoot.querySelectorAll('.share-btn[data-share]').forEach((btn) => {
791
+ btn.addEventListener('click', () => this._copyShareUrl())
792
+ })
793
+
794
+ // Burbuja "Apoya al proyecto": aparece sola al cargar (una vez por carga),
795
+ // y también con hover sobre la moneda. Clic en la burbuja abre el modal.
796
+ const bubble = this.shadowRoot.querySelector('.bubble')
797
+ if (bubble) {
798
+ bubble.addEventListener('click', () => this.open())
799
+
800
+ // Hover: muestra la burbuja en lugar del tooltip nativo.
801
+ const enter = () => {
802
+ clearTimeout(this._hoverHideTimer)
803
+ this._bubbleTimers.forEach(clearTimeout)
804
+ this._bubbleTimers = []
805
+ this._showBubble()
806
+ }
807
+ const leave = () => {
808
+ clearTimeout(this._hoverHideTimer)
809
+ this._hoverHideTimer = setTimeout(() => this._hideBubble(), 250)
810
+ }
811
+ if (trigger) {
812
+ trigger.addEventListener('mouseenter', enter)
813
+ trigger.addEventListener('mouseleave', leave)
814
+ }
815
+ bubble.addEventListener('mouseenter', enter)
816
+ bubble.addEventListener('mouseleave', leave)
817
+
818
+ if (!this._bubbleAutoShown) {
819
+ this._bubbleAutoShown = true
820
+ const timeout = parseInt(this.getAttribute('bubble-timeout') || '', 10)
821
+ const hideAfter = Number.isFinite(timeout) ? timeout : 6000
822
+ this._bubbleTimers.push(
823
+ setTimeout(() => this._showBubble(), 700),
824
+ setTimeout(() => this._hideBubble(), 700 + hideAfter),
825
+ )
826
+ } else if (this._bubbleVisible) {
827
+ bubble.classList.add('show')
828
+ }
829
+ }
830
+ }
831
+
832
+ _showBubble() {
833
+ this._bubbleVisible = true
834
+ const bubble = this.shadowRoot.querySelector('.bubble')
835
+ if (bubble) bubble.classList.add('show')
836
+ // Se va si la ventana/página pierde el foco (blur).
837
+ window.addEventListener('blur', this._onBlur)
838
+ }
839
+
840
+ _hideBubble() {
841
+ this._bubbleVisible = false
842
+ window.removeEventListener('blur', this._onBlur)
843
+ const bubble = this.shadowRoot.querySelector('.bubble')
844
+ if (bubble) bubble.classList.remove('show')
845
+ }
846
+ }
847
+
848
+ function escapeHtml(s) {
849
+ return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
850
+ }
851
+ function escapeAttr(s) {
852
+ return escapeHtml(s).replace(/"/g, '&quot;')
853
+ }
854
+
855
+ if (typeof customElements !== 'undefined' && !customElements.get('dotrino-support')) {
856
+ customElements.define('dotrino-support', DotrinoSupport)
857
+ }
858
+
859
+ export { DotrinoSupport }
860
+ export default DotrinoSupport
Binary file
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 270.93332 270.93332">
2
+ <g transform="translate(28.947702,-41.883531)">
3
+ <g transform="matrix(0.67188176,1.0488761,-1.0422716,0.67466777,217.54163,-47.421796)">
4
+ <path fill="#ff3310" d="m 187.73621,172.15946 -73.61353,-47.36097 0.17119,2.24896 c 0.0942,1.23693 0.4026,4.21349 0.68542,6.61458 0.28281,2.40109 0.76072,6.68734 1.06202,9.525 0.72258,6.80541 2.10929,19.36133 2.1834,19.76942 2.74744,3.18095 19.34208,9.09542 19.34208,9.09542 0,0 -19.16069,7.52806 -19.41581,9.60752 0,0.41996 -0.35443,3.93103 -0.78762,7.80237 -0.77006,6.88189 -2.75747,25.31726 -3.06921,28.47006 l -0.15697,1.5875 z"/>
5
+ <path fill="#ff3310" d="m 18.872668,172.15946 73.613488,-47.36097 -0.17119,2.24896 c -0.0942,1.23693 -0.4026,4.21349 -0.68542,6.61458 -0.28281,2.40109 -0.76072,6.68734 -1.06202,9.525 -0.72258,6.80541 -2.109287,19.36133 -2.183397,19.76942 -2.74744,3.18095 -19.342071,9.09542 -19.342071,9.09542 0,0 19.160681,7.52806 19.415801,9.60752 0,0.41996 0.35443,3.93103 0.78762,7.80237 0.770057,6.88189 2.757467,25.31726 3.069207,28.47006 l 0.15697,1.5875 z"/>
6
+ </g>
7
+ </g>
8
+ </svg>