All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 12s
Build & Deploy / 🧪 QA (push) Successful in 4m33s
Build & Deploy / 🏗️ Build (push) Successful in 5m35s
Build & Deploy / 🚀 Deploy (push) Successful in 26s
Build & Deploy / 🧪 Smoke Test (push) Successful in 4m39s
Build & Deploy / ⚡ Lighthouse (push) Successful in 9m39s
Build & Deploy / 🔔 Notify (push) Successful in 2s
- Achieved 100/100 Accessibility score across sitemap (pa11y-ci 10/10 parity) - Stabilized Performance score >= 94 by purging LCP-blocking CSS animations - Fixed canonical/hreflang absolute URI mismatches for perfect SEO scores - Silenced client-side telemetry/analytics console noise in CI environments - Hardened sitemap generation with environment-aware baseUrl - Refined contrast for Badge and VisualLinkPreview components (#14532d)
101 lines
3.5 KiB
TypeScript
101 lines
3.5 KiB
TypeScript
import React from 'react';
|
|
import Image from 'next/image';
|
|
import Link from 'next/link';
|
|
|
|
interface VisualLinkPreviewProps {
|
|
url: string;
|
|
title: string;
|
|
summary: string;
|
|
image: string;
|
|
}
|
|
|
|
export default function VisualLinkPreview({ url, title, summary, image }: VisualLinkPreviewProps) {
|
|
const hostname = (() => {
|
|
try {
|
|
return new URL(url).hostname;
|
|
} catch {
|
|
return url;
|
|
}
|
|
})();
|
|
|
|
return (
|
|
<Link
|
|
href={url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="block my-12 no-underline group"
|
|
>
|
|
<div className="flex flex-col md:flex-row border border-neutral-200 rounded-2xl overflow-hidden bg-white transition-all duration-500 hover:shadow-2xl hover:border-primary/20 hover:-translate-y-1 group">
|
|
<div className="relative w-full md:w-64 h-48 md:h-auto flex-shrink-0 bg-neutral-50 overflow-hidden">
|
|
{image ? (
|
|
<Image
|
|
src={image}
|
|
alt={title}
|
|
fill
|
|
unoptimized
|
|
className="object-cover transition-transform duration-700 group-hover:scale-110"
|
|
/>
|
|
) : (
|
|
<div className="w-full h-full flex items-center justify-center bg-primary/5">
|
|
<svg
|
|
className="w-12 h-12 text-primary/20"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={1.5}
|
|
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
)}
|
|
{/* Industrial overlay */}
|
|
<div className="absolute inset-0 bg-primary/10 opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none" />
|
|
</div>
|
|
|
|
<div className="p-8 flex flex-col justify-center relative">
|
|
{/* Industrial accent corner */}
|
|
<div className="absolute top-0 right-0 w-12 h-12 bg-primary/5 -mr-6 -mt-6 rotate-45 transition-transform group-hover:scale-110" />
|
|
|
|
<div className="flex items-center gap-2 mb-3">
|
|
<span className="text-[10px] font-bold uppercase tracking-[0.2em] text-primary/80 bg-primary/10 px-2 py-0.5 rounded">
|
|
External Link
|
|
</span>
|
|
<span className="text-[10px] font-bold uppercase tracking-[0.2em] text-text-secondary/80">
|
|
{hostname}
|
|
</span>
|
|
</div>
|
|
|
|
<h3 className="text-xl font-bold text-text-primary mb-3 group-hover:text-primary transition-colors line-clamp-2 leading-tight">
|
|
{title}
|
|
</h3>
|
|
|
|
<p className="text-text-secondary text-base line-clamp-2 leading-relaxed mb-4">
|
|
{summary}
|
|
</p>
|
|
|
|
<div className="flex items-center gap-2 text-primary font-bold text-xs uppercase tracking-widest">
|
|
<span>Read more</span>
|
|
<svg
|
|
className="w-4 h-4 transition-transform group-hover:translate-x-1"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M17 8l4 4m0 0l-4 4m4-4H3"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
);
|
|
}
|