@omnivoice/page-agent 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +87 -15
- package/dist/esm/SimulatorMask-DIoKZt1z.js +379 -0
- package/dist/esm/SimulatorMask-DIoKZt1z.js.map +1 -0
- package/dist/esm/page-agent.d.ts +1076 -0
- package/dist/esm/page-agent.js +4783 -0
- package/dist/esm/page-agent.js.map +1 -0
- package/dist/iife/page-agent.demo.js +433 -0
- package/dist/voice/index.d.ts +533 -0
- package/dist/voice/index.js +1096 -0
- package/dist/voice/index.js.map +1 -0
- package/package.json +68 -28
- package/server/agent-gateway.mjs +215 -0
- package/src/voice/README.md +0 -48
- package/src/voice/bridge.ts +0 -230
- package/src/voice/config.ts +0 -200
- package/src/voice/engines.ts +0 -161
- package/src/voice/index.ts +0 -17
- package/src/voice/session.ts +0 -309
- package/src/voice/types.ts +0 -98
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SimonLuvRamen
|
|
4
|
+
Copyright (c) 2026 Alibaba Group Holding Limited
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,13 +1,85 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @omnivoice/page-agent
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Agente de GUI para aplicações web: um script (ou um `mountWidget()`) dá a qualquer página o seu próprio agente que
|
|
4
|
+
lê o DOM e age nele. Este pacote é um fork do [`page-agent`](https://github.com/alibaba/page-agent) (Alibaba, MIT) com
|
|
5
|
+
um widget central, um gateway que guarda a chave do provedor e um módulo de voz **opcional**.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
- OpenAI Realtime full-duplex voice integration
|
|
7
|
-
- Suporte a motores local (on-device) e realtime (cloud)
|
|
8
|
-
- Documentação em `src/voice/README.md`
|
|
7
|
+
## Instalação
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm install @omnivoice/page-agent zod
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`zod` (`^3.25.0 || ^4.0.0`) é peer dependency. O pacote não depende de `@omnivoice/core`, `llms`, `page-controller`
|
|
14
|
+
nem `ui`: esses módulos vão embutidos no build.
|
|
15
|
+
|
|
16
|
+
## Uso (ESM)
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { mountWidget } from "@omnivoice/page-agent";
|
|
20
|
+
|
|
21
|
+
const widget = mountWidget({
|
|
22
|
+
model: "gpt-4o-mini",
|
|
23
|
+
baseURL: "https://seu-gateway.exemplo.com/v1",
|
|
24
|
+
language: "pt-BR",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await widget.run("Preencha o nome com Maria Oliveira");
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`mountWidget` só cria o botão lançador. O agente é criado ao abrir o painel (ou em `widget.ready()` / `widget.run()`).
|
|
31
|
+
A voz do navegador (`createVoice`) só é criada nessa hora, e o reconhecimento de fala só abre quando a pessoa clica
|
|
32
|
+
no botão de microfone. Para desligá-la: `voice: { input: false, output: false }`.
|
|
33
|
+
|
|
34
|
+
Também são exportados `PageAgent`, `createVoice`, `tool` e os tipos `PageAgentConfig`, `Widget`, `WidgetOptions`,
|
|
35
|
+
`Voice` e `VoiceOptions`.
|
|
36
|
+
|
|
37
|
+
## Uso por CDN (IIFE)
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<script src="https://cdn.jsdelivr.net/npm/@omnivoice/page-agent@VERSAO/dist/iife/page-agent.demo.js"
|
|
41
|
+
data-auto-init="false"></script>
|
|
42
|
+
<script>
|
|
43
|
+
window.PageAgent.mountWidget({ model: "gpt-4o-mini", baseURL: "/agent/v1" });
|
|
44
|
+
</script>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Sem `data-auto-init="false"` (ou com `?autoInit` diferente de `false` na URL do script) o script cria um agente de
|
|
48
|
+
demonstração ao carregar. Troque `VERSAO` por uma versão publicada.
|
|
49
|
+
|
|
50
|
+
## Gateway (chave do provedor só no servidor)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
AGENT_API_KEY=... AGENT_BASE_URL=https://api.openai.com/v1 AGENT_MODEL=gpt-4o-mini npx omnivoice-agent-gateway
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
O gateway expõe `GET /config`, `POST /v1/chat/completions` e `GET /health`, e injeta a chave ao chamar o provedor.
|
|
57
|
+
As variáveis `AGENT_*` estão descritas no cabeçalho de `server/agent-gateway.mjs`.
|
|
58
|
+
|
|
59
|
+
## Voz em tempo real (opcional)
|
|
60
|
+
|
|
61
|
+
`@omnivoice/page-agent/voice` é um módulo à parte. **Importar o agente não o carrega**, e importá-lo, criar a
|
|
62
|
+
configuração ou criar motores não liga microfone, conexão de áudio nem síntese: isso só acontece quando você chama
|
|
63
|
+
`createVoiceSession(...)`. Veja o uso, os modos (`local` e `realtime`), a configuração e o protocolo em
|
|
64
|
+
[`src/voice/README.md`](./src/voice/README.md).
|
|
65
|
+
|
|
66
|
+
## Verificação do pacote
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm ci
|
|
70
|
+
npm run build:package # dist/esm, dist/iife, dist/voice e server/
|
|
71
|
+
npm run verify:package # identidade, tarball efetivo, dist atual, tipos
|
|
72
|
+
npm run test:consumer # empacota e testa o tarball num projeto novo
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`npm publish` roda o build e o guard em modo `--release` antes de enviar.
|
|
76
|
+
|
|
77
|
+
## Desenvolvimento do laboratório
|
|
78
|
+
|
|
79
|
+
O restante deste documento descreve o laboratório local deste repositório (`index.html`, `src/main.ts`, proxy
|
|
80
|
+
`server.mjs`), que consome o fork vendorizado em `vendor/page-agent` e não o pacote npm.
|
|
81
|
+
|
|
82
|
+
### Rodar
|
|
11
83
|
|
|
12
84
|
Requisitos: Node `^22.22.1 || >=24` e npm `^11.6.3`, mais uma conexão para instalar os pacotes.
|
|
13
85
|
|
|
@@ -16,7 +88,7 @@ O piso de versão vem de `vendor/page-agent`, não do laboratório: o monorepo d
|
|
|
16
88
|
verifica essa precondição antes de comparar os artefatos.
|
|
17
89
|
|
|
18
90
|
```bash
|
|
19
|
-
cd
|
|
91
|
+
cd <raiz deste repositório>
|
|
20
92
|
cp .env.example .env
|
|
21
93
|
# edite .env e coloque OPENAI_API_KEY=...
|
|
22
94
|
npm install
|
|
@@ -32,13 +104,13 @@ Abra <http://127.0.0.1:8797>.
|
|
|
32
104
|
|
|
33
105
|
O mesmo processo serve o frontend e o proxy da API. O endpoint `http://127.0.0.1:8797/api/health` mostra se a chave foi carregada. A porta `8787` pertence a outro serviço (`agent-relay`) e não é usada pelo laboratório.
|
|
34
106
|
|
|
35
|
-
|
|
107
|
+
### Suíte de desafios
|
|
36
108
|
|
|
37
109
|
O laboratório agora contém mais de 100 desafios locais em 10 categorias: formulários, navegação, loading, recuperação, DOM profundo, dados, permissões, interação visual, adversarial e limites. O seletor mostra no máximo 12 cards por vez para não poluir o DOM que o PageAgent precisa observar; todos continuam disponíveis por busca e filtro.
|
|
38
110
|
|
|
39
111
|
Cada desafio tem uma tarefa, uma dificuldade, um alvo renderizado, reset determinístico e evidência esperada. Os casos de cross-origin, gesto real e canvas são representados como limites explícitos da superfície local, não como falsos sucessos.
|
|
40
112
|
|
|
41
|
-
|
|
113
|
+
### Comandos do laboratório
|
|
42
114
|
|
|
43
115
|
Para não precisar controlar processos manualmente:
|
|
44
116
|
|
|
@@ -54,7 +126,7 @@ make test # executa os testes Playwright
|
|
|
54
126
|
|
|
55
127
|
O navegador recebe apenas `local-browser-session`. A chave real fica no `.env` e é usada pelo `server.mjs`, que encaminha chamadas para `https://api.openai.com/v1/chat/completions`.
|
|
56
128
|
|
|
57
|
-
|
|
129
|
+
### Como testar
|
|
58
130
|
|
|
59
131
|
1. Clique em `Preencher nome`, `Escolher especialidade` ou `Clicar em agendar` para preparar uma tarefa.
|
|
60
132
|
2. Clique em `Executar com PageAgent`.
|
|
@@ -69,7 +141,7 @@ Se aparecer `EADDRINUSE` na porta `8797`, não troque a porta: verifique qual pr
|
|
|
69
141
|
|
|
70
142
|
O `make down` encerra somente a sessão registrada em `.pageagent.pid`; ele não encerra serviços externos.
|
|
71
143
|
|
|
72
|
-
|
|
144
|
+
### Testes automatizados
|
|
73
145
|
|
|
74
146
|
```bash
|
|
75
147
|
npx playwright install chromium
|
|
@@ -78,7 +150,7 @@ npm test
|
|
|
78
150
|
|
|
79
151
|
Os testes cobrem o carregamento da página, acessibilidade básica, envio do formulário, animação do cursor e botões de comando rápido. A execução do LLM não é incluída no teste automatizado para não consumir créditos.
|
|
80
152
|
|
|
81
|
-
|
|
153
|
+
### Arquivos principais
|
|
82
154
|
|
|
83
155
|
- `src/main.ts`: instancia o `PageAgent` real e monta o laboratório.
|
|
84
156
|
- `src/durable-runtime.ts`: checkpoint IndexedDB, lease por aba, recuperação após `pagehide` e proteção contra replay cego.
|
|
@@ -90,11 +162,11 @@ Os testes cobrem o carregamento da página, acessibilidade básica, envio do for
|
|
|
90
162
|
- `tests/lab.spec.ts`: testes Playwright.
|
|
91
163
|
- `vite.config.ts`: encaminha `/api` para o proxy local.
|
|
92
164
|
|
|
93
|
-
|
|
165
|
+
### Evidência em tempo real
|
|
94
166
|
|
|
95
167
|
Durante cada execução, o laboratório inicia um `ActionJournal` antes do agente. Ele captura `input`, `change`, `click`, `submit` e mutações relevantes do DOM enquanto a ferramenta está executando. A evidência é coalescida por `actionId` e adicionada ao histórico do próximo passo; ela indica que houve alteração, mas o sucesso final continua dependendo do verificador do desafio.
|
|
96
168
|
|
|
97
|
-
|
|
169
|
+
### Retomada e recursos do navegador
|
|
98
170
|
|
|
99
171
|
O widget agora cria um `runId` e salva checkpoints locais em IndexedDB. Em refresh ou navegação completa, a última ação fica como `unknown`; o agente não a repete automaticamente sem reconciliação. O lease em `localStorage` evita duas abas executarem o mesmo run, e `pagehide`/`pageshow` fazem a transição do documento.
|
|
100
172
|
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
"use strict";
|
|
3
|
+
try {
|
|
4
|
+
if (typeof document != "undefined") {
|
|
5
|
+
var elementStyle = document.createElement("style");
|
|
6
|
+
elementStyle.appendChild(document.createTextNode(`._wrapper_1ooyb_1 {
|
|
7
|
+
position: fixed;
|
|
8
|
+
inset: 0;
|
|
9
|
+
z-index: 2147483641; /* 确保在所有元素之上,除了 panel */
|
|
10
|
+
cursor: wait;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
|
|
13
|
+
display: none;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
._wrapper_1ooyb_1._visible_1ooyb_11 {
|
|
17
|
+
display: block;
|
|
18
|
+
}
|
|
19
|
+
/* AI 光标样式 */
|
|
20
|
+
._cursor_1dgwb_2 {
|
|
21
|
+
position: absolute;
|
|
22
|
+
width: var(--cursor-size, 75px);
|
|
23
|
+
height: var(--cursor-size, 75px);
|
|
24
|
+
pointer-events: none;
|
|
25
|
+
z-index: 10000;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
._cursorBorder_1dgwb_10 {
|
|
29
|
+
position: absolute;
|
|
30
|
+
width: 100%;
|
|
31
|
+
height: 100%;
|
|
32
|
+
background: linear-gradient(45deg, rgb(57, 182, 255), rgb(189, 69, 251));
|
|
33
|
+
mask-image: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20100%20100'%20fill='none'%3e%3cg%3e%3cpath%20d='M%2015%2042%20L%2015%2036.99%20Q%2015%2031.99%2023.7%2031.99%20L%2028.05%2031.99%20Q%2032.41%2031.99%2032.41%2021.99%20L%2032.41%2017%20Q%2032.41%2012%2041.09%2016.95%20L%2076.31%2037.05%20Q%2085%2042%2076.31%2046.95%20L%2041.09%2067.05%20Q%2032.41%2072%2032.41%2062.01%20L%2032.41%2057.01%20Q%2032.41%2052.01%2023.7%2052.01%20L%2019.35%2052.01%20Q%2015%2052.01%2015%2047.01%20Z'%20fill='none'%20stroke='%23000000'%20stroke-width='6'%20stroke-miterlimit='10'%20style='stroke:%20light-dark(rgb(0,%200,%200),%20rgb(255,%20255,%20255));'/%3e%3c/g%3e%3c/svg%3e");
|
|
34
|
+
mask-size: 100% 100%;
|
|
35
|
+
mask-repeat: no-repeat;
|
|
36
|
+
|
|
37
|
+
transform-origin: center;
|
|
38
|
+
transform: rotate(-135deg) scale(1.2);
|
|
39
|
+
margin-left: -10px;
|
|
40
|
+
margin-top: -18px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
._cursorFilling_1dgwb_25 {
|
|
44
|
+
position: absolute;
|
|
45
|
+
width: 100%;
|
|
46
|
+
height: 100%;
|
|
47
|
+
background: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20100%20100'%3e%3cdefs%3e%3c/defs%3e%3cg%20xmlns='http://www.w3.org/2000/svg'%20style='filter:%20drop-shadow(light-dark(rgba(0,%200,%200,%200.4),%20rgba(237,%20237,%20237,%200.4))%203px%204px%204px);'%3e%3cpath%20d='M%2015%2042%20L%2015%2036.99%20Q%2015%2031.99%2023.7%2031.99%20L%2028.05%2031.99%20Q%2032.41%2031.99%2032.41%2021.99%20L%2032.41%2017%20Q%2032.41%2012%2041.09%2016.95%20L%2076.31%2037.05%20Q%2085%2042%2076.31%2046.95%20L%2041.09%2067.05%20Q%2032.41%2072%2032.41%2062.01%20L%2032.41%2057.01%20Q%2032.41%2052.01%2023.7%2052.01%20L%2019.35%2052.01%20Q%2015%2052.01%2015%2047.01%20Z'%20fill='%23ffffff'%20stroke='none'%20style='fill:%20%23ffffff;'/%3e%3c/g%3e%3c/svg%3e");
|
|
48
|
+
background-size: 100% 100%;
|
|
49
|
+
background-repeat: no-repeat;
|
|
50
|
+
|
|
51
|
+
transform-origin: center;
|
|
52
|
+
transform: rotate(-135deg) scale(1.2);
|
|
53
|
+
margin-left: -10px;
|
|
54
|
+
margin-top: -18px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
._cursorRipple_1dgwb_39 {
|
|
58
|
+
position: absolute;
|
|
59
|
+
width: 100%;
|
|
60
|
+
height: 100%;
|
|
61
|
+
pointer-events: none;
|
|
62
|
+
margin-left: -50%;
|
|
63
|
+
margin-top: -50%;
|
|
64
|
+
|
|
65
|
+
&::after {
|
|
66
|
+
content: '';
|
|
67
|
+
opacity: 0;
|
|
68
|
+
position: absolute;
|
|
69
|
+
inset: 0;
|
|
70
|
+
border: 4px solid rgba(57, 182, 255, 1);
|
|
71
|
+
border-radius: 50%;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
._cursor_1dgwb_2._clicking_1dgwb_57 ._cursorRipple_1dgwb_39::after {
|
|
76
|
+
animation: _cursor-ripple_1dgwb_1 300ms ease-out forwards;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@keyframes _cursor-ripple_1dgwb_1 {
|
|
80
|
+
0% {
|
|
81
|
+
transform: scale(0);
|
|
82
|
+
opacity: 1;
|
|
83
|
+
}
|
|
84
|
+
100% {
|
|
85
|
+
transform: scale(2);
|
|
86
|
+
opacity: 0;
|
|
87
|
+
}
|
|
88
|
+
}`));
|
|
89
|
+
document.head.appendChild(elementStyle);
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.error("vite-plugin-css-injected-by-js", e);
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
95
|
+
import { Motion } from "ai-motion";
|
|
96
|
+
function isPageDark() {
|
|
97
|
+
try {
|
|
98
|
+
if (hasDarkModeClass()) return true;
|
|
99
|
+
if (hasDarkModeDataAttribute()) return true;
|
|
100
|
+
if (isColorSchemeDark()) return true;
|
|
101
|
+
if (isBackgroundDark()) return true;
|
|
102
|
+
if (isMainContentBackgroundDark()) return true;
|
|
103
|
+
if (isTextColorLight()) return true;
|
|
104
|
+
return false;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.warn("Error determining if page is dark:", error);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function hasDarkModeClass() {
|
|
111
|
+
const DEFAULT_DARK_MODE_CLASSES = ["dark", "dark-mode", "theme-dark", "night", "night-mode"];
|
|
112
|
+
const htmlElement = document.documentElement;
|
|
113
|
+
const bodyElement = document.body || document.documentElement;
|
|
114
|
+
for (const className of DEFAULT_DARK_MODE_CLASSES) {
|
|
115
|
+
if (htmlElement.classList.contains(className) || bodyElement?.classList.contains(className)) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
function hasDarkModeDataAttribute() {
|
|
122
|
+
const htmlElement = document.documentElement;
|
|
123
|
+
const bodyElement = document.body || document.documentElement;
|
|
124
|
+
const dataAttrs = ["data-theme", "data-color-mode", "data-bs-theme", "data-mui-color-scheme"];
|
|
125
|
+
for (const attr of dataAttrs) {
|
|
126
|
+
const bodyValue = bodyElement?.getAttribute(attr);
|
|
127
|
+
const htmlValue = htmlElement.getAttribute(attr);
|
|
128
|
+
if (bodyValue?.toLowerCase() === "dark" || htmlValue?.toLowerCase() === "dark") {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
function isColorSchemeDark() {
|
|
135
|
+
const meta = document.querySelector('meta[name="color-scheme"]');
|
|
136
|
+
const metaContent = meta?.content.toLowerCase();
|
|
137
|
+
if (metaContent === "dark" || metaContent === "only dark") return true;
|
|
138
|
+
const rootStyle = window.getComputedStyle(document.documentElement);
|
|
139
|
+
const colorScheme = rootStyle.getPropertyValue("color-scheme").trim().toLowerCase();
|
|
140
|
+
return colorScheme === "dark" || colorScheme === "only dark";
|
|
141
|
+
}
|
|
142
|
+
function isBackgroundDark() {
|
|
143
|
+
const htmlStyle = window.getComputedStyle(document.documentElement);
|
|
144
|
+
const bodyStyle = window.getComputedStyle(document.body || document.documentElement);
|
|
145
|
+
const htmlBgColor = htmlStyle.backgroundColor;
|
|
146
|
+
const bodyBgColor = bodyStyle.backgroundColor;
|
|
147
|
+
if (isColorDark(bodyBgColor)) {
|
|
148
|
+
return true;
|
|
149
|
+
} else if (bodyBgColor === "transparent" || bodyBgColor.startsWith("rgba(0, 0, 0, 0)")) {
|
|
150
|
+
return isColorDark(htmlBgColor);
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
function isTextColorLight() {
|
|
155
|
+
const LIGHT_TEXT_LUMINANCE = 200;
|
|
156
|
+
const bodyStyle = window.getComputedStyle(document.body || document.documentElement);
|
|
157
|
+
const luminance = getLuminance(bodyStyle.color);
|
|
158
|
+
return luminance !== null && luminance > LIGHT_TEXT_LUMINANCE;
|
|
159
|
+
}
|
|
160
|
+
function isMainContentBackgroundDark() {
|
|
161
|
+
const { innerWidth: vw, innerHeight: vh } = window;
|
|
162
|
+
const minArea = vw * vh * 0.5;
|
|
163
|
+
const selectors = ["#app", "#root", "#__next"];
|
|
164
|
+
for (const selector of selectors) {
|
|
165
|
+
const el = document.querySelector(selector);
|
|
166
|
+
if (!el) continue;
|
|
167
|
+
const rect = el.getBoundingClientRect();
|
|
168
|
+
if (rect.width * rect.height < minArea) continue;
|
|
169
|
+
if (isColorDark(window.getComputedStyle(el).backgroundColor)) return true;
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
function parseRgbColor(colorString) {
|
|
174
|
+
const rgbMatch = /rgba?\((\d+),\s*(\d+),\s*(\d+)/.exec(colorString);
|
|
175
|
+
if (!rgbMatch) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
r: parseInt(rgbMatch[1]),
|
|
180
|
+
g: parseInt(rgbMatch[2]),
|
|
181
|
+
b: parseInt(rgbMatch[3])
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function getLuminance(colorString) {
|
|
185
|
+
if (!colorString || colorString === "transparent" || colorString.startsWith("rgba(0, 0, 0, 0)")) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const rgb = parseRgbColor(colorString);
|
|
189
|
+
if (!rgb) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b;
|
|
193
|
+
}
|
|
194
|
+
function isColorDark(colorString, threshold = 128) {
|
|
195
|
+
const luminance = getLuminance(colorString);
|
|
196
|
+
return luminance !== null && luminance < threshold;
|
|
197
|
+
}
|
|
198
|
+
const wrapper = "_wrapper_1ooyb_1";
|
|
199
|
+
const visible = "_visible_1ooyb_11";
|
|
200
|
+
const styles = {
|
|
201
|
+
wrapper,
|
|
202
|
+
visible
|
|
203
|
+
};
|
|
204
|
+
const cursor = "_cursor_1dgwb_2";
|
|
205
|
+
const cursorBorder = "_cursorBorder_1dgwb_10";
|
|
206
|
+
const cursorFilling = "_cursorFilling_1dgwb_25";
|
|
207
|
+
const cursorRipple = "_cursorRipple_1dgwb_39";
|
|
208
|
+
const clicking = "_clicking_1dgwb_57";
|
|
209
|
+
const cursorStyles = {
|
|
210
|
+
cursor,
|
|
211
|
+
cursorBorder,
|
|
212
|
+
cursorFilling,
|
|
213
|
+
cursorRipple,
|
|
214
|
+
clicking
|
|
215
|
+
};
|
|
216
|
+
class SimulatorMask extends EventTarget {
|
|
217
|
+
shown = false;
|
|
218
|
+
wrapper = document.createElement("div");
|
|
219
|
+
motion = null;
|
|
220
|
+
#disposed = false;
|
|
221
|
+
#cursor = document.createElement("div");
|
|
222
|
+
#currentCursorX = 0;
|
|
223
|
+
#currentCursorY = 0;
|
|
224
|
+
#targetCursorX = 0;
|
|
225
|
+
#targetCursorY = 0;
|
|
226
|
+
constructor() {
|
|
227
|
+
super();
|
|
228
|
+
this.wrapper.id = "page-agent-runtime_simulator-mask";
|
|
229
|
+
this.wrapper.className = styles.wrapper;
|
|
230
|
+
this.wrapper.setAttribute("data-browser-use-ignore", "true");
|
|
231
|
+
this.wrapper.setAttribute("data-page-agent-ignore", "true");
|
|
232
|
+
try {
|
|
233
|
+
const motion = new Motion({
|
|
234
|
+
mode: isPageDark() ? "dark" : "light",
|
|
235
|
+
styles: { position: "absolute", inset: "0" }
|
|
236
|
+
});
|
|
237
|
+
this.motion = motion;
|
|
238
|
+
this.wrapper.appendChild(motion.element);
|
|
239
|
+
motion.autoResize(this.wrapper);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
console.warn("[SimulatorMask] Motion overlay unavailable:", e);
|
|
242
|
+
}
|
|
243
|
+
this.wrapper.addEventListener("click", (e) => {
|
|
244
|
+
e.stopPropagation();
|
|
245
|
+
e.preventDefault();
|
|
246
|
+
});
|
|
247
|
+
this.wrapper.addEventListener("mousedown", (e) => {
|
|
248
|
+
e.stopPropagation();
|
|
249
|
+
e.preventDefault();
|
|
250
|
+
});
|
|
251
|
+
this.wrapper.addEventListener("mouseup", (e) => {
|
|
252
|
+
e.stopPropagation();
|
|
253
|
+
e.preventDefault();
|
|
254
|
+
});
|
|
255
|
+
this.wrapper.addEventListener("mousemove", (e) => {
|
|
256
|
+
e.stopPropagation();
|
|
257
|
+
e.preventDefault();
|
|
258
|
+
});
|
|
259
|
+
this.wrapper.addEventListener("wheel", (e) => {
|
|
260
|
+
e.stopPropagation();
|
|
261
|
+
e.preventDefault();
|
|
262
|
+
});
|
|
263
|
+
this.wrapper.addEventListener("keydown", (e) => {
|
|
264
|
+
e.stopPropagation();
|
|
265
|
+
e.preventDefault();
|
|
266
|
+
});
|
|
267
|
+
this.wrapper.addEventListener("keyup", (e) => {
|
|
268
|
+
e.stopPropagation();
|
|
269
|
+
e.preventDefault();
|
|
270
|
+
});
|
|
271
|
+
this.#createCursor();
|
|
272
|
+
document.body.appendChild(this.wrapper);
|
|
273
|
+
this.#moveCursorToTarget();
|
|
274
|
+
const movePointerToListener = (event) => {
|
|
275
|
+
const { x, y } = event.detail;
|
|
276
|
+
this.setCursorPosition(x, y);
|
|
277
|
+
};
|
|
278
|
+
const clickPointerListener = () => {
|
|
279
|
+
this.triggerClickAnimation();
|
|
280
|
+
};
|
|
281
|
+
const enablePassThroughListener = () => {
|
|
282
|
+
this.wrapper.style.pointerEvents = "none";
|
|
283
|
+
};
|
|
284
|
+
const disablePassThroughListener = () => {
|
|
285
|
+
this.wrapper.style.pointerEvents = "auto";
|
|
286
|
+
};
|
|
287
|
+
window.addEventListener("PageAgent::MovePointerTo", movePointerToListener);
|
|
288
|
+
window.addEventListener("PageAgent::ClickPointer", clickPointerListener);
|
|
289
|
+
window.addEventListener("PageAgent::EnablePassThrough", enablePassThroughListener);
|
|
290
|
+
window.addEventListener("PageAgent::DisablePassThrough", disablePassThroughListener);
|
|
291
|
+
this.addEventListener("dispose", () => {
|
|
292
|
+
window.removeEventListener("PageAgent::MovePointerTo", movePointerToListener);
|
|
293
|
+
window.removeEventListener("PageAgent::ClickPointer", clickPointerListener);
|
|
294
|
+
window.removeEventListener("PageAgent::EnablePassThrough", enablePassThroughListener);
|
|
295
|
+
window.removeEventListener("PageAgent::DisablePassThrough", disablePassThroughListener);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
#createCursor() {
|
|
299
|
+
this.#cursor.className = cursorStyles.cursor;
|
|
300
|
+
const rippleContainer = document.createElement("div");
|
|
301
|
+
rippleContainer.className = cursorStyles.cursorRipple;
|
|
302
|
+
this.#cursor.appendChild(rippleContainer);
|
|
303
|
+
const fillingLayer = document.createElement("div");
|
|
304
|
+
fillingLayer.className = cursorStyles.cursorFilling;
|
|
305
|
+
this.#cursor.appendChild(fillingLayer);
|
|
306
|
+
const borderLayer = document.createElement("div");
|
|
307
|
+
borderLayer.className = cursorStyles.cursorBorder;
|
|
308
|
+
this.#cursor.appendChild(borderLayer);
|
|
309
|
+
this.wrapper.appendChild(this.#cursor);
|
|
310
|
+
}
|
|
311
|
+
#moveCursorToTarget() {
|
|
312
|
+
if (this.#disposed) return;
|
|
313
|
+
const newX = this.#currentCursorX + (this.#targetCursorX - this.#currentCursorX) * 0.2;
|
|
314
|
+
const newY = this.#currentCursorY + (this.#targetCursorY - this.#currentCursorY) * 0.2;
|
|
315
|
+
const xDistance = Math.abs(newX - this.#targetCursorX);
|
|
316
|
+
if (xDistance > 0) {
|
|
317
|
+
if (xDistance < 2) {
|
|
318
|
+
this.#currentCursorX = this.#targetCursorX;
|
|
319
|
+
} else {
|
|
320
|
+
this.#currentCursorX = newX;
|
|
321
|
+
}
|
|
322
|
+
this.#cursor.style.left = `${this.#currentCursorX}px`;
|
|
323
|
+
}
|
|
324
|
+
const yDistance = Math.abs(newY - this.#targetCursorY);
|
|
325
|
+
if (yDistance > 0) {
|
|
326
|
+
if (yDistance < 2) {
|
|
327
|
+
this.#currentCursorY = this.#targetCursorY;
|
|
328
|
+
} else {
|
|
329
|
+
this.#currentCursorY = newY;
|
|
330
|
+
}
|
|
331
|
+
this.#cursor.style.top = `${this.#currentCursorY}px`;
|
|
332
|
+
}
|
|
333
|
+
requestAnimationFrame(() => this.#moveCursorToTarget());
|
|
334
|
+
}
|
|
335
|
+
setCursorPosition(x, y) {
|
|
336
|
+
if (this.#disposed) return;
|
|
337
|
+
this.#targetCursorX = x;
|
|
338
|
+
this.#targetCursorY = y;
|
|
339
|
+
}
|
|
340
|
+
triggerClickAnimation() {
|
|
341
|
+
if (this.#disposed) return;
|
|
342
|
+
this.#cursor.classList.remove(cursorStyles.clicking);
|
|
343
|
+
this.#cursor.getBoundingClientRect();
|
|
344
|
+
this.#cursor.classList.add(cursorStyles.clicking);
|
|
345
|
+
}
|
|
346
|
+
show() {
|
|
347
|
+
if (this.shown || this.#disposed) return;
|
|
348
|
+
this.shown = true;
|
|
349
|
+
this.motion?.start();
|
|
350
|
+
this.motion?.fadeIn();
|
|
351
|
+
this.wrapper.classList.add(styles.visible);
|
|
352
|
+
this.#currentCursorX = window.innerWidth / 2;
|
|
353
|
+
this.#currentCursorY = window.innerHeight / 2;
|
|
354
|
+
this.#targetCursorX = this.#currentCursorX;
|
|
355
|
+
this.#targetCursorY = this.#currentCursorY;
|
|
356
|
+
this.#cursor.style.left = `${this.#currentCursorX}px`;
|
|
357
|
+
this.#cursor.style.top = `${this.#currentCursorY}px`;
|
|
358
|
+
}
|
|
359
|
+
hide() {
|
|
360
|
+
if (!this.shown || this.#disposed) return;
|
|
361
|
+
this.shown = false;
|
|
362
|
+
this.motion?.fadeOut();
|
|
363
|
+
this.motion?.pause();
|
|
364
|
+
this.#cursor.classList.remove(cursorStyles.clicking);
|
|
365
|
+
setTimeout(() => {
|
|
366
|
+
this.wrapper.classList.remove(styles.visible);
|
|
367
|
+
}, 800);
|
|
368
|
+
}
|
|
369
|
+
dispose() {
|
|
370
|
+
this.#disposed = true;
|
|
371
|
+
this.motion?.dispose();
|
|
372
|
+
this.wrapper.remove();
|
|
373
|
+
this.dispatchEvent(new Event("dispose"));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
export {
|
|
377
|
+
SimulatorMask
|
|
378
|
+
};
|
|
379
|
+
//# sourceMappingURL=SimulatorMask-DIoKZt1z.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SimulatorMask-DIoKZt1z.js","sources":["../../vendor/page-agent/packages/page-controller/src/mask/checkDarkMode.ts","../../vendor/page-agent/packages/page-controller/src/mask/SimulatorMask.ts"],"sourcesContent":["/**\n * A comprehensive function to determine if the page is currently in a dark theme.\n * Heuristic check. Only work for common patterns. Return false by default.\n */\nexport function isPageDark() {\n\ttry {\n\t\tif (hasDarkModeClass()) return true\n\t\tif (hasDarkModeDataAttribute()) return true\n\t\tif (isColorSchemeDark()) return true\n\t\tif (isBackgroundDark()) return true\n\t\tif (isMainContentBackgroundDark()) return true\n\t\tif (isTextColorLight()) return true\n\n\t\treturn false\n\t} catch (error) {\n\t\tconsole.warn('Error determining if page is dark:', error)\n\t\treturn false\n\t}\n}\n\n/**\n * Checks for common dark mode CSS classes on the html or body elements.\n */\nfunction hasDarkModeClass() {\n\tconst DEFAULT_DARK_MODE_CLASSES = ['dark', 'dark-mode', 'theme-dark', 'night', 'night-mode']\n\n\tconst htmlElement = document.documentElement\n\tconst bodyElement = document.body || document.documentElement // can be null in some cases\n\n\t// Check class names on <html> and <body>\n\tfor (const className of DEFAULT_DARK_MODE_CLASSES) {\n\t\tif (htmlElement.classList.contains(className) || bodyElement?.classList.contains(className)) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n/**\n * Some UI frameworks use data attributes to indicate theme\n */\nfunction hasDarkModeDataAttribute() {\n\tconst htmlElement = document.documentElement\n\tconst bodyElement = document.body || document.documentElement // can be null in some cases\n\n\tconst dataAttrs = ['data-theme', 'data-color-mode', 'data-bs-theme', 'data-mui-color-scheme']\n\tfor (const attr of dataAttrs) {\n\t\tconst bodyValue = bodyElement?.getAttribute(attr)\n\t\tconst htmlValue = htmlElement.getAttribute(attr)\n\n\t\tif (bodyValue?.toLowerCase() === 'dark' || htmlValue?.toLowerCase() === 'dark') {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n/**\n * Checks the CSS `color-scheme` property and `<meta name=\"color-scheme\">` tag.\n * Only \"dark\"/\"only dark\" counts as dark; \"light dark\" is ambiguous and ignored.\n */\nfunction isColorSchemeDark() {\n\t// Check <meta name=\"color-scheme\" content=\"dark\">\n\tconst meta = document.querySelector<HTMLMetaElement>('meta[name=\"color-scheme\"]')\n\tconst metaContent = meta?.content.toLowerCase()\n\tif (metaContent === 'dark' || metaContent === 'only dark') return true\n\n\t// Check the computed color-scheme CSS property on :root\n\tconst rootStyle = window.getComputedStyle(document.documentElement)\n\tconst colorScheme = rootStyle.getPropertyValue('color-scheme').trim().toLowerCase()\n\treturn colorScheme === 'dark' || colorScheme === 'only dark'\n}\n\n/**\n * Checks the background color of the body element to determine if the page is dark.\n */\nfunction isBackgroundDark() {\n\t// We check both <html> and <body> because some pages set the color on <html>\n\tconst htmlStyle = window.getComputedStyle(document.documentElement)\n\tconst bodyStyle = window.getComputedStyle(document.body || document.documentElement)\n\n\t// Get background colors\n\tconst htmlBgColor = htmlStyle.backgroundColor\n\tconst bodyBgColor = bodyStyle.backgroundColor\n\n\t// The body's background might be transparent, in which case we should\n\t// fall back to the html element's background.\n\tif (isColorDark(bodyBgColor)) {\n\t\treturn true\n\t} else if (bodyBgColor === 'transparent' || bodyBgColor.startsWith('rgba(0, 0, 0, 0)')) {\n\t\treturn isColorDark(htmlBgColor)\n\t}\n\n\treturn false\n}\n\n/**\n * Checks if the text color on the body is light, which implies a dark background.\n */\nfunction isTextColorLight() {\n\t/** Luminance (0-255) above which body text is considered light */\n\tconst LIGHT_TEXT_LUMINANCE = 200\n\n\tconst bodyStyle = window.getComputedStyle(document.body || document.documentElement)\n\tconst luminance = getLuminance(bodyStyle.color)\n\n\t// Light text has high luminance (e.g. white text on dark bg)\n\treturn luminance !== null && luminance > LIGHT_TEXT_LUMINANCE\n}\n\n/**\n * Checks the background color of major layout elements (#app, #root, etc.).\n * Many SPAs render into a container that may have its own dark background while\n * <body> remains transparent.\n */\nfunction isMainContentBackgroundDark() {\n\tconst { innerWidth: vw, innerHeight: vh } = window\n\tconst minArea = vw * vh * 0.5\n\n\tconst selectors = ['#app', '#root', '#__next']\n\tfor (const selector of selectors) {\n\t\tconst el = document.querySelector(selector)\n\t\tif (!el) continue\n\n\t\tconst rect = el.getBoundingClientRect()\n\t\tif (rect.width * rect.height < minArea) continue\n\n\t\tif (isColorDark(window.getComputedStyle(el).backgroundColor)) return true\n\t}\n\treturn false\n}\n\n// --- utils ---\n\n/**\n * Parses an RGB or RGBA color string and returns an object with r, g, b properties.\n * @param {string} colorString - e.g., \"rgb(34, 34, 34)\" or \"rgba(0, 0, 0, 0.5)\"\n * @returns {{r: number, g: number, b: number}|null}\n */\nfunction parseRgbColor(colorString: string) {\n\tconst rgbMatch = /rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/.exec(colorString)\n\tif (!rgbMatch) {\n\t\treturn null // Not a valid rgb/rgba string\n\t}\n\treturn {\n\t\tr: parseInt(rgbMatch[1]),\n\t\tg: parseInt(rgbMatch[2]),\n\t\tb: parseInt(rgbMatch[3]),\n\t}\n}\n\n/**\n * Calculates the perceived luminance (0-255) of a CSS color string.\n * @param {string} colorString - e.g., \"rgb(50, 50, 50)\" or \"rgba(0, 0, 0, 0.5)\"\n * @returns {number|null} - The luminance, or null if the color is transparent or unparseable.\n */\nfunction getLuminance(colorString: string): number | null {\n\tif (!colorString || colorString === 'transparent' || colorString.startsWith('rgba(0, 0, 0, 0)')) {\n\t\treturn null // Transparent has no meaningful luminance\n\t}\n\n\tconst rgb = parseRgbColor(colorString)\n\tif (!rgb) {\n\t\treturn null // Could not parse color\n\t}\n\n\t// Standard perceived luminance formula\n\treturn 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b\n}\n\n/**\n * Determines if a color is \"dark\" based on its calculated luminance.\n * @param {string} colorString - The CSS color string (e.g., \"rgb(50, 50, 50)\").\n * @param {number} threshold - A value between 0 and 255. Colors with luminance below this will be considered dark. Default is 128.\n */\nfunction isColorDark(colorString: string, threshold = 128) {\n\tconst luminance = getLuminance(colorString)\n\treturn luminance !== null && luminance < threshold\n}\n","import { Motion } from 'ai-motion'\n\nimport { isPageDark } from './checkDarkMode'\n\nimport styles from './SimulatorMask.module.css'\nimport cursorStyles from './cursor.module.css'\n\nexport class SimulatorMask extends EventTarget {\n\tshown: boolean = false\n\twrapper = document.createElement('div')\n\tmotion: Motion | null = null\n\n\t#disposed = false\n\n\t#cursor = document.createElement('div')\n\n\t#currentCursorX = 0\n\t#currentCursorY = 0\n\n\t#targetCursorX = 0\n\t#targetCursorY = 0\n\n\tconstructor() {\n\t\tsuper()\n\n\t\tthis.wrapper.id = 'page-agent-runtime_simulator-mask'\n\t\tthis.wrapper.className = styles.wrapper\n\t\tthis.wrapper.setAttribute('data-browser-use-ignore', 'true')\n\t\tthis.wrapper.setAttribute('data-page-agent-ignore', 'true')\n\n\t\ttry {\n\t\t\tconst motion = new Motion({\n\t\t\t\tmode: isPageDark() ? 'dark' : 'light',\n\t\t\t\tstyles: { position: 'absolute', inset: '0' },\n\t\t\t})\n\t\t\tthis.motion = motion\n\t\t\tthis.wrapper.appendChild(motion.element)\n\t\t\tmotion.autoResize(this.wrapper)\n\t\t} catch (e) {\n\t\t\tconsole.warn('[SimulatorMask] Motion overlay unavailable:', e)\n\t\t}\n\n\t\t// Capture all mouse, keyboard, and wheel events\n\t\tthis.wrapper.addEventListener('click', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('mousedown', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('mouseup', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('mousemove', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('wheel', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('keydown', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\t\tthis.wrapper.addEventListener('keyup', (e) => {\n\t\t\te.stopPropagation()\n\t\t\te.preventDefault()\n\t\t})\n\n\t\t// Create AI cursor\n\t\tthis.#createCursor()\n\t\t// this.show()\n\n\t\tdocument.body.appendChild(this.wrapper)\n\n\t\tthis.#moveCursorToTarget()\n\n\t\t// global events\n\t\t// @note Mask should be isolated from the rest of the code.\n\t\t// Global events are easier to manage and cleanup.\n\n\t\tconst movePointerToListener = (event: Event) => {\n\t\t\tconst { x, y } = (event as CustomEvent).detail\n\t\t\tthis.setCursorPosition(x, y)\n\t\t}\n\t\tconst clickPointerListener = () => {\n\t\t\tthis.triggerClickAnimation()\n\t\t}\n\t\tconst enablePassThroughListener = () => {\n\t\t\tthis.wrapper.style.pointerEvents = 'none'\n\t\t}\n\t\tconst disablePassThroughListener = () => {\n\t\t\tthis.wrapper.style.pointerEvents = 'auto'\n\t\t}\n\n\t\twindow.addEventListener('PageAgent::MovePointerTo', movePointerToListener)\n\t\twindow.addEventListener('PageAgent::ClickPointer', clickPointerListener)\n\t\twindow.addEventListener('PageAgent::EnablePassThrough', enablePassThroughListener)\n\t\twindow.addEventListener('PageAgent::DisablePassThrough', disablePassThroughListener)\n\n\t\tthis.addEventListener('dispose', () => {\n\t\t\twindow.removeEventListener('PageAgent::MovePointerTo', movePointerToListener)\n\t\t\twindow.removeEventListener('PageAgent::ClickPointer', clickPointerListener)\n\t\t\twindow.removeEventListener('PageAgent::EnablePassThrough', enablePassThroughListener)\n\t\t\twindow.removeEventListener('PageAgent::DisablePassThrough', disablePassThroughListener)\n\t\t})\n\t}\n\n\t#createCursor() {\n\t\tthis.#cursor.className = cursorStyles.cursor\n\n\t\t// Create ripple effect container\n\t\tconst rippleContainer = document.createElement('div')\n\t\trippleContainer.className = cursorStyles.cursorRipple\n\t\tthis.#cursor.appendChild(rippleContainer)\n\n\t\t// Create filling layer\n\t\tconst fillingLayer = document.createElement('div')\n\t\tfillingLayer.className = cursorStyles.cursorFilling\n\t\tthis.#cursor.appendChild(fillingLayer)\n\n\t\t// Create border layer\n\t\tconst borderLayer = document.createElement('div')\n\t\tborderLayer.className = cursorStyles.cursorBorder\n\t\tthis.#cursor.appendChild(borderLayer)\n\n\t\tthis.wrapper.appendChild(this.#cursor)\n\t}\n\n\t#moveCursorToTarget() {\n\t\tif (this.#disposed) return\n\n\t\tconst newX = this.#currentCursorX + (this.#targetCursorX - this.#currentCursorX) * 0.2\n\t\tconst newY = this.#currentCursorY + (this.#targetCursorY - this.#currentCursorY) * 0.2\n\n\t\tconst xDistance = Math.abs(newX - this.#targetCursorX)\n\t\tif (xDistance > 0) {\n\t\t\tif (xDistance < 2) {\n\t\t\t\tthis.#currentCursorX = this.#targetCursorX\n\t\t\t} else {\n\t\t\t\tthis.#currentCursorX = newX\n\t\t\t}\n\t\t\tthis.#cursor.style.left = `${this.#currentCursorX}px`\n\t\t}\n\n\t\tconst yDistance = Math.abs(newY - this.#targetCursorY)\n\t\tif (yDistance > 0) {\n\t\t\tif (yDistance < 2) {\n\t\t\t\tthis.#currentCursorY = this.#targetCursorY\n\t\t\t} else {\n\t\t\t\tthis.#currentCursorY = newY\n\t\t\t}\n\t\t\tthis.#cursor.style.top = `${this.#currentCursorY}px`\n\t\t}\n\n\t\trequestAnimationFrame(() => this.#moveCursorToTarget())\n\t}\n\n\tsetCursorPosition(x: number, y: number) {\n\t\tif (this.#disposed) return\n\n\t\tthis.#targetCursorX = x\n\t\tthis.#targetCursorY = y\n\t}\n\n\ttriggerClickAnimation() {\n\t\tif (this.#disposed) return\n\n\t\tthis.#cursor.classList.remove(cursorStyles.clicking)\n\t\t// Force reflow to restart animation\n\t\tthis.#cursor.getBoundingClientRect()\n\t\tthis.#cursor.classList.add(cursorStyles.clicking)\n\t}\n\n\tshow() {\n\t\tif (this.shown || this.#disposed) return\n\n\t\tthis.shown = true\n\t\tthis.motion?.start()\n\t\tthis.motion?.fadeIn()\n\n\t\tthis.wrapper.classList.add(styles.visible)\n\n\t\t// Initialize cursor position\n\t\tthis.#currentCursorX = window.innerWidth / 2\n\t\tthis.#currentCursorY = window.innerHeight / 2\n\t\tthis.#targetCursorX = this.#currentCursorX\n\t\tthis.#targetCursorY = this.#currentCursorY\n\t\tthis.#cursor.style.left = `${this.#currentCursorX}px`\n\t\tthis.#cursor.style.top = `${this.#currentCursorY}px`\n\t}\n\n\thide() {\n\t\tif (!this.shown || this.#disposed) return\n\n\t\tthis.shown = false\n\t\tthis.motion?.fadeOut()\n\t\tthis.motion?.pause()\n\n\t\tthis.#cursor.classList.remove(cursorStyles.clicking)\n\n\t\tsetTimeout(() => {\n\t\t\tthis.wrapper.classList.remove(styles.visible)\n\t\t}, 800) // Match the animation duration\n\t}\n\n\tdispose() {\n\t\tthis.#disposed = true\n\t\tthis.motion?.dispose()\n\t\tthis.wrapper.remove()\n\t\tthis.dispatchEvent(new Event('dispose'))\n\t}\n}\n"],"names":[],"mappings":"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"}
|