diff --git a/app/page.tsx b/app/page.tsx index b8ecbbd..5edc8bd 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,74 +1,372 @@ -"use client" -import { useEffect, useRef, useState } from 'react' -import MainMeme from './Components/MainMeme' -import getMemes from './methods/getMemes' -import MemeSettings from './Components/MemeSettings' -import { SettingsContext } from '@/context/SettingsProvider' -import { Spicy_Rice } from 'next/font/google' -import Spinner from './Components/Spinner' -export default function Home() { - const [data, setData] = useState({ success: false, data: { memes: [] } }) - const [isMemeLoading, setIsMemeLoading] = useState(true) - const [selectedMeme, setSelectedMeme] = useState(null) - const [memeSettings, setMemeSettings] = useState(null) - const memeRef = useRef(null) +'use client'; + +import React, { useRef, useState, useEffect } from 'react'; + +// --- types --- +type Item = + | { id: string; kind: 'img'; src: string; x: number; y: number; w: number; h: number } + | { id: string; kind: 'text'; text: string; x: number; y: number }; + +type Category = { name: string; items: string[] }; // items are /stickers/... paths + +// --- configure your sticker sets here --- +const CATEGORIES: Category[] = [ + { + name: 'Accessories', + items: [ + '/stickers/accessories/mask.png', + '/stickers/accessories/chain.png', + '/stickers/accessories/hat.png', + ], + }, + { + name: 'Characters', + items: [ + '/stickers/characters/char1.png', + '/stickers/characters/char2.png', + '/stickers/characters/char3.png', + '/stickers/characters/char4.png', + ], + }, +]; + +const CANVAS_SIZE = 600; + +export default function MemeMakerPage() { + const canvasRef = useRef(null); + const bgRef = useRef(null); // background + const [items, setItems] = useState([]); + const [activeId, setActiveId] = useState(null); + const [dragOffset, setDragOffset] = useState<{ dx: number; dy: number } | null>(null); + const [catIndex, setCatIndex] = useState(0); + + // draw loop useEffect(() => { - const fetchMemes = async () => { - try { - const response: MemeResponse = await getMemes() - if (!response.success) throw new Error('Something went wrong') - setData(() => response) - setSelectedMeme(() => response.data.memes[Math.floor(Math.random() * response.data.memes.length)]) - setIsMemeLoading(() => false) - } catch (error) { - console.log(error) - setData({ success: false, data: { memes: [] } }) - setIsMemeLoading(() => false) - } finally { - setIsMemeLoading(() => false) + const c = canvasRef.current; + if (!c) return; + const ctx = c.getContext('2d'); + if (!ctx) return; + + // background color and drop shadow frame + ctx.clearRect(0, 0, c.width, c.height); + ctx.fillStyle = '#ffffff'; + ctx.fillRect(0, 0, c.width, c.height); + + // draw uploaded background if any + if (bgRef.current) { + ctx.drawImage(bgRef.current, 0, 0, c.width, c.height); + } + + // draw items in-order + for (const it of items) { + if (it.kind === 'img') { + const img = new Image(); + img.src = it.src; + img.onload = () => { + const ctx2 = c.getContext('2d'); + if (!ctx2) return; + ctx2.drawImage(img, it.x, it.y, it.w, it.h); + // highlight active + if (activeId === it.id) { + ctx2.strokeStyle = '#6b7280'; + ctx2.setLineDash([6, 4]); + ctx2.strokeRect(it.x, it.y, it.w, it.h); + ctx2.setLineDash([]); + } + }; + } else { + ctx.font = 'bold 36px Impact, Arial Black, sans-serif'; + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + ctx.fillStyle = '#000000'; + ctx.strokeStyle = '#ffffff'; + ctx.lineWidth = 6; + // outline (white) then fill (black) -> classic meme look + ctx.strokeText(it.text, it.x, it.y); + ctx.fillText(it.text, it.x, it.y); + if (activeId === it.id) { + const w = ctx.measureText(it.text).width; + const h = 40; + ctx.strokeStyle = '#6b7280'; + ctx.setLineDash([6, 4]); + ctx.strokeRect(it.x - 4, it.y - 4, w + 8, h + 8); + ctx.setLineDash([]); + } } } - fetchMemes() - }, []) - useEffect(() => { - setMemeSettings(() => { - const memeSetting: MemeTextSetting[] = [] - let memeSettings: MemeTextSettings = { - id: "", - settings: [] - }; - for (let i = 0; i < selectedMeme?.box_count!; i++) { - memeSetting.push({ - color: '#ffffff', - fontSize: 40, - text: `Text #${i + 1}`, - fontFamily: 'arial', - textAlign: 'left', - verticalAlign: 'top', - width: 500, - height: 500, - textDecoration: 'outline', - outlineColor: '#000000', - outlineWidth: 1, - isAllCaps: true, - isBold: false, - isItalic: false, - opacity: 1 - }) + }, [items, activeId]); + + // hit test to select items + function pick(x: number, y: number) { + // from topmost to bottom + for (let i = items.length - 1; i >= 0; i--) { + const it = items[i]; + if (it.kind === 'img') { + if (x >= it.x && x <= it.x + it.w && y >= it.y && y <= it.y + it.h) { + return it; + } + } else { + // rough text box + const c = canvasRef.current!; + const ctx = c.getContext('2d')!; + ctx.font = 'bold 36px Impact, Arial Black, sans-serif'; + const w = ctx.measureText(it.text).width; + const h = 40; + if (x >= it.x && x <= it.x + w && y >= it.y && y <= it.y + h) return it; } - memeSettings.id = selectedMeme?.id! - memeSettings.settings = memeSetting - return memeSettings - }) - }, [selectedMeme]) - if (isMemeLoading) return - if (!data.success) return

Something went wrong

+ } + return null; + } + + function onPointerDown(e: React.PointerEvent) { + const rect = e.currentTarget.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + const it = pick(x, y); + if (it) { + setActiveId(it.id); + setDragOffset({ dx: x - (it as any).x, dy: y - (it as any).y }); + } else { + setActiveId(null); + } + } + function onPointerMove(e: React.PointerEvent) { + if (!dragOffset || !activeId) return; + const rect = e.currentTarget.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + setItems(prev => + prev.map(it => + it.id === activeId ? { ...it, x: x - dragOffset.dx, y: y - dragOffset.dy } as Item : it + ) + ); + } + function onPointerUp() { + setDragOffset(null); + } + + // add from palette + function addSticker(src: string) { + const img = new Image(); + img.src = src; + img.onload = () => { + const scale = Math.min(1, 300 / Math.max(img.width, img.height)); + const w = img.width * scale; + const h = img.height * scale; + const id = crypto.randomUUID(); + setItems(prev => [...prev, { id, kind: 'img', src, x: 20, y: 20, w, h }]); + setActiveId(id); + }; + } + + // upload helpers + function handleUploadSticker(e: React.ChangeEvent) { + const file = e.target.files?.[0]; + if (!file) return; + const url = URL.createObjectURL(file); + addSticker(url); + e.currentTarget.value = ''; + } + + function handleUploadBackground(e: React.ChangeEvent) { + const file = e.target.files?.[0]; + if (!file) return; + const url = URL.createObjectURL(file); + const img = new Image(); + img.src = url; + img.onload = () => { + bgRef.current = img; + // redraw + setItems(prev => [...prev]); + }; + e.currentTarget.value = ''; + } + + function addText() { + const id = crypto.randomUUID(); + setItems(prev => [...prev, { id, kind: 'text', text: 'Your text', x: 30, y: 30 }]); + setActiveId(id); + } + + function editActiveText() { + if (!activeId) return; + const it = items.find(i => i.id === activeId && i.kind === 'text') as Extract | undefined; + if (!it) return; + const txt = prompt('Edit text:', it.text); + if (txt == null) return; + setItems(prev => prev.map(p => (p.id === it.id ? { ...p, text: txt } : p))); + } + + function resetAll() { + bgRef.current = null; + setItems([]); + setActiveId(null); + } + + function savePNG() { + const c = canvasRef.current; + if (!c) return; + const link = document.createElement('a'); + link.download = 'meme.png'; + link.href = c.toDataURL('image/png'); + link.click(); + } + + // wheel to resize active image + function onWheel(e: React.WheelEvent) { + if (!activeId) return; + setItems(prev => + prev.map(it => { + if (it.id !== activeId || it.kind !== 'img') return it; + const factor = e.deltaY < 0 ? 1.05 : 0.95; + return { ...it, w: it.w * factor, h: it.h * factor }; + }) + ); + } + return ( - -
- - -
-
- ) +
+
+

+ MEME MAKER +

+ +
+ {/* Canvas card */} +
+ + + {/* Controls */} +
+ + + + +
+ +
+ +
+
+ + {/* Palette */} +
+

+ CLICK TO ADD STICKER +

+ + {/* Category header + arrows */} +
+ +
{CATEGORIES[catIndex].name}
+ +
+ + {/* items scroller */} +
+ {CATEGORIES[catIndex].items.map(src => ( + + ))} +
+
+
+
+ + {/* tiny styles for buttons/chips to match the vibe */} + +
+ ); }