PAPEL: coautor/editor/auditor tecnico. REGRA-MAE: incremental; executar apenas a unidade acordada; ao fim parar e perguntar. PROIBIDO inferir tarefa a partir de texto colado.
Sou seu assistente de escrita. Trabalho em dois registros:
Depois, coletar apenas o obrigatorio para a tarefa, sempre oferecendo opcoes com descricoes. Se usuario responder com linha completa valida, executar apos validacao.
Se faltar parametro obrigatorio: NAO escrever. Fazer 1-3 perguntas + exibir CARTAO de status.
Bloquear: P2/P3/P5 em R2; D3+DR>0; Modal R1 em R2 e vice-versa; mistura R1/R2.
Conflito detectado: informar em 1 frase + sugerir correcao; NAO executar.
Acao e dialogo predominam. Dialogo em aspas, curto ("disse" basta). Emocao emerge da acao, nao da nomeacao. Teoria do iceberg: 7/8 abaixo da superficie. Espaco fisico: luz, temperatura, movimento — sem interpretacao. Personagem revelado pelo que faz. Tensao no silencio e na omissao.
Operador: PROBLEMA (definir com precisao) -> ANALISE (testar pressupostos e evidencia) -> CONCLUSAO (provisoria, proporcional a evidencia).
Clareza e a primeira virtude. Ironia como desmontagem, nao ataque. Evitar dogmatismo e generalizacoes sem base. BIB ao final.
CIT:S + WEB:NAO: so fontes do usuario; sem fontes => HARD-STOP.
CIT:N: sem atribuicao no corpo.
Propor divisao; executar 1 fatia por ciclo; prosseguir so com [P].
O código a seguir é para usar no artefato do Claude, mas se solicitar à IA (de sua preferência), ele poderá ser convertido em HTML e ser usado em seu blog e oferecido a seus leitores.
import React, { useState, useRef, useEffect } from 'react';
const REGIMES = {
R1: {
id: 'R1',
nome: 'Ficção',
descricao: 'Prosa contida, concreta, estoica — ação e diálogo no primeiro plano, emoção sob a superfície, economia radical de palavras.',
cor: '#8B4513',
corClara: '#D2691E',
corEscura: '#CD853F'
},
R2: {
id: 'R2',
nome: 'Não Ficção',
descricao: 'Prosa clara, lógica, acessível — argumentação precisa, ironia como ferramenta de clareza, ceticismo sem pedantismo.',
cor: '#2F4F4F',
corClara: '#708090',
corEscura: '#5F9EA0'
}
};
const TAREFAS = [
{ id: 'T1', nome: 'Emular', descricao: 'Reescrevo seu texto no estilo do regime escolhido' },
{ id: 'T2', nome: 'Expandir', descricao: 'Desenvolvo e amplio um trecho existente' },
{ id: 'T3', nome: 'Gerar', descricao: 'Crio texto inédito a partir de tema ou briefing' },
{ id: 'T4', nome: 'Auditar', descricao: 'Analiso seu texto e aponto ajustes de estilo' },
{ id: 'T5', nome: 'Ajustar', descricao: 'Faço correções mínimas sem alterar estrutura' },
{ id: 'T6', nome: 'Canonizar', descricao: 'Preparo versão final normalizada do corpus' }
];
const UNIDADES = [
{ id: 'U1', nome: 'Parágrafo', descricao: 'Um parágrafo por vez' },
{ id: 'U2', nome: 'Bloco', descricao: '600 a 900 palavras' },
{ id: 'U3', nome: 'Cena', descricao: 'Uma cena completa', soR1: true },
{ id: 'U4', nome: 'Capítulo', descricao: 'Em etapas, uma por vez' },
{ id: 'U5', nome: 'Arquivo', descricao: 'Em fatias, uma por vez' }
];
const MODOS = [
{ id: 'OFIC', nome: 'Oficina', descricao: 'Entrega + notas + log + menu completo' },
{ id: 'PUB', nome: 'Publicação', descricao: 'Só entrega limpa + menu resumido' }
];
const PERFIS = {
geral: [
{ id: 'P1', nome: 'Economia radical', descricao: 'Cortar todo o dispensável' },
{ id: 'P4', nome: 'Contenção emocional', descricao: 'Evitar nomeação de sentimentos' },
{ id: 'P6', nome: 'Clareza lógica', descricao: 'Progressão argumentativa nítida' },
{ id: 'P7', nome: 'Ironia sutil', descricao: 'Wit sem sarcasmo' },
{ id: 'P8', nome: 'Precisão', descricao: 'Exatidão terminológica' }
],
R1: [
{ id: 'P2', nome: 'Subtexto/iceberg', descricao: 'Emoção abaixo da superfície' },
{ id: 'P3', nome: 'Ação concreta', descricao: 'Priorizar gesto e movimento' },
{ id: 'P5', nome: 'Diálogo essencial', descricao: 'Fala curta, sem didascália' }
]
};
const LIMITES = [
{ id: 'L1', nome: 'Sem evento novo', descricao: 'Mantenho enredo/argumento intacto' },
{ id: 'L2', nome: 'Microevento', descricao: 'Pequenas adições permitidas' },
{ id: 'L3', nome: 'Evento novo', descricao: 'Posso introduzir elementos novos' }
];
const DISCURSO_R1 = {
tipo: [
{ id: 'D1', nome: 'Indireto' },
{ id: 'D2', nome: 'Indireto livre' },
{ id: 'D3', nome: 'Direto' }
],
fala: [
{ id: 'DR0', nome: 'Sem fala' },
{ id: 'DR1', nome: 'Fala rara' },
{ id: 'DR2', nome: 'Fala frequente' }
],
extensao: [
{ id: 'F1', nome: 'Fala mínima' },
{ id: 'F2', nome: 'Fala média' },
{ id: 'F3', nome: 'Fala expandida' }
]
};
const DISCURSO_R2 = {
tipo: [
{ id: 'NF:ARG', nome: 'Argumentativo' },
{ id: 'NF:ENS', nome: 'Ensaístico' },
{ id: 'NF:EXP', nome: 'Expositivo' }
],
nivel: [
{ id: 'ND1', nome: 'Formal' },
{ id: 'ND2', nome: 'Conversacional' },
{ id: 'ND3', nome: 'Técnico' }
]
};
const SC_FT = {
SC: [
{ id: 'SCa', nome: 'Violência/guerra' },
{ id: 'SCb', nome: 'Perda/morte' },
{ id: 'SCc', nome: 'Coragem/medo' },
{ id: 'SCd', nome: 'Solidão/alienação' }
],
FT: [
{ id: 'FTa', nome: 'Natureza' },
{ id: 'FTb', nome: 'Relações' },
{ id: 'FTc', nome: 'Trabalho' },
{ id: 'FTd', nome: 'Resistência' }
]
};
const BIB_FORMATOS = ['ABNT', 'APA', 'Chicago', 'MLA'];
const AUDIT_SUBMENU = [
{ id: 'a', nome: 'Voz/estilo', descricao: 'Aderência ao regime' },
{ id: 'b', nome: 'Discurso', descricao: 'Tipos e consistência' },
{ id: 'c', nome: 'Linguística', descricao: 'Gramática, coesão, coerência' },
{ id: 'd', nome: 'Engenharia frasal', descricao: 'Ritmo, extensão, estrutura' },
{ id: 'e', nome: 'Anti-template', descricao: 'Detectar clichês e padrões de IA' },
{ id: 'f', nome: 'Completa', descricao: 'Todas as dimensões' },
{ id: 'g', nome: 'Calibrada', descricao: 'Foco em problemas prioritários' }
];
const DEVICE_SIZES = {
mobile: { width: 375, label: 'Celular', icon: '📱' },
tablet: { width: 768, label: 'Tablet', icon: '📟' },
desktop: { width: '100%', label: 'PC', icon: '🖥️' }
};
// Respostas automáticas do assistente
const ASSISTANT_RESPONSES = {
default: "Entendi sua pergunta. Posso ajudar com dúvidas sobre os parâmetros de configuração, estilos de escrita, ou como usar melhor o assistente. O que gostaria de saber?",
regime: "Os regimes definem o estilo fundamental:\n\n**R1 (Ficção)**: Prosa estoica inspirada em Hemingway — frases curtas, ação concreta, emoção sob a superfície, diálogos mínimos.\n\n**R2 (Não Ficção)**: Clareza lógica à Russell — argumentação precisa, ironia como ferramenta, ceticismo metodológico.",
tarefa: "As tarefas disponíveis são:\n\n• **Emular** — reescreve seu texto no estilo\n• **Expandir** — desenvolve um trecho\n• **Gerar** — cria texto novo\n• **Auditar** — analisa e sugere ajustes\n• **Ajustar** — correções mínimas\n• **Canonizar** — versão final",
perfil: "Os perfis refinam o estilo base. Você pode escolher até 2. Exemplos:\n\n• **Economia radical** — corta todo o dispensável\n• **Subtexto/iceberg** — emoção implícita (só ficção)\n• **Clareza lógica** — progressão argumentativa nítida",
discurso: "O discurso define como a narrativa ou argumento se apresenta:\n\n**Ficção**: Indireto, Indireto Livre ou Direto, com frequência de falas configurável.\n\n**Não Ficção**: Argumentativo, Ensaístico ou Expositivo, em registro formal, conversacional ou técnico.",
help: "Posso ajudar com:\n\n• Explicar qualquer parâmetro\n• Sugerir configurações para seu projeto\n• Esclarecer diferenças entre opções\n• Dar exemplos de cada estilo\n\nDigite sua dúvida!"
};
export default function AssistenteEscrita() {
const [etapa, setEtapa] = useState(0);
const [config, setConfig] = useState({
regime: null,
tarefa: null,
unidade: null,
modo: null,
perfis: [],
fonte: null,
limite: null,
discursoTipo: null,
discursoFala: null,
discursoExt: null,
scft: [],
bib: null,
cit: null,
web: 'NAO',
texto: '',
contexto: '',
auditTipo: null
});
const [showResumo, setShowResumo] = useState(false);
const [promptGerado, setPromptGerado] = useState('');
// Estados de UI
const [darkMode, setDarkMode] = useState(false);
const [deviceView, setDeviceView] = useState('desktop');
const [isFullscreen, setIsFullscreen] = useState(false);
const [uploadedFiles, setUploadedFiles] = useState([]);
// Estados do Chat
const [chatOpen, setChatOpen] = useState(false);
const [chatMessages, setChatMessages] = useState([
{
role: 'assistant',
content: 'Olá! Sou o assistente de configuração. Posso ajudar com dúvidas sobre os parâmetros, estilos de escrita ou como usar melhor esta ferramenta. Digite sua pergunta!',
timestamp: new Date()
}
]);
const [chatInput, setChatInput] = useState('');
const [isTyping, setIsTyping] = useState(false);
const containerRef = useRef(null);
const fileInputRef = useRef(null);
const chatEndRef = useRef(null);
const chatInputRef = useRef(null);
const regimeAtual = config.regime ? REGIMES[config.regime] : null;
// Cores do tema
const theme = {
bg: darkMode ? '#1a1a1a' : '#faf8f5',
bgGradient: darkMode
? 'linear-gradient(180deg, #1a1a1a 0%, #2d2d2d 100%)'
: 'linear-gradient(180deg, #faf8f5 0%, #f5f2ed 100%)',
cardBg: darkMode ? '#2d2d2d' : '#fff',
text: darkMode ? '#e5e5e5' : '#333',
textMuted: darkMode ? '#999' : '#666',
textLight: darkMode ? '#777' : '#888',
border: darkMode ? '#444' : '#e8e4dc',
borderLight: darkMode ? '#555' : '#d4d4d4',
inputBg: darkMode ? '#333' : '#fff',
hoverBg: darkMode ? '#3a3a3a' : '#f5f5f5',
chatUserBg: darkMode ? '#4a4a4a' : '#e8e4dc',
chatAssistantBg: darkMode ? '#363636' : '#f5f2ed',
accentBg: (cor) => darkMode ? `${cor}25` : `${cor}10`,
accentBorder: (cor) => darkMode ? `${cor}50` : `${cor}30`,
};
// Scroll do chat
useEffect(() => {
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [chatMessages]);
// Fullscreen toggle
const toggleFullscreen = () => {
if (!document.fullscreenElement) {
containerRef.current?.requestFullscreen?.();
setIsFullscreen(true);
} else {
document.exitFullscreen?.();
setIsFullscreen(false);
}
};
useEffect(() => {
const handleFullscreenChange = () => {
setIsFullscreen(!!document.fullscreenElement);
};
document.addEventListener('fullscreenchange', handleFullscreenChange);
return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);
}, []);
// Gerar resposta do assistente
const generateAssistantResponse = (userMessage) => {
const msg = userMessage.toLowerCase();
if (msg.includes('regime') || msg.includes('ficção') || msg.includes('não ficção') || msg.includes('r1') || msg.includes('r2')) {
return ASSISTANT_RESPONSES.regime;
}
if (msg.includes('tarefa') || msg.includes('emular') || msg.includes('expandir') || msg.includes('gerar') || msg.includes('auditar')) {
return ASSISTANT_RESPONSES.tarefa;
}
if (msg.includes('perfil') || msg.includes('perfis') || msg.includes('iceberg') || msg.includes('economia')) {
return ASSISTANT_RESPONSES.perfil;
}
if (msg.includes('discurso') || msg.includes('narrativa') || msg.includes('indireto') || msg.includes('argumentativo')) {
return ASSISTANT_RESPONSES.discurso;
}
if (msg.includes('ajuda') || msg.includes('help') || msg.includes('como') || msg.includes('o que')) {
return ASSISTANT_RESPONSES.help;
}
return ASSISTANT_RESPONSES.default;
};
// Enviar mensagem no chat
const sendChatMessage = () => {
if (!chatInput.trim()) return;
const userMessage = {
role: 'user',
content: chatInput.trim(),
timestamp: new Date()
};
setChatMessages(prev => [...prev, userMessage]);
setChatInput('');
setIsTyping(true);
// Simular tempo de resposta
setTimeout(() => {
const assistantMessage = {
role: 'assistant',
content: generateAssistantResponse(userMessage.content),
timestamp: new Date()
};
setChatMessages(prev => [...prev, assistantMessage]);
setIsTyping(false);
}, 800 + Math.random() * 700);
};
// File upload handler
const handleFileUpload = (e) => {
const files = Array.from(e.target.files);
const validFiles = files.filter(file => {
const ext = file.name.split('.').pop().toLowerCase();
return ['pdf', 'doc', 'docx'].includes(ext);
});
validFiles.forEach(file => {
const reader = new FileReader();
reader.onload = (event) => {
setUploadedFiles(prev => [...prev, {
name: file.name,
size: file.size,
type: file.type,
data: event.target.result
}]);
};
reader.readAsDataURL(file);
});
};
const removeFile = (index) => {
setUploadedFiles(prev => prev.filter((_, i) => i !== index));
};
const formatFileSize = (bytes) => {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
};
const atualizarConfig = (campo, valor) => {
setConfig(prev => ({ ...prev, [campo]: valor }));
};
const togglePerfil = (perfilId) => {
setConfig(prev => {
const novos = prev.perfis.includes(perfilId)
? prev.perfis.filter(p => p !== perfilId)
: prev.perfis.length < 2 ? [...prev.perfis, perfilId] : prev.perfis;
return { ...prev, perfis: novos };
});
};
const toggleScFt = (id) => {
setConfig(prev => {
const novos = prev.scft.includes(id)
? prev.scft.filter(s => s !== id)
: prev.scft.length < 2 ? [...prev.scft, id] : prev.scft;
return { ...prev, scft: novos };
});
};
const gerarPrompt = () => {
const linhas = [];
linhas.push(`Regime: ${config.regime} (${REGIMES[config.regime].nome})`);
linhas.push(`Tarefa: ${config.tarefa}`);
linhas.push(`Unidade: ${config.unidade}`);
linhas.push(`Modo: ${config.modo}`);
if (config.perfis.length > 0) linhas.push(`Perfis: ${config.perfis.join(', ')}`);
linhas.push(`Fonte: ${config.fonte}`);
linhas.push(`Limite: ${config.limite}`);
if (config.regime === 'R1') {
linhas.push(`Discurso: ${config.discursoTipo}+${config.discursoFala}${config.discursoFala !== 'DR0' ? '+' + config.discursoExt : ''}`);
} else {
linhas.push(`Discurso: ${config.discursoTipo}+${config.discursoFala}`);
}
if (config.scft.length > 0) linhas.push(`SC/FT: ${config.scft.join(', ')}`);
if (config.contexto) linhas.push(`CX: ${config.contexto}`);
if (config.regime === 'R2') {
linhas.push(`BIB: ${config.bib}; CIT: ${config.cit}; WEB: ${config.web}`);
}
if (config.tarefa === 'T4' && config.auditTipo) {
linhas.push(`Auditoria: [${config.auditTipo}]`);
}
if (uploadedFiles.length > 0) {
linhas.push(`Arquivos anexados: ${uploadedFiles.map(f => f.name).join(', ')}`);
}
return linhas.join('\n');
};
const proximaEtapa = () => {
if (etapa === 8) {
setPromptGerado(gerarPrompt());
setShowResumo(true);
} else {
setEtapa(prev => prev + 1);
}
};
const etapaAnterior = () => {
if (showResumo) {
setShowResumo(false);
} else {
setEtapa(prev => Math.max(0, prev - 1));
}
};
const reiniciar = () => {
setEtapa(0);
setConfig({
regime: null, tarefa: null, unidade: null, modo: null, perfis: [],
fonte: null, limite: null, discursoTipo: null, discursoFala: null,
discursoExt: null, scft: [], bib: null, cit: null, web: 'NAO',
texto: '', contexto: '', auditTipo: null
});
setShowResumo(false);
setPromptGerado('');
setUploadedFiles([]);
};
const podeAvancar = () => {
switch (etapa) {
case 0: return config.regime !== null;
case 1: return config.tarefa !== null;
case 2: return config.unidade !== null;
case 3: return config.modo !== null;
case 4: return true;
case 5: return config.fonte !== null && config.limite !== null;
case 6: return config.discursoTipo !== null && config.discursoFala !== null &&
(config.regime === 'R2' || config.discursoFala === 'DR0' || config.discursoExt !== null);
case 7: return true;
case 8: return config.regime === 'R1' || (config.bib !== null && config.cit !== null);
default: return true;
}
};
const getAccentColor = () => {
if (regimeAtual) {
return darkMode ? regimeAtual.corEscura : regimeAtual.cor;
}
return darkMode ? '#888' : '#1a1a1a';
};
const CardOpcao = ({ selecionado, onClick, titulo, descricao, disabled, small }) => (
<button
onClick={onClick}
disabled={disabled}
style={{
padding: small ? '12px 16px' : '20px 24px',
border: selecionado ? `2px solid ${getAccentColor()}` : `1px solid ${theme.borderLight}`,
borderRadius: '8px',
background: selecionado ? theme.accentBg(getAccentColor()) : disabled ? theme.hoverBg : theme.cardBg,
cursor: disabled ? 'not-allowed' : 'pointer',
textAlign: 'left',
opacity: disabled ? 0.5 : 1,
transition: 'all 0.2s ease',
width: '100%'
}}
>
<div style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: small ? '16px' : '18px',
fontWeight: 600,
color: selecionado ? getAccentColor() : theme.text,
marginBottom: descricao ? '4px' : 0
}}>
{titulo}
</div>
{descricao && (
<div style={{
fontFamily: "'Source Sans Pro', sans-serif",
fontSize: '14px',
color: theme.textMuted,
lineHeight: 1.4
}}>
{descricao}
</div>
)}
</button>
);
const ChipOpcao = ({ selecionado, onClick, label, disabled }) => (
<button
onClick={onClick}
disabled={disabled}
style={{
padding: '8px 16px',
border: selecionado ? `2px solid ${getAccentColor()}` : `1px solid ${theme.borderLight}`,
borderRadius: '20px',
background: selecionado ? getAccentColor() : theme.cardBg,
color: selecionado ? '#fff' : theme.text,
cursor: disabled ? 'not-allowed' : 'pointer',
fontFamily: "'Source Sans Pro', sans-serif",
fontSize: '14px',
fontWeight: 500,
opacity: disabled ? 0.5 : 1,
transition: 'all 0.2s ease'
}}
>
{label}
</button>
);
const ToolbarButton = ({ active, onClick, children, title }) => (
<button
onClick={onClick}
title={title}
style={{
padding: '8px 12px',
border: active ? `2px solid ${getAccentColor()}` : `1px solid ${theme.borderLight}`,
borderRadius: '6px',
background: active ? theme.accentBg(getAccentColor()) : theme.cardBg,
color: active ? getAccentColor() : theme.textMuted,
cursor: 'pointer',
fontFamily: "'Source Sans Pro', sans-serif",
fontSize: '13px',
fontWeight: 500,
transition: 'all 0.2s ease',
display: 'flex',
alignItems: 'center',
gap: '6px'
}}
>
{children}
</button>
);
// Componente de Chat
const ChatPanel = () => (
<div style={{
position: 'fixed',
bottom: deviceView === 'mobile' ? 0 : '24px',
right: deviceView === 'mobile' ? 0 : '24px',
width: deviceView === 'mobile' ? '100%' : '380px',
height: chatOpen ? (deviceView === 'mobile' ? '100%' : '500px') : '56px',
background: theme.cardBg,
borderRadius: deviceView === 'mobile' ? (chatOpen ? '0' : '0') : '16px',
boxShadow: `0 8px 32px rgba(0,0,0,${darkMode ? '0.4' : '0.2'})`,
border: `1px solid ${theme.border}`,
display: 'flex',
flexDirection: 'column',
transition: 'all 0.3s ease',
zIndex: 1000,
overflow: 'hidden'
}}>
{/* Chat Header */}
<div
onClick={() => setChatOpen(!chatOpen)}
style={{
padding: '16px 20px',
background: getAccentColor(),
color: '#fff',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
flexShrink: 0
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<span style={{ fontSize: '20px' }}>💬</span>
<div>
<div style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontWeight: 600,
fontSize: '16px'
}}>
Assistente
</div>
<div style={{
fontSize: '12px',
opacity: 0.9
}}>
Tire suas dúvidas
</div>
</div>
</div>
<span style={{
fontSize: '18px',
transform: chatOpen ? 'rotate(180deg)' : 'rotate(0)',
transition: 'transform 0.3s ease'
}}>
▲
</span>
</div>
{/* Chat Messages */}
{chatOpen && (
<>
<div style={{
flex: 1,
overflowY: 'auto',
padding: '16px',
display: 'flex',
flexDirection: 'column',
gap: '12px'
}}>
{chatMessages.map((msg, index) => (
<div
key={index}
style={{
display: 'flex',
justifyContent: msg.role === 'user' ? 'flex-end' : 'flex-start'
}}
>
<div style={{
maxWidth: '85%',
padding: '12px 16px',
borderRadius: msg.role === 'user'
? '16px 16px 4px 16px'
: '16px 16px 16px 4px',
background: msg.role === 'user'
? getAccentColor()
: theme.chatAssistantBg,
color: msg.role === 'user' ? '#fff' : theme.text,
fontFamily: "'Source Sans Pro', sans-serif",
fontSize: '14px',
lineHeight: 1.5,
whiteSpace: 'pre-wrap'
}}>
{msg.content}
</div>
</div>
))}
{isTyping && (
<div style={{ display: 'flex', justifyContent: 'flex-start' }}>
<div style={{
padding: '12px 16px',
borderRadius: '16px 16px 16px 4px',
background: theme.chatAssistantBg,
color: theme.textMuted,
fontFamily: "'Source Sans Pro', sans-serif",
fontSize: '14px'
}}>
<span style={{ animation: 'pulse 1s infinite' }}>●</span>
<span style={{ animation: 'pulse 1s infinite 0.2s' }}> ●</span>
<span style={{ animation: 'pulse 1s infinite 0.4s' }}> ●</span>
</div>
</div>
)}
<div ref={chatEndRef} />
</div>
{/* Chat Input */}
<div style={{
padding: '16px',
borderTop: `1px solid ${theme.border}`,
display: 'flex',
gap: '12px',
flexShrink: 0
}}>
<input
ref={chatInputRef}
type="text"
value={chatInput}
onChange={(e) => setChatInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendChatMessage()}
placeholder="Digite sua pergunta..."
style={{
flex: 1,
padding: '12px 16px',
border: `1px solid ${theme.borderLight}`,
borderRadius: '24px',
background: theme.inputBg,
color: theme.text,
fontFamily: "'Source Sans Pro', sans-serif",
fontSize: '14px',
outline: 'none'
}}
/>
<button
onClick={sendChatMessage}
disabled={!chatInput.trim()}
style={{
width: '44px',
height: '44px',
borderRadius: '50%',
border: 'none',
background: chatInput.trim() ? getAccentColor() : theme.borderLight,
color: '#fff',
cursor: chatInput.trim() ? 'pointer' : 'not-allowed',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '18px',
transition: 'all 0.2s ease'
}}
>
➤
</button>
</div>
</>
)}
</div>
);
const renderEtapa = () => {
if (showResumo) {
return (
<div style={{ animation: 'fadeIn 0.3s ease' }}>
<h2 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '28px',
marginBottom: '24px',
color: getAccentColor()
}}>
Configuração Completa
</h2>
<div style={{
background: darkMode ? '#222' : '#faf8f5',
border: `1px solid ${theme.border}`,
borderRadius: '8px',
padding: '24px',
marginBottom: '24px',
fontFamily: "'IBM Plex Mono', monospace",
fontSize: '14px',
lineHeight: 1.8,
whiteSpace: 'pre-wrap',
color: theme.text
}}>
{promptGerado}
</div>
{/* Upload de arquivos */}
<div style={{ marginBottom: '24px' }}>
<label style={{
display: 'block',
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '18px',
marginBottom: '12px',
color: theme.text
}}>
Arquivos de contexto (PDF, DOC):
</label>
<div
onClick={() => fileInputRef.current?.click()}
style={{
border: `2px dashed ${theme.borderLight}`,
borderRadius: '8px',
padding: '24px',
textAlign: 'center',
cursor: 'pointer',
background: theme.hoverBg,
transition: 'all 0.2s ease',
marginBottom: '12px'
}}
>
<div style={{ fontSize: '32px', marginBottom: '8px' }}>📄</div>
<div style={{
fontFamily: "'Source Sans Pro', sans-serif",
color: theme.textMuted,
fontSize: '14px'
}}>
Clique para selecionar arquivos PDF ou DOC
</div>
<input
ref={fileInputRef}
type="file"
accept=".pdf,.doc,.docx"
multiple
onChange={handleFileUpload}
style={{ display: 'none' }}
/>
</div>
{uploadedFiles.length > 0 && (
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{uploadedFiles.map((file, index) => (
<div
key={index}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '12px 16px',
background: theme.accentBg(getAccentColor()),
border: `1px solid ${theme.accentBorder(getAccentColor())}`,
borderRadius: '6px'
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<span style={{ fontSize: '20px' }}>
{file.name.endsWith('.pdf') ? '📕' : '📘'}
</span>
<div>
<div style={{
fontFamily: "'Source Sans Pro', sans-serif",
fontWeight: 600,
color: theme.text,
fontSize: '14px'
}}>
{file.name}
</div>
<div style={{
fontFamily: "'IBM Plex Mono', monospace",
fontSize: '12px',
color: theme.textMuted
}}>
{formatFileSize(file.size)}
</div>
</div>
</div>
<button
onClick={() => removeFile(index)}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
fontSize: '18px',
color: theme.textMuted,
padding: '4px 8px'
}}
>
✕
</button>
</div>
))}
</div>
)}
</div>
<div style={{ marginBottom: '24px' }}>
<label style={{
display: 'block',
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '18px',
marginBottom: '12px',
color: theme.text
}}>
Cole seu texto ou briefing aqui:
</label>
<textarea
value={config.texto}
onChange={(e) => atualizarConfig('texto', e.target.value)}
placeholder={config.fonte === 'briefing'
? 'Descreva o que deseja que seja gerado...'
: 'Cole o texto a ser trabalhado...'}
style={{
width: '100%',
minHeight: '200px',
padding: '16px',
border: `1px solid ${theme.borderLight}`,
borderRadius: '8px',
fontFamily: "'Source Sans Pro', sans-serif",
fontSize: '16px',
lineHeight: 1.6,
resize: 'vertical',
background: theme.inputBg,
color: theme.text
}}
/>
</div>
<div style={{
background: theme.accentBg(getAccentColor()),
border: `1px solid ${theme.accentBorder(getAccentColor())}`,
borderRadius: '8px',
padding: '20px',
marginBottom: '24px'
}}>
<h3 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '16px',
color: getAccentColor(),
marginBottom: '12px'
}}>
Prompt para copiar:
</h3>
<div style={{
fontFamily: "'IBM Plex Mono', monospace",
fontSize: '13px',
lineHeight: 1.6,
color: theme.text
}}>
{promptGerado}
{config.texto && (
<>
<br /><br />
---TEXTO---<br />
{config.texto.substring(0, 200)}{config.texto.length > 200 ? '...' : ''}
</>
)}
</div>
</div>
</div>
);
}
switch (etapa) {
case 0:
return (
<div style={{ animation: 'fadeIn 0.3s ease' }}>
<h2 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '28px',
marginBottom: '12px',
color: theme.text
}}>
Escolha o Regime
</h2>
<p style={{
fontFamily: "'Source Sans Pro', sans-serif",
color: theme.textMuted,
marginBottom: '32px',
fontSize: '16px',
lineHeight: 1.5
}}>
Cada regime possui características estilísticas próprias que guiarão toda a escrita.
</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
{Object.values(REGIMES).map(regime => (
<CardOpcao
key={regime.id}
selecionado={config.regime === regime.id}
onClick={() => atualizarConfig('regime', regime.id)}
titulo={`[${regime.id}] ${regime.nome}`}
descricao={regime.descricao}
/>
))}
</div>
</div>
);
case 1:
return (
<div style={{ animation: 'fadeIn 0.3s ease' }}>
<h2 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '28px',
marginBottom: '12px',
color: getAccentColor()
}}>
Qual tarefa deseja realizar?
</h2>
<p style={{
fontFamily: "'Source Sans Pro', sans-serif",
color: theme.textMuted,
marginBottom: '32px'
}}>
Selecione a operação que melhor se aplica ao seu objetivo.
</p>
<div style={{ display: 'grid', gridTemplateColumns: deviceView === 'mobile' ? '1fr' : 'repeat(2, 1fr)', gap: '12px' }}>
{TAREFAS.map(tarefa => (
<CardOpcao
key={tarefa.id}
selecionado={config.tarefa === tarefa.id}
onClick={() => atualizarConfig('tarefa', tarefa.id)}
titulo={`[${tarefa.id}] ${tarefa.nome}`}
descricao={tarefa.descricao}
small
/>
))}
</div>
</div>
);
case 2:
return (
<div style={{ animation: 'fadeIn 0.3s ease' }}>
<h2 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '28px',
marginBottom: '12px',
color: getAccentColor()
}}>
Unidade de Trabalho
</h2>
<p style={{
fontFamily: "'Source Sans Pro', sans-serif",
color: theme.textMuted,
marginBottom: '32px'
}}>
Define a extensão de cada ciclo de trabalho.
</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
{UNIDADES.map(unidade => (
<CardOpcao
key={unidade.id}
selecionado={config.unidade === unidade.id}
onClick={() => atualizarConfig('unidade', unidade.id)}
titulo={`[${unidade.id}] ${unidade.nome}`}
descricao={unidade.descricao}
disabled={unidade.soR1 && config.regime === 'R2'}
small
/>
))}
</div>
</div>
);
case 3:
return (
<div style={{ animation: 'fadeIn 0.3s ease' }}>
<h2 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '28px',
marginBottom: '12px',
color: getAccentColor()
}}>
Modo de Saída
</h2>
<p style={{
fontFamily: "'Source Sans Pro', sans-serif",
color: theme.textMuted,
marginBottom: '32px'
}}>
Escolha o formato das entregas.
</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
{MODOS.map(modo => (
<CardOpcao
key={modo.id}
selecionado={config.modo === modo.id}
onClick={() => atualizarConfig('modo', modo.id)}
titulo={`[${modo.id}] ${modo.nome}`}
descricao={modo.descricao}
/>
))}
</div>
</div>
);
case 4:
const perfisDisponiveis = config.regime === 'R1'
? [...PERFIS.geral, ...PERFIS.R1]
: PERFIS.geral;
return (
<div style={{ animation: 'fadeIn 0.3s ease' }}>
<h2 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '28px',
marginBottom: '12px',
color: getAccentColor()
}}>
Perfis Estilísticos
</h2>
<p style={{
fontFamily: "'Source Sans Pro', sans-serif",
color: theme.textMuted,
marginBottom: '32px'
}}>
Selecione até 2 perfis para refinar o estilo. (Opcional)
</p>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{perfisDisponiveis.map(perfil => (
<ChipOpcao
key={perfil.id}
selecionado={config.perfis.includes(perfil.id)}
onClick={() => togglePerfil(perfil.id)}
label={`${perfil.id} ${perfil.nome}`}
/>
))}
</div>
{config.perfis.length > 0 && (
<div style={{
marginTop: '20px',
padding: '16px',
background: theme.hoverBg,
borderRadius: '8px'
}}>
<strong style={{ color: theme.text }}>Selecionados:</strong>
{config.perfis.map(pid => {
const perfil = perfisDisponiveis.find(p => p.id === pid);
return (
<div key={pid} style={{ marginTop: '8px', fontSize: '14px', color: theme.textMuted }}>
<strong style={{ color: theme.text }}>{perfil?.nome}:</strong> {perfil?.descricao}
</div>
);
})}
</div>
)}
</div>
);
case 5:
return (
<div style={{ animation: 'fadeIn 0.3s ease' }}>
<h2 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '28px',
marginBottom: '12px',
color: getAccentColor()
}}>
Fonte e Limite
</h2>
<div style={{ marginBottom: '32px' }}>
<h3 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '20px',
marginBottom: '16px',
color: theme.text
}}>
Fonte do texto
</h3>
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap' }}>
{[
{ id: 'colado', nome: 'Texto colado' },
{ id: 'arquivo', nome: 'Arquivo anexado' },
{ id: 'briefing', nome: 'Briefing/descrição' }
].map(fonte => (
<ChipOpcao
key={fonte.id}
selecionado={config.fonte === fonte.id}
onClick={() => atualizarConfig('fonte', fonte.id)}
label={fonte.nome}
/>
))}
</div>
</div>
<div>
<h3 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '20px',
marginBottom: '16px',
color: theme.text
}}>
Limite de intervenção
</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
{LIMITES.map(limite => (
<CardOpcao
key={limite.id}
selecionado={config.limite === limite.id}
onClick={() => atualizarConfig('limite', limite.id)}
titulo={`[${limite.id}] ${limite.nome}`}
descricao={limite.descricao}
small
/>
))}
</div>
</div>
</div>
);
case 6:
const discurso = config.regime === 'R1' ? DISCURSO_R1 : DISCURSO_R2;
return (
<div style={{ animation: 'fadeIn 0.3s ease' }}>
<h2 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '28px',
marginBottom: '12px',
color: getAccentColor()
}}>
Tipo de Discurso
</h2>
<div style={{ marginBottom: '24px' }}>
<h3 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '18px',
marginBottom: '12px',
color: theme.text
}}>
{config.regime === 'R1' ? 'Modo narrativo' : 'Tipo textual'}
</h3>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
{discurso.tipo.map(t => (
<ChipOpcao
key={t.id}
selecionado={config.discursoTipo === t.id}
onClick={() => atualizarConfig('discursoTipo', t.id)}
label={t.nome}
/>
))}
</div>
</div>
<div style={{ marginBottom: '24px' }}>
<h3 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '18px',
marginBottom: '12px',
color: theme.text
}}>
{config.regime === 'R1' ? 'Frequência de fala' : 'Nível de formalidade'}
</h3>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
{(config.regime === 'R1' ? discurso.fala : discurso.nivel).map(f => (
<ChipOpcao
key={f.id}
selecionado={config.discursoFala === f.id}
onClick={() => atualizarConfig('discursoFala', f.id)}
label={f.nome}
/>
))}
</div>
</div>
{config.regime === 'R1' && config.discursoFala && config.discursoFala !== 'DR0' && (
<div>
<h3 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '18px',
marginBottom: '12px',
color: theme.text
}}>
Extensão das falas
</h3>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
{discurso.extensao.map(e => (
<ChipOpcao
key={e.id}
selecionado={config.discursoExt === e.id}
onClick={() => atualizarConfig('discursoExt', e.id)}
label={e.nome}
/>
))}
</div>
</div>
)}
</div>
);
case 7:
return (
<div style={{ animation: 'fadeIn 0.3s ease' }}>
<h2 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '28px',
marginBottom: '12px',
color: getAccentColor()
}}>
Contexto e Subtexto
</h2>
<div style={{ marginBottom: '24px' }}>
<h3 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '18px',
marginBottom: '12px',
color: theme.text
}}>
Subtexto Crítico (SC) — opcional, até 2
</h3>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
{SC_FT.SC.map(sc => (
<ChipOpcao
key={sc.id}
selecionado={config.scft.includes(sc.id)}
onClick={() => toggleScFt(sc.id)}
label={sc.nome}
/>
))}
</div>
</div>
<div style={{ marginBottom: '24px' }}>
<h3 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '18px',
marginBottom: '12px',
color: theme.text
}}>
Fricção Temática (FT) — opcional, até 2
</h3>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
{SC_FT.FT.map(ft => (
<ChipOpcao
key={ft.id}
selecionado={config.scft.includes(ft.id)}
onClick={() => toggleScFt(ft.id)}
label={ft.nome}
/>
))}
</div>
</div>
<div>
<h3 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '18px',
marginBottom: '12px',
color: theme.text
}}>
Contexto adicional (CX)
</h3>
<textarea
value={config.contexto}
onChange={(e) => atualizarConfig('contexto', e.target.value)}
placeholder={config.regime === 'R1'
? 'lugar + ação física + objeto + luz/clima + tensão tácita + gesto + silêncio'
: 'tema/problema + objetivo + audiência + registro + evidência + limite'}
style={{
width: '100%',
minHeight: '100px',
padding: '12px',
border: `1px solid ${theme.borderLight}`,
borderRadius: '8px',
fontFamily: "'Source Sans Pro', sans-serif",
fontSize: '14px',
resize: 'vertical',
background: theme.inputBg,
color: theme.text
}}
/>
</div>
{config.tarefa === 'T4' && (
<div style={{ marginTop: '24px' }}>
<h3 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '18px',
marginBottom: '12px',
color: theme.text
}}>
Tipo de Auditoria
</h3>
<div style={{ display: 'grid', gridTemplateColumns: deviceView === 'mobile' ? '1fr' : 'repeat(2, 1fr)', gap: '8px' }}>
{AUDIT_SUBMENU.map(item => (
<CardOpcao
key={item.id}
selecionado={config.auditTipo === item.id}
onClick={() => atualizarConfig('auditTipo', item.id)}
titulo={`[${item.id}] ${item.nome}`}
descricao={item.descricao}
small
/>
))}
</div>
</div>
)}
</div>
);
case 8:
if (config.regime === 'R1') {
return (
<div style={{ animation: 'fadeIn 0.3s ease' }}>
<h2 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '28px',
marginBottom: '12px',
color: getAccentColor()
}}>
Configuração Pronta
</h2>
<p style={{
fontFamily: "'Source Sans Pro', sans-serif",
color: theme.textMuted,
marginBottom: '32px',
lineHeight: 1.6
}}>
Todos os parâmetros obrigatórios para ficção foram coletados.
Clique em "Gerar Prompt" para ver a configuração final e inserir seu texto.
</p>
<div style={{
padding: '20px',
background: theme.accentBg(getAccentColor()),
border: `1px solid ${theme.accentBorder(getAccentColor())}`,
borderRadius: '8px'
}}>
<div style={{
fontFamily: "'IBM Plex Mono', monospace",
fontSize: '13px',
color: theme.textMuted
}}>
<strong style={{ color: theme.text }}>Prévia:</strong> {config.regime} | {config.tarefa} | {config.unidade} | {config.modo} |
{config.perfis.length > 0 ? ` ${config.perfis.join('+')} |` : ''} {config.limite} | {config.discursoTipo}
</div>
</div>
</div>
);
}
return (
<div style={{ animation: 'fadeIn 0.3s ease' }}>
<h2 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '28px',
marginBottom: '12px',
color: getAccentColor()
}}>
Referências (R2)
</h2>
<div style={{ marginBottom: '24px' }}>
<h3 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '18px',
marginBottom: '12px',
color: theme.text
}}>
Formato bibliográfico
</h3>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
{BIB_FORMATOS.map(formato => (
<ChipOpcao
key={formato}
selecionado={config.bib === formato}
onClick={() => atualizarConfig('bib', formato)}
label={formato}
/>
))}
</div>
</div>
<div style={{ marginBottom: '24px' }}>
<h3 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '18px',
marginBottom: '12px',
color: theme.text
}}>
Citação no corpo
</h3>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
<ChipOpcao
selecionado={config.cit === 'S'}
onClick={() => atualizarConfig('cit', 'S')}
label="CIT:S — Com atribuição"
/>
<ChipOpcao
selecionado={config.cit === 'N'}
onClick={() => atualizarConfig('cit', 'N')}
label="CIT:N — Sem nomes"
/>
</div>
</div>
<div>
<h3 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '18px',
marginBottom: '12px',
color: theme.text
}}>
Pesquisa web
</h3>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
<ChipOpcao
selecionado={config.web === 'SIM'}
onClick={() => atualizarConfig('web', 'SIM')}
label="WEB:SIM — Permitir busca"
/>
<ChipOpcao
selecionado={config.web === 'NAO'}
onClick={() => atualizarConfig('web', 'NAO')}
label="WEB:NÃO — Só fontes fornecidas"
/>
</div>
</div>
</div>
);
default:
return null;
}
};
const deviceWidth = DEVICE_SIZES[deviceView].width;
return (
<div
ref={containerRef}
style={{
minHeight: '100vh',
background: theme.bgGradient,
fontFamily: "'Source Sans Pro', -apple-system, sans-serif",
transition: 'all 0.3s ease'
}}
>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Crimson+Text:ital,wght@0,400;0,600;0,700;1,400&family=Source+Sans+Pro:wght@400;600&family=IBM+Plex+Mono:wght@400;500&display=swap');
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulse {
0%, 100% { opacity: 0.4; }
50% { opacity: 1; }
}
* { box-sizing: border-box; margin: 0; padding: 0; }
button:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0,0,0,${darkMode ? '0.3' : '0.1'});
}
textarea:focus, input:focus, button:focus {
outline: none;
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: ${darkMode ? '#2d2d2d' : '#f1f1f1'};
}
::-webkit-scrollbar-thumb {
background: ${darkMode ? '#555' : '#ccc'};
border-radius: 4px;
}
`}</style>
{/* Toolbar */}
<div style={{
position: 'sticky',
top: 0,
zIndex: 100,
background: theme.cardBg,
borderBottom: `1px solid ${theme.border}`,
padding: '12px 24px',
display: 'flex',
justifyContent: 'center',
gap: '8px',
flexWrap: 'wrap'
}}>
{/* Device buttons */}
<div style={{ display: 'flex', gap: '4px', marginRight: '16px' }}>
{Object.entries(DEVICE_SIZES).map(([key, { label, icon }]) => (
<ToolbarButton
key={key}
active={deviceView === key}
onClick={() => setDeviceView(key)}
title={label}
>
{icon} <span style={{ display: deviceView === 'mobile' ? 'none' : 'inline' }}>{label}</span>
</ToolbarButton>
))}
</div>
{/* Theme toggle */}
<ToolbarButton
active={darkMode}
onClick={() => setDarkMode(!darkMode)}
title={darkMode ? 'Modo claro' : 'Modo escuro'}
>
{darkMode ? '☀️' : '🌙'} <span style={{ display: deviceView === 'mobile' ? 'none' : 'inline' }}>{darkMode ? 'Claro' : 'Escuro'}</span>
</ToolbarButton>
{/* Fullscreen toggle */}
<ToolbarButton
active={isFullscreen}
onClick={toggleFullscreen}
title={isFullscreen ? 'Sair da tela cheia' : 'Tela cheia'}
>
{isFullscreen ? '⊡' : '⛶'} <span style={{ display: deviceView === 'mobile' ? 'none' : 'inline' }}>{isFullscreen ? 'Sair' : 'Tela cheia'}</span>
</ToolbarButton>
{/* Chat toggle */}
<ToolbarButton
active={chatOpen}
onClick={() => setChatOpen(!chatOpen)}
title="Abrir chat de ajuda"
>
💬 <span style={{ display: deviceView === 'mobile' ? 'none' : 'inline' }}>Ajuda</span>
</ToolbarButton>
</div>
{/* Device frame container */}
<div style={{
display: 'flex',
justifyContent: 'center',
padding: deviceView === 'desktop' ? '0' : '24px',
paddingBottom: chatOpen && deviceView !== 'mobile' ? '540px' : (deviceView === 'desktop' ? '0' : '24px'),
minHeight: 'calc(100vh - 60px)',
transition: 'padding 0.3s ease'
}}>
<div style={{
width: typeof deviceWidth === 'number' ? `${deviceWidth}px` : deviceWidth,
maxWidth: '100%',
background: theme.bg,
borderRadius: deviceView === 'desktop' ? '0' : '24px',
boxShadow: deviceView === 'desktop' ? 'none' : `0 8px 32px rgba(0,0,0,${darkMode ? '0.4' : '0.15'})`,
border: deviceView === 'desktop' ? 'none' : `1px solid ${theme.border}`,
overflow: 'hidden',
transition: 'all 0.3s ease'
}}>
{/* Header */}
<header style={{
padding: deviceView === 'mobile' ? '16px 20px' : '24px 32px',
borderBottom: `1px solid ${theme.border}`,
background: theme.cardBg
}}>
<div style={{
maxWidth: '800px',
margin: '0 auto',
display: 'flex',
alignItems: deviceView === 'mobile' ? 'flex-start' : 'center',
justifyContent: 'space-between',
flexDirection: deviceView === 'mobile' ? 'column' : 'row',
gap: '12px'
}}>
<div>
<h1 style={{
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: deviceView === 'mobile' ? '22px' : '28px',
fontWeight: 700,
color: theme.text,
letterSpacing: '-0.5px'
}}>
Assistente de Escrita
</h1>
<p style={{
fontFamily: "'Source Sans Pro', sans-serif",
fontSize: '14px',
color: theme.textLight,
marginTop: '4px'
}}>
Configurador Interativo de Estilo
</p>
</div>
{regimeAtual && (
<div style={{
padding: '8px 16px',
background: theme.accentBg(getAccentColor()),
border: `1px solid ${theme.accentBorder(getAccentColor())}`,
borderRadius: '6px',
fontFamily: "'IBM Plex Mono', monospace",
fontSize: '13px',
color: getAccentColor(),
fontWeight: 500
}}>
{regimeAtual.id} — {regimeAtual.nome}
</div>
)}
</div>
</header>
{/* Progress */}
{!showResumo && (
<div style={{
maxWidth: '800px',
margin: '0 auto',
padding: deviceView === 'mobile' ? '16px 20px 0' : '24px 32px 0'
}}>
<div style={{
display: 'flex',
gap: '4px',
marginBottom: '8px'
}}>
{[...Array(9)].map((_, i) => (
<div
key={i}
style={{
flex: 1,
height: '4px',
borderRadius: '2px',
background: i <= etapa ? getAccentColor() : (darkMode ? '#444' : '#e0e0e0'),
transition: 'background 0.3s ease'
}}
/>
))}
</div>
<div style={{
fontFamily: "'IBM Plex Mono', monospace",
fontSize: '12px',
color: theme.textLight
}}>
Etapa {etapa + 1} de 9
</div>
</div>
)}
{/* Content */}
<main style={{
maxWidth: '800px',
margin: '0 auto',
padding: deviceView === 'mobile' ? '20px' : '32px'
}}>
<div style={{
background: theme.cardBg,
borderRadius: '12px',
padding: deviceView === 'mobile' ? '20px' : '32px',
boxShadow: `0 1px 3px rgba(0,0,0,${darkMode ? '0.2' : '0.05'}), 0 4px 12px rgba(0,0,0,${darkMode ? '0.2' : '0.05'})`,
border: `1px solid ${theme.border}`
}}>
{renderEtapa()}
</div>
{/* Navigation */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
marginTop: '24px',
gap: '16px',
flexDirection: deviceView === 'mobile' ? 'column-reverse' : 'row'
}}>
<button
onClick={etapa === 0 && !showResumo ? reiniciar : etapaAnterior}
style={{
padding: '12px 24px',
background: theme.cardBg,
border: `1px solid ${theme.borderLight}`,
borderRadius: '8px',
fontFamily: "'Source Sans Pro', sans-serif",
fontSize: '15px',
fontWeight: 600,
color: theme.textMuted,
cursor: 'pointer',
transition: 'all 0.2s ease',
flex: deviceView === 'mobile' ? 1 : 'none'
}}
>
{etapa === 0 && !showResumo ? 'Reiniciar' : '← Voltar'}
</button>
{showResumo ? (
<button
onClick={reiniciar}
style={{
padding: '12px 32px',
background: getAccentColor(),
border: 'none',
borderRadius: '8px',
fontFamily: "'Source Sans Pro', sans-serif",
fontSize: '15px',
fontWeight: 600,
color: '#fff',
cursor: 'pointer',
transition: 'all 0.2s ease',
flex: deviceView === 'mobile' ? 1 : 'none'
}}
>
Nova Configuração
</button>
) : (
<button
onClick={proximaEtapa}
disabled={!podeAvancar()}
style={{
padding: '12px 32px',
background: podeAvancar() ? getAccentColor() : (darkMode ? '#555' : '#ccc'),
border: 'none',
borderRadius: '8px',
fontFamily: "'Source Sans Pro', sans-serif",
fontSize: '15px',
fontWeight: 600,
color: '#fff',
cursor: podeAvancar() ? 'pointer' : 'not-allowed',
transition: 'all 0.2s ease',
flex: deviceView === 'mobile' ? 1 : 'none'
}}
>
{etapa === 8 ? 'Gerar Prompt →' : 'Próximo →'}
</button>
)}
</div>
</main>
{/* Footer */}
<footer style={{
textAlign: 'center',
padding: '24px',
fontFamily: "'Source Sans Pro', sans-serif",
fontSize: '13px',
color: theme.textLight
}}>
Estilos: Ficção (prosa concreta, estoica) • Não Ficção (clareza lógica, ceticismo metodológico)
</footer>
</div>
</div>
{/* Chat Panel */}
<ChatPanel />
</div>
);
}
Comentários
Postar um comentário