primocode 9.8.0-beta.4 → 9.8.0-beta.6
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/README.md +15 -4
- package/app/ansi.js +55 -0
- package/app/ide.css +345 -0
- package/app/ide.html +183 -0
- package/app/ide.js +639 -0
- package/bin/primocode.js +315 -83
- package/kits/acolhedor/index.html +130 -0
- package/kits/acolhedor/script.js +25 -0
- package/kits/acolhedor/style.css +146 -0
- package/kits/editorial/index.html +115 -0
- package/kits/editorial/script.js +19 -0
- package/kits/editorial/style.css +112 -0
- package/kits/premium/index.html +83 -0
- package/kits/premium/script.js +11 -0
- package/kits/premium/style.css +87 -0
- package/kits/vitrine/index.html +108 -0
- package/kits/vitrine/script.js +20 -0
- package/kits/vitrine/style.css +117 -0
- package/lib/app.js +41 -6
- package/lib/browser.js +14 -1
- package/lib/checkpoint-git.js +218 -0
- package/lib/checkpoint.js +159 -44
- package/lib/comandos-extra.js +481 -0
- package/lib/config.js +5 -2
- package/lib/ide.js +337 -0
- package/lib/kits.js +165 -54
- package/lib/skills.js +6 -1
- package/lib/ui.js +171 -17
- package/package.json +2 -2
- package/skills/apresentacao-pptx/SKILL.md +52 -0
- package/skills/cartaz-de-arte/LICENSE.txt +202 -0
- package/skills/cartaz-de-arte/SKILL.md +47 -0
- package/skills/criar-skill/LICENSE.txt +202 -0
- package/skills/criar-skill/SKILL.md +49 -0
- package/skills/depurar-de-verdade/LICENSE.txt +21 -0
- package/skills/depurar-de-verdade/SKILL.md +14 -5
- package/skills/documento-word/SKILL.md +57 -0
- package/skills/interface-revisada/SKILL.md +47 -0
- package/skills/me-ensina/LICENSE.txt +21 -0
- package/skills/me-ensina/SKILL.md +27 -0
- package/skills/me-entrevista/LICENSE.txt +21 -0
- package/skills/me-entrevista/SKILL.md +27 -0
- package/skills/pdf-bonito/SKILL.md +48 -0
- package/skills/planilha-excel/SKILL.md +47 -0
- package/skills/react-sem-dor/SKILL.md +48 -0
- package/skills/revisao-antes-de-entregar/LICENSE.txt +21 -0
- package/skills/revisao-antes-de-entregar/SKILL.md +6 -1
- package/skills/temas-prontos/LICENSE.txt +202 -0
- package/skills/temas-prontos/SKILL.md +50 -0
- package/skills/testar-o-site/LICENSE.txt +202 -0
- package/skills/testar-o-site/SKILL.md +46 -0
- package/skills/video-no-remotion/SKILL.md +48 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Premium: um só movimento, na abertura. O arco sobe devagar quando a página
|
|
2
|
+
// carrega, e nada mais se mexe: animação em toda seção tira o ar de calma.
|
|
3
|
+
(function(){
|
|
4
|
+
var d=document;
|
|
5
|
+
d.documentElement.classList.add('js');
|
|
6
|
+
var arco=d.getElementById('arco');
|
|
7
|
+
if(!arco) return;
|
|
8
|
+
// Dois quadros de espera: garante que o navegador pintou o estado inicial
|
|
9
|
+
// antes de ligar a transição (senão ele pula direto para o fim).
|
|
10
|
+
requestAnimationFrame(function(){ requestAnimationFrame(function(){ arco.classList.add('pronto'); }); });
|
|
11
|
+
})();
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
:root{
|
|
2
|
+
/* ── A MARCA ───────────────────────────────────────────────────────
|
|
3
|
+
Para mudar a cara da página inteira, troque SÓ estas linhas:
|
|
4
|
+
--primaria a cor do arco e dos títulos (escura e sóbria)
|
|
5
|
+
--destaque o fio de luz no arco e os detalhes (um metal: latão,
|
|
6
|
+
cobre, prata)
|
|
7
|
+
--apoio as linhas finas e o fundo do depoimento
|
|
8
|
+
--fundo o fundo da página (claro e neutro)
|
|
9
|
+
--fonte-titulo / --fonte-texto as duas fontes (e o link do
|
|
10
|
+
Google Fonts no index.html)
|
|
11
|
+
O resto do arquivo usa estas variáveis: não precisa mexer nele. */
|
|
12
|
+
--primaria:#1f3a34;
|
|
13
|
+
--destaque:#b8955a;
|
|
14
|
+
--apoio:#dcd9d2;
|
|
15
|
+
--fundo:#f4f4f1;
|
|
16
|
+
--fonte-titulo:'Bodoni Moda',Georgia,serif;
|
|
17
|
+
--fonte-texto:'Manrope',system-ui,sans-serif;
|
|
18
|
+
|
|
19
|
+
--tinta:#1c1c1b;
|
|
20
|
+
--suave:#66655f;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Premium: muito espaço vazio e um único elemento marcante (o arco).
|
|
24
|
+
Tudo o mais é fino, calmo e centrado. O luxo aqui é o que NÃO tem. */
|
|
25
|
+
*{margin:0;padding:0;box-sizing:border-box;}
|
|
26
|
+
html{scroll-behavior:smooth;}
|
|
27
|
+
body{font-family:var(--fonte-texto);font-size:1.02rem;line-height:1.75;color:var(--tinta);background:var(--fundo);-webkit-font-smoothing:antialiased;}
|
|
28
|
+
a{color:inherit;}
|
|
29
|
+
:focus-visible{outline:2px solid var(--destaque);outline-offset:4px;}
|
|
30
|
+
h1,h2{font-family:var(--fonte-titulo);font-weight:400;line-height:1.12;color:var(--primaria);letter-spacing:-.01em;}
|
|
31
|
+
.pular{position:absolute;left:-9999px;top:8px;background:var(--tinta);color:var(--fundo);padding:8px 14px;z-index:10;}
|
|
32
|
+
.pular:focus{left:8px;}
|
|
33
|
+
|
|
34
|
+
.topo{display:flex;align-items:center;justify-content:space-between;padding:28px clamp(20px,5vw,64px);}
|
|
35
|
+
.marca{font-family:var(--fonte-titulo);font-size:1.6rem;font-weight:500;text-decoration:none;color:var(--primaria);}
|
|
36
|
+
.menu{display:flex;gap:32px;font-size:.95rem;}
|
|
37
|
+
.menu a{text-decoration:none;color:var(--suave);transition:color .3s;}
|
|
38
|
+
.menu a:hover{color:var(--tinta);}
|
|
39
|
+
|
|
40
|
+
.botao{display:inline-block;padding:14px 32px;border:1px solid var(--primaria);color:var(--primaria);font:500 .98rem var(--fonte-texto);text-decoration:none;letter-spacing:.01em;transition:background .35s,color .35s;}
|
|
41
|
+
.botao:hover{background:var(--primaria);color:var(--fundo);}
|
|
42
|
+
.botao-cheio{background:var(--primaria);color:var(--fundo);}
|
|
43
|
+
.botao-cheio:hover{background:transparent;color:var(--primaria);}
|
|
44
|
+
|
|
45
|
+
/* O arco: uma janela alta, com um fio de metal por dentro e um sol baixo */
|
|
46
|
+
.abertura{display:flex;flex-direction:column;align-items:center;text-align:center;padding:clamp(24px,5vw,56px) 24px clamp(90px,12vw,160px);}
|
|
47
|
+
.arco{position:relative;width:min(380px,70vw);aspect-ratio:3/4.2;border-radius:999px 999px 0 0;background:var(--primaria);overflow:hidden;margin-bottom:clamp(40px,6vw,72px);transform-origin:50% 100%;}
|
|
48
|
+
.arco::before{content:'';position:absolute;inset:18px 18px 0;border:1px solid var(--destaque);border-bottom:0;border-radius:999px 999px 0 0;opacity:.8;}
|
|
49
|
+
.arco-luz{position:absolute;left:50%;bottom:-18%;width:64%;aspect-ratio:1;transform:translateX(-50%);border-radius:50%;background:radial-gradient(circle at 50% 40%,var(--destaque),color-mix(in srgb,var(--destaque) 30%,var(--primaria)) 70%);opacity:.95;}
|
|
50
|
+
.abertura h1{font-size:clamp(2.2rem,4.8vw,4.1rem);max-width:18ch;margin-bottom:22px;}
|
|
51
|
+
.abertura-texto{color:var(--suave);font-size:1.1rem;margin-bottom:40px;}
|
|
52
|
+
|
|
53
|
+
/* Entrada: só o arco sobe, uma vez, ao abrir a página */
|
|
54
|
+
.js .arco{opacity:0;transform:scaleY(.86);}
|
|
55
|
+
.js .arco.pronto{opacity:1;transform:none;transition:opacity 1.4s ease,transform 1.6s cubic-bezier(.2,.7,.1,1);}
|
|
56
|
+
|
|
57
|
+
.manifesto{padding:clamp(80px,12vw,160px) 24px;border-top:1px solid var(--apoio);}
|
|
58
|
+
.manifesto p{max-width:30ch;margin:0 auto;text-align:center;font-family:var(--fonte-titulo);font-weight:400;font-size:clamp(1.5rem,2.7vw,2.2rem);line-height:1.4;color:var(--tinta);}
|
|
59
|
+
|
|
60
|
+
/* O trabalho: três colunas de texto com um fio em cima, sem caixa */
|
|
61
|
+
.trabalho{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(32px,5vw,80px);max-width:1160px;margin:0 auto;padding:clamp(60px,8vw,120px) clamp(24px,5vw,64px);}
|
|
62
|
+
.frente{border-top:1px solid var(--destaque);padding-top:28px;}
|
|
63
|
+
.frente h2{font-size:clamp(1.45rem,2vw,1.8rem);font-weight:400;margin-bottom:12px;}
|
|
64
|
+
.frente p{color:var(--suave);max-width:34ch;}
|
|
65
|
+
|
|
66
|
+
.depoimento{background:var(--apoio);padding:clamp(80px,11vw,150px) 24px;text-align:center;}
|
|
67
|
+
.depoimento p{font-family:var(--fonte-titulo);font-style:italic;font-weight:400;font-size:clamp(1.4rem,2.6vw,2.1rem);line-height:1.3;max-width:28ch;margin:0 auto;color:var(--primaria);}
|
|
68
|
+
.depoimento footer{margin-top:24px;color:var(--suave);font-size:.95rem;}
|
|
69
|
+
|
|
70
|
+
.contato{text-align:center;padding:clamp(90px,12vw,160px) 24px;}
|
|
71
|
+
.contato h2{font-size:clamp(2rem,4vw,3.2rem);margin-bottom:48px;}
|
|
72
|
+
.contato-grade{display:grid;grid-template-columns:repeat(3,auto);justify-content:center;gap:clamp(28px,6vw,96px);margin-bottom:56px;color:var(--suave);}
|
|
73
|
+
.contato-grade a{text-decoration:none;border-bottom:1px solid var(--apoio);transition:border-color .3s;}
|
|
74
|
+
.contato-grade a:hover{border-color:var(--destaque);}
|
|
75
|
+
|
|
76
|
+
.rodape{border-top:1px solid var(--apoio);padding:28px;text-align:center;font-size:.9rem;color:var(--suave);}
|
|
77
|
+
|
|
78
|
+
@media (max-width:760px){
|
|
79
|
+
.trabalho{grid-template-columns:1fr;}
|
|
80
|
+
.contato-grade{grid-template-columns:1fr;gap:20px;}
|
|
81
|
+
.menu{gap:20px;}
|
|
82
|
+
}
|
|
83
|
+
@media (prefers-reduced-motion:reduce){
|
|
84
|
+
html{scroll-behavior:auto;}
|
|
85
|
+
*{transition:none!important;}
|
|
86
|
+
.js .arco{opacity:1;transform:none;}
|
|
87
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang='pt-BR'>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset='UTF-8'>
|
|
5
|
+
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
|
6
|
+
<!-- TROQUE: título e descrição com o nome e a oferta -->
|
|
7
|
+
<title>Sua Marca, a oferta que chama</title>
|
|
8
|
+
<meta name='description' content='Uma frase que diz o que é, quando e onde.'>
|
|
9
|
+
<link rel='preconnect' href='https://fonts.googleapis.com'>
|
|
10
|
+
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
|
|
11
|
+
<!-- TROQUE (se quiser): a fonte. Se trocar aqui, troque --fonte-titulo e --fonte-texto no topo do style.css -->
|
|
12
|
+
<link href='https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,600;12..96,800&display=swap' rel='stylesheet'>
|
|
13
|
+
<link rel='stylesheet' href='style.css'>
|
|
14
|
+
<script src='script.js' defer></script>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<a class='pular' href='#conteudo'>Pular para o conteúdo</a>
|
|
18
|
+
|
|
19
|
+
<!-- TROQUE: o nome e o link do pedido (WhatsApp, loja, ingresso) -->
|
|
20
|
+
<header class='topo'>
|
|
21
|
+
<a href='#' class='marca'>Sua Marca</a>
|
|
22
|
+
<a href='https://wa.me/5500000000000' class='botao botao-preto'>Quero o meu</a>
|
|
23
|
+
</header>
|
|
24
|
+
|
|
25
|
+
<main id='conteudo'>
|
|
26
|
+
<!-- TROQUE: o título curto e barulhento (poucas palavras), o selo e a frase de apoio -->
|
|
27
|
+
<section class='palco'>
|
|
28
|
+
<h1>Uma frase curta que dá fome.</h1>
|
|
29
|
+
<p class='selo'><span>Só nesta semana</span></p>
|
|
30
|
+
<div class='palco-base'>
|
|
31
|
+
<p class='palco-texto'>O que é, para quem é, e o motivo para pedir hoje. Duas frases, sem enrolar.</p>
|
|
32
|
+
<a href='https://wa.me/5500000000000' class='botao botao-preto'>Fazer meu pedido</a>
|
|
33
|
+
</div>
|
|
34
|
+
</section>
|
|
35
|
+
|
|
36
|
+
<!-- TROQUE: a data (no texto E no data-alvo, formato 2026-12-31T20:00), o lugar e o preço. Sem data? Apague o <p id='contagem'>. -->
|
|
37
|
+
<section class='quando' aria-label='Quando e onde'>
|
|
38
|
+
<div class='quando-data'>
|
|
39
|
+
<p class='quando-rotulo'>Quando</p>
|
|
40
|
+
<p class='quando-valor'>Sábado, 12 de dezembro</p>
|
|
41
|
+
<p id='contagem' class='contagem' data-alvo='2026-12-12T19:00'></p>
|
|
42
|
+
</div>
|
|
43
|
+
<div class='quando-lugar'>
|
|
44
|
+
<p class='quando-rotulo'>Onde</p>
|
|
45
|
+
<p class='quando-valor'>Rua Exemplo, 100, Sua Cidade</p>
|
|
46
|
+
</div>
|
|
47
|
+
<div class='quando-preco'>
|
|
48
|
+
<p class='quando-rotulo'>A partir de</p>
|
|
49
|
+
<p class='quando-valor'>R$ 00</p>
|
|
50
|
+
</div>
|
|
51
|
+
</section>
|
|
52
|
+
|
|
53
|
+
<!-- TROQUE: os produtos (ou atrações). O primeiro é a estrela e fica grande. Pode ter 4 a 6. -->
|
|
54
|
+
<section id='cardapio' class='mosaico' aria-label='O que tem'>
|
|
55
|
+
<article class='peca peca-estrela'>
|
|
56
|
+
<div class='arte arte-circulos' aria-hidden='true'></div>
|
|
57
|
+
<h2>O campeão da casa</h2>
|
|
58
|
+
<p>Por que todo mundo pede este. Uma frase que dá vontade.</p>
|
|
59
|
+
<p class='etiqueta'>R$ 00</p>
|
|
60
|
+
</article>
|
|
61
|
+
<article class='peca peca-azul'>
|
|
62
|
+
<div class='arte arte-meia' aria-hidden='true'></div>
|
|
63
|
+
<h3>Segundo item</h3>
|
|
64
|
+
<p>Uma frase curta e gostosa.</p>
|
|
65
|
+
<p class='etiqueta'>R$ 00</p>
|
|
66
|
+
</article>
|
|
67
|
+
<article class='peca peca-branca'>
|
|
68
|
+
<div class='arte arte-listras' aria-hidden='true'></div>
|
|
69
|
+
<h3>Terceiro item</h3>
|
|
70
|
+
<p>Uma frase curta e gostosa.</p>
|
|
71
|
+
<p class='etiqueta'>R$ 00</p>
|
|
72
|
+
</article>
|
|
73
|
+
<article class='peca peca-amarela'>
|
|
74
|
+
<div class='arte arte-gota' aria-hidden='true'></div>
|
|
75
|
+
<h3>Quarto item</h3>
|
|
76
|
+
<p>Uma frase curta e gostosa.</p>
|
|
77
|
+
<p class='etiqueta'>R$ 00</p>
|
|
78
|
+
</article>
|
|
79
|
+
<article class='peca peca-preta'>
|
|
80
|
+
<div class='arte arte-estrela' aria-hidden='true'></div>
|
|
81
|
+
<h3>Quinto item</h3>
|
|
82
|
+
<p>Uma frase curta e gostosa.</p>
|
|
83
|
+
<p class='etiqueta'>R$ 00</p>
|
|
84
|
+
</article>
|
|
85
|
+
</section>
|
|
86
|
+
|
|
87
|
+
<!-- TROQUE: como pedir, em três verbos (é uma sequência de verdade) e uma frase -->
|
|
88
|
+
<section class='passos'>
|
|
89
|
+
<p class='passos-verbos'>Escolha. <span>Chame.</span> Aproveite.</p>
|
|
90
|
+
<p class='passos-texto'>Uma frase que explica o caminho: onde pedir, em quanto tempo chega, como pagar.</p>
|
|
91
|
+
</section>
|
|
92
|
+
|
|
93
|
+
<!-- TROQUE: o último empurrão e os contatos -->
|
|
94
|
+
<section id='pedir' class='final'>
|
|
95
|
+
<h2>Bora?</h2>
|
|
96
|
+
<p>O convite final, com a urgência verdadeira (acaba quando?).</p>
|
|
97
|
+
<a href='https://wa.me/5500000000000' class='botao botao-preto'>Chamar no WhatsApp</a>
|
|
98
|
+
<p class='final-redes'><a href='https://instagram.com/'>@suamarca</a> no Instagram</p>
|
|
99
|
+
</section>
|
|
100
|
+
</main>
|
|
101
|
+
|
|
102
|
+
<!-- TROQUE: ano, nome e cidade -->
|
|
103
|
+
<footer class='rodape'>
|
|
104
|
+
<p>© 2026 Sua Marca, Sua Cidade</p>
|
|
105
|
+
<a href='#'>Voltar ao topo</a>
|
|
106
|
+
</footer>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Vitrine: a pressa de verdade é a data. O script conta quantos dias faltam
|
|
2
|
+
// para o data-alvo e escreve em palavras ("Faltam 12 dias"), em vez de um
|
|
3
|
+
// relógio piscando. Se a data passou ou está vazia, o selo some sozinho.
|
|
4
|
+
(function(){
|
|
5
|
+
var el=document.getElementById('contagem');
|
|
6
|
+
if(!el) return;
|
|
7
|
+
var alvo=new Date(el.getAttribute('data-alvo')||'');
|
|
8
|
+
if(isNaN(alvo)) { el.textContent=''; return; }
|
|
9
|
+
function escrever(){
|
|
10
|
+
var ms=alvo-new Date();
|
|
11
|
+
if(ms<=0){ el.textContent=''; return; }
|
|
12
|
+
var dias=Math.floor(ms/864e5), horas=Math.floor(ms/36e5);
|
|
13
|
+
if(dias>1) el.textContent='Faltam '+dias+' dias';
|
|
14
|
+
else if(dias===1) el.textContent='É amanhã';
|
|
15
|
+
else if(horas>=1) el.textContent='Faltam '+horas+(horas===1?' hora':' horas');
|
|
16
|
+
else el.textContent='É agora';
|
|
17
|
+
}
|
|
18
|
+
escrever();
|
|
19
|
+
setInterval(escrever,6e4);
|
|
20
|
+
})();
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
:root{
|
|
2
|
+
/* ── A MARCA ───────────────────────────────────────────────────────
|
|
3
|
+
Para mudar a cara da página inteira, troque SÓ estas linhas:
|
|
4
|
+
--primaria o bloco de abertura e a peça estrela
|
|
5
|
+
--destaque o selo, as etiquetas de preço e o bloco final
|
|
6
|
+
--apoio a faixa de data e as peças de cor
|
|
7
|
+
--fonte-titulo / --fonte-texto a fonte (e o link do Google
|
|
8
|
+
Fonts no index.html)
|
|
9
|
+
Cores vivas e fortes funcionam melhor aqui. O resto do arquivo usa
|
|
10
|
+
estas variáveis: não precisa mexer nele. */
|
|
11
|
+
--primaria:#ffd23f;
|
|
12
|
+
--destaque:#ff4f8b;
|
|
13
|
+
--apoio:#2d5bff;
|
|
14
|
+
--fonte-titulo:'Bricolage Grotesque',system-ui,sans-serif;
|
|
15
|
+
--fonte-texto:'Bricolage Grotesque',system-ui,sans-serif;
|
|
16
|
+
|
|
17
|
+
--tinta:#141414;
|
|
18
|
+
--branco:#ffffff;
|
|
19
|
+
--lado:clamp(16px,3vw,40px);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Vitrine: blocos de cor chapada, tipo enorme e contorno grosso, como
|
|
23
|
+
cartaz de rua. Nada de sombra suave nem vidro: tudo é plano e forte. */
|
|
24
|
+
*{margin:0;padding:0;box-sizing:border-box;}
|
|
25
|
+
html{scroll-behavior:smooth;}
|
|
26
|
+
body{font-family:var(--fonte-texto);font-size:1.1rem;line-height:1.5;color:var(--tinta);background:var(--branco);-webkit-font-smoothing:antialiased;overflow-x:hidden;}
|
|
27
|
+
a{color:inherit;}
|
|
28
|
+
:focus-visible{outline:4px solid var(--apoio);outline-offset:3px;}
|
|
29
|
+
h1,h2,h3{font-family:var(--fonte-titulo);font-weight:800;line-height:.95;letter-spacing:-.035em;}
|
|
30
|
+
.pular{position:absolute;left:-9999px;top:8px;background:var(--tinta);color:var(--branco);padding:8px 14px;z-index:10;}
|
|
31
|
+
.pular:focus{left:8px;}
|
|
32
|
+
|
|
33
|
+
.topo{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px var(--lado);background:var(--primaria);border-bottom:3px solid var(--tinta);}
|
|
34
|
+
.marca{font-family:var(--fonte-titulo);font-weight:800;font-size:1.6rem;letter-spacing:-.04em;text-decoration:none;}
|
|
35
|
+
|
|
36
|
+
.botao{display:inline-block;padding:14px 26px;border:3px solid var(--tinta);border-radius:14px;background:var(--branco);color:var(--tinta);font:800 1.05rem var(--fonte-titulo);text-decoration:none;box-shadow:5px 5px 0 var(--tinta);transition:transform .15s,box-shadow .15s;}
|
|
37
|
+
.botao:hover{transform:translate(-2px,-2px);box-shadow:7px 7px 0 var(--tinta);}
|
|
38
|
+
.botao:active{transform:translate(3px,3px);box-shadow:2px 2px 0 var(--tinta);}
|
|
39
|
+
.botao-preto{background:var(--tinta);color:var(--branco);box-shadow:5px 5px 0 var(--destaque);}
|
|
40
|
+
.botao-preto:hover{box-shadow:7px 7px 0 var(--destaque);}
|
|
41
|
+
.botao-preto:active{box-shadow:2px 2px 0 var(--destaque);}
|
|
42
|
+
|
|
43
|
+
/* Palco: o título ocupa a largura toda, e o selo gira no canto */
|
|
44
|
+
.palco{position:relative;background:var(--primaria);padding:clamp(40px,6vw,80px) var(--lado) clamp(48px,6vw,80px);border-bottom:3px solid var(--tinta);overflow:hidden;}
|
|
45
|
+
.palco h1{font-size:clamp(3.4rem,12.5vw,13rem);max-width:10ch;position:relative;z-index:1;}
|
|
46
|
+
.selo{position:absolute;right:clamp(16px,6vw,110px);top:clamp(24px,5vw,70px);width:clamp(120px,15vw,210px);aspect-ratio:1;display:grid;place-items:center;text-align:center;border-radius:50%;background:var(--destaque);font-family:var(--fonte-titulo);font-weight:800;font-size:clamp(1rem,1.6vw,1.45rem);line-height:1.05;transform:rotate(-12deg);z-index:2;
|
|
47
|
+
/* a borda serrilhada do selo: um polígono de 24 pontas */
|
|
48
|
+
clip-path:polygon(50% 0%,56.5% 7.4%,65.4% 3.4%,68.8% 12.6%,78.6% 12.4%,78.3% 22.1%,87.6% 25%,84.1% 34.1%,92.1% 39.8%,86.4% 47.6%,92.1% 55.8%,84.1% 61%,87.6% 70%,78.3% 73%,78.6% 82.7%,68.8% 82.4%,65.4% 91.6%,56.5% 87.6%,50% 95%,43.5% 87.6%,34.6% 91.6%,31.2% 82.4%,21.4% 82.7%,21.7% 73%,12.4% 70%,15.9% 61%,7.9% 55.8%,13.6% 47.6%,7.9% 39.8%,15.9% 34.1%,12.4% 25%,21.7% 22.1%,21.4% 12.4%,31.2% 12.6%,34.6% 3.4%,43.5% 7.4%);}
|
|
49
|
+
.selo span{max-width:8ch;}
|
|
50
|
+
.palco-base{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:24px;margin-top:clamp(28px,4vw,48px);}
|
|
51
|
+
.palco-texto{font-size:clamp(1.2rem,1.8vw,1.5rem);font-weight:600;max-width:34ch;}
|
|
52
|
+
|
|
53
|
+
/* Quando e onde: uma faixa de três colunas com filete preto entre elas */
|
|
54
|
+
.quando{display:grid;grid-template-columns:1.3fr 1.3fr 1fr;background:var(--apoio);color:var(--branco);border-bottom:3px solid var(--tinta);}
|
|
55
|
+
.quando>div{padding:28px var(--lado);}
|
|
56
|
+
.quando>div+div{border-left:3px solid var(--tinta);}
|
|
57
|
+
.quando-rotulo{font-weight:600;opacity:.85;}
|
|
58
|
+
.quando-valor{font-family:var(--fonte-titulo);font-weight:800;font-size:clamp(1.5rem,2.6vw,2.3rem);line-height:1.05;letter-spacing:-.02em;margin-top:4px;}
|
|
59
|
+
.contagem{margin-top:10px;display:inline-block;background:var(--primaria);color:var(--tinta);font-weight:800;padding:4px 12px;border-radius:999px;border:2px solid var(--tinta);}
|
|
60
|
+
.contagem:empty{display:none;}
|
|
61
|
+
|
|
62
|
+
/* Mosaico: peças de tamanhos e cores diferentes, e não cards iguais */
|
|
63
|
+
.mosaico{display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:minmax(250px,auto);gap:3px;background:var(--tinta);border-bottom:3px solid var(--tinta);}
|
|
64
|
+
.peca{position:relative;display:flex;flex-direction:column;justify-content:flex-end;gap:8px;padding:28px;overflow:hidden;background:var(--branco);}
|
|
65
|
+
.peca h2,.peca h3,.peca p{position:relative;z-index:1;}
|
|
66
|
+
.peca h3{font-size:clamp(1.6rem,2.4vw,2.2rem);}
|
|
67
|
+
.peca p{max-width:28ch;font-weight:600;}
|
|
68
|
+
.peca-estrela{grid-column:span 2;grid-row:span 2;background:var(--destaque);}
|
|
69
|
+
.peca-estrela h2{font-size:clamp(2.6rem,5.5vw,5.2rem);max-width:9ch;}
|
|
70
|
+
.peca-estrela p{font-size:1.25rem;}
|
|
71
|
+
.peca-azul{background:var(--apoio);color:var(--branco);}
|
|
72
|
+
.peca-amarela{background:var(--primaria);}
|
|
73
|
+
.peca-preta{background:var(--tinta);color:var(--branco);}
|
|
74
|
+
.etiqueta{position:absolute!important;top:22px;right:22px;background:var(--branco);color:var(--tinta);font-family:var(--fonte-titulo);font-weight:800;font-size:1.3rem;padding:6px 14px;border:3px solid var(--tinta);border-radius:10px;transform:rotate(4deg);}
|
|
75
|
+
.peca-estrela .etiqueta{font-size:2rem;padding:8px 18px;transform:rotate(-5deg);}
|
|
76
|
+
|
|
77
|
+
/* As artes: formas geométricas no lugar da foto (o pedido pode não ter foto) */
|
|
78
|
+
.arte{position:absolute;z-index:0;}
|
|
79
|
+
.arte-circulos{right:-8%;top:10%;width:62%;aspect-ratio:1;border-radius:50%;background:radial-gradient(circle,var(--primaria) 0 22%,transparent 22.5% 36%,var(--primaria) 36.5% 50%,transparent 50.5%);}
|
|
80
|
+
.arte-meia{right:-30px;top:-30px;width:170px;aspect-ratio:1;border-radius:50%;background:var(--primaria);}
|
|
81
|
+
.arte-listras{left:0;right:0;top:0;height:46%;background:repeating-linear-gradient(-45deg,var(--destaque) 0 16px,transparent 16px 32px);opacity:.9;}
|
|
82
|
+
.arte-gota{right:24px;top:70px;width:110px;aspect-ratio:1;border-radius:50% 0 50% 50%;background:var(--destaque);transform:rotate(-45deg);}
|
|
83
|
+
.arte-estrela{right:22px;top:64px;width:120px;aspect-ratio:1;background:var(--primaria);clip-path:polygon(50% 0,61% 35%,98% 35%,68% 57%,79% 91%,50% 70%,21% 91%,32% 57%,2% 35%,39% 35%);}
|
|
84
|
+
|
|
85
|
+
/* Passos: três verbos em uma linha enorme */
|
|
86
|
+
.passos{padding:clamp(56px,8vw,110px) var(--lado);border-bottom:3px solid var(--tinta);}
|
|
87
|
+
.passos-verbos{font-family:var(--fonte-titulo);font-weight:800;font-size:clamp(2.6rem,9vw,8.5rem);line-height:.95;letter-spacing:-.04em;}
|
|
88
|
+
.passos-verbos span{color:var(--apoio);}
|
|
89
|
+
.passos-texto{margin-top:24px;font-size:1.3rem;font-weight:600;max-width:40ch;}
|
|
90
|
+
|
|
91
|
+
.final{background:var(--destaque);padding:clamp(56px,8vw,110px) var(--lado);text-align:center;border-bottom:3px solid var(--tinta);}
|
|
92
|
+
.final h2{font-size:clamp(4.5rem,16vw,14rem);}
|
|
93
|
+
.final p{font-size:1.3rem;font-weight:600;margin:16px auto 28px;max-width:34ch;}
|
|
94
|
+
.final .botao-preto{box-shadow:5px 5px 0 var(--primaria);}
|
|
95
|
+
.final-redes{font-size:1.05rem!important;margin-top:28px!important;}
|
|
96
|
+
|
|
97
|
+
.rodape{display:flex;justify-content:space-between;gap:16px;padding:22px var(--lado);background:var(--tinta);color:var(--branco);font-weight:600;}
|
|
98
|
+
|
|
99
|
+
@media (max-width:960px){
|
|
100
|
+
.mosaico{grid-template-columns:1fr 1fr;}
|
|
101
|
+
.peca-estrela{grid-column:span 2;grid-row:span 1;min-height:380px;}
|
|
102
|
+
}
|
|
103
|
+
@media (max-width:640px){
|
|
104
|
+
.quando{grid-template-columns:1fr;}
|
|
105
|
+
.quando>div+div{border-left:0;border-top:3px solid var(--tinta);}
|
|
106
|
+
.mosaico{grid-template-columns:1fr;}
|
|
107
|
+
.peca-estrela,.peca-preta{grid-column:auto;}
|
|
108
|
+
.peca{min-height:230px;}
|
|
109
|
+
.peca-estrela{min-height:400px;padding-top:110px;}
|
|
110
|
+
.selo{position:relative;right:auto;top:auto;width:116px;margin-top:22px;}
|
|
111
|
+
.arte-circulos{width:46%;top:22%;right:-12%;}
|
|
112
|
+
.palco{padding-bottom:40px;}
|
|
113
|
+
.palco-base{flex-direction:column;align-items:flex-start;}
|
|
114
|
+
.rodape{flex-direction:column;}
|
|
115
|
+
.topo .botao{padding:10px 16px;font-size:.95rem;}
|
|
116
|
+
}
|
|
117
|
+
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto;}*{transition:none!important;}}
|
package/lib/app.js
CHANGED
|
@@ -98,6 +98,9 @@ function tipoDe(arquivo) {
|
|
|
98
98
|
* @param {()=>void} op.interromper para o que estiver rodando
|
|
99
99
|
* @param {()=>object} op.estado { pasta, modo, esforco, projeto }
|
|
100
100
|
* @param {string} op.versao
|
|
101
|
+
* @param {()=>string} [op.pastaProjeto] liga a IDE (/ide) nesta pasta
|
|
102
|
+
* @param {'rc'|'ide'} [op.abrirEm] qual tela a janela abre
|
|
103
|
+
* @param {boolean} [op.semJanela] só o servidor, sem abrir navegador
|
|
101
104
|
*/
|
|
102
105
|
async function abrir(op) {
|
|
103
106
|
const chave = crypto.randomBytes(16).toString('hex');
|
|
@@ -117,6 +120,13 @@ async function abrir(op) {
|
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
/* A IDE (lib/ide.js) mora neste mesmo servidor e fala pelo mesmo fluxo:
|
|
124
|
+
"um app ao invés de CLI, aí fica uma IDE e uma CLI". Só sobe quando
|
|
125
|
+
quem abriu diz qual é a pasta do projeto. */
|
|
126
|
+
const ide = op.pastaProjeto
|
|
127
|
+
? require('./ide.js').montar({ pasta: op.pastaProjeto, emitir: (e, d) => emitir(e, d) })
|
|
128
|
+
: null;
|
|
129
|
+
|
|
120
130
|
const pararEspelho = espelhar((pedaco) => {
|
|
121
131
|
if (ehRuido(pedaco)) return;
|
|
122
132
|
memoria.push(pedaco);
|
|
@@ -175,7 +185,11 @@ async function abrir(op) {
|
|
|
175
185
|
/app.js sem parâmetro nenhum. A página vinha, ficava sem estilo e
|
|
176
186
|
sem script, e o "app bonito" era um formulário branco. */
|
|
177
187
|
if (rota.startsWith('/api/')) {
|
|
178
|
-
|
|
188
|
+
// A prévia da IDE leva a chave no CAMINHO (ver lib/ide.js): os
|
|
189
|
+
// arquivos que a página do projeto pede por endereço relativo não
|
|
190
|
+
// carregam query nenhuma.
|
|
191
|
+
const naPrevia = rota.match(/^\/api\/ide\/previa\/([0-9a-f]+)/);
|
|
192
|
+
const dada = (naPrevia && naPrevia[1]) || url.searchParams.get('k') || req.headers['x-primo-chave'];
|
|
179
193
|
if (dada !== chave) {
|
|
180
194
|
res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
181
195
|
return res.end('chave inválida — abra a janela com o comando /app');
|
|
@@ -185,7 +199,13 @@ async function abrir(op) {
|
|
|
185
199
|
127.0.0.1 sem preflight. Ela não saberia a chave, mas o cinto e o
|
|
186
200
|
suspensório custam três linhas. */
|
|
187
201
|
const origem = req.headers.origin;
|
|
188
|
-
|
|
202
|
+
/* A exceção é a prévia da IDE: ela roda num iframe sandbox de origem
|
|
203
|
+
opaca, e o navegador manda `Origin: null` até num <script
|
|
204
|
+
type="module">. Só GET e só nas rotas da prévia, que apenas LEEM o
|
|
205
|
+
projeto — nenhuma delas dá ordem ao agente. */
|
|
206
|
+
const previaOpaca = origem === 'null' && req.method === 'GET'
|
|
207
|
+
&& rota.startsWith('/api/ide/previa/');
|
|
208
|
+
if (origem && !previaOpaca && !origem.startsWith(`http://127.0.0.1:${servidor.__porta}`)
|
|
189
209
|
&& !origem.startsWith(`http://localhost:${servidor.__porta}`)) {
|
|
190
210
|
res.writeHead(403).end();
|
|
191
211
|
return;
|
|
@@ -254,6 +274,8 @@ async function abrir(op) {
|
|
|
254
274
|
return res.end(JSON.stringify(r));
|
|
255
275
|
}
|
|
256
276
|
|
|
277
|
+
if (ide && await ide.atender(req, res, url)) return;
|
|
278
|
+
|
|
257
279
|
if (rota === '/api/estado') {
|
|
258
280
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
259
281
|
return res.end(JSON.stringify(Object.assign({ versao: op.versao }, op.estado())));
|
|
@@ -262,7 +284,7 @@ async function abrir(op) {
|
|
|
262
284
|
// Estáticos. `basename` corta qualquer "../" antes de virar caminho:
|
|
263
285
|
// este servidor fala com o disco do usuário e não pode servir o que
|
|
264
286
|
// estiver fora da pasta do aplicativo.
|
|
265
|
-
const nome = rota === '/' ? 'index.html' : path.basename(rota);
|
|
287
|
+
const nome = rota === '/' ? 'index.html' : rota === '/ide' ? 'ide.html' : path.basename(rota);
|
|
266
288
|
const arquivo = path.join(WEB, nome);
|
|
267
289
|
fs.readFile(arquivo, (erro, dados) => {
|
|
268
290
|
if (erro) { res.writeHead(404).end('não encontrado'); return; }
|
|
@@ -274,11 +296,22 @@ async function abrir(op) {
|
|
|
274
296
|
const porta = await escutar(servidor, PORTA_PADRAO);
|
|
275
297
|
servidor.__porta = porta;
|
|
276
298
|
const url = `http://127.0.0.1:${porta}/?k=${chave}`;
|
|
299
|
+
const urlIde = `http://127.0.0.1:${porta}/ide?k=${chave}`;
|
|
277
300
|
|
|
278
|
-
|
|
301
|
+
// `semJanela` é para o teste (e para quem só quer o endereço): sem ele,
|
|
302
|
+
// rodar a suíte abriria uma janela de verdade na tela de quem roda.
|
|
303
|
+
const janela = op.semJanela ? { modo: 'nenhum' }
|
|
304
|
+
: await abrirJanela(op.abrirEm === 'ide' && ide ? urlIde : url);
|
|
279
305
|
return {
|
|
280
|
-
url, porta, chave, servidor, janela,
|
|
281
|
-
fechar() {
|
|
306
|
+
url, urlIde: ide ? urlIde : null, porta, chave, servidor, janela,
|
|
307
|
+
fechar() {
|
|
308
|
+
try { pararEspelho(); } catch {}
|
|
309
|
+
try { if (ide) ide.fechar(); } catch {}
|
|
310
|
+
try { servidor.close(); } catch {}
|
|
311
|
+
// SSE abertos seguram o close() para sempre; a janela é que
|
|
312
|
+
// fechou, então as conexões dela também.
|
|
313
|
+
for (const r of ouvintes) { try { r.end(); } catch {} }
|
|
314
|
+
},
|
|
282
315
|
};
|
|
283
316
|
}
|
|
284
317
|
|
|
@@ -345,6 +378,8 @@ function abrirJanela(url) {
|
|
|
345
378
|
const filho = execFile(exe, [
|
|
346
379
|
`--app=${url}`,
|
|
347
380
|
`--user-data-dir=${perfil}`,
|
|
381
|
+
// Sem Keychain: ver SEM_KEYCHAIN em lib/browser.js.
|
|
382
|
+
...require('./browser.js').SEM_KEYCHAIN,
|
|
348
383
|
'--window-size=1180,820',
|
|
349
384
|
'--no-first-run',
|
|
350
385
|
'--no-default-browser-check',
|
package/lib/browser.js
CHANGED
|
@@ -177,6 +177,16 @@ function httpPutJson(url, timeoutMs = 3000) {
|
|
|
177
177
|
|
|
178
178
|
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
179
179
|
|
|
180
|
+
/* O Chrome NUNCA mexe no Keychain do Mac (nem no cofre de senhas do Linux).
|
|
181
|
+
* "Para de fazer coisas de tentar armazenar chave no Chrome, eu apaguei o
|
|
182
|
+
* Chrome." (24/09/2026)
|
|
183
|
+
* Cada perfil novo do PrimoCode (o do agente, o revisor escondido, a janela
|
|
184
|
+
* do /rc e do /ide) fazia o Chrome pedir o "Chrome Safe Storage" ao sistema —
|
|
185
|
+
* uma janela de senha do Mac, do nada, várias vezes. O PrimoCode não guarda
|
|
186
|
+
* senha em navegador nenhum: estas duas flags fazem o Chrome usar um cofre de
|
|
187
|
+
* mentira e texto simples no próprio perfil, sem tocar no do sistema. */
|
|
188
|
+
const SEM_KEYCHAIN = ['--use-mock-keychain', '--password-store=basic'];
|
|
189
|
+
|
|
180
190
|
// ---- WebSocket client mínimo (RFC 6455) para CDP ----------------
|
|
181
191
|
// Suficiente pra CDP (mensagens curtas, texto UTF-8, um cliente por página).
|
|
182
192
|
class MiniWebSocket {
|
|
@@ -393,6 +403,7 @@ async function ensureChromeRunning() {
|
|
|
393
403
|
const args = [
|
|
394
404
|
`--remote-debugging-port=${DEBUG_PORT}`,
|
|
395
405
|
`--user-data-dir=${PROFILE_DIR}`,
|
|
406
|
+
...SEM_KEYCHAIN,
|
|
396
407
|
'--no-first-run',
|
|
397
408
|
'--no-default-browser-check',
|
|
398
409
|
'--disable-features=Translate',
|
|
@@ -839,6 +850,7 @@ async function subirRevisor() {
|
|
|
839
850
|
// Zero: o Chrome escolhe uma porta livre e escreve qual no perfil.
|
|
840
851
|
`--remote-debugging-port=${process.env.PRIMOCODE_CDP_PORT_REVISOR || 0}`,
|
|
841
852
|
`--user-data-dir=${perfilRevisor}`,
|
|
853
|
+
...SEM_KEYCHAIN,
|
|
842
854
|
// Sempre invisível: é o pedido, e é o que separa este do navegador da
|
|
843
855
|
// pessoa. Não há variável para ligar a janela aqui de propósito.
|
|
844
856
|
'--headless=new', '--disable-gpu', '--disable-dev-shm-usage',
|
|
@@ -1116,7 +1128,8 @@ function fecharRevisor() {
|
|
|
1116
1128
|
}
|
|
1117
1129
|
|
|
1118
1130
|
module.exports = {
|
|
1119
|
-
|
|
1131
|
+
SEM_KEYCHAIN,
|
|
1132
|
+
scriptDaSeta, verSeta, findChrome,
|
|
1120
1133
|
open, click, clickText, clickSelector, type, typeInSelector,
|
|
1121
1134
|
key, screenshot, evaluate, waitFor, wait, scroll, close,
|
|
1122
1135
|
inspecionarEscondido, rodarEscondido, abrirEscondido, fecharRevisor,
|