style: update recent posts layout to 4 columns matching product categories and fix payload cms text typography styling
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Successful in 2m39s
Build & Deploy / 🏗️ Build (push) Successful in 3m50s
Build & Deploy / 🚀 Deploy (push) Successful in 14s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 6m22s
Build & Deploy / 🔔 Notify (push) Successful in 6s
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Successful in 2m39s
Build & Deploy / 🏗️ Build (push) Successful in 3m50s
Build & Deploy / 🚀 Deploy (push) Successful in 14s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 6m22s
Build & Deploy / 🔔 Notify (push) Successful in 6s
This commit is contained in:
@@ -123,11 +123,87 @@ const jsxConverters: JSXConverters = {
|
|||||||
return <span key="text">{text}</span>;
|
return <span key="text">{text}</span>;
|
||||||
},
|
},
|
||||||
// Use div instead of p for paragraphs to allow nested block elements (like the lists above)
|
// Use div instead of p for paragraphs to allow nested block elements (like the lists above)
|
||||||
paragraph: ({ children }: any) => <div className="mb-4 leading-relaxed">{children}</div>,
|
paragraph: ({ children }: any) => (
|
||||||
// Scale headings to prevent multiple H1s (H1 -> H2, etc)
|
<div className="mb-6 leading-relaxed text-text-secondary">{children}</div>
|
||||||
h1: ({ children }: any) => <h2 className="text-3xl md:text-4xl font-bold my-6">{children}</h2>,
|
),
|
||||||
h2: ({ children }: any) => <h3 className="text-2xl md:text-3xl font-bold my-5">{children}</h3>,
|
// Scale headings to prevent multiple H1s (H1 -> H2, etc) and style natively
|
||||||
h3: ({ children }: any) => <h4 className="text-xl md:text-2xl font-bold my-4">{children}</h4>,
|
heading: ({ node, children }: any) => {
|
||||||
|
const tag = node?.tag;
|
||||||
|
if (tag === 'h1')
|
||||||
|
return (
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold mt-12 mb-6 text-text-primary">{children}</h2>
|
||||||
|
);
|
||||||
|
if (tag === 'h2')
|
||||||
|
return (
|
||||||
|
<h3 className="text-2xl md:text-3xl font-bold mt-10 mb-5 text-text-primary">{children}</h3>
|
||||||
|
);
|
||||||
|
if (tag === 'h3')
|
||||||
|
return (
|
||||||
|
<h4 className="text-xl md:text-2xl font-bold mt-8 mb-4 text-text-primary">{children}</h4>
|
||||||
|
);
|
||||||
|
if (tag === 'h4')
|
||||||
|
return (
|
||||||
|
<h5 className="text-lg md:text-xl font-bold mt-6 mb-4 text-text-primary">{children}</h5>
|
||||||
|
);
|
||||||
|
if (tag === 'h5')
|
||||||
|
return (
|
||||||
|
<h6 className="text-base md:text-lg font-bold mt-6 mb-4 text-text-primary">{children}</h6>
|
||||||
|
);
|
||||||
|
return <h6 className="text-base font-bold mt-6 mb-4 text-text-primary">{children}</h6>;
|
||||||
|
},
|
||||||
|
list: ({ node, children }: any) => {
|
||||||
|
if (node?.listType === 'number') {
|
||||||
|
return (
|
||||||
|
<ol className="list-decimal pl-6 my-6 space-y-2 text-text-secondary marker:text-primary marker:font-bold">
|
||||||
|
{children}
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (node?.listType === 'check') {
|
||||||
|
return <ul className="list-none pl-0 my-6 space-y-2 text-text-secondary">{children}</ul>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ul className="list-disc pl-6 my-6 space-y-2 text-text-secondary marker:text-primary">
|
||||||
|
{children}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
listitem: ({ node, children }: any) => {
|
||||||
|
if (node?.checked != null) {
|
||||||
|
return (
|
||||||
|
<li className="flex items-center gap-3 mb-2 leading-relaxed">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={node.checked}
|
||||||
|
readOnly
|
||||||
|
className="mt-1 w-4 h-4 text-primary focus:ring-primary border-gray-300 rounded"
|
||||||
|
/>
|
||||||
|
<span>{children}</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <li className="mb-2 leading-relaxed">{children}</li>;
|
||||||
|
},
|
||||||
|
quote: ({ children }: any) => (
|
||||||
|
<blockquote className="border-l-4 border-primary bg-primary/5 rounded-r-2xl pl-6 py-4 my-8 italic text-text-secondary shadow-sm">
|
||||||
|
{children}
|
||||||
|
</blockquote>
|
||||||
|
),
|
||||||
|
link: ({ node, children }: any) => {
|
||||||
|
// Handling Payload CMS link nodes
|
||||||
|
const href = node?.fields?.url || node?.url || '#';
|
||||||
|
const newTab = node?.fields?.newTab || node?.newTab;
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
target={newTab ? '_blank' : undefined}
|
||||||
|
rel={newTab ? 'noopener noreferrer' : undefined}
|
||||||
|
className="text-primary no-underline hover:underline font-medium transition-colors"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
blocks: {
|
blocks: {
|
||||||
// ... preserved existing blocks ...
|
// ... preserved existing blocks ...
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Image from 'next/image';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { getAllPosts } from '@/lib/blog';
|
import { getAllPosts } from '@/lib/blog';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
import { Section, Container, Heading, Card, Badge } from '../../components/ui';
|
import { Section, Container, Heading } from '../../components/ui';
|
||||||
|
|
||||||
interface RecentPostsProps {
|
interface RecentPostsProps {
|
||||||
locale: string;
|
locale: string;
|
||||||
@@ -13,7 +13,7 @@ interface RecentPostsProps {
|
|||||||
export default async function RecentPosts({ locale, data }: RecentPostsProps) {
|
export default async function RecentPosts({ locale, data }: RecentPostsProps) {
|
||||||
const t = await getTranslations('Blog');
|
const t = await getTranslations('Blog');
|
||||||
const posts = await getAllPosts(locale);
|
const posts = await getAllPosts(locale);
|
||||||
const recentPosts = posts.slice(0, 3);
|
const recentPosts = posts.slice(0, 4);
|
||||||
|
|
||||||
if (recentPosts.length === 0) return null;
|
if (recentPosts.length === 0) return null;
|
||||||
|
|
||||||
@@ -21,9 +21,9 @@ export default async function RecentPosts({ locale, data }: RecentPostsProps) {
|
|||||||
const subtitle = data?.subtitle || t('latestNews');
|
const subtitle = data?.subtitle || t('latestNews');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section className="bg-neutral py-16 md:py-24">
|
<Section className="bg-neutral-light py-0 md:py-0 lg:py-0">
|
||||||
<Container>
|
<Container className="py-12 md:py-16">
|
||||||
<div className="flex flex-col md:flex-row items-start md:items-end justify-between mb-12 md:mb-16 gap-6">
|
<div className="flex flex-col md:flex-row items-start md:items-end justify-between gap-6">
|
||||||
<Heading level={2} subtitle={subtitle} className="mb-0 text-primary">
|
<Heading level={2} subtitle={subtitle} className="mb-0 text-primary">
|
||||||
{title}
|
{title}
|
||||||
</Heading>
|
</Heading>
|
||||||
@@ -35,91 +35,73 @@ export default async function RecentPosts({ locale, data }: RecentPostsProps) {
|
|||||||
<span className="ml-2 transition-transform group-hover:translate-x-2">→</span>
|
<span className="ml-2 transition-transform group-hover:translate-x-2">→</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul className="grid grid-cols-1 gap-10 list-none p-0 m-0">
|
|
||||||
{recentPosts.map((post, idx) => (
|
|
||||||
<li key={`${post.slug}-${idx}`}>
|
|
||||||
<Link
|
|
||||||
href={`/${locale}/blog/${post.slug}`}
|
|
||||||
className="group block h-full focus:outline-none"
|
|
||||||
>
|
|
||||||
<Card
|
|
||||||
tag="article"
|
|
||||||
className="relative flex flex-col justify-end border-none shadow-lg hover:shadow-2xl transition-all duration-500 rounded-3xl overflow-hidden min-h-[400px] md:min-h-[450px]"
|
|
||||||
>
|
|
||||||
{post.frontmatter.featuredImage && (
|
|
||||||
<>
|
|
||||||
<Image
|
|
||||||
src={post.frontmatter.featuredImage.split('?')[0]}
|
|
||||||
alt={post.frontmatter.title}
|
|
||||||
fill
|
|
||||||
className="absolute inset-0 w-full h-full object-cover transition-transform duration-1000 group-hover:scale-105"
|
|
||||||
style={{
|
|
||||||
objectPosition: `${post.frontmatter.focalX ?? 50}% ${post.frontmatter.focalY ?? 50}%`,
|
|
||||||
}}
|
|
||||||
sizes="(max-width: 768px) 100vw, 100vw"
|
|
||||||
loading="lazy"
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-neutral-dark/10 group-hover:bg-neutral-dark/5 transition-colors duration-500" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="relative z-10 w-full p-6 md:p-8 bg-gradient-to-t from-neutral-dark/90 via-neutral-dark/60 to-transparent flex flex-col pt-32">
|
|
||||||
<div className="flex flex-wrap items-center gap-4 mb-4">
|
|
||||||
{post.frontmatter.category && (
|
|
||||||
<Badge variant="accent" className="shadow-md">
|
|
||||||
{post.frontmatter.category}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
{(new Date(post.frontmatter.date) > new Date() ||
|
|
||||||
post.frontmatter.public === false) && (
|
|
||||||
<span className="px-2 py-0.5 border border-white/40 text-white/90 rounded uppercase tracking-widest text-[10px] md:text-xs font-bold bg-neutral-dark/40 shadow-sm">
|
|
||||||
Draft Preview
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-3 text-xs md:text-sm font-bold text-white/80 mb-3 tracking-widest uppercase">
|
|
||||||
<time dateTime={post.frontmatter.date} suppressHydrationWarning>
|
|
||||||
{new Date(post.frontmatter.date).toLocaleDateString(locale, {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
})}
|
|
||||||
</time>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 className="text-xl md:text-2xl font-bold text-white mb-4 group-hover:text-accent transition-colors drop-shadow-md leading-tight max-w-4xl">
|
|
||||||
{post.frontmatter.title}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="mt-auto flex items-center justify-between border-t border-white/20 pt-6">
|
|
||||||
<span className="text-accent text-sm md:text-base font-extrabold group-hover:text-white transition-colors">
|
|
||||||
{t('readMore')}
|
|
||||||
</span>
|
|
||||||
<div className="w-10 h-10 rounded-full bg-white/10 flex items-center justify-center text-accent group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 backdrop-blur-sm border border-white/20">
|
|
||||||
<svg
|
|
||||||
className="w-5 h-5 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>
|
|
||||||
</Card>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
<ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 m-0 p-0 list-none">
|
||||||
|
{recentPosts.map((post, idx) => (
|
||||||
|
<li key={`${post.slug}-${idx}`} className="block">
|
||||||
|
<Link
|
||||||
|
href={`/${locale}/blog/${post.slug}`}
|
||||||
|
className="group block relative h-[400px] md:h-[500px] lg:h-[650px] overflow-hidden border-b md:border-b-0 md:border-r border-white/10 last:border-0 focus:outline-none"
|
||||||
|
>
|
||||||
|
{post.frontmatter.featuredImage && (
|
||||||
|
<>
|
||||||
|
<Image
|
||||||
|
src={post.frontmatter.featuredImage.split('?')[0]}
|
||||||
|
alt={post.frontmatter.title}
|
||||||
|
fill
|
||||||
|
className="object-cover transition-transform duration-1000 group-hover:scale-110"
|
||||||
|
style={{
|
||||||
|
objectPosition: `${post.frontmatter.focalX ?? 50}% ${post.frontmatter.focalY ?? 50}%`,
|
||||||
|
}}
|
||||||
|
sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 25vw"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-primary-dark/40 group-hover:bg-primary-dark/60 transition-all duration-500" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="absolute inset-0 p-8 md:p-10 flex flex-col justify-end text-white">
|
||||||
|
<div className="mb-4 md:mb-6 transform transition-all duration-500 group-hover:-translate-y-4">
|
||||||
|
<div className="flex flex-wrap items-center gap-2 mb-4">
|
||||||
|
{post.frontmatter.category && (
|
||||||
|
<span className="px-3 py-1 bg-accent text-primary-dark rounded-full text-[10px] md:text-xs font-bold uppercase tracking-wider shadow-sm">
|
||||||
|
{post.frontmatter.category}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<time
|
||||||
|
dateTime={post.frontmatter.date}
|
||||||
|
suppressHydrationWarning
|
||||||
|
className="px-3 py-1 text-white/80 text-[10px] md:text-xs font-bold uppercase tracking-widest border border-white/20 rounded-full bg-white/10 backdrop-blur-md"
|
||||||
|
>
|
||||||
|
{new Date(post.frontmatter.date).toLocaleDateString(locale, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
})}
|
||||||
|
</time>
|
||||||
|
{(new Date(post.frontmatter.date) > new Date() ||
|
||||||
|
post.frontmatter.public === false) && (
|
||||||
|
<span className="px-2 py-0.5 border border-white/40 text-white/90 rounded uppercase tracking-widest text-[10px] md:text-xs font-bold bg-neutral-dark/40 shadow-sm">
|
||||||
|
Draft Preview
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl md:text-2xl font-bold mb-2 md:mb-4 leading-tight drop-shadow-md">
|
||||||
|
{post.frontmatter.title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center text-accent font-bold tracking-wider uppercase text-xs md:text-xs opacity-100 md:opacity-0 group-hover:opacity-100 transition-all duration-500 delay-100">
|
||||||
|
{t('readMore')}{' '}
|
||||||
|
<span className="ml-2 transition-transform group-hover:translate-x-2">
|
||||||
|
→
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user