eubottura's picture
ROLE
c0e8c3f verified
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
const inputText = document.getElementById('inputText');
const outputContainer = document.getElementById('outputContainer');
const formatBtn = document.getElementById('formatBtn');
const copyBtn = document.getElementById('copyBtn');
const downloadBtn = document.getElementById('downloadBtn');
const clearBtn = document.getElementById('clearBtn');
const languageSelect = document.getElementById('languageSelect');
const uppercaseCheck = document.getElementById('uppercaseCheck');
// Tabu words for each language (expanded lists)
const tabuWords = {
PT: ['o', 'a', 'os', 'as', 'um', 'uma', 'me', 'te', 'se', 'lhe', 'nos', 'de', 'do', 'da', 'em', 'no', 'na', 'por', 'para', 'com', 'ao', 'e', 'ou', 'mas', 'que', 'como', 'porque', 'quando', 'ent茫o', 's贸', 'j谩', 'tamb茅m', 'tipo', 'nosso', 'nossa', 'at茅', 'sem', 'sob', 'sobre', 'tr谩s', 'ante', 'ap贸s', 'entre', 'durante', 'desde', 'perante', 'mal', 'bem', 'muito', 'pouco', 't茫o', 'mais', 'menos', 'tal', 'qual', 'seu', 'sua', 'seus', 'suas', 'meu', 'minha', 'meus', 'minhas', 'teu', 'tua', 'teus', 'tuas', 'nosso', 'nossa', 'nossos', 'nossas', 'vosso', 'vossa', 'vossos', 'vossas', 'este', 'esta', 'estes', 'estas', 'isto', 'esse', 'essa', 'esses', 'essas', 'isso', 'aquele', 'aquela', 'aqueles', 'aquelas', 'aquilo', 'cada', 'todo', 'toda', 'todos', 'todas', 'outro', 'outra', 'outros', 'outras', 'mesmo', 'mesma', 'mesmos', 'mesmas', 'pr贸prio', 'pr贸pria', 'pr贸prios', 'pr贸prias', 'demais', 'demais', 'certo', 'certa', 'certos', 'certas', 'diverso', 'diversa', 'diversos', 'diversas', 'algum', 'alguma', 'alguns', 'algumas', 'nenhum', 'nenhuma', 'nenhuns', 'nenhumas', 'muito', 'muita', 'muitos', 'muitas', 'pouco', 'pouca', 'poucos', 'poucas', 'tanto', 'tanta', 'tantos', 'tantas', 'quanto', 'quanta', 'quantos', 'quantas', 'varios', 'varias', 'bastante', 'suficiente', 'demais', 'demasiado', 'menos', 'mais', 'melhor', 'pior', 'maior', 'menor', 'talvez', 'acaso', 'caso', 'vez', 'vezes', 'hora', 'horas', 'dia', 'dias', 'noite', 'noites', 'manh茫', 'tarde', 'tarde', 'ano', 'anos', 'm锚s', 'meses', 'semana', 'semanas', 'hoje', 'ontem', 'amanh茫', 'antes', 'depois', 'agora', 'sempre', 'nunca', 'ainda', 'j谩', 'logo', 'depois', 'cedo', 'tarde', 'cedo', 'tarde', 'longe', 'perto', 'acima', 'abaixo', 'dentro', 'fora', 'atr谩s', 'diante', 'frente', 'meio', 'lado', 'outro', 'mesmo', 'pr贸ximo', 'distante', 'junto', 'separado', 'certo', 'duro', 'f谩cil', 'dif铆cil', 'poss铆vel', 'imposs铆vel', 'necess谩rio', 'importante', 'grande', 'pequeno', 'alto', 'baixo', 'comprido', 'curto', 'largo', 'estreito', 'grosso', 'fino', 'cheio', 'vazio', 'claro', 'escuro', 'quente', 'frio', 'seco', 'molhado', 'limpo', 'sujo', 'novo', 'velho', 'antigo', 'moderno', 'bonito', 'feio', 'bom', 'mau', 'melhor', 'pior', 'caro', 'barato', 'r谩pido', 'lento', 'certo', 'errado', 'verdadeiro', 'falso', 'real', 'fict铆cio', 'verdade', 'mentira', 'verdadeiro', 'falso', 'certo', 'incerto', 'seguro', 'perigoso', 'tranquilo', 'agitado', 'calmo', 'nervoso', 'alegre', 'triste', 'feliz', 'infeliz', 'contente', 'descontente', 'satisfeito', 'insatisfeito', 'orgulhoso', 'envergonhado', 'surpreso', 'indiferente', 'interessado', 'entediado', 'cansado', 'descansado', 'faminto', 'satisfeito', 'sede', 'satisfeito', 'com fome', 'com sede', 'com sono', 'acordado', 'dormindo', 'vivo', 'morto', 'nascendo', 'morrendo', 'crescendo', 'diminuindo', 'aumentando', 'diminuindo', 'subindo', 'descendo', 'entrando', 'saindo', 'chegando', 'partindo', 'indo', 'vindo', 'ficando', 'saindo', 'voltando', 'retornando', 'continuando', 'parando', 'come莽ando', 'terminando', 'iniciando', 'finalizando', 'abrindo', 'fechando', 'ligando', 'desligando', 'acendendo', 'apagando', 'acendendo', 'apagando', 'aquecendo', 'esfriando', 'secando', 'molhando', 'limpando', 'sujando', 'enchendo', 'esvaziando', 'cheio', 'vazio', 'claro', 'escuro', 'quente', 'frio', 'seco', 'molhado', 'limpo', 'sujo', 'novo', 'velho', 'antigo', 'moderno', 'bonito', 'feio', 'bom', 'mau', 'melhor', 'pior', 'caro', 'barato', 'r谩pido', 'lento', 'certo', 'errado', 'verdadeiro', 'falso', 'real', 'fict铆cio', 'verdade', 'mentira', 'verdadeiro', 'falso', 'certo', 'incerto', 'seguro', 'perigoso', 'tranquilo', 'agitado', 'calmo', 'nervoso', 'alegre', 'triste', 'feliz', 'infeliz', 'contente', 'descontente', 'satisfeito', 'insatisfeito', 'orgulhoso', 'envergonhado', 'surpreso', 'indiferente', 'interessado', 'entediado', 'cansado', 'descansado', 'faminto', 'satisfeito', 'sede', 'satisfeito', 'com fome', 'com sede', 'com sono', 'acordado', 'dormindo', 'vivo', 'morto', 'nascendo', 'morrendo', 'crescendo', 'diminuindo', 'aumentando', 'diminuindo', 'subindo', 'descendo', 'entrando', 'saindo', 'chegando', 'partindo', 'indo', 'vindo', 'ficando', 'saindo', 'voltando', 'retornando', 'continuando', 'parando', 'come莽ando', 'terminando', 'iniciando', 'finalizando', 'abrindo', 'fechando', 'ligando', 'desligando', 'acendendo', 'apagando', 'acendendo', 'apagando', 'aquecendo', 'esfriando', 'secando', 'molhando', 'limpando', 'sujando', 'enchendo', 'esvaziando'],
EN: ['a', 'an', 'the', 'of', 'in', 'on', 'at', 'to', 'for', 'with', 'from', 'by', 'and', 'or', 'but', 'that', 'me', 'you', 'it', 'he', 'she', 'we', 'they', 'us', 'them', 'his', 'her', 'its', 'our', 'their', 'myself', 'yourself', 'himself', 'herself', 'itself', 'ourselves', 'yourselves', 'themselves', 'this', 'that', 'these', 'those', 'some', 'any', 'many', 'much', 'few', 'little', 'more', 'most', 'other', 'another', 'such', 'same', 'different', 'own', 'main', 'only', 'very', 'quite', 'rather', 'fairly', 'pretty', 'really', 'extremely', 'incredibly', 'absolutely', 'totally', 'completely', 'entirely', 'fully', 'wholly', 'altogether', 'exceedingly', 'intensely', 'highly', 'greatly', 'seriously', 'badly', 'terribly', 'awfully', 'horribly', 'dreadfully', 'frightfully', 'terribly', 'awfully', 'horribly', 'dreadfully', 'frightfully', 'badly', 'seriously', 'greatly', 'highly', 'intensely', 'exceedingly', 'altogether', 'wholly', 'fully', 'entirely', 'completely', 'totally', 'absolutely', 'incredibly', 'extremely', 'really', 'pretty', 'fairly', 'rather', 'quite', 'very', 'only', 'main', 'own', 'different', 'same', 'such', 'another', 'other', 'most', 'more', 'little', 'few', 'much', 'many', 'any', 'some', 'those', 'these', 'that', 'this', 'themselves', 'yourselves', 'ourselves', 'itself', 'herself', 'himself', 'yourself', 'myself', 'their', 'our', 'its', 'her', 'his', 'them', 'us', 'they', 'we', 'she', 'he', 'it', 'you', 'me', 'but', 'or', 'and', 'by', 'from', 'with', 'for', 'to', 'at', 'on', 'in', 'of', 'the', 'an', 'a'],
ES: ['de', 'en', 'a', 'por', 'para', 'con', 'y', 'que', 'el', 'la', 'los', 'las', 'un', 'una', 'unos', 'unas', 'me', 'te', 'se', 'nos', 'os', 'le', 'les', 'lo', 'la', 'los', 'las', 'mi', 'tu', 'su', 'nuestro', 'vuestra', 'este', 'ese', 'aquel', 'esta', 'esa', 'aquella', 'estos', 'esos', 'aquellos', 'estas', 'esas', 'aquellas', 'esto', 'eso', 'aquello', 'cada', 'todo', 'toda', 'todos', 'todas', 'otro', 'otra', 'otros', 'otras', 'mismo', 'misma', 'mismos', 'mismas', 'pr贸ximo', 'pr贸xima', 'pr贸ximos', 'pr贸ximas', 'distante', 'distantes', 'junto', 'juntos', 'junta', 'juntas', 'separado', 'separados', 'separada', 'separadas', 'cierto', 'cierta', 'ciertos', 'ciertas', 'duro', 'duros', 'dura', 'duras', 'f谩cil', 'f谩ciles', 'dif铆cil', 'dif铆ciles', 'posible', 'posibles', 'imposible', 'imposibles', 'necesario', 'necesarios', 'necesaria', 'necesarias', 'importante', 'importantes', 'grande', 'grandes', 'peque帽o', 'peque帽a', 'peque帽os', 'peque帽as', 'alto', 'altos', 'alta', 'altas', 'bajo', 'bajos', 'baja', 'bajas', 'comprido', 'compridos', 'comprida', 'compridas', 'largo', 'largos', 'larga', 'largas', 'corto', 'cortos', 'corta', 'cortas', 'estrecho', 'estrechos', 'estrecha', 'estrechas', 'grosso', 'grueso', 'grosa', 'gruesa', 'grosos', 'gruesos', 'finos', 'fino', 'fina', 'finas', 'cheio', 'lleno', 'cheios', 'llenos', 'cheia', 'llena', 'cheias', 'llenas', 'vazio', 'vac铆o', 'vazios', 'vac铆os', 'vazia', 'vac铆a', 'vazias', 'vac铆as', 'claro', 'clara', 'claros', 'claras', 'escuro', 'oscuro', 'escura', 'oscura', 'escuros', 'oscuros', 'escuras', 'oscuras', 'quente', 'caliente', 'quentes', 'calientes', 'frio', 'fr铆o', 'fria', 'fr铆a', 'frios', 'fr铆os', 'frias', 'fr铆as', 'seco', 'secos', 'seca', 'secas', 'molhado', 'mojado', 'molhados', 'mojados', 'molhada', 'mojada', 'molhadas', 'mojadas', 'limpo', 'limpios', 'limpia', 'limpias', 'sujo', 'sucio', 'sujos', 'sucios', 'suja', 'sucia', 'sujas', 'sucias', 'novo', 'nuevo', 'nuevos', 'nueva', 'nuevas', 'velho', 'viejo', 'viejos', 'vieja', 'viejas', 'antigo', 'antiguo', 'antiguos', 'antigua', 'antiguas', 'moderno', 'modernos', 'moderna', 'modernas', 'bonito', 'bonitos', 'bonita', 'bonitas', 'feio', 'feo', 'feos', 'fea', 'feas', 'bom', 'bueno', 'buenos', 'buena', 'buenas', 'mau', 'malo', 'malos', 'mala', 'malas', 'melhor', 'mejor', 'melhores', 'mejores', 'pior', 'peor', 'piores', 'peores', 'caro', 'caros', 'cara', 'caras', 'barato', 'baratos', 'barata', 'baratas', 'r谩pido', 'r谩pidos', 'r谩pida', 'r谩pidas', 'lento', 'lentos', 'lenta', 'lentas', 'certo', 'cierto', 'ciertos', 'cierta', 'ciertas', 'errado', 'equivocado', 'equivocados', 'equivocada', 'equivocadas', 'verdadeiro', 'verdadero', 'verdaderos', 'verdadera', 'verdaderas', 'falso', 'falsos', 'falsa', 'falsas', 'real', 'reales', 'fict铆cio', 'ficticio', 'ficticios', 'ficticia', 'ficticias', 'verdade', 'verdad', 'mentira', 'mentiras', 'seguro', 'seguros', 'segura', 'seguras', 'perigoso', 'peligroso', 'peligrosos', 'peligrosa', 'peligrosas', 'tranquilo', 'tranquilos', 'tranquila', 'tranquilas', 'agitado', 'agitados', 'agitada', 'agitadas', 'calmo', 'calmos', 'calma', 'calmas', 'nervoso', 'nerviosos', 'nerviosa', 'nerviosas', 'alegre', 'alegres', 'triste', 'tristes', 'feliz', 'felices', 'infeliz', 'infelices', 'contente', 'contentos', 'contenta', 'contentas', 'descontente', 'descontentos', 'descontenta', 'descontentas', 'satisfeito', 'satisfecho', 'satisfechos', 'satisfecha', 'satisfechas', 'insatisfeito', 'insatisfecho', 'insatisfechos', 'insatisfecha', 'insatisfechas', 'orgulhoso', 'orgulloso', 'orgullosos', 'orgullosa', 'orgullosas', 'envergonhado', 'avergonzado', 'avergonzados', 'avergonzada', 'avergonzadas', 'surpreso', 'sorprendido', 'sorprendidos', 'sorprendida', 'sorprendidas', 'indiferente', 'indiferentes', 'interessado', 'interesado', 'interesados', 'interesada', 'interesadas', 'entediado', 'aburrido', 'aburridos', 'aburrida', 'aburridas', 'cansado', 'cansados', 'cansada', 'cansadas', 'descansado', 'descansados', 'descansada', 'descansadas', 'faminto', 'hambriento', 'hambrientos', 'hambrienta', 'hambrientas', 'satisfeito', 'satisfecho', 'satisfechos', 'satisfecha', 'satisfechas', 'sede', 'sed', 'satisfeito', 'satisfecho', 'satisfechos', 'satisfecha', 'satisfechas', 'com fome', 'con hambre', 'com sede', 'con sed', 'com sono', 'con sue帽o', 'acordado', 'despierto', 'despiertos', 'despierta', 'despiertas', 'dormindo', 'durmiendo', 'vivo', 'vivos', 'viva', 'vivas', 'morto', 'muerto', 'muertos', 'muerta', 'muertas', 'nascendo', 'naciendo', 'morrendo', 'muriendo', 'crescendo', 'creciendo', 'diminuindo', 'disminuyendo', 'aumentando', 'aumentando', 'subindo', 'subiendo', 'descendo', 'bajando', 'entrando', 'entrando', 'saindo', 'saliendo', 'chegando', 'llegando', 'partindo', 'partiendo', 'indo', 'yendo', 'vindo', 'viniendo', 'ficando', 'quedando', 'saindo', 'saliendo', 'voltando', 'volviendo', 'retornando', 'retornando', 'continuando', 'continuando', 'parando', 'parando', 'come莽ando', 'comenzando', 'terminando', 'terminando', 'iniciando', 'iniciando', 'finalizando', 'finalizando', 'abrindo', 'abriendo', 'fechando', 'cerrando', 'ligando', 'encendiendo', 'desligando', 'apagando', 'acendendo', 'encendiendo', 'apagando', 'apagando', 'aquecendo', 'calentando', 'esfriando', 'enfriando', 'secando', 'secando', 'molhando', 'mojando', 'limpando', 'limpiando', 'sujando', 'ensuciando', 'enchendo', 'llenando', 'esvaziando', 'vaciando']
};
// Connectives - MUST always start new blocks, never end them
const connectives = {
PT: ['E', '脡', 'QUE'],
EN: ['AND', 'IS', 'THAT'],
ES: ['Y', 'ES', 'QUE']
};
// Negation words - must be isolated in short blocks
const negationWords = {
PT: ['N脙O', 'NUNCA'],
EN: ['NOT', 'NEVER'],
ES: ['NO', 'NUNCA']
};
// Format text function
function formatText() {
const text = inputText.value.trim();
if (!text) {
outputContainer.innerHTML = '<p class="text-gray-500 text-center py-20">Please enter some text to format</p>';
return;
}
const language = languageSelect.value;
const uppercaseMode = uppercaseCheck.checked;
try {
const formatted = processText(text, language, uppercaseMode);
outputContainer.innerHTML = `<pre class="whitespace-pre-wrap font-sans text-gray-800">${formatted}</pre>`;
} catch (error) {
outputContainer.innerHTML = `<p class="text-red-500 text-center py-20">Error formatting text: ${error.message}</p>`;
}
}
// Text processing function
function processText(text, language, uppercaseMode) {
// PRE-PROCESSING & NORMALIZATION
// Clean whitespace: Remove duplicate spaces and normalize line breaks
let normalized = text.replace(/\s+/g, ' ').trim();
// Convert commas to "!" (attached to preceding word without space)
normalized = normalized.replace(/,/g, '!');
// Normalize exclamation marks: Convert all "!!" or "!!!" sequences to single "!"
normalized = normalized.replace(/!+/g, '!');
// Preserve all original punctuation exactly as positioned in source text
// (already handled by above steps)
// DETERMINISTIC SPLITTING (STRONG RULES - HIGHEST PRIORITY)
// Split immediately after every "!", "?", or "." (punctuation stays with preceding word)
const sentences = normalized.split(/([.!?]+)/).filter(s => s.trim() !== '');
let blocks = [];
for (let i = 0; i < sentences.length; i += 2) {
const sentence = sentences[i];
const punctuation = sentences[i + 1] || '';
// Process each sentence
const sentenceBlocks = processSentence(sentence + punctuation, language);
blocks = blocks.concat(sentenceBlocks);
}
// Apply heuristic optimizations
blocks = applyHeuristics(blocks, language);
// Apply uppercase if needed
if (uppercaseMode) {
blocks = blocks.map(block => block.toUpperCase());
}
return blocks.join('\n');
}
// Process individual sentence with zero-error tolerance
function processSentence(sentence, language) {
const words = sentence.trim().split(/\s+/);
const blocks = [];
let currentBlock = '';
let i = 0;
while (i < words.length) {
const word = words[i];
const cleanWord = word.replace(/[.!?!,]/g, '');
// STRONG RULE: Connectives must start new blocks, never end them
if (isConnective(cleanWord, language)) {
if (currentBlock) {
blocks.push(resolveBlockEnding(currentBlock.trim(), language));
}
currentBlock = word;
i++;
continue;
}
// Handle negation - isolate in short blocks
if (isNegation(cleanWord, language)) {
if (currentBlock) {
blocks.push(resolveBlockEnding(currentBlock.trim(), language));
}
blocks.push(word);
currentBlock = '';
i++;
continue;
}
// Calculate character count (excluding spaces and punctuation)
const testBlock = currentBlock ? `${currentBlock} ${word}` : word;
const charCount = testBlock.replace(/\s/g, '').replace(/[.!?!,]/g, '').length;
// EXCEPTION: Single word blocks can exceed 11 characters
if (!currentBlock && charCount > 11) {
blocks.push(word);
currentBlock = '';
i++;
continue;
}
// Check if adding word would exceed 11 character limit
if (currentBlock && charCount > 11) {
// Resolve current block with anti-weakening principle
const resolvedBlock = resolveBlockEnding(currentBlock.trim(), language);
if (resolvedBlock) {
blocks.push(resolvedBlock);
}
currentBlock = word;
} else {
currentBlock = testBlock;
}
i++;
}
// Handle remaining block with anti-weakening principle
if (currentBlock) {
const resolvedBlock = resolveBlockEnding(currentBlock.trim(), language);
if (resolvedBlock) {
blocks.push(resolvedBlock);
}
}
return blocks;
}
// Resolve block ending - ZERO ERROR TOLERANCE for tabu endings
function resolveBlockEnding(block, language) {
if (!block) return '';
const words = block.split(/\s+/);
if (words.length === 0) return '';
const lastWord = words[words.length - 1].replace(/[.!?!,]/g, '');
const lastWordClean = lastWord;
// STRONG RULE: Never end with tabu words
if (isTabu(lastWordClean, language)) {
if (words.length === 1) {
// Single word is tabu - this shouldn't happen, but return as-is
return block;
}
// Move tabu word to next block
const newBlock = words.slice(0, -1).join(' ');
return newBlock;
}
// STRONG RULE: Never end with 2-3 character words
if (lastWordClean.length >= 2 && lastWordClean.length <= 3) {
if (words.length === 1) {
return block; // Single word exception
}
// Move short word to next block
const newBlock = words.slice(0, -1).join(' ');
return newBlock;
}
return block;
}
// Check if word is a connective
function isConnective(word, language) {
if (!word) return false;
const upperWord = word.toUpperCase();
return connectives[language].includes(upperWord);
}
// Check if word is tabu
function isTabu(word, language) {
if (!word) return false;
return tabuWords[language].includes(word.toLowerCase());
}
// Check if word is a negation
function isNegation(word, language) {
if (!word) return false;
const upperWord = word.toUpperCase();
return negationWords[language].includes(upperWord);
}
// Apply heuristic optimizations AFTER strong rules are satisfied
function applyHeuristics(blocks, language) {
const optimizedBlocks = [];
for (let i = 0; i < blocks.length; i++) {
let block = blocks[i];
const cleanBlock = block.replace(/[.!?!,]/g, '').toUpperCase();
// Price formatting: Isolate "POR/AND/Y <number>" and "E/AND <number>"
const priceMatch = block.match(/^(POR|E|AND|Y)\s+(\d+)/i);
if (priceMatch) {
optimizedBlocks.push(priceMatch[1]); // Preposition
optimizedBlocks.push(priceMatch[2]); // Number
continue;
}
// Material lists: Keep "FEITO EM/MADE IN/HECHO EN" intact
if (language === 'PT' && /^FEITO\s+EM/i.test(block)) {
const parts = block.split(/\s+/);
if (parts.length > 2) {
optimizedBlocks.push('FEITO EM');
const materials = parts.slice(2).join(' ');
// Split materials by E connective
const materialParts = materials.split(/\s+E\s+/i);
materialParts.forEach((mat, idx) => {
if (idx > 0) optimizedBlocks.push('E');
if (mat.trim()) optimizedBlocks.push(mat.trim());
});
continue;
}
}
if (language === 'EN' && /^MADE\s+IN/i.test(block)) {
const parts = block.split(/\s+/);
if (parts.length > 2) {
optimizedBlocks.push('MADE IN');
const materials = parts.slice(2).join(' ');
// Split materials by AND connective
const materialParts = materials.split(/\s+AND\s+/i);
materialParts.forEach((mat, idx) => {
if (idx > 0) optimizedBlocks.push('AND');
if (mat.trim()) optimizedBlocks.push(mat.trim());
});
continue;
}
}
if (language === 'ES' && /^HECHO\s+EN/i.test(block)) {
const parts = block.split(/\s+/);
if (parts.length > 2) {
optimizedBlocks.push('HECHO EN');
const materials = parts.slice(2).join(' ');
// Split materials by Y connective
const materialParts = materials.split(/\s+Y\s+/i);
materialParts.forEach((mat, idx) => {
if (idx > 0) optimizedBlocks.push('Y');
if (mat.trim()) optimizedBlocks.push(mat.trim());
});
continue;
}
}
// CTA standardization
if (language === 'PT' && /CLIQUE\s+EM\s+SAIBA\s+MAIS/i.test(block)) {
optimizedBlocks.push('CLIQUE');
optimizedBlocks.push('EM SAIBA');
optimizedBlocks.push('MAIS');
continue;
}
if (language === 'EN' && /CLICK\s+HERE\s+TO\s+LEARN\s+MORE/i.test(block)) {
optimizedBlocks.push('CLICK');
optimizedBlocks.push('HERE TO');
optimizedBlocks.push('LEARN MORE');
continue;
}
if (language === 'ES' && /HAZ\s+CLIC\s+AQU脥\s+PARA\s+SABER\s+M脕S/i.test(block)) {
optimizedBlocks.push('HAZ CLIC');
optimizedBlocks.push('AQU脥 PARA');
optimizedBlocks.push('SABER M脕S');
continue;
}
// Subject optimization: Keep short subjects together (A/O/AS/OS + noun)
const subjectMatch = block.match(/^(A|O|AS|OS|THE)\s+(\w{4,})/i);
if (subjectMatch && block.replace(/\s/g, '').length <= 11) {
optimizedBlocks.push(block);
continue;
}
optimizedBlocks.push(block);
}
// Post-optimization validation to ensure no rule violations
return validateAndFixBlocks(optimizedBlocks, language);
}
// Final validation - ZERO ERROR TOLERANCE
function validateAndFixBlocks(blocks, language) {
const validatedBlocks = [];
for (let i = 0; i < blocks.length; i++) {
const block = blocks[i];
const words = block.split(/\s+/);
if (words.length === 0) continue;
const lastWord = words[words.length - 1].replace(/[.!?!,]/g, '');
// Check for tabu ending
if (isTabu(lastWord, language)) {
if (words.length > 1) {
// Move tabu word to next block or create new block
const newBlock = words.slice(0, -1).join(' ');
if (newBlock) validatedBlocks.push(newBlock);
// Check if we can append to next block
if (i + 1 < blocks.length) {
const nextBlock = blocks[i + 1];
const combined = `${lastWord} ${nextBlock}`;
const charCount = combined.replace(/\s/g, '').length;
if (charCount <= 11) {
blocks[i + 1] = combined;
continue;
}
}
validatedBlocks.push(lastWord);
} else {
validatedBlocks.push(block);
}
continue;
}
// Check for 2-3 character word ending
if (lastWord.length >= 2 && lastWord.length <= 3) {
if (words.length > 1) {
// Move short word to next block or create new block
const newBlock = words.slice(0, -1).join(' ');
if (newBlock) validatedBlocks.push(newBlock);
// Check if we can append to next block
if (i + 1 < blocks.length) {
const nextBlock = blocks[i + 1];
const combined = `${lastWord} ${nextBlock}`;
const charCount = combined.replace(/\s/g, '').length;
if (charCount <= 11) {
blocks[i + 1] = combined;
continue;
}
}
validatedBlocks.push(lastWord);
} else {
validatedBlocks.push(block);
}
continue;
}
// Check character limit
const charCount = block.replace(/\s/g, '').replace(/[.!?!,]/g, '').length;
if (charCount > 11 && words.length > 1) {
// Need to split further
const splitResult = splitLongBlock(block, language);
validatedBlocks.push(...splitResult);
} else {
validatedBlocks.push(block);
}
}
return validatedBlocks;
}
// Split blocks that exceed character limit while respecting rules
function splitLongBlock(block, language) {
const words = block.split(/\s+/);
const result = [];
let currentBlock = '';
for (const word of words) {
const cleanWord = word.replace(/[.!?!,]/g, '');
// STRONG RULE: Connectives start new blocks
if (isConnective(cleanWord, language)) {
if (currentBlock) result.push(currentBlock.trim());
currentBlock = word;
continue;
}
const testBlock = currentBlock ? `${currentBlock} ${word}` : word;
const charCount = testBlock.replace(/\s/g, '').replace(/[.!?!,]/g, '').length;
if (currentBlock && charCount > 11) {
const resolved = resolveBlockEnding(currentBlock.trim(), language);
if (resolved) result.push(resolved);
currentBlock = word;
} else {
currentBlock = testBlock;
}
}
if (currentBlock) {
const resolved = resolveBlockEnding(currentBlock.trim(), language);
if (resolved) result.push(resolved);
}
return result;
}
// Copy to clipboard
function copyToClipboard() {
const text = outputContainer.innerText;
if (!text || text.includes('Formatted text will appear here') || text.includes('Please enter some text to format')) {
return;
}
navigator.clipboard.writeText(text).then(() => {
// Show feedback
const originalText = copyBtn.innerHTML;
copyBtn.innerHTML = '<i data-feather="check" class="w-5 h-5 mr-1"></i> Copied!';
feather.replace();
setTimeout(() => {
copyBtn.innerHTML = originalText;
feather.replace();
}, 2000);
}).catch(err => {
console.error('Failed to copy:', err);
});
}
// Download text
function downloadText() {
const text = outputContainer.innerText;
if (!text || text.includes('Formatted text will appear here') || text.includes('Please enter some text to format')) {
return;
}
const blob = new Blob([text], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'formatted-text.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// Clear input
function clearInput() {
inputText.value = '';
outputContainer.innerHTML = '<p class="text-gray-500 text-center py-20">Formatted text will appear here</p>';
inputText.focus();
}
// Event Listeners
formatBtn.addEventListener('click', formatText);
copyBtn.addEventListener('click', copyToClipboard);
downloadBtn.addEventListener('click', downloadText);
clearBtn.addEventListener('click', clearInput);
// Format on Ctrl+Enter
inputText.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
formatText();
}
});
// Initialize Feather icons
feather.replace();
});