primocode 8.32.0 → 8.33.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/app/app.css CHANGED
@@ -27,7 +27,34 @@
27
27
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Mono", Menlo,
28
28
  Consolas, "Liberation Mono", monospace;
29
29
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
30
+
31
+ /* A barra lateral e o diálogo de configurações. Nomes novos, mesma
32
+ família de cores — nada aqui inventa cor: tudo sai das de cima. */
33
+ --barra: #0b1119;
34
+ --alto: #16202c;
35
+ --largura-lado: 262px;
36
+ }
37
+
38
+ /* Tema claro. Só os TOKENS mudam; nenhuma regra abaixo sabe que ele existe,
39
+ e é isso que faz o tema inteiro caber em vinte linhas. */
40
+ html[data-tema="claro"] {
41
+ --azul: #0e7490;
42
+ --azul-fundo: #0891b2;
43
+ --texto: #0f1922;
44
+ --fraco: #46596b;
45
+ --apagado: #7b8fa1;
46
+ --fundo: #f4f7f9;
47
+ --painel: #ffffff;
48
+ --borda: #d5e0e7;
49
+ --borda-viva: #b7c8d3;
50
+ --barra: #eef3f6;
51
+ --alto: #e3ebf0;
30
52
  }
53
+ /* A cor de acento escolhida nas configurações. */
54
+ html[data-cor="verde"] { --azul:#34d399; --azul-fundo:#10b981; }
55
+ html[data-cor="roxo"] { --azul:#a78bfa; --azul-fundo:#8b5cf6; }
56
+ html[data-cor="ambar"] { --azul:#fbbf24; --azul-fundo:#d97706; }
57
+ html[data-cor="rosa"] { --azul:#fb7185; --azul-fundo:#e11d48; }
31
58
 
32
59
  * { box-sizing: border-box; }
33
60
 
@@ -48,70 +75,170 @@ html, body {
48
75
  }
49
76
 
50
77
  body {
51
- display: grid;
52
- grid-template-rows: auto 1fr auto;
53
78
  /* O brilho azul atrás de tudo. Um só, bem aberto e bem fraco: dá
54
79
  profundidade sem virar papel de parede. */
55
80
  background-image:
56
81
  radial-gradient(120% 80% at 50% -20%, rgba(34, 211, 238, .08), transparent 62%);
57
82
  }
58
83
 
59
- /* ── topo ───────────────────────────────────────────────────────────────── */
60
-
61
- #topo {
62
- display: flex; align-items: center; justify-content: space-between;
63
- gap: 16px; padding: 11px 18px;
64
- border-bottom: 1px solid var(--borda);
65
- background: rgba(10, 15, 22, .72);
66
- backdrop-filter: blur(14px);
67
- /* Na janela do Chrome em modo app não há barra de título: esta faixa é a
68
- barra, e precisa poder arrastar a janela. */
84
+ /* ── a mesma planta da página /code do Conecta ──────────────────────────
85
+ Barra lateral fixa à esquerda, conversa ocupando o resto. As duas telas
86
+ são o mesmo produto e têm de ser reconhecíveis uma na outra no primeiro
87
+ olhar — quem sai do terminal para a janela ou do celular para a janela
88
+ não pode ter de reaprender onde ficam as coisas. */
89
+ .tela { display: flex; height: 100%; }
90
+ .lado {
91
+ width: var(--largura-lado); flex: 0 0 var(--largura-lado);
92
+ background: var(--barra); border-right: 1px solid var(--borda);
93
+ display: flex; flex-direction: column; min-height: 0;
69
94
  -webkit-app-region: drag;
70
- user-select: none;
71
95
  }
72
- #topo button, #topo .chip { -webkit-app-region: no-drag; }
96
+ .lado button, .lado .chip { -webkit-app-region: no-drag; }
97
+ .lado-topo { padding: 14px 12px 10px; display: flex; flex-direction: column; gap: 12px; }
98
+ .marca-linha { display: flex; align-items: center; gap: 9px; padding: 0 4px; }
99
+ .marca-linha .nome { font-weight: 620; letter-spacing: -.01em; }
100
+ .marca-linha .versao { margin-left: auto; font-family: var(--mono); font-size: 11px; color: var(--apagado); }
73
101
 
74
- .marca { display: flex; align-items: center; gap: 9px; }
75
- .logo { width: 19px; height: 19px; color: var(--azul); flex: 0 0 auto; }
76
- .nome { font-weight: 640; letter-spacing: -.012em; }
77
- .versao { color: var(--apagado); font-size: 11.5px; font-family: var(--mono); }
102
+ .novo {
103
+ display: flex; align-items: center; gap: 9px; width: 100%;
104
+ padding: 9px 12px; border-radius: 9px; cursor: pointer;
105
+ background: var(--alto); border: 1px solid var(--borda-viva);
106
+ color: inherit; font: inherit; font-size: 13.5px; font-weight: 500;
107
+ transition: border-color .15s;
108
+ }
109
+ .novo:hover { border-color: var(--azul); }
78
110
 
79
- .estado { display: flex; gap: 7px; align-items: center; min-width: 0; }
111
+ .rotulo { padding: 14px 16px 6px; font-size: 11px; letter-spacing: .08em;
112
+ text-transform: uppercase; color: var(--apagado); }
113
+ .recentes { flex: 1 1 0; min-height: 0; overflow-y: auto; padding: 0 8px 8px; }
114
+ .item {
115
+ display: block; width: 100%; text-align: left; padding: 8px 10px; cursor: pointer;
116
+ border: 0; background: none; color: var(--fraco); font: inherit; font-size: 13px;
117
+ border-radius: 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
118
+ }
119
+ .item:hover { background: var(--alto); color: var(--texto); }
120
+ .item[aria-current="true"] { background: var(--alto); color: var(--texto); font-weight: 500; }
121
+ .item .quando { display: block; font-size: 11px; color: var(--apagado); font-family: var(--mono); }
122
+ .vazio-lista { padding: 6px 12px; font-size: 12.5px; color: var(--apagado); line-height: 1.5; }
123
+ .vazio-lista code { font-family: var(--mono); color: var(--azul); }
124
+
125
+ .lado-pe { border-top: 1px solid var(--borda); padding: 8px; display: flex; flex-direction: column; gap: 6px; }
126
+ .pe-btn {
127
+ display: flex; align-items: center; gap: 9px; padding: 8px 10px; cursor: pointer;
128
+ border: 0; background: none; color: var(--fraco); font: inherit; font-size: 13.5px;
129
+ border-radius: 8px; width: 100%; text-align: left;
130
+ }
131
+ .pe-btn:hover { background: var(--alto); color: var(--texto); }
132
+ .chips { display: flex; flex-wrap: wrap; gap: 5px; padding: 0 4px 2px; }
80
133
  .chip {
81
- font-family: var(--mono); font-size: 11.5px;
82
- color: var(--fraco); background: var(--painel);
83
- border: 1px solid var(--borda); border-radius: 999px;
84
- padding: 3px 10px; white-space: nowrap;
85
- max-width: 300px; overflow: hidden; text-overflow: ellipsis;
134
+ font-family: var(--mono); font-size: 11px; color: var(--fraco);
135
+ background: var(--alto); border: 1px solid var(--borda);
136
+ border-radius: 6px; padding: 2px 7px;
137
+ max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
86
138
  }
87
139
  .chip:empty { display: none; }
88
- #modo { color: var(--azul); border-color: var(--borda-viva); }
89
140
 
90
- /* ── conversa ───────────────────────────────────────────────────────────── */
141
+ .conversa { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
142
+ .rolo { flex: 1 1 0; overflow-y: auto; scroll-behavior: smooth; }
143
+ .rolo::-webkit-scrollbar { width: 10px; }
144
+ .rolo::-webkit-scrollbar-thumb { background: #1c2836; border-radius: 8px; border: 3px solid var(--fundo); }
145
+ .tira { max-width: 860px; margin: 0 auto; padding: 26px 22px 40px; }
91
146
 
92
- #rolo { overflow-y: auto; scroll-behavior: smooth; }
93
- #rolo::-webkit-scrollbar { width: 10px; }
94
- #rolo::-webkit-scrollbar-thumb { background: #1c2836; border-radius: 8px; border: 3px solid var(--fundo); }
95
-
96
- #conversa { max-width: 860px; margin: 0 auto; padding: 26px 22px 40px; }
97
-
98
- .boas-vindas { padding: 12vh 0 0; text-align: center; }
99
- .boas-vindas h1 {
100
- font-size: 27px; font-weight: 620; letter-spacing: -.025em; margin: 0 0 10px;
101
- }
102
- .boas-vindas p {
103
- color: var(--fraco); max-width: 460px; margin: 0 auto 26px; line-height: 1.6;
104
- }
105
- .atalhos { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
106
- .atalhos button {
147
+ .vazio { display: flex; flex-direction: column; align-items: center; gap: 14px;
148
+ padding: 9vh 22px 0; text-align: center; }
149
+ .vazio h1 { font-size: 26px; font-weight: 620; letter-spacing: -.025em; margin: 4px 0 0; }
150
+ .vazio p { color: var(--fraco); max-width: 460px; margin: 0; line-height: 1.6; }
151
+ .sugestoes { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 6px; }
152
+ .sugestao {
107
153
  font: inherit; font-size: 13px; color: var(--fraco); cursor: pointer;
108
154
  background: var(--painel); border: 1px solid var(--borda);
109
155
  border-radius: 999px; padding: 7px 15px;
110
156
  transition: border-color .16s, color .16s, transform .16s;
111
157
  }
112
- .atalhos button:hover {
113
- color: var(--texto); border-color: var(--borda-viva); transform: translateY(-1px);
158
+ .sugestao:hover { color: var(--texto); border-color: var(--azul); transform: translateY(-1px); }
159
+
160
+ /* "trabalhando", com o ponto que pulsa. Fica ACIMA do campo, para não
161
+ empurrar o texto que a pessoa está digitando. */
162
+ #pensando {
163
+ max-width: 860px; margin: 0 auto 9px; display: flex; align-items: center; gap: 8px;
164
+ font-family: var(--mono); font-size: 12px; color: var(--apagado);
165
+ }
166
+ .pulso {
167
+ width: 8px; height: 8px; border-radius: 50%; background: var(--azul);
168
+ box-shadow: 0 0 0 0 rgba(34, 211, 238, .55);
169
+ }
170
+ @media (prefers-reduced-motion: no-preference) {
171
+ .pulso { animation: bater 1.4s ease-out infinite; }
172
+ @keyframes bater {
173
+ to { box-shadow: 0 0 0 9px rgba(34, 211, 238, 0); }
174
+ }
175
+ }
176
+ .dica {
177
+ max-width: 860px; margin: 8px auto 0;
178
+ color: var(--apagado); font-size: 11.5px; text-align: center;
179
+ }
180
+
181
+ .rodape { border-top: 1px solid var(--borda); background: rgba(10, 15, 22, .82);
182
+ backdrop-filter: blur(14px); padding: 12px 22px 14px; }
183
+ html[data-tema="claro"] .rodape { background: rgba(255, 255, 255, .85); }
184
+ .caixa { max-width: 860px; margin: 0 auto; display: flex; align-items: flex-end; gap: 10px; }
185
+ .campo-volta {
186
+ flex: 1 1 auto; display: flex; align-items: flex-end; gap: 8px;
187
+ background: var(--painel); border: 1px solid var(--borda-viva); border-radius: 12px;
188
+ padding: 9px 12px; transition: border-color .15s;
189
+ }
190
+ .campo-volta:focus-within { border-color: var(--azul); }
191
+ .chevron { font-family: var(--mono); color: var(--azul); font-weight: 600; padding-bottom: 1px; }
192
+ /* O textarea entrega a moldura para a `.campo-volta`. Sem isto ele desenha a
193
+ caixa DELE dentro da caixa de fora — duas bordas, uma dentro da outra. */
194
+ #campo {
195
+ flex: 1 1 auto; background: none; border: 0; outline: none; color: inherit;
196
+ resize: none; font-family: var(--mono); font-size: 13.5px; line-height: 1.6;
197
+ max-height: 190px; padding: 0;
198
+ }
199
+ #campo::placeholder { color: var(--apagado); }
200
+ .enviar {
201
+ flex: 0 0 auto; width: 34px; height: 34px; border-radius: 10px; cursor: pointer;
202
+ display: grid; place-items: center; border: 0;
203
+ background: var(--azul); color: #04252c; transition: background .16s, opacity .16s;
204
+ }
205
+ html[data-tema="claro"] .enviar { color: #fff; }
206
+ .enviar:disabled { background: #16323d; color: #3f6472; cursor: default; }
207
+ .enviar.parar { background: rgba(251, 113, 133, .14); color: var(--falha); }
208
+
209
+ /* ── configurações ──────────────────────────────────────────────────── */
210
+ dialog {
211
+ border: 1px solid var(--borda-viva); border-radius: 14px; padding: 0;
212
+ background: var(--painel); color: var(--texto); max-width: 440px; width: calc(100% - 32px);
213
+ }
214
+ dialog::backdrop { background: rgba(4, 8, 12, .62); }
215
+ .dlg-topo { display: flex; align-items: center; justify-content: space-between; gap: 12px;
216
+ padding: 14px 17px; border-bottom: 1px solid var(--borda); }
217
+ .dlg-topo h2 { margin: 0; font-size: 15.5px; font-weight: 620; }
218
+ .dlg-corpo { padding: 15px 17px 19px; display: flex; flex-direction: column; gap: 19px; }
219
+ .grupo > h3 { margin: 0 0 9px; font-size: 11px; letter-spacing: .08em;
220
+ text-transform: uppercase; color: var(--apagado); font-weight: 500; }
221
+ .opcoes { display: flex; gap: 8px; flex-wrap: wrap; }
222
+ .opcao, .copiar {
223
+ padding: 7px 14px; border-radius: 8px; cursor: pointer; font: inherit; font-size: 13px;
224
+ border: 1px solid var(--borda-viva); background: none; color: var(--fraco);
114
225
  }
226
+ .opcao[aria-pressed="true"] { border-color: var(--azul); color: var(--texto); }
227
+ .copiar { padding: 4px 10px; font-size: 12px; }
228
+ .cores { display: flex; gap: 10px; }
229
+ .cor { width: 30px; height: 30px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; }
230
+ .cor[aria-pressed="true"] { border-color: var(--texto); }
231
+ .linha-info { display: flex; justify-content: space-between; gap: 12px; font-size: 13.5px; color: var(--fraco); }
232
+ .linha-info b { color: var(--texto); font-weight: 500; }
233
+ .dlg-corpo code { font-family: var(--mono); color: var(--azul); }
234
+
235
+ /* ── conversa ───────────────────────────────────────────────────────────── */
236
+
237
+ #rolo { overflow-y: auto; scroll-behavior: smooth; }
238
+ #rolo::-webkit-scrollbar { width: 10px; }
239
+ #rolo::-webkit-scrollbar-thumb { background: #1c2836; border-radius: 8px; border: 3px solid var(--fundo); }
240
+
241
+ #conversa { max-width: 860px; margin: 0 auto; padding: 26px 22px 40px; }
115
242
 
116
243
  /* O que o usuário disse: bolha à direita, curta, com a marca de lado. */
117
244
  .voce {
@@ -150,66 +277,6 @@ body {
150
277
  border: 1px solid rgba(251, 113, 133, .26); border-radius: 10px;
151
278
  }
152
279
 
153
- /* ── barra de baixo ─────────────────────────────────────────────────────── */
154
-
155
- #baixo {
156
- border-top: 1px solid var(--borda);
157
- background: rgba(10, 15, 22, .82);
158
- backdrop-filter: blur(14px);
159
- padding: 12px 22px 14px;
160
- }
161
-
162
- #pensando {
163
- max-width: 860px; margin: 0 auto 9px;
164
- display: flex; align-items: center; gap: 9px;
165
- color: var(--azul); font-family: var(--mono); font-size: 12.5px;
166
- }
167
- .pulso {
168
- width: 7px; height: 7px; border-radius: 50%; background: var(--azul);
169
- animation: bater 1.15s ease-in-out infinite;
170
- }
171
- @keyframes bater {
172
- 0%, 100% { opacity: .28; transform: scale(.72); }
173
- 50% { opacity: 1; transform: scale(1); }
174
- }
175
-
176
- #forma {
177
- max-width: 860px; margin: 0 auto;
178
- display: flex; align-items: flex-end; gap: 9px;
179
- background: var(--painel);
180
- border: 1px solid var(--borda);
181
- border-radius: 15px; padding: 8px 8px 8px 15px;
182
- transition: border-color .16s, box-shadow .16s;
183
- }
184
- #forma:focus-within {
185
- border-color: rgba(34, 211, 238, .45);
186
- box-shadow: 0 0 0 3px rgba(34, 211, 238, .09);
187
- }
188
-
189
- #campo {
190
- flex: 1; font: inherit; font-size: 14.5px; line-height: 1.55;
191
- color: var(--texto); background: none; border: 0; outline: 0;
192
- resize: none; max-height: 44vh; padding: 6px 0;
193
- }
194
- #campo::placeholder { color: var(--apagado); }
195
-
196
- #forma button {
197
- flex: 0 0 auto; width: 34px; height: 34px; border-radius: 10px;
198
- display: grid; place-items: center; cursor: pointer; border: 0;
199
- transition: background .16s, color .16s, opacity .16s;
200
- }
201
- #forma button svg { width: 18px; height: 18px; }
202
- #mandar { background: var(--azul); color: #04252c; }
203
- #mandar:hover { background: #67e8f9; }
204
- #mandar:disabled { background: #16323d; color: #3f6472; cursor: default; }
205
- #parar { background: rgba(251, 113, 133, .14); color: var(--falha); }
206
- #parar:hover { background: rgba(251, 113, 133, .24); }
207
-
208
- .dica {
209
- max-width: 860px; margin: 8px auto 0;
210
- color: var(--apagado); font-size: 11.5px; text-align: center;
211
- }
212
-
213
280
  /* ── arrastar arquivo ───────────────────────────────────────────────────── */
214
281
 
215
282
  #alvo-solto {
@@ -224,7 +291,9 @@ body.soltando #alvo-solto { display: grid; }
224
291
  }
225
292
  #alvo-solto span { color: var(--fraco); font-size: 13px; }
226
293
 
227
- @media (max-width: 620px) {
228
- .estado #pasta { display: none; }
229
- #conversa, #forma, #pensando, .dica { max-width: none; }
294
+ @media (max-width: 760px) {
295
+ /* Janela estreita: a barra lateral sai da frente. O botão de novo chat e o
296
+ histórico continuam alcançáveis pelo terminal (/rc de novo, /conversas). */
297
+ .lado { display: none; }
298
+ .tira, .caixa { max-width: none; }
230
299
  }
package/app/app.js CHANGED
@@ -51,8 +51,20 @@
51
51
  }
52
52
 
53
53
  function limparBoasVindas() {
54
- var b = conversa.querySelector(".boas-vindas");
55
- if (b) b.remove();
54
+ var b = document.getElementById("vazio");
55
+ if (b) b.hidden = true;
56
+ // A tira das mensagens nasce escondida para a tela vazia poder ocupar o
57
+ // meio. Esconder uma sem mostrar a outra deixaria a janela em branco na
58
+ // primeira mensagem — e nada na tela é o pior jeito de responder.
59
+ var t = document.getElementById("conversa");
60
+ if (t) t.hidden = false;
61
+ }
62
+
63
+ function voltarAoVazio() {
64
+ var b = document.getElementById("vazio");
65
+ var t = document.getElementById("conversa");
66
+ if (t) { t.innerHTML = ""; t.hidden = true; }
67
+ if (b) b.hidden = false;
56
68
  }
57
69
 
58
70
  /* ── ANSI → HTML ───────────────────────────────────────────────────────
@@ -162,7 +174,10 @@
162
174
  mandar.hidden = ocupado;
163
175
  campo.placeholder = ocupado
164
176
  ? "ele está trabalhando — pode ir escrevendo o próximo"
165
- : "Peça alguma coisa… (Enter manda, Shift+Enter quebra linha)";
177
+ // O "Enter manda, Shift+Enter quebra linha" saiu daqui: ele já está
178
+ // escrito na dica logo abaixo do campo, e dizer a mesma coisa duas
179
+ // vezes na mesma tela faz a pessoa ler nenhuma das duas.
180
+ : "Peça alguma coisa…";
166
181
  }
167
182
 
168
183
  /* ── o fluxo ───────────────────────────────────────────────────────────
@@ -218,7 +233,7 @@
218
233
  }
219
234
  });
220
235
 
221
- document.querySelectorAll(".atalhos button").forEach(function (b) {
236
+ document.querySelectorAll(".sugestao").forEach(function (b) {
222
237
  b.addEventListener("click", function () { enviar(b.dataset.manda); });
223
238
  });
224
239
 
@@ -266,6 +281,158 @@
266
281
  ajustarAltura();
267
282
  });
268
283
 
284
+ /* ── a barra lateral ─────────────────────────────────────────────────
285
+ Mascote, novo chat, histórico e configurações — a mesma coisa da página
286
+ /code do Conecta, e de propósito: quem sai de uma tela para a outra não
287
+ pode ter de reaprender onde ficam as coisas. */
288
+
289
+ var CHAVE_TEMA = "primocode_tema";
290
+ var CHAVE_COR = "primocode_cor";
291
+ var pego = function (k, p) { try { return localStorage.getItem(k) || p; } catch (e) { return p; } };
292
+ var guardo = function (k, v) { try { localStorage.setItem(k, v); } catch (e) {} };
293
+
294
+ function marcarEscolha(caixaId, atributo, valor) {
295
+ var caixa = document.getElementById(caixaId);
296
+ if (!caixa) return;
297
+ Array.prototype.forEach.call(caixa.children, function (b) {
298
+ b.setAttribute("aria-pressed", b.dataset[atributo] === valor ? "true" : "false");
299
+ });
300
+ }
301
+ function aplicarTema(qual) {
302
+ // "sistema" não é um terceiro tema: é a ausência de escolha. Guardar
303
+ // "escuro" quando a pessoa quer seguir o aparelho faria a janela ignorar
304
+ // o modo noturno para sempre, sem ela ter como saber por quê.
305
+ var raiz = document.documentElement;
306
+ if (qual === "sistema") {
307
+ raiz.setAttribute("data-tema",
308
+ window.matchMedia("(prefers-color-scheme: dark)").matches ? "escuro" : "claro");
309
+ } else {
310
+ raiz.setAttribute("data-tema", qual);
311
+ }
312
+ marcarEscolha("temas", "tema", qual);
313
+ }
314
+ function aplicarCor(cor) {
315
+ document.documentElement.setAttribute("data-cor", cor);
316
+ marcarEscolha("cores", "cor", cor);
317
+ }
318
+
319
+ function desenharMarca() {
320
+ if (!window.PrimoMarca) return;
321
+ var t = document.getElementById("marcaTopo");
322
+ var g = document.getElementById("marcaGrande");
323
+ if (t) t.innerHTML = window.PrimoMarca.svg({ tamanho: 22 });
324
+ if (g) g.innerHTML = window.PrimoMarca.svg({ tamanho: 82, rotulo: "PrimoCode" });
325
+ }
326
+
327
+ function quando(iso) {
328
+ if (!iso) return "";
329
+ var d = new Date(iso);
330
+ if (isNaN(d)) return "";
331
+ var dias = Math.floor((Date.now() - d.getTime()) / 86400000);
332
+ if (dias <= 0) return d.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
333
+ if (dias === 1) return "ontem";
334
+ if (dias < 7) return dias + " dias";
335
+ return d.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit" });
336
+ }
337
+
338
+ var conversaAberta = null;
339
+
340
+ function carregarConversas() {
341
+ var caixa = document.getElementById("recentes");
342
+ if (!caixa) return;
343
+ pedir("/api/conversas").then(function (r) { return r.json(); }).then(function (d) {
344
+ if (!d || !d.ok || !d.conversas || !d.conversas.length) return; // deixa o texto de vazio
345
+ caixa.innerHTML = "";
346
+ d.conversas.forEach(function (c) {
347
+ var b = document.createElement("button");
348
+ b.className = "item";
349
+ b.setAttribute("aria-current", c.id === conversaAberta ? "true" : "false");
350
+ var t = document.createElement("span");
351
+ // textContent, nunca innerHTML: o título vem cifrado do banco e foi
352
+ // escrito por gente — um "<img onerror>" ali viraria script na janela.
353
+ t.textContent = c.titulo || "Conversa";
354
+ var q = document.createElement("span");
355
+ q.className = "quando";
356
+ q.textContent = quando(c.atualizada) + (c.mensagens ? " · " + c.mensagens + " msg" : "");
357
+ b.appendChild(t); b.appendChild(q);
358
+ b.onclick = function () { abrirConversa(c.id); };
359
+ caixa.appendChild(b);
360
+ });
361
+ }).catch(function () { /* sem conta: fica o texto de vazio */ });
362
+ }
363
+
364
+ function abrirConversa(id) {
365
+ conversaAberta = id;
366
+ pedir("/api/conversa/" + encodeURIComponent(id)).then(function (r) { return r.json(); })
367
+ .then(function (d) {
368
+ if (!d || !d.ok) return;
369
+ voltarAoVazio();
370
+ limparBoasVindas();
371
+ var tira = document.getElementById("conversa");
372
+ (d.mensagens || []).forEach(function (m) {
373
+ var linha = document.createElement("div");
374
+ if (m.papel === "user") {
375
+ linha.className = "voce";
376
+ var dentro = document.createElement("div");
377
+ dentro.textContent = m.texto;
378
+ linha.appendChild(dentro);
379
+ } else {
380
+ linha.className = "saida";
381
+ linha.textContent = m.texto;
382
+ }
383
+ tira.appendChild(linha);
384
+ });
385
+ rolo.scrollTop = rolo.scrollHeight;
386
+ carregarConversas();
387
+ }).catch(function () {});
388
+ }
389
+
390
+ function ligarLado() {
391
+ var novo = document.getElementById("btnNovo");
392
+ if (novo) novo.onclick = function () {
393
+ conversaAberta = null;
394
+ voltarAoVazio();
395
+ carregarConversas();
396
+ campo.focus();
397
+ };
398
+
399
+ var dlg = document.getElementById("dlgConfig");
400
+ var abrir = document.getElementById("btnConfig");
401
+ var fechar = document.getElementById("fecharConfig");
402
+ if (abrir && dlg) abrir.onclick = function () {
403
+ // Os três valores vêm do estado que o terminal já manda; repetir a
404
+ // consulta aqui só para abrir um diálogo seria uma ida à toa.
405
+ ["pasta", "modo", "esforco"].forEach(function (k) {
406
+ var de = document.getElementById(k);
407
+ var para = document.getElementById(k + "Config");
408
+ if (de && para) para.textContent = de.textContent || "—";
409
+ });
410
+ dlg.showModal();
411
+ };
412
+ if (fechar && dlg) fechar.onclick = function () { dlg.close(); };
413
+
414
+ var temas = document.getElementById("temas");
415
+ if (temas) temas.onclick = function (ev) {
416
+ var b = ev.target.closest("[data-tema]"); if (!b) return;
417
+ guardo(CHAVE_TEMA, b.dataset.tema); aplicarTema(b.dataset.tema);
418
+ };
419
+ var cores = document.getElementById("cores");
420
+ if (cores) cores.onclick = function (ev) {
421
+ var b = ev.target.closest("[data-cor]"); if (!b) return;
422
+ guardo(CHAVE_COR, b.dataset.cor); aplicarCor(b.dataset.cor);
423
+ };
424
+
425
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
426
+ if (pego(CHAVE_TEMA, "escuro") === "sistema") aplicarTema("sistema");
427
+ });
428
+ }
429
+
430
+ desenharMarca();
431
+ aplicarTema(pego(CHAVE_TEMA, "escuro"));
432
+ aplicarCor(pego(CHAVE_COR, "ciano"));
433
+ ligarLado();
434
+ carregarConversas();
435
+
269
436
  pedir("/api/estado").then(function (r) { return r.json(); }).then(pintarEstado)
270
437
  .catch(function () {});
271
438
  ligar();