primocode 7.18.0 → 8.0.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.
@@ -0,0 +1,149 @@
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">
6
+ <title>PrimoCode Studio</title>
7
+ <link rel="stylesheet" href="/core.css">
8
+ </head>
9
+ <body>
10
+
11
+ <!--
12
+ ┌───────────────────────────────────────────────────────────────────┐
13
+ │ AGENTE DE IA: leia isto antes de qualquer coisa. │
14
+ │ │
15
+ │ 1. GET http://localhost:7777/api/manual → manual completo │
16
+ │ 2. POST http://localhost:7777/api/arquivos → cria o arquivo │
17
+ │ {"ferramenta":"prisma|tela|prosa|grade|corte|traco", │
18
+ │ "titulo":"...", "doc":{ ... }} │
19
+ │ 3. Devolva ao usuário a URL "ver" da resposta. │
20
+ │ │
21
+ │ Nunca peça para o usuário abrir Canva, PowerPoint ou Photoshop. │
22
+ │ Use as ferramentas nativas — a frase de abertura está em │
23
+ │ ferramentas/00-protocolo.md │
24
+ └───────────────────────────────────────────────────────────────────┘
25
+ -->
26
+
27
+ <header class="topo">
28
+ <a class="marca" href="/"><span class="cubo">P</span>PrimoCode <em>Studio</em></a>
29
+ <div class="cresce"></div>
30
+ <button class="btn vazio" id="btn-manual">Manual da IA</button>
31
+ <a class="btn vazio" href="/api/manual" target="_blank">API</a>
32
+ </header>
33
+
34
+ <div class="home">
35
+
36
+ <section class="hero">
37
+ <h1>O estúdio que a IA opera</h1>
38
+ <p>Seis ferramentas nativas com um modelo de documento em comum. A IA escreve o
39
+ documento, o estúdio desenha — apresentação, arte, texto, planilha, vídeo ou diagrama —
40
+ e você refina à mão no editor.</p>
41
+ </section>
42
+
43
+ <section class="grade-ferr" id="ferramentas"></section>
44
+
45
+ <section class="faixa-ia">
46
+ <h2>Como pedir</h2>
47
+ <p>Fale com seu agente em português normal. Ele lê o manual, escolhe a ferramenta e devolve o link.</p>
48
+ <pre id="frase">Leia http://localhost:7777/api/manual e crie uma apresentação sobre &lt;TEMA&gt;
49
+ usando o Prisma. Me devolva a URL "ver" da resposta.</pre>
50
+ <div class="acoes">
51
+ <button class="btn primo" id="copiar">Copiar</button>
52
+ <a class="btn" href="/api/manual" target="_blank">Ler o manual</a>
53
+ <span id="copiado" style="font-size:12px;color:#4ade80;opacity:0;transition:opacity .2s">copiado</span>
54
+ </div>
55
+ </section>
56
+
57
+ <h2 class="secao-tit">Criados recentemente</h2>
58
+ <div class="lista-arq" id="arquivos"></div>
59
+ <div class="vazio-msg" id="vazio" style="display:none">Nada ainda. Escolha uma ferramenta acima.</div>
60
+
61
+ </div>
62
+
63
+ <div class="folha" id="folha">
64
+ <div class="caixa"><h3>Manual para agentes</h3><pre id="manual">carregando…</pre></div>
65
+ </div>
66
+
67
+ <script src="/js/icones.js"></script>
68
+ <script>
69
+ (function () {
70
+ var $ = function (s) { return document.querySelector(s); };
71
+ var FERRS = [];
72
+
73
+ function fugir(s) { return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;"); }
74
+
75
+ fetch("/api/ferramentas").then(function (r) { return r.json(); }).then(function (lista) {
76
+ FERRS = lista;
77
+ $("#ferramentas").innerHTML = lista.map(function (f) {
78
+ return '<div class="cartao-ferr" data-slug="' + f.slug + '">' +
79
+ '<div class="icone" style="background:' + f.cor + '22;color:' + f.cor + '">' +
80
+ svgIcone(iconeDe(f.slug)) + "</div>" +
81
+ "<b>" + fugir(f.nome) + "</b>" +
82
+ '<div class="papel">' + fugir(f.papel) + "</div>" +
83
+ "<p>" + fugir(f.resumo) + "</p>" +
84
+ '<div class="equiv">no lugar de ' + fugir(f.equivale) + "</div></div>";
85
+ }).join("");
86
+
87
+ Array.prototype.forEach.call(document.querySelectorAll(".cartao-ferr"), function (c) {
88
+ c.onclick = function () { criar(c.dataset.slug); };
89
+ });
90
+ carregarArquivos();
91
+ });
92
+
93
+ function iconeDe(slug) {
94
+ return { prisma: "tela", tela: "lapis", prosa: "livro",
95
+ grade: "tabela", corte: "video", traco: "conector" }[slug] || "quadrado";
96
+ }
97
+
98
+ function corDe(slug) {
99
+ var f = FERRS.filter(function (x) { return x.slug === slug; })[0];
100
+ return f ? f.cor : "#888";
101
+ }
102
+
103
+ function criar(slug) {
104
+ fetch("/api/arquivos", {
105
+ method: "POST", headers: { "Content-Type": "application/json" },
106
+ body: JSON.stringify({ ferramenta: slug, titulo: "Sem título" }),
107
+ }).then(function (r) { return r.json(); }).then(function (a) {
108
+ location.href = "/t/" + slug + "/" + a.id;
109
+ });
110
+ }
111
+
112
+ function carregarArquivos() {
113
+ fetch("/api/arquivos").then(function (r) { return r.json(); }).then(function (lista) {
114
+ if (!lista.length) { $("#vazio").style.display = "block"; return; }
115
+ $("#arquivos").innerHTML = lista.map(function (a) {
116
+ var cor = corDe(a.ferramenta);
117
+ var quando = a.atualizado ? a.atualizado.slice(0, 10).split("-").reverse().join("/") : "";
118
+ return '<div class="item-arq">' +
119
+ '<div class="tag" style="background:' + cor + '22;color:' + cor + '">' + svgIcone(iconeDe(a.ferramenta)) + "</div>" +
120
+ '<div class="cresce"><b>' + fugir(a.titulo) + "</b>" +
121
+ '<div class="meta">' + fugir(a.ferramenta) + " · " + a.pecas +
122
+ (a.pecas === 1 ? " peça · " : " peças · ") + quando + "</div></div>" +
123
+ '<a class="btn" href="/t/' + a.ferramenta + "/" + a.id + '">Editar</a>' +
124
+ '<a class="btn primo" href="/v/' + a.id + '" target="_blank">Abrir</a></div>';
125
+ }).join("");
126
+ });
127
+ }
128
+
129
+ $("#copiar").onclick = function () {
130
+ navigator.clipboard.writeText($("#frase").textContent).then(function () {
131
+ var c = $("#copiado");
132
+ c.style.opacity = 1;
133
+ setTimeout(function () { c.style.opacity = 0; }, 1500);
134
+ });
135
+ };
136
+
137
+ var folha = $("#folha");
138
+ $("#btn-manual").onclick = function () {
139
+ folha.classList.add("on");
140
+ if ($("#manual").textContent.length < 200) {
141
+ fetch("/api/manual").then(function (r) { return r.text(); })
142
+ .then(function (t) { $("#manual").textContent = t; });
143
+ }
144
+ };
145
+ folha.onclick = function (e) { if (e.target === folha) folha.classList.remove("on"); };
146
+ })();
147
+ </script>
148
+ </body>
149
+ </html>
@@ -0,0 +1,262 @@
1
+ /* ==========================================================================
2
+ audio.js — trilha sonora do Corte.
3
+
4
+ Dois caminhos:
5
+ tipo "arquivo" → toca um áudio seu (upload em /midia ou URL direta)
6
+ tipo "gerada" → o estúdio sintetiza a música na hora, sem arquivo
7
+
8
+ Tudo passa por um nó de saída único para poder ser gravado junto do vídeo.
9
+ ========================================================================== */
10
+
11
+ var ESTILOS_TRILHA = {
12
+ pulso: {
13
+ rotulo: "Pulso", bpm: 112, raiz: 45,
14
+ escala: [0, 2, 3, 5, 7, 8, 10],
15
+ progressao: [0, 5, 3, 4],
16
+ bateria: true, ondaPad: "sawtooth", ondaArp: "square",
17
+ corte: 1600, ganhoPad: 0.055, ganhoArp: 0.05, arpDiv: 2,
18
+ },
19
+ ambiente: {
20
+ rotulo: "Ambiente", bpm: 68, raiz: 41,
21
+ escala: [0, 2, 4, 7, 9],
22
+ progressao: [0, 3, 5, 3],
23
+ bateria: false, ondaPad: "sawtooth", ondaArp: "sine",
24
+ corte: 900, ganhoPad: 0.085, ganhoArp: 0.028, arpDiv: 1,
25
+ },
26
+ lofi: {
27
+ rotulo: "Lo-fi", bpm: 76, raiz: 43,
28
+ escala: [0, 2, 3, 5, 7, 8, 10],
29
+ progressao: [0, 3, 4, 2],
30
+ bateria: true, ondaPad: "triangle", ondaArp: "sine",
31
+ corte: 1100, ganhoPad: 0.07, ganhoArp: 0.04, arpDiv: 2, solto: true,
32
+ },
33
+ epico: {
34
+ rotulo: "Épico", bpm: 88, raiz: 38,
35
+ escala: [0, 2, 3, 5, 7, 8, 10],
36
+ progressao: [0, 5, 6, 4],
37
+ bateria: true, ondaPad: "sawtooth", ondaArp: "triangle",
38
+ corte: 2200, ganhoPad: 0.09, ganhoArp: 0.055, arpDiv: 2, oitavaBaixo: true,
39
+ },
40
+ alegre: {
41
+ rotulo: "Alegre", bpm: 124, raiz: 48,
42
+ escala: [0, 2, 4, 5, 7, 9, 11],
43
+ progressao: [0, 4, 5, 3],
44
+ bateria: true, ondaPad: "triangle", ondaArp: "square",
45
+ corte: 2600, ganhoPad: 0.05, ganhoArp: 0.045, arpDiv: 4,
46
+ },
47
+ tenso: {
48
+ rotulo: "Tenso", bpm: 100, raiz: 36,
49
+ escala: [0, 1, 3, 5, 6, 8, 10],
50
+ progressao: [0, 0, 1, 0],
51
+ bateria: true, ondaPad: "sawtooth", ondaArp: "sawtooth",
52
+ corte: 700, ganhoPad: 0.075, ganhoArp: 0.035, arpDiv: 4,
53
+ },
54
+ };
55
+
56
+ function nomesEstilosTrilha() { return Object.keys(ESTILOS_TRILHA); }
57
+
58
+ function _hz(midi) { return 440 * Math.pow(2, (midi - 69) / 12); }
59
+
60
+
61
+ function criarTrilha(cfg, aoErro) {
62
+ cfg = cfg || {};
63
+ var Ctx = window.AudioContext || window.webkitAudioContext;
64
+ if (!Ctx) return null;
65
+
66
+ var ctx = new Ctx();
67
+ var mestre = ctx.createGain();
68
+ mestre.gain.value = cfg.volume != null ? cfg.volume : 0.7;
69
+
70
+ var compressor = ctx.createDynamicsCompressor();
71
+ compressor.threshold.value = -14;
72
+ compressor.ratio.value = 4;
73
+ mestre.connect(compressor);
74
+ compressor.connect(ctx.destination);
75
+
76
+ // saída paralela: é daqui que a gravação puxa o som
77
+ var paraGravar = ctx.createMediaStreamDestination();
78
+ compressor.connect(paraGravar);
79
+
80
+ var api = {
81
+ ctx: ctx,
82
+ fluxo: paraGravar.stream,
83
+ tocando: false,
84
+ tocar: function () {},
85
+ parar: function () {},
86
+ volume: function (v) { mestre.gain.value = v; },
87
+ esmaecer: function () {},
88
+ duracao: function () { return null; },
89
+ };
90
+
91
+ /* --------------------------------------------------------- arquivo ---- */
92
+
93
+ if (cfg.tipo !== "gerada" && cfg.src) {
94
+ var el = new Audio();
95
+ el.crossOrigin = "anonymous";
96
+ el.src = cfg.src;
97
+ el.loop = cfg.repetir !== false;
98
+ el.preload = "auto";
99
+ el.onerror = function () { if (aoErro) aoErro("não consegui carregar o áudio"); };
100
+
101
+ var fonte = null;
102
+ try {
103
+ fonte = ctx.createMediaElementSource(el);
104
+ fonte.connect(mestre);
105
+ } catch (_) {
106
+ el.volume = mestre.gain.value; // sem Web Audio: toca direto mesmo
107
+ }
108
+
109
+ api.elemento = el;
110
+ api.tocar = function () {
111
+ if (ctx.state === "suspended") ctx.resume();
112
+ el.currentTime = cfg.inicio || 0;
113
+ var p = el.play();
114
+ if (p && p.catch) p.catch(function () { if (aoErro) aoErro("o navegador bloqueou o áudio; clique na tela"); });
115
+ api.tocando = true;
116
+ };
117
+ api.parar = function () { el.pause(); api.tocando = false; };
118
+ api.duracao = function () { return isFinite(el.duration) ? el.duration : null; };
119
+ api.esmaecer = function (segundos) {
120
+ var agora = ctx.currentTime;
121
+ mestre.gain.cancelScheduledValues(agora);
122
+ mestre.gain.setValueAtTime(mestre.gain.value, agora);
123
+ mestre.gain.linearRampToValueAtTime(0.0001, agora + (segundos || 1.2));
124
+ };
125
+ return api;
126
+ }
127
+
128
+ /* ---------------------------------------------------------- gerada ---- */
129
+
130
+ var E = ESTILOS_TRILHA[cfg.estilo] || ESTILOS_TRILHA.pulso;
131
+ var tempoBatida = 60 / E.bpm;
132
+ var passo = 0, proximo = 0, relogio = null;
133
+
134
+ function grau(n) {
135
+ var oitava = Math.floor(n / E.escala.length);
136
+ return E.raiz + E.escala[((n % E.escala.length) + E.escala.length) % E.escala.length] + 12 * oitava;
137
+ }
138
+
139
+ function nota(freq, quando, dur, onda, ganho, corte) {
140
+ var o = ctx.createOscillator();
141
+ var g = ctx.createGain();
142
+ var f = ctx.createBiquadFilter();
143
+ f.type = "lowpass";
144
+ f.frequency.setValueAtTime(corte || E.corte, quando);
145
+ o.type = onda;
146
+ o.frequency.setValueAtTime(freq, quando);
147
+ g.gain.setValueAtTime(0, quando);
148
+ g.gain.linearRampToValueAtTime(ganho, quando + Math.min(0.06, dur * 0.3));
149
+ g.gain.exponentialRampToValueAtTime(0.0001, quando + dur);
150
+ o.connect(f); f.connect(g); g.connect(mestre);
151
+ o.start(quando);
152
+ o.stop(quando + dur + 0.05);
153
+ }
154
+
155
+ function pad(acorde, quando, dur) {
156
+ [0, 2, 4].forEach(function (t, i) {
157
+ nota(_hz(grau(acorde + t) + 12), quando, dur, E.ondaPad,
158
+ E.ganhoPad * (i === 0 ? 1 : 0.7), E.corte);
159
+ });
160
+ nota(_hz(grau(acorde) - (E.oitavaBaixo ? 12 : 0)), quando, dur, "sine", 0.11, 420);
161
+ }
162
+
163
+ function bumbo(quando) {
164
+ var o = ctx.createOscillator(), g = ctx.createGain();
165
+ o.type = "sine";
166
+ o.frequency.setValueAtTime(150, quando);
167
+ o.frequency.exponentialRampToValueAtTime(45, quando + 0.12);
168
+ g.gain.setValueAtTime(0.32, quando);
169
+ g.gain.exponentialRampToValueAtTime(0.0001, quando + 0.28);
170
+ o.connect(g); g.connect(mestre);
171
+ o.start(quando); o.stop(quando + 0.3);
172
+ }
173
+
174
+ var ruido = null;
175
+ function bufferRuido() {
176
+ if (ruido) return ruido;
177
+ ruido = ctx.createBuffer(1, ctx.sampleRate * 0.4, ctx.sampleRate);
178
+ var d = ruido.getChannelData(0);
179
+ for (var i = 0; i < d.length; i++) d[i] = Math.random() * 2 - 1;
180
+ return ruido;
181
+ }
182
+
183
+ function chimbal(quando, forte) {
184
+ var s = ctx.createBufferSource(), g = ctx.createGain(), f = ctx.createBiquadFilter();
185
+ s.buffer = bufferRuido();
186
+ f.type = "highpass";
187
+ f.frequency.value = forte ? 4000 : 7000;
188
+ g.gain.setValueAtTime(forte ? 0.09 : 0.045, quando);
189
+ g.gain.exponentialRampToValueAtTime(0.0001, quando + (forte ? 0.16 : 0.055));
190
+ s.connect(f); f.connect(g); g.connect(mestre);
191
+ s.start(quando); s.stop(quando + 0.25);
192
+ }
193
+
194
+ function caixa(quando) {
195
+ var s = ctx.createBufferSource(), g = ctx.createGain(), f = ctx.createBiquadFilter();
196
+ s.buffer = bufferRuido();
197
+ f.type = "bandpass";
198
+ f.frequency.value = 1900;
199
+ g.gain.setValueAtTime(0.13, quando);
200
+ g.gain.exponentialRampToValueAtTime(0.0001, quando + 0.19);
201
+ s.connect(f); f.connect(g); g.connect(mestre);
202
+ s.start(quando); s.stop(quando + 0.25);
203
+ }
204
+
205
+ function agendarPasso(p, quando) {
206
+ var naBarra = p % 16;
207
+ var compasso = Math.floor(p / 16);
208
+ var acorde = E.progressao[compasso % E.progressao.length];
209
+
210
+ if (naBarra === 0) pad(acorde, quando, tempoBatida * 3.9);
211
+
212
+ // arpejo
213
+ var div = E.arpDiv || 2;
214
+ if (naBarra % (4 / div) === 0) {
215
+ var passoArp = Math.floor(naBarra / (4 / div));
216
+ var seq = [0, 2, 4, 2, 4, 6, 4, 2];
217
+ nota(_hz(grau(acorde + seq[passoArp % seq.length]) + 12),
218
+ quando, tempoBatida * 0.55, E.ondaArp, E.ganhoArp, E.corte * 1.4);
219
+ }
220
+
221
+ if (E.bateria) {
222
+ var atraso = E.solto ? 0.018 : 0; // lo-fi: levada atrasada
223
+ if (naBarra % 8 === 0) bumbo(quando);
224
+ if (naBarra === 4 || naBarra === 12) caixa(quando + atraso);
225
+ if (naBarra % 2 === 0) chimbal(quando + atraso, naBarra % 8 === 4);
226
+ }
227
+ }
228
+
229
+ function bombear() {
230
+ while (proximo < ctx.currentTime + 0.2) {
231
+ agendarPasso(passo, proximo);
232
+ proximo += tempoBatida / 4;
233
+ passo++;
234
+ }
235
+ }
236
+
237
+ api.tocar = function () {
238
+ if (api.tocando) return;
239
+ if (ctx.state === "suspended") ctx.resume();
240
+ passo = 0;
241
+ proximo = ctx.currentTime + 0.08;
242
+ api.tocando = true;
243
+ bombear();
244
+ relogio = setInterval(bombear, 25);
245
+ };
246
+ api.parar = function () {
247
+ api.tocando = false;
248
+ clearInterval(relogio);
249
+ };
250
+ api.esmaecer = function (segundos) {
251
+ var agora = ctx.currentTime;
252
+ mestre.gain.cancelScheduledValues(agora);
253
+ mestre.gain.setValueAtTime(mestre.gain.value, agora);
254
+ mestre.gain.linearRampToValueAtTime(0.0001, agora + (segundos || 1.2));
255
+ };
256
+
257
+ return api;
258
+ }
259
+
260
+ if (typeof module !== "undefined" && module.exports) {
261
+ module.exports = { criarTrilha: criarTrilha, ESTILOS_TRILHA: ESTILOS_TRILHA };
262
+ }