Anime Meme Generator | Create Epic Manga Memes
/**
* THEME CONFIGURATION — Tailwind v4
*/
@theme {
/* Anime Color Palette */
--color-primary: #FF2D95;
--color-primary-foreground: #FFFFFF;
--color-secondary: #00D4FF;
--color-secondary-foreground: #0D0D0D;
--color-destructive: #FF4757;
--color-destructive-foreground: #FFFFFF;
--color-muted: #1A1A2E;
--color-muted-foreground: #A0A0B0;
--color-accent: #9D4EDD;
--color-accent-foreground: #FFFFFF;
--color-surface: #16162A;
--color-surface-foreground: #FFFFFF;
--color-border: #2D2D4A;
--color-input: #1A1A2E;
--color-ring: #FF2D95;
--color-neon-pink: #FF2D95;
--color-neon-cyan: #00D4FF;
--color-neon-purple: #9D4EDD;
--color-dark-bg: #0D0D1A;
--color-card-bg: #16162A;
/* Radius */
--radius-sm: 0.5rem;
--radius-md: 0.75rem;
--radius-lg: 1rem;
--radius-xl: 1.5rem;
/* Fonts */
--font-display: 'Bangers', cursive;
--font-sans: 'Inter', system-ui, sans-serif;
}
import { useState, useRef, useEffect, useCallback } from 'react';
import { Download, Upload, Sparkles, Image as ImageIcon, Type, Palette, RefreshCw } from 'lucide-react';
// Import your anime template images
import animeShocked from '@/assets/generated/anime-shocked.png';
import animeSmug from '@/assets/generated/anime-smug.png';
import animeCrying from '@/assets/generated/anime-crying.png';
import animeDetermined from '@/assets/generated/anime-determined.png';
import animeConfused from '@/assets/generated/anime-confused.png';
import animeVictory from '@/assets/generated/anime-victory.png';
const templates = [
{ id: 'shocked', name: 'Shocked', src: animeShocked },
{ id: 'smug', name: 'Smug', src: animeSmug },
{ id: 'crying', name: 'Crying', src: animeCrying },
{ id: 'determined', name: 'Determined', src: animeDetermined },
{ id: 'confused', name: 'Confused', src: animeConfused },
{ id: 'victory', name: 'Victory', src: animeVictory },
];
const textColors = [
{ name: 'White', value: '#FFFFFF' },
{ name: 'Pink', value: '#FF2D95' },
{ name: 'Cyan', value: '#00D4FF' },
{ name: 'Yellow', value: '#FFE66D' },
{ name: 'Black', value: '#000000' },
];
export default function Index() {
const [selectedTemplate, setSelectedTemplate] = useState(templates[0]);
const [customImage, setCustomImage] = useState
(null);
const [topText, setTopText] = useState('When you realize');
const [bottomText, setBottomText] = useState('The anime was better');
const [fontSize, setFontSize] = useState(32);
const [textColor, setTextColor] = useState('#FFFFFF');
const [strokeColor, setStrokeColor] = useState('#000000');
const canvasRef = useRef(null);
const fileInputRef = useRef(null);
const currentImage = customImage || selectedTemplate.src;
const renderMeme = useCallback(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
// Text settings
ctx.font = `bold ${fontSize}px Bangers, Impact, sans-serif`;
ctx.textAlign = 'center';
ctx.lineWidth = fontSize / 8;
ctx.strokeStyle = strokeColor;
ctx.fillStyle = textColor;
// Draw top text
if (topText) {
const topLines = wrapText(ctx, topText.toUpperCase(), canvas.width - 40);
topLines.forEach((line, i) => {
const y = fontSize + 10 + i * (fontSize + 5);
ctx.strokeText(line, canvas.width / 2, y);
ctx.fillText(line, canvas.width / 2, y);
});
}
// Draw bottom text
if (bottomText) {
const bottomLines = wrapText(ctx, bottomText.toUpperCase(), canvas.width - 40);
bottomLines.forEach((line, i) => {
const y = canvas.height - (bottomLines.length - i - 1) * (fontSize + 5) - 20;
ctx.strokeText(line, canvas.width / 2, y);
ctx.fillText(line, canvas.width / 2, y);
});
}
};
img.src = currentImage;
}, [currentImage, topText, bottomText, fontSize, textColor, strokeColor]);
useEffect(() => {
renderMeme();
}, [renderMeme]);
const wrapText = (ctx: CanvasRenderingContext2D, text: string, maxWidth: number): string[] => {
const words = text.split(' ');
const lines: string[] = [];
let currentLine = '';
for (const word of words) {
const testLine = currentLine + (currentLine ? ' ' : '') + word;
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && currentLine) {
lines.push(currentLine);
currentLine = word;
} else {
currentLine = testLine;
}
}
if (currentLine) lines.push(currentLine);
return lines;
};
const handleImageUpload = (e: React.ChangeEvent) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
setCustomImage(event.target?.result as string);
};
reader.readAsDataURL(file);
}
};
const downloadMeme = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const link = document.createElement('a');
link.download = 'anime-meme.png';
link.href = canvas.toDataURL('image/png');
link.click();
};
const resetToTemplate = () => {
setCustomImage(null);
};
return (
{/* Header */}
{/* Left Column - Controls */}
{/* Template Selection */}
Choose Template
{templates.map((template) => (
))}
{/* Upload Custom Image */}
{customImage && (
)}
{/* Text Controls */}
{/* Style Controls */}
setFontSize(Number(e.target.value))}
className="w-full accent-neon-pink"
/>
{textColors.map((color) => (
{textColors.map((color) => (
{/* Right Column - Preview & Download */}
{/* Download Button */}
{/* Tips */}
💡 Pro Tips for Bloggers
- • Use short, punchy text for maximum impact
- • Upload your own screenshots for custom memes
- • High contrast colors work best for readability
- • Downloaded memes are saved as PNG for best quality
{/* Footer */}
);
}
import { Routes, Route } from 'react-router';
import Index from '@/pages/Index';
export default function App() {
return (
} />
);
}
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router';
import App from './App';
import { AppProviders } from './providers';
import './index.css';
createRoot(document.getElementById('root')!).render(
,
);