Files
citpl_cms_react_express_pos…/src/components/BlogDetailPage.jsx
T

1135 lines
46 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useState } from 'react';
import { ArrowLeft, Clock, Calendar, Share2, ArrowRight, Mail, CheckCircle2, Copy, Check, Send, Tag } from 'lucide-react';
import { assetUrl } from '../lib/assetUrl';
const normalizeTags = (rawTags) => {
if (!rawTags) return [];
if (Array.isArray(rawTags)) return rawTags.map(t => String(t).trim()).filter(Boolean);
if (typeof rawTags === 'string') {
try {
const parsed = JSON.parse(rawTags);
if (Array.isArray(parsed)) return parsed.map(t => String(t).trim()).filter(Boolean);
} catch {}
return rawTags.split(',').map(t => t.trim()).filter(Boolean);
}
return [];
};
export default function BlogDetailPage({ blog, allBlogs = [], isMobile, onNavigate, onSelectBlog }) {
const [newsletterEmail, setNewsletterEmail] = useState('');
const [newsletterSubmitted, setNewsletterSubmitted] = useState(false);
const [copied, setCopied] = useState(false);
useEffect(() => {
window.scrollTo({ top: 0, behavior: 'smooth' });
}, [blog?.id || blog?.slug]);
useEffect(() => {
if (!blog) return;
const prevTitle = document.title;
const pageTitle = blog.metaTitle || `${blog.title} | Cavin Infotech`;
document.title = pageTitle;
const metaDesc = blog.metaDescription || blog.excerpt || '';
const tagList = normalizeTags(blog.tags);
const metaKeywords = blog.keywords || tagList.join(', ');
const setMetaTag = (name, content, attr = 'name') => {
if (!content) return;
let el = document.querySelector(`meta[${attr}="${name}"]`);
if (!el) {
el = document.createElement('meta');
el.setAttribute(attr, name);
document.head.appendChild(el);
}
el.setAttribute('content', content);
};
setMetaTag('description', metaDesc);
setMetaTag('keywords', metaKeywords);
setMetaTag('og:title', pageTitle, 'property');
setMetaTag('og:description', metaDesc, 'property');
setMetaTag('og:type', 'article', 'property');
setMetaTag('og:url', window.location.href, 'property');
if (blog.ogImage || blog.coverImage || blog.image) {
setMetaTag('og:image', assetUrl(blog.ogImage || blog.coverImage || blog.image), 'property');
}
setMetaTag('twitter:card', 'summary_large_image');
setMetaTag('twitter:title', pageTitle);
setMetaTag('twitter:description', metaDesc);
let canonical = document.querySelector('link[rel="canonical"]');
if (!canonical) {
canonical = document.createElement('link');
canonical.setAttribute('rel', 'canonical');
document.head.appendChild(canonical);
}
canonical.setAttribute('href', blog.canonicalURL || window.location.href);
let scriptTag = document.getElementById('json-ld-article-schema');
if (!scriptTag) {
scriptTag = document.createElement('script');
scriptTag.id = 'json-ld-article-schema';
scriptTag.type = 'application/ld+json';
document.head.appendChild(scriptTag);
}
const jsonLdData = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
'headline': blog.title,
'description': metaDesc,
'image': assetUrl(blog.ogImage || blog.coverImage || blog.image || '/assets/cavin_logo.svg'),
'datePublished': blog.publishDate || blog.date || new Date().toISOString(),
'author': {
'@type': 'Person',
'name': blog.author || 'Cavin Infotech',
'jobTitle': blog.authorDesignation || ''
},
'publisher': {
'@type': 'Organization',
'name': 'Cavin Infotech',
'logo': {
'@type': 'ImageObject',
'url': window.location.origin + assetUrl('/assets/cavin_logo.svg')
}
},
'mainEntityOfPage': {
'@type': 'WebPage',
'@id': blog.canonicalURL || window.location.href
},
'keywords': metaKeywords
};
scriptTag.textContent = JSON.stringify(jsonLdData);
return () => {
document.title = prevTitle;
if (scriptTag) scriptTag.remove();
};
}, [blog]);
if (!blog) {
return (
<div style={{
background: '#020611',
color: '#ffffff',
minHeight: '100vh',
paddingTop: '8rem',
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
}}>
<h2 style={{ fontSize: '2rem', fontWeight: 700, marginBottom: '1rem' }}>Article Not Found</h2>
<p style={{ color: '#94a3b8', marginBottom: '2rem' }}>The requested publication could not be loaded.</p>
<button
onClick={() => onNavigate('Blogs')}
style={{
background: 'linear-gradient(135deg, #ffaa00 0%, #ff8800 100%)',
color: '#000000',
border: 'none',
padding: '0.8rem 2rem',
borderRadius: '50px',
fontWeight: 700,
cursor: 'pointer',
boxShadow: '0 4px 20px rgba(255, 170, 0, 0.3)'
}}
>
Return to Blogs
</button>
</div>
);
}
const bgPatternUrl = assetUrl('/assets/Background pattern About Us Hero section.svg');
const spotlightUrl = assetUrl('/assets/Spotlight vector.svg');
const authorName = (typeof blog.authorName === 'string' && blog.authorName.trim())
? blog.authorName.trim()
: (typeof blog.author === 'string' && blog.author.trim()
? blog.author.trim()
: (blog.author?.firstname ? `${blog.author.firstname} ${blog.author.lastname || ''}`.trim() : 'Cavin Editorial Team'));
const authorRole = typeof blog.authorDesignation === 'string' ? blog.authorDesignation : 'Chief AI Architect & Head of Data Engineering';
const authorAvatar = '/assets/arvindh_agentic_ai.jpg';
const relatedArticles = allBlogs.filter(b => b.id !== blog.id).slice(0, 2);
const handleNewsletterSubmit = (e) => {
e.preventDefault();
if (newsletterEmail) {
setNewsletterSubmitted(true);
setTimeout(() => {
setNewsletterSubmitted(false);
setNewsletterEmail('');
}, 5000);
}
};
const handleCopyLink = () => {
navigator.clipboard.writeText(window.location.href);
setCopied(true);
setTimeout(() => setCopied(false), 2500);
};
const handleSocialShare = (platform) => {
const url = encodeURIComponent(window.location.href);
const text = encodeURIComponent(blog.title);
let shareUrl = '';
if (platform === 'twitter') {
shareUrl = `https://twitter.com/intent/tweet?url=${url}&text=${text}`;
} else if (platform === 'facebook') {
shareUrl = `https://www.facebook.com/sharer/sharer.php?u=${url}`;
} else if (platform === 'linkedin') {
shareUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${url}`;
}
if (shareUrl) window.open(shareUrl, '_blank', 'noopener,noreferrer');
};
return (
<div style={{
background: '#020611',
color: '#ffffff',
minHeight: '100vh',
position: 'relative',
overflow: 'hidden',
paddingTop: isMobile ? '5.5rem' : '7rem',
paddingBottom: '6rem'
}}>
{/* Background Pattern SVG Overlay - Fixed Hero Height Only */}
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: isMobile ? '450px' : '580px',
backgroundImage: `url('${bgPatternUrl}')`,
backgroundSize: 'cover',
backgroundPosition: 'center top',
opacity: 0.85,
pointerEvents: 'none',
zIndex: 0,
WebkitMaskImage: 'linear-gradient(to bottom, rgba(0,0,0,1) 60%, rgba(0,0,0,0) 100%)',
maskImage: 'linear-gradient(to bottom, rgba(0,0,0,1) 60%, rgba(0,0,0,0) 100%)',
}}
/>
{/* Spotlight Vector SVG at Top-Left Corner - Fixed Hero Height Only */}
<div
style={{
position: 'absolute',
top: 0,
left: 0,
width: isMobile ? '100%' : '85%',
height: isMobile ? '450px' : '580px',
maxWidth: '1250px',
backgroundImage: `url('${spotlightUrl}')`,
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'top left',
pointerEvents: 'none',
zIndex: 1,
mixBlendMode: 'screen',
opacity: 0.85,
WebkitMaskImage: 'linear-gradient(to bottom, rgba(0,0,0,1) 60%, rgba(0,0,0,0) 100%)',
maskImage: 'linear-gradient(to bottom, rgba(0,0,0,1) 60%, rgba(0,0,0,0) 100%)',
}}
/>
{/* Main Content Container */}
<div style={{
position: 'relative',
zIndex: 2,
maxWidth: '1140px',
margin: '0 auto',
padding: isMobile ? '0 1.25rem' : '0 2.5rem'
}}>
{/* Top Breadcrumbs & Back Button */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '2rem',
flexWrap: 'wrap',
gap: '1rem'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', fontSize: '0.85rem', color: '#64748b' }}>
<span style={{ cursor: 'pointer', transition: 'color 0.2s' }} onClick={() => onNavigate('Home')}>Home</span>
<span>/</span>
<span style={{ cursor: 'pointer', transition: 'color 0.2s' }} onClick={() => onNavigate('Blogs')}>Blogs</span>
<span>/</span>
<span style={{ color: '#38bdf8', fontWeight: 600, maxWidth: isMobile ? '160px' : '320px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{blog.title}
</span>
</div>
<button
onClick={() => onNavigate('Blogs')}
style={{
display: 'flex',
alignItems: 'center',
gap: '0.45rem',
background: 'rgba(255, 255, 255, 0.05)',
border: '1px solid rgba(255, 255, 255, 0.12)',
color: '#ffffff',
padding: '0.45rem 1.1rem',
borderRadius: '50px',
fontSize: '0.82rem',
fontWeight: 600,
cursor: 'pointer',
transition: 'all 0.2s ease'
}}
onMouseEnter={e => { e.currentTarget.style.borderColor = '#38bdf8'; e.currentTarget.style.color = '#38bdf8'; }}
onMouseLeave={e => { e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.12)'; e.currentTarget.style.color = '#ffffff'; }}
>
<ArrowLeft size={14} /> Back to Blogs
</button>
</div>
{/* HERO DESIGN SECTION */}
<div style={{ marginBottom: '1.75rem' }}>
{/* Combined Category & Read Time Pill Badge */}
<div style={{ marginBottom: '1.25rem' }}>
<div style={{
display: 'inline-flex',
alignItems: 'center',
gap: '0.65rem',
background: 'rgba(56, 189, 248, 0.08)',
border: '1px solid rgba(56, 189, 248, 0.45)',
borderRadius: '50px',
padding: '0.35rem 1rem',
boxShadow: '0 2px 14px rgba(56, 189, 248, 0.12)'
}}>
<span style={{
color: '#38bdf8',
fontSize: '0.8rem',
fontWeight: 600,
letterSpacing: '0.02em'
}}>
{blog.category || 'Leadership'}
</span>
<span style={{ color: 'rgba(255, 255, 255, 0.3)', fontSize: '0.75rem' }}></span>
<span style={{
color: '#7dd3fc',
fontSize: '0.8rem',
fontWeight: 500
}}>
{blog.readTime || '8 min read'}
</span>
</div>
</div>
{/* Title */}
<h1 style={{
fontSize: isMobile ? '2.1rem' : '3.1rem',
fontWeight: 700,
color: '#ffffff',
lineHeight: 1.22,
letterSpacing: '-0.02em',
marginBottom: '1rem'
}}>
{blog.title}
</h1>
{/* Subtitle / Excerpt */}
{(blog.subtitle || blog.excerpt) && (
<p style={{
fontSize: isMobile ? '1.02rem' : '1.15rem',
color: '#94a3b8',
lineHeight: 1.6,
maxWidth: '860px',
marginBottom: '2.5rem',
fontWeight: 400
}}>
{blog.subtitle || blog.excerpt}
</p>
)}
</div>
{/* HERO FEATURED IMAGE */}
{(blog.image || blog.coverImage || blog.ogImage) && (
<div style={{
width: '100%',
height: isMobile ? '250px' : '520px',
borderRadius: '20px',
overflow: 'hidden',
position: 'relative',
marginBottom: '1.75rem',
border: '1px solid rgba(255, 255, 255, 0.12)',
boxShadow: '0 25px 60px rgba(0, 0, 0, 0.75)'
}}>
<img
src={assetUrl(blog.image || blog.coverImage || blog.ogImage)}
alt={blog.title}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
onError={(e) => {
const titleLower = (blog.title || '').toLowerCase();
const catLower = (blog.category || '').toLowerCase();
let fallback = '/assets/arvindh_agentic_ai.jpg';
if (titleLower.includes('llm')) fallback = '/assets/arvindh_llm_financial.jpg';
else if (titleLower.includes('supply chain')) fallback = '/assets/arvindh_supply_chain.jpg';
else if (titleLower.includes('prodmax')) fallback = '/assets/prodmax_dashboard.png';
else if (titleLower.includes('vr') || titleLower.includes('incidents')) fallback = '/assets/whyus_vr_man.jpg';
else if (titleLower.includes('scada') || titleLower.includes('plc') || titleLower.includes('speed vs')) fallback = '/assets/arvindh_agentic_ai.jpg';
else if (catLower.includes('ai')) fallback = '/assets/arvindh_agentic_ai.jpg';
const resolvedFallback = assetUrl(fallback);
if (e.currentTarget.src !== resolvedFallback) {
e.currentTarget.src = resolvedFallback;
}
}}
/>
</div>
)}
{/* METADATA & ACTION BAR UNDER FEATURED IMAGE */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '3.5rem',
flexWrap: 'wrap',
gap: '1.5rem',
paddingTop: '0.5rem',
paddingBottom: '1.5rem',
borderBottom: '1px solid rgba(255, 255, 255, 0.08)'
}}>
{/* Written by & Published on */}
<div style={{ display: 'flex', alignItems: 'center', gap: isMobile ? '1.75rem' : '3.5rem', flexWrap: 'wrap' }}>
<div>
<div style={{ fontSize: '0.78rem', color: '#94a3b8', marginBottom: '0.2rem', fontWeight: 500 }}>
Written by
</div>
<div style={{ fontSize: '0.98rem', color: '#ffffff', fontWeight: 700 }}>
{authorName}
</div>
</div>
<div>
<div style={{ fontSize: '0.78rem', color: '#94a3b8', marginBottom: '0.2rem', fontWeight: 500 }}>
Published on
</div>
<div style={{ fontSize: '0.98rem', color: '#ffffff', fontWeight: 700 }}>
{blog.date || '11 Jul 2026'}
</div>
</div>
</div>
{/* Social Share & Action Icons */}
<div style={{ display: 'flex', alignItems: 'center', gap: '0.65rem', flexWrap: 'wrap' }}>
<button
onClick={handleCopyLink}
style={{
display: 'flex',
alignItems: 'center',
gap: '0.45rem',
background: 'rgba(255, 255, 255, 0.05)',
border: '1px solid rgba(255, 255, 255, 0.15)',
color: '#ffffff',
padding: '0.5rem 0.95rem',
borderRadius: '8px',
fontSize: '0.82rem',
fontWeight: 500,
cursor: 'pointer',
transition: 'all 0.2s ease'
}}
onMouseEnter={e => { e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.3)'; e.currentTarget.style.background = 'rgba(255, 255, 255, 0.08)'; }}
onMouseLeave={e => { e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.15)'; e.currentTarget.style.background = 'rgba(255, 255, 255, 0.05)'; }}
>
{copied ? <Check size={14} style={{ color: '#10b981' }} /> : <Copy size={14} />}
{copied ? 'Copied!' : 'Copy link'}
</button>
{/* X / Twitter */}
<button
onClick={() => handleSocialShare('twitter')}
title="Share on X"
style={{
width: '36px',
height: '36px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(255, 255, 255, 0.05)',
border: '1px solid rgba(255, 255, 255, 0.15)',
borderRadius: '8px',
color: '#ffffff',
fontSize: '0.85rem',
fontWeight: 700,
cursor: 'pointer',
transition: 'all 0.2s ease'
}}
onMouseEnter={e => { e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.3)'; e.currentTarget.style.background = 'rgba(255, 255, 255, 0.08)'; }}
onMouseLeave={e => { e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.15)'; e.currentTarget.style.background = 'rgba(255, 255, 255, 0.05)'; }}
>
𝕏
</button>
{/* Facebook */}
<button
onClick={() => handleSocialShare('facebook')}
title="Share on Facebook"
style={{
width: '36px',
height: '36px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(255, 255, 255, 0.05)',
border: '1px solid rgba(255, 255, 255, 0.15)',
borderRadius: '8px',
color: '#ffffff',
cursor: 'pointer',
transition: 'all 0.2s ease'
}}
onMouseEnter={e => { e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.3)'; e.currentTarget.style.background = 'rgba(255, 255, 255, 0.08)'; }}
onMouseLeave={e => { e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.15)'; e.currentTarget.style.background = 'rgba(255, 255, 255, 0.05)'; }}
>
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor">
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
</svg>
</button>
{/* LinkedIn */}
<button
onClick={() => handleSocialShare('linkedin')}
title="Share on LinkedIn"
style={{
width: '36px',
height: '36px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(255, 255, 255, 0.05)',
border: '1px solid rgba(255, 255, 255, 0.15)',
borderRadius: '8px',
color: '#ffffff',
cursor: 'pointer',
transition: 'all 0.2s ease'
}}
onMouseEnter={e => { e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.3)'; e.currentTarget.style.background = 'rgba(255, 255, 255, 0.08)'; }}
onMouseLeave={e => { e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.15)'; e.currentTarget.style.background = 'rgba(255, 255, 255, 0.05)'; }}
>
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14m-.5 15.5v-5.3a3.26 3.26 0 0 0-3.26-3.26c-.85 0-1.84.52-2.28 1.3v-1.11h-2.79v8.37h2.79v-4.93c0-0.77.62-1.4 1.39-1.4a1.4 1.4 0 0 1 1.4 1.4v4.93h2.75M6.46 10.9v8.37H9.25V10.9H6.46M7.86 6.74a1.65 1.65 0 0 0-1.66 1.66 1.66 1.66 0 0 0 1.66 1.65c.92 0 1.66-.74 1.66-1.65a1.65 1.65 0 0 0-1.66-1.66z"/>
</svg>
</button>
</div>
</div>
{/* TWO COLUMN ARTICLE CONTENT LAYOUT */}
<div style={{
display: 'grid',
gridTemplateColumns: isMobile ? '1fr' : '1fr 320px',
gap: '3.5rem',
alignItems: 'start',
marginBottom: '5rem'
}}>
{/* MAIN ARTICLE COLUMN */}
<div>
{/* Lead Excerpt Quote Block */}
{blog.excerpt && (
<div style={{
background: 'linear-gradient(135deg, rgba(27, 54, 93, 0.3) 0%, rgba(13, 17, 26, 0.75) 100%)',
borderLeft: '4px solid #1b365d',
borderRadius: '0 16px 16px 0',
padding: isMobile ? '1.25rem 1.5rem' : '1.75rem 2rem',
color: '#ffffff',
fontSize: '1.15rem',
lineHeight: 1.65,
fontWeight: 500,
marginBottom: '2.5rem',
fontStyle: 'italic',
boxShadow: '0 10px 30px rgba(0,0,0,0.4)'
}}>
"{blog.excerpt}"
</div>
)}
{/* Article Content Body */}
<div style={{
color: '#cbd5e1',
fontSize: '1.05rem',
lineHeight: 1.85,
display: 'flex',
flexDirection: 'column',
gap: '1.5rem'
}}>
{(blog.content || blog.excerpt || '')
.replace(/(!\[.*?\]\(.*?\))/g, '\n\n$1\n\n')
.split(/\n\s*\n/)
.map(b => b.trim())
.filter(Boolean)
.map((block, idx) => {
if (block.startsWith('# ')) {
return (
<h1 key={idx} style={{
fontSize: isMobile ? '1.8rem' : '2.2rem',
fontWeight: 800,
color: '#ffffff',
marginTop: '2rem',
marginBottom: '0.75rem',
letterSpacing: '-0.02em',
borderBottom: '1px solid rgba(255, 255, 255, 0.12)',
paddingBottom: '0.75rem'
}}>
{block.replace('# ', '')}
</h1>
);
}
if (block.startsWith('## ')) {
return (
<h2 key={idx} style={{
fontSize: isMobile ? '1.5rem' : '1.75rem',
fontWeight: 700,
color: '#ffffff',
marginTop: '1.75rem',
marginBottom: '0.5rem',
letterSpacing: '-0.01em',
borderBottom: '1px solid rgba(255, 255, 255, 0.08)',
paddingBottom: '0.65rem'
}}>
{block.replace('## ', '')}
</h2>
);
}
if (block.startsWith('### ')) {
return (
<h3 key={idx} style={{
fontSize: '1.35rem',
fontWeight: 700,
color: '#38bdf8',
marginTop: '1.35rem',
marginBottom: '0.35rem'
}}>
{block.replace('### ', '')}
</h3>
);
}
if (block.startsWith('#### ')) {
return (
<h4 key={idx} style={{
fontSize: '1.15rem',
fontWeight: 600,
color: '#7dd3fc',
marginTop: '1.1rem',
marginBottom: '0.3rem'
}}>
{block.replace('#### ', '')}
</h4>
);
}
if (block.startsWith('> ')) {
return (
<blockquote key={idx} style={{
borderLeft: '4px solid #38bdf8',
background: 'rgba(56, 189, 248, 0.05)',
margin: '1rem 0',
padding: '1rem 1.5rem',
borderRadius: '0 12px 12px 0',
fontStyle: 'italic',
color: '#e2e8f0'
}}>
{block.replace('> ', '')}
</blockquote>
);
}
if (block.startsWith('- ') || block.startsWith('* ')) {
const items = block.split('\n');
return (
<ul key={idx} style={{ paddingLeft: '0.5rem', margin: '0.5rem 0', display: 'flex', flexDirection: 'column', gap: '0.6rem' }}>
{items.map((item, itemIdx) => {
const itemText = item.replace(/^[-*]\s+/, '');
return (
<li key={itemIdx} style={{ display: 'flex', alignItems: 'flex-start', gap: '0.75rem', listStyle: 'none' }}>
<CheckCircle2 size={18} style={{ color: '#38bdf8', marginTop: '0.25rem', flexShrink: 0 }} />
<span>{itemText}</span>
</li>
);
})}
</ul>
);
}
if (/^\d+\.\s/.test(block)) {
const items = block.split('\n');
return (
<ol key={idx} style={{ paddingLeft: '1.25rem', margin: '0.5rem 0', display: 'flex', flexDirection: 'column', gap: '0.6rem' }}>
{items.map((item, itemIdx) => {
const itemText = item.replace(/^\d+\.\s+/, '');
return (
<li key={itemIdx} style={{ color: '#cbd5e1' }}>
<span>{itemText}</span>
</li>
);
})}
</ol>
);
}
const mdImgMatch = block.match(/!\[(.*?)\]\((.*?)\)/);
const htmlImgMatch = block.match(/<img[^>]+src=["']([^"']+)["'][^>]*>/i);
if (mdImgMatch || htmlImgMatch) {
const caption = mdImgMatch ? (mdImgMatch[1] ? mdImgMatch[1].trim() : '') : '';
const rawImgUrl = mdImgMatch ? mdImgMatch[2] : htmlImgMatch[1];
const resolvedSrc = assetUrl(rawImgUrl);
const isFilename = caption && /\.(jpg|jpeg|png|gif|webp|svg|bmp)$/i.test(caption);
const displayCaption = caption && !isFilename ? caption : null;
return (
<figure key={idx} style={{ margin: '2rem 0', width: '100%' }}>
<img
src={resolvedSrc}
alt={displayCaption || 'Blog content image'}
style={{
width: '100%',
maxHeight: '520px',
objectFit: 'cover',
borderRadius: '18px',
border: '1px solid rgba(255, 255, 255, 0.12)',
boxShadow: '0 15px 35px rgba(0,0,0,0.5)'
}}
onError={(e) => {
if (rawImgUrl && rawImgUrl.includes('/uploads/')) {
const uploadPath = rawImgUrl.substring(rawImgUrl.indexOf('/uploads/'));
const fallback = assetUrl(uploadPath);
if (e.currentTarget.src !== fallback) {
e.currentTarget.src = fallback;
}
}
}}
/>
{displayCaption && (
<figcaption style={{
fontSize: '0.85rem',
color: '#94a3b8',
marginTop: '0.6rem',
textAlign: 'center',
fontStyle: 'italic'
}}>
{displayCaption}
</figcaption>
)}
</figure>
);
}
return <p key={idx} style={{ margin: 0 }}>{block}</p>;
})}
</div>
{/* TAG BADGES SECTION */}
{normalizeTags(blog.tags).length > 0 && (
<div style={{
marginTop: '2.5rem',
paddingTop: '1.5rem',
borderTop: '1px solid rgba(255, 255, 255, 0.08)',
display: 'flex',
alignItems: 'center',
gap: '0.75rem',
flexWrap: 'wrap'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', color: '#94a3b8', fontSize: '0.85rem', fontWeight: 600 }}>
<Tag size={15} style={{ color: '#38bdf8' }} /> Tags:
</div>
{normalizeTags(blog.tags).map((tag, tagIdx) => (
<span
key={tagIdx}
style={{
background: 'rgba(56, 189, 248, 0.08)',
border: '1px solid rgba(56, 189, 248, 0.25)',
color: '#38bdf8',
padding: '0.3rem 0.85rem',
borderRadius: '50px',
fontSize: '0.8rem',
fontWeight: 500,
letterSpacing: '0.01em',
transition: 'all 0.2s ease',
cursor: 'pointer'
}}
onMouseEnter={e => {
e.currentTarget.style.background = 'rgba(56, 189, 248, 0.18)';
e.currentTarget.style.borderColor = 'rgba(56, 189, 248, 0.5)';
}}
onMouseLeave={e => {
e.currentTarget.style.background = 'rgba(56, 189, 248, 0.08)';
e.currentTarget.style.borderColor = 'rgba(56, 189, 248, 0.25)';
}}
>
#{tag}
</span>
))}
</div>
)}
{/* AUTHOR SECTION - MATCHING MOCKUP DESIGN EXACTLY */}
<div style={{
borderLeft: '1.5px solid #ffffff',
paddingLeft: '1.75rem',
marginTop: '4rem',
marginBottom: '2rem'
}}>
{/* Italic Author Bio Paragraph */}
<p style={{
fontStyle: 'italic',
color: '#ffffff',
fontSize: isMobile ? '1.02rem' : '1.12rem',
lineHeight: 1.7,
fontWeight: 400,
margin: 0
}}>
{blog.authorBio || `${authorName} is ${authorRole} at CAVIN INFOTECH FZE, a Dubai-based technology company delivering AR/VR experiences, AI solutions, and digital transformation for real estate and enterprises across the UAE and beyond. Connect with us to explore a smarter way to sell.`}
</p>
{/* Author Profile Row */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: '0.85rem',
marginTop: '1.75rem'
}}>
<img
src={assetUrl(blog.authorAvatar || authorAvatar)}
alt={authorName}
style={{
width: '44px',
height: '44px',
borderRadius: '50%',
objectFit: 'cover',
border: '1px solid rgba(255, 255, 255, 0.2)',
flexShrink: 0
}}
/>
<div>
<div style={{ fontSize: '0.98rem', fontWeight: 700, color: '#ffffff', lineHeight: 1.3 }}>
{authorName}
</div>
<div style={{ fontSize: '0.85rem', color: '#94a3b8', fontWeight: 400, marginTop: '0.15rem' }}>
{authorRole}
</div>
</div>
</div>
</div>
</div>
{/* RIGHT SIDEBAR */}
<div style={{
position: isMobile ? 'relative' : 'sticky',
top: '100px'
}}>
{/* Weekly Newsletter Subscription Card */}
<div style={{
background: '#0d111a',
border: '1px solid rgba(255, 255, 255, 0.08)',
borderRadius: '20px',
padding: '2.25rem 1.75rem',
boxShadow: '0 20px 50px rgba(0, 0, 0, 0.6)',
position: 'relative',
overflow: 'hidden'
}}>
{/* Paperplane Icon Container */}
<div style={{
width: '44px',
height: '44px',
borderRadius: '12px',
background: 'rgba(255, 255, 255, 0.03)',
border: '1px solid rgba(255, 255, 255, 0.12)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#ffffff',
marginBottom: '1.5rem'
}}>
<Send size={18} style={{ transform: 'rotate(-25deg)', transformOrigin: 'center' }} />
</div>
{/* Title */}
<h3 style={{
fontSize: '1.25rem',
fontWeight: 700,
color: '#ffffff',
marginBottom: '0.65rem',
letterSpacing: '-0.01em'
}}>
Weekly newsletter
</h3>
{/* Description */}
<p style={{
fontSize: '0.92rem',
color: '#94a3b8',
lineHeight: 1.55,
marginBottom: '1.5rem'
}}>
No spam. Just the latest releases and tips, interesting articles, and exclusive interviews in your inbox every week.
</p>
{!newsletterSubmitted ? (
<form onSubmit={handleNewsletterSubmit} style={{ display: 'flex', flexDirection: 'column' }}>
{/* Email Input */}
<input
type="email"
required
placeholder="Enter your email"
value={newsletterEmail}
onChange={(e) => setNewsletterEmail(e.target.value)}
style={{
width: '100%',
background: 'rgba(255, 255, 255, 0.03)',
border: '1px solid rgba(255, 255, 255, 0.12)',
borderRadius: '10px',
padding: '0.8rem 1rem',
color: '#ffffff',
fontSize: '0.92rem',
outline: 'none',
boxSizing: 'border-box',
transition: 'border-color 0.2s ease'
}}
onFocus={e => e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.3)'}
onBlur={e => e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.12)'}
/>
{/* Privacy Policy text */}
<div style={{
fontSize: '0.82rem',
color: '#64748b',
marginTop: '0.55rem',
marginBottom: '1.5rem'
}}>
Read about our <a href="#privacy" onClick={(e) => e.preventDefault()} style={{ color: '#94a3b8', textDecoration: 'underline', cursor: 'pointer' }}>privacy policy</a>.
</div>
{/* Subscribe Button */}
<button
type="submit"
style={{
width: '100%',
background: 'linear-gradient(180deg, #1b365d 0%, #132847 100%)',
border: '1px solid rgba(255, 255, 255, 0.08)',
color: '#ffffff',
padding: '0.85rem 1.25rem',
borderRadius: '50px',
fontSize: '0.98rem',
fontWeight: 700,
cursor: 'pointer',
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.4)',
transition: 'all 0.25s ease'
}}
onMouseEnter={e => {
e.currentTarget.style.background = 'linear-gradient(180deg, #224373 0%, #173259 100%)';
e.currentTarget.style.transform = 'translateY(-1px)';
}}
onMouseLeave={e => {
e.currentTarget.style.background = 'linear-gradient(180deg, #1b365d 0%, #132847 100%)';
e.currentTarget.style.transform = 'translateY(0)';
}}
>
Subscribe
</button>
</form>
) : (
<div style={{
background: 'rgba(16, 185, 129, 0.15)',
border: '1px solid rgba(16, 185, 129, 0.3)',
padding: '1rem',
borderRadius: '12px',
color: '#34d399',
fontSize: '0.88rem',
textAlign: 'center',
fontWeight: 600
}}>
Thanks for subscribing! Check your inbox for confirmation.
</div>
)}
</div>
</div>
</div>
{/* BOTTOM "FROM THE BLOG" SECTION - MATCHING MOCKUP DESIGN */}
{relatedArticles.length > 0 && (
<div style={{ paddingTop: '4.5rem', borderTop: '1px solid rgba(255, 255, 255, 0.08)' }}>
<div style={{
display: 'grid',
gridTemplateColumns: isMobile ? '1fr' : '300px 1fr',
gap: isMobile ? '2.5rem' : '3.5rem',
alignItems: 'start'
}}>
{/* Left Column Header & CTA */}
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
<span style={{
color: '#ffaa00',
fontSize: '0.85rem',
fontWeight: 700,
letterSpacing: '0.02em',
marginBottom: '0.6rem'
}}>
Latest
</span>
<h2 style={{
fontSize: '2.4rem',
fontWeight: 800,
color: '#ffffff',
lineHeight: 1.2,
letterSpacing: '-0.02em',
margin: '0 0 0.85rem 0'
}}>
From the blog
</h2>
<p style={{
color: '#94a3b8',
fontSize: '0.92rem',
lineHeight: 1.55,
margin: '0 0 2rem 0',
maxWidth: '260px'
}}>
The latest industry news, interviews, technologies, and resources.
</p>
<button
onClick={() => onNavigate('Home')}
style={{
background: '#091b33',
border: '1px solid rgba(255, 255, 255, 0.15)',
color: '#ffffff',
padding: '0.75rem 1.6rem',
borderRadius: '50px',
fontSize: '0.9rem',
fontWeight: 700,
cursor: 'pointer',
transition: 'all 0.25s ease',
marginBottom: '2.5rem',
boxShadow: '0 4px 15px rgba(0, 0, 0, 0.4)'
}}
onMouseEnter={e => {
e.currentTarget.style.background = '#0e2646';
e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.3)';
}}
onMouseLeave={e => {
e.currentTarget.style.background = '#091b33';
e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.15)';
}}
>
View All Posts
</button>
{/* Chevron Navigation Indicator */}
<div style={{
width: '36px',
height: '36px',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#ffffff',
fontSize: '1.2rem',
cursor: 'pointer',
opacity: 0.8
}}>
<ArrowRight size={18} />
</div>
</div>
{/* Right Column Blog Cards */}
<div style={{
display: 'grid',
gridTemplateColumns: isMobile ? '1fr' : 'repeat(2, minmax(0, 1fr))',
gap: '1.25rem'
}}>
{relatedArticles.map((rel, i) => (
<div
key={i}
onClick={() => { if (onSelectBlog) onSelectBlog(rel); }}
style={{
height: '440px',
borderRadius: '18px',
overflow: 'hidden',
position: 'relative',
cursor: 'pointer',
border: '1px solid rgba(255, 255, 255, 0.12)',
boxShadow: '0 12px 35px rgba(0, 0, 0, 0.5)',
transition: 'all 0.35s cubic-bezier(0.4, 0, 0.2, 1)'
}}
onMouseEnter={e => {
e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.3)';
e.currentTarget.style.transform = 'translateY(-6px)';
e.currentTarget.style.boxShadow = '0 18px 45px rgba(0, 0, 0, 0.7)';
}}
onMouseLeave={e => {
e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.12)';
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = '0 12px 35px rgba(0, 0, 0, 0.5)';
}}
>
{/* Full-bleed card image */}
<img
src={assetUrl(rel.image)}
alt={rel.title}
style={{
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
objectFit: 'cover',
display: 'block'
}}
/>
{/* Dark bottom panel */}
<div style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
padding: '1.25rem 1.25rem 1.15rem',
background: 'rgba(8, 14, 26, 0.88)',
backdropFilter: 'blur(16px)',
WebkitBackdropFilter: 'blur(16px)',
borderTop: '1px solid rgba(255, 255, 255, 0.08)'
}}>
<span style={{
fontSize: '0.68rem',
fontWeight: 700,
textTransform: 'uppercase',
color: '#94a3b8',
letterSpacing: '0.08em',
display: 'block'
}}>
{(rel.category || 'ARTICLE').toUpperCase()}
</span>
<h3 style={{
fontSize: '0.98rem',
fontWeight: 700,
color: '#ffffff',
margin: '0.35rem 0 0.75rem 0',
lineHeight: 1.4,
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden'
}}>
{rel.title}
</h3>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '0.35rem',
color: '#ffffff',
fontSize: '0.85rem',
fontWeight: 700
}}>
Read Blog <ArrowRight size={14} style={{ color: '#ffffff' }} />
</div>
</div>
</div>
))}
</div>
</div>
</div>
)}
</div>
</div>
);
}