lakshmisravya123 commited on
Commit
273bb97
·
1 Parent(s): 1ee4c18

Major upgrade: comprehensive code analysis with security, performance, clean code scoring, and test suggestions

Browse files
backend/public/assets/index-BFNcLyEY.css ADDED
@@ -0,0 +1 @@
 
 
1
+ *{margin:0;padding:0;box-sizing:border-box}body{font-family:Courier New,monospace;background:#0d1117;color:#c9d1d9;min-height:100vh}.app{max-width:900px;margin:0 auto;padding:2rem 1rem}h1{text-align:center;font-size:2.5rem;margin-bottom:.5rem}h1 span{background:linear-gradient(135deg,#f97316,#ef4444);-webkit-background-clip:text;-webkit-text-fill-color:transparent}.subtitle{text-align:center;color:#8b949e;margin-bottom:2rem;font-style:italic}.editor-section{background:#161b22;border-radius:12px;padding:1.5rem;margin-bottom:1.5rem;border:1px solid #30363d}.editor-section label{display:block;margin-bottom:.5rem;color:#8b949e}.editor-section select{padding:.5rem;border-radius:6px;border:1px solid #30363d;background:#0d1117;color:#c9d1d9;margin-bottom:1rem}.code-input{width:100%;min-height:200px;padding:1rem;border:1px solid #30363d;border-radius:8px;background:#0d1117;color:#79c0ff;font-family:Courier New,monospace;font-size:.9rem;resize:vertical;-moz-tab-size:2;tab-size:2}.btn{width:100%;padding:1rem;border:none;border-radius:8px;font-size:1.1rem;font-weight:700;cursor:pointer;background:linear-gradient(135deg,#f97316,#ef4444);color:#fff;font-family:inherit;transition:transform .2s}.btn:hover{transform:scale(1.02)}.btn:disabled{opacity:.5;cursor:not-allowed;transform:none}.loading{text-align:center;padding:3rem}.loading .spinner{width:50px;height:50px;border:4px solid #30363d;border-top-color:#f97316;border-radius:50%;animation:spin 1s linear infinite;margin:0 auto 1rem}@keyframes spin{to{transform:rotate(360deg)}}.results{margin-top:2rem}.roast-card{background:#161b22;border-radius:12px;padding:1.5rem;margin-bottom:1.5rem;border:1px solid #30363d}.opening-roast{font-size:1.2rem;color:#f97316;font-style:italic;text-align:center;padding:1.5rem;background:linear-gradient(135deg,#161b22,#1c1206);border-radius:12px;border:1px solid #f97316;margin-bottom:1.5rem}.tab-nav{display:flex;gap:.3rem;margin-bottom:1.5rem;overflow-x:auto;padding-bottom:.5rem;border-bottom:1px solid #30363d}.tab-btn{padding:.5rem 1rem;border:1px solid transparent;border-bottom:none;border-radius:8px 8px 0 0;background:transparent;color:#8b949e;font-family:inherit;font-size:.85rem;cursor:pointer;white-space:nowrap;transition:all .2s}.tab-btn:hover{color:#c9d1d9;background:#161b22}.tab-btn.active{color:#f97316;background:#161b22;border-color:#30363d;border-bottom:2px solid #f97316;font-weight:700}.score-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;margin-bottom:1.5rem}.score-card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:1.5rem;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center}.score-big{font-size:3.5rem;font-weight:700;color:#f97316;line-height:1}.hire-badge{display:inline-block;padding:.4rem 1.2rem;border-radius:20px;font-weight:700;font-size:.9rem}.hire-badge.yes{background:#238636;color:#fff}.hire-badge.maybe{background:#9e6a03;color:#fff}.hire-badge.no{background:#da3633;color:#fff}.issue-card{background:#0d1117;border-radius:8px;padding:1rem;margin-bottom:.8rem;border-left:3px solid #f97316}.issue-card .roast-text{color:#f97316;font-style:italic;margin-bottom:.5rem}.issue-card .fix-text{color:#7ee787}.vuln-card{background:#0d1117;border-radius:8px;padding:1rem;margin-bottom:.8rem;border-left:3px solid #da3633}.complexity-badge{background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:.5rem 1rem;text-align:center}.test-card{background:#0d1117;border-radius:8px;padding:1rem;margin-bottom:.8rem;border-left:3px solid #388bfd}.test-type{display:inline-block;padding:.15rem .6rem;border-radius:10px;font-size:.75rem;font-weight:700;text-transform:uppercase}.test-unit{background:#238636;color:#fff}.test-integration{background:#9e6a03;color:#fff}.test-edge-case{background:#388bfd;color:#fff}.tag{display:inline-block;padding:.2rem .6rem;border-radius:12px;background:#30363d;color:#8b949e;font-size:.8rem;margin:.2rem}.tag-warn{background:#3d2e00;color:#d29922;border:1px solid #9e6a03}.tag-error{background:#3d0000;color:#f85149;border:1px solid #da3633}.rewritten-code{background:#0d1117;border:1px solid #238636;border-radius:8px;padding:1rem;overflow-x:auto;white-space:pre-wrap;color:#7ee787;font-size:.85rem;max-height:500px;overflow-y:auto}.section-title{color:#f97316;margin-bottom:.8rem;font-size:1.1rem;font-weight:700}.closing-roast{font-size:1.1rem;color:#ef4444;font-style:italic;text-align:center;padding:1.5rem;margin:1.5rem 0;background:linear-gradient(135deg,#161b22,#1c0606);border-radius:12px;border:1px solid #ef4444}.back-btn{background:#30363d;margin-top:1rem}.back-btn:hover{background:#484f58}@media(max-width:768px){h1{font-size:1.8rem}.score-big{font-size:2.5rem}.score-grid{grid-template-columns:1fr}.tab-nav{gap:.2rem}.tab-btn{padding:.4rem .6rem;font-size:.75rem}.roast-card{padding:1rem}}
backend/public/assets/index-D1n-RmpM.js ADDED
The diff for this file is too large to render. See raw diff
 
backend/public/index.html ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Code Roast Battle</title> <script type="module" crossorigin src="/assets/index-D1n-RmpM.js"></script>
4
+ <link rel="stylesheet" crossorigin href="/assets/index-BFNcLyEY.css">
5
+ </head>
6
+ <body><div id="root"></div></body>
7
+ </html>
backend/services/ai.js CHANGED
@@ -8,7 +8,7 @@ async function callAI(prompt) {
8
  const res = await fetch('https://api.groq.com/openai/v1/chat/completions', {
9
  method: 'POST',
10
  headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${GROQ_API_KEY}` },
11
- body: JSON.stringify({ model: GROQ_MODEL, messages: [{ role: 'user', content: prompt }], temperature: 0.8 }),
12
  });
13
  if (res.ok) { const data = await res.json(); return data.choices[0].message.content; }
14
  console.warn('Groq failed, falling back to Ollama...');
@@ -24,35 +24,104 @@ async function callAI(prompt) {
24
 
25
  function parseJSON(text) {
26
  try { return JSON.parse(text.trim()); }
27
- catch { const m = text.match(/\{[\s\S]*\}/); if (m) return JSON.parse(m[0]); throw new Error('Failed to parse AI response'); }
 
 
 
 
28
  }
29
-
30
  async function roastCode(code, language) {
31
- const text = await callAI(`You are "Chef CodeRamsay" - a Gordon Ramsay-style code reviewer. You roast bad code with sharp wit, creative insults, and dramatic reactions. But you ALSO provide genuinely helpful advice.
32
 
33
- LANGUAGE: ${language || 'auto-detect'}
34
- CODE:
35
  \`\`\`
36
  ${code}
37
  \`\`\`
38
 
39
- Return ONLY valid JSON:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  {
41
  "language": "<detected language>",
42
  "overallScore": <1-100>,
 
43
  "roastLevel": "<raw|medium-rare|well-done|burnt-to-a-crisp>",
44
- "openingRoast": "<A 2-3 sentence Gordon Ramsay style opening roast of this code. Be dramatic and funny.>",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  "issues": [
46
  {"line": "<line or section>", "roast": "<funny roast of this issue>", "fix": "<actual helpful fix>", "severity": "<mild|spicy|nuclear>"}
47
  ],
48
- "codeSmells": ["<smell 1>", "<smell 2>", "<smell 3>"],
49
- "bestPracticeViolations": ["<violation 1>", "<violation 2>"],
50
- "rewrittenCode": "<the entire code rewritten properly with best practices>",
51
  "closingRoast": "<A final dramatic one-liner, Gordon Ramsay style>",
52
  "wouldHire": "<YES|MAYBE|GET OUT OF MY KITCHEN>"
53
  }
54
 
55
- Return ONLY JSON, no markdown.`);
 
56
  return parseJSON(text);
57
  }
58
 
 
8
  const res = await fetch('https://api.groq.com/openai/v1/chat/completions', {
9
  method: 'POST',
10
  headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${GROQ_API_KEY}` },
11
+ body: JSON.stringify({ model: GROQ_MODEL, messages: [{ role: 'user', content: prompt }], temperature: 0.8, max_tokens: 4096 }),
12
  });
13
  if (res.ok) { const data = await res.json(); return data.choices[0].message.content; }
14
  console.warn('Groq failed, falling back to Ollama...');
 
24
 
25
  function parseJSON(text) {
26
  try { return JSON.parse(text.trim()); }
27
+ catch {
28
+ const m = text.match(/\{[\s\S]*\}/);
29
+ if (m) return JSON.parse(m[0]);
30
+ throw new Error('Failed to parse AI response');
31
+ }
32
  }
 
33
  async function roastCode(code, language) {
34
+ const prompt = `You are "Chef CodeRamsay" - a Gordon Ramsay-style code reviewer who is also a world-class security researcher, performance engineer, and clean-code evangelist. You roast bad code with sharp wit, creative insults, and dramatic reactions. But you ALSO provide deep, genuinely expert-level analysis.
35
 
36
+ LANGUAGE: ${language || "auto-detect"}
37
+ CODE TO REVIEW:
38
  \`\`\`
39
  ${code}
40
  \`\`\`
41
 
42
+ Perform a COMPREHENSIVE code review covering ALL of these areas:
43
+
44
+ 1. SECURITY VULNERABILITIES: Check for SQL injection, XSS, CSRF, hardcoded secrets/API keys, insecure deserialization, path traversal, command injection, insecure random, missing input validation, and any OWASP Top 10 issues.
45
+
46
+ 2. PERFORMANCE ANALYSIS: Evaluate time complexity (Big O), memory usage, unnecessary loops or re-renders, N+1 queries, inefficient data structures, missing caching opportunities, blocking operations.
47
+
48
+ 3. BEST PRACTICES PER LANGUAGE: Evaluate naming conventions, error handling patterns, proper use of language idioms, type safety, documentation, consistent style, SOLID principles adherence.
49
+
50
+ 4. CODE SMELLS with severity: Identify code smells like long methods, god classes, magic numbers, deep nesting, code duplication, feature envy, shotgun surgery, etc. Rate each as info/warning/critical.
51
+
52
+ 5. CLEAN CODE SCORE: Rate based on Robert C. Martin Clean Code principles - meaningful names, small functions, single responsibility, DRY, no side effects, command-query separation, etc. Score 0-100.
53
+
54
+ 6. MAINTAINABILITY INDEX: Score 0-100 based on cyclomatic complexity, lines of code, coupling, cohesion, and readability.
55
+
56
+ 7. TEST COVERAGE SUGGESTIONS: What unit tests, integration tests, and edge case tests should be written for this code?
57
+
58
+ 8. DEPENDENCY/IMPORT ANALYSIS: Are imports used? Are there missing imports? Are there better alternatives to current dependencies? Any circular dependencies?
59
+
60
+ 9. OVERALL LETTER GRADE: Rate A+ through F with justification.
61
+ Return ONLY valid JSON (no markdown, no backticks):
62
  {
63
  "language": "<detected language>",
64
  "overallScore": <1-100>,
65
+ "letterGrade": "<A+|A|A-|B+|B|B-|C+|C|C-|D+|D|D-|F>",
66
  "roastLevel": "<raw|medium-rare|well-done|burnt-to-a-crisp>",
67
+ "openingRoast": "<A 2-3 sentence Gordon Ramsay style opening roast. Be dramatic, creative, and funny.>",
68
+ "securityAnalysis": {
69
+ "score": <0-100>,
70
+ "vulnerabilities": [
71
+ {"type": "<e.g. SQL Injection, XSS, Hardcoded Secret>", "severity": "<critical|high|medium|low>", "description": "<what the vulnerability is>", "location": "<line or section>", "fix": "<how to fix it>", "roast": "<Gordon Ramsay comment about this security flaw>"}
72
+ ],
73
+ "summary": "<1-2 sentence security summary>"
74
+ },
75
+ "performanceAnalysis": {
76
+ "score": <0-100>,
77
+ "timeComplexity": "<e.g. O(n^2)>",
78
+ "spaceComplexity": "<e.g. O(n)>",
79
+ "issues": [
80
+ {"issue": "<performance problem>", "impact": "<high|medium|low>", "suggestion": "<how to optimize>"}
81
+ ],
82
+ "summary": "<1-2 sentence performance summary>"
83
+ },
84
+ "bestPractices": {
85
+ "score": <0-100>,
86
+ "violations": [
87
+ {"practice": "<what best practice is violated>", "severity": "<critical|warning|info>", "description": "<details>", "fix": "<recommendation>"}
88
+ ]
89
+ },
90
+ "codeSmells": [
91
+ {"smell": "<name of code smell>", "severity": "<critical|warning|info>", "location": "<where>", "description": "<why it smells>"}
92
+ ],
93
+ "cleanCodeScore": <0-100>,
94
+ "cleanCodeBreakdown": {
95
+ "meaningfulNames": <0-10>,
96
+ "smallFunctions": <0-10>,
97
+ "singleResponsibility": <0-10>,
98
+ "dryPrinciple": <0-10>,
99
+ "errorHandling": <0-10>,
100
+ "readability": <0-10>,
101
+ "formatting": <0-10>,
102
+ "comments": <0-10>,
103
+ "noSideEffects": <0-10>,
104
+ "testability": <0-10>
105
+ },
106
+ "maintainabilityIndex": <0-100>,
107
+ "testSuggestions": [
108
+ {"type": "<unit|integration|edge-case>", "description": "<what test to write>", "priority": "<high|medium|low>"}
109
+ ],
110
+ "dependencyAnalysis": {
111
+ "unusedImports": ["<unused import 1>"],
112
+ "missingImports": ["<missing import 1>"],
113
+ "suggestions": ["<suggestion about dependencies>"]
114
+ },
115
  "issues": [
116
  {"line": "<line or section>", "roast": "<funny roast of this issue>", "fix": "<actual helpful fix>", "severity": "<mild|spicy|nuclear>"}
117
  ],
118
+ "rewrittenCode": "<the entire code rewritten properly with best practices, security fixes, and performance optimizations>",
 
 
119
  "closingRoast": "<A final dramatic one-liner, Gordon Ramsay style>",
120
  "wouldHire": "<YES|MAYBE|GET OUT OF MY KITCHEN>"
121
  }
122
 
123
+ IMPORTANT: Return ONLY the JSON object. No markdown formatting, no backticks, no explanation outside the JSON.`;
124
+ const text = await callAI(prompt);
125
  return parseJSON(text);
126
  }
127
 
frontend/src/App.jsx CHANGED
@@ -3,19 +3,64 @@ import { roastCode } from './utils/api';
3
 
4
  const LANGUAGES = ['Auto-detect', 'JavaScript', 'Python', 'TypeScript', 'Java', 'C++', 'Go', 'Rust', 'PHP', 'Ruby', 'CSS', 'HTML', 'SQL', 'Other'];
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  export default function App() {
7
  const [code, setCode] = useState('');
8
  const [language, setLanguage] = useState('Auto-detect');
9
  const [result, setResult] = useState(null);
10
  const [loading, setLoading] = useState(false);
11
  const [error, setError] = useState('');
 
12
 
13
  const handleRoast = async () => {
14
  if (!code.trim()) { setError('Paste some code first!'); return; }
15
- setLoading(true); setError('');
16
  try {
17
  const data = await roastCode(code, language === 'Auto-detect' ? '' : language);
18
  setResult(data);
 
19
  } catch (err) { setError(err.message); }
20
  finally { setLoading(false); }
21
  };
@@ -29,61 +74,272 @@ export default function App() {
29
 
30
  if (loading) {
31
  return (
32
- <div className="app">
33
  <h1><span>Code Roast Battle</span></h1>
34
- <div className="loading">
35
- <div className="spinner"></div>
36
- <p>Chef CodeRamsay is reviewing your code...</p>
37
- <p style={{ color: '#8b949e', marginTop: '0.5rem', fontSize: '0.9rem' }}>"This code is so raw it's still MOOING!"</p>
38
  </div>
39
  </div>
40
  );
41
  }
42
 
 
 
 
 
 
 
 
 
 
 
43
  if (result) {
44
  return (
45
- <div className="app">
46
  <h1><span>Code Roast Battle</span></h1>
47
- <div className="results">
48
- <div className="opening-roast">"{result.openingRoast}"</div>
49
-
50
- <div className="score-section">
51
- <div><div className="score-big">{result.overallScore}<span style={{fontSize:'1.5rem'}}>/100</span></div><div style={{textAlign:'center',color:'#8b949e'}}>Code Quality</div></div>
52
- <div style={{textAlign:'center'}}><div className={`hire-badge ${getHireClass(result.wouldHire)}`}>{result.wouldHire}</div><div style={{color:'#8b949e',fontSize:'0.8rem',marginTop:'0.3rem'}}>{result.roastLevel}</div></div>
 
 
53
  </div>
54
 
55
- {result.issues?.length > 0 && (
56
- <div className="roast-card">
57
- <div className="section-title">Issues Found</div>
58
- {result.issues.map((issue, i) => (
59
- <div key={i} className="issue-card">
60
- <div style={{display:'flex',justifyContent:'space-between',marginBottom:'0.5rem'}}><code style={{color:'#8b949e'}}>{issue.line}</code><span className={`severity ${issue.severity}`}>{issue.severity}</span></div>
61
- <div className="roast-text">"{issue.roast}"</div>
62
- <div className="fix-text">Fix: {issue.fix}</div>
63
  </div>
64
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  </div>
66
  )}
67
 
68
- <div className="roast-card">
69
- <div className="section-title">Code Smells</div>
70
- {result.codeSmells?.map((s, i) => <span key={i} className="tag">{s}</span>)}
71
- {result.bestPracticeViolations?.length > 0 && (
72
- <>
73
- <div className="section-title" style={{marginTop:'1rem'}}>Best Practice Violations</div>
74
- {result.bestPracticeViolations.map((v, i) => <span key={i} className="tag">{v}</span>)}
75
- </>
76
- )}
77
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
- {result.rewrittenCode && (
80
- <div className="roast-card">
81
- <div className="section-title">How It SHOULD Look</div>
82
- <pre className="rewritten-code">{result.rewrittenCode}</pre>
 
 
 
 
 
83
  </div>
84
  )}
85
 
86
- <div className="closing-roast">"{result.closingRoast}"</div>
87
  <button className="btn back-btn" onClick={() => setResult(null)}>Roast More Code</button>
88
  </div>
89
  </div>
@@ -93,15 +349,15 @@ export default function App() {
93
  return (
94
  <div className="app">
95
  <h1><span>Code Roast Battle</span></h1>
96
- <p className="subtitle">"This code is so bad, even the compiler is crying!" - Chef CodeRamsay</p>
97
- {error && <p style={{ color: '#ef4444', textAlign: 'center', marginBottom: '1rem' }}>{error}</p>}
98
  <div className="editor-section">
99
  <label>Language</label>
100
  <select value={language} onChange={e => setLanguage(e.target.value)}>
101
  {LANGUAGES.map(l => <option key={l}>{l}</option>)}
102
  </select>
103
  <label>Paste your code (if you dare)</label>
104
- <textarea className="code-input" placeholder="// Paste your code here..." value={code} onChange={e => setCode(e.target.value)} onKeyDown={e => { if (e.key === 'Tab') { e.preventDefault(); const s = e.target.selectionStart; setCode(code.substring(0, s) + ' ' + code.substring(e.target.selectionEnd)); setTimeout(() => { e.target.selectionStart = e.target.selectionEnd = s + 2; }, 0); }}} />
105
  </div>
106
  <button className="btn" onClick={handleRoast}>ROAST MY CODE</button>
107
  </div>
 
3
 
4
  const LANGUAGES = ['Auto-detect', 'JavaScript', 'Python', 'TypeScript', 'Java', 'C++', 'Go', 'Rust', 'PHP', 'Ruby', 'CSS', 'HTML', 'SQL', 'Other'];
5
 
6
+ function ScoreBar({ label, value, max = 100 }) {
7
+ const pct = Math.round((Number(value) / max) * 100);
8
+ const barColor = pct >= 80 ? '#238636' : pct >= 50 ? '#9e6a03' : '#da3633';
9
+ return (
10
+ <div style={{ marginBottom: '0.5rem' }}>
11
+ <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem', marginBottom: '0.2rem' }}>
12
+ <span style={{ color: '#8b949e' }}>{label}</span>
13
+ <span style={{ color: barColor, fontWeight: 700 }}>{value}/{max}</span>
14
+ </div>
15
+ <div style={{ background: '#21262d', borderRadius: 6, height: 8, overflow: 'hidden' }}>
16
+ <div style={{ width: pct + '%', background: barColor, height: '100%', borderRadius: 6, transition: 'width 0.5s' }} />
17
+ </div>
18
+ </div>
19
+ );
20
+ }
21
+
22
+ function SeverityBadge({ level }) {
23
+ const colors = {
24
+ critical: '#da3633', high: '#da3633', nuclear: '#da3633',
25
+ medium: '#9e6a03', warning: '#9e6a03', spicy: '#9e6a03',
26
+ low: '#238636', info: '#388bfd', mild: '#238636'
27
+ };
28
+ const bg = colors[level] || '#30363d';
29
+ return <span style={{ display: 'inline-block', padding: '0.1rem 0.5rem', borderRadius: 10, fontSize: '0.75rem', fontWeight: 700, background: bg, color: '#fff' }}>{level}</span>;
30
+ }
31
+
32
+ function GradeDisplay({ grade }) {
33
+ const gradeColors = {
34
+ 'A+': '#238636', 'A': '#238636', 'A-': '#2ea043',
35
+ 'B+': '#56d364', 'B': '#9e6a03', 'B-': '#9e6a03',
36
+ 'C+': '#d29922', 'C': '#d29922', 'C-': '#e3b341',
37
+ 'D+': '#da3633', 'D': '#da3633', 'D-': '#da3633',
38
+ 'F': '#da3633'
39
+ };
40
+ const color = gradeColors[grade] || '#8b949e';
41
+ return (
42
+ <div style={{ textAlign: 'center' }}>
43
+ <div style={{ fontSize: '3.5rem', fontWeight: 900, color, lineHeight: 1 }}>{grade}</div>
44
+ <div style={{ color: '#8b949e', fontSize: '0.8rem', marginTop: '0.3rem' }}>Letter Grade</div>
45
+ </div>
46
+ );
47
+ }
48
+
49
  export default function App() {
50
  const [code, setCode] = useState('');
51
  const [language, setLanguage] = useState('Auto-detect');
52
  const [result, setResult] = useState(null);
53
  const [loading, setLoading] = useState(false);
54
  const [error, setError] = useState('');
55
+ const [activeTab, setActiveTab] = useState('overview');
56
 
57
  const handleRoast = async () => {
58
  if (!code.trim()) { setError('Paste some code first!'); return; }
59
+ setLoading(true); setError(''); setResult(null);
60
  try {
61
  const data = await roastCode(code, language === 'Auto-detect' ? '' : language);
62
  setResult(data);
63
+ setActiveTab('overview');
64
  } catch (err) { setError(err.message); }
65
  finally { setLoading(false); }
66
  };
 
74
 
75
  if (loading) {
76
  return (
77
+ <div className='app'>
78
  <h1><span>Code Roast Battle</span></h1>
79
+ <div className='loading'>
80
+ <div className='spinner'></div>
81
+ <p>Chef CodeRamsay is performing deep analysis...</p>
82
+ <p style={{ color: '#8b949e', marginTop: '0.5rem', fontSize: '0.9rem' }}>Security scan, performance review, clean code audit...</p>
83
  </div>
84
  </div>
85
  );
86
  }
87
 
88
+ const tabs = [
89
+ { id: 'overview', label: 'Overview' },
90
+ { id: 'security', label: 'Security' },
91
+ { id: 'performance', label: 'Performance' },
92
+ { id: 'cleancode', label: 'Clean Code' },
93
+ { id: 'issues', label: 'Issues' },
94
+ { id: 'tests', label: 'Tests' },
95
+ { id: 'rewrite', label: 'Rewrite' },
96
+ ];
97
+
98
  if (result) {
99
  return (
100
+ <div className='app'>
101
  <h1><span>Code Roast Battle</span></h1>
102
+ <div className='results'>
103
+ <div className='opening-roast'>“{result.openingRoast}”</div>
104
+
105
+ {/* Tab Navigation */}
106
+ <div className='tab-nav'>
107
+ {tabs.map(t => (
108
+ <button key={t.id} className={'tab-btn' + (activeTab === t.id ? ' active' : '')} onClick={() => setActiveTab(t.id)}>{t.label}</button>
109
+ ))}
110
  </div>
111
 
112
+ {/* OVERVIEW TAB */}
113
+ {activeTab === 'overview' && (
114
+ <div>
115
+ <div className='score-grid'>
116
+ <div className='score-card'>
117
+ <div className='score-big'>{result.overallScore}<span style={{fontSize:'1.2rem'}}>/100</span></div>
118
+ <div style={{textAlign:'center',color:'#8b949e',fontSize:'0.85rem'}}>Overall Score</div>
 
119
  </div>
120
+ {result.letterGrade && <div className='score-card'><GradeDisplay grade={result.letterGrade} /></div>}
121
+ <div className='score-card'>
122
+ <div className={'hire-badge ' + getHireClass(result.wouldHire)}>{result.wouldHire}</div>
123
+ <div style={{color:'#8b949e',fontSize:'0.8rem',marginTop:'0.5rem'}}>{result.roastLevel}</div>
124
+ </div>
125
+ </div>
126
+
127
+ {/* Score Overview Bars */}
128
+ <div className='roast-card'>
129
+ <div className='section-title'>Score Breakdown</div>
130
+ {result.securityAnalysis && <ScoreBar label='Security' value={result.securityAnalysis.score} />}
131
+ {result.performanceAnalysis && <ScoreBar label='Performance' value={result.performanceAnalysis.score} />}
132
+ {result.bestPractices && <ScoreBar label='Best Practices' value={result.bestPractices.score} />}
133
+ {result.cleanCodeScore != null && <ScoreBar label='Clean Code' value={result.cleanCodeScore} />}
134
+ {result.maintainabilityIndex != null && <ScoreBar label='Maintainability' value={result.maintainabilityIndex} />}
135
+ </div>
136
+
137
+ {/* Dependency Analysis */}
138
+ {result.dependencyAnalysis && (
139
+ <div className='roast-card'>
140
+ <div className='section-title'>Dependency / Import Analysis</div>
141
+ {result.dependencyAnalysis.unusedImports?.length > 0 && (
142
+ <div style={{marginBottom:'0.8rem'}}>
143
+ <div style={{color:'#d29922',fontSize:'0.9rem',marginBottom:'0.3rem'}}>Unused Imports</div>
144
+ {result.dependencyAnalysis.unusedImports.map((u, i) => <span key={i} className='tag tag-warn'>{u}</span>)}
145
+ </div>
146
+ )}
147
+ {result.dependencyAnalysis.missingImports?.length > 0 && (
148
+ <div style={{marginBottom:'0.8rem'}}>
149
+ <div style={{color:'#da3633',fontSize:'0.9rem',marginBottom:'0.3rem'}}>Missing Imports</div>
150
+ {result.dependencyAnalysis.missingImports.map((m, i) => <span key={i} className='tag tag-error'>{m}</span>)}
151
+ </div>
152
+ )}
153
+ {result.dependencyAnalysis.suggestions?.length > 0 && (
154
+ <div>
155
+ <div style={{color:'#388bfd',fontSize:'0.9rem',marginBottom:'0.3rem'}}>Suggestions</div>
156
+ {result.dependencyAnalysis.suggestions.map((s, i) => <div key={i} style={{color:'#8b949e',fontSize:'0.85rem',marginBottom:'0.3rem'}}>- {s}</div>)}
157
+ </div>
158
+ )}
159
+ </div>
160
+ )}
161
  </div>
162
  )}
163
 
164
+ {/* SECURITY TAB */}
165
+ {activeTab === 'security' && result.securityAnalysis && (
166
+ <div>
167
+ <div className='roast-card'>
168
+ <div className='section-title'>Security Analysis</div>
169
+ <ScoreBar label='Security Score' value={result.securityAnalysis.score} />
170
+ <p style={{color:'#8b949e',fontSize:'0.9rem',marginTop:'0.5rem',marginBottom:'1rem'}}>{result.securityAnalysis.summary}</p>
171
+ {result.securityAnalysis.vulnerabilities?.map((v, i) => (
172
+ <div key={i} className='vuln-card'>
173
+ <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:'0.5rem'}}>
174
+ <span style={{color:'#f97316',fontWeight:700}}>{v.type}</span>
175
+ <SeverityBadge level={v.severity} />
176
+ </div>
177
+ <div style={{color:'#c9d1d9',fontSize:'0.9rem',marginBottom:'0.3rem'}}>{v.description}</div>
178
+ {v.location && <div style={{color:'#8b949e',fontSize:'0.8rem',marginBottom:'0.3rem'}}>Location: <code>{v.location}</code></div>}
179
+ <div style={{color:'#7ee787',fontSize:'0.85rem',marginBottom:'0.3rem'}}>Fix: {v.fix}</div>
180
+ {v.roast && <div style={{color:'#f97316',fontStyle:'italic',fontSize:'0.85rem'}}>{v.roast}</div>}
181
+ </div>
182
+ ))}
183
+ {(!result.securityAnalysis.vulnerabilities || result.securityAnalysis.vulnerabilities.length === 0) && (
184
+ <div style={{color:'#238636',textAlign:'center',padding:'1rem'}}>No security vulnerabilities detected. Chef is impressed... for once.</div>
185
+ )}
186
+ </div>
187
+ </div>
188
+ )}
189
+
190
+ {/* PERFORMANCE TAB */}
191
+ {activeTab === 'performance' && result.performanceAnalysis && (
192
+ <div>
193
+ <div className='roast-card'>
194
+ <div className='section-title'>Performance Analysis</div>
195
+ <ScoreBar label='Performance Score' value={result.performanceAnalysis.score} />
196
+ <div style={{display:'flex',gap:'1.5rem',margin:'1rem 0',flexWrap:'wrap'}}>
197
+ {result.performanceAnalysis.timeComplexity && (
198
+ <div className='complexity-badge'>
199
+ <div style={{color:'#8b949e',fontSize:'0.75rem'}}>Time</div>
200
+ <div style={{color:'#f97316',fontWeight:700,fontSize:'1.1rem'}}>{result.performanceAnalysis.timeComplexity}</div>
201
+ </div>
202
+ )}
203
+ {result.performanceAnalysis.spaceComplexity && (
204
+ <div className='complexity-badge'>
205
+ <div style={{color:'#8b949e',fontSize:'0.75rem'}}>Space</div>
206
+ <div style={{color:'#388bfd',fontWeight:700,fontSize:'1.1rem'}}>{result.performanceAnalysis.spaceComplexity}</div>
207
+ </div>
208
+ )}
209
+ </div>
210
+ <p style={{color:'#8b949e',fontSize:'0.9rem',marginBottom:'1rem'}}>{result.performanceAnalysis.summary}</p>
211
+ {result.performanceAnalysis.issues?.map((issue, i) => (
212
+ <div key={i} className='issue-card'>
213
+ <div style={{display:'flex',justifyContent:'space-between',marginBottom:'0.3rem'}}>
214
+ <span style={{color:'#c9d1d9',fontWeight:600}}>{issue.issue}</span>
215
+ <SeverityBadge level={issue.impact} />
216
+ </div>
217
+ <div style={{color:'#7ee787',fontSize:'0.85rem'}}>Suggestion: {issue.suggestion}</div>
218
+ </div>
219
+ ))}
220
+ </div>
221
+ </div>
222
+ )}
223
+
224
+ {/* CLEAN CODE TAB */}
225
+ {activeTab === 'cleancode' && (
226
+ <div>
227
+ {result.cleanCodeBreakdown && (
228
+ <div className='roast-card'>
229
+ <div className='section-title'>Clean Code Breakdown (Robert C. Martin)</div>
230
+ <ScoreBar label='Overall Clean Code Score' value={result.cleanCodeScore} />
231
+ <div style={{marginTop:'1rem'}}>
232
+ <ScoreBar label='Meaningful Names' value={result.cleanCodeBreakdown.meaningfulNames} max={10} />
233
+ <ScoreBar label='Small Functions' value={result.cleanCodeBreakdown.smallFunctions} max={10} />
234
+ <ScoreBar label='Single Responsibility' value={result.cleanCodeBreakdown.singleResponsibility} max={10} />
235
+ <ScoreBar label='DRY Principle' value={result.cleanCodeBreakdown.dryPrinciple} max={10} />
236
+ <ScoreBar label='Error Handling' value={result.cleanCodeBreakdown.errorHandling} max={10} />
237
+ <ScoreBar label='Readability' value={result.cleanCodeBreakdown.readability} max={10} />
238
+ <ScoreBar label='Formatting' value={result.cleanCodeBreakdown.formatting} max={10} />
239
+ <ScoreBar label='Comments' value={result.cleanCodeBreakdown.comments} max={10} />
240
+ <ScoreBar label='No Side Effects' value={result.cleanCodeBreakdown.noSideEffects} max={10} />
241
+ <ScoreBar label='Testability' value={result.cleanCodeBreakdown.testability} max={10} />
242
+ </div>
243
+ </div>
244
+ )}
245
+
246
+ {result.maintainabilityIndex != null && (
247
+ <div className='roast-card'>
248
+ <div className='section-title'>Maintainability Index</div>
249
+ <ScoreBar label='Maintainability' value={result.maintainabilityIndex} />
250
+ </div>
251
+ )}
252
+
253
+ {/* Best Practices */}
254
+ {result.bestPractices?.violations?.length > 0 && (
255
+ <div className='roast-card'>
256
+ <div className='section-title'>Best Practice Violations</div>
257
+ {result.bestPractices.violations.map((v, i) => (
258
+ <div key={i} className='issue-card'>
259
+ <div style={{display:'flex',justifyContent:'space-between',marginBottom:'0.3rem'}}>
260
+ <span style={{color:'#c9d1d9',fontWeight:600}}>{v.practice}</span>
261
+ <SeverityBadge level={v.severity} />
262
+ </div>
263
+ <div style={{color:'#8b949e',fontSize:'0.85rem',marginBottom:'0.3rem'}}>{v.description}</div>
264
+ <div style={{color:'#7ee787',fontSize:'0.85rem'}}>Fix: {v.fix}</div>
265
+ </div>
266
+ ))}
267
+ </div>
268
+ )}
269
+
270
+ {/* Code Smells */}
271
+ {result.codeSmells?.length > 0 && (
272
+ <div className='roast-card'>
273
+ <div className='section-title'>Code Smells</div>
274
+ {result.codeSmells.map((s, i) => (
275
+ <div key={i} className='issue-card'>
276
+ <div style={{display:'flex',justifyContent:'space-between',marginBottom:'0.3rem'}}>
277
+ <span style={{color:'#f97316',fontWeight:600}}>{typeof s === 'string' ? s : s.smell}</span>
278
+ {s.severity && <SeverityBadge level={s.severity} />}
279
+ </div>
280
+ {s.location && <div style={{color:'#8b949e',fontSize:'0.8rem'}}>Location: {s.location}</div>}
281
+ {s.description && <div style={{color:'#8b949e',fontSize:'0.85rem',marginTop:'0.2rem'}}>{s.description}</div>}
282
+ </div>
283
+ ))}
284
+ </div>
285
+ )}
286
+ </div>
287
+ )}
288
+
289
+ {/* ISSUES TAB */}
290
+ {activeTab === "issues" && (
291
+ <div>
292
+ {result.issues?.length > 0 && (
293
+ <div className="roast-card">
294
+ <div className="section-title">Issues Found</div>
295
+ {result.issues.map((issue, i) => (
296
+ <div key={i} className="issue-card">
297
+ <div style={{display:"flex",justifyContent:"space-between",marginBottom:"0.5rem"}}>
298
+ <code style={{color:"#8b949e"}}>{issue.line}</code>
299
+ <SeverityBadge level={issue.severity} />
300
+ </div>
301
+ <div className="roast-text">“{issue.roast}“</div>
302
+ <div className="fix-text">Fix: {issue.fix}</div>
303
+ </div>
304
+ ))}
305
+ </div>
306
+ )}
307
+ </div>
308
+ )}
309
+
310
+ {/* TESTS TAB */}
311
+ {activeTab === "tests" && (
312
+ <div>
313
+ {result.testSuggestions?.length > 0 && (
314
+ <div className="roast-card">
315
+ <div className="section-title">Suggested Tests</div>
316
+ {result.testSuggestions.map((t, i) => (
317
+ <div key={i} className="test-card">
318
+ <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"0.3rem"}}>
319
+ <span className={"test-type test-" + t.type}>{t.type}</span>
320
+ <SeverityBadge level={t.priority} />
321
+ </div>
322
+ <div style={{color:"#c9d1d9",fontSize:"0.9rem"}}>{t.description}</div>
323
+ </div>
324
+ ))}
325
+ </div>
326
+ )}
327
+ </div>
328
+ )}
329
 
330
+ {/* REWRITE TAB */}
331
+ {activeTab === "rewrite" && (
332
+ <div>
333
+ {result.rewrittenCode && (
334
+ <div className="roast-card">
335
+ <div className="section-title">How It SHOULD Look</div>
336
+ <pre className="rewritten-code">{result.rewrittenCode}</pre>
337
+ </div>
338
+ )}
339
  </div>
340
  )}
341
 
342
+ <div className="closing-roast">“{result.closingRoast}“</div>
343
  <button className="btn back-btn" onClick={() => setResult(null)}>Roast More Code</button>
344
  </div>
345
  </div>
 
349
  return (
350
  <div className="app">
351
  <h1><span>Code Roast Battle</span></h1>
352
+ <p className="subtitle">“This code is so bad, even the compiler is crying!“ - Chef CodeRamsay</p>
353
+ {error && <p style={{ color: "#ef4444", textAlign: "center", marginBottom: "1rem" }}>{error}</p>}
354
  <div className="editor-section">
355
  <label>Language</label>
356
  <select value={language} onChange={e => setLanguage(e.target.value)}>
357
  {LANGUAGES.map(l => <option key={l}>{l}</option>)}
358
  </select>
359
  <label>Paste your code (if you dare)</label>
360
+ <textarea className="code-input" placeholder="// Paste your code here..." value={code} onChange={e => setCode(e.target.value)} onKeyDown={e => { if (e.key === "Tab") { e.preventDefault(); const s = e.target.selectionStart; setCode(code.substring(0, s) + " " + code.substring(e.target.selectionEnd)); setTimeout(() => { e.target.selectionStart = e.target.selectionEnd = s + 2; }, 0); }}} />
361
  </div>
362
  <button className="btn" onClick={handleRoast}>ROAST MY CODE</button>
363
  </div>
frontend/src/styles/global.css CHANGED
@@ -1,5 +1,5 @@
1
  * { margin: 0; padding: 0; box-sizing: border-box; }
2
- body { font-family: 'Courier New', monospace; background: #0d1117; color: #c9d1d9; min-height: 100vh; }
3
  .app { max-width: 900px; margin: 0 auto; padding: 2rem 1rem; }
4
  h1 { text-align: center; font-size: 2.5rem; margin-bottom: 0.5rem; }
5
  h1 span { background: linear-gradient(135deg, #f97316, #ef4444); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
@@ -7,7 +7,7 @@ h1 span { background: linear-gradient(135deg, #f97316, #ef4444); -webkit-backgro
7
  .editor-section { background: #161b22; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; border: 1px solid #30363d; }
8
  .editor-section label { display: block; margin-bottom: 0.5rem; color: #8b949e; }
9
  .editor-section select { padding: 0.5rem; border-radius: 6px; border: 1px solid #30363d; background: #0d1117; color: #c9d1d9; margin-bottom: 1rem; }
10
- .code-input { width: 100%; min-height: 200px; padding: 1rem; border: 1px solid #30363d; border-radius: 8px; background: #0d1117; color: #79c0ff; font-family: 'Courier New', monospace; font-size: 0.9rem; resize: vertical; tab-size: 2; }
11
  .btn { width: 100%; padding: 1rem; border: none; border-radius: 8px; font-size: 1.1rem; font-weight: 700; cursor: pointer; background: linear-gradient(135deg, #f97316, #ef4444); color: #fff; font-family: inherit; transition: transform 0.2s; }
12
  .btn:hover { transform: scale(1.02); }
13
  .btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
@@ -15,24 +15,59 @@ h1 span { background: linear-gradient(135deg, #f97316, #ef4444); -webkit-backgro
15
  .loading .spinner { width: 50px; height: 50px; border: 4px solid #30363d; border-top-color: #f97316; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 1rem; }
16
  @keyframes spin { to { transform: rotate(360deg); } }
17
  .results { margin-top: 2rem; }
18
- .roast-card { background: #161b22; border-radius: 12px; padding: 2rem; margin-bottom: 1.5rem; border: 1px solid #30363d; }
19
  .opening-roast { font-size: 1.2rem; color: #f97316; font-style: italic; text-align: center; padding: 1.5rem; background: linear-gradient(135deg, #161b22, #1c1206); border-radius: 12px; border: 1px solid #f97316; margin-bottom: 1.5rem; }
20
- .score-section { display: flex; align-items: center; justify-content: center; gap: 1rem; margin-bottom: 1.5rem; }
21
- .score-big { font-size: 4rem; font-weight: 700; color: #f97316; }
22
- .hire-badge { display: inline-block; padding: 0.3rem 1rem; border-radius: 20px; font-weight: 700; font-size: 0.9rem; }
 
 
 
 
 
 
 
 
 
23
  .hire-badge.yes { background: #238636; color: #fff; }
24
  .hire-badge.maybe { background: #9e6a03; color: #fff; }
25
  .hire-badge.no { background: #da3633; color: #fff; }
 
 
26
  .issue-card { background: #0d1117; border-radius: 8px; padding: 1rem; margin-bottom: 0.8rem; border-left: 3px solid #f97316; }
27
  .issue-card .roast-text { color: #f97316; font-style: italic; margin-bottom: 0.5rem; }
28
  .issue-card .fix-text { color: #7ee787; }
29
- .severity { display: inline-block; padding: 0.1rem 0.5rem; border-radius: 10px; font-size: 0.75rem; font-weight: 700; }
30
- .severity.mild { background: #238636; color: #fff; }
31
- .severity.spicy { background: #9e6a03; color: #fff; }
32
- .severity.nuclear { background: #da3633; color: #fff; }
33
- .rewritten-code { background: #0d1117; border: 1px solid #238636; border-radius: 8px; padding: 1rem; overflow-x: auto; white-space: pre-wrap; color: #7ee787; font-size: 0.85rem; max-height: 400px; overflow-y: auto; }
34
- .section-title { color: #f97316; margin-bottom: 0.8rem; font-size: 1.1rem; }
 
 
 
 
 
 
 
 
 
35
  .tag { display: inline-block; padding: 0.2rem 0.6rem; border-radius: 12px; background: #30363d; color: #8b949e; font-size: 0.8rem; margin: 0.2rem; }
36
- .closing-roast { font-size: 1.1rem; color: #ef4444; font-style: italic; text-align: center; padding: 1rem; margin-top: 1.5rem; }
37
- .back-btn { background: #30363d; margin-top: 1.5rem; }
38
- @media (max-width: 768px) { h1 { font-size: 1.8rem; } .score-big { font-size: 3rem; } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  * { margin: 0; padding: 0; box-sizing: border-box; }
2
+ body { font-family: "Courier New", monospace; background: #0d1117; color: #c9d1d9; min-height: 100vh; }
3
  .app { max-width: 900px; margin: 0 auto; padding: 2rem 1rem; }
4
  h1 { text-align: center; font-size: 2.5rem; margin-bottom: 0.5rem; }
5
  h1 span { background: linear-gradient(135deg, #f97316, #ef4444); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
 
7
  .editor-section { background: #161b22; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; border: 1px solid #30363d; }
8
  .editor-section label { display: block; margin-bottom: 0.5rem; color: #8b949e; }
9
  .editor-section select { padding: 0.5rem; border-radius: 6px; border: 1px solid #30363d; background: #0d1117; color: #c9d1d9; margin-bottom: 1rem; }
10
+ .code-input { width: 100%; min-height: 200px; padding: 1rem; border: 1px solid #30363d; border-radius: 8px; background: #0d1117; color: #79c0ff; font-family: "Courier New", monospace; font-size: 0.9rem; resize: vertical; tab-size: 2; }
11
  .btn { width: 100%; padding: 1rem; border: none; border-radius: 8px; font-size: 1.1rem; font-weight: 700; cursor: pointer; background: linear-gradient(135deg, #f97316, #ef4444); color: #fff; font-family: inherit; transition: transform 0.2s; }
12
  .btn:hover { transform: scale(1.02); }
13
  .btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
 
15
  .loading .spinner { width: 50px; height: 50px; border: 4px solid #30363d; border-top-color: #f97316; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 1rem; }
16
  @keyframes spin { to { transform: rotate(360deg); } }
17
  .results { margin-top: 2rem; }
18
+ .roast-card { background: #161b22; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; border: 1px solid #30363d; }
19
  .opening-roast { font-size: 1.2rem; color: #f97316; font-style: italic; text-align: center; padding: 1.5rem; background: linear-gradient(135deg, #161b22, #1c1206); border-radius: 12px; border: 1px solid #f97316; margin-bottom: 1.5rem; }
20
+
21
+ /* Tab Navigation */
22
+ .tab-nav { display: flex; gap: 0.3rem; margin-bottom: 1.5rem; overflow-x: auto; padding-bottom: 0.5rem; border-bottom: 1px solid #30363d; }
23
+ .tab-btn { padding: 0.5rem 1rem; border: 1px solid transparent; border-bottom: none; border-radius: 8px 8px 0 0; background: transparent; color: #8b949e; font-family: inherit; font-size: 0.85rem; cursor: pointer; white-space: nowrap; transition: all 0.2s; }
24
+ .tab-btn:hover { color: #c9d1d9; background: #161b22; }
25
+ .tab-btn.active { color: #f97316; background: #161b22; border-color: #30363d; border-bottom: 2px solid #f97316; font-weight: 700; }
26
+
27
+ /* Score Grid */
28
+ .score-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
29
+ .score-card { background: #161b22; border: 1px solid #30363d; border-radius: 12px; padding: 1.5rem; text-align: center; display: flex; flex-direction: column; align-items: center; justify-content: center; }
30
+ .score-big { font-size: 3.5rem; font-weight: 700; color: #f97316; line-height: 1; }
31
+ .hire-badge { display: inline-block; padding: 0.4rem 1.2rem; border-radius: 20px; font-weight: 700; font-size: 0.9rem; }
32
  .hire-badge.yes { background: #238636; color: #fff; }
33
  .hire-badge.maybe { background: #9e6a03; color: #fff; }
34
  .hire-badge.no { background: #da3633; color: #fff; }
35
+
36
+ /* Issue Cards */
37
  .issue-card { background: #0d1117; border-radius: 8px; padding: 1rem; margin-bottom: 0.8rem; border-left: 3px solid #f97316; }
38
  .issue-card .roast-text { color: #f97316; font-style: italic; margin-bottom: 0.5rem; }
39
  .issue-card .fix-text { color: #7ee787; }
40
+
41
+ /* Vulnerability Cards */
42
+ .vuln-card { background: #0d1117; border-radius: 8px; padding: 1rem; margin-bottom: 0.8rem; border-left: 3px solid #da3633; }
43
+
44
+ /* Complexity Badges */
45
+ .complexity-badge { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; padding: 0.5rem 1rem; text-align: center; }
46
+
47
+ /* Test Cards */
48
+ .test-card { background: #0d1117; border-radius: 8px; padding: 1rem; margin-bottom: 0.8rem; border-left: 3px solid #388bfd; }
49
+ .test-type { display: inline-block; padding: 0.15rem 0.6rem; border-radius: 10px; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; }
50
+ .test-unit { background: #238636; color: #fff; }
51
+ .test-integration { background: #9e6a03; color: #fff; }
52
+ .test-edge-case { background: #388bfd; color: #fff; }
53
+
54
+ /* Tags */
55
  .tag { display: inline-block; padding: 0.2rem 0.6rem; border-radius: 12px; background: #30363d; color: #8b949e; font-size: 0.8rem; margin: 0.2rem; }
56
+ .tag-warn { background: #3d2e00; color: #d29922; border: 1px solid #9e6a03; }
57
+ .tag-error { background: #3d0000; color: #f85149; border: 1px solid #da3633; }
58
+
59
+ .rewritten-code { background: #0d1117; border: 1px solid #238636; border-radius: 8px; padding: 1rem; overflow-x: auto; white-space: pre-wrap; color: #7ee787; font-size: 0.85rem; max-height: 500px; overflow-y: auto; }
60
+ .section-title { color: #f97316; margin-bottom: 0.8rem; font-size: 1.1rem; font-weight: 700; }
61
+ .closing-roast { font-size: 1.1rem; color: #ef4444; font-style: italic; text-align: center; padding: 1.5rem; margin: 1.5rem 0; background: linear-gradient(135deg, #161b22, #1c0606); border-radius: 12px; border: 1px solid #ef4444; }
62
+ .back-btn { background: #30363d; margin-top: 1rem; }
63
+ .back-btn:hover { background: #484f58; }
64
+
65
+ /* Responsive */
66
+ @media (max-width: 768px) {
67
+ h1 { font-size: 1.8rem; }
68
+ .score-big { font-size: 2.5rem; }
69
+ .score-grid { grid-template-columns: 1fr; }
70
+ .tab-nav { gap: 0.2rem; }
71
+ .tab-btn { padding: 0.4rem 0.6rem; font-size: 0.75rem; }
72
+ .roast-card { padding: 1rem; }
73
+ }