A simple SEO inspecter Tool, to get social media card previews
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Refine SEO tool appearance

+359 -261
+359 -261
src/components/SEOTester.tsx
··· 6 6 import { Separator } from '@/components/ui/separator'; 7 7 import { Progress } from '@/components/ui/progress'; 8 8 import { useToast } from '@/hooks/use-toast'; 9 - import { Search, ExternalLink, Image, FileText, Tag, Globe, AlertCircle, CheckCircle, Zap, TrendingUp, Eye, Share2, Target } from 'lucide-react'; 9 + import { Search, ExternalLink, Image, FileText, Tag, Globe, AlertCircle, CheckCircle, Zap, TrendingUp, Eye, Share2, Target, Terminal, Code, Bug, Cpu, Database, Monitor, Server } from 'lucide-react'; 10 10 11 11 interface SEOData { 12 12 title?: string; ··· 139 139 setSeoData(null); 140 140 141 141 try { 142 - // Simulate progress updates 143 142 setProgress(20); 144 143 145 - // First, let's try to validate the URL format 146 144 const urlObj = new URL(url); 147 145 setProgress(40); 148 146 149 - // For demonstration, we'll use a CORS proxy service 150 - // In a real app, you'd want to use a backend service 151 147 const proxyUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`; 152 148 const response = await fetch(proxyUrl); 153 149 setProgress(70); ··· 170 166 setSeoScore(score); 171 167 setProgress(100); 172 168 173 - // Trigger results animation 174 169 setTimeout(() => { 175 170 setShowResults(true); 176 171 }, 200); ··· 225 220 return 'F'; 226 221 }; 227 222 228 - const ScoreIndicator = ({ hasValue, label }: { hasValue: boolean; label: string }) => ( 229 - <div className="flex items-center gap-2 group cursor-default"> 230 - <div className="relative"> 231 - {hasValue ? ( 232 - <CheckCircle className="h-4 w-4 text-success group-hover:scale-110 transition-transform duration-200" /> 233 - ) : ( 234 - <AlertCircle className="h-4 w-4 text-destructive group-hover:scale-110 transition-transform duration-200" /> 235 - )} 236 - </div> 237 - <span className="text-sm group-hover:text-foreground transition-colors duration-200">{label}</span> 238 - </div> 239 - ); 240 - 241 223 const AnimatedCounter = ({ value, duration = 1000 }: { value: number; duration?: number }) => { 242 224 const [count, setCount] = useState(0); 243 225 ··· 261 243 return <span>{count}</span>; 262 244 }; 263 245 246 + const DataField = ({ label, value, isGood }: { label: string; value: string; isGood?: boolean }) => ( 247 + <div className="bg-muted/20 p-3 rounded border-l-2 border-l-primary/50"> 248 + <div className="flex items-center justify-between mb-2"> 249 + <span className="text-muted-foreground font-mono text-xs">{label}</span> 250 + {value && isGood !== undefined && ( 251 + <div className={`w-2 h-2 rounded-full ${isGood ? 'bg-green-500' : 'bg-orange-500'}`}></div> 252 + )} 253 + </div> 254 + <div className="text-foreground break-words font-mono text-sm"> 255 + {value || <span className="text-red-500">❌ null</span>} 256 + </div> 257 + </div> 258 + ); 259 + 264 260 return ( 265 - <div className="min-h-screen bg-gradient-subtle relative overflow-hidden"> 266 - {/* Animated background elements */} 267 - <div className="absolute inset-0 overflow-hidden pointer-events-none"> 268 - <div className="absolute -top-40 -right-40 w-80 h-80 bg-primary/5 rounded-full animate-pulse-glow"></div> 269 - <div className="absolute -bottom-40 -left-40 w-96 h-96 bg-primary-glow/5 rounded-full animate-pulse-glow" style={{animationDelay: '1s'}}></div> 261 + <div className="min-h-screen bg-background relative overflow-hidden"> 262 + {/* Terminal-style background pattern */} 263 + <div className="absolute inset-0 opacity-5"> 264 + <div className="absolute inset-0" style={{ 265 + backgroundImage: `radial-gradient(circle at 1px 1px, rgb(255,255,255) 1px, transparent 0)`, 266 + backgroundSize: '20px 20px' 267 + }}></div> 270 268 </div> 271 269 272 270 <div className="container mx-auto px-4 py-8 relative z-10"> 273 - <div className="max-w-4xl mx-auto"> 274 - {/* Header */} 275 - <div className="text-center mb-8 animate-fade-in"> 276 - <div className="inline-flex items-center gap-2 mb-4"> 277 - <div className="p-2 bg-gradient-primary rounded-xl shadow-elegant"> 278 - <Target className="h-6 w-6 text-white" /> 271 + <div className="max-w-6xl mx-auto"> 272 + {/* Header - Dev Tool Style */} 273 + <div className="mb-8 animate-fade-in"> 274 + <div className="flex items-center gap-3 mb-4 p-4 bg-muted/50 rounded-lg border border-border/50"> 275 + <div className="flex items-center gap-2"> 276 + <Terminal className="h-5 w-5 text-primary" /> 277 + <div className="flex items-center gap-1 text-xs font-mono"> 278 + <span className="text-primary">$</span> 279 + <span className="text-muted-foreground">seo-analyzer</span> 280 + <span className="text-primary">--analyze</span> 281 + </div> 282 + </div> 283 + <Separator orientation="vertical" className="h-4" /> 284 + <div className="flex items-center gap-2 text-xs"> 285 + <div className="w-2 h-2 rounded-full bg-success animate-pulse"></div> 286 + <span className="text-muted-foreground font-mono">dev-tools</span> 279 287 </div> 280 - <h1 className="text-4xl font-bold text-foreground">SEO Analyzer</h1> 288 + </div> 289 + 290 + <div className="text-left"> 291 + <h1 className="text-2xl font-bold text-foreground font-mono flex items-center gap-2"> 292 + <Code className="h-6 w-6 text-primary" /> 293 + SEO Debug Console 294 + </h1> 295 + <p className="text-sm text-muted-foreground font-mono mt-1"> 296 + → Advanced web scraping and metadata analysis tool 297 + </p> 281 298 </div> 282 - <p className="text-lg text-muted-foreground max-w-2xl mx-auto"> 283 - Get comprehensive SEO insights with our advanced analyzer. 284 - Discover optimization opportunities and track your website's performance. 285 - </p> 286 299 </div> 287 300 288 - {/* URL Input Form */} 289 - <Card className="mb-8 shadow-card animate-scale-in border-0 bg-card/80 backdrop-blur-sm"> 290 - <CardContent className="pt-6"> 301 + {/* Command Input - Dev Tool Style */} 302 + <Card className="mb-8 animate-scale-in border border-border bg-card"> 303 + <CardHeader className="pb-3"> 304 + <div className="flex items-center gap-2 text-sm"> 305 + <Monitor className="h-4 w-4 text-primary" /> 306 + <span className="font-mono text-muted-foreground">Network Analysis</span> 307 + <Separator orientation="vertical" className="h-4" /> 308 + <span className="font-mono text-xs text-muted-foreground">Status: Ready</span> 309 + </div> 310 + </CardHeader> 311 + <CardContent> 291 312 <form onSubmit={handleSubmit} className="space-y-4"> 292 - <div className="flex gap-4"> 313 + <div className="flex gap-3"> 293 314 <div className="flex-1 relative"> 315 + <div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-primary font-mono text-sm"> 316 + 317 + </div> 294 318 <Input 295 319 type="url" 296 320 placeholder="https://example.com" 297 321 value={url} 298 322 onChange={(e) => setUrl(e.target.value)} 299 - className="text-lg pr-12 border-2 focus:border-primary transition-all duration-300" 323 + className="font-mono pl-8 pr-10 bg-muted/30 border-border focus:border-primary" 300 324 required 301 325 /> 302 - <Globe className="absolute right-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-muted-foreground" /> 326 + <Globe className="absolute right-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" /> 303 327 </div> 304 328 <Button 305 329 type="submit" 306 - size="lg" 307 330 disabled={isLoading} 308 - className="bg-gradient-primary hover:shadow-elegant transition-all duration-300 hover:scale-105 relative overflow-hidden group" 331 + className="bg-primary hover:bg-primary/90 font-mono text-sm" 309 332 > 310 - <div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-700 ease-in-out"></div> 311 333 {isLoading ? ( 312 334 <> 313 - <Zap className="h-4 w-4 mr-2 animate-bounce-subtle" /> 314 - Analyzing... 335 + <Cpu className="h-4 w-4 mr-2 animate-spin" /> 336 + Processing 315 337 </> 316 338 ) : ( 317 339 <> 318 340 <Search className="h-4 w-4 mr-2" /> 319 - Analyze SEO 341 + Execute 320 342 </> 321 343 )} 322 344 </Button> 323 345 </div> 324 346 325 347 {isLoading && ( 326 - <div className="space-y-2 animate-fade-in"> 327 - <div className="flex items-center justify-between text-sm"> 328 - <span className="text-muted-foreground">Analyzing website...</span> 329 - <span className="text-primary font-medium">{progress}%</span> 348 + <div className="space-y-3 animate-fade-in bg-muted/30 p-3 rounded-md"> 349 + <div className="flex items-center justify-between text-xs font-mono"> 350 + <span className="text-muted-foreground">$ curl -s {url} | seo-parse</span> 351 + <span className="text-primary">{progress}%</span> 352 + </div> 353 + <Progress value={progress} className="h-1" /> 354 + <div className="text-xs font-mono text-muted-foreground"> 355 + ⚡ Fetching HTML content... Parsing metadata... Calculating scores... 330 356 </div> 331 - <Progress value={progress} className="h-2" /> 332 357 </div> 333 358 )} 334 359 </form> 335 360 </CardContent> 336 361 </Card> 337 362 338 - {/* Error Message */} 363 + {/* Error Console */} 339 364 {error && ( 340 - <Card className="mb-8 border-destructive"> 341 - <CardContent className="pt-6"> 342 - <div className="flex items-center gap-2 text-destructive"> 343 - <AlertCircle className="h-5 w-5" /> 344 - <span>{error}</span> 365 + <Card className="mb-8 border-destructive bg-destructive/5"> 366 + <CardHeader className="pb-3"> 367 + <div className="flex items-center gap-2 text-sm"> 368 + <Bug className="h-4 w-4 text-destructive" /> 369 + <span className="font-mono text-destructive">Error Console</span> 370 + </div> 371 + </CardHeader> 372 + <CardContent> 373 + <div className="bg-destructive/10 p-3 rounded font-mono text-sm"> 374 + <div className="flex items-start gap-2"> 375 + <span className="text-destructive">✗</span> 376 + <div className="text-destructive"> 377 + <div className="font-semibold">RuntimeError:</div> 378 + <div className="text-xs mt-1">{error}</div> 379 + </div> 380 + </div> 345 381 </div> 346 382 </CardContent> 347 383 </Card> ··· 350 386 {/* Results */} 351 387 {seoData && seoScore && ( 352 388 <div className={`space-y-6 ${showResults ? 'animate-fade-in-up' : 'opacity-0'}`}> 353 - {/* SEO Score Dashboard */} 354 - <Card className="shadow-card border-0 bg-gradient-primary text-white relative overflow-hidden"> 355 - <div className="absolute inset-0 bg-gradient-to-br from-primary via-primary-glow to-primary opacity-90"></div> 356 - <CardContent className="pt-6 relative z-10"> 357 - <div className="text-center mb-6"> 358 - <div className="inline-flex items-center gap-3 mb-4"> 359 - <TrendingUp className="h-8 w-8" /> 360 - <h2 className="text-2xl font-bold">SEO Score</h2> 389 + {/* Performance Metrics Dashboard */} 390 + <Card className="border border-border bg-card"> 391 + <CardHeader className="pb-3"> 392 + <div className="flex items-center justify-between"> 393 + <div className="flex items-center gap-2"> 394 + <Database className="h-4 w-4 text-primary" /> 395 + <span className="font-mono text-sm">Performance Metrics</span> 361 396 </div> 362 - <div className="relative inline-flex items-center justify-center"> 363 - <div className="text-6xl font-bold mb-2"> 364 - <AnimatedCounter value={seoScore.total} /> 365 - </div> 366 - <div className="absolute -top-2 -right-8 text-lg font-medium bg-white/20 px-2 py-1 rounded-full"> 367 - {getScoreGrade(seoScore.total)} 368 - </div> 369 - </div> 370 - <p className="text-white/80 mb-6">out of 100 points</p> 397 + <Badge variant="outline" className="font-mono text-xs"> 398 + {getScoreGrade(seoScore.total)} 399 + </Badge> 371 400 </div> 372 - 373 - <div className="grid grid-cols-3 gap-4 text-center"> 374 - <div className="bg-white/10 rounded-lg p-4"> 375 - <div className="text-2xl font-bold mb-1"> 376 - <AnimatedCounter value={seoScore.breakdown.basic} /> 401 + </CardHeader> 402 + <CardContent> 403 + <div className="grid grid-cols-1 md:grid-cols-4 gap-4"> 404 + {/* Overall Score */} 405 + <div className="md:col-span-1 bg-muted/30 p-4 rounded-lg"> 406 + <div className="text-center"> 407 + <div className="text-3xl font-bold font-mono text-primary mb-1"> 408 + <AnimatedCounter value={seoScore.total} /> 409 + </div> 410 + <div className="text-xs text-muted-foreground font-mono">TOTAL SCORE</div> 411 + <div className="text-xs text-muted-foreground">/100</div> 377 412 </div> 378 - <div className="text-sm text-white/80">Basic SEO</div> 379 - <div className="text-xs text-white/60">/ 40 pts</div> 380 413 </div> 381 - <div className="bg-white/10 rounded-lg p-4"> 382 - <div className="text-2xl font-bold mb-1"> 383 - <AnimatedCounter value={seoScore.breakdown.social} /> 414 + 415 + {/* Breakdown */} 416 + <div className="md:col-span-3 grid grid-cols-3 gap-3"> 417 + <div className="bg-muted/20 p-3 rounded"> 418 + <div className="flex items-center gap-2 mb-2"> 419 + <FileText className="h-3 w-3 text-blue-500" /> 420 + <span className="text-xs font-mono text-muted-foreground">BASIC</span> 421 + </div> 422 + <div className="text-xl font-bold font-mono"> 423 + <AnimatedCounter value={seoScore.breakdown.basic} /> 424 + </div> 425 + <div className="text-xs text-muted-foreground">/40 pts</div> 426 + </div> 427 + 428 + <div className="bg-muted/20 p-3 rounded"> 429 + <div className="flex items-center gap-2 mb-2"> 430 + <Share2 className="h-3 w-3 text-green-500" /> 431 + <span className="text-xs font-mono text-muted-foreground">SOCIAL</span> 432 + </div> 433 + <div className="text-xl font-bold font-mono"> 434 + <AnimatedCounter value={seoScore.breakdown.social} /> 435 + </div> 436 + <div className="text-xs text-muted-foreground">/30 pts</div> 384 437 </div> 385 - <div className="text-sm text-white/80">Social Media</div> 386 - <div className="text-xs text-white/60">/ 30 pts</div> 387 - </div> 388 - <div className="bg-white/10 rounded-lg p-4"> 389 - <div className="text-2xl font-bold mb-1"> 390 - <AnimatedCounter value={seoScore.breakdown.technical} /> 438 + 439 + <div className="bg-muted/20 p-3 rounded"> 440 + <div className="flex items-center gap-2 mb-2"> 441 + <Server className="h-3 w-3 text-orange-500" /> 442 + <span className="text-xs font-mono text-muted-foreground">TECH</span> 443 + </div> 444 + <div className="text-xl font-bold font-mono"> 445 + <AnimatedCounter value={seoScore.breakdown.technical} /> 446 + </div> 447 + <div className="text-xs text-muted-foreground">/30 pts</div> 391 448 </div> 392 - <div className="text-sm text-white/80">Technical</div> 393 - <div className="text-xs text-white/60">/ 30 pts</div> 394 449 </div> 395 450 </div> 396 451 </CardContent> 397 452 </Card> 398 453 399 - {/* Quick Overview */} 400 - <Card className="shadow-card border-0 bg-card/80 backdrop-blur-sm"> 401 - <CardHeader> 402 - <CardTitle className="flex items-center gap-2"> 403 - <Eye className="h-5 w-5" /> 404 - Quick Overview 405 - </CardTitle> 454 + {/* Status Indicators */} 455 + <Card className="border border-border bg-card"> 456 + <CardHeader className="pb-3"> 457 + <div className="flex items-center gap-2"> 458 + <Eye className="h-4 w-4 text-primary" /> 459 + <span className="font-mono text-sm">System Status</span> 460 + </div> 406 461 </CardHeader> 407 462 <CardContent> 408 - <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> 409 - <ScoreIndicator hasValue={!!seoData.title} label="Page Title" /> 410 - <ScoreIndicator hasValue={!!seoData.description} label="Meta Description" /> 411 - <ScoreIndicator hasValue={!!seoData.ogImage} label="OG Image" /> 412 - <ScoreIndicator hasValue={!!seoData.canonical} label="Canonical URL" /> 413 - <ScoreIndicator hasValue={!!seoData.h1} label="H1 Tag" /> 414 - <ScoreIndicator hasValue={!!seoData.lang} label="Language" /> 415 - <ScoreIndicator hasValue={!!seoData.viewport} label="Viewport" /> 416 - <ScoreIndicator hasValue={!!seoData.charset} label="Charset" /> 463 + <div className="grid grid-cols-2 md:grid-cols-4 gap-3 font-mono text-xs"> 464 + <div className="flex items-center gap-2 p-2 bg-muted/20 rounded"> 465 + {!!seoData.title ? ( 466 + <CheckCircle className="h-3 w-3 text-green-500" /> 467 + ) : ( 468 + <AlertCircle className="h-3 w-3 text-red-500" /> 469 + )} 470 + <span>title</span> 471 + </div> 472 + <div className="flex items-center gap-2 p-2 bg-muted/20 rounded"> 473 + {!!seoData.description ? ( 474 + <CheckCircle className="h-3 w-3 text-green-500" /> 475 + ) : ( 476 + <AlertCircle className="h-3 w-3 text-red-500" /> 477 + )} 478 + <span>description</span> 479 + </div> 480 + <div className="flex items-center gap-2 p-2 bg-muted/20 rounded"> 481 + {!!seoData.ogImage ? ( 482 + <CheckCircle className="h-3 w-3 text-green-500" /> 483 + ) : ( 484 + <AlertCircle className="h-3 w-3 text-red-500" /> 485 + )} 486 + <span>og:image</span> 487 + </div> 488 + <div className="flex items-center gap-2 p-2 bg-muted/20 rounded"> 489 + {!!seoData.canonical ? ( 490 + <CheckCircle className="h-3 w-3 text-green-500" /> 491 + ) : ( 492 + <AlertCircle className="h-3 w-3 text-red-500" /> 493 + )} 494 + <span>canonical</span> 495 + </div> 496 + <div className="flex items-center gap-2 p-2 bg-muted/20 rounded"> 497 + {!!seoData.h1 ? ( 498 + <CheckCircle className="h-3 w-3 text-green-500" /> 499 + ) : ( 500 + <AlertCircle className="h-3 w-3 text-red-500" /> 501 + )} 502 + <span>h1</span> 503 + </div> 504 + <div className="flex items-center gap-2 p-2 bg-muted/20 rounded"> 505 + {!!seoData.lang ? ( 506 + <CheckCircle className="h-3 w-3 text-green-500" /> 507 + ) : ( 508 + <AlertCircle className="h-3 w-3 text-red-500" /> 509 + )} 510 + <span>lang</span> 511 + </div> 512 + <div className="flex items-center gap-2 p-2 bg-muted/20 rounded"> 513 + {!!seoData.viewport ? ( 514 + <CheckCircle className="h-3 w-3 text-green-500" /> 515 + ) : ( 516 + <AlertCircle className="h-3 w-3 text-red-500" /> 517 + )} 518 + <span>viewport</span> 519 + </div> 520 + <div className="flex items-center gap-2 p-2 bg-muted/20 rounded"> 521 + {!!seoData.charset ? ( 522 + <CheckCircle className="h-3 w-3 text-green-500" /> 523 + ) : ( 524 + <AlertCircle className="h-3 w-3 text-red-500" /> 525 + )} 526 + <span>charset</span> 527 + </div> 417 528 </div> 418 529 </CardContent> 419 530 </Card> 420 531 421 - {/* Basic SEO */} 422 - <Card className="shadow-card border-0 bg-card/80 backdrop-blur-sm"> 423 - <CardHeader> 424 - <CardTitle className="flex items-center gap-2"> 425 - <FileText className="h-5 w-5 text-primary" /> 426 - Basic SEO 427 - <Badge variant="secondary" className="ml-auto"> 428 - {seoScore.breakdown.basic}/40 pts 429 - </Badge> 430 - </CardTitle> 431 - </CardHeader> 432 - <CardContent className="space-y-6"> 433 - <div className="group hover:bg-muted/30 p-3 rounded-lg transition-colors duration-200"> 434 - <label className="text-sm font-medium text-muted-foreground flex items-center gap-2"> 435 - <Tag className="h-3 w-3" /> 436 - Page Title 437 - {seoData.title && ( 438 - <Badge variant={seoData.title.length >= 30 && seoData.title.length <= 60 ? 'default' : 'secondary'} className="text-xs"> 439 - {seoData.title.length} chars 440 - </Badge> 441 - )} 442 - </label> 443 - <p className="mt-2 text-foreground font-medium">{seoData.title || 'Not found'}</p> 444 - {seoData.title && ( 445 - <div className="mt-2 text-xs text-muted-foreground"> 446 - {seoData.title.length < 30 && <span className="text-yellow-600">⚠️ Too short (recommended: 30-60 characters)</span>} 447 - {seoData.title.length > 60 && <span className="text-yellow-600">⚠️ Too long (recommended: 30-60 characters)</span>} 448 - {seoData.title.length >= 30 && seoData.title.length <= 60 && <span className="text-green-600">✓ Good length</span>} 532 + {/* Detailed Analysis */} 533 + <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> 534 + {/* Basic Metadata */} 535 + <Card className="border border-border bg-card"> 536 + <CardHeader className="pb-3"> 537 + <div className="flex items-center justify-between"> 538 + <div className="flex items-center gap-2"> 539 + <FileText className="h-4 w-4 text-primary" /> 540 + <span className="font-mono text-sm">Basic Metadata</span> 449 541 </div> 450 - )} 451 - </div> 452 - 453 - <Separator /> 454 - 455 - <div> 456 - <label className="text-sm font-medium text-muted-foreground">Meta Description</label> 457 - <p className="mt-1 text-foreground">{seoData.description || 'Not found'}</p> 458 - {seoData.description && ( 459 - <p className="text-xs text-muted-foreground mt-1"> 460 - Length: {seoData.description.length} characters 461 - </p> 462 - )} 463 - </div> 542 + <Badge variant="outline" className="font-mono text-xs"> 543 + {seoScore.breakdown.basic}/40 544 + </Badge> 545 + </div> 546 + </CardHeader> 547 + <CardContent className="space-y-4"> 548 + <DataField 549 + label="title" 550 + value={seoData.title || ''} 551 + isGood={!!seoData.title && seoData.title.length >= 30 && seoData.title.length <= 60} 552 + /> 553 + <DataField 554 + label="description" 555 + value={seoData.description || ''} 556 + isGood={!!seoData.description && seoData.description.length >= 120 && seoData.description.length <= 160} 557 + /> 558 + <DataField 559 + label="h1" 560 + value={seoData.h1 || ''} 561 + isGood={!!seoData.h1} 562 + /> 563 + <DataField 564 + label="keywords" 565 + value={seoData.keywords || ''} 566 + /> 567 + </CardContent> 568 + </Card> 464 569 465 - {seoData.keywords && ( 466 - <> 467 - <Separator /> 468 - <div> 469 - <label className="text-sm font-medium text-muted-foreground">Keywords</label> 470 - <p className="mt-1 text-foreground">{seoData.keywords}</p> 570 + {/* Open Graph Data */} 571 + <Card className="border border-border bg-card"> 572 + <CardHeader className="pb-3"> 573 + <div className="flex items-center justify-between"> 574 + <div className="flex items-center gap-2"> 575 + <Share2 className="h-4 w-4 text-primary" /> 576 + <span className="font-mono text-sm">Social Media</span> 471 577 </div> 472 - </> 473 - )} 474 - 475 - {seoData.canonical && ( 476 - <> 477 - <Separator /> 478 - <div> 479 - <label className="text-sm font-medium text-muted-foreground">Canonical URL</label> 480 - <div className="flex items-center gap-2 mt-1"> 481 - <p className="text-foreground break-all">{seoData.canonical}</p> 482 - <Button variant="ghost" size="sm" asChild> 483 - <a href={seoData.canonical} target="_blank" rel="noopener noreferrer"> 484 - <ExternalLink className="h-3 w-3" /> 485 - </a> 486 - </Button> 487 - </div> 488 - </div> 489 - </> 490 - )} 491 - </CardContent> 492 - </Card> 578 + <Badge variant="outline" className="font-mono text-xs"> 579 + {seoScore.breakdown.social}/30 580 + </Badge> 581 + </div> 582 + </CardHeader> 583 + <CardContent className="space-y-4"> 584 + <DataField 585 + label="og:title" 586 + value={seoData.ogTitle || ''} 587 + isGood={!!seoData.ogTitle} 588 + /> 589 + <DataField 590 + label="og:description" 591 + value={seoData.ogDescription || ''} 592 + isGood={!!seoData.ogDescription} 593 + /> 594 + <DataField 595 + label="og:image" 596 + value={seoData.ogImage || ''} 597 + isGood={!!seoData.ogImage} 598 + /> 599 + <DataField 600 + label="og:type" 601 + value={seoData.ogType || ''} 602 + /> 603 + </CardContent> 604 + </Card> 605 + </div> 493 606 494 - {/* Open Graph */} 495 - <Card className="shadow-card border-0 bg-card/80 backdrop-blur-sm"> 496 - <CardHeader> 497 - <CardTitle className="flex items-center gap-2"> 498 - <Share2 className="h-5 w-5 text-primary" /> 499 - Open Graph (Facebook) 500 - <Badge variant="secondary" className="ml-auto"> 501 - {seoScore.breakdown.social}/30 pts 607 + {/* Technical SEO */} 608 + <Card className="border border-border bg-card"> 609 + <CardHeader className="pb-3"> 610 + <div className="flex items-center justify-between"> 611 + <div className="flex items-center gap-2"> 612 + <Server className="h-4 w-4 text-primary" /> 613 + <span className="font-mono text-sm">Technical Analysis</span> 614 + </div> 615 + <Badge variant="outline" className="font-mono text-xs"> 616 + {seoScore.breakdown.technical}/30 502 617 </Badge> 503 - </CardTitle> 618 + </div> 504 619 </CardHeader> 505 - <CardContent className="space-y-4"> 506 - {seoData.ogImage && ( 507 - <div> 508 - <label className="text-sm font-medium text-muted-foreground">OG Image</label> 509 - <div className="mt-2"> 510 - <img 511 - src={seoData.ogImage} 512 - alt="OG Preview" 513 - className="max-w-sm h-auto border rounded-md shadow-sm" 514 - onError={(e) => { 515 - e.currentTarget.style.display = 'none'; 516 - }} 517 - /> 518 - <p className="text-xs text-muted-foreground mt-1 break-all">{seoData.ogImage}</p> 519 - </div> 520 - </div> 521 - )} 522 - 523 - <div> 524 - <label className="text-sm font-medium text-muted-foreground">OG Title</label> 525 - <p className="mt-1 text-foreground">{seoData.ogTitle || 'Not found'}</p> 620 + <CardContent> 621 + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> 622 + <DataField 623 + label="canonical" 624 + value={seoData.canonical || ''} 625 + isGood={!!seoData.canonical} 626 + /> 627 + <DataField 628 + label="lang" 629 + value={seoData.lang || ''} 630 + isGood={!!seoData.lang} 631 + /> 632 + <DataField 633 + label="viewport" 634 + value={seoData.viewport || ''} 635 + isGood={!!seoData.viewport} 636 + /> 637 + <DataField 638 + label="charset" 639 + value={seoData.charset || ''} 640 + isGood={!!seoData.charset} 641 + /> 642 + <DataField 643 + label="robots" 644 + value={seoData.metaRobots || 'index,follow'} 645 + /> 646 + <DataField 647 + label="url" 648 + value={seoData.url} 649 + /> 526 650 </div> 527 - 528 - <div> 529 - <label className="text-sm font-medium text-muted-foreground">OG Description</label> 530 - <p className="mt-1 text-foreground">{seoData.ogDescription || 'Not found'}</p> 531 - </div> 532 - 533 - {seoData.ogType && ( 534 - <div> 535 - <label className="text-sm font-medium text-muted-foreground">OG Type</label> 536 - <Badge variant="secondary" className="mt-1">{seoData.ogType}</Badge> 537 - </div> 538 - )} 539 651 </CardContent> 540 652 </Card> 541 653 542 654 {/* Twitter Cards */} 543 - <Card className="shadow-card border-0 bg-card/80 backdrop-blur-sm"> 544 - <CardHeader> 545 - <CardTitle className="flex items-center gap-2"> 546 - <Image className="h-5 w-5 text-primary" /> 547 - Twitter Cards 548 - </CardTitle> 549 - </CardHeader> 550 - <CardContent className="space-y-4"> 551 - {seoData.twitterCard && ( 552 - <div> 553 - <label className="text-sm font-medium text-muted-foreground">Card Type</label> 554 - <Badge variant="secondary" className="mt-1">{seoData.twitterCard}</Badge> 655 + {(seoData.twitterCard || seoData.twitterTitle || seoData.twitterDescription) && ( 656 + <Card className="border border-border bg-card"> 657 + <CardHeader className="pb-3"> 658 + <div className="flex items-center gap-2"> 659 + <span className="font-mono text-sm">Twitter Cards</span> 555 660 </div> 556 - )} 557 - 558 - <div> 559 - <label className="text-sm font-medium text-muted-foreground">Twitter Title</label> 560 - <p className="mt-1 text-foreground">{seoData.twitterTitle || 'Not found'}</p> 561 - </div> 562 - 563 - <div> 564 - <label className="text-sm font-medium text-muted-foreground">Twitter Description</label> 565 - <p className="mt-1 text-foreground">{seoData.twitterDescription || 'Not found'}</p> 566 - </div> 567 - 568 - {seoData.twitterImage && ( 569 - <div> 570 - <label className="text-sm font-medium text-muted-foreground">Twitter Image</label> 571 - <div className="mt-2"> 572 - <img 573 - src={seoData.twitterImage} 574 - alt="Twitter Preview" 575 - className="max-w-sm h-auto border rounded-md shadow-sm" 576 - onError={(e) => { 577 - e.currentTarget.style.display = 'none'; 578 - }} 579 - /> 580 - <p className="text-xs text-muted-foreground mt-1 break-all">{seoData.twitterImage}</p> 581 - </div> 661 + </CardHeader> 662 + <CardContent> 663 + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> 664 + <DataField 665 + label="twitter:card" 666 + value={seoData.twitterCard || ''} 667 + /> 668 + <DataField 669 + label="twitter:title" 670 + value={seoData.twitterTitle || ''} 671 + /> 672 + <DataField 673 + label="twitter:description" 674 + value={seoData.twitterDescription || ''} 675 + /> 676 + <DataField 677 + label="twitter:image" 678 + value={seoData.twitterImage || ''} 679 + /> 582 680 </div> 583 - )} 584 - </CardContent> 585 - </Card> 681 + </CardContent> 682 + </Card> 683 + )} 586 684 </div> 587 685 )} 588 686 </div>