sagadeck 1.2.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 +21 -0
- package/README.md +305 -0
- package/SKILL.md +148 -0
- package/bin/sagadeck.js +511 -0
- package/docs/ANDAMENTO.md +1255 -0
- package/docs/EXPERIENCIAS.md +39 -0
- package/docs/PLANO-EXPERIENCIAS.md +39 -0
- package/docs/PLANO-TESTE-AULAS-TECNICAS.md +44 -0
- package/docs/PLANO-TESTE-DESINFORMACAO.md +38 -0
- package/docs/REFERENCIA.md +1131 -0
- package/package.json +59 -0
- package/src/adaptive-layouts.js +18 -0
- package/src/ai/art-direction.js +8 -0
- package/src/ai/commands.js +130 -0
- package/src/ai/context.js +245 -0
- package/src/ai/deck-ai.js +1135 -0
- package/src/ai/directions.js +74 -0
- package/src/ai/document-materials.js +49 -0
- package/src/ai/document-visuals.js +215 -0
- package/src/ai/evaluation.js +31 -0
- package/src/ai/ground.js +55 -0
- package/src/ai/llm.js +292 -0
- package/src/ai/quality.js +51 -0
- package/src/ai/relay.js +45 -0
- package/src/ai/staged-generation.js +24 -0
- package/src/ai/transform.js +858 -0
- package/src/ai/usage.js +17 -0
- package/src/ai/variety.js +80 -0
- package/src/ai/video-frames.js +24 -0
- package/src/ai/video-generation.js +126 -0
- package/src/ai/web-operation.js +31 -0
- package/src/algo-catalog.js +153 -0
- package/src/algo-trace.js +139 -0
- package/src/api-client.js +514 -0
- package/src/api-collections.js +14 -0
- package/src/api-demo.js +222 -0
- package/src/aspect.js +40 -0
- package/src/build.js +323 -0
- package/src/carousel.js +56 -0
- package/src/chrome.js +65 -0
- package/src/code-highlight.js +68 -0
- package/src/code-lab-browser.js +22 -0
- package/src/code-lab.js +3 -0
- package/src/code-language.js +52 -0
- package/src/csv.js +74 -0
- package/src/decision-lab.js +20 -0
- package/src/deck-file.js +119 -0
- package/src/diagram/napkin-examples.js +13 -0
- package/src/diagram/napkin.js +539 -0
- package/src/docx.js +128 -0
- package/src/dynamics/git-sim.js +200 -0
- package/src/dynamics/layouts.js +112 -0
- package/src/elements.js +554 -0
- package/src/experiences.js +124 -0
- package/src/exploration.js +31 -0
- package/src/export/browser.js +53 -0
- package/src/export/estudo.js +67 -0
- package/src/export/pptx.js +475 -0
- package/src/export/roteiro.js +40 -0
- package/src/export/shots.js +182 -0
- package/src/figures/charts.js +383 -0
- package/src/figures/diagrams.js +117 -0
- package/src/figures/icons.js +166 -0
- package/src/figures/material-pictos.js +20 -0
- package/src/figures/pictos.js +255 -0
- package/src/figures/points.js +106 -0
- package/src/figures/qr.js +29 -0
- package/src/figures/ufmap.js +48 -0
- package/src/fiscal/autofix.js +377 -0
- package/src/fiscal/normalize.js +115 -0
- package/src/graph-lab.js +24 -0
- package/src/hologram.js +30 -0
- package/src/identity.js +86 -0
- package/src/import/crop.js +59 -0
- package/src/import/index.js +132 -0
- package/src/import/legacy-word.js +42 -0
- package/src/import/merge.js +45 -0
- package/src/import/office.js +123 -0
- package/src/import/omml.js +69 -0
- package/src/import/pdf-render.js +56 -0
- package/src/import/pptx.js +701 -0
- package/src/import/xml.js +42 -0
- package/src/infographic.js +401 -0
- package/src/layouts.js +723 -0
- package/src/lessons.js +171 -0
- package/src/library.js +399 -0
- package/src/markup.js +116 -0
- package/src/master.js +214 -0
- package/src/mcp/server.js +371 -0
- package/src/meeting.js +42 -0
- package/src/motion.js +56 -0
- package/src/package.js +116 -0
- package/src/playground.js +19 -0
- package/src/portal-scene.js +20 -0
- package/src/preferences.js +61 -0
- package/src/protect.js +51 -0
- package/src/purpose.js +24 -0
- package/src/pytrace.js +1092 -0
- package/src/research/duckduckgo.js +73 -0
- package/src/research/research.js +202 -0
- package/src/research/svg-image.js +17 -0
- package/src/research/visuals.js +52 -0
- package/src/runtime/api-collection-ui.js +5 -0
- package/src/runtime/api-collections.js +28 -0
- package/src/runtime/api-core.js +530 -0
- package/src/runtime/api-ui.js +966 -0
- package/src/runtime/base.css +1774 -0
- package/src/runtime/calc.js +139 -0
- package/src/runtime/code-lab.js +371 -0
- package/src/runtime/decision-lab.js +49 -0
- package/src/runtime/diagram.js +341 -0
- package/src/runtime/fit.js +268 -0
- package/src/runtime/fonts/caveat.css +2 -0
- package/src/runtime/fonts/comic-neue.css +3 -0
- package/src/runtime/fonts/fredoka.css +2 -0
- package/src/runtime/fonts/index.json +10 -0
- package/src/runtime/fonts/inter.css +2 -0
- package/src/runtime/fonts/patrick-hand.css +2 -0
- package/src/runtime/fonts/plus-jakarta-sans.css +2 -0
- package/src/runtime/fonts/press-start-2p.css +2 -0
- package/src/runtime/fonts/vt323.css +2 -0
- package/src/runtime/formula.js +148 -0
- package/src/runtime/graph-lab.js +72 -0
- package/src/runtime/meeting.css +7 -0
- package/src/runtime/meeting.js +10 -0
- package/src/runtime/motion.js +15 -0
- package/src/runtime/playground.js +11 -0
- package/src/runtime/points.js +19 -0
- package/src/runtime/portal-scene.css +6 -0
- package/src/runtime/portal-scene.js +22 -0
- package/src/runtime/runtime.js +827 -0
- package/src/runtime/science.js +89 -0
- package/src/runtime/skins/aluminio.css +31 -0
- package/src/runtime/skins/arcade.css +25 -0
- package/src/runtime/skins/aurora.css +17 -0
- package/src/runtime/skins/bauhaus.css +19 -0
- package/src/runtime/skins/editorial.css +19 -0
- package/src/runtime/skins/jornal.css +17 -0
- package/src/runtime/skins/manual-noite.css +14 -0
- package/src/runtime/skins/manual.css +14 -0
- package/src/runtime/skins/noite.css +21 -0
- package/src/runtime/skins/oceano.css +17 -0
- package/src/runtime/skins/pop.css +23 -0
- package/src/runtime/skins/prata.css +18 -0
- package/src/runtime/skins/prisma.css +28 -0
- package/src/runtime/skins/rabisco.css +18 -0
- package/src/runtime/skins/relevo.css +33 -0
- package/src/runtime/skins/sinal.css +16 -0
- package/src/runtime/skins/terminal.css +23 -0
- package/src/runtime/vendor/KATEX-LICENSE.txt +21 -0
- package/src/runtime/vendor/MERMAID-LICENSE.txt +21 -0
- package/src/runtime/vendor/PLOTLY-LICENSE.txt +21 -0
- package/src/runtime/vendor/katex.css +1 -0
- package/src/runtime/vendor/mermaid.min.js +3636 -0
- package/src/runtime/vendor/plotly.min.js +3620 -0
- package/src/runtime/video-player.js +35 -0
- package/src/science.js +162 -0
- package/src/studio/ai-response.js +36 -0
- package/src/studio/api-routes.js +152 -0
- package/src/studio/assets/contexto/leia-me.md +12 -0
- package/src/studio/assets/contexto/vendas.csv +7 -0
- package/src/studio/assets/imagens/colecao-arquitetura.png +0 -0
- package/src/studio/assets/imagens/colecao-cor.png +0 -0
- package/src/studio/assets/imagens/colecao-equipe.png +0 -0
- package/src/studio/assets/imagens/lavanda-cover.jpg +0 -0
- package/src/studio/code-routes.js +32 -0
- package/src/studio/command-approval.js +48 -0
- package/src/studio/demo-decks.js +98 -0
- package/src/studio/direction-routes.js +29 -0
- package/src/studio/errors.js +65 -0
- package/src/studio/exploration-demo.js +17 -0
- package/src/studio/exporting.js +105 -0
- package/src/studio/generation-report.js +10 -0
- package/src/studio/instance.js +48 -0
- package/src/studio/layout-samples.js +152 -0
- package/src/studio/project.js +371 -0
- package/src/studio/public/api-collections.js +28 -0
- package/src/studio/public/app.js +5265 -0
- package/src/studio/public/art-preview.js +10 -0
- package/src/studio/public/calc-fields.js +11 -0
- package/src/studio/public/chat-progress.js +47 -0
- package/src/studio/public/developer-fields.js +22 -0
- package/src/studio/public/explorer.js +443 -0
- package/src/studio/public/history.js +58 -0
- package/src/studio/public/index.html +867 -0
- package/src/studio/public/inspector.js +270 -0
- package/src/studio/public/lab-fields.js +5 -0
- package/src/studio/public/library.css +275 -0
- package/src/studio/public/library.html +55 -0
- package/src/studio/public/library.js +471 -0
- package/src/studio/public/merge-decks.js +155 -0
- package/src/studio/public/review-ui.js +100 -0
- package/src/studio/public/screenshot-editor.js +140 -0
- package/src/studio/public/share-ui.js +72 -0
- package/src/studio/public/slide-clipboard.js +23 -0
- package/src/studio/public/slide-form.js +1263 -0
- package/src/studio/public/studio-awareness.js +154 -0
- package/src/studio/public/studio-next.css +303 -0
- package/src/studio/public/style.css +1262 -0
- package/src/studio/public/ui-icons.js +2 -0
- package/src/studio/public/viewers.js +372 -0
- package/src/studio/public/visual-editor.js +326 -0
- package/src/studio/public/workspace.js +18 -0
- package/src/studio/public-files.js +1 -0
- package/src/studio/request-helpers.js +41 -0
- package/src/studio/server.js +1583 -0
- package/src/studio/share-routes.js +146 -0
- package/src/studio/slide-copy-routes.js +26 -0
- package/src/studio/slide-copy.js +30 -0
- package/src/studio/snapshot.js +160 -0
- package/src/studio/style-routes.js +103 -0
- package/src/studio/template-collections.js +97 -0
- package/src/studio/versions.js +95 -0
- package/src/studio/video-download.js +29 -0
- package/src/studio/visual-keys.js +52 -0
- package/src/studio/workspaces.js +39 -0
- package/src/table.js +93 -0
- package/src/templates/scaffold.js +372 -0
- package/src/themes.js +604 -0
- package/src/trace-view.js +450 -0
- package/src/uid.js +19 -0
- package/src/video-player.js +40 -0
- package/src/visual-edits.js +99 -0
- package/src/ws.js +134 -0
- package/templates/cenario/Texto no cen/303/241rio.yaml" +171 -0
- package/templates/cenario/imagens/cidade.svg +30 -0
- package/templates/cenario/imagens/pessoa.svg +15 -0
- package/templates/cenario-e-ciencia.yaml +47 -0
- package/templates/ensaio-api.yaml +160 -0
- package/templates/exemplo-alegre.yaml +75 -0
- package/templates/exemplo-keynote.yaml +90 -0
- package/templates/exemplo-napkin.yaml +139 -0
- package/templates/exemplo.yaml +228 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { portalDemo } from '../portal-scene.js';
|
|
2
|
+
// Modelos de fábrica são código; as apresentações criadas ficam somente na biblioteca.
|
|
3
|
+
import { COLLECTION_NAMES, COLLECTION_ASSETS, collectionDeck } from './template-collections.js';
|
|
4
|
+
import { LAYOUT_SAMPLES } from './layout-samples.js';
|
|
5
|
+
import { explorationDemo } from './exploration-demo.js';
|
|
6
|
+
export function demoAssets(kind) { return kind === 'lavanda' || kind === 'novidades' ? ['lavanda-cover.jpg'] : COLLECTION_ASSETS[kind] || []; }
|
|
7
|
+
// arquivos do projeto que o modelo traz (fora de imagens/): a planilha e as anotações da demo de novidades
|
|
8
|
+
export function demoProjectFiles(kind) { return kind === 'novidades' ? ['contexto/vendas.csv', 'contexto/leia-me.md'] : []; }
|
|
9
|
+
const item=(title,text,extra={})=>({title,text,...extra});
|
|
10
|
+
const route='flowchart LR\n A([Pedido]):::hi --> B{Dados válidos?}\n B -->|sim| C[Executar tarefa]\n B -.->|não| D[Informar erro]:::vazado\n C ==> E[Guardar resultado]:::em';
|
|
11
|
+
export const DEMO_NAMES={cinematica:"Demo — mundos que se transformam",explorar:'Demo — explore uma ideia',lavanda:'Estúdio lavanda',executivo:'Relatório executivo',workshop:'Workshop visual',compacto:'Demo — material de consulta',avancado:'Demo — recursos avançados',diagramas:'Demo — diagramas vivos',novidades:'Demo — novidades',hidraulica:'Aula de hidráulica',algoritmos:'Aula de algoritmos',...COLLECTION_NAMES};
|
|
12
|
+
export function demoDeck(kind) {
|
|
13
|
+
if(kind==='cinematica')return portalDemo();
|
|
14
|
+
if(kind==='explorar')return explorationDemo();
|
|
15
|
+
if(Object.hasOwn(COLLECTION_NAMES,kind))return collectionDeck(kind);
|
|
16
|
+
if(!DEMO_NAMES[kind])throw Error('Modelo não encontrado.');
|
|
17
|
+
// cada demo com a sua cara (tema, tom e figura da capa): na vitrine, as capas não podem parecer a mesma
|
|
18
|
+
const LOOK={lavanda:['bauhaus','lavanda'],executivo:['prata','corporativo'],workshop:['sinal','lavanda'],compacto:['manual'],avancado:['terminal'],diagramas:['oceano'],novidades:['pop'],hidraulica:['oceano'],algoritmos:['noite']};
|
|
19
|
+
const [theme,palette]=LOOK[kind]||['editorial','lavanda'];
|
|
20
|
+
const common={title:DEMO_NAMES[kind],theme,...(palette?{palette}:{}),duration:15,maxWords:140};
|
|
21
|
+
const COVER_FIG={compacto:{icon:'book-open',size:300},avancado:{icon:'terminal',size:300},diagramas:{icon:'workflow',size:320},novidades:{icon:'sparkles',size:320},hidraulica:{icon:'waves',size:340},algoritmos:{icon:'chart-bar',size:320}};
|
|
22
|
+
const cover={layout:'cover',title:DEMO_NAMES[kind],subtitle:'Um modelo para adaptar ao seu conteúdo',kicker:'SAGADECK · MODELOS',tone:{compacto:'light',diagramas:'accent',novidades:'light',hidraulica:'light'}[kind]||'dark',...(COVER_FIG[kind]?{figure:COVER_FIG[kind]}:{})};
|
|
23
|
+
const end={layout:'end',title:'O próximo passo\ncomeça aqui.',subtitle:'Duplique este modelo na biblioteca e adapte os textos.'};
|
|
24
|
+
const items=[item('Descobrir','Comece pela pergunta certa.',{icon:'search'}),item('Experimentar','Teste uma hipótese com dados.',{icon:'flask-conical'}),item('Construir','Transforme o aprendizado em produto.',{icon:'blocks'}),item('Observar','Acompanhe o uso e os resultados.',{icon:'chart-column'}),item('Evoluir','Use o que aprendeu para melhorar.',{icon:'sparkles'})];
|
|
25
|
+
let slides;
|
|
26
|
+
if(kind==='lavanda')slides=[cover,{layout:'mosaic',title:'Cinco ideias, uma composição.',items},{layout:'ribbon',title:'Um caminho em três momentos.',items:items.slice(0,3),tone:'accent'},{layout:'quote',quote:'Uma boa apresentação deixa a ideia respirar.',by:'Princípio deste modelo',tone:'dark'},{layout:'mosaic',title:'A grade cresce com o conteúdo.',items:[...items,...items.slice(0,3)].map((x,i)=>({...x,title:`${i+1}. ${x.title}`}))},end];
|
|
27
|
+
if(kind==='executivo')slides=[{...cover,subtitle:'Decisões claras, números contextualizados.'},{layout:'mosaic',title:'O que precisa de uma decisão.',items:[item('Prioridade','Melhorar o atendimento nos canais digitais.',{value:'01'}),item('Investimento','Validar o piloto antes de expandir.',{value:'02'}),item('Próximo marco','Apresentar resultados em quatro semanas.',{value:'03'})]},{layout:'stats',title:'O piloto em números',stats:[{value:'24',label:'participantes'},{value:'4',label:'semanas'},{value:'3',label:'hipóteses'}],source:'Dados fictícios para demonstrar o modelo.'},{layout:'ribbon',title:'Plano de trabalho',items:items.slice(0,4)},{layout:'compare',title:'A decisão em perspectiva',left:{title:'Continuar como está',text:'Sem custo de mudança; os problemas atuais permanecem.'},right:{title:'Testar em pequena escala',text:'Aprendizado limitado, com critérios de sucesso definidos.',hl:true}},end];
|
|
28
|
+
if(kind==='workshop')slides=[{...cover,subtitle:'Aprender, experimentar, explicar.'},{layout:'question',question:'O que acontece depois do pedido?',options:['A resposta vem na hora','Recebemos um identificador','Depende do modo'],hint:'Escolha antes de avançar.'},{layout:'diagram',title:'Do pedido ao resultado',mermaid:route},{layout:'codewalk',title:'Uma chamada, três observações',language:'javascript',code:'const response = await fetch(url);\nif (!response.ok) throw new Error(response.status);\nconst dados = await response.json();\nconsole.log(dados);',steps:[{title:'Pedido',text:'A execução aguarda a resposta HTTP.',highlight:[1]},{title:'Validação',text:'Um erro HTTP interrompe o fluxo.',highlight:[2]},{title:'Resultado',text:'O JSON vira um objeto.',highlight:[3,4]}]},{layout:'dossier',title:'Para consultar depois',items:[item('URL','Identifica o recurso. Evite colocar segredos no endereço.'),item('Status','Indica o resultado HTTP. Um 200 não substitui validar o conteúdo.'),item('Corpo','Carrega os dados do pedido ou da resposta.'),item('Timeout','Limita quanto tempo a aplicação espera.')]},end];
|
|
29
|
+
if(kind==='compacto') {
|
|
30
|
+
const code=['import time','import requests','','def acompanhar(base, pedido, timeout=60):',' inicio = requests.post(',' f"{base}/tarefas", json=pedido, timeout=10',' )',' inicio.raise_for_status()',' tarefa = inicio.json()["id"]',' limite = time.monotonic() + timeout','',' while time.monotonic() < limite:',' resposta = requests.get(',' f"{base}/tarefas/{tarefa}", timeout=10',' )',' resposta.raise_for_status()',' dados = resposta.json()',' status = dados["status"]','',' if status == "finished":',' return dados["resultado"]',' if status == "failed":',' raise RuntimeError(dados["erro"])','',' time.sleep(1)','',' raise TimeoutError("A tarefa não terminou")'].join('\n');
|
|
31
|
+
slides=[{...cover,subtitle:'Código, payloads e instruções no mesmo material.'},{layout:'code',title:'No palco: explique a ideia.',language:'python',code:'resposta = iniciar(pedido)\nid = resposta["id"]\n\nwhile not terminou(id):\n esperar(1)\n\nmostrar(resultado(id))',notes:'Versão conceitual para explicar ao público; não é código executável.'},{layout:'code',title:'Na consulta: o exemplo completo.',density:'dense',language:'python',code,source:'API fictícia. Ajuste as rotas e instale requests. 27 linhas sem ocultar o final.'},{layout:'dossier',title:'Como ler a resposta',items:[item('Identificador','Guarde o id devolvido ao iniciar. Ele é usado nas consultas seguintes.',{code:'{"id":"tarefa-42","status":"started"}'}),item('Enquanto executa','Consulte com um intervalo definido e limite total de espera.',{code:'{"id":"tarefa-42","status":"running"}'}),item('Ao finalizar','Valide o status antes de ler o resultado de cada etapa.',{code:'{"status":"finished",\n "resultado":[\n {"etapa":"ocr","paginas":12},\n {"etapa":"resumo","texto":"…"}\n ]}'}),item('Se falhar','Trate falha, timeout e resposta inválida separadamente. Não repita operações não idempotentes sem critério.',{code:'{"status":"failed",\n "erro":"arquivo ilegível"}'})]},{layout:'mosaic',title:'Oito detalhes em uma página.',items:['URL','Método','Headers','Corpo','Status','Timeout','Polling','Resposta'].map(title=>item(title,'Confira este campo na documentação do serviço antes de executar.')),source:'Demonstração de grade automática; edite, insira ou exclua um item.'},end];
|
|
32
|
+
}
|
|
33
|
+
if(kind==='diagramas')slides=[{...cover,subtitle:'Edite o código Mermaid no painel e veja o desenho mudar.'},{layout:'diagram',title:'Fluxo com decisão',mermaid:route,caption:'Abra Formatar → Código Mermaid. Troque um rótulo ou acrescente uma etapa.'},{layout:'diagram',title:'Uma conversa entre serviços',mermaid:'sequenceDiagram\n participant App\n participant API\n participant Worker\n App->>API: Iniciar tarefa\n API->>Worker: Processar\n API-->>App: id da tarefa\n loop Até finalizar\n App->>API: Consultar id\n API-->>App: status\n end\n Worker-->>API: Resultado'},{layout:'diagram',title:'Estados de uma execução',mermaid:'stateDiagram-v2\n [*] --> Criada\n Criada --> Executando\n Executando --> Finalizada\n Executando --> Falhou\n Falhou --> NovaTentativa\n NovaTentativa --> Executando\n Finalizada --> [*]'},{layout:'diagram',title:'O que entra no produto?',mermaid:'mindmap\n root((Produto))\n Pessoas\n Necessidades\n Experiência\n Tecnologia\n Serviços\n Dados\n Operação\n Métricas\n Melhoria'},end];
|
|
34
|
+
if(kind==='avancado')slides=[
|
|
35
|
+
{...cover,subtitle:'Quando a apresentação também precisa servir de consulta.'},
|
|
36
|
+
{layout:'code',title:'Exemplo completo: acompanhar uma tarefa',density:'dense',language:'python',code:'import time\n\nimport requests\n\ndef acompanhar(base, tarefa, timeout=60):\n limite = time.monotonic() + timeout\n while time.monotonic() < limite:\n resposta = requests.get(\n f"{base}/tarefas/{tarefa}",\n timeout=10,\n )\n resposta.raise_for_status()\n dados = resposta.json()\n status = dados["status"]\n\n if status == "finished":\n return dados["resultado"]\n\n if status == "failed":\n raise RuntimeError(dados["erro"])\n\n time.sleep(1)\n\n raise TimeoutError("A tarefa não terminou")',source:'Exemplo demonstrativo completo. As linhas numeradas acompanham o código até o final.'},
|
|
37
|
+
{layout:'dossier',title:'Dados para acompanhar a tarefa',items:[item('Identificador','Guarde o id devolvido ao iniciar o processamento.',{code:'{"id":"tarefa-42","status":"started"}'}),item('Enquanto executa','Consulte o estado em intervalos definidos e com timeout.',{code:'{"id":"tarefa-42","status":"running"}'}),item('Resultado','Leia o conteúdo somente depois de confirmar o estado final.',{code:'{"status":"finished","resultado":{"paginas":12}}'}),item('Falha','Exiba a causa sem repetir operações não idempotentes.',{code:'{"status":"failed","erro":"arquivo ilegível"}'})]},
|
|
38
|
+
{layout:'mosaic',title:'Oito campos para revisar',items:['URL','Método','Headers','Corpo','Status','Timeout','Polling','Resposta'].map((title)=>item(title,'Confira este campo na documentação antes de executar.'))},
|
|
39
|
+
{layout:'statement',kicker:'AVISO · SEGURANÇA',text:'Não coloque tokens no código ou na apresentação.',add:{aviso:{tipo:'perigo',titulo:'Segredo exposto',texto:'Use Ambientes para guardar chaves na sua máquina. Elas não devem entrar no YAML do deck.'}}},
|
|
40
|
+
end,
|
|
41
|
+
];
|
|
42
|
+
// um slide de cada recurso novo; a pasta do projeto já traz contexto/vendas.csv (aba Arquivos › planilha)
|
|
43
|
+
if(kind==='novidades'){
|
|
44
|
+
const vendas={labels:['jan','fev','mar','abr','mai','jun'],series:[{name:'Norte',values:[10.5,12,15,17,16,19]},{name:'Sul',values:[7,9,8,11,13,14]}]};
|
|
45
|
+
slides=[
|
|
46
|
+
{...cover,subtitle:'Carrossel, gráficos do Excel, fórmulas vivas, dinâmicas a dois e a pasta do projeto.',kicker:'SAGADECK · NOVIDADES'},
|
|
47
|
+
{layout:'dossier',title:'Onde está cada novidade',items:[
|
|
48
|
+
item('Pasta do projeto','Aba Arquivos, à esquerda: a pasta da apresentação, com contexto/ para anexos, prints e planilhas.',{icon:'folder-tree'}),
|
|
49
|
+
item('Planilha → gráfico','Abra contexto/vendas.csv: o Studio lê os tipos das colunas e sugere gráficos, com ou sem IA.',{icon:'table'}),
|
|
50
|
+
item('Colar do Excel','No gráfico de dados, Formatar › Dados: Ctrl+V de uma tabela preenche a planilha.',{icon:'clipboard-paste'}),
|
|
51
|
+
item('Fórmulas vivas','Fórmulas e funções: escreva a fórmula; cada letra vira um controle deslizante.',{icon:'sigma'}),
|
|
52
|
+
item('Dinâmicas a dois','Duelo de commits, dois terminais e turnos: um quadro por clique.',{icon:'users'}),
|
|
53
|
+
item('Carrossel e zoom','Carrossel em roda ou em anéis; o Foco guiado aproxima devagar de cada detalhe.',{icon:'sparkles'})]},
|
|
54
|
+
{...LAYOUT_SAMPLES.carousel,title:'Carrossel em roda'},
|
|
55
|
+
{...LAYOUT_SAMPLES.carousel,title:'Carrossel em anéis',style:'rings'},
|
|
56
|
+
{layout:'chart',title:'Vendas por região, direto da planilha',chart:{chart:'line',csv:'contexto/vendas.csv',...vendas,xLabel:'Mês',yLabel:'R$ milhões'},side:'Este gráfico lê **contexto/vendas.csv**. Mude um número lá (aba Arquivos) e ele acompanha.'},
|
|
57
|
+
{layout:'chart',title:'Colado do Excel: colunas agrupadas',chart:{chart:'column',...vendas,xLabel:'Mês'},side:'No Formatar, a planilha do gráfico aceita **Ctrl+V** de uma tabela do Excel: a primeira linha vira o nome das séries.'},
|
|
58
|
+
{...LAYOUT_SAMPLES.science},
|
|
59
|
+
{layout:'science',title:'Queda livre: medida × teoria',plot:{functions:[{fn:'h0 - g*x^2/2',name:'teoria'}],x:[0,2],params:{h0:{value:20,min:5,max:40,label:'altura'},g:{value:9.8,min:1,max:25,label:'gravidade'}},points:'t\tmedido\n0\t20\n0,5\t18,9\n1\t15,2\n1,5\t8,8\n1,9\t2,1',pointsName:'medido'}},
|
|
60
|
+
{...LAYOUT_SAMPLES.duel},
|
|
61
|
+
{...LAYOUT_SAMPLES.terminals,turns:[...LAYOUT_SAMPLES.terminals.turns]},
|
|
62
|
+
{...LAYOUT_SAMPLES.turns},
|
|
63
|
+
{layout:'spotlight',title:'Foco guiado com zoom lento',image:'imagens/lavanda-cover.jpg',zoom:true,hotspots:[{title:'A imagem inteira',text:'O primeiro passo mostra tudo.',x:0,y:0,width:100,height:100,zoom:false},{title:'Um detalhe',text:'A imagem aproxima devagar do ponto da vez.',x:30,y:25,width:30,height:35},{title:'Outro detalhe',text:'Cada foco pode ter o seu zoom.',x:60,y:55,width:25,height:30,zoom:2.4}]},
|
|
64
|
+
end,
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
// aula de engenharia: equações, curva que se mexe, calculadora ao vivo, exercício resolvido passo a passo
|
|
68
|
+
if(kind==='hidraulica')slides=[
|
|
69
|
+
{...cover,kicker:'HIDRÁULICA · AULA 3',title:'Escoamento em tubos:\ndo laminar ao turbulento',subtitle:'Navier–Stokes, número de Reynolds e perda de carga, com contas e um exercício resolvido.'},
|
|
70
|
+
{layout:'dossier',title:'Três ideias para hoje',items:[item('Viscosidade','A resistência do fluido a escoar. Água a 20 °C: ν ≈ 1,0 × 10⁻⁶ m²/s.',{icon:'droplet'}),item('Número de Reynolds','Compara a inércia com a viscosidade: Re = VD/ν. Abaixo de ~2300 o escoamento é laminar.',{icon:'waves'}),item('Perda de carga','A energia que o atrito com a parede consome: h_f = f (L/D) V²/2g.',{icon:'trending-down'})]},
|
|
71
|
+
{layout:'science',title:'As equações de Navier–Stokes (incompressível)',equations:[{label:'Continuidade: o que entra sai',latex:'\\nabla \\cdot \\mathbf{u} = 0'},{label:'Quantidade de movimento',latex:'\\rho\\left(\\frac{\\partial \\mathbf{u}}{\\partial t} + \\mathbf{u}\\cdot\\nabla\\mathbf{u}\\right) = -\\nabla p + \\mu \\nabla^2 \\mathbf{u} + \\rho \\mathbf{g}'},{label:'Inércia ÷ viscosidade',latex:'Re = \\frac{\\rho V D}{\\mu} = \\frac{V D}{\\nu}'}],plot:false},
|
|
72
|
+
{layout:'science',title:'Laminar: o perfil de velocidade é uma parábola',equations:[{label:'Hagen–Poiseuille',latex:'u(r) = u_{max}\\left(1 - \\frac{r^2}{R^2}\\right)'},{label:'Velocidade média',latex:'V = \\frac{u_{max}}{2}'}],plot:{functions:[{fn:'umax*(1 - (x/R)^2)',name:'u(r)'}],x:[-0.05,0.05],params:{umax:{value:2,min:0.5,max:4,label:'u máx (m/s)'},R:{value:0.05,min:0.01,max:0.05,step:0.005,label:'raio (m)'}}}},
|
|
73
|
+
{layout:'calc',title:'Qual é o regime? Mexa na velocidade e no diâmetro',inputs:{V:{label:'Velocidade',latex:'V',value:1.5,min:0.01,max:4,step:0.01,unit:'m/s'},D:{label:'Diâmetro',latex:'D',value:0.05,min:0.005,max:0.3,step:0.005,unit:'m'},nu:{label:'Viscosidade cinemática (água, 20 °C)',latex:'\\nu = 1{,}004\\times10^{-6}\\ \\mathrm{m^2/s}',value:0.000001004,fixed:true},eps:{label:'Rugosidade (PVC)',latex:'\\varepsilon = 0{,}05\\ \\mathrm{mm}',value:0.00005,fixed:true}},
|
|
74
|
+
outputs:[{name:'Re',label:'Número de Reynolds',latex:'Re = \\frac{VD}{\\nu}',fn:'V*D/nu',decimals:0},{name:'regime',label:'Regime',of:'Re',cases:[{below:2300,text:'Laminar',color:'s3'},{below:4000,text:'Transição',color:'em'},{text:'Turbulento',color:'hi'}],scale:{min:100,max:1000000,log:true}},{name:'f',label:'Fator de atrito',latex:'f = \\frac{64}{Re}\\ \\text{ou Swamee–Jain}',of:'Re',cases:[{below:2300,fn:'64/Re'},{fn:'0.25/(log10(eps/(3.7*D) + 5.74/Re^0.9))^2'}],decimals:4}],note:'Abaixo de 2300 vale f = 64/Re; acima de 4000, Swamee–Jain (explícita, no lugar de Colebrook).'},
|
|
75
|
+
{layout:'solution',title:'Exercício: perda de carga num tubo de PVC',problem:'Água a 20 °C escoa a 1,5 m/s num tubo de PVC de 50 mm. Qual é o regime e quanto de carga se perde em 100 m?',givens:[{symbol:'V',value:'1,5',unit:'m/s',label:'velocidade'},{symbol:'D',value:'0,05',unit:'m',label:'diâmetro'},{symbol:'L',value:'100',unit:'m',label:'comprimento'},{latex:'\\nu = 1{,}004\\times10^{-6}\\ \\mathrm{m^2/s}',label:'viscosidade'},{latex:'\\varepsilon = 0{,}05\\ \\mathrm{mm}',label:'rugosidade'}],find:'Re,\\ f,\\ h_f',
|
|
76
|
+
steps:[{text:'Reynolds decide o regime',latex:'Re = \\frac{VD}{\\nu} = \\frac{1{,}5 \\cdot 0{,}05}{1{,}004\\times10^{-6}} \\approx 74\\,700'},{text:'Re > 4000: turbulento. A rugosidade relativa entra na conta',latex:'\\varepsilon/D = \\frac{0{,}05}{50} = 0{,}001'},{text:'Fator de atrito (Swamee–Jain)',latex:'f = \\frac{0{,}25}{\\left[\\log\\left(\\frac{\\varepsilon}{3{,}7D} + \\frac{5{,}74}{Re^{0{,}9}}\\right)\\right]^2} \\approx 0{,}0230'},{text:'Darcy–Weisbach',latex:'h_f = f\\,\\frac{L}{D}\\,\\frac{V^2}{2g} = 0{,}0230 \\cdot \\frac{100}{0{,}05} \\cdot \\frac{1{,}5^2}{2 \\cdot 9{,}81}'}],
|
|
77
|
+
answer:{latex:'h_f \\approx 5{,}3\\ \\mathrm{m}',text:'Turbulento; perde cerca de 5,3 m de coluna d’água em 100 m.'}},
|
|
78
|
+
{layout:'science',title:'Diagrama de Moody (versão explícita)',equations:[{label:'Laminar',latex:'f = 64/Re'},{label:'Turbulento (Swamee–Jain)',latex:'f = 0{,}25\\,/\\,[\\log(\\varepsilon/3{,}7D + 5{,}74/Re^{0{,}9})]^2'}],plot:{functions:[{fn:'64/x',name:'laminar: 64/Re'},{fn:'0.25/(log10(r/3.7 + 5.74/x^0.9))^2',name:'turbulento'}],x:[600,10000000],xlog:true,ylog:true,params:{r:{value:0.001,min:0.00001,max:0.05,step:0.00001,label:'ε/D'}},layout:{xaxis:{title:{text:'Re'}},yaxis:{title:{text:'f'}}}}},
|
|
79
|
+
{layout:'compare',title:'Laminar × turbulento',left:{label:'Laminar',title:'Camadas que deslizam',text:'Re < 2300: perfil parabólico; o atrito depende só de Re.',items:['f = 64/Re','perda ∝ V']},right:{label:'Turbulento',title:'Redemoinhos que misturam',text:'Re > 4000: perfil achatado; a rugosidade da parede pesa.',items:['f por Colebrook / Moody','perda ∝ V² (aprox.)'],hl:true}},
|
|
80
|
+
{layout:'question',question:'Água a 0,5 m/s num tubo de 4 mm: qual é o regime?',options:['Laminar','Transição','Turbulento'],hint:'Re = VD/ν ≈ 2000.'},
|
|
81
|
+
end,
|
|
82
|
+
];
|
|
83
|
+
// aula de computação: o algoritmo rodando de verdade (barras, código e contadores) e a complexidade
|
|
84
|
+
if(kind==='algoritmos')slides=[
|
|
85
|
+
{...cover,kicker:'ALGORITMOS · AULA 5',title:'Ordenar e buscar:\nvendo o algoritmo pensar',subtitle:'Cada clique é um passo. O botão Tocar anda sozinho.'},
|
|
86
|
+
{layout:'algo',title:'Bubble sort: o maior sobe como bolha',algorithm:'bubble',array:[5,1,4,2,8,3]},
|
|
87
|
+
{layout:'algo',title:'Insertion sort: como organizar cartas na mão',algorithm:'insertion',array:[5,1,4,2,8,3]},
|
|
88
|
+
{layout:'algo',title:'Quicksort: escolhe um pivô e divide',algorithm:'quick',array:[7,2,9,4,3,8,1,6]},
|
|
89
|
+
{layout:'science',title:'Por que isso importa: n² × n log n',equations:[{label:'Bubble, insertion, selection',latex:'O(n^2)'},{label:'Merge sort, quicksort (médio)',latex:'O(n \\log n)'}],plot:{functions:[{fn:'x^2',name:'n²'},{fn:'x*log2(x)',name:'n log n'}],x:[1,64],layout:{xaxis:{title:{text:'n (tamanho do vetor)'}},yaxis:{title:{text:'operações'}}}}},
|
|
90
|
+
{layout:'algo',title:'Busca linear: olha um por um',algorithm:'linear',array:[2,5,8,12,16,23,38,56,72,91],target:23},
|
|
91
|
+
{layout:'algo',title:'Busca binária: descarta metade a cada passo',algorithm:'binary',array:[2,5,8,12,16,23,38,56,72,91],target:23},
|
|
92
|
+
{layout:'question',question:'Numa lista ordenada de 1 milhão de nomes, quantas comparações a busca binária faz no pior caso?',options:['Cerca de 20','Cerca de 1.000','Cerca de 500.000'],hint:'log₂(1.000.000) ≈ 20.'},
|
|
93
|
+
{layout:'codewalk',title:'A busca binária em Python',language:'python',code:'def busca_binaria(v, alvo):\n ini, fim = 0, len(v) - 1\n while ini <= fim:\n meio = (ini + fim) // 2\n if v[meio] == alvo:\n return meio\n if v[meio] < alvo:\n ini = meio + 1\n else:\n fim = meio - 1\n return -1',steps:[{title:'Os limites',text:'ini e fim marcam a faixa onde o alvo ainda pode estar.',highlight:[2]},{title:'O meio',text:'Divisão inteira: // descarta a parte fracionária.',highlight:[4]},{title:'Metade fora',text:'Cada volta descarta metade da faixa.',highlight:[7,8,9,10]},{title:'Não achou',text:'A faixa ficou vazia: devolve -1.',highlight:[11]}]},
|
|
94
|
+
end,
|
|
95
|
+
];
|
|
96
|
+
if(kind==='lavanda')slides[0]={...cover,tone:'light',title:'Ideias que\nganham forma.',subtitle:'Estúdio lavanda · um modelo para explorar',figure:{image:'imagens/lavanda-cover.jpg',fit:'cover',radius:100}};
|
|
97
|
+
return {...common,slides};
|
|
98
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Rotas das direções aprovadas (~/.sagadeck/direcoes.json; ver src/ai/directions.js).
|
|
2
|
+
// GET lista; POST {name} fotografa a capa atual como receita reutilizável. Só no Studio local.
|
|
3
|
+
import { loadDirections, saveDirection } from "../ai/directions.js";
|
|
4
|
+
|
|
5
|
+
export async function directionRoutes({ req, res, pathname, W, readJSON, apiBlocked }) {
|
|
6
|
+
const send = (status, data) => { res.writeHead(status, { "Content-Type": "application/json" }); res.end(JSON.stringify(data)); };
|
|
7
|
+
if (pathname === "/api/directions" && req.method === "GET") {
|
|
8
|
+
const d = loadDirections();
|
|
9
|
+
send(200, { file: d.file, exists: d.exists, error: d.error, directions: Object.values(d.directions) });
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
if (pathname === "/api/directions" && req.method === "POST") {
|
|
13
|
+
const blocked = apiBlocked(req);
|
|
14
|
+
if (blocked) { send(blocked.code, { error: "Salvar direções só no Studio desta máquina." }); return true; }
|
|
15
|
+
if (!W?.spec) { send(400, { error: "Abra uma apresentação primeiro." }); return true; }
|
|
16
|
+
const body = await readJSON(req);
|
|
17
|
+
const cover = (W.spec.slides || []).find((s) => s && s.layout === "cover") || (W.spec.slides || [])[0] || {};
|
|
18
|
+
try {
|
|
19
|
+
const name = saveDirection(body.name, {
|
|
20
|
+
theme: W.spec.theme || null, palette: W.spec.palette || null,
|
|
21
|
+
tone: cover.tone || null, ambient: cover.ambient || null, transition: cover.transition || null,
|
|
22
|
+
notes: `capa "${String(cover.title || "").slice(0, 80)}"`,
|
|
23
|
+
});
|
|
24
|
+
send(200, { name });
|
|
25
|
+
} catch (e) { send(400, { error: e.message }); }
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const remembered=new WeakMap();
|
|
4
|
+
const messages={
|
|
5
|
+
AI_TOKEN_LIMIT:'A IA atingiu o limite de tamanho da resposta. Tente um pedido menor; pode ser necessário ampliar o limite do modelo.',
|
|
6
|
+
AI_INVALID_OUTPUT:'A IA devolveu conteúdo inválido e não conseguiu corrigi-lo automaticamente. Tente novamente com um pedido mais específico.',
|
|
7
|
+
AI_PROVIDER_INTERRUPTED:'O provedor interrompeu a resposta da IA. As tentativas automáticas não resolveram; tente novamente em alguns instantes.',
|
|
8
|
+
TIMEOUT:'O serviço demorou além do tempo permitido. Tente novamente; o limite de espera pode precisar de ajuste.',
|
|
9
|
+
RATE_LIMIT:'O provedor limitou temporariamente os pedidos. Aguarde um pouco antes de tentar novamente.',
|
|
10
|
+
NO_CREDIT:'O provedor informou falta de créditos. O responsável pelo SagaDeck precisa verificar a conta.',
|
|
11
|
+
ACCESS_DENIED:'O serviço recusou o acesso. Sua sessão ou a configuração de acesso precisa ser verificada.',
|
|
12
|
+
SERVICE_UNAVAILABLE:'O serviço está indisponível ou falhou ao responder. Tente novamente em alguns instantes.',
|
|
13
|
+
CONNECTION_FAILED:'Não foi possível conectar ao serviço. Verifique a conexão e tente novamente.',
|
|
14
|
+
FILE_NOT_FOUND:'Um arquivo necessário não foi encontrado. Reabra a apresentação ou envie o arquivo novamente.',
|
|
15
|
+
STORAGE_FULL:'Não há espaço disponível para salvar o arquivo. O responsável precisa liberar espaço.',
|
|
16
|
+
FILE_PERMISSION:'O serviço não tem permissão para acessar ou salvar o arquivo.',
|
|
17
|
+
APP_UNKNOWN:'Não foi possível concluir a operação. A causa não foi determinada.',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function errorDiagnostic(error,{status}={}) {
|
|
21
|
+
const e=error instanceof Error?error:new Error(String(error));
|
|
22
|
+
if(remembered.has(e))return remembered.get(e);
|
|
23
|
+
const causes=[];for(let c=e;c&&causes.length<8;c=c.cause)causes.push(c);
|
|
24
|
+
if(e.aborted||causes.some(c=>c.name==='AbortError'))return {error:'Operação interrompida.',diagnostic:{code:'CANCELLED'}};
|
|
25
|
+
const http=e.status||status;
|
|
26
|
+
let code=messages[e.code]?e.code:null;
|
|
27
|
+
if(!code)code=causes.some(c=>c.name==='TimeoutError'||['ETIMEDOUT','UND_ERR_HEADERS_TIMEOUT','UND_ERR_BODY_TIMEOUT'].includes(c.code))?'TIMEOUT'
|
|
28
|
+
:http===429?'RATE_LIMIT':http===402?'NO_CREDIT':[401,403].includes(http)?'ACCESS_DENIED'
|
|
29
|
+
:causes.some(c=>c.code==='ENOENT')?'FILE_NOT_FOUND':causes.some(c=>c.code==='ENOSPC')?'STORAGE_FULL'
|
|
30
|
+
:causes.some(c=>['EACCES','EPERM'].includes(c.code))?'FILE_PERMISSION'
|
|
31
|
+
:causes.some(c=>['ECONNREFUSED','ENOTFOUND','ECONNRESET','EAI_AGAIN'].includes(c.code))?'CONNECTION_FAILED'
|
|
32
|
+
:http===503||http===502||(e.name==='LLMError'&&http>=500)?'SERVICE_UNAVAILABLE':http===400?'INVALID_INPUT':http===404?'NOT_FOUND':'APP_UNKNOWN';
|
|
33
|
+
const id=crypto.randomUUID().slice(0,8);
|
|
34
|
+
const summary=code==='INVALID_INPUT'||code==='NOT_FOUND'||http===409?e.message:messages[code];
|
|
35
|
+
const result={error:`${summary} Código: ${code} · ${id}. Se persistir, contate o responsável pelo SagaDeck e informe esse código.`,diagnostic:{code,id}};
|
|
36
|
+
remembered.set(e,result);
|
|
37
|
+
// Sem chaves, prompts, documentos ou resposta bruta do provedor no diagnóstico.
|
|
38
|
+
console.error(`[SagaDeck ${id}] ${code}`,JSON.stringify({status:http,name:e.name,causeCode:e.cause?.code}));
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function installErrorResponses(res) {
|
|
43
|
+
let contentType='';
|
|
44
|
+
const writeHead=res.writeHead.bind(res);
|
|
45
|
+
res.writeHead=(status,...args)=>{
|
|
46
|
+
const headers=typeof args[0]==='string'?args[1]:args[0];
|
|
47
|
+
if(headers&&!Array.isArray(headers))contentType=String(Object.entries(headers).find(([key])=>key.toLowerCase()==='content-type')?.[1]||contentType);
|
|
48
|
+
return writeHead(status,...args);
|
|
49
|
+
};
|
|
50
|
+
const end=res.end.bind(res);
|
|
51
|
+
res.end=(data,...args)=>{
|
|
52
|
+
if(typeof data==='string'&&String(res.getHeader('Content-Type')||contentType).includes('application/json')) {
|
|
53
|
+
try {
|
|
54
|
+
const payload=JSON.parse(data);
|
|
55
|
+
if(payload?.error&&!payload.diagnostic&&!payload.kind) data=JSON.stringify({...payload,...errorDiagnostic(payload.error,{status:res.statusCode})});
|
|
56
|
+
} catch { /* respostas que não são JSON mantêm seu formato */ }
|
|
57
|
+
}
|
|
58
|
+
return end(data,...args);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function chatErrorResult(error,spec,targetSlide) {
|
|
63
|
+
const diagnostic=errorDiagnostic(error);
|
|
64
|
+
return {reply:`A IA falhou e não mudei nada: ${diagnostic.error}`,diagnostic:diagnostic.diagnostic,spec,actions:[],targetSlide,mode:'error'};
|
|
65
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Um modelo geral, sem dados da pessoa ou dependência de rede.
|
|
2
|
+
export function explorationDemo() {
|
|
3
|
+
const calc = { layout: 'calc', title: 'A espera cresce na mesma proporção?', illustrative: true,
|
|
4
|
+
prediction: 'O que acontece quando a demanda se aproxima da capacidade?',
|
|
5
|
+
explanation: 'A curva varia a demanda mantendo a capacidade escolhida. Compare os cenários.', sweep: 'demanda',
|
|
6
|
+
inputs: { demanda: { label: 'Demanda', value: 2, min: 1, max: 9, step: 1, unit: 'tarefas/s' }, capacidade: { label: 'Capacidade', value: 10, min: 10, max: 20, step: 1, unit: 'tarefas/s' } },
|
|
7
|
+
outputs: [{ name: 'espera', label: 'Tempo médio no sistema', fn: '1/(capacidade-demanda)', unit: 's', decimals: 3, scale: { min: 0, max: 1 } }],
|
|
8
|
+
scenarios: [{ label: 'Folga', values: { demanda: 2 }, explanation: 'Existe uma grande margem de capacidade.' }, { label: 'Perto do limite', values: { demanda: 9 }, explanation: 'A espera cresce muito ao se aproximar da capacidade.' }, { label: 'Mais capacidade', values: { demanda: 9, capacidade: 18 }, explanation: 'Compare com o cenário anterior sem mudar a demanda.' }],
|
|
9
|
+
consulta: 'Modelo ilustrativo M/M/1: chegadas de Poisson e tempos de serviço exponenciais, uma fila e um servidor, regime estacionário com demanda menor que capacidade. O tempo médio no sistema é 1/(capacidade-demanda). Não é uma previsão de um serviço real.' };
|
|
10
|
+
return { title: 'Explore uma ideia', theme: 'bauhaus', purpose: 'palestra', duration: 8, slides: [
|
|
11
|
+
{ layout: 'cover', title: 'Uma pergunta.\nVários caminhos.', subtitle: 'Preveja, experimente e explique.', tone: 'light', figure: { icon: 'flask-conical', size: 320 } },
|
|
12
|
+
calc,
|
|
13
|
+
{ layout: 'canvas', elements: [{ text: 'Uma solicitação', as: 'title', x: 150, y: 140, w: 1500 }, { text: 'Pedido', as: 'lead', card: true, continuity: 'pedido', x: 200, y: 470, w: 360, h: 150 }] },
|
|
14
|
+
{ layout: 'canvas', elements: [{ text: 'Agora olhe por dentro', as: 'title', x: 150, y: 140, w: 1500 }, { text: 'Pedido', as: 'lead', card: true, continuity: 'pedido', x: 750, y: 350, w: 650, h: 260 }, { text: 'O objeto continua reconhecível enquanto a explicação se aprofunda.', as: 'body', x: 180, y: 760, w: 1500 }] },
|
|
15
|
+
{ layout: 'statement', text: 'Não basta ver o resultado.\nExperimente o que o faz mudar.', tone: 'dark' },
|
|
16
|
+
] };
|
|
17
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { expandApiCollections } from '../api-collections.js';
|
|
2
|
+
import {meetingSpec} from '../meeting.js';
|
|
3
|
+
// Exportar a apresentação aberta ou da biblioteca: .sagadeck, PowerPoint, PDF, roteiro, material de estudo e "Baixar
|
|
4
|
+
// tudo". PPTX/PDF/roteiro/estudo usam o Chrome invisível (os mesmos exportadores do CLI), numa pasta temporária.
|
|
5
|
+
// (Saiu de server.js: o servidor só decide o que exportar e chama sendExport.)
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import JSZip from "jszip";
|
|
10
|
+
import { buildHTML } from "../build.js";
|
|
11
|
+
import { THEMES } from "../themes.js";
|
|
12
|
+
import { loadPreferences } from "../preferences.js";
|
|
13
|
+
import { packDeck, EXTENSION, MIME } from "../package.js";
|
|
14
|
+
|
|
15
|
+
const slugify = (s) => String(s || "deck").normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase()
|
|
16
|
+
.replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "deck";
|
|
17
|
+
|
|
18
|
+
export function sendHtml(res,spec,audience){
|
|
19
|
+
const out=buildHTML(spec,{audience});res.writeHead(200,{'Content-Type':'text/html; charset=utf-8','Content-Disposition':'attachment; filename="apresentacao.html"'});res.end(out.html);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Gera e envia um arquivo da apresentação. PPTX/PDF/roteiro usam o Chrome invisível (os mesmos
|
|
23
|
+
// exportadores de "sagadeck pptx | pdf | roteiro"), numa pasta temporária.
|
|
24
|
+
export async function sendExport(res, kind, spec, name, { notes = true, inline = false, audience } = {}) {
|
|
25
|
+
spec=meetingSpec(spec,audience);
|
|
26
|
+
const cd = (file) => `attachment; filename="${slugify(file.replace(/\.\w+$/, ""))}${path.extname(file)}"; filename*=UTF-8''${encodeURIComponent(file)}`;
|
|
27
|
+
if (kind === "sagadeck") {
|
|
28
|
+
const { zip, missing } = await packDeck(spec, { baseDir: spec._dir || process.cwd(), name, generator: "sagadeck studio" });
|
|
29
|
+
res.writeHead(200, { "Content-Type": MIME, "Content-Disposition": cd(name + EXTENSION), "X-Sagadeck-Missing": encodeURIComponent(JSON.stringify(missing)) });
|
|
30
|
+
res.end(zip);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const kinds = {
|
|
34
|
+
pptx: { file: `${name}.pptx`, mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
|
|
35
|
+
pdf: { file: `${name}.pdf`, mime: "application/pdf" },
|
|
36
|
+
roteiro: { file: `${name} - roteiro.pdf`, mime: "application/pdf" },
|
|
37
|
+
// a visão de estudo do mesmo deck: cada slide inteiro + o texto de consulta (sem as notas do apresentador)
|
|
38
|
+
estudo: { file: `${name} - material de estudo.pdf`, mime: "application/pdf" },
|
|
39
|
+
"estudo-html": { file: `${name} - material de estudo.html`, mime: "text/html; charset=utf-8" },
|
|
40
|
+
// "Baixar tudo": o que se leva para apresentar, num clique (PowerPoint com as notas, PDF e roteiro)
|
|
41
|
+
tudo: { file: `${name}.zip`, mime: "application/zip" },
|
|
42
|
+
};
|
|
43
|
+
const k = kinds[kind];
|
|
44
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), `sagadeck-${kind}-`));
|
|
45
|
+
try {
|
|
46
|
+
if (["pptx","pdf","tudo"].includes(kind)) spec=expandApiCollections(spec);
|
|
47
|
+
const r = buildHTML(spec);
|
|
48
|
+
const htmlFile = path.join(tmp, "deck.html");
|
|
49
|
+
fs.writeFileSync(htmlFile, r.html);
|
|
50
|
+
const errors = [];
|
|
51
|
+
const make = async (what) => {
|
|
52
|
+
const out = path.join(tmp, `saida-${what}`);
|
|
53
|
+
if (what === "pptx") {
|
|
54
|
+
const { exportPptx } = await import("../export/pptx.js");
|
|
55
|
+
errors.push(...(await exportPptx(htmlFile, out, { theme: r.theme, meta: { ...r.meta, slides: r.slidesMeta }, notes })).errors);
|
|
56
|
+
} else if (what === "pdf") {
|
|
57
|
+
const { pdf } = await import("../export/shots.js");
|
|
58
|
+
// Preferências › Exportação: tema escuro com par claro (manual-noite → manual) sai claro no PDF, bom para imprimir
|
|
59
|
+
const light = loadPreferences().exportacao.pdfClaro === true ? lightVariant(spec) : null;
|
|
60
|
+
let file = htmlFile;
|
|
61
|
+
if (light) { file = path.join(tmp, "deck-claro.html"); fs.writeFileSync(file, buildHTML(light).html); }
|
|
62
|
+
await pdf(file, out);
|
|
63
|
+
} else if (what === "estudo" || what === "estudo-html") {
|
|
64
|
+
const { shots } = await import("../export/shots.js");
|
|
65
|
+
const { estudoHTML, estudoPDF } = await import("../export/estudo.js");
|
|
66
|
+
const { files } = await shots(htmlFile, path.join(tmp, "fotos"), { scale: what === "estudo" ? 0.6 : 0.75, jpeg: true });
|
|
67
|
+
const html = estudoHTML({ title: r.meta.title, author: r.meta.author, date: spec.date, slidesMeta: r.slidesMeta, shotFiles: files });
|
|
68
|
+
if (what === "estudo-html") fs.writeFileSync(out, html); else await estudoPDF(html, out);
|
|
69
|
+
} else {
|
|
70
|
+
const { shots } = await import("../export/shots.js");
|
|
71
|
+
const { roteiroPDF } = await import("../export/roteiro.js");
|
|
72
|
+
const { files } = await shots(htmlFile, path.join(tmp, "miniaturas"), { scale: 0.5, jpeg: true });
|
|
73
|
+
await roteiroPDF({ slidesMeta: r.slidesMeta, shotFiles: files, outFile: out, title: r.meta.title, author: r.meta.author, duration: spec.duration });
|
|
74
|
+
}
|
|
75
|
+
return fs.readFileSync(out);
|
|
76
|
+
};
|
|
77
|
+
let body;
|
|
78
|
+
if (kind === "tudo") {
|
|
79
|
+
const zip = new JSZip();
|
|
80
|
+
for (const what of ["pptx", "pdf", "roteiro"]) zip.file(kinds[what].file, await make(what));
|
|
81
|
+
body = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
|
|
82
|
+
} else body = await make(kind);
|
|
83
|
+
res.writeHead(200, {
|
|
84
|
+
"Content-Type": k.mime, "Content-Disposition": inline ? "inline" : cd(k.file),
|
|
85
|
+
// só o que afeta o arquivo (falha ao exportar, arquivo não encontrado); o fiscal de conteúdo fica no Revisar
|
|
86
|
+
"X-Sagadeck-Warnings": encodeURIComponent(JSON.stringify([...(r.warnings || []).filter((w) => /não encontrado/.test(w)), ...errors].slice(0, 20))),
|
|
87
|
+
});
|
|
88
|
+
res.end(body);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
console.error(`[Studio] exportação ${kind} falhou:`, e.message);
|
|
91
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
92
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
93
|
+
} finally {
|
|
94
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// O mesmo deck no tema claro do par (deck e slides com tema escuro que tem par claro); null se não há o que trocar
|
|
99
|
+
export function lightVariant(spec) {
|
|
100
|
+
const swap = (name) => (THEMES[name]?.dark && THEMES[name].pair ? THEMES[name].pair : null);
|
|
101
|
+
const deckTo = swap(spec.theme);
|
|
102
|
+
const slides = (spec.slides || []).map((s) => (s.theme && swap(s.theme) ? { ...s, theme: swap(s.theme) } : s));
|
|
103
|
+
if (!deckTo && slides.every((s, i) => s === spec.slides[i])) return null;
|
|
104
|
+
return { ...spec, theme: deckTo || spec.theme, slides };
|
|
105
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function saveGenReport(dir, generation) {
|
|
5
|
+
const reportDir = path.join(dir, '.sagadeck');
|
|
6
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
7
|
+
fs.writeFileSync(path.join(reportDir, 'avaliacao-geracao.json'), JSON.stringify({
|
|
8
|
+
createdAt: new Date().toISOString(), quality: generation.quality, variety: generation.variety,
|
|
9
|
+
}, null, 2), 'utf8');
|
|
10
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Uma instância local do Studio: porta padrão única (CLI, npm run dev e o pacote Python) e identificação por
|
|
2
|
+
// /api/instance. Abrir o Studio de novo acha o que já está aberto (mesma biblioteca) e aponta para ele, em vez de
|
|
3
|
+
// subir um segundo servidor que disputa os mesmos arquivos.
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_PORT = 3517;
|
|
10
|
+
// a versão: o package.json mais perto (no repositório, dois níveis acima; no motor empacotado do pip, ao lado)
|
|
11
|
+
export const VERSION = (() => {
|
|
12
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
for (let k = 0; k < 4; k++, dir = path.dirname(dir)) {
|
|
14
|
+
try { const v = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")).version; if (v) return v; } catch {}
|
|
15
|
+
}
|
|
16
|
+
return "0.0.0";
|
|
17
|
+
})();
|
|
18
|
+
|
|
19
|
+
// carimbo do código rodando (para o "é antigão?" nunca mais): versão + commit curto + início.
|
|
20
|
+
// fora de um clone git (pacote instalado), commit vira "empacotado".
|
|
21
|
+
export function buildInfo({ exec = null } = {}) {
|
|
22
|
+
let commit = "empacotado";
|
|
23
|
+
try {
|
|
24
|
+
commit = String(execFileSync("git", ["rev-parse", "--short", "HEAD"], { cwd: path.dirname(fileURLToPath(import.meta.url)), stdio: ["ignore", "pipe", "ignore"], timeout: 5000 }).toString().trim() || "empacotado");
|
|
25
|
+
} catch { /* sem git por perto */ }
|
|
26
|
+
if (exec) commit = String(exec);
|
|
27
|
+
if (!/^[0-9a-f]{4,40}$/.test(commit)) commit = "empacotado";
|
|
28
|
+
return { version: VERSION, commit, started: new Date().toISOString(), pid: process.pid };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// quem está na porta: { app: "sagadeck-studio", version, pid, library, ... } | null (ninguém ou outra coisa)
|
|
32
|
+
export async function probeInstance(port, host = "127.0.0.1") { const h = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
33
|
+
try {
|
|
34
|
+
const r = await fetch(`http://${h.includes(":") ? `[${h}]` : h}:${port}/api/instance`, { signal: AbortSignal.timeout(1500) });
|
|
35
|
+
if (!r.ok) return null;
|
|
36
|
+
const j = await r.json();
|
|
37
|
+
return j?.app === "sagadeck-studio" ? j : null;
|
|
38
|
+
} catch { return null; }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// o que fazer com a instância achada: reusar (mesma biblioteca e versão) ou avisar
|
|
42
|
+
export function instanceDecision(found, { library, version = VERSION }) {
|
|
43
|
+
if (!found) return { action: "start" };
|
|
44
|
+
const same = (a, b) => path.resolve(a || "").toLowerCase() === path.resolve(b || "").toLowerCase();
|
|
45
|
+
if (!same(found.library, library)) return { action: "conflict", why: `outra biblioteca (${found.library})` };
|
|
46
|
+
if (found.version !== version) return { action: "conflict", why: `outra versão (${found.version}; esta é ${version}): feche a aberta (processo ${found.pid}) e abra de novo` };
|
|
47
|
+
return { action: "reuse" };
|
|
48
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { portalExample } from '../portal-scene.js';
|
|
2
|
+
import { playgroundExample } from '../playground.js';
|
|
3
|
+
// Conteúdo de exemplo de cada layout — usado na galeria de layouts do Studio (prévia no tema do deck).
|
|
4
|
+
import { graphLabExample } from '../graph-lab.js';
|
|
5
|
+
import { codeLabExample } from '../code-lab.js';
|
|
6
|
+
export const LAYOUT_INFO = {
|
|
7
|
+
portal: ["Portal e transformação", "Entre em um elemento, transforme o cenário e revele texto integrado ao ambiente"],
|
|
8
|
+
playground: ["Experiência personalizada", "HTML, CSS e JavaScript interativos em um frame isolado"],
|
|
9
|
+
codelab: ['Código explorável', 'Alunos alteram código e entradas, executam offline e acompanham estruturas e variáveis passo a passo'],
|
|
10
|
+
graphlab: ['Rede explorável', 'Navegue pelos nós, teste rotas, altere custos, remova conexões e reorganize a rede ao vivo'],
|
|
11
|
+
mosaic: ["Grade adaptável", "De 1 a 12 itens: as colunas se ajustam à quantidade e ao tamanho do texto"],
|
|
12
|
+
ribbon: ["Cápsulas", "Etapas em painéis arredondados, com colunas automáticas"],
|
|
13
|
+
dossier: ["Página de consulta", "Material compacto com seções, código e notas na mesma página"],
|
|
14
|
+
decisionlab: ["Laboratório de decisões", "Compare automação e revisão humana com premissas ajustáveis ao vivo"],
|
|
15
|
+
solution: ["Exercício resolvido", "Enunciado e dados de um lado; cada clique revela o próximo passo da conta, até a resposta em destaque"],
|
|
16
|
+
calc: ["Calculadora ao vivo", "Mexa nas entradas e veja as fórmulas, as faixas (\"laminar\", \"turbulento\"…) e a régua mudarem na hora"],
|
|
17
|
+
algo: ["Algoritmo animado", "Qualquer algoritmo rodando de verdade (ordenação, busca em texto, grafo, árvore ou o seu código em Python): as estruturas desenhadas, a linha do código, as variáveis como num depurador e o botão Tocar"],
|
|
18
|
+
carousel: ["Carrossel", "Um item por clique com foto e texto: roda em semicírculo que gira, ou a foto em anéis que giram e travam"],
|
|
19
|
+
duel: ["Duelo de commits", "Duas pessoas no mesmo arquivo, um clique por jogada: o Git calcula push recusado, merge e conflito, e o grafo cresce"],
|
|
20
|
+
terminals: ["Dois terminais", "Comandos digitados lado a lado; com o roteiro do duelo, a saída do Git (e o git log --graph) sai calculada"],
|
|
21
|
+
turns: ["Turnos a dois", "Code review, cliente × servidor, debate ou role-play: uma fala por clique, alternando os lados"],
|
|
22
|
+
science: ["Fórmulas e funções", "Escreva a fórmula e ela vira curva; cada letra vira um controle deslizante. Aceita pontos colados do Excel e superfície 3D"],
|
|
23
|
+
infographic: ["Infográfico", "Arco, ramos, lados, trilhas ou metrô: de 2 a 8 itens coloridos em volta de um centro"],
|
|
24
|
+
diagram: ["Diagrama", "Fluxograma, sequência, estados, UML, jornada, mapa mental (Mermaid) nas cores do tema"],
|
|
25
|
+
scenography: ["Texto no cenário", "13 composições (palco, terminal, café, embarque, pregão, cinema, revista…), sem animação"],
|
|
26
|
+
cover: ["Capa", "Abertura com título, subtítulo e autor"],
|
|
27
|
+
section: ["Seção", "Abre um novo ato ou capítulo"],
|
|
28
|
+
statement: ["Frase de impacto", "Uma ideia em letras grandes"],
|
|
29
|
+
calendar: ["Calendário", "Datas num calendário: um cartão por mês com o mini calendário e os eventos marcados; o que não tem data vai em \"sem data\""],
|
|
30
|
+
poster: ["Pôster (infográfico)", "Uma página como figura de revista científica: ilustração principal e painéis com letra (a, b, c), desenho, texto curto e números"],
|
|
31
|
+
definition: ["Definição", "O termo em destaque, a origem da palavra em partes e a definição, com um ícone ilustrativo"],
|
|
32
|
+
headline: ["Manchete", "Uma frase enorme ocupando o slide"],
|
|
33
|
+
quote: ["Citação", "Uma fala com autor"],
|
|
34
|
+
onepage: ["One-page", "Tudo numa página: jornada com ícones, problema com números, solução e um painel (números, gráficos, mapa por UF)"],
|
|
35
|
+
status: ["Status semanal", "Feito, em andamento, bloqueios, riscos e próximos passos, com saúde, avanço e telas da semana"],
|
|
36
|
+
hub: ["Mapa de caminhos", "Opções clicáveis que levam cada uma à sua parte da apresentação (processo com vários caminhos)"],
|
|
37
|
+
number: ["Número grande", "Um número que conta até o valor"],
|
|
38
|
+
split: ["Texto e figura", "Texto de um lado, figura do outro"],
|
|
39
|
+
full: ["Página inteira", "Imagem ou figura ocupando tudo"],
|
|
40
|
+
cards: ["Cartões", "2 a 4 cartões com ícone"],
|
|
41
|
+
bento: ["Mosaico", "Blocos de tamanhos diferentes (bento)"],
|
|
42
|
+
stats: ["Indicadores", "KPIs com tendência"],
|
|
43
|
+
steps: ["Etapas", "Processo em passos conectados"],
|
|
44
|
+
funnel: ["Funil", "Etapas que afunilam"],
|
|
45
|
+
pyramid: ["Pirâmide", "Níveis do topo à base"],
|
|
46
|
+
list: ["Lista", "Itens numerados grandes"],
|
|
47
|
+
agenda: ["Agenda", "Seções com a atual destacada"],
|
|
48
|
+
timeline: ["Linha do tempo", "Eventos em sequência"],
|
|
49
|
+
chart: ["Gráfico de dados", "Barras, colunas, linhas, rosca: cole a tabela do Excel ou digite os valores"],
|
|
50
|
+
table: ["Tabela", "Tabela de verdade nas cores do tema: cole do Excel, destaque linha e total"],
|
|
51
|
+
compare: ["Comparação", "A × B lado a lado"],
|
|
52
|
+
matrix: ["Matriz 2×2", "Quatro quadrantes"],
|
|
53
|
+
question: ["Pergunta", "Pergunta para a plateia com timer"],
|
|
54
|
+
poll: ["Enquete", "Votação ao vivo"],
|
|
55
|
+
image: ["Imagem", "Imagem em tela cheia com legenda"],
|
|
56
|
+
code: ["Código", "Código com linhas destacadas"],
|
|
57
|
+
codewalk: ["Código guiado", "Explique uma linha por vez, com destaques e saída simulada"],
|
|
58
|
+
api: ["API ao vivo", "Requisição que executa na hora (tipo Postman): síncrono, polling ou streaming, com código curl/Python"],
|
|
59
|
+
spotlight: ["Foco guiado", "Conduza o olhar por detalhes de uma imagem ou diagrama"],
|
|
60
|
+
kinetic: ["Tipografia cinética", "Frases em sequência viram parte da cena com ritmo e estilos tipográficos que mudam"],
|
|
61
|
+
video: ["Vídeo", "Player YouTube online ou MP4 incorporado"],
|
|
62
|
+
blocks: ["Livre (blocos)", "Monte com blocos em linhas e colunas"],
|
|
63
|
+
canvas: ["Livre (posições)", "Posicione cada elemento à mão"],
|
|
64
|
+
references: ["Referências", "Fontes em duas colunas"],
|
|
65
|
+
end: ["Encerramento", "Obrigado e contatos"],
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const LAYOUT_SAMPLES = {
|
|
69
|
+
portal: portalExample,
|
|
70
|
+
playground: playgroundExample,
|
|
71
|
+
codelab: codeLabExample,
|
|
72
|
+
graphlab: graphLabExample,
|
|
73
|
+
mosaic: {layout:"mosaic",title:"Uma ideia, vários caminhos",items:[{title:"Descobrir",text:"Entenda o problema."},{title:"Experimentar",text:"Teste a hipótese."},{title:"Construir",text:"Transforme em produto."},{title:"Aprender",text:"Use o resultado para melhorar."},{title:"Compartilhar",text:"Mostre o que funcionou."}]},
|
|
74
|
+
ribbon: {layout:"ribbon",title:"Do primeiro passo ao resultado",items:[{title:"Explorar",text:"Encontre uma oportunidade."},{title:"Testar",text:"Experimente em pequena escala."},{title:"Evoluir",text:"Repita o que funciona."}]},
|
|
75
|
+
dossier: {layout:"dossier",title:"Referência rápida",items:[{title:"Requisição",text:"GET consulta o recurso.",code:"GET /exemplos/42"},{title:"Resposta",text:"Confira o status antes de usar o corpo.",code:'{"id":42,"status":"ok"}'}]},
|
|
76
|
+
decisionlab: {layout:"decisionlab", title:"A revisão melhora o resultado?", lab:{volume:10000,errorRate:2,reviewRate:100,catchRate:60,introducedRate:0.2,seconds:30}},
|
|
77
|
+
infographic: { layout: "infographic", shape: "arco", title: "Os ==desafios== do projeto", center: { title: "Desafios" }, items: [{ title: "Acesso", text: "Pedido pela Bridge" }, { title: "Dados", text: "Mockados no experimento" }, { title: "Segurança", text: "Chaves no Key Vault" }, { title: "Infra", text: "Namespace e firewall" }] },
|
|
78
|
+
diagram: { layout: "diagram", kicker: "Processo", title: "Da ideia à ==produção==", mermaid: "flowchart LR\n A([:lightbulb: Ideia]):::hi --> B{Vale testar?}\n B -->|sim| C[:flask-conical: Experimento]\n B -.->|não| X[Arquivar]:::vazado\n C ==> D[:rocket: Produção]:::em" },
|
|
79
|
+
solution: {layout:"solution", title:"Exercício: quanto rende em 3 anos?", problem:"R$ 1.000 aplicados a 1% ao mês, com juros compostos. Quanto há depois de 36 meses?", givens:[{symbol:"C", value:"1000", unit:"R\\$", label:"capital"}, {symbol:"i", value:"0,01", label:"taxa ao mês"}, {symbol:"n", value:"36", label:"meses"}], find:"M", steps:[{text:"Juros compostos", latex:"M = C\\,(1 + i)^n"}, {text:"Substitui os dados", latex:"M = 1000 \\cdot 1{,}01^{36}"}, {text:"Calcula a potência", latex:"1{,}01^{36} \\approx 1{,}4308"}], answer:{latex:"M \\approx R\\$\\,1.430{,}77", text:"Rende cerca de 43% em 3 anos."}},
|
|
80
|
+
calc: {layout:"calc", title:"Qual é o regime do escoamento?", inputs:{V:{label:"Velocidade", latex:"V", value:1.5, min:0.01, max:4, step:0.01, unit:"m/s"}, D:{label:"Diâmetro", latex:"D", value:0.05, min:0.005, max:0.3, step:0.005, unit:"m"}, nu:{label:"Viscosidade (água, 20 °C)", latex:"\\nu = 1{,}0\\times10^{-6}\\ \\mathrm{m^2/s}", value:0.000001, fixed:true}}, outputs:[{name:"Re", label:"Número de Reynolds", latex:"Re = \\frac{VD}{\\nu}", fn:"V*D/nu", decimals:0}, {name:"regime", label:"Regime", of:"Re", cases:[{below:2300, text:"Laminar", color:"s3"}, {below:4000, text:"Transição", color:"em"}, {text:"Turbulento", color:"hi"}], scale:{min:100, max:1000000, log:true}}]},
|
|
81
|
+
algo: {layout:"algo", title:"Bubble sort passo a passo", algorithm:"bubble", array:[5, 1, 4, 2, 8, 3]},
|
|
82
|
+
carousel: {layout:"carousel", title:"Cinco lugares, cinco lições", style:"arc", items:[{title:"Montanha", text:"Subir devagar é o jeito mais rápido de chegar.", label:"Capítulo 1"}, {title:"Mar", text:"Tudo o que é fundo começa raso."}, {title:"Cidade", text:"Muita gente, muita luz, pouco silêncio."}, {title:"Deserto", text:"Onde o pouco vira muito."}, {title:"Floresta", text:"Cada árvore conta uma história."}]},
|
|
83
|
+
duel: {layout:"duel", title:"Duelo de commits", file:"soma.js", base:"function soma(a, b) {\n return a + b;\n}", people:["Ana","Beto"], bet:"Vai dar conflito?",
|
|
84
|
+
turns:[{who:1, edit:{2:" return a + b + 0;"}, commit:"ajusta soma"}, {who:2, edit:{2:" return b + a;"}, commit:"inverte a ordem"}, {who:1, push:true}, {who:2, push:true}, {who:2, pull:true},
|
|
85
|
+
{who:2, resolve:{2:" return a + b;"}, commit:"resolve o conflito"}, {who:2, push:true}, {who:1, pull:true}]},
|
|
86
|
+
terminals: {layout:"terminals", title:"Dois terminais", file:"README.md", base:"# Projeto\nversão 1", people:["Ana","Beto"],
|
|
87
|
+
turns:[{who:1, edit:{2:"versão 2"}, commit:"sobe a versão"}, {who:1, push:true}, {who:2, pull:true}, {who:2, log:true}]},
|
|
88
|
+
turns: {layout:"turns", title:"Code review ao vivo", people:[{name:"Ana", role:"autora do PR"}, {name:"Beto", role:"revisor"}],
|
|
89
|
+
turns:[{who:1, text:"Abri o PR com a validação do Pix."}, {who:2, text:"Por que não usa o **schema** que já existe?", tag:"pergunta"}, {who:1, text:"Boa. Troquei e testei assim:", method:"POST", url:"/pix", status:201, code:'{ "valor": 10 }', language:"json"}, {who:2, text:"Aprovado.", tag:"aprovado"}]},
|
|
90
|
+
science: {layout:"science", title:"Mexa no a e no b", equations:[{label:"A função",latex:"f(x) = a\\sin(bx)"},{label:"A derivada",latex:"f'(x) = ab\\cos(bx)"}], plot:{functions:["a*sin(b*x)","a*b*cos(b*x)"], x:[-6.3,6.3], params:{a:{value:1.5,min:0,max:3,label:"amplitude"},b:{value:1,min:0.5,max:3,label:"frequência"}}}},
|
|
91
|
+
scenography: {layout:"scenography", scene:"stage", title:"ALÉM DO\nÓBVIO", kicker:"UMA NOVA PERSPECTIVA", subtitle:"Ideias que ocupam espaço.", tone:"dark", deco:"none"},
|
|
92
|
+
cover: { layout: "cover", kicker: "Evento 2026", title: "Título da ==apresentação==", subtitle: "Um subtítulo curto", author: "Seu nome", figure: { icon: "rocket", size: 320 } },
|
|
93
|
+
section: { layout: "section", number: 2, kicker: "Parte dois", title: "O problema" },
|
|
94
|
+
statement: { layout: "statement", kicker: "A ideia", text: "Uma frase que ==muda tudo==." },
|
|
95
|
+
calendar: { layout: "calendar", kicker: "Agenda", title: "Próximos lançamentos", events: [
|
|
96
|
+
{ date: "2026-11-12", title: "Beta aberto", tag: "Produto", text: "Para os 500 primeiros" }, { date: "2026-11-28", title: "Evento de lançamento", tag: "Evento" },
|
|
97
|
+
{ date: "2027-01-15", title: "Versão 2.0", tag: "Produto", text: "Com o app de celular" }], undated: [{ title: "Integração com o banco", text: "Em negociação" }] },
|
|
98
|
+
poster: { layout: "poster", kicker: "Receita", title: "Pão de fermentação natural", subtitle: "Do fermento ao forno em quatro etapas", flow: true,
|
|
99
|
+
panels: [
|
|
100
|
+
{ title: "Fermento", icon: "wheat", text: "Farinha e água, alimentados por 5 dias", facts: [{ value: "1:1", label: "farinha e água" }] },
|
|
101
|
+
{ title: "Massa", icon: "hand", text: "Misture e dobre a cada 30 min", facts: [{ value: "75%", label: "hidratação" }] },
|
|
102
|
+
{ title: "Descanso", icon: "thermometer", text: "Na geladeira, a massa ganha sabor", facts: [{ value: "12 h", label: "a 4 °C" }] },
|
|
103
|
+
{ title: "Forno", icon: "flame", text: "Panela tampada, depois sem tampa", facts: [{ value: "250 °C", label: "45 min" }] },
|
|
104
|
+
], key: "Tempos para uma cozinha a 24 °C." },
|
|
105
|
+
definition: { layout: "definition", kicker: "O que é", term: "Hidrologia", origin: "do grego", parts: [{ word: "hydor", meaning: "água" }, { word: "logos", meaning: "ciência" }], text: "É a ciência que estuda ==a água sobre a Terra==: sua quantidade, distribuição e circulação, e sua relação com o meio ambiente e com os seres vivos.", icon: "droplet" },
|
|
106
|
+
headline: { layout: "headline", text: "78% ==desistem==", caption: "no segundo passo" },
|
|
107
|
+
quote: { layout: "quote", quote: "Dados vencem opiniões.", by: "Alguém sábio" },
|
|
108
|
+
onepage: { layout: "onepage", title: "Cadastro digital", journey: [{ icon: "user", title: "Cliente chega", text: "Fila de 40 min" }, { icon: "file-text", title: "Preenche papel" }, { icon: "check", title: "Conta aberta", text: "Até 5 dias" }], problem: { text: "Cadastro em papel atrasa a abertura de contas", numbers: [{ value: "5 dias", label: "para abrir uma conta" }] }, solution: { text: "Cadastro no tablet, com assinatura digital", items: ["Conta aberta no mesmo dia"] } },
|
|
109
|
+
status: { layout: "status", kicker: "Semana 39", title: "Portal do cliente", health: "risco", progress: 65, done: ["Tela de login", "Integração com o cadastro"], doing: ["Recuperação de senha"], blocked: [{ text: "Liberação de firewall", owner: "Infra", due: "30/09" }], upcoming: ["Homologar com o negócio"] },
|
|
110
|
+
hub: { layout: "hub", title: "Qual é o seu caso?", question: "Cada caminho tem o seu prazo", options: [{ icon: "flask-conical", title: "Experimento", meta: "30 dias", goto: "exp" }, { icon: "rocket", title: "Projeto", meta: "DEV, HOM e PROD", goto: "proj" }] },
|
|
111
|
+
number: { layout: "number", value: 42, suffix: "%", label: "mais rápido" },
|
|
112
|
+
split: { layout: "split", title: "Texto e figura", bullets: ["Primeiro ponto", "Segundo ponto"], figure: { icon: "lightbulb", size: 300 } },
|
|
113
|
+
full: { layout: "full", figure: { chart: "donut", value: 72, center: "72%" }, title: "Figura em página inteira", overlay: "left" },
|
|
114
|
+
cards: { layout: "cards", title: "Três pilares", items: [{ icon: "shield", title: "Seguro" }, { icon: "zap", title: "Rápido" }, { icon: "heart", title: "Simples" }] },
|
|
115
|
+
bento: { layout: "bento", title: "Em um olhar", tiles: [{ value: "8,4 mi", title: "Receita", hl: true }, { icon: "users", title: "NPS 91" }, { icon: "rocket", title: "3 lançamentos" }, { icon: "globe", title: "2 países" }, { icon: "check", title: "Zero falhas" }] },
|
|
116
|
+
stats: { layout: "stats", title: "Resultados", stats: [{ value: "+42%", label: "Receita" }, { value: "0,6%", label: "Churn" }, { value: "91", label: "NPS" }] },
|
|
117
|
+
steps: { layout: "steps", title: "Como funciona", steps: [{ title: "Coletar" }, { title: "Analisar" }, { title: "Agir" }] },
|
|
118
|
+
funnel: { layout: "funnel", title: "Funil", stages: [{ title: "Visitas", value: "120k" }, { title: "Cadastro", value: "18k" }, { title: "Pagantes", value: "900" }] },
|
|
119
|
+
pyramid: { layout: "pyramid", title: "Pirâmide", levels: [{ title: "Topo" }, { title: "Meio" }, { title: "Base" }] },
|
|
120
|
+
list: { layout: "list", title: "Lista", items: ["Primeiro item", "Segundo item", "Terceiro item"] },
|
|
121
|
+
agenda: { layout: "agenda", title: "Agenda", current: 2, items: [{ title: "Abertura" }, { title: "O problema" }, { title: "A solução" }] },
|
|
122
|
+
timeline: { layout: "timeline", title: "Linha do tempo", events: [{ when: "2019", title: "Início" }, { when: "2022", title: "Virada" }, { when: "2026", title: "Hoje" }] },
|
|
123
|
+
table: { layout: "table", title: "Vazões máximas anuais", head: ["Ano", "Vazão (m³/s)", "Ordem", "TR (anos)"], rows: [["1984", "2.218,0", "1", "9,0"], ["1985", "1.980,5", "2", "4,5"], ["1986", "1.712,3", "3", "3,0"], ["1987", "1.445,0", "4", "2,3"]], highlight: { row: 1 }, side: "A maior cheia do período tem ==TR de 9 anos==.", source: "Posto fluviométrico (exemplo)" },
|
|
124
|
+
chart: { layout: "chart", title: "Crescimento", chart: { chart: "column", data: [{ label: "A", value: 30 }, { label: "B", value: 55 }, { label: "C", value: 80 }] }, side: "Subiu ==2,6×==" },
|
|
125
|
+
compare: { layout: "compare", title: "Antes × depois", left: { label: "Antes", value: "3 dias" }, right: { label: "Depois", value: "2 h", hl: true } },
|
|
126
|
+
matrix: { layout: "matrix", title: "Matriz", x: ["Fácil", "Difícil"], y: ["Alto impacto", "Baixo impacto"], cells: [{ title: "Faça já", hl: true }, { title: "Planeje" }, { title: "Delegue" }, { title: "Evite" }] },
|
|
127
|
+
question: { layout: "question", question: "Qual você escolheria?", options: ["Opção A", "Opção B", "Opção C"], timer: 30 },
|
|
128
|
+
poll: { layout: "poll", question: "Vote agora", id: "exemplo", options: ["Sim", "Não", "Talvez"] },
|
|
129
|
+
image: { layout: "image", figure: { picto: "scene", name: "desk" }, title: "Imagem em tela cheia" },
|
|
130
|
+
code: { layout: "code", title: "Código", filename: "exemplo.py", language: "Python", code: "def ola():\n return 'mundo'", highlight: [2] },
|
|
131
|
+
codewalk: { layout: "codewalk", kicker: "Laboratório · API", title: "Uma API em ==três movimentos==", filename: "primeira-api.js", language: "JavaScript",
|
|
132
|
+
code: "const resposta = await fetch('/api/aulas');\n\nconst aulas = await resposta.json();\n\nconst ativas = aulas.filter(aula => aula.ativa);\n\nconsole.log(ativas.length);",
|
|
133
|
+
steps: [{ title: "Faça a pergunta", text: "fetch envia o pedido. await dá tempo para a resposta chegar.", highlight: [1], output: "GET /api/aulas → 200 OK" },
|
|
134
|
+
{ title: "Transforme a resposta", text: "O corpo JSON vira uma lista que o código consegue usar.", highlight: [3], output: "[{ id: 1, ativa: true },\n { id: 2, ativa: false }]" },
|
|
135
|
+
{ title: "Escolha o que importa", text: "Só as aulas ativas passam pelo filtro. Qual será a saída?", highlight: [5, 7], output: "1" }] },
|
|
136
|
+
spotlight: { layout: "spotlight", kicker: "Olhar de quem programa", title: "O detalhe que ==muda tudo==", caption: "Inspeção de uma resposta HTTP · clique nos detalhes para explorar",
|
|
137
|
+
figure: { svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 600"><rect width="1000" height="600" fill="#101725"/><rect width="1000" height="70" fill="#1d273a"/><circle cx="30" cy="35" r="7" fill="#f09282"/><circle cx="55" cy="35" r="7" fill="#e5bd76"/><circle cx="80" cy="35" r="7" fill="#93c1a4"/><g font-family="monospace"><text x="125" y="44" font-size="22" fill="#a1adc4">API EXPLORER / aulas</text><rect x="40" y="110" width="920" height="84" rx="10" fill="#263348"/><text x="66" y="162" font-size="28" fill="#9ce3c3">GET</text><text x="145" y="162" font-size="28" fill="#e8edf5">/api/aulas</text><rect x="40" y="230" width="920" height="56" rx="8" fill="#142d2a"/><text x="65" y="267" font-size="24" fill="#9ce3c3">200 OK</text><text x="780" y="267" font-size="22" fill="#9ce3c3">42 ms</text><text x="55" y="341" font-size="25" fill="#c0cee9">{</text><text x="90" y="390" font-size="25" fill="#9fc3ff">"aulas": [</text><text x="125" y="439" font-size="25" fill="#e8edf5">{ "id": 1, "ativa": true }</text><text x="90" y="488" font-size="25" fill="#9fc3ff">]</text><text x="55" y="537" font-size="25" fill="#c0cee9">}</text></g></svg>' },
|
|
138
|
+
hotspots: [{ x: 4, y: 18, width: 92, height: 15, title: "O pedido", text: "O verbo GET consulta o recurso. A rota identifica a coleção de aulas." },
|
|
139
|
+
{ x: 4, y: 38, width: 92, height: 10, title: "A confirmação", text: "200 OK confirma o sucesso. O tempo ajuda a investigar a experiência do usuário." },
|
|
140
|
+
{ x: 5, y: 52, width: 78, height: 39, title: "Os dados", text: "A estrutura JSON é o contrato que o front-end precisa entender." }] },
|
|
141
|
+
kinetic: { layout: "kinetic", figure: { svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1920 1080"><defs><linearGradient id="stage" x2="0" y2="1"><stop stop-color="#697a78"/><stop offset="1" stop-color="#26353a"/></linearGradient><linearGradient id="window" x2="0" y2="1"><stop stop-color="#e1cba1"/><stop offset="1" stop-color="#8d9990"/></linearGradient></defs><path fill="url(#stage)" d="M0 0h1920v1080H0z"/><path fill="#202b2e" d="M0 0h430v1080H0zM1490 0h430v1080h-430z"/><path fill="url(#window)" d="M500 90h910v660H500z"/><path fill="#d0b88e" opacity=".4" d="M500 560h910v190H500z"/><path fill="#293432" d="M0 850h1920v230H0z"/><path fill="#394342" d="M720 814h480v36H720z"/><path fill="#26302f" d="M836 555c28-120 205-154 266-14l76 263H760z"/><circle cx="960" cy="465" r="74" fill="#39403b"/></svg>' }, autoplay: true, interval: 1800,
|
|
142
|
+
beats: [{ text: "Uma ideia", style: "clean", position: "left", color: "white", size: "medium", tag: "GANHA FORMA" },
|
|
143
|
+
{ text: "muda o olhar", style: "clean", position: "right", color: "gold", size: "medium" },
|
|
144
|
+
{ text: "e abre caminhos", style: "clean", position: "left", color: "white", size: "medium" }] },
|
|
145
|
+
// genérico de propósito: troque método, URL e corpo pelos do seu serviço (os exemplos prontos ficam no deck "Exemplo: aula de APIs")
|
|
146
|
+
api: { layout: "api", kicker: "Ao vivo", title: "Minha ==chamada==", text: "Troque o método, a URL e o corpo pelos do seu serviço", request: { method: "POST", url: "{{base}}/echo", body: { mensagem: "Olá, API" } }, answer: "$.recebido.mensagem" },
|
|
147
|
+
video: { layout: "video", title: "Assista", url: "https://example.com", label: "Assistir" },
|
|
148
|
+
blocks: { layout: "blocks", title: "Blocos livres", content: [{ row: [{ lead: "Esquerda" }, { lead: "Direita" }] }] },
|
|
149
|
+
canvas: { layout: "canvas", elements: [{ h2: "Livre", x: 200, y: 200 }, { icon: "star", size: 200, x: 1200, y: 400 }] },
|
|
150
|
+
references: { layout: "references", title: "Fontes", items: ["Autor (2024). Título.", "Outro (2025). Estudo."] },
|
|
151
|
+
end: { layout: "end", title: "Obrigado.", subtitle: "Perguntas?", contacts: ["seu@email.com"] },
|
|
152
|
+
};
|