first commit

This commit is contained in:
mstfyldz
2026-08-16 17:18:23 +03:00
commit 5ace6898b6
61 changed files with 12065 additions and 0 deletions
+350
View File
@@ -0,0 +1,350 @@
"use client";
import React, { useState, useEffect } from "react";
import { JournalPost, MoodType, PostType } from "./types";
import { MOODS } from "./mockData";
import { X, Feather, Save, StickyNote, BookOpen, Code, Camera, Volume2 } from "lucide-react";
interface EditPostModalProps {
post: JournalPost | null;
isOpen: boolean;
onClose: () => void;
onUpdatePost: (updatedPost: JournalPost) => void;
}
export const EditPostModal: React.FC<EditPostModalProps> = ({
post,
isOpen,
onClose,
onUpdatePost,
}) => {
const [type, setType] = useState<PostType>("sticky");
const [mood, setMood] = useState<MoodType>("spark");
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [marginNote, setMarginNote] = useState("");
const [tagInput, setTagInput] = useState("");
const [codeSnippet, setCodeSnippet] = useState("");
const [stampedText, setStampedText] = useState("");
const [imageUrl, setImageUrl] = useState("");
const [imageCaption, setImageCaption] = useState("");
const [likes, setLikes] = useState(0);
useEffect(() => {
if (post) {
setType(post.type);
setMood(post.mood);
setTitle(post.title || "");
setContent(post.content);
setMarginNote(post.marginNotes ? post.marginNotes.join(", ") : "");
setTagInput(post.tags ? post.tags.join(", ") : "");
setCodeSnippet(post.codeSnippet || "");
setStampedText(post.stampedText || "");
setImageUrl(post.imageUrl || "");
setImageCaption(post.imageCaption || "");
setLikes(post.likes || 0);
}
}, [post]);
if (!isOpen || !post) return null;
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!content.trim()) return;
const moodObj = MOODS.find((m) => m.id === mood) || MOODS[0];
const tags = tagInput
.split(",")
.map((t) => t.trim().replace(/^#/, ""))
.filter((t) => t.length > 0);
const updated: JournalPost = {
...post,
type,
mood,
moodLabel: moodObj.label,
title: title.trim() ? title : undefined,
content,
marginNotes: marginNote.trim()
? marginNote.split(",").map((n) => n.trim())
: undefined,
tags: tags.length > 0 ? tags : ["genel"],
stampedText: stampedText.trim() ? stampedText.trim() : undefined,
codeSnippet: type === "code" && codeSnippet.trim() ? codeSnippet : undefined,
codeLanguage: type === "code" ? "typescript" : undefined,
imageUrl: type === "polaroid" && imageUrl.trim() ? imageUrl : undefined,
imageCaption: type === "polaroid" && imageCaption.trim() ? imageCaption : undefined,
likes,
};
onUpdatePost(updated);
onClose();
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-xs animate-in fade-in duration-200">
<div
className="relative w-full max-w-xl notebook-sheet rounded-2xl p-6 sm:p-8 shadow-2xl border border-[#18181B]/15 max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
{/* Washi Tape */}
<div className="washi-tape washi-tape-amber top-[-10px] left-1/2 -translate-x-1/2 w-40" />
{/* Header */}
<div className="flex items-center justify-between pb-4 border-b border-[#18181B]/10 mb-5">
<div className="flex items-center gap-2.5">
<div className="w-8 h-8 rounded-full bg-[#D97706] text-white flex items-center justify-center font-bold text-sm">
</div>
<div>
<h2 className="font-heading font-extrabold text-lg text-[#18181B]">
Notu Düzenle (Admin)
</h2>
<p className="text-xs font-mono text-[#787D89]">
ID: {post.id} {post.date}
</p>
</div>
</div>
<button
onClick={onClose}
className="p-1.5 rounded-lg text-[#787D89] hover:text-[#18181B] hover:bg-[#18181B]/5 transition-colors cursor-pointer"
>
<X className="w-5 h-5" />
</button>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
{/* Format Selector */}
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1.5">
Not Formatı:
</label>
<div className="grid grid-cols-4 gap-2">
<button
type="button"
onClick={() => setType("sticky")}
className={`flex items-center justify-center gap-1 p-2 rounded-lg text-xs font-heading font-semibold border transition-all ${
type === "sticky"
? "bg-[#FEF3C7] border-[#D97706] text-[#B45309] shadow-xs"
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
}`}
>
<StickyNote className="w-3.5 h-3.5" />
<span>Post-it</span>
</button>
<button
type="button"
onClick={() => setType("notebook")}
className={`flex items-center justify-center gap-1 p-2 rounded-lg text-xs font-heading font-semibold border transition-all ${
type === "notebook"
? "bg-[#FAF7F2] border-[#18181B] text-[#18181B] shadow-xs font-bold"
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
}`}
>
<BookOpen className="w-3.5 h-3.5" />
<span>Defter</span>
</button>
<button
type="button"
onClick={() => setType("code")}
className={`flex items-center justify-center gap-1 p-2 rounded-lg text-xs font-heading font-semibold border transition-all ${
type === "code"
? "bg-[#1E232D] border-[#1E232D] text-white shadow-xs"
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
}`}
>
<Code className="w-3.5 h-3.5" />
<span>Kod</span>
</button>
<button
type="button"
onClick={() => setType("polaroid")}
className={`flex items-center justify-center gap-1 p-2 rounded-lg text-xs font-heading font-semibold border transition-all ${
type === "polaroid"
? "bg-[#E0F2FE] border-[#0284C7] text-[#0369A1] shadow-xs"
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
}`}
>
<Camera className="w-3.5 h-3.5" />
<span>Polaroid</span>
</button>
</div>
</div>
{/* Mood Selector */}
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1.5">
Ruh Hali (Mood):
</label>
<div className="flex items-center gap-2 overflow-x-auto pb-1 scrollbar-none">
{MOODS.filter((m) => m.id !== "all").map((m) => (
<button
key={m.id}
type="button"
onClick={() => setMood(m.id)}
className={`px-3 py-1.5 rounded-lg text-xs font-heading font-semibold shrink-0 border transition-all ${
mood === m.id
? "bg-[#18181B] text-[#FAF7F2] border-[#18181B]"
: "bg-[#FAF7F2] text-[#52525B] border-[#18181B]/10 hover:bg-[#EFE7D6]"
}`}
>
{m.label}
</button>
))}
</div>
</div>
{/* Title */}
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
Başlık:
</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Başlık..."
className="w-full px-3 py-2 text-sm bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-serif"
/>
</div>
{/* Content */}
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
İçerik:
</label>
<textarea
required
rows={4}
value={content}
onChange={(e) => setContent(e.target.value)}
className="w-full px-3.5 py-2.5 text-sm bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-serif leading-relaxed"
/>
</div>
{/* Stamped Text */}
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
Damga Metni (Örn: ONAYLANDI, SUÇLU, COMPILED):
</label>
<input
type="text"
value={stampedText}
onChange={(e) => setStampedText(e.target.value)}
placeholder="ONAYLANDI"
className="w-full px-3 py-1.5 text-xs font-mono bg-white/80 border border-[#18181B]/15 rounded-lg uppercase"
/>
</div>
{/* Code Snippet */}
{type === "code" && (
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
Kod Parçası:
</label>
<textarea
rows={3}
value={codeSnippet}
onChange={(e) => setCodeSnippet(e.target.value)}
className="w-full px-3.5 py-2 text-xs font-mono bg-[#1E232D] text-emerald-400 border border-white/10 rounded-lg focus:outline-none"
/>
</div>
)}
{/* Polaroid Image URL */}
{type === "polaroid" && (
<div className="space-y-2">
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
Resim URL:
</label>
<input
type="text"
value={imageUrl}
onChange={(e) => setImageUrl(e.target.value)}
placeholder="https://images.unsplash.com/..."
className="w-full px-3 py-1.5 text-xs bg-white/80 border border-[#18181B]/15 rounded-lg font-mono"
/>
</div>
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
Resim Yazısı (Caption):
</label>
<input
type="text"
value={imageCaption}
onChange={(e) => setImageCaption(e.target.value)}
placeholder="Zirve esintisi..."
className="w-full px-3 py-1.5 text-xs bg-white/80 border border-[#18181B]/15 rounded-lg font-handwritten text-lg"
/>
</div>
</div>
)}
{/* Margin Notes Input */}
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
Kenar Notları (Virgülle ayırın):
</label>
<input
type="text"
value={marginNote}
onChange={(e) => setMarginNote(e.target.value)}
placeholder="← tam olarak bu!, not al"
className="w-full px-3 py-1.5 text-sm bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-handwritten text-lg text-[#E11D48]"
/>
</div>
{/* Tags */}
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
Etiketler (Virgülle ayırın):
</label>
<input
type="text"
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
placeholder="felsefe, gece, kahve"
className="w-full px-3 py-1.5 text-xs bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-mono"
/>
</div>
{/* Likes */}
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
Beğeni Sayısı:
</label>
<input
type="number"
value={likes}
onChange={(e) => setLikes(parseInt(e.target.value, 10) || 0)}
className="w-32 px-3 py-1.5 text-xs bg-white/80 border border-[#18181B]/15 rounded-lg font-mono"
/>
</div>
{/* Actions */}
<div className="flex items-center justify-end gap-2.5 pt-3 border-t border-[#18181B]/10">
<button
type="button"
onClick={onClose}
className="px-4 py-2 rounded-lg text-xs font-heading font-semibold text-[#52525B] hover:bg-[#18181B]/5 transition-colors"
>
Vazgeç
</button>
<button
type="submit"
className="flex items-center gap-1.5 px-5 py-2 rounded-lg bg-[#059669] text-white text-xs font-heading font-bold hover:bg-[#047857] shadow-sm transition-all cursor-pointer"
>
<Save className="w-3.5 h-3.5" />
<span>Günü Güncelle</span>
</button>
</div>
</form>
</div>
</div>
);
};
+139
View File
@@ -0,0 +1,139 @@
"use client";
import React, { useState, useEffect } from "react";
import { Search, Sparkles, Plus, Dices, Feather, Clock } from "lucide-react";
interface HeaderProps {
searchQuery: string;
onSearchChange: (q: string) => void;
onNewPostClick: () => void;
onRandomPostClick: () => void;
postCount: number;
}
export const Header: React.FC<HeaderProps> = ({
searchQuery,
onSearchChange,
onNewPostClick,
onRandomPostClick,
postCount,
}) => {
const [timeString, setTimeString] = useState<string>("16:20");
useEffect(() => {
const updateTime = () => {
const now = new Date();
setTimeString(
now.toLocaleTimeString("tr-TR", {
hour: "2-digit",
minute: "2-digit",
})
);
};
updateTime();
const timer = setInterval(updateTime, 30000);
return () => clearInterval(timer);
}, []);
return (
<header className="w-full border-b border-[#18181B]/10 bg-[#FAF7F2]/80 backdrop-blur-md sticky top-0 z-30 transition-colors">
<div className="max-w-6xl mx-auto px-4 sm:px-6 py-3.5 flex flex-col md:flex-row items-center justify-between gap-4">
{/* Brand & Identity */}
<div className="flex items-center gap-3.5 w-full md:w-auto justify-between md:justify-start">
<div className="flex items-center gap-2.5">
<div className="w-9 h-9 rounded-full bg-[#18181B] text-[#FAF7F2] flex items-center justify-center font-heading font-black text-lg shadow-sm border border-[#18181B]/20">
M
</div>
<div>
<div className="flex items-center gap-2">
<span className="font-heading font-extrabold text-xl tracking-tight text-[#18181B]">
mstfyldz
</span>
<span className="text-[11px] font-mono uppercase px-2 py-0.5 rounded bg-[#18181B]/5 text-[#18181B]/70 border border-[#18181B]/10">
v1.0 log
</span>
</div>
<p className="text-xs font-handwritten text-[#787D89] text-base leading-none mt-0.5">
Mustafa Yıldız Kişisel Karalama &amp; Mood Defteri
</p>
</div>
</div>
{/* Mobile CTAs */}
<div className="flex items-center gap-2 md:hidden">
<button
onClick={onRandomPostClick}
title="Rastgele Bir Not Aç"
aria-label="Rastgele Bir Not Aç"
className="p-2 rounded-lg bg-[#FAF7F2] border border-[#18181B]/15 text-[#18181B] hover:bg-[#F0ECE1] transition-all shadow-xs"
>
<Dices className="w-4 h-4" />
</button>
<button
onClick={onNewPostClick}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#E11D48] text-white text-xs font-heading font-bold shadow-xs hover:bg-[#9F1239] transition-all"
>
<Plus className="w-3.5 h-3.5" />
<span>Karala</span>
</button>
</div>
</div>
{/* Telemetry & Search & Actions */}
<div className="flex items-center gap-3 w-full md:w-auto justify-end">
{/* Live Telemetry Pill */}
<div className="hidden lg:flex items-center gap-2 px-3 py-1.5 rounded-full bg-[#F3ECE2] border border-[#18181B]/10 text-[11px] font-mono text-[#52525B]">
<span className="w-2 h-2 rounded-full bg-[#059669] animate-pulse" />
<span>Muğla, TR</span>
<span></span>
<div className="flex items-center gap-1">
<Clock className="w-3 h-3 text-[#787D89]" />
<span>{timeString}</span>
</div>
<span></span>
<span>{postCount} Not Kayıtlı</span>
</div>
{/* Search Box */}
<div className="relative flex-1 md:w-56">
<Search className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2 text-[#787D89]" />
<input
type="text"
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
placeholder="Defterde ara..."
className="w-full pl-8 pr-3 py-1.5 text-xs bg-white/70 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 focus:border-[#E11D48] transition-all placeholder:text-[#A6ABB5] font-serif"
/>
{searchQuery && (
<button
onClick={() => onSearchChange("")}
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-xs text-[#787D89] hover:text-[#18181B]"
>
&times;
</button>
)}
</div>
{/* Desktop Actions */}
<div className="hidden md:flex items-center gap-2">
<button
onClick={onRandomPostClick}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#FAF7F2] border border-[#18181B]/15 text-[#18181B] text-xs font-heading font-medium hover:bg-[#F0ECE1] hover:shadow-xs transition-all cursor-pointer"
>
<Dices className="w-3.5 h-3.5 text-[#D97706]" />
<span>Rastgele Not</span>
</button>
<button
onClick={onNewPostClick}
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg bg-[#E11D48] text-white text-xs font-heading font-bold hover:bg-[#9F1239] shadow-xs hover:shadow transition-all cursor-pointer"
>
<Feather className="w-3.5 h-3.5" />
<span>+ Yeni Düşünce</span>
</button>
</div>
</div>
</div>
</header>
);
};
+450
View File
@@ -0,0 +1,450 @@
"use client";
import React, { useState } from "react";
import { JournalPost } from "./types";
import {
Heart,
MessageSquare,
Tag,
Play,
Pause,
Maximize2,
Terminal,
Volume2,
CornerDownRight,
Check,
Sparkles
} from "lucide-react";
interface JournalCardProps {
post: JournalPost;
onOpenDetail: (post: JournalPost) => void;
onTagClick: (tag: string) => void;
}
export const JournalCard: React.FC<JournalCardProps> = ({
post,
onOpenDetail,
onTagClick,
}) => {
const [likes, setLikes] = useState(post.likes);
const [hasLiked, setHasLiked] = useState(false);
const [isPlayingAudio, setIsPlayingAudio] = useState(false);
const [stampFeedback, setStampFeedback] = useState(false);
const handleLike = (e: React.MouseEvent) => {
e.stopPropagation();
if (!hasLiked) {
setLikes((prev) => prev + 1);
setHasLiked(true);
setStampFeedback(true);
setTimeout(() => setStampFeedback(false), 1200);
} else {
setLikes((prev) => prev - 1);
setHasLiked(false);
}
};
const toggleAudio = (e: React.MouseEvent) => {
e.stopPropagation();
setIsPlayingAudio(!isPlayingAudio);
};
// Render format based on type
if (post.type === "sticky") {
return (
<div
onClick={() => onOpenDetail(post)}
className="group relative bg-[#FEF3C7] border border-[#D97706]/20 p-5 sm:p-6 rounded-lg shadow-sm hover:shadow-md transition-all duration-300 transform hover:-translate-y-1 rotate-[-1deg] cursor-pointer flex flex-col justify-between"
>
{/* Washi Tape across top */}
<div className="washi-tape washi-tape-amber top-[-10px] left-1/2 -translate-x-1/2 w-28" />
<div>
{/* Header */}
<div className="flex items-center justify-between text-xs font-mono text-[#D97706] mb-3 pt-2">
<span className="font-bold">{post.date} {post.timestamp}</span>
{post.stampedText && (
<span className="rubber-stamp text-[9px] text-[#B45309] border-[#B45309] rotate-[3deg]">
{post.stampedText}
</span>
)}
</div>
{/* Content */}
<p className="font-serif text-[#18181B] text-base sm:text-lg leading-snug font-medium mb-4">
&ldquo;{post.content}&rdquo;
</p>
{/* Margin Notes */}
{post.marginNotes && post.marginNotes.length > 0 && (
<div className="my-2 p-2 rounded bg-[#FDE68A]/40 border border-[#D97706]/20">
{post.marginNotes.map((note, idx) => (
<div key={idx} className="font-handwritten text-[#92400E] text-base leading-tight">
{note}
</div>
))}
</div>
)}
</div>
{/* Footer */}
<div className="flex items-center justify-between pt-3 border-t border-[#D97706]/15 mt-3">
<div className="flex items-center gap-1.5 flex-wrap">
{post.tags.map((tag) => (
<button
key={tag}
onClick={(e) => {
e.stopPropagation();
onTagClick(tag);
}}
className="text-[11px] font-mono text-[#92400E] hover:underline"
>
#{tag}
</button>
))}
</div>
<button
onClick={handleLike}
className={`flex items-center gap-1 text-xs font-mono px-2 py-1 rounded transition-colors ${
hasLiked ? "text-[#E11D48] font-bold" : "text-[#B45309] hover:text-[#18181B]"
}`}
>
<Heart className={`w-3.5 h-3.5 ${hasLiked ? "fill-current" : ""}`} />
<span>{likes}</span>
</button>
</div>
</div>
);
}
if (post.type === "polaroid") {
return (
<div
onClick={() => onOpenDetail(post)}
className="group relative bg-[#FAF7F2] border border-[#18181B]/12 p-3 sm:p-4 rounded-sm shadow-sm hover:shadow-lg transition-all duration-300 transform hover:rotate-[0.5deg] rotate-[1deg] cursor-pointer"
>
{/* Washi Tape */}
<div className="washi-tape top-[-10px] left-1/2 -translate-x-1/2 w-32" />
{/* Image Container */}
<div className="w-full aspect-[4/3] rounded-xs overflow-hidden bg-[#18181B]/5 relative mb-3">
{post.imageUrl && (
<img
src={post.imageUrl}
alt={post.imageCaption || "Polaroid memory"}
className="w-full h-full object-cover group-hover:scale-102 transition-transform duration-500"
/>
)}
<div className="absolute bottom-2 right-2 px-2 py-0.5 rounded bg-black/60 backdrop-blur-xs text-white text-[10px] font-mono">
{post.date}
</div>
</div>
{/* Polaroid Handwritten Caption */}
<div className="px-1 pb-1">
<div className="font-handwritten text-[#18181B] text-xl leading-snug font-bold">
{post.imageCaption || post.content}
</div>
<p className="text-xs font-serif text-[#52525B] mt-1.5 line-clamp-2">
{post.content}
</p>
{/* Margin Note */}
{post.marginNotes && post.marginNotes.length > 0 && (
<div className="font-handwritten text-[#0284C7] text-base mt-2 flex items-center gap-1">
<CornerDownRight className="w-3.5 h-3.5 shrink-0" />
<span>{post.marginNotes[0]}</span>
</div>
)}
{/* Footer */}
<div className="flex items-center justify-between pt-2.5 border-t border-[#18181B]/8 mt-3">
<div className="flex items-center gap-1.5">
{post.tags.map((tag) => (
<button
key={tag}
onClick={(e) => {
e.stopPropagation();
onTagClick(tag);
}}
className="text-[10px] font-mono text-[#787D89] hover:text-[#18181B]"
>
#{tag}
</button>
))}
</div>
<button
onClick={handleLike}
className={`flex items-center gap-1 text-xs font-mono transition-colors ${
hasLiked ? "text-[#E11D48] font-bold" : "text-[#787D89] hover:text-[#18181B]"
}`}
>
<Heart className={`w-3.5 h-3.5 ${hasLiked ? "fill-current" : ""}`} />
<span>{likes}</span>
</button>
</div>
</div>
</div>
);
}
if (post.type === "code") {
return (
<div
onClick={() => onOpenDetail(post)}
className="group relative bg-[#1E232D] text-[#FAF7F2] p-5 sm:p-6 rounded-xl shadow-md hover:shadow-xl transition-all duration-300 border border-white/10 cursor-pointer"
>
{/* Terminal Header */}
<div className="flex items-center justify-between pb-3 border-b border-white/10 mb-3">
<div className="flex items-center gap-2">
<div className="flex items-center gap-1.5">
<div className="w-2.5 h-2.5 rounded-full bg-[#EF4444]" />
<div className="w-2.5 h-2.5 rounded-full bg-[#F59E0B]" />
<div className="w-2.5 h-2.5 rounded-full bg-[#10B981]" />
</div>
<span className="text-xs font-mono text-white/60 ml-2">
{post.title || "terminal_scrap.rs"}
</span>
</div>
<span className="text-[10px] font-mono uppercase px-2 py-0.5 rounded bg-white/10 text-white/80">
{post.codeLanguage || "code"}
</span>
</div>
{/* Snippet */}
{post.codeSnippet && (
<pre className="p-3.5 rounded-lg bg-black/40 text-emerald-400 font-mono text-xs overflow-x-auto leading-relaxed border border-white/5 my-2">
<code>{post.codeSnippet}</code>
</pre>
)}
{/* Narrative */}
<p className="text-xs sm:text-sm font-serif text-white/80 mt-3 leading-relaxed">
{post.content}
</p>
{/* Handwritten Margin Note on Dark Card */}
{post.marginNotes && post.marginNotes.length > 0 && (
<div className="font-handwritten text-[#FDE047] text-base mt-2 flex items-center gap-1.5">
<span>&gt;&gt;</span>
<span>{post.marginNotes.join(" • ")}</span>
</div>
)}
{/* Footer */}
<div className="flex items-center justify-between pt-3 border-t border-white/10 mt-4">
<div className="flex items-center gap-2">
{post.tags.map((tag) => (
<button
key={tag}
onClick={(e) => {
e.stopPropagation();
onTagClick(tag);
}}
className="text-[10px] font-mono text-white/60 hover:text-white"
>
#{tag}
</button>
))}
</div>
<button
onClick={handleLike}
className={`flex items-center gap-1 text-xs font-mono ${
hasLiked ? "text-[#E11D48] font-bold" : "text-white/60 hover:text-white"
}`}
>
<Heart className={`w-3.5 h-3.5 ${hasLiked ? "fill-current" : ""}`} />
<span>{likes}</span>
</button>
</div>
</div>
);
}
if (post.type === "audio") {
return (
<div
onClick={() => onOpenDetail(post)}
className="group relative bg-[#F3ECE2] border border-[#18181B]/12 p-5 sm:p-6 rounded-xl shadow-sm hover:shadow-md transition-all duration-300 cursor-pointer"
>
{/* Brass clip top right */}
<div className="binder-clip absolute top-[-6px] right-6 w-8 h-4 rounded-xs" />
{/* Header */}
<div className="flex items-center justify-between text-xs font-mono text-[#52525B] mb-2">
<span className="font-bold flex items-center gap-1.5">
<Volume2 className="w-3.5 h-3.5 text-[#0284C7]" />
SES KAYDI {post.date}
</span>
<span className="text-[11px] font-mono">{post.audioDuration || "01:00"}</span>
</div>
{/* Cassette Tape Mockup */}
<div className="my-3 p-3.5 rounded-lg bg-[#18181B] text-[#FAF7F2] flex items-center gap-3">
<button
onClick={toggleAudio}
title={isPlayingAudio ? "Durdur" : "Dinle"}
aria-label={isPlayingAudio ? "Durdur" : "Dinle"}
className="w-10 h-10 rounded-full bg-[#0284C7] hover:bg-[#0369A1] text-white flex items-center justify-center transition-transform active:scale-95 shrink-0"
>
{isPlayingAudio ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4 ml-0.5" />}
</button>
<div className="flex-1 overflow-hidden">
<div className="text-xs font-mono font-bold truncate text-white">
{post.audioTitle || "voice_note.m4a"}
</div>
{/* Animated Audio Bars */}
<div className="flex items-end gap-1 h-5 mt-1.5">
{[40, 75, 30, 90, 60, 85, 45, 95, 35, 70, 50, 80, 65, 40].map((h, i) => (
<div
key={i}
className={`w-1 rounded-full transition-all duration-150 ${
isPlayingAudio ? "bg-[#38BDF8] animate-pulse" : "bg-white/30"
}`}
style={{
height: isPlayingAudio ? `${Math.max(20, (h * (i % 2 === 0 ? 1.2 : 0.8)) % 100)}%` : `${h * 0.4}%`,
}}
/>
))}
</div>
</div>
</div>
<p className="text-xs sm:text-sm font-serif text-[#18181B] mt-2 leading-relaxed">
{post.content}
</p>
{post.marginNotes && post.marginNotes.length > 0 && (
<div className="font-handwritten text-[#0284C7] text-base mt-2">
* {post.marginNotes[0]}
</div>
)}
{/* Footer */}
<div className="flex items-center justify-between pt-3 border-t border-[#18181B]/8 mt-3">
<div className="flex items-center gap-1.5">
{post.tags.map((tag) => (
<button
key={tag}
onClick={(e) => {
e.stopPropagation();
onTagClick(tag);
}}
className="text-[10px] font-mono text-[#787D89] hover:text-[#18181B]"
>
#{tag}
</button>
))}
</div>
<button
onClick={handleLike}
className={`flex items-center gap-1 text-xs font-mono ${
hasLiked ? "text-[#E11D48] font-bold" : "text-[#787D89] hover:text-[#18181B]"
}`}
>
<Heart className={`w-3.5 h-3.5 ${hasLiked ? "fill-current" : ""}`} />
<span>{likes}</span>
</button>
</div>
</div>
);
}
// Default: Field Notebook Page (Longform / Deep Note)
return (
<div
onClick={() => onOpenDetail(post)}
className="group relative notebook-sheet rounded-xl p-5 sm:p-7 shadow-sm hover:shadow-lg transition-all duration-300 border border-[#18181B]/10 cursor-pointer"
>
{/* Top Binder Clip */}
<div className="binder-clip absolute top-[-7px] left-8 w-10 h-4.5 rounded-xs" />
{/* Header with stamped date and mood badge */}
<div className="flex items-start justify-between gap-2 pb-3 border-b border-[#18181B]/8 mb-4">
<div>
<div className="flex items-center gap-2">
<span className="font-mono text-xs font-bold text-[#18181B]">
{post.date} {post.timestamp}
</span>
<span className="text-[10px] font-mono px-2 py-0.5 rounded-full bg-[#18181B]/5 text-[#52525B]">
{post.moodLabel}
</span>
</div>
{post.title && (
<h3 className="font-heading font-extrabold text-lg sm:text-xl text-[#18181B] mt-1.5 group-hover:text-[#E11D48] transition-colors leading-snug">
{post.title}
</h3>
)}
</div>
{post.stampedText && (
<span className="rubber-stamp text-[10px] text-[#059669] border-[#059669] rotate-[-2deg] shrink-0">
{post.stampedText}
</span>
)}
</div>
{/* Body with Highlighter markers */}
<div className="font-serif text-[#18181B] text-sm sm:text-base leading-relaxed space-y-2">
<p className="line-clamp-4 whitespace-pre-line">
{post.content}
</p>
</div>
{/* Marginalia & Handwritten Comments */}
{post.marginNotes && post.marginNotes.length > 0 && (
<div className="my-3.5 p-3 rounded-lg bg-[#FAF7F2] border border-[#18181B]/8 space-y-1">
<div className="text-[10px] font-mono uppercase text-[#787D89] font-bold">Kenar Notları:</div>
{post.marginNotes.map((note, idx) => (
<div key={idx} className="font-handwritten text-[#E11D48] text-base leading-tight flex items-center gap-1.5">
<span></span>
<span>{note}</span>
</div>
))}
</div>
)}
{/* Footer */}
<div className="flex items-center justify-between pt-3 border-t border-[#18181B]/8 mt-4">
<div className="flex items-center gap-1.5 flex-wrap">
{post.tags.map((tag) => (
<button
key={tag}
onClick={(e) => {
e.stopPropagation();
onTagClick(tag);
}}
className="text-[11px] font-mono px-2 py-0.5 rounded bg-[#F3ECE2] text-[#52525B] hover:bg-[#EFE7D6] hover:text-[#18181B] transition-colors"
>
#{tag}
</button>
))}
</div>
<div className="flex items-center gap-3">
<button
onClick={handleLike}
className={`flex items-center gap-1 text-xs font-mono transition-colors ${
hasLiked ? "text-[#E11D48] font-bold" : "text-[#787D89] hover:text-[#18181B]"
}`}
>
<Heart className={`w-3.5 h-3.5 ${hasLiked ? "fill-current" : ""}`} />
<span>{likes}</span>
</button>
<span className="text-xs font-heading font-semibold text-[#18181B] flex items-center gap-1 group-hover:translate-x-0.5 transition-transform">
<span>Oku</span>
<Maximize2 className="w-3 h-3 text-[#787D89]" />
</span>
</div>
</div>
</div>
);
};
+195
View File
@@ -0,0 +1,195 @@
"use client";
import React from "react";
import { MOODS } from "./mockData";
import { MoodType } from "./types";
import { Zap, Moon, Sparkles, Feather, Camera, BookOpen, Sun, CloudRain, Wind, Radio } from "lucide-react";
interface MoodTrackerProps {
activeMood: MoodType;
onSelectMood: (mood: MoodType) => void;
filteredCount: number;
}
export const MoodTracker: React.FC<MoodTrackerProps> = ({
activeMood,
onSelectMood,
filteredCount,
}) => {
const activeMoodObj = MOODS.find((m) => m.id === activeMood) || MOODS[0];
const getMoodIcon = (iconName: string, className = "w-4 h-4") => {
switch (iconName) {
case "Zap":
return <Zap className={className} />;
case "Moon":
return <Moon className={className} />;
case "Sparkles":
return <Sparkles className={className} />;
case "Feather":
return <Feather className={className} />;
case "Camera":
return <Camera className={className} />;
default:
return <BookOpen className={className} />;
}
};
return (
<div className="w-full relative mb-8">
{/* Notebook Top Header Card */}
<div className="notebook-sheet rounded-2xl p-5 sm:p-7 relative overflow-hidden transition-all duration-300">
{/* Top Paper Header Elements */}
<div className="flex flex-col md:flex-row md:items-start justify-between gap-4 pb-5 border-b border-[#18181B]/10">
<div>
<div className="flex items-center gap-2">
<span className="font-handwritten text-2xl sm:text-3xl text-[#18181B] font-bold tracking-tight">
MOOD TRACKER &amp; WEATHER LOG
</span>
<span className="rubber-stamp text-[10px] text-[#E11D48] border-[#E11D48] rotate-[-2deg] hidden sm:inline-flex">
CANLI SİNYAL
</span>
</div>
<p className="text-xs sm:text-sm font-serif text-[#52525B] mt-1 max-w-xl">
Günün farklı saatlerindeki zihinsel dalgalanmalar, anlık aydınlanmalar ve saçma gözlemler haritası.
</p>
</div>
{/* Stamped Date & Wax Seal Badge */}
<div className="flex items-center gap-3.5 self-start md:self-auto">
<div className="text-right font-mono">
<div className="text-xs uppercase tracking-wider text-[#787D89] font-semibold">GÜNCEL DURUM</div>
<div className="text-sm font-bold text-[#18181B] flex items-center justify-end gap-1.5 mt-0.5">
<span className="w-2 h-2 rounded-full bg-[#E11D48] animate-ping" />
<span>{activeMoodObj.stampText}</span>
</div>
</div>
{/* Interactive Wax Seal */}
<button
onClick={() => onSelectMood("all")}
title="Tüm Arşive Dön"
aria-label="Tüm Arşive Dön"
className="wax-seal w-12 h-12 rounded-full flex flex-col items-center justify-center text-white cursor-pointer relative shrink-0"
>
<span className="font-heading font-black text-lg leading-none">M</span>
<span className="text-[7px] font-mono tracking-tighter uppercase opacity-80 leading-none">LOG</span>
</button>
</div>
</div>
{/* The Atmospheric Mood Frequency Wave */}
<div className="py-4 relative">
<div className="flex items-center justify-between text-[11px] font-mono text-[#787D89] mb-1">
<span className="flex items-center gap-1">
<Sun className="w-3.5 h-3.5 text-[#D97706]" /> 08:00 (Sabah Kahvesi)
</span>
<span className="flex items-center gap-1 hidden sm:flex">
<Wind className="w-3.5 h-3.5 text-[#0284C7]" /> 14:30 (Kaos &amp; Fikirler)
</span>
<span className="flex items-center gap-1">
<CloudRain className="w-3.5 h-3.5 text-[#7C3AED]" /> 23:45 (Gece Melankolisi)
</span>
</div>
{/* SVG Wave Graphic */}
<div className="w-full h-28 sm:h-36 relative my-3 flex items-center justify-center">
<svg
className="w-full h-full"
viewBox="0 0 800 120"
preserveAspectRatio="xMidYMid meet"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
{/* Reference Grid lines */}
<line x1="0" y1="65" x2="800" y2="65" stroke="rgba(24, 24, 27, 0.08)" strokeDasharray="4 4" />
<line x1="0" y1="25" x2="800" y2="25" stroke="rgba(24, 24, 27, 0.04)" strokeDasharray="2 4" />
<line x1="0" y1="105" x2="800" y2="105" stroke="rgba(24, 24, 27, 0.04)" strokeDasharray="2 4" />
{/* Background gradient fill */}
<path
d="M 0 65 C 60 25, 140 25, 200 65 C 260 105, 340 105, 400 65 C 460 25, 540 25, 600 65 C 660 105, 740 105, 800 65 L 800 120 L 0 120 Z"
fill="url(#moodGradient)"
opacity="0.22"
/>
{/* Mathematically precise smooth continuous wave curve */}
<path
d="M 0 65 C 60 25, 140 25, 200 65 C 260 105, 340 105, 400 65 C 460 25, 540 25, 600 65 C 660 105, 740 105, 800 65"
stroke="#18181B"
strokeWidth="2.5"
strokeLinecap="round"
fill="none"
/>
{/* Perfectly round circles positioned exactly at wave peaks & valleys */}
<circle cx="100" cy="45" r="7" fill="#D97706" stroke="#FAF7F2" strokeWidth="2.5" />
<circle cx="300" cy="85" r="7" fill="#E11D48" stroke="#FAF7F2" strokeWidth="2.5" />
<circle cx="500" cy="45" r="7" fill="#059669" stroke="#FAF7F2" strokeWidth="2.5" />
<circle cx="700" cy="85" r="8" fill="#7C3AED" stroke="#FAF7F2" strokeWidth="2.5" className="animate-pulse" />
<defs>
<linearGradient id="moodGradient" x1="0" y1="0" x2="800" y2="0" gradientUnits="userSpaceOnUse">
<stop stopColor="#D97706" />
<stop offset="0.33" stopColor="#E11D48" />
<stop offset="0.66" stopColor="#059669" />
<stop offset="1" stopColor="#7C3AED" />
</linearGradient>
</defs>
</svg>
{/* Handwritten Mood Annotations on Wave */}
<div className="absolute top-1 left-[8%] text-xs font-handwritten text-[#D97706] rotate-[-4deg] hidden sm:block">
"kahve etkisi ⚡"
</div>
<div className="absolute bottom-1 left-[32%] text-xs font-handwritten text-[#E11D48] rotate-[3deg] hidden sm:block">
"ani fikir! 💡"
</div>
<div className="absolute top-1 left-[58%] text-xs font-handwritten text-[#059669] rotate-[-2deg] hidden sm:block">
"sakin anlar 🌿"
</div>
<div className="absolute bottom-1 right-[8%] text-xs font-handwritten text-[#7C3AED] rotate-[-3deg] hidden sm:block">
"gece kodu ☕"
</div>
</div>
</div>
{/* Live Broadcast Status Banner */}
<div className="mt-1 mb-5 px-3.5 py-2.5 rounded-xl bg-[#F4EFE6] border border-[#18181B]/10 flex items-center justify-between gap-3 text-xs">
<div className="flex items-center gap-2 text-[#18181B]">
<Radio className="w-3.5 h-3.5 text-[#E11D48] animate-pulse shrink-0" />
<span className="font-serif">
<strong>Şu anki Zihinsel Frekans:</strong> {activeMoodObj.label} <em>{activeMoodObj.sublabel}</em>
</span>
</div>
<span className="font-mono text-[11px] text-[#787D89] shrink-0">
{filteredCount} sonuç gösteriliyor
</span>
</div>
{/* Mood Filter Pill Tabs */}
<div className="flex items-center gap-2 overflow-x-auto pb-1 scrollbar-none">
{MOODS.map((mood) => {
const isSelected = activeMood === mood.id;
return (
<button
key={mood.id}
onClick={() => onSelectMood(mood.id)}
className={`flex items-center gap-2 px-3.5 py-2 rounded-xl text-xs font-heading font-semibold transition-all duration-200 shrink-0 cursor-pointer ${
isSelected
? "bg-[#18181B] text-[#FAF7F2] shadow-sm translate-y-[-1px]"
: "bg-[#FAF7F2] text-[#52525B] hover:bg-[#EFE7D6] hover:text-[#18181B] border border-[#18181B]/10"
}`}
>
<span style={{ color: isSelected ? "#FAF7F2" : mood.color }}>
{getMoodIcon(mood.iconName, "w-3.5 h-3.5")}
</span>
<span>{mood.label}</span>
</button>
);
})}
</div>
</div>
</div>
);
};
+283
View File
@@ -0,0 +1,283 @@
"use client";
import React, { useState } from "react";
import { JournalPost, MoodType, PostType } from "./types";
import { MOODS } from "./mockData";
import { X, Feather, Sparkles, Tag, StickyNote, BookOpen, Code, Camera, Volume2, Check } from "lucide-react";
interface NewPostModalProps {
isOpen: boolean;
onClose: () => void;
onAddPost: (post: JournalPost) => void;
}
export const NewPostModal: React.FC<NewPostModalProps> = ({
isOpen,
onClose,
onAddPost,
}) => {
const [type, setType] = useState<PostType>("sticky");
const [mood, setMood] = useState<MoodType>("spark");
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [marginNote, setMarginNote] = useState("");
const [tagInput, setTagInput] = useState("");
const [codeSnippet, setCodeSnippet] = useState("");
const [stampedText, setStampedText] = useState("KARALAMA");
if (!isOpen) return null;
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!content.trim()) return;
const now = new Date();
const dateStr = now.toLocaleDateString("tr-TR", {
day: "2-digit",
month: "short",
year: "numeric",
}).toUpperCase();
const timeStr = now.toLocaleTimeString("tr-TR", {
hour: "2-digit",
minute: "2-digit",
});
const moodObj = MOODS.find((m) => m.id === mood) || MOODS[0];
const tags = tagInput
.split(",")
.map((t) => t.trim().replace(/^#/, ""))
.filter((t) => t.length > 0);
if (tags.length === 0) tags.push("yeni-not");
const newPost: JournalPost = {
id: `post-${Date.now()}`,
type,
date: dateStr,
timestamp: timeStr,
mood,
moodLabel: moodObj.label,
title: title.trim() ? title : undefined,
content,
marginNotes: marginNote.trim() ? [marginNote.trim()] : undefined,
tags,
likes: 1,
stampedText: stampedText || moodObj.stampText,
codeSnippet: type === "code" && codeSnippet.trim() ? codeSnippet : undefined,
codeLanguage: type === "code" ? "typescript" : undefined,
};
onAddPost(newPost);
// Reset form
setTitle("");
setContent("");
setMarginNote("");
setTagInput("");
setCodeSnippet("");
onClose();
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-xs animate-in fade-in duration-200">
<div
className="relative w-full max-w-xl notebook-sheet rounded-2xl p-6 sm:p-8 shadow-2xl border border-[#18181B]/15 max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
{/* Washi Tape across top */}
<div className="washi-tape washi-tape-rose top-[-10px] left-1/2 -translate-x-1/2 w-40" />
{/* Header */}
<div className="flex items-center justify-between pb-4 border-b border-[#18181B]/10 mb-5">
<div className="flex items-center gap-2.5">
<div className="w-8 h-8 rounded-full bg-[#E11D48] text-white flex items-center justify-center">
<Feather className="w-4 h-4" />
</div>
<div>
<h2 className="font-heading font-extrabold text-lg text-[#18181B]">
Yeni Düşünce Karala
</h2>
<p className="text-xs font-handwritten text-[#787D89] text-base leading-none">
Aklına gelen o saçma veya harika şeyi hemen kaydet
</p>
</div>
</div>
<button
onClick={onClose}
title="Kapat"
aria-label="Kapat"
className="p-1.5 rounded-lg text-[#787D89] hover:text-[#18181B] hover:bg-[#18181B]/5 transition-colors cursor-pointer"
>
<X className="w-5 h-5" />
</button>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
{/* Format Selector */}
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1.5">
Not Formatı:
</label>
<div className="grid grid-cols-3 gap-2">
<button
type="button"
onClick={() => setType("sticky")}
className={`flex items-center justify-center gap-1.5 p-2 rounded-lg text-xs font-heading font-semibold border transition-all cursor-pointer ${
type === "sticky"
? "bg-[#FEF3C7] border-[#D97706] text-[#B45309] shadow-xs"
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
}`}
>
<StickyNote className="w-3.5 h-3.5" />
<span>Post-it / Fikir</span>
</button>
<button
type="button"
onClick={() => setType("notebook")}
className={`flex items-center justify-center gap-1.5 p-2 rounded-lg text-xs font-heading font-semibold border transition-all cursor-pointer ${
type === "notebook"
? "bg-[#FAF7F2] border-[#18181B] text-[#18181B] shadow-xs font-bold"
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
}`}
>
<BookOpen className="w-3.5 h-3.5" />
<span>Derin Yazı</span>
</button>
<button
type="button"
onClick={() => setType("code")}
className={`flex items-center justify-center gap-1.5 p-2 rounded-lg text-xs font-heading font-semibold border transition-all cursor-pointer ${
type === "code"
? "bg-[#1E232D] border-[#1E232D] text-white shadow-xs"
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
}`}
>
<Code className="w-3.5 h-3.5" />
<span>Kod Kırıntısı</span>
</button>
</div>
</div>
{/* Mood Selector */}
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1.5">
Ruh Hali (Mood):
</label>
<div className="flex items-center gap-2 overflow-x-auto pb-1 scrollbar-none">
{MOODS.filter((m) => m.id !== "all").map((m) => (
<button
key={m.id}
type="button"
onClick={() => setMood(m.id)}
className={`px-3 py-1.5 rounded-lg text-xs font-heading font-semibold shrink-0 border transition-all cursor-pointer ${
mood === m.id
? "bg-[#18181B] text-[#FAF7F2] border-[#18181B]"
: "bg-[#FAF7F2] text-[#52525B] border-[#18181B]/10 hover:bg-[#EFE7D6]"
}`}
>
{m.label}
</button>
))}
</div>
</div>
{/* Title (for notebook or code) */}
{type !== "sticky" && (
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
Başlık (Opsiyonel):
</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Yazı veya karalama başlığı..."
className="w-full px-3 py-2 text-sm bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-serif"
/>
</div>
)}
{/* Main Content */}
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
Düşünce / İçerik:
</label>
<textarea
required
rows={4}
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Aklındakileri dök... (Filtresiz ve dürüst)"
className="w-full px-3.5 py-2.5 text-sm bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-serif leading-relaxed"
/>
</div>
{/* Code Snippet (if code type) */}
{type === "code" && (
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
Kod Parçası:
</label>
<textarea
rows={3}
value={codeSnippet}
onChange={(e) => setCodeSnippet(e.target.value)}
placeholder="// const bug = fixLater();"
className="w-full px-3.5 py-2 text-xs font-mono bg-[#1E232D] text-emerald-400 border border-white/10 rounded-lg focus:outline-none"
/>
</div>
)}
{/* Margin Notes Input */}
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
El Yazısı Kenar Notu:
</label>
<input
type="text"
value={marginNote}
onChange={(e) => setMarginNote(e.target.value)}
placeholder="← tam olarak bu! / not al"
className="w-full px-3 py-1.5 text-sm bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-handwritten text-lg text-[#E11D48]"
/>
</div>
{/* Tags */}
<div>
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
Etiketler (Virgülle ayırın):
</label>
<input
type="text"
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
placeholder="felsefe, gece, kahve, kod"
className="w-full px-3 py-1.5 text-xs bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-mono"
/>
</div>
{/* Actions */}
<div className="flex items-center justify-end gap-2.5 pt-3 border-t border-[#18181B]/10">
<button
type="button"
onClick={onClose}
className="px-4 py-2 rounded-lg text-xs font-heading font-semibold text-[#52525B] hover:bg-[#18181B]/5 transition-colors cursor-pointer"
>
Vazgeç
</button>
<button
type="submit"
className="flex items-center gap-1.5 px-5 py-2 rounded-lg bg-[#E11D48] text-white text-xs font-heading font-bold hover:bg-[#9F1239] shadow-sm transition-all cursor-pointer"
>
<Feather className="w-3.5 h-3.5" />
<span>Deftere Ekle</span>
</button>
</div>
</form>
</div>
</div>
);
};
+179
View File
@@ -0,0 +1,179 @@
"use client";
import React, { useState } from "react";
import { JournalPost } from "./types";
import { X, Heart, Share2, CornerDownRight, Check, BookOpen, Volume2 } from "lucide-react";
interface PostDetailModalProps {
post: JournalPost | null;
onClose: () => void;
onTagClick: (tag: string) => void;
}
export const PostDetailModal: React.FC<PostDetailModalProps> = ({
post,
onClose,
onTagClick,
}) => {
const [likes, setLikes] = useState(post?.likes || 0);
const [hasLiked, setHasLiked] = useState(false);
const [copied, setCopied] = useState(false);
if (!post) return null;
const handleLike = () => {
if (!hasLiked) {
setLikes((prev) => prev + 1);
setHasLiked(true);
} else {
setLikes((prev) => prev - 1);
setHasLiked(false);
}
};
const handleShare = () => {
navigator.clipboard.writeText(window.location.href);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-xs animate-in fade-in duration-200">
<div
className="relative w-full max-w-2xl notebook-sheet rounded-2xl p-6 sm:p-9 shadow-2xl border border-[#18181B]/15 max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
{/* Top binder clip */}
<div className="binder-clip absolute top-[-8px] left-10 w-12 h-5 rounded-xs" />
{/* Close Button */}
<button
onClick={onClose}
title="Kapat"
aria-label="Kapat"
className="absolute top-5 right-5 p-1.5 rounded-lg text-[#787D89] hover:text-[#18181B] hover:bg-[#18181B]/5 transition-colors cursor-pointer"
>
<X className="w-5 h-5" />
</button>
{/* Metadata Header */}
<div className="flex items-center gap-2 text-xs font-mono text-[#787D89] mb-3">
<span className="font-bold text-[#18181B]">{post.date} {post.timestamp}</span>
<span></span>
<span className="px-2 py-0.5 rounded-full bg-[#18181B]/5 text-[#52525B]">
{post.moodLabel}
</span>
{post.stampedText && (
<span className="rubber-stamp text-[10px] text-[#E11D48] border-[#E11D48] ml-auto">
{post.stampedText}
</span>
)}
</div>
{/* Post Title */}
{post.title && (
<h1 className="font-heading font-black text-2xl sm:text-3xl text-[#18181B] leading-tight mb-4">
{post.title}
</h1>
)}
{/* Polaroid Image if any */}
{post.imageUrl && (
<div className="my-4 p-3 bg-white border border-[#18181B]/10 rounded shadow-sm">
<img
src={post.imageUrl}
alt={post.imageCaption || "Polaroid"}
className="w-full max-h-80 object-cover rounded-xs"
/>
{post.imageCaption && (
<div className="font-handwritten text-[#18181B] text-xl font-bold mt-2 px-1">
{post.imageCaption}
</div>
)}
</div>
)}
{/* Code Snippet if any */}
{post.codeSnippet && (
<div className="my-4">
<div className="px-3 py-1.5 bg-[#1E232D] text-white/70 text-xs font-mono rounded-t-lg border border-b-0 border-white/10 flex items-center justify-between">
<span>{post.title || "snippet.rs"}</span>
<span className="uppercase text-[10px]">{post.codeLanguage || "code"}</span>
</div>
<pre className="p-4 rounded-b-lg bg-[#141820] text-emerald-400 font-mono text-xs overflow-x-auto leading-relaxed border border-white/10">
<code>{post.codeSnippet}</code>
</pre>
</div>
)}
{/* Body Content */}
<div className="font-serif text-[#18181B] text-base sm:text-lg leading-relaxed whitespace-pre-line my-4 space-y-4">
{post.content}
</div>
{/* Margin Notes */}
{post.marginNotes && post.marginNotes.length > 0 && (
<div className="my-5 p-4 rounded-xl bg-[#FAF7F2] border border-[#18181B]/10 space-y-1.5">
<div className="text-[11px] font-mono uppercase text-[#787D89] font-bold">
Yazarın El Yazısı Notları:
</div>
{post.marginNotes.map((note, idx) => (
<div key={idx} className="font-handwritten text-[#E11D48] text-xl leading-tight flex items-center gap-2">
<CornerDownRight className="w-4 h-4 shrink-0" />
<span>{note}</span>
</div>
))}
</div>
)}
{/* Tags */}
<div className="flex items-center gap-2 flex-wrap pt-4 border-t border-[#18181B]/10 mt-6">
{post.tags.map((tag) => (
<button
key={tag}
onClick={() => {
onTagClick(tag);
onClose();
}}
className="text-xs font-mono px-2.5 py-1 rounded bg-[#F3ECE2] text-[#52525B] hover:bg-[#EFE7D6] hover:text-[#18181B] transition-colors"
>
#{tag}
</button>
))}
</div>
{/* Actions */}
<div className="flex items-center justify-between pt-4 mt-2">
<div className="flex items-center gap-2">
<button
onClick={handleLike}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg border text-xs font-mono transition-all ${
hasLiked
? "bg-[#FFE4E6] border-[#E11D48] text-[#E11D48] font-bold"
: "bg-white border-[#18181B]/15 text-[#52525B] hover:text-[#18181B]"
}`}
>
<Heart className={`w-4 h-4 ${hasLiked ? "fill-current" : ""}`} />
<span>{likes} Beğeni</span>
</button>
<button
onClick={handleShare}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-white border border-[#18181B]/15 text-xs font-mono text-[#52525B] hover:text-[#18181B] transition-all"
>
{copied ? <Check className="w-4 h-4 text-[#059669]" /> : <Share2 className="w-4 h-4" />}
<span>{copied ? "Kopyalandı!" : "Paylaş"}</span>
</button>
</div>
<button
onClick={onClose}
className="px-4 py-1.5 rounded-lg bg-[#18181B] text-[#FAF7F2] text-xs font-heading font-semibold hover:bg-black transition-colors"
>
Kapat
</button>
</div>
</div>
</div>
);
};
+241
View File
@@ -0,0 +1,241 @@
import { JournalPost, MoodConfig } from "./types";
export const MOODS: MoodConfig[] = [
{
id: "all",
label: "Tüm Notlar",
sublabel: "Bütün karalamalar",
iconName: "BookOpen",
color: "#18181B",
bgColor: "#EFE7D6",
stampText: "ARŞİV",
},
{
id: "focus",
label: "Aşırı Odak & Kaos",
sublabel: "Hyper Focus & Coffee",
iconName: "Zap",
color: "#D97706",
bgColor: "#FEF3C7",
stampText: "⚡ HYPER",
},
{
id: "night",
label: "Gece Melankolisi",
sublabel: "Saat 03:00 kafası",
iconName: "Moon",
color: "#7C3AED",
bgColor: "#EDE9FE",
stampText: "☕ GECE",
},
{
id: "spark",
label: "Fikir Patlaması",
sublabel: "Rastgele aydınlanmalar",
iconName: "Sparkles",
color: "#E11D48",
bgColor: "#FFE4E6",
stampText: "💡 SPARK",
},
{
id: "calm",
label: "Sakin & Karalama",
sublabel: "Dingin anlar & çay",
iconName: "Feather",
color: "#059669",
bgColor: "#D1FAE5",
stampText: "🌿 SAKİN",
},
{
id: "visual",
label: "Polaroid & Kırıntılar",
sublabel: "Görsel hafıza & ses",
iconName: "Camera",
color: "#0284C7",
bgColor: "#E0F2FE",
stampText: "📷 SCRAP",
},
];
export const INITIAL_POSTS: JournalPost[] = [
{
id: "post-today-1",
type: "notebook",
date: "16 AĞU 2026",
timestamp: "16:45",
mood: "spark",
moodLabel: "Fikir Patlaması",
title: "Prisma & PostgreSQL ile Mimari Senkronizasyon",
content: `Bugün defterin tüm veri katmanını uzaktaki PostgreSQL sunucusuna ve Prisma ORM mimarisine bağladık. Metin kırıntılarını kaybetmeden canlı veritabanına taşımak harika hissettiriyor.
Bir sistem karmaşıklaştıkça tasarım dilinin doğallığını koruyabilmesi en kritik başarı kriterimizdir.`,
marginNotes: [
"← veritabanı canlıda! 🚀",
"65.109... sunucusu aktif"
],
tags: ["postgresql", "prisma", "mimari", "backend"],
likes: 18,
stampedText: "SENKRONİZE",
},
{
id: "post-today-2",
type: "sticky",
date: "16 AĞU 2026",
timestamp: "16:55",
mood: "focus",
moodLabel: "Aşırı Odak & Kaos",
content: "NextAuth paketini projeye dahil etmeden, HMAC SHA-256 imzalı HTTP-only çerezler ve .env değişkenleri ile kendi hafif admin yetkilendirmemizi yazdık. Bazen en iyi bağımlılık, hiç eklenmemiş olan bağımlılıktır.",
tags: ["güvenlik", "admin", "cookies", "sadelik"],
likes: 29,
stampedText: "DOĞRU TESPİT",
marginNotes: ["(0 bağımlılık, %100 hafiflik)"],
},
{
id: "post-today-3",
type: "code",
date: "16 AĞU 2026",
timestamp: "17:08",
mood: "night",
moodLabel: "Gece Melankolisi",
title: "SVG Bezier Matematiksel Düzeltmesi",
content: "SVG grafiklerinde preserveAspectRatio parametresini unuttuğunuzda daireleriniz elipse dönüşebilir. Kubik Bezier simetrik dalga denklemi ve 1:1 dairesel oranlar ile frekans haritası mükemmel geometriye kavuştu.",
codeSnippet: `// Kubik Bezier simetrik dalga & dairesel koordinatlar
const wavePath = "M 0 65 C 60 25, 140 25, 200 65 C 260 105, 340 105, 400 65";
// Point 1 (Kahve etkisi): cx=100, cy=45, r=7 (tam yuvarlak)`,
codeLanguage: "typescript",
tags: ["svg", "math", "frontend", "ui-ux"],
likes: 31,
marginNotes: ["r=7 tam yuvarlak", "yamuk dairelere son!"],
stampedText: "FIXED",
},
{
id: "post-today-4",
type: "polaroid",
date: "16 AĞU 2026",
timestamp: "17:15",
mood: "visual",
moodLabel: "Polaroid & Kırıntılar",
content: "Kod derlendi, PostgreSQL veritabanı eşitlendi, harita eğrileri yerini buldu. Akşamüstü Muğla rüzgarında demli bir fincan çay molası.",
imageCaption: "Muğla Esintisi & Veritabanı Zaferi ☕🏔️",
imageUrl: "https://images.unsplash.com/photo-1501785888041-af3ef285b470?auto=format&fit=crop&w=1000&q=80",
tags: ["muğla", "doğa", "kahve", "mola"],
likes: 45,
marginNotes: ["rakım: Muğla", "derleme süresi: 693ms"],
},
{
id: "post-today-5",
type: "audio",
date: "16 AĞU 2026",
timestamp: "17:20",
mood: "calm",
moodLabel: "Sakin & Karalama",
content: "Admin panelini tasarlarken kurumsal soğuk paneller yerine günlüğün mum mühürlü, kağıt dokulu estetigini bozmamak harika bir karardı. İçerik yönetim alanı bile defterin bir parçası gibi hissettiriyor.",
audioTitle: "Defter_Ruhu_Ve_Yonetim_Notu.m4a",
audioDuration: "02:15",
tags: ["ses-kaydı", "tasarım", "ux", "düşünceler"],
likes: 22,
marginNotes: ["* arka planda rüzgar sesi mevcut"],
},
{
id: "post-1",
type: "notebook",
date: "16 AĞU 2026",
timestamp: "15:42",
mood: "spark",
moodLabel: "Fikir Patlaması",
title: "Neden Her Şeyi Karmaşıklaştırmaya Bayılıyoruz?",
content: `Bazen oturup kod yazarken veya hayatı planlarken kendimi 15 katmanlı bir soyutlama tasarlarken buluyorum. Oysa tek ihtiyacım olan bir metin dosyası ve bir fincan iyi demlenmiş çay.
Basitlik tembellik değil, aksine zihinsel bir lükstür. Bir sistemi basit tutabilmek için önce onun tüm karmaşıklığını çiğneyip sindirmiş olmak gerekir. Tıpkı bu defter gibi: ne kadar az kurumsal filtre, o kadar çok hakikat.`,
marginNotes: [
"← tam olarak bu!",
"not: yarın bu konuyu tekrar oku",
"çayı tazelemeyi unutma ☕"
],
highlightWords: ["Basitlik tembellik değil", "zihinsel bir lükstür"],
tags: ["felsefe", "kodlama", "sadelik", "düşünceler"],
likes: 12,
stampedText: "ONAYLANDI",
},
{
id: "post-2",
type: "polaroid",
date: "16 AĞU 2026",
timestamp: "14:15",
mood: "visual",
moodLabel: "Polaroid & Kırıntılar",
content: "Zirveye doğru tırmanırken kahve termosunu açtığım o rüzgarlı an. Şehirdeki tüm o Slack bildirimleri ve sonsuz toplantılar burada sadece birer fısıltı.",
imageCaption: "The ridge line view! 🏔️ Zirve esintisi ve dökülen kahve.",
imageUrl: "https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=1000&q=80",
tags: ["doğa", "kaçış", "fotoğraf", "dağlar"],
likes: 24,
marginNotes: ["rakım: ~2100m", "kahve soğumadan önce"],
},
{
id: "post-3",
type: "sticky",
date: "16 AĞU 2026",
timestamp: "11:20",
mood: "focus",
moodLabel: "Aşırı Odak & Kaos",
content: "İnsan beyni bazen 100 sayfalık teknik dokümanı 20 saniyede tarar, ama evden çıkarken kapıyı kilitleyip kilitlemediğini 5 dakika boyunca sokak ortasında sorgular.",
tags: ["beyin", "saçmalıklar", "gözlem"],
likes: 42,
stampedText: "DOĞRU TESPİT",
marginNotes: ["(anahtarı yine cebimde buldum)"],
},
{
id: "post-4",
type: "code",
date: "15 AĞU 2026",
timestamp: "23:45",
mood: "night",
moodLabel: "Gece Melankolisi",
title: "Rust & Borrow Checker ile Gece Terapisi",
content: "Rust'ta borrow checker ile kavga etmek bir nevi Zen meditasyonu gibi. Sizi acelecilikten arındırıyor ve acımasız bir hakikate zorluyor.",
codeSnippet: `// Gece yarısı varoluşsal döngü
fn main() -> Result<(), MentalState> {
let mut coffee = Coffee::brew("Dark Roast")?;
let mut energy = 100;
while energy > 0 {
code_with_passion(&mut coffee);
energy -= 10;
}
Ok(Sleep::now())
}`,
codeLanguage: "rust",
tags: ["rust", "yazılım", "gece", "kod"],
likes: 19,
marginNotes: ["derleyici haklıydı...", "03:12'de çalıştı!"],
stampedText: "COMPILED",
},
{
id: "post-5",
type: "audio",
date: "15 AĞU 2026",
timestamp: "21:10",
mood: "calm",
moodLabel: "Sakin & Karalama",
content: "Kadıköy sahilinde yürürken rüzgarın ve vapur düdüğünün arkasında aklıma gelen podcast fikri. Neden yapay zeka çağında el yazısı defterlere bu kadar aşığız?",
audioTitle: "Gece Vapuru & Defter Romantizmi.m4a",
audioDuration: "01:42",
tags: ["ses-kaydı", "kadıköy", "nostalji", "vapur"],
likes: 15,
marginNotes: ["arka plandaki martı sesi gerçek"],
},
{
id: "post-6",
type: "sticky",
date: "14 AĞU 2026",
timestamp: "18:05",
mood: "spark",
moodLabel: "Fikir Patlaması",
content: "Bugün sokaktaki bir tekir kedi bana öyle bir baktı ki, sanki dün gece geç saatte yazdığım CSS dosyasında 14 tane '!important' kullandığımı biliyordu.",
tags: ["kediler", "css", "vicdan-azabı"],
likes: 38,
stampedText: "SUÇLU",
marginNotes: ["(temizleyeceğim söz)"],
},
];
+38
View File
@@ -0,0 +1,38 @@
export type MoodType = "all" | "focus" | "night" | "spark" | "calm" | "visual";
export interface MoodConfig {
id: MoodType;
label: string;
sublabel: string;
iconName: string;
color: string;
bgColor: string;
stampText: string;
}
export type PostType = "notebook" | "sticky" | "polaroid" | "code" | "audio";
export interface JournalPost {
id: string;
type: PostType;
date: string;
timestamp: string;
mood: MoodType;
moodLabel: string;
title?: string;
content: string;
marginNotes?: string[];
tags: string[];
likes: number;
highlightWords?: string[];
authorNote?: string;
// Specific properties
imageUrl?: string;
imageCaption?: string;
codeSnippet?: string;
codeLanguage?: string;
audioDuration?: string;
audioTitle?: string;
stampedText?: string;
}