// NEKO — Shop layer: catalog, product cards, cart, FAQ accordion, sky/terrain decor
// Exports to window. Requires site-core.jsx.
// ---------------------------------------------------------------
// Catalog (placeholder content — cosmetic / supporter only)
// ---------------------------------------------------------------
const NEKO_CATEGORIES = ["Rütbeler", "Kozmetikler", "Petler", "Efektler", "Kasalar", "Etiketler", "Paketler", "Diyar Özelleştirme"];
const NEKO_PRODUCTS = [
{ id: "rank-gezgin", name: "Gezgin Rütbesi", cat: "Rütbeler", price: 950, badge: "Destekçi", badgeCls: "badge-supporter", desc: "Renkli sohbet rengi, /nick ve sezonluk selamlama.", ph: "rütbe ikonu\n(piksel art)" },
{ id: "rank-muhafiz", name: "Muhafız Rütbesi", cat: "Rütbeler", price: 1900, badge: "Destekçi", badgeCls: "badge-supporter", desc: "Gezgin'in tümü + dolu sunucuda sıra önceliği.", ph: "rütbe ikonu\n(piksel art)" },
{ id: "koz-sapka", name: "Kâşif Şapkası", cat: "Kozmetikler", price: 260, badge: "Kozmetik", badgeCls: "badge-cosmetic", desc: "Geniş kenarlı piksel şapka. Yalnızca görünüm.", ph: "şapka ikonu\n(piksel art)" },
{ id: "koz-pelerin", name: "Gece Pelerini", cat: "Kozmetikler", price: 540, badge: "Kozmetik", badgeCls: "badge-cosmetic", desc: "Yıldız desenli pelerin; rüzgârda dalgalanır.", ph: "pelerin ikonu\n(piksel art)" },
{ id: "pet-tilki", name: "Alev Tilkisi", cat: "Petler", price: 480, badge: "Kozmetik", badgeCls: "badge-cosmetic", desc: "Yanında dolaşan tilki dostun. Oyun gücü vermez.", ph: "pet ikonu\n(piksel art)" },
{ id: "pet-golem", name: "Taş Golemi", cat: "Petler", price: 520, badge: "Kozmetik", badgeCls: "badge-cosmetic", desc: "Ağır, sessiz ve sadık. Peşinden gelir.", ph: "pet ikonu\n(piksel art)" },
{ id: "fx-ender", name: "Ender İzi", cat: "Efektler", price: 320, badge: "Yeni", badgeCls: "badge-new", desc: "Yürürken beliren mor kıvılcımlar.", ph: "efekt ikonu\n(piksel art)" },
{ id: "fx-bal", name: "Bal Damlası", cat: "Efektler", price: 300, badge: "Kozmetik", badgeCls: "badge-cosmetic", desc: "Zıpladığında altın damlalar saçılır.", ph: "efekt ikonu\n(piksel art)" },
{ id: "kasa-gun", name: "Gün Batımı Kasası", cat: "Kasalar", price: 250, badge: "Kozmetik", badgeCls: "badge-cosmetic", desc: "Rastgele bir kozmetik içerir. Yalnızca görsel ödüller.", ph: "kasa ikonu\n(piksel art)" },
{ id: "kasa-obs", name: "Obsidyen Kasası", cat: "Kasalar", price: 400, badge: "Yeni", badgeCls: "badge-new", desc: "Gece temalı kozmetik koleksiyonu.", ph: "kasa ikonu\n(piksel art)", soldOut: true },
{ id: "tag-kasif", name: "\u201CKâşif\u201D Etiketi", cat: "Etiketler", price: 150, badge: "Kozmetik", badgeCls: "badge-cosmetic", desc: "Sohbette isminin yanında belirir.", ph: "etiket\n(piksel art)" },
{ id: "tag-bekci", name: "\u201CSurların Bekçisi\u201D", cat: "Etiketler", price: 180, badge: "Kozmetik", badgeCls: "badge-cosmetic", desc: "Sezon 4'e özel sohbet etiketi.", ph: "etiket\n(piksel art)" },
{ id: "paket-sezon", name: "Sezon Destekçi Paketi", cat: "Paketler", price: 2400, badge: "Destekçi", badgeCls: "badge-supporter", desc: "Gezgin Rütbesi + bir pet + bir etiket.", ph: "paket ikonu\n(piksel art)" },
{ id: "paket-koz", name: "Kozmetik Başlangıç", cat: "Paketler", price: 900, badge: "Kozmetik", badgeCls: "badge-cosmetic", desc: "Bir pet, bir efekt ve bir etiket seç.", ph: "paket ikonu\n(piksel art)" },
{ id: "diyar-renk", name: "Diyar Renk Paketi", cat: "Diyar Özelleştirme", price: 700, badge: "Diyar", badgeCls: "badge-diyar", desc: "Kasabanın profil rengini ve harita vurgusunu özelleştir.", ph: "renk paleti\n(piksel art)" },
{ id: "diyar-ikon", name: "Diyar İkon Paketi", cat: "Diyar Özelleştirme", price: 850, badge: "Diyar", badgeCls: "badge-diyar", desc: "Diyarın için özel ikonlar, amblemler ve küçük rozetler.", ph: "amblem seti\n(piksel art)" },
{ id: "diyar-bayrak", name: "Diyar Bayrak Seti", cat: "Diyar Özelleştirme", price: 1100, badge: "Diyar", badgeCls: "badge-diyar", desc: "Topluluğunu temsil eden piksel bayrak stilleri.", ph: "bayrak önizleme\n(piksel art)" },
{ id: "diyar-kimlik", name: "Diyar Kimlik Paketi", cat: "Diyar Özelleştirme", price: 2100, badge: "Diyar", badgeCls: "badge-diyar", desc: "Renk, ikon ve çerçeveyi tek pakette aç.", ph: "kimlik paketi\n(piksel art)" },
];
const NEKO_PACKS = [
{ id: "k500", amount: 500, bonus: null, note: "Denemek için" },
{ id: "k1000", amount: 1000, bonus: "+%5 bonus", note: "En çok tercih edilen başlangıç" },
{ id: "k2500", amount: 2500, bonus: "+%10 bonus", note: "En popüler", popular: true },
{ id: "k5000", amount: 5000, bonus: "+%15 bonus", note: "Sezonu rahat geçir" },
];
const NEKO_MODES = [
{ name: "RPG Towny", desc: "Kasabanı kur, ittifaklar oluştur, bölgeni geliştir ve Neko dünyasında kendi hikâyeni yaz.", stat: "128 kasaba", href: null },
{ name: "Görevler", desc: "Haftalık görev zincirleri, özel NPC rotaları, zindanlar ve ödüllü maceralar.", stat: "240+ görev", href: null },
{ name: "Pazar", desc: "Oyuncular arası ticaret, ilanlar, ürün listeleme ve sunucu ekonomisinin merkezi.", stat: "Oyuncu pazarı", href: "Pazar.html" },
{ name: "Etkinlikler", desc: "Sezonluk etkinlikler, topluluk yarışmaları, özel görevler ve ödüllü aktiviteler.", stat: "Haftalık", href: "Etkinlikler.html" },
{ name: "Kozmetikler", desc: "Şapka, pelerin, efekt, pet ve karakterini öne çıkaran görsel özelleştirmeler.", stat: "Yeni: Alev Tilkisi", href: "Magaza.html?kategori=Kozmetikler" },
{ name: "Topluluk", desc: "Discord, arkadaşlıklar, ekipler, sohbet kanalları ve oyuncularla birlikte büyüyen bir dünya.", stat: NEKO.discord, href: "#topluluk", mascot: true },
{ name: "Duyurular & Güncellemeler", desc: "Sunucu duyuruları, bakım notları, yenilikler, sezon haberleri ve geliştirme güncellemeleri.", stat: "Sezon 4", href: "Duyurular.html" },
];
const NEKO_NEWS = [
{ title: "Sezon 4 başladı: yeni harita ve sur bölgesi", cat: "Güncelleme", date: "12 Mayıs 2026", ph: "sezon görseli — 16:9" },
{ title: "Cuma etkinliği: Sur Savunması geri döndü", cat: "Etkinlik", date: "29 Mayıs 2026", ph: "etkinlik görseli — 16:9" },
{ title: "Mağazaya yaz kozmetikleri eklendi", cat: "Mağaza", date: "3 Haziran 2026", ph: "kozmetik görseli — 16:9" },
{ title: "Kural güncellemesi: yeni itiraz süreci", cat: "Duyuru", date: "8 Haziran 2026", ph: "duyuru görseli — 16:9" },
];
const NEKO_SUPPORTERS = [
{ user: "TaşçıUsta", item: "Gezgin Rütbesi", hours: 128, seed: 0, showCosmetics: true, cosmetics: ["Kâşif Şapkası", "Ender İzi", "Alev Tilkisi"] },
{ user: "GölgeNehri", item: "Alev Tilkisi", hours: 342, seed: 1, showCosmetics: false, cosmetics: [] },
{ user: "KuleMimarı", item: "Ender İzi", hours: 76, seed: 2, showCosmetics: true, cosmetics: ["Gece Pelerini", "“Kâşif” Etiketi"] },
{ user: "SessizDağ", item: "Gün Batımı Kasası", hours: 215, seed: 3, showCosmetics: true, cosmetics: ["Taş Golemi", "Bal Damlası", "“Surların Bekçisi”"] },
];
// Backwards-compatible alias (older drafts referenced NEKO_ACTIVITY)
const NEKO_ACTIVITY = NEKO_SUPPORTERS;
const NEKO_FAQ_KREDI = [
{ q: "Kredi nedir?", a: "Kredi, Neko'nın site bakiyesidir. Hesabına yüklersin ve mağazadaki kozmetik ürünlerde harcarsın. Gerçek paraya geri çevrilemez." },
{ q: "Krediler oyun gücü verir mi?", a: "Hayır. Kredilerle yalnızca görsel ürünler alınır: petler, efektler, etiketler ve sezonluk kozmetikler. Neko'da oyun gücü satılmaz." },
{ q: "Yüklediğim kredi ne zaman hesabıma geçer?", a: "Ödeme altyapısı bağlandığında yükleme anında işlenecek. Şu an bu sayfa tasarım önizlemesidir; gerçek ödeme alınmaz." },
{ q: "İade alabilir miyim?", a: "Kullanılmamış krediler için 14 gün içinde destek ekibine yazabilirsin. Harcanan krediler iade edilmez." },
];
const NEKO_FAQ_DESTEK = [
{ q: "Sunucuya bağlanamıyorum", a: "Adresi kontrol et: " + NEKO.ip + " ve sürümün " + NEKO.version + " aralığında olduğundan emin ol. Sorun sürerse Hata Bildirimi kategorisinden bilet aç." },
{ q: "Satın aldığım ürün gelmedi", a: "Önce sunucudan çıkıp tekrar gir. Ürün hâlâ yoksa Ödeme Sorunu kategorisinden, işlem numaranla birlikte bilet aç." },
{ q: "Ban itirazı nasıl yapılır?", a: "Aşağıdaki formdan Ban İtirazı kategorisini seç. Kullanıcı adını ve itiraz gerekçeni yaz; ekip 48 saat içinde döner." },
{ q: "Hesabıma giremiyorum", a: "Giriş, Minecraft hesabınla yapılır; ayrı bir şifre yoktur. Sorun yaşıyorsan Hesap / Giriş kategorisinden bize ulaş." },
];
// ---------------------------------------------------------------
// Small shared pieces
// ---------------------------------------------------------------
function ItemSlot({ label, size }) {
const tiny = typeof size === "number" && size < 72;
return (
);
}
function Price({ value, big }) {
return (
{value}
Kredi
);
}
// ---------------------------------------------------------------
// Product card types — active site logic (category → A/B/C)
// A: V.I.P. / rütbe / destekçi paketleri (premium, altın vurgu)
// B: eşya & kozmetik satışları (eşya çerçevesi, kompakt)
// C: efekt / pet / karakter görselleri (büyülü, cyan-mor)
// Eski taslak varyantlar ("A/B/C" tek tip) Tweaks'te durmaya devam eder;
// "auto" seçiliyken kart tipi ürün kategorisinden türetilir.
// ---------------------------------------------------------------
function nekoCardType(p) {
if (p.cat === "Diyar Özelleştirme") return "D";
if (p.cat === "Rütbeler" || p.badge === "Destekçi") return "A";
if (p.cat === "Petler" || p.cat === "Efektler") return "C";
return "B";
}
const NEKO_TYPE_COLORS = { A: "var(--c-gold)", B: "var(--c-accent)", C: "var(--c-magic)", D: "var(--c-diyar)" };
function CardAddBtn({ p, onAdd, cls, style, label }) {
return (
);
}
// Type A — V.I.P. / Destekçi: güçlü çerçeve, altın vurgu, p2w-değil notu
function ProductCardTypeA({ p, onAdd }) {
return (
V.I.P. · Destekçi
{p.badge}
{p.name}
{p.desc}
Oyun gücü vermez — görsel & destekçi ayrıcalıkları
);
}
// Type B — Eşya / Kozmetik: eşya-çerçevesi hissi, kompakt, grid dostu
function ProductCardTypeB({ p, onAdd }) {
return (
);
}
// Type C — Efekt / Pet: büyülü önizleme alanı, cyan-mor detaylar
function ProductCardTypeC({ p, onAdd }) {
const kind = p.cat === "Petler" ? "Pet" : "Efekt";
return (
);
}
// Type D — Diyar Özelleştirme: arma/sancak hissi, RPG Towny diyar kimliği
// "Diyar Önizle" → DiyarPreviewModal (görsel prototip editörü)
function ProductCardTypeD({ p, onAdd }) {
const [pv, setPv] = React.useState(false);
return (
Diyar Kimliği
Diyar
{p.name}
RPG Towny
{p.desc}
{pv &&
setPv(false)} />}
);
}
// ---------------------------------------------------------------
// Diyar Önizlemesi — RPG Towny diyar kimliği editörü (görsel prototip)
// Renk / arma / sancak seçimleri örnek “Asteria Diyarı” üzerinde canlı
// önizlenir. Seçimler satın alım sonrası oyun içinde uygulanır.
// ---------------------------------------------------------------
const DIYAR_COLORS = [
["Zümrüt", "#76C5FF"], ["Buz Mavisi", "#76C5FF"], ["Mor Sis", "#C48CF5"],
["Güneş", "#F5C53C"], ["Kor", "#EE3F86"], ["Gümüş", "#A6B0BC"],
];
const DIYAR_EMBLEMS = ["Kale", "Kılıç", "Yıldız", "Kurt", "Çapa", "Taç"];
const DIYAR_BANNERS = [
["Kırlangıç", "polygon(0 0, 100% 0, 100% 100%, 50% 76%, 0 100%)"],
["Sivri Uç", "polygon(0 0, 100% 0, 100% 78%, 50% 100%, 0 78%)"],
["Kalıp", "polygon(0 0, 100% 0, 100% 100%, 0 100%)"],
];
function DiyarPreviewModal({ p, onAdd, onClose }) {
const [ci, setCi] = React.useState(0);
const [ei, setEi] = React.useState(0);
const [bi, setBi] = React.useState(0);
const [after, setAfter] = React.useState(true);
React.useEffect(() => {
const onKey = (e) => { if (e.key === "Escape") onClose(); };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [onClose]);
const col = after ? DIYAR_COLORS[ci][1] : "#5B6473"; // önce: boyasız taş grisi
const emblem = DIYAR_EMBLEMS[ei].slice(0, 2);
const mapCells = 21; // 7×3 mini harita
const body = (
Diyar Önizlemesi
{p.name}
RPG Towny
{/* Sol — canlı önizleme: Asteria Diyarı */}
{/* Diyar künyesi */}
Asteria Diyarı
LV 24
{/* Sancak */}
{/* Harita işaretçisi */}
Harita görünümü
{Array.from({ length: mapCells }).map((_, i) => )}
Asteria
{/* Önce / Sonra */}
{/* Sağ — seçiciler */}
Sancak rengi — {DIYAR_COLORS[ci][0]}
{DIYAR_COLORS.map(([n, c], i) => (
))}
Arma — {DIYAR_EMBLEMS[ei]}
{DIYAR_EMBLEMS.map((n, i) => (
))}
Sancak stili — {DIYAR_BANNERS[bi][0]}
{DIYAR_BANNERS.map(([n, clip], i) => (
))}
Görsel önizleme — seçimlerin satın alım sonrası oyun içinde diyarına uygulanır.
);
// Kartın clip-path'li atalarından kaçmak için body'ye portal
return ReactDOM.createPortal(body, document.body);
}
// ---------------------------------------------------------------
// Product card — "auto" (aktif site: kategoriye göre A/B/C/D) +
// eski taslak varyantlar A / B / C (Tweaks'ten zorlanabilir, silinmedi)
// + D (yeni diyar kartı, Tweaks'ten tüm ürünlerde önizlenebilir)
// ---------------------------------------------------------------
function ProductCard({ p, variant, onAdd }) {
const v = variant || "auto";
if (v === "auto") {
const t = nekoCardType(p);
if (t === "A") return ;
if (t === "C") return ;
if (t === "D") return ;
return ;
}
if (v === "D") return ;
const addBtn = (cls, style, label) => (
);
if (v === "B") {
return (
{p.name}
{p.desc}
{addBtn("btn btn-secondary btn-sm", { width: "100%" })}
);
}
if (v === "C") {
return (
{addBtn("btn btn-primary btn-sm", { width: "100%", justifyContent: "space-between", fontFamily: "var(--font-body)", fontSize: 13 },
p.soldOut ? "Tükendi" :
Sepete Ekle{p.price} Kredi)}
);
}
// Variant A — Eşya Çerçevesi
return (
{p.name}
{p.badge}
{p.desc}
{addBtn("btn btn-primary btn-sm")}
);
}
// ---------------------------------------------------------------
// Cart — localStorage, cross-component event sync
// ---------------------------------------------------------------
function readCart() {
try { return JSON.parse(localStorage.getItem("neko-cart-v1") || "[]"); } catch (e) { return []; }
}
function writeCart(items) {
try { localStorage.setItem("neko-cart-v1", JSON.stringify(items)); } catch (e) {}
window.dispatchEvent(new CustomEvent("neko:cart"));
}
function useCart() {
const [items, setItems] = React.useState(readCart);
React.useEffect(() => {
const on = () => setItems(readCart());
window.addEventListener("neko:cart", on);
window.addEventListener("storage", on);
return () => { window.removeEventListener("neko:cart", on); window.removeEventListener("storage", on); };
}, []);
const add = (p) => {
const next = [...readCart()];
const ex = next.find((i) => i.id === p.id);
if (ex) ex.qty += 1; else next.push({ id: p.id, qty: 1 });
writeCart(next);
nekoToast(p.name + " sepete eklendi");
};
const remove = (id) => writeCart(readCart().filter((i) => i.id !== id));
const count = items.reduce((s, i) => s + i.qty, 0);
const detailed = items.map((i) => ({ ...i, p: NEKO_PRODUCTS.find((p) => p.id === i.id) })).filter((i) => i.p);
const total = detailed.reduce((s, i) => s + i.p.price * i.qty, 0);
return { items: detailed, add, remove, count, total };
}
function CartDrawer({ cart, open, onClose }) {
React.useEffect(() => {
if (!open) return;
const onKey = (e) => { if (e.key === "Escape") onClose(); };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open, onClose]);
if (!open) return null;
return (
Sepet {cart.count > 0 ? "· " + cart.count : ""}
{cart.items.length === 0 ? (
[ ]
Sepetin boş. Mağazadan kozmetik bir şeyler seç.
) : (
{cart.items.map((i) => (
{i.p.name}{i.qty > 1 ? " ×" + i.qty : ""}
{i.p.price * i.qty} Kredi
))}
)}
);
}
// ---------------------------------------------------------------
// FAQ accordion
// ---------------------------------------------------------------
function FAQAccordion({ items, idPrefix }) {
const [openIdx, setOpenIdx] = React.useState(0);
return (
{items.map((f, i) => {
const open = openIdx === i;
return (
{open && (
{f.a}
)}
);
})}
);
}
// ---------------------------------------------------------------
// Sky + blocky terrain (hero decor — squares only, no SVG)
// ---------------------------------------------------------------
const nekoTerrainHeights = [3, 4, 4, 5, 6, 5, 7, 8, 8, 7, 6, 5, 5, 4, 5, 6, 7, 7, 6, 5, 4, 4, 3, 3, 4, 5, 5, 6, 5, 4, 3, 3];
const nekoOreCols = { 7: 2, 12: 1, 18: 3, 25: 1 };
function NekoTerrain({ theme, unit }) {
const u = unit || 16;
const sun = theme === "sunlight";
return (
{nekoTerrainHeights.map((h, i) => (
{Array.from({ length: h }).map((_, j) => (
))}
))}
);
}
function NekoSky({ theme }) {
const sun = theme === "sunlight";
const stars = [[8, 18, 4], [16, 38, 3], [26, 12, 5], [38, 30, 3], [55, 10, 4], [64, 26, 3], [76, 16, 5], [88, 34, 3], [93, 12, 4], [46, 44, 3]];
return (
{sun ? (
) : (
{stars.map(([x, y, s], i) => (
))}
)}
);
}
Object.assign(window, {
NEKO_CATEGORIES, NEKO_PRODUCTS, NEKO_PACKS, NEKO_MODES, NEKO_NEWS,
NEKO_ACTIVITY, NEKO_SUPPORTERS, NEKO_FAQ_KREDI, NEKO_FAQ_DESTEK,
NEKO_TYPE_COLORS, nekoCardType, CardAddBtn,
ProductCardTypeA, ProductCardTypeB, ProductCardTypeC, ProductCardTypeD,
ItemSlot, Price, ProductCard, useCart, CartDrawer, FAQAccordion,
NekoTerrain, NekoSky, DiyarPreviewModal, DIYAR_COLORS, DIYAR_EMBLEMS, DIYAR_BANNERS,
});