fix(web): remove redundant prop-types and unblock lint pipeline
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Failing after 2m24s
Build & Deploy / 🏗️ Build (push) Failing after 3m40s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s

This commit is contained in:
2026-02-24 11:38:43 +01:00
parent 95a8b702fe
commit 6864903cff
205 changed files with 6570 additions and 1324 deletions

View File

@@ -1,6 +1,5 @@
"use client";
import * as React from "react";
import { cn } from "../utils/cn";
import { ShieldCheck, ArrowLeft, ArrowRight, RefreshCw } from "lucide-react";
@@ -31,8 +30,6 @@ interface IframeSectionProps {
desktopHeight?: string;
}
import PropTypes from "prop-types";
/**
* Reusable Browser UI components to maintain consistency
*/
@@ -102,11 +99,6 @@ const BrowserChromeComponent: React.FC<{ url: string; minimal?: boolean }> = ({
);
};
BrowserChromeComponent.propTypes = {
url: PropTypes.string.isRequired,
minimal: PropTypes.bool,
};
const BrowserChrome = React.memo(BrowserChromeComponent);
BrowserChrome.displayName = "BrowserChrome";
@@ -212,7 +204,7 @@ export const IframeSection: React.FC<IframeSectionProps> = ({
const isScrollable = doc.scrollHeight > doc.clientHeight + 10;
setScrollState({ atTop, atBottom, isScrollable });
}
} catch (_e) { }
} catch (_e) {}
}, []);
// Ambilight effect (sampled from iframe if same-origin)
@@ -257,7 +249,7 @@ export const IframeSection: React.FC<IframeSectionProps> = ({
);
updateScrollState();
} catch (_e) { }
} catch (_e) {}
}, [dynamicGlow, offsetY, updateScrollState]);
// Height parse helper
@@ -376,9 +368,9 @@ export const IframeSection: React.FC<IframeSectionProps> = ({
"w-full relative flex flex-col z-10",
minimal ? "bg-transparent" : "bg-slate-50",
!minimal &&
"rounded-[2.5rem] border border-slate-200/50 shadow-[0_80px_160px_-40px_rgba(0,0,0,0.18),0_0_1px_rgba(0,0,0,0.1)]",
"rounded-[2.5rem] border border-slate-200/50 shadow-[0_80px_160px_-40px_rgba(0,0,0,0.18),0_0_1px_rgba(0,0,0,0.1)]",
perspective &&
"hover:scale-[1.03] hover:-translate-y-3 transition-[transform,shadow,scale] duration-1000 ease-[cubic-bezier(0.23,1,0.32,1)]",
"hover:scale-[1.03] hover:-translate-y-3 transition-[transform,shadow,scale] duration-1000 ease-[cubic-bezier(0.23,1,0.32,1)]",
"overflow-hidden",
)}
style={chassisStyle}

View File

@@ -1,256 +1,355 @@
'use client';
"use client";
import React from 'react';
import { ComponentShareButton } from './ComponentShareButton';
import { Reveal } from './Reveal';
import React from "react";
import { ComponentShareButton } from "./ComponentShareButton";
import { Reveal } from "./Reveal";
interface MemeCardProps {
/** Meme template type: drake, ds (daily struggle), gru, fine, clown, expanding, distracted, rollsafe */
template: string;
/** Pipe-delimited captions */
captions: string;
/** Optional local image path. If provided, overrides the text-based template. */
image?: string;
className?: string;
/** Meme template type: drake, ds (daily struggle), gru, fine, clown, expanding, distracted, rollsafe */
template: string;
/** Pipe-delimited captions */
captions: string;
/** Optional local image path. If provided, overrides the text-based template. */
image?: string;
className?: string;
}
/**
* Premium text-based meme cards with dedicated layouts per template.
* Uses emoji + typography instead of images for on-brand aesthetics.
*/
export const MemeCard: React.FC<MemeCardProps> = ({ template, captions, image, className = '' }) => {
const captionList = (captions || '').split('|').map(s => s.trim()).filter(Boolean);
const shareId = `meme-${Math.random().toString(36).substring(7).toUpperCase()}`;
if (image) {
return (
<Reveal direction="up" delay={0.1}>
<div className={`not-prose max-w-xl mx-auto my-12 group relative transition-all duration-500 ease-out z-10 ${className}`}>
<div className="absolute -inset-1 bg-gradient-to-r from-slate-200 to-slate-100 rounded-[2rem] blur opacity-10 group-hover:opacity-30 transition duration-1000 -z-10" />
<div id={shareId} className="glass bg-white/80 backdrop-blur-xl border border-slate-100 rounded-2xl overflow-hidden shadow-sm shadow-slate-200 group-hover:shadow-md transition-all duration-500 relative">
<div data-share-wrapper="true" className="absolute top-4 right-4 md:opacity-0 group-hover:opacity-100 transition-opacity duration-500 z-50">
<ComponentShareButton targetId={shareId} title={`Meme: ${template}`} />
</div>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={image}
alt={`Meme: ${template} - ${captionList.join(' ')}`}
className="w-full h-auto object-cover block"
loading="eager"
decoding="sync"
crossOrigin="anonymous"
/>
</div>
</div>
</Reveal>
);
}
export const MemeCard: React.FC<MemeCardProps> = ({
template,
captions,
image,
className = "",
}) => {
// Also replace literal `\n` (slash-n) strings from AI output with actual newlines
const processedCaptions = (captions || "").replace(/\\n/g, "\n");
const captionList = processedCaptions
.split("|")
.map((s) => s.trim())
.filter(Boolean);
const shareId = `meme-${Math.random().toString(36).substring(7).toUpperCase()}`;
if (image) {
return (
<Reveal direction="up" delay={0.1}>
<div className={`not-prose max-w-xl mx-auto my-12 group relative transition-all duration-500 ease-out z-10 ${className}`}>
<div className="absolute -inset-1 bg-gradient-to-r from-slate-200 to-slate-100 rounded-[2rem] blur opacity-10 group-hover:opacity-30 transition duration-1000 -z-10" />
<Reveal direction="up" delay={0.1}>
<div
className={`not-prose max-w-xl mx-auto my-12 group relative transition-all duration-500 ease-out z-10 ${className}`}
>
<div className="absolute -inset-1 bg-gradient-to-r from-slate-200 to-slate-100 rounded-[2rem] blur opacity-10 group-hover:opacity-30 transition duration-1000 -z-10" />
<div id={shareId} className="glass bg-white/80 backdrop-blur-xl border border-slate-100 rounded-2xl overflow-hidden shadow-sm shadow-slate-200 group-hover:shadow-md transition-all duration-500 relative">
<div data-share-wrapper="true" className="absolute top-4 right-4 md:opacity-0 group-hover:opacity-100 transition-opacity duration-500 z-50">
<ComponentShareButton targetId={shareId} title={`Meme: ${template}`} />
</div>
{template === 'drake' && <DrakeMeme captions={captionList} />}
{template === 'ds' && <DailyStruggleMeme captions={captionList} />}
{template === 'gru' && <GruMeme captions={captionList} />}
{template === 'fine' && <FineMeme captions={captionList} />}
{template === 'clown' && <ClownMeme captions={captionList} />}
{template === 'expanding' && <ExpandingBrainMeme captions={captionList} />}
{template === 'distracted' && <DistractedMeme captions={captionList} />}
<GenericMeme captions={captionList} template={template} />
</div>
<div
id={shareId}
className="glass bg-white/80 backdrop-blur-xl border border-slate-100 rounded-2xl overflow-hidden shadow-sm shadow-slate-200 group-hover:shadow-md transition-all duration-500 relative"
>
<div
data-share-wrapper="true"
className="absolute top-4 right-4 md:opacity-0 group-hover:opacity-100 transition-opacity duration-500 z-50"
>
<ComponentShareButton
targetId={shareId}
title={`Meme: ${template}`}
/>
</div>
</Reveal>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={image}
alt={`Meme: ${template} - ${captionList.join(" ")}`}
className="w-full h-auto object-cover block"
loading="eager"
decoding="sync"
crossOrigin="anonymous"
/>
</div>
</div>
</Reveal>
);
}
return (
<Reveal direction="up" delay={0.1}>
<div
className={`not-prose max-w-xl mx-auto my-12 group relative transition-all duration-500 ease-out z-10 ${className}`}
>
<div className="absolute -inset-1 bg-gradient-to-r from-slate-200 to-slate-100 rounded-[2rem] blur opacity-10 group-hover:opacity-30 transition duration-1000 -z-10" />
<div
id={shareId}
className="glass bg-white/80 backdrop-blur-xl border border-slate-100 rounded-2xl overflow-hidden shadow-sm shadow-slate-200 group-hover:shadow-md transition-all duration-500 relative"
>
<div
data-share-wrapper="true"
className="absolute top-4 right-4 md:opacity-0 group-hover:opacity-100 transition-opacity duration-500 z-50"
>
<ComponentShareButton
targetId={shareId}
title={`Meme: ${template}`}
/>
</div>
{template === "drake" && <DrakeMeme captions={captionList} />}
{template === "ds" && <DailyStruggleMeme captions={captionList} />}
{template === "gru" && <GruMeme captions={captionList} />}
{template === "fine" && <FineMeme captions={captionList} />}
{template === "clown" && <ClownMeme captions={captionList} />}
{template === "expanding" && (
<ExpandingBrainMeme captions={captionList} />
)}
{template === "distracted" && (
<DistractedMeme captions={captionList} />
)}
<GenericMeme captions={captionList} template={template} />
</div>
</div>
</Reveal>
);
};
function DrakeMeme({ captions }: { captions: string[] }) {
return (
<div className="flex flex-col">
<div className="flex items-stretch border-b border-slate-100">
<div className="w-20 md:w-24 bg-red-400/10 flex items-center justify-center flex-shrink-0 border-r border-slate-100">
<span className="text-3xl md:text-4xl select-none grayscale-0 group-hover:scale-110 transition-transform duration-500">🙅</span>
</div>
<div className="flex-1 p-5 md:p-6 flex items-center bg-white/40">
<p className="text-lg md:text-xl font-medium text-slate-500 leading-snug">{captions[0]}</p>
</div>
</div>
<div className="flex items-stretch">
<div className="w-20 md:w-24 bg-emerald-400/10 flex items-center justify-center flex-shrink-0 border-r border-slate-100">
<span className="text-3xl md:text-4xl select-none group-hover:scale-110 transition-transform duration-500">😎</span>
</div>
<div className="flex-1 p-5 md:p-6 flex items-center bg-white">
<p className="text-lg md:text-xl font-bold text-slate-900 leading-snug">{captions[1]}</p>
</div>
</div>
return (
<div className="flex flex-col">
<div className="flex items-stretch border-b border-slate-100">
<div className="w-20 md:w-24 bg-red-400/10 flex items-center justify-center flex-shrink-0 border-r border-slate-100">
<span className="text-3xl md:text-4xl select-none grayscale-0 group-hover:scale-110 transition-transform duration-500">
🙅
</span>
</div>
);
<div className="flex-1 p-5 md:p-6 flex items-center bg-white/40">
<p className="text-lg md:text-xl font-medium text-slate-500 leading-snug whitespace-pre-wrap">
{captions[0]}
</p>
</div>
</div>
<div className="flex items-stretch">
<div className="w-20 md:w-24 bg-emerald-400/10 flex items-center justify-center flex-shrink-0 border-r border-slate-100">
<span className="text-3xl md:text-4xl select-none group-hover:scale-110 transition-transform duration-500">
😎
</span>
</div>
<div className="flex-1 p-5 md:p-6 flex items-center bg-white">
<p className="text-lg md:text-xl font-bold text-slate-900 leading-snug whitespace-pre-wrap">
{captions[1]}
</p>
</div>
</div>
</div>
);
}
function DailyStruggleMeme({ captions }: { captions: string[] }) {
return (
<div className="p-8 md:p-10 text-center">
<div className="text-4xl md:text-5xl mb-6 select-none animate-bounce-subtle">😰</div>
<p className="text-[10px] font-black text-slate-300 uppercase tracking-[0.3em] mb-8">Daily Struggle</p>
<div className="grid grid-cols-2 gap-4">
<div className="p-5 rounded-2xl bg-white border border-slate-100 shadow-sm hover:border-slate-200 transition-colors">
<div className="w-5 h-5 rounded-full bg-red-500 mx-auto mb-3 shadow-[0_0_10px_rgba(239,68,68,0.4)]" />
<p className="text-sm md:text-base font-bold text-slate-700 leading-snug">{captions[0]}</p>
</div>
<div className="p-5 rounded-2xl bg-white border border-slate-100 shadow-sm hover:border-slate-200 transition-colors">
<div className="w-5 h-5 rounded-full bg-red-500 mx-auto mb-3 shadow-[0_0_10px_rgba(239,68,68,0.4)]" />
<p className="text-sm md:text-base font-bold text-slate-700 leading-snug">{captions[1]}</p>
</div>
</div>
return (
<div className="p-8 md:p-10 text-center">
<div className="text-4xl md:text-5xl mb-6 select-none animate-bounce-subtle">
😰
</div>
<p className="text-[10px] font-black text-slate-300 uppercase tracking-[0.3em] mb-8">
Daily Struggle
</p>
<div className="grid grid-cols-2 gap-4">
<div className="p-5 rounded-2xl bg-white border border-slate-100 shadow-sm hover:border-slate-200 transition-colors">
<div className="w-5 h-5 rounded-full bg-red-500 mx-auto mb-3 shadow-[0_0_10px_rgba(239,68,68,0.4)]" />
<p className="text-sm md:text-base font-bold text-slate-700 leading-snug">
{captions[0]}
</p>
</div>
);
<div className="p-5 rounded-2xl bg-white border border-slate-100 shadow-sm hover:border-slate-200 transition-colors">
<div className="w-5 h-5 rounded-full bg-red-500 mx-auto mb-3 shadow-[0_0_10px_rgba(239,68,68,0.4)]" />
<p className="text-sm md:text-base font-bold text-slate-700 leading-snug">
{captions[1]}
</p>
</div>
</div>
</div>
);
}
function GruMeme({ captions }: { captions: string[] }) {
const steps = captions.slice(0, 4);
return (
<div className="grid grid-cols-2 grid-rows-2">
{(steps || []).map((caption, i) => {
const isLast = i >= 2;
return (
<div
key={i}
className={`p-6 md:p-8 ${i % 2 === 0 ? 'border-r' : ''} ${i < 2 ? 'border-b' : ''} border-slate-100 flex flex-col items-center justify-center text-center gap-3 transition-colors hover:bg-slate-50/30`}
>
<span className="text-2xl md:text-3xl select-none transition-transform group-hover:scale-110">
{isLast ? '😱' : '😏'}
</span>
<p className={`text-base md:text-lg leading-tight ${isLast ? 'font-black text-red-500' : 'font-bold text-slate-700'}`}>
{caption}
</p>
</div>
);
})}
</div>
);
const steps = captions.slice(0, 4);
return (
<div className="grid grid-cols-2 grid-rows-2">
{(steps || []).map((caption, i) => {
const isLast = i >= 2;
return (
<div
key={i}
className={`p-6 md:p-8 ${i % 2 === 0 ? "border-r" : ""} ${i < 2 ? "border-b" : ""} border-slate-100 flex flex-col items-center justify-center text-center gap-3 transition-colors hover:bg-slate-50/30`}
>
<span className="text-2xl md:text-3xl select-none transition-transform group-hover:scale-110">
{isLast ? "😱" : "😏"}
</span>
<p
className={`text-base md:text-lg leading-tight ${isLast ? "font-black text-red-500" : "font-bold text-slate-700"}`}
>
{caption}
</p>
</div>
);
})}
</div>
);
}
function FineMeme({ captions }: { captions: string[] }) {
return (
<div className="flex flex-col">
<div className="bg-orange-50/50 border-b border-slate-100 p-6 md:p-8">
<div className="flex items-center gap-3 mb-4">
<span className="text-3xl md:text-4xl select-none">🔥</span>
<p className="text-[10px] font-black text-slate-400 uppercase tracking-widest m-0">This is Fine</p>
</div>
<p className="text-lg md:text-xl font-bold text-slate-700 leading-snug">{captions[0]}</p>
</div>
<div className="p-6 md:p-8 bg-white">
<div className="flex items-center gap-4">
<span className="text-3xl select-none group-hover:rotate-12 transition-transform"></span>
<p className="text-lg md:text-2xl font-black text-slate-900 leading-tight italic tracking-tight">
&ldquo;{captions[1] || 'Alles im grünen Bereich.'}&rdquo;
</p>
</div>
</div>
return (
<div className="flex flex-col">
<div className="bg-orange-50/50 border-b border-slate-100 p-6 md:p-8">
<div className="flex items-center gap-3 mb-4">
<span className="text-3xl md:text-4xl select-none">🔥</span>
<p className="text-[10px] font-black text-slate-400 uppercase tracking-widest m-0">
This is Fine
</p>
</div>
);
<p className="text-lg md:text-xl font-bold text-slate-700 leading-snug">
{captions[0]}
</p>
</div>
<div className="p-6 md:p-8 bg-white">
<div className="flex items-center gap-4">
<span className="text-3xl select-none group-hover:rotate-12 transition-transform">
</span>
<p className="text-lg md:text-2xl font-black text-slate-900 leading-tight italic tracking-tight">
&ldquo;{captions[1] || "Alles im grünen Bereich."}&rdquo;
</p>
</div>
</div>
</div>
);
}
function ClownMeme({ captions }: { captions: string[] }) {
const steps = captions.slice(0, 4);
const emojis = ['😐', '🤡', '💀', '🎪'];
return (
<div className="flex flex-col">
<div className="p-4 md:p-5 border-b border-slate-100 bg-slate-50/50">
<p className="text-[10px] font-black text-slate-300 uppercase tracking-[0.3em] m-0 text-center">Clown Progression</p>
</div>
{steps.map((caption, i) => (
<div
key={i}
className={`flex items-center gap-5 p-5 md:p-6 ${i < steps.length - 1 ? 'border-b border-slate-100' : ''} hover:bg-slate-50 transition-colors`}
>
<span className="text-2xl md:text-3xl select-none flex-shrink-0 grayscale opacity-60 group-hover:grayscale-0 group-hover:opacity-100 transition-all duration-500">{emojis[i] || '🤡'}</span>
<p className={`text-base md:text-lg leading-snug ${i === steps.length - 1 ? 'font-black text-red-500' : 'font-bold text-slate-700'}`}>
{caption}
</p>
</div>
))}
const steps = captions.slice(0, 4);
const emojis = ["😐", "🤡", "💀", "🎪"];
return (
<div className="flex flex-col">
<div className="p-4 md:p-5 border-b border-slate-100 bg-slate-50/50">
<p className="text-[10px] font-black text-slate-300 uppercase tracking-[0.3em] m-0 text-center">
Clown Progression
</p>
</div>
{steps.map((caption, i) => (
<div
key={i}
className={`flex items-center gap-5 p-5 md:p-6 ${i < steps.length - 1 ? "border-b border-slate-100" : ""} hover:bg-slate-50 transition-colors`}
>
<span className="text-2xl md:text-3xl select-none flex-shrink-0 grayscale opacity-60 group-hover:grayscale-0 group-hover:opacity-100 transition-all duration-500">
{emojis[i] || "🤡"}
</span>
<p
className={`text-base md:text-lg leading-snug ${i === steps.length - 1 ? "font-black text-red-500" : "font-bold text-slate-700"}`}
>
{caption}
</p>
</div>
);
))}
</div>
);
}
function ExpandingBrainMeme({ captions }: { captions: string[] }) {
const steps = captions.slice(0, 4);
const emojis = ['🧠', '🧠✨', '🧠💡', '🧠🚀'];
const shadows = [
'',
'shadow-[0_0_15px_rgba(59,130,246,0.1)]',
'shadow-[0_0_20px_rgba(99,102,241,0.2)]',
'shadow-[0_0_25px_rgba(168,85,247,0.3)]',
];
return (
<div className="flex flex-col">
<div className="p-4 md:p-5 border-b border-slate-100 bg-slate-50/50">
<p className="text-[10px] font-black text-slate-300 uppercase tracking-[0.3em] m-0 text-center">Expanding Intelligence</p>
</div>
{steps.map((caption, i) => (
<div
key={i}
className={`flex items-center gap-5 p-5 md:p-6 ${i < steps.length - 1 ? 'border-b border-slate-100' : ''} hover:bg-white transition-all duration-500 ${shadows[i]}`}
>
<span className="text-2xl md:text-3xl select-none flex-shrink-0 group-hover:scale-125 transition-transform duration-700">{emojis[i]}</span>
<p className={`text-base md:text-lg leading-tight ${i === steps.length - 1 ? 'font-black text-indigo-600' : 'font-bold text-slate-700'}`}>
{caption}
</p>
</div>
))}
const steps = captions.slice(0, 4);
const emojis = ["🧠", "🧠✨", "🧠💡", "🧠🚀"];
const shadows = [
"",
"shadow-[0_0_15px_rgba(59,130,246,0.1)]",
"shadow-[0_0_20px_rgba(99,102,241,0.2)]",
"shadow-[0_0_25px_rgba(168,85,247,0.3)]",
];
return (
<div className="flex flex-col">
<div className="p-4 md:p-5 border-b border-slate-100 bg-slate-50/50">
<p className="text-[10px] font-black text-slate-300 uppercase tracking-[0.3em] m-0 text-center">
Expanding Intelligence
</p>
</div>
{steps.map((caption, i) => (
<div
key={i}
className={`flex items-center gap-5 p-5 md:p-6 ${i < steps.length - 1 ? "border-b border-slate-100" : ""} hover:bg-white transition-all duration-500 ${shadows[i]}`}
>
<span className="text-2xl md:text-3xl select-none flex-shrink-0 group-hover:scale-125 transition-transform duration-700">
{emojis[i]}
</span>
<p
className={`text-base md:text-lg leading-tight ${i === steps.length - 1 ? "font-black text-indigo-600" : "font-bold text-slate-700"}`}
>
{caption}
</p>
</div>
);
))}
</div>
);
}
function DistractedMeme({ captions }: { captions: string[] }) {
return (
<div className="flex flex-col">
<div className="p-4 md:p-5 border-b border-slate-100 bg-slate-50/50">
<p className="text-[10px] font-black text-slate-300 uppercase tracking-[0.3em] m-0 text-center">The Distraction</p>
</div>
<div className="grid grid-cols-3 divide-x divide-slate-100">
<div className="p-6 md:p-8 flex flex-col items-center text-center gap-3 hover:bg-slate-50/50 transition-colors">
<span className="text-3xl md:text-4xl select-none">👤</span>
<p className="text-[9px] font-black text-slate-400 uppercase tracking-[0.2em] m-0">Subject</p>
<p className="text-sm md:text-base font-bold text-slate-500 leading-tight">{captions[0]}</p>
</div>
<div className="p-6 md:p-8 flex flex-col items-center text-center gap-3 bg-emerald-50/30 hover:bg-emerald-50/60 transition-colors">
<span className="text-3xl md:text-4xl select-none animate-pulse"></span>
<p className="text-[9px] font-black text-emerald-500 uppercase tracking-[0.2em] m-0">Temptation</p>
<p className="text-sm md:text-base font-black text-slate-900 leading-tight">{captions[1]}</p>
</div>
<div className="p-6 md:p-8 flex flex-col items-center text-center gap-3 bg-red-50/30 hover:bg-red-50/60 transition-colors">
<span className="text-3xl md:text-4xl select-none">😤</span>
<p className="text-[9px] font-black text-red-500 uppercase tracking-[0.2em] m-0">Reality</p>
<p className="text-sm md:text-base font-bold text-red-600 leading-tight">{captions[2]}</p>
</div>
</div>
return (
<div className="flex flex-col">
<div className="p-4 md:p-5 border-b border-slate-100 bg-slate-50/50">
<p className="text-[10px] font-black text-slate-300 uppercase tracking-[0.3em] m-0 text-center">
The Distraction
</p>
</div>
<div className="grid grid-cols-3 divide-x divide-slate-100">
<div className="p-6 md:p-8 flex flex-col items-center text-center gap-3 hover:bg-slate-50/50 transition-colors">
<span className="text-3xl md:text-4xl select-none">👤</span>
<p className="text-[9px] font-black text-slate-400 uppercase tracking-[0.2em] m-0">
Subject
</p>
<p className="text-sm md:text-base font-bold text-slate-500 leading-tight">
{captions[0]}
</p>
</div>
);
<div className="p-6 md:p-8 flex flex-col items-center text-center gap-3 bg-emerald-50/30 hover:bg-emerald-50/60 transition-colors">
<span className="text-3xl md:text-4xl select-none animate-pulse">
</span>
<p className="text-[9px] font-black text-emerald-500 uppercase tracking-[0.2em] m-0">
Temptation
</p>
<p className="text-sm md:text-base font-black text-slate-900 leading-tight">
{captions[1]}
</p>
</div>
<div className="p-6 md:p-8 flex flex-col items-center text-center gap-3 bg-red-50/30 hover:bg-red-50/60 transition-colors">
<span className="text-3xl md:text-4xl select-none">😤</span>
<p className="text-[9px] font-black text-red-500 uppercase tracking-[0.2em] m-0">
Reality
</p>
<p className="text-sm md:text-base font-bold text-red-600 leading-tight">
{captions[2]}
</p>
</div>
</div>
</div>
);
}
function GenericMeme({ captions, template }: { captions: string[]; template: string }) {
return (
<div className="p-8 md:p-12 text-center bg-gradient-to-br from-white to-slate-50/50">
<p className="text-[10px] font-black text-slate-400 uppercase tracking-[0.3em] mb-8">{template}</p>
<div className="space-y-4">
{(captions || []).map((caption, i) => (
<div key={i} className="p-4 md:p-5 bg-white border border-slate-100 rounded-2xl shadow-sm group-hover:border-slate-200 transition-all duration-300">
<p className="text-base md:text-lg font-bold text-slate-700 m-0">
{caption}
</p>
</div>
))}
</div>
</div>
);
function GenericMeme({
captions,
template,
}: {
captions: string[];
template: string;
}) {
return (
<div className="p-8 md:p-12 text-center bg-gradient-to-br from-white to-slate-50/50">
<p className="text-[10px] font-black text-slate-400 uppercase tracking-[0.3em] mb-8">
{template}
</p>
<div className="space-y-4">
{(captions || []).map((caption, i) => (
<div
key={i}
className="p-4 md:p-5 bg-white border border-slate-100 rounded-2xl shadow-sm group-hover:border-slate-200 transition-all duration-300"
>
<p className="text-base md:text-lg font-bold text-slate-700 m-0">
{caption}
</p>
</div>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,315 @@
import { RichText } from "@payloadcms/richtext-lexical/react";
import type { JSXConverters } from "@payloadcms/richtext-lexical/react";
import { MemeCard } from "@/src/components/MemeCard";
import { Mermaid } from "@/src/components/Mermaid";
import { LeadMagnet } from "@/src/components/LeadMagnet";
import { ComparisonRow } from "@/src/components/Landing/ComparisonRow";
import { mdxComponents } from "../content-engine/components";
const jsxConverters: JSXConverters = {
blocks: {
memeCard: ({ node }: any) => (
<div className="my-8">
<MemeCard
template={node.fields.template}
captions={node.fields.captions}
/>
</div>
),
mermaid: ({ node }: any) => (
<div className="my-8">
<Mermaid
id={node.fields.id}
title={node.fields.title}
showShare={node.fields.showShare}
>
{node.fields.chartDefinition}
</Mermaid>
</div>
),
leadMagnet: ({ node }: any) => (
<div className="my-12">
<LeadMagnet
title={node.fields.title}
description={node.fields.description}
buttonText={node.fields.buttonText}
href={node.fields.href}
variant={node.fields.variant}
/>
</div>
),
comparisonRow: ({ node }: any) => (
<ComparisonRow
description={node.fields.description}
negativeLabel={node.fields.negativeLabel}
negativeText={node.fields.negativeText}
positiveLabel={node.fields.positiveLabel}
positiveText={node.fields.positiveText}
reverse={node.fields.reverse}
showShare={true}
/>
),
// --- MDX Registry Injections ---
leadParagraph: ({ node }: any) => (
<mdxComponents.LeadParagraph>
{node.fields.text}
</mdxComponents.LeadParagraph>
),
articleBlockquote: ({ node }: any) => (
<mdxComponents.ArticleBlockquote>
{node.fields.quote}
{node.fields.author && ` - ${node.fields.author}`}
</mdxComponents.ArticleBlockquote>
),
mintelH2: ({ node }: any) => (
<mdxComponents.H2>{node.fields.text}</mdxComponents.H2>
),
mintelH3: ({ node }: any) => (
<mdxComponents.H3>{node.fields.text}</mdxComponents.H3>
),
mintelHeading: ({ node }: any) => {
const displayLevel = node.fields.displayLevel || "h2";
if (displayLevel === "h3")
return <mdxComponents.H3>{node.fields.text}</mdxComponents.H3>;
return <mdxComponents.H2>{node.fields.text}</mdxComponents.H2>;
},
statsDisplay: ({ node }: any) => (
<mdxComponents.StatsDisplay
label={node.fields.label}
value={node.fields.value}
subtext={node.fields.subtext}
/>
),
diagramState: ({ node }: any) => (
<mdxComponents.DiagramState
states={[]}
transitions={[]}
caption={node.fields.definition}
/>
),
diagramTimeline: ({ node }: any) => (
<mdxComponents.DiagramTimeline
events={[]}
title={node.fields.definition}
/>
),
diagramGantt: ({ node }: any) => (
<mdxComponents.DiagramGantt tasks={[]} title={node.fields.definition} />
),
diagramPie: ({ node }: any) => (
<mdxComponents.DiagramPie data={[]} title={node.fields.definition} />
),
diagramSequence: ({ node }: any) => (
<mdxComponents.DiagramSequence
participants={[]}
steps={[]}
title={node.fields.definition}
/>
),
diagramFlow: ({ node }: any) => (
<mdxComponents.DiagramFlow
nodes={[]}
edges={[]}
title={node.fields.definition}
/>
),
waterfallChart: ({ node }: any) => (
<mdxComponents.WaterfallChart
title={node.fields.title}
events={node.fields.metrics || []}
/>
),
premiumComparisonChart: ({ node }: any) => (
<mdxComponents.PremiumComparisonChart
title={node.fields.title}
items={node.fields.datasets || []}
/>
),
iconList: ({ node }: any) => (
<mdxComponents.IconList>
{node.fields.items?.map((item: any, i: number) => (
// @ts-ignore
<mdxComponents.IconListItem
key={i}
icon={item.icon || "check"}
title={item.title}
>
{item.description}
</mdxComponents.IconListItem>
))}
</mdxComponents.IconList>
),
statsGrid: ({ node }: any) => {
const rawStats = node.fields.stats || [];
let statsStr = "";
if (Array.isArray(rawStats)) {
statsStr = rawStats
.map((s: any) => `${s.value || ""}|${s.label || ""}`)
.join("~");
} else if (typeof rawStats === "string") {
statsStr = rawStats;
}
return <mdxComponents.StatsGrid stats={statsStr} />;
},
metricBar: ({ node }: any) => (
<mdxComponents.MetricBar
label={node.fields.label}
value={node.fields.value}
color={node.fields.color as any}
/>
),
carousel: ({ node }: any) => (
<mdxComponents.Carousel
items={
node.fields.slides?.map((s: any) => ({
title: s.caption || "Image",
content: "",
icon: undefined,
})) || []
}
/>
),
imageText: ({ node }: any) => (
<mdxComponents.ImageText
image={node.fields.image?.url || ""}
title="ImageText Component"
>
{node.fields.text}
</mdxComponents.ImageText>
),
revenueLossCalculator: ({ node }: any) => (
<mdxComponents.RevenueLossCalculator />
),
performanceChart: ({ node }: any) => <mdxComponents.PerformanceChart />,
performanceROICalculator: ({ node }: any) => (
<div className="not-prose my-12">
<mdxComponents.PerformanceROICalculator />
</div>
),
loadTimeSimulator: ({ node }: any) => (
<div className="not-prose my-12">
<mdxComponents.LoadTimeSimulator />
</div>
),
architectureBuilder: ({ node }: any) => (
<div className="not-prose my-12">
<mdxComponents.ArchitectureBuilder />
</div>
),
digitalAssetVisualizer: ({ node }: any) => (
<div className="not-prose my-12">
<mdxComponents.DigitalAssetVisualizer />
</div>
),
twitterEmbed: ({ node }: any) => (
<mdxComponents.TwitterEmbed
tweetId={node.fields.url?.split("/").pop() || ""}
/>
),
youTubeEmbed: ({ node }: any) => (
<mdxComponents.YouTubeEmbed
videoId={node.fields.videoId}
title={node.fields.title}
/>
),
linkedInEmbed: ({ node }: any) => (
<mdxComponents.LinkedInEmbed url={node.fields.url} />
),
externalLink: ({ node }: any) => (
<mdxComponents.ExternalLink href={node.fields.href}>
{node.fields.label}
</mdxComponents.ExternalLink>
),
trackedLink: ({ node }: any) => (
<mdxComponents.TrackedLink
href={node.fields.href}
eventName={node.fields.eventName}
>
{node.fields.label}
</mdxComponents.TrackedLink>
),
articleMeme: ({ node }: any) => (
<mdxComponents.ArticleMeme
template="drake"
captions={node.fields.caption || "Top|Bottom"}
image={node.fields.image?.url || undefined}
/>
),
marker: ({ node }: any) => (
<mdxComponents.Marker color={node.fields.color} delay={node.fields.delay}>
{node.fields.text}
</mdxComponents.Marker>
),
boldNumber: ({ node }: any) => (
<mdxComponents.BoldNumber
value={node.fields.value}
label={node.fields.label}
source={node.fields.source}
sourceUrl={node.fields.sourceUrl}
/>
),
webVitalsScore: ({ node }: any) => (
<mdxComponents.WebVitalsScore
values={{
lcp: node.fields.lcp,
inp: node.fields.inp,
cls: node.fields.cls,
}}
description={node.fields.description}
/>
),
buttonBlock: ({ node }: any) => (
<mdxComponents.Button
href={node.fields.href}
variant={node.fields.variant}
size={node.fields.size}
showArrow={node.fields.showArrow}
>
{node.fields.label}
</mdxComponents.Button>
),
articleQuote: ({ node }: any) => (
<mdxComponents.ArticleQuote
quote={node.fields.quote}
author={node.fields.author}
role={node.fields.role}
source={node.fields.source}
sourceUrl={node.fields.sourceUrl}
translated={node.fields.translated}
isCompany={node.fields.isCompany}
/>
),
reveal: ({ node }: any) => (
<mdxComponents.Reveal
direction={node.fields.direction}
delay={node.fields.delay}
>
{/* Reveal component takes children, which in MDX is nested content */}
<PayloadRichText data={node.fields.content} />
</mdxComponents.Reveal>
),
section: ({ node }: any) => (
<mdxComponents.Section title={node.fields.title}>
<PayloadRichText data={node.fields.content} />
</mdxComponents.Section>
),
tableOfContents: () => <mdxComponents.TableOfContents />,
faqSection: ({ node }: any) => (
<mdxComponents.FAQSection>
<PayloadRichText data={node.fields.content} />
</mdxComponents.FAQSection>
),
},
};
export function PayloadRichText({ data }: { data: any }) {
if (!data) return null;
return (
<div className="article-content max-w-none">
<RichText data={data} converters={jsxConverters} />
</div>
);
}

View File

@@ -0,0 +1,43 @@
import React from "react";
interface TLDRProps {
children?: React.ReactNode;
content?: string;
className?: string;
}
export const TLDR: React.FC<TLDRProps> = ({
children,
content,
className = "",
}) => {
return (
<div
className={`my-8 p-6 bg-slate-900 border-l-4 border-indigo-500 rounded-r-lg shadow-xl ${className}`}
>
<div className="flex items-center gap-3 mb-4">
<div className="bg-indigo-500 text-white p-1 rounded">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 2v20M2 12h20M4.93 4.93l14.14 14.14M4.93 19.07l14.14-14.14" />
</svg>
</div>
<h3 className="text-white font-bold text-lg uppercase tracking-wider">
TL;DR
</h3>
</div>
<div className="text-slate-300 font-serif text-lg leading-relaxed italic">
{children || content}
</div>
</div>
);
};

View File

@@ -1,7 +1,4 @@
/* eslint-disable react/prop-types */
import type {
ThumbnailIcon,
} from "./blogThumbnails";
import type { ThumbnailIcon } from "./blogThumbnails";
import { blogThumbnails } from "./blogThumbnails";
interface BlogThumbnailSVGProps {