feat: dark cinematic hero + public memory write section
- HeroSection: animated warmgold/burgundy/indigo orbs on #060304 bg, film grain CSS overlay, larger glowing typography - WriteSection: new public component – collapsible form (name optional, title, textarea) posts to /api/memories without auth, shows success state and refreshes page - API: remove isAdmin() guard from POST /api/memories, accept author field - DB: migration adds author TEXT column to memories (try/catch for existing DBs) - Types: add author: string | null to Memory type - MemorySection: display "— [Name]" beneath date when author is set - globals.css: grain keyframe animation for film-texture overlay Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,11 +20,7 @@ export async function GET() {
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
if (!isAdmin()) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { title, content } = await req.json()
|
||||
const { title, content, author } = await req.json()
|
||||
if (!title?.trim() || !content?.trim()) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Titel und Inhalt sind erforderlich' },
|
||||
@@ -34,8 +30,8 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
const db = getDb()
|
||||
const result = db
|
||||
.prepare('INSERT INTO memories (title, content) VALUES (?, ?)')
|
||||
.run(title.trim(), content.trim())
|
||||
.prepare('INSERT INTO memories (title, content, author) VALUES (?, ?, ?)')
|
||||
.run(title.trim(), content.trim(), author?.trim() || null)
|
||||
const memory = db
|
||||
.prepare('SELECT * FROM memories WHERE id = ?')
|
||||
.get(result.lastInsertRowid)
|
||||
|
||||
@@ -19,6 +19,27 @@
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
/* Film grain overlay */
|
||||
.grain-overlay {
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='1'/%3E%3C/svg%3E");
|
||||
background-size: 200px 200px;
|
||||
animation: grain 0.8s steps(1) infinite;
|
||||
}
|
||||
|
||||
@keyframes grain {
|
||||
0% { background-position: 0 0; }
|
||||
10% { background-position: -50px -30px; }
|
||||
20% { background-position: 30px -60px; }
|
||||
30% { background-position: -70px 20px; }
|
||||
40% { background-position: 60px -10px; }
|
||||
50% { background-position: -20px 50px; }
|
||||
60% { background-position: 40px 30px; }
|
||||
70% { background-position: -60px -40px; }
|
||||
80% { background-position: 20px 60px; }
|
||||
90% { background-position: -40px -20px; }
|
||||
100% { background-position: 0 0; }
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
|
||||
@@ -4,6 +4,7 @@ import HeroSection from '@/components/HeroSection'
|
||||
import PhotoSlideshow from '@/components/PhotoSlideshow'
|
||||
import PhotoGallery from '@/components/PhotoGallery'
|
||||
import MemorySection from '@/components/MemorySection'
|
||||
import WriteSection from '@/components/WriteSection'
|
||||
import MusicPlayer from '@/components/MusicPlayer'
|
||||
import VideoGallery from '@/components/VideoGallery'
|
||||
|
||||
@@ -81,6 +82,9 @@ export default async function HomePage() {
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Write section – public */}
|
||||
<WriteSection />
|
||||
|
||||
{/* Memories */}
|
||||
<MemorySection memories={memories} />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user