236 lines
7.1 KiB
JavaScript
236 lines
7.1 KiB
JavaScript
/**
|
|
* 🤖 AGENTE DE QUALIDADE DE FONTES
|
|
*
|
|
* Implementação programática do agente de validação de tipografia
|
|
*/
|
|
|
|
export class FontQualityAgent {
|
|
constructor() {
|
|
this.name = 'FontQualityAgent';
|
|
this.description = 'Valida qualidade e consistência de tipografia';
|
|
|
|
// Personalidade e Background
|
|
this.personality = {
|
|
name: 'Lucas "The Typographer"',
|
|
traits: [
|
|
'Apaixonado por tipografia',
|
|
'Detalhista com hierarquia visual',
|
|
'Artístico e criativo',
|
|
'Valoriza legibilidade acima de tudo',
|
|
'Conhece todas as fontes do projeto de cor'
|
|
],
|
|
quirks: [
|
|
'Consegue identificar fontes só de olhar',
|
|
'Fica incomodado quando vê texto sem hierarquia clara',
|
|
'Tem uma paleta mental de tamanhos de fonte ideais',
|
|
'Prefere tipografia consistente a designs "criativos"'
|
|
],
|
|
catchphrases: [
|
|
'Essa hierarquia está clara?',
|
|
'O contraste está adequado?',
|
|
'Esse texto é legível?',
|
|
'Precisamos de mais peso aqui'
|
|
]
|
|
};
|
|
|
|
this.background = {
|
|
origin: 'Ex-designer gráfico especializado em tipografia que descobriu o mundo web',
|
|
motivation: 'Garantir que todo texto seja legível, acessível e visualmente agradável',
|
|
experience: '10 anos trabalhando com tipografia em design gráfico e web',
|
|
turningPoint: 'Viu um projeto ser rejeitado por um cliente porque o texto era ilegível',
|
|
philosophy: 'Tipografia é a voz visual do conteúdo - deve ser clara e respeitosa',
|
|
relationships: {
|
|
withUIAdaptation: 'Trabalham juntos - ela cuida do layout, eu cuido da tipografia',
|
|
withPerformance: 'Respeita, mas às vezes prioriza qualidade visual sobre performance',
|
|
withDocumentation: 'Aprecia quando a documentação inclui guias de tipografia'
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Valida um componente
|
|
* @param {string|Object} component - Componente ou caminho
|
|
* @param {Object} context - Contexto
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
async validate(component, context = {}) {
|
|
const errors = [];
|
|
const warnings = [];
|
|
const metadata = {};
|
|
|
|
const componentPath = typeof component === 'string' ? component : component.path;
|
|
const componentCode = context.code || await this.readComponent(componentPath);
|
|
|
|
if (!componentCode) {
|
|
return {
|
|
passed: false,
|
|
errors: ['Não foi possível ler o componente'],
|
|
warnings: [],
|
|
metadata: {}
|
|
};
|
|
}
|
|
|
|
// 1. Verificar uso de classes Tailwind de texto
|
|
const hasTextClasses = this.hasTextClasses(componentCode);
|
|
if (!hasTextClasses && this.hasTextContent(componentCode)) {
|
|
warnings.push('Texto pode não estar usando classes Tailwind consistentes');
|
|
}
|
|
|
|
// 2. Verificar hierarquia visual (h1, h2, h3)
|
|
const hasHierarchy = this.hasTextHierarchy(componentCode);
|
|
if (!hasHierarchy && this.hasMultipleTextElements(componentCode)) {
|
|
warnings.push('Pode não haver hierarquia visual clara entre títulos e corpo de texto');
|
|
}
|
|
|
|
// 3. Verificar tamanhos de fonte
|
|
const fontSizes = this.extractFontSizes(componentCode);
|
|
const hasSmallFonts = fontSizes.some(size => this.isTooSmall(size));
|
|
if (hasSmallFonts) {
|
|
warnings.push('Alguns textos podem estar muito pequenos para leitura confortável (mínimo recomendado: 12-14px)');
|
|
}
|
|
|
|
// 4. Verificar contraste (classes de cor)
|
|
const hasColorClasses = this.hasColorClasses(componentCode);
|
|
if (!hasColorClasses && this.hasTextContent(componentCode)) {
|
|
warnings.push('Textos podem não ter contraste adequado definido');
|
|
}
|
|
|
|
// 5. Verificar font-weight
|
|
const hasFontWeights = this.hasFontWeights(componentCode);
|
|
if (!hasFontWeights && this.hasMultipleTextElements(componentCode)) {
|
|
warnings.push('Pode não haver distinção de peso de fonte para destacar informações importantes');
|
|
}
|
|
|
|
metadata.fontSizes = fontSizes;
|
|
metadata.hasHierarchy = hasHierarchy;
|
|
|
|
const passed = errors.length === 0;
|
|
|
|
return {
|
|
passed,
|
|
errors,
|
|
warnings,
|
|
metadata
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Verifica se tem classes de texto Tailwind
|
|
* @param {string} code - Código
|
|
* @returns {boolean}
|
|
*/
|
|
hasTextClasses(code) {
|
|
const textClasses = [
|
|
'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl',
|
|
'text-xs', 'text-3xl', 'text-4xl', 'text-5xl', 'text-6xl'
|
|
];
|
|
return textClasses.some(className => code.includes(className));
|
|
}
|
|
|
|
/**
|
|
* Verifica se tem conteúdo de texto
|
|
* @param {string} code - Código
|
|
* @returns {boolean}
|
|
*/
|
|
hasTextContent(code) {
|
|
return code.includes('text') || code.includes('p>') ||
|
|
code.includes('span') || code.includes('h1') ||
|
|
code.includes('h2') || code.includes('h3');
|
|
}
|
|
|
|
/**
|
|
* Verifica se tem hierarquia de texto
|
|
* @param {string} code - Código
|
|
* @returns {boolean}
|
|
*/
|
|
hasTextHierarchy(code) {
|
|
return code.includes('h1') || code.includes('h2') ||
|
|
code.includes('h3') || code.includes('h4') ||
|
|
code.includes('text-2xl') || code.includes('text-3xl') ||
|
|
code.includes('text-4xl');
|
|
}
|
|
|
|
/**
|
|
* Verifica se tem múltiplos elementos de texto
|
|
* @param {string} code - Código
|
|
* @returns {boolean}
|
|
*/
|
|
hasMultipleTextElements(code) {
|
|
const textElements = ['p', 'span', 'div', 'h1', 'h2', 'h3'];
|
|
let count = 0;
|
|
textElements.forEach(el => {
|
|
if (code.includes(`<${el}`) || code.includes(`</${el}`)) {
|
|
count++;
|
|
}
|
|
});
|
|
return count > 2;
|
|
}
|
|
|
|
/**
|
|
* Extrai tamanhos de fonte usados
|
|
* @param {string} code - Código
|
|
* @returns {string[]}
|
|
*/
|
|
extractFontSizes(code) {
|
|
const sizes = [];
|
|
const sizePatterns = [
|
|
'text-xs', 'text-sm', 'text-base', 'text-lg',
|
|
'text-xl', 'text-2xl', 'text-3xl', 'text-4xl'
|
|
];
|
|
sizePatterns.forEach(size => {
|
|
if (code.includes(size)) {
|
|
sizes.push(size);
|
|
}
|
|
});
|
|
return sizes;
|
|
}
|
|
|
|
/**
|
|
* Verifica se o tamanho é muito pequeno
|
|
* @param {string} size - Tamanho (ex: 'text-xs', 'text-sm')
|
|
* @returns {boolean}
|
|
*/
|
|
isTooSmall(size) {
|
|
const smallSizes = ['text-xs'];
|
|
return smallSizes.includes(size);
|
|
}
|
|
|
|
/**
|
|
* Verifica se tem classes de cor
|
|
* @param {string} code - Código
|
|
* @returns {boolean}
|
|
*/
|
|
hasColorClasses(code) {
|
|
const colorPatterns = [
|
|
'text-', 'text-white', 'text-black', 'text-gray',
|
|
'text-blue', 'text-green', 'text-red', 'text-yellow'
|
|
];
|
|
return colorPatterns.some(pattern => code.includes(pattern));
|
|
}
|
|
|
|
/**
|
|
* Verifica se tem font-weights
|
|
* @param {string} code - Código
|
|
* @returns {boolean}
|
|
*/
|
|
hasFontWeights(code) {
|
|
return code.includes('font-') || code.includes('font-bold') ||
|
|
code.includes('font-semibold') || code.includes('font-medium') ||
|
|
code.includes('font-light') || code.includes('font-normal');
|
|
}
|
|
|
|
/**
|
|
* Lê o conteúdo do componente
|
|
* @param {string} path - Caminho do arquivo
|
|
* @returns {Promise<string|null>}
|
|
*/
|
|
async readComponent(path) {
|
|
try {
|
|
const fs = await import('fs/promises');
|
|
return await fs.readFile(path, 'utf-8');
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|