@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 +21 -0
- package/README.md +131 -0
- package/package.json +38 -0
- package/src/coin.js +19 -0
- package/src/coin.svg +23 -0
- package/src/index.d.ts +42 -0
- package/src/index.js +860 -0
- package/src/monedabg.png +0 -0
- package/src/monedalogo.svg +8 -0
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 = 'iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAABWVBMVEX////NqXFcPx/jw4i2kFyLXzFBKRF2VCl/XC+zkFTlzZXq1aPq1KGPcUVwUSzOr3bbvoPp16navYLkz5/lzZXbv4LDpnBRNhm0klnPsXbJpmnZvodAKBGecjiIZzlqTihNMxa+oGmIZDKujVdvUCmde0VZPBt3VSqXbzZ5WS6cekZqSyNUOBlFLBLt2KXs3LH457f77cX98M3+89D14avy3aPcw4Xcu3O8llO0ikSsg0GrgT6kejrKp2Xw2ZumayaVZyqVWBmKUhnnyoXt1ZWjZB6pciuobiucYR7r0I3EnFK8kkqzfDTLpFiUXSGJShDiw3qEShSbczbewHmlfkGTbDJ4RBXTrWLZtWiIYiyDXSnSqlyMZjFwOxFxQxbrw2bgvnHhvGzktljxz3bwyWjgr0/w1YfcsFrCl0zZpkrSnETKkTrDijjBjUK2eCu5hDm+gS57VyjswV6p87gEAAAALnRSTlMABRYmAwFRUXC7sMzbVixtjuqg9P7N5pT0+ff7beDRxrL279fh+NLz9Z+i/e/R3aH4oQAAIthJREFUeNrVnflD20bX74NLeRKaS9Jbt4SEpyF907R9kxKb2mixjOUlJkgWtY2N5KUFkwRsAjj0///hnlkkzUgzspMmbe5Ji8FmmY+/Z5vRSLpx49PZQiqV+iKwVGphkXnxyy9vfPa2mPpi6T83by0vf3Ub2VfY4HF5+dbN/7O0klogJMGHz9EWUkt37i5/9fXm80wW2dbWFnzI+pbJZDa/vv1/l28CzuLnqcuXSIilu8u3vwGErd+2tn4DyzKW8Y3ifAU0qcXPT4rU0s3l25sZrAE2HoMByWRyOfzxefr28s2Vhc8qJpbufvsNEgLMB2EcKg6Sy+XzefSw+fXy3aXU56LF3e/Smexv1CiGP3gY8SY2MnAfJI9B8IfNb779DFgWV1bvfZPxESgHEGymt5W1+6qm6QUDW0HXNVUtFte20zuUAdvODny5s/3tzS8W/1Uxbt1/Th0KIcD/2Vz6QVHVjZJplsvlSqVS5sw0TQOIikq1tkMwasTSt2/9a7KsrN56kNkK1chmn2+vqYBAx//iRQmsTm0XfVF68ZLyGJq6Vk0TkD0wQHmwfCf1r2BQnyIo2UwaILAM5fLLUt2gnrSmPECmKGvFoqXamt6o10sl0ApoDM1RqnsEZG8vDSj3KMqX/yhGlsY2fMiki7q5X/m9Ui7tGrqKImEzn4vmKXj796pApGoGCPSCKlNsVvd8qyoY5R8rkwhjK8hRoIVWKiMKNKwHtXxGZDmadDFNU0FxVC+9BCc0DXAygoFsrfCPOVhq/b9EDRQZ2byCKACipKtKLZeRVo8g7aK42MMwmrFrvkQ/q4OPBbZ2a/2fQFlc+v5BUDSym0Vjv9WqvKxra+l8BEJYCDmWKhIGdClXzDZyMWrb9x4uffJkvLJx73mAkb5fLwOGqRfTuazEMlISjIKczNh9WUG/xTkIRNFXVz65V23R0pdNq7tlCIy2+oBo4Zd0pjfJSDjym37ehVEfKI7eNiutlmkQD2s2m8qjH9Y/YRe28vB+lmYqwDAhvs3OWi2TDVtFpj+JhUgchJI0D5SibaDyYxrFA8RxcNBU7m18KlEW1v8nTbuQ7Ob9buWwYmrKZpSC5RDoQXuTTVrRabJqKooLVaiM3fQAQBSl2Sx+IlFWNh5lcP2DhLtmVA5b+/qDfIxCysGBbEZAQAWKAqroroKtqXQ2Pn76Wlz64cGWH+MaZCrAyAkwOMdiWXIsiN9jheUDxYWHUEDoru0RFEV9/LHTV2odJyvUUW3eL1V+3zfW8kIMGQjnWEGvGIJgEoTSrrQOK0YRc3jKx3av1MYjGuUZxSi3yrvFzZkYvHOxHPmYHgQEkXiObqJcDKJ4yNwfVz+ie608pm6V3VRNSJNqOjMHR0Y0LQwzVi0dAzlAKriWsQ8ohuMR0598tOy19EOaNLnZbQjyiqHIMLglE1nm5TIWD4I0ARLiXz3bJSQQKB8nzNd/pLU8U9yFIFd3ZBhcLZSm3k2RY1GOAxwZWBQcKci3XE/9af0jhPzC+j0y7djaVPdB8bXMfByymp4PSiHLEQpCSBy72zpstS0Xm9X7+ySp1UdZmnQ7lda+lpZ61RZdjkvsFpmSHteDCIJAgMQy+oeHpk1InJ9X/07y+tLnQANU6hDlxZwUA3dZW+JIj+lRE8hxQDGIJECi7x8e9vXBxyABjvtbtJZDtjIeZOQc84HshILIObAkbtHRhod/QPZCHI71899Jw6mNNaJHpmi2KnroVhgunncT+96IIFVRnPsYCMQdOGr78M+KYQ0c5++RpFaBA40yb+1DtgprILuiKKyDApC8BKQp0oOQOI6Kerq25VCSD/SuhdVHeO6xtWmXufCIYQhIhPP1PNu8S9wq5HDR6DsQ8l3VJ/mg3LW4StuSTa3SMteY8BBxJJMgljBERGWQoyAchERHJDYmUYcfkoUX1+9lcdMe4xAFusC5BEhAQ2bre1zWjVIEIEUA8UksMPuH9Q/oS6AObvl+NStdxUHQAnB+Zw/GeoQMhlut1vL5HMDs1HiQYxmHix1KhTTc6qoIxNLev1tZ+h/ctm/lMUc2SQ+BCLmd6tHoBOzo6BW11+SJURPxoLUtH+RYhoEEQZKEJKqqP37PDnLlh03MkVMr78UBk/lM7ejkzcnRq9OdXGxVa+f0Fbz4ZlTdQWqxIHEMIghyKUJiAwciea8knHr4DWkTrcqhqWST4yOk2Mpm0vC+H73ayYWTKqYW7uDHWvXo5OysuYdWHIHjWMZR9EHGNiJpIw7Vfq/p7+LqGp4OZov7h/sz4nwroNjK7I3enDR3MtnozJCvIDUgOj06m0yUKkI5jiUrghFwgCQ2RHzFICTt90jC64+ypL+Cel6cwUFr+m9buebkzVEtIz7Mxja9KDpAjubZ+fmgiVdQpBiEA0BsqIx9HTBsW/th7oBf+W8OxUc2vduqqLM4aNeba745eZUT972iko6yWHU0eWspaNUhykHCw+ewEIDROtzXEIg9d8CnHqZJITRaFTuXlHe3/BcBY1TLSGp6XlDTSeKtVr3zi0u32RRzBBgIRGu3oKvHpv8yV6/y5eoart05rdXqbGaTqyD68FumOTnxMaS9yQ7X9NI+8aDZdC+uLr2mEoAMfDV8Dgpi69B/tzGI1pmrLi7dy5KGFxJWOpudGRzZvbNJNSNvsvLMojXHQerHgeJMr8Z4qo45HMeJ6YE9SgOSikFI/juHc6VubeJAf2AeVtbm4MiNJs1M0gxXIAg3rfUUZXzVcIgkMQ5fDxi9BhP5vo4/0+ZwrtUHeJibxmErOdBJI1KdjPKJhxFkjnXAFHSl2ChprhcLciIH4YDh6+1Kq6tpCERfn+1YGXw4DSq6sTkrYf2W80AOds+MLGXxTS8nCIpzz9VKdcfzBbGibkVBsHNp2GY5V+p/a34FMauyRbjg487kbCeiRlbKURMECJN3rXrJpm1izKtsDfuTjkj2O+hR135JLovr+LDaVs1IqIR099LWFrhVZitpCiIWRMgBDI1Sw3EFUY4gEAcCgDAxdWKJZTG1jHt35Fi6cJGazsuRW2VGk2ryVEqcsiQcA9fVzJLlOgKvIgGOlTAqFQNzaP+b1HOtoiM5OGMJMy/THv6WOZvUEuSQzm+jjsWUQCAx7YHDJ92AgwphVqgk9yTxjg7Sr3yHS0geMtb9pGVqeHh+dra5lTS5TUq9zEyK63QdG0jwFITTQ9NCDt0oY0k6un4rJVuNW/xlEw8VSqERXeDN8tVjc3L2nF/8yc4nSMSzeA4gKe1rjsW7FcdRAEn2gaNjGOodabN4Hw+3tgulMCJIMH/FnwOHl4to8d6C8IsMNO2CJmWddInR6KAgBcOstHW0c0q/J0nBi7eIIPch0sOjauFhZ4KC17mS01X8iHotkYPrETFJxK90xoyCUS4bmERdldTC23iU6d3fze0ss0c0EulQBidnuVmLP+/hWJFqTkgYr+JAOoVCAcV7giQgCF77sCpoEpJl10bY5R7IV4Oz54kYmVxO0r4nOFZYBTVzX1cZDhajgzbjEUn0jlEUSrJyG7sLCFJKswfPY+s87mQzOy+IrBaGGIKuRN83NSGHQbcV7pZLVBJR4rqLh4cEsTK+W2VjINC9TGrSrkTMUWObrIhjDeI9u6oaZVOLZCush+Hby3IBkbSLgsT1BRIET29RLcwGJTzy5v9WPa9uJYV5hCMqyHHEsQRzQTCjvBsL804nACnUy3UMoi/HJbmJF9vZCBHZ1ua5IjlsGwfZ2ZFEyCwOmNeWDc2OcoSKFErgW22wR7GOK/UtHn6t/rtZlYNsZSYTZmouBJktiCTO2TKom6ZuSzmQJA0dgWi3ok3wEvGnYhmtN0g5ssp5jXUpSYcV5diTRDq35MOXc93cZQXhOZAkuxqW5LtIBl5cxhk1r1dMJUGQ2nmVbs1IKIMMR1SQOTj8fGWYhijO0eCxJC8wSFe9yTdakHvR2JRSRc/LQTITL0PmgVmRGrGNM8kcA4ZjTDm0sNfdNRk1OgwHIXmBfaurf5uKhDoenVV+WUwIdQVXECHDPBzH4ZHOaOKNdolo/KW6L4bP0fYNSki9RHyryIX7wjIOjHSj3NiTg9TOj2UpV8ZRi5ZCcaBHu3ZS/kp+0uX1wCSFxosplkS9y1X1r7EgxdJLKycFyQwmz7OZmRw5Jj5q8kAfRJZEbXZ2TkjqdVzNO3pMESCZlq60uG8Rz8pr5bqSyQiO0GLbg0iXulWcQ5ixhAHCuRVbB+sF0paIQPRGPe5bNGdt18vaDlsfQkfCgpxlMjPlyPF+xXBwJUTKwfUljbrO5lwWBEkyLYAi3Ud3ec+Cf8WXJYc7ysQEBAgySc9Ug9cj3iyKHCs2qQ04OjoP0uZA7KuGBhycb93ZRCB5G3tWWCWC+o2/Phtl5g4P2SxEyGHLZrWFzrShizEARJte2Qike38l9CwEkkk3Xmq1iEOFIHvQ9M7rVtF9Af6WMoFjyf0KSAytoUk42u2CfXVZQCDFoCambj9HA1orlayc5D2XCpITYYg5IiV9JgeA6NNpRwYCvjXVEMi95cWgz0Ljz1mluidznmxtUk2kEHEwjnUsyVjs0m6Mo2AYNhQLGYh20cC+1fnuiy/95ItAavaLaVUaBt4kP8upEjiabIAwESJbvArmg9qU+lY7TqJfXo0NJMnaUhgiYNvTkl2ThvPkaCYHgyHh8GIcqmyxhIJ07GmnLQExLq8udTZIUl9hkGK9XszLPAtCnTmpE32SEasR2yAnLiG0U5TL4Te89lST+pZ9cWGzQbLy9SYJkYYC4xSHuzfKMSC5WHBwHHszORza8SZwUBD90pZGu/b2ncpWkv9s5nGIlLSqDCTPh3qUIu5WrF+Jml5OEE0IQlHGFzru26MYXehSIEgQSJtWkpsAkstUp3W7RgbHBwL6UJ3syCHiXrVXje3+ETTvYzWBo+Cv/mhvNdy3t+MGkGy0Ly7voFEpjStrJycy5FlnzzOx50UYiOOU31Z2LGkWx2NV5FeFQoE+GL5vtSUGIDjahyTaU1/h8RfrDTefE5PkJ8e5jJAiwAjlOBXrQTkGQkFYiALJWLo/DbHeGr4iRgzkLYr2obqMF7S+RiB5pz5V8nmxIlANRRARCiLHKb8nOdwzE/EsKojGF8ICpkGrbwU/xq23JEhEaev8YkyifQGm60tpFBo1qz5tkuHFSZqTWgAhxdgDjtPo3uqZgogKOnYrH8SwLzUZiHZ+YeFo/w6dTnqntgODq9r1y+pOnrWQxDvbYYVgIRgxGI5mjMNjOJxohERihASJ3/fq1mVb0gDrzoVjBA3wzR0YZX57ejXe40ECnvxgxD8VpRCeQSFYV5SChJkqBEG6IFk61qVsSlJwLiw9SFu30jUYmzK9smr+JrcISW3SnCmGiONYzGHJclbB5yiEhqL9nJ8nBjgoEVhaF2rK2h2UfdMAkFca75yaP8JNnmRvUuUJGC2oT8VOPOD3+EX1iMQ6X0GiIOq5JgTBGe0cJWfcbaW+TdeAwL26cNG4dnjDINXJXl7EEOTb2Eb3yJ5LyiERhCuCOktBQOxz2zBiKPhhfH6u4jWhW6iMVNGIihQkMGbkymBPRBF1KS46/E2wSpSDFsOxLQSJeReUFM2x24bY1PNzC4NA20hBnKsLryawHfjnIZDoC3t8nuIpjmMcboRjzEaIFulNiIPpJOohN40lHIY9eWvhRfnlBZjnVvegBDhXlwoZWwxl78zln9yrxSBYjGBrNTeZ4gMkAqIJQArBEwkgzlsHz7Gg//0CgexVQ5AYzt7A24t4E+9QHESgBrd31OdwKMaYm9+Ket+QpOOcJyjioNDXoSKCIsgsBFKt7sWtVnW8ao1/bi4tlHigB44VAxG18T5SPP9Sa9gDAgLTdgBpnoKXIJBgaDxIE0AkDCyEHIPnEILoSYZAcANZCHpiwlEAkAE53oNB0ICsq7HCjzEYfNPyqqenUYQoRuhSzAZxN+ZXlGNsx0l8HBYLvwQgbG3xL34DXAikEIAo1aYQxOdRAKQ6i0IJyl+UY8DrwQrC+xYMWdPZL/xPrHOtwFQYnQpTiIGgrZIIpNkUkiiWO4si3DEaO3tiEI8PKYjYEIggqRUoCD68G4AoBITuj+bNO3eb4qDgAsMTnAHCYLB6jKPL1snGgjAY8KhhEDAM4lEQjx9hCDL2QWIQx0kQBEPEMY6uW0cseJZ8BwKJpQTcA9hnb12Ngnxx20NXJaAgBwfNg+hwAURpypWIxbYIg41zjiNAsTkQuiUzABFmN/gGANFZEIWA0IEdgCGoAGQQAQkRaBciwXAi+6rFIOygw12MzGsAEhWOJjT77BwrohPX8jDIpce8yRQHEwGI55/2eHwcOWNQiDEYcGrwcohJEkyzxlrcBynIhIBAi5L6Fo8HWhRX8TxFYN7YiTBGGEQQUjUQxvuB2AQkUI6hGb8BEIh7DUAWlvFYnKux648rMmjXstwYRUJYMAzMRqz3FiT8bgaESxC2Nj4jIDZ0vwgEEJx3lwOPN3/YCIR+HfkON8mfOAyOYzwngf/d55cSDTWLgqD5yOLyGowCgTh4u33MQJFxMf60PEFFfWp+iLHQVArCHW0k5iAQvdBAM8Qbt5QBApm+dYLRRYZsXTqu1JvEGEIhpBz0+dg304MP5/5LMZLB2ZuBhvpjdIj6poJOfR9cAAgzuGDU8KR1gX3LjSMIHcqyZBjs2BNFsBgbW5e28LeMx5eDyZmDLqGIF3/vEJDxBTp/YzAQDBZAHNeVUswRFFEQiQIxCPQbYbwQ7nEHRQ/eZIRAjOJ/0KHQNcuB4YzhbfdTJ08zsKZjSySDPLQtAUDEr4LBiCi4XwogjuTNARALkldhbQnt7L8PPzcYwNvuD4xHgZemlyp9Cj+IGBIgxnMGNqcD84svL8eS3255E2WMTiPDS6ap79AJ/APn3djhBhe+7ZY2VZkv4+kpwPjgJCtmwMOwLmxL8ncgaSm4rKBF7BsL35Kfn146oiGin9aubOx+wtfjb5YoUSaAyCHwGzye2sJ8CP8N3px5uJ4sL+AjVkUV/ZbpW3q5C0sEokoYY4q/X9UWhwXrFI42tWSh6E5OXMSBy8iNG3fXbPSLLi/Gg/CbuB+0G/y7wgvBUKjzaCHgEBH4fj2dijwF/QiEyGiACzw50P6fNRtJMoYg4YZH/gA+gcBoED+1ROYjzMUwB8eAM/tKk/i046Dsi4Kd7j1buQ9ODb/OB1HV6FBV/UqzZRBAMJYwMLOkpPCQQiCCRt2WphmIdRiBrcFshGzCVjHJxVuQZUz32as8SKOhjyNwjMUJfA6/8RaTRMQYxM2z6o3Y88H3n50peD2c7pBfXFY1NPJLKOB4N4KKOzRKgx+1xpVuq+M5EKLzcG72HQFJ4KAVy/Xs0lTAN0Df7bgTz8M+cYue7nazCKOENuBi7IROwg1XM+oNXVV5BCGFJl0amQHCUQQN36BRGnvuQGgoRFx0RYjgrLGlIjonWUVBIsmeAFLXCZx8RjpraScKwnJEKfwJhbXbGKB+O6TjQA5QVNvBGUqpe/jvWFBJLNHsH+25N0qGJh1/uF83Pq0WkISBLlEjnDE0Sna07Q443LMTBReH8LyeWypaWrHsd2OLhmh0tLqxW+8kBoMWX+fAKx10vUMIIuRgp0KWWXJk6wIgyJGHL2UT7peFIIG/po6vKAizTuYP16iXDD1YMkscfdx4EhmHYFJnlBvReSm59NbgDDLapDnAIHeCU0JX7qO/ZluNCwgdyRCN0q6R8P6LIApREiGI0KeoIOWy5XmiabZ3NnDP3hzgazwxJ/GlvsUeYE0vxgkgpXZhbh2CQ5kCRWzCEYnzOAYIUql7EjtzvLMjBeVh+xZz2YS7KvqbxLfCgdHje/RM8q5ptnU9GswSgvBgRlwRW+RYAo6RUy6PpSCD0aSJjyBp7MmIS4/Q6TPYt9CZTeF7yYywbe53DenhsQSLkrCONUjQAwQxZBzewD05OUAglr7CnZDfQWNUp+/GYy3iF8Fu4u4+kiS6SWEGBf1FM0BEHCO1UnZGUhDIWQoCUW9xF+T4Be3l1O3x1aWlRf3C38/WNsumwR03LsxnWBLdBxmzIRLEeZzDNVuGJwcZvWni/V8af07lyr023lH0bqra5IAjOwp6rKu7Xx4yzwuGzB2wjLwhiSCCoRqH++7xSCrIyegAr/D8yJ/2tnCrgyWxr6CKF/iDwAWDDq29X+l3jejYC5FjrexPRsJE7FkCQUbeyKq0NIoxIsaBHL9p4opiP4yciHgH7xPWrKupqhVkx+e7/VZfti0kESUOkigIjNo1D01C4HPwLKOTKvqpQexKL9S31ClIIgVp9yutvmF8AIoYJAj1GIfXaVUcwfhp0CBB8Iq6+kP0ZN3FXzpo65A2TgQBSSrDDySRggg4jlXkWNjQnhDCQj7gBxcEwSVej5/RvqSTjZuNhqpJQdpDcK7uB4MEwc5zxEL92NmHjIUBjo8pCf4wIjju8cnRAT6q80P8GgMLD3/EG4bsQBJu+6D/Va/Suv4QEg7ESgIZKThAXEJwTC1QBtM0iSCeviG48M7Sj10iyTskiWz3c7d/eMiStGOfyED0KIjYsdBb3j6sWGTgUQ5sg4PRq20cKkPRdXdS33e7viRykHb3+o8/r2V7vNF/UucSKyIIdOOwZY8iIKMQBAuiHMP3ag+F13hZ/3nYxZJc2UmS9A7/OLxuJ1pEIAmIyLHQMPVWS/dChtB8kCoSBL5ye+ILIaUeD5Emul2fJkjSRSR/9tuzTJq2El0Lhqm1UGsiAjn2BRlVgUMZaU8k10FaR4q020gSPQEEk1x350eRgDgiRRS7ghMWsaYAxa2egCAjT3F7ssueYUm6jCRokzM+54Tj6Hb7f74XyfwgSI/DtnvMgjSbIQ8SRBkRQRT9ifTCVOtDTKJN6zY6obfLGuLxPx/6JN3uXCg+iD0jRkYQH4dd1w9ybqMk/ogEaZ6cKgqKkJ/k16EjknQNu96AWsKDcEZIhpFnEyWZB8QzWq22y0QHgqAgVBEIkNcgyLGSfHOSpZ/w4PRpCZwrCQSR/PG0NwOkzfkWrYhykEEb4tzlwrzZ5CPEU5qjPQXFvPM06dptCxu9IVibpOBuAkkPkfzZj72QDCLIWmEhsczDeL5qsjGPIn1UxanX6yZfZHblMSbpaOBcCZLAtxCSa9GrySRB00gwAhCtcljRvFEUpElASIR4B6PXaQV9pj6bcX3D1WtE0oV4B+dKAgGSp3/88UcsUHgSLkqSQAbo6rGqNxKCkEffsdCX7vXqrCuAPrkOnEsHEhNGzSEEIEByjUUZJqgyJ4iL78/i4II4OlYYigAEC1I9qW4r6NPOk5nXYV562qfOdWUX2l3THFLDEOQz+gwhgZgfykWJg9hxEMdAt4Bx6cQpBInUQrd69KrqITrr2eyLFy8S5xrqhRKECQMisB4viinyLjEI2zQO9P5hy1TD7UgouQo4vIPmUQ2/4F3PczlpcC4SJo2ShkiGc5A8vfZVk2giTVvuwO62mDvyYBAo20qcQ3l1dAqOpczlWMS5gKTXa2v1uq23Z5NQlN5wmBAmlCTaozi2Wfm9bNrMpjcFRbsSQSFNLw4QlLHmuyr24urTfq/XG7ZRmADJMJmERUFBZM6SJAQZ693K75VdwHDZjXvHWBJio5Dj6FVaQQHiPp33OuWpjWtMomulK9+54MueP3Js9Dlk/f710z8PYebY7/cY/2pHSSJBYhv4jlWaE13YQpWbUFBdCMfrNN616w3nv3L8CoQJJiEBP+xRgBAjYoByfdhqtfqA0hvKJGFAxpDbzXKlbBbGgmk7vTCzMiIkhAMCHWfe97qv1dIzQgIBPyUkvWRDJNfXFQjbPhaTRYkFCWqtTXyvV33sCOftSiDLMW6sSMKCcjhqzhsgQZhgkq5NSMxebyYJRumjW+ruhymMJ6HX1thFN619uUu2hcgWf+lGbzIpVJqQsKCCjJrO0/e7B0lq4yny+F7b9jWZBwWx9Pf30X1md9vBfIbLXvXd0suXL0vmrmGPZy1j+8u+kHj/whwQ6Nfve3+b1JOQ5NJuz5YkRLnumya6qXGptFuvB/NdcrPjFy/Ik7rNV0UJic+B9YA+5X0CPQx4RNJHJPWABAdzZOjcp/2+L0yva5Z2d3fJXZvx7Zp3EcsuWfRHEc8vQQhJCEf16BTNpUbHXvvXD7iB3dKzp3hUlGQYDLePH9mv8OdRwD6fwoiToduCR4qJ7NC0v8qF9KgSbTpPPuj2dQHJeHrla9IPSHr9AKQfA2HMLyz0GngodUUOiEqPTo9QC3mA4xyvBOnPPvA2fOvPqHcByVQ1zBCkzzpSn0BFh4/MNM0g3mnq0vXI1F1KgperQw7vgzluLFKSflvVri4so8s4Teg8PVH0EIx4OQlriRAkssjlQeOOOHBt1J6t37jxd0mG9uXV1NJFJD6NQBGTy78iRawZex+qr1/XtslpE3+HIyC5vh5q4+nU0to9CUokmyWDaJqYJJK7XEhXr2r09Cfwq791S0RMgqynORfvxjhQ5gHpsdMTeccV2Y/CkbhQzqHfJY3Xh8dHQLLkk3Scy3djB7nXPCA9EusRDq7jkmwRCtwKwjxNO68PzLuRLPyEkPTblnNx4djtJE1CoGGEJD7llexJIYsq2K226Zk4w18/yu1Pl34lJNdDdXB5YVnzuRfNviYPgkm4o4mx3U5oxwzKuq926JlQg96vH+mGtCsbzwhJT3edi8sEUfpxkhmSxFIX2vgDcrw+TVMO6/rj3YE6tUoD5bqrDs7fDuSR0hdKEu3muT1onCbumTdwt5uBW3me9uxj3uo4JOnr7uD83FWNYW8OELkkzGY6VpKzgS8HiRW3/ezj3nx6celX6l4gijc5d1zUfIlA+lyTIgGR7h5wAOMvkMP13erXj307cBQoVJRepwgoAzFKmLeG4rxlsIcYGBCycbT6CrwqjTbIwr+B9nRj5cZHt5RfGyF93fO8ycQTofT4/OtXd5kkwTYItDvR3UZeVVP8W0cMn61//BvNU/d6GvrXCFA824igRBXpmlGQhnD51HG87VcIY9vf1wtyLN34RBbG/PV1m6Lgpliaf4dMH09JGvzhK3KSCnYqjOGf/zL8qNkqLsoGi6KM3rwZKUW9bTJTLlEhEbUpCMTfWa5AP8JiOMYnlMOPlF85lKOTN6OmYnXiK0ZDtpAIKolGs6+ztl19/der0/QD109d2vWT9dSNT20rjH9BrECPim5puqaCLqakR4mlLcJxCXoUsRiQqZTgzAX703qVxL+gbSk2EctRUylq6HiKGZLIOKZTTZvaWIvm0dEr8Kmi48e83X228dFrh9S/OJQ+kuXV0cnoqLmNYNpmYLHcC6E+BdNsJAXW4rRWVfyT0BzL7kJwLNz454xHue5BtFRPX+FbAVeVNcfW0TbhGAXCQAygBILAFGtOcMbVWBv+wxgClH6vfW9tew9g/iI3NlaUYhHdMyjYg62qTrGooOv6oJsfI4j09pp/VyG8Pt97vLGUuvHP28IShD3L0h92VAzz6jXGOeKve9PEt2/+CxgoBHvOjda+frb6j6sRhP3K6hMWBQkzbN97BK6zd3p6im7U/BrefPz/a3zj5tPTGmIoqnaw9R8fZhg+fbK6snjjXzTOw4Lq3m13dPX+2pqyvU3lSFe3tx+sAYGqkat+hWd2GN3rZ/+OT0VlWd94fM2xBAnY7AaR3hYdsC4Y0OA83lj/d8WIsPwkQunhBpjLw0wSa39WFJQltbT65PE12g7Rv45MsLr+4im/dWj405PVpdRnReHHy8rS6sbj3s9dfPydLqMGB7LZdcfu8PHG6tLKvx8XScqsrAPN4x9+RMZuJ2y38VM//wQM65+nEgIaEGd9fXV14+HDx4+///77x48fP3y4AQDrIMP/HwwRosWFFLWFxU87/v8HixBYE/r2+YAAAAAASUVORK5CYII='
|
|
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)}">×</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, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
850
|
+
}
|
|
851
|
+
function escapeAttr(s) {
|
|
852
|
+
return escapeHtml(s).replace(/"/g, '"')
|
|
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
|
package/src/monedabg.png
ADDED
|
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>
|