primocode 8.16.0 → 8.18.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.
Files changed (2) hide show
  1. package/lib/effort.js +179 -34
  2. package/package.json +1 -1
package/lib/effort.js CHANGED
@@ -132,29 +132,100 @@ function layoutEffort(idx) {
132
132
  // leem como listras — e listra clara demais apaga o texto branco por cima.
133
133
  // O fundo tem de parecer um pano roxo respirando atrás do painel, não uma
134
134
  // bandeira. Os tons claros ficam de fora por isso.
135
- const ROXOS = [54, 55, 56, 57, 57, 93, 93, 99, 98, 92, 56, 55];
135
+ // A rampa do plasma, do fundo frio ao branco quente.
136
+ //
137
+ // Ela é LONGA de propósito: o degradê é desenhado em meia-célula (▀), então
138
+ // há o dobro de linhas para atravessar, e uma rampa curta viraria degrau
139
+ // visível. Começa quase preta e termina em branco — é a amplitude que faz o
140
+ // pico parecer luz, e não só "roxo mais claro".
141
+ // A rampa é a paleta do PrimoCode, não uma cor qualquer.
142
+ //
143
+ // O plasma era roxo — bonito, e de outro programa. A marca do CLI é o ciano
144
+ // (brand 45, o cyan-400 do design system; brandDim 31 é a mesma cor
145
+ // recuando), e é dele que a rampa sai: do teal profundo ao branco, passando
146
+ // exatamente pelo 45 no meio do caminho. Assim o pico da onda é a cor da
147
+ // marca acesa, e não um roxo emprestado.
148
+ const RAMPA = [23, 23, 24, 30, 31, 37, 38, 44, 45, 51, 87, 123, 159, 195, 231];
149
+
150
+ // Mantido pelo nome antigo porque o resto do arquivo (e o teste) já o
151
+ // conhece; o conteúdo é que mudou de família.
152
+ const ROXOS = RAMPA;
153
+
154
+ // Quanto o plasma é amortecido nas linhas que têm texto.
155
+ //
156
+ // Baixou de 0.5 para 0.26: "os textos devem ser extremamente legíveis". Com
157
+ // 0.5 a linha ainda chegava ao ciano médio, e ciano claro atrás de texto
158
+ // claro é leitura no limite. Em 0.26 a linha de texto vive no terço escuro
159
+ // da rampa — teal profundo — e o texto branco salta. O brilho não se perde:
160
+ // ele acontece nas faixas abertas, logo acima e abaixo, que é onde ninguém
161
+ // precisa ler.
162
+ const TETO_SOB_TEXTO = 0.26;
163
+
164
+ // E um teto absoluto, além da escala: por mais alto que o campo suba, atrás
165
+ // de texto ele não passa daqui. A escala sozinha não garante — bastava um
166
+ // pico forte para a linha clarear inteira.
167
+ const INDICE_MAX_TEXTO = 2; // até o 24 (#005f87): teal fundo, sempre
168
+ //
169
+ // O 2 não foi escolhido a olho. Medi o contraste de TODAS as células de
170
+ // texto ao longo de dezenas de quadros, pela fórmula de luminância relativa
171
+ // da WCAG. Com teto 4 o pior caso dava 4.08 — abaixo do mínimo AA (4.5),
172
+ // e o vilão era o índice 3 (#008787, teal médio) atrás dos traços da régua.
173
+ // Com teto 2 o pior caso passa de 7, que é o nível AAA. O teste refaz essa
174
+ // medida e reprova se algum dia cair.
175
+
176
+ /* O campo: o que decide a cor de cada meia-célula.
177
+ *
178
+ * Três coisas somadas, e é a soma que dá a sensação de energia em vez de
179
+ * listra:
180
+ *
181
+ * 1. uma ONDA DIAGONAL atravessando o painel, devagar;
182
+ * 2. um PULSO RADIAL saindo do próprio rótulo "ultracode" — é o detalhe
183
+ * que amarra a animação ao que ela significa: a potência irradia de
184
+ * onde você está;
185
+ * 3. uma segunda onda lenta, fora de fase, que impede o padrão de se
186
+ * repetir de forma óbvia.
187
+ *
188
+ * `x` é a coluna, `y` a meia-linha (duas por linha de texto), `foco` a
189
+ * coluna do rótulo selecionado.
190
+ */
191
+ function campoUltra(x, y, fase, foco, altura) {
192
+ // O terminal tem célula alta e estreita: sem corrigir, o pulso radial
193
+ // sairia como uma elipse achatada em vez de círculo.
194
+ const dx = (x - foco) * 0.5;
195
+ const dy = (y - altura / 2) * 1.1;
196
+ const raio = Math.sqrt(dx * dx + dy * dy);
197
+
198
+ const diagonal = Math.sin((x * 0.11) + (y * 0.30) + fase * 1.15);
199
+ const pulso = Math.sin(raio * 0.42 - fase * 2.1) * Math.exp(-raio * 0.035);
200
+ const lenta = Math.sin(x * 0.037 - fase * 0.5 + Math.cos(y * 0.19) * 0.8);
201
+
202
+ const v = diagonal * 0.42 + pulso * 0.78 + lenta * 0.34;
203
+ // O divisor maior que a amplitude puxa a média para baixo: o painel fica
204
+ // a maior parte do tempo em roxo fundo, e o branco vira PICO — evento,
205
+ // não estado. Centrado, o branco virava o normal e não impressionava
206
+ // mais nada.
207
+ return Math.max(0, Math.min(1, (v + 1.15) / 2.75));
208
+ }
209
+
210
+ // `escala` escurece a linha SEM achatá-la.
211
+ //
212
+ // A primeira versão usava Math.min(v, teto): tudo acima do teto virava a
213
+ // mesma cor, e as linhas de texto ganhavam platôs chapados — retângulos
214
+ // visíveis atravessando o plasma. Multiplicar preserva a forma da onda e só
215
+ // baixa a amplitude: a linha fica escura o bastante para ler e continua
216
+ // fazendo parte do mesmo pano.
217
+ function corDoCampo(v, escala, tetoIndice) {
218
+ const t = escala ? v * escala : v;
219
+ let i = Math.round(t * (RAMPA.length - 1));
220
+ if (tetoIndice != null) i = Math.min(i, tetoIndice);
221
+ return RAMPA[Math.max(0, Math.min(RAMPA.length - 1, i))];
222
+ }
136
223
 
224
+ // Guardada para quem já usava a onda antiga (e para o teste da rampa).
137
225
  function fundoRoxo(x, fase) {
138
- // Duas ondas de períodos diferentes: o encontro delas dá o desenho
139
- // irregular de faixas, em vez de listras repetidas e óbvias.
140
- //
141
- // As frequências caíram (0.55 → 0.13) porque as faixas tinham três
142
- // colunas de largura: de longe aquilo não era um fundo ondulando, era
143
- // chuviscado. Faixa larga anda devagar pela tela, que é o movimento que
144
- // se quer ver — o painel respira em vez de piscar.
145
- const faixa = x / 2;
146
- const onda = Math.sin(faixa * 0.13 + fase) * 0.62 + Math.sin(faixa * 0.041 - fase * 0.55) * 0.38;
147
- // A curva ³ empurra os valores para o MEIO da rampa: os extremos (o mais
148
- // escuro e o mais claro) só aparecem no pico da onda, e o painel passa a
149
- // maior parte do tempo num roxo médio, onde o texto branco se lê.
150
- const suave = Math.pow(onda, 3) * 0.55 + onda * 0.45;
151
- const i = Math.round((suave + 1) / 2 * (ROXOS.length - 1));
152
- return ROXOS[Math.max(0, Math.min(ROXOS.length - 1, i))];
226
+ return corDoCampo(campoUltra(x, 0, fase, 0, 2));
153
227
  }
154
228
 
155
- // Pinta a linha inteira (até a largura do painel) com a onda por baixo.
156
- // `destaque` é o trecho que fica branco e forte (o nível escolhido); o resto
157
- // da linha sai num lilás claro, para o escolhido saltar aos olhos.
158
229
  function pintarLinhaUltra(texto, largura, fase, opts = {}) {
159
230
  const base = opts.base || 231; // branco
160
231
  const apagado = opts.apagado || base;
@@ -179,25 +250,98 @@ function pintarLinhaUltra(texto, largura, fase, opts = {}) {
179
250
  return saida + '\x1b[0m';
180
251
  }
181
252
 
253
+ /* O painel do ultracode, desenhado em MEIA-CÉLULA.
254
+ *
255
+ * O caractere "▀" ocupa a metade de cima da célula. Pintando a frente dele
256
+ * com uma cor e o fundo com outra, cada linha do terminal carrega DUAS
257
+ * linhas de imagem. É o que separa um degradê em blocos de um degradê que
258
+ * parece luz — e é como demo de terminal sempre fez.
259
+ *
260
+ * Onde há letra, a célula deixa de ser meia e vira fundo liso: não dá para
261
+ * ter o glifo do texto e o do meio-bloco na mesma célula. Aí o fundo é a
262
+ * média das duas metades, limitada para o texto continuar legível.
263
+ */
264
+ function painelUltra(L, fase) {
265
+ const alturaMeia = L.linhas.length * 2;
266
+ const foco = (L.destaque.de + L.destaque.ate) / 2;
267
+ // Duas cores de texto, as duas claras. Sobre o teal escuro das linhas de
268
+ // texto, tanto o branco quanto o ciano-claro passam com folga — e a
269
+ // diferença entre eles ainda separa o que está escolhido do resto.
270
+ //
271
+ // O lilás de antes (189) não era da paleta e, sobre ciano, ficava sujo.
272
+ // TUDO branco. A hierarquia fica no negrito, não na cor.
273
+ //
274
+ // O secundário era 195 (ciano quase branco). Bonito, e o cálculo de
275
+ // contraste reprovou: sobre o teal mais claro que o fundo de texto pode
276
+ // ter, o 195 dá 6.57 — passa o mínimo AA (4.5) e não chega ao AAA (7.0).
277
+ // E não adiantava clarear um pouco: sobre aquele fundo, NENHUMA cor
278
+ // chega a 7 a não ser o branco puro, que dá 7.20. Como o pedido foi
279
+ // "extremamente legíveis", o branco ganhou e a diferença entre escolhido
280
+ // e resto passou a ser o peso da fonte — que separa igual e não custa
281
+ // contraste nenhum.
282
+ const BRANCO = 231;
283
+ const CLARO = 231;
284
+
285
+ const corDoTexto = (l, x) => {
286
+ if (l.tipo === 'titulo') return { cor: BRANCO, negrito: true };
287
+ if (l.tipo === 'nomes') {
288
+ const dentro = x >= L.destaque.de && x < L.destaque.ate;
289
+ return { cor: dentro ? BRANCO : CLARO, negrito: dentro };
290
+ }
291
+ return { cor: CLARO, negrito: false };
292
+ };
293
+
294
+ return L.linhas.map((l, y) => {
295
+ // A régua "▔" some no ultra: sobre o plasma ela virava uma listra
296
+ // clara cortando o topo do bloco.
297
+ const texto = l.tipo === 'regua' ? '' : l.texto;
298
+ // Linha COM texto é acalmada inteira, não só debaixo das letras.
299
+ //
300
+ // Limitar só a célula do glifo não bastava: o vão entre as palavras
301
+ // recebia o plasma cheio, e um pico branco encostado na letra some
302
+ // com ela do mesmo jeito. A linha toda vira superfície escura, e o
303
+ // brilho fica para as faixas abertas — que é onde ele aparece sem
304
+ // atrapalhar ninguém.
305
+ const linhaCalma = texto.trim().length > 0 ? TETO_SOB_TEXTO : 0;
306
+ let saida = '';
307
+ let fg = -1, bg = -1, forte = null;
308
+
309
+ for (let x = 0; x < L.cols; x++) {
310
+ const ch = texto[x] || ' ';
311
+ const temLetra = ch !== ' ';
312
+ const cima = campoUltra(x, y * 2, fase, foco, alturaMeia);
313
+ const baixo = campoUltra(x, y * 2 + 1, fase, foco, alturaMeia);
314
+
315
+ let glifo, corFrente, corFundo, querForte;
316
+ if (temLetra) {
317
+ const t = corDoTexto(l, x);
318
+ glifo = ch;
319
+ corFrente = t.cor;
320
+ querForte = t.negrito;
321
+ corFundo = corDoCampo((cima + baixo) / 2, TETO_SOB_TEXTO, INDICE_MAX_TEXTO);
322
+ } else {
323
+ glifo = '▀'; // ▀
324
+ const teto = linhaCalma ? INDICE_MAX_TEXTO : null;
325
+ corFrente = corDoCampo(cima, linhaCalma, teto);
326
+ corFundo = corDoCampo(baixo, linhaCalma, teto);
327
+ querForte = false;
328
+ }
329
+
330
+ if (corFundo !== bg) { saida += `\x1b[48;5;${corFundo}m`; bg = corFundo; }
331
+ if (corFrente !== fg) { saida += `\x1b[38;5;${corFrente}m`; fg = corFrente; }
332
+ if (querForte !== forte) { saida += querForte ? '\x1b[1m' : '\x1b[22m'; forte = querForte; }
333
+ saida += glifo;
334
+ }
335
+ return saida + '\x1b[0m';
336
+ }).join('\n');
337
+ }
338
+
182
339
  function desenharEffort(idx, fase) {
183
340
  const L = layoutEffort(idx);
184
341
  const ultra = NOMES[idx] === 'ultracode';
185
342
 
186
343
  if (ultra) {
187
- // No ultracode o painel todo vira fundo animado. Cada linha é pintada
188
- // com a MESMA onda, então as faixas ficam alinhadas de cima a baixo.
189
- const LILAS = 189; // lilás bem claro, para o que não é o escolhido
190
- return L.linhas.map((l) => {
191
- // A régua é uma fileira de "▔". Sobre o roxo ela virava uma
192
- // listra clara atravessando o topo e cortava o bloco em dois.
193
- // No ultra ela some e vira fundo liso: a borda de cima do painel
194
- // passa a ser o próprio degradê, que é o que se quer ver.
195
- if (l.tipo === 'regua') return pintarLinhaUltra('', L.cols, fase, {});
196
- if (l.tipo === 'titulo') return pintarLinhaUltra(l.texto, L.cols, fase, { negrito: true });
197
- if (l.tipo === 'nomes') return pintarLinhaUltra(l.texto, L.cols, fase, { destaque: L.destaque, apagado: LILAS });
198
- if (l.tipo === 'resumo' || l.tipo === 'extremos') return pintarLinhaUltra(l.texto, L.cols, fase, { base: LILAS, apagado: LILAS });
199
- return pintarLinhaUltra(l.texto, L.cols, fase, {});
200
- }).join('\n');
344
+ return painelUltra(L, fase);
201
345
  }
202
346
 
203
347
  return L.linhas.map((l) => {
@@ -224,5 +368,6 @@ function desenharEffort(idx, fase) {
224
368
  }
225
369
 
226
370
  module.exports = { NIVEIS, NOMES, normalizar, nivelDe, indiceDe, modeloDe, pesquisaDe,
227
- layoutEffort, fundoRoxo, pintarLinhaUltra, desenharEffort, ROXOS };
371
+ layoutEffort, fundoRoxo, pintarLinhaUltra, desenharEffort, ROXOS, RAMPA,
372
+ campoUltra, corDoCampo, painelUltra };
228
373
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "8.16.0",
3
+ "version": "8.18.0",
4
4
  "description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modelos grátis. Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
5
5
  "main": "bin/primocode.js",
6
6
  "bin": {